feat: 双token

This commit is contained in:
2026-06-16 16:16:47 +08:00
parent d9b8567b6d
commit 172c83ea73
8 changed files with 455 additions and 79 deletions

View File

@@ -1,37 +1,169 @@
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 } from '@/api/modules/user'
/** 账户信息 */
export interface AccountState {
/** 账户ID */
id: string
/** 用户名 */
username: string
/** 年龄 */
age?: number
/** 性别 */
sex?: string
/** 手机号 */
phone?: string
/** 邮箱 */
email?: string
/** 用户昵称 */
nickName?: string
/** 头像 */
avatar?: string
avatar?: string | null
/** 个性签名 */
signature?: string | null
/** 年龄 */
age?: number | null
/** 性别1=男2=女 */
sex?: number | null
/** 生日 */
birthday?: string
birthday?: string | null
}
export const useAccountStore = defineStore(
'account',
() => {
const accountInfo = ref<AccountState>()
const token = ref('')
/** 账户信息 */
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)
return { accountInfo, token }
/**
* 判断当前是否已登录
* token 存在且已验证有效才算已登录
*/
const isLoggedIn = () => isTokenValid.value
/**
* 初始化认证状态
*
* 用持久化的 Access Token 向服务端验证是否仍然有效。
* 只会在首次调用时真正发请求,后续调用直接跳过。
*
* - 无 token → 无操作
* - token 有效 → 拉取用户信息,标记已登录
* - token 过期 → 尝试用 Refresh Token 刷新,刷新失败则清除
*/
const initAuth = async () => {
// 已验证过,跳过
if (isInitialized.value) return
isInitialized.value = true
// 没有持久化的 Access Token
if (!accessToken.value) return
try {
await fetchUserInfo()
// 成功获取用户信息token 有效
isTokenValid.value = true
} catch {
// Access Token 可能过期,尝试用 Refresh Token 刷新
if (refreshToken.value) {
try {
await doRefresh()
isTokenValid.value = true
} catch {
// 刷新也失败,清除
clearAuth()
}
} else {
clearAuth()
}
}
}
/**
* 用户登录
* 调用登录 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
}
/**
* 使用 Refresh Token 刷新 Access Token
* 成功后更新 accessTokenRefresh Token 不变(由服务端行为决定)
*/
const doRefresh = async () => {
const res = await refreshApi({ refreshToken: refreshToken.value })
accessToken.value = res.data.accessToken
// 如果服务端返回了新的 refreshToken 则更新
if (res.data.refreshToken) {
refreshToken.value = res.data.refreshToken
}
await fetchUserInfo()
}
/** 清除所有认证状态 */
const clearAuth = () => {
accessToken.value = ''
refreshToken.value = ''
accountInfo.value = null
isTokenValid.value = false
isInitialized.value = false
}
return {
accountInfo,
accessToken,
refreshToken,
isLoggedIn,
initAuth,
login,
register,
logout,
fetchUserInfo
}
},
{
persist: {
pick: ['accountInfo', 'token']
pick: ['accessToken', 'refreshToken']
}
}
)