Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-19-client-auth-system/proposal.md
huang b9733c4913
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
fix: 修正零售价架构错误 + 清理旧微信配置 + 归档提案 + 前端接口文档
1. 修正 retail_price 架构:
   - 删除 batch-pricing 接口的 pricing_target 字段和 retail_price 分支
     (上级只能改下级成本价,不能改零售价)
   - 新增 PATCH /api/admin/packages/:id/retail-price 接口
     (代理自己改自己的零售价,校验 retail_price >= cost_price)

2. 清理旧微信 YAML 配置(已全部迁移到数据库 tb_wechat_config):
   - 删除 config.yaml 中 wechat.official_account 配置节
   - 删除 NewOfficialAccountApp() 旧工厂函数
   - 清理 personal_customer service 中的死代码(旧登录/绑定微信方法)
   - 清理 docker-compose.prod.yml 中旧微信环境变量和证书挂载注释

3. 归档四个已完成提案到 openspec/changes/archive/

4. 新增前端接口变更说明文档(docs/前端接口变更说明.md)

5. 修正归档提案和 specs 中关于 pricing_target 的错误描述
2026-03-19 17:39:43 +08:00

8.2 KiB
Raw Blame History

Why

系统需要一套面向个人客户C 端)的完整认证体系,替代已删除的旧 H5 登录接口。客户端(微信公众号 H5 / 微信小程序)的登录流程与 B 端完全不同:基于资产标识符而非用户账号密码,先验证资产 → 再微信授权 → 自动绑定资产 → 可选绑定手机号。同时,公众号和小程序可能使用不同 AppID 且不一定绑定同一微信开放平台,需要支持多 OpenID 管理。

前置依赖:提案 0client-api-data-model-fixes)已完成 PersonalCustomer.wx_open_id 索引变更和旧接口删除。

What Changes

新增模型

  • PersonalCustomerOpenID:个人客户 OpenID 关联表,支持同一客户在不同 AppID公众号/小程序)下的多 OpenID 记录。唯一索引 UNIQUE(app_id, open_id) WHERE deleted_at IS NULL

认证接口(/api/c/v1/auth/

  • A1 验证资产标识符 POST /verify-asset:无需认证。输入 SN/IMEI/虚拟号/ICCID/MSISDN → 返回 asset_token(短时效 JWT5 分钟过期payload 含 asset_type + asset_id。IP 级别限频30 次/分钟)防暴力枚举。不暴露内部 asset_id
  • A2 微信公众号登录 POST /wechat-login:无需认证。用微信 OAuth code + asset_token → 查找/创建客户 → 绑定资产 → 签发有状态 JWT TokenRedis 存储)→ 返回 token + 是否需要绑定手机号
  • A3 微信小程序登录 POST /miniapp-login:无需认证。用小程序 jscode2session + asset_token → 同 A2 后续流程
  • A4 发送验证码 POST /send-code:无需认证。限频:同手机号 60s、同 IP 20 次/小时、每手机号 10 次/天
  • A5 绑定手机号 POST /bind-phone:需 JWT。首次绑定检查重复
  • A6 换绑手机号 POST /change-phone:需 JWT。双重验证码旧+新手机)
  • A7 退出登录 POST /logout:需 JWT。删除 Redis token 记录

基础设施

  • 有状态 JWT Token 管理JWT payload 仅含 customer_id + expRedis 存储 token 有效状态,支持服务端主动失效(封禁/强制下线)
  • PersonalAuthMiddleware 增强:增加 Redis 有效性检查token 不在 Redis 中则拒绝
  • 统一资产解析公共方法 resolveAssetFromIdentifier():个人客户调用不走 shop_id 数据权限过滤
  • OpenID 安全规范:所有需要 OpenID 的接口支付、充值OpenID 由后端根据 customer_id + app_type 查 PersonalCustomerOpenID 表获取,禁止客户端传入
  • 手机号绑定配置:通过 Viper 配置 client.require_phone_bindingboolean登录时检查并返回 need_bind_phone 标识

登录完整流程

用户打开客户端
    │
    ▼
输入资产标识符SN/IMEI/虚拟号/ICCID
    │
    ▼
[A1] POST /verify-asset ──→ 返回 asset_token5分钟有效
    │
    ▼
微信授权(前端完成)
    │
    ├─── 公众号 ──→ [A2] POST /wechat-login (code + asset_token)
    │
    └─── 小程序 ──→ [A3] POST /miniapp-login (code + asset_token)
                          │
                          ▼
                   ┌──────────────────┐
                   │ 解析 asset_token  │
                   │ 获取微信 openid   │
                   │ 查找/创建客户     │
                   │ 绑定资产          │
                   │ 签发 JWT + Redis  │
                   └──────┬───────────┘
                          │
                          ▼
                   返回 { token, need_bind_phone, is_new_user }
                          │
                          ▼
               need_bind_phone == true?
                   │           │
                  YES          NO
                   │           │
                   ▼           ▼
          [A4] 发送验证码    进入主页面
          [A5] 绑定手机号
                   │
                   ▼
              进入主页面

客户查找/创建逻辑A2/A3 共享)

收到 openid + (可选)unionid
    │
    ▼
查 PersonalCustomerOpenID WHERE app_id=当前AppID AND open_id=openid
    │
    ├── 找到 → 获取 customer_id → 已有客户
    │
    └── 没找到
         │
         ▼
    有 unionid
         │
         ├── YES → 查 PersonalCustomerOpenID WHERE union_id=unionid
         │         │
         │         ├── 找到 → 获取 customer_id → 新增当前 AppID 的 openid 记录
         │         │
         │         └── 没找到 → 创建新客户 + openid 记录
         │
         └── NO → 创建新客户 + openid 记录

Capabilities

New Capabilities

  • client-asset-token资产验证令牌机制。A1 接口、asset_token JWT 生成/验证、IP 限频、安全规范(不暴露 asset_id
  • client-wechat-login:微信登录(公众号+小程序。A2/A3 接口、OAuth/jscode2session 对接、客户查找/创建/合并逻辑、资产绑定(首次绑定时触发 asset_status 从 1→2、OpenID 多记录管理
  • client-phone-bindng:手机号绑定/换绑。A4/A5/A6 接口、验证码发送/校验、限频规则、绑定/换绑逻辑
  • client-token-management:有状态 JWT Token 管理。签发、Redis 存储、有效性检查、退出登录A7、服务端主动失效
  • personal-customer-openidPersonalCustomerOpenID 模型定义、唯一索引、与 PersonalCustomer 的关系

Modified Capabilities

  • personal-customerPersonalCustomer 模型行为变化——登录逻辑从手机号+验证码改为微信授权wx_open_id 字段保留但逻辑迁移到 PersonalCustomerOpenID 表
  • asset-lifecycle-status:首次客户绑定资产时,asset_status 从 1在库自动更新为 2已销售使用条件更新确保幂等
  • wechat-official-accountOAuth 配置来源变化——从 YAML 静态配置改为从 WechatConfig 表动态读取公众号/小程序 AppID+AppSecret

微信 SDK 使用说明

本提案使用项目中已有的微信 SDKpkg/wechat/,基于 PowerWeChat v3同时需要扩展小程序能力

场景 SDK 方法 文件 状态
A2 公众号登录 OfficialAccountService.GetUserInfoDetailed(code) pkg/wechat/official_account.go:69 已有,直接复用
A3 小程序登录 MiniAppService.Code2Session(code) pkg/wechat/miniapp.go 需新建,直接 HTTP 调用微信 jscode2session
SDK 实例创建 NewOfficialAccountAppFromConfig(wechatConfig) pkg/wechat/config.go 需新增,从 DB 动态创建
SDK 实例创建 NewMiniAppServiceFromConfig(wechatConfig) pkg/wechat/config.go 需新增
SDK 实例创建 NewPaymentAppFromConfig(wechatConfig, appID) pkg/wechat/config.go 需新增,供提案 2 支付使用

现有 NewOfficialAccountApp(cfg) 从 YAML 创建实例,客户端场景需要从 tb_wechat_config DB 动态加载。

Impact

  • 新增文件internal/model/personal_customer_openid.go(模型)、internal/handler/app/client_auth.go(认证 Handlerinternal/service/client_auth/service.go(认证 Serviceinternal/store/postgres/personal_customer_openid_store.goStorepkg/wechat/miniapp.go(小程序 SDK 封装)、DTO 文件、迁移文件、常量定义
  • 修改文件internal/middleware/personal_auth.go(增加 Redis 检查)、internal/routes/personal.go(新增路由)、internal/bootstrap/(注册新模块)、cmd/api/docs.go + cmd/gendocs/main.go(文档生成器)、pkg/config/defaults/config.yaml(新增 client 配置节)、internal/model/system.goAutoMigrate 注册新模型)、pkg/wechat/config.go(新增 3 个 DB 动态工厂函数)pkg/wechat/wechat.go(新增 MiniAppServiceInterface
  • 新增 API 路由/api/c/v1/auth/ 下 7 个端点
  • 数据库变更:新建 tb_personal_customer_openid
  • 新增依赖:无(微信 SDK 已有 PowerWeChat v3小程序 jscode2session 为纯 HTTP 调用)
  • 配置变更config.yaml 新增 client.require_phone_binding 配置项