# 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 + 回调 → 扫码登录 → 注册绑定 → 公众号切流**。