646 lines
15 KiB
Markdown
646 lines
15 KiB
Markdown
|
|
# 微信集成文档
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本文档详细描述了 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<string>("");
|
|||
|
|
|
|||
|
|
// 防止 CSRF 攻击的 state 参数
|
|||
|
|
const state = ref<string>("");
|
|||
|
|
|
|||
|
|
// 用户 openid
|
|||
|
|
const openid = ref<string>("");
|
|||
|
|
|
|||
|
|
// 用户 userid
|
|||
|
|
const userid = ref<string>("");
|
|||
|
|
|
|||
|
|
// 企业 ID
|
|||
|
|
const corpid = ref<string>("");
|
|||
|
|
|
|||
|
|
// 是否企业微信登录
|
|||
|
|
const corpidLogin = ref<boolean>(false);
|
|||
|
|
|
|||
|
|
// 是否是柜子管理员
|
|||
|
|
const isCabinetAdmin = ref<boolean>(false);
|
|||
|
|
|
|||
|
|
// 企业微信用户姓名
|
|||
|
|
const name = ref<string>("");
|
|||
|
|
|
|||
|
|
// 企业微信用户 ID
|
|||
|
|
const qyUserId = ref<number>(0);
|
|||
|
|
|
|||
|
|
// 汇邦云用户信息
|
|||
|
|
const ab98User = ref<ab98UserDTO | null>(null);
|
|||
|
|
|
|||
|
|
// 余额信息
|
|||
|
|
const balance = ref<number>(0);
|
|||
|
|
const useBalance = ref<number>(0);
|
|||
|
|
const balanceLimit = ref<number>(0);
|
|||
|
|
|
|||
|
|
// 处理状态
|
|||
|
|
const isHandleWxCallbackComplete = ref<boolean>(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<string>('');
|
|||
|
|
const sex = ref<string>('');
|
|||
|
|
const name = ref<string>('');
|
|||
|
|
const userid = ref<string>("");
|
|||
|
|
const registered = ref<boolean>(false);
|
|||
|
|
const tel = ref<string>("");
|
|||
|
|
const token = ref<string>("");
|
|||
|
|
|
|||
|
|
// 登录状态
|
|||
|
|
const isLogin = ref<boolean>(false);
|
|||
|
|
const tokenLogin = ref<string>("");
|
|||
|
|
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<string>({
|
|||
|
|
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<QyLoginResponse>({
|
|||
|
|
url: "/api/v1/wx/qyLogin",
|
|||
|
|
method: "POST",
|
|||
|
|
data: params
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 获取余额
|
|||
|
|
```typescript
|
|||
|
|
// 接口: GET /api/v1/balance
|
|||
|
|
export const getBalanceApi = (corpid: string, openid: string) => {
|
|||
|
|
return request<GetBalanceResponse>({
|
|||
|
|
url: "/api/v1/balance",
|
|||
|
|
method: "GET",
|
|||
|
|
params: { corpid, openid }
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 企业微信用户余额
|
|||
|
|
// 接口: GET /api/v1/balance/qyUser
|
|||
|
|
export const getBalanceByQyUserid = (corpid: string, userid: string) => {
|
|||
|
|
return request<GetBalanceResponse>({
|
|||
|
|
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<LoginData>({
|
|||
|
|
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
|
|||
|
|
<template>
|
|||
|
|
<div class="user-info">
|
|||
|
|
<!-- 显示用户信息 -->
|
|||
|
|
<div v-if="ab98UserStore.isLogin">
|
|||
|
|
<img :src="ab98UserStore.face_img" class="avatar" />
|
|||
|
|
<div class="info">
|
|||
|
|
<h3>{{ ab98UserStore.name }}</h3>
|
|||
|
|
<p>手机号: {{ ab98UserStore.tel }}</p>
|
|||
|
|
<p>余额: ¥{{ wxStore.balance }}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 未登录状态 -->
|
|||
|
|
<div v-else class="login-prompt">
|
|||
|
|
<p>请先登录</p>
|
|||
|
|
<button @click="handleLogin">微信登录</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { useWxStore } from '@/pinia/stores/wx';
|
|||
|
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user';
|
|||
|
|
|
|||
|
|
const wxStore = useWxStore();
|
|||
|
|
const ab98UserStore = useAb98UserStore();
|
|||
|
|
|
|||
|
|
// 处理登录
|
|||
|
|
const handleLogin = () => {
|
|||
|
|
// 跳转到微信授权页面
|
|||
|
|
const authUrl = generateWxAuthUrl();
|
|||
|
|
window.location.href = authUrl;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 生成微信授权 URL
|
|||
|
|
const generateWxAuthUrl = () => {
|
|||
|
|
const appId = import.meta.env.VITE_WX_APP_ID;
|
|||
|
|
const redirectUri = encodeURIComponent(window.location.origin + '/callback');
|
|||
|
|
return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=wx_auth#wechat_redirect`;
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 等待微信回调完成
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="loading-container" v-if="!wxStore.isHandleWxCallbackComplete">
|
|||
|
|
<van-loading>微信认证中...</van-loading>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-else>
|
|||
|
|
<!-- 正常页面内容 -->
|
|||
|
|
<ProductList />
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { useWxStore } from '@/pinia/stores/wx';
|
|||
|
|
|
|||
|
|
const wxStore = useWxStore();
|
|||
|
|
|
|||
|
|
// 等待微信回调处理完成
|
|||
|
|
const waitForWxCallback = async () => {
|
|||
|
|
const success = await wxStore.waitForHandleWxCallbackComplete();
|
|||
|
|
if (!success) {
|
|||
|
|
// 处理超时情况
|
|||
|
|
console.error('微信认证超时');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
waitForWxCallback();
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
|
|||
|
|
### 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<Ab98UserDTO | null>(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 接口、配置说明和常见问题解决方案。
|