feat: 归档佣金计算触发和快照变更,同步规范文档
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m40s

- 归档 OpenSpec 变更到 archive 目录
- 创建 2 个新的主规范文件:commission-trigger 和 order-commission-snapshot
- 实现订单佣金快照字段和支付自动触发
- 确保事务一致性,所有佣金操作在同一事务内完成
- 提取成本价计算为公共工具函数
This commit is contained in:
2026-01-29 14:58:35 +08:00
parent c9fee7f2f6
commit d977000a66
14 changed files with 542 additions and 66 deletions

View File

@@ -12,6 +12,10 @@ import (
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/queue"
"github.com/break/junhong_cmp_fiber/pkg/utils"
"github.com/bytedance/sonic"
"go.uber.org/zap"
"gorm.io/gorm"
)
@@ -22,6 +26,8 @@ type Service struct {
walletStore *postgres.WalletStore
purchaseValidationService *purchase_validation.Service
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore
queueClient *queue.Client
logger *zap.Logger
}
func New(
@@ -31,6 +37,8 @@ func New(
walletStore *postgres.WalletStore,
purchaseValidationService *purchase_validation.Service,
allocationConfigStore *postgres.ShopSeriesAllocationConfigStore,
queueClient *queue.Client,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
@@ -39,6 +47,8 @@ func New(
walletStore: walletStore,
purchaseValidationService: purchaseValidationService,
allocationConfigStore: allocationConfigStore,
queueClient: queueClient,
logger: logger,
}
}
@@ -67,6 +77,16 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
userID := middleware.GetUserIDFromContext(ctx)
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
var seriesID *uint
var sellerShopID *uint
var sellerCostPrice int64
if validationResult.Allocation != nil {
seriesID = &validationResult.Allocation.SeriesID
sellerShopID = &validationResult.Allocation.ShopID
sellerCostPrice = utils.CalculateCostPrice(validationResult.Allocation, validationResult.TotalPrice)
}
order := &model.Order{
BaseModel: model.BaseModel{
Creator: userID,
@@ -82,6 +102,9 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
PaymentStatus: model.PaymentStatusPending,
CommissionStatus: model.CommissionStatusPending,
CommissionConfigVersion: configVersion,
SeriesID: seriesID,
SellerShopID: sellerShopID,
SellerCostPrice: sellerCostPrice,
}
var items []*model.OrderItem
@@ -264,7 +287,7 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
}
now := time.Now()
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
result := tx.Model(&model.Wallet{}).
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version).
Updates(map[string]any{
@@ -288,6 +311,13 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
return s.activatePackage(ctx, tx, order)
})
if err != nil {
return err
}
s.enqueueCommissionCalculation(ctx, orderID)
return nil
}
func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, paymentMethod string) error {
@@ -308,7 +338,7 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
}
now := time.Now()
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.Order{}).Where("id = ?", order.ID).Updates(map[string]any{
"payment_status": model.PaymentStatusPaid,
"payment_method": paymentMethod,
@@ -319,6 +349,13 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
return s.activatePackage(ctx, tx, order)
})
if err != nil {
return err
}
s.enqueueCommissionCalculation(ctx, order.ID)
return nil
}
func (s *Service) activatePackage(ctx context.Context, tx *gorm.DB, order *model.Order) error {
@@ -373,6 +410,37 @@ func (s *Service) snapshotCommissionConfig(ctx context.Context, allocationID uin
return config.Version
}
func (s *Service) enqueueCommissionCalculation(ctx context.Context, orderID uint) {
if s.queueClient == nil {
s.logger.Warn("队列客户端未初始化,跳过佣金计算任务入队", zap.Uint("order_id", orderID))
return
}
payload := map[string]interface{}{
"order_id": orderID,
}
payloadBytes, err := sonic.Marshal(payload)
if err != nil {
s.logger.Error("佣金计算任务载荷序列化失败",
zap.Uint("order_id", orderID),
zap.Error(err))
return
}
if err := s.queueClient.EnqueueTask(ctx, constants.TaskTypeCommission, payloadBytes); err != nil {
s.logger.Error("佣金计算任务入队失败",
zap.Uint("order_id", orderID),
zap.Error(err),
zap.String("task_type", constants.TaskTypeCommission))
return
}
s.logger.Info("佣金计算任务已入队",
zap.Uint("order_id", orderID),
zap.String("task_type", constants.TaskTypeCommission))
}
func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderItem) *dto.OrderResponse {
var itemResponses []*dto.OrderItemResponse
for _, item := range items {