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