Quellcode durchsuchen

refactor(auth): 重构认证模块以支持双token模式

将用户认证逻辑从user store迁移到token store
新增双token模式支持及相关类型定义
更新路由拦截器和http模块以适配新的认证结构
feige996 vor 8 Monaten
Ursprung
Commit
153a374469
8 geänderte Dateien mit 184 neuen und 121 gelöschten Zeilen
  1. 6 10
      src/api/login.ts
  2. 33 15
      src/api/types/login.ts
  3. 7 6
      src/http/http.ts
  4. 5 3
      src/pages/me/me.vue
  5. 4 4
      src/router/interceptor.ts
  6. 112 0
      src/store/token.ts
  7. 12 83
      src/store/user.ts
  8. 5 0
      src/utils/index.ts

+ 6 - 10
src/api/login.ts

@@ -1,4 +1,4 @@
-import type { ICaptcha, IUpdateInfo, IUpdatePassword, IUserInfoVo, IUserLogin } from './types/login'
+import type { IAuthLoginRes, ICaptcha, IDoubleTokenRes, IUpdateInfo, IUpdatePassword, IUserInfoRes } from './types/login'
 import { http } from '@/http/http'
 
 /**
@@ -24,7 +24,7 @@ export function getCode() {
  * @param loginForm 登录表单
  */
 export function login(loginForm: ILoginForm) {
-  return http.post<IUserLogin>('/user/login', loginForm)
+  return http.post<IAuthLoginRes>('/auth/login', loginForm)
 }
 
 /**
@@ -32,21 +32,21 @@ export function login(loginForm: ILoginForm) {
  * @param refreshToken 刷新token
  */
 export function refreshToken(refreshToken: string) {
-  return http.post<IUserLogin>('/user/refreshToken', { refreshToken })
+  return http.post<IDoubleTokenRes>('/auth/refreshToken', { refreshToken })
 }
 
 /**
  * 获取用户信息
  */
 export function getUserInfo() {
-  return http.get<IUserInfoVo>('/user/info')
+  return http.get<IUserInfoRes>('/user/info')
 }
 
 /**
  * 退出登录
  */
 export function logout() {
-  return http.get<void>('/user/logout')
+  return http.get<void>('/auth/logout')
 }
 
 /**
@@ -77,15 +77,11 @@ export function getWxCode() {
   })
 }
 
-/**
- * 微信登录参数
- */
-
 /**
  * 微信登录
  * @param params 微信登录参数,包含code
  * @returns Promise 包含登录结果
  */
 export function wxLogin(data: { code: string }) {
-  return http.post<IUserLogin>('/user/wxLogin', data)
+  return http.post<IAuthLoginRes>('/auth/wxLogin', data)
 }

+ 33 - 15
src/api/types/login.ts

@@ -1,24 +1,42 @@
-/**
- * 用户信息
- */
-export interface IUserInfoVo {
-  id: number | string
-  username: string
-  avatar: string
+// 认证模式类型
+export type AuthMode = 'single' | 'double'
+
+// 单Token响应类型
+export interface ISingleTokenRes {
   token: string
-  refreshToken?: string
-  refreshExpire?: number
+  expiresIn: number // 有效期(秒)
+}
+
+// 双Token响应类型
+export interface IDoubleTokenRes {
+  accessToken: string
+  refreshToken: string
+  accessExpiresIn: number // 访问令牌有效期(秒)
+  refreshExpiresIn: number // 刷新令牌有效期(秒)
 }
 
 /**
- * 登录返回的信息
+ * 登录返回的信息,其实就是 token 信息
  */
-export interface IUserLogin {
-  id: string
+export type IAuthLoginRes = ISingleTokenRes | IDoubleTokenRes
+
+/**
+ * 用户信息
+ */
+export interface IUserInfoRes {
+  userId: number
   username: string
-  token: string
-  refreshToken?: string
-  refreshExpire?: number
+  nickname: string
+  avatar?: string
+  [key: string]: any // 允许其他扩展字段
+}
+
+// 认证存储数据结构
+export interface AuthStorage {
+  mode: AuthMode
+  tokens: ISingleTokenRes | IDoubleTokenRes
+  userInfo?: IUserInfoRes
+  loginTime: number // 登录时间戳
 }
 
 /**

+ 7 - 6
src/http/http.ts

@@ -1,6 +1,7 @@
+import type { IDoubleTokenRes } from '@/api/types/login'
 import type { CustomRequestOptions } from '@/http/types'
 import { nextTick } from 'vue'
-import { useUserStore } from '@/store/user'
+import { useTokenStore } from '@/store/token'
 
 // 刷新 token 状态管理
 let refreshing = false // 防止重复刷新 token 标识
@@ -27,15 +28,15 @@ export function http<T>(options: CustomRequestOptions) {
         }
         const resData: IResData<T> = res.data as IResData<T>
         if ((res.statusCode === 401) || (resData.code === 401)) {
-          const store = useUserStore()
+          const tokenStore = useTokenStore()
           if (sessionMode === 'single') {
             // 未启用双token策略,清理用户信息,跳转到登录页
-            store.logout()
+            tokenStore.logout()
             uni.navigateTo({ url: '/pages/login/login' })
             return reject(res)
           }
           /* -------- 无感刷新 token ----------- */
-          const { refreshToken } = store.userInfo || {}
+          const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
           // token 失效的,且有刷新 token 的,才放到请求队列里
           if ((res.statusCode === 401 || resData.code === 401) && refreshToken) {
             taskQueue.push(() => {
@@ -47,7 +48,7 @@ export function http<T>(options: CustomRequestOptions) {
             refreshing = true
             try {
               // 发起刷新 token 请求(使用 store 的 refreshToken 方法)
-              await store.refreshToken()
+              await tokenStore.refreshToken()
               // 刷新 token 成功
               refreshing = false
               nextTick(() => {
@@ -74,7 +75,7 @@ export function http<T>(options: CustomRequestOptions) {
                 })
               })
               // 清除用户信息
-              await store.logout()
+              await tokenStore.logout()
               // 跳转到登录页
               setTimeout(() => {
                 uni.navigateTo({ url: '/pages/login/login' })

+ 5 - 3
src/pages/me/me.vue

@@ -13,9 +13,11 @@
 import type { IUploadSuccessInfo } from '@/api/types/login'
 import { storeToRefs } from 'pinia'
 import { useUserStore } from '@/store'
+import { useTokenStore } from '@/store/token'
 import { useUpload } from '@/utils/uploadFile'
 
 const userStore = useUserStore()
+const tokenStore = useTokenStore()
 // 使用storeToRefs解构userInfo
 const { userInfo } = storeToRefs(userStore)
 
@@ -38,7 +40,7 @@ async function handleLogin() {
   // #ifdef MP-WEIXIN
 
   // 微信登录
-  await userStore.wxLogin()
+  await tokenStore.wxLogin()
   // #endif
   // #ifndef MP-WEIXIN
   uni.navigateTo({ url: '/pages/login/login' })
@@ -80,7 +82,7 @@ function handleLogout() {
     success: (res) => {
       if (res.confirm) {
         // 清空用户信息
-        useUserStore().logout()
+        useTokenStore().logout()
         // 执行退出登录逻辑
         uni.showToast({
           title: '退出登录成功',
@@ -140,7 +142,7 @@ function handleLogout() {
 
     <view class="mt-20 px-3">
       <view class="m-auto w-160px text-center">
-        <button v-if="userStore.hasLogin" type="warn" class="w-full" @click="handleLogout">
+        <button v-if="tokenStore.hasLogin" type="warn" class="w-full" @click="handleLogout">
           退出登录
         </button>
         <button v-else type="primary" class="w-full" @click="handleLogin">

+ 4 - 4
src/router/interceptor.ts

@@ -4,7 +4,7 @@
  * 可以设置路由白名单,或者黑名单,看业务需要选哪一个
  * 我这里应为大部分都可以随便进入,所以使用黑名单
  */
-import { useUserStore } from '@/store'
+import { useTokenStore } from '@/store/token'
 import { tabbarStore } from '@/tabbar/store'
 import { getLastPage, parseUrlToObj } from '@/utils/index'
 import { EXCLUDE_PAGE_LIST, isNeedLogin, LOGIN_PAGE, LOGIN_PAGE_LIST } from './login'
@@ -46,12 +46,12 @@ export const navigateToInterceptor = {
     }
     const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(path)}`
 
-    const userStore = useUserStore()
-    console.log('userStore.hasLogin:', userStore.hasLogin)
+    const tokenStore = useTokenStore()
+    console.log('tokenStore.hasLogin:', tokenStore.hasLogin)
 
     // #region 1/2 需要登录的情况 ---------------------------
     if (isNeedLogin) {
-      if (userStore.hasLogin) {
+      if (tokenStore.hasLogin) {
         return
       }
       else {

+ 112 - 0
src/store/token.ts

@@ -0,0 +1,112 @@
+import type { IAuthLoginRes, IDoubleTokenRes, ISingleTokenRes } from '@/api/types/login'
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import {
+  login as _login,
+  logout as _logout,
+  refreshToken as _refreshToken,
+  wxLogin as _wxLogin,
+  getWxCode,
+} from '@/api/login'
+import { isDoubleTokenMode } from '@/utils'
+import { useUserStore } from './user'
+
+// 初始化状态
+const tokenInfoState = isDoubleTokenMode
+  ? {
+      accessToken: '',
+      accessExpiresIn: 0,
+      refreshToken: '',
+      refreshExpiresIn: 0,
+    }
+  : {
+      token: '',
+      expiresIn: 0,
+    }
+
+export const useTokenStore = defineStore(
+  'token',
+  () => {
+    // 定义用户信息
+    const tokenInfo = ref<IAuthLoginRes>({ ...tokenInfoState })
+    // 设置用户信息
+    const setTokenInfo = (val: IAuthLoginRes) => {
+      tokenInfo.value = val
+    }
+
+    async function _postLogin(tokenInfo: IAuthLoginRes) {
+      setTokenInfo(tokenInfo)
+      const userStore = useUserStore()
+      await userStore.fetchUserInfo()
+    }
+
+    /**
+     * 用户登录
+     * @param credentials 登录参数
+     * @returns R<IUserLogin>
+     */
+    const login = async (credentials: {
+      username: string
+      password: string
+      code: string
+      uuid: string
+    }) => {
+      const res = await _login(credentials)
+      console.log('普通登录-res: ', res)
+      await _postLogin(res.data)
+      uni.showToast({
+        title: '登录成功',
+        icon: 'success',
+      })
+    }
+
+    /**
+     * 微信登录
+     */
+    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',
+      })
+    }
+
+    /**
+     * 退出登录 并 删除用户信息
+     */
+    const logout = async () => {
+      _logout()
+      const userStore = useUserStore()
+      await userStore.removeUserInfo()
+    }
+
+    /**
+     * 刷新token
+     */
+    const refreshToken = async () => {
+      const refreshToken = (tokenInfo.value as IDoubleTokenRes).refreshToken
+      const res = await _refreshToken(refreshToken)
+      console.log('刷新token-res: ', res)
+      setTokenInfo(res.data)
+    }
+
+    const hasLogin = computed(() => isDoubleTokenMode
+      ? (tokenInfo.value as IDoubleTokenRes).accessToken
+      : (tokenInfo.value as ISingleTokenRes).token,
+    )
+
+    return {
+      tokenInfo,
+      hasLogin,
+      login,
+      wxLogin,
+      logout,
+      refreshToken,
+    }
+  },
+)

+ 12 - 83
src/store/user.ts

@@ -1,40 +1,30 @@
-import type { IUserInfoVo } from '@/api/types/login'
+import type { IUserInfoRes } from '@/api/types/login'
 import { defineStore } from 'pinia'
 import { ref } from 'vue'
 import {
-  getUserInfo as _getUserInfo,
-  login as _login,
-  logout as _logout,
-  refreshToken as _refreshToken,
-  wxLogin as _wxLogin,
-  getWxCode,
+  getUserInfo,
 } from '@/api/login'
 
 // 初始化状态
-const userInfoState: IUserInfoVo = {
-  id: 0,
+const userInfoState: IUserInfoRes = {
+  userId: 0,
   username: '',
+  nickname: '',
   avatar: '/static/images/default-avatar.png',
-  token: '',
-  refreshToken: '',
-  refreshExpire: 0,
 }
 
 export const useUserStore = defineStore(
   'user',
   () => {
     // 定义用户信息
-    const userInfo = ref<IUserInfoVo>({ ...userInfoState })
+    const userInfo = ref<IUserInfoRes>({ ...userInfoState })
     // 设置用户信息
-    const setUserInfo = (val: IUserInfoVo) => {
+    const setUserInfo = (val: IUserInfoRes) => {
       console.log('设置用户信息', val)
       // 若头像为空 则使用默认头像
       if (!val.avatar) {
         val.avatar = userInfoState.avatar
       }
-      else {
-        val.avatar = 'https://oss.laf.run/ukw0y1-site/avatar.jpg?feige'
-      }
       userInfo.value = val
     }
     const setUserAvatar = (avatar: string) => {
@@ -51,79 +41,18 @@ export const useUserStore = defineStore(
     /**
      * 获取用户信息
      */
-    const getUserInfo = async () => {
-      const res = await _getUserInfo()
-      const userInfo = res.data
-      setUserInfo(userInfo)
-      // TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
-      return res
-    }
-    /**
-     * 用户登录
-     * @param credentials 登录参数
-     * @returns R<IUserLogin>
-     */
-    const login = async (credentials: {
-      username: string
-      password: string
-      code: string
-      uuid: string
-    }) => {
-      const res = await _login(credentials)
-      console.log('登录信息', res)
-      uni.showToast({
-        title: '登录成功',
-        icon: 'success',
-      })
-      await getUserInfo()
-      return res
-    }
-
-    /**
-     * 刷新token
-     */
-    const refreshToken = async () => {
-      const res = await _refreshToken(userInfo.value.refreshToken)
-      setUserInfo({
-        ...userInfo.value,
-        token: res.data.token,
-        refreshToken: res.data.refreshToken,
-        refreshExpire: res.data.refreshExpire,
-      })
-      await getUserInfo()
-      return res
-    }
-
-    /**
-     * 退出登录 并 删除用户信息
-     */
-    const logout = async () => {
-      _logout()
-      removeUserInfo()
-    }
-    /**
-     * 微信登录
-     */
-    const wxLogin = async () => {
-      // 获取微信小程序登录的code
-      const data = await getWxCode()
-      console.log('微信登录code', data)
-
-      const res = await _wxLogin(data)
-      await getUserInfo()
+    const fetchUserInfo = async () => {
+      const res = await getUserInfo()
+      setUserInfo(res.data)
       return res
     }
 
     return {
       userInfo,
-      login,
-      wxLogin,
+      removeUserInfo,
+      fetchUserInfo,
       setUserInfo,
-      getUserInfo,
       setUserAvatar,
-      logout,
-      hasLogin: computed(() => !!userInfo.value.token),
-      refreshToken,
     }
   },
   {

+ 5 - 0
src/utils/index.ts

@@ -170,3 +170,8 @@ export function getEnvBaseUploadUrl() {
 
   return baseUploadUrl
 }
+
+/**
+ * 是否是双token模式
+ */
+export const isDoubleTokenMode = import.meta.env.VITE_AUTH_MODE === 'double'