feat(token): 添加token类型判断和过期处理逻辑

添加单/双token类型判断函数
实现token过期时间计算和存储
增加token有效性检查和自动刷新功能
完善登录/登出/刷新token的错误处理
This commit is contained in:
feige996
2025-08-22 17:08:40 +08:00
parent 153a374469
commit 464e2decf0
2 changed files with 217 additions and 33 deletions

View File

@@ -1,6 +1,6 @@
import type { IAuthLoginRes, IDoubleTokenRes, ISingleTokenRes } from '@/api/types/login'
import type { IAuthLoginRes } from '@/api/types/login'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { computed, ref } from 'vue' // 修复:导入 computed
import {
login as _login,
logout as _logout,
@@ -8,9 +8,13 @@ import {
wxLogin as _wxLogin,
getWxCode,
} from '@/api/login'
import { isDoubleTokenRes, isSingleTokenRes } from '@/api/types/login'
import { isDoubleTokenMode } from '@/utils'
import { useUserStore } from './user'
// 修复:添加 isSingleTokenMode 变量
export const isSingleTokenMode = !isDoubleTokenMode
// 初始化状态
const tokenInfoState = isDoubleTokenMode
? {
@@ -32,8 +36,54 @@ export const useTokenStore = defineStore(
// 设置用户信息
const setTokenInfo = (val: IAuthLoginRes) => {
tokenInfo.value = val
// 计算并存储过期时间
const now = Date.now()
if (isSingleTokenRes(val)) {
// 单token模式
const expireTime = now + val.expiresIn * 1000
uni.setStorageSync('accessTokenExpireTime', expireTime)
}
else if (isDoubleTokenRes(val)) {
// 双token模式
const accessExpireTime = now + val.accessExpiresIn * 1000
const refreshExpireTime = now + val.refreshExpiresIn * 1000
uni.setStorageSync('accessTokenExpireTime', accessExpireTime)
uni.setStorageSync('refreshTokenExpireTime', refreshExpireTime)
}
}
/**
* 判断token是否过期
*/
const isTokenExpired = computed(() => {
const now = Date.now()
const expireTime = uni.getStorageSync('accessTokenExpireTime')
if (!expireTime)
return true
return now >= expireTime
})
/**
* 判断refreshToken是否过期
*/
const isRefreshTokenExpired = computed(() => {
if (!isDoubleTokenMode)
return true
const now = Date.now()
const refreshExpireTime = uni.getStorageSync('refreshTokenExpireTime')
if (!refreshExpireTime)
return true
return now >= refreshExpireTime
})
/**
* 登录成功后处理逻辑
* @param tokenInfo 登录返回的token信息
*/
async function _postLogin(tokenInfo: IAuthLoginRes) {
setTokenInfo(tokenInfo)
const userStore = useUserStore()
@@ -43,7 +93,7 @@ export const useTokenStore = defineStore(
/**
* 用户登录
* @param credentials 登录参数
* @returns R<IUserLogin>
* @returns 登录结果
*/
const login = async (credentials: {
username: string
@@ -51,62 +101,178 @@ export const useTokenStore = defineStore(
code: string
uuid: string
}) => {
const res = await _login(credentials)
console.log('普通登录-res: ', res)
await _postLogin(res.data)
uni.showToast({
title: '登录成功',
icon: 'success',
})
try {
const res = await _login(credentials)
console.log('普通登录-res: ', res)
await _postLogin(res.data)
uni.showToast({
title: '登录成功',
icon: 'success',
})
return res
}
catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: '登录失败,请重试',
icon: 'error',
})
throw error
}
}
/**
* 微信登录
* @returns 登录结果
*/
const wxLogin = async () => {
// 获取微信小程序登录的code
const code = await getWxCode()
console.log('微信登录-code: ', code)
const res = await _wxLogin(code)
console.log('微信登录-res: ', res)
await _postLogin(res.data)
uni.showToast({
title: '登录成功',
icon: 'success',
})
try {
// 获取微信小程序登录的code
const code = await getWxCode()
console.log('微信登录-code: ', code)
const res = await _wxLogin(code)
console.log('微信登录-res: ', res)
await _postLogin(res.data)
uni.showToast({
title: '登录成功',
icon: 'success',
})
return res
}
catch (error) {
console.error('微信登录失败:', error)
uni.showToast({
title: '微信登录失败,请重试',
icon: 'error',
})
throw error
}
}
/**
* 退出登录 并 删除用户信息
*/
const logout = async () => {
_logout()
const userStore = useUserStore()
await userStore.removeUserInfo()
try {
await _logout()
// 清除存储的过期时间
uni.removeStorageSync('accessTokenExpireTime')
uni.removeStorageSync('refreshTokenExpireTime')
}
catch (error) {
console.error('退出登录失败:', error)
}
finally {
// 无论成功失败都需要清除本地token信息
const userStore = useUserStore()
await userStore.removeUserInfo()
}
}
/**
* 刷新token
* @returns 刷新结果
*/
const refreshToken = async () => {
const refreshToken = (tokenInfo.value as IDoubleTokenRes).refreshToken
const res = await _refreshToken(refreshToken)
console.log('刷新token-res: ', res)
setTokenInfo(res.data)
if (!isDoubleTokenMode) {
console.error('单token模式不支持刷新token')
throw new Error('单token模式不支持刷新token')
}
try {
// 安全检查确保refreshToken存在
if (!isDoubleTokenRes(tokenInfo.value) || !tokenInfo.value.refreshToken) {
throw new Error('无效的refreshToken')
}
const refreshToken = tokenInfo.value.refreshToken
const res = await _refreshToken(refreshToken)
console.log('刷新token-res: ', res)
setTokenInfo(res.data)
return res
}
catch (error) {
console.error('刷新token失败:', error)
throw error
}
}
const hasLogin = computed(() => isDoubleTokenMode
? (tokenInfo.value as IDoubleTokenRes).accessToken
: (tokenInfo.value as ISingleTokenRes).token,
)
/**
* 获取有效的token
* 注意在computed中不直接调用异步函数只做状态判断
* 实际的刷新操作应由调用方处理
*/
const getValidToken = computed(() => {
// token已过期返回空
if (isTokenExpired.value) {
return ''
}
if (isSingleTokenMode) {
return isSingleTokenRes(tokenInfo.value) ? tokenInfo.value.token : ''
}
else {
return isDoubleTokenRes(tokenInfo.value) ? tokenInfo.value.accessToken : ''
}
})
/**
* 检查是否有登录信息不考虑token是否过期
*/
const hasLoginInfo = computed(() => {
if (isDoubleTokenMode) {
return isDoubleTokenRes(tokenInfo.value) && !!tokenInfo.value.accessToken
}
else {
return isSingleTokenRes(tokenInfo.value) && !!tokenInfo.value.token
}
})
/**
* 检查是否已登录且token有效
*/
const hasValidLogin = computed(() => {
return hasLoginInfo.value && !isTokenExpired.value
})
/**
* 尝试获取有效的token如果过期且可刷新则刷新token
* @returns 有效的token或空字符串
*/
const tryGetValidToken = async (): Promise<string> => {
if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) {
try {
await refreshToken()
return getValidToken.value
}
catch (error) {
console.error('尝试刷新token失败:', error)
return ''
}
}
return getValidToken.value
}
return {
tokenInfo,
hasLogin,
// 核心API方法
login,
wxLogin,
logout,
// 认证状态判断(最常用的)
hasLogin: hasValidLogin,
// 内部系统使用的方法
refreshToken,
tryGetValidToken,
// 调试或特殊场景可能需要直接访问的信息
tokenInfo,
}
},
{
// 添加持久化配置确保刷新页面后token信息不丢失
persist: true,
},
)