diff --git a/backend/internal/repository/user_repo.go b/backend/internal/repository/user_repo.go index 0d8c25c6..006a5464 100644 --- a/backend/internal/repository/user_repo.go +++ b/backend/internal/repository/user_repo.go @@ -329,17 +329,20 @@ func (r *userRepository) UpdateBalance(ctx context.Context, id int64, amount flo return nil } +// DeductBalance 扣除用户余额 +// 透支策略:允许余额变为负数,确保当前请求能够完成 +// 中间件会阻止余额 <= 0 的用户发起后续请求 func (r *userRepository) DeductBalance(ctx context.Context, id int64, amount float64) error { client := clientFromContext(ctx, r.client) n, err := client.User.Update(). - Where(dbuser.IDEQ(id), dbuser.BalanceGTE(amount)). + Where(dbuser.IDEQ(id)). AddBalance(-amount). Save(ctx) if err != nil { return err } if n == 0 { - return service.ErrInsufficientBalance + return service.ErrUserNotFound } return nil } diff --git a/backend/internal/repository/user_repo_integration_test.go b/backend/internal/repository/user_repo_integration_test.go index ab2195e3..19e6d6a3 100644 --- a/backend/internal/repository/user_repo_integration_test.go +++ b/backend/internal/repository/user_repo_integration_test.go @@ -290,9 +290,14 @@ func (s *UserRepoSuite) TestDeductBalance() { func (s *UserRepoSuite) TestDeductBalance_InsufficientFunds() { user := s.mustCreateUser(&service.User{Email: "insuf@test.com", Balance: 5}) + // 透支策略:允许扣除超过余额的金额 err := s.repo.DeductBalance(s.ctx, user.ID, 999) - s.Require().Error(err, "expected error for insufficient balance") - s.Require().ErrorIs(err, service.ErrInsufficientBalance) + s.Require().NoError(err, "DeductBalance should allow overdraft") + + // 验证余额变为负数 + got, err := s.repo.GetByID(s.ctx, user.ID) + s.Require().NoError(err) + s.Require().InDelta(-994.0, got.Balance, 1e-6, "Balance should be negative after overdraft") } func (s *UserRepoSuite) TestDeductBalance_ExactAmount() { @@ -306,6 +311,19 @@ func (s *UserRepoSuite) TestDeductBalance_ExactAmount() { s.Require().InDelta(0.0, got.Balance, 1e-6) } +func (s *UserRepoSuite) TestDeductBalance_AllowsOverdraft() { + user := s.mustCreateUser(&service.User{Email: "overdraft@test.com", Balance: 5.0}) + + // 扣除超过余额的金额 - 应该成功 + err := s.repo.DeductBalance(s.ctx, user.ID, 10.0) + s.Require().NoError(err, "DeductBalance should allow overdraft") + + // 验证余额为负 + got, err := s.repo.GetByID(s.ctx, user.ID) + s.Require().NoError(err) + s.Require().InDelta(-5.0, got.Balance, 1e-6, "Balance should be -5.0 after overdraft") +} + // --- Concurrency --- func (s *UserRepoSuite) TestUpdateConcurrency() { @@ -511,6 +529,6 @@ func (s *UserRepoSuite) TestUpdateConcurrency_NotFound() { func (s *UserRepoSuite) TestDeductBalance_NotFound() { err := s.repo.DeductBalance(s.ctx, 999999, 5) s.Require().Error(err, "expected error for non-existent user") - // DeductBalance 在用户不存在时返回 ErrInsufficientBalance 因为 WHERE 条件不匹配 - s.Require().ErrorIs(err, service.ErrInsufficientBalance) + // DeductBalance 在用户不存在时返回 ErrUserNotFound + s.Require().ErrorIs(err, service.ErrUserNotFound) }