Files
junhong_cmp_fiber/openspec/changes/archive/2026-01-10-add-personal-customer-wechat/proposal.md
huang 9c6d4a3bd4 实现个人客户微信认证和短信验证功能
- 添加个人客户微信登录和手机验证码登录接口
- 实现个人客户设备、ICCID、手机号关联管理
- 添加短信发送服务(HTTP 客户端)
- 添加微信认证服务(含 mock 实现)
- 添加 JWT Token 生成和验证工具
- 创建数据库迁移脚本(personal_customer 关联表)
- 修复测试文件中的路由注册参数错误
- 重构 scripts 目录结构(分离独立脚本到子目录)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 11:42:38 +08:00

239 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Change: 添加个人客户和微信登录
## Why
个人客户是系统的重要用户群体,他们通过 H5/小程序访问系统,使用 ICCID/设备号登录并绑定微信。个人客户不参与 RBAC 权限体系,但需要独立的认证流程和数据存储。
## What Changes
### 新增功能
- **个人客户登录流程**: 通过 ICCID/设备号 + 微信授权登录,首次需绑定手机号
- **微信绑定**: 存储 OpenID/UnionID 用于微信支付和通知(用户唯一标识)
- **个人客户认证中间件**: 独立于 B 端账号的认证体系
- **短信验证码**: 对接武汉聚惠富通行业短信平台发送验证码
- **ICCID/设备号绑定记录**: 记录微信用户使用过哪些 ICCID/设备号
### 核心业务模型
#### 用户身份识别
- **个人客户 (PersonalCustomer)** = **微信用户**(通过 `wx_open_id` 唯一标识)
- **ICCID/设备号** 是独立的资源(可以被充值、使用),不是用户身份
- 任何人拿到 ICCID/设备号 都可以使用,没有所有权概念
#### 数据模型关系
1. **PersonalCustomer**: 微信用户主表不存储手机号、ICCID
2. **PersonalCustomerPhone**: 微信用户绑定的手机号(一对多)
3. **PersonalCustomerICCID**: 微信用户使用过的 ICCID 记录(多对多)
4. **PersonalCustomerDevice**: 微信用户使用过的设备号记录(多对多,可选)
### 业务规则
1. **用户身份**:个人客户由微信 OpenID/UnionID 唯一标识
2. **手机号绑定**:一个微信用户可以绑定多个手机号(用于接收验证码)
3. **ICCID/设备号绑定**:记录微信用户使用过哪些 ICCID/设备号(用于业务追踪)
4. **充值业务**:充值是充到 ICCID/设备号上,不是充到用户账户
### 登录流程
```
用户扫码/进入H5
输入 ICCID/设备号(业务标识,不存储到用户表)
微信授权登录
获取 wx_open_id, wx_union_id
检查微信用户是否存在
├─ 是 → 记录 ICCID 绑定关系 → 登录成功
└─ 否 → 创建新用户 → 提示绑定手机号
输入手机号 → 发送验证码 → 验证
创建手机号绑定记录 → 创建 ICCID 绑定记录 → 登录成功
```
## 数据模型设计
### 1. PersonalCustomer个人客户 = 微信用户)
```go
type PersonalCustomer struct {
ID uint // 主键
WxOpenID string // 微信OpenID唯一标识必填
WxUnionID string // 微信UnionID必填
Nickname string // 微信昵称
AvatarURL string // 微信头像URL
Status int // 状态 0=禁用 1=启用
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
```
**索引**:
- 唯一索引: `wx_open_id` (where deleted_at IS NULL)
- 普通索引: `wx_union_id`
**说明**:
- 移除 `phone``iccid``imei` 字段
- 微信信息是唯一标识用户的字段
### 2. PersonalCustomerPhone微信用户的手机号
```go
type PersonalCustomerPhone struct {
ID uint // 主键
CustomerID uint // 关联个人客户 ID微信用户
Phone string // 手机号
IsPrimary bool // 是否主手机号(用于通知等)
VerifiedAt time.Time // 验证通过时间
Status int // 状态 0=禁用 1=启用
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
```
**索引**:
- 唯一索引: `(customer_id, phone)` (where deleted_at IS NULL)
- 普通索引: `phone`
**说明**:
- 一个微信用户可以绑定多个手机号
- 手机号用于接收验证码、通知等
### 3. PersonalCustomerICCIDICCID 与微信用户的绑定关系)
```go
type PersonalCustomerICCID struct {
ID uint // 主键
CustomerID uint // 关联个人客户 ID微信用户
ICCID string // ICCID20位数字
BindAt time.Time // 绑定时间
LastUsedAt time.Time // 最后使用时间
Status int // 状态 0=禁用 1=启用
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
```
**索引**:
- 唯一索引: `(customer_id, iccid)` (where deleted_at IS NULL)
- 普通索引: `iccid` - 查询某个 ICCID 被哪些用户使用过
**说明**:
- 记录微信用户使用过哪些 ICCID
- 一个 ICCID 可以被多个微信用户使用过
- 一个微信用户可以使用多个 ICCID
### 4. PersonalCustomerDevice设备号与微信用户的绑定关系可选
```go
type PersonalCustomerDevice struct {
ID uint // 主键
CustomerID uint // 关联个人客户 ID微信用户
DeviceNo string // 设备号/IMEI
BindAt time.Time // 绑定时间
LastUsedAt time.Time // 最后使用时间
Status int // 状态 0=禁用 1=启用
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
```
**索引**:
- 唯一索引: `(customer_id, device_no)` (where deleted_at IS NULL)
- 普通索引: `device_no`
**说明**:
- 记录微信用户使用过哪些设备号
- 与 ICCID 类似的多对多关系
## Impact
- **Affected specs**: personal-customer (新建)
- **Affected code**:
- `internal/model/personal_customer.go` - 需要修改(移除 phone 字段)
- `internal/model/personal_customer_phone.go` - 新增
- `internal/model/personal_customer_iccid.go` - 新增
- `internal/model/personal_customer_device.go` - 新增(可选)
- `internal/store/postgres/personal_customer_store.go` - 需要扩展
- `internal/store/postgres/personal_customer_phone_store.go` - 新增
- `internal/store/postgres/personal_customer_iccid_store.go` - 新增
- `internal/service/personal_customer_service.go` - 扩展登录逻辑
- `internal/handler/personal_customer_handler.go` - 新增
- `internal/middleware/personal_auth.go` - 个人客户认证中间件
- `pkg/sms/` - 短信验证码服务(对接武汉聚惠富通行业短信)
- `config/config.yaml` - 新增短信服务配置项
- `migrations/` - 新增数据库迁移脚本
## 依赖关系
本提案依赖 **add-user-organization-model** 提案中的 PersonalCustomer 模型定义。
## 短信服务对接方案
### 第三方服务信息
- **服务商**: 武汉聚惠富通(行业短信)
- **接口网关**: `https://gateway.sms.whjhft.com:8443/sms`
- **协议**: HTTP JSON API v1.6
- **接口文档**: `docs/第三方文档/SMS_HTTP_1.6.md`
### 使用接口
**短信批量发送接口**: `POST /api/sendMessageMass`
**发送方式**: 直接发送内容(不使用短信模板)
**短信内容格式**: `【签名】自定义内容`
- 签名部分需提前向服务商报备并审核通过
- 示例: `【签名】您的验证码是1234565分钟内有效`
- 不使用 `templateId``params` 参数,只使用 `content` 字段
### 实现方案
1. **包结构**: `pkg/sms/`
- `client.go` - 短信客户端封装
- `types.go` - 请求/响应类型定义
- `error.go` - 错误码映射
2. **配置管理**:
```yaml
sms:
gateway_url: "https://gateway.sms.whjhft.com:8443/sms"
username: "账号用户名"
password: "账号密码"
signature: "【签名】"
timeout: 10s
```
3. **核心功能**:
- 生成 Sign 签名MD5 计算)
- 发送验证码短信
- 错误处理和日志记录
- 超时和重试机制
4. **安全要求**:
- 短信密码不得硬编码,必须从配置文件读取
- Sign 计算遵循官方规范:`MD5(userName + timestamp + MD5(password))`
- 时间戳与服务器时间误差不得超过5分钟
5. **错误处理**:
- 余额不足code=5记录错误日志返回用户友好提示
- 时间戳错误code=16检查服务器时间同步
- 账号异常code=3, 4记录错误日志通知管理员
- 其他错误:参考文档响应状态码列表
## 注意事项
- **数据模型变更**PersonalCustomer 模型需要移除 `phone` 字段,新增 PersonalCustomerPhone、PersonalCustomerICCID 关联表
- **微信 SDK 集成**:可以先预留接口或使用 Mock 实现,后续对接具体的微信 OAuth API
- **短信签名**:需要提前向服务商报备,使用报备通过的签名
- **业务逻辑实现**:本提案重点在数据模型建立,具体业务逻辑(登录流程、绑定流程)后续实现
- **ICCID/设备号充值**:充值是充到 ICCID/设备号资源上,不是充到用户账户,需与后续的资产模块协同设计