import { computed, ref } from "vue"; import { forInObject, isNull, isObject, parse, storage } from "../utils"; import { router } from "../router"; import { request } from "../service"; import type { UserInfo } from "../types"; import { type LoginData, getUsersInfo, refreshTokenApi } from "@/services/user"; import { dict } from "./dict"; export type Token = { token: string; // 访问token expire: number; // token过期时间(秒) refreshToken: string; // 刷新token refreshExpire: number; // 刷新token过期时间(秒) }; export class User { /** * 用户信息,响应式对象 */ info = ref(null); /** * token刷新中的Promise,防止并发刷新 */ private refreshing: Promise | null = null; /** * 用户信息,响应式对象 */ courseId = ref(null); /** * 当前token,字符串或null */ token: string | 'Basic ZW5kOmVuZA=='; constructor() { // 获取本地用户信息 const userInfo = storage.get("userInfo"); // 获取本地token const token = storage.get("token") as string | 'Basic ZW5kOmVuZA=='; // 如果token为空字符串则置为null this.token = token == "" ? 'Basic ZW5kOmVuZA==' : token; if (this.token == 'Basic ZW5kOmVuZA==') { return; } // 初始化用户信息 if (userInfo != null && isObject(userInfo)) { this.set(userInfo); } else { this.get() } } /** * 获取用户信息(从服务端拉取最新信息并更新本地) * @returns Promise */ async get() { try { const res = await getUsersInfo() if (res != null) { this.set(res); } } catch (err) { console.warn("获取用户信息失败:", err); } } /** * 设置用户信息并存储到本地 * @param data 用户信息对象 */ set(data: any) { if (isNull(data)) { return; } // 设置 this.info.value = parse(data)!; // 持久化到本地存储 storage.set("userInfo", data, 0); } setCourseId(id: string) { this.courseId.value = id } /** * 更新用户信息(本地与服务端同步) * @param data 新的用户信息 */ async update(data: any) { if (isNull(data) || isNull(this.info.value)) { return; } // 本地同步更新 forInObject(data, (value, key) => { this.info.value![key] = value; }); // 同步到服务端 await request({ url: "/app/user/info/updatePerson", method: "POST", data }); } /** * 移除用户信息 */ remove() { this.info.value = null; storage.remove("userInfo"); } /** * 判断用户信息是否为空 * @returns boolean */ isNull() { return this.info.value == null; } /** * 清除本地所有用户信息和token */ clear() { storage.remove("userInfo"); storage.remove("token"); storage.remove("refreshToken"); this.token = 'Basic ZW5kOmVuZA=='; this.remove(); this.info.value = null; this.courseId.value = null; } /** * 退出登录,清除所有信息并跳转到登录页 */ logout() { this.clear(); router.login(); } /** * 设置token并存储到本地 * @param data Token对象 */ setToken(data: LoginData) { this.token = `Bearer ${data?.access_token}`; // 访问token,提前5秒过期,防止边界问题 storage.set("token", this.token, 7 * 24 * 60 * 60 - 5); this.get(); dict.refresh(); // 存储refresh_token(服务端默认30天有效期) if (data != null && data.refresh_token != null) { storage.set("refreshToken", data.refresh_token, 30 * 24 * 60 * 60 - 5); } } /** * 刷新token(调用服务端OAuth2接口,自动更新本地token) * @returns Promise 新的token */ async refreshToken(): Promise { // 防止并发刷新 if (this.refreshing != null) { return this.refreshing; } const storedRefreshToken = storage.get("refreshToken") as string; if (storedRefreshToken == '' || storedRefreshToken.length == 0) { return Promise.reject(new Error("No refresh token available")); } this.refreshing = (async () => { try { const res = await refreshTokenApi(storedRefreshToken); if (res != null && res.data != null) { const respData = res.data as UTSJSONObject; const loginData: LoginData = { access_token: respData["access_token"] as string, refresh_token: respData["refresh_token"] as string, }; this.setToken(loginData); return this.token; } throw new Error("Refresh token response invalid"); } finally { this.refreshing = null; } })(); return this.refreshing; } } /** * 单例用户对象,项目全局唯一 */ export const user = new User(); /** * 用户信息,响应式对象 */ export const userInfo = computed(() => user.info.value);