token.ts 11 KB

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