diff --git a/internal/service/order/service.go b/internal/service/order/service.go index 619ad7f..55ceee4 100644 --- a/internal/service/order/service.go +++ b/internal/service/order/service.go @@ -1120,26 +1120,31 @@ func (s *Service) createOrderWithWalletPayment(ctx context.Context, order *model } actualAmount := *order.ActualPaidAmount - // 1. 事务外:检查钱包余额(快速失败) - wallet, err := s.agentWalletStore.GetMainWallet(ctx, operatorShopID) - if err != nil { - if err == gorm.ErrRecordNotFound { - return errors.New(errors.CodeWalletNotFound, "钱包不存在") + // 事务内:加锁读取钱包 + 余额检查 + 创建订单 + 扣款 + 创建流水 + 激活套餐 + // 使用 FOR UPDATE 悲观锁替代乐观锁,避免并发请求因 version 过期导致误报余额不足 + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 1. 加锁读取钱包,并发请求排队等待,不会冲突 + var wallet model.AgentWallet + if err := tx.Set("gorm:query_option", "FOR UPDATE"). + Where("shop_id = ? AND wallet_type = ?", operatorShopID, constants.AgentWalletTypeMain). + First(&wallet).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return errors.New(errors.CodeWalletNotFound, "钱包不存在") + } + return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败") } - return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败") - } - if wallet.Balance < actualAmount { - return errors.New(errors.CodeInsufficientBalance, "余额不足") - } - // 2. 事务内:创建订单 + 扣款 + 创建流水 + 激活套餐 - err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - // 2.1 创建订单 + // 2. 余额检查(加锁后读到的是最新余额,结果准确) + if wallet.Balance < actualAmount { + return errors.New(errors.CodeInsufficientBalance, "余额不足") + } + + // 3. 创建订单 if err := tx.Create(order).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建订单失败") } - // 2.2 创建订单明细 + // 4. 创建订单明细 for _, item := range items { item.OrderID = order.ID } @@ -1147,30 +1152,24 @@ func (s *Service) createOrderWithWalletPayment(ctx context.Context, order *model return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败") } - // 2.3 扣减钱包余额(乐观锁) - result := tx.Model(&model.AgentWallet{}). - Where("id = ? AND balance >= ? AND version = ?", wallet.ID, actualAmount, wallet.Version). - Updates(map[string]any{ - "balance": gorm.Expr("balance - ?", actualAmount), - "version": gorm.Expr("version + 1"), - }) - if result.Error != nil { - return errors.Wrap(errors.CodeDatabaseError, result.Error, "扣减钱包余额失败") - } - if result.RowsAffected == 0 { - return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突") + // 5. 扣减钱包余额(FOR UPDATE 已保证串行,无需 version 条件) + if err := tx.Model(&wallet).Updates(map[string]any{ + "balance": gorm.Expr("balance - ?", actualAmount), + "version": gorm.Expr("version + 1"), + }).Error; err != nil { + return errors.Wrap(errors.CodeDatabaseError, err, "扣减钱包余额失败") } - // 2.4 创建钱包流水 + // 6. 创建钱包流水 var relatedShopID *uint if order.PurchaseRole == model.PurchaseRolePurchaseForSubordinate { relatedShopID = &buyerShopID } - if err := s.createWalletTransaction(ctx, tx, wallet, order, actualAmount, order.PurchaseRole, relatedShopID); err != nil { + if err := s.createWalletTransaction(ctx, tx, &wallet, order, actualAmount, order.PurchaseRole, relatedShopID); err != nil { return err } - // 2.5 激活套餐 + // 7. 激活套餐 if err := s.activatePackage(ctx, tx, order); err != nil { return err }