- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
793 lines
25 KiB
Markdown
793 lines
25 KiB
Markdown
# OMS 微信登录整合方案
|
||
|
||
> 版本:v1.0
|
||
> 日期:2026-06-24
|
||
> 策略:**B — 公众号回调独占 OMS**(不与 WMS 共用同一套公众号服务器配置,WMS 微信能力逐步下线或改用独立公众号)
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
### 1.1 背景
|
||
|
||
WMS 已实现基于**微信公众号临时二维码 + 扫码事件回调 + Redis 桥接 + 前端轮询**的登录方案。OMS 客户门户登录页已预留微信入口(`wechat-login.vue`),但当前为 UI 占位,后端无微信相关接口。
|
||
|
||
OMS 后端为 C#(`SimpleAdmin.Api` + JWT),客户实体 `t_customer` 已预留 `WechatOpenid` 等字段,仓储层已有 `GetByWechatOpenidAsync`,具备约 40% 基础能力。
|
||
|
||
### 1.2 目标
|
||
|
||
| 目标 | 说明 |
|
||
|------|------|
|
||
| PC 客户扫码登录 | 客户门户 `/` 切换微信登录,扫码后自动签发 JWT |
|
||
| 微信授权注册 | 未绑定用户通过公众号菜单进入 H5 注册页 |
|
||
| 微信授权绑定 | 已有 OMS 客户账号,绑定当前微信 openId |
|
||
| 沿用同一公众号 | AppId / Secret / Token 与现网 WMS 使用同一公众号,但**服务器 URL 改指向 OMS** |
|
||
| 仅客户门户 | 管理端 `/admin` 不提供微信扫码登录 |
|
||
|
||
### 1.3 策略 B 说明
|
||
|
||
```
|
||
微信公众平台服务器配置 URL
|
||
旧:WMS → https://{wms}/apiwechat/{appId}
|
||
新:OMS → https://api.nanafa.com/admin/wechat/mp/{appId}
|
||
```
|
||
|
||
- 同一公众号的 AppId、Secret、Token、EncodingAESKey 配置迁移到 OMS(Consul / appsettings)
|
||
- 微信服务器回调**只由 OMS 处理**,无需 `system=wms/oms` 分流
|
||
- WMS 侧微信扫码登录、模板消息等能力需单独下线或改用其他公众号(不在本方案范围)
|
||
|
||
---
|
||
|
||
## 2. 总体架构
|
||
|
||
### 2.1 登录时序(PC 扫码)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant PC as 客户浏览器
|
||
participant FE as OMS 前端
|
||
participant API as OMS API (C#)
|
||
participant Redis as Redis
|
||
participant WX as 微信公众号
|
||
participant DB as MySQL
|
||
|
||
PC->>FE: 客户登录页 → 微信扫码
|
||
FE->>FE: 生成 loginUuid (UUID v4)
|
||
FE->>API: GET /admin/wechat/mp/login-qrcode?loginUuid=xxx
|
||
API->>WX: 创建临时二维码 ticket (scene, 600s)
|
||
API-->>FE: { qrCodeUrl }
|
||
FE->>FE: 展示二维码,每 3s 轮询
|
||
|
||
Note over WX: 用户微信扫码
|
||
WX->>API: POST /admin/wechat/mp/{appId} (SCAN / SUBSCRIBE)
|
||
API->>DB: 按 openId 查 t_customer.WechatOpenid
|
||
alt 未绑定
|
||
API-->>WX: 回复文本「请注册或绑定账号」
|
||
else 已绑定
|
||
API->>Redis: SET oms:wechat:login:{loginUuid} = openId, TTL 600s
|
||
end
|
||
|
||
FE->>API: POST /admin/rbac/auth/wx-login { loginUuid }
|
||
API->>Redis: GET openId
|
||
API->>DB: 查客户 + 校验状态
|
||
API->>API: 签发 JWT (OmsLoginType.Customer)
|
||
API-->>FE: LoginResultDto
|
||
FE->>PC: 存 Token,跳转 /home/workplace
|
||
```
|
||
|
||
### 2.2 模块划分
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ OMS 前端 (Vue3 + Arco Design) │
|
||
│ login/wechat-login.vue │ wechat/register │ bind │
|
||
└──────────────────────────┬──────────────────────────────┘
|
||
│ HTTPS
|
||
┌──────────────────────────▼──────────────────────────────┐
|
||
│ SimpleAdmin.Api │
|
||
│ ┌─────────────┐ ┌──────────────────┐ ┌────────────┐ │
|
||
│ │ AuthService │ │ WeChatMpController│ │WeChatOAuth │ │
|
||
│ │ + wx-login │ │ 回调/二维码 │ │ 注册/绑定 │ │
|
||
│ └──────┬──────┘ └────────┬─────────┘ └─────┬──────┘ │
|
||
│ │ │ │ │
|
||
│ ┌──────▼───────────────────▼───────────────────▼──────┐ │
|
||
│ │ WeChatMpService / ScanHandler / SubscribeHandler │ │
|
||
│ └──────┬───────────────────────────────┬──────────────┘ │
|
||
│ │ │ │
|
||
│ ┌──────▼──────┐ ┌──────▼──────┐ │
|
||
│ │ Redis │ │ MySQL │ │
|
||
│ │ 扫码会话桥接 │ │ t_customer │ │
|
||
│ └─────────────┘ └─────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
▲
|
||
│ 微信服务器推送
|
||
┌──────┴──────┐
|
||
│ 微信公众号 │
|
||
└─────────────┘
|
||
```
|
||
|
||
### 2.3 技术选型
|
||
|
||
| 组件 | 选型 |
|
||
|------|------|
|
||
| 微信 SDK | `Senparc.Weixin.MP` 或 `SKIT.FlurlHttpClient.Wechat.Api` |
|
||
| 扫码会话 | `IDistributedCache` + Redis(`StackExchange.Redis`) |
|
||
| 认证 | 复用现有 JWT(`AuthService` / `SimpleJwtService`) |
|
||
| 配置 | Consul `idwms/admin` 增加 `WeChat:Mp` 节点 |
|
||
|
||
---
|
||
|
||
## 3. 数据库设计
|
||
|
||
### 3.1 主表:`t_customer`(扩展现有表)
|
||
|
||
OMS 不单独建 `sys_user_wechat`,微信绑定信息直接落在客户表(与领域模型 `Customer` 一致)。
|
||
|
||
#### 现有字段(已存在)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `WechatOpenid` | `varchar(64)` NULL | 公众号 openId,登录主键 |
|
||
| `WechatNickname` | `varchar(64)` NULL | 微信昵称 |
|
||
| `WechatAvatar` | `varchar(255)` NULL | 微信头像 URL |
|
||
|
||
#### 建议新增字段(可选,便于扩展)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `WechatUnionid` | `varchar(64)` NULL | 开放平台 unionId(若后续接开放平台) |
|
||
| `WechatBindTime` | `datetime` NULL | 微信绑定时间 |
|
||
|
||
#### 建议索引
|
||
|
||
```sql
|
||
-- openId 唯一(允许多条 NULL)
|
||
CREATE UNIQUE INDEX uk_customer_wechat_openid
|
||
ON t_customer (WechatOpenid);
|
||
|
||
-- 辅助查询(若尚未存在)
|
||
CREATE INDEX idx_customer_phone ON t_customer (Phone);
|
||
CREATE INDEX idx_customer_email ON t_customer (Email);
|
||
```
|
||
|
||
#### 业务约束
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| 一微信绑一客户 | `WechatOpenid` 全局唯一(非 NULL 时) |
|
||
| 一客户绑一微信 | 更新绑定时校验该客户 `WechatOpenid` 为空 |
|
||
| 仅主客户可微信登录 | `ParentCustomerId IS NULL`;子客户拒绝扫码登录 |
|
||
| 子客户 | 不走微信扫码,继续账号密码登录 |
|
||
|
||
### 3.2 Redis 键设计(非持久化表)
|
||
|
||
| Key | Value | TTL | 说明 |
|
||
|-----|-------|-----|------|
|
||
| `oms:wechat:login:{loginUuid}` | `openId` | 600s | PC 扫码登录桥接 |
|
||
| `oms:wechat:oauth:state:{state}` | 随机 nonce | 300s | OAuth state 防 CSRF(可选) |
|
||
|
||
### 3.3 与 WMS `sys_user_wechat` 的映射关系
|
||
|
||
| WMS | OMS | 迁移说明 |
|
||
|-----|-----|----------|
|
||
| `officialaccounts_openid` | `t_customer.WechatOpenid` | 按手机号/邮箱/客户编码对齐后一次性导入 |
|
||
| `nickname` | `WechatNickname` | 同步 |
|
||
| `headimgurl` | `WechatAvatar` | 同步 |
|
||
| `vip_id` → `sys_user` | `Customer.UserId` → RBAC `user` | 需业务侧建立客户映射,非本方案自动完成 |
|
||
|
||
---
|
||
|
||
## 4. 后端方案
|
||
|
||
### 4.1 目录结构(建议)
|
||
|
||
```
|
||
src/SimpleAdmin.Api/
|
||
├── WeChat/
|
||
│ ├── Options/
|
||
│ │ └── WeChatMpOptions.cs
|
||
│ ├── Controllers/
|
||
│ │ ├── WeChatMpController.cs # 公众号回调 + 二维码
|
||
│ │ └── WeChatCustomerController.cs # 注册 / 绑定
|
||
│ ├── Handlers/
|
||
│ │ ├── IWeChatMessageHandler.cs
|
||
│ │ ├── WeChatScanHandler.cs
|
||
│ │ └── WeChatSubscribeHandler.cs
|
||
│ ├── Services/
|
||
│ │ ├── IWeChatMpService.cs
|
||
│ │ ├── WeChatMpService.cs
|
||
│ │ ├── IWeChatLoginSessionStore.cs
|
||
│ │ └── RedisWeChatLoginSessionStore.cs
|
||
│ └── Dtos/
|
||
│ ├── WxLoginInput.cs
|
||
│ ├── WxLoginQrcodeOutput.cs
|
||
│ ├── WeChatOAuthUserInfoOutput.cs
|
||
│ ├── WeChatCustomerRegisterInput.cs
|
||
│ └── WeChatCustomerBindInput.cs
|
||
└── Auth/
|
||
└── AuthService.cs # 新增 WxLoginAsync
|
||
```
|
||
|
||
### 4.2 配置项
|
||
|
||
Consul / `appsettings` 示例:
|
||
|
||
```json
|
||
{
|
||
"WeChat": {
|
||
"Mp": {
|
||
"AppId": "wxd5141ba46c856e1f",
|
||
"Secret": "********",
|
||
"Token": "********",
|
||
"EncodingAesKey": "********",
|
||
"OAuthRedirectBase": "https://oms.nanafa.com"
|
||
}
|
||
},
|
||
"Redis": {
|
||
"ConnectionString": "localhost:6379,defaultDatabase=0"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 接口清单
|
||
|
||
所有接口前缀与现网 OMS API 一致:`https://api.nanafa.com/admin`(`ASPNETCORE_PATHBASE=/admin`)。
|
||
|
||
#### 4.3.1 公众号服务器回调
|
||
|
||
| 方法 | 路径 | 鉴权 | 说明 |
|
||
|------|------|------|------|
|
||
| GET | `/wechat/mp/{appId}` | 匿名 | 微信接入验签,返回 `echostr` |
|
||
| POST | `/wechat/mp/{appId}` | 匿名 | 接收消息/事件(SCAN、SUBSCRIBE 等) |
|
||
|
||
**GET 参数:** `signature`, `timestamp`, `nonce`, `echostr`
|
||
|
||
**POST 参数:** 同微信文档;Body 为 XML(明文或 AES 加密)
|
||
|
||
**路由分发:**
|
||
|
||
| 事件 | Handler | 行为 |
|
||
|------|---------|------|
|
||
| `SCAN` | `WeChatScanHandler` | 已关注用户扫码 |
|
||
| `subscribe` | `WeChatSubscribeHandler` | 未关注用户扫码关注(需处理 `qrscene_` 前缀) |
|
||
| 其他 | 默认空响应或客服消息 | 按需扩展 |
|
||
|
||
---
|
||
|
||
#### 4.3.2 获取登录二维码
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| GET | `/wechat/mp/login-qrcode` | 匿名 |
|
||
|
||
**Query:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `loginUuid` | string | 是 | 前端生成的 UUID,标识一次登录会话 |
|
||
|
||
**Response `200`:**
|
||
|
||
```json
|
||
{
|
||
"qrCodeUrl": "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=xxx",
|
||
"expiresIn": 600
|
||
}
|
||
```
|
||
|
||
**后端逻辑:**
|
||
|
||
1. 校验 `loginUuid` 非空且格式合法(UUID)
|
||
2. 构造 scene JSON:`{"type":"login","loginUuid":"{loginUuid}"}`
|
||
3. 调用微信 `qrCodeCreateTmpTicket(scene, 600)`
|
||
4. 返回二维码图片 URL
|
||
|
||
---
|
||
|
||
#### 4.3.3 微信扫码登录(轮询)
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| POST | `/rbac/auth/wx-login` | 匿名 |
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"loginUuid": "550e8400-e29b-41d4-a716-446655440000"
|
||
}
|
||
```
|
||
|
||
**Response `200`(`LoginResultDto`,与密码登录一致):**
|
||
|
||
```json
|
||
{
|
||
"accessToken": "eyJ...",
|
||
"refreshToken": "eyJ...",
|
||
"tokenType": "Bearer",
|
||
"expiresIn": 3600,
|
||
"userId": "guid"
|
||
}
|
||
```
|
||
|
||
**错误码(业务异常 message):**
|
||
|
||
| 场景 | message |
|
||
|------|---------|
|
||
| Redis 无值 | `请扫码登录` |
|
||
| openId 未绑定客户 | `未绑定微信,请先注册或绑定账号` |
|
||
| 客户已禁用/冻结 | `客户已禁用` / `客户已冻结` |
|
||
| 子客户 | `子客户不支持微信扫码登录` |
|
||
| 未关联 RBAC 用户 | `客户未关联用户` |
|
||
|
||
**核心逻辑(`AuthService.WxLoginAsync`):**
|
||
|
||
```
|
||
1. openId = Redis.Get("oms:wechat:login:{loginUuid}")
|
||
2. customer = CustomerRepository.GetByWechatOpenidAsync(openId)
|
||
3. 校验:customer != null && ParentCustomerId == null
|
||
4. 校验:Status 非 Disabled / Frozen
|
||
5. 更新 LastLoginIp、LastLoginTime
|
||
6. userGuid = GetRbacUserGuidByMapIdAsync(customer.UserId)
|
||
7. claims = BuildClaimsAsync(userGuid, OmsLoginType.Customer)
|
||
8. return BuildLoginResult(userGuid, claims)
|
||
9. (可选)Redis.Delete(loginUuid) 防止重复使用
|
||
```
|
||
|
||
与 `LoginCustomerAsync` 的差异:**跳过密码校验**,JWT 签发流程相同。
|
||
|
||
---
|
||
|
||
#### 4.3.4 OAuth 获取微信用户信息
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| POST | `/wechat/oauth/user-info` | 匿名 |
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"code": "微信 OAuth 回调 code"
|
||
}
|
||
```
|
||
|
||
**Response `200`:**
|
||
|
||
```json
|
||
{
|
||
"openid": "oXXXX",
|
||
"nickname": "张三",
|
||
"sex": 1,
|
||
"province": "广东",
|
||
"city": "深圳",
|
||
"country": "中国",
|
||
"headimgurl": "https://...",
|
||
"unionid": "uXXXX"
|
||
}
|
||
```
|
||
|
||
**逻辑:**
|
||
|
||
1. `code` → `access_token` + `openid`(`sns/oauth2/access_token`)
|
||
2. `access_token` + `openid` → 用户信息(`sns/userinfo`)
|
||
3. 若该 `openid` 已绑定客户,返回业务提示码供前端展示「已绑定」
|
||
|
||
---
|
||
|
||
#### 4.3.5 微信授权注册客户
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| POST | `/wechat/customer/register` | 匿名 |
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"customerCode": "C20260001",
|
||
"customerName": "张三",
|
||
"phone": "13800138000",
|
||
"email": "a@example.com",
|
||
"password": "Abc@12345",
|
||
"wechat": {
|
||
"openid": "oXXXX",
|
||
"nickname": "张三",
|
||
"avatar": "https://...",
|
||
"unionid": "uXXXX"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Response `200`:** 空体或 `{ "customerId": 1 }`
|
||
|
||
**校验:**
|
||
|
||
| 项 | 规则 |
|
||
|----|------|
|
||
| openid | 必填;`GetByWechatOpenidAsync` 不得已存在 |
|
||
| customerCode / phone / email | 复用 `CustomerService` 唯一性校验 |
|
||
| password | 强度校验(与现网一致) |
|
||
| 角色 | 自动分配 `Customer` 角色 |
|
||
|
||
**逻辑:**
|
||
|
||
1. 创建 RBAC User(Account 默认用手机号)
|
||
2. 创建 `t_customer`,写入 `WechatOpenid`、`WechatNickname`、`WechatAvatar`、`WechatBindTime`
|
||
3. 事务:User + Customer 同成功同回滚
|
||
|
||
---
|
||
|
||
#### 4.3.6 微信授权绑定已有客户
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| POST | `/wechat/customer/bind` | 匿名 |
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"account": "13800138000",
|
||
"password": "******",
|
||
"wechat": {
|
||
"openid": "oXXXX",
|
||
"nickname": "张三",
|
||
"avatar": "https://...",
|
||
"unionid": "uXXXX"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Response `200`:** 空体
|
||
|
||
**校验:**
|
||
|
||
| 项 | 规则 |
|
||
|----|------|
|
||
| 账号密码 | 等同客户密码登录校验 |
|
||
| openid | 不得已被其他客户绑定 |
|
||
| 目标客户 | 主客户;`WechatOpenid` 必须为空 |
|
||
| 状态 | 客户非禁用/冻结 |
|
||
|
||
**逻辑:**
|
||
|
||
1. `FindCustomerByAccountAsync(account)` + 密码 MD5 校验
|
||
2. 更新 `WechatOpenid`、`WechatNickname`、`WechatAvatar`、`WechatBindTime`
|
||
3. 可选同步 RBAC User 头像
|
||
|
||
---
|
||
|
||
#### 4.3.7 微信公众号内免密登录(可选,二期)
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| POST | `/rbac/auth/wx-public-login` | 匿名 |
|
||
|
||
**Request:** `{ "openid": "oXXXX" }`
|
||
|
||
**说明:** 用于微信内 H5 页(如充值审核),Token 短时效(24h),非持久 Cookie。一期可不做。
|
||
|
||
---
|
||
|
||
### 4.4 事件处理逻辑
|
||
|
||
#### 4.4.1 SCAN(已关注用户扫码)
|
||
|
||
```
|
||
输入:FromUserName = openId, EventKey = scene JSON 字符串
|
||
1. customer = GetByWechatOpenidAsync(openId)
|
||
2. if customer == null → 回复「未绑定账号,请通过菜单注册或绑定」
|
||
3. 解析 EventKey JSON:type == "login"
|
||
4. loginUuid = json.loginUuid
|
||
5. Redis.Set("oms:wechat:login:{loginUuid}", openId, 600s)
|
||
6. 可选回复「登录成功,请返回电脑端」
|
||
```
|
||
|
||
#### 4.4.2 SUBSCRIBE(未关注用户扫码后关注)
|
||
|
||
```
|
||
输入:EventKey = "qrscene_{sceneJson}"
|
||
1. content = EventKey.Replace("qrscene_", "")
|
||
2. 其余逻辑同 SCAN
|
||
```
|
||
|
||
> WMS 已知缺陷:未处理 `qrscene_` 前缀导致未关注用户扫码失败。OMS 必须实现。
|
||
|
||
#### 4.4.3 二维码 scene 格式
|
||
|
||
```json
|
||
{
|
||
"type": "login",
|
||
"loginUuid": "550e8400-e29b-41d4-a716-446655440000"
|
||
}
|
||
```
|
||
|
||
策略 B 下**不需要** `system` 字段。
|
||
|
||
---
|
||
|
||
### 4.5 鉴权白名单
|
||
|
||
以下路径 `[AllowAnonymous]`,且 JWT 中间件不拦截:
|
||
|
||
```
|
||
GET /wechat/mp/{appId}
|
||
POST /wechat/mp/{appId}
|
||
GET /wechat/mp/login-qrcode
|
||
POST /rbac/auth/wx-login
|
||
POST /wechat/oauth/user-info
|
||
POST /wechat/customer/register
|
||
POST /wechat/customer/bind
|
||
```
|
||
|
||
---
|
||
|
||
### 4.6 领域层改动清单
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `Customer.cs` | 可选增加 `WechatUnionid`、`WechatBindTime` |
|
||
| `ICustomerRepository` | 已有 `GetByWechatOpenidAsync`,无需改 |
|
||
| `CustomerRepository.cs` | 已有实现 |
|
||
| `CustomerService.cs` | 抽取 `BuildCustomerEntity` 支持写入 `WechatOpenid`;或注册逻辑放 `WeChatCustomerController` 专用 Service |
|
||
| `AuthService.cs` | 新增 `WxLoginAsync` |
|
||
| `CreateCustomerInput` | 微信注册走独立 DTO,不污染管理端创建接口 |
|
||
|
||
---
|
||
|
||
## 5. 前端方案
|
||
|
||
### 5.1 页面与路由
|
||
|
||
| 路由 | 组件 | 门户 | 说明 |
|
||
|------|------|------|------|
|
||
| `/` | `views/login/index.vue` | 客户 | 已有;微信 Tab 对接真实接口 |
|
||
| `/admin` | 同上 | 管理端 | 不展示微信入口(已实现 `isCustomerLogin`) |
|
||
| `/wechat/register` | `views/wechat/register/index.vue` | 公开 H5 | 公众号菜单「注册」 |
|
||
| `/wechat/bind` | `views/wechat/bind/index.vue` | 公开 H5 | 公众号菜单「绑定账号」 |
|
||
|
||
**路由注册(`src/router/index.ts`):**
|
||
|
||
```typescript
|
||
{
|
||
path: '/wechat/register',
|
||
name: 'WechatRegister',
|
||
component: () => import('@/views/wechat/register/index.vue'),
|
||
meta: { requiresAuth: false },
|
||
},
|
||
{
|
||
path: '/wechat/bind',
|
||
name: 'WechatBind',
|
||
component: () => import('@/views/wechat/bind/index.vue'),
|
||
meta: { requiresAuth: false },
|
||
},
|
||
```
|
||
|
||
### 5.2 API 封装
|
||
|
||
新建 `src/api/wechat/index.ts`:
|
||
|
||
| 方法 | 请求 | 说明 |
|
||
|------|------|------|
|
||
| `getLoginQrcode(loginUuid)` | `GET /wechat/mp/login-qrcode?loginUuid=` | 获取二维码 |
|
||
| `wxLogin(loginUuid)` | `POST /rbac/auth/wx-login` | 轮询登录 |
|
||
| `getOAuthUserInfo(code)` | `POST /wechat/oauth/user-info` | H5 授权 |
|
||
| `registerByWechat(data)` | `POST /wechat/customer/register` | 注册 |
|
||
| `bindByWechat(data)` | `POST /wechat/customer/bind` | 绑定 |
|
||
|
||
请求走现有 RBAC axios 实例(`OpenAPI.BASE` = admin API),微信登录相关接口需 `skipAuth: true`(不携带 Token)。
|
||
|
||
### 5.3 `wechat-login.vue` 改造要点
|
||
|
||
参考 WMS `wms0414/src/views/Login/components/wechat-login.vue`,适配 OMS:
|
||
|
||
```
|
||
1. onMounted → loadQrCode()
|
||
2. loginUuid = uuidv4()
|
||
3. GET login-qrcode → 展示 qrCodeUrl
|
||
4. setInterval 每 3s → POST wx-login
|
||
5. 成功 → rbacUserStore 存 Token + setLoginPortalType(Customer) + getCurrentUser() + 跳转首页
|
||
6. setTimeout 600000 → 停止轮询(二维码过期)
|
||
7. 刷新按钮 → 重新 loadQrCode()
|
||
```
|
||
|
||
**`rbacUserStore` 扩展:**
|
||
|
||
```typescript
|
||
async function loginByWxResult(result: LoginResultDto) {
|
||
saveTokens(result);
|
||
setLoginPortalType(OmsLoginType.Customer);
|
||
await getCurrentUser();
|
||
}
|
||
```
|
||
|
||
### 5.4 H5 注册页流程
|
||
|
||
```
|
||
公众号菜单 → 微信 OAuth 授权
|
||
→ 回调 https://oms.nanafa.com/wechat/register?code=xxx
|
||
→ POST /wechat/oauth/user-info { code }
|
||
→ 若 openid 已绑定 → 提示已注册
|
||
→ 否则展示表单(客户编码、名称、手机、邮箱、密码)
|
||
→ POST /wechat/customer/register
|
||
→ 成功提示「可返回 PC 扫码登录」
|
||
```
|
||
|
||
### 5.5 H5 绑定页流程
|
||
|
||
```
|
||
公众号菜单 → OAuth → /wechat/bind?code=xxx
|
||
→ getOAuthUserInfo
|
||
→ 表单:账号 + 密码
|
||
→ POST /wechat/customer/bind
|
||
→ 成功提示
|
||
```
|
||
|
||
### 5.6 国际化
|
||
|
||
在 `src/views/login/locale/zh-CN.ts` / `en-US.ts` 补充:
|
||
|
||
| Key | 中文 |
|
||
|-----|------|
|
||
| `login.wechat.qrValid` | 二维码十分钟有效 |
|
||
| `login.wechat.scanPending` | 请使用微信扫一扫 |
|
||
| `login.wechat.notBound` | 未绑定微信,请先在公众号注册或绑定 |
|
||
|
||
### 5.7 前端文件清单
|
||
|
||
| 操作 | 路径 |
|
||
|------|------|
|
||
| 改造 | `src/views/login/components/wechat-login.vue` |
|
||
| 新增 | `src/api/wechat/index.ts` |
|
||
| 新增 | `src/views/wechat/register/index.vue` |
|
||
| 新增 | `src/views/wechat/bind/index.vue` |
|
||
| 修改 | `src/store/modules/rbac-user.ts`(`loginByWxResult`) |
|
||
| 修改 | `src/router/index.ts`(H5 路由) |
|
||
| 修改 | `src/views/login/locale/*.ts` |
|
||
|
||
---
|
||
|
||
## 6. 微信公众号配置
|
||
|
||
### 6.1 服务器配置(迁移到 OMS)
|
||
|
||
| 配置项 | 值 |
|
||
|--------|-----|
|
||
| URL | `https://api.nanafa.com/admin/wechat/mp/{AppId}` |
|
||
| Token | 与 `WeChat:Mp:Token` 一致 |
|
||
| EncodingAESKey | 与配置一致 |
|
||
| 消息加解密方式 | 安全模式(推荐)或明文 |
|
||
|
||
### 6.2 网页授权域名
|
||
|
||
```
|
||
oms.nanafa.com
|
||
```
|
||
|
||
### 6.3 自定义菜单(示例)
|
||
|
||
| 菜单 | 类型 | 链接 |
|
||
|------|------|------|
|
||
| 注册账号 | 网页授权 | `https://open.weixin.qq.com/connect/oauth2/authorize?appid={AppId}&redirect_uri={encode(oms.nanafa.com/wechat/register)}&response_type=code&scope=snsapi_userinfo&state=register#wechat_redirect` |
|
||
| 绑定账号 | 网页授权 | 同上,`redirect_uri` 指向 `/wechat/bind` |
|
||
|
||
### 6.4 迁移检查清单
|
||
|
||
- [ ] 微信公众平台服务器 URL 从 WMS 改为 OMS
|
||
- [ ] 点击「启用」后 OMS 验签接口返回正确 `echostr`
|
||
- [ ] WMS 侧公众号相关定时任务/模板消息评估停用或迁移
|
||
- [ ] 确认 AppId / Secret 写入 OMS Consul
|
||
|
||
---
|
||
|
||
## 7. 部署与 Nginx
|
||
|
||
### 7.1 API 路径
|
||
|
||
现网 OMS API:`https://api.nanafa.com/admin/*`
|
||
|
||
微信回调完整 URL:
|
||
|
||
```
|
||
https://api.nanafa.com/admin/wechat/mp/wxd5141ba46c856e1f
|
||
```
|
||
|
||
无需额外 nginx 规则(已代理到 admin 服务),需确认:
|
||
|
||
- `proxy_read_timeout` ≥ 30s(微信回调一般足够)
|
||
- 支持 POST `application/xml` 或 `text/xml`
|
||
|
||
### 7.2 前端 H5
|
||
|
||
`oms.nanafa.com` 静态站点已存在;新增 `/wechat/*` 走路由回退 `index.html`(与 SPA 一致)。
|
||
|
||
---
|
||
|
||
## 8. 实施阶段
|
||
|
||
### 阶段 1:后端基础(2 天)
|
||
|
||
- [ ] 引入微信 SDK + Redis
|
||
- [ ] `WeChatMpOptions` + Consul 配置
|
||
- [ ] `WeChatMpController` 验签 + 消息入口
|
||
- [ ] `WeChatScanHandler` / `WeChatSubscribeHandler`
|
||
- [ ] `RedisWeChatLoginSessionStore`
|
||
- [ ] 数据库索引脚本
|
||
|
||
### 阶段 2:扫码登录闭环(2 天)
|
||
|
||
- [ ] `GET /wechat/mp/login-qrcode`
|
||
- [ ] `POST /rbac/auth/wx-login`
|
||
- [ ] 前端 `wechat-login.vue` 对接
|
||
- [ ] `rbacUserStore.loginByWxResult`
|
||
|
||
### 阶段 3:注册 / 绑定(2 天)
|
||
|
||
- [ ] `POST /wechat/oauth/user-info`
|
||
- [ ] `POST /wechat/customer/register`
|
||
- [ ] `POST /wechat/customer/bind`
|
||
- [ ] H5 页面 + 路由
|
||
- [ ] 公众号菜单配置
|
||
|
||
### 阶段 4:切流与验收(1 天)
|
||
|
||
- [ ] 公众号服务器 URL 切换到 OMS
|
||
- [ ] 全链路测试(见第 9 节)
|
||
- [ ] WMS 微信登录入口下线或隐藏
|
||
- [ ] (可选)WMS openId 数据迁移脚本
|
||
|
||
---
|
||
|
||
## 9. 测试用例
|
||
|
||
| # | 场景 | 预期 |
|
||
|---|------|------|
|
||
| 1 | 已绑定客户 PC 扫码 | 3s 内轮询成功,跳转首页,JWT 有效 |
|
||
| 2 | 未绑定客户扫码 | 公众号收到提示;PC 轮询返回「请扫码登录」或超时 |
|
||
| 3 | 二维码 10 分钟过期 | 前端停止轮询;刷新可重新获取 |
|
||
| 4 | 未关注公众号用户扫码 | SUBSCRIBE 事件正确处理 `qrscene_` |
|
||
| 5 | 微信注册新客 | 客户表写入 openId,随后可扫码登录 |
|
||
| 6 | 微信绑定已有账号 | 绑定后扫码可登录 |
|
||
| 7 | 重复绑定同一 openId | 拒绝 |
|
||
| 8 | 子客户 openId 绑定主客户流程外 | 子客户扫码登录被拒绝 |
|
||
| 9 | 禁用/冻结客户扫码 | 登录失败,明确提示 |
|
||
| 10 | 管理端 `/admin` | 不显示微信入口 |
|
||
| 11 | 公众号验签 GET | 返回 echostr |
|
||
| 12 | Token 刷新 | 微信登录后 refresh-token 正常 |
|
||
|
||
---
|
||
|
||
## 10. 风险与回滚
|
||
|
||
| 风险 | 缓解 |
|
||
|------|------|
|
||
| 公众号 URL 切换后 WMS 微信能力立即失效 | 选低峰期切换;提前通知;准备回滚 URL |
|
||
| Redis 不可用 | 扫码登录不可用;密码登录不受影响 |
|
||
| 老客户无 openId | 提供绑定 H5 + 可选批量迁移脚本 |
|
||
| 多实例 API | 必须使用 Redis,禁止 `IMemoryCache` 做单机桥接 |
|
||
|
||
**回滚:** 微信公众平台服务器 URL 改回 WMS 地址,OMS 前端隐藏微信入口。
|
||
|
||
---
|
||
|
||
## 11. 附录:与 WMS 接口对照
|
||
|
||
| 能力 | WMS (Java) | OMS (C#) |
|
||
|------|------------|----------|
|
||
| 二维码 | `GET /apiwechat/loginqrcode` | `GET /wechat/mp/login-qrcode` |
|
||
| 轮询登录 | `POST /wxlogin` | `POST /rbac/auth/wx-login` |
|
||
| 公众号回调 | `GET/POST /apiwechat/{appId}` | `GET/POST /wechat/mp/{appId}` |
|
||
| OAuth 用户信息 | `POST /wechat/officialAccounts/getWxUserinfo` | `POST /wechat/oauth/user-info` |
|
||
| 注册 | `POST /wechat/wxUserAdd` | `POST /wechat/customer/register` |
|
||
| 绑定 | `POST /wechat/wxUserBinding` | `POST /wechat/customer/bind` |
|
||
| 绑定存储 | `sys_user_wechat` | `t_customer.WechatOpenid` |
|
||
| 会话桥接 | Redis `loginuuid→openId` | Redis `oms:wechat:login:{loginUuid}` |
|
||
| 认证 | Sa-Token | JWT `LoginResultDto` |
|
||
|
||
---
|
||
|
||
## 12. 总结
|
||
|
||
本方案采用**策略 B**:同一公众号的服务器配置**独占 OMS**,不做 WMS/OMS 双系统回调分流。OMS 复用 WMS 已验证的「二维码 + 事件 + Redis + 轮询」模式,结合现有 `t_customer.WechatOpenid` 与 JWT 认证体系,实现客户门户微信扫码登录及 H5 注册绑定。实施顺序建议:**Redis + 回调 → 扫码登录 → 注册绑定 → 公众号切流**。
|