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