token.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import type { IAuthLoginRes } from '@/api/types/login'
  2. import { defineStore } from 'pinia'
  3. import { computed, ref } from 'vue' // 修复:导入 computed
  4. import {
  5. login as _login,
  6. logout as _logout,
  7. refreshToken as _refreshToken,
  8. wxLogin as _wxLogin,
  9. getWxCode,
  10. } from '@/api/login'
  11. import { isDoubleTokenRes, isSingleTokenRes } from '@/api/types/login'
  12. import { isDoubleTokenMode } from '@/utils'
  13. import { useUserStore } from './user'
  14. // 初始化状态
  15. const tokenInfoState = isDoubleTokenMode
  16. ? {
  17. accessToken: '',
  18. accessExpiresIn: 0,
  19. refreshToken: '',
  20. refreshExpiresIn: 0,
  21. }
  22. : {
  23. token: '',
  24. expiresIn: 0,
  25. }
  26. export const useTokenStore = defineStore(
  27. 'token',
  28. () => {
  29. // 定义用户信息
  30. const tokenInfo = ref<IAuthLoginRes>({ ...tokenInfoState })
  31. // 设置用户信息
  32. const setTokenInfo = (val: IAuthLoginRes) => {
  33. tokenInfo.value = val
  34. // 计算并存储过期时间
  35. const now = Date.now()
  36. if (isSingleTokenRes(val)) {
  37. // 单token模式
  38. const expireTime = now + val.expiresIn * 1000
  39. uni.setStorageSync('accessTokenExpireTime', expireTime)
  40. }
  41. else if (isDoubleTokenRes(val)) {
  42. // 双token模式
  43. const accessExpireTime = now + val.accessExpiresIn * 1000
  44. const refreshExpireTime = now + val.refreshExpiresIn * 1000
  45. uni.setStorageSync('accessTokenExpireTime', accessExpireTime)
  46. uni.setStorageSync('refreshTokenExpireTime', refreshExpireTime)
  47. }
  48. }
  49. /**
  50. * 判断token是否过期
  51. */
  52. const isTokenExpired = computed(() => {
  53. const now = Date.now()
  54. const expireTime = uni.getStorageSync('accessTokenExpireTime')
  55. if (!expireTime)
  56. return true
  57. return now >= expireTime
  58. })
  59. /**
  60. * 判断refreshToken是否过期
  61. */
  62. const isRefreshTokenExpired = computed(() => {
  63. if (!isDoubleTokenMode)
  64. return true
  65. const now = Date.now()
  66. const refreshExpireTime = uni.getStorageSync('refreshTokenExpireTime')
  67. if (!refreshExpireTime)
  68. return true
  69. return now >= refreshExpireTime
  70. })
  71. /**
  72. * 登录成功后处理逻辑
  73. * @param tokenInfo 登录返回的token信息
  74. */
  75. async function _postLogin(tokenInfo: IAuthLoginRes) {
  76. setTokenInfo(tokenInfo)
  77. const userStore = useUserStore()
  78. await userStore.fetchUserInfo()
  79. }
  80. /**
  81. * 用户登录
  82. * @param credentials 登录参数
  83. * @returns 登录结果
  84. */
  85. const login = async (credentials: {
  86. username: string
  87. password: string
  88. code: string
  89. uuid: string
  90. }) => {
  91. try {
  92. const res = await _login(credentials)
  93. console.log('普通登录-res: ', res)
  94. await _postLogin(res.data)
  95. uni.showToast({
  96. title: '登录成功',
  97. icon: 'success',
  98. })
  99. return res
  100. }
  101. catch (error) {
  102. console.error('登录失败:', error)
  103. uni.showToast({
  104. title: '登录失败,请重试',
  105. icon: 'error',
  106. })
  107. throw error
  108. }
  109. }
  110. /**
  111. * 微信登录
  112. * @returns 登录结果
  113. */
  114. const wxLogin = async () => {
  115. try {
  116. // 获取微信小程序登录的code
  117. const code = await getWxCode()
  118. console.log('微信登录-code: ', code)
  119. const res = await _wxLogin(code)
  120. console.log('微信登录-res: ', res)
  121. await _postLogin(res.data)
  122. uni.showToast({
  123. title: '登录成功',
  124. icon: 'success',
  125. })
  126. return res
  127. }
  128. catch (error) {
  129. console.error('微信登录失败:', error)
  130. uni.showToast({
  131. title: '微信登录失败,请重试',
  132. icon: 'error',
  133. })
  134. throw error
  135. }
  136. }
  137. /**
  138. * 退出登录 并 删除用户信息
  139. */
  140. const logout = async () => {
  141. try {
  142. await _logout()
  143. // 清除存储的过期时间
  144. uni.removeStorageSync('accessTokenExpireTime')
  145. uni.removeStorageSync('refreshTokenExpireTime')
  146. }
  147. catch (error) {
  148. console.error('退出登录失败:', error)
  149. }
  150. finally {
  151. // 无论成功失败,都需要清除本地token信息
  152. const userStore = useUserStore()
  153. await userStore.removeUserInfo()
  154. }
  155. }
  156. /**
  157. * 刷新token
  158. * @returns 刷新结果
  159. */
  160. const refreshToken = async () => {
  161. if (!isDoubleTokenMode) {
  162. console.error('单token模式不支持刷新token')
  163. throw new Error('单token模式不支持刷新token')
  164. }
  165. try {
  166. // 安全检查,确保refreshToken存在
  167. if (!isDoubleTokenRes(tokenInfo.value) || !tokenInfo.value.refreshToken) {
  168. throw new Error('无效的refreshToken')
  169. }
  170. const refreshToken = tokenInfo.value.refreshToken
  171. const res = await _refreshToken(refreshToken)
  172. console.log('刷新token-res: ', res)
  173. setTokenInfo(res.data)
  174. return res
  175. }
  176. catch (error) {
  177. console.error('刷新token失败:', error)
  178. throw error
  179. }
  180. }
  181. /**
  182. * 获取有效的token
  183. * 注意:在computed中不直接调用异步函数,只做状态判断
  184. * 实际的刷新操作应由调用方处理
  185. */
  186. const getValidToken = computed(() => {
  187. // token已过期,返回空
  188. if (isTokenExpired.value) {
  189. return ''
  190. }
  191. if (!isDoubleTokenMode) {
  192. return isSingleTokenRes(tokenInfo.value) ? tokenInfo.value.token : ''
  193. }
  194. else {
  195. return isDoubleTokenRes(tokenInfo.value) ? tokenInfo.value.accessToken : ''
  196. }
  197. })
  198. /**
  199. * 检查是否有登录信息(不考虑token是否过期)
  200. */
  201. const hasLoginInfo = computed(() => {
  202. if (isDoubleTokenMode) {
  203. return isDoubleTokenRes(tokenInfo.value) && !!tokenInfo.value.accessToken
  204. }
  205. else {
  206. return isSingleTokenRes(tokenInfo.value) && !!tokenInfo.value.token
  207. }
  208. })
  209. /**
  210. * 检查是否已登录且token有效
  211. */
  212. const hasValidLogin = computed(() => {
  213. return hasLoginInfo.value && !isTokenExpired.value
  214. })
  215. /**
  216. * 尝试获取有效的token,如果过期且可刷新,则刷新token
  217. * @returns 有效的token或空字符串
  218. */
  219. const tryGetValidToken = async (): Promise<string> => {
  220. if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) {
  221. try {
  222. await refreshToken()
  223. return getValidToken.value
  224. }
  225. catch (error) {
  226. console.error('尝试刷新token失败:', error)
  227. return ''
  228. }
  229. }
  230. return getValidToken.value
  231. }
  232. return {
  233. // 核心API方法
  234. login,
  235. wxLogin,
  236. logout,
  237. // 认证状态判断(最常用的)
  238. hasLogin: hasValidLogin,
  239. // 内部系统使用的方法
  240. refreshToken,
  241. tryGetValidToken,
  242. // 调试或特殊场景可能需要直接访问的信息
  243. tokenInfo,
  244. setTokenInfo,
  245. }
  246. },
  247. {
  248. // 添加持久化配置,确保刷新页面后token信息不丢失
  249. persist: true,
  250. },
  251. )