# 微信登录、企业微信登录与Fake登录流程详细文档 ## 文档概述 本文档详细描述了智柜宝系统中三种登录方式的完整流程、参数说明、返回信息及实现逻辑。 ## 一、微信登录(普通微信公众号登录) ### 1.1 登录流程图 ``` 用户访问应用 ↓ 检查微信环境(micromessenger) ↓ 跳转微信授权页面 ↓ 用户授权 ↓ 微信回调携带 code 和 state ↓ 调用 getOpenIdApi 换取 openid ↓ 调用 getBalanceApi 获取余额 ↓ 完成认证 ``` ### 1.2 核心实现 #### 前端回调处理(`src/main.ts` 或页面组件) ```typescript onMounted(() => { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const state = urlParams.get('state'); if (code) { wxStore.handleWxCallback({ code, state }); } }); ``` #### Store 处理逻辑(`src/pinia/stores/wx.ts:81-147`) ```typescript const handleWxCallback = async (params: { corpid?: string; code?: string; state?: string; }) => { isHandleWxCallbackComplete.value = false; if (params.code) { code.value = params.code; state.value = params.state || state.value; corpid.value = params.corpid || corpid.value; corpidLogin.value = !!corpid.value; try { // 微信公众号登录(无 corpid) if (!corpid.value) { const res = await getOpenIdApi({ code: params.code }); if (res && res.code == 0) { openid.value = res.data; } // 获取余额信息 if (openid.value) { corpid.value = "wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw"; // 默认企业ID const balanceRes = await getBalanceApi(corpid.value, openid.value); if (balanceRes && balanceRes.code == 0) { balance.value = balanceRes.data.balance; useBalance.value = balanceRes.data.useBalance; balanceLimit.value = balanceRes.data.balanceLimit; userid.value = balanceRes.data.userid; if (!ab98User.value && balanceRes.data.ab98User) { setAb98User(balanceRes.data.ab98User); } } } } } catch (err) { console.error('获取 openid 失败:', err); } finally { isHandleWxCallbackComplete.value = true; } } else { isHandleWxCallbackComplete.value = true; } }; ``` ### 1.3 API 接口详情 #### 获取 OpenID **接口地址**: `GET /api/v1/payment/getOpenId` **请求参数**: ```typescript interface GetOpenIdRequestParams { code: string; // 微信授权后返回的授权码 } ``` **返回数据**: ```typescript interface ApiResponseData { code: number; // 状态码:0 表示成功 msg: string; // 消息 data: string; // openid 字符串 } ``` **示例响应**: ```json { "code": 0, "msg": "success", "data": "oMRxw6Eum0DB1IjI_pEX_yrawBHw" } ``` #### 获取余额 **接口地址**: `GET /api/v1/payment/getBalance` **请求参数**: ```typescript { corpid: string; // 企业ID openid: string; // 微信用户唯一标识 } ``` **返回数据**: ```typescript interface GetBalanceResponse { userid: string; // 系统用户ID corpid: string; // 企业ID balance: number; // 剩余借呗 useBalance: number; // 已用借呗 balanceLimit: number; // 借呗总额 ab98User: ab98UserDTO; // 汇邦云用户信息 } ``` **示例响应**: ```json { "code": 0, "msg": "success", "data": { "userid": "woZ1ZrEgAAV9AEdRt1MGQxSg-KDJrDlA", "corpid": "wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw", "balance": 5000, "useBalance": 2000, "balanceLimit": 7000, "ab98User": { "userid": "123456", "name": "张三", "tel": "13800138000", "sex": "男", "faceImg": "https://example.com/avatar.jpg", "registered": true, "ab98Balance": 10000 } } } ``` ### 1.4 微信授权 URL 生成 ```typescript const generateWxAuthUrl = () => { const appId = import.meta.env.VITE_WX_APP_ID; const redirectUri = encodeURIComponent(window.location.origin + '/callback'); const scope = 'snsapi_userinfo'; // 或 snsapi_base const state = Math.random().toString(36).substring(2, 15); return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`; }; ``` **参数说明**: - `appid`: 微信公众号的 AppID - `redirect_uri`: 授权后回调的网址(需 URL 编码) - `scope`: 授权范围 - `snsapi_base`: 静默授权,只能获取 openid - `snsapi_userinfo`: 需用户授权,可获取用户信息 - `state`: 自定义参数,用于防止 CSRF 攻击 --- ## 二、企业微信登录 ### 2.1 登录流程图 ``` 用户访问应用 ↓ 检测 URL 中的 corpid 参数 ↓ 跳转企业微信授权页面 ↓ 用户授权 ↓ 企业微信回调携带 corpid、code、state ↓ 调用 qyLogin 验证并获取用户信息 ↓ 调用 getBalanceByQyUserid 获取余额 ↓ 关联 AB98 用户系统 ↓ 完成认证 ``` ### 2.2 核心实现 #### 路由守卫处理(`src/router/guard.ts:22-31`) ```typescript // 企业微信登录参数检测 const urlParams = new URLSearchParams(window.location.search); const corpid = urlParams.get('corpid') || undefined; if (corpid) { return true; } const isAdmin = urlParams.get('isAdmin') || undefined; if (isAdmin) { return true; } ``` #### Store 处理逻辑(`src/pinia/stores/wx.ts:93-109`) ```typescript if (!corpid.value) { // 微信公众号登录 const res = await getOpenIdApi({ code: params.code }); if (res && res.code == 0) { openid.value = res.data; } } else { // 企业微信登录 const res = await qyLogin({ corpid: corpid.value, code: params.code, state: params.state }); if (res && res.code == 0) { userid.value = res.data.userid; // 系统用户ID openid.value = res.data.openid; // 微信 openid isCabinetAdmin.value = res.data.isCabinetAdmin === 1; // 是否为柜子管理员 name.value = res.data.name; // 用户姓名 qyUserId.value = res.data.qyUserId; // 企业微信用户ID setAb98User(res.data.ab98User); // 设置 AB98 用户信息 } } ``` #### 余额获取(`src/pinia/stores/wx.ts:66-79`) ```typescript const refreshBalance = async () => { if (corpid.value && userid.value) { const res = await getBalanceByQyUserid(corpid.value, userid.value); if (res && res.code == 0) { balance.value = res.data.balance; useBalance.value = res.data.useBalance; balanceLimit.value = res.data.balanceLimit; if (res.data.ab98User) { setAb98User(res.data.ab98User); } } } }; ``` ### 2.3 API 接口详情 #### 企业微信登录 **接口地址**: `GET /api/v1/payment/login/qy` **请求参数**: ```typescript interface QyLoginRequestParams { corpid: string; // 企业ID code: string; // 企业微信授权后返回的授权码 state?: string; // 状态参数(防CSRF) } ``` **返回数据**: ```typescript interface QyLoginDTO { userid: string; // 系统用户ID openid: string; // 微信 openid isCabinetAdmin: number; // 是否为柜子管理员(1是 0否) qyUserId: number; // 企业微信用户ID name: string; // 用户姓名 ab98User: ab98UserDTO; // 汇邦云用户信息 } ``` **示例响应**: ```json { "code": 0, "msg": "success", "data": { "userid": "woZ1ZrEgAAV9AEdRt1MGQxSg-KDJrDlA", "openid": "oMRxw6Eum0DB1IjI_pEX_yrawBHw", "isCabinetAdmin": 1, "qyUserId": 10001, "name": "张三", "ab98User": { "userid": "123456", "name": "张三", "tel": "13800138000", "sex": "男", "faceImg": "https://example.com/avatar.jpg", "registered": true, "ab98Balance": 10000 } } } ``` #### 根据企业微信用户ID获取余额 **接口地址**: `GET /api/v1/payment/getBalanceByQyUserid` **请求参数**: ```typescript { corpid: string; // 企业ID userid: string; // 系统用户ID } ``` **返回数据**: 同 `GetBalanceResponse` 结构 ### 2.4 企业微信授权 URL 生成 ```typescript const generateQyWxAuthUrl = () => { const corpid = import.meta.env.VITE_WX_CORP_ID; const redirectUri = encodeURIComponent(window.location.origin + '/callback'); const scope = 'snsapi_base'; // 企业微信通常使用 base const state = Math.random().toString(36).substring(2, 15); return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${corpid}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`; }; ``` --- ## 三、Fake登录(模拟企业微信登录) ### 3.1 应用场景 Fake登录用于开发测试环境,模拟企业微信登录流程,无需真实的企业微信授权,直接使用预设的测试账号信息。 ### 3.2 登录流程图 ``` 触发 fake 登录 ↓ 设置 fake 登录状态 ↓ 设置默认企业ID ↓ 设置默认用户ID ↓ 调用 fakeQyLoginApi ↓ 获取模拟用户信息 ↓ 完成认证 ``` ### 3.3 核心实现(`src/pinia/stores/wx.ts:149-163`) ```typescript const fakeQyLogin = async () => { isFakeQyLogin.value = true; // 标记为 fake 登录 corpid.value = "wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw"; // 默认企业ID corpidLogin.value = true; // 标记为企业微信登录 userid.value = "woZ1ZrEgAAV9AEdRt1MGQxSg-KDJrDlA"; // 默认用户ID // 调用 fake API const res = await fakeQyLoginApi({ corpid: corpid.value, userid: userid.value }); if (res && res.code == 0) { userid.value = res.data.userid; openid.value = "oMRxw6Eum0DB1IjI_pEX_yrawBHw"; // 固定的测试 openid isCabinetAdmin.value = res.data.isCabinetAdmin === 1; name.value = res.data.name; qyUserId.value = res.data.qyUserId; setAb98User(res.data.ab98User); } }; ``` ### 3.4 API 接口详情 **接口地址**: `GET /api/v1/payment/login/qy/fake` **请求参数**: ```typescript { corpid: string; // 企业ID userid: string; // 系统用户ID } ``` **返回数据**: 与 `QyLoginDTO` 相同 **示例响应**: ```json { "code": 0, "msg": "success", "data": { "userid": "woZ1ZrEgAAV9AEdRt1MGQxSg-KDJrDlA", "openid": "oMRxw6Eum0DB1IjI_pEX_yrawBHw", "isCabinetAdmin": 1, "qyUserId": 10001, "name": "测试用户", "ab98User": { "userid": "123456", "name": "测试用户", "tel": "13800138000", "sex": "男", "faceImg": "https://example.com/test-avatar.jpg", "registered": true, "ab98Balance": 10000 } } } ``` ### 3.5 调用方式 #### 在页面中调用 ```typescript import { useWxStore } from '@/pinia/stores/wx'; const wxStore = useWxStore(); // 触发 fake 登录 const handleFakeLogin = () => { wxStore.fakeQyLogin(); }; ``` #### 在组件模板中调用 ```vue ``` --- ## 四、状态管理(Pinia Store) ### 4.1 WxStore 状态定义(`src/pinia/stores/wx.ts:7-38`) ```typescript export const useWxStore = defineStore("wx", () => { // 授权信息 const code = ref(""); // 微信授权 code const state = ref(""); // 防CSRF的state参数 // 用户标识 const openid = ref(""); // 微信用户唯一标识 const userid = ref(""); // 系统用户ID const qyUserId = ref(0); // 企业微信用户ID // 企业信息 const corpid = ref(""); // 企业ID const corpidLogin = ref(false);// 是否企业微信登录 // 用户信息 const isCabinetAdmin = ref(false); // 是否是柜子管理员 const name = ref(""); // 企业微信用户姓名 const ab98User = ref(null); // 汇邦云用户信息 // 余额信息 const balance = ref(0); // 剩余借呗 const useBalance = ref(0); // 已用借呗 const balanceLimit = ref(0); // 借呗总额 // 登录状态 const isFakeQyLogin = ref(false); // 是否fake登录 const isHandleWxCallbackComplete = ref(false); // 回调处理是否完成 }); ``` ### 4.2 AB98用户Store(`src/pinia/stores/ab98-user.ts`) ```typescript export const useAb98UserStore = defineStore("ab98User", () => { // 用户基本信息 const face_img = ref(''); // 用户头像 const sex = ref(''); // 性别 const name = ref(''); // 姓名 const userid = ref(""); // 用户ID const registered = ref(false); // 是否已注册 const tel = ref(""); // 手机号 const token = ref(""); // 认证令牌 // 登录状态 const isLogin = ref(false); const tokenLogin = ref(""); const loginCode = '1'; // 默认验证码 }); ``` ### 4.3 核心方法 #### 设置 AB98 用户信息 ```typescript const setAb98User = (user: ab98UserDTO) => { ab98User.value = user; const ab98UserStore = useAb98UserStore(); ab98UserStore.setUserInfo({ face_img: ab98User.value.faceImg || "", success: true, sex: ab98User.value.sex || "", name: ab98User.value.name || "", userid: ab98User.value.userid || "", registered: true, tel: ab98User.value.tel || "", }); }; ``` #### 等待回调完成 ```typescript const waitForHandleWxCallbackComplete = async (timeout = 30000): Promise => { const startTime = Date.now(); while (!isHandleWxCallbackComplete.value) { if (Date.now() - startTime > timeout) { console.warn('等待 handleWxCallback 完成超时'); return false; } await new Promise(resolve => setTimeout(resolve, 100)); } return true; }; ``` --- ## 五、数据类型定义 ### 5.1 ab98UserDTO ```typescript interface ab98UserDTO { /** 主键ID */ ab98UserId?: number; /** openid */ openid?: string; /** 汇邦云用户唯一ID */ userid?: string; /** 真实姓名 */ name?: string; /** 手机号码 */ tel?: string; /** 身份证号码 */ idnum?: string; /** 性别(男 女) */ sex?: string; /** 人脸照片地址 */ faceImg?: string; /** 身份证正面地址 */ idcardFront?: string; /** 身份证背面地址 */ idcardBack?: string; /** 身份证登记地址 */ address?: string; /** 是否已注册(0未注册 1已注册) */ registered?: boolean; /** 借呗余额 单位分 */ ab98Balance?: number; } ``` ### 5.2 通用响应类型 ```typescript interface ApiResponseData { code: number; // 状态码:0表示成功,其他值表示失败 msg: string; // 消息 data: T; // 数据 } interface ApiResponseMsgData { code: number; // 状态码 msg: string; // 消息 data: T; // 数据 } ``` --- ## 六、环境配置 ### 6.1 环境变量 #### 开发环境(`.env.development`) ```env # 微信公众号配置 VITE_WX_APP_ID=your_dev_app_id VITE_WX_CORP_ID=your_dev_corp_id # API 基础地址 VITE_BASE_URL=https://apifoxmock.com/m1/2930465-2145633-default ``` #### 生产环境(`.env.production`) ```env # 微信公众号配置 VITE_WX_APP_ID=your_prod_app_id VITE_WX_CORP_ID=your_prod_corp_id # API 基础地址 VITE_BASE_URL=https://api.example.com ``` ### 6.2 微信环境检测(`src/common/utils/wx.ts`) ```typescript function checkInWeChat(targetUrl: string) { const ua = navigator.userAgent.toLowerCase(); if (!ua.includes('micromessenger')) { // 不在微信内,提示用户在微信中打开 const weChatUrl = `weixin://dl/business/?t=${encodeURIComponent(targetUrl)}`; window.location.href = weChatUrl; setTimeout(() => { if (document.hidden) return; alert("未检测到微信,请手动打开微信并访问链接。"); }, 2000); } else { console.log("已在微信内,无需跳转"); } } ``` --- ## 七、路由守卫与权限控制 ### 7.1 登录检查(`src/router/guard.ts`) ```typescript router.beforeEach((to, _from) => { // 检测企业微信登录参数 const urlParams = new URLSearchParams(window.location.search); const corpid = urlParams.get('corpid') || undefined; if (corpid) return true; const isAdmin = urlParams.get('isAdmin') || undefined; if (isAdmin) return true; // 检查 AB98 用户登录状态 const ab98UserStore = useAb98UserStore(); if (!ab98UserStore.isLogin) { if (isWhiteList(to)) return true; // 白名单页面直接访问 return "/ab98"; // 重定向到登录页 } return true; }); ``` ### 7.2 等待认证完成 在页面组件中使用: ```vue ``` --- ## 八、错误处理与调试 ### 8.1 常见错误 #### 1. 微信认证失败 **可能原因**: - 回调域名未在微信公众平台配置 - AppID 或 Secret 错误 - 网络问题导致 API 调用失败 **解决方案**: ```typescript const retryWxAuth = async (maxRetries = 3) => { for (let i = 0; i < maxRetries; i++) { try { await wxStore.handleWxCallback(params); if (wxStore.isHandleWxCallbackComplete) break; } catch (error) { console.error(`微信认证重试 ${i + 1} 失败:`, error); if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000)); } } }; ``` #### 2. OpenID 获取失败 **调试方法**: ```typescript const debugWxAuth = async () => { console.log('当前 URL 参数:', window.location.search); const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (!code) { console.error('未获取到微信 code'); return; } console.log('获取到 code:', code); try { const response = await getOpenIdApi({ code }); console.log('OpenID 响应:', response); } catch (error) { console.error('获取 OpenID 失败:', error); } }; ``` ### 8.2 日志输出 在关键位置添加日志: ```typescript const handleWxCallback = async (params: any) => { console.log('=== 微信回调开始 ==='); console.log('参数:', params); console.log('corpid:', corpid.value); console.log('corpidLogin:', corpidLogin.value); // ... 处理逻辑 console.log('=== 微信回调完成 ==='); }; ``` --- ## 九、最佳实践 ### 9.1 安全性 1. **CSRF 防护** - 使用 `state` 参数防止 CSRF 攻击 - 生成随机字符串作为 `state` 值 2. **Token 加密** ```typescript const encryptToken = (token: string) => { return btoa(encodeURIComponent(token)); }; ``` 3. **敏感信息存储** - 避免在 localStorage 中存储敏感信息 - 使用加密后存储 ### 9.2 性能优化 1. **减少 API 调用** ```typescript const cachedUserInfo = ref(null); const getUserInfo = async (forceRefresh = false) => { if (cachedUserInfo.value && !forceRefresh) { return cachedUserInfo.value; } const userInfo = await fetchUserInfo(); cachedUserInfo.value = userInfo; return userInfo; }; ``` 2. **并行请求** ```typescript const loadUserData = async () => { const [userInfo, balanceInfo] = await Promise.all([ getUserInfo(), getBalanceInfo() ]); return { userInfo, balanceInfo }; }; ``` ### 9.3 用户体验 1. **加载状态提示** ```vue ``` 2. **错误提示** ```typescript const handleError = (error: any) => { Toast.fail(error.message || '操作失败'); }; ``` --- ## 十、测试 ### 10.1 单元测试 ```typescript // tests/stores/wx.test.ts import { describe, it, expect } from "vitest"; import { setActivePinia, createPinia } from "pinia"; import { useWxStore } from "@/pinia/stores/wx"; describe("Wx Store", () => { beforeEach(() => { setActivePinia(createPinia()); }); it("应该处理微信回调", async () => { const wxStore = useWxStore(); await wxStore.handleWxCallback({ code: "test_code", state: "test_state" }); expect(wxStore.code).toBe("test_code"); expect(wxStore.state).toBe("test_state"); }); it("应该进行 fake 登录", async () => { const wxStore = useWxStore(); await wxStore.fakeQyLogin(); expect(wxStore.isFakeQyLogin).toBe(true); }); }); ``` ### 10.2 集成测试 ```typescript // tests/integration/login.test.ts describe("登录流程集成测试", () => { it("应该完成完整的微信认证流程", async () => { // 1. 模拟微信回调 const params = { code: "test_code", state: "test_state" }; // 2. 处理回调 await wxStore.handleWxCallback(params); // 3. 验证状态更新 expect(wxStore.isHandleWxCallbackComplete).toBe(true); // 4. 检查用户信息 expect(wxStore.openid).toBeTruthy(); }); }); ``` --- ## 十一、总结 ### 11.1 三种登录方式对比 | 特性 | 微信登录 | 企业微信登录 | Fake登录 | |------|----------|--------------|----------| | 触发方式 | 微信授权回调 | 企业微信授权回调 | 手动调用 | | 依赖 | 微信公众号 | 企业微信 | 无 | | 适用环境 | 生产/测试 | 生产/测试 | 仅测试 | | 获取openid | ✓ | ✓ | ✓(模拟) | | 获取用户信息 | ✓ | ✓ | ✓(模拟) | | 获取余额 | ✓ | ✓ | ✓(模拟) | | 管理员权限 | 根据后端返回 | 根据后端返回 | 根据后端返回 | ### 11.2 关键要点 1. **微信登录**:使用 `snsapi_userinfo` 或 `snsapi_base` 授权,通过 `code` 获取 `openid` 2. **企业微信登录**:需要 `corpid` 参数,通过 `code` 获取企业微信用户信息 3. **Fake登录**:仅用于测试环境,直接使用预设的测试账号 4. **状态管理**:使用 Pinia 管理登录状态和用户信息 5. **余额系统**:通过 `balance`、`useBalance`、`balanceLimit` 管理借呗额度 6. **AB98集成**:三种登录方式最终都会关联到汇邦云用户系统 ### 11.3 开发注意事项 1. 确保在微信/企业微信环境下测试 2. 正确配置回调域名 3. 使用 `state` 参数防止 CSRF 攻击 4. 合理处理 API 调用错误 5. 在测试环境使用 Fake 登录提高效率 6. 遵循状态管理最佳实践 --- **文档版本**: v1.0 **最后更新**: 2025-11-07 **维护者**: 智柜宝开发团队