Files
webgame-frontend/src/stores/modules/account.ts

211 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia'
import { ref } from 'vue'
import {
login as loginApi,
register as registerApi,
refresh as refreshApi,
type LoginRequest,
type RegisterRequest
} from '@/api/modules/account'
import {
getUserInfo,
updateUserInfo as updateUserInfoApi,
uploadAvatar as uploadAvatarApi,
deleteAvatar as deleteAvatarApi,
updateStatus as updateStatusApi,
type UserStatusType,
type UpdateUserInfoRequest
} from '@/api/modules/user'
/** 账户信息 */
export interface AccountState {
/** 用户唯一标识UUID */
userId?: string
/** 用户昵称 */
nickName?: string
/** 头像 */
avatar?: string | null
/** 个性签名 */
signature?: string | null
/** 年龄 */
age?: number | null
/** 性别1=男2=女 */
sex?: number | null
/** 生日 */
birthday?: string | null
/** 在线状态ONLINE / AWAY / DND / INVISIBLE */
status?: UserStatusType
}
export const useAccountStore = defineStore(
'account',
() => {
/** 账户信息 */
const accountInfo = ref<AccountState | null>(null)
/** Access Token30 分钟有效,每次请求通过 saToken 头携带) */
const accessToken = ref('')
/** Refresh Token30 天有效,仅用于刷新 Access Token */
const refreshToken = ref('')
/** token 是否已验证有效 */
const isTokenValid = ref(false)
/** 是否已完成初始验证(防止重复请求) */
const isInitialized = ref(false)
/**
* 用户登录
* 调用登录 API成功后存储双 Token 并获取用户信息
* @param params - 登录请求参数(用户名、密码)
*/
const login = async (params: LoginRequest) => {
const res = await loginApi(params)
accessToken.value = res.data.accessToken
refreshToken.value = res.data.refreshToken
await fetchUserInfo()
isTokenValid.value = true
}
/**
* 用户注册
* 调用注册 API成功后自动登录存储双 Token 并获取用户信息)
* @param params - 注册请求参数(用户名、密码)
*/
const register = async (params: RegisterRequest) => {
const res = await registerApi(params)
accessToken.value = res.data.accessToken
refreshToken.value = res.data.refreshToken
await fetchUserInfo()
isTokenValid.value = true
}
/**
* 退出登录
* 清除令牌、账户信息和验证状态
*/
const logout = () => {
clearAuth()
}
/**
* 获取当前登录用户信息
* 需要先有 Access Token 才能调用
*/
const fetchUserInfo = async () => {
const res = await getUserInfo()
accountInfo.value = res.data
}
/**
* 刷新 Access Token带并发锁
*
* 由刷新拦截器的 doRefreshToken 和 SSE 的 onUnauthorized 调用。
* 返回新的 Access Token 字符串,方便调用方直接使用。
*
* 并发控制:若已有刷新正在进行中,返回同一个 Promise
* 确保无论并发触发多少次,实际只发出一次 /account/refresh 请求。
*
* @returns 新的 Access Token
*/
/** 正在进行的刷新 Promisenull 表示无刷新进行中) */
let refreshPromise: Promise<string> | null = null
const refreshAccessToken = async (): Promise<string> => {
// 已有刷新进行中 → 复用同一个 Promise避免并发重复请求
if (refreshPromise) {
return refreshPromise
}
if (!refreshToken.value) {
throw new Error('无 Refresh Token无法刷新')
}
refreshPromise = (async () => {
try {
const res = await refreshApi({ refreshToken: refreshToken.value })
accessToken.value = res.data.accessToken
if (res.data.refreshToken) {
refreshToken.value = res.data.refreshToken
}
isTokenValid.value = true
return accessToken.value
} finally {
refreshPromise = null
}
})()
return refreshPromise
}
/**
* 更新当前用户的个人资料
* 调用后端接口持久化,并同步更新本地 accountInfo
* @param data - 要更新的字段(所有字段可选)
*/
const updateProfile = async (data: UpdateUserInfoRequest) => {
const res = await updateUserInfoApi(data)
accountInfo.value = res.data
}
/**
* 上传用户头像
* 调用后端接口上传文件并更新 DB同步更新本地 accountInfo
* @param file - 头像文件
*/
const uploadAvatar = async (file: File) => {
const res = await uploadAvatarApi(file)
accountInfo.value = res.data
}
/**
* 删除用户头像
* 调用后端接口删除文件并清空 DB同步更新本地 accountInfo
*/
const removeAvatar = async () => {
const res = await deleteAvatarApi()
accountInfo.value = res.data
}
/**
* 更新当前用户的在线状态
* 调用后端接口持久化,并同步更新本地 accountInfo
* @param status - 新状态值
*/
const updateUserStatus = async (status: UserStatusType) => {
await updateStatusApi({ status })
if (accountInfo.value) {
accountInfo.value = { ...accountInfo.value, status }
}
}
/** 清除所有认证状态 */
const clearAuth = () => {
accessToken.value = ''
refreshToken.value = ''
accountInfo.value = null
isTokenValid.value = false
isInitialized.value = false
}
console.log('useAccountStore', accessToken.value)
return {
accountInfo,
accessToken,
refreshToken,
login,
register,
logout,
refreshAccessToken,
fetchUserInfo,
updateProfile,
uploadAvatar,
removeAvatar,
updateUserStatus
}
},
{
persist: {
pick: ['accessToken', 'refreshToken']
}
}
)