# 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/设备号资源上，不是充到用户账户，需与后续的资产模块协同设计
