Files
aiot-uniapp/src/store/token.ts

342 lines
10 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.

/* eslint-disable brace-style */ // 原因unibest 官方维护的代码,尽量不要大概,避免难以合并
import type {
AuthLoginReqVO,
AuthRegisterReqVO,
AuthSmsLoginReqVO,
ILoginForm,
} from '@/api/login'
import type { IAuthLoginRes } from '@/api/types/login'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue' // 修复:导入 computed
import { useToast } from 'wot-design-uni'
import {
login as _login,
logout as _logout,
refreshToken as _refreshToken,
wxLogin as _wxLogin,
getWxCode,
register,
smsLogin,
} from '@/api/login'
import { isDoubleTokenRes, isSingleTokenRes } from '@/api/types/login'
import { isDoubleTokenMode } from '@/utils'
import { useDictStore } from './dict'
import { useUserStore } from './user'
// 初始化状态
const tokenInfoState = isDoubleTokenMode
? {
accessToken: '',
// accessExpiresIn: 0,
refreshToken: '',
// refreshExpiresIn: 0,
expiresTime: 0,
}
: {
token: '',
expiresIn: 0,
}
export const useTokenStore = defineStore(
'token',
() => {
const toast = useToast()
// 定义用户信息
const tokenInfo = ref<IAuthLoginRes>({ ...tokenInfoState })
// 设置用户信息
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 = val.expiresTime
// const refreshExpireTime = now + val.refreshExpiresIn * 1000
uni.setStorageSync('accessTokenExpireTime', accessExpireTime)
// uni.setStorageSync('refreshTokenExpireTime', refreshExpireTime)
// add by 芋艿:目前后端没有返回 refreshToken 的过期时间,所以这里暂时不存储 refreshToken 过期时间
}
}
/**
* 判断token是否过期
*/
const isTokenExpired = computed(() => {
if (!tokenInfo.value) {
return true
}
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
// add by 芋艿:目前后端没有返回 refreshToken 的过期时间,所以这里暂时不做过期判断,先全部返回 false 非过期
return false
})
/**
* 登录成功后处理逻辑
* @param tokenInfo 登录返回的token信息
*/
async function _postLogin(tokenInfo: IAuthLoginRes) {
// 设置认证信息
setTokenInfo(tokenInfo)
// 获取用户信息
const userStore = useUserStore()
await userStore.fetchUserInfo()
// add by 芋艿:加载字典数据(异步)
const dictStore = useDictStore()
dictStore.loadDictCache().then()
}
/**
* 用户登录:账号登录、注册登录、短信登录、三方登录等
* 有的时候后端会用一个接口返回token和用户信息有的时候会分开2个接口一个获取token一个获取用户信息
* 各有利弊看业务场景和系统复杂度这里使用2个接口返回的来模拟
* @param loginForm 登录参数
* @returns 登录结果
*/
const login = async (loginForm: ILoginForm) => {
let typeName = ''
try {
let res: IAuthLoginRes
switch (loginForm.type) {
case 'register': {
res = await register(loginForm as AuthRegisterReqVO)
typeName = '注册'
break
}
case 'sms': {
res = await smsLogin(loginForm as AuthSmsLoginReqVO)
typeName = '注册'
break
}
default: {
res = await _login(loginForm as AuthLoginReqVO)
typeName = '登录'
}
}
// console.log('普通登录-res: ', res)
await _postLogin(res)
// 注释 by 芋艿:使用 wd-toast 替代
// uni.showToast({
// title: `${typeName}成功`,
// icon: 'success',
// })
toast.success(`${typeName}成功`)
return res
}
catch (error) {
console.error(`${typeName}失败:`, error)
// 注释 by 芋艿:避免覆盖 http.ts 中的错误提示
// uni.showToast({
// title: `${typeName}失败,请重试`,
// icon: 'error',
// })
throw error
}
}
/**
* 微信登录
* 有的时候后端会用一个接口返回token和用户信息有的时候会分开2个接口一个获取token一个获取用户信息
* 各有利弊看业务场景和系统复杂度这里使用2个接口返回的来模拟
* @returns 登录结果
*/
const wxLogin = async () => {
try {
// 获取微信小程序登录的code
const code = await getWxCode()
console.log('微信登录-code: ', code)
const res = await _wxLogin(code)
console.log('微信登录-res: ', res)
await _postLogin(res)
// 注释 by 芋艿:使用 wd-toast 替代
// uni.showToast({
// title: '登录成功',
// icon: 'success',
// })
toast.success('登录成功')
return res
}
catch (error) {
console.error('微信登录失败:', error)
// 注释 by 芋艿:使用 wd-toast 替代
// uni.showToast({
// title: '微信登录失败,请重试',
// icon: 'error',
// })
toast.error('微信登录失败,请重试')
throw error
}
}
/**
* 退出登录 并 删除用户信息
*/
const logout = async () => {
try {
// TODO 实现自己的退出登录逻辑
await _logout()
}
catch (error) {
console.error('退出登录失败:', error)
}
finally {
// 无论成功失败都需要清除本地token信息
// 清除存储的过期时间
uni.removeStorageSync('accessTokenExpireTime')
// uni.removeStorageSync('refreshTokenExpireTime')
console.log('退出登录-清除用户信息')
tokenInfo.value = { ...tokenInfoState }
uni.removeStorageSync('token')
const userStore = useUserStore()
userStore.clearUserInfo()
// add by 芋艿:清空字典缓存
const dictStore = useDictStore()
dictStore.clearDictCache()
}
}
/**
* 刷新token
* @returns 刷新结果
*/
const refreshToken = async () => {
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)
return res
}
catch (error) {
console.error('刷新token失败:', error)
throw error
}
}
/**
* 获取有效的token
* 注意在computed中不直接调用异步函数只做状态判断
* 实际的刷新操作应由调用方处理
*/
const getValidToken = computed(() => {
// token已过期返回空
if (isTokenExpired.value) {
return ''
}
if (!isDoubleTokenMode) {
return isSingleTokenRes(tokenInfo.value) ? tokenInfo.value.token : ''
}
else {
return isDoubleTokenRes(tokenInfo.value) ? tokenInfo.value.accessToken : ''
}
})
/**
* 检查是否有登录信息不考虑token是否过期
*/
const hasLoginInfo = computed(() => {
if (!tokenInfo.value) {
return false
}
if (isDoubleTokenMode) {
return isDoubleTokenRes(tokenInfo.value) && !!tokenInfo.value.accessToken
}
else {
return isSingleTokenRes(tokenInfo.value) && !!tokenInfo.value.token
}
})
/**
* 检查是否已登录且token有效
*/
const hasValidLogin = computed(() => {
console.log('hasValidLogin', hasLoginInfo.value, !isTokenExpired.value)
if (isDoubleTokenMode) {
// add by 芋艿:双令牌场景下,以刷新令牌过期为准。而刷新令牌是否过期,通过请求时返回 401 来判断(由于后端 refreshToken 不返回过期时间)
// 即相比下面的判断方式,去掉了“!isTokenExpired.value”
// 如果不这么做:访问令牌过期时(刷新令牌没过期),会导致刷新界面时,直接认为是令牌过期,导致跳转到登录界面
return hasLoginInfo.value
}
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 {
// 核心API方法
login,
wxLogin,
logout,
// 认证状态判断(最常用的)
hasLogin: hasValidLogin,
// 内部系统使用的方法
refreshToken,
tryGetValidToken,
validToken: getValidToken,
// 调试或特殊场景可能需要直接访问的信息
tokenInfo,
setTokenInfo,
}
},
{
// 添加持久化配置确保刷新页面后token信息不丢失
persist: true,
},
)