diff --git a/backend/internal/e2e/rental_flow_test.go b/backend/internal/e2e/rental_flow_test.go index 62a509f..f7fbc2b 100644 --- a/backend/internal/e2e/rental_flow_test.go +++ b/backend/internal/e2e/rental_flow_test.go @@ -53,28 +53,28 @@ func TestRentalFullFlowWithMockPayment(t *testing.T) { assertEqual(t, "支付状态", paymentDTO.Status, "paid") assertEqual(t, "支付金额", paymentDTO.AmountCent, int64(53300)) - paidOrder, err := services.order.FindAdmin(orderDTO.ID) + paidOrder, err := services.order.FindAdmin(t.Context(), orderDTO.ID) if err != nil { t.Fatalf("读取已支付订单失败: %v", err) } assertEqual(t, "支付后订单状态", paidOrder.Status, "pending_handoff") assertEqual(t, "支付后交接状态", paidOrder.HandoffStatus, "pending_owner") - if _, err := services.order.SubmitHandoff(owner.ID, orderDTO.ID, order.SubmitHandoffRequest{Content: "账号:demo,密码:demo-pass"}); err != nil { + if _, err := services.order.SubmitHandoff(t.Context(), owner.ID, orderDTO.ID, order.SubmitHandoffRequest{Content: "账号:demo,密码:demo-pass"}); err != nil { t.Fatalf("号主交接失败: %v", err) } - if err := services.order.ConfirmReceive(renter.ID, orderDTO.ID); err != nil { + if err := services.order.ConfirmReceive(t.Context(), renter.ID, orderDTO.ID); err != nil { t.Fatalf("租客确认收号失败: %v", err) } - if _, err := services.order.SubmitCheckout(renter.ID, orderDTO.ID, order.SubmitCheckoutRequest{ + if _, err := services.order.SubmitCheckout(t.Context(), renter.ID, orderDTO.ID, order.SubmitCheckoutRequest{ Content: "租客发起结账", ConsumableAmountCent: 700, CoinConsumedM: 90, }); err != nil { t.Fatalf("租客发起结账失败: %v", err) } - countered, err := services.order.CounterCheckout(owner.ID, orderDTO.ID, order.CounterCheckoutRequest{ + countered, err := services.order.CounterCheckout(t.Context(), owner.ID, orderDTO.ID, order.CounterCheckoutRequest{ ConsumableAmountCent: 1200, CoinConsumedM: 100, DepositDeductAmountCent: 3000, @@ -84,11 +84,11 @@ func TestRentalFullFlowWithMockPayment(t *testing.T) { t.Fatalf("号主反价失败: %v", err) } assertEqual(t, "反价状态", countered.Status, "countered") - if err := services.order.AcceptCheckout(renter.ID, orderDTO.ID); err != nil { + if err := services.order.AcceptCheckout(t.Context(), renter.ID, orderDTO.ID); err != nil { t.Fatalf("租客接受反价失败: %v", err) } - completed, err := services.order.FindAdmin(orderDTO.ID) + completed, err := services.order.FindAdmin(t.Context(), orderDTO.ID) if err != nil { t.Fatalf("读取完成订单失败: %v", err) } @@ -119,20 +119,20 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { if _, err := services.payment.Start(t.Context(), renter.ID, orderDTO.ID, payment.StartPaymentRequest{}, "127.0.0.1"); err != nil { t.Fatalf("启动 mock 支付失败: %v", err) } - if _, err := services.order.SubmitHandoff(owner.ID, orderDTO.ID, order.SubmitHandoffRequest{Content: "账号:demo,密码:demo-pass"}); err != nil { + if _, err := services.order.SubmitHandoff(t.Context(), owner.ID, orderDTO.ID, order.SubmitHandoffRequest{Content: "账号:demo,密码:demo-pass"}); err != nil { t.Fatalf("号主交接失败: %v", err) } - if err := services.order.ConfirmReceive(renter.ID, orderDTO.ID); err != nil { + if err := services.order.ConfirmReceive(t.Context(), renter.ID, orderDTO.ID); err != nil { t.Fatalf("租客确认收号失败: %v", err) } - if _, err := services.order.SubmitCheckout(renter.ID, orderDTO.ID, order.SubmitCheckoutRequest{ + if _, err := services.order.SubmitCheckout(t.Context(), renter.ID, orderDTO.ID, order.SubmitCheckoutRequest{ Content: "租客发起结账", ConsumableAmountCent: 700, CoinConsumedM: 90, }); err != nil { t.Fatalf("租客发起结账失败: %v", err) } - if _, err := services.order.CounterCheckout(owner.ID, orderDTO.ID, order.CounterCheckoutRequest{ + if _, err := services.order.CounterCheckout(t.Context(), owner.ID, orderDTO.ID, order.CounterCheckoutRequest{ ConsumableAmountCent: 1200, CoinConsumedM: 100, DepositDeductAmountCent: 3000, @@ -141,7 +141,7 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { t.Fatalf("号主反价失败: %v", err) } - disputeDTO, err := services.dispute.Create(renter.ID, orderDTO.ID, dispute.CreateRequest{ + disputeDTO, err := services.dispute.Create(t.Context(), renter.ID, orderDTO.ID, dispute.CreateRequest{ Type: "checkout_amount", Description: "不同意扣押金,申请客服仲裁", }) @@ -149,7 +149,7 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { t.Fatalf("创建结账争议失败: %v", err) } assertEqual(t, "争议类型", disputeDTO.Type, "checkout_dispute") - resolved, err := services.dispute.Arbitrate(adminID, disputeDTO.ID, dispute.ArbitrateRequest{ + resolved, err := services.dispute.Arbitrate(t.Context(), adminID, disputeDTO.ID, dispute.ArbitrateRequest{ Result: "release_deposit", Remark: "确认无押金扣除,退回押金", }, auditlog.Meta{IP: "127.0.0.1", RequestID: "e2e-arbitrate"}) @@ -158,7 +158,7 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { } assertEqual(t, "争议状态", resolved.Status, "resolved") - completed, err := services.order.FindAdmin(orderDTO.ID) + completed, err := services.order.FindAdmin(t.Context(), orderDTO.ID) if err != nil { t.Fatalf("读取仲裁后订单失败: %v", err) } @@ -209,15 +209,15 @@ func newFlowServices(db *gorm.DB) flowServices { configRepo := paymentconfig.NewRepository(db, &paymentconfig.MockEncryptor{}) paymentRepo := payment.NewRepository(db, configRepo, orderRepo, walletRepo) disputeRepo := dispute.NewRepository(db) - orderRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) + orderRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } return refund.Status, nil }) - disputeRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) + disputeRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } @@ -364,7 +364,7 @@ func seedRealname(t *testing.T, db *gorm.DB, userID uint64, name string, verifie func seedMockPaymentConfig(t *testing.T, service *paymentconfig.Service, adminID uint64) { t.Helper() - _, err := service.Create(paymentconfig.CreateRequest{ + _, err := service.Create(t.Context(), paymentconfig.CreateRequest{ Name: "E2E Mock 支付", Provider: "mock", MerchantID: "mock-e2e", @@ -424,14 +424,14 @@ func createListingUnderReview(t *testing.T, service *listing.Service, ownerID ui func adjustAndApproveListing(t *testing.T, service *listing.Service, adminID uint64, listingID uint64) *listing.ListingDTO { t.Helper() - adjusted, err := service.AdjustReviewPrice(adminID, listingID, listing.AdminPriceAdjustRequest{ + adjusted, err := service.AdjustReviewPrice(t.Context(), adminID, listingID, listing.AdminPriceAdjustRequest{ BuyerTotalPriceCent: 38300, Reason: "E2E 审核调价", }, auditlog.Meta{IP: "127.0.0.1", RequestID: "e2e-adjust-price"}) if err != nil { t.Fatalf("审核调价失败: %v", err) } - approved, err := service.Approve(adjusted.ID) + approved, err := service.Approve(t.Context(), adjusted.ID) if err != nil { t.Fatalf("审核通过失败: %v", err) } @@ -441,7 +441,7 @@ func adjustAndApproveListing(t *testing.T, service *listing.Service, adminID uin func mustCreateOrder(t *testing.T, service *order.Service, renterID uint64, listingID uint64) *order.OrderDTO { t.Helper() - dto, err := service.Create(renterID, order.CreateRequest{ListingID: listingID}) + dto, err := service.Create(t.Context(), renterID, order.CreateRequest{ListingID: listingID}) if err != nil { t.Fatalf("创建订单失败: %v", err) } diff --git a/backend/internal/modules/chat/handler.go b/backend/internal/modules/chat/handler.go index 04c1057..971961b 100644 --- a/backend/internal/modules/chat/handler.go +++ b/backend/internal/modules/chat/handler.go @@ -37,7 +37,7 @@ func (h *Handler) AdminList(c *gin.Context) { filter := c.DefaultQuery("filter", "all") page, pageSize := parsePagination(c) principal := Principal{Type: "admin", ID: adminID} - result, err := h.service.ListConversationsWithFilter(principal, page, pageSize, filter) + result, err := h.service.ListConversationsWithFilter(c.Request.Context(), principal, page, pageSize, filter) if err != nil { writeChatError(c, err) return @@ -73,7 +73,7 @@ func (h *Handler) OrderConversation(c *gin.Context) { if !ok { return } - item, err := h.service.FindOrderConversation(userID, orderID) + item, err := h.service.FindOrderConversation(c.Request.Context(), userID, orderID) if err != nil { writeChatError(c, err) return @@ -87,7 +87,7 @@ func (h *Handler) EnsureSupportConversation(c *gin.Context) { response.Unauthorized(c, "缺少用户上下文") return } - item, err := h.service.EnsureSupportConversation(userID) + item, err := h.service.EnsureSupportConversation(c.Request.Context(), userID) if err != nil { writeChatError(c, err) return @@ -165,7 +165,7 @@ func (h *Handler) AdminTransfer(c *gin.Context) { return } principal := Principal{Type: "admin", ID: adminID} - if err := h.service.TransferConversation(principal, id, req); err != nil { + if err := h.service.TransferConversation(c.Request.Context(), principal, id, req); err != nil { writeChatError(c, err) return } @@ -173,7 +173,7 @@ func (h *Handler) AdminTransfer(c *gin.Context) { } func (h *Handler) AdminSupportAdmins(c *gin.Context) { - admins, err := h.service.GetAvailableSupportAdmins() + admins, err := h.service.GetAvailableSupportAdmins(c.Request.Context()) if err != nil { writeChatError(c, err) return @@ -197,7 +197,7 @@ func (h *Handler) AdminUpdateRemark(c *gin.Context) { return } principal := Principal{Type: "admin", ID: adminID} - if err := h.service.UpdateRemark(principal, id, req); err != nil { + if err := h.service.UpdateRemark(c.Request.Context(), principal, id, req); err != nil { writeChatError(c, err) return } @@ -210,7 +210,7 @@ func (h *Handler) AdminListQuickReplies(c *gin.Context) { response.Unauthorized(c, "缺少管理员上下文") return } - replies, err := h.service.ListQuickReplies(adminID) + replies, err := h.service.ListQuickReplies(c.Request.Context(), adminID) if err != nil { writeChatError(c, err) return @@ -229,7 +229,7 @@ func (h *Handler) AdminCreateQuickReply(c *gin.Context) { response.BadRequest(c, "标题和内容不能为空") return } - reply, err := h.service.CreateQuickReply(adminID, req) + reply, err := h.service.CreateQuickReply(c.Request.Context(), adminID, req) if err != nil { writeChatError(c, err) return @@ -252,7 +252,7 @@ func (h *Handler) AdminUpdateQuickReply(c *gin.Context) { response.BadRequest(c, "请求参数错误") return } - if err := h.service.UpdateQuickReply(adminID, id, req); err != nil { + if err := h.service.UpdateQuickReply(c.Request.Context(), adminID, id, req); err != nil { writeChatError(c, err) return } @@ -269,7 +269,7 @@ func (h *Handler) AdminDeleteQuickReply(c *gin.Context) { if !ok { return } - if err := h.service.DeleteQuickReply(adminID, id); err != nil { + if err := h.service.DeleteQuickReply(c.Request.Context(), adminID, id); err != nil { writeChatError(c, err) return } @@ -277,7 +277,7 @@ func (h *Handler) AdminDeleteQuickReply(c *gin.Context) { } func (h *Handler) AdminGetAutoWelcome(c *gin.Context) { - message := h.service.GetAutoWelcomeMessage() + message := h.service.GetAutoWelcomeMessage(c.Request.Context()) response.OK(c, gin.H{"message": message}) } @@ -289,7 +289,7 @@ func (h *Handler) AdminUpdateAutoWelcome(c *gin.Context) { response.BadRequest(c, "话术内容不能为空") return } - if err := h.service.UpdateAutoWelcomeMessage(req.Message); err != nil { + if err := h.service.UpdateAutoWelcomeMessage(c.Request.Context(), req.Message); err != nil { writeChatError(c, err) return } @@ -298,7 +298,7 @@ func (h *Handler) AdminUpdateAutoWelcome(c *gin.Context) { func (h *Handler) list(c *gin.Context, principal Principal) { page, pageSize := parsePagination(c) - result, err := h.service.ListConversations(principal, page, pageSize) + result, err := h.service.ListConversations(c.Request.Context(), principal, page, pageSize) if err != nil { writeChatError(c, err) return @@ -311,7 +311,7 @@ func (h *Handler) detail(c *gin.Context, principal Principal) { if !ok { return } - item, err := h.service.FindConversation(principal, id) + item, err := h.service.FindConversation(c.Request.Context(), principal, id) if err != nil { writeChatError(c, err) return @@ -325,7 +325,7 @@ func (h *Handler) messages(c *gin.Context, principal Principal) { return } page, pageSize := parsePagination(c) - result, err := h.service.Messages(principal, id, page, pageSize) + result, err := h.service.Messages(c.Request.Context(), principal, id, page, pageSize) if err != nil { writeChatError(c, err) return @@ -343,7 +343,7 @@ func (h *Handler) send(c *gin.Context, principal Principal) { response.BadRequest(c, "消息格式不正确") return } - message, err := h.service.SendMessage(principal, id, req) + message, err := h.service.SendMessage(c.Request.Context(), principal, id, req) if err != nil { writeChatError(c, err) return @@ -356,7 +356,7 @@ func (h *Handler) markRead(c *gin.Context, principal Principal) { if !ok { return } - if err := h.service.MarkRead(principal, id); err != nil { + if err := h.service.MarkRead(c.Request.Context(), principal, id); err != nil { writeChatError(c, err) return } diff --git a/backend/internal/modules/chat/repository.go b/backend/internal/modules/chat/repository.go index b2d4e8a..0405709 100644 --- a/backend/internal/modules/chat/repository.go +++ b/backend/internal/modules/chat/repository.go @@ -1,6 +1,7 @@ package chat import ( + "context" "encoding/json" "errors" "fmt" @@ -118,10 +119,11 @@ func (r *Repository) NotifyNewConversation(conversationID uint64) { }) } -func (r *Repository) ListConversations(principal Principal, page, pageSize int) (*PaginatedResult, error) { +func (r *Repository) ListConversations(ctx context.Context, principal Principal, page, pageSize int) (*PaginatedResult, error) { page, pageSize = normalizePagination(page, pageSize) + db := r.db.WithContext(ctx) var total int64 - countDB := r.db.Table("chat_conversations AS c"). + countDB := db.Table("chat_conversations AS c"). Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id"). Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID) if err := countDB.Count(&total).Error; err != nil { @@ -130,7 +132,7 @@ func (r *Repository) ListConversations(principal Principal, page, pageSize int) var rows []conversationRow offset := (page - 1) * pageSize - err := r.conversationQuery(principal). + err := r.conversationQuery(ctx, principal). Order("COALESCE(c.last_message_at, c.created_at) DESC, c.id DESC"). Offset(offset). Limit(pageSize). @@ -145,17 +147,18 @@ func (r *Repository) ListConversations(principal Principal, page, pageSize int) return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil } -func (r *Repository) FindConversation(principal Principal, id uint64) (*ConversationDTO, error) { +func (r *Repository) FindConversation(ctx context.Context, principal Principal, id uint64) (*ConversationDTO, error) { + db := r.db.WithContext(ctx) // 管理员可以查看任意会话,无需是 participant if principal.Type == "admin" { var conversation model.ChatConversation - if err := r.db.First(&conversation, id).Error; err != nil { + if err := db.First(&conversation, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrConversationNotFound } return nil, err } - participants, err := r.participants(conversation.ID) + participants, err := r.participants(ctx, conversation.ID) if err != nil { return nil, err } @@ -179,14 +182,14 @@ func (r *Repository) FindConversation(principal Principal, id uint64) (*Conversa // 普通用户需要是 participant var row conversationRow - err := r.conversationQuery(principal).Where("c.id = ?", id).First(&row).Error + err := r.conversationQuery(ctx, principal).Where("c.id = ?", id).First(&row).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrConversationNotFound } return nil, err } - participants, err := r.participants(row.ID) + participants, err := r.participants(ctx, row.ID) if err != nil { return nil, err } @@ -194,17 +197,17 @@ func (r *Repository) FindConversation(principal Principal, id uint64) (*Conversa return &dto, nil } -func (r *Repository) FindOrderConversation(userID uint64, orderID uint64) (*ConversationDTO, error) { +func (r *Repository) FindOrderConversation(ctx context.Context, userID uint64, orderID uint64) (*ConversationDTO, error) { var row conversationRow principal := Principal{Type: "user", ID: userID} - err := r.conversationQuery(principal).Where("c.order_id = ?", orderID).First(&row).Error + err := r.conversationQuery(ctx, principal).Where("c.order_id = ?", orderID).First(&row).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrConversationNotFound } return nil, err } - participants, err := r.participants(row.ID) + participants, err := r.participants(ctx, row.ID) if err != nil { return nil, err } @@ -212,9 +215,9 @@ func (r *Repository) FindOrderConversation(userID uint64, orderID uint64) (*Conv return &dto, nil } -func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO, error) { +func (r *Repository) EnsureSupportConversation(ctx context.Context, userID uint64) (*ConversationDTO, error) { var conversationID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var existing model.ChatConversation err := tx.Table("chat_conversations AS c"). Select("c.*"). @@ -288,7 +291,7 @@ func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO, if err != nil { return nil, err } - item, err := r.FindConversation(Principal{Type: "user", ID: userID}, conversationID) + item, err := r.FindConversation(ctx, Principal{Type: "user", ID: userID}, conversationID) if err != nil { return nil, err } @@ -296,18 +299,19 @@ func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO, return item, nil } -func (r *Repository) Messages(principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) { +func (r *Repository) Messages(ctx context.Context, principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) { page, pageSize = normalizePagination(page, pageSize) + db := r.db.WithContext(ctx) // 管理员可以查看任意会话的消息,普通用户需要是 participant if principal.Type != "admin" { - if _, err := r.findParticipant(r.db, principal, conversationID, false); err != nil { + if _, err := r.findParticipant(db, principal, conversationID, false); err != nil { return nil, err } } else { // 管理员需要验证会话存在 var count int64 - if err := r.db.Model(&model.ChatConversation{}).Where("id = ?", conversationID).Count(&count).Error; err != nil { + if err := db.Model(&model.ChatConversation{}).Where("id = ?", conversationID).Count(&count).Error; err != nil { return nil, err } if count == 0 { @@ -316,28 +320,28 @@ func (r *Repository) Messages(principal Principal, conversationID uint64, page, } var total int64 - if err := r.db.Model(&model.ChatMessage{}).Where("conversation_id = ?", conversationID).Count(&total).Error; err != nil { + if err := db.Model(&model.ChatMessage{}).Where("conversation_id = ?", conversationID).Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []model.ChatMessage - if err := r.db.Where("conversation_id = ?", conversationID). + if err := db.Where("conversation_id = ?", conversationID). Order("id ASC"). Offset(offset). Limit(pageSize). Find(&rows).Error; err != nil { return nil, err } - items, err := r.toMessageDTOs(principal, rows) + items, err := r.toMessageDTOs(ctx, principal, rows) if err != nil { return nil, err } return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil } -func (r *Repository) SendMessage(principal Principal, conversationID uint64, req SendMessageRequest) (*MessageDTO, error) { +func (r *Repository) SendMessage(ctx context.Context, principal Principal, conversationID uint64, req SendMessageRequest) (*MessageDTO, error) { var messageID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var conversation model.ChatConversation if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&conversation, conversationID).Error; err != nil { return err @@ -409,10 +413,10 @@ func (r *Repository) SendMessage(principal Principal, conversationID uint64, req return nil, err } var message model.ChatMessage - if err := r.db.First(&message, messageID).Error; err != nil { + if err := r.db.WithContext(ctx).First(&message, messageID).Error; err != nil { return nil, err } - items, err := r.toMessageDTOs(principal, []model.ChatMessage{message}) + items, err := r.toMessageDTOs(ctx, principal, []model.ChatMessage{message}) if err != nil { return nil, err } @@ -442,8 +446,8 @@ func (r *Repository) SendMessage(principal Principal, conversationID uint64, req return &items[0], nil } -func (r *Repository) MarkRead(principal Principal, conversationID uint64) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *Repository) MarkRead(ctx context.Context, principal Principal, conversationID uint64) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 管理员可以不是 participant,直接返回成功 if principal.Type == "admin" { // 尝试查找 participant 记录,如果有就更新 @@ -471,8 +475,8 @@ func (r *Repository) MarkRead(principal Principal, conversationID uint64) error }) } -func (r *Repository) conversationQuery(principal Principal) *gorm.DB { - return r.db.Table("chat_conversations AS c"). +func (r *Repository) conversationQuery(ctx context.Context, principal Principal) *gorm.DB { + return r.db.WithContext(ctx).Table("chat_conversations AS c"). Select(`c.id, c.order_id, c.type, c.title, c.status, c.last_message_id, c.last_message_preview, c.last_message_at, c.created_at, c.updated_at, cp.role, ( @@ -503,12 +507,12 @@ func (r *Repository) findParticipant(tx *gorm.DB, principal Principal, conversat return &participant, nil } -func (r *Repository) participants(conversationID uint64) ([]ParticipantDTO, error) { +func (r *Repository) participants(ctx context.Context, conversationID uint64) ([]ParticipantDTO, error) { var rows []model.ChatParticipant - if err := r.db.Where("conversation_id = ?", conversationID).Order("id ASC").Find(&rows).Error; err != nil { + if err := r.db.WithContext(ctx).Where("conversation_id = ?", conversationID).Order("id ASC").Find(&rows).Error; err != nil { return nil, err } - userNames, userAvatars, adminNames, err := r.participantNames(rows) + userNames, userAvatars, adminNames, err := r.participantNames(ctx, rows) if err != nil { return nil, err } @@ -538,7 +542,7 @@ func (r *Repository) participants(conversationID uint64) ([]ParticipantDTO, erro return items, nil } -func (r *Repository) toMessageDTOs(principal Principal, rows []model.ChatMessage) ([]MessageDTO, error) { +func (r *Repository) toMessageDTOs(ctx context.Context, principal Principal, rows []model.ChatMessage) ([]MessageDTO, error) { userIDs := make([]uint64, 0) adminIDs := make([]uint64, 0) for _, row := range rows { @@ -549,11 +553,11 @@ func (r *Repository) toMessageDTOs(principal Principal, rows []model.ChatMessage adminIDs = append(adminIDs, row.SenderID) } } - userNames, userAvatars, err := r.userNames(userIDs) + userNames, userAvatars, err := r.userNames(ctx, userIDs) if err != nil { return nil, err } - adminNames, err := r.adminNames(adminIDs) + adminNames, err := r.adminNames(ctx, adminIDs) if err != nil { return nil, err } @@ -590,7 +594,7 @@ func (r *Repository) toMessageDTOs(principal Principal, rows []model.ChatMessage return items, nil } -func (r *Repository) participantNames(rows []model.ChatParticipant) (map[uint64]string, map[uint64]string, map[uint64]string, error) { +func (r *Repository) participantNames(ctx context.Context, rows []model.ChatParticipant) (map[uint64]string, map[uint64]string, map[uint64]string, error) { userIDs := make([]uint64, 0) adminIDs := make([]uint64, 0) for _, row := range rows { @@ -601,25 +605,25 @@ func (r *Repository) participantNames(rows []model.ChatParticipant) (map[uint64] adminIDs = append(adminIDs, row.ParticipantID) } } - userNames, userAvatars, err := r.userNames(userIDs) + userNames, userAvatars, err := r.userNames(ctx, userIDs) if err != nil { return nil, nil, nil, err } - adminNames, err := r.adminNames(adminIDs) + adminNames, err := r.adminNames(ctx, adminIDs) if err != nil { return nil, nil, nil, err } return userNames, userAvatars, adminNames, nil } -func (r *Repository) userNames(ids []uint64) (map[uint64]string, map[uint64]string, error) { +func (r *Repository) userNames(ctx context.Context, ids []uint64) (map[uint64]string, map[uint64]string, error) { names := map[uint64]string{} avatars := map[uint64]string{} if len(ids) == 0 { return names, avatars, nil } var users []model.User - if err := r.db.Where("id IN ?", uniqueIDs(ids)).Find(&users).Error; err != nil { + if err := r.db.WithContext(ctx).Where("id IN ?", uniqueIDs(ids)).Find(&users).Error; err != nil { return nil, nil, err } for _, user := range users { @@ -633,13 +637,13 @@ func (r *Repository) userNames(ids []uint64) (map[uint64]string, map[uint64]stri return names, avatars, nil } -func (r *Repository) adminNames(ids []uint64) (map[uint64]string, error) { +func (r *Repository) adminNames(ctx context.Context, ids []uint64) (map[uint64]string, error) { names := map[uint64]string{} if len(ids) == 0 { return names, nil } var admins []model.AdminUser - if err := r.db.Where("id IN ?", uniqueIDs(ids)).Find(&admins).Error; err != nil { + if err := r.db.WithContext(ctx).Where("id IN ?", uniqueIDs(ids)).Find(&admins).Error; err != nil { return nil, err } for _, admin := range admins { @@ -802,8 +806,8 @@ func messagePreview(content string, attachments []string) string { } // TransferConversation 转接会话给其他客服 -func (r *Repository) TransferConversation(principal Principal, conversationID uint64, toAdminID uint64) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *Repository) TransferConversation(ctx context.Context, principal Principal, conversationID uint64, toAdminID uint64) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 验证当前操作者是会话参与者 if _, err := r.findParticipant(tx, principal, conversationID, false); err != nil { return err @@ -855,7 +859,8 @@ func (r *Repository) TransferConversation(principal Principal, conversationID ui } // GetAvailableSupportAdmins 获取可用客服列表及其会话数 -func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) { +func (r *Repository) GetAvailableSupportAdmins(ctx context.Context) ([]SupportAdminDTO, error) { + db := r.db.WithContext(ctx) // 仅展示客服角色管理员,超级管理员即使有 chat:view 权限也不作为客服候选。 type adminRow struct { ID uint64 @@ -863,7 +868,7 @@ func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) { SupportStatus string } var admins []adminRow - err := r.db.Table("admin_users AS au"). + err := db.Table("admin_users AS au"). Select("au.id, COALESCE(NULLIF(au.nickname, ''), au.username) AS nickname, au.support_status"). Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id"). Joins("JOIN roles AS r ON r.id = aur.role_id"). @@ -885,7 +890,7 @@ func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) { adminIDs[i] = a.ID } if len(adminIDs) > 0 { - r.db.Table("chat_participants"). + db.Table("chat_participants"). Select("participant_id AS admin_id, COUNT(*) AS count"). Where("participant_type = ? AND role = ? AND participant_id IN ?", "admin", "support", adminIDs). Group("participant_id"). @@ -909,19 +914,20 @@ func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) { } // ListConversationsWithFilter 支持筛选的会话列表 -func (r *Repository) ListConversationsWithFilter(principal Principal, page, pageSize int, filter string) (*PaginatedResult, error) { +func (r *Repository) ListConversationsWithFilter(ctx context.Context, principal Principal, page, pageSize int, filter string) (*PaginatedResult, error) { page, pageSize = normalizePagination(page, pageSize) + db := r.db.WithContext(ctx) // 管理员在"全部"模式下直接查询所有会话 if principal.Type == "admin" && filter == "all" { var total int64 - if err := r.db.Model(&model.ChatConversation{}).Count(&total).Error; err != nil { + if err := db.Model(&model.ChatConversation{}).Count(&total).Error; err != nil { return nil, err } var conversations []model.ChatConversation offset := (page - 1) * pageSize - if err := r.db.Order("COALESCE(last_message_at, created_at) DESC, id DESC"). + if err := db.Order("COALESCE(last_message_at, created_at) DESC, id DESC"). Offset(offset). Limit(pageSize). Find(&conversations).Error; err != nil { @@ -930,7 +936,7 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page items := make([]ConversationDTO, 0, len(conversations)) for _, conv := range conversations { - participants, err := r.participants(conv.ID) + participants, err := r.participants(ctx, conv.ID) if err != nil { return nil, err } @@ -955,7 +961,7 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page // 其他情况使用原有逻辑 var total int64 - countDB := r.db.Table("chat_conversations AS c"). + countDB := db.Table("chat_conversations AS c"). Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id") switch filter { @@ -965,7 +971,7 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page case "unassigned": // 未分配客服的会话 countDB = countDB.Where("c.id NOT IN (?)", - r.db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support")) + db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support")) default: // 普通用户的全部会话 countDB = countDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID) @@ -978,13 +984,13 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page var rows []conversationRow offset := (page - 1) * pageSize - queryDB := r.conversationQuery(principal) + queryDB := r.conversationQuery(ctx, principal) switch filter { case "mine": queryDB = queryDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID) case "unassigned": queryDB = queryDB.Where("c.id NOT IN (?)", - r.db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support")) + db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support")) default: // 普通用户的全部会话 queryDB = queryDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID) @@ -1001,7 +1007,7 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page items := make([]ConversationDTO, 0, len(rows)) for _, row := range rows { - participants, err := r.participants(row.ID) + participants, err := r.participants(ctx, row.ID) if err != nil { return nil, err } @@ -1048,16 +1054,16 @@ func encodeStringList(items []string) datatypes.JSON { } // UpdateRemark 更新会话备注 -func (r *Repository) UpdateRemark(principal Principal, conversationID uint64, remark string) error { - return r.db.Model(&model.ChatParticipant{}). +func (r *Repository) UpdateRemark(ctx context.Context, principal Principal, conversationID uint64, remark string) error { + return r.db.WithContext(ctx).Model(&model.ChatParticipant{}). Where("conversation_id = ? AND participant_type = ? AND participant_id = ?", conversationID, principal.Type, principal.ID). Update("remark", remark).Error } // ListQuickReplies 获取快捷回复列表(个人 + 全局) -func (r *Repository) ListQuickReplies(adminID uint64) ([]QuickReplyDTO, error) { +func (r *Repository) ListQuickReplies(ctx context.Context, adminID uint64) ([]QuickReplyDTO, error) { var replies []model.ChatQuickReply - err := r.db.Where("admin_user_id = ? OR admin_user_id = 0", adminID). + err := r.db.WithContext(ctx).Where("admin_user_id = ? OR admin_user_id = 0", adminID). Order("admin_user_id DESC, sort_order ASC, id ASC"). Find(&replies).Error if err != nil { @@ -1078,7 +1084,7 @@ func (r *Repository) ListQuickReplies(adminID uint64) ([]QuickReplyDTO, error) { } // CreateQuickReply 创建快捷回复 -func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) { +func (r *Repository) CreateQuickReply(ctx context.Context, adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) { ownerID := adminID if req.IsGlobal { ownerID = 0 @@ -1089,7 +1095,7 @@ func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyReques Content: req.Content, SortOrder: req.SortOrder, } - if err := r.db.Create(&reply).Error; err != nil { + if err := r.db.WithContext(ctx).Create(&reply).Error; err != nil { return nil, err } return &QuickReplyDTO{ @@ -1103,8 +1109,8 @@ func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyReques } // UpdateQuickReply 更新快捷回复 -func (r *Repository) UpdateQuickReply(adminID uint64, replyID uint64, req UpdateQuickReplyRequest) error { - query := r.db.Model(&model.ChatQuickReply{}).Where("id = ? AND (admin_user_id = ? OR admin_user_id = 0)", replyID, adminID) +func (r *Repository) UpdateQuickReply(ctx context.Context, adminID uint64, replyID uint64, req UpdateQuickReplyRequest) error { + query := r.db.WithContext(ctx).Model(&model.ChatQuickReply{}).Where("id = ? AND (admin_user_id = ? OR admin_user_id = 0)", replyID, adminID) updates := map[string]interface{}{} if req.Title != "" { updates["title"] = req.Title @@ -1122,23 +1128,23 @@ func (r *Repository) UpdateQuickReply(adminID uint64, replyID uint64, req Update } // DeleteQuickReply 删除快捷回复 -func (r *Repository) DeleteQuickReply(adminID uint64, replyID uint64) error { - return r.db.Where("id = ? AND admin_user_id = ?", replyID, adminID). +func (r *Repository) DeleteQuickReply(ctx context.Context, adminID uint64, replyID uint64) error { + return r.db.WithContext(ctx).Where("id = ? AND admin_user_id = ?", replyID, adminID). Delete(&model.ChatQuickReply{}).Error } // GetAutoWelcomeMessage 获取建群自动话术 -func (r *Repository) GetAutoWelcomeMessage() string { +func (r *Repository) GetAutoWelcomeMessage(ctx context.Context) string { var cfg model.SystemConfig - if err := r.db.Where("`key` = ?", "chat.auto_welcome_message").First(&cfg).Error; err != nil { + if err := r.db.WithContext(ctx).Where("`key` = ?", "chat.auto_welcome_message").First(&cfg).Error; err != nil { return "欢迎加入订单群聊!如有任何问题,请随时沟通。" } return cfg.Value } // UpdateAutoWelcomeMessage 更新建群自动话术 -func (r *Repository) UpdateAutoWelcomeMessage(message string) error { - return r.db.Model(&model.SystemConfig{}). +func (r *Repository) UpdateAutoWelcomeMessage(ctx context.Context, message string) error { + return r.db.WithContext(ctx).Model(&model.SystemConfig{}). Where("`key` = ?", "chat.auto_welcome_message"). Update("value", message).Error } diff --git a/backend/internal/modules/chat/service.go b/backend/internal/modules/chat/service.go index 5f187a5..409aa98 100644 --- a/backend/internal/modules/chat/service.go +++ b/backend/internal/modules/chat/service.go @@ -1,6 +1,7 @@ package chat import ( + "context" "errors" "net/url" "strings" @@ -21,45 +22,45 @@ func NewService(repo *Repository) *Service { return &Service{repo: repo} } -func (s *Service) ListConversations(principal Principal, page, pageSize int) (*PaginatedResult, error) { +func (s *Service) ListConversations(ctx context.Context, principal Principal, page, pageSize int) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListConversations(principal, page, pageSize) + return s.repo.ListConversations(ctx, principal, page, pageSize) } -func (s *Service) FindConversation(principal Principal, id uint64) (*ConversationDTO, error) { +func (s *Service) FindConversation(ctx context.Context, principal Principal, id uint64) (*ConversationDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindConversation(principal, id) + return s.repo.FindConversation(ctx, principal, id) } -func (s *Service) FindOrderConversation(userID uint64, orderID uint64) (*ConversationDTO, error) { +func (s *Service) FindOrderConversation(ctx context.Context, userID uint64, orderID uint64) (*ConversationDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindOrderConversation(userID, orderID) + return s.repo.FindOrderConversation(ctx, userID, orderID) } -func (s *Service) EnsureSupportConversation(userID uint64) (*ConversationDTO, error) { +func (s *Service) EnsureSupportConversation(ctx context.Context, userID uint64) (*ConversationDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if userID == 0 { return nil, ErrPermissionDenied } - return s.repo.EnsureSupportConversation(userID) + return s.repo.EnsureSupportConversation(ctx, userID) } -func (s *Service) Messages(principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) { +func (s *Service) Messages(ctx context.Context, principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.Messages(principal, conversationID, page, pageSize) + return s.repo.Messages(ctx, principal, conversationID, page, pageSize) } -func (s *Service) SendMessage(principal Principal, conversationID uint64, req SendMessageRequest) (*MessageDTO, error) { +func (s *Service) SendMessage(ctx context.Context, principal Principal, conversationID uint64, req SendMessageRequest) (*MessageDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } @@ -75,7 +76,7 @@ func (s *Service) SendMessage(principal Principal, conversationID uint64, req Se if !ok { return nil, ErrInvalidMessage } - return s.repo.SendMessage(principal, conversationID, req) + return s.repo.SendMessage(ctx, principal, conversationID, req) } func normalizeAttachmentURLS(items []string) ([]string, bool) { @@ -112,52 +113,52 @@ func normalizeAttachmentURLS(items []string) ([]string, bool) { return result, true } -func (s *Service) MarkRead(principal Principal, conversationID uint64) error { +func (s *Service) MarkRead(ctx context.Context, principal Principal, conversationID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.MarkRead(principal, conversationID) + return s.repo.MarkRead(ctx, principal, conversationID) } -func (s *Service) TransferConversation(principal Principal, conversationID uint64, req TransferRequest) error { +func (s *Service) TransferConversation(ctx context.Context, principal Principal, conversationID uint64, req TransferRequest) error { if s.repo == nil { return ErrDependencyUnavailable } if conversationID == 0 || req.ToAdminID == 0 { return ErrInvalidMessage } - return s.repo.TransferConversation(principal, conversationID, req.ToAdminID) + return s.repo.TransferConversation(ctx, principal, conversationID, req.ToAdminID) } -func (s *Service) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) { +func (s *Service) GetAvailableSupportAdmins(ctx context.Context) ([]SupportAdminDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.GetAvailableSupportAdmins() + return s.repo.GetAvailableSupportAdmins(ctx) } -func (s *Service) ListConversationsWithFilter(principal Principal, page, pageSize int, filter string) (*PaginatedResult, error) { +func (s *Service) ListConversationsWithFilter(ctx context.Context, principal Principal, page, pageSize int, filter string) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListConversationsWithFilter(principal, page, pageSize, filter) + return s.repo.ListConversationsWithFilter(ctx, principal, page, pageSize, filter) } -func (s *Service) UpdateRemark(principal Principal, conversationID uint64, req UpdateRemarkRequest) error { +func (s *Service) UpdateRemark(ctx context.Context, principal Principal, conversationID uint64, req UpdateRemarkRequest) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.UpdateRemark(principal, conversationID, req.Remark) + return s.repo.UpdateRemark(ctx, principal, conversationID, req.Remark) } -func (s *Service) ListQuickReplies(adminID uint64) ([]QuickReplyDTO, error) { +func (s *Service) ListQuickReplies(ctx context.Context, adminID uint64) ([]QuickReplyDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListQuickReplies(adminID) + return s.repo.ListQuickReplies(ctx, adminID) } -func (s *Service) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) { +func (s *Service) CreateQuickReply(ctx context.Context, adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } @@ -166,33 +167,33 @@ func (s *Service) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest) if req.Title == "" || req.Content == "" { return nil, ErrInvalidMessage } - return s.repo.CreateQuickReply(adminID, req) + return s.repo.CreateQuickReply(ctx, adminID, req) } -func (s *Service) UpdateQuickReply(adminID uint64, replyID uint64, req UpdateQuickReplyRequest) error { +func (s *Service) UpdateQuickReply(ctx context.Context, adminID uint64, replyID uint64, req UpdateQuickReplyRequest) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.UpdateQuickReply(adminID, replyID, req) + return s.repo.UpdateQuickReply(ctx, adminID, replyID, req) } -func (s *Service) DeleteQuickReply(adminID uint64, replyID uint64) error { +func (s *Service) DeleteQuickReply(ctx context.Context, adminID uint64, replyID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.DeleteQuickReply(adminID, replyID) + return s.repo.DeleteQuickReply(ctx, adminID, replyID) } -func (s *Service) GetAutoWelcomeMessage() string { +func (s *Service) GetAutoWelcomeMessage(ctx context.Context) string { if s.repo == nil { return "" } - return s.repo.GetAutoWelcomeMessage() + return s.repo.GetAutoWelcomeMessage(ctx) } -func (s *Service) UpdateAutoWelcomeMessage(message string) error { +func (s *Service) UpdateAutoWelcomeMessage(ctx context.Context, message string) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.UpdateAutoWelcomeMessage(message) + return s.repo.UpdateAutoWelcomeMessage(ctx, message) } diff --git a/backend/internal/modules/dispute/handler.go b/backend/internal/modules/dispute/handler.go index bf3d4fe..47f8e6f 100644 --- a/backend/internal/modules/dispute/handler.go +++ b/backend/internal/modules/dispute/handler.go @@ -34,7 +34,7 @@ func (h *Handler) Create(c *gin.Context) { response.BadRequest(c, "申诉信息不完整") return } - item, err := h.service.Create(userID, orderID, req) + item, err := h.service.Create(c.Request.Context(), userID, orderID, req) if err != nil { writeDisputeError(c, err) return @@ -49,7 +49,7 @@ func (h *Handler) List(c *gin.Context) { return } page, pageSize := parsePagination(c) - result, err := h.service.ListForUser(userID, page, pageSize) + result, err := h.service.ListForUser(c.Request.Context(), userID, page, pageSize) if err != nil { writeDisputeError(c, err) return @@ -67,7 +67,7 @@ func (h *Handler) Detail(c *gin.Context) { if !ok { return } - item, err := h.service.FindForUser(userID, id) + item, err := h.service.FindForUser(c.Request.Context(), userID, id) if err != nil { writeDisputeError(c, err) return @@ -77,7 +77,7 @@ func (h *Handler) Detail(c *gin.Context) { func (h *Handler) AdminList(c *gin.Context) { page, pageSize := parsePagination(c) - result, err := h.service.ListAdmin(page, pageSize) + result, err := h.service.ListAdmin(c.Request.Context(), page, pageSize) if err != nil { writeDisputeError(c, err) return @@ -100,7 +100,7 @@ func (h *Handler) AdminArbitrate(c *gin.Context) { response.BadRequest(c, "仲裁结果和备注不能为空") return } - item, err := h.service.Arbitrate(adminID, id, req, auditMeta(c)) + item, err := h.service.Arbitrate(c.Request.Context(), adminID, id, req, auditMeta(c)) if err != nil { writeDisputeError(c, err) return diff --git a/backend/internal/modules/dispute/repository.go b/backend/internal/modules/dispute/repository.go index df06252..7340ccc 100644 --- a/backend/internal/modules/dispute/repository.go +++ b/backend/internal/modules/dispute/repository.go @@ -1,6 +1,7 @@ package dispute import ( + "context" "encoding/json" "errors" "fmt" @@ -23,7 +24,7 @@ type Repository struct { } // RefundFunc 由 payment 模块注入,避免 dispute 与 payment 形成循环依赖。 -type RefundFunc func(orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +type RefundFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) type refundAction struct { OrderID uint64 @@ -40,9 +41,9 @@ func (r *Repository) SetRefundFunc(fn RefundFunc) { r.refundFunc = fn } -func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) { +func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) { var createdID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -140,18 +141,19 @@ func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (* if err != nil { return nil, err } - return r.FindForUser(userID, createdID) + return r.FindForUser(ctx, userID, createdID) } -func (r *Repository) ListForUser(userID uint64, page, pageSize int) (*PaginatedResult, error) { - conditions := r.db.Model(&model.Dispute{}).Where("initiator_id = ? OR target_user_id = ?", userID, userID) +func (r *Repository) ListForUser(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) { + db := r.db.WithContext(ctx) + conditions := db.Model(&model.Dispute{}).Where("initiator_id = ? OR target_user_id = ?", userID, userID) var total int64 if err := conditions.Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []disputeRow - err := r.baseQuery(). + err := r.baseQuery(ctx). Where("d.initiator_id = ? OR d.target_user_id = ?", userID, userID). Order("d.id DESC"). Offset(offset).Limit(pageSize). @@ -162,9 +164,9 @@ func (r *Repository) ListForUser(userID uint64, page, pageSize int) (*PaginatedR return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil } -func (r *Repository) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) { +func (r *Repository) FindForUser(ctx context.Context, userID uint64, id uint64) (*DisputeDTO, error) { var row disputeRow - if err := r.baseQuery(). + if err := r.baseQuery(ctx). Where("d.id = ? AND (d.initiator_id = ? OR d.target_user_id = ?)", id, userID, userID). First(&row).Error; err != nil { return nil, err @@ -173,23 +175,23 @@ func (r *Repository) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) return &dto, nil } -func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) { +func (r *Repository) ListAdmin(ctx context.Context, page, pageSize int) (*PaginatedResult, error) { var total int64 - if err := r.db.Model(&model.Dispute{}).Count(&total).Error; err != nil { + if err := r.db.WithContext(ctx).Model(&model.Dispute{}).Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []disputeRow - err := r.baseQuery().Order("d.id DESC").Offset(offset).Limit(pageSize).Scan(&rows).Error + err := r.baseQuery(ctx).Order("d.id DESC").Offset(offset).Limit(pageSize).Scan(&rows).Error if err != nil { return nil, err } return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil } -func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) { +func (r *Repository) Arbitrate(ctx context.Context, adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) { var refund *refundAction - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var row model.Dispute if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil { return err @@ -328,9 +330,9 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, if err != nil { return nil, err } - r.startRefundBestEffort(refund) + r.startRefundBestEffort(ctx, refund) var row disputeRow - if err := r.baseQuery().Where("d.id = ?", id).First(&row).Error; err != nil { + if err := r.baseQuery(ctx).Where("d.id = ?", id).First(&row).Error; err != nil { return nil, err } dto := row.toDTO() @@ -442,11 +444,11 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b }, nil } -func (r *Repository) startRefundBestEffort(action *refundAction) { +func (r *Repository) startRefundBestEffort(ctx context.Context, action *refundAction) { if action == nil || r.refundFunc == nil { return } - _, _ = r.refundFunc(action.OrderID, action.RefundAmountCent, action.BizType, action.Remark) + _, _ = r.refundFunc(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark) } func renterFrozenBalance(tx *gorm.DB, renterID uint64) (int64, error) { @@ -463,8 +465,8 @@ func renterFrozenBalance(tx *gorm.DB, renterID uint64) (int64, error) { return account.FrozenBalanceCent, nil } -func (r *Repository) baseQuery() *gorm.DB { - return r.db.Table("disputes AS d"). +func (r *Repository) baseQuery(ctx context.Context) *gorm.DB { + return r.db.WithContext(ctx).Table("disputes AS d"). Select("d.*, o.order_no, l.listing_no, a.title"). Joins("JOIN rental_orders AS o ON o.id = d.order_id"). Joins("JOIN rental_listings AS l ON l.id = o.listing_id"). diff --git a/backend/internal/modules/dispute/service.go b/backend/internal/modules/dispute/service.go index 61621f4..52cf30c 100644 --- a/backend/internal/modules/dispute/service.go +++ b/backend/internal/modules/dispute/service.go @@ -1,6 +1,9 @@ package dispute -import "errors" +import ( + "context" + "errors" +) var ( ErrDependencyUnavailable = errors.New("dependency unavailable") @@ -18,43 +21,43 @@ func NewService(repo *Repository) *Service { return &Service{repo: repo} } -func (s *Service) Create(userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) { +func (s *Service) Create(ctx context.Context, userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || req.Type == "" || req.Description == "" { return nil, ErrInvalidDispute } - return s.repo.Create(userID, orderID, req) + return s.repo.Create(ctx, userID, orderID, req) } -func (s *Service) ListForUser(userID uint64, page, pageSize int) (*PaginatedResult, error) { +func (s *Service) ListForUser(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListForUser(userID, page, pageSize) + return s.repo.ListForUser(ctx, userID, page, pageSize) } -func (s *Service) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) { +func (s *Service) FindForUser(ctx context.Context, userID uint64, id uint64) (*DisputeDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindForUser(userID, id) + return s.repo.FindForUser(ctx, userID, id) } -func (s *Service) ListAdmin(page, pageSize int) (*PaginatedResult, error) { +func (s *Service) ListAdmin(ctx context.Context, page, pageSize int) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListAdmin(page, pageSize) + return s.repo.ListAdmin(ctx, page, pageSize) } -func (s *Service) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) { +func (s *Service) Arbitrate(ctx context.Context, adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if id == 0 || req.Result == "" || req.Remark == "" { return nil, ErrInvalidDispute } - return s.repo.Arbitrate(adminID, id, req, meta) + return s.repo.Arbitrate(ctx, adminID, id, req, meta) } diff --git a/backend/internal/modules/listing/handler.go b/backend/internal/modules/listing/handler.go index 5ea1a16..c3b59db 100644 --- a/backend/internal/modules/listing/handler.go +++ b/backend/internal/modules/listing/handler.go @@ -1,6 +1,7 @@ package listing import ( + "context" "encoding/json" "errors" "io" @@ -117,7 +118,7 @@ func (h *Handler) SubmitReview(c *gin.Context) { } func (h *Handler) ListPendingReview(c *gin.Context) { - items, err := h.service.ListPendingReview() + items, err := h.service.ListPendingReview(c.Request.Context()) if err != nil { writeListingError(c, err) return @@ -130,7 +131,7 @@ func (h *Handler) ListAdmin(c *gin.Context) { if !ok { return } - result, err := h.service.ListAdmin(query) + result, err := h.service.ListAdmin(c.Request.Context(), query) if err != nil { writeListingError(c, err) return @@ -143,7 +144,7 @@ func (h *Handler) FindAdmin(c *gin.Context) { if !ok { return } - item, err := h.service.FindAdmin(id) + item, err := h.service.FindAdmin(c.Request.Context(), id) if err != nil { writeListingError(c, err) return @@ -159,7 +160,7 @@ func (h *Handler) AdminMarkAbnormal(c *gin.Context) { h.adminAction(c, h.service.AdminMarkAbnormal) } -func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActionRequest, AuditMeta) (*ListingDTO, error)) { +func (h *Handler) adminAction(c *gin.Context, fn func(context.Context, uint64, uint64, AdminActionRequest, AuditMeta) (*ListingDTO, error)) { adminID, ok := currentAdminID(c) if !ok { response.Unauthorized(c, "缺少管理员上下文") @@ -174,7 +175,7 @@ func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActio response.BadRequest(c, "操作原因不能为空") return } - item, err := fn(adminID, id, req, auditMeta(c)) + item, err := fn(c.Request.Context(), adminID, id, req, auditMeta(c)) if err != nil { writeListingError(c, err) return @@ -195,7 +196,7 @@ func (h *Handler) Approve(c *gin.Context) { if !ok { return } - item, err := h.service.Approve(id) + item, err := h.service.Approve(c.Request.Context(), id) if err != nil { writeListingError(c, err) return @@ -218,7 +219,7 @@ func (h *Handler) AdjustReviewPrice(c *gin.Context) { response.BadRequest(c, "调价参数不正确") return } - item, err := h.service.AdjustReviewPrice(adminID, id, req, auditMeta(c)) + item, err := h.service.AdjustReviewPrice(c.Request.Context(), adminID, id, req, auditMeta(c)) if err != nil { writeListingError(c, err) return @@ -236,7 +237,7 @@ func (h *Handler) Reject(c *gin.Context) { response.BadRequest(c, "审核拒绝原因不能为空") return } - item, err := h.service.Reject(id, req) + item, err := h.service.Reject(c.Request.Context(), id, req) if err != nil { writeListingError(c, err) return @@ -254,7 +255,7 @@ func (h *Handler) Offline(c *gin.Context) { if !ok { return } - item, err := h.service.Offline(ownerID, id) + item, err := h.service.Offline(c.Request.Context(), ownerID, id) if err != nil { writeListingError(c, err) return @@ -263,7 +264,7 @@ func (h *Handler) Offline(c *gin.Context) { } func (h *Handler) ListPublic(c *gin.Context) { - items, err := h.service.ListPublic(parsePublicListQuery(c)) + items, err := h.service.ListPublic(c.Request.Context(), parsePublicListQuery(c)) if err != nil { writeListingError(c, err) return @@ -276,7 +277,7 @@ func (h *Handler) FindPublic(c *gin.Context) { if !ok { return } - item, err := h.service.FindPublic(id) + item, err := h.service.FindPublic(c.Request.Context(), id) if err != nil { writeListingError(c, err) return @@ -289,7 +290,7 @@ func (h *Handler) Cover(c *gin.Context) { if !ok { return } - key, err := h.service.FindPublicCoverKey(id) + key, err := h.service.FindPublicCoverKey(c.Request.Context(), id) if err != nil { writeListingError(c, err) return @@ -307,7 +308,7 @@ func (h *Handler) Screenshot(c *gin.Context) { response.BadRequest(c, "截图序号不正确") return } - key, err := h.service.FindPublicScreenshotKey(id, index) + key, err := h.service.FindPublicScreenshotKey(c.Request.Context(), id, index) if err != nil { writeListingError(c, err) return @@ -350,7 +351,7 @@ func (h *Handler) ListMine(c *gin.Context) { response.Unauthorized(c, "缺少用户上下文") return } - items, err := h.service.ListMine(ownerID) + items, err := h.service.ListMine(c.Request.Context(), ownerID) if err != nil { writeListingError(c, err) return @@ -368,7 +369,7 @@ func (h *Handler) FindMine(c *gin.Context) { if !ok { return } - item, err := h.service.FindMine(ownerID, id) + item, err := h.service.FindMine(c.Request.Context(), ownerID, id) if err != nil { writeListingError(c, err) return diff --git a/backend/internal/modules/listing/repository.go b/backend/internal/modules/listing/repository.go index a33fd27..034cfdb 100644 --- a/backend/internal/modules/listing/repository.go +++ b/backend/internal/modules/listing/repository.go @@ -1,6 +1,7 @@ package listing import ( + "context" "encoding/json" "errors" "fmt" @@ -47,9 +48,9 @@ func initialPublishState(reviewRequired bool) (string, string, *time.Time) { return "published", "approved", &now } -func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bool) (*ListingDTO, error) { +func (r *Repository) Create(ctx context.Context, ownerID uint64, req CreateRequest, reviewRequired bool) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listingNo, err := r.nextListingNo(tx, time.Now()) if err != nil { return err @@ -107,9 +108,9 @@ type externalUploadCreate struct { ParsedPayload []byte } -func (r *Repository) CreateFromExternalUpload(upload externalUploadCreate, req CreateRequest) (*ListingDTO, error) { +func (r *Repository) CreateFromExternalUpload(ctx context.Context, upload externalUploadCreate, req CreateRequest) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listingNo, err := r.nextListingNo(tx, time.Now()) if err != nil { return err @@ -182,9 +183,9 @@ func (r *Repository) CreateFromExternalUpload(upload externalUploadCreate, req C return dto, err } -func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest, reviewRequired bool) (*ListingDTO, error) { +func (r *Repository) Update(ctx context.Context, ownerID uint64, listingID uint64, req UpdateRequest, reviewRequired bool) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findOwnedForUpdate(tx, ownerID, listingID) if err != nil { return err @@ -230,9 +231,9 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest, return dto, err } -func (r *Repository) SubmitReview(ownerID uint64, listingID uint64, reviewRequired bool) (*ListingDTO, error) { +func (r *Repository) SubmitReview(ctx context.Context, ownerID uint64, listingID uint64, reviewRequired bool) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findOwnedForUpdate(tx, ownerID, listingID) if err != nil { return err @@ -258,9 +259,9 @@ func (r *Repository) SubmitReview(ownerID uint64, listingID uint64, reviewRequir return dto, err } -func (r *Repository) ListPendingReview() ([]ListingDTO, error) { +func (r *Repository) ListPendingReview(ctx context.Context) ([]ListingDTO, error) { var rows []listingRow - err := r.baseQuery(). + err := r.baseQuery(ctx). Where("l.review_status = ? AND l.status <> ?", "pending", "offline"). Order("l.updated_at ASC, l.id ASC"). Limit(200). @@ -271,7 +272,7 @@ func (r *Repository) ListPendingReview() ([]ListingDTO, error) { return rowsToDTO(rows), nil } -func (r *Repository) ListAdmin(query AdminListQuery) (*AdminListResult, error) { +func (r *Repository) ListAdmin(ctx context.Context, query AdminListQuery) (*AdminListResult, error) { page := query.Page if page <= 0 { page = 1 @@ -287,13 +288,13 @@ func (r *Repository) ListAdmin(query AdminListQuery) (*AdminListResult, error) { pageSize = 100 } - countDB := r.applyAdminListFilters(r.db.Table("rental_listings AS l"), query) + countDB := r.applyAdminListFilters(r.db.WithContext(ctx).Table("rental_listings AS l"), query) var total int64 if err := countDB.Count(&total).Error; err != nil { return nil, err } - db := r.applyAdminListFilters(r.baseQuery(), query) + db := r.applyAdminListFilters(r.baseQuery(ctx), query) offset := (page - 1) * pageSize if offset < 0 { offset = 0 @@ -386,21 +387,21 @@ func (r *Repository) ensureUploadOwnerUser(tx *gorm.DB, admin *model.AdminUser) return &user, nil } -func (r *Repository) FindAdmin(listingID uint64) (*ListingDTO, error) { - return r.findDTO("l.id = ?", listingID) +func (r *Repository) FindAdmin(ctx context.Context, listingID uint64) (*ListingDTO, error) { + return r.findDTO(ctx, "l.id = ?", listingID) } -func (r *Repository) AdminOffline(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { - return r.adminUpdateStatus(adminID, listingID, req, meta, "offline", "offline", "listing.admin_offline", "商品已被后台下架", "你的租号商品已被后台下架,请查看原因后处理。") +func (r *Repository) AdminOffline(ctx context.Context, adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { + return r.adminUpdateStatus(ctx, adminID, listingID, req, meta, "offline", "offline", "listing.admin_offline", "商品已被后台下架", "你的租号商品已被后台下架,请查看原因后处理。") } -func (r *Repository) AdminMarkAbnormal(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { - return r.adminUpdateStatus(adminID, listingID, req, meta, "abnormal", "abnormal", "listing.mark_abnormal", "商品已被标记异常", "你的租号商品已被后台标记异常,请联系客服处理。") +func (r *Repository) AdminMarkAbnormal(ctx context.Context, adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { + return r.adminUpdateStatus(ctx, adminID, listingID, req, meta, "abnormal", "abnormal", "listing.mark_abnormal", "商品已被标记异常", "你的租号商品已被后台标记异常,请联系客服处理。") } -func (r *Repository) adminUpdateStatus(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta, listingStatus string, accountStatus string, action string, title string, content string) (*ListingDTO, error) { +func (r *Repository) adminUpdateStatus(ctx context.Context, adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta, listingStatus string, accountStatus string, action string, title string, content string) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findForReviewUpdate(tx, listingID) if err != nil { return err @@ -453,9 +454,9 @@ func (r *Repository) adminUpdateStatus(adminID uint64, listingID uint64, req Adm return dto, err } -func (r *Repository) Approve(listingID uint64) (*ListingDTO, error) { +func (r *Repository) Approve(ctx context.Context, listingID uint64) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findForReviewUpdate(tx, listingID) if err != nil { return err @@ -492,9 +493,9 @@ func (r *Repository) Approve(listingID uint64) (*ListingDTO, error) { return dto, err } -func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) { +func (r *Repository) AdjustReviewPrice(ctx context.Context, adminID uint64, listingID uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findForReviewUpdate(tx, listingID) if err != nil { return err @@ -583,9 +584,9 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm return dto, err } -func (r *Repository) Reject(listingID uint64, req ReviewRequest) (*ListingDTO, error) { +func (r *Repository) Reject(ctx context.Context, listingID uint64, req ReviewRequest) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findForReviewUpdate(tx, listingID) if err != nil { return err @@ -621,9 +622,9 @@ func (r *Repository) Reject(listingID uint64, req ReviewRequest) (*ListingDTO, e return dto, err } -func (r *Repository) Offline(ownerID uint64, listingID uint64) (*ListingDTO, error) { +func (r *Repository) Offline(ctx context.Context, ownerID uint64, listingID uint64) (*ListingDTO, error) { var dto *ListingDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { listing, account, err := r.findOwnedForUpdate(tx, ownerID, listingID) if err != nil { return err @@ -648,14 +649,14 @@ func (r *Repository) Offline(ownerID uint64, listingID uint64) (*ListingDTO, err return dto, err } -func (r *Repository) ListPublic(query PublicListQuery) (*PublicListResult, error) { +func (r *Repository) ListPublic(ctx context.Context, query PublicListQuery) (*PublicListResult, error) { page, pageSize := normalizedPublicPage(query) if canListPublicWithSQL(query) { - return r.listPublicPage(query, page, pageSize) + return r.listPublicPage(ctx, query, page, pageSize) } var rows []listingRow - err := r.baseQuery(). + err := r.baseQuery(ctx). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Order("l.published_at DESC, l.id DESC"). Scan(&rows).Error @@ -694,9 +695,9 @@ func (r *Repository) ListPublic(query PublicListQuery) (*PublicListResult, error }, nil } -func (r *Repository) listPublicPage(query PublicListQuery, page int, pageSize int) (*PublicListResult, error) { +func (r *Repository) listPublicPage(ctx context.Context, query PublicListQuery, page int, pageSize int) (*PublicListResult, error) { var total int64 - if err := r.db.Table("rental_listings AS l"). + if err := r.db.WithContext(ctx).Table("rental_listings AS l"). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Count(&total).Error; err != nil { return nil, err @@ -704,7 +705,7 @@ func (r *Repository) listPublicPage(query PublicListQuery, page int, pageSize in var rows []listingRow offset := (page - 1) * pageSize - err := applyPublicSQLSort(r.baseQuery(), query.Sort). + err := applyPublicSQLSort(r.baseQuery(ctx), query.Sort). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Limit(pageSize). Offset(offset). @@ -712,7 +713,7 @@ func (r *Repository) listPublicPage(query PublicListQuery, page int, pageSize in if err != nil { return nil, err } - zoneCounts, err := r.publicZoneCountsCached() + zoneCounts, err := r.publicZoneCountsCached(ctx) if err != nil { return nil, err } @@ -786,7 +787,7 @@ func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB { } } -func (r *Repository) publicZoneCountsCached() (map[string]int64, error) { +func (r *Repository) publicZoneCountsCached(ctx context.Context) (map[string]int64, error) { now := time.Now() r.publicZoneCountsMu.Lock() defer r.publicZoneCountsMu.Unlock() @@ -795,7 +796,7 @@ func (r *Repository) publicZoneCountsCached() (map[string]int64, error) { } var rows []publicZoneRow - err := r.db.Table("rental_listings AS l"). + err := r.db.WithContext(ctx).Table("rental_listings AS l"). Select("a.login_platform, a.haf_coin_amount, a.asset_summary"). Joins("JOIN game_accounts AS a ON a.id = l.account_id"). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). @@ -844,9 +845,9 @@ func copyPublicZoneCounts(counts map[string]int64) map[string]int64 { return copied } -func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) { +func (r *Repository) ListMine(ctx context.Context, ownerID uint64) ([]ListingDTO, error) { var rows []listingRow - err := r.baseQuery(). + err := r.baseQuery(ctx). Where("l.owner_id = ?", ownerID). Order("l.id DESC"). Scan(&rows).Error @@ -1232,8 +1233,8 @@ func timeRangeCoversHour(start int, end int, hour int) bool { return hour >= start || hour <= end } -func (r *Repository) FindPublic(id uint64) (*ListingDTO, error) { - dto, err := r.findDTO("l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) +func (r *Repository) FindPublic(ctx context.Context, id uint64) (*ListingDTO, error) { + dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) if err != nil { return nil, err } @@ -1241,15 +1242,15 @@ func (r *Repository) FindPublic(id uint64) (*ListingDTO, error) { return dto, nil } -func (r *Repository) FindPublicCoverKey(id uint64) (string, error) { - return r.FindPublicScreenshotKey(id, 0) +func (r *Repository) FindPublicCoverKey(ctx context.Context, id uint64) (string, error) { + return r.FindPublicScreenshotKey(ctx, id, 0) } -func (r *Repository) FindPublicScreenshotKey(id uint64, index int) (string, error) { +func (r *Repository) FindPublicScreenshotKey(ctx context.Context, id uint64, index int) (string, error) { if index < 0 { return "", gorm.ErrRecordNotFound } - dto, err := r.findDTO("l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) + dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) if err != nil { return "", err } @@ -1262,8 +1263,8 @@ func (r *Repository) FindPublicScreenshotKey(id uint64, index int) (string, erro return "", gorm.ErrRecordNotFound } -func (r *Repository) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) { - dto, err := r.findDTO("l.id = ? AND l.owner_id = ?", id, ownerID) +func (r *Repository) FindMine(ctx context.Context, ownerID uint64, id uint64) (*ListingDTO, error) { + dto, err := r.findDTO(ctx, "l.id = ? AND l.owner_id = ?", id, ownerID) if err != nil { return nil, err } @@ -1283,9 +1284,9 @@ func (r *Repository) findOwnedForUpdate(tx *gorm.DB, ownerID uint64, listingID u return &listing, &account, nil } -func (r *Repository) findDTO(where string, args ...any) (*ListingDTO, error) { +func (r *Repository) findDTO(ctx context.Context, where string, args ...any) (*ListingDTO, error) { var row listingRow - err := r.baseQuery(). + err := r.baseQuery(ctx). Where(where, args...). First(&row).Error if err != nil { @@ -1295,8 +1296,8 @@ func (r *Repository) findDTO(where string, args ...any) (*ListingDTO, error) { return &dto, nil } -func (r *Repository) baseQuery() *gorm.DB { - return r.db.Table("rental_listings AS l"). +func (r *Repository) baseQuery(ctx context.Context) *gorm.DB { + return r.db.WithContext(ctx).Table("rental_listings AS l"). Select(`l.*, a.title, a.description, a.game_name, a.server_region, a.login_platform, a.rank_level, a.haf_coin_amount, a.asset_summary, a.screenshot_urls, COALESCE(u.phone, '') AS owner_phone, COALESCE(u.nickname, '') AS owner_nickname`). Joins("JOIN game_accounts AS a ON a.id = l.account_id"). diff --git a/backend/internal/modules/listing/service.go b/backend/internal/modules/listing/service.go index 45d34e1..b016194 100644 --- a/backend/internal/modules/listing/service.go +++ b/backend/internal/modules/listing/service.go @@ -96,7 +96,7 @@ func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest) if err != nil { return nil, err } - return s.repo.Create(ownerID, req, reviewRequired) + return s.repo.Create(ctx, ownerID, req, reviewRequired) } func (s *Service) ImportExternalUpload(ctx context.Context, req ExternalUploadRequest, meta ExternalUploadMeta) (*ExternalUploadResponse, error) { @@ -132,7 +132,7 @@ func (s *Service) ImportExternalUpload(ctx context.Context, req ExternalUploadRe continue } parsedPayload, _ := json.Marshal(item) - dto, err := s.repo.CreateFromExternalUpload(externalUploadCreate{ + dto, err := s.repo.CreateFromExternalUpload(ctx, externalUploadCreate{ UploaderName: uploaderName, ClientUploadTime: clientUploadTime, ClientIP: meta.IP, @@ -187,7 +187,7 @@ func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req Upd if err != nil { return nil, err } - return s.repo.Update(ownerID, id, req, reviewRequired) + return s.repo.Update(ctx, ownerID, id, req, reviewRequired) } func (s *Service) SubmitReview(ctx context.Context, ownerID uint64, id uint64) (*ListingDTO, error) { @@ -198,124 +198,124 @@ func (s *Service) SubmitReview(ctx context.Context, ownerID uint64, id uint64) ( if err != nil { return nil, err } - return s.repo.SubmitReview(ownerID, id, reviewRequired) + return s.repo.SubmitReview(ctx, ownerID, id, reviewRequired) } -func (s *Service) ListPendingReview() ([]ListingDTO, error) { +func (s *Service) ListPendingReview(ctx context.Context) ([]ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListPendingReview() + return s.repo.ListPendingReview(ctx) } -func (s *Service) ListAdmin(query AdminListQuery) (*AdminListResult, error) { +func (s *Service) ListAdmin(ctx context.Context, query AdminListQuery) (*AdminListResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListAdmin(query) + return s.repo.ListAdmin(ctx, query) } -func (s *Service) FindAdmin(id uint64) (*ListingDTO, error) { +func (s *Service) FindAdmin(ctx context.Context, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindAdmin(id) + return s.repo.FindAdmin(ctx, id) } -func (s *Service) AdminOffline(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { +func (s *Service) AdminOffline(ctx context.Context, adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if req.Reason == "" { return nil, ErrInvalidInput } - return s.repo.AdminOffline(adminID, id, req, meta) + return s.repo.AdminOffline(ctx, adminID, id, req, meta) } -func (s *Service) AdminMarkAbnormal(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { +func (s *Service) AdminMarkAbnormal(ctx context.Context, adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if req.Reason == "" { return nil, ErrInvalidInput } - return s.repo.AdminMarkAbnormal(adminID, id, req, meta) + return s.repo.AdminMarkAbnormal(ctx, adminID, id, req, meta) } -func (s *Service) Approve(id uint64) (*ListingDTO, error) { +func (s *Service) Approve(ctx context.Context, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.Approve(id) + return s.repo.Approve(ctx, id) } -func (s *Service) AdjustReviewPrice(adminID uint64, id uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) { +func (s *Service) AdjustReviewPrice(ctx context.Context, adminID uint64, id uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if req.BuyerRatio <= 0 && req.BuyerTotalPriceCent <= 0 { return nil, ErrInvalidPrice } - return s.repo.AdjustReviewPrice(adminID, id, req, meta) + return s.repo.AdjustReviewPrice(ctx, adminID, id, req, meta) } -func (s *Service) Reject(id uint64, req ReviewRequest) (*ListingDTO, error) { +func (s *Service) Reject(ctx context.Context, id uint64, req ReviewRequest) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if req.Reason == "" { return nil, ErrInvalidInput } - return s.repo.Reject(id, req) + return s.repo.Reject(ctx, id, req) } -func (s *Service) Offline(ownerID uint64, id uint64) (*ListingDTO, error) { +func (s *Service) Offline(ctx context.Context, ownerID uint64, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.Offline(ownerID, id) + return s.repo.Offline(ctx, ownerID, id) } -func (s *Service) ListPublic(query PublicListQuery) (*PublicListResult, error) { +func (s *Service) ListPublic(ctx context.Context, query PublicListQuery) (*PublicListResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListPublic(query) + return s.repo.ListPublic(ctx, query) } -func (s *Service) ListMine(ownerID uint64) ([]ListingDTO, error) { +func (s *Service) ListMine(ctx context.Context, ownerID uint64) ([]ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListMine(ownerID) + return s.repo.ListMine(ctx, ownerID) } -func (s *Service) FindPublic(id uint64) (*ListingDTO, error) { +func (s *Service) FindPublic(ctx context.Context, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindPublic(id) + return s.repo.FindPublic(ctx, id) } -func (s *Service) FindPublicCoverKey(id uint64) (string, error) { +func (s *Service) FindPublicCoverKey(ctx context.Context, id uint64) (string, error) { if s.repo == nil { return "", ErrDependencyUnavailable } - return s.repo.FindPublicCoverKey(id) + return s.repo.FindPublicCoverKey(ctx, id) } -func (s *Service) FindPublicScreenshotKey(id uint64, index int) (string, error) { +func (s *Service) FindPublicScreenshotKey(ctx context.Context, id uint64, index int) (string, error) { if s.repo == nil { return "", ErrDependencyUnavailable } - return s.repo.FindPublicScreenshotKey(id, index) + return s.repo.FindPublicScreenshotKey(ctx, id, index) } -func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) { +func (s *Service) FindMine(ctx context.Context, ownerID uint64, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindMine(ownerID, id) + return s.repo.FindMine(ctx, ownerID, id) } type publishRules struct { diff --git a/backend/internal/modules/order/admin_actions.go b/backend/internal/modules/order/admin_actions.go index 117e15a..a32c879 100644 --- a/backend/internal/modules/order/admin_actions.go +++ b/backend/internal/modules/order/admin_actions.go @@ -1,6 +1,7 @@ package order import ( + "context" "time" "hfb_sys/backend/internal/model" @@ -9,9 +10,9 @@ import ( "gorm.io/gorm" ) -func (r *Repository) AdminClose(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { +func (r *Repository) AdminClose(ctx context.Context, adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { var refund *refundAction - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { assets, err := r.lockOrderAssets(tx, orderID) if err != nil { return err @@ -91,12 +92,12 @@ func (r *Repository) AdminClose(adminID uint64, orderID uint64, req AdminActionR if err != nil { return err } - r.startRefundBestEffort(refund) + r.startRefundBestEffort(ctx, refund) return nil } -func (r *Repository) AdminMarkAbnormal(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *Repository) AdminMarkAbnormal(ctx context.Context, adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { assets, err := r.lockOrderAssets(tx, orderID) if err != nil { return err @@ -162,9 +163,10 @@ func (r *Repository) AdminMarkAbnormal(adminID uint64, orderID uint64, req Admin } // AdminRefund 触发后台人工退款,退款由 payment 模块走渠道原路退回。 -func (r *Repository) AdminRefund(orderID uint64) (*RefundStatusDTO, error) { +func (r *Repository) AdminRefund(ctx context.Context, orderID uint64) (*RefundStatusDTO, error) { var order model.RentalOrder - if err := r.db.First(&order, orderID).Error; err != nil { + db := r.db.WithContext(ctx) + if err := db.First(&order, orderID).Error; err != nil { return nil, err } if order.RefundStatus == refundStatusRefunded { @@ -177,12 +179,12 @@ func (r *Repository) AdminRefund(orderID uint64) (*RefundStatusDTO, error) { if totalCent <= 0 { return nil, ErrInvalidCheckoutAmount } - status, err := r.refundFunc(orderID, totalCent, refundBizAdmin, "后台人工退款") + status, err := r.refundFunc(ctx, orderID, totalCent, refundBizAdmin, "后台人工退款") if err != nil { return nil, err } // 重新读取订单,拿到 payment 模块更新后的退款字段。 - if err := r.db.First(&order, orderID).Error; err != nil { + if err := db.First(&order, orderID).Error; err != nil { return nil, err } dto := r.buildRefundStatusDTO(&order) @@ -193,9 +195,9 @@ func (r *Repository) AdminRefund(orderID uint64) (*RefundStatusDTO, error) { } // AdminRefundStatus 查询订单退款状态。 -func (r *Repository) AdminRefundStatus(orderID uint64) (*RefundStatusDTO, error) { +func (r *Repository) AdminRefundStatus(ctx context.Context, orderID uint64) (*RefundStatusDTO, error) { var order model.RentalOrder - if err := r.db.First(&order, orderID).Error; err != nil { + if err := r.db.WithContext(ctx).First(&order, orderID).Error; err != nil { return nil, err } return r.buildRefundStatusDTO(&order), nil diff --git a/backend/internal/modules/order/checkout.go b/backend/internal/modules/order/checkout.go index adc280c..7c7f8fd 100644 --- a/backend/internal/modules/order/checkout.go +++ b/backend/internal/modules/order/checkout.go @@ -1,6 +1,7 @@ package order import ( + "context" "time" "hfb_sys/backend/internal/model" @@ -10,9 +11,9 @@ import ( "gorm.io/gorm/clause" ) -func (r *Repository) SubmitCheckout(userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) { +func (r *Repository) SubmitCheckout(ctx context.Context, userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) { var recordID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -70,16 +71,16 @@ func (r *Repository) SubmitCheckout(userID uint64, orderID uint64, req SubmitChe if err != nil { return nil, err } - return r.findHandoffRecord(recordID) + return r.findHandoffRecord(ctx, recordID) } -func (r *Repository) ConfirmReturn(userID uint64, orderID uint64) error { - return r.ConfirmCheckout(userID, orderID) +func (r *Repository) ConfirmReturn(ctx context.Context, userID uint64, orderID uint64) error { + return r.ConfirmCheckout(ctx, userID, orderID) } -func (r *Repository) ConfirmCheckout(userID uint64, orderID uint64) error { +func (r *Repository) ConfirmCheckout(ctx context.Context, userID uint64, orderID uint64) error { var refund *refundAction - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -112,13 +113,13 @@ func (r *Repository) ConfirmCheckout(userID uint64, orderID uint64) error { if err != nil { return err } - r.startRefundBestEffort(refund) + r.startRefundBestEffort(ctx, refund) return nil } -func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) { +func (r *Repository) CounterCheckout(ctx context.Context, userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) { var checkoutID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -181,7 +182,7 @@ func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterC if err != nil { return nil, err } - checkout, err := r.findCheckout(checkoutID) + checkout, err := r.findCheckout(ctx, checkoutID) if err != nil { return nil, err } @@ -194,9 +195,9 @@ func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterC return &dto, nil } -func (r *Repository) AcceptCheckout(userID uint64, orderID uint64) error { +func (r *Repository) AcceptCheckout(ctx context.Context, userID uint64, orderID uint64) error { var refund *refundAction - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -224,6 +225,6 @@ func (r *Repository) AcceptCheckout(userID uint64, orderID uint64) error { if err != nil { return err } - r.startRefundBestEffort(refund) + r.startRefundBestEffort(ctx, refund) return nil } diff --git a/backend/internal/modules/order/handler.go b/backend/internal/modules/order/handler.go index 32f3133..c92989a 100644 --- a/backend/internal/modules/order/handler.go +++ b/backend/internal/modules/order/handler.go @@ -1,6 +1,7 @@ package order import ( + "context" "errors" "net/http" "strconv" @@ -30,7 +31,7 @@ func (h *Handler) Create(c *gin.Context) { response.BadRequest(c, "订单信息不完整") return } - item, err := h.service.Create(userID, req) + item, err := h.service.Create(c.Request.Context(), userID, req) if err != nil { writeOrderError(c, err) return @@ -44,7 +45,7 @@ func (h *Handler) List(c *gin.Context) { response.Unauthorized(c, "缺少用户上下文") return } - items, err := h.service.ListForUser(userID) + items, err := h.service.ListForUser(c.Request.Context(), userID) if err != nil { writeOrderError(c, err) return @@ -54,7 +55,7 @@ func (h *Handler) List(c *gin.Context) { func (h *Handler) AdminList(c *gin.Context) { page, pageSize := parsePagination(c) - result, err := h.service.ListAdmin(page, pageSize) + result, err := h.service.ListAdmin(c.Request.Context(), page, pageSize) if err != nil { writeOrderError(c, err) return @@ -67,7 +68,7 @@ func (h *Handler) AdminDetail(c *gin.Context) { if !ok { return } - item, err := h.service.FindAdmin(id) + item, err := h.service.FindAdmin(c.Request.Context(), id) if err != nil { writeOrderError(c, err) return @@ -80,7 +81,7 @@ func (h *Handler) AdminHandoffRecords(c *gin.Context) { if !ok { return } - items, err := h.service.HandoffRecordsAdmin(id) + items, err := h.service.HandoffRecordsAdmin(c.Request.Context(), id) if err != nil { writeOrderError(c, err) return @@ -101,7 +102,7 @@ func (h *Handler) AdminRefund(c *gin.Context) { if !ok { return } - item, err := h.service.AdminRefund(orderID) + item, err := h.service.AdminRefund(c.Request.Context(), orderID) if err != nil { writeOrderError(c, err) return @@ -114,7 +115,7 @@ func (h *Handler) AdminRefundStatus(c *gin.Context) { if !ok { return } - item, err := h.service.AdminRefundStatus(orderID) + item, err := h.service.AdminRefundStatus(c.Request.Context(), orderID) if err != nil { writeOrderError(c, err) return @@ -122,7 +123,7 @@ func (h *Handler) AdminRefundStatus(c *gin.Context) { response.OK(c, item) } -func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActionRequest, AuditMeta) error, okData gin.H) { +func (h *Handler) adminAction(c *gin.Context, fn func(context.Context, uint64, uint64, AdminActionRequest, AuditMeta) error, okData gin.H) { adminID, ok := currentAdminID(c) if !ok { response.Unauthorized(c, "缺少管理员上下文") @@ -137,7 +138,7 @@ func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActio response.BadRequest(c, "操作原因不能为空") return } - if err := fn(adminID, id, req, auditMeta(c)); err != nil { + if err := fn(c.Request.Context(), adminID, id, req, auditMeta(c)); err != nil { writeOrderError(c, err) return } @@ -162,7 +163,7 @@ func (h *Handler) Detail(c *gin.Context) { if !ok { return } - item, err := h.service.FindForUser(userID, id) + item, err := h.service.FindForUser(c.Request.Context(), userID, id) if err != nil { writeOrderError(c, err) return @@ -180,7 +181,7 @@ func (h *Handler) Cancel(c *gin.Context) { if !ok { return } - if err := h.service.Cancel(userID, id); err != nil { + if err := h.service.Cancel(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } @@ -197,7 +198,7 @@ func (h *Handler) Pay(c *gin.Context) { if !ok { return } - if err := h.service.Pay(userID, id); err != nil { + if err := h.service.Pay(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } @@ -219,7 +220,7 @@ func (h *Handler) SubmitHandoff(c *gin.Context) { response.BadRequest(c, "交接说明不能为空") return } - record, err := h.service.SubmitHandoff(userID, id, req) + record, err := h.service.SubmitHandoff(c.Request.Context(), userID, id, req) if err != nil { writeOrderError(c, err) return @@ -237,7 +238,7 @@ func (h *Handler) ConfirmReceive(c *gin.Context) { if !ok { return } - if err := h.service.ConfirmReceive(userID, id); err != nil { + if err := h.service.ConfirmReceive(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } @@ -254,7 +255,7 @@ func (h *Handler) HandoffRecords(c *gin.Context) { if !ok { return } - items, err := h.service.HandoffRecords(userID, id) + items, err := h.service.HandoffRecords(c.Request.Context(), userID, id) if err != nil { writeOrderError(c, err) return @@ -277,7 +278,7 @@ func (h *Handler) SubmitReturn(c *gin.Context) { response.BadRequest(c, "归还说明不能为空") return } - record, err := h.service.SubmitReturn(userID, id, req) + record, err := h.service.SubmitReturn(c.Request.Context(), userID, id, req) if err != nil { writeOrderError(c, err) return @@ -295,7 +296,7 @@ func (h *Handler) ConfirmReturn(c *gin.Context) { if !ok { return } - if err := h.service.ConfirmReturn(userID, id); err != nil { + if err := h.service.ConfirmReturn(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } @@ -317,7 +318,7 @@ func (h *Handler) SubmitCheckout(c *gin.Context) { response.BadRequest(c, "结账说明不能为空") return } - record, err := h.service.SubmitCheckout(userID, id, req) + record, err := h.service.SubmitCheckout(c.Request.Context(), userID, id, req) if err != nil { writeOrderError(c, err) return @@ -335,7 +336,7 @@ func (h *Handler) ConfirmCheckout(c *gin.Context) { if !ok { return } - if err := h.service.ConfirmCheckout(userID, id); err != nil { + if err := h.service.ConfirmCheckout(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } @@ -357,7 +358,7 @@ func (h *Handler) CounterCheckout(c *gin.Context) { response.BadRequest(c, "结账修正原因不能为空") return } - checkout, err := h.service.CounterCheckout(userID, id, req) + checkout, err := h.service.CounterCheckout(c.Request.Context(), userID, id, req) if err != nil { writeOrderError(c, err) return @@ -375,7 +376,7 @@ func (h *Handler) AcceptCheckout(c *gin.Context) { if !ok { return } - if err := h.service.AcceptCheckout(userID, id); err != nil { + if err := h.service.AcceptCheckout(c.Request.Context(), userID, id); err != nil { writeOrderError(c, err) return } diff --git a/backend/internal/modules/order/handoff.go b/backend/internal/modules/order/handoff.go index 740e95d..1df6377 100644 --- a/backend/internal/modules/order/handoff.go +++ b/backend/internal/modules/order/handoff.go @@ -1,6 +1,8 @@ package order import ( + "context" + "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/modules/notification" @@ -8,9 +10,9 @@ import ( "gorm.io/gorm/clause" ) -func (r *Repository) SubmitHandoff(userID uint64, orderID uint64, req SubmitHandoffRequest) (*HandoffRecordDTO, error) { +func (r *Repository) SubmitHandoff(ctx context.Context, userID uint64, orderID uint64, req SubmitHandoffRequest) (*HandoffRecordDTO, error) { var recordID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err @@ -52,16 +54,17 @@ func (r *Repository) SubmitHandoff(userID uint64, orderID uint64, req SubmitHand if err != nil { return nil, err } - return r.findHandoffRecord(recordID) + return r.findHandoffRecord(ctx, recordID) } -func (r *Repository) HandoffRecords(userID uint64, orderID uint64) ([]HandoffRecordDTO, error) { +func (r *Repository) HandoffRecords(ctx context.Context, userID uint64, orderID uint64) ([]HandoffRecordDTO, error) { var order model.RentalOrder - if err := r.db.Where("id = ? AND (renter_id = ? OR owner_id = ?)", orderID, userID, userID).First(&order).Error; err != nil { + db := r.db.WithContext(ctx) + if err := db.Where("id = ? AND (renter_id = ? OR owner_id = ?)", orderID, userID, userID).First(&order).Error; err != nil { return nil, err } var records []model.HandoffRecord - if err := r.db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil { + if err := db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil { return nil, err } items := make([]HandoffRecordDTO, 0, len(records)) @@ -71,17 +74,18 @@ func (r *Repository) HandoffRecords(userID uint64, orderID uint64) ([]HandoffRec return items, nil } -func (r *Repository) SubmitReturn(userID uint64, orderID uint64, req SubmitReturnRequest) (*HandoffRecordDTO, error) { - return r.SubmitCheckout(userID, orderID, SubmitCheckoutRequest{Content: req.Content}) +func (r *Repository) SubmitReturn(ctx context.Context, userID uint64, orderID uint64, req SubmitReturnRequest) (*HandoffRecordDTO, error) { + return r.SubmitCheckout(ctx, userID, orderID, SubmitCheckoutRequest{Content: req.Content}) } -func (r *Repository) HandoffRecordsAdmin(orderID uint64) ([]HandoffRecordDTO, error) { +func (r *Repository) HandoffRecordsAdmin(ctx context.Context, orderID uint64) ([]HandoffRecordDTO, error) { var order model.RentalOrder - if err := r.db.First(&order, orderID).Error; err != nil { + db := r.db.WithContext(ctx) + if err := db.First(&order, orderID).Error; err != nil { return nil, err } var records []model.HandoffRecord - if err := r.db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil { + if err := db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil { return nil, err } items := make([]HandoffRecordDTO, 0, len(records)) @@ -91,9 +95,9 @@ func (r *Repository) HandoffRecordsAdmin(orderID uint64) ([]HandoffRecordDTO, er return items, nil } -func (r *Repository) findHandoffRecord(id uint64) (*HandoffRecordDTO, error) { +func (r *Repository) findHandoffRecord(ctx context.Context, id uint64) (*HandoffRecordDTO, error) { var record model.HandoffRecord - if err := r.db.First(&record, id).Error; err != nil { + if err := r.db.WithContext(ctx).First(&record, id).Error; err != nil { return nil, err } dto := toHandoffDTO(record) diff --git a/backend/internal/modules/order/lifecycle.go b/backend/internal/modules/order/lifecycle.go index cf4bc4b..7c5055e 100644 --- a/backend/internal/modules/order/lifecycle.go +++ b/backend/internal/modules/order/lifecycle.go @@ -1,6 +1,7 @@ package order import ( + "context" "time" "hfb_sys/backend/internal/model" @@ -11,9 +12,9 @@ import ( "gorm.io/gorm/clause" ) -func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) { +func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequest) (*OrderDTO, error) { var createdID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var listing model.RentalListing if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil { return err @@ -88,7 +89,7 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro if err != nil { return nil, err } - return r.FindForUser(renterID, createdID) + return r.FindForUser(ctx, renterID, createdID) } func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDepositCent int64) (int64, int64, error) { @@ -127,14 +128,14 @@ func calculateDepositWaiver(originalDepositCent int64, quotaCent int64, usedCent } // Pay 保留旧接口兼容,但真实付款必须走 payment 模块的渠道支付入口。 -func (r *Repository) Pay(userID uint64, orderID uint64) error { +func (r *Repository) Pay(ctx context.Context, userID uint64, orderID uint64) error { return ErrChannelPaymentRequired } // ConfirmPaidFromChannel 在乐刷确认支付后推进订单状态;租客资金不进入站内钱包。 -func (r *Repository) ConfirmPaidFromChannel(orderID uint64, providerBizNo string) error { +func (r *Repository) ConfirmPaidFromChannel(ctx context.Context, orderID uint64, providerBizNo string) error { var newConvID uint64 - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { assets, err := r.lockOrderAssets(tx, orderID) if err != nil { return err @@ -200,9 +201,9 @@ func (r *Repository) ConfirmPaidFromChannel(orderID uint64, providerBizNo string return nil } -func (r *Repository) Cancel(userID uint64, orderID uint64) error { +func (r *Repository) Cancel(ctx context.Context, userID uint64, orderID uint64) error { var refund *refundAction - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("id = ? AND renter_id = ?", orderID, userID). @@ -261,12 +262,12 @@ func (r *Repository) Cancel(userID uint64, orderID uint64) error { if err != nil { return err } - r.startRefundBestEffort(refund) + r.startRefundBestEffort(ctx, refund) return nil } -func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *Repository) ConfirmReceive(ctx context.Context, userID uint64, orderID uint64) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { return err diff --git a/backend/internal/modules/order/queries.go b/backend/internal/modules/order/queries.go index 91e1494..7aa3f3d 100644 --- a/backend/internal/modules/order/queries.go +++ b/backend/internal/modules/order/queries.go @@ -1,6 +1,7 @@ package order import ( + "context" "strconv" "time" @@ -9,9 +10,10 @@ import ( "gorm.io/gorm" ) -func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) { +func (r *Repository) ListForUser(ctx context.Context, userID uint64) ([]OrderDTO, error) { var rows []orderRow - err := r.baseQuery(). + db := r.db.WithContext(ctx) + err := r.baseQuery(ctx). Where("o.renter_id = ? OR o.owner_id = ?", userID, userID). Order("o.id DESC"). Scan(&rows).Error @@ -19,27 +21,28 @@ func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) { return nil, err } items := make([]OrderDTO, 0, len(rows)) - paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(r.db) + paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db) for _, row := range rows { dto := row.toDTOForUser(userID) applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes) if shouldAttachCheckout(row.Status) { - dto.Checkout = r.latestCheckoutDTOForUser(row.ID, userID, row.RentalOrder) + dto.Checkout = r.latestCheckoutDTOForUser(ctx, row.ID, userID, row.RentalOrder) } items = append(items, dto) } return items, nil } -func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) { +func (r *Repository) ListAdmin(ctx context.Context, page, pageSize int) (*PaginatedResult, error) { var total int64 - if err := r.adminQuery().Count(&total).Error; err != nil { + db := r.db.WithContext(ctx) + if err := r.adminQuery(ctx).Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []orderRow - err := r.adminQuery(). + err := r.adminQuery(ctx). Order("o.id DESC"). Limit(pageSize). Offset(offset). @@ -49,7 +52,7 @@ func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) { } items := make([]OrderDTO, 0, len(rows)) - paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(r.db) + paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db) for _, row := range rows { dto := row.toAdminDTO() applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes) @@ -64,27 +67,29 @@ func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) { }, nil } -func (r *Repository) FindAdmin(orderID uint64) (*OrderDTO, error) { +func (r *Repository) FindAdmin(ctx context.Context, orderID uint64) (*OrderDTO, error) { var row orderRow - if err := r.adminQuery().Where("o.id = ?", orderID).First(&row).Error; err != nil { + db := r.db.WithContext(ctx) + if err := r.adminQuery(ctx).Where("o.id = ?", orderID).First(&row).Error; err != nil { return nil, err } dto := row.toAdminDTO() - applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(r.db)) - dto.Checkout = r.latestCheckoutAdminDTO(orderID) + applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(db)) + dto.Checkout = r.latestCheckoutAdminDTO(ctx, orderID) return &dto, nil } -func (r *Repository) FindForUser(userID uint64, orderID uint64) (*OrderDTO, error) { +func (r *Repository) FindForUser(ctx context.Context, userID uint64, orderID uint64) (*OrderDTO, error) { var row orderRow - if err := r.baseQuery(). + db := r.db.WithContext(ctx) + if err := r.baseQuery(ctx). Where("o.id = ? AND (o.renter_id = ? OR o.owner_id = ?)", orderID, userID, userID). First(&row).Error; err != nil { return nil, err } dto := row.toDTOForUser(userID) - applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(r.db)) - dto.Checkout = r.latestCheckoutDTOForUser(orderID, userID, row.RentalOrder) + applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(db)) + dto.Checkout = r.latestCheckoutDTOForUser(ctx, orderID, userID, row.RentalOrder) return &dto, nil } @@ -109,9 +114,9 @@ func applyPaymentDeadline(dto *OrderDTO, order model.RentalOrder, timeoutMinutes dto.PaymentDeadlineAt = &deadline } -func (r *Repository) latestCheckoutAdminDTO(orderID uint64) *CheckoutDTO { +func (r *Repository) latestCheckoutAdminDTO(ctx context.Context, orderID uint64) *CheckoutDTO { var checkout model.OrderCheckout - if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { + if err := r.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { return nil } dto := toCheckoutAdminDTO(checkout) @@ -127,32 +132,32 @@ func shouldAttachCheckout(status string) bool { } } -func (r *Repository) latestCheckoutDTOForUser(orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO { +func (r *Repository) latestCheckoutDTOForUser(ctx context.Context, orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO { var checkout model.OrderCheckout - if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { + if err := r.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { return nil } dto := toCheckoutDTOForUser(checkout, userID, order) return &dto } -func (r *Repository) findCheckout(id uint64) (*model.OrderCheckout, error) { +func (r *Repository) findCheckout(ctx context.Context, id uint64) (*model.OrderCheckout, error) { var checkout model.OrderCheckout - if err := r.db.First(&checkout, id).Error; err != nil { + if err := r.db.WithContext(ctx).First(&checkout, id).Error; err != nil { return nil, err } return &checkout, nil } -func (r *Repository) baseQuery() *gorm.DB { - return r.db.Table("rental_orders AS o"). +func (r *Repository) baseQuery(ctx context.Context) *gorm.DB { + return r.db.WithContext(ctx).Table("rental_orders AS o"). Select("o.*, l.listing_no, a.title, a.server_region, a.login_platform"). Joins("JOIN rental_listings AS l ON l.id = o.listing_id"). Joins("JOIN game_accounts AS a ON a.id = o.account_id") } -func (r *Repository) adminQuery() *gorm.DB { - return r.db.Table("rental_orders AS o"). +func (r *Repository) adminQuery(ctx context.Context) *gorm.DB { + return r.db.WithContext(ctx).Table("rental_orders AS o"). Select("o.*, l.listing_no, a.title, a.server_region, a.login_platform, owner.phone AS owner_phone, renter.phone AS renter_phone"). Joins("JOIN rental_listings AS l ON l.id = o.listing_id"). Joins("JOIN game_accounts AS a ON a.id = o.account_id"). diff --git a/backend/internal/modules/order/refund.go b/backend/internal/modules/order/refund.go index 9537476..2bbdea5 100644 --- a/backend/internal/modules/order/refund.go +++ b/backend/internal/modules/order/refund.go @@ -1,6 +1,7 @@ package order import ( + "context" "log" "hfb_sys/backend/internal/model" @@ -24,11 +25,11 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b }, nil } -func (r *Repository) startRefundBestEffort(action *refundAction) { +func (r *Repository) startRefundBestEffort(ctx context.Context, action *refundAction) { if action == nil || r.refundFunc == nil { return } - if _, err := r.refundFunc(action.OrderID, action.RefundAmountCent, action.BizType, action.Remark); err != nil { + if _, err := r.refundFunc(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark); err != nil { log.Printf("[order] start refund failed order_id=%d biz_type=%s amount_cent=%d err=%v", action.OrderID, action.BizType, action.RefundAmountCent, err) } } diff --git a/backend/internal/modules/order/repository.go b/backend/internal/modules/order/repository.go index 0e69199..b419709 100644 --- a/backend/internal/modules/order/repository.go +++ b/backend/internal/modules/order/repository.go @@ -1,13 +1,15 @@ package order import ( + "context" + "hfb_sys/backend/internal/modules/chat" "gorm.io/gorm" ) // RefundFunc 由 payment 模块注入,避免 order 与 payment 形成循环依赖。 -type RefundFunc func(orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +type RefundFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) type refundAction struct { OrderID uint64 RefundAmountCent int64 diff --git a/backend/internal/modules/order/repository_integration_test.go b/backend/internal/modules/order/repository_integration_test.go index ebaf0d2..040b6c5 100644 --- a/backend/internal/modules/order/repository_integration_test.go +++ b/backend/internal/modules/order/repository_integration_test.go @@ -102,7 +102,7 @@ func TestRepositoryCreateOrderValidatesListing(t *testing.T) { } db.Create(&listing) - _, err := repo.Create(renter.ID, CreateRequest{ListingID: listing.ID}) + _, err := repo.Create(t.Context(), renter.ID, CreateRequest{ListingID: listing.ID}) if err != tc.wantErr { t.Fatalf("error = %v, want %v", err, tc.wantErr) @@ -144,7 +144,7 @@ func TestRepositoryCreateOrderRejectsOwnListing(t *testing.T) { db.Create(&listing) // 号主尝试租自己的商品 - _, err := repo.Create(owner.ID, CreateRequest{ListingID: listing.ID}) + _, err := repo.Create(t.Context(), owner.ID, CreateRequest{ListingID: listing.ID}) if err != ErrCannotRentOwnListing { t.Fatalf("error = %v, want ErrCannotRentOwnListing", err) diff --git a/backend/internal/modules/order/service.go b/backend/internal/modules/order/service.go index 9bb001c..d9d0368 100644 --- a/backend/internal/modules/order/service.go +++ b/backend/internal/modules/order/service.go @@ -1,6 +1,9 @@ package order -import "errors" +import ( + "context" + "errors" +) var ( ErrDependencyUnavailable = errors.New("dependency unavailable") @@ -32,179 +35,179 @@ func NewService(repo *Repository) *Service { return &Service{repo: repo} } -func (s *Service) Create(userID uint64, req CreateRequest) (*OrderDTO, error) { +func (s *Service) Create(ctx context.Context, userID uint64, req CreateRequest) (*OrderDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if req.ListingID == 0 { return nil, ErrInvalidRentHours } - return s.repo.Create(userID, req) + return s.repo.Create(ctx, userID, req) } -func (s *Service) Cancel(userID uint64, orderID uint64) error { +func (s *Service) Cancel(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.Cancel(userID, orderID) + return s.repo.Cancel(ctx, userID, orderID) } -func (s *Service) Pay(userID uint64, orderID uint64) error { +func (s *Service) Pay(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } if orderID == 0 { return ErrOrderCannotPay } - return s.repo.Pay(userID, orderID) + return s.repo.Pay(ctx, userID, orderID) } -func (s *Service) SubmitHandoff(userID uint64, orderID uint64, req SubmitHandoffRequest) (*HandoffRecordDTO, error) { +func (s *Service) SubmitHandoff(ctx context.Context, userID uint64, orderID uint64, req SubmitHandoffRequest) (*HandoffRecordDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || req.Content == "" { return nil, ErrOrderCannotHandoff } - return s.repo.SubmitHandoff(userID, orderID, req) + return s.repo.SubmitHandoff(ctx, userID, orderID, req) } -func (s *Service) ConfirmReceive(userID uint64, orderID uint64) error { +func (s *Service) ConfirmReceive(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.ConfirmReceive(userID, orderID) + return s.repo.ConfirmReceive(ctx, userID, orderID) } -func (s *Service) HandoffRecords(userID uint64, orderID uint64) ([]HandoffRecordDTO, error) { +func (s *Service) HandoffRecords(ctx context.Context, userID uint64, orderID uint64) ([]HandoffRecordDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.HandoffRecords(userID, orderID) + return s.repo.HandoffRecords(ctx, userID, orderID) } -func (s *Service) SubmitReturn(userID uint64, orderID uint64, req SubmitReturnRequest) (*HandoffRecordDTO, error) { +func (s *Service) SubmitReturn(ctx context.Context, userID uint64, orderID uint64, req SubmitReturnRequest) (*HandoffRecordDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || req.Content == "" { return nil, ErrOrderCannotReturn } - return s.repo.SubmitReturn(userID, orderID, req) + return s.repo.SubmitReturn(ctx, userID, orderID, req) } -func (s *Service) SubmitCheckout(userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) { +func (s *Service) SubmitCheckout(ctx context.Context, userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || req.Content == "" { return nil, ErrCheckoutCannotSubmit } - return s.repo.SubmitCheckout(userID, orderID, req) + return s.repo.SubmitCheckout(ctx, userID, orderID, req) } -func (s *Service) ConfirmReturn(userID uint64, orderID uint64) error { +func (s *Service) ConfirmReturn(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.ConfirmReturn(userID, orderID) + return s.repo.ConfirmReturn(ctx, userID, orderID) } -func (s *Service) ConfirmCheckout(userID uint64, orderID uint64) error { +func (s *Service) ConfirmCheckout(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.ConfirmCheckout(userID, orderID) + return s.repo.ConfirmCheckout(ctx, userID, orderID) } -func (s *Service) CounterCheckout(userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) { +func (s *Service) CounterCheckout(ctx context.Context, userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || req.Reason == "" { return nil, ErrCheckoutCannotCounter } - return s.repo.CounterCheckout(userID, orderID, req) + return s.repo.CounterCheckout(ctx, userID, orderID, req) } -func (s *Service) AcceptCheckout(userID uint64, orderID uint64) error { +func (s *Service) AcceptCheckout(ctx context.Context, userID uint64, orderID uint64) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.AcceptCheckout(userID, orderID) + return s.repo.AcceptCheckout(ctx, userID, orderID) } -func (s *Service) ListForUser(userID uint64) ([]OrderDTO, error) { +func (s *Service) ListForUser(ctx context.Context, userID uint64) ([]OrderDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListForUser(userID) + return s.repo.ListForUser(ctx, userID) } -func (s *Service) ListAdmin(page, pageSize int) (*PaginatedResult, error) { +func (s *Service) ListAdmin(ctx context.Context, page, pageSize int) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.ListAdmin(page, pageSize) + return s.repo.ListAdmin(ctx, page, pageSize) } -func (s *Service) FindAdmin(orderID uint64) (*OrderDTO, error) { +func (s *Service) FindAdmin(ctx context.Context, orderID uint64) (*OrderDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindAdmin(orderID) + return s.repo.FindAdmin(ctx, orderID) } -func (s *Service) HandoffRecordsAdmin(orderID uint64) ([]HandoffRecordDTO, error) { +func (s *Service) HandoffRecordsAdmin(ctx context.Context, orderID uint64) ([]HandoffRecordDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.HandoffRecordsAdmin(orderID) + return s.repo.HandoffRecordsAdmin(ctx, orderID) } -func (s *Service) AdminClose(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { +func (s *Service) AdminClose(ctx context.Context, adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { if s.repo == nil { return ErrDependencyUnavailable } if orderID == 0 || req.Reason == "" { return ErrOrderCannotComplete } - return s.repo.AdminClose(adminID, orderID, req, meta) + return s.repo.AdminClose(ctx, adminID, orderID, req, meta) } -func (s *Service) AdminMarkAbnormal(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { +func (s *Service) AdminMarkAbnormal(ctx context.Context, adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error { if s.repo == nil { return ErrDependencyUnavailable } if orderID == 0 || req.Reason == "" { return ErrOrderCannotComplete } - return s.repo.AdminMarkAbnormal(adminID, orderID, req, meta) + return s.repo.AdminMarkAbnormal(ctx, adminID, orderID, req, meta) } -func (s *Service) AdminRefund(orderID uint64) (*RefundStatusDTO, error) { +func (s *Service) AdminRefund(ctx context.Context, orderID uint64) (*RefundStatusDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 { return nil, ErrOrderCannotComplete } - return s.repo.AdminRefund(orderID) + return s.repo.AdminRefund(ctx, orderID) } -func (s *Service) AdminRefundStatus(orderID uint64) (*RefundStatusDTO, error) { +func (s *Service) AdminRefundStatus(ctx context.Context, orderID uint64) (*RefundStatusDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 { return nil, ErrOrderCannotComplete } - return s.repo.AdminRefundStatus(orderID) + return s.repo.AdminRefundStatus(ctx, orderID) } -func (s *Service) FindForUser(userID uint64, orderID uint64) (*OrderDTO, error) { +func (s *Service) FindForUser(ctx context.Context, userID uint64, orderID uint64) (*OrderDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.FindForUser(userID, orderID) + return s.repo.FindForUser(ctx, userID, orderID) } diff --git a/backend/internal/modules/order/service_test.go b/backend/internal/modules/order/service_test.go index 5f6696b..452582e 100644 --- a/backend/internal/modules/order/service_test.go +++ b/backend/internal/modules/order/service_test.go @@ -8,7 +8,7 @@ import ( // TestServiceDependencyChecks 测试所有 Service 方法的依赖检查 func TestServiceCreateWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.Create(1, CreateRequest{ListingID: 100}) + _, err := svc.Create(t.Context(), 1, CreateRequest{ListingID: 100}) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("Create() error = %v, want ErrDependencyUnavailable", err) } @@ -16,7 +16,7 @@ func TestServiceCreateWithNilRepo(t *testing.T) { func TestServiceCreateWithZeroListingID(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.Create(1, CreateRequest{ListingID: 0}) + _, err := svc.Create(t.Context(), 1, CreateRequest{ListingID: 0}) if !errors.Is(err, ErrInvalidRentHours) { t.Fatalf("Create() error = %v, want ErrInvalidRentHours", err) } @@ -24,7 +24,7 @@ func TestServiceCreateWithZeroListingID(t *testing.T) { func TestServiceCancelWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - err := svc.Cancel(1, 100) + err := svc.Cancel(t.Context(), 1, 100) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("Cancel() error = %v, want ErrDependencyUnavailable", err) } @@ -32,7 +32,7 @@ func TestServiceCancelWithNilRepo(t *testing.T) { func TestServicePayWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - err := svc.Pay(1, 100) + err := svc.Pay(t.Context(), 1, 100) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("Pay() error = %v, want ErrDependencyUnavailable", err) } @@ -40,7 +40,7 @@ func TestServicePayWithNilRepo(t *testing.T) { func TestServicePayWithZeroOrderID(t *testing.T) { svc := &Service{repo: &Repository{}} - err := svc.Pay(1, 0) + err := svc.Pay(t.Context(), 1, 0) if !errors.Is(err, ErrOrderCannotPay) { t.Fatalf("Pay() error = %v, want ErrOrderCannotPay", err) } @@ -48,7 +48,7 @@ func TestServicePayWithZeroOrderID(t *testing.T) { func TestServiceSubmitHandoffWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.SubmitHandoff(1, 100, SubmitHandoffRequest{Content: "test"}) + _, err := svc.SubmitHandoff(t.Context(), 1, 100, SubmitHandoffRequest{Content: "test"}) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("SubmitHandoff() error = %v, want ErrDependencyUnavailable", err) } @@ -56,7 +56,7 @@ func TestServiceSubmitHandoffWithNilRepo(t *testing.T) { func TestServiceSubmitHandoffWithEmptyContent(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.SubmitHandoff(1, 100, SubmitHandoffRequest{Content: ""}) + _, err := svc.SubmitHandoff(t.Context(), 1, 100, SubmitHandoffRequest{Content: ""}) if !errors.Is(err, ErrOrderCannotHandoff) { t.Fatalf("SubmitHandoff() error = %v, want ErrOrderCannotHandoff", err) } @@ -64,7 +64,7 @@ func TestServiceSubmitHandoffWithEmptyContent(t *testing.T) { func TestServiceConfirmReceiveWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - err := svc.ConfirmReceive(1, 100) + err := svc.ConfirmReceive(t.Context(), 1, 100) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("ConfirmReceive() error = %v, want ErrDependencyUnavailable", err) } @@ -72,7 +72,7 @@ func TestServiceConfirmReceiveWithNilRepo(t *testing.T) { func TestServiceSubmitReturnWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.SubmitReturn(1, 100, SubmitReturnRequest{Content: "test"}) + _, err := svc.SubmitReturn(t.Context(), 1, 100, SubmitReturnRequest{Content: "test"}) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("SubmitReturn() error = %v, want ErrDependencyUnavailable", err) } @@ -80,7 +80,7 @@ func TestServiceSubmitReturnWithNilRepo(t *testing.T) { func TestServiceSubmitReturnWithEmptyContent(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.SubmitReturn(1, 100, SubmitReturnRequest{Content: ""}) + _, err := svc.SubmitReturn(t.Context(), 1, 100, SubmitReturnRequest{Content: ""}) if !errors.Is(err, ErrOrderCannotReturn) { t.Fatalf("SubmitReturn() error = %v, want ErrOrderCannotReturn", err) } @@ -88,7 +88,7 @@ func TestServiceSubmitReturnWithEmptyContent(t *testing.T) { func TestServiceSubmitCheckoutWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.SubmitCheckout(1, 100, SubmitCheckoutRequest{Content: "test"}) + _, err := svc.SubmitCheckout(t.Context(), 1, 100, SubmitCheckoutRequest{Content: "test"}) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("SubmitCheckout() error = %v, want ErrDependencyUnavailable", err) } diff --git a/backend/internal/modules/payment/repository.go b/backend/internal/modules/payment/repository.go index 8257d18..77645cd 100644 --- a/backend/internal/modules/payment/repository.go +++ b/backend/internal/modules/payment/repository.go @@ -69,22 +69,22 @@ func (c runtimePaymentConfig) isMockMode() bool { return c.Provider == "mock" } -func (r *Repository) defaultRuntimeConfig() (*runtimePaymentConfig, error) { +func (r *Repository) defaultRuntimeConfig(ctx context.Context) (*runtimePaymentConfig, error) { if r.configRepo == nil { return nil, ErrPaymentUnavailable } - dto, err := r.configRepo.FindDefaultAny(true) + dto, err := r.configRepo.FindDefaultAny(ctx, true) if err != nil { return nil, err } return runtimeConfigFromDTO(dto), nil } -func (r *Repository) runtimeConfigForPayment(payment *model.PaymentOrder) (*runtimePaymentConfig, error) { +func (r *Repository) runtimeConfigForPayment(ctx context.Context, payment *model.PaymentOrder) (*runtimePaymentConfig, error) { provider := firstNonEmpty(payment.Provider, "mock") merchantID := payment.MerchantID if r.configRepo != nil && merchantID != "" { - dto, err := r.configRepo.FindByProviderMerchant(provider, merchantID, true) + dto, err := r.configRepo.FindByProviderMerchant(ctx, provider, merchantID, true) if err == nil { return runtimeConfigFromDTO(dto), nil } @@ -94,7 +94,7 @@ func (r *Repository) runtimeConfigForPayment(payment *model.PaymentOrder) (*runt } if provider != "leshua" { if provider == "lakala" && r.configRepo != nil { - dto, err := r.configRepo.FindDefaultByProvider(provider, true) + dto, err := r.configRepo.FindDefaultByProvider(ctx, provider, true) if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (r *Repository) runtimeConfigForPayment(payment *model.PaymentOrder) (*runt if r.configRepo == nil { return nil, ErrPaymentUnavailable } - dto, err := r.configRepo.FindDefaultByProvider(provider, true) + dto, err := r.configRepo.FindDefaultByProvider(ctx, provider, true) if err != nil { return nil, err } @@ -136,7 +136,7 @@ func runtimeConfigFromDTO(dto *paymentconfig.ConfigDTO) *runtimePaymentConfig { } func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { - defaultConfig, err := r.defaultRuntimeConfig() + defaultConfig, err := r.defaultRuntimeConfig(ctx) if err != nil { return nil, ErrPaymentUnavailable } @@ -144,12 +144,12 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r if err != nil { return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, payment) if err != nil { return nil, ErrPaymentUnavailable } if payment.Status == "paid" { - r.recordConfigUsage(runtimeConfig, payment) + r.recordConfigUsage(ctx, runtimeConfig, payment) dto := toDTO(*payment) return &dto, nil } @@ -166,12 +166,12 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r if err != nil { return nil, err } - r.recordConfigUsage(runtimeConfig, latest) + r.recordConfigUsage(ctx, runtimeConfig, latest) dto := toDTO(*latest) return &dto, nil } if payment.Status == "paying" && (payment.TDCode != "" || payment.JSPayURL != "" || payment.JSPayInfo != "") { - r.recordConfigUsage(runtimeConfig, payment) + r.recordConfigUsage(ctx, runtimeConfig, payment) dto := toDTO(*payment) return &dto, nil } @@ -221,7 +221,7 @@ func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, r if err != nil { return nil, err } - r.recordConfigUsage(runtimeConfig, latest) + r.recordConfigUsage(ctx, runtimeConfig, latest) log.Printf("[payment] payment result order_id=%d order_no=%s payment_id=%d provider=%s amount_cent=%d status=%s provider_order_id=%s", orderID, orderRow.OrderNo, latest.ID, runtimeConfig.Provider, latest.AmountCent, latest.Status, latest.ProviderOrderID) dto := toDTO(*latest) @@ -233,7 +233,7 @@ func (r *Repository) StartWalletRecharge(ctx context.Context, userID uint64, req if userID == 0 || amountCent < moneyCent(MinWalletRechargeAmount) { return nil, ErrPaymentCannotStart } - runtimeConfig, err := r.defaultRuntimeConfig() + runtimeConfig, err := r.defaultRuntimeConfig(ctx) if err != nil { return nil, ErrPaymentUnavailable } @@ -254,7 +254,7 @@ func (r *Repository) StartWalletRecharge(ctx context.Context, userID uint64, req if err != nil { return nil, err } - r.recordConfigUsage(runtimeConfig, latest) + r.recordConfigUsage(ctx, runtimeConfig, latest) dto := toDTO(*latest) return &dto, nil } @@ -303,7 +303,7 @@ func (r *Repository) StartWalletRecharge(ctx context.Context, userID uint64, req if err != nil { return nil, err } - r.recordConfigUsage(runtimeConfig, latest) + r.recordConfigUsage(ctx, runtimeConfig, latest) log.Printf("[payment] wallet recharge result user_id=%d payment_id=%d provider=%s amount_cent=%d status=%s provider_order_id=%s", userID, latest.ID, runtimeConfig.Provider, latest.AmountCent, latest.Status, latest.ProviderOrderID) dto := toDTO(*latest) @@ -318,7 +318,7 @@ func (r *Repository) QueryWalletRecharge(ctx context.Context, userID uint64, pay } return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(&payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, &payment) if err != nil { return nil, ErrPaymentUnavailable } @@ -352,7 +352,7 @@ func (r *Repository) Query(ctx context.Context, userID uint64, orderID uint64) ( } return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(&payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, &payment) if err != nil { return nil, ErrPaymentUnavailable } @@ -392,7 +392,7 @@ func (r *Repository) HandleNotify(ctx context.Context, provider string, params m if err != nil { return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, payment) if err != nil { return nil, ErrPaymentUnavailable } @@ -423,7 +423,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou } return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(&originalPayment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, &originalPayment) if err != nil { return nil, ErrPaymentUnavailable } @@ -471,7 +471,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou if err := r.db.WithContext(ctx).Create(&refundOrder).Error; err != nil { return nil, err } - r.recordConfigUsage(runtimeConfig, &refundOrder) + r.recordConfigUsage(ctx, runtimeConfig, &refundOrder) if err := r.updateOrderRefundStatus(ctx, orderID, refundAmountCent); err != nil { log.Printf("[payment] mock update order refund status failed order_id=%d err=%v", orderID, err) } @@ -484,7 +484,7 @@ func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmou } log.Printf("[payment] refund start order_id=%d order_no=%s payment_id=%d biz_type=%s provider=%s amount_cent=%d merchant_refund_id=%s origin_third_order_id=%s origin_provider_order_id=%s", orderID, originalPayment.OrderNo, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, merchantRefundID, originalPayment.ThirdOrderID, refundOriginProviderOrderID(originalPayment)) - r.recordConfigUsage(runtimeConfig, &refundOrder) + r.recordConfigUsage(ctx, runtimeConfig, &refundOrder) if err := r.markOrderRefunding(ctx, orderID, refundAmountCent); err != nil { log.Printf("[payment] mark order refunding failed order_id=%d err=%v", orderID, err) } @@ -560,7 +560,7 @@ func (r *Repository) QueryRefundStatus(ctx context.Context, orderID uint64) (*Re } return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(&payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, &payment) if err != nil { return nil, ErrPaymentUnavailable } @@ -656,7 +656,7 @@ func (r *Repository) HandleRefundNotify(ctx context.Context, provider string, pa if err != nil { return nil, err } - runtimeConfig, err := r.runtimeConfigForPayment(payment) + runtimeConfig, err := r.runtimeConfigForPayment(ctx, payment) if err != nil { return nil, ErrPaymentUnavailable } @@ -947,7 +947,7 @@ func (r *Repository) confirmPaid(ctx context.Context, payment *model.PaymentOrde if r.orderRepo == nil { return ErrDependencyUnavailable } - if err := r.orderRepo.ConfirmPaidFromChannel(payment.OrderID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo)); err != nil { + if err := r.orderRepo.ConfirmPaidFromChannel(ctx, payment.OrderID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo)); err != nil { return err } } @@ -1041,11 +1041,11 @@ func (r *Repository) verifyNotify(ctx context.Context, payment *model.PaymentOrd return verify, nil } -func (r *Repository) recordConfigUsage(runtimeConfig *runtimePaymentConfig, payment *model.PaymentOrder) { +func (r *Repository) recordConfigUsage(ctx context.Context, runtimeConfig *runtimePaymentConfig, payment *model.PaymentOrder) { if r.configRepo == nil || runtimeConfig == nil || payment == nil || runtimeConfig.ID == 0 { return } - if err := r.configRepo.RecordUsage(runtimeConfig.ID, payment.ID, runtimeConfig.Provider, runtimeConfig.MerchantID, payment.AmountCent, payment.BizType); err != nil { + if err := r.configRepo.RecordUsage(ctx, runtimeConfig.ID, payment.ID, runtimeConfig.Provider, runtimeConfig.MerchantID, payment.AmountCent, payment.BizType); err != nil { log.Printf("[payment] record config usage failed config_id=%d payment_id=%d err=%v", runtimeConfig.ID, payment.ID, err) } } diff --git a/backend/internal/modules/paymentconfig/handler.go b/backend/internal/modules/paymentconfig/handler.go index b66bba9..9ba851f 100644 --- a/backend/internal/modules/paymentconfig/handler.go +++ b/backend/internal/modules/paymentconfig/handler.go @@ -37,7 +37,7 @@ func (h *Handler) List(c *gin.Context) { return } - resp, err := h.service.List(query) + resp, err := h.service.List(c.Request.Context(), query) if err != nil { response.Error(c, http.StatusInternalServerError, "internal_error", "查询失败") return @@ -62,7 +62,7 @@ func (h *Handler) Get(c *gin.Context) { includeSecret := c.Query("include_secret") == "true" - config, err := h.service.Get(id, includeSecret) + config, err := h.service.Get(c.Request.Context(), id, includeSecret) if err == ErrConfigNotFound { response.NotFound(c, "配置不存在") return @@ -87,7 +87,7 @@ func (h *Handler) ExportBackup(c *gin.Context) { return } - backup, err := h.service.ExportBackup(adminID, auditMeta(c)) + backup, err := h.service.ExportBackup(c.Request.Context(), adminID, auditMeta(c)) if err == ErrDecryptionFailed { response.Error(c, http.StatusInternalServerError, "decrypt_failed", "密钥解密失败") return @@ -128,7 +128,7 @@ func (h *Handler) ImportBackup(c *gin.Context) { return } - result, err := h.service.ImportBackup(backup, adminID, auditMeta(c)) + result, err := h.service.ImportBackup(c.Request.Context(), backup, adminID, auditMeta(c)) if err == ErrInvalidBackup || err == ErrEmptyBackup || err == ErrInvalidProvider || err == ErrNameRequired || err == ErrMerchantIDRequired || err == ErrGatewayURLRequired || err == ErrSignKeyRequired || err == ErrNotifyKeyRequired || err == ErrNotifyURLRequired || @@ -168,7 +168,7 @@ func (h *Handler) Create(c *gin.Context) { return } - config, err := h.service.Create(req, adminID, auditMeta(c)) + config, err := h.service.Create(c.Request.Context(), req, adminID, auditMeta(c)) if err == ErrNameRequired || err == ErrMerchantIDRequired || err == ErrGatewayURLRequired || err == ErrSignKeyRequired || err == ErrNotifyKeyRequired || err == ErrInvalidProvider || err == ErrNotifyURLRequired || err == ErrInvalidSignType || err == ErrAppIDRequired || @@ -210,7 +210,7 @@ func (h *Handler) Update(c *gin.Context) { return } - config, err := h.service.Update(id, req, adminID, auditMeta(c)) + config, err := h.service.Update(c.Request.Context(), id, req, adminID, auditMeta(c)) if err == ErrConfigNotFound { response.NotFound(c, "配置不存在") return @@ -246,7 +246,7 @@ func (h *Handler) Delete(c *gin.Context) { return } - err = h.service.Delete(id, adminID, auditMeta(c)) + err = h.service.Delete(c.Request.Context(), id, adminID, auditMeta(c)) if err == ErrConfigNotFound { response.NotFound(c, "配置不存在") return diff --git a/backend/internal/modules/paymentconfig/repository.go b/backend/internal/modules/paymentconfig/repository.go index 816f45d..b86fe4b 100644 --- a/backend/internal/modules/paymentconfig/repository.go +++ b/backend/internal/modules/paymentconfig/repository.go @@ -1,6 +1,7 @@ package paymentconfig import ( + "context" "encoding/json" "errors" "hfb_sys/backend/internal/auditlog" @@ -26,11 +27,11 @@ func NewRepository(db *gorm.DB, encryptor Encryptor) *Repository { } // List 获取配置列表 -func (r *Repository) List(query ListQuery) ([]ConfigDTO, int64, error) { +func (r *Repository) List(ctx context.Context, query ListQuery) ([]ConfigDTO, int64, error) { var items []model.PaymentMerchantConfig var total int64 - db := r.db.Model(&model.PaymentMerchantConfig{}) + db := r.db.WithContext(ctx).Model(&model.PaymentMerchantConfig{}) // 过滤条件 if query.Provider != "" { @@ -79,9 +80,9 @@ func (r *Repository) List(query ListQuery) ([]ConfigDTO, int64, error) { } // FindByID 根据 ID 查询配置 -func (r *Repository) FindByID(id uint64, includeSecret bool) (*ConfigDTO, error) { +func (r *Repository) FindByID(ctx context.Context, id uint64, includeSecret bool) (*ConfigDTO, error) { var item model.PaymentMerchantConfig - if err := r.db.Where("id = ?", id).First(&item).Error; err != nil { + if err := r.db.WithContext(ctx).Where("id = ?", id).First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrConfigNotFound } @@ -95,9 +96,9 @@ func (r *Repository) FindByID(id uint64, includeSecret bool) (*ConfigDTO, error) } // FindDefault 查询默认配置 -func (r *Repository) FindDefault(provider string) (*model.PaymentMerchantConfig, error) { +func (r *Repository) FindDefault(ctx context.Context, provider string) (*model.PaymentMerchantConfig, error) { var item model.PaymentMerchantConfig - if err := r.db.Where("provider = ? AND is_default = ? AND status = ?", provider, true, "active").First(&item).Error; err != nil { + if err := r.db.WithContext(ctx).Where("provider = ? AND is_default = ? AND status = ?", provider, true, "active").First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNoActiveConfigFound } @@ -107,9 +108,9 @@ func (r *Repository) FindDefault(provider string) (*model.PaymentMerchantConfig, } // FindDefaultAny 查询任意服务商的默认启用配置。 -func (r *Repository) FindDefaultAny(includeSecret bool) (*ConfigDTO, error) { +func (r *Repository) FindDefaultAny(ctx context.Context, includeSecret bool) (*ConfigDTO, error) { var item model.PaymentMerchantConfig - if err := r.db.Where("status = ?", "active").Order("is_default DESC, id DESC").First(&item).Error; err != nil { + if err := r.db.WithContext(ctx).Where("status = ?", "active").Order("is_default DESC, id DESC").First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNoActiveConfigFound } @@ -123,8 +124,8 @@ func (r *Repository) FindDefaultAny(includeSecret bool) (*ConfigDTO, error) { } // FindDefaultByProvider 查询指定服务商的默认启用配置。 -func (r *Repository) FindDefaultByProvider(provider string, includeSecret bool) (*ConfigDTO, error) { - item, err := r.FindDefault(provider) +func (r *Repository) FindDefaultByProvider(ctx context.Context, provider string, includeSecret bool) (*ConfigDTO, error) { + item, err := r.FindDefault(ctx, provider) if err != nil { return nil, err } @@ -136,9 +137,9 @@ func (r *Repository) FindDefaultByProvider(provider string, includeSecret bool) } // FindByProviderMerchant 根据服务商和商户号查配置,用于历史支付单继续使用原商户密钥。 -func (r *Repository) FindByProviderMerchant(provider string, merchantID string, includeSecret bool) (*ConfigDTO, error) { +func (r *Repository) FindByProviderMerchant(ctx context.Context, provider string, merchantID string, includeSecret bool) (*ConfigDTO, error) { var item model.PaymentMerchantConfig - if err := r.db.Where("provider = ? AND merchant_id = ?", provider, merchantID). + if err := r.db.WithContext(ctx).Where("provider = ? AND merchant_id = ?", provider, merchantID). Order("status = 'active' DESC, is_default DESC, id DESC"). First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -154,9 +155,9 @@ func (r *Repository) FindByProviderMerchant(provider string, merchantID string, } // ExportBackup 导出所有支付配置备份,包含解密后的密钥。 -func (r *Repository) ExportBackup(actorID uint64, meta AuditMeta) (*ExportBackup, error) { +func (r *Repository) ExportBackup(ctx context.Context, actorID uint64, meta AuditMeta) (*ExportBackup, error) { var backup *ExportBackup - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var items []model.PaymentMerchantConfig if err := tx.Order("is_default DESC, id DESC").Find(&items).Error; err != nil { return err @@ -193,7 +194,7 @@ func (r *Repository) ExportBackup(actorID uint64, meta AuditMeta) (*ExportBackup } // ImportBackup 导入支付配置备份,按 provider + merchant_id 更新或新增。 -func (r *Repository) ImportBackup(backup ExportBackup, actorID uint64, meta AuditMeta) (*ImportBackupResult, error) { +func (r *Repository) ImportBackup(ctx context.Context, backup ExportBackup, actorID uint64, meta AuditMeta) (*ImportBackupResult, error) { if backup.Type != "payment_config_backup" || backup.Version <= 0 { return nil, ErrInvalidBackup } @@ -204,7 +205,7 @@ func (r *Repository) ImportBackup(backup ExportBackup, actorID uint64, meta Audi activeKey := backupActiveKey(backup.Configs) result := &ImportBackupResult{Total: len(backup.Configs)} - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if activeKey != "" { if err := deactivateOtherConfigs(tx, 0, actorID); err != nil { return err @@ -299,23 +300,23 @@ func (r *Repository) ImportBackup(backup ExportBackup, actorID uint64, meta Audi } // FindActiveByProvider 查询提供商的所有激活配置 -func (r *Repository) FindActiveByProvider(provider string) ([]model.PaymentMerchantConfig, error) { +func (r *Repository) FindActiveByProvider(ctx context.Context, provider string) ([]model.PaymentMerchantConfig, error) { var items []model.PaymentMerchantConfig - if err := r.db.Where("provider = ? AND status = ?", provider, "active").Order("is_default DESC, id DESC").Find(&items).Error; err != nil { + if err := r.db.WithContext(ctx).Where("provider = ? AND status = ?", provider, "active").Order("is_default DESC, id DESC").Find(&items).Error; err != nil { return nil, err } return items, nil } // Create 创建配置 -func (r *Repository) Create(req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { +func (r *Repository) Create(ctx context.Context, req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { // 验证必填字段 if err := r.validateCreateRequest(req); err != nil { return nil, err } var dto ConfigDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 加密密钥 encryptedSignKey, err := r.encryptor.Encrypt(req.SignKey) if err != nil { @@ -388,7 +389,7 @@ func (r *Repository) Create(req CreateRequest, actorID uint64, meta AuditMeta) ( } // Update 更新配置 -func (r *Repository) Update(id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { +func (r *Repository) Update(ctx context.Context, id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { if req.SignType != nil && *req.SignType != "" && !isValidSignType(*req.SignType) { return nil, ErrInvalidSignType } @@ -400,7 +401,7 @@ func (r *Repository) Update(id uint64, req UpdateRequest, actorID uint64, meta A } var dto ConfigDTO - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var item model.PaymentMerchantConfig if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", id).First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -507,8 +508,8 @@ func (r *Repository) Update(id uint64, req UpdateRequest, actorID uint64, meta A } // Delete 删除配置 -func (r *Repository) Delete(id uint64, actorID uint64, meta AuditMeta) error { - return r.db.Transaction(func(tx *gorm.DB) error { +func (r *Repository) Delete(ctx context.Context, id uint64, actorID uint64, meta AuditMeta) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var item model.PaymentMerchantConfig if err := tx.Where("id = ?", id).First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -540,9 +541,9 @@ func (r *Repository) Delete(id uint64, actorID uint64, meta AuditMeta) error { } // IncrementUsage 增加使用统计 -func (r *Repository) IncrementUsage(id uint64, amountCent int64) error { +func (r *Repository) IncrementUsage(ctx context.Context, id uint64, amountCent int64) error { now := time.Now() - return r.db.Model(&model.PaymentMerchantConfig{}).Where("id = ?", id).Updates(map[string]any{ + return r.db.WithContext(ctx).Model(&model.PaymentMerchantConfig{}).Where("id = ?", id).Updates(map[string]any{ "total_transactions": gorm.Expr("total_transactions + ?", 1), "total_amount_cent": gorm.Expr("total_amount_cent + ?", amountCent), "last_used_at": now, @@ -550,11 +551,11 @@ func (r *Repository) IncrementUsage(id uint64, amountCent int64) error { } // RecordUsage 记录支付配置命中情况,同一个支付单只记录一次。 -func (r *Repository) RecordUsage(configID uint64, paymentOrderID uint64, provider string, merchantID string, amountCent int64, bizType string) error { +func (r *Repository) RecordUsage(ctx context.Context, configID uint64, paymentOrderID uint64, provider string, merchantID string, amountCent int64, bizType string) error { if configID == 0 || paymentOrderID == 0 { return nil } - return r.db.Transaction(func(tx *gorm.DB) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var existing model.PaymentConfigUsageLog err := tx.Where("payment_order_id = ?", paymentOrderID).First(&existing).Error if err == nil { diff --git a/backend/internal/modules/paymentconfig/service.go b/backend/internal/modules/paymentconfig/service.go index efe1781..0c7485a 100644 --- a/backend/internal/modules/paymentconfig/service.go +++ b/backend/internal/modules/paymentconfig/service.go @@ -1,5 +1,7 @@ package paymentconfig +import "context" + type Service struct { repo *Repository } @@ -9,8 +11,8 @@ func NewService(repo *Repository) *Service { } // List 获取配置列表 -func (s *Service) List(query ListQuery) (*ListResponse, error) { - items, total, err := s.repo.List(query) +func (s *Service) List(ctx context.Context, query ListQuery) (*ListResponse, error) { + items, total, err := s.repo.List(ctx, query) if err != nil { return nil, err } @@ -33,38 +35,38 @@ func (s *Service) List(query ListQuery) (*ListResponse, error) { } // Get 获取单个配置 -func (s *Service) Get(id uint64, includeSecret bool) (*ConfigDTO, error) { - return s.repo.FindByID(id, includeSecret) +func (s *Service) Get(ctx context.Context, id uint64, includeSecret bool) (*ConfigDTO, error) { + return s.repo.FindByID(ctx, id, includeSecret) } // ExportBackup 导出支付配置备份。 -func (s *Service) ExportBackup(actorID uint64, meta AuditMeta) (*ExportBackup, error) { - return s.repo.ExportBackup(actorID, meta) +func (s *Service) ExportBackup(ctx context.Context, actorID uint64, meta AuditMeta) (*ExportBackup, error) { + return s.repo.ExportBackup(ctx, actorID, meta) } // ImportBackup 导入支付配置备份。 -func (s *Service) ImportBackup(backup ExportBackup, actorID uint64, meta AuditMeta) (*ImportBackupResult, error) { - return s.repo.ImportBackup(backup, actorID, meta) +func (s *Service) ImportBackup(ctx context.Context, backup ExportBackup, actorID uint64, meta AuditMeta) (*ImportBackupResult, error) { + return s.repo.ImportBackup(ctx, backup, actorID, meta) } // Create 创建配置 -func (s *Service) Create(req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { - return s.repo.Create(req, actorID, meta) +func (s *Service) Create(ctx context.Context, req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { + return s.repo.Create(ctx, req, actorID, meta) } // Update 更新配置 -func (s *Service) Update(id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { - return s.repo.Update(id, req, actorID, meta) +func (s *Service) Update(ctx context.Context, id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { + return s.repo.Update(ctx, id, req, actorID, meta) } // Delete 删除配置 -func (s *Service) Delete(id uint64, actorID uint64, meta AuditMeta) error { - return s.repo.Delete(id, actorID, meta) +func (s *Service) Delete(ctx context.Context, id uint64, actorID uint64, meta AuditMeta) error { + return s.repo.Delete(ctx, id, actorID, meta) } // GetDefaultConfig 获取默认配置(用于支付模块调用) -func (s *Service) GetDefaultConfig(provider string) (*ConfigDTO, error) { - config, err := s.repo.FindDefault(provider) +func (s *Service) GetDefaultConfig(ctx context.Context, provider string) (*ConfigDTO, error) { + config, err := s.repo.FindDefault(ctx, provider) if err != nil { return nil, err } diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 6e483e9..494945c 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -164,8 +164,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { paymentHandler := payment.NewHandler(paymentService) // Inject refund function into order repo to avoid circular dependency if orderRepo != nil && paymentRepo != nil { - orderRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) + orderRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } @@ -200,8 +200,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { disputeRepo = dispute.NewRepository(deps.DB) } if disputeRepo != nil && paymentRepo != nil { - disputeRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) + disputeRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err }