feat(登录): 添加用户信息认证工具函数并重构登录逻辑
refactor: 将登录页面的用户信息处理逻辑提取到独立工具函数 test: 添加用户信息认证工具函数的单元测试 chore: 添加vitest测试框架及相关依赖
Showing
7 changed files
with
97 additions
and
18 deletions
| 1 | ### | 1 | ### |
| 2 | # @Date: 2025-03-20 23:40:15 | 2 | # @Date: 2025-03-20 23:40:15 |
| 3 | # @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | # @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - # @LastEditTime: 2025-12-02 18:34:41 | 4 | + # @LastEditTime: 2025-12-09 21:05:15 |
| 5 | # @FilePath: /mlaj/.env.development | 5 | # @FilePath: /mlaj/.env.development |
| 6 | # @Description: 文件描述 | 6 | # @Description: 文件描述 |
| 7 | ### | 7 | ### | ... | ... |
| ... | @@ -7,6 +7,7 @@ | ... | @@ -7,6 +7,7 @@ |
| 7 | "dev": ". ~/.nvm/nvm.sh && nvm use 18.19.1 && vite", | 7 | "dev": ". ~/.nvm/nvm.sh && nvm use 18.19.1 && vite", |
| 8 | "build": ". ~/.nvm/nvm.sh && nvm use 18.19.1 && vite build", | 8 | "build": ". ~/.nvm/nvm.sh && nvm use 18.19.1 && vite build", |
| 9 | "preview": "vite preview", | 9 | "preview": "vite preview", |
| 10 | + "test": "vitest run", | ||
| 10 | "tar": "tar -czvpf dist.tar.gz mlaj", | 11 | "tar": "tar -czvpf dist.tar.gz mlaj", |
| 11 | "build_tar": "npm run build && npm run tar", | 12 | "build_tar": "npm run build && npm run tar", |
| 12 | "scp-dev": "scp dist.tar.gz ipadbiz-inner:/opt/space-dev/f", | 13 | "scp-dev": "scp dist.tar.gz ipadbiz-inner:/opt/space-dev/f", |
| ... | @@ -62,6 +63,7 @@ | ... | @@ -62,6 +63,7 @@ |
| 62 | "tailwindcss": "^3.4.1", | 63 | "tailwindcss": "^3.4.1", |
| 63 | "unplugin-auto-import": "^19.1.1", | 64 | "unplugin-auto-import": "^19.1.1", |
| 64 | "unplugin-vue-components": "^28.4.1", | 65 | "unplugin-vue-components": "^28.4.1", |
| 65 | - "vite": "^6.2.0" | 66 | + "vite": "^6.2.0", |
| 67 | + "vitest": "^3.2.0" | ||
| 66 | } | 68 | } |
| 67 | } | 69 | } | ... | ... |
This diff is collapsed. Click to expand it.
src/utils/__tests__/auth_user_info.test.js
0 → 100644
| 1 | +import { describe, expect, it, vi } from 'vitest' | ||
| 2 | +import { applyUserInfoAuth } from '../auth_user_info' | ||
| 3 | + | ||
| 4 | +const createStorage = () => { | ||
| 5 | + const store = {} | ||
| 6 | + return { | ||
| 7 | + store, | ||
| 8 | + setItem: vi.fn((key, value) => { | ||
| 9 | + store[key] = value | ||
| 10 | + }), | ||
| 11 | + getItem: vi.fn((key) => store[key] ?? null), | ||
| 12 | + removeItem: vi.fn((key) => { | ||
| 13 | + delete store[key] | ||
| 14 | + }), | ||
| 15 | + clear: vi.fn(() => { | ||
| 16 | + Object.keys(store).forEach((key) => delete store[key]) | ||
| 17 | + }) | ||
| 18 | + } | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +describe('applyUserInfoAuth', () => { | ||
| 22 | + it('从 response.data.user_info 写入鉴权与缓存', () => { | ||
| 23 | + const storage = createStorage() | ||
| 24 | + const set_auth_headers = vi.fn() | ||
| 25 | + const response = { | ||
| 26 | + data: { | ||
| 27 | + user_info: { | ||
| 28 | + user_id: '100', | ||
| 29 | + HTTP_USER_TOKEN: 'token-xxx', | ||
| 30 | + user_name: '张三' | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + const ok = applyUserInfoAuth(response, { storage, set_auth_headers }) | ||
| 36 | + | ||
| 37 | + expect(ok).toBe(true) | ||
| 38 | + expect(set_auth_headers).toHaveBeenCalledTimes(1) | ||
| 39 | + expect(set_auth_headers).toHaveBeenCalledWith('100', 'token-xxx') | ||
| 40 | + expect(storage.setItem).toHaveBeenCalledTimes(1) | ||
| 41 | + expect(storage.setItem).toHaveBeenCalledWith('user_info', JSON.stringify(response.data.user_info)) | ||
| 42 | + }) | ||
| 43 | + | ||
| 44 | + it('缺少 user_id 或 HTTP_USER_TOKEN 时不做任何写入', () => { | ||
| 45 | + const storage = createStorage() | ||
| 46 | + const set_auth_headers = vi.fn() | ||
| 47 | + | ||
| 48 | + const ok = applyUserInfoAuth({ user_id: '100' }, { storage, set_auth_headers }) | ||
| 49 | + | ||
| 50 | + expect(ok).toBe(false) | ||
| 51 | + expect(set_auth_headers).not.toHaveBeenCalled() | ||
| 52 | + expect(storage.setItem).not.toHaveBeenCalled() | ||
| 53 | + }) | ||
| 54 | + | ||
| 55 | + it('支持直接传入 user_info 对象', () => { | ||
| 56 | + const storage = createStorage() | ||
| 57 | + const set_auth_headers = vi.fn() | ||
| 58 | + const user_info = { user_id: 1, HTTP_USER_TOKEN: 't' } | ||
| 59 | + | ||
| 60 | + const ok = applyUserInfoAuth(user_info, { storage, set_auth_headers }) | ||
| 61 | + | ||
| 62 | + expect(ok).toBe(true) | ||
| 63 | + expect(set_auth_headers).toHaveBeenCalledWith(1, 't') | ||
| 64 | + expect(storage.setItem).toHaveBeenCalledWith('user_info', JSON.stringify(user_info)) | ||
| 65 | + }) | ||
| 66 | +}) |
src/utils/auth_user_info.js
0 → 100644
| 1 | +export const applyUserInfoAuth = (input, options = {}) => { | ||
| 2 | + const user_info = input && input.data && input.data.user_info ? input.data.user_info : input | ||
| 3 | + if (!user_info || typeof user_info !== 'object') { | ||
| 4 | + return false | ||
| 5 | + } | ||
| 6 | + | ||
| 7 | + const { user_id, HTTP_USER_TOKEN } = user_info | ||
| 8 | + if (!user_id || !HTTP_USER_TOKEN) { | ||
| 9 | + return false | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + const set_auth_headers = options.set_auth_headers | ||
| 13 | + if (typeof set_auth_headers === 'function') { | ||
| 14 | + set_auth_headers(user_id, HTTP_USER_TOKEN) | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + const storage = Object.prototype.hasOwnProperty.call(options, 'storage') ? options.storage : globalThis.localStorage | ||
| 18 | + if (storage && typeof storage.setItem === 'function') { | ||
| 19 | + storage.setItem('user_info', JSON.stringify(user_info || {})) | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + return true | ||
| 23 | +} |
| ... | @@ -192,6 +192,7 @@ import { smsAPI } from "@/api/common"; | ... | @@ -192,6 +192,7 @@ import { smsAPI } from "@/api/common"; |
| 192 | import { showToast } from "vant"; | 192 | import { showToast } from "vant"; |
| 193 | import UserAgreement from "@/components/ui/UserAgreement.vue"; | 193 | import UserAgreement from "@/components/ui/UserAgreement.vue"; |
| 194 | import { setAuthHeaders } from "@/utils/axios"; | 194 | import { setAuthHeaders } from "@/utils/axios"; |
| 195 | +import { applyUserInfoAuth } from "@/utils/auth_user_info"; | ||
| 195 | import weixinLogo from '@/assets/images/weixin_logo_lg.jpeg' | 196 | import weixinLogo from '@/assets/images/weixin_logo_lg.jpeg' |
| 196 | import { startWxAuth } from '@/router/guards' | 197 | import { startWxAuth } from '@/router/guards' |
| 197 | import { wxInfo } from '@/utils/tools' | 198 | import { wxInfo } from '@/utils/tools' |
| ... | @@ -310,14 +311,7 @@ const handleSubmit = async () => { | ... | @@ -310,14 +311,7 @@ const handleSubmit = async () => { |
| 310 | error.value = response.msg || "登录失败,请检查您的输入项"; | 311 | error.value = response.msg || "登录失败,请检查您的输入项"; |
| 311 | return; | 312 | return; |
| 312 | } else { | 313 | } else { |
| 313 | - // 获取data里面的 user_id, HTTP_USER_TOKEN, 并设置到后面所有的请求头里面,headers.User-Id, headers.User-Token | 314 | + applyUserInfoAuth(response, { set_auth_headers: setAuthHeaders, storage: localStorage }) |
| 314 | - const { user_id, HTTP_USER_TOKEN } = response?.data?.user_info || {}; | ||
| 315 | - if (user_id && HTTP_USER_TOKEN) { | ||
| 316 | - // 设置认证请求头 | ||
| 317 | - setAuthHeaders(user_id, HTTP_USER_TOKEN); | ||
| 318 | - // 缓存user_info | ||
| 319 | - localStorage.setItem('user_info', JSON.stringify(response?.data?.user_info || {})); | ||
| 320 | - } | ||
| 321 | } | 315 | } |
| 322 | 316 | ||
| 323 | const { code, data } = await getUserInfoAPI(); | 317 | const { code, data } = await getUserInfoAPI(); | ... | ... |
| ... | @@ -90,6 +90,7 @@ import { showToast } from 'vant' | ... | @@ -90,6 +90,7 @@ import { showToast } from 'vant' |
| 90 | import { useTitle } from '@vueuse/core' | 90 | import { useTitle } from '@vueuse/core' |
| 91 | import { setAuthHeaders } from "@/utils/axios"; | 91 | import { setAuthHeaders } from "@/utils/axios"; |
| 92 | import VideoBackground from '@/components/ui/VideoBackground.vue' | 92 | import VideoBackground from '@/components/ui/VideoBackground.vue' |
| 93 | +import { applyUserInfoAuth } from '@/utils/auth_user_info' | ||
| 93 | 94 | ||
| 94 | // 导入接口 | 95 | // 导入接口 |
| 95 | import { smsAPI } from '@/api/common' | 96 | import { smsAPI } from '@/api/common' |
| ... | @@ -172,14 +173,7 @@ const handleLogin = async () => { | ... | @@ -172,14 +173,7 @@ const handleLogin = async () => { |
| 172 | try { | 173 | try { |
| 173 | const res = await loginAPI({ mobile: phone.value, sms_code: code.value, entry: entry.value, referrer_user_id: referrer_user_id.value }) | 174 | const res = await loginAPI({ mobile: phone.value, sms_code: code.value, entry: entry.value, referrer_user_id: referrer_user_id.value }) |
| 174 | if (res.code) { | 175 | if (res.code) { |
| 175 | - // 获取data里面的 user_id, HTTP_USER_TOKEN, 并设置到后面所有的请求头里面,headers.User-Id, headers.User-Token | 176 | + applyUserInfoAuth(res, { set_auth_headers: setAuthHeaders, storage: localStorage }) |
| 176 | - const { user_id, HTTP_USER_TOKEN } = res?.data?.user_info || {}; | ||
| 177 | - if (user_id && HTTP_USER_TOKEN) { | ||
| 178 | - // 设置认证请求头 | ||
| 179 | - setAuthHeaders(user_id, HTTP_USER_TOKEN); | ||
| 180 | - // 缓存user_info | ||
| 181 | - localStorage.setItem('user_info', JSON.stringify(res?.data?.user_info || {})); | ||
| 182 | - } | ||
| 183 | const userInfo = await userInfoAPI() | 177 | const userInfo = await userInfoAPI() |
| 184 | // 登录之后需要判断是否有完善个人信息 | 178 | // 登录之后需要判断是否有完善个人信息 |
| 185 | if (userInfo.code) { | 179 | if (userInfo.code) { | ... | ... |
-
Please register or login to post a comment