新增钱包、换卡、标签系统的数据模型和规范

本次提交完成 add-wallet-transfer-tag-models 提案的实施和归档:

## 新增功能模块
- 钱包系统:用户/代理钱包管理,支持充值、扣款、退款、乐观锁防并发
- 换卡记录:物联卡更换历史追溯,包含套餐快照(JSONB)
- 标签系统:设备/IoT卡/号卡的统一标签管理
- 运营商渠道:四大运营商(CMCC/CUCC/CTCC/CBN)的渠道管理

## 数据库变更
- 新增 6 张表:tb_wallet, tb_wallet_transaction, tb_recharge_record, tb_card_replacement_record, tb_tag, tb_resource_tag
- 修改 2 张表:tb_carrier(新增渠道字段), tb_order(新增混合支付字段)
- 迁移版本:v6 → v7(执行时间 282.5ms)

## 代码变更
- 新增 8 个 Go 模型(符合统一规范:gorm.Model + BaseModel)
- 新增 40+ 个常量定义(含完整中文注释)
- 新增 7 个 Redis Key 生成函数
- 修复模型规范:移除重复字段,统一使用 gorm.Model 嵌入

## 文档变更
- 新增 3 个业务文档:数据模型设计、字段说明、迁移验证报告
- 更新 AGENTS.md:新增 Model 模型规范和常量注释规范
- 新增 4 个 OpenSpec 规范:wallet, carrier, card-replacement, tag
- 更新 1 个 OpenSpec 规范:iot-order(支持混合支付)

## 验证通过
-  LSP 诊断:所有模型和常量文件无错误
-  OpenSpec 验证:openspec validate --strict 通过
-  迁移执行:表结构创建成功,索引正确
-  提案归档:2026-01-13-add-wallet-transfer-tag-models

变更文件统计:29 个文件,新增 3682 行
This commit is contained in:
2026-01-13 15:47:32 +08:00
parent 2150fb6ab9
commit 6e2dc325d7
29 changed files with 3682 additions and 87 deletions

View File

@@ -0,0 +1,203 @@
# wallet Specification
## Purpose
TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose after archive.
## Requirements
### Requirement: 钱包实体定义
系统 SHALL 定义钱包(Wallet)实体,统一管理用户钱包和代理钱包,支持余额管理、充值、扣款等操作。
**核心概念**
- **用户钱包**:普通用户和企业用户的钱包,用于购买套餐
- **代理钱包**:代理商的钱包,支持预充值,可用成本价购买套餐
**实体字段**
- `id`:钱包 ID主键BIGINT
- `user_id`:用户 IDBIGINT关联 tb_account.id
- `wallet_type`钱包类型VARCHAR(20),枚举值:"user"-用户钱包 | "agent"-代理钱包)
- `balance`余额BIGINT单位默认 0
- `frozen_balance`冻结余额BIGINT单位默认 0用于订单待支付、提现申请中等场景
- `currency`币种VARCHAR(10),默认 "CNY"
- `status`钱包状态INT1-正常 2-冻结 3-关闭)
- `version`版本号INT默认 0乐观锁字段用于防止并发扣款
- `creator`:创建人 IDBIGINT
- `updater`:更新人 IDBIGINT
- `created_at`创建时间TIMESTAMP自动填充
- `updated_at`更新时间TIMESTAMP自动填充
- `deleted_at`删除时间TIMESTAMP可空软删除
**唯一约束**`(user_id, wallet_type, currency)``deleted_at IS NULL` 条件下唯一
**可用余额计算**:可用余额 = balance - frozen_balance
#### Scenario: 创建用户钱包
- **WHEN** 用户ID 为 2001首次充值
- **THEN** 系统创建钱包记录,`user_id` 为 2001`wallet_type` 为 "user"`balance` 为 0`status` 为 1正常
#### Scenario: 创建代理钱包
- **WHEN** 代理商ID 为 123首次充值
- **THEN** 系统创建钱包记录,`user_id` 为 123`wallet_type` 为 "agent"`balance` 为 0`status` 为 1正常
#### Scenario: 计算可用余额
- **WHEN** 用户钱包余额为 10000 分100 元),冻结余额为 3000 分30 元)
- **THEN** 系统计算可用余额为 7000 分70 元)
---
### Requirement: 钱包明细记录
系统 SHALL 记录所有钱包余额变动,包括充值、扣款、退款、分佣、提现等操作,确保完整的审计追踪。
**实体字段**
- `id`:明细 ID主键BIGINT
- `wallet_id`:钱包 IDBIGINT关联 tb_wallet.id
- `user_id`:用户 IDBIGINT关联 tb_account.id
- `transaction_type`交易类型VARCHAR(20),枚举值:"recharge"-充值 | "deduct"-扣款 | "refund"-退款 | "commission"-分佣 | "withdrawal"-提现)
- `amount`变动金额BIGINT单位正数为增加负数为减少
- `balance_before`变动前余额BIGINT单位
- `balance_after`变动后余额BIGINT单位
- `status`交易状态INT1-成功 2-失败 3-处理中)
- `reference_type`关联业务类型VARCHAR(50),如 "order" | "commission" | "withdrawal" | "topup"
- `reference_id`:关联业务 IDBIGINT
- `remark`备注TEXT
- `metadata`扩展信息JSONB如手续费、支付方式等
- `creator`:创建人 IDBIGINT
- `created_at`创建时间TIMESTAMP自动填充
- `updated_at`更新时间TIMESTAMP自动填充
- `deleted_at`删除时间TIMESTAMP可空软删除
#### Scenario: 充值创建明细记录
- **WHEN** 用户ID 为 2001充值 10000 分100 元)
- **THEN** 系统创建钱包明细记录,`transaction_type` 为 "recharge"`amount` 为 10000`balance_before` 为 0`balance_after` 为 10000`status` 为 1成功
#### Scenario: 购买套餐扣款创建明细记录
- **WHEN** 用户ID 为 2001使用钱包支付购买套餐金额 3000 分30 元)
- **THEN** 系统创建钱包明细记录,`transaction_type` 为 "deduct"`amount` 为 -3000`balance_before` 为 10000`balance_after` 为 7000`reference_type` 为 "order"`reference_id` 为订单 ID
#### Scenario: 分佣发放创建明细记录
- **WHEN** 代理ID 为 123的分佣 5000 分50 元)审批通过并发放
- **THEN** 系统创建钱包明细记录,`transaction_type` 为 "commission"`amount` 为 5000`balance_before` 为 20000`balance_after` 为 25000`reference_type` 为 "commission"`reference_id` 为分佣记录 ID
---
### Requirement: 充值记录管理
系统 SHALL 记录所有充值操作,包括充值订单号、金额、支付方式、支付状态等信息。
**实体字段**
- `id`:充值记录 ID主键BIGINT
- `user_id`:用户 IDBIGINT关联 tb_account.id
- `wallet_id`:钱包 IDBIGINT关联 tb_wallet.id
- `recharge_no`充值订单号VARCHAR(50),唯一)
- `amount`充值金额BIGINT单位
- `payment_method`支付方式VARCHAR(20),枚举值:"alipay"-支付宝 | "wechat"-微信 | "bank"-银行转账 | "offline"-线下)
- `payment_channel`支付渠道VARCHAR(50)
- `payment_transaction_id`第三方支付交易号VARCHAR(100)
- `status`充值状态INT1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)
- `paid_at`支付时间TIMESTAMP可空
- `completed_at`完成时间TIMESTAMP可空
- `creator`:创建人 IDBIGINT
- `updater`:更新人 IDBIGINT
- `created_at`创建时间TIMESTAMP自动填充
- `updated_at`更新时间TIMESTAMP自动填充
- `deleted_at`删除时间TIMESTAMP可空软删除
#### Scenario: 创建充值订单
- **WHEN** 用户ID 为 2001发起充值 10000 分100 元),选择支付宝支付
- **THEN** 系统创建充值记录,生成唯一的 `recharge_no``amount` 为 10000`payment_method` 为 "alipay"`status` 为 1待支付
#### Scenario: 充值支付完成
- **WHEN** 用户完成支付宝支付
- **THEN** 系统将充值记录状态从 1待支付变更为 2已支付记录 `paid_at` 时间和 `payment_transaction_id`
#### Scenario: 充值到账
- **WHEN** 充值记录状态为 2已支付系统处理充值到账
- **THEN** 系统将钱包余额增加 10000 分,创建钱包明细记录,将充值记录状态变更为 3已完成记录 `completed_at` 时间
---
### Requirement: 钱包余额操作
系统 SHALL 支持钱包余额的充值、扣款、退款、冻结、解冻等操作,使用乐观锁防止并发问题。
**操作类型**
- **充值**:增加钱包余额
- **扣款**:减少钱包余额(如购买套餐)
- **退款**:增加钱包余额(如订单退款)
- **冻结**:将部分余额转为冻结状态(如订单待支付)
- **解冻**:将冻结余额转回可用余额(如订单取消)
**并发控制**
- 使用 `version` 字段实现乐观锁
- 每次更新余额时,检查 `version` 是否匹配
- 如果 `version` 不匹配,说明有并发更新,操作失败并重试
#### Scenario: 钱包充值
- **WHEN** 用户钱包当前余额为 10000 分,充值 5000 分
- **THEN** 系统将钱包余额更新为 15000 分,`version` 从 1 变更为 2创建钱包明细记录
#### Scenario: 钱包扣款
- **WHEN** 用户钱包当前余额为 15000 分,购买套餐扣款 3000 分
- **THEN** 系统检查可用余额15000 - 0 = 15000≥ 3000将钱包余额更新为 12000 分,`version` 从 2 变更为 3创建钱包明细记录
#### Scenario: 余额不足扣款失败
- **WHEN** 用户钱包当前余额为 2000 分,购买套餐需要扣款 3000 分
- **THEN** 系统检查可用余额2000 - 0 = 2000< 3000拒绝扣款返回错误信息"余额不足"
#### Scenario: 并发扣款乐观锁生效
- **WHEN** 用户钱包当前余额为 10000 分version 为 1两个并发请求同时扣款 3000 分和 5000 分
- **THEN** 第一个请求成功,余额变为 7000 分version 变为 2第二个请求因 version 不匹配失败需重新读取最新余额7000 分)后重试
#### Scenario: 冻结余额
- **WHEN** 用户创建订单 10001订单金额 3000 分,选择钱包支付
- **THEN** 系统将钱包的 `frozen_balance` 增加 3000 分,可用余额减少 3000 分
#### Scenario: 解冻余额
- **WHEN** 用户取消订单 10001订单金额 3000 分
- **THEN** 系统将钱包的 `frozen_balance` 减少 3000 分,可用余额增加 3000 分
---
### Requirement: 钱包数据校验
系统 SHALL 对钱包数据进行校验,确保数据完整性和一致性。
**校验规则**
- `user_id`:必填,≥ 1
- `wallet_type`:必填,枚举值 "user" | "agent"
- `balance`:必填,≥ 0
- `frozen_balance`:必填,≥ 0≤ balance
- `currency`:必填,长度 1-10 字符
- `status`:必填,枚举值 1-3
- `version`:必填,≥ 0
#### Scenario: 创建钱包时 user_id 无效
- **WHEN** 创建钱包,`user_id` 为 0
- **THEN** 系统拒绝创建,返回错误信息"用户 ID 无效"
#### Scenario: 创建钱包时 wallet_type 无效
- **WHEN** 创建钱包,`wallet_type` 为 "invalid"
- **THEN** 系统拒绝创建,返回错误信息"钱包类型无效"
#### Scenario: 冻结余额超过总余额
- **WHEN** 钱包余额为 10000 分,尝试冻结 15000 分
- **THEN** 系统拒绝操作,返回错误信息"冻结余额不能超过总余额"