161 lines
3.8 KiB
TypeScript
161 lines
3.8 KiB
TypeScript
/**
|
||
* 用户认证状态管理
|
||
*/
|
||
import { defineStore } from 'pinia';
|
||
import { ref, computed } from 'vue';
|
||
import type { UserInfo } from '@/types/chat';
|
||
|
||
// 开发环境默认跳过登录校验,避免频繁登录打断调试
|
||
const DEV_AUTH_BYPASS = import.meta.env.DEV;
|
||
|
||
// MARK: dev 默认 token(当 URL 无 token 参数时使用)
|
||
const DEV_DEFAULT_TOKEN = '';
|
||
const DEV_BYPASS_USER: UserInfo = {
|
||
id: 'dev-user',
|
||
username: 'dev-user',
|
||
nickname: '开发环境用户',
|
||
};
|
||
|
||
// 认证接口返回格式
|
||
interface AuthResponse {
|
||
status: number;
|
||
message: string;
|
||
data: {
|
||
token: string;
|
||
userInfo: {
|
||
userId: number | string;
|
||
userName?: string;
|
||
realName?: string;
|
||
email?: string;
|
||
userIcon?: string;
|
||
[key: string]: unknown;
|
||
};
|
||
[key: string]: unknown;
|
||
} | null;
|
||
}
|
||
|
||
// 认证接口
|
||
const AUTH_BASE_URL = (import.meta.env.VITE_AUTH_BASE_URL || '').replace(/\/$/, '');
|
||
const AUTH_CHECK_URL = `${AUTH_BASE_URL}/newapi/api/login/validateToken`;
|
||
const AUTH_TOKEN_STORAGE_KEY = 'DEV_DEFAULT_TOKEN';
|
||
|
||
export const useAuthStore = defineStore('auth', () => {
|
||
// 状态
|
||
const token = ref<string | null>(null);
|
||
const user = ref<UserInfo | null>(null);
|
||
const isInitialized = ref(false);
|
||
|
||
// 计算属性
|
||
const isAuthenticated = computed(() => DEV_AUTH_BYPASS || !!token.value);
|
||
const userId = computed(() => user.value?.username || null); // username 用于 OSS 路径和数据库 user_id
|
||
|
||
/**
|
||
* 验证 token 并获取用户信息
|
||
*/
|
||
async function checkToken(tokenToCheck: string): Promise<UserInfo | null> {
|
||
try {
|
||
const response = await fetch(AUTH_CHECK_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
authorization: tokenToCheck,
|
||
},
|
||
});
|
||
if (!response.ok) {
|
||
return null;
|
||
}
|
||
const data: AuthResponse = await response.json();
|
||
|
||
if (data.status === 1000 && data.data?.userInfo) {
|
||
const { userInfo } = data.data;
|
||
return {
|
||
...userInfo,
|
||
id: String(userInfo.userId),
|
||
username: userInfo.userName,
|
||
nickname: userInfo.realName,
|
||
email: userInfo.email,
|
||
avatar: userInfo.userIcon,
|
||
};
|
||
} else {
|
||
window.$toast?.('[Auth] Token 验证失败:Token无效');
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
|
||
console.error('[Auth] Token 验证失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化 - 从 URL 参数获取 token,验证后设置用户信息
|
||
*/
|
||
async function init() {
|
||
const searchParams = new URLSearchParams(window.location.search);
|
||
const urlToken = searchParams.get('token');
|
||
|
||
// 获取 token:URL > localStorage > 默认值
|
||
const tokenValue = urlToken
|
||
|| localStorage.getItem(AUTH_TOKEN_STORAGE_KEY)
|
||
|| DEV_DEFAULT_TOKEN;
|
||
|
||
if (DEV_AUTH_BYPASS && !tokenValue) {
|
||
token.value = null;
|
||
user.value = DEV_BYPASS_USER;
|
||
isInitialized.value = true;
|
||
return;
|
||
}
|
||
|
||
if (!tokenValue) {
|
||
isInitialized.value = true;
|
||
window.$toast?.('未登录,请先登录', 'error');
|
||
return;
|
||
}
|
||
|
||
// 验证 token
|
||
const userInfo = await checkToken(tokenValue);
|
||
|
||
if (userInfo) {
|
||
|
||
token.value = tokenValue;
|
||
user.value = userInfo;
|
||
} else {
|
||
// 验证失败,清空
|
||
token.value = null;
|
||
user.value = null;
|
||
}
|
||
|
||
isInitialized.value = true;
|
||
}
|
||
|
||
/**
|
||
* 设置用户信息
|
||
*/
|
||
function setUser(userInfo: UserInfo) {
|
||
user.value = userInfo;
|
||
}
|
||
|
||
/**
|
||
* 获取认证 header
|
||
*/
|
||
function getAuthHeader(): Record<string, string> {
|
||
if (token.value) {
|
||
return { Authorization: `Bearer ${token.value}` };
|
||
}
|
||
return {};
|
||
}
|
||
|
||
return {
|
||
// 状态
|
||
token,
|
||
user,
|
||
isAuthenticated,
|
||
userId,
|
||
isInitialized,
|
||
|
||
// 方法
|
||
setUser,
|
||
getAuthHeader,
|
||
init,
|
||
};
|
||
});
|