hookehuyr

feat(登录): 添加用户信息认证工具函数并重构登录逻辑

refactor: 将登录页面的用户信息处理逻辑提取到独立工具函数
test: 添加用户信息认证工具函数的单元测试
chore: 添加vitest测试框架及相关依赖
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.
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 +})
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) {
......