From c6848aba0622872ab4fc51ed198708150ddaa48f Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Thu, 26 Mar 2026 18:14:16 +0800 Subject: [PATCH] refactor: update repository interfaces and improve dependency injection - Refactored repository structures to use interfaces instead of concrete types, enhancing flexibility and testability. - Updated various repository methods to accept interfaces, allowing for better dependency management. - Modified wire generation to accommodate new repository interfaces, ensuring proper service injection throughout the application. - Enhanced handler methods to utilize DTOs for filtering and data handling, improving code clarity and maintainability. --- cmd/server/wire_gen.go | 11 +- internal/cache/repository_adapter.go | 8 +- internal/dto/filter.go | 51 ++++++ internal/handler/admin_log_handler.go | 12 +- internal/handler/material_handler.go | 6 +- internal/handler/system_message_handler.go | 26 +-- internal/pkg/hook/moderation_hooks.go | 16 +- internal/pkg/openai/client.go | 36 ++++- internal/repository/channel_repo.go | 30 ++-- internal/repository/comment_repo.go | 76 ++++++--- internal/repository/data_change_log_repo.go | 50 +++--- internal/repository/device_token_repo.go | 63 +++++--- internal/repository/group_repo.go | 27 ++++ internal/repository/login_log_repo.go | 51 +++--- internal/repository/material_repo.go | 91 ++++++----- internal/repository/message_repo.go | 151 +++++++++++++----- internal/repository/notification_repo.go | 39 +++-- internal/repository/operation_log_repo.go | 50 +++--- internal/repository/post_repo.go | 141 ++++++++++------ internal/repository/push_repo.go | 54 ++++--- .../repository/system_notification_repo.go | 45 ++++-- internal/repository/user_activity_repo.go | 45 ++++-- internal/repository/user_repo.go | 111 ++++++++----- internal/repository/vote_repo.go | 33 ++-- internal/service/admin_comment_service.go | 6 +- internal/service/admin_dashboard_service.go | 16 +- internal/service/admin_group_service.go | 4 +- internal/service/admin_post_service.go | 4 +- internal/service/admin_user_service.go | 4 +- internal/service/async_log_manager.go | 28 ++-- internal/service/channel_service.go | 4 +- internal/service/chat_service.go | 39 ++--- internal/service/comment_service.go | 6 +- internal/service/group_service.go | 88 +++------- internal/service/hot_rank_worker.go | 4 +- internal/service/log_cleanup_service.go | 12 +- internal/service/log_service.go | 25 +-- internal/service/material_service.go | 14 +- internal/service/message_service.go | 12 +- internal/service/notification_service.go | 4 +- internal/service/post_service.go | 4 +- internal/service/push_service.go | 12 +- internal/service/system_message_service.go | 65 +++++++- internal/service/user_activity_service.go | 4 +- internal/service/user_service.go | 4 +- internal/service/vote_service.go | 8 +- internal/wire/handler.go | 6 +- internal/wire/infrastructure.go | 4 +- internal/wire/repository.go | 3 +- internal/wire/service.go | 94 ++++++----- 50 files changed, 1034 insertions(+), 663 deletions(-) create mode 100644 internal/dto/filter.go diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 64c8061..12a96f2 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -55,7 +55,7 @@ func InitializeApp() (*App, error) { loginLogRepository := repository.NewLoginLogRepository(db) dataChangeLogRepository := repository.NewDataChangeLogRepository(db) logger := wire.ProvideLogger() - asyncLogManager := wire.ProvideAsyncLogManager(db, operationLogRepository, loginLogRepository, dataChangeLogRepository, logger, config) + asyncLogManager := wire.ProvideAsyncLogManager(operationLogRepository, loginLogRepository, dataChangeLogRepository, logger, config) operationLogService := wire.ProvideOperationLogService(asyncLogManager, operationLogRepository) loginLogService := wire.ProvideLoginLogService(asyncLogManager, loginLogRepository) dataChangeLogService := wire.ProvideDataChangeLogService(asyncLogManager, dataChangeLogRepository) @@ -70,10 +70,11 @@ func InitializeApp() (*App, error) { return nil, err } uploadService := wire.ProvideUploadService(s3Client, userService) - chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache, uploadService) - messageService := wire.ProvideMessageService(db, messageRepository, cache, uploadService) + chatService := wire.ProvideChatService(messageRepository, userRepository, hub, cache, uploadService) + messageService := wire.ProvideMessageService(messageRepository, cache, uploadService) groupRepository := repository.NewGroupRepository(db) - groupService := wire.ProvideGroupService(db, groupRepository, userRepository, messageRepository, hub, cache) + groupJoinRequestRepository := repository.NewGroupJoinRequestRepository(db) + groupService := wire.ProvideGroupService(groupRepository, userRepository, messageRepository, groupJoinRequestRepository, systemNotificationRepository, hub, cache) messageHandler := wire.ProvideMessageHandler(chatService, messageService, userService, groupService, hub) notificationRepository := repository.NewNotificationRepository(db) notificationService := wire.ProvideNotificationService(notificationRepository, cache) @@ -81,7 +82,7 @@ func InitializeApp() (*App, error) { uploadHandler := handler.NewUploadHandler(uploadService) jwtService := wire.ProvideJWTService(config) pushHandler := handler.NewPushHandler(pushService) - systemMessageHandler := wire.ProvideSystemMessageHandler(systemMessageService, systemNotificationRepository, cache) + systemMessageHandler := wire.ProvideSystemMessageHandler(systemMessageService) groupHandler := wire.ProvideGroupHandler(groupService, userService) stickerRepository := repository.NewStickerRepository(db) stickerService := wire.ProvideStickerService(stickerRepository) diff --git a/internal/cache/repository_adapter.go b/internal/cache/repository_adapter.go index b8d94f9..dd2a59f 100644 --- a/internal/cache/repository_adapter.go +++ b/internal/cache/repository_adapter.go @@ -7,11 +7,11 @@ import ( // ConversationRepositoryAdapter 适配 MessageRepository 到 ConversationRepository 接口 type ConversationRepositoryAdapter struct { - repo *repository.MessageRepository + repo repository.MessageRepository } // NewConversationRepositoryAdapter 创建适配器 -func NewConversationRepositoryAdapter(repo *repository.MessageRepository) ConversationRepository { +func NewConversationRepositoryAdapter(repo repository.MessageRepository) ConversationRepository { return &ConversationRepositoryAdapter{repo: repo} } @@ -42,11 +42,11 @@ func (a *ConversationRepositoryAdapter) GetUnreadCount(userID, convID string) (i // MessageRepositoryAdapter 适配 MessageRepository 到 MessageRepository 接口 type MessageRepositoryAdapter struct { - repo *repository.MessageRepository + repo repository.MessageRepository } // NewMessageRepositoryAdapter 创建适配器 -func NewMessageRepositoryAdapter(repo *repository.MessageRepository) MessageRepository { +func NewMessageRepositoryAdapter(repo repository.MessageRepository) MessageRepository { return &MessageRepositoryAdapter{repo: repo} } diff --git a/internal/dto/filter.go b/internal/dto/filter.go new file mode 100644 index 0000000..c490512 --- /dev/null +++ b/internal/dto/filter.go @@ -0,0 +1,51 @@ +package dto + +import "time" + +// LogFilter 日志过滤条件 +type LogFilter struct { + UserID string + Operation string + TargetType string + TargetID string + Status string + IP string + StartTime string + EndTime string +} + +// LoginFilter 登录日志过滤条件 +type LoginFilter struct { + UserID string + Event string + Result string + FailReason string + LoginType string + IP string + StartTime time.Time + EndTime time.Time +} + +// DataChangeFilter 数据变更日志过滤条件 +type DataChangeFilter struct { + UserID string + OperatorID string + ChangeType string + TargetType string + OperatorType string + IP string + StartTime string + EndTime string +} + +// MaterialFileQueryParams 学习资料查询参数 +type MaterialFileQueryParams struct { + SubjectID string + FileType string + Status string + Keyword string + Page int + PageSize int + SortBy string + SortOrder string +} diff --git a/internal/handler/admin_log_handler.go b/internal/handler/admin_log_handler.go index e543875..b7766f7 100644 --- a/internal/handler/admin_log_handler.go +++ b/internal/handler/admin_log_handler.go @@ -4,8 +4,8 @@ import ( "strconv" "time" + "carrot_bbs/internal/dto" "carrot_bbs/internal/pkg/response" - "carrot_bbs/internal/repository" "carrot_bbs/internal/service" "github.com/gin-gonic/gin" @@ -37,7 +37,7 @@ func (h *AdminLogHandler) GetOperationLogs(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - filters := repository.LogFilter{ + filters := dto.LogFilter{ UserID: c.Query("user_id"), Operation: c.Query("operation"), TargetType: c.Query("target_type"), @@ -70,7 +70,7 @@ func (h *AdminLogHandler) GetLoginLogs(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - filters := repository.LoginFilter{ + filters := dto.LoginFilter{ UserID: c.Query("user_id"), Event: c.Query("event"), Result: c.Query("result"), @@ -113,7 +113,7 @@ func (h *AdminLogHandler) GetDataChangeLogs(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - filters := repository.DataChangeFilter{ + filters := dto.DataChangeFilter{ UserID: c.Query("user_id"), OperatorID: c.Query("operator_id"), ChangeType: c.Query("change_type"), @@ -190,7 +190,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) { "logs": logs, }) case "login": - logs, _, err := h.loginLogService.GetLoginLogs(c.Request.Context(), repository.LoginFilter{}, 1, 10000) + logs, _, err := h.loginLogService.GetLoginLogs(c.Request.Context(), dto.LoginFilter{}, 1, 10000) if err != nil { response.HandleError(c, err, "failed to export login logs") return @@ -200,7 +200,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) { "log": logs, }) case "data_change": - logs, _, err := h.dataChangeLogService.GetDataChangeLogs(c.Request.Context(), repository.DataChangeFilter{}, 1, 10000) + logs, _, err := h.dataChangeLogService.GetDataChangeLogs(c.Request.Context(), dto.DataChangeFilter{}, 1, 10000) if err != nil { response.HandleError(c, err, "failed to export data change logs") return diff --git a/internal/handler/material_handler.go b/internal/handler/material_handler.go index 97d1dea..50809cd 100644 --- a/internal/handler/material_handler.go +++ b/internal/handler/material_handler.go @@ -3,9 +3,9 @@ package handler import ( "strconv" + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "carrot_bbs/internal/pkg/response" - "carrot_bbs/internal/repository" "carrot_bbs/internal/service" "github.com/gin-gonic/gin" @@ -147,7 +147,7 @@ func (h *MaterialHandler) ListMaterials(c *gin.Context) { fileType := c.Query("file_type") keyword := c.Query("keyword") - params := repository.MaterialFileQueryParams{ + params := dto.MaterialFileQueryParams{ SubjectID: subjectID, FileType: fileType, Keyword: keyword, @@ -328,7 +328,7 @@ func (h *MaterialHandler) AdminListMaterials(c *gin.Context) { status := c.Query("status") keyword := c.Query("keyword") - params := repository.MaterialFileQueryParams{ + params := dto.MaterialFileQueryParams{ SubjectID: subjectID, FileType: fileType, Status: status, diff --git a/internal/handler/system_message_handler.go b/internal/handler/system_message_handler.go index 23ea641..00c1101 100644 --- a/internal/handler/system_message_handler.go +++ b/internal/handler/system_message_handler.go @@ -3,33 +3,24 @@ package handler import ( "strconv" - "carrot_bbs/internal/cache" - "github.com/gin-gonic/gin" "carrot_bbs/internal/dto" "carrot_bbs/internal/pkg/response" - "carrot_bbs/internal/repository" "carrot_bbs/internal/service" ) // SystemMessageHandler 系统消息处理器 type SystemMessageHandler struct { systemMsgService service.SystemMessageService - notifyRepo *repository.SystemNotificationRepository - cache cache.Cache } // NewSystemMessageHandler 创建系统消息处理器 func NewSystemMessageHandler( systemMsgService service.SystemMessageService, - notifyRepo *repository.SystemNotificationRepository, - cacheBackend cache.Cache, ) *SystemMessageHandler { return &SystemMessageHandler{ systemMsgService: systemMsgService, - notifyRepo: notifyRepo, - cache: cacheBackend, } } @@ -53,8 +44,8 @@ func (h *SystemMessageHandler) GetSystemMessages(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - // 获取当前用户的系统通知(从独立表中获取) - notifications, total, err := h.notifyRepo.GetByReceiverID(userID, page, pageSize) + // 获取当前用户的系统通知 + notifications, total, err := h.systemMsgService.GetNotifications(userID, page, pageSize) if err != nil { response.InternalServerError(c, "failed to get system messages") return @@ -83,7 +74,7 @@ func (h *SystemMessageHandler) GetSystemMessagesByCursor(c *gin.Context) { pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) // 获取当前用户的系统通知(使用兼容旧版接口) - notifications, nextCursor, hasMore, err := h.notifyRepo.GetByReceiverIDCursorLegacy(userID, cursorStr, pageSize) + notifications, nextCursor, hasMore, err := h.systemMsgService.GetNotificationsByCursor(userID, cursorStr, pageSize) if err != nil { response.InternalServerError(c, "failed to get system messages") return @@ -117,7 +108,7 @@ func (h *SystemMessageHandler) GetUnreadCount(c *gin.Context) { } // 获取当前用户的未读通知数 - unreadCount, err := h.notifyRepo.GetUnreadCount(userID) + unreadCount, err := h.systemMsgService.GetUnreadCount(userID) if err != nil { response.InternalServerError(c, "failed to get unread count") return @@ -145,12 +136,11 @@ func (h *SystemMessageHandler) MarkAsRead(c *gin.Context) { } // 标记为已读 - err = h.notifyRepo.MarkAsRead(notificationID, userID) + err = h.systemMsgService.MarkAsRead(notificationID, userID) if err != nil { response.InternalServerError(c, "failed to mark as read") return } - cache.InvalidateUnreadSystem(h.cache, userID) response.SuccessWithMessage(c, "marked as read", nil) } @@ -165,12 +155,11 @@ func (h *SystemMessageHandler) MarkAllAsRead(c *gin.Context) { } // 标记当前用户所有通知为已读 - err := h.notifyRepo.MarkAllAsRead(userID) + err := h.systemMsgService.MarkAllAsRead(userID) if err != nil { response.InternalServerError(c, "failed to mark all as read") return } - cache.InvalidateUnreadSystem(h.cache, userID) response.SuccessWithMessage(c, "all messages marked as read", nil) } @@ -192,12 +181,11 @@ func (h *SystemMessageHandler) DeleteSystemMessage(c *gin.Context) { } // 删除通知 - err = h.notifyRepo.Delete(notificationID, userID) + err = h.systemMsgService.DeleteNotification(notificationID, userID) if err != nil { response.InternalServerError(c, "failed to delete notification") return } - cache.InvalidateUnreadSystem(h.cache, userID) response.SuccessWithMessage(c, "notification deleted", nil) } diff --git a/internal/pkg/hook/moderation_hooks.go b/internal/pkg/hook/moderation_hooks.go index 26fca59..296130f 100644 --- a/internal/pkg/hook/moderation_hooks.go +++ b/internal/pkg/hook/moderation_hooks.go @@ -13,7 +13,7 @@ import ( type PostModerationHook struct { name string postAIService PostAIService - postRepo *repository.PostRepository + postRepo repository.PostRepository strictMode bool } @@ -23,7 +23,7 @@ type PostAIService interface { ModerateComment(ctx context.Context, content string, images []string) error } -func NewPostModerationHook(postAIService PostAIService, postRepo *repository.PostRepository, strictMode bool) *PostModerationHook { +func NewPostModerationHook(postAIService PostAIService, postRepo repository.PostRepository, strictMode bool) *PostModerationHook { return &PostModerationHook{ name: "moderation.post.ai", postAIService: postAIService, @@ -107,11 +107,11 @@ func (h *PostModerationHook) Execute(ctx *HookContext) error { type CommentModerationHook struct { name string postAIService PostAIService - commentRepo *repository.CommentRepository + commentRepo repository.CommentRepository strictMode bool } -func NewCommentModerationHook(postAIService PostAIService, commentRepo *repository.CommentRepository, strictMode bool) *CommentModerationHook { +func NewCommentModerationHook(postAIService PostAIService, commentRepo repository.CommentRepository, strictMode bool) *CommentModerationHook { return &CommentModerationHook{ name: "moderation.comment.ai", postAIService: postAIService, @@ -300,8 +300,8 @@ func (h *SensitiveWordHook) executeComment(ctx *HookContext) error { type ModerationHooks struct { postAIService PostAIService sensitiveService SensitiveService - postRepo *repository.PostRepository - commentRepo *repository.CommentRepository + postRepo repository.PostRepository + commentRepo repository.CommentRepository strictMode bool replaceStr string } @@ -309,8 +309,8 @@ type ModerationHooks struct { func NewModerationHooks( postAIService PostAIService, sensitiveService SensitiveService, - postRepo *repository.PostRepository, - commentRepo *repository.CommentRepository, + postRepo repository.PostRepository, + commentRepo repository.CommentRepository, strictMode bool, replaceStr string, ) *ModerationHooks { diff --git a/internal/pkg/openai/client.go b/internal/pkg/openai/client.go index 266c2f3..a5ff8f6 100644 --- a/internal/pkg/openai/client.go +++ b/internal/pkg/openai/client.go @@ -69,14 +69,46 @@ func (c *clientImpl) ModeratePost(ctx context.Context, title, content string, im if !c.IsEnabled() { return true, "", nil } - return c.moderateContentInBatches(ctx, fmt.Sprintf("帖子标题:%s\n帖子内容:%s", title, content), images) + + // 构建帖子提示词,处理标题或内容为空的情况 + var prompt strings.Builder + if strings.TrimSpace(title) != "" { + prompt.WriteString("帖子标题:" + title) + } + if strings.TrimSpace(content) != "" { + if prompt.Len() > 0 { + prompt.WriteString("\n") + } + prompt.WriteString("帖子内容:" + content) + } + // 如果标题和内容都为空但有图片,说明是纯图片帖子 + if prompt.Len() == 0 && len(normalizeImageURLs(images)) > 0 { + prompt.WriteString("帖子内容:[仅包含图片]") + } + if prompt.Len() == 0 { + prompt.WriteString("帖子内容:[空]") + } + + return c.moderateContentInBatches(ctx, prompt.String(), images) } func (c *clientImpl) ModerateComment(ctx context.Context, content string, images []string) (bool, string, error) { if !c.IsEnabled() { return true, "", nil } - return c.moderateContentInBatches(ctx, fmt.Sprintf("评论内容:%s", content), images) + + // 构建评论提示词,处理纯图片评论的情况 + var prompt strings.Builder + if strings.TrimSpace(content) != "" { + prompt.WriteString("评论内容:" + content) + } else if len(normalizeImageURLs(images)) > 0 { + // 评论只有图片没有文字,说明是纯图片评论 + prompt.WriteString("评论内容:[仅包含图片]") + } else { + prompt.WriteString("评论内容:[空]") + } + + return c.moderateContentInBatches(ctx, prompt.String(), images) } func (c *clientImpl) moderateContentInBatches(ctx context.Context, contentPrompt string, images []string) (bool, string, error) { diff --git a/internal/repository/channel_repo.go b/internal/repository/channel_repo.go index 58e47cb..04cd2fc 100644 --- a/internal/repository/channel_repo.go +++ b/internal/repository/channel_repo.go @@ -6,16 +6,26 @@ import ( "gorm.io/gorm" ) -// ChannelRepository 频道配置仓储 -type ChannelRepository struct { +// ChannelRepository 频道配置仓储接口 +type ChannelRepository interface { + ListActive() ([]*model.Channel, error) + ListAll() ([]*model.Channel, error) + Create(channel *model.Channel) error + Update(channel *model.Channel) error + GetByID(id string) (*model.Channel, error) + Delete(id string) error +} + +// channelRepository 频道配置仓储实现 +type channelRepository struct { db *gorm.DB } -func NewChannelRepository(db *gorm.DB) *ChannelRepository { - return &ChannelRepository{db: db} +func NewChannelRepository(db *gorm.DB) ChannelRepository { + return &channelRepository{db: db} } -func (r *ChannelRepository) ListActive() ([]*model.Channel, error) { +func (r *channelRepository) ListActive() ([]*model.Channel, error) { var channels []*model.Channel err := r.db. Where("is_active = ?", true). @@ -24,7 +34,7 @@ func (r *ChannelRepository) ListActive() ([]*model.Channel, error) { return channels, err } -func (r *ChannelRepository) ListAll() ([]*model.Channel, error) { +func (r *channelRepository) ListAll() ([]*model.Channel, error) { var channels []*model.Channel err := r.db. Order("sort_order ASC, created_at ASC"). @@ -32,15 +42,15 @@ func (r *ChannelRepository) ListAll() ([]*model.Channel, error) { return channels, err } -func (r *ChannelRepository) Create(channel *model.Channel) error { +func (r *channelRepository) Create(channel *model.Channel) error { return r.db.Create(channel).Error } -func (r *ChannelRepository) Update(channel *model.Channel) error { +func (r *channelRepository) Update(channel *model.Channel) error { return r.db.Save(channel).Error } -func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) { +func (r *channelRepository) GetByID(id string) (*model.Channel, error) { var channel model.Channel if err := r.db.First(&channel, "id = ?", id).Error; err != nil { return nil, err @@ -48,7 +58,7 @@ func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) { return &channel, nil } -func (r *ChannelRepository) Delete(id string) error { +func (r *channelRepository) Delete(id string) error { return r.db.Delete(&model.Channel{}, "id = ?", id).Error } diff --git a/internal/repository/comment_repo.go b/internal/repository/comment_repo.go index c2f50de..eb3a0cc 100644 --- a/internal/repository/comment_repo.go +++ b/internal/repository/comment_repo.go @@ -8,23 +8,47 @@ import ( "gorm.io/gorm" ) -// CommentRepository 评论仓储 -type CommentRepository struct { +// CommentRepository 评论仓储接口 +type CommentRepository interface { + Create(comment *model.Comment) error + GetByID(id string) (*model.Comment, error) + Update(comment *model.Comment) error + UpdateModerationStatus(commentID string, status model.CommentStatus) error + Delete(id string) error + ApplyPublishedStats(comment *model.Comment) error + GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error) + GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error) + GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) + GetReplies(parentID string) ([]*model.Comment, error) + Like(commentID, userID string) error + Unlike(commentID, userID string) error + IsLiked(commentID, userID string) bool + IsLikedBatch(commentIDs []string, userID string) map[string]bool + GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) + GetAdminCommentByID(id string) (*model.Comment, error) + BatchDelete(ids []string) ([]string, error) + GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) + GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) + GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) +} + +// commentRepository 评论仓储实现 +type commentRepository struct { db *gorm.DB } // NewCommentRepository 创建评论仓储 -func NewCommentRepository(db *gorm.DB) *CommentRepository { - return &CommentRepository{db: db} +func NewCommentRepository(db *gorm.DB) CommentRepository { + return &commentRepository{db: db} } // Create 创建评论 -func (r *CommentRepository) Create(comment *model.Comment) error { +func (r *commentRepository) Create(comment *model.Comment) error { return r.db.Create(comment).Error } // GetByID 根据ID获取评论 -func (r *CommentRepository) GetByID(id string) (*model.Comment, error) { +func (r *commentRepository) GetByID(id string) (*model.Comment, error) { var comment model.Comment err := r.db.Preload("User").First(&comment, "id = ?", id).Error if err != nil { @@ -34,12 +58,12 @@ func (r *CommentRepository) GetByID(id string) (*model.Comment, error) { } // Update 更新评论 -func (r *CommentRepository) Update(comment *model.Comment) error { +func (r *commentRepository) Update(comment *model.Comment) error { return r.db.Save(comment).Error } // UpdateModerationStatus 更新评论审核状态 -func (r *CommentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error { +func (r *commentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error { // 审核状态更新属于管理操作,不应影响评论内容更新时间(updated_at) return r.db.Model(&model.Comment{}). Where("id = ?", commentID). @@ -47,7 +71,7 @@ func (r *CommentRepository) UpdateModerationStatus(commentID string, status mode } // Delete 删除评论(软删除,同时清理关联数据) -func (r *CommentRepository) Delete(id string) error { +func (r *commentRepository) Delete(id string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 先查询评论获取post_id和parent_id var comment model.Comment @@ -91,7 +115,7 @@ func (r *CommentRepository) Delete(id string) error { } // ApplyPublishedStats 在评论审核通过后更新帖子评论数/回复数 -func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error { +func (r *commentRepository) ApplyPublishedStats(comment *model.Comment) error { if comment == nil { return nil } @@ -118,7 +142,7 @@ func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error { } // GetByPostID 获取帖子评论 -func (r *CommentRepository) GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error) { +func (r *commentRepository) GetByPostID(postID string, page, pageSize int) ([]*model.Comment, int64, error) { var comments []*model.Comment var total int64 @@ -136,7 +160,7 @@ func (r *CommentRepository) GetByPostID(postID string, page, pageSize int) ([]*m // GetByPostIDWithReplies 获取帖子评论(包含回复,扁平化结构) // 所有层级的回复都扁平化展示在顶级评论的 replies 中 -func (r *CommentRepository) GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error) { +func (r *commentRepository) GetByPostIDWithReplies(postID string, page, pageSize, replyLimit int) ([]*model.Comment, int64, error) { var comments []*model.Comment var total int64 @@ -212,7 +236,7 @@ func (r *CommentRepository) GetByPostIDWithReplies(postID string, page, pageSize } // loadFlatReplies 加载评论的所有回复(扁平化,所有层级都在同一层) -func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit int) { +func (r *commentRepository) loadFlatReplies(rootComment *model.Comment, limit int) { var allReplies []*model.Comment // 查询所有以该评论为根评论的回复(不包括顶级评论本身) @@ -226,7 +250,7 @@ func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit in } // GetRepliesByRootID 根据根评论ID分页获取回复(扁平化) -func (r *CommentRepository) GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) { +func (r *commentRepository) GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) { var replies []*model.Comment var total int64 @@ -246,7 +270,7 @@ func (r *CommentRepository) GetRepliesByRootID(rootID string, page, pageSize int } // GetReplies 获取回复 -func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error) { +func (r *commentRepository) GetReplies(parentID string) ([]*model.Comment, error) { var comments []*model.Comment err := r.db.Where("parent_id = ? AND status = ?", parentID, model.CommentStatusPublished). Preload("User"). @@ -256,7 +280,7 @@ func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error } // Like 点赞评论(使用事务保证数据一致性) -func (r *CommentRepository) Like(commentID, userID string) error { +func (r *commentRepository) Like(commentID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 检查是否已经点赞 var existing model.CommentLike @@ -285,7 +309,7 @@ func (r *CommentRepository) Like(commentID, userID string) error { } // Unlike 取消点赞评论(使用事务保证数据一致性) -func (r *CommentRepository) Unlike(commentID, userID string) error { +func (r *commentRepository) Unlike(commentID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { result := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{}) if result.Error != nil { @@ -301,7 +325,7 @@ func (r *CommentRepository) Unlike(commentID, userID string) error { } // IsLiked 检查是否已点赞 -func (r *CommentRepository) IsLiked(commentID, userID string) bool { +func (r *commentRepository) IsLiked(commentID, userID string) bool { var count int64 r.db.Model(&model.CommentLike{}).Where("comment_id = ? AND user_id = ?", commentID, userID).Count(&count) return count > 0 @@ -309,7 +333,7 @@ func (r *CommentRepository) IsLiked(commentID, userID string) bool { // IsLikedBatch 批量检查是否点赞(解决 N+1 问题) // 返回 map[commentID]bool -func (r *CommentRepository) IsLikedBatch(commentIDs []string, userID string) map[string]bool { +func (r *commentRepository) IsLikedBatch(commentIDs []string, userID string) map[string]bool { result := make(map[string]bool) if len(commentIDs) == 0 || userID == "" { return result @@ -347,7 +371,7 @@ type AdminCommentListFilter struct { } // GetAdminCommentList 获取管理端评论列表 -func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) { +func (r *commentRepository) GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) { var comments []*model.Comment var total int64 @@ -388,7 +412,7 @@ func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) ( } // GetAdminCommentByID 获取管理端评论详情(包含关联信息) -func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, error) { +func (r *commentRepository) GetAdminCommentByID(id string) (*model.Comment, error) { var comment model.Comment err := r.db.Preload("User").First(&comment, "id = ?", id).Error if err != nil { @@ -398,7 +422,7 @@ func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, erro } // BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题) -func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) { +func (r *commentRepository) BatchDelete(ids []string) ([]string, error) { if len(ids) == 0 { return nil, nil } @@ -452,7 +476,7 @@ func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) { } // GetCommentsByPostIDForAdmin 管理端获取帖子的评论列表(包含所有状态) -func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) { +func (r *commentRepository) GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) { var comments []*model.Comment var total int64 @@ -472,7 +496,7 @@ func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pag // GetCommentsByCursor 游标分页获取帖子评论(包含回复) // 排序方式:created_at DESC -func (r *CommentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) { +func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) { // 构建基础查询 - 只查询顶级评论 query := r.db.WithContext(ctx).Model(&model.Comment{}). Where("post_id = ? AND parent_id IS NULL AND status = ?", postID, model.CommentStatusPublished) @@ -531,7 +555,7 @@ func (r *CommentRepository) GetCommentsByCursor(ctx context.Context, postID stri } // loadRepliesForComments 为评论列表加载回复 -func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) { +func (r *commentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) { if len(comments) == 0 { return } @@ -593,7 +617,7 @@ func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, re // GetRepliesByCursor 游标分页获取根评论的回复 // 排序方式:created_at ASC(回复按时间正序) -func (r *CommentRepository) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) { +func (r *commentRepository) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.Comment{}). Where("root_id = ? AND status = ?", rootID, model.CommentStatusPublished) diff --git a/internal/repository/data_change_log_repo.go b/internal/repository/data_change_log_repo.go index 1d12e63..f99d81c 100644 --- a/internal/repository/data_change_log_repo.go +++ b/internal/repository/data_change_log_repo.go @@ -3,28 +3,40 @@ package repository import ( "context" + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "gorm.io/gorm" ) -// DataChangeLogRepository 数据变更日志仓储 -type DataChangeLogRepository struct { +// DataChangeLogRepository 数据变更日志仓储接口 +type DataChangeLogRepository interface { + CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error + BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error + GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) + GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) + GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) + DeleteOldLogs(ctx context.Context, beforeDate string) error + GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error) +} + +// dataChangeLogRepository 数据变更日志仓储实现 +type dataChangeLogRepository struct { db *gorm.DB } // NewDataChangeLogRepository 创建数据变更日志仓储 -func NewDataChangeLogRepository(db *gorm.DB) *DataChangeLogRepository { - return &DataChangeLogRepository{db: db} +func NewDataChangeLogRepository(db *gorm.DB) DataChangeLogRepository { + return &dataChangeLogRepository{db: db} } // CreateDataChangeLog 创建单条数据变更日志 -func (r *DataChangeLogRepository) CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error { +func (r *dataChangeLogRepository) CreateDataChangeLog(ctx context.Context, log *model.DataChangeLog) error { return r.db.WithContext(ctx).Create(log).Error } // BatchCreateDataChangeLogs 批量创建数据变更日志 -func (r *DataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error { +func (r *dataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context, logs []*model.DataChangeLog) error { if len(logs) == 0 { return nil } @@ -32,7 +44,7 @@ func (r *DataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context, } // GetDataChangeLogs 获取数据变更日志列表(分页) -func (r *DataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) { +func (r *dataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) { query := r.db.WithContext(ctx).Model(&model.DataChangeLog{}) if filters.UserID != "" { @@ -75,22 +87,22 @@ func (r *DataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters } // GetDataChangeLogsByUser 获取指定用户的数据变更日志 -func (r *DataChangeLogRepository) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { - return r.GetDataChangeLogs(ctx, DataChangeFilter{UserID: userID}, page, pageSize) +func (r *dataChangeLogRepository) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { + return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{UserID: userID}, page, pageSize) } // GetDataChangeLogsByOperator 获取指定操作人的日志 -func (r *DataChangeLogRepository) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { - return r.GetDataChangeLogs(ctx, DataChangeFilter{OperatorID: operatorID}, page, pageSize) +func (r *dataChangeLogRepository) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { + return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{OperatorID: operatorID}, page, pageSize) } // DeleteOldLogs 删除旧的数据变更日志(用于定时清理) -func (r *DataChangeLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { +func (r *dataChangeLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.DataChangeLog{}).Error } // GetStatistics 获取数据变更日志统计 -func (r *DataChangeLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error) { +func (r *dataChangeLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*DataChangeLogStatistics, error) { stats := &DataChangeLogStatistics{} query := r.db.WithContext(ctx).Model(&model.DataChangeLog{}) @@ -116,18 +128,6 @@ func (r *DataChangeLogRepository) GetStatistics(ctx context.Context, startTime, return stats, nil } -// DataChangeFilter 数据变更日志过滤条件 -type DataChangeFilter struct { - UserID string - OperatorID string - ChangeType string - TargetType string - OperatorType string - IP string - StartTime string - EndTime string -} - // DataChangeLogStatistics 数据变更日志统计 type DataChangeLogStatistics struct { TotalCount int64 diff --git a/internal/repository/device_token_repo.go b/internal/repository/device_token_repo.go index 49cb5a6..cbba104 100644 --- a/internal/repository/device_token_repo.go +++ b/internal/repository/device_token_repo.go @@ -8,23 +8,44 @@ import ( "gorm.io/gorm" ) -// DeviceTokenRepository 设备Token仓储 -type DeviceTokenRepository struct { +// DeviceTokenRepository 设备Token仓储接口 +type DeviceTokenRepository interface { + Create(token *model.DeviceToken) error + GetByID(id int64) (*model.DeviceToken, error) + Update(token *model.DeviceToken) error + Delete(id int64) error + GetByUserID(userID string) ([]*model.DeviceToken, error) + GetActiveByUserID(userID string) ([]*model.DeviceToken, error) + GetByDeviceID(deviceID string) (*model.DeviceToken, error) + GetByPushToken(pushToken string) (*model.DeviceToken, error) + DeactivateAllExcept(userID int64, deviceID string) error + Upsert(token *model.DeviceToken) error + UpdateLastUsed(deviceID string) error + Deactivate(deviceID string) error + Activate(deviceID string) error + DeleteByUserID(userID int64) error + GetDeviceCountByUserID(userID int64) (int64, error) + GetActiveDeviceCountByUserID(userID int64) (int64, error) + DeleteInactiveDevices(before time.Time) error +} + +// deviceTokenRepository 设备Token仓储实现 +type deviceTokenRepository struct { db *gorm.DB } // NewDeviceTokenRepository 创建设备Token仓储 -func NewDeviceTokenRepository(db *gorm.DB) *DeviceTokenRepository { - return &DeviceTokenRepository{db: db} +func NewDeviceTokenRepository(db *gorm.DB) DeviceTokenRepository { + return &deviceTokenRepository{db: db} } // Create 创建设备Token -func (r *DeviceTokenRepository) Create(token *model.DeviceToken) error { +func (r *deviceTokenRepository) Create(token *model.DeviceToken) error { return r.db.Create(token).Error } // GetByID 根据ID获取设备Token -func (r *DeviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) { +func (r *deviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) { var token model.DeviceToken err := r.db.First(&token, "id = ?", id).Error if err != nil { @@ -34,18 +55,18 @@ func (r *DeviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) { } // Update 更新设备Token -func (r *DeviceTokenRepository) Update(token *model.DeviceToken) error { +func (r *deviceTokenRepository) Update(token *model.DeviceToken) error { return r.db.Save(token).Error } // Delete 删除设备Token(软删除) -func (r *DeviceTokenRepository) Delete(id int64) error { +func (r *deviceTokenRepository) Delete(id int64) error { return r.db.Delete(&model.DeviceToken{}, id).Error } // GetByUserID 获取用户所有设备 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *DeviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken, error) { +func (r *deviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken, error) { var tokens []*model.DeviceToken err := r.db.Where("user_id = ?", userID). Order("created_at DESC"). @@ -55,7 +76,7 @@ func (r *DeviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken // GetActiveByUserID 获取用户活跃设备 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *DeviceTokenRepository) GetActiveByUserID(userID string) ([]*model.DeviceToken, error) { +func (r *deviceTokenRepository) GetActiveByUserID(userID string) ([]*model.DeviceToken, error) { var tokens []*model.DeviceToken err := r.db.Where("user_id = ? AND is_active = ?", userID, true). Order("last_used_at DESC"). @@ -64,7 +85,7 @@ func (r *DeviceTokenRepository) GetActiveByUserID(userID string) ([]*model.Devic } // GetByDeviceID 根据设备ID获取设备Token -func (r *DeviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceToken, error) { +func (r *deviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceToken, error) { var token model.DeviceToken err := r.db.Where("device_id = ?", deviceID).First(&token).Error if err != nil { @@ -74,7 +95,7 @@ func (r *DeviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceTok } // GetByPushToken 根据推送Token获取设备信息 -func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) { +func (r *deviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) { var token model.DeviceToken err := r.db.Where("push_token = ?", pushToken).First(&token).Error if err != nil { @@ -84,7 +105,7 @@ func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceT } // DeactivateAllExcept 登出其他设备(停用除指定设备外的所有设备) -func (r *DeviceTokenRepository) DeactivateAllExcept(userID int64, deviceID string) error { +func (r *deviceTokenRepository) DeactivateAllExcept(userID int64, deviceID string) error { return r.db.Model(&model.DeviceToken{}). Where("user_id = ? AND device_id != ?", userID, deviceID). Update("is_active", false).Error @@ -92,7 +113,7 @@ func (r *DeviceTokenRepository) DeactivateAllExcept(userID int64, deviceID strin // Upsert 创建或更新设备Token // 如果设备ID已存在,则更新Token和激活状态;否则创建新记录 -func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error { +func (r *deviceTokenRepository) Upsert(token *model.DeviceToken) error { var existing model.DeviceToken err := r.db.Where("device_id = ?", token.DeviceID).First(&existing).Error @@ -113,21 +134,21 @@ func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error { } // UpdateLastUsed 更新最后使用时间 -func (r *DeviceTokenRepository) UpdateLastUsed(deviceID string) error { +func (r *deviceTokenRepository) UpdateLastUsed(deviceID string) error { return r.db.Model(&model.DeviceToken{}). Where("device_id = ?", deviceID). Update("last_used_at", time.Now()).Error } // Deactivate 停用设备 -func (r *DeviceTokenRepository) Deactivate(deviceID string) error { +func (r *deviceTokenRepository) Deactivate(deviceID string) error { return r.db.Model(&model.DeviceToken{}). Where("device_id = ?", deviceID). Update("is_active", false).Error } // Activate 激活设备 -func (r *DeviceTokenRepository) Activate(deviceID string) error { +func (r *deviceTokenRepository) Activate(deviceID string) error { return r.db.Model(&model.DeviceToken{}). Where("device_id = ?", deviceID). Updates(map[string]interface{}{ @@ -137,12 +158,12 @@ func (r *DeviceTokenRepository) Activate(deviceID string) error { } // DeleteByUserID 删除用户所有设备Token -func (r *DeviceTokenRepository) DeleteByUserID(userID int64) error { +func (r *deviceTokenRepository) DeleteByUserID(userID int64) error { return r.db.Where("user_id = ?", userID).Delete(&model.DeviceToken{}).Error } // GetDeviceCountByUserID 获取用户设备数量 -func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) { +func (r *deviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) { var count int64 err := r.db.Model(&model.DeviceToken{}). Where("user_id = ?", userID). @@ -151,7 +172,7 @@ func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, err } // GetActiveDeviceCountByUserID 获取用户活跃设备数量 -func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) { +func (r *deviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) { var count int64 err := r.db.Model(&model.DeviceToken{}). Where("user_id = ? AND is_active = ?", userID, true). @@ -160,7 +181,7 @@ func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int6 } // DeleteInactiveDevices 删除长时间未使用的设备 -func (r *DeviceTokenRepository) DeleteInactiveDevices(before time.Time) error { +func (r *deviceTokenRepository) DeleteInactiveDevices(before time.Time) error { return r.db.Where("is_active = ? AND last_used_at < ?", false, before). Delete(&model.DeviceToken{}).Error } diff --git a/internal/repository/group_repo.go b/internal/repository/group_repo.go index 8e32a77..cbe2b43 100644 --- a/internal/repository/group_repo.go +++ b/internal/repository/group_repo.go @@ -17,6 +17,7 @@ type GroupRepository interface { Update(group *model.Group) error Delete(id string) error GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error) + TransferOwner(groupID, oldOwnerID, newOwnerID string) error // 群成员操作 AddMember(member *model.GroupMember) error @@ -90,6 +91,32 @@ func (r *groupRepository) Update(group *model.Group) error { return r.db.Save(group).Error } +// TransferOwner 转让群主(在事务中更新群主和角色) +func (r *groupRepository) TransferOwner(groupID, oldOwnerID, newOwnerID string) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // 更新群组的群主 + if err := tx.Model(&model.Group{}).Where("id = ?", groupID).Update("owner_id", newOwnerID).Error; err != nil { + return err + } + + // 更新原群主为管理员 + if err := tx.Model(&model.GroupMember{}). + Where("group_id = ? AND user_id = ?", groupID, oldOwnerID). + Update("role", model.GroupRoleAdmin).Error; err != nil { + return err + } + + // 更新新群主为群主 + if err := tx.Model(&model.GroupMember{}). + Where("group_id = ? AND user_id = ?", groupID, newOwnerID). + Update("role", model.GroupRoleOwner).Error; err != nil { + return err + } + + return nil + }) +} + // Delete 删除群组 func (r *groupRepository) Delete(id string) error { return r.db.Transaction(func(tx *gorm.DB) error { diff --git a/internal/repository/login_log_repo.go b/internal/repository/login_log_repo.go index 3412dd3..0a11d06 100644 --- a/internal/repository/login_log_repo.go +++ b/internal/repository/login_log_repo.go @@ -4,28 +4,41 @@ import ( "context" "time" + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "gorm.io/gorm" ) -// LoginLogRepository 登录日志仓储 -type LoginLogRepository struct { +// LoginLogRepository 登录日志仓储接口 +type LoginLogRepository interface { + CreateLoginLog(ctx context.Context, log *model.LoginLog) error + BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error + GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) + GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) + GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) + GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) + DeleteOldLogs(ctx context.Context, beforeDate string) error + GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) +} + +// loginLogRepository 登录日志仓储实现 +type loginLogRepository struct { db *gorm.DB } // NewLoginLogRepository 创建登录日志仓储 -func NewLoginLogRepository(db *gorm.DB) *LoginLogRepository { - return &LoginLogRepository{db: db} +func NewLoginLogRepository(db *gorm.DB) LoginLogRepository { + return &loginLogRepository{db: db} } // CreateLoginLog 创建单条登录日志 -func (r *LoginLogRepository) CreateLoginLog(ctx context.Context, log *model.LoginLog) error { +func (r *loginLogRepository) CreateLoginLog(ctx context.Context, log *model.LoginLog) error { return r.db.WithContext(ctx).Create(log).Error } // BatchCreateLoginLogs 批量创建登录日志 -func (r *LoginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error { +func (r *loginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*model.LoginLog) error { if len(logs) == 0 { return nil } @@ -33,7 +46,7 @@ func (r *LoginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*m } // GetLoginLogs 获取登录日志列表(分页) -func (r *LoginLogRepository) GetLoginLogs(ctx context.Context, filters LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) { +func (r *loginLogRepository) GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) { query := r.db.WithContext(ctx).Model(&model.LoginLog{}) if filters.UserID != "" { @@ -76,12 +89,12 @@ func (r *LoginLogRepository) GetLoginLogs(ctx context.Context, filters LoginFilt } // GetLoginLogsByUser 获取指定用户的登录日志 -func (r *LoginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) { - return r.GetLoginLogs(ctx, LoginFilter{UserID: userID}, page, pageSize) +func (r *loginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) { + return r.GetLoginLogs(ctx, dto.LoginFilter{UserID: userID}, page, pageSize) } // GetFailedLoginsByIP 获取指定IP的失败登录记录(用于风控) -func (r *LoginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) { +func (r *loginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow time.Duration) (int64, error) { startTime := time.Now().Add(-timeWindow) var count int64 err := r.db.WithContext(ctx). @@ -92,7 +105,7 @@ func (r *LoginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string, } // GetRecentSuccessLogins 获取最近成功的登录记录 -func (r *LoginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) { +func (r *loginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) { var logs []*model.LoginLog err := r.db.WithContext(ctx). Where("user_id = ? AND result = ? AND event = ?", userID, string(model.LoginResultSuccess), string(model.LoginEventLogin)). @@ -103,12 +116,12 @@ func (r *LoginLogRepository) GetRecentSuccessLogins(ctx context.Context, userID } // DeleteOldLogs 删除旧的登录日志(用于定时清理) -func (r *LoginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { +func (r *loginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.LoginLog{}).Error } // GetStatistics 获取登录日志统计(使用单次 GROUP BY 查询,避免多次 COUNT) -func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) { +func (r *loginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) { stats := &LoginLogStatistics{} query := r.db.WithContext(ctx).Model(&model.LoginLog{}) @@ -155,18 +168,6 @@ func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTi return stats, nil } -// LoginFilter 登录日志过滤条件 -type LoginFilter struct { - UserID string - Event string - Result string - FailReason string - LoginType string - IP string - StartTime time.Time - EndTime time.Time -} - // LoginLogStatistics 登录日志统计 type LoginLogStatistics struct { TotalCount int64 diff --git a/internal/repository/material_repo.go b/internal/repository/material_repo.go index 77e8f68..b99231e 100644 --- a/internal/repository/material_repo.go +++ b/internal/repository/material_repo.go @@ -1,22 +1,34 @@ package repository import ( + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "gorm.io/gorm" ) -// MaterialSubjectRepository 学科分类仓储 -type MaterialSubjectRepository struct { +// MaterialSubjectRepository 学科分类仓储接口 +type MaterialSubjectRepository interface { + ListActive() ([]*model.MaterialSubject, error) + ListAll() ([]*model.MaterialSubject, error) + GetByID(id string) (*model.MaterialSubject, error) + Create(subject *model.MaterialSubject) error + Update(subject *model.MaterialSubject) error + Delete(id string) error + CountMaterials(subjectID string) (int64, error) +} + +// materialSubjectRepository 学科分类仓储实现 +type materialSubjectRepository struct { db *gorm.DB } -func NewMaterialSubjectRepository(db *gorm.DB) *MaterialSubjectRepository { - return &MaterialSubjectRepository{db: db} +func NewMaterialSubjectRepository(db *gorm.DB) MaterialSubjectRepository { + return &materialSubjectRepository{db: db} } // ListActive 获取所有启用的学科分类 -func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) { +func (r *materialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) { var subjects []*model.MaterialSubject err := r.db. Where("is_active = ?", true). @@ -26,7 +38,7 @@ func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, erro } // ListAll 获取所有学科分类(管理端用) -func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) { +func (r *materialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) { var subjects []*model.MaterialSubject err := r.db. Order("sort_order ASC, created_at ASC"). @@ -35,7 +47,7 @@ func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) } // GetByID 根据ID获取学科分类 -func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) { +func (r *materialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) { var subject model.MaterialSubject if err := r.db.First(&subject, "id = ?", id).Error; err != nil { return nil, err @@ -44,50 +56,53 @@ func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject, } // Create 创建学科分类 -func (r *MaterialSubjectRepository) Create(subject *model.MaterialSubject) error { +func (r *materialSubjectRepository) Create(subject *model.MaterialSubject) error { return r.db.Create(subject).Error } // Update 更新学科分类 -func (r *MaterialSubjectRepository) Update(subject *model.MaterialSubject) error { +func (r *materialSubjectRepository) Update(subject *model.MaterialSubject) error { return r.db.Save(subject).Error } // Delete 删除学科分类 -func (r *MaterialSubjectRepository) Delete(id string) error { +func (r *materialSubjectRepository) Delete(id string) error { return r.db.Delete(&model.MaterialSubject{}, "id = ?", id).Error } // CountMaterials 统计学科下的资料数量 -func (r *MaterialSubjectRepository) CountMaterials(subjectID string) (int64, error) { +func (r *materialSubjectRepository) CountMaterials(subjectID string) (int64, error) { var count int64 err := r.db.Model(&model.MaterialFile{}).Where("subject_id = ?", subjectID).Count(&count).Error return count, err } -// MaterialFileRepository 文件资料仓储 -type MaterialFileRepository struct { +// MaterialFileRepository 文件资料仓储接口 +type MaterialFileRepository interface { + List(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) + GetByID(id string) (*model.MaterialFile, error) + GetByIDWithSubject(id string) (*model.MaterialFile, error) + Create(file *model.MaterialFile) error + Update(file *model.MaterialFile) error + Delete(id string) error + IncrementDownloadCount(id string) error + GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) + Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error) + GetHotMaterials(limit int) ([]*model.MaterialFile, error) + GetLatestMaterials(limit int) ([]*model.MaterialFile, error) +} + +// materialFileRepository 文件资料仓储实现 +type materialFileRepository struct { db *gorm.DB } -func NewMaterialFileRepository(db *gorm.DB) *MaterialFileRepository { - return &MaterialFileRepository{db: db} -} - -// ListQueryParams 查询参数 -type MaterialFileQueryParams struct { - SubjectID string - FileType string - Status string - Keyword string - Page int - PageSize int - SortBy string - SortOrder string +func NewMaterialFileRepository(db *gorm.DB) MaterialFileRepository { + return &materialFileRepository{db: db} } // List 获取资料列表(支持筛选和分页) -func (r *MaterialFileRepository) List(params MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) { +func (r *materialFileRepository) List(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) { var files []*model.MaterialFile var total int64 @@ -144,7 +159,7 @@ func (r *MaterialFileRepository) List(params MaterialFileQueryParams) ([]*model. } // GetByID 根据ID获取资料详情 -func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error) { +func (r *materialFileRepository) GetByID(id string) (*model.MaterialFile, error) { var file model.MaterialFile if err := r.db.First(&file, "id = ?", id).Error; err != nil { return nil, err @@ -153,7 +168,7 @@ func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error) } // GetByIDWithSubject 根据ID获取资料详情(包含学科信息) -func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) { +func (r *materialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) { var file model.MaterialFile if err := r.db.Preload("Subject").First(&file, "id = ?", id).Error; err != nil { return nil, err @@ -162,28 +177,28 @@ func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialF } // Create 创建资料 -func (r *MaterialFileRepository) Create(file *model.MaterialFile) error { +func (r *materialFileRepository) Create(file *model.MaterialFile) error { return r.db.Create(file).Error } // Update 更新资料 -func (r *MaterialFileRepository) Update(file *model.MaterialFile) error { +func (r *materialFileRepository) Update(file *model.MaterialFile) error { return r.db.Save(file).Error } // Delete 删除资料 -func (r *MaterialFileRepository) Delete(id string) error { +func (r *materialFileRepository) Delete(id string) error { return r.db.Delete(&model.MaterialFile{}, "id = ?", id).Error } // IncrementDownloadCount 增加下载次数 -func (r *MaterialFileRepository) IncrementDownloadCount(id string) error { +func (r *materialFileRepository) IncrementDownloadCount(id string) error { return r.db.Model(&model.MaterialFile{}).Where("id = ?", id). UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error } // GetBySubjectID 根据学科ID获取资料列表 -func (r *MaterialFileRepository) GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) { +func (r *materialFileRepository) GetBySubjectID(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) { var files []*model.MaterialFile var total int64 @@ -202,7 +217,7 @@ func (r *MaterialFileRepository) GetBySubjectID(subjectID string, page, pageSize } // Search 搜索资料 -func (r *MaterialFileRepository) Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error) { +func (r *materialFileRepository) Search(keyword string, page, pageSize int) ([]*model.MaterialFile, int64, error) { var files []*model.MaterialFile var total int64 @@ -225,7 +240,7 @@ func (r *MaterialFileRepository) Search(keyword string, page, pageSize int) ([]* } // GetHotMaterials 获取热门资料(按下载次数排序) -func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) { +func (r *materialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) { var files []*model.MaterialFile err := r.db. Where("status = ?", model.MaterialStatusActive). @@ -236,7 +251,7 @@ func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFi } // GetLatestMaterials 获取最新资料 -func (r *MaterialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) { +func (r *materialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) { var files []*model.MaterialFile err := r.db. Where("status = ?", model.MaterialStatusActive). diff --git a/internal/repository/message_repo.go b/internal/repository/message_repo.go index e895715..f0a7390 100644 --- a/internal/repository/message_repo.go +++ b/internal/repository/message_repo.go @@ -14,23 +14,64 @@ import ( "gorm.io/gorm/clause" ) -// MessageRepository 消息仓储 -type MessageRepository struct { +// MessageRepository 消息仓储接口 +type MessageRepository interface { + CreateMessage(msg *model.Message) error + GetConversation(id string) (*model.Conversation, error) + GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error) + CreateConversationWithParticipants(conv *model.Conversation, participants []string) error + GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error) + GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error) + GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error) + GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error) + GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) + GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error) + UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error + UpdatePinned(conversationID string, userID string, isPinned bool) error + GetUnreadCount(conversationID string, userID string) (int64, error) + UpdateConversationLastSeq(conversationID string, seq int64) error + GetNextSeq(conversationID string) (int64, error) + CreateMessageWithSeq(msg *model.Message) error + GetAllUnreadCount(userID string) (int64, error) + GetMessageByID(messageID string) (*model.Message, error) + CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) + UpdateMessageStatus(messageID string, status model.MessageStatus) error + RecallMessage(messageID string, userID string) error + GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) + GetSystemMessagesUnreadCount(userID string) (int64, error) + MarkAllSystemMessagesAsRead(userID string) error + GetConversationByGroupID(groupID string) (*model.Conversation, error) + RemoveParticipant(conversationID string, userID string) error + AddParticipant(conversationID string, userID string) error + DeleteConversationByGroupID(groupID string) error + HideConversationForUser(conversationID, userID string) error + BatchWriteMessages(ctx context.Context, messages []*model.Message) error + BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error + UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error + BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error + BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error + UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error + GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) + GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) +} + +// messageRepository 消息仓储实现 +type messageRepository struct { db *gorm.DB } // NewMessageRepository 创建消息仓储 -func NewMessageRepository(db *gorm.DB) *MessageRepository { - return &MessageRepository{db: db} +func NewMessageRepository(db *gorm.DB) MessageRepository { + return &messageRepository{db: db} } // CreateMessage 创建消息 -func (r *MessageRepository) CreateMessage(msg *model.Message) error { +func (r *messageRepository) CreateMessage(msg *model.Message) error { return r.db.Create(msg).Error } // GetConversation 获取会话 -func (r *MessageRepository) GetConversation(id string) (*model.Conversation, error) { +func (r *messageRepository) GetConversation(id string) (*model.Conversation, error) { var conv model.Conversation err := r.db.Preload("Group").First(&conv, "id = ?", id).Error if err != nil { @@ -39,10 +80,32 @@ func (r *MessageRepository) GetConversation(id string) (*model.Conversation, err return &conv, nil } +// CreateConversationWithParticipants 创建会话并添加参与者(在事务中) +func (r *messageRepository) CreateConversationWithParticipants(conv *model.Conversation, participants []string) error { + return r.db.Transaction(func(tx *gorm.DB) error { + if err := tx.Create(conv).Error; err != nil { + return err + } + + for _, userID := range participants { + participant := model.ConversationParticipant{ + ConversationID: conv.ID, + UserID: userID, + LastReadSeq: 0, + } + if err := tx.Create(&participant).Error; err != nil { + return err + } + } + + return nil + }) +} + // GetOrCreatePrivateConversation 获取或创建私聊会话 // 使用参与者关系表来管理会话 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error) { +func (r *messageRepository) GetOrCreatePrivateConversation(user1ID, user2ID string) (*model.Conversation, error) { var conv model.Conversation // 查找两个用户共同参与的私聊会话 @@ -91,7 +154,7 @@ func (r *MessageRepository) GetOrCreatePrivateConversation(user1ID, user2ID stri // GetConversations 获取用户会话列表 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error) { +func (r *messageRepository) GetConversations(userID string, page, pageSize int) ([]*model.Conversation, int64, error) { var convs []*model.Conversation var total int64 @@ -121,7 +184,7 @@ func (r *MessageRepository) GetConversations(userID string, page, pageSize int) } // GetMessages 获取会话消息 -func (r *MessageRepository) GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error) { +func (r *messageRepository) GetMessages(conversationID string, page, pageSize int) ([]*model.Message, int64, error) { var messages []*model.Message var total int64 @@ -142,7 +205,7 @@ func (r *MessageRepository) GetMessages(conversationID string, page, pageSize in } // GetMessagesAfterSeq 获取指定seq之后的消息(用于增量同步) -func (r *MessageRepository) GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error) { +func (r *messageRepository) GetMessagesAfterSeq(conversationID string, afterSeq int64, limit int) ([]*model.Message, error) { var messages []*model.Message err := r.db.Where("conversation_id = ? AND seq > ?", conversationID, afterSeq). Order("seq ASC"). @@ -157,7 +220,7 @@ func (r *MessageRepository) GetMessagesAfterSeq(conversationID string, afterSeq } // GetMessagesBeforeSeq 获取指定seq之前的历史消息(用于下拉加载更多) -func (r *MessageRepository) GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error) { +func (r *messageRepository) GetMessagesBeforeSeq(conversationID string, beforeSeq int64, limit int) ([]*model.Message, error) { var messages []*model.Message err := r.db.Where("conversation_id = ? AND seq < ?", conversationID, beforeSeq). Order("seq DESC"). // 降序获取最新消息在前 @@ -176,7 +239,7 @@ func (r *MessageRepository) GetMessagesBeforeSeq(conversationID string, beforeSe } // GetConversationParticipants 获取会话参与者 -func (r *MessageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) { +func (r *messageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) { var participants []*model.ConversationParticipant err := r.db.Where("conversation_id = ?", conversationID).Find(&participants).Error return participants, err @@ -184,7 +247,7 @@ func (r *MessageRepository) GetConversationParticipants(conversationID string) ( // GetParticipant 获取用户在会话中的参与者信息 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error) { +func (r *messageRepository) GetParticipant(conversationID string, userID string) (*model.ConversationParticipant, error) { var participant model.ConversationParticipant err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error if err != nil { @@ -211,7 +274,7 @@ func (r *MessageRepository) GetParticipant(conversationID string, userID string) // UpdateLastReadSeq 更新已读位置 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error { +func (r *messageRepository) UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error { result := r.db.Model(&model.ConversationParticipant{}). Where("conversation_id = ? AND user_id = ?", conversationID, userID). Update("last_read_seq", lastReadSeq) @@ -246,7 +309,7 @@ func (r *MessageRepository) UpdateLastReadSeq(conversationID string, userID stri } // UpdatePinned 更新会话置顶状态(用户维度) -func (r *MessageRepository) UpdatePinned(conversationID string, userID string, isPinned bool) error { +func (r *messageRepository) UpdatePinned(conversationID string, userID string, isPinned bool) error { result := r.db.Model(&model.ConversationParticipant{}). Where("conversation_id = ? AND user_id = ?", conversationID, userID). Update("is_pinned", isPinned) @@ -277,7 +340,7 @@ func (r *MessageRepository) UpdatePinned(conversationID string, userID string, i // GetUnreadCount 获取未读消息数 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) GetUnreadCount(conversationID string, userID string) (int64, error) { +func (r *messageRepository) GetUnreadCount(conversationID string, userID string) (int64, error) { var participant model.ConversationParticipant err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error if err != nil { @@ -292,7 +355,7 @@ func (r *MessageRepository) GetUnreadCount(conversationID string, userID string) } // UpdateConversationLastSeq 更新会话的最后消息seq和时间 -func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq int64) error { +func (r *messageRepository) UpdateConversationLastSeq(conversationID string, seq int64) error { return r.db.Model(&model.Conversation{}). Where("id = ?", conversationID). Updates(map[string]interface{}{ @@ -302,7 +365,7 @@ func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq } // GetNextSeq 获取会话的下一个seq值 -func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) { +func (r *messageRepository) GetNextSeq(conversationID string) (int64, error) { var conv model.Conversation err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error if err != nil { @@ -312,7 +375,7 @@ func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) { } // CreateMessageWithSeq 创建消息并更新seq(事务操作) -func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error { +func (r *messageRepository) CreateMessageWithSeq(msg *model.Message) error { return r.db.Transaction(func(tx *gorm.DB) error { // 获取当前seq并+1 var conv model.Conversation @@ -350,7 +413,7 @@ func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error { // GetAllUnreadCount 获取用户所有会话的未读消息总数 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *MessageRepository) GetAllUnreadCount(userID string) (int64, error) { +func (r *messageRepository) GetAllUnreadCount(userID string) (int64, error) { var totalUnread int64 err := r.db.Table("conversation_participants AS cp"). Joins("LEFT JOIN messages AS m ON m.conversation_id = cp.conversation_id AND m.sender_id <> ? AND m.seq > cp.last_read_seq AND m.deleted_at IS NULL", userID). @@ -361,7 +424,7 @@ func (r *MessageRepository) GetAllUnreadCount(userID string) (int64, error) { } // GetMessageByID 根据ID获取消息 -func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, error) { +func (r *messageRepository) GetMessageByID(messageID string) (*model.Message, error) { var message model.Message err := r.db.First(&message, "id = ?", messageID).Error if err != nil { @@ -373,7 +436,7 @@ func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, er } // CountMessagesBySenderInConversation 统计会话中某用户已发送消息数 -func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) { +func (r *messageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) { var count int64 err := r.db.Model(&model.Message{}). Where("conversation_id = ? AND sender_id = ?", conversationID, senderID). @@ -382,15 +445,25 @@ func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID, } // UpdateMessageStatus 更新消息状态 -func (r *MessageRepository) UpdateMessageStatus(messageID int64, status model.MessageStatus) error { +func (r *messageRepository) UpdateMessageStatus(messageID string, status model.MessageStatus) error { return r.db.Model(&model.Message{}). Where("id = ?", messageID). Update("status", status).Error } +// RecallMessage 撤回消息(2分钟内) +func (r *messageRepository) RecallMessage(messageID string, userID string) error { + return r.db.Model(&model.Message{}). + Where("id = ? AND sender_id = ?", messageID, userID). + Updates(map[string]interface{}{ + "status": model.MessageStatusRecalled, + "segments": model.MessageSegments{}, + }).Error +} + // GetOrCreateSystemParticipant 获取或创建用户在系统会话中的参与者记录 // 系统会话是虚拟会话,但需要参与者记录来跟踪已读状态 -func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) { +func (r *messageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) { var participant model.ConversationParticipant err := r.db.Where("conversation_id = ? AND user_id = ?", model.SystemConversationID, userID).First(&participant).Error @@ -418,7 +491,7 @@ func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model. } // GetSystemMessagesUnreadCount 获取系统消息未读数 -func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) { +func (r *messageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) { // 获取或创建参与者记录 participant, err := r.GetOrCreateSystemParticipant(userID) if err != nil { @@ -436,7 +509,7 @@ func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64, } // MarkAllSystemMessagesAsRead 标记所有系统消息已读 -func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error { +func (r *messageRepository) MarkAllSystemMessagesAsRead(userID string) error { // 获取系统会话的最新 seq var maxSeq int64 err := r.db.Model(&model.Message{}). @@ -465,7 +538,7 @@ func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error { } // GetConversationByGroupID 通过群组ID获取会话 -func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) { +func (r *messageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) { var conv model.Conversation err := r.db.Where("group_id = ?", groupID).First(&conv).Error if err != nil { @@ -476,14 +549,14 @@ func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Con // RemoveParticipant 移除会话参与者 // 当用户退出群聊时,需要同时移除其在对应会话中的参与者记录 -func (r *MessageRepository) RemoveParticipant(conversationID string, userID string) error { +func (r *messageRepository) RemoveParticipant(conversationID string, userID string) error { return r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID). Delete(&model.ConversationParticipant{}).Error } // AddParticipant 添加会话参与者 // 当用户加入群聊时,需要同时将其添加到对应会话的参与者记录 -func (r *MessageRepository) AddParticipant(conversationID string, userID string) error { +func (r *messageRepository) AddParticipant(conversationID string, userID string) error { // 先检查是否已经是参与者 var count int64 err := r.db.Model(&model.ConversationParticipant{}). @@ -509,7 +582,7 @@ func (r *MessageRepository) AddParticipant(conversationID string, userID string) // DeleteConversationByGroupID 删除群组对应的会话及其参与者 // 当解散群组时调用 -func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error { +func (r *messageRepository) DeleteConversationByGroupID(groupID string) error { // 获取群组对应的会话 conv, err := r.GetConversationByGroupID(groupID) if err != nil { @@ -538,7 +611,7 @@ func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error { } // HideConversationForUser 仅对当前用户隐藏会话(私聊删除) -func (r *MessageRepository) HideConversationForUser(conversationID, userID string) error { +func (r *messageRepository) HideConversationForUser(conversationID, userID string) error { now := time.Now() return r.db.Model(&model.ConversationParticipant{}). Where("conversation_id = ? AND user_id = ?", conversationID, userID). @@ -554,7 +627,7 @@ type ParticipantUpdate struct { // BatchWriteMessages 批量写入消息 // 使用 GORM 的 CreateInBatches 实现高效批量插入 -func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []*model.Message) error { +func (r *messageRepository) BatchWriteMessages(ctx context.Context, messages []*model.Message) error { if len(messages) == 0 { return nil } @@ -563,7 +636,7 @@ func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []* // BatchUpdateParticipants 批量更新参与者(使用 CASE WHEN 优化) // 使用单条 SQL 更新多条记录,避免循环执行 UPDATE -func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error { +func (r *messageRepository) BatchUpdateParticipants(ctx context.Context, updates []ParticipantUpdate) error { if len(updates) == 0 { return nil } @@ -601,7 +674,7 @@ func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates } // UpdateConversationLastSeqWithContext 更新会话最后消息序号 -func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error { +func (r *messageRepository) UpdateConversationLastSeqWithContext(ctx context.Context, convID string, lastSeq int64, lastMsgTime time.Time) error { return r.db.WithContext(ctx). Model(&model.Conversation{}). Where("id = ?", convID). @@ -613,7 +686,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Con } // BatchWriteMessagesWithTx 在事务中批量写入消息 -func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error { +func (r *messageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*model.Message) error { if len(messages) == 0 { return nil } @@ -621,7 +694,7 @@ func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*mo } // BatchUpdateParticipantsWithTx 在事务中批量更新参与者 -func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error { +func (r *messageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates []ParticipantUpdate) error { if len(updates) == 0 { return nil } @@ -649,7 +722,7 @@ func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates [ } // UpdateConversationLastSeqWithTx 在事务中更新会话最后消息序号 -func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error { +func (r *messageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID string, lastSeq int64, lastMsgTime time.Time) error { return tx.Model(&model.Conversation{}). Where("id = ?", convID). Updates(map[string]interface{}{ @@ -663,7 +736,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID // GetMessagesByCursor 游标分页获取会话消息 // 消息按 seq DESC 排序 -func (r *MessageRepository) GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) { +func (r *messageRepository) GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.Message{}).Where("conversation_id = ?", conversationID) @@ -722,7 +795,7 @@ func (r *MessageRepository) GetMessagesByCursor(ctx context.Context, conversatio // GetConversationsByCursor 游标分页获取用户会话列表 // 会话按置顶优先,然后按 updated_at DESC 排序 // 注意:会话列表的排序涉及 conversation_participants 表的 is_pinned 和 updated_at 字段 -func (r *MessageRepository) GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) { +func (r *messageRepository) GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) { // 构建基础查询 - 关联 conversation_participants 表 query := r.db.WithContext(ctx).Model(&model.Conversation{}). Joins("INNER JOIN conversation_participants cp ON conversations.id = cp.conversation_id"). diff --git a/internal/repository/notification_repo.go b/internal/repository/notification_repo.go index 4523316..6aa8784 100644 --- a/internal/repository/notification_repo.go +++ b/internal/repository/notification_repo.go @@ -8,23 +8,36 @@ import ( "gorm.io/gorm" ) -// NotificationRepository 通知仓储 -type NotificationRepository struct { +// NotificationRepository 通知仓储接口 +type NotificationRepository interface { + Create(notification *model.Notification) error + GetByID(id string) (*model.Notification, error) + GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) + MarkAsRead(id string) error + MarkAllAsRead(userID string) error + Delete(id string) error + GetUnreadCount(userID string) (int64, error) + DeleteAllByUserID(userID string) error + GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) +} + +// notificationRepository 通知仓储实现 +type notificationRepository struct { db *gorm.DB } // NewNotificationRepository 创建通知仓储 -func NewNotificationRepository(db *gorm.DB) *NotificationRepository { - return &NotificationRepository{db: db} +func NewNotificationRepository(db *gorm.DB) NotificationRepository { + return ¬ificationRepository{db: db} } // Create 创建通知 -func (r *NotificationRepository) Create(notification *model.Notification) error { +func (r *notificationRepository) Create(notification *model.Notification) error { return r.db.Create(notification).Error } // GetByID 根据ID获取通知 -func (r *NotificationRepository) GetByID(id string) (*model.Notification, error) { +func (r *notificationRepository) GetByID(id string) (*model.Notification, error) { var notification model.Notification err := r.db.First(¬ification, "id = ?", id).Error if err != nil { @@ -34,7 +47,7 @@ func (r *NotificationRepository) GetByID(id string) (*model.Notification, error) } // GetByUserID 获取用户通知 -func (r *NotificationRepository) GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) { +func (r *notificationRepository) GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) { var notifications []*model.Notification var total int64 @@ -53,29 +66,29 @@ func (r *NotificationRepository) GetByUserID(userID string, page, pageSize int, } // MarkAsRead 标记为已读 -func (r *NotificationRepository) MarkAsRead(id string) error { +func (r *notificationRepository) MarkAsRead(id string) error { return r.db.Model(&model.Notification{}).Where("id = ?", id).Update("is_read", true).Error } // MarkAllAsRead 标记所有为已读 -func (r *NotificationRepository) MarkAllAsRead(userID string) error { +func (r *notificationRepository) MarkAllAsRead(userID string) error { return r.db.Model(&model.Notification{}).Where("user_id = ?", userID).Update("is_read", true).Error } // Delete 删除通知 -func (r *NotificationRepository) Delete(id string) error { +func (r *notificationRepository) Delete(id string) error { return r.db.Delete(&model.Notification{}, "id = ?", id).Error } // GetUnreadCount 获取未读数量 -func (r *NotificationRepository) GetUnreadCount(userID string) (int64, error) { +func (r *notificationRepository) GetUnreadCount(userID string) (int64, error) { var count int64 err := r.db.Model(&model.Notification{}).Where("user_id = ? AND is_read = ?", userID, false).Count(&count).Error return count, err } // DeleteAllByUserID 删除用户所有通知 -func (r *NotificationRepository) DeleteAllByUserID(userID string) error { +func (r *notificationRepository) DeleteAllByUserID(userID string) error { return r.db.Where("user_id = ?", userID).Delete(&model.Notification{}).Error } @@ -83,7 +96,7 @@ func (r *NotificationRepository) DeleteAllByUserID(userID string) error { // GetNotificationsByCursor 游标分页获取用户通知 // 排序方式:created_at DESC -func (r *NotificationRepository) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) { +func (r *notificationRepository) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID) diff --git a/internal/repository/operation_log_repo.go b/internal/repository/operation_log_repo.go index 204e55c..b5a45b7 100644 --- a/internal/repository/operation_log_repo.go +++ b/internal/repository/operation_log_repo.go @@ -3,28 +3,40 @@ package repository import ( "context" + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "gorm.io/gorm" ) -// OperationLogRepository 操作日志仓储 -type OperationLogRepository struct { +// OperationLogRepository 操作日志仓储接口 +type OperationLogRepository interface { + CreateOperationLog(ctx context.Context, log *model.OperationLog) error + BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error + GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) + GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) + GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) + DeleteOldLogs(ctx context.Context, beforeDate string) error + GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) +} + +// operationLogRepository 操作日志仓储实现 +type operationLogRepository struct { db *gorm.DB } // NewOperationLogRepository 创建操作日志仓储 -func NewOperationLogRepository(db *gorm.DB) *OperationLogRepository { - return &OperationLogRepository{db: db} +func NewOperationLogRepository(db *gorm.DB) OperationLogRepository { + return &operationLogRepository{db: db} } // CreateOperationLog 创建单条操作日志 -func (r *OperationLogRepository) CreateOperationLog(ctx context.Context, log *model.OperationLog) error { +func (r *operationLogRepository) CreateOperationLog(ctx context.Context, log *model.OperationLog) error { return r.db.WithContext(ctx).Create(log).Error } // BatchCreateOperationLogs 批量创建操作日志 -func (r *OperationLogRepository) BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error { +func (r *operationLogRepository) BatchCreateOperationLogs(ctx context.Context, logs []*model.OperationLog) error { if len(logs) == 0 { return nil } @@ -32,7 +44,7 @@ func (r *OperationLogRepository) BatchCreateOperationLogs(ctx context.Context, l } // GetOperationLogs 获取操作日志列表(分页) -func (r *OperationLogRepository) GetOperationLogs(ctx context.Context, filters LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) { +func (r *operationLogRepository) GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) { query := r.db.WithContext(ctx).Model(&model.OperationLog{}) if filters.UserID != "" { @@ -75,22 +87,22 @@ func (r *OperationLogRepository) GetOperationLogs(ctx context.Context, filters L } // GetOperationLogsByUser 获取指定用户的操作日志 -func (r *OperationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) { - return r.GetOperationLogs(ctx, LogFilter{UserID: userID}, page, pageSize) +func (r *operationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) { + return r.GetOperationLogs(ctx, dto.LogFilter{UserID: userID}, page, pageSize) } // GetOperationLogsByTimeRange 按时间范围获取操作日志 -func (r *OperationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) { - return r.GetOperationLogs(ctx, LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize) +func (r *operationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) { + return r.GetOperationLogs(ctx, dto.LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize) } // DeleteOldLogs 删除旧的操作日志(用于定时清理) -func (r *OperationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { +func (r *operationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate string) error { return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.OperationLog{}).Error } // GetStatistics 获取操作日志统计(使用单次 GROUP BY 查询,避免多次 COUNT) -func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) { +func (r *operationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) { stats := &OperationLogStatistics{} query := r.db.WithContext(ctx).Model(&model.OperationLog{}) @@ -125,18 +137,6 @@ func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, e return stats, nil } -// LogFilter 日志过滤条件 -type LogFilter struct { - UserID string - Operation string - TargetType string - TargetID string - Status string - IP string - StartTime string - EndTime string -} - // OperationLogStatistics 操作日志统计 type OperationLogStatistics struct { TotalCount int64 diff --git a/internal/repository/post_repo.go b/internal/repository/post_repo.go index 1e753a0..77b459c 100644 --- a/internal/repository/post_repo.go +++ b/internal/repository/post_repo.go @@ -11,18 +11,63 @@ import ( "gorm.io/gorm" ) -// PostRepository 帖子仓储 -type PostRepository struct { +// PostRepository 帖子仓储接口 +type PostRepository interface { + Create(post *model.Post, images []string) error + GetByID(id string) (*model.Post, error) + Update(post *model.Post) error + UpdateWithImages(post *model.Post, images *[]string) error + UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error + Delete(id string) error + List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) + GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) + GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error) + Like(postID, userID string) error + Unlike(postID, userID string) error + IsLiked(postID, userID string) bool + IsLikedBatch(postIDs []string, userID string) map[string]bool + Favorite(postID, userID string) error + Unfavorite(postID, userID string) error + IsFavorited(postID, userID string) bool + IsFavoritedBatch(postIDs []string, userID string) map[string]bool + IncrementViews(postID string) error + IncrementShares(postID string) (int, error) + Search(keyword string, page, pageSize int) ([]*model.Post, int64, error) + GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error) + GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) + ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) + ListPinnedPublishedPostIDs() ([]string, error) + GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) + GetByIDs(ids []string) ([]*model.Post, error) + CreateWithContext(ctx context.Context, post *model.Post, images []string) error + GetByIDWithContext(ctx context.Context, id string) (*model.Post, error) + UpdateWithContext(ctx context.Context, post *model.Post) error + DeleteWithContext(ctx context.Context, id string) error + AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) + GetByIDForAdmin(id string) (*model.Post, error) + BatchDelete(ids []string) ([]string, error) + BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) + UpdatePinStatus(postID string, isPinned bool) error + UpdateFeatureStatus(postID string, isFeatured bool) error + GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) + SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) + GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) + GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) + GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) +} + +// postRepository 帖子仓储实现 +type postRepository struct { db *gorm.DB } // NewPostRepository 创建帖子仓储 -func NewPostRepository(db *gorm.DB) *PostRepository { - return &PostRepository{db: db} +func NewPostRepository(db *gorm.DB) PostRepository { + return &postRepository{db: db} } // getDB 获取数据库连接(优先使用 context 中的事务) -func (r *PostRepository) getDB(ctx context.Context) *gorm.DB { +func (r *postRepository) getDB(ctx context.Context) *gorm.DB { if tx := GetTxFromContext(ctx); tx != nil { return tx } @@ -30,7 +75,7 @@ func (r *PostRepository) getDB(ctx context.Context) *gorm.DB { } // Create 创建帖子 -func (r *PostRepository) Create(post *model.Post, images []string) error { +func (r *postRepository) Create(post *model.Post, images []string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 创建帖子 if err := tx.Create(post).Error; err != nil { @@ -68,7 +113,7 @@ func (r *PostRepository) Create(post *model.Post, images []string) error { } // GetByID 根据ID获取帖子 -func (r *PostRepository) GetByID(id string) (*model.Post, error) { +func (r *postRepository) GetByID(id string) (*model.Post, error) { var post model.Post err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error if err != nil { @@ -78,7 +123,7 @@ func (r *PostRepository) GetByID(id string) (*model.Post, error) { } // Update 更新帖子 -func (r *PostRepository) Update(post *model.Post) error { +func (r *postRepository) Update(post *model.Post) error { now := time.Now() post.UpdatedAt = now post.ContentEditedAt = &now @@ -86,7 +131,7 @@ func (r *PostRepository) Update(post *model.Post) error { } // UpdateWithImages 更新帖子及其图片(images=nil 表示不更新图片) -func (r *PostRepository) UpdateWithImages(post *model.Post, images *[]string) error { +func (r *postRepository) UpdateWithImages(post *model.Post, images *[]string) error { return r.db.Transaction(func(tx *gorm.DB) error { now := time.Now() post.UpdatedAt = now @@ -119,7 +164,7 @@ func (r *PostRepository) UpdateWithImages(post *model.Post, images *[]string) er } // UpdateModerationStatus 更新帖子审核状态 -func (r *PostRepository) UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error { +func (r *postRepository) UpdateModerationStatus(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error { // 审核状态更新属于管理操作,不应影响帖子内容更新时间(updated_at) updates := map[string]interface{}{ "status": status, @@ -132,7 +177,7 @@ func (r *PostRepository) UpdateModerationStatus(postID string, status model.Post } // Delete 删除帖子(软删除,同时清理关联数据) -func (r *PostRepository) Delete(id string) error { +func (r *postRepository) Delete(id string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 删除帖子图片 if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil { @@ -166,7 +211,7 @@ func (r *PostRepository) Delete(id string) error { // List 分页获取帖子列表 // includePending=true 时,仅在指定 userID 下额外返回 pending(用于作者查看自己待审核帖子) -func (r *PostRepository) List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) { +func (r *postRepository) List(page, pageSize int, userID string, includePending bool, channelID *string) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -196,7 +241,7 @@ func (r *PostRepository) List(page, pageSize int, userID string, includePending } // GetUserPosts 获取用户帖子 -func (r *PostRepository) GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) { +func (r *postRepository) GetUserPosts(userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -227,7 +272,7 @@ func (r *PostRepository) GetUserPosts(userID string, page, pageSize int, include } // GetFavorites 获取用户收藏 -func (r *PostRepository) GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error) { +func (r *postRepository) GetFavorites(userID string, page, pageSize int) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -241,7 +286,7 @@ func (r *PostRepository) GetFavorites(userID string, page, pageSize int) ([]*mod } // Like 点赞帖子 -func (r *PostRepository) Like(postID, userID string) error { +func (r *postRepository) Like(postID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 检查是否已经点赞 var existing model.PostLike @@ -272,7 +317,7 @@ func (r *PostRepository) Like(postID, userID string) error { } // Unlike 取消点赞 -func (r *PostRepository) Unlike(postID, userID string) error { +func (r *postRepository) Unlike(postID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.PostLike{}) if result.Error != nil { @@ -290,7 +335,7 @@ func (r *PostRepository) Unlike(postID, userID string) error { } // IsLiked 检查是否点赞 -func (r *PostRepository) IsLiked(postID, userID string) bool { +func (r *postRepository) IsLiked(postID, userID string) bool { var count int64 r.db.Model(&model.PostLike{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count) return count > 0 @@ -298,7 +343,7 @@ func (r *PostRepository) IsLiked(postID, userID string) bool { // IsLikedBatch 批量检查是否点赞(解决 N+1 问题) // 返回 map[postID]bool -func (r *PostRepository) IsLikedBatch(postIDs []string, userID string) map[string]bool { +func (r *postRepository) IsLikedBatch(postIDs []string, userID string) map[string]bool { result := make(map[string]bool) if len(postIDs) == 0 || userID == "" { return result @@ -324,7 +369,7 @@ func (r *PostRepository) IsLikedBatch(postIDs []string, userID string) map[strin } // Favorite 收藏帖子 -func (r *PostRepository) Favorite(postID, userID string) error { +func (r *postRepository) Favorite(postID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 检查是否已经收藏 var existing model.Favorite @@ -355,7 +400,7 @@ func (r *PostRepository) Favorite(postID, userID string) error { } // Unfavorite 取消收藏 -func (r *PostRepository) Unfavorite(postID, userID string) error { +func (r *postRepository) Unfavorite(postID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.Favorite{}) if result.Error != nil { @@ -374,7 +419,7 @@ func (r *PostRepository) Unfavorite(postID, userID string) error { } // IsFavorited 检查是否收藏 -func (r *PostRepository) IsFavorited(postID, userID string) bool { +func (r *postRepository) IsFavorited(postID, userID string) bool { var count int64 r.db.Model(&model.Favorite{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count) return count > 0 @@ -382,7 +427,7 @@ func (r *PostRepository) IsFavorited(postID, userID string) bool { // IsFavoritedBatch 批量检查是否收藏(解决 N+1 问题) // 返回 map[postID]bool -func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[string]bool { +func (r *postRepository) IsFavoritedBatch(postIDs []string, userID string) map[string]bool { result := make(map[string]bool) if len(postIDs) == 0 || userID == "" { return result @@ -408,7 +453,7 @@ func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[s } // IncrementViews 增加帖子观看量 -func (r *PostRepository) IncrementViews(postID string) error { +func (r *postRepository) IncrementViews(postID string) error { return r.db.Model(&model.Post{}).Where("id = ?", postID). UpdateColumns(map[string]interface{}{ "views_count": gorm.Expr("views_count + 1"), @@ -417,7 +462,7 @@ func (r *PostRepository) IncrementViews(postID string) error { } // IncrementShares 已发布帖子分享次数 +1,返回新的 shares_count;非已发布或不存在返回 gorm.ErrRecordNotFound -func (r *PostRepository) IncrementShares(postID string) (int, error) { +func (r *postRepository) IncrementShares(postID string) (int, error) { var newCount int err := r.db.Transaction(func(tx *gorm.DB) error { res := tx.Model(&model.Post{}). @@ -443,7 +488,7 @@ func (r *PostRepository) IncrementShares(postID string) (int, error) { } // Search 搜索帖子 -func (r *PostRepository) Search(keyword string, page, pageSize int) ([]*model.Post, int64, error) { +func (r *postRepository) Search(keyword string, page, pageSize int) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -472,7 +517,7 @@ func (r *PostRepository) Search(keyword string, page, pageSize int) ([]*model.Po } // GetFollowingPosts 获取关注用户的帖子 -func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error) { +func (r *postRepository) GetFollowingPosts(userID string, page, pageSize int) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -493,7 +538,7 @@ func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([ } // GetHotPosts 热门榜降级:Redis 未就绪时按最新发布排序 -func (r *PostRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) { +func (r *postRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -520,7 +565,7 @@ type PostHotSnapshot struct { } // ListPublishedPostIDsSince 按发布时间倒序取 ID(热门候选池,避免全表扫快照) -func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) { +func (r *postRepository) ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) { if limit <= 0 { return nil, nil } @@ -547,7 +592,7 @@ func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) ( } // ListPinnedPublishedPostIDs 已发布且置顶的帖子 ID(越新发布的置顶越靠前) -func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) { +func (r *postRepository) ListPinnedPublishedPostIDs() ([]string, error) { type row struct { ID string `gorm:"column:id"` } @@ -570,7 +615,7 @@ func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) { } // GetPostHotSnapshotsByIDs 仅拉取候选 ID 对应的热度字段(分批 IN 查询) -func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) { +func (r *postRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) { if len(ids) == 0 { return nil, nil } @@ -596,7 +641,7 @@ func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapsh } // GetByIDs 根据ID列表获取帖子(保持传入顺序) -func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) { +func (r *postRepository) GetByIDs(ids []string) ([]*model.Post, error) { if len(ids) == 0 { return []*model.Post{}, nil } @@ -628,7 +673,7 @@ func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) { // ========== Context-aware methods for transaction support ========== // CreateWithContext 创建帖子(支持事务) -func (r *PostRepository) CreateWithContext(ctx context.Context, post *model.Post, images []string) error { +func (r *postRepository) CreateWithContext(ctx context.Context, post *model.Post, images []string) error { db := r.getDB(ctx) // 如果 context 中有事务,直接使用 @@ -643,7 +688,7 @@ func (r *PostRepository) CreateWithContext(ctx context.Context, post *model.Post } // createWithTx 使用指定事务创建帖子 -func (r *PostRepository) createWithTx(tx *gorm.DB, post *model.Post, images []string) error { +func (r *postRepository) createWithTx(tx *gorm.DB, post *model.Post, images []string) error { // 创建帖子 if err := tx.Create(post).Error; err != nil { return err @@ -665,7 +710,7 @@ func (r *PostRepository) createWithTx(tx *gorm.DB, post *model.Post, images []st } // GetByIDWithContext 根据ID获取帖子(支持事务) -func (r *PostRepository) GetByIDWithContext(ctx context.Context, id string) (*model.Post, error) { +func (r *postRepository) GetByIDWithContext(ctx context.Context, id string) (*model.Post, error) { var post model.Post err := r.getDB(ctx).WithContext(ctx).Preload("User").Preload("Images").First(&post, "id = ?", id).Error if err != nil { @@ -675,7 +720,7 @@ func (r *PostRepository) GetByIDWithContext(ctx context.Context, id string) (*mo } // UpdateWithContext 更新帖子(支持事务) -func (r *PostRepository) UpdateWithContext(ctx context.Context, post *model.Post) error { +func (r *postRepository) UpdateWithContext(ctx context.Context, post *model.Post) error { now := time.Now() post.UpdatedAt = now post.ContentEditedAt = &now @@ -683,7 +728,7 @@ func (r *PostRepository) UpdateWithContext(ctx context.Context, post *model.Post } // DeleteWithContext 删除帖子(支持事务) -func (r *PostRepository) DeleteWithContext(ctx context.Context, id string) error { +func (r *postRepository) DeleteWithContext(ctx context.Context, id string) error { db := r.getDB(ctx) // 如果 context 中有事务,直接使用 @@ -698,7 +743,7 @@ func (r *PostRepository) DeleteWithContext(ctx context.Context, id string) error } // deleteWithTx 使用指定事务删除帖子 -func (r *PostRepository) deleteWithTx(tx *gorm.DB, id string) error { +func (r *postRepository) deleteWithTx(tx *gorm.DB, id string) error { // 删除帖子图片 if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil { return err @@ -742,7 +787,7 @@ type AdminPostListQuery struct { } // AdminList 管理端分页获取帖子列表(支持多条件筛选) -func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) { +func (r *postRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) { var posts []*model.Post var total int64 @@ -793,7 +838,7 @@ func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int } // GetByIDForAdmin 管理端根据ID获取帖子(包含所有状态) -func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) { +func (r *postRepository) GetByIDForAdmin(id string) (*model.Post, error) { var post model.Post err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error if err != nil { @@ -803,7 +848,7 @@ func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) { } // BatchDelete 批量删除帖子(使用单次事务批量删除,避免 N+1 问题) -func (r *PostRepository) BatchDelete(ids []string) ([]string, error) { +func (r *postRepository) BatchDelete(ids []string) ([]string, error) { if len(ids) == 0 { return nil, nil } @@ -855,7 +900,7 @@ func (r *PostRepository) BatchDelete(ids []string) ([]string, error) { } // BatchUpdateStatus 批量更新帖子审核状态(使用单条 SQL 批量更新,避免 N+1 问题) -func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) { +func (r *postRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) { if len(ids) == 0 { return nil, nil } @@ -900,7 +945,7 @@ func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus } // UpdatePinStatus 更新帖子置顶状态 -func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error { +func (r *postRepository) UpdatePinStatus(postID string, isPinned bool) error { // 置顶状态属于管理操作,不应影响帖子内容更新时间(updated_at) return r.db.Model(&model.Post{}).Where("id = ?", postID). UpdateColumns(map[string]any{ @@ -910,7 +955,7 @@ func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error { } // UpdateFeatureStatus 更新帖子加精状态 -func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) error { +func (r *postRepository) UpdateFeatureStatus(postID string, isFeatured bool) error { // 加精状态属于管理操作,不应影响帖子内容更新时间(updated_at) return r.db.Model(&model.Post{}).Where("id = ?", postID). UpdateColumns(map[string]any{ @@ -923,7 +968,7 @@ func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) err // GetPostsByCursor 游标分页获取帖子列表 // includePending=true 时,仅在指定 userID 下额外返回 pending(用于作者查看自己待审核帖子) -func (r *PostRepository) GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { +func (r *postRepository) GetPostsByCursor(ctx context.Context, userID string, includePending bool, channelID *string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { db := r.getDB(ctx).WithContext(ctx) // 构建基础查询 @@ -993,7 +1038,7 @@ func (r *PostRepository) GetPostsByCursor(ctx context.Context, userID string, in } // SearchPostsByCursor 游标分页搜索帖子 -func (r *PostRepository) SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { +func (r *postRepository) SearchPostsByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { db := r.getDB(ctx).WithContext(ctx) query := db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished) @@ -1059,7 +1104,7 @@ func (r *PostRepository) SearchPostsByCursor(ctx context.Context, keyword string } // GetUserPostsByCursor 游标分页获取用户帖子 -func (r *PostRepository) GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { +func (r *postRepository) GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { db := r.getDB(ctx).WithContext(ctx) query := db.Model(&model.Post{}).Where("user_id = ?", userID) @@ -1119,7 +1164,7 @@ func (r *PostRepository) GetUserPostsByCursor(ctx context.Context, userID string } // GetFollowingPostsByCursor 游标分页获取关注用户的帖子 -func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { +func (r *postRepository) GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { db := r.getDB(ctx).WithContext(ctx) // 子查询:获取当前用户关注的所有用户ID @@ -1175,7 +1220,7 @@ func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID s } // GetHotPostsByCursor 游标分页获取热门帖子 -func (r *PostRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { +func (r *postRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) { db := r.getDB(ctx).WithContext(ctx) // 热门游标降级:与 GetHotPosts 一致,按最新发布排序 diff --git a/internal/repository/push_repo.go b/internal/repository/push_repo.go index 78c29c7..8f7d431 100644 --- a/internal/repository/push_repo.go +++ b/internal/repository/push_repo.go @@ -8,23 +8,41 @@ import ( "gorm.io/gorm" ) -// PushRecordRepository 推送记录仓储 -type PushRecordRepository struct { +// PushRecordRepository 推送记录仓储接口 +type PushRecordRepository interface { + Create(record *model.PushRecord) error + GetByID(id int64) (*model.PushRecord, error) + Update(record *model.PushRecord) error + GetPendingPushes(limit int) ([]*model.PushRecord, error) + GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error) + GetByMessageID(messageID int64) ([]*model.PushRecord, error) + GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) + BatchCreate(records []*model.PushRecord) error + BatchUpdateStatus(ids []int64, status model.PushStatus) error + UpdateStatus(id int64, status model.PushStatus) error + MarkAsFailed(id int64, errMsg string) error + MarkAsDelivered(id int64) error + DeleteExpiredRecords() error + GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error) +} + +// pushRecordRepository 推送记录仓储实现 +type pushRecordRepository struct { db *gorm.DB } // NewPushRecordRepository 创建推送记录仓储 -func NewPushRecordRepository(db *gorm.DB) *PushRecordRepository { - return &PushRecordRepository{db: db} +func NewPushRecordRepository(db *gorm.DB) PushRecordRepository { + return &pushRecordRepository{db: db} } // Create 创建推送记录 -func (r *PushRecordRepository) Create(record *model.PushRecord) error { +func (r *pushRecordRepository) Create(record *model.PushRecord) error { return r.db.Create(record).Error } // GetByID 根据ID获取推送记录 -func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) { +func (r *pushRecordRepository) GetByID(id int64) (*model.PushRecord, error) { var record model.PushRecord err := r.db.First(&record, "id = ?", id).Error if err != nil { @@ -34,12 +52,12 @@ func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) { } // Update 更新推送记录 -func (r *PushRecordRepository) Update(record *model.PushRecord) error { +func (r *pushRecordRepository) Update(record *model.PushRecord) error { return r.db.Save(record).Error } // GetPendingPushes 获取待推送记录 -func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) { +func (r *pushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) { var records []*model.PushRecord err := r.db.Where("push_status = ?", model.PushStatusPending). Where("expired_at IS NULL OR expired_at > ?", time.Now()). @@ -51,7 +69,7 @@ func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, // GetByUserID 根据用户ID获取推送记录 // userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致 -func (r *PushRecordRepository) GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error) { +func (r *pushRecordRepository) GetByUserID(userID string, limit, offset int) ([]*model.PushRecord, error) { var records []*model.PushRecord err := r.db.Where("user_id = ?", userID). Order("created_at DESC"). @@ -62,7 +80,7 @@ func (r *PushRecordRepository) GetByUserID(userID string, limit, offset int) ([] } // GetByMessageID 根据消息ID获取推送记录 -func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) { +func (r *pushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) { var records []*model.PushRecord err := r.db.Where("message_id = ?", messageID). Order("created_at DESC"). @@ -71,7 +89,7 @@ func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRec } // GetFailedPushesForRetry 获取失败待重试的推送 -func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) { +func (r *pushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) { var records []*model.PushRecord err := r.db.Where("push_status = ?", model.PushStatusFailed). Where("retry_count < max_retry"). @@ -83,7 +101,7 @@ func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.Push } // BatchCreate 批量创建推送记录 -func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error { +func (r *pushRecordRepository) BatchCreate(records []*model.PushRecord) error { if len(records) == 0 { return nil } @@ -91,7 +109,7 @@ func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error { } // BatchUpdateStatus 批量更新推送状态 -func (r *PushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushStatus) error { +func (r *pushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushStatus) error { if len(ids) == 0 { return nil } @@ -107,7 +125,7 @@ func (r *PushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushS } // UpdateStatus 更新单条记录状态 -func (r *PushRecordRepository) UpdateStatus(id int64, status model.PushStatus) error { +func (r *pushRecordRepository) UpdateStatus(id int64, status model.PushStatus) error { updates := map[string]interface{}{ "push_status": status, } @@ -120,7 +138,7 @@ func (r *PushRecordRepository) UpdateStatus(id int64, status model.PushStatus) e } // MarkAsFailed 标记为失败 -func (r *PushRecordRepository) MarkAsFailed(id int64, errMsg string) error { +func (r *pushRecordRepository) MarkAsFailed(id int64, errMsg string) error { return r.db.Model(&model.PushRecord{}). Where("id = ?", id). Updates(map[string]interface{}{ @@ -131,7 +149,7 @@ func (r *PushRecordRepository) MarkAsFailed(id int64, errMsg string) error { } // MarkAsDelivered 标记为已送达 -func (r *PushRecordRepository) MarkAsDelivered(id int64) error { +func (r *pushRecordRepository) MarkAsDelivered(id int64) error { return r.db.Model(&model.PushRecord{}). Where("id = ?", id). Updates(map[string]interface{}{ @@ -141,13 +159,13 @@ func (r *PushRecordRepository) MarkAsDelivered(id int64) error { } // DeleteExpiredRecords 删除过期的推送记录(软删除) -func (r *PushRecordRepository) DeleteExpiredRecords() error { +func (r *pushRecordRepository) DeleteExpiredRecords() error { return r.db.Where("expired_at IS NOT NULL AND expired_at < ?", time.Now()). Delete(&model.PushRecord{}).Error } // GetStatsByUserID 获取用户推送统计 -func (r *PushRecordRepository) GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error) { +func (r *pushRecordRepository) GetStatsByUserID(userID int64) (map[model.PushStatus]int64, error) { type statusCount struct { Status model.PushStatus Count int64 diff --git a/internal/repository/system_notification_repo.go b/internal/repository/system_notification_repo.go index 4b39930..a69e62f 100644 --- a/internal/repository/system_notification_repo.go +++ b/internal/repository/system_notification_repo.go @@ -10,23 +10,38 @@ import ( "gorm.io/gorm" ) -// SystemNotificationRepository 系统通知仓储 -type SystemNotificationRepository struct { +// SystemNotificationRepository 系统通知仓储接口 +type SystemNotificationRepository interface { + Create(notification *model.SystemNotification) error + GetByID(id int64) (*model.SystemNotification, error) + GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) + GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error) + GetUnreadCount(receiverID string) (int64, error) + MarkAsRead(id int64, receiverID string) error + MarkAllAsRead(receiverID string) error + Delete(id int64, receiverID string) error + GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error) + GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error) + GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) +} + +// systemNotificationRepository 系统通知仓储实现 +type systemNotificationRepository struct { db *gorm.DB } // NewSystemNotificationRepository 创建系统通知仓储 -func NewSystemNotificationRepository(db *gorm.DB) *SystemNotificationRepository { - return &SystemNotificationRepository{db: db} +func NewSystemNotificationRepository(db *gorm.DB) SystemNotificationRepository { + return &systemNotificationRepository{db: db} } // Create 创建系统通知 -func (r *SystemNotificationRepository) Create(notification *model.SystemNotification) error { +func (r *systemNotificationRepository) Create(notification *model.SystemNotification) error { return r.db.Create(notification).Error } // GetByID 根据ID获取通知 -func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) { +func (r *systemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) { var notification model.SystemNotification err := r.db.First(¬ification, "id = ?", id).Error if err != nil { @@ -36,7 +51,7 @@ func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotificat } // GetByReceiverID 获取用户的通知列表 -func (r *SystemNotificationRepository) GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) { +func (r *systemNotificationRepository) GetByReceiverID(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) { var notifications []*model.SystemNotification var total int64 @@ -53,7 +68,7 @@ func (r *SystemNotificationRepository) GetByReceiverID(receiverID string, page, } // GetUnreadByReceiverID 获取用户的未读通知列表 -func (r *SystemNotificationRepository) GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error) { +func (r *systemNotificationRepository) GetUnreadByReceiverID(receiverID string, limit int) ([]*model.SystemNotification, error) { var notifications []*model.SystemNotification err := r.db.Where("receiver_id = ? AND is_read = ?", receiverID, false). Order("created_at DESC"). @@ -63,7 +78,7 @@ func (r *SystemNotificationRepository) GetUnreadByReceiverID(receiverID string, } // GetUnreadCount 获取用户未读通知数量 -func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) { +func (r *systemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) { var count int64 err := r.db.Model(&model.SystemNotification{}). Where("receiver_id = ? AND is_read = ?", receiverID, false). @@ -72,7 +87,7 @@ func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64, } // MarkAsRead 标记单条通知为已读 -func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) error { +func (r *systemNotificationRepository) MarkAsRead(id int64, receiverID string) error { now := model.SystemNotification{}.UpdatedAt return r.db.Model(&model.SystemNotification{}). Where("id = ? AND receiver_id = ?", id, receiverID). @@ -83,7 +98,7 @@ func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) e } // MarkAllAsRead 标记用户所有通知为已读 -func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error { +func (r *systemNotificationRepository) MarkAllAsRead(receiverID string) error { now := model.SystemNotification{}.UpdatedAt return r.db.Model(&model.SystemNotification{}). Where("receiver_id = ? AND is_read = ?", receiverID, false). @@ -94,13 +109,13 @@ func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error { } // Delete 删除通知(软删除) -func (r *SystemNotificationRepository) Delete(id int64, receiverID string) error { +func (r *systemNotificationRepository) Delete(id int64, receiverID string) error { return r.db.Where("id = ? AND receiver_id = ?", id, receiverID). Delete(&model.SystemNotification{}).Error } // GetByType 获取用户指定类型的通知 -func (r *SystemNotificationRepository) GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error) { +func (r *systemNotificationRepository) GetByType(receiverID string, notifyType model.SystemNotificationType, page, pageSize int) ([]*model.SystemNotification, int64, error) { var notifications []*model.SystemNotification var total int64 @@ -119,7 +134,7 @@ func (r *SystemNotificationRepository) GetByType(receiverID string, notifyType m // GetByReceiverIDCursor 游标分页获取用户的通知列表 // 使用统一的 cursor 包进行游标分页 -func (r *SystemNotificationRepository) GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error) { +func (r *systemNotificationRepository) GetByReceiverIDCursor(ctx context.Context, receiverID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.SystemNotification], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID) @@ -165,7 +180,7 @@ func (r *SystemNotificationRepository) GetByReceiverIDCursor(ctx context.Context // GetByReceiverIDCursorLegacy 游标分页获取用户的通知列表(旧版兼容接口) // 已废弃:请使用 GetByReceiverIDCursor -func (r *SystemNotificationRepository) GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) { +func (r *systemNotificationRepository) GetByReceiverIDCursorLegacy(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) { // 构建基础查询 query := r.db.Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID) diff --git a/internal/repository/user_activity_repo.go b/internal/repository/user_activity_repo.go index dcd3956..2455105 100644 --- a/internal/repository/user_activity_repo.go +++ b/internal/repository/user_activity_repo.go @@ -36,21 +36,36 @@ type activityCache interface { Expire(ctx context.Context, key string, ttl time.Duration) error } -// UserActivityRepository 用户活跃数据仓储 -type UserActivityRepository struct { +// UserActivityRepository 用户活跃数据仓储接口 +type UserActivityRepository interface { + RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error + GetDAU(ctx context.Context, date time.Time) (int64, error) + GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error) + GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error) + SaveStat(ctx context.Context, stat *model.UserActivityStat) error + GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error) + RecordActivityToRedis(ctx context.Context, userID string) error + GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error) + GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error) + GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error) + GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error) +} + +// userActivityRepository 用户活跃数据仓储实现 +type userActivityRepository struct { db *gorm.DB cache activityCache } // NewUserActivityRepository 创建用户活跃数据仓储 -func NewUserActivityRepository(db *gorm.DB, cache activityCache) *UserActivityRepository { - return &UserActivityRepository{db: db, cache: cache} +func NewUserActivityRepository(db *gorm.DB, cache activityCache) UserActivityRepository { + return &userActivityRepository{db: db, cache: cache} } // ==================== 数据库操作方法 ==================== // RecordActivity 记录用户活跃(写入数据库) -func (r *UserActivityRepository) RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error { +func (r *userActivityRepository) RecordActivity(ctx context.Context, userID, loginType, ip, userAgent string) error { log := &model.UserActiveLog{ UserID: userID, ActiveDate: time.Now(), @@ -62,7 +77,7 @@ func (r *UserActivityRepository) RecordActivity(ctx context.Context, userID, log } // GetDAU 获取指定日期的日活用户数(从数据库) -func (r *UserActivityRepository) GetDAU(ctx context.Context, date time.Time) (int64, error) { +func (r *userActivityRepository) GetDAU(ctx context.Context, date time.Time) (int64, error) { var count int64 startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) endOfDay := startOfDay.Add(24 * time.Hour) @@ -77,7 +92,7 @@ func (r *UserActivityRepository) GetDAU(ctx context.Context, date time.Time) (in } // GetActiveUserIDs 获取指定日期的活跃用户ID列表 -func (r *UserActivityRepository) GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error) { +func (r *userActivityRepository) GetActiveUserIDs(ctx context.Context, date time.Time) ([]string, error) { var userIDs []string startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) endOfDay := startOfDay.Add(24 * time.Hour) @@ -92,7 +107,7 @@ func (r *UserActivityRepository) GetActiveUserIDs(ctx context.Context, date time } // GetUserActivityCount 获取用户在指定日期范围内的活跃天数 -func (r *UserActivityRepository) GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error) { +func (r *userActivityRepository) GetUserActivityCount(ctx context.Context, userID string, startDate, endDate time.Time) (int64, error) { var count int64 startOfDay := time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, startDate.Location()) endOfDay := time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 0, endDate.Location()) @@ -107,7 +122,7 @@ func (r *UserActivityRepository) GetUserActivityCount(ctx context.Context, userI } // SaveStat 保存统计数据快照 -func (r *UserActivityRepository) SaveStat(ctx context.Context, stat *model.UserActivityStat) error { +func (r *userActivityRepository) SaveStat(ctx context.Context, stat *model.UserActivityStat) error { return r.db.WithContext(ctx). Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "stat_type"}, {Name: "stat_date"}}, @@ -117,7 +132,7 @@ func (r *UserActivityRepository) SaveStat(ctx context.Context, stat *model.UserA } // GetStat 获取统计数据快照 -func (r *UserActivityRepository) GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error) { +func (r *userActivityRepository) GetStat(ctx context.Context, statType string, date time.Time) (*model.UserActivityStat, error) { var stat model.UserActivityStat startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) @@ -134,7 +149,7 @@ func (r *UserActivityRepository) GetStat(ctx context.Context, statType string, d // RecordActivityToRedis 记录用户活跃到 Redis Sorted Set // score 为时间戳,member 为用户ID -func (r *UserActivityRepository) RecordActivityToRedis(ctx context.Context, userID string) error { +func (r *userActivityRepository) RecordActivityToRedis(ctx context.Context, userID string) error { now := time.Now() score := float64(now.Unix()) @@ -164,7 +179,7 @@ func (r *UserActivityRepository) RecordActivityToRedis(ctx context.Context, user } // GetDAUFromRedis 从 Redis 获取日活用户数 -func (r *UserActivityRepository) GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error) { +func (r *userActivityRepository) GetDAUFromRedis(ctx context.Context, date time.Time) (int64, error) { key := formatDAUKey(date) count, err := r.cache.ZCard(ctx, key) if err != nil { @@ -174,7 +189,7 @@ func (r *UserActivityRepository) GetDAUFromRedis(ctx context.Context, date time. } // GetWAUFromRedis 从 Redis 获取周活用户数 -func (r *UserActivityRepository) GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error) { +func (r *userActivityRepository) GetWAUFromRedis(ctx context.Context, year int, week int) (int64, error) { key := formatWAUKey(year, week) count, err := r.cache.ZCard(ctx, key) if err != nil { @@ -184,7 +199,7 @@ func (r *UserActivityRepository) GetWAUFromRedis(ctx context.Context, year int, } // GetMAUFromRedis 从 Redis 获取月活用户数 -func (r *UserActivityRepository) GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error) { +func (r *userActivityRepository) GetMAUFromRedis(ctx context.Context, year int, month int) (int64, error) { key := formatMAUKey(year, month) count, err := r.cache.ZCard(ctx, key) if err != nil { @@ -194,7 +209,7 @@ func (r *UserActivityRepository) GetMAUFromRedis(ctx context.Context, year int, } // GetDAUUserIDsFromRedis 获取日活用户ID列表 -func (r *UserActivityRepository) GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error) { +func (r *userActivityRepository) GetDAUUserIDsFromRedis(ctx context.Context, date time.Time) ([]string, error) { key := formatDAUKey(date) // 获取所有成员(从最小分数到最大分数) members, err := r.cache.ZRangeByScore(ctx, key, "-inf", "+inf", 0, -1) diff --git a/internal/repository/user_repo.go b/internal/repository/user_repo.go index 33a4fc1..c0e5f7f 100644 --- a/internal/repository/user_repo.go +++ b/internal/repository/user_repo.go @@ -8,23 +8,60 @@ import ( "gorm.io/gorm/clause" ) -// UserRepository 用户仓储 -type UserRepository struct { +// UserRepository 用户仓储接口 +type UserRepository interface { + Create(user *model.User) error + GetByID(id string) (*model.User, error) + GetByUsername(username string) (*model.User, error) + GetByEmail(email string) (*model.User, error) + GetByPhone(phone string) (*model.User, error) + Update(user *model.User) error + Delete(id string) error + List(page, pageSize int) ([]*model.User, int64, error) + GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) + GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) + CreateFollow(follow *model.Follow) error + DeleteFollow(followerID, followingID string) error + IsFollowing(followerID, followingID string) (bool, error) + IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) + IncrementFollowersCount(userID string) error + DecrementFollowersCount(userID string) error + IncrementFollowingCount(userID string) error + DecrementFollowingCount(userID string) error + RefreshFollowersCount(userID string) error + GetPostsCount(userID string) (int64, error) + GetPostsCountBatch(userIDs []string) (map[string]int64, error) + RefreshFollowingCount(userID string) error + IsBlocked(blockerID, blockedID string) (bool, error) + IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) + IsBlockedEitherDirection(userA, userB string) (bool, error) + BlockUserAndCleanupRelations(blockerID, blockedID string) error + UnblockUser(blockerID, blockedID string) error + GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) + Search(keyword string, page, pageSize int) ([]*model.User, int64, error) + GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) + AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) + GetCommentsCount(userID string) (int64, error) + UpdateStatus(userID string, status model.UserStatus) error +} + +// userRepository 用户仓储实现 +type userRepository struct { db *gorm.DB } // NewUserRepository 创建用户仓储 -func NewUserRepository(db *gorm.DB) *UserRepository { - return &UserRepository{db: db} +func NewUserRepository(db *gorm.DB) UserRepository { + return &userRepository{db: db} } // Create 创建用户 -func (r *UserRepository) Create(user *model.User) error { +func (r *userRepository) Create(user *model.User) error { return r.db.Create(user).Error } // GetByID 根据ID获取用户 -func (r *UserRepository) GetByID(id string) (*model.User, error) { +func (r *userRepository) GetByID(id string) (*model.User, error) { var user model.User err := r.db.First(&user, "id = ?", id).Error if err != nil { @@ -34,7 +71,7 @@ func (r *UserRepository) GetByID(id string) (*model.User, error) { } // GetByUsername 根据用户名获取用户 -func (r *UserRepository) GetByUsername(username string) (*model.User, error) { +func (r *userRepository) GetByUsername(username string) (*model.User, error) { var user model.User err := r.db.First(&user, "username = ?", username).Error if err != nil { @@ -44,7 +81,7 @@ func (r *UserRepository) GetByUsername(username string) (*model.User, error) { } // GetByEmail 根据邮箱获取用户 -func (r *UserRepository) GetByEmail(email string) (*model.User, error) { +func (r *userRepository) GetByEmail(email string) (*model.User, error) { var user model.User err := r.db.First(&user, "email = ?", email).Error if err != nil { @@ -54,7 +91,7 @@ func (r *UserRepository) GetByEmail(email string) (*model.User, error) { } // GetByPhone 根据手机号获取用户 -func (r *UserRepository) GetByPhone(phone string) (*model.User, error) { +func (r *userRepository) GetByPhone(phone string) (*model.User, error) { var user model.User err := r.db.First(&user, "phone = ?", phone).Error if err != nil { @@ -64,17 +101,17 @@ func (r *UserRepository) GetByPhone(phone string) (*model.User, error) { } // Update 更新用户 -func (r *UserRepository) Update(user *model.User) error { +func (r *userRepository) Update(user *model.User) error { return r.db.Save(user).Error } // Delete 删除用户 -func (r *UserRepository) Delete(id string) error { +func (r *userRepository) Delete(id string) error { return r.db.Delete(&model.User{}, "id = ?", id).Error } // List 分页获取用户列表 -func (r *UserRepository) List(page, pageSize int) ([]*model.User, int64, error) { +func (r *userRepository) List(page, pageSize int) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -87,7 +124,7 @@ func (r *UserRepository) List(page, pageSize int) ([]*model.User, int64, error) } // GetFollowers 获取用户粉丝 -func (r *UserRepository) GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) { +func (r *userRepository) GetFollowers(userID string, page, pageSize int) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -104,7 +141,7 @@ func (r *UserRepository) GetFollowers(userID string, page, pageSize int) ([]*mod } // GetFollowing 获取用户关注 -func (r *UserRepository) GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) { +func (r *userRepository) GetFollowing(userID string, page, pageSize int) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -121,17 +158,17 @@ func (r *UserRepository) GetFollowing(userID string, page, pageSize int) ([]*mod } // CreateFollow 创建关注关系 -func (r *UserRepository) CreateFollow(follow *model.Follow) error { +func (r *userRepository) CreateFollow(follow *model.Follow) error { return r.db.Create(follow).Error } // DeleteFollow 删除关注关系 -func (r *UserRepository) DeleteFollow(followerID, followingID string) error { +func (r *userRepository) DeleteFollow(followerID, followingID string) error { return r.db.Where("follower_id = ? AND following_id = ?", followerID, followingID).Delete(&model.Follow{}).Error } // IsFollowing 检查是否关注了某用户 -func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, error) { +func (r *userRepository) IsFollowing(followerID, followingID string) (bool, error) { var count int64 err := r.db.Model(&model.Follow{}).Where("follower_id = ? AND following_id = ?", followerID, followingID).Count(&count).Error if err != nil { @@ -142,7 +179,7 @@ func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, erro // IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题) // 返回 map[targetUserID]bool -func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) { +func (r *userRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) { result := make(map[string]bool) if len(targetUserIDs) == 0 || followerID == "" { return result, nil @@ -171,31 +208,31 @@ func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []str } // IncrementFollowersCount 增加用户粉丝数 -func (r *UserRepository) IncrementFollowersCount(userID string) error { +func (r *userRepository) IncrementFollowersCount(userID string) error { return r.db.Model(&model.User{}).Where("id = ?", userID). UpdateColumn("followers_count", gorm.Expr("followers_count + 1")).Error } // DecrementFollowersCount 减少用户粉丝数 -func (r *UserRepository) DecrementFollowersCount(userID string) error { +func (r *userRepository) DecrementFollowersCount(userID string) error { return r.db.Model(&model.User{}).Where("id = ? AND followers_count > 0", userID). UpdateColumn("followers_count", gorm.Expr("followers_count - 1")).Error } // IncrementFollowingCount 增加用户关注数 -func (r *UserRepository) IncrementFollowingCount(userID string) error { +func (r *userRepository) IncrementFollowingCount(userID string) error { return r.db.Model(&model.User{}).Where("id = ?", userID). UpdateColumn("following_count", gorm.Expr("following_count + 1")).Error } // DecrementFollowingCount 减少用户关注数 -func (r *UserRepository) DecrementFollowingCount(userID string) error { +func (r *userRepository) DecrementFollowingCount(userID string) error { return r.db.Model(&model.User{}).Where("id = ? AND following_count > 0", userID). UpdateColumn("following_count", gorm.Expr("following_count - 1")).Error } // RefreshFollowersCount 刷新用户粉丝数(通过实际计数) -func (r *UserRepository) RefreshFollowersCount(userID string) error { +func (r *userRepository) RefreshFollowersCount(userID string) error { var count int64 err := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Count(&count).Error if err != nil { @@ -206,7 +243,7 @@ func (r *UserRepository) RefreshFollowersCount(userID string) error { } // GetPostsCount 获取用户帖子数(实时计算) -func (r *UserRepository) GetPostsCount(userID string) (int64, error) { +func (r *userRepository) GetPostsCount(userID string) (int64, error) { var count int64 err := r.db.Model(&model.Post{}). Where("user_id = ? AND status = ?", userID, model.PostStatusPublished). @@ -216,7 +253,7 @@ func (r *UserRepository) GetPostsCount(userID string) (int64, error) { // GetPostsCountBatch 批量获取用户帖子数(实时计算) // 返回 map[userID]postsCount -func (r *UserRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, error) { +func (r *userRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, error) { result := make(map[string]int64) if len(userIDs) == 0 { return result, nil @@ -251,7 +288,7 @@ func (r *UserRepository) GetPostsCountBatch(userIDs []string) (map[string]int64, } // RefreshFollowingCount 刷新用户关注数(通过实际计数) -func (r *UserRepository) RefreshFollowingCount(userID string) error { +func (r *userRepository) RefreshFollowingCount(userID string) error { var count int64 err := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Count(&count).Error if err != nil { @@ -262,7 +299,7 @@ func (r *UserRepository) RefreshFollowingCount(userID string) error { } // IsBlocked 检查拉黑关系是否存在(blocker -> blocked) -func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) { +func (r *userRepository) IsBlocked(blockerID, blockedID string) (bool, error) { var count int64 err := r.db.Model(&model.UserBlock{}). Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID). @@ -275,7 +312,7 @@ func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) { // IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题) // 返回 map[blockedUserID]bool -func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) { +func (r *userRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) { result := make(map[string]bool) if len(blockedIDs) == 0 || blockerID == "" { return result, nil @@ -304,7 +341,7 @@ func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) ( } // IsBlockedEitherDirection 检查是否任一方向存在拉黑 -func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) { +func (r *userRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) { var count int64 err := r.db.Model(&model.UserBlock{}). Where("(blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)", @@ -317,7 +354,7 @@ func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, er } // BlockUserAndCleanupRelations 拉黑用户并清理双向关注关系(事务) -func (r *UserRepository) BlockUserAndCleanupRelations(blockerID, blockedID string) error { +func (r *userRepository) BlockUserAndCleanupRelations(blockerID, blockedID string) error { return r.db.Transaction(func(tx *gorm.DB) error { block := &model.UserBlock{ BlockerID: blockerID, @@ -364,13 +401,13 @@ func (r *UserRepository) BlockUserAndCleanupRelations(blockerID, blockedID strin } // UnblockUser 取消拉黑 -func (r *UserRepository) UnblockUser(blockerID, blockedID string) error { +func (r *userRepository) UnblockUser(blockerID, blockedID string) error { return r.db.Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID). Delete(&model.UserBlock{}).Error } // GetBlockedUsers 获取用户黑名单列表 -func (r *UserRepository) GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) { +func (r *userRepository) GetBlockedUsers(blockerID string, page, pageSize int) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -388,7 +425,7 @@ func (r *UserRepository) GetBlockedUsers(blockerID string, page, pageSize int) ( } // Search 搜索用户 -func (r *UserRepository) Search(keyword string, page, pageSize int) ([]*model.User, int64, error) { +func (r *userRepository) Search(keyword string, page, pageSize int) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -421,7 +458,7 @@ func (r *UserRepository) Search(keyword string, page, pageSize int) ([]*model.Us // GetMutualFollowStatus 批量获取双向关注状态 // 返回 map[userID][isFollowing, isFollowingMe] -func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) { +func (r *userRepository) GetMutualFollowStatus(currentUserID string, targetUserIDs []string) (map[string][2]bool, error) { result := make(map[string][2]bool) if len(targetUserIDs) == 0 { @@ -465,7 +502,7 @@ func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserI } // AdminList 管理端分页获取用户列表(支持关键词和状态筛选) -func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) { +func (r *userRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) { var users []*model.User var total int64 @@ -501,7 +538,7 @@ func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) ( } // GetCommentsCount 获取用户评论数 -func (r *UserRepository) GetCommentsCount(userID string) (int64, error) { +func (r *userRepository) GetCommentsCount(userID string) (int64, error) { var count int64 err := r.db.Model(&model.Comment{}). Where("user_id = ?", userID). @@ -510,6 +547,6 @@ func (r *UserRepository) GetCommentsCount(userID string) (int64, error) { } // UpdateStatus 更新用户状态 -func (r *UserRepository) UpdateStatus(userID string, status model.UserStatus) error { +func (r *userRepository) UpdateStatus(userID string, status model.UserStatus) error { return r.db.Model(&model.User{}).Where("id = ?", userID).Update("status", status).Error } diff --git a/internal/repository/vote_repo.go b/internal/repository/vote_repo.go index 97ecd50..2e2d183 100644 --- a/internal/repository/vote_repo.go +++ b/internal/repository/vote_repo.go @@ -7,18 +7,29 @@ import ( "gorm.io/gorm" ) -// VoteRepository 投票仓储 -type VoteRepository struct { +// VoteRepository 投票仓储接口 +type VoteRepository interface { + CreateOptions(postID string, options []string) error + GetOptionsByPostID(postID string) ([]model.VoteOption, error) + Vote(postID, userID, optionID string) error + Unvote(postID, userID string) error + GetUserVote(postID, userID string) (*model.UserVote, error) + UpdateOption(optionID, content string) error + DeleteOptionsByPostID(postID string) error +} + +// voteRepository 投票仓储实现 +type voteRepository struct { db *gorm.DB } // NewVoteRepository 创建投票仓储 -func NewVoteRepository(db *gorm.DB) *VoteRepository { - return &VoteRepository{db: db} +func NewVoteRepository(db *gorm.DB) VoteRepository { + return &voteRepository{db: db} } // CreateOptions 批量创建投票选项(使用批量插入,避免 N+1 问题) -func (r *VoteRepository) CreateOptions(postID string, options []string) error { +func (r *voteRepository) CreateOptions(postID string, options []string) error { if len(options) == 0 { return nil } @@ -38,14 +49,14 @@ func (r *VoteRepository) CreateOptions(postID string, options []string) error { } // GetOptionsByPostID 获取帖子的所有投票选项 -func (r *VoteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) { +func (r *voteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) { var options []model.VoteOption err := r.db.Where("post_id = ?", postID).Order("sort_order ASC").Find(&options).Error return options, err } // Vote 用户投票 -func (r *VoteRepository) Vote(postID, userID, optionID string) error { +func (r *voteRepository) Vote(postID, userID, optionID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 检查用户是否已投票 var existing model.UserVote @@ -83,7 +94,7 @@ func (r *VoteRepository) Vote(postID, userID, optionID string) error { } // Unvote 取消投票 -func (r *VoteRepository) Unvote(postID, userID string) error { +func (r *voteRepository) Unvote(postID, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 获取用户的投票记录 var userVote model.UserVote @@ -112,7 +123,7 @@ func (r *VoteRepository) Unvote(postID, userID string) error { } // GetUserVote 获取用户在指定帖子的投票 -func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, error) { +func (r *voteRepository) GetUserVote(postID, userID string) (*model.UserVote, error) { var userVote model.UserVote err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error if err != nil { @@ -125,13 +136,13 @@ func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, er } // UpdateOption 更新选项内容 -func (r *VoteRepository) UpdateOption(optionID, content string) error { +func (r *voteRepository) UpdateOption(optionID, content string) error { return r.db.Model(&model.VoteOption{}).Where("id = ?", optionID). Update("content", content).Error } // DeleteOptionsByPostID 删除帖子的所有投票选项 -func (r *VoteRepository) DeleteOptionsByPostID(postID string) error { +func (r *voteRepository) DeleteOptionsByPostID(postID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 删除关联的用户投票记录 if err := tx.Where("post_id = ?", postID).Delete(&model.UserVote{}).Error; err != nil { diff --git a/internal/service/admin_comment_service.go b/internal/service/admin_comment_service.go index 23cdd4d..e511189 100644 --- a/internal/service/admin_comment_service.go +++ b/internal/service/admin_comment_service.go @@ -25,12 +25,12 @@ type AdminCommentService interface { // adminCommentServiceImpl 管理端评论服务实现 type adminCommentServiceImpl struct { - commentRepo *repository.CommentRepository - postRepo *repository.PostRepository + commentRepo repository.CommentRepository + postRepo repository.PostRepository } // NewAdminCommentService 创建管理端评论服务 -func NewAdminCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository) AdminCommentService { +func NewAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository) AdminCommentService { return &adminCommentServiceImpl{ commentRepo: commentRepo, postRepo: postRepo, diff --git a/internal/service/admin_dashboard_service.go b/internal/service/admin_dashboard_service.go index 587b2d1..37f2019 100644 --- a/internal/service/admin_dashboard_service.go +++ b/internal/service/admin_dashboard_service.go @@ -28,21 +28,21 @@ type AdminDashboardService interface { // adminDashboardServiceImpl 管理端仪表盘服务实现 type adminDashboardServiceImpl struct { db *gorm.DB - userRepo *repository.UserRepository - postRepo *repository.PostRepository - commentRepo *repository.CommentRepository + userRepo repository.UserRepository + postRepo repository.PostRepository + commentRepo repository.CommentRepository groupRepo repository.GroupRepository - activityRepo *repository.UserActivityRepository + activityRepo repository.UserActivityRepository } // NewAdminDashboardService 创建管理端仪表盘服务 func NewAdminDashboardService( db *gorm.DB, - userRepo *repository.UserRepository, - postRepo *repository.PostRepository, - commentRepo *repository.CommentRepository, + userRepo repository.UserRepository, + postRepo repository.PostRepository, + commentRepo repository.CommentRepository, groupRepo repository.GroupRepository, - activityRepo *repository.UserActivityRepository, + activityRepo repository.UserActivityRepository, ) AdminDashboardService { return &adminDashboardServiceImpl{ db: db, diff --git a/internal/service/admin_group_service.go b/internal/service/admin_group_service.go index a54b84f..6054a57 100644 --- a/internal/service/admin_group_service.go +++ b/internal/service/admin_group_service.go @@ -30,13 +30,13 @@ type AdminGroupService interface { // adminGroupServiceImpl 管理端群组服务实现 type adminGroupServiceImpl struct { groupRepo repository.GroupRepository - userRepo *repository.UserRepository + userRepo repository.UserRepository } // NewAdminGroupService 创建管理端群组服务 func NewAdminGroupService( groupRepo repository.GroupRepository, - userRepo *repository.UserRepository, + userRepo repository.UserRepository, ) AdminGroupService { return &adminGroupServiceImpl{ groupRepo: groupRepo, diff --git a/internal/service/admin_post_service.go b/internal/service/admin_post_service.go index f5e39e8..6a7a91a 100644 --- a/internal/service/admin_post_service.go +++ b/internal/service/admin_post_service.go @@ -34,13 +34,13 @@ type AdminPostService interface { // adminPostServiceImpl 管理端帖子服务实现 type adminPostServiceImpl struct { - postRepo *repository.PostRepository + postRepo repository.PostRepository cache cache.Cache logService *LogService } // NewAdminPostService 创建管理端帖子服务 -func NewAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) AdminPostService { +func NewAdminPostService(postRepo repository.PostRepository, cacheBackend cache.Cache) AdminPostService { return &adminPostServiceImpl{ postRepo: postRepo, cache: cacheBackend, diff --git a/internal/service/admin_user_service.go b/internal/service/admin_user_service.go index 59ab24a..7195557 100644 --- a/internal/service/admin_user_service.go +++ b/internal/service/admin_user_service.go @@ -20,11 +20,11 @@ type AdminUserService interface { // adminUserServiceImpl 管理端用户服务实现 type adminUserServiceImpl struct { - userRepo *repository.UserRepository + userRepo repository.UserRepository } // NewAdminUserService 创建管理端用户服务 -func NewAdminUserService(userRepo *repository.UserRepository) AdminUserService { +func NewAdminUserService(userRepo repository.UserRepository) AdminUserService { return &adminUserServiceImpl{ userRepo: userRepo, } diff --git a/internal/service/async_log_manager.go b/internal/service/async_log_manager.go index 229ba53..05ac55c 100644 --- a/internal/service/async_log_manager.go +++ b/internal/service/async_log_manager.go @@ -11,7 +11,6 @@ import ( "carrot_bbs/internal/repository" "go.uber.org/zap" - "gorm.io/gorm" ) // AsyncLogConfig 异步日志配置 @@ -31,7 +30,6 @@ type AsyncLogManager struct { dataChangeChan chan *model.DataChangeLog cfg *AsyncLogConfig - db *gorm.DB wg sync.WaitGroup ctx context.Context cancel context.CancelFunc @@ -42,10 +40,9 @@ type AsyncLogManager struct { // NewAsyncLogManager 创建异步日志管理器 func NewAsyncLogManager( - db *gorm.DB, - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, logger *zap.Logger, cfg *AsyncLogConfig, ) *AsyncLogManager { @@ -56,7 +53,6 @@ func NewAsyncLogManager( loginChan: make(chan *model.LoginLog, cfg.BufferSize), dataChangeChan: make(chan *model.DataChangeLog, cfg.BufferSize), cfg: cfg, - db: db, ctx: ctx, cancel: cancel, logger: logger, @@ -90,9 +86,9 @@ func NewAsyncLogManager( // worker 处理协程 func (m *AsyncLogManager) worker( id int, - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, ) { opBatch := make([]*model.OperationLog, 0, m.cfg.BatchSize) loginBatch := make([]*model.LoginLog, 0, m.cfg.BatchSize) @@ -164,9 +160,9 @@ func (m *AsyncLogManager) flushBatches( ops []*model.OperationLog, logins []*model.LoginLog, changes []*model.DataChangeLog, - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, ) { if len(ops) == 0 && len(logins) == 0 && len(changes) == 0 { return @@ -194,17 +190,17 @@ func (m *AsyncLogManager) flushBatches( } // flushOperationLogs 刷新操作日志 -func (m *AsyncLogManager) flushOperationLogs(logs []*model.OperationLog, repo *repository.OperationLogRepository) error { +func (m *AsyncLogManager) flushOperationLogs(logs []*model.OperationLog, repo repository.OperationLogRepository) error { return repo.BatchCreateOperationLogs(context.Background(), logs) } // flushLoginLogs 刷新登录日志 -func (m *AsyncLogManager) flushLoginLogs(logs []*model.LoginLog, repo *repository.LoginLogRepository) error { +func (m *AsyncLogManager) flushLoginLogs(logs []*model.LoginLog, repo repository.LoginLogRepository) error { return repo.BatchCreateLoginLogs(context.Background(), logs) } // flushDataChangeLogs 刷新数据变更日志 -func (m *AsyncLogManager) flushDataChangeLogs(logs []*model.DataChangeLog, repo *repository.DataChangeLogRepository) error { +func (m *AsyncLogManager) flushDataChangeLogs(logs []*model.DataChangeLog, repo repository.DataChangeLogRepository) error { return repo.BatchCreateDataChangeLogs(context.Background(), logs) } diff --git a/internal/service/channel_service.go b/internal/service/channel_service.go index 80062fa..32adc36 100644 --- a/internal/service/channel_service.go +++ b/internal/service/channel_service.go @@ -21,11 +21,11 @@ type ChannelService interface { } type channelServiceImpl struct { - channelRepo *repository.ChannelRepository + channelRepo repository.ChannelRepository cache cache.Cache } -func NewChannelService(channelRepo *repository.ChannelRepository, c cache.Cache) ChannelService { +func NewChannelService(channelRepo repository.ChannelRepository, c cache.Cache) ChannelService { return &channelServiceImpl{channelRepo: channelRepo, cache: c} } diff --git a/internal/service/chat_service.go b/internal/service/chat_service.go index afcf5fd..f2ca4bf 100644 --- a/internal/service/chat_service.go +++ b/internal/service/chat_service.go @@ -56,9 +56,8 @@ type ChatService interface { // chatServiceImpl 聊天服务实现 type chatServiceImpl struct { - db *gorm.DB - repo *repository.MessageRepository - userRepo *repository.UserRepository + repo repository.MessageRepository + userRepo repository.UserRepository sensitive SensitiveService sseHub *sse.Hub @@ -69,9 +68,8 @@ type chatServiceImpl struct { // NewChatService 创建聊天服务 func NewChatService( - db *gorm.DB, - repo *repository.MessageRepository, - userRepo *repository.UserRepository, + repo repository.MessageRepository, + userRepo repository.UserRepository, sensitive SensitiveService, sseHub *sse.Hub, cacheBackend cache.Cache, @@ -90,7 +88,6 @@ func NewChatService( ) return &chatServiceImpl{ - db: db, repo: repo, userRepo: userRepo, sensitive: sensitive, @@ -538,13 +535,9 @@ func (s *chatServiceImpl) GetAllUnreadCount(ctx context.Context, userID string) // RecallMessage 撤回消息(2分钟内) func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, userID string) error { // 获取消息 - var message model.Message - err := s.db.First(&message, "id = ?", messageID).Error + message, err := s.repo.GetMessageByID(messageID) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return errors.New("message not found") - } - return fmt.Errorf("failed to get message: %w", err) + return errors.New("message not found") } // 验证是否是消息发送者 @@ -563,10 +556,7 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u } // 更新消息状态为已撤回,并清空原始消息内容,仅保留撤回占位 - err = s.db.Model(&message).Updates(map[string]interface{}{ - "status": model.MessageStatusRecalled, - "segments": model.MessageSegments{}, - }).Error + err = s.repo.RecallMessage(messageID, userID) if err != nil { return fmt.Errorf("failed to recall message: %w", err) } @@ -604,22 +594,15 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u // DeleteMessage 删除消息(仅对自己可见) func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, userID string) error { // 获取消息 - var message model.Message - err := s.db.First(&message, "id = ?", messageID).Error + message, err := s.repo.GetMessageByID(messageID) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return errors.New("message not found") - } - return fmt.Errorf("failed to get message: %w", err) + return errors.New("message not found") } // 验证用户是否是会话参与者 _, err = s.getParticipant(ctx, message.ConversationID, userID) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return errors.New("no permission to delete this message") - } - return fmt.Errorf("failed to get participant: %w", err) + return errors.New("no permission to delete this message") } // 对于删除消息,我们使用软删除,但需要确保只对当前用户隐藏 @@ -629,7 +612,7 @@ func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, u } // 更新消息状态为已删除 - err = s.db.Model(&message).Update("status", model.MessageStatusDeleted).Error + err = s.repo.UpdateMessageStatus(message.ID, model.MessageStatusDeleted) if err != nil { return fmt.Errorf("failed to delete message: %w", err) } diff --git a/internal/service/comment_service.go b/internal/service/comment_service.go index 2417d9e..b94a0b3 100644 --- a/internal/service/comment_service.go +++ b/internal/service/comment_service.go @@ -15,8 +15,8 @@ import ( ) type CommentService struct { - commentRepo *repository.CommentRepository - postRepo *repository.PostRepository + commentRepo repository.CommentRepository + postRepo repository.PostRepository systemMessageService SystemMessageService cache cache.Cache postAIService *PostAIService @@ -24,7 +24,7 @@ type CommentService struct { hookManager *hook.Manager } -func NewCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, hookManager *hook.Manager) *CommentService { +func NewCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, hookManager *hook.Manager) *CommentService { return &CommentService{ commentRepo: commentRepo, postRepo: postRepo, diff --git a/internal/service/group_service.go b/internal/service/group_service.go index 88c5a4b..19ed87b 100644 --- a/internal/service/group_service.go +++ b/internal/service/group_service.go @@ -106,25 +106,23 @@ type GroupMembersResult struct { // groupService 群组服务实现 type groupService struct { - db *gorm.DB groupRepo repository.GroupRepository - userRepo *repository.UserRepository - messageRepo *repository.MessageRepository + userRepo repository.UserRepository + messageRepo repository.MessageRepository requestRepo repository.GroupJoinRequestRepository - notifyRepo *repository.SystemNotificationRepository + notifyRepo repository.SystemNotificationRepository sseHub *sse.Hub cache cache.Cache } // NewGroupService 创建群组服务 -func NewGroupService(db *gorm.DB, groupRepo repository.GroupRepository, userRepo *repository.UserRepository, messageRepo *repository.MessageRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService { +func NewGroupService(groupRepo repository.GroupRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository, requestRepo repository.GroupJoinRequestRepository, notifyRepo repository.SystemNotificationRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService { return &groupService{ - db: db, groupRepo: groupRepo, userRepo: userRepo, messageRepo: messageRepo, - requestRepo: repository.NewGroupJoinRequestRepository(db), - notifyRepo: repository.NewSystemNotificationRepository(db), + requestRepo: requestRepo, + notifyRepo: notifyRepo, sseHub: sseHub, cache: cacheBackend, } @@ -263,43 +261,17 @@ func (s *groupService) CreateGroup(ownerID string, name string, description stri GroupID: &group.ID, } - // 在事务中创建会话和参与者 - err = s.db.Transaction(func(tx *gorm.DB) error { - // 创建会话 - if err := tx.Create(conversation).Error; err != nil { - return err + // 收集所有参与者 + participants := make([]string, 0, len(memberIDs)+1) + participants = append(participants, ownerID) + for _, memberID := range memberIDs { + if memberID != ownerID { + participants = append(participants, memberID) } + } - // 添加群主为会话参与者 - ownerParticipant := model.ConversationParticipant{ - ConversationID: conversation.ID, - UserID: ownerID, - LastReadSeq: 0, - } - if err := tx.Create(&ownerParticipant).Error; err != nil { - return err - } - - // 添加被邀请的成员为会话参与者 - for _, memberID := range memberIDs { - if memberID == ownerID { - continue - } - participant := model.ConversationParticipant{ - ConversationID: conversation.ID, - UserID: memberID, - LastReadSeq: 0, - } - if err := tx.Create(&participant).Error; err != nil { - // 单个参与者添加失败继续处理其他成员 - continue - } - } - - return nil - }) - - if err != nil { + // 使用仓储创建会话和参与者 + if err := s.messageRepo.CreateConversationWithParticipants(conversation, participants); err != nil { // 记录错误但不影响群组创建成功 } @@ -418,30 +390,8 @@ func (s *groupService) TransferOwner(userID string, groupID string, newOwnerID s return ErrNotGroupMember } - // 在事务中更新 - return s.db.Transaction(func(tx *gorm.DB) error { - // 更新群组的群主 - group.OwnerID = newOwnerID - if err := tx.Save(group).Error; err != nil { - return err - } - - // 更新原群主为管理员 - if err := tx.Model(&model.GroupMember{}). - Where("group_id = ? AND user_id = ?", groupID, userID). - Update("role", model.GroupRoleAdmin).Error; err != nil { - return err - } - - // 更新新群主为群主 - if err := tx.Model(&model.GroupMember{}). - Where("group_id = ? AND user_id = ?", groupID, newOwnerID). - Update("role", model.GroupRoleOwner).Error; err != nil { - return err - } - - return nil - }) + // 使用仓储的 TransferOwner 方法(在事务中更新) + return s.groupRepo.TransferOwner(groupID, userID, newOwnerID) } // GetUserGroups 获取用户加入的群组列表 @@ -593,9 +543,7 @@ func (s *groupService) collectGroupReviewerIDs(groupID string, ownerID string, s if ownerID != "" && ownerID != skipUserID { reviewerSet[ownerID] = struct{}{} } - - var reviewers []model.GroupMember - if err := s.db.Where("group_id = ? AND role IN ?", groupID, []string{model.GroupRoleOwner, model.GroupRoleAdmin}).Find(&reviewers).Error; err == nil { + if reviewers, err := s.groupRepo.GetAdmins(groupID); err == nil { for _, reviewer := range reviewers { if reviewer.UserID == "" || reviewer.UserID == skipUserID { continue diff --git a/internal/service/hot_rank_worker.go b/internal/service/hot_rank_worker.go index f64e573..02bbcdb 100644 --- a/internal/service/hot_rank_worker.go +++ b/internal/service/hot_rank_worker.go @@ -19,7 +19,7 @@ import ( // HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET(仅 TopN)及 top_ids(Redis+本地缓存) type HotRankWorker struct { cfg *config.Config - postRepo *repository.PostRepository + postRepo repository.PostRepository c cache.Cache mu sync.Mutex stopOnce sync.Once @@ -28,7 +28,7 @@ type HotRankWorker struct { } // NewHotRankWorker 创建热门榜重算 worker(cache 需为 Redis 实现才有意义) -func NewHotRankWorker(cfg *config.Config, postRepo *repository.PostRepository, c cache.Cache) *HotRankWorker { +func NewHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, c cache.Cache) *HotRankWorker { return &HotRankWorker{ cfg: cfg, postRepo: postRepo, diff --git a/internal/service/log_cleanup_service.go b/internal/service/log_cleanup_service.go index 7ecf9de..a68f676 100644 --- a/internal/service/log_cleanup_service.go +++ b/internal/service/log_cleanup_service.go @@ -30,9 +30,9 @@ type LogCleanupService interface { // logCleanupServiceImpl 日志清理服务实现 type logCleanupServiceImpl struct { - operationRepo *repository.OperationLogRepository - loginRepo *repository.LoginLogRepository - dataChangeRepo *repository.DataChangeLogRepository + operationRepo repository.OperationLogRepository + loginRepo repository.LoginLogRepository + dataChangeRepo repository.DataChangeLogRepository cfg *LogCleanupConfig logger *zap.Logger ctx context.Context @@ -41,9 +41,9 @@ type logCleanupServiceImpl struct { // NewLogCleanupService 创建日志清理服务 func NewLogCleanupService( - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, cfg *LogCleanupConfig, logger *zap.Logger, ) LogCleanupService { diff --git a/internal/service/log_service.go b/internal/service/log_service.go index 49a3c43..b68599f 100644 --- a/internal/service/log_service.go +++ b/internal/service/log_service.go @@ -4,6 +4,7 @@ import ( "context" "time" + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "carrot_bbs/internal/repository" ) @@ -11,7 +12,7 @@ import ( // OperationLogService 操作日志服务接口 type OperationLogService interface { RecordOperation(log *model.OperationLog) - GetOperationLogs(ctx context.Context, filters repository.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) + GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) GetStatistics(ctx context.Context, startTime, endTime string) (*repository.OperationLogStatistics, error) @@ -20,13 +21,13 @@ type OperationLogService interface { // operationLogServiceImpl 操作日志服务实现 type operationLogServiceImpl struct { asyncManager *AsyncLogManager - repo *repository.OperationLogRepository + repo repository.OperationLogRepository } // NewOperationLogService 创建操作日志服务 func NewOperationLogService( asyncManager *AsyncLogManager, - repo *repository.OperationLogRepository, + repo repository.OperationLogRepository, ) OperationLogService { return &operationLogServiceImpl{ asyncManager: asyncManager, @@ -40,7 +41,7 @@ func (s *operationLogServiceImpl) RecordOperation(log *model.OperationLog) { } // GetOperationLogs 获取操作日志列表 -func (s *operationLogServiceImpl) GetOperationLogs(ctx context.Context, filters repository.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) { +func (s *operationLogServiceImpl) GetOperationLogs(ctx context.Context, filters dto.LogFilter, page, pageSize int) ([]*model.OperationLog, int64, error) { return s.repo.GetOperationLogs(ctx, filters, page, pageSize) } @@ -62,7 +63,7 @@ func (s *operationLogServiceImpl) GetStatistics(ctx context.Context, startTime, // LoginLogService 登录日志服务接口 type LoginLogService interface { RecordLogin(log *model.LoginLog) - GetLoginLogs(ctx context.Context, filters repository.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) + GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow string) (int64, error) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) @@ -72,13 +73,13 @@ type LoginLogService interface { // loginLogServiceImpl 登录日志服务实现 type loginLogServiceImpl struct { asyncManager *AsyncLogManager - repo *repository.LoginLogRepository + repo repository.LoginLogRepository } // NewLoginLogService 创建登录日志服务 func NewLoginLogService( asyncManager *AsyncLogManager, - repo *repository.LoginLogRepository, + repo repository.LoginLogRepository, ) LoginLogService { return &loginLogServiceImpl{ asyncManager: asyncManager, @@ -92,7 +93,7 @@ func (s *loginLogServiceImpl) RecordLogin(log *model.LoginLog) { } // GetLoginLogs 获取登录日志列表 -func (s *loginLogServiceImpl) GetLoginLogs(ctx context.Context, filters repository.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) { +func (s *loginLogServiceImpl) GetLoginLogs(ctx context.Context, filters dto.LoginFilter, page, pageSize int) ([]*model.LoginLog, int64, error) { return s.repo.GetLoginLogs(ctx, filters, page, pageSize) } @@ -123,7 +124,7 @@ func (s *loginLogServiceImpl) GetStatistics(ctx context.Context, startTime, endT // DataChangeLogService 数据变更日志服务接口 type DataChangeLogService interface { RecordDataChange(log *model.DataChangeLog) - GetDataChangeLogs(ctx context.Context, filters repository.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) + GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) GetStatistics(ctx context.Context, startTime, endTime string) (*repository.DataChangeLogStatistics, error) @@ -132,13 +133,13 @@ type DataChangeLogService interface { // dataChangeLogServiceImpl 数据变更日志服务实现 type dataChangeLogServiceImpl struct { asyncManager *AsyncLogManager - repo *repository.DataChangeLogRepository + repo repository.DataChangeLogRepository } // NewDataChangeLogService 创建数据变更日志服务 func NewDataChangeLogService( asyncManager *AsyncLogManager, - repo *repository.DataChangeLogRepository, + repo repository.DataChangeLogRepository, ) DataChangeLogService { return &dataChangeLogServiceImpl{ asyncManager: asyncManager, @@ -152,7 +153,7 @@ func (s *dataChangeLogServiceImpl) RecordDataChange(log *model.DataChangeLog) { } // GetDataChangeLogs 获取数据变更日志列表 -func (s *dataChangeLogServiceImpl) GetDataChangeLogs(ctx context.Context, filters repository.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) { +func (s *dataChangeLogServiceImpl) GetDataChangeLogs(ctx context.Context, filters dto.DataChangeFilter, page, pageSize int) ([]*model.DataChangeLog, int64, error) { return s.repo.GetDataChangeLogs(ctx, filters, page, pageSize) } diff --git a/internal/service/material_service.go b/internal/service/material_service.go index 57d7d11..1c989f2 100644 --- a/internal/service/material_service.go +++ b/internal/service/material_service.go @@ -2,6 +2,8 @@ package service import ( "errors" + + "carrot_bbs/internal/dto" "carrot_bbs/internal/model" "carrot_bbs/internal/repository" ) @@ -21,7 +23,7 @@ type MaterialService interface { DeleteSubject(id string) error // 资料相关 - ListMaterials(params repository.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) + ListMaterials(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) GetMaterialByID(id string) (*model.MaterialFile, error) GetMaterialByIDWithSubject(id string) (*model.MaterialFile, error) GetMaterialsBySubject(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) @@ -36,14 +38,14 @@ type MaterialService interface { // materialServiceImpl 学习资料服务实现 type materialServiceImpl struct { - subjectRepo *repository.MaterialSubjectRepository - fileRepo *repository.MaterialFileRepository + subjectRepo repository.MaterialSubjectRepository + fileRepo repository.MaterialFileRepository } // NewMaterialService 创建学习资料服务 func NewMaterialService( - subjectRepo *repository.MaterialSubjectRepository, - fileRepo *repository.MaterialFileRepository, + subjectRepo repository.MaterialSubjectRepository, + fileRepo repository.MaterialFileRepository, ) MaterialService { return &materialServiceImpl{ subjectRepo: subjectRepo, @@ -127,7 +129,7 @@ func (s *materialServiceImpl) DeleteSubject(id string) error { // 资料相关方法 // ===================================================== -func (s *materialServiceImpl) ListMaterials(params repository.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) { +func (s *materialServiceImpl) ListMaterials(params dto.MaterialFileQueryParams) ([]*model.MaterialFile, int64, error) { return s.fileRepo.List(params) } diff --git a/internal/service/message_service.go b/internal/service/message_service.go index a5e5ba9..182f965 100644 --- a/internal/service/message_service.go +++ b/internal/service/message_service.go @@ -10,7 +10,6 @@ import ( "carrot_bbs/internal/repository" "go.uber.org/zap" - "gorm.io/gorm" ) // 缓存TTL常量 @@ -25,10 +24,8 @@ const ( // MessageService 消息服务 type MessageService struct { - db *gorm.DB - // 基础仓储 - messageRepo *repository.MessageRepository + messageRepo repository.MessageRepository // 缓存相关字段 conversationCache *cache.ConversationCache @@ -40,7 +37,7 @@ type MessageService struct { } // NewMessageService 创建消息服务 -func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository, cacheBackend cache.Cache, uploadService *UploadService) *MessageService { +func NewMessageService(messageRepo repository.MessageRepository, cacheBackend cache.Cache, uploadService *UploadService) *MessageService { // 创建适配器 convRepoAdapter := cache.NewConversationRepositoryAdapter(messageRepo) msgRepoAdapter := cache.NewMessageRepositoryAdapter(messageRepo) @@ -54,7 +51,6 @@ func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository, c ) return &MessageService{ - db: db, messageRepo: messageRepo, conversationCache: conversationCache, baseCache: cacheBackend, @@ -161,7 +157,7 @@ func (s *MessageService) GetConversations(ctx context.Context, userID string, pa // 生成缓存键 cacheKey := cache.ConversationListKey(userID, page, pageSize) - result, err := cache.GetOrLoadTyped[*ConversationListResult]( + result, err := cache.GetOrLoadTyped( s.baseCache, cacheKey, conversationTTL, @@ -252,7 +248,7 @@ func (s *MessageService) GetUnreadCount(ctx context.Context, conversationID stri // 生成缓存键 cacheKey := cache.UnreadDetailKey(userID, conversationID) - return cache.GetOrLoadTyped[int64]( + return cache.GetOrLoadTyped( s.baseCache, cacheKey, unreadTTL, diff --git a/internal/service/notification_service.go b/internal/service/notification_service.go index 538b90e..5f58a7a 100644 --- a/internal/service/notification_service.go +++ b/internal/service/notification_service.go @@ -20,12 +20,12 @@ const ( // NotificationService 通知服务 type NotificationService struct { - notificationRepo *repository.NotificationRepository + notificationRepo repository.NotificationRepository cache cache.Cache } // NewNotificationService 创建通知服务 -func NewNotificationService(notificationRepo *repository.NotificationRepository, cacheBackend cache.Cache) *NotificationService { +func NewNotificationService(notificationRepo repository.NotificationRepository, cacheBackend cache.Cache) *NotificationService { return &NotificationService{ notificationRepo: notificationRepo, cache: cacheBackend, diff --git a/internal/service/post_service.go b/internal/service/post_service.go index e334b32..5a00f16 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -73,7 +73,7 @@ type PostService interface { // postServiceImpl 帖子服务实现 type postServiceImpl struct { - postRepo *repository.PostRepository + postRepo repository.PostRepository systemMessageService SystemMessageService cache cache.Cache postAIService *PostAIService @@ -82,7 +82,7 @@ type postServiceImpl struct { hookManager *hook.Manager } -func NewPostService(postRepo *repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, txManager repository.TransactionManager, hookManager *hook.Manager) PostService { +func NewPostService(postRepo repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, txManager repository.TransactionManager, hookManager *hook.Manager) PostService { return &postServiceImpl{ postRepo: postRepo, systemMessageService: systemMessageService, diff --git a/internal/service/push_service.go b/internal/service/push_service.go index b1a53df..97f171a 100644 --- a/internal/service/push_service.go +++ b/internal/service/push_service.go @@ -62,9 +62,9 @@ type PushService interface { // pushServiceImpl 推送服务实现 type pushServiceImpl struct { - pushRepo *repository.PushRecordRepository - deviceRepo *repository.DeviceTokenRepository - messageRepo *repository.MessageRepository + pushRepo repository.PushRecordRepository + deviceRepo repository.DeviceTokenRepository + messageRepo repository.MessageRepository sseHub *sse.Hub // 推送队列 @@ -81,9 +81,9 @@ type pushTask struct { // NewPushService 创建推送服务 func NewPushService( - pushRepo *repository.PushRecordRepository, - deviceRepo *repository.DeviceTokenRepository, - messageRepo *repository.MessageRepository, + pushRepo repository.PushRecordRepository, + deviceRepo repository.DeviceTokenRepository, + messageRepo repository.MessageRepository, sseHub *sse.Hub, ) PushService { return &pushServiceImpl{ diff --git a/internal/service/system_message_service.go b/internal/service/system_message_service.go index a9fd040..19e9cab 100644 --- a/internal/service/system_message_service.go +++ b/internal/service/system_message_service.go @@ -26,22 +26,30 @@ type SystemMessageService interface { // 发送系统公告 SendSystemAnnouncement(ctx context.Context, userIDs []string, title string, content string) error SendBroadcastAnnouncement(ctx context.Context, title string, content string) error + + // 查询通知(供 Handler 层的 REST API 使用) + GetNotifications(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) + GetNotificationsByCursor(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) + GetUnreadCount(receiverID string) (int64, error) + MarkAsRead(notificationID int64, receiverID string) error + MarkAllAsRead(receiverID string) error + DeleteNotification(notificationID int64, receiverID string) error } type systemMessageServiceImpl struct { - notifyRepo *repository.SystemNotificationRepository + notifyRepo repository.SystemNotificationRepository pushService PushService - userRepo *repository.UserRepository - postRepo *repository.PostRepository + userRepo repository.UserRepository + postRepo repository.PostRepository cache cache.Cache } // NewSystemMessageService 创建系统消息服务 func NewSystemMessageService( - notifyRepo *repository.SystemNotificationRepository, + notifyRepo repository.SystemNotificationRepository, pushService PushService, - userRepo *repository.UserRepository, - postRepo *repository.PostRepository, + userRepo repository.UserRepository, + postRepo repository.PostRepository, cacheBackend cache.Cache, ) SystemMessageService { return &systemMessageServiceImpl{ @@ -402,6 +410,51 @@ func (s *systemMessageServiceImpl) createNotification(ctx context.Context, userI return notification, nil } +// GetNotifications 获取通知列表(供 Handler 调用) +func (s *systemMessageServiceImpl) GetNotifications(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) { + return s.notifyRepo.GetByReceiverID(receiverID, page, pageSize) +} + +// GetNotificationsByCursor 游标分页获取通知列表(供 Handler 调用) +func (s *systemMessageServiceImpl) GetNotificationsByCursor(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) { + return s.notifyRepo.GetByReceiverIDCursorLegacy(receiverID, cursorStr, pageSize) +} + +// GetUnreadCount 获取未读数量(供 Handler 调用) +func (s *systemMessageServiceImpl) GetUnreadCount(receiverID string) (int64, error) { + return s.notifyRepo.GetUnreadCount(receiverID) +} + +// MarkAsRead 标记单条为已读(供 Handler 调用) +func (s *systemMessageServiceImpl) MarkAsRead(notificationID int64, receiverID string) error { + err := s.notifyRepo.MarkAsRead(notificationID, receiverID) + if err != nil { + return err + } + cache.InvalidateUnreadSystem(s.cache, receiverID) + return nil +} + +// MarkAllAsRead 标记全部为已读(供 Handler 调用) +func (s *systemMessageServiceImpl) MarkAllAsRead(receiverID string) error { + err := s.notifyRepo.MarkAllAsRead(receiverID) + if err != nil { + return err + } + cache.InvalidateUnreadSystem(s.cache, receiverID) + return nil +} + +// DeleteNotification 删除通知(供 Handler 调用) +func (s *systemMessageServiceImpl) DeleteNotification(notificationID int64, receiverID string) error { + err := s.notifyRepo.Delete(notificationID, receiverID) + if err != nil { + return err + } + cache.InvalidateUnreadSystem(s.cache, receiverID) + return nil +} + // getActorInfo 获取操作者信息 func (s *systemMessageServiceImpl) getActorInfo(ctx context.Context, operatorID string) (string, string, error) { // 从用户仓储获取用户信息 diff --git a/internal/service/user_activity_service.go b/internal/service/user_activity_service.go index 09bd3b3..5a98a50 100644 --- a/internal/service/user_activity_service.go +++ b/internal/service/user_activity_service.go @@ -35,11 +35,11 @@ type UserActivityService interface { // userActivityService 实现 type userActivityService struct { - repo *repository.UserActivityRepository + repo repository.UserActivityRepository } // NewUserActivityService 创建用户活跃统计服务 -func NewUserActivityService(repo *repository.UserActivityRepository) UserActivityService { +func NewUserActivityService(repo repository.UserActivityRepository) UserActivityService { return &userActivityService{repo: repo} } diff --git a/internal/service/user_service.go b/internal/service/user_service.go index a2d749d..270c122 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -59,7 +59,7 @@ type UserService interface { // userServiceImpl 用户服务实现 type userServiceImpl struct { - userRepo *repository.UserRepository + userRepo repository.UserRepository systemMessageService SystemMessageService emailCodeService EmailCodeService logService *LogService @@ -67,7 +67,7 @@ type userServiceImpl struct { // NewUserService 创建用户服务 func NewUserService( - userRepo *repository.UserRepository, + userRepo repository.UserRepository, systemMessageService SystemMessageService, emailService EmailService, cacheBackend cache.Cache, diff --git a/internal/service/vote_service.go b/internal/service/vote_service.go index 6b360ca..713dbb5 100644 --- a/internal/service/vote_service.go +++ b/internal/service/vote_service.go @@ -16,8 +16,8 @@ import ( // VoteService 投票服务 type VoteService struct { - voteRepo *repository.VoteRepository - postRepo *repository.PostRepository + voteRepo repository.VoteRepository + postRepo repository.PostRepository cache cache.Cache postAIService *PostAIService systemMessageService SystemMessageService @@ -25,8 +25,8 @@ type VoteService struct { // NewVoteService 创建投票服务 func NewVoteService( - voteRepo *repository.VoteRepository, - postRepo *repository.PostRepository, + voteRepo repository.VoteRepository, + postRepo repository.PostRepository, cache cache.Cache, postAIService *PostAIService, systemMessageService SystemMessageService, diff --git a/internal/wire/handler.go b/internal/wire/handler.go index df36028..deb519b 100644 --- a/internal/wire/handler.go +++ b/internal/wire/handler.go @@ -1,10 +1,8 @@ package wire import ( - "carrot_bbs/internal/cache" "carrot_bbs/internal/handler" "carrot_bbs/internal/pkg/sse" - "carrot_bbs/internal/repository" "carrot_bbs/internal/service" "github.com/google/wire" @@ -101,10 +99,8 @@ func ProvideMessageHandler( // ProvideSystemMessageHandler 提供系统消息处理器 func ProvideSystemMessageHandler( systemMsgService service.SystemMessageService, - systemNotificationRepo *repository.SystemNotificationRepository, - cacheBackend cache.Cache, ) *handler.SystemMessageHandler { - return handler.NewSystemMessageHandler(systemMsgService, systemNotificationRepo, cacheBackend) + return handler.NewSystemMessageHandler(systemMsgService) } // ProvideGroupHandler 提供群组处理器 diff --git a/internal/wire/infrastructure.go b/internal/wire/infrastructure.go index 4298621..5fafb24 100644 --- a/internal/wire/infrastructure.go +++ b/internal/wire/infrastructure.go @@ -135,8 +135,8 @@ func ProvideBuiltinHooks(manager *hook.Manager) *hook.BuiltinHooks { func ProvideModerationHooks( manager *hook.Manager, postAIService *service.PostAIService, - postRepo *repository.PostRepository, - commentRepo *repository.CommentRepository, + postRepo repository.PostRepository, + commentRepo repository.CommentRepository, cfg *config.Config, ) *hook.ModerationHooks { strictMode := cfg.OpenAI.StrictModeration diff --git a/internal/wire/repository.go b/internal/wire/repository.go index a8d6b92..8fcadca 100644 --- a/internal/wire/repository.go +++ b/internal/wire/repository.go @@ -19,6 +19,7 @@ var RepositorySet = wire.NewSet( repository.NewDeviceTokenRepository, repository.NewSystemNotificationRepository, repository.NewGroupRepository, + repository.NewGroupJoinRequestRepository, repository.NewStickerRepository, repository.NewVoteRepository, repository.NewChannelRepository, @@ -34,7 +35,7 @@ var RepositorySet = wire.NewSet( ) // ProvideUserActivityRepository 提供用户活跃数据仓储 -func ProvideUserActivityRepository(db *gorm.DB, cache cache.Cache) *repository.UserActivityRepository { +func ProvideUserActivityRepository(db *gorm.DB, cache cache.Cache) repository.UserActivityRepository { return repository.NewUserActivityRepository(db, cache) } diff --git a/internal/wire/service.go b/internal/wire/service.go index d2e37be..d9cefdb 100644 --- a/internal/wire/service.go +++ b/internal/wire/service.go @@ -86,9 +86,9 @@ func ProvidePostAIService(client openai.Client) *service.PostAIService { // ProvidePushService 提供推送服务 func ProvidePushService( - pushRepo *repository.PushRecordRepository, - deviceTokenRepo *repository.DeviceTokenRepository, - messageRepo *repository.MessageRepository, + pushRepo repository.PushRecordRepository, + deviceTokenRepo repository.DeviceTokenRepository, + messageRepo repository.MessageRepository, sseHub *sse.Hub, ) service.PushService { return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, sseHub) @@ -96,17 +96,17 @@ func ProvidePushService( // ProvideSystemMessageService 提供系统消息服务 func ProvideSystemMessageService( - notifyRepo *repository.SystemNotificationRepository, + notifyRepo repository.SystemNotificationRepository, pushService service.PushService, - userRepo *repository.UserRepository, - postRepo *repository.PostRepository, + userRepo repository.UserRepository, + postRepo repository.PostRepository, cacheBackend cache.Cache, ) service.SystemMessageService { return service.NewSystemMessageService(notifyRepo, pushService, userRepo, postRepo, cacheBackend) } func ProvidePostService( - postRepo *repository.PostRepository, + postRepo repository.PostRepository, systemMessageService service.SystemMessageService, postAIService *service.PostAIService, cacheBackend cache.Cache, @@ -119,8 +119,8 @@ func ProvidePostService( } func ProvideCommentService( - commentRepo *repository.CommentRepository, - postRepo *repository.PostRepository, + commentRepo repository.CommentRepository, + postRepo repository.PostRepository, systemMessageService service.SystemMessageService, postAIService *service.PostAIService, cacheBackend cache.Cache, @@ -131,17 +131,16 @@ func ProvideCommentService( // ProvideMessageService 提供消息服务 func ProvideMessageService( - db *gorm.DB, - messageRepo *repository.MessageRepository, + messageRepo repository.MessageRepository, cacheBackend cache.Cache, uploadService *service.UploadService, ) *service.MessageService { - return service.NewMessageService(db, messageRepo, cacheBackend, uploadService) + return service.NewMessageService(messageRepo, cacheBackend, uploadService) } // ProvideNotificationService 提供通知服务 func ProvideNotificationService( - notificationRepo *repository.NotificationRepository, + notificationRepo repository.NotificationRepository, cacheBackend cache.Cache, ) *service.NotificationService { return service.NewNotificationService(notificationRepo, cacheBackend) @@ -149,7 +148,7 @@ func ProvideNotificationService( // ProvideUserService 提供用户服务 func ProvideUserService( - userRepo *repository.UserRepository, + userRepo repository.UserRepository, systemMessageService service.SystemMessageService, emailService service.EmailService, cacheBackend cache.Cache, @@ -166,8 +165,8 @@ func ProvideStickerService( // ProvideVoteService 提供投票服务 func ProvideVoteService( - voteRepo *repository.VoteRepository, - postRepo *repository.PostRepository, + voteRepo repository.VoteRepository, + postRepo repository.PostRepository, cache cache.Cache, postAIService *service.PostAIService, systemMessageService service.SystemMessageService, @@ -175,21 +174,20 @@ func ProvideVoteService( return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService) } -func ProvideChannelService(channelRepo *repository.ChannelRepository, cacheBackend cache.Cache) service.ChannelService { +func ProvideChannelService(channelRepo repository.ChannelRepository, cacheBackend cache.Cache) service.ChannelService { return service.NewChannelService(channelRepo, cacheBackend) } // ProvideChatService 提供聊天服务 // Note: sensitiveService 传 nil,与 main.go 保持一致 func ProvideChatService( - db *gorm.DB, - messageRepo *repository.MessageRepository, - userRepo *repository.UserRepository, + messageRepo repository.MessageRepository, + userRepo repository.UserRepository, sseHub *sse.Hub, cacheBackend cache.Cache, uploadService *service.UploadService, ) service.ChatService { - return service.NewChatService(db, messageRepo, userRepo, nil, sseHub, cacheBackend, uploadService) + return service.NewChatService(messageRepo, userRepo, nil, sseHub, cacheBackend, uploadService) } // ProvideScheduleService 提供日程服务 @@ -210,14 +208,15 @@ func ProvideScheduleSyncService( // ProvideGroupService 提供群组服务 func ProvideGroupService( - db *gorm.DB, groupRepo repository.GroupRepository, - userRepo *repository.UserRepository, - messageRepo *repository.MessageRepository, + userRepo repository.UserRepository, + messageRepo repository.MessageRepository, + requestRepo repository.GroupJoinRequestRepository, + notifyRepo repository.SystemNotificationRepository, sseHub *sse.Hub, cacheBackend cache.Cache, ) service.GroupService { - return service.NewGroupService(db, groupRepo, userRepo, messageRepo, sseHub, cacheBackend) + return service.NewGroupService(groupRepo, userRepo, messageRepo, requestRepo, notifyRepo, sseHub, cacheBackend) } // ProvideUploadService 提供上传服务 @@ -229,7 +228,7 @@ func ProvideUploadService( } // ProvideUserActivityService 提供用户活跃统计服务 -func ProvideUserActivityService(repo *repository.UserActivityRepository) service.UserActivityService { +func ProvideUserActivityService(repo repository.UserActivityRepository) service.UserActivityService { return service.NewUserActivityService(repo) } @@ -242,24 +241,24 @@ func ProvideRoleService( } // ProvideAdminUserService 提供管理端用户服务 -func ProvideAdminUserService(userRepo *repository.UserRepository) service.AdminUserService { +func ProvideAdminUserService(userRepo repository.UserRepository) service.AdminUserService { return service.NewAdminUserService(userRepo) } // ProvideAdminPostService 提供管理端帖子服务 -func ProvideAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) service.AdminPostService { +func ProvideAdminPostService(postRepo repository.PostRepository, cacheBackend cache.Cache) service.AdminPostService { return service.NewAdminPostService(postRepo, cacheBackend) } // ProvideAdminCommentService 提供管理端评论服务 -func ProvideAdminCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository) service.AdminCommentService { +func ProvideAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository) service.AdminCommentService { return service.NewAdminCommentService(commentRepo, postRepo) } // ProvideAdminGroupService 提供管理端群组服务 func ProvideAdminGroupService( groupRepo repository.GroupRepository, - userRepo *repository.UserRepository, + userRepo repository.UserRepository, ) service.AdminGroupService { return service.NewAdminGroupService(groupRepo, userRepo) } @@ -267,21 +266,20 @@ func ProvideAdminGroupService( // ProvideAdminDashboardService 提供管理端仪表盘服务 func ProvideAdminDashboardService( db *gorm.DB, - userRepo *repository.UserRepository, - postRepo *repository.PostRepository, - commentRepo *repository.CommentRepository, + userRepo repository.UserRepository, + postRepo repository.PostRepository, + commentRepo repository.CommentRepository, groupRepo repository.GroupRepository, - activityRepo *repository.UserActivityRepository, + activityRepo repository.UserActivityRepository, ) service.AdminDashboardService { return service.NewAdminDashboardService(db, userRepo, postRepo, commentRepo, groupRepo, activityRepo) } // ProvideAsyncLogManager 提供异步日志管理器 func ProvideAsyncLogManager( - db *gorm.DB, - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, logger *zap.Logger, cfg *config.Config, ) *service.AsyncLogManager { @@ -300,13 +298,13 @@ func ProvideAsyncLogManager( asyncCfg.FlushInterval = 3 * time.Second } - return service.NewAsyncLogManager(db, operationRepo, loginRepo, dataChangeRepo, logger, asyncCfg) + return service.NewAsyncLogManager(operationRepo, loginRepo, dataChangeRepo, logger, asyncCfg) } // ProvideOperationLogService 提供操作日志服务 func ProvideOperationLogService( asyncManager *service.AsyncLogManager, - operationRepo *repository.OperationLogRepository, + operationRepo repository.OperationLogRepository, ) service.OperationLogService { return service.NewOperationLogService(asyncManager, operationRepo) } @@ -314,7 +312,7 @@ func ProvideOperationLogService( // ProvideLoginLogService 提供登录日志服务 func ProvideLoginLogService( asyncManager *service.AsyncLogManager, - loginRepo *repository.LoginLogRepository, + loginRepo repository.LoginLogRepository, ) service.LoginLogService { return service.NewLoginLogService(asyncManager, loginRepo) } @@ -322,16 +320,16 @@ func ProvideLoginLogService( // ProvideDataChangeLogService 提供数据变更日志服务 func ProvideDataChangeLogService( asyncManager *service.AsyncLogManager, - dataChangeRepo *repository.DataChangeLogRepository, + dataChangeRepo repository.DataChangeLogRepository, ) service.DataChangeLogService { return service.NewDataChangeLogService(asyncManager, dataChangeRepo) } // ProvideLogCleanupService 提供日志清理服务 func ProvideLogCleanupService( - operationRepo *repository.OperationLogRepository, - loginRepo *repository.LoginLogRepository, - dataChangeRepo *repository.DataChangeLogRepository, + operationRepo repository.OperationLogRepository, + loginRepo repository.LoginLogRepository, + dataChangeRepo repository.DataChangeLogRepository, logger *zap.Logger, cfg *config.Config, ) service.LogCleanupService { @@ -355,7 +353,7 @@ func ProvideLogService( } // ProvideQRCodeLoginService 提供二维码登录服务 -func ProvideHotRankWorker(cfg *config.Config, postRepo *repository.PostRepository, cacheBackend cache.Cache) *service.HotRankWorker { +func ProvideHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, cacheBackend cache.Cache) *service.HotRankWorker { return service.NewHotRankWorker(cfg, postRepo, cacheBackend) } @@ -372,8 +370,8 @@ func ProvideQRCodeLoginService( // ProvideMaterialService 提供学习资料服务 func ProvideMaterialService( - subjectRepo *repository.MaterialSubjectRepository, - fileRepo *repository.MaterialFileRepository, + subjectRepo repository.MaterialSubjectRepository, + fileRepo repository.MaterialFileRepository, ) service.MaterialService { return service.NewMaterialService(subjectRepo, fileRepo) }