新增钱包、换卡、标签系统的数据模型和规范
本次提交完成 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:
71
internal/model/card_replacement.go
Normal file
71
internal/model/card_replacement.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PackageSnapshot 套餐快照,记录换卡时的套餐信息
|
||||
type PackageSnapshot struct {
|
||||
PackageID uint `json:"package_id"` // 套餐ID
|
||||
PackageName string `json:"package_name"` // 套餐名称
|
||||
PackageType string `json:"package_type"` // 套餐类型
|
||||
DataQuota int64 `json:"data_quota"` // 流量额度(KB)
|
||||
DataUsed int64 `json:"data_used"` // 已使用流量(KB)
|
||||
ValidFrom time.Time `json:"valid_from"` // 生效时间
|
||||
ValidTo time.Time `json:"valid_to"` // 失效时间
|
||||
Price int64 `json:"price"` // 套餐价格(分)
|
||||
RemainingDays int `json:"remaining_days"` // 剩余天数
|
||||
TransferReason string `json:"transfer_reason,omitempty"` // 转移原因
|
||||
}
|
||||
|
||||
// Value 实现 driver.Valuer 接口
|
||||
func (p PackageSnapshot) Value() (driver.Value, error) {
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
// Scan 实现 sql.Scanner 接口
|
||||
func (p *PackageSnapshot) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, p)
|
||||
}
|
||||
|
||||
// CardReplacementRecord 换卡记录模型
|
||||
// 记录物联卡更换历史,包含套餐快照便于追溯
|
||||
// 支持损坏、丢失、故障等多种换卡原因,需要审批流程
|
||||
type CardReplacementRecord struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
ReplacementNo string `gorm:"column:replacement_no;type:varchar(50);not null;uniqueIndex:idx_card_replacement_no,where:deleted_at IS NULL;comment:换卡单号" json:"replacement_no"`
|
||||
OldCardID uint `gorm:"column:old_card_id;not null;index:idx_card_replacement_old_card;comment:老卡ID" json:"old_card_id"`
|
||||
OldIccid string `gorm:"column:old_iccid;type:varchar(50);not null;comment:老卡ICCID" json:"old_iccid"`
|
||||
NewCardID uint `gorm:"column:new_card_id;not null;index:idx_card_replacement_new_card;comment:新卡ID" json:"new_card_id"`
|
||||
NewIccid string `gorm:"column:new_iccid;type:varchar(50);not null;comment:新卡ICCID" json:"new_iccid"`
|
||||
OldOwnerType string `gorm:"column:old_owner_type;type:varchar(20);not null;index:idx_card_replacement_old_owner,priority:1;comment:老卡所有者类型" json:"old_owner_type"`
|
||||
OldOwnerID uint `gorm:"column:old_owner_id;not null;index:idx_card_replacement_old_owner,priority:2;comment:老卡所有者ID" json:"old_owner_id"`
|
||||
OldAgentID *uint `gorm:"column:old_agent_id;comment:老卡代理ID" json:"old_agent_id,omitempty"`
|
||||
NewOwnerType string `gorm:"column:new_owner_type;type:varchar(20);not null;index:idx_card_replacement_new_owner,priority:1;comment:新卡所有者类型" json:"new_owner_type"`
|
||||
NewOwnerID uint `gorm:"column:new_owner_id;not null;index:idx_card_replacement_new_owner,priority:2;comment:新卡所有者ID" json:"new_owner_id"`
|
||||
NewAgentID *uint `gorm:"column:new_agent_id;comment:新卡代理ID" json:"new_agent_id,omitempty"`
|
||||
PackageSnapshot *PackageSnapshot `gorm:"column:package_snapshot;type:jsonb;comment:套餐快照" json:"package_snapshot,omitempty"`
|
||||
ReplacementReason string `gorm:"column:replacement_reason;type:varchar(20);not null;comment:换卡原因 damaged-损坏 lost-丢失 malfunction-故障 upgrade-升级 other-其他" json:"replacement_reason"`
|
||||
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_card_replacement_status;comment:换卡状态 1-待审批 2-已通过 3-已拒绝 4-已完成" json:"status"`
|
||||
ApprovedBy *uint `gorm:"column:approved_by;comment:审批人ID" json:"approved_by,omitempty"`
|
||||
ApprovedAt *time.Time `gorm:"column:approved_at;comment:审批时间" json:"approved_at,omitempty"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CardReplacementRecord) TableName() string {
|
||||
return "tb_card_replacement_record"
|
||||
}
|
||||
@@ -4,15 +4,16 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Carrier 运营商模型
|
||||
// 存储运营商基础信息(中国移动、中国联通、中国电信)
|
||||
type Carrier struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
CarrierCode string `gorm:"column:carrier_code;type:varchar(50);uniqueIndex:idx_carrier_code,where:deleted_at IS NULL;not null;comment:运营商编码(CMCC/CUCC/CTCC)" json:"carrier_code"`
|
||||
CarrierName string `gorm:"column:carrier_name;type:varchar(100);not null;comment:运营商名称(中国移动/中国联通/中国电信)" json:"carrier_name"`
|
||||
Description string `gorm:"column:description;type:varchar(500);comment:运营商描述" json:"description"`
|
||||
Status int `gorm:"column:status;type:int;default:1;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
CarrierCode string `gorm:"column:carrier_code;type:varchar(50);uniqueIndex:idx_carrier_code,where:deleted_at IS NULL;not null;comment:运营商编码(CMCC/CUCC/CTCC)" json:"carrier_code"`
|
||||
CarrierName string `gorm:"column:carrier_name;type:varchar(100);not null;comment:运营商名称(中国移动/中国联通/中国电信)" json:"carrier_name"`
|
||||
CarrierType string `gorm:"column:carrier_type;type:varchar(20);not null;default:'CMCC';uniqueIndex:idx_carrier_type_channel,priority:1,where:deleted_at IS NULL;comment:运营商类型" json:"carrier_type"`
|
||||
ChannelName *string `gorm:"column:channel_name;type:varchar(100);comment:渠道名称" json:"channel_name,omitempty"`
|
||||
ChannelCode *string `gorm:"column:channel_code;type:varchar(50);uniqueIndex:idx_carrier_type_channel,priority:2,where:deleted_at IS NULL;comment:渠道编码" json:"channel_code,omitempty"`
|
||||
Description string `gorm:"column:description;type:varchar(500);comment:运营商描述" json:"description"`
|
||||
Status int `gorm:"column:status;type:int;default:1;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -11,22 +11,24 @@ import (
|
||||
// 支持两种订单类型:套餐订单(单卡/设备级)、号卡订单
|
||||
type Order struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
OrderNo string `gorm:"column:order_no;type:varchar(100);uniqueIndex:idx_order_no,where:deleted_at IS NULL;not null;comment:订单号(唯一标识)" json:"order_no"`
|
||||
OrderType int `gorm:"column:order_type;type:int;not null;comment:订单类型 1-套餐订单 2-号卡订单" json:"order_type"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;comment:IoT卡ID(单卡套餐订单时有值)" json:"iot_card_id"`
|
||||
DeviceID uint `gorm:"column:device_id;index;comment:设备ID(设备级套餐订单时有值)" json:"device_id"`
|
||||
NumberCardID uint `gorm:"column:number_card_id;index;comment:号卡ID(号卡订单时有值)" json:"number_card_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;comment:套餐ID(套餐订单时有值)" json:"package_id"`
|
||||
UserID uint `gorm:"column:user_id;index;not null;comment:用户ID" json:"user_id"`
|
||||
AgentID uint `gorm:"column:agent_id;index;comment:代理用户ID" json:"agent_id"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:订单金额(分为单位)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);comment:支付方式 wallet-钱包 online-在线支付 carrier-运营商支付" json:"payment_method"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-待支付 2-已支付 3-已完成 4-已取消 5-已退款" json:"status"`
|
||||
CarrierOrderID string `gorm:"column:carrier_order_id;type:varchar(255);comment:运营商订单ID" json:"carrier_order_id"`
|
||||
CarrierOrderData datatypes.JSON `gorm:"column:carrier_order_data;type:jsonb;comment:运营商订单原始数据(JSON)" json:"carrier_order_data"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at"`
|
||||
BaseModel `gorm:"embedded"`
|
||||
OrderNo string `gorm:"column:order_no;type:varchar(100);uniqueIndex:idx_order_no,where:deleted_at IS NULL;not null;comment:订单号(唯一标识)" json:"order_no"`
|
||||
OrderType int `gorm:"column:order_type;type:int;not null;comment:订单类型 1-套餐订单 2-号卡订单" json:"order_type"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;comment:IoT卡ID(单卡套餐订单时有值)" json:"iot_card_id"`
|
||||
DeviceID uint `gorm:"column:device_id;index;comment:设备ID(设备级套餐订单时有值)" json:"device_id"`
|
||||
NumberCardID uint `gorm:"column:number_card_id;index;comment:号卡ID(号卡订单时有值)" json:"number_card_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;comment:套餐ID(套餐订单时有值)" json:"package_id"`
|
||||
UserID uint `gorm:"column:user_id;index;not null;comment:用户ID" json:"user_id"`
|
||||
AgentID uint `gorm:"column:agent_id;index;comment:代理用户ID" json:"agent_id"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:订单金额(分为单位)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);comment:支付方式 wallet-钱包 online-在线支付 carrier-运营商支付" json:"payment_method"`
|
||||
WalletPaymentAmount int64 `gorm:"column:wallet_payment_amount;type:bigint;not null;default:0;comment:钱包支付金额(分)" json:"wallet_payment_amount"`
|
||||
OnlinePaymentAmount int64 `gorm:"column:online_payment_amount;type:bigint;not null;default:0;comment:在线支付金额(分)" json:"online_payment_amount"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-待支付 2-已支付 3-已完成 4-已取消 5-已退款" json:"status"`
|
||||
CarrierOrderID string `gorm:"column:carrier_order_id;type:varchar(255);comment:运营商订单ID" json:"carrier_order_id"`
|
||||
CarrierOrderData datatypes.JSON `gorm:"column:carrier_order_data;type:jsonb;comment:运营商订单原始数据(JSON)" json:"carrier_order_data"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
37
internal/model/tag.go
Normal file
37
internal/model/tag.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Tag 标签模型
|
||||
// 用于设备、IoT卡、号卡的分类标记,支持自定义颜色
|
||||
// UsageCount 字段记录标签使用次数,便于展示热门标签
|
||||
type Tag struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
Name string `gorm:"column:name;type:varchar(100);not null;uniqueIndex:idx_tag_name,where:deleted_at IS NULL;comment:标签名称" json:"name"`
|
||||
Color *string `gorm:"column:color;type:varchar(20);comment:标签颜色(十六进制)" json:"color,omitempty"`
|
||||
UsageCount int `gorm:"column:usage_count;type:int;not null;default:0;index:idx_tag_usage;comment:使用次数" json:"usage_count"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Tag) TableName() string {
|
||||
return "tb_tag"
|
||||
}
|
||||
|
||||
// ResourceTag 资源-标签关联模型
|
||||
// 统一管理设备、IoT卡、号卡与标签的多对多关系
|
||||
// ResourceType 取值: device/iot_card/number_card
|
||||
type ResourceTag struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
ResourceType string `gorm:"column:resource_type;type:varchar(20);not null;uniqueIndex:idx_resource_tag_unique,priority:1,where:deleted_at IS NULL;index:idx_resource_tag_resource,priority:1;index:idx_resource_tag_composite,priority:1;comment:资源类型 device-设备 iot_card-IoT卡 number_card-号卡" json:"resource_type"`
|
||||
ResourceID uint `gorm:"column:resource_id;not null;uniqueIndex:idx_resource_tag_unique,priority:2,where:deleted_at IS NULL;index:idx_resource_tag_resource,priority:2;comment:资源ID" json:"resource_id"`
|
||||
TagID uint `gorm:"column:tag_id;not null;uniqueIndex:idx_resource_tag_unique,priority:3,where:deleted_at IS NULL;index:idx_resource_tag_tag;index:idx_resource_tag_composite,priority:2;comment:标签ID" json:"tag_id"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ResourceTag) TableName() string {
|
||||
return "tb_resource_tag"
|
||||
}
|
||||
97
internal/model/wallet.go
Normal file
97
internal/model/wallet.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Wallet 钱包模型
|
||||
// 用户和代理的资金账户,支持充值、消费、提现等操作
|
||||
// 使用乐观锁(version字段)防止并发余额冲突
|
||||
type Wallet struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
UserID uint `gorm:"column:user_id;not null;index:idx_wallet_user,priority:1;comment:用户ID" json:"user_id"`
|
||||
WalletType string `gorm:"column:wallet_type;type:varchar(20);not null;uniqueIndex:idx_wallet_user_type_currency,priority:2;comment:钱包类型 user-用户钱包 agent-代理钱包" json:"wallet_type"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null;default:0;comment:余额(分)" json:"balance"`
|
||||
FrozenBalance int64 `gorm:"column:frozen_balance;type:bigint;not null;default:0;comment:冻结余额(分)" json:"frozen_balance"`
|
||||
Currency string `gorm:"column:currency;type:varchar(10);not null;default:'CNY';uniqueIndex:idx_wallet_user_type_currency,priority:3;comment:币种" json:"currency"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_wallet_status;comment:钱包状态 1-正常 2-冻结 3-关闭" json:"status"`
|
||||
Version int `gorm:"column:version;type:int;not null;default:0;comment:版本号(乐观锁)" json:"version"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Wallet) TableName() string {
|
||||
return "tb_wallet"
|
||||
}
|
||||
|
||||
// WalletMetadata 钱包交易扩展信息
|
||||
// 用于存储交易相关的额外数据(JSONB格式)
|
||||
type WalletMetadata map[string]interface{}
|
||||
|
||||
// Value 实现 driver.Valuer 接口
|
||||
func (m WalletMetadata) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Scan 实现 sql.Scanner 接口
|
||||
func (m *WalletMetadata) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*m = make(WalletMetadata)
|
||||
return nil
|
||||
}
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, m)
|
||||
}
|
||||
|
||||
// WalletTransaction 钱包交易记录模型
|
||||
// 记录所有钱包余额变动,包含变动前后余额用于对账
|
||||
// 支持关联业务对象(订单、分佣、提现等)
|
||||
type WalletTransaction struct {
|
||||
gorm.Model
|
||||
WalletID uint `gorm:"column:wallet_id;not null;index:idx_wallet_tx_wallet;comment:钱包ID" json:"wallet_id"`
|
||||
UserID uint `gorm:"column:user_id;not null;index:idx_wallet_tx_user;comment:用户ID" json:"user_id"`
|
||||
TransactionType string `gorm:"column:transaction_type;type:varchar(20);not null;comment:交易类型 recharge-充值 deduct-扣款 refund-退款 commission-分佣 withdrawal-提现" json:"transaction_type"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:变动金额(分)正数为增加 负数为减少" json:"amount"`
|
||||
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null;comment:变动前余额(分)" json:"balance_before"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null;comment:变动后余额(分)" json:"balance_after"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:交易状态 1-成功 2-失败 3-处理中" json:"status"`
|
||||
ReferenceType *string `gorm:"column:reference_type;type:varchar(50);index:idx_wallet_tx_ref,priority:1;comment:关联业务类型 order/commission/withdrawal/topup" json:"reference_type,omitempty"`
|
||||
ReferenceID *uint `gorm:"column:reference_id;type:bigint;index:idx_wallet_tx_ref,priority:2;comment:关联业务ID" json:"reference_id,omitempty"`
|
||||
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
|
||||
Metadata WalletMetadata `gorm:"column:metadata;type:jsonb;comment:扩展信息" json:"metadata,omitempty"`
|
||||
Creator uint `gorm:"column:creator;comment:创建人ID" json:"creator"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (WalletTransaction) TableName() string {
|
||||
return "tb_wallet_transaction"
|
||||
}
|
||||
|
||||
// RechargeRecord 充值记录模型
|
||||
// 用户和代理的钱包充值订单,记录支付流程和状态
|
||||
type RechargeRecord struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
UserID uint `gorm:"column:user_id;not null;index:idx_recharge_user;comment:用户ID" json:"user_id"`
|
||||
WalletID uint `gorm:"column:wallet_id;not null;comment:钱包ID" json:"wallet_id"`
|
||||
RechargeNo string `gorm:"column:recharge_no;type:varchar(50);not null;uniqueIndex:idx_recharge_no;comment:充值订单号" json:"recharge_no"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:充值金额(分)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);not null;comment:支付方式 alipay-支付宝 wechat-微信 bank-银行转账 offline-线下" json:"payment_method"`
|
||||
PaymentChannel *string `gorm:"column:payment_channel;type:varchar(50);comment:支付渠道" json:"payment_channel,omitempty"`
|
||||
PaymentTransactionID *string `gorm:"column:payment_transaction_id;type:varchar(100);comment:第三方支付交易号" json:"payment_transaction_id,omitempty"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_recharge_status;comment:充值状态 1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款" json:"status"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (RechargeRecord) TableName() string {
|
||||
return "tb_recharge_record"
|
||||
}
|
||||
Reference in New Issue
Block a user