feat: 实现订单超时自动取消功能,支持钱包余额解冻和 Asynq Scheduler 统一调度
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m58s

- 新增 expires_at 字段和复合索引,待支付订单 30 分钟超时自动取消
- 实现 cancelOrder/unfreezeWalletForCancel 钱包余额解冻逻辑
- 创建 Asynq 定时任务(order_expire/alert_check/data_cleanup)
- 将原有 time.Ticker 轮询迁移至 Asynq Scheduler 统一调度
- 同步 delta specs 到 main specs 并归档变更
This commit is contained in:
2026-02-28 17:16:15 +08:00
parent 5bb0ff0ddf
commit e661b59bb9
35 changed files with 1157 additions and 314 deletions

View File

@@ -37,27 +37,27 @@ func (w *AgentWallet) GetAvailableBalance() int64 {
// AgentWalletTransaction 代理钱包交易记录模型
// 记录所有代理钱包余额变动
type AgentWalletTransaction struct {
ID uint `gorm:"column:id;primaryKey" json:"id"`
AgentWalletID uint `gorm:"column:agent_wallet_id;not null;index;comment:代理钱包ID" json:"agent_wallet_id"`
ShopID uint `gorm:"column:shop_id;not null;index;comment:店铺ID(冗余字段,便于查询)" json:"shop_id"`
UserID uint `gorm:"column:user_id;not null;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"`
TransactionSubtype *string `gorm:"column:transaction_subtype;type:varchar(50);comment:交易子类型(细分 order_payment 场景)" json:"transaction_subtype,omitempty"`
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);comment:关联业务类型(order | commission | withdrawal | topup)" json:"reference_type,omitempty"`
ReferenceID *uint `gorm:"column:reference_id;comment:关联业务ID" json:"reference_id,omitempty"`
RelatedShopID *uint `gorm:"column:related_shop_id;comment:关联店铺ID代购时记录下级店铺" json:"related_shop_id,omitempty"`
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
Metadata *string `gorm:"column:metadata;type:jsonb;comment:扩展信息(如手续费、支付方式等)" json:"metadata,omitempty"`
Creator uint `gorm:"column:creator;not null;comment:创建人ID" json:"creator"`
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
ID uint `gorm:"column:id;primaryKey" json:"id"`
AgentWalletID uint `gorm:"column:agent_wallet_id;not null;index;comment:代理钱包ID" json:"agent_wallet_id"`
ShopID uint `gorm:"column:shop_id;not null;index;comment:店铺ID(冗余字段,便于查询)" json:"shop_id"`
UserID uint `gorm:"column:user_id;not null;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"`
TransactionSubtype *string `gorm:"column:transaction_subtype;type:varchar(50);comment:交易子类型(细分 order_payment 场景)" json:"transaction_subtype,omitempty"`
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);comment:关联业务类型(order | commission | withdrawal | topup)" json:"reference_type,omitempty"`
ReferenceID *uint `gorm:"column:reference_id;comment:关联业务ID" json:"reference_id,omitempty"`
RelatedShopID *uint `gorm:"column:related_shop_id;comment:关联店铺ID代购时记录下级店铺" json:"related_shop_id,omitempty"`
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
Metadata *string `gorm:"column:metadata;type:jsonb;comment:扩展信息(如手续费、支付方式等)" json:"metadata,omitempty"`
Creator uint `gorm:"column:creator;not null;comment:创建人ID" json:"creator"`
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
}
// TableName 指定表名

View File

@@ -29,8 +29,8 @@ type StandaloneIotCardResponse struct {
IMSI string `json:"imsi,omitempty" description:"IMSI"`
MSISDN string `json:"msisdn,omitempty" description:"卡接入号"`
BatchNo string `json:"batch_no,omitempty" description:"批次号"`
Supplier string `json:"supplier,omitempty" description:"供应商"`
Status int `json:"status" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
Supplier string `json:"supplier,omitempty" description:"供应商"`
Status int `json:"status" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
ShopID *uint `json:"shop_id,omitempty" description:"店铺ID"`
ShopName string `json:"shop_name,omitempty" description:"店铺名称"`
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间"`

View File

@@ -28,6 +28,7 @@ type OrderListRequest struct {
PurchaseRole string `json:"purchase_role" query:"purchase_role" validate:"omitempty,oneof=self_purchase purchased_by_parent purchased_by_platform purchase_for_subordinate" description:"订单角色 (self_purchase:自己购买, purchased_by_parent:上级代理购买, purchased_by_platform:平台代购, purchase_for_subordinate:给下级购买)"`
StartTime *time.Time `json:"start_time" query:"start_time" description:"创建时间起始"`
EndTime *time.Time `json:"end_time" query:"end_time" description:"创建时间结束"`
IsExpired *bool `json:"is_expired" query:"is_expired" description:"是否已过期 (true:已过期, false:未过期)"`
}
type PayOrderRequest struct {
@@ -44,21 +45,21 @@ type OrderItemResponse struct {
}
type OrderResponse struct {
ID uint `json:"id" description:"订单ID"`
OrderNo string `json:"order_no" description:"订单号"`
OrderType string `json:"order_type" description:"订单类型 (single_card:单卡购买, device:设备购买)"`
BuyerType string `json:"buyer_type" description:"买家类型 (personal:个人客户, agent:代理商)"`
BuyerID uint `json:"buyer_id" description:"买家ID"`
IotCardID *uint `json:"iot_card_id,omitempty" description:"IoT卡ID"`
DeviceID *uint `json:"device_id,omitempty" description:"设备ID"`
TotalAmount int64 `json:"total_amount" description:"订单总金额(分)"`
PaymentMethod string `json:"payment_method,omitempty" description:"支付方式 (wallet:钱包支付, wechat:微信支付, alipay:支付宝支付)"`
PaymentStatus int `json:"payment_status" description:"支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)"`
PaymentStatusText string `json:"payment_status_text" description:"支付状态文本"`
PaidAt *time.Time `json:"paid_at,omitempty" description:"支付时间"`
IsPurchaseOnBehalf bool `json:"is_purchase_on_behalf" description:"是否为代购订单"`
CommissionStatus int `json:"commission_status" description:"佣金状态 (1:待计算, 2:已计算)"`
CommissionConfigVersion int `json:"commission_config_version" description:"佣金配置版本"`
ID uint `json:"id" description:"订单ID"`
OrderNo string `json:"order_no" description:"订单号"`
OrderType string `json:"order_type" description:"订单类型 (single_card:单卡购买, device:设备购买)"`
BuyerType string `json:"buyer_type" description:"买家类型 (personal:个人客户, agent:代理商)"`
BuyerID uint `json:"buyer_id" description:"买家ID"`
IotCardID *uint `json:"iot_card_id,omitempty" description:"IoT卡ID"`
DeviceID *uint `json:"device_id,omitempty" description:"设备ID"`
TotalAmount int64 `json:"total_amount" description:"订单总金额(分)"`
PaymentMethod string `json:"payment_method,omitempty" description:"支付方式 (wallet:钱包支付, wechat:微信支付, alipay:支付宝支付)"`
PaymentStatus int `json:"payment_status" description:"支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)"`
PaymentStatusText string `json:"payment_status_text" description:"支付状态文本"`
PaidAt *time.Time `json:"paid_at,omitempty" description:"支付时间"`
IsPurchaseOnBehalf bool `json:"is_purchase_on_behalf" description:"是否为代购订单"`
CommissionStatus int `json:"commission_status" description:"佣金状态 (1:待计算, 2:已计算)"`
CommissionConfigVersion int `json:"commission_config_version" description:"佣金配置版本"`
// 操作者信息
OperatorID *uint `json:"operator_id,omitempty" description:"操作者ID"`
@@ -74,6 +75,10 @@ type OrderResponse struct {
Items []*OrderItemResponse `json:"items" description:"订单明细列表"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
// 订单超时信息
ExpiresAt *time.Time `json:"expires_at,omitempty" description:"订单过期时间"`
IsExpired bool `json:"is_expired" description:"是否已过期"`
}
type OrderListResponse struct {

View File

@@ -22,8 +22,8 @@ type IotCard struct {
IMSI string `gorm:"column:imsi;type:varchar(50);comment:IMSI" json:"imsi"`
MSISDN string `gorm:"column:msisdn;type:varchar(20);comment:MSISDN(手机号码)" json:"msisdn"`
BatchNo string `gorm:"column:batch_no;type:varchar(100);comment:批次号" json:"batch_no"`
Supplier string `gorm:"column:supplier;type:varchar(255);comment:供应商" json:"supplier"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-在库 2-已分销 3-已激活 4-已停用" json:"status"`
Supplier string `gorm:"column:supplier;type:varchar(255);comment:供应商" json:"supplier"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-在库 2-已分销 3-已激活 4-已停用" json:"status"`
ShopID *uint `gorm:"column:shop_id;index;comment:店铺ID(NULL=平台所有,有值=店铺所有)" json:"shop_id,omitempty"`
ActivatedAt *time.Time `gorm:"column:activated_at;comment:激活时间" json:"activated_at"`
ActivationStatus int `gorm:"column:activation_status;type:int;default:0;not null;comment:激活状态 0-未激活 1-已激活" json:"activation_status"`

View File

@@ -52,6 +52,9 @@ type Order struct {
// 订单角色(标识订单中的买卖关系)
PurchaseRole string `gorm:"column:purchase_role;type:varchar(50);index:idx_orders_purchase_role;comment:订单角色self_purchase/purchased_by_parent/purchased_by_platform/purchase_for_subordinate" json:"purchase_role,omitempty"`
// 订单超时信息
ExpiresAt *time.Time `gorm:"column:expires_at;comment:订单过期时间(NULL表示不过期)" json:"expires_at,omitempty"`
}
// TableName 指定表名