- 添加个人客户微信登录和手机验证码登录接口 - 实现个人客户设备、ICCID、手机号关联管理 - 添加短信发送服务(HTTP 客户端) - 添加微信认证服务(含 mock 实现) - 添加 JWT Token 生成和验证工具 - 创建数据库迁移脚本(personal_customer 关联表) - 修复测试文件中的路由注册参数错误 - 重构 scripts 目录结构(分离独立脚本到子目录) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
239 lines
8.4 KiB
Markdown
239 lines
8.4 KiB
Markdown
# 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. PersonalCustomerICCID(ICCID 与微信用户的绑定关系)
|
||
|
||
```go
|
||
type PersonalCustomerICCID struct {
|
||
ID uint // 主键
|
||
CustomerID uint // 关联个人客户 ID(微信用户)
|
||
ICCID string // ICCID(20位数字)
|
||
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`
|
||
|
||
**发送方式**: 直接发送内容(不使用短信模板)
|
||
|
||
**短信内容格式**: `【签名】自定义内容`
|
||
- 签名部分需提前向服务商报备并审核通过
|
||
- 示例: `【签名】您的验证码是123456,5分钟内有效`
|
||
- 不使用 `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/设备号资源上,不是充到用户账户,需与后续的资产模块协同设计
|