# C 端认证系统功能总结 ## 概述 本次实现了面向个人客户(C 端)的完整认证体系,替代旧 H5 登录接口。支持微信公众号和小程序两种登录方式,基于「资产标识符验证 → 微信授权 → 自动绑定资产 → 可选绑定手机号」的流程。 ## 接口列表 | 接口 | 路径 | 认证 | 说明 | |------|------|------|------| | A1 | `POST /api/c/v1/auth/verify-asset` | 否 | 资产标识符验证,返回 asset_token | | A2 | `POST /api/c/v1/auth/wechat-login` | 否 | 微信公众号登录 | | A3 | `POST /api/c/v1/auth/miniapp-login` | 否 | 微信小程序登录 | | A4 | `POST /api/c/v1/auth/send-code` | 否 | 发送手机验证码 | | A5 | `POST /api/c/v1/auth/bind-phone` | 是 | 首次绑定手机号 | | A6 | `POST /api/c/v1/auth/change-phone` | 是 | 换绑手机号(双验证码) | | A7 | `POST /api/c/v1/auth/logout` | 是 | 退出登录 | ## 登录流程 ``` 用户输入资产标识符(SN/IMEI/ICCID) │ ▼ [A1] verify-asset → asset_token(5分钟有效) │ ▼ 微信授权(前端完成) │ ├── 公众号 → [A2] wechat-login (code + asset_token) └── 小程序 → [A3] miniapp-login (code + asset_token) │ ▼ 解析 asset_token → 获取微信 openid → 查找/创建客户 → 绑定资产 → 签发 JWT + Redis 存储 │ ▼ 返回 { token, need_bind_phone, is_new_user } │ ▼ need_bind_phone == true? YES → [A4] 发送验证码 → [A5] 绑定手机号 NO → 进入主页面 ``` ## 核心设计 ### 有状态 JWT(JWT + Redis) - JWT payload 仅含 `customer_id` + `exp` - 登录时将 token 写入 Redis,TTL 与 JWT 一致 - 每次请求在中间件同时校验 JWT 签名和 Redis 有效状态 - 支持服务端主动失效(封禁、强制下线、退出登录) - 单点登录:新登录覆盖旧 token ### OpenID 多记录管理 - 新增 `tb_personal_customer_openid` 表 - 同一客户可在多个 AppID(公众号/小程序)下拥有不同 OpenID - 唯一约束:`UNIQUE(app_id, open_id) WHERE deleted_at IS NULL` - 客户查找逻辑:openid 精确匹配 → unionid 回退合并 → 创建新客户 ### 资产绑定 - 每次登录创建 `PersonalCustomerDevice` 绑定记录 - 同一资产允许被多个客户绑定(支持转手场景) - 首次绑定时自动将资产状态从「在库(1)」更新为「已销售(2)」 ### 微信配置动态加载 - 登录时从数据库 `tb_wechat_config` 动态读取激活配置 - 优先走 WechatConfigService 的 Redis 缓存 - 小程序登录直接 HTTP 调用微信 `jscode2session`(不依赖 PowerWeChat SDK) ## 限流策略 | 接口 | 维度 | 限制 | |------|------|------| | A1 | IP | 30 次/分钟 | | A4 | 手机号 | 60 秒冷却 | | A4 | IP | 20 次/小时 | | A4 | 手机号 | 10 次/天 | ## 新增/修改文件 ### 新增文件 | 文件 | 说明 | |------|------| | `internal/model/personal_customer_openid.go` | OpenID 关联模型 | | `internal/model/dto/client_auth_dto.go` | A1-A7 请求/响应 DTO | | `internal/store/postgres/personal_customer_openid_store.go` | OpenID Store | | `internal/service/client_auth/service.go` | 认证 Service(核心业务逻辑) | | `internal/handler/app/client_auth.go` | 认证 Handler(7 个端点) | | `pkg/wechat/miniapp.go` | 小程序 SDK 封装 | | `migrations/000083_add_personal_customer_openid.up.sql` | 迁移文件 | | `migrations/000083_add_personal_customer_openid.down.sql` | 回滚文件 | ### 修改文件 | 文件 | 说明 | |------|------| | `internal/middleware/personal_auth.go` | 增加 Redis 双重校验 | | `pkg/constants/redis.go` | 新增 token 和限流 Redis Key | | `pkg/errors/codes.go` | 新增错误码 1180-1186 | | `pkg/config/defaults/config.yaml` | 新增 `client.require_phone_binding` | | `pkg/wechat/wechat.go` | 新增 MiniAppServiceInterface | | `pkg/wechat/config.go` | 新增 3 个 DB 动态工厂函数 | | `internal/bootstrap/types.go` | 新增 ClientAuth Handler 字段 | | `internal/bootstrap/handlers.go` | 实例化 ClientAuth Handler | | `internal/bootstrap/services.go` | 初始化 ClientAuth Service | | `internal/bootstrap/stores.go` | 初始化 OpenID Store | | `internal/routes/personal.go` | 注册 7 个认证端点 | | `cmd/api/docs.go` | 注册文档生成器 | | `cmd/gendocs/main.go` | 注册文档生成器 | ## 错误码 | 码值 | 常量名 | 说明 | |------|--------|------| | 1180 | CodeAssetNotFound | 资产不存在 | | 1181 | CodeWechatConfigUnavailable | 微信配置不可用 | | 1182 | CodeSmsSendFailed | 短信发送失败 | | 1183 | CodeVerificationCodeInvalid | 验证码错误或已过期 | | 1184 | CodePhoneAlreadyBound | 手机号已被其他客户绑定 | | 1185 | CodeAlreadyBoundPhone | 已绑定手机号不可重复绑定 | | 1186 | CodeOldPhoneMismatch | 旧手机号与当前绑定不匹配 | ## 数据库变更 - 新建表 `tb_personal_customer_openid`(迁移 000083) - 唯一索引:`idx_pco_app_id_open_id` (app_id, open_id) 软删除条件 - 普通索引:`idx_pco_customer_id` (customer_id) - 条件索引:`idx_pco_union_id` (union_id) WHERE union_id != '' ## 配置项 | 配置路径 | 环境变量 | 默认值 | 说明 | |---------|---------|-------|------| | `client.require_phone_binding` | `JUNHONG_CLIENT_REQUIRE_PHONE_BINDING` | `true` | 是否要求绑定手机号 |