继续补齐核心模块 Context 超时控制

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