diff --git a/backend/internal/modules/withdrawal/admin.go b/backend/internal/modules/withdrawal/admin.go index 5b59fc1..75017d7 100644 --- a/backend/internal/modules/withdrawal/admin.go +++ b/backend/internal/modules/withdrawal/admin.go @@ -10,6 +10,7 @@ import ( "hfb_sys/backend/internal/modules/wallet" "gorm.io/gorm" + "gorm.io/gorm/clause" ) // 管理员查询提现列表 @@ -71,35 +72,32 @@ func (r *Repository) AdminFindByID(ctx context.Context, id uint64) (*WithdrawalD // 管理员审核提现 func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewWithdrawalRequest) (*WithdrawalDetailDTO, error) { - db := r.db.WithContext(ctx) - var withdrawal model.WithdrawalRequest - if err := db.First(&withdrawal, id).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrWithdrawalNotFound + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var withdrawal model.WithdrawalRequest + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&withdrawal, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrWithdrawalNotFound + } + return err } - return nil, err - } - // 只有待审核状态可以审核 - if withdrawal.Status != "pending" { - return nil, ErrWithdrawalLocked - } + // 事务内加行锁后重新校验,只有待审核状态可以审核,避免并发重复处理 + if withdrawal.Status != "pending" { + return ErrWithdrawalLocked + } - now := time.Now() + now := time.Now() + if req.Approved { + // 审核通过,进入处理中状态 + withdrawal.Status = "processing" + } else { + // 审核拒绝,解冻余额 + withdrawal.Status = "rejected" + } + withdrawal.ReviewedBy = &adminID + withdrawal.ReviewedAt = &now + withdrawal.ReviewRemark = req.Remark - if req.Approved { - // 审核通过,进入处理中状态 - withdrawal.Status = "processing" - } else { - // 审核拒绝,解冻余额 - withdrawal.Status = "rejected" - } - - withdrawal.ReviewedBy = &adminID - withdrawal.ReviewedAt = &now - withdrawal.ReviewRemark = req.Remark - - err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Save(&withdrawal).Error; err != nil { return err } @@ -129,7 +127,6 @@ func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewW return nil }) - if err != nil { return nil, err } @@ -141,28 +138,27 @@ func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewW // 管理员确认打款 func (r *Repository) ConfirmPayment(ctx context.Context, adminID, id uint64, req ConfirmPaymentRequest) (*WithdrawalDetailDTO, error) { - db := r.db.WithContext(ctx) - var withdrawal model.WithdrawalRequest - if err := db.First(&withdrawal, id).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrWithdrawalNotFound + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var withdrawal model.WithdrawalRequest + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&withdrawal, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrWithdrawalNotFound + } + return err } - return nil, err - } - // 只有处理中状态可以确认打款 - if withdrawal.Status != "processing" { - return nil, ErrWithdrawalLocked - } + // 事务内加行锁后重新校验,只有处理中状态可以确认打款,避免并发重复扣减冻结余额 + if withdrawal.Status != "processing" { + return ErrWithdrawalLocked + } - now := time.Now() - withdrawal.Status = "completed" - withdrawal.PaidBy = &adminID - withdrawal.PaidAt = &now - withdrawal.PaymentProofURL = req.PaymentProofURL - withdrawal.PaymentRemark = req.Remark + now := time.Now() + withdrawal.Status = "completed" + withdrawal.PaidBy = &adminID + withdrawal.PaidAt = &now + withdrawal.PaymentProofURL = req.PaymentProofURL + withdrawal.PaymentRemark = req.Remark - err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Save(&withdrawal).Error; err != nil { return err } @@ -182,7 +178,6 @@ func (r *Repository) ConfirmPayment(ctx context.Context, adminID, id uint64, req return nil }) - if err != nil { return nil, err } diff --git a/backend/internal/modules/withdrawal/user.go b/backend/internal/modules/withdrawal/user.go index f346b76..a033ea9 100644 --- a/backend/internal/modules/withdrawal/user.go +++ b/backend/internal/modules/withdrawal/user.go @@ -8,6 +8,7 @@ import ( "hfb_sys/backend/internal/modules/wallet" "gorm.io/gorm" + "gorm.io/gorm/clause" ) // 用户创建提现申请 @@ -146,22 +147,22 @@ func (r *Repository) FindByID(ctx context.Context, userID, id uint64) (*Withdraw // 用户取消提现 func (r *Repository) Cancel(ctx context.Context, userID, id uint64) error { - db := r.db.WithContext(ctx) - var withdrawal model.WithdrawalRequest - if err := db.Where("id = ? AND user_id = ?", id, userID). - First(&withdrawal).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrWithdrawalNotFound + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var withdrawal model.WithdrawalRequest + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + Where("id = ? AND user_id = ?", id, userID). + First(&withdrawal).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrWithdrawalNotFound + } + return err } - return err - } - // 只有待审核状态可以取消 - if withdrawal.Status != "pending" { - return ErrWithdrawalLocked - } + // 事务内加行锁后重新校验,只有待审核状态可以取消,避免与管理员审核并发重复解冻 + if withdrawal.Status != "pending" { + return ErrWithdrawalLocked + } - return db.Transaction(func(tx *gorm.DB) error { // 更新状态 if err := tx.Model(&withdrawal).Update("status", "cancelled").Error; err != nil { return err