From 7937b0df8aa3546cbb9085a520ff096a9128d3a9 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 5 Jul 2026 20:33:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=90=E7=8E=B0=E6=8B=92?= =?UTF-8?q?=E7=BB=9D=E5=AE=A1=E6=A0=B8=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/e2e/rental_flow_test.go | 3 +- backend/internal/modules/withdrawal/admin.go | 5 ++- backend/internal/modules/withdrawal/dto.go | 2 +- .../internal/modules/withdrawal/dto_test.go | 41 +++++++++++++++++++ .../internal/modules/withdrawal/handler.go | 2 + .../internal/modules/withdrawal/service.go | 4 ++ 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 backend/internal/modules/withdrawal/dto_test.go diff --git a/backend/internal/e2e/rental_flow_test.go b/backend/internal/e2e/rental_flow_test.go index 3439c56..27ab334 100644 --- a/backend/internal/e2e/rental_flow_test.go +++ b/backend/internal/e2e/rental_flow_test.go @@ -526,8 +526,9 @@ func assertWalletAndWithdrawal(t *testing.T, walletService *wallet.Service, with assertEqual(t, "提现申请金额", req.AmountCent, int64(10000)) assertEqual(t, "提现申请状态", req.Status, "pending") + approved := true reviewed, err := withdrawalService.Review(t.Context(), adminID, req.ID, withdrawal.ReviewWithdrawalRequest{ - Approved: true, + Approved: &approved, Remark: "E2E 审核通过", }) if err != nil { diff --git a/backend/internal/modules/withdrawal/admin.go b/backend/internal/modules/withdrawal/admin.go index 75017d7..7316907 100644 --- a/backend/internal/modules/withdrawal/admin.go +++ b/backend/internal/modules/withdrawal/admin.go @@ -87,7 +87,8 @@ func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewW } now := time.Now() - if req.Approved { + approved := *req.Approved + if approved { // 审核通过,进入处理中状态 withdrawal.Status = "processing" } else { @@ -103,7 +104,7 @@ func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewW } // 如果拒绝,解冻余额 - if !req.Approved { + if !approved { if err := wallet.AppendEntries(tx, wallet.Entry{ UserID: withdrawal.UserID, Direction: "out", diff --git a/backend/internal/modules/withdrawal/dto.go b/backend/internal/modules/withdrawal/dto.go index 42a67ea..75fe421 100644 --- a/backend/internal/modules/withdrawal/dto.go +++ b/backend/internal/modules/withdrawal/dto.go @@ -61,7 +61,7 @@ type CreateWithdrawalRequest struct { } type ReviewWithdrawalRequest struct { - Approved bool `json:"approved" binding:"required"` + Approved *bool `json:"approved" binding:"required"` Remark string `json:"remark"` } diff --git a/backend/internal/modules/withdrawal/dto_test.go b/backend/internal/modules/withdrawal/dto_test.go new file mode 100644 index 0000000..f86edac --- /dev/null +++ b/backend/internal/modules/withdrawal/dto_test.go @@ -0,0 +1,41 @@ +package withdrawal + +import ( + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestReviewWithdrawalRequestBindApprovedFalse(t *testing.T) { + gin.SetMode(gin.TestMode) + + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest("POST", "/admin/withdrawals/1/review", strings.NewReader(`{"approved":false,"remark":"资料不符"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + var req ReviewWithdrawalRequest + if err := c.ShouldBindJSON(&req); err != nil { + t.Fatalf("approved=false 应该通过参数绑定: %v", err) + } + if req.Approved == nil { + t.Fatal("approved 字段应该被识别为已传入") + } + if *req.Approved { + t.Fatal("approved=false 不应该被绑定成 true") + } +} + +func TestReviewWithdrawalRequestBindMissingApproved(t *testing.T) { + gin.SetMode(gin.TestMode) + + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest("POST", "/admin/withdrawals/1/review", strings.NewReader(`{"remark":"资料不符"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + var req ReviewWithdrawalRequest + if err := c.ShouldBindJSON(&req); err == nil { + t.Fatal("缺少 approved 字段时应该参数绑定失败") + } +} diff --git a/backend/internal/modules/withdrawal/handler.go b/backend/internal/modules/withdrawal/handler.go index 67f3e75..97fff09 100644 --- a/backend/internal/modules/withdrawal/handler.go +++ b/backend/internal/modules/withdrawal/handler.go @@ -230,6 +230,8 @@ func writeError(c *gin.Context, err error) { response.BadRequest(c, "提现金额超过最大限额") case ErrWithdrawalLocked: response.BadRequest(c, "提现申请状态已锁定,无法操作") + case ErrInvalidReviewAction: + response.BadRequest(c, "审核参数无效") case ErrUnauthorized: response.Unauthorized(c, "无权限操作") default: diff --git a/backend/internal/modules/withdrawal/service.go b/backend/internal/modules/withdrawal/service.go index 4f5c535..a4eee2b 100644 --- a/backend/internal/modules/withdrawal/service.go +++ b/backend/internal/modules/withdrawal/service.go @@ -13,6 +13,7 @@ var ( ErrMinWithdrawalAmount = errors.New("amount below minimum withdrawal") ErrMaxWithdrawalAmount = errors.New("amount exceeds maximum withdrawal") ErrWithdrawalLocked = errors.New("withdrawal status locked") + ErrInvalidReviewAction = errors.New("invalid review action") ErrUnauthorized = errors.New("unauthorized") ) @@ -99,6 +100,9 @@ func (s *Service) Review(ctx context.Context, adminID, id uint64, req ReviewWith if s.repo == nil { return nil, ErrDependencyUnavailable } + if req.Approved == nil { + return nil, ErrInvalidReviewAction + } return s.repo.Review(ctx, adminID, id, req) }