token.ts 7.2 KB

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