# 微信集成文档 ## 概述 本文档详细描述了 MobVue 项目中微信和企业微信的集成方案,包括认证流程、API 调用、状态管理等。 ## 微信认证流程 ### 1. 微信公众号认证 #### 认证流程图 ``` 用户访问应用 ↓ 检查是否已认证 ↓ 未认证 → 跳转微信授权页面 ↓ 用户授权 ↓ 微信回调携带 code ↓ 使用 code 换取 openid ↓ 获取用户信息 ↓ 完成认证 ``` #### 代码实现 ```typescript // src/App.vue - 微信回调处理 onMounted(() => { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const state = urlParams.get('state'); if (code) { wxStore.handleWxCallback({ code, state }); } }); ``` ### 2. 企业微信认证 #### 认证流程图 ``` 用户访问应用 ↓ 检查企业微信环境 ↓ 跳转企业微信授权 ↓ 用户授权 ↓ 企业微信回调携带 code ↓ 使用 code 换取用户信息 ↓ 获取企业微信用户详情 ↓ 关联 AB98 用户系统 ↓ 完成认证 ``` #### 代码实现 ```typescript // src/pinia/stores/wx.ts - 企业微信登录 const qyLogin = async ({ corpid, code, state }: QyLoginParams) => { const res = await qyLoginApi({ corpid, code, state }); if (res && res.code == 0) { userid.value = res.data.userid; openid.value = res.data.openid; isCabinetAdmin.value = res.data.isCabinetAdmin === 1; name.value = res.data.name; qyUserId.value = res.data.qyUserId; setAb98User(res.data.ab98User); } }; ``` ## 状态管理 ### 微信状态 Store (src/pinia/stores/wx.ts) #### 状态定义 ```typescript export const useWxStore = defineStore("wx", () => { // 微信授权 code const code = ref(""); // 防止 CSRF 攻击的 state 参数 const state = ref(""); // 用户 openid const openid = ref(""); // 用户 userid const userid = ref(""); // 企业 ID const corpid = ref(""); // 是否企业微信登录 const corpidLogin = ref(false); // 是否是柜子管理员 const isCabinetAdmin = ref(false); // 企业微信用户姓名 const name = ref(""); // 企业微信用户 ID const qyUserId = ref(0); // 汇邦云用户信息 const ab98User = ref(null); // 余额信息 const balance = ref(0); const useBalance = ref(0); const balanceLimit = ref(0); // 处理状态 const isHandleWxCallbackComplete = ref(false); }); ``` #### 核心方法 ```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 { if (!corpid.value) { // 微信公众号登录 const res = await getOpenIdApi({ code: params.code }); if (res && res.code == 0) { openid.value = res.data; } } else { // 企业微信登录 await qyLogin({ corpid: corpid.value, code: params.code, state: params.state }); } // 获取余额信息 if (openid.value) { await refreshBalance(); } } finally { isHandleWxCallbackComplete.value = true; } } else { isHandleWxCallbackComplete.value = true; } }; // 刷新余额 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); } } } }; ``` ### 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(""); const registered = ref(false); const tel = ref(""); const token = ref(""); // 登录状态 const isLogin = ref(false); const tokenLogin = ref(""); const loginCode = '1'; // 默认验证码 }); ``` #### 核心方法 ```typescript // 设置用户信息 const setUserInfo = (data: LoginData) => { face_img.value = data.face_img; localStorage.setItem(STORAGE_KEYS.FACE, encodeURIComponent(data.face_img)); sex.value = data.sex; localStorage.setItem(STORAGE_KEYS.SEX, encodeURIComponent(data.sex)); name.value = data.name; localStorage.setItem(STORAGE_KEYS.NAME, encodeURIComponent(data.name)); userid.value = data.userid; localStorage.setItem(STORAGE_KEYS.USERID, encodeURIComponent(data.userid)); registered.value = data.registered; localStorage.setItem(STORAGE_KEYS.REGISTERED, JSON.stringify(data.registered)); tel.value = data.tel; localStorage.setItem(STORAGE_KEYS.TEL, encodeURIComponent(data.tel)); localStorage.setItem(STORAGE_KEYS.LOGIN_CODE, encodeURIComponent(loginCode)); }; // Token 登录 const tokenLogin = async (token: string, userid: string, openid: string) => { const res = await tokenLoginApi({ token, userid, openid }); if (res?.code === 0 && res.data?.success) { setTel(res.data.tel); setUserInfo(res.data); if (!isLogin.value) { setIsLogin(true); } } }; ``` ## API 接口 ### 微信相关接口 (src/common/apis/shop/) #### 获取 OpenID ```typescript // 接口: GET /api/v1/wx/getOpenId export const getOpenIdApi = (params: { code: string }) => { return request({ url: "/api/v1/wx/getOpenId", method: "GET", params }); }; ``` #### 企业微信登录 ```typescript // 接口: POST /api/v1/wx/qyLogin export const qyLogin = (params: { corpid: string; code: string; state?: string; }) => { return request({ url: "/api/v1/wx/qyLogin", method: "POST", data: params }); }; ``` #### 获取余额 ```typescript // 接口: GET /api/v1/balance export const getBalanceApi = (corpid: string, openid: string) => { return request({ url: "/api/v1/balance", method: "GET", params: { corpid, openid } }); }; // 企业微信用户余额 // 接口: GET /api/v1/balance/qyUser export const getBalanceByQyUserid = (corpid: string, userid: string) => { return request({ url: "/api/v1/balance/qyUser", method: "GET", params: { corpid, userid } }); }; ``` ### AB98 系统接口 (src/common/apis/ab98/) #### Token 登录 ```typescript // 接口: POST /api/v1/ab98/tokenLogin export const tokenLogin = (params: { token: string; userid: string; openid: string; }) => { return request({ url: "/api/v1/ab98/tokenLogin", method: "POST", data: params }); }; ``` ## 配置说明 ### 微信配置 #### 公众号配置 ```typescript // 微信公众号配置 const wxConfig = { appId: 'YOUR_APP_ID', redirectUri: encodeURIComponent('https://your-domain.com/callback'), scope: 'snsapi_userinfo', // 或 snsapi_base state: 'STATE_PARAM' }; // 生成授权 URL const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${wxConfig.appId}&redirect_uri=${wxConfig.redirectUri}&response_type=code&scope=${wxConfig.scope}&state=${wxConfig.state}#wechat_redirect`; ``` #### 企业微信配置 ```typescript // 企业微信配置 const qywxConfig = { corpid: 'YOUR_CORP_ID', agentId: 'YOUR_AGENT_ID', redirectUri: encodeURIComponent('https://your-domain.com/qy-callback'), state: 'STATE_PARAM' }; // 生成企业微信授权 URL const qyAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${qywxConfig.corpid}&redirect_uri=${qywxConfig.redirectUri}&response_type=code&scope=snsapi_base&state=${qywxConfig.state}#wechat_redirect`; ``` ### 环境变量配置 #### 开发环境 (.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 ``` ## 使用示例 ### 1. 页面中使用微信状态 ```vue ``` ### 2. 等待微信回调完成 ```vue ``` ## 常见问题 ### 1. 微信认证失败 #### 可能原因 - 回调域名未配置 - AppID 或 Secret 错误 - 网络问题导致 API 调用失败 #### 解决方案 ```typescript // 检查回调域名 const checkCallbackDomain = () => { const currentDomain = window.location.hostname; // 确保当前域名在微信白名单中 }; // 重试机制 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); // 手动调用获取 OpenID try { const response = await getOpenIdApi({ code }); console.log('OpenID 响应:', response); } catch (error) { console.error('获取 OpenID 失败:', error); } }; ``` ### 3. 余额信息不更新 #### 刷新策略 ```typescript // 定期刷新余额 const startBalanceRefresh = () => { // 每 30 秒刷新一次余额 setInterval(() => { if (wxStore.corpid && wxStore.userid) { wxStore.refreshBalance(); } }, 30000); }; // 在关键操作后刷新余额 const afterOrderCreate = async () => { // 创建订单后刷新余额 await wxStore.refreshBalance(); }; ``` ## 安全考虑 ### 1. Token 安全 #### 存储安全 ```typescript // 敏感信息加密存储 const encryptToken = (token: string) => { return btoa(encodeURIComponent(token)); }; const decryptToken = (encrypted: string) => { return decodeURIComponent(atob(encrypted)); }; ``` ### 2. 防 CSRF 攻击 #### State 参数验证 ```typescript // 生成随机的 state 参数 const generateState = () => { return Math.random().toString(36).substring(2, 15); }; // 验证 state 参数 const validateState = (receivedState: string, expectedState: string) => { return receivedState === expectedState; }; ``` ## 性能优化 ### 1. 减少不必要的 API 调用 ```typescript // 缓存用户信息 const cachedUserInfo = ref(null); const getUserInfo = async (forceRefresh = false) => { if (cachedUserInfo.value && !forceRefresh) { return cachedUserInfo.value; } // 调用 API 获取用户信息 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 }; }; ``` ## 测试指南 ### 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("should handle wx callback", 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"); }); }); ``` ### 2. 集成测试 ```typescript // tests/integration/wx-auth.test.ts import { describe, it, expect } from "vitest"; describe("微信认证集成测试", () => { it("应该完成完整的微信认证流程", async () => { // 模拟微信回调 // 验证状态更新 // 检查用户信息 }); }); ``` 通过本文档,您可以全面了解 MobVue 项目中微信集成的各个方面,包括认证流程、状态管理、API 接口、配置说明和常见问题解决方案。