refactor: update repository interfaces and improve dependency injection
All checks were successful
Build Backend / build (push) Successful in 12m45s
Build Backend / build-docker (push) Successful in 2m40s

- 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.
This commit is contained in:
lafay
2026-03-26 18:14:16 +08:00
parent 7b41dfeb00
commit c6848aba06
50 changed files with 1034 additions and 663 deletions

View File

@@ -55,7 +55,7 @@ func InitializeApp() (*App, error) {
loginLogRepository := repository.NewLoginLogRepository(db) loginLogRepository := repository.NewLoginLogRepository(db)
dataChangeLogRepository := repository.NewDataChangeLogRepository(db) dataChangeLogRepository := repository.NewDataChangeLogRepository(db)
logger := wire.ProvideLogger() 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) operationLogService := wire.ProvideOperationLogService(asyncLogManager, operationLogRepository)
loginLogService := wire.ProvideLoginLogService(asyncLogManager, loginLogRepository) loginLogService := wire.ProvideLoginLogService(asyncLogManager, loginLogRepository)
dataChangeLogService := wire.ProvideDataChangeLogService(asyncLogManager, dataChangeLogRepository) dataChangeLogService := wire.ProvideDataChangeLogService(asyncLogManager, dataChangeLogRepository)
@@ -70,10 +70,11 @@ func InitializeApp() (*App, error) {
return nil, err return nil, err
} }
uploadService := wire.ProvideUploadService(s3Client, userService) uploadService := wire.ProvideUploadService(s3Client, userService)
chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache, uploadService) chatService := wire.ProvideChatService(messageRepository, userRepository, hub, cache, uploadService)
messageService := wire.ProvideMessageService(db, messageRepository, cache, uploadService) messageService := wire.ProvideMessageService(messageRepository, cache, uploadService)
groupRepository := repository.NewGroupRepository(db) 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) messageHandler := wire.ProvideMessageHandler(chatService, messageService, userService, groupService, hub)
notificationRepository := repository.NewNotificationRepository(db) notificationRepository := repository.NewNotificationRepository(db)
notificationService := wire.ProvideNotificationService(notificationRepository, cache) notificationService := wire.ProvideNotificationService(notificationRepository, cache)
@@ -81,7 +82,7 @@ func InitializeApp() (*App, error) {
uploadHandler := handler.NewUploadHandler(uploadService) uploadHandler := handler.NewUploadHandler(uploadService)
jwtService := wire.ProvideJWTService(config) jwtService := wire.ProvideJWTService(config)
pushHandler := handler.NewPushHandler(pushService) pushHandler := handler.NewPushHandler(pushService)
systemMessageHandler := wire.ProvideSystemMessageHandler(systemMessageService, systemNotificationRepository, cache) systemMessageHandler := wire.ProvideSystemMessageHandler(systemMessageService)
groupHandler := wire.ProvideGroupHandler(groupService, userService) groupHandler := wire.ProvideGroupHandler(groupService, userService)
stickerRepository := repository.NewStickerRepository(db) stickerRepository := repository.NewStickerRepository(db)
stickerService := wire.ProvideStickerService(stickerRepository) stickerService := wire.ProvideStickerService(stickerRepository)

View File

@@ -7,11 +7,11 @@ import (
// ConversationRepositoryAdapter 适配 MessageRepository 到 ConversationRepository 接口 // ConversationRepositoryAdapter 适配 MessageRepository 到 ConversationRepository 接口
type ConversationRepositoryAdapter struct { type ConversationRepositoryAdapter struct {
repo *repository.MessageRepository repo repository.MessageRepository
} }
// NewConversationRepositoryAdapter 创建适配器 // NewConversationRepositoryAdapter 创建适配器
func NewConversationRepositoryAdapter(repo *repository.MessageRepository) ConversationRepository { func NewConversationRepositoryAdapter(repo repository.MessageRepository) ConversationRepository {
return &ConversationRepositoryAdapter{repo: repo} return &ConversationRepositoryAdapter{repo: repo}
} }
@@ -42,11 +42,11 @@ func (a *ConversationRepositoryAdapter) GetUnreadCount(userID, convID string) (i
// MessageRepositoryAdapter 适配 MessageRepository 到 MessageRepository 接口 // MessageRepositoryAdapter 适配 MessageRepository 到 MessageRepository 接口
type MessageRepositoryAdapter struct { type MessageRepositoryAdapter struct {
repo *repository.MessageRepository repo repository.MessageRepository
} }
// NewMessageRepositoryAdapter 创建适配器 // NewMessageRepositoryAdapter 创建适配器
func NewMessageRepositoryAdapter(repo *repository.MessageRepository) MessageRepository { func NewMessageRepositoryAdapter(repo repository.MessageRepository) MessageRepository {
return &MessageRepositoryAdapter{repo: repo} return &MessageRepositoryAdapter{repo: repo}
} }

51
internal/dto/filter.go Normal file
View File

@@ -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
}

View File

@@ -4,8 +4,8 @@ import (
"strconv" "strconv"
"time" "time"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/pkg/response" "carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service" "carrot_bbs/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -37,7 +37,7 @@ func (h *AdminLogHandler) GetOperationLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := repository.LogFilter{ filters := dto.LogFilter{
UserID: c.Query("user_id"), UserID: c.Query("user_id"),
Operation: c.Query("operation"), Operation: c.Query("operation"),
TargetType: c.Query("target_type"), TargetType: c.Query("target_type"),
@@ -70,7 +70,7 @@ func (h *AdminLogHandler) GetLoginLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := repository.LoginFilter{ filters := dto.LoginFilter{
UserID: c.Query("user_id"), UserID: c.Query("user_id"),
Event: c.Query("event"), Event: c.Query("event"),
Result: c.Query("result"), Result: c.Query("result"),
@@ -113,7 +113,7 @@ func (h *AdminLogHandler) GetDataChangeLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := repository.DataChangeFilter{ filters := dto.DataChangeFilter{
UserID: c.Query("user_id"), UserID: c.Query("user_id"),
OperatorID: c.Query("operator_id"), OperatorID: c.Query("operator_id"),
ChangeType: c.Query("change_type"), ChangeType: c.Query("change_type"),
@@ -190,7 +190,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) {
"logs": logs, "logs": logs,
}) })
case "login": 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 { if err != nil {
response.HandleError(c, err, "failed to export login logs") response.HandleError(c, err, "failed to export login logs")
return return
@@ -200,7 +200,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) {
"log": logs, "log": logs,
}) })
case "data_change": 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 { if err != nil {
response.HandleError(c, err, "failed to export data change logs") response.HandleError(c, err, "failed to export data change logs")
return return

View File

@@ -3,9 +3,9 @@ package handler
import ( import (
"strconv" "strconv"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response" "carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service" "carrot_bbs/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -147,7 +147,7 @@ func (h *MaterialHandler) ListMaterials(c *gin.Context) {
fileType := c.Query("file_type") fileType := c.Query("file_type")
keyword := c.Query("keyword") keyword := c.Query("keyword")
params := repository.MaterialFileQueryParams{ params := dto.MaterialFileQueryParams{
SubjectID: subjectID, SubjectID: subjectID,
FileType: fileType, FileType: fileType,
Keyword: keyword, Keyword: keyword,
@@ -328,7 +328,7 @@ func (h *MaterialHandler) AdminListMaterials(c *gin.Context) {
status := c.Query("status") status := c.Query("status")
keyword := c.Query("keyword") keyword := c.Query("keyword")
params := repository.MaterialFileQueryParams{ params := dto.MaterialFileQueryParams{
SubjectID: subjectID, SubjectID: subjectID,
FileType: fileType, FileType: fileType,
Status: status, Status: status,

View File

@@ -3,33 +3,24 @@ package handler
import ( import (
"strconv" "strconv"
"carrot_bbs/internal/cache"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"carrot_bbs/internal/dto" "carrot_bbs/internal/dto"
"carrot_bbs/internal/pkg/response" "carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service" "carrot_bbs/internal/service"
) )
// SystemMessageHandler 系统消息处理器 // SystemMessageHandler 系统消息处理器
type SystemMessageHandler struct { type SystemMessageHandler struct {
systemMsgService service.SystemMessageService systemMsgService service.SystemMessageService
notifyRepo *repository.SystemNotificationRepository
cache cache.Cache
} }
// NewSystemMessageHandler 创建系统消息处理器 // NewSystemMessageHandler 创建系统消息处理器
func NewSystemMessageHandler( func NewSystemMessageHandler(
systemMsgService service.SystemMessageService, systemMsgService service.SystemMessageService,
notifyRepo *repository.SystemNotificationRepository,
cacheBackend cache.Cache,
) *SystemMessageHandler { ) *SystemMessageHandler {
return &SystemMessageHandler{ return &SystemMessageHandler{
systemMsgService: systemMsgService, systemMsgService: systemMsgService,
notifyRepo: notifyRepo,
cache: cacheBackend,
} }
} }
@@ -53,8 +44,8 @@ func (h *SystemMessageHandler) GetSystemMessages(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) 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 { if err != nil {
response.InternalServerError(c, "failed to get system messages") response.InternalServerError(c, "failed to get system messages")
return return
@@ -83,7 +74,7 @@ func (h *SystemMessageHandler) GetSystemMessagesByCursor(c *gin.Context) {
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) 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 { if err != nil {
response.InternalServerError(c, "failed to get system messages") response.InternalServerError(c, "failed to get system messages")
return 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 { if err != nil {
response.InternalServerError(c, "failed to get unread count") response.InternalServerError(c, "failed to get unread count")
return 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 { if err != nil {
response.InternalServerError(c, "failed to mark as read") response.InternalServerError(c, "failed to mark as read")
return return
} }
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "marked as read", nil) 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 { if err != nil {
response.InternalServerError(c, "failed to mark all as read") response.InternalServerError(c, "failed to mark all as read")
return return
} }
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "all messages marked as read", nil) 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 { if err != nil {
response.InternalServerError(c, "failed to delete notification") response.InternalServerError(c, "failed to delete notification")
return return
} }
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "notification deleted", nil) response.SuccessWithMessage(c, "notification deleted", nil)
} }

View File

@@ -13,7 +13,7 @@ import (
type PostModerationHook struct { type PostModerationHook struct {
name string name string
postAIService PostAIService postAIService PostAIService
postRepo *repository.PostRepository postRepo repository.PostRepository
strictMode bool strictMode bool
} }
@@ -23,7 +23,7 @@ type PostAIService interface {
ModerateComment(ctx context.Context, content string, images []string) error 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{ return &PostModerationHook{
name: "moderation.post.ai", name: "moderation.post.ai",
postAIService: postAIService, postAIService: postAIService,
@@ -107,11 +107,11 @@ func (h *PostModerationHook) Execute(ctx *HookContext) error {
type CommentModerationHook struct { type CommentModerationHook struct {
name string name string
postAIService PostAIService postAIService PostAIService
commentRepo *repository.CommentRepository commentRepo repository.CommentRepository
strictMode bool strictMode bool
} }
func NewCommentModerationHook(postAIService PostAIService, commentRepo *repository.CommentRepository, strictMode bool) *CommentModerationHook { func NewCommentModerationHook(postAIService PostAIService, commentRepo repository.CommentRepository, strictMode bool) *CommentModerationHook {
return &CommentModerationHook{ return &CommentModerationHook{
name: "moderation.comment.ai", name: "moderation.comment.ai",
postAIService: postAIService, postAIService: postAIService,
@@ -300,8 +300,8 @@ func (h *SensitiveWordHook) executeComment(ctx *HookContext) error {
type ModerationHooks struct { type ModerationHooks struct {
postAIService PostAIService postAIService PostAIService
sensitiveService SensitiveService sensitiveService SensitiveService
postRepo *repository.PostRepository postRepo repository.PostRepository
commentRepo *repository.CommentRepository commentRepo repository.CommentRepository
strictMode bool strictMode bool
replaceStr string replaceStr string
} }
@@ -309,8 +309,8 @@ type ModerationHooks struct {
func NewModerationHooks( func NewModerationHooks(
postAIService PostAIService, postAIService PostAIService,
sensitiveService SensitiveService, sensitiveService SensitiveService,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
commentRepo *repository.CommentRepository, commentRepo repository.CommentRepository,
strictMode bool, strictMode bool,
replaceStr string, replaceStr string,
) *ModerationHooks { ) *ModerationHooks {

View File

@@ -69,14 +69,46 @@ func (c *clientImpl) ModeratePost(ctx context.Context, title, content string, im
if !c.IsEnabled() { if !c.IsEnabled() {
return true, "", nil 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) { func (c *clientImpl) ModerateComment(ctx context.Context, content string, images []string) (bool, string, error) {
if !c.IsEnabled() { if !c.IsEnabled() {
return true, "", nil 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) { func (c *clientImpl) moderateContentInBatches(ctx context.Context, contentPrompt string, images []string) (bool, string, error) {

View File

@@ -6,16 +6,26 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// ChannelRepository 频道配置仓储 // ChannelRepository 频道配置仓储接口
type ChannelRepository struct { 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 db *gorm.DB
} }
func NewChannelRepository(db *gorm.DB) *ChannelRepository { func NewChannelRepository(db *gorm.DB) ChannelRepository {
return &ChannelRepository{db: db} return &channelRepository{db: db}
} }
func (r *ChannelRepository) ListActive() ([]*model.Channel, error) { func (r *channelRepository) ListActive() ([]*model.Channel, error) {
var channels []*model.Channel var channels []*model.Channel
err := r.db. err := r.db.
Where("is_active = ?", true). Where("is_active = ?", true).
@@ -24,7 +34,7 @@ func (r *ChannelRepository) ListActive() ([]*model.Channel, error) {
return channels, err return channels, err
} }
func (r *ChannelRepository) ListAll() ([]*model.Channel, error) { func (r *channelRepository) ListAll() ([]*model.Channel, error) {
var channels []*model.Channel var channels []*model.Channel
err := r.db. err := r.db.
Order("sort_order ASC, created_at ASC"). Order("sort_order ASC, created_at ASC").
@@ -32,15 +42,15 @@ func (r *ChannelRepository) ListAll() ([]*model.Channel, error) {
return channels, err 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 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 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 var channel model.Channel
if err := r.db.First(&channel, "id = ?", id).Error; err != nil { if err := r.db.First(&channel, "id = ?", id).Error; err != nil {
return nil, err return nil, err
@@ -48,7 +58,7 @@ func (r *ChannelRepository) GetByID(id string) (*model.Channel, error) {
return &channel, nil 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 return r.db.Delete(&model.Channel{}, "id = ?", id).Error
} }

View File

@@ -8,23 +8,47 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// CommentRepository 评论仓储 // CommentRepository 评论仓储接口
type CommentRepository struct { 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 db *gorm.DB
} }
// NewCommentRepository 创建评论仓储 // NewCommentRepository 创建评论仓储
func NewCommentRepository(db *gorm.DB) *CommentRepository { func NewCommentRepository(db *gorm.DB) CommentRepository {
return &CommentRepository{db: db} return &commentRepository{db: db}
} }
// Create 创建评论 // Create 创建评论
func (r *CommentRepository) Create(comment *model.Comment) error { func (r *commentRepository) Create(comment *model.Comment) error {
return r.db.Create(comment).Error return r.db.Create(comment).Error
} }
// GetByID 根据ID获取评论 // GetByID 根据ID获取评论
func (r *CommentRepository) GetByID(id string) (*model.Comment, error) { func (r *commentRepository) GetByID(id string) (*model.Comment, error) {
var comment model.Comment var comment model.Comment
err := r.db.Preload("User").First(&comment, "id = ?", id).Error err := r.db.Preload("User").First(&comment, "id = ?", id).Error
if err != nil { if err != nil {
@@ -34,12 +58,12 @@ func (r *CommentRepository) GetByID(id string) (*model.Comment, error) {
} }
// Update 更新评论 // Update 更新评论
func (r *CommentRepository) Update(comment *model.Comment) error { func (r *commentRepository) Update(comment *model.Comment) error {
return r.db.Save(comment).Error return r.db.Save(comment).Error
} }
// UpdateModerationStatus 更新评论审核状态 // UpdateModerationStatus 更新评论审核状态
func (r *CommentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error { func (r *commentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus) error {
// 审核状态更新属于管理操作不应影响评论内容更新时间updated_at // 审核状态更新属于管理操作不应影响评论内容更新时间updated_at
return r.db.Model(&model.Comment{}). return r.db.Model(&model.Comment{}).
Where("id = ?", commentID). Where("id = ?", commentID).
@@ -47,7 +71,7 @@ func (r *CommentRepository) UpdateModerationStatus(commentID string, status mode
} }
// Delete 删除评论(软删除,同时清理关联数据) // Delete 删除评论(软删除,同时清理关联数据)
func (r *CommentRepository) Delete(id string) error { func (r *commentRepository) Delete(id string) error {
return r.db.Transaction(func(tx *gorm.DB) error { return r.db.Transaction(func(tx *gorm.DB) error {
// 先查询评论获取post_id和parent_id // 先查询评论获取post_id和parent_id
var comment model.Comment var comment model.Comment
@@ -91,7 +115,7 @@ func (r *CommentRepository) Delete(id string) error {
} }
// ApplyPublishedStats 在评论审核通过后更新帖子评论数/回复数 // ApplyPublishedStats 在评论审核通过后更新帖子评论数/回复数
func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error { func (r *commentRepository) ApplyPublishedStats(comment *model.Comment) error {
if comment == nil { if comment == nil {
return nil return nil
} }
@@ -118,7 +142,7 @@ func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error {
} }
// GetByPostID 获取帖子评论 // 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 comments []*model.Comment
var total int64 var total int64
@@ -136,7 +160,7 @@ func (r *CommentRepository) GetByPostID(postID string, page, pageSize int) ([]*m
// GetByPostIDWithReplies 获取帖子评论(包含回复,扁平化结构) // GetByPostIDWithReplies 获取帖子评论(包含回复,扁平化结构)
// 所有层级的回复都扁平化展示在顶级评论的 replies 中 // 所有层级的回复都扁平化展示在顶级评论的 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 comments []*model.Comment
var total int64 var total int64
@@ -212,7 +236,7 @@ func (r *CommentRepository) GetByPostIDWithReplies(postID string, page, pageSize
} }
// loadFlatReplies 加载评论的所有回复(扁平化,所有层级都在同一层) // loadFlatReplies 加载评论的所有回复(扁平化,所有层级都在同一层)
func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit int) { func (r *commentRepository) loadFlatReplies(rootComment *model.Comment, limit int) {
var allReplies []*model.Comment var allReplies []*model.Comment
// 查询所有以该评论为根评论的回复(不包括顶级评论本身) // 查询所有以该评论为根评论的回复(不包括顶级评论本身)
@@ -226,7 +250,7 @@ func (r *CommentRepository) loadFlatReplies(rootComment *model.Comment, limit in
} }
// GetRepliesByRootID 根据根评论ID分页获取回复扁平化 // 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 replies []*model.Comment
var total int64 var total int64
@@ -246,7 +270,7 @@ func (r *CommentRepository) GetRepliesByRootID(rootID string, page, pageSize int
} }
// GetReplies 获取回复 // GetReplies 获取回复
func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error) { func (r *commentRepository) GetReplies(parentID string) ([]*model.Comment, error) {
var comments []*model.Comment var comments []*model.Comment
err := r.db.Where("parent_id = ? AND status = ?", parentID, model.CommentStatusPublished). err := r.db.Where("parent_id = ? AND status = ?", parentID, model.CommentStatusPublished).
Preload("User"). Preload("User").
@@ -256,7 +280,7 @@ func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error
} }
// Like 点赞评论(使用事务保证数据一致性) // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 检查是否已经点赞 // 检查是否已经点赞
var existing model.CommentLike var existing model.CommentLike
@@ -285,7 +309,7 @@ func (r *CommentRepository) Like(commentID, userID string) error {
} }
// Unlike 取消点赞评论(使用事务保证数据一致性) // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
result := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{}) result := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{})
if result.Error != nil { if result.Error != nil {
@@ -301,7 +325,7 @@ func (r *CommentRepository) Unlike(commentID, userID string) error {
} }
// IsLiked 检查是否已点赞 // IsLiked 检查是否已点赞
func (r *CommentRepository) IsLiked(commentID, userID string) bool { func (r *commentRepository) IsLiked(commentID, userID string) bool {
var count int64 var count int64
r.db.Model(&model.CommentLike{}).Where("comment_id = ? AND user_id = ?", commentID, userID).Count(&count) r.db.Model(&model.CommentLike{}).Where("comment_id = ? AND user_id = ?", commentID, userID).Count(&count)
return count > 0 return count > 0
@@ -309,7 +333,7 @@ func (r *CommentRepository) IsLiked(commentID, userID string) bool {
// IsLikedBatch 批量检查是否点赞(解决 N+1 问题) // IsLikedBatch 批量检查是否点赞(解决 N+1 问题)
// 返回 map[commentID]bool // 返回 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) result := make(map[string]bool)
if len(commentIDs) == 0 || userID == "" { if len(commentIDs) == 0 || userID == "" {
return result return result
@@ -347,7 +371,7 @@ type AdminCommentListFilter struct {
} }
// GetAdminCommentList 获取管理端评论列表 // 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 comments []*model.Comment
var total int64 var total int64
@@ -388,7 +412,7 @@ func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) (
} }
// GetAdminCommentByID 获取管理端评论详情(包含关联信息) // GetAdminCommentByID 获取管理端评论详情(包含关联信息)
func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, error) { func (r *commentRepository) GetAdminCommentByID(id string) (*model.Comment, error) {
var comment model.Comment var comment model.Comment
err := r.db.Preload("User").First(&comment, "id = ?", id).Error err := r.db.Preload("User").First(&comment, "id = ?", id).Error
if err != nil { if err != nil {
@@ -398,7 +422,7 @@ func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, erro
} }
// BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题) // BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题)
func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) { func (r *commentRepository) BatchDelete(ids []string) ([]string, error) {
if len(ids) == 0 { if len(ids) == 0 {
return nil, nil return nil, nil
} }
@@ -452,7 +476,7 @@ func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) {
} }
// GetCommentsByPostIDForAdmin 管理端获取帖子的评论列表(包含所有状态) // 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 comments []*model.Comment
var total int64 var total int64
@@ -472,7 +496,7 @@ func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pag
// GetCommentsByCursor 游标分页获取帖子评论(包含回复) // GetCommentsByCursor 游标分页获取帖子评论(包含回复)
// 排序方式created_at DESC // 排序方式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{}). query := r.db.WithContext(ctx).Model(&model.Comment{}).
Where("post_id = ? AND parent_id IS NULL AND status = ?", postID, model.CommentStatusPublished) 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 为评论列表加载回复 // loadRepliesForComments 为评论列表加载回复
func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) { func (r *commentRepository) loadRepliesForComments(comments []*model.Comment, replyLimit int) {
if len(comments) == 0 { if len(comments) == 0 {
return return
} }
@@ -593,7 +617,7 @@ func (r *CommentRepository) loadRepliesForComments(comments []*model.Comment, re
// GetRepliesByCursor 游标分页获取根评论的回复 // GetRepliesByCursor 游标分页获取根评论的回复
// 排序方式created_at ASC回复按时间正序 // 排序方式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{}). query := r.db.WithContext(ctx).Model(&model.Comment{}).
Where("root_id = ? AND status = ?", rootID, model.CommentStatusPublished) Where("root_id = ? AND status = ?", rootID, model.CommentStatusPublished)

View File

@@ -3,28 +3,40 @@ package repository
import ( import (
"context" "context"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
// DataChangeLogRepository 数据变更日志仓储 // DataChangeLogRepository 数据变更日志仓储接口
type DataChangeLogRepository struct { 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 db *gorm.DB
} }
// NewDataChangeLogRepository 创建数据变更日志仓储 // NewDataChangeLogRepository 创建数据变更日志仓储
func NewDataChangeLogRepository(db *gorm.DB) *DataChangeLogRepository { func NewDataChangeLogRepository(db *gorm.DB) DataChangeLogRepository {
return &DataChangeLogRepository{db: db} return &dataChangeLogRepository{db: db}
} }
// CreateDataChangeLog 创建单条数据变更日志 // 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 return r.db.WithContext(ctx).Create(log).Error
} }
// BatchCreateDataChangeLogs 批量创建数据变更日志 // 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 { if len(logs) == 0 {
return nil return nil
} }
@@ -32,7 +44,7 @@ func (r *DataChangeLogRepository) BatchCreateDataChangeLogs(ctx context.Context,
} }
// GetDataChangeLogs 获取数据变更日志列表(分页) // 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{}) query := r.db.WithContext(ctx).Model(&model.DataChangeLog{})
if filters.UserID != "" { if filters.UserID != "" {
@@ -75,22 +87,22 @@ func (r *DataChangeLogRepository) GetDataChangeLogs(ctx context.Context, filters
} }
// GetDataChangeLogsByUser 获取指定用户的数据变更日志 // GetDataChangeLogsByUser 获取指定用户的数据变更日志
func (r *DataChangeLogRepository) GetDataChangeLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { 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) return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{UserID: userID}, page, pageSize)
} }
// GetDataChangeLogsByOperator 获取指定操作人的日志 // GetDataChangeLogsByOperator 获取指定操作人的日志
func (r *DataChangeLogRepository) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error) { 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) return r.GetDataChangeLogs(ctx, dto.DataChangeFilter{OperatorID: operatorID}, page, pageSize)
} }
// DeleteOldLogs 删除旧的数据变更日志(用于定时清理) // 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 return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.DataChangeLog{}).Error
} }
// GetStatistics 获取数据变更日志统计 // 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{} stats := &DataChangeLogStatistics{}
query := r.db.WithContext(ctx).Model(&model.DataChangeLog{}) query := r.db.WithContext(ctx).Model(&model.DataChangeLog{})
@@ -116,18 +128,6 @@ func (r *DataChangeLogRepository) GetStatistics(ctx context.Context, startTime,
return stats, nil return stats, nil
} }
// DataChangeFilter 数据变更日志过滤条件
type DataChangeFilter struct {
UserID string
OperatorID string
ChangeType string
TargetType string
OperatorType string
IP string
StartTime string
EndTime string
}
// DataChangeLogStatistics 数据变更日志统计 // DataChangeLogStatistics 数据变更日志统计
type DataChangeLogStatistics struct { type DataChangeLogStatistics struct {
TotalCount int64 TotalCount int64

View File

@@ -8,23 +8,44 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// DeviceTokenRepository 设备Token仓储 // DeviceTokenRepository 设备Token仓储接口
type DeviceTokenRepository struct { 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 db *gorm.DB
} }
// NewDeviceTokenRepository 创建设备Token仓储 // NewDeviceTokenRepository 创建设备Token仓储
func NewDeviceTokenRepository(db *gorm.DB) *DeviceTokenRepository { func NewDeviceTokenRepository(db *gorm.DB) DeviceTokenRepository {
return &DeviceTokenRepository{db: db} return &deviceTokenRepository{db: db}
} }
// Create 创建设备Token // Create 创建设备Token
func (r *DeviceTokenRepository) Create(token *model.DeviceToken) error { func (r *deviceTokenRepository) Create(token *model.DeviceToken) error {
return r.db.Create(token).Error return r.db.Create(token).Error
} }
// GetByID 根据ID获取设备Token // 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 var token model.DeviceToken
err := r.db.First(&token, "id = ?", id).Error err := r.db.First(&token, "id = ?", id).Error
if err != nil { if err != nil {
@@ -34,18 +55,18 @@ func (r *DeviceTokenRepository) GetByID(id int64) (*model.DeviceToken, error) {
} }
// Update 更新设备Token // Update 更新设备Token
func (r *DeviceTokenRepository) Update(token *model.DeviceToken) error { func (r *deviceTokenRepository) Update(token *model.DeviceToken) error {
return r.db.Save(token).Error return r.db.Save(token).Error
} }
// Delete 删除设备Token软删除 // Delete 删除设备Token软删除
func (r *DeviceTokenRepository) Delete(id int64) error { func (r *deviceTokenRepository) Delete(id int64) error {
return r.db.Delete(&model.DeviceToken{}, id).Error return r.db.Delete(&model.DeviceToken{}, id).Error
} }
// GetByUserID 获取用户所有设备 // GetByUserID 获取用户所有设备
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var tokens []*model.DeviceToken
err := r.db.Where("user_id = ?", userID). err := r.db.Where("user_id = ?", userID).
Order("created_at DESC"). Order("created_at DESC").
@@ -55,7 +76,7 @@ func (r *DeviceTokenRepository) GetByUserID(userID string) ([]*model.DeviceToken
// GetActiveByUserID 获取用户活跃设备 // GetActiveByUserID 获取用户活跃设备
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var tokens []*model.DeviceToken
err := r.db.Where("user_id = ? AND is_active = ?", userID, true). err := r.db.Where("user_id = ? AND is_active = ?", userID, true).
Order("last_used_at DESC"). Order("last_used_at DESC").
@@ -64,7 +85,7 @@ func (r *DeviceTokenRepository) GetActiveByUserID(userID string) ([]*model.Devic
} }
// GetByDeviceID 根据设备ID获取设备Token // 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 var token model.DeviceToken
err := r.db.Where("device_id = ?", deviceID).First(&token).Error err := r.db.Where("device_id = ?", deviceID).First(&token).Error
if err != nil { if err != nil {
@@ -74,7 +95,7 @@ func (r *DeviceTokenRepository) GetByDeviceID(deviceID string) (*model.DeviceTok
} }
// GetByPushToken 根据推送Token获取设备信息 // GetByPushToken 根据推送Token获取设备信息
func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) { func (r *deviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceToken, error) {
var token model.DeviceToken var token model.DeviceToken
err := r.db.Where("push_token = ?", pushToken).First(&token).Error err := r.db.Where("push_token = ?", pushToken).First(&token).Error
if err != nil { if err != nil {
@@ -84,7 +105,7 @@ func (r *DeviceTokenRepository) GetByPushToken(pushToken string) (*model.DeviceT
} }
// DeactivateAllExcept 登出其他设备(停用除指定设备外的所有设备) // 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{}). return r.db.Model(&model.DeviceToken{}).
Where("user_id = ? AND device_id != ?", userID, deviceID). Where("user_id = ? AND device_id != ?", userID, deviceID).
Update("is_active", false).Error Update("is_active", false).Error
@@ -92,7 +113,7 @@ func (r *DeviceTokenRepository) DeactivateAllExcept(userID int64, deviceID strin
// Upsert 创建或更新设备Token // Upsert 创建或更新设备Token
// 如果设备ID已存在则更新Token和激活状态否则创建新记录 // 如果设备ID已存在则更新Token和激活状态否则创建新记录
func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error { func (r *deviceTokenRepository) Upsert(token *model.DeviceToken) error {
var existing model.DeviceToken var existing model.DeviceToken
err := r.db.Where("device_id = ?", token.DeviceID).First(&existing).Error err := r.db.Where("device_id = ?", token.DeviceID).First(&existing).Error
@@ -113,21 +134,21 @@ func (r *DeviceTokenRepository) Upsert(token *model.DeviceToken) error {
} }
// UpdateLastUsed 更新最后使用时间 // UpdateLastUsed 更新最后使用时间
func (r *DeviceTokenRepository) UpdateLastUsed(deviceID string) error { func (r *deviceTokenRepository) UpdateLastUsed(deviceID string) error {
return r.db.Model(&model.DeviceToken{}). return r.db.Model(&model.DeviceToken{}).
Where("device_id = ?", deviceID). Where("device_id = ?", deviceID).
Update("last_used_at", time.Now()).Error Update("last_used_at", time.Now()).Error
} }
// Deactivate 停用设备 // Deactivate 停用设备
func (r *DeviceTokenRepository) Deactivate(deviceID string) error { func (r *deviceTokenRepository) Deactivate(deviceID string) error {
return r.db.Model(&model.DeviceToken{}). return r.db.Model(&model.DeviceToken{}).
Where("device_id = ?", deviceID). Where("device_id = ?", deviceID).
Update("is_active", false).Error Update("is_active", false).Error
} }
// Activate 激活设备 // Activate 激活设备
func (r *DeviceTokenRepository) Activate(deviceID string) error { func (r *deviceTokenRepository) Activate(deviceID string) error {
return r.db.Model(&model.DeviceToken{}). return r.db.Model(&model.DeviceToken{}).
Where("device_id = ?", deviceID). Where("device_id = ?", deviceID).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
@@ -137,12 +158,12 @@ func (r *DeviceTokenRepository) Activate(deviceID string) error {
} }
// DeleteByUserID 删除用户所有设备Token // 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 return r.db.Where("user_id = ?", userID).Delete(&model.DeviceToken{}).Error
} }
// GetDeviceCountByUserID 获取用户设备数量 // GetDeviceCountByUserID 获取用户设备数量
func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) { func (r *deviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.DeviceToken{}). err := r.db.Model(&model.DeviceToken{}).
Where("user_id = ?", userID). Where("user_id = ?", userID).
@@ -151,7 +172,7 @@ func (r *DeviceTokenRepository) GetDeviceCountByUserID(userID int64) (int64, err
} }
// GetActiveDeviceCountByUserID 获取用户活跃设备数量 // GetActiveDeviceCountByUserID 获取用户活跃设备数量
func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) { func (r *deviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.DeviceToken{}). err := r.db.Model(&model.DeviceToken{}).
Where("user_id = ? AND is_active = ?", userID, true). Where("user_id = ? AND is_active = ?", userID, true).
@@ -160,7 +181,7 @@ func (r *DeviceTokenRepository) GetActiveDeviceCountByUserID(userID int64) (int6
} }
// DeleteInactiveDevices 删除长时间未使用的设备 // 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). return r.db.Where("is_active = ? AND last_used_at < ?", false, before).
Delete(&model.DeviceToken{}).Error Delete(&model.DeviceToken{}).Error
} }

View File

@@ -17,6 +17,7 @@ type GroupRepository interface {
Update(group *model.Group) error Update(group *model.Group) error
Delete(id string) error Delete(id string) error
GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error) GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error)
TransferOwner(groupID, oldOwnerID, newOwnerID string) error
// 群成员操作 // 群成员操作
AddMember(member *model.GroupMember) error AddMember(member *model.GroupMember) error
@@ -90,6 +91,32 @@ func (r *groupRepository) Update(group *model.Group) error {
return r.db.Save(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 删除群组 // Delete 删除群组
func (r *groupRepository) Delete(id string) error { func (r *groupRepository) Delete(id string) error {
return r.db.Transaction(func(tx *gorm.DB) error { return r.db.Transaction(func(tx *gorm.DB) error {

View File

@@ -4,28 +4,41 @@ import (
"context" "context"
"time" "time"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
// LoginLogRepository 登录日志仓储 // LoginLogRepository 登录日志仓储接口
type LoginLogRepository struct { 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 db *gorm.DB
} }
// NewLoginLogRepository 创建登录日志仓储 // NewLoginLogRepository 创建登录日志仓储
func NewLoginLogRepository(db *gorm.DB) *LoginLogRepository { func NewLoginLogRepository(db *gorm.DB) LoginLogRepository {
return &LoginLogRepository{db: db} return &loginLogRepository{db: db}
} }
// CreateLoginLog 创建单条登录日志 // 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 return r.db.WithContext(ctx).Create(log).Error
} }
// BatchCreateLoginLogs 批量创建登录日志 // 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 { if len(logs) == 0 {
return nil return nil
} }
@@ -33,7 +46,7 @@ func (r *LoginLogRepository) BatchCreateLoginLogs(ctx context.Context, logs []*m
} }
// GetLoginLogs 获取登录日志列表(分页) // 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{}) query := r.db.WithContext(ctx).Model(&model.LoginLog{})
if filters.UserID != "" { if filters.UserID != "" {
@@ -76,12 +89,12 @@ func (r *LoginLogRepository) GetLoginLogs(ctx context.Context, filters LoginFilt
} }
// GetLoginLogsByUser 获取指定用户的登录日志 // GetLoginLogsByUser 获取指定用户的登录日志
func (r *LoginLogRepository) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error) { 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) return r.GetLoginLogs(ctx, dto.LoginFilter{UserID: userID}, page, pageSize)
} }
// GetFailedLoginsByIP 获取指定IP的失败登录记录用于风控 // 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) startTime := time.Now().Add(-timeWindow)
var count int64 var count int64
err := r.db.WithContext(ctx). err := r.db.WithContext(ctx).
@@ -92,7 +105,7 @@ func (r *LoginLogRepository) GetFailedLoginsByIP(ctx context.Context, ip string,
} }
// GetRecentSuccessLogins 获取最近成功的登录记录 // 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 var logs []*model.LoginLog
err := r.db.WithContext(ctx). err := r.db.WithContext(ctx).
Where("user_id = ? AND result = ? AND event = ?", userID, string(model.LoginResultSuccess), string(model.LoginEventLogin)). 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 删除旧的登录日志(用于定时清理) // 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 return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.LoginLog{}).Error
} }
// GetStatistics 获取登录日志统计(使用单次 GROUP BY 查询,避免多次 COUNT // 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{} stats := &LoginLogStatistics{}
query := r.db.WithContext(ctx).Model(&model.LoginLog{}) query := r.db.WithContext(ctx).Model(&model.LoginLog{})
@@ -155,18 +168,6 @@ func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTi
return stats, nil 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 登录日志统计 // LoginLogStatistics 登录日志统计
type LoginLogStatistics struct { type LoginLogStatistics struct {
TotalCount int64 TotalCount int64

View File

@@ -1,22 +1,34 @@
package repository package repository
import ( import (
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
// MaterialSubjectRepository 学科分类仓储 // MaterialSubjectRepository 学科分类仓储接口
type MaterialSubjectRepository struct { 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 db *gorm.DB
} }
func NewMaterialSubjectRepository(db *gorm.DB) *MaterialSubjectRepository { func NewMaterialSubjectRepository(db *gorm.DB) MaterialSubjectRepository {
return &MaterialSubjectRepository{db: db} return &materialSubjectRepository{db: db}
} }
// ListActive 获取所有启用的学科分类 // ListActive 获取所有启用的学科分类
func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) { func (r *materialSubjectRepository) ListActive() ([]*model.MaterialSubject, error) {
var subjects []*model.MaterialSubject var subjects []*model.MaterialSubject
err := r.db. err := r.db.
Where("is_active = ?", true). Where("is_active = ?", true).
@@ -26,7 +38,7 @@ func (r *MaterialSubjectRepository) ListActive() ([]*model.MaterialSubject, erro
} }
// ListAll 获取所有学科分类(管理端用) // ListAll 获取所有学科分类(管理端用)
func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) { func (r *materialSubjectRepository) ListAll() ([]*model.MaterialSubject, error) {
var subjects []*model.MaterialSubject var subjects []*model.MaterialSubject
err := r.db. err := r.db.
Order("sort_order ASC, created_at ASC"). Order("sort_order ASC, created_at ASC").
@@ -35,7 +47,7 @@ func (r *MaterialSubjectRepository) ListAll() ([]*model.MaterialSubject, error)
} }
// GetByID 根据ID获取学科分类 // GetByID 根据ID获取学科分类
func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) { func (r *materialSubjectRepository) GetByID(id string) (*model.MaterialSubject, error) {
var subject model.MaterialSubject var subject model.MaterialSubject
if err := r.db.First(&subject, "id = ?", id).Error; err != nil { if err := r.db.First(&subject, "id = ?", id).Error; err != nil {
return nil, err return nil, err
@@ -44,50 +56,53 @@ func (r *MaterialSubjectRepository) GetByID(id string) (*model.MaterialSubject,
} }
// Create 创建学科分类 // Create 创建学科分类
func (r *MaterialSubjectRepository) Create(subject *model.MaterialSubject) error { func (r *materialSubjectRepository) Create(subject *model.MaterialSubject) error {
return r.db.Create(subject).Error return r.db.Create(subject).Error
} }
// Update 更新学科分类 // Update 更新学科分类
func (r *MaterialSubjectRepository) Update(subject *model.MaterialSubject) error { func (r *materialSubjectRepository) Update(subject *model.MaterialSubject) error {
return r.db.Save(subject).Error return r.db.Save(subject).Error
} }
// Delete 删除学科分类 // Delete 删除学科分类
func (r *MaterialSubjectRepository) Delete(id string) error { func (r *materialSubjectRepository) Delete(id string) error {
return r.db.Delete(&model.MaterialSubject{}, "id = ?", id).Error return r.db.Delete(&model.MaterialSubject{}, "id = ?", id).Error
} }
// CountMaterials 统计学科下的资料数量 // CountMaterials 统计学科下的资料数量
func (r *MaterialSubjectRepository) CountMaterials(subjectID string) (int64, error) { func (r *materialSubjectRepository) CountMaterials(subjectID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.MaterialFile{}).Where("subject_id = ?", subjectID).Count(&count).Error err := r.db.Model(&model.MaterialFile{}).Where("subject_id = ?", subjectID).Count(&count).Error
return count, err return count, err
} }
// MaterialFileRepository 文件资料仓储 // MaterialFileRepository 文件资料仓储接口
type MaterialFileRepository struct { 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 db *gorm.DB
} }
func NewMaterialFileRepository(db *gorm.DB) *MaterialFileRepository { func NewMaterialFileRepository(db *gorm.DB) MaterialFileRepository {
return &MaterialFileRepository{db: db} return &materialFileRepository{db: db}
}
// ListQueryParams 查询参数
type MaterialFileQueryParams struct {
SubjectID string
FileType string
Status string
Keyword string
Page int
PageSize int
SortBy string
SortOrder string
} }
// List 获取资料列表(支持筛选和分页) // 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 files []*model.MaterialFile
var total int64 var total int64
@@ -144,7 +159,7 @@ func (r *MaterialFileRepository) List(params MaterialFileQueryParams) ([]*model.
} }
// GetByID 根据ID获取资料详情 // GetByID 根据ID获取资料详情
func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error) { func (r *materialFileRepository) GetByID(id string) (*model.MaterialFile, error) {
var file model.MaterialFile var file model.MaterialFile
if err := r.db.First(&file, "id = ?", id).Error; err != nil { if err := r.db.First(&file, "id = ?", id).Error; err != nil {
return nil, err return nil, err
@@ -153,7 +168,7 @@ func (r *MaterialFileRepository) GetByID(id string) (*model.MaterialFile, error)
} }
// GetByIDWithSubject 根据ID获取资料详情包含学科信息 // GetByIDWithSubject 根据ID获取资料详情包含学科信息
func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) { func (r *materialFileRepository) GetByIDWithSubject(id string) (*model.MaterialFile, error) {
var file model.MaterialFile var file model.MaterialFile
if err := r.db.Preload("Subject").First(&file, "id = ?", id).Error; err != nil { if err := r.db.Preload("Subject").First(&file, "id = ?", id).Error; err != nil {
return nil, err return nil, err
@@ -162,28 +177,28 @@ func (r *MaterialFileRepository) GetByIDWithSubject(id string) (*model.MaterialF
} }
// Create 创建资料 // Create 创建资料
func (r *MaterialFileRepository) Create(file *model.MaterialFile) error { func (r *materialFileRepository) Create(file *model.MaterialFile) error {
return r.db.Create(file).Error return r.db.Create(file).Error
} }
// Update 更新资料 // Update 更新资料
func (r *MaterialFileRepository) Update(file *model.MaterialFile) error { func (r *materialFileRepository) Update(file *model.MaterialFile) error {
return r.db.Save(file).Error return r.db.Save(file).Error
} }
// Delete 删除资料 // Delete 删除资料
func (r *MaterialFileRepository) Delete(id string) error { func (r *materialFileRepository) Delete(id string) error {
return r.db.Delete(&model.MaterialFile{}, "id = ?", id).Error return r.db.Delete(&model.MaterialFile{}, "id = ?", id).Error
} }
// IncrementDownloadCount 增加下载次数 // IncrementDownloadCount 增加下载次数
func (r *MaterialFileRepository) IncrementDownloadCount(id string) error { func (r *materialFileRepository) IncrementDownloadCount(id string) error {
return r.db.Model(&model.MaterialFile{}).Where("id = ?", id). return r.db.Model(&model.MaterialFile{}).Where("id = ?", id).
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error
} }
// GetBySubjectID 根据学科ID获取资料列表 // 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 files []*model.MaterialFile
var total int64 var total int64
@@ -202,7 +217,7 @@ func (r *MaterialFileRepository) GetBySubjectID(subjectID string, page, pageSize
} }
// Search 搜索资料 // 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 files []*model.MaterialFile
var total int64 var total int64
@@ -225,7 +240,7 @@ func (r *MaterialFileRepository) Search(keyword string, page, pageSize int) ([]*
} }
// GetHotMaterials 获取热门资料(按下载次数排序) // GetHotMaterials 获取热门资料(按下载次数排序)
func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) { func (r *materialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFile, error) {
var files []*model.MaterialFile var files []*model.MaterialFile
err := r.db. err := r.db.
Where("status = ?", model.MaterialStatusActive). Where("status = ?", model.MaterialStatusActive).
@@ -236,7 +251,7 @@ func (r *MaterialFileRepository) GetHotMaterials(limit int) ([]*model.MaterialFi
} }
// GetLatestMaterials 获取最新资料 // GetLatestMaterials 获取最新资料
func (r *MaterialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) { func (r *materialFileRepository) GetLatestMaterials(limit int) ([]*model.MaterialFile, error) {
var files []*model.MaterialFile var files []*model.MaterialFile
err := r.db. err := r.db.
Where("status = ?", model.MaterialStatusActive). Where("status = ?", model.MaterialStatusActive).

View File

@@ -14,23 +14,64 @@ import (
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// MessageRepository 消息仓储 // MessageRepository 消息仓储接口
type MessageRepository struct { 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 db *gorm.DB
} }
// NewMessageRepository 创建消息仓储 // NewMessageRepository 创建消息仓储
func NewMessageRepository(db *gorm.DB) *MessageRepository { func NewMessageRepository(db *gorm.DB) MessageRepository {
return &MessageRepository{db: db} return &messageRepository{db: db}
} }
// CreateMessage 创建消息 // CreateMessage 创建消息
func (r *MessageRepository) CreateMessage(msg *model.Message) error { func (r *messageRepository) CreateMessage(msg *model.Message) error {
return r.db.Create(msg).Error return r.db.Create(msg).Error
} }
// GetConversation 获取会话 // GetConversation 获取会话
func (r *MessageRepository) GetConversation(id string) (*model.Conversation, error) { func (r *messageRepository) GetConversation(id string) (*model.Conversation, error) {
var conv model.Conversation var conv model.Conversation
err := r.db.Preload("Group").First(&conv, "id = ?", id).Error err := r.db.Preload("Group").First(&conv, "id = ?", id).Error
if err != nil { if err != nil {
@@ -39,10 +80,32 @@ func (r *MessageRepository) GetConversation(id string) (*model.Conversation, err
return &conv, nil 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 获取或创建私聊会话 // GetOrCreatePrivateConversation 获取或创建私聊会话
// 使用参与者关系表来管理会话 // 使用参与者关系表来管理会话
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var conv model.Conversation
// 查找两个用户共同参与的私聊会话 // 查找两个用户共同参与的私聊会话
@@ -91,7 +154,7 @@ func (r *MessageRepository) GetOrCreatePrivateConversation(user1ID, user2ID stri
// GetConversations 获取用户会话列表 // GetConversations 获取用户会话列表
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 convs []*model.Conversation
var total int64 var total int64
@@ -121,7 +184,7 @@ func (r *MessageRepository) GetConversations(userID string, page, pageSize int)
} }
// GetMessages 获取会话消息 // 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 messages []*model.Message
var total int64 var total int64
@@ -142,7 +205,7 @@ func (r *MessageRepository) GetMessages(conversationID string, page, pageSize in
} }
// GetMessagesAfterSeq 获取指定seq之后的消息用于增量同步 // 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 var messages []*model.Message
err := r.db.Where("conversation_id = ? AND seq > ?", conversationID, afterSeq). err := r.db.Where("conversation_id = ? AND seq > ?", conversationID, afterSeq).
Order("seq ASC"). Order("seq ASC").
@@ -157,7 +220,7 @@ func (r *MessageRepository) GetMessagesAfterSeq(conversationID string, afterSeq
} }
// GetMessagesBeforeSeq 获取指定seq之前的历史消息用于下拉加载更多 // 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 var messages []*model.Message
err := r.db.Where("conversation_id = ? AND seq < ?", conversationID, beforeSeq). err := r.db.Where("conversation_id = ? AND seq < ?", conversationID, beforeSeq).
Order("seq DESC"). // 降序获取最新消息在前 Order("seq DESC"). // 降序获取最新消息在前
@@ -176,7 +239,7 @@ func (r *MessageRepository) GetMessagesBeforeSeq(conversationID string, beforeSe
} }
// GetConversationParticipants 获取会话参与者 // GetConversationParticipants 获取会话参与者
func (r *MessageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) { func (r *messageRepository) GetConversationParticipants(conversationID string) ([]*model.ConversationParticipant, error) {
var participants []*model.ConversationParticipant var participants []*model.ConversationParticipant
err := r.db.Where("conversation_id = ?", conversationID).Find(&participants).Error err := r.db.Where("conversation_id = ?", conversationID).Find(&participants).Error
return participants, err return participants, err
@@ -184,7 +247,7 @@ func (r *MessageRepository) GetConversationParticipants(conversationID string) (
// GetParticipant 获取用户在会话中的参与者信息 // GetParticipant 获取用户在会话中的参与者信息
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var participant model.ConversationParticipant
err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error
if err != nil { if err != nil {
@@ -211,7 +274,7 @@ func (r *MessageRepository) GetParticipant(conversationID string, userID string)
// UpdateLastReadSeq 更新已读位置 // UpdateLastReadSeq 更新已读位置
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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{}). result := r.db.Model(&model.ConversationParticipant{}).
Where("conversation_id = ? AND user_id = ?", conversationID, userID). Where("conversation_id = ? AND user_id = ?", conversationID, userID).
Update("last_read_seq", lastReadSeq) Update("last_read_seq", lastReadSeq)
@@ -246,7 +309,7 @@ func (r *MessageRepository) UpdateLastReadSeq(conversationID string, userID stri
} }
// UpdatePinned 更新会话置顶状态(用户维度) // 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{}). result := r.db.Model(&model.ConversationParticipant{}).
Where("conversation_id = ? AND user_id = ?", conversationID, userID). Where("conversation_id = ? AND user_id = ?", conversationID, userID).
Update("is_pinned", isPinned) Update("is_pinned", isPinned)
@@ -277,7 +340,7 @@ func (r *MessageRepository) UpdatePinned(conversationID string, userID string, i
// GetUnreadCount 获取未读消息数 // GetUnreadCount 获取未读消息数
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var participant model.ConversationParticipant
err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error
if err != nil { if err != nil {
@@ -292,7 +355,7 @@ func (r *MessageRepository) GetUnreadCount(conversationID string, userID string)
} }
// UpdateConversationLastSeq 更新会话的最后消息seq和时间 // 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{}). return r.db.Model(&model.Conversation{}).
Where("id = ?", conversationID). Where("id = ?", conversationID).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
@@ -302,7 +365,7 @@ func (r *MessageRepository) UpdateConversationLastSeq(conversationID string, seq
} }
// GetNextSeq 获取会话的下一个seq值 // GetNextSeq 获取会话的下一个seq值
func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) { func (r *messageRepository) GetNextSeq(conversationID string) (int64, error) {
var conv model.Conversation var conv model.Conversation
err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error
if err != nil { if err != nil {
@@ -312,7 +375,7 @@ func (r *MessageRepository) GetNextSeq(conversationID string) (int64, error) {
} }
// CreateMessageWithSeq 创建消息并更新seq事务操作 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 获取当前seq并+1 // 获取当前seq并+1
var conv model.Conversation var conv model.Conversation
@@ -350,7 +413,7 @@ func (r *MessageRepository) CreateMessageWithSeq(msg *model.Message) error {
// GetAllUnreadCount 获取用户所有会话的未读消息总数 // GetAllUnreadCount 获取用户所有会话的未读消息总数
// userID 参数为 string 类型UUID格式与JWT中user_id保持一致 // 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 var totalUnread int64
err := r.db.Table("conversation_participants AS cp"). 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). 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获取消息 // GetMessageByID 根据ID获取消息
func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, error) { func (r *messageRepository) GetMessageByID(messageID string) (*model.Message, error) {
var message model.Message var message model.Message
err := r.db.First(&message, "id = ?", messageID).Error err := r.db.First(&message, "id = ?", messageID).Error
if err != nil { if err != nil {
@@ -373,7 +436,7 @@ func (r *MessageRepository) GetMessageByID(messageID string) (*model.Message, er
} }
// CountMessagesBySenderInConversation 统计会话中某用户已发送消息数 // CountMessagesBySenderInConversation 统计会话中某用户已发送消息数
func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) { func (r *messageRepository) CountMessagesBySenderInConversation(conversationID, senderID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.Message{}). err := r.db.Model(&model.Message{}).
Where("conversation_id = ? AND sender_id = ?", conversationID, senderID). Where("conversation_id = ? AND sender_id = ?", conversationID, senderID).
@@ -382,15 +445,25 @@ func (r *MessageRepository) CountMessagesBySenderInConversation(conversationID,
} }
// UpdateMessageStatus 更新消息状态 // 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{}). return r.db.Model(&model.Message{}).
Where("id = ?", messageID). Where("id = ?", messageID).
Update("status", status).Error 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 获取或创建用户在系统会话中的参与者记录 // GetOrCreateSystemParticipant 获取或创建用户在系统会话中的参与者记录
// 系统会话是虚拟会话,但需要参与者记录来跟踪已读状态 // 系统会话是虚拟会话,但需要参与者记录来跟踪已读状态
func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) { func (r *messageRepository) GetOrCreateSystemParticipant(userID string) (*model.ConversationParticipant, error) {
var participant model.ConversationParticipant var participant model.ConversationParticipant
err := r.db.Where("conversation_id = ? AND user_id = ?", err := r.db.Where("conversation_id = ? AND user_id = ?",
model.SystemConversationID, userID).First(&participant).Error model.SystemConversationID, userID).First(&participant).Error
@@ -418,7 +491,7 @@ func (r *MessageRepository) GetOrCreateSystemParticipant(userID string) (*model.
} }
// GetSystemMessagesUnreadCount 获取系统消息未读数 // GetSystemMessagesUnreadCount 获取系统消息未读数
func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) { func (r *messageRepository) GetSystemMessagesUnreadCount(userID string) (int64, error) {
// 获取或创建参与者记录 // 获取或创建参与者记录
participant, err := r.GetOrCreateSystemParticipant(userID) participant, err := r.GetOrCreateSystemParticipant(userID)
if err != nil { if err != nil {
@@ -436,7 +509,7 @@ func (r *MessageRepository) GetSystemMessagesUnreadCount(userID string) (int64,
} }
// MarkAllSystemMessagesAsRead 标记所有系统消息已读 // MarkAllSystemMessagesAsRead 标记所有系统消息已读
func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error { func (r *messageRepository) MarkAllSystemMessagesAsRead(userID string) error {
// 获取系统会话的最新 seq // 获取系统会话的最新 seq
var maxSeq int64 var maxSeq int64
err := r.db.Model(&model.Message{}). err := r.db.Model(&model.Message{}).
@@ -465,7 +538,7 @@ func (r *MessageRepository) MarkAllSystemMessagesAsRead(userID string) error {
} }
// GetConversationByGroupID 通过群组ID获取会话 // GetConversationByGroupID 通过群组ID获取会话
func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) { func (r *messageRepository) GetConversationByGroupID(groupID string) (*model.Conversation, error) {
var conv model.Conversation var conv model.Conversation
err := r.db.Where("group_id = ?", groupID).First(&conv).Error err := r.db.Where("group_id = ?", groupID).First(&conv).Error
if err != nil { if err != nil {
@@ -476,14 +549,14 @@ func (r *MessageRepository) GetConversationByGroupID(groupID string) (*model.Con
// RemoveParticipant 移除会话参与者 // 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). return r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).
Delete(&model.ConversationParticipant{}).Error Delete(&model.ConversationParticipant{}).Error
} }
// AddParticipant 添加会话参与者 // AddParticipant 添加会话参与者
// 当用户加入群聊时,需要同时将其添加到对应会话的参与者记录 // 当用户加入群聊时,需要同时将其添加到对应会话的参与者记录
func (r *MessageRepository) AddParticipant(conversationID string, userID string) error { func (r *messageRepository) AddParticipant(conversationID string, userID string) error {
// 先检查是否已经是参与者 // 先检查是否已经是参与者
var count int64 var count int64
err := r.db.Model(&model.ConversationParticipant{}). err := r.db.Model(&model.ConversationParticipant{}).
@@ -509,7 +582,7 @@ func (r *MessageRepository) AddParticipant(conversationID string, userID string)
// DeleteConversationByGroupID 删除群组对应的会话及其参与者 // DeleteConversationByGroupID 删除群组对应的会话及其参与者
// 当解散群组时调用 // 当解散群组时调用
func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error { func (r *messageRepository) DeleteConversationByGroupID(groupID string) error {
// 获取群组对应的会话 // 获取群组对应的会话
conv, err := r.GetConversationByGroupID(groupID) conv, err := r.GetConversationByGroupID(groupID)
if err != nil { if err != nil {
@@ -538,7 +611,7 @@ func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error {
} }
// HideConversationForUser 仅对当前用户隐藏会话(私聊删除) // HideConversationForUser 仅对当前用户隐藏会话(私聊删除)
func (r *MessageRepository) HideConversationForUser(conversationID, userID string) error { func (r *messageRepository) HideConversationForUser(conversationID, userID string) error {
now := time.Now() now := time.Now()
return r.db.Model(&model.ConversationParticipant{}). return r.db.Model(&model.ConversationParticipant{}).
Where("conversation_id = ? AND user_id = ?", conversationID, userID). Where("conversation_id = ? AND user_id = ?", conversationID, userID).
@@ -554,7 +627,7 @@ type ParticipantUpdate struct {
// BatchWriteMessages 批量写入消息 // BatchWriteMessages 批量写入消息
// 使用 GORM 的 CreateInBatches 实现高效批量插入 // 使用 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 { if len(messages) == 0 {
return nil return nil
} }
@@ -563,7 +636,7 @@ func (r *MessageRepository) BatchWriteMessages(ctx context.Context, messages []*
// BatchUpdateParticipants 批量更新参与者(使用 CASE WHEN 优化) // BatchUpdateParticipants 批量更新参与者(使用 CASE WHEN 优化)
// 使用单条 SQL 更新多条记录,避免循环执行 UPDATE // 使用单条 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 { if len(updates) == 0 {
return nil return nil
} }
@@ -601,7 +674,7 @@ func (r *MessageRepository) BatchUpdateParticipants(ctx context.Context, updates
} }
// UpdateConversationLastSeqWithContext 更新会话最后消息序号 // 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). return r.db.WithContext(ctx).
Model(&model.Conversation{}). Model(&model.Conversation{}).
Where("id = ?", convID). Where("id = ?", convID).
@@ -613,7 +686,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithContext(ctx context.Con
} }
// BatchWriteMessagesWithTx 在事务中批量写入消息 // 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 { if len(messages) == 0 {
return nil return nil
} }
@@ -621,7 +694,7 @@ func (r *MessageRepository) BatchWriteMessagesWithTx(tx *gorm.DB, messages []*mo
} }
// BatchUpdateParticipantsWithTx 在事务中批量更新参与者 // 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 { if len(updates) == 0 {
return nil return nil
} }
@@ -649,7 +722,7 @@ func (r *MessageRepository) BatchUpdateParticipantsWithTx(tx *gorm.DB, updates [
} }
// UpdateConversationLastSeqWithTx 在事务中更新会话最后消息序号 // 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{}). return tx.Model(&model.Conversation{}).
Where("id = ?", convID). Where("id = ?", convID).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
@@ -663,7 +736,7 @@ func (r *MessageRepository) UpdateConversationLastSeqWithTx(tx *gorm.DB, convID
// GetMessagesByCursor 游标分页获取会话消息 // GetMessagesByCursor 游标分页获取会话消息
// 消息按 seq DESC 排序 // 消息按 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) 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 游标分页获取用户会话列表 // GetConversationsByCursor 游标分页获取用户会话列表
// 会话按置顶优先,然后按 updated_at DESC 排序 // 会话按置顶优先,然后按 updated_at DESC 排序
// 注意:会话列表的排序涉及 conversation_participants 表的 is_pinned 和 updated_at 字段 // 注意:会话列表的排序涉及 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 表 // 构建基础查询 - 关联 conversation_participants 表
query := r.db.WithContext(ctx).Model(&model.Conversation{}). query := r.db.WithContext(ctx).Model(&model.Conversation{}).
Joins("INNER JOIN conversation_participants cp ON conversations.id = cp.conversation_id"). Joins("INNER JOIN conversation_participants cp ON conversations.id = cp.conversation_id").

View File

@@ -8,23 +8,36 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// NotificationRepository 通知仓储 // NotificationRepository 通知仓储接口
type NotificationRepository struct { 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 db *gorm.DB
} }
// NewNotificationRepository 创建通知仓储 // NewNotificationRepository 创建通知仓储
func NewNotificationRepository(db *gorm.DB) *NotificationRepository { func NewNotificationRepository(db *gorm.DB) NotificationRepository {
return &NotificationRepository{db: db} return &notificationRepository{db: db}
} }
// Create 创建通知 // Create 创建通知
func (r *NotificationRepository) Create(notification *model.Notification) error { func (r *notificationRepository) Create(notification *model.Notification) error {
return r.db.Create(notification).Error return r.db.Create(notification).Error
} }
// GetByID 根据ID获取通知 // GetByID 根据ID获取通知
func (r *NotificationRepository) GetByID(id string) (*model.Notification, error) { func (r *notificationRepository) GetByID(id string) (*model.Notification, error) {
var notification model.Notification var notification model.Notification
err := r.db.First(&notification, "id = ?", id).Error err := r.db.First(&notification, "id = ?", id).Error
if err != nil { if err != nil {
@@ -34,7 +47,7 @@ func (r *NotificationRepository) GetByID(id string) (*model.Notification, error)
} }
// GetByUserID 获取用户通知 // 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 notifications []*model.Notification
var total int64 var total int64
@@ -53,29 +66,29 @@ func (r *NotificationRepository) GetByUserID(userID string, page, pageSize int,
} }
// MarkAsRead 标记为已读 // 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 return r.db.Model(&model.Notification{}).Where("id = ?", id).Update("is_read", true).Error
} }
// MarkAllAsRead 标记所有为已读 // 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 return r.db.Model(&model.Notification{}).Where("user_id = ?", userID).Update("is_read", true).Error
} }
// Delete 删除通知 // Delete 删除通知
func (r *NotificationRepository) Delete(id string) error { func (r *notificationRepository) Delete(id string) error {
return r.db.Delete(&model.Notification{}, "id = ?", id).Error return r.db.Delete(&model.Notification{}, "id = ?", id).Error
} }
// GetUnreadCount 获取未读数量 // GetUnreadCount 获取未读数量
func (r *NotificationRepository) GetUnreadCount(userID string) (int64, error) { func (r *notificationRepository) GetUnreadCount(userID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.Notification{}).Where("user_id = ? AND is_read = ?", userID, false).Count(&count).Error err := r.db.Model(&model.Notification{}).Where("user_id = ? AND is_read = ?", userID, false).Count(&count).Error
return count, err return count, err
} }
// DeleteAllByUserID 删除用户所有通知 // 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 return r.db.Where("user_id = ?", userID).Delete(&model.Notification{}).Error
} }
@@ -83,7 +96,7 @@ func (r *NotificationRepository) DeleteAllByUserID(userID string) error {
// GetNotificationsByCursor 游标分页获取用户通知 // GetNotificationsByCursor 游标分页获取用户通知
// 排序方式created_at DESC // 排序方式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) query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID)

View File

@@ -3,28 +3,40 @@ package repository
import ( import (
"context" "context"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
// OperationLogRepository 操作日志仓储 // OperationLogRepository 操作日志仓储接口
type OperationLogRepository struct { 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 db *gorm.DB
} }
// NewOperationLogRepository 创建操作日志仓储 // NewOperationLogRepository 创建操作日志仓储
func NewOperationLogRepository(db *gorm.DB) *OperationLogRepository { func NewOperationLogRepository(db *gorm.DB) OperationLogRepository {
return &OperationLogRepository{db: db} return &operationLogRepository{db: db}
} }
// CreateOperationLog 创建单条操作日志 // 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 return r.db.WithContext(ctx).Create(log).Error
} }
// BatchCreateOperationLogs 批量创建操作日志 // 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 { if len(logs) == 0 {
return nil return nil
} }
@@ -32,7 +44,7 @@ func (r *OperationLogRepository) BatchCreateOperationLogs(ctx context.Context, l
} }
// GetOperationLogs 获取操作日志列表(分页) // 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{}) query := r.db.WithContext(ctx).Model(&model.OperationLog{})
if filters.UserID != "" { if filters.UserID != "" {
@@ -75,22 +87,22 @@ func (r *OperationLogRepository) GetOperationLogs(ctx context.Context, filters L
} }
// GetOperationLogsByUser 获取指定用户的操作日志 // GetOperationLogsByUser 获取指定用户的操作日志
func (r *OperationLogRepository) GetOperationLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.OperationLog, int64, error) { 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) return r.GetOperationLogs(ctx, dto.LogFilter{UserID: userID}, page, pageSize)
} }
// GetOperationLogsByTimeRange 按时间范围获取操作日志 // GetOperationLogsByTimeRange 按时间范围获取操作日志
func (r *OperationLogRepository) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error) { 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) return r.GetOperationLogs(ctx, dto.LogFilter{StartTime: startTime, EndTime: endTime}, page, pageSize)
} }
// DeleteOldLogs 删除旧的操作日志(用于定时清理) // 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 return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.OperationLog{}).Error
} }
// GetStatistics 获取操作日志统计(使用单次 GROUP BY 查询,避免多次 COUNT) // 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{} stats := &OperationLogStatistics{}
query := r.db.WithContext(ctx).Model(&model.OperationLog{}) query := r.db.WithContext(ctx).Model(&model.OperationLog{})
@@ -125,18 +137,6 @@ func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, e
return stats, nil return stats, nil
} }
// LogFilter 日志过滤条件
type LogFilter struct {
UserID string
Operation string
TargetType string
TargetID string
Status string
IP string
StartTime string
EndTime string
}
// OperationLogStatistics 操作日志统计 // OperationLogStatistics 操作日志统计
type OperationLogStatistics struct { type OperationLogStatistics struct {
TotalCount int64 TotalCount int64

View File

@@ -11,18 +11,63 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// PostRepository 帖子仓储 // PostRepository 帖子仓储接口
type PostRepository struct { 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 db *gorm.DB
} }
// NewPostRepository 创建帖子仓储 // NewPostRepository 创建帖子仓储
func NewPostRepository(db *gorm.DB) *PostRepository { func NewPostRepository(db *gorm.DB) PostRepository {
return &PostRepository{db: db} return &postRepository{db: db}
} }
// getDB 获取数据库连接(优先使用 context 中的事务) // 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 { if tx := GetTxFromContext(ctx); tx != nil {
return tx return tx
} }
@@ -30,7 +75,7 @@ func (r *PostRepository) getDB(ctx context.Context) *gorm.DB {
} }
// Create 创建帖子 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 创建帖子 // 创建帖子
if err := tx.Create(post).Error; err != nil { if err := tx.Create(post).Error; err != nil {
@@ -68,7 +113,7 @@ func (r *PostRepository) Create(post *model.Post, images []string) error {
} }
// GetByID 根据ID获取帖子 // GetByID 根据ID获取帖子
func (r *PostRepository) GetByID(id string) (*model.Post, error) { func (r *postRepository) GetByID(id string) (*model.Post, error) {
var post model.Post var post model.Post
err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error
if err != nil { if err != nil {
@@ -78,7 +123,7 @@ func (r *PostRepository) GetByID(id string) (*model.Post, error) {
} }
// Update 更新帖子 // Update 更新帖子
func (r *PostRepository) Update(post *model.Post) error { func (r *postRepository) Update(post *model.Post) error {
now := time.Now() now := time.Now()
post.UpdatedAt = now post.UpdatedAt = now
post.ContentEditedAt = &now post.ContentEditedAt = &now
@@ -86,7 +131,7 @@ func (r *PostRepository) Update(post *model.Post) error {
} }
// UpdateWithImages 更新帖子及其图片images=nil 表示不更新图片) // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
now := time.Now() now := time.Now()
post.UpdatedAt = now post.UpdatedAt = now
@@ -119,7 +164,7 @@ func (r *PostRepository) UpdateWithImages(post *model.Post, images *[]string) er
} }
// UpdateModerationStatus 更新帖子审核状态 // 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 // 审核状态更新属于管理操作不应影响帖子内容更新时间updated_at
updates := map[string]interface{}{ updates := map[string]interface{}{
"status": status, "status": status,
@@ -132,7 +177,7 @@ func (r *PostRepository) UpdateModerationStatus(postID string, status model.Post
} }
// Delete 删除帖子(软删除,同时清理关联数据) // Delete 删除帖子(软删除,同时清理关联数据)
func (r *PostRepository) Delete(id string) error { func (r *postRepository) Delete(id string) error {
return r.db.Transaction(func(tx *gorm.DB) error { return r.db.Transaction(func(tx *gorm.DB) error {
// 删除帖子图片 // 删除帖子图片
if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil { 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 分页获取帖子列表 // List 分页获取帖子列表
// includePending=true 时,仅在指定 userID 下额外返回 pending用于作者查看自己待审核帖子 // 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 posts []*model.Post
var total int64 var total int64
@@ -196,7 +241,7 @@ func (r *PostRepository) List(page, pageSize int, userID string, includePending
} }
// GetUserPosts 获取用户帖子 // 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 posts []*model.Post
var total int64 var total int64
@@ -227,7 +272,7 @@ func (r *PostRepository) GetUserPosts(userID string, page, pageSize int, include
} }
// GetFavorites 获取用户收藏 // 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 posts []*model.Post
var total int64 var total int64
@@ -241,7 +286,7 @@ func (r *PostRepository) GetFavorites(userID string, page, pageSize int) ([]*mod
} }
// Like 点赞帖子 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 检查是否已经点赞 // 检查是否已经点赞
var existing model.PostLike var existing model.PostLike
@@ -272,7 +317,7 @@ func (r *PostRepository) Like(postID, userID string) error {
} }
// Unlike 取消点赞 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.PostLike{}) result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.PostLike{})
if result.Error != nil { if result.Error != nil {
@@ -290,7 +335,7 @@ func (r *PostRepository) Unlike(postID, userID string) error {
} }
// IsLiked 检查是否点赞 // IsLiked 检查是否点赞
func (r *PostRepository) IsLiked(postID, userID string) bool { func (r *postRepository) IsLiked(postID, userID string) bool {
var count int64 var count int64
r.db.Model(&model.PostLike{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count) r.db.Model(&model.PostLike{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count)
return count > 0 return count > 0
@@ -298,7 +343,7 @@ func (r *PostRepository) IsLiked(postID, userID string) bool {
// IsLikedBatch 批量检查是否点赞(解决 N+1 问题) // IsLikedBatch 批量检查是否点赞(解决 N+1 问题)
// 返回 map[postID]bool // 返回 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) result := make(map[string]bool)
if len(postIDs) == 0 || userID == "" { if len(postIDs) == 0 || userID == "" {
return result return result
@@ -324,7 +369,7 @@ func (r *PostRepository) IsLikedBatch(postIDs []string, userID string) map[strin
} }
// Favorite 收藏帖子 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 检查是否已经收藏 // 检查是否已经收藏
var existing model.Favorite var existing model.Favorite
@@ -355,7 +400,7 @@ func (r *PostRepository) Favorite(postID, userID string) error {
} }
// Unfavorite 取消收藏 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.Favorite{}) result := tx.Where("post_id = ? AND user_id = ?", postID, userID).Delete(&model.Favorite{})
if result.Error != nil { if result.Error != nil {
@@ -374,7 +419,7 @@ func (r *PostRepository) Unfavorite(postID, userID string) error {
} }
// IsFavorited 检查是否收藏 // IsFavorited 检查是否收藏
func (r *PostRepository) IsFavorited(postID, userID string) bool { func (r *postRepository) IsFavorited(postID, userID string) bool {
var count int64 var count int64
r.db.Model(&model.Favorite{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count) r.db.Model(&model.Favorite{}).Where("post_id = ? AND user_id = ?", postID, userID).Count(&count)
return count > 0 return count > 0
@@ -382,7 +427,7 @@ func (r *PostRepository) IsFavorited(postID, userID string) bool {
// IsFavoritedBatch 批量检查是否收藏(解决 N+1 问题) // IsFavoritedBatch 批量检查是否收藏(解决 N+1 问题)
// 返回 map[postID]bool // 返回 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) result := make(map[string]bool)
if len(postIDs) == 0 || userID == "" { if len(postIDs) == 0 || userID == "" {
return result return result
@@ -408,7 +453,7 @@ func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[s
} }
// IncrementViews 增加帖子观看量 // IncrementViews 增加帖子观看量
func (r *PostRepository) IncrementViews(postID string) error { func (r *postRepository) IncrementViews(postID string) error {
return r.db.Model(&model.Post{}).Where("id = ?", postID). return r.db.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]interface{}{ UpdateColumns(map[string]interface{}{
"views_count": gorm.Expr("views_count + 1"), "views_count": gorm.Expr("views_count + 1"),
@@ -417,7 +462,7 @@ func (r *PostRepository) IncrementViews(postID string) error {
} }
// IncrementShares 已发布帖子分享次数 +1返回新的 shares_count非已发布或不存在返回 gorm.ErrRecordNotFound // 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 var newCount int
err := r.db.Transaction(func(tx *gorm.DB) error { err := r.db.Transaction(func(tx *gorm.DB) error {
res := tx.Model(&model.Post{}). res := tx.Model(&model.Post{}).
@@ -443,7 +488,7 @@ func (r *PostRepository) IncrementShares(postID string) (int, error) {
} }
// Search 搜索帖子 // 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 posts []*model.Post
var total int64 var total int64
@@ -472,7 +517,7 @@ func (r *PostRepository) Search(keyword string, page, pageSize int) ([]*model.Po
} }
// GetFollowingPosts 获取关注用户的帖子 // 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 posts []*model.Post
var total int64 var total int64
@@ -493,7 +538,7 @@ func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([
} }
// GetHotPosts 热门榜降级Redis 未就绪时按最新发布排序 // 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 posts []*model.Post
var total int64 var total int64
@@ -520,7 +565,7 @@ type PostHotSnapshot struct {
} }
// ListPublishedPostIDsSince 按发布时间倒序取 ID热门候选池避免全表扫快照 // 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 { if limit <= 0 {
return nil, nil return nil, nil
} }
@@ -547,7 +592,7 @@ func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) (
} }
// ListPinnedPublishedPostIDs 已发布且置顶的帖子 ID越新发布的置顶越靠前 // ListPinnedPublishedPostIDs 已发布且置顶的帖子 ID越新发布的置顶越靠前
func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) { func (r *postRepository) ListPinnedPublishedPostIDs() ([]string, error) {
type row struct { type row struct {
ID string `gorm:"column:id"` ID string `gorm:"column:id"`
} }
@@ -570,7 +615,7 @@ func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) {
} }
// GetPostHotSnapshotsByIDs 仅拉取候选 ID 对应的热度字段(分批 IN 查询) // GetPostHotSnapshotsByIDs 仅拉取候选 ID 对应的热度字段(分批 IN 查询)
func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) { func (r *postRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) {
if len(ids) == 0 { if len(ids) == 0 {
return nil, nil return nil, nil
} }
@@ -596,7 +641,7 @@ func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapsh
} }
// GetByIDs 根据ID列表获取帖子保持传入顺序 // GetByIDs 根据ID列表获取帖子保持传入顺序
func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) { func (r *postRepository) GetByIDs(ids []string) ([]*model.Post, error) {
if len(ids) == 0 { if len(ids) == 0 {
return []*model.Post{}, nil return []*model.Post{}, nil
} }
@@ -628,7 +673,7 @@ func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) {
// ========== Context-aware methods for transaction support ========== // ========== Context-aware methods for transaction support ==========
// CreateWithContext 创建帖子(支持事务) // 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) db := r.getDB(ctx)
// 如果 context 中有事务,直接使用 // 如果 context 中有事务,直接使用
@@ -643,7 +688,7 @@ func (r *PostRepository) CreateWithContext(ctx context.Context, post *model.Post
} }
// createWithTx 使用指定事务创建帖子 // 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 { if err := tx.Create(post).Error; err != nil {
return err return err
@@ -665,7 +710,7 @@ func (r *PostRepository) createWithTx(tx *gorm.DB, post *model.Post, images []st
} }
// GetByIDWithContext 根据ID获取帖子支持事务 // 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 var post model.Post
err := r.getDB(ctx).WithContext(ctx).Preload("User").Preload("Images").First(&post, "id = ?", id).Error err := r.getDB(ctx).WithContext(ctx).Preload("User").Preload("Images").First(&post, "id = ?", id).Error
if err != nil { if err != nil {
@@ -675,7 +720,7 @@ func (r *PostRepository) GetByIDWithContext(ctx context.Context, id string) (*mo
} }
// UpdateWithContext 更新帖子(支持事务) // 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() now := time.Now()
post.UpdatedAt = now post.UpdatedAt = now
post.ContentEditedAt = &now post.ContentEditedAt = &now
@@ -683,7 +728,7 @@ func (r *PostRepository) UpdateWithContext(ctx context.Context, post *model.Post
} }
// DeleteWithContext 删除帖子(支持事务) // 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) db := r.getDB(ctx)
// 如果 context 中有事务,直接使用 // 如果 context 中有事务,直接使用
@@ -698,7 +743,7 @@ func (r *PostRepository) DeleteWithContext(ctx context.Context, id string) error
} }
// deleteWithTx 使用指定事务删除帖子 // 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 { if err := tx.Where("post_id = ?", id).Delete(&model.PostImage{}).Error; err != nil {
return err return err
@@ -742,7 +787,7 @@ type AdminPostListQuery struct {
} }
// AdminList 管理端分页获取帖子列表(支持多条件筛选) // 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 posts []*model.Post
var total int64 var total int64
@@ -793,7 +838,7 @@ func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int
} }
// GetByIDForAdmin 管理端根据ID获取帖子包含所有状态 // GetByIDForAdmin 管理端根据ID获取帖子包含所有状态
func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) { func (r *postRepository) GetByIDForAdmin(id string) (*model.Post, error) {
var post model.Post var post model.Post
err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error
if err != nil { if err != nil {
@@ -803,7 +848,7 @@ func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) {
} }
// BatchDelete 批量删除帖子(使用单次事务批量删除,避免 N+1 问题) // BatchDelete 批量删除帖子(使用单次事务批量删除,避免 N+1 问题)
func (r *PostRepository) BatchDelete(ids []string) ([]string, error) { func (r *postRepository) BatchDelete(ids []string) ([]string, error) {
if len(ids) == 0 { if len(ids) == 0 {
return nil, nil return nil, nil
} }
@@ -855,7 +900,7 @@ func (r *PostRepository) BatchDelete(ids []string) ([]string, error) {
} }
// BatchUpdateStatus 批量更新帖子审核状态(使用单条 SQL 批量更新,避免 N+1 问题) // 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 { if len(ids) == 0 {
return nil, nil return nil, nil
} }
@@ -900,7 +945,7 @@ func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus
} }
// UpdatePinStatus 更新帖子置顶状态 // UpdatePinStatus 更新帖子置顶状态
func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error { func (r *postRepository) UpdatePinStatus(postID string, isPinned bool) error {
// 置顶状态属于管理操作不应影响帖子内容更新时间updated_at // 置顶状态属于管理操作不应影响帖子内容更新时间updated_at
return r.db.Model(&model.Post{}).Where("id = ?", postID). return r.db.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{ UpdateColumns(map[string]any{
@@ -910,7 +955,7 @@ func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error {
} }
// UpdateFeatureStatus 更新帖子加精状态 // UpdateFeatureStatus 更新帖子加精状态
func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) error { func (r *postRepository) UpdateFeatureStatus(postID string, isFeatured bool) error {
// 加精状态属于管理操作不应影响帖子内容更新时间updated_at // 加精状态属于管理操作不应影响帖子内容更新时间updated_at
return r.db.Model(&model.Post{}).Where("id = ?", postID). return r.db.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{ UpdateColumns(map[string]any{
@@ -923,7 +968,7 @@ func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) err
// GetPostsByCursor 游标分页获取帖子列表 // GetPostsByCursor 游标分页获取帖子列表
// includePending=true 时,仅在指定 userID 下额外返回 pending用于作者查看自己待审核帖子 // 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) db := r.getDB(ctx).WithContext(ctx)
// 构建基础查询 // 构建基础查询
@@ -993,7 +1038,7 @@ func (r *PostRepository) GetPostsByCursor(ctx context.Context, userID string, in
} }
// SearchPostsByCursor 游标分页搜索帖子 // 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) db := r.getDB(ctx).WithContext(ctx)
query := db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished) query := db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished)
@@ -1059,7 +1104,7 @@ func (r *PostRepository) SearchPostsByCursor(ctx context.Context, keyword string
} }
// GetUserPostsByCursor 游标分页获取用户帖子 // 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) db := r.getDB(ctx).WithContext(ctx)
query := db.Model(&model.Post{}).Where("user_id = ?", userID) query := db.Model(&model.Post{}).Where("user_id = ?", userID)
@@ -1119,7 +1164,7 @@ func (r *PostRepository) GetUserPostsByCursor(ctx context.Context, userID string
} }
// GetFollowingPostsByCursor 游标分页获取关注用户的帖子 // 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) db := r.getDB(ctx).WithContext(ctx)
// 子查询获取当前用户关注的所有用户ID // 子查询获取当前用户关注的所有用户ID
@@ -1175,7 +1220,7 @@ func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID s
} }
// GetHotPostsByCursor 游标分页获取热门帖子 // 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) db := r.getDB(ctx).WithContext(ctx)
// 热门游标降级:与 GetHotPosts 一致,按最新发布排序 // 热门游标降级:与 GetHotPosts 一致,按最新发布排序

View File

@@ -8,23 +8,41 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// PushRecordRepository 推送记录仓储 // PushRecordRepository 推送记录仓储接口
type PushRecordRepository struct { 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 db *gorm.DB
} }
// NewPushRecordRepository 创建推送记录仓储 // NewPushRecordRepository 创建推送记录仓储
func NewPushRecordRepository(db *gorm.DB) *PushRecordRepository { func NewPushRecordRepository(db *gorm.DB) PushRecordRepository {
return &PushRecordRepository{db: db} return &pushRecordRepository{db: db}
} }
// Create 创建推送记录 // Create 创建推送记录
func (r *PushRecordRepository) Create(record *model.PushRecord) error { func (r *pushRecordRepository) Create(record *model.PushRecord) error {
return r.db.Create(record).Error return r.db.Create(record).Error
} }
// GetByID 根据ID获取推送记录 // GetByID 根据ID获取推送记录
func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) { func (r *pushRecordRepository) GetByID(id int64) (*model.PushRecord, error) {
var record model.PushRecord var record model.PushRecord
err := r.db.First(&record, "id = ?", id).Error err := r.db.First(&record, "id = ?", id).Error
if err != nil { if err != nil {
@@ -34,12 +52,12 @@ func (r *PushRecordRepository) GetByID(id int64) (*model.PushRecord, error) {
} }
// Update 更新推送记录 // Update 更新推送记录
func (r *PushRecordRepository) Update(record *model.PushRecord) error { func (r *pushRecordRepository) Update(record *model.PushRecord) error {
return r.db.Save(record).Error return r.db.Save(record).Error
} }
// GetPendingPushes 获取待推送记录 // GetPendingPushes 获取待推送记录
func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) { func (r *pushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord, error) {
var records []*model.PushRecord var records []*model.PushRecord
err := r.db.Where("push_status = ?", model.PushStatusPending). err := r.db.Where("push_status = ?", model.PushStatusPending).
Where("expired_at IS NULL OR expired_at > ?", time.Now()). Where("expired_at IS NULL OR expired_at > ?", time.Now()).
@@ -51,7 +69,7 @@ func (r *PushRecordRepository) GetPendingPushes(limit int) ([]*model.PushRecord,
// GetByUserID 根据用户ID获取推送记录 // GetByUserID 根据用户ID获取推送记录
// userID 参数为 string 类型UUID格式与JWT中user_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 var records []*model.PushRecord
err := r.db.Where("user_id = ?", userID). err := r.db.Where("user_id = ?", userID).
Order("created_at DESC"). Order("created_at DESC").
@@ -62,7 +80,7 @@ func (r *PushRecordRepository) GetByUserID(userID string, limit, offset int) ([]
} }
// GetByMessageID 根据消息ID获取推送记录 // GetByMessageID 根据消息ID获取推送记录
func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) { func (r *pushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRecord, error) {
var records []*model.PushRecord var records []*model.PushRecord
err := r.db.Where("message_id = ?", messageID). err := r.db.Where("message_id = ?", messageID).
Order("created_at DESC"). Order("created_at DESC").
@@ -71,7 +89,7 @@ func (r *PushRecordRepository) GetByMessageID(messageID int64) ([]*model.PushRec
} }
// GetFailedPushesForRetry 获取失败待重试的推送 // GetFailedPushesForRetry 获取失败待重试的推送
func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) { func (r *pushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.PushRecord, error) {
var records []*model.PushRecord var records []*model.PushRecord
err := r.db.Where("push_status = ?", model.PushStatusFailed). err := r.db.Where("push_status = ?", model.PushStatusFailed).
Where("retry_count < max_retry"). Where("retry_count < max_retry").
@@ -83,7 +101,7 @@ func (r *PushRecordRepository) GetFailedPushesForRetry(limit int) ([]*model.Push
} }
// BatchCreate 批量创建推送记录 // BatchCreate 批量创建推送记录
func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error { func (r *pushRecordRepository) BatchCreate(records []*model.PushRecord) error {
if len(records) == 0 { if len(records) == 0 {
return nil return nil
} }
@@ -91,7 +109,7 @@ func (r *PushRecordRepository) BatchCreate(records []*model.PushRecord) error {
} }
// BatchUpdateStatus 批量更新推送状态 // 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 { if len(ids) == 0 {
return nil return nil
} }
@@ -107,7 +125,7 @@ func (r *PushRecordRepository) BatchUpdateStatus(ids []int64, status model.PushS
} }
// UpdateStatus 更新单条记录状态 // 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{}{ updates := map[string]interface{}{
"push_status": status, "push_status": status,
} }
@@ -120,7 +138,7 @@ func (r *PushRecordRepository) UpdateStatus(id int64, status model.PushStatus) e
} }
// MarkAsFailed 标记为失败 // 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{}). return r.db.Model(&model.PushRecord{}).
Where("id = ?", id). Where("id = ?", id).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
@@ -131,7 +149,7 @@ func (r *PushRecordRepository) MarkAsFailed(id int64, errMsg string) error {
} }
// MarkAsDelivered 标记为已送达 // MarkAsDelivered 标记为已送达
func (r *PushRecordRepository) MarkAsDelivered(id int64) error { func (r *pushRecordRepository) MarkAsDelivered(id int64) error {
return r.db.Model(&model.PushRecord{}). return r.db.Model(&model.PushRecord{}).
Where("id = ?", id). Where("id = ?", id).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
@@ -141,13 +159,13 @@ func (r *PushRecordRepository) MarkAsDelivered(id int64) error {
} }
// DeleteExpiredRecords 删除过期的推送记录(软删除) // 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()). return r.db.Where("expired_at IS NOT NULL AND expired_at < ?", time.Now()).
Delete(&model.PushRecord{}).Error Delete(&model.PushRecord{}).Error
} }
// GetStatsByUserID 获取用户推送统计 // 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 { type statusCount struct {
Status model.PushStatus Status model.PushStatus
Count int64 Count int64

View File

@@ -10,23 +10,38 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// SystemNotificationRepository 系统通知仓储 // SystemNotificationRepository 系统通知仓储接口
type SystemNotificationRepository struct { 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 db *gorm.DB
} }
// NewSystemNotificationRepository 创建系统通知仓储 // NewSystemNotificationRepository 创建系统通知仓储
func NewSystemNotificationRepository(db *gorm.DB) *SystemNotificationRepository { func NewSystemNotificationRepository(db *gorm.DB) SystemNotificationRepository {
return &SystemNotificationRepository{db: db} return &systemNotificationRepository{db: db}
} }
// Create 创建系统通知 // Create 创建系统通知
func (r *SystemNotificationRepository) Create(notification *model.SystemNotification) error { func (r *systemNotificationRepository) Create(notification *model.SystemNotification) error {
return r.db.Create(notification).Error return r.db.Create(notification).Error
} }
// GetByID 根据ID获取通知 // GetByID 根据ID获取通知
func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) { func (r *systemNotificationRepository) GetByID(id int64) (*model.SystemNotification, error) {
var notification model.SystemNotification var notification model.SystemNotification
err := r.db.First(&notification, "id = ?", id).Error err := r.db.First(&notification, "id = ?", id).Error
if err != nil { if err != nil {
@@ -36,7 +51,7 @@ func (r *SystemNotificationRepository) GetByID(id int64) (*model.SystemNotificat
} }
// GetByReceiverID 获取用户的通知列表 // 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 notifications []*model.SystemNotification
var total int64 var total int64
@@ -53,7 +68,7 @@ func (r *SystemNotificationRepository) GetByReceiverID(receiverID string, page,
} }
// GetUnreadByReceiverID 获取用户的未读通知列表 // 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 var notifications []*model.SystemNotification
err := r.db.Where("receiver_id = ? AND is_read = ?", receiverID, false). err := r.db.Where("receiver_id = ? AND is_read = ?", receiverID, false).
Order("created_at DESC"). Order("created_at DESC").
@@ -63,7 +78,7 @@ func (r *SystemNotificationRepository) GetUnreadByReceiverID(receiverID string,
} }
// GetUnreadCount 获取用户未读通知数量 // GetUnreadCount 获取用户未读通知数量
func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) { func (r *systemNotificationRepository) GetUnreadCount(receiverID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.SystemNotification{}). err := r.db.Model(&model.SystemNotification{}).
Where("receiver_id = ? AND is_read = ?", receiverID, false). Where("receiver_id = ? AND is_read = ?", receiverID, false).
@@ -72,7 +87,7 @@ func (r *SystemNotificationRepository) GetUnreadCount(receiverID string) (int64,
} }
// MarkAsRead 标记单条通知为已读 // MarkAsRead 标记单条通知为已读
func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) error { func (r *systemNotificationRepository) MarkAsRead(id int64, receiverID string) error {
now := model.SystemNotification{}.UpdatedAt now := model.SystemNotification{}.UpdatedAt
return r.db.Model(&model.SystemNotification{}). return r.db.Model(&model.SystemNotification{}).
Where("id = ? AND receiver_id = ?", id, receiverID). Where("id = ? AND receiver_id = ?", id, receiverID).
@@ -83,7 +98,7 @@ func (r *SystemNotificationRepository) MarkAsRead(id int64, receiverID string) e
} }
// MarkAllAsRead 标记用户所有通知为已读 // MarkAllAsRead 标记用户所有通知为已读
func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error { func (r *systemNotificationRepository) MarkAllAsRead(receiverID string) error {
now := model.SystemNotification{}.UpdatedAt now := model.SystemNotification{}.UpdatedAt
return r.db.Model(&model.SystemNotification{}). return r.db.Model(&model.SystemNotification{}).
Where("receiver_id = ? AND is_read = ?", receiverID, false). Where("receiver_id = ? AND is_read = ?", receiverID, false).
@@ -94,13 +109,13 @@ func (r *SystemNotificationRepository) MarkAllAsRead(receiverID string) error {
} }
// Delete 删除通知(软删除) // 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). return r.db.Where("id = ? AND receiver_id = ?", id, receiverID).
Delete(&model.SystemNotification{}).Error Delete(&model.SystemNotification{}).Error
} }
// GetByType 获取用户指定类型的通知 // 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 notifications []*model.SystemNotification
var total int64 var total int64
@@ -119,7 +134,7 @@ func (r *SystemNotificationRepository) GetByType(receiverID string, notifyType m
// GetByReceiverIDCursor 游标分页获取用户的通知列表 // GetByReceiverIDCursor 游标分页获取用户的通知列表
// 使用统一的 cursor 包进行游标分页 // 使用统一的 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) query := r.db.WithContext(ctx).Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID)
@@ -165,7 +180,7 @@ func (r *SystemNotificationRepository) GetByReceiverIDCursor(ctx context.Context
// GetByReceiverIDCursorLegacy 游标分页获取用户的通知列表(旧版兼容接口) // GetByReceiverIDCursorLegacy 游标分页获取用户的通知列表(旧版兼容接口)
// 已废弃:请使用 GetByReceiverIDCursor // 已废弃:请使用 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) query := r.db.Model(&model.SystemNotification{}).Where("receiver_id = ?", receiverID)

View File

@@ -36,21 +36,36 @@ type activityCache interface {
Expire(ctx context.Context, key string, ttl time.Duration) error Expire(ctx context.Context, key string, ttl time.Duration) error
} }
// UserActivityRepository 用户活跃数据仓储 // UserActivityRepository 用户活跃数据仓储接口
type UserActivityRepository struct { 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 db *gorm.DB
cache activityCache cache activityCache
} }
// NewUserActivityRepository 创建用户活跃数据仓储 // NewUserActivityRepository 创建用户活跃数据仓储
func NewUserActivityRepository(db *gorm.DB, cache activityCache) *UserActivityRepository { func NewUserActivityRepository(db *gorm.DB, cache activityCache) UserActivityRepository {
return &UserActivityRepository{db: db, cache: cache} return &userActivityRepository{db: db, cache: cache}
} }
// ==================== 数据库操作方法 ==================== // ==================== 数据库操作方法 ====================
// RecordActivity 记录用户活跃(写入数据库) // 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{ log := &model.UserActiveLog{
UserID: userID, UserID: userID,
ActiveDate: time.Now(), ActiveDate: time.Now(),
@@ -62,7 +77,7 @@ func (r *UserActivityRepository) RecordActivity(ctx context.Context, userID, log
} }
// GetDAU 获取指定日期的日活用户数(从数据库) // 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 var count int64
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
endOfDay := startOfDay.Add(24 * time.Hour) endOfDay := startOfDay.Add(24 * time.Hour)
@@ -77,7 +92,7 @@ func (r *UserActivityRepository) GetDAU(ctx context.Context, date time.Time) (in
} }
// GetActiveUserIDs 获取指定日期的活跃用户ID列表 // 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 var userIDs []string
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
endOfDay := startOfDay.Add(24 * time.Hour) endOfDay := startOfDay.Add(24 * time.Hour)
@@ -92,7 +107,7 @@ func (r *UserActivityRepository) GetActiveUserIDs(ctx context.Context, date time
} }
// GetUserActivityCount 获取用户在指定日期范围内的活跃天数 // 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 var count int64
startOfDay := time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, startDate.Location()) 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()) 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 保存统计数据快照 // 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). return r.db.WithContext(ctx).
Clauses(clause.OnConflict{ Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "stat_type"}, {Name: "stat_date"}}, Columns: []clause.Column{{Name: "stat_type"}, {Name: "stat_date"}},
@@ -117,7 +132,7 @@ func (r *UserActivityRepository) SaveStat(ctx context.Context, stat *model.UserA
} }
// GetStat 获取统计数据快照 // 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 var stat model.UserActivityStat
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) 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 // RecordActivityToRedis 记录用户活跃到 Redis Sorted Set
// score 为时间戳member 为用户ID // 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() now := time.Now()
score := float64(now.Unix()) score := float64(now.Unix())
@@ -164,7 +179,7 @@ func (r *UserActivityRepository) RecordActivityToRedis(ctx context.Context, user
} }
// GetDAUFromRedis 从 Redis 获取日活用户数 // 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) key := formatDAUKey(date)
count, err := r.cache.ZCard(ctx, key) count, err := r.cache.ZCard(ctx, key)
if err != nil { if err != nil {
@@ -174,7 +189,7 @@ func (r *UserActivityRepository) GetDAUFromRedis(ctx context.Context, date time.
} }
// GetWAUFromRedis 从 Redis 获取周活用户数 // 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) key := formatWAUKey(year, week)
count, err := r.cache.ZCard(ctx, key) count, err := r.cache.ZCard(ctx, key)
if err != nil { if err != nil {
@@ -184,7 +199,7 @@ func (r *UserActivityRepository) GetWAUFromRedis(ctx context.Context, year int,
} }
// GetMAUFromRedis 从 Redis 获取月活用户数 // 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) key := formatMAUKey(year, month)
count, err := r.cache.ZCard(ctx, key) count, err := r.cache.ZCard(ctx, key)
if err != nil { if err != nil {
@@ -194,7 +209,7 @@ func (r *UserActivityRepository) GetMAUFromRedis(ctx context.Context, year int,
} }
// GetDAUUserIDsFromRedis 获取日活用户ID列表 // 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) key := formatDAUKey(date)
// 获取所有成员(从最小分数到最大分数) // 获取所有成员(从最小分数到最大分数)
members, err := r.cache.ZRangeByScore(ctx, key, "-inf", "+inf", 0, -1) members, err := r.cache.ZRangeByScore(ctx, key, "-inf", "+inf", 0, -1)

View File

@@ -8,23 +8,60 @@ import (
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// UserRepository 用户仓储 // UserRepository 用户仓储接口
type UserRepository struct { 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 db *gorm.DB
} }
// NewUserRepository 创建用户仓储 // NewUserRepository 创建用户仓储
func NewUserRepository(db *gorm.DB) *UserRepository { func NewUserRepository(db *gorm.DB) UserRepository {
return &UserRepository{db: db} return &userRepository{db: db}
} }
// Create 创建用户 // Create 创建用户
func (r *UserRepository) Create(user *model.User) error { func (r *userRepository) Create(user *model.User) error {
return r.db.Create(user).Error return r.db.Create(user).Error
} }
// GetByID 根据ID获取用户 // GetByID 根据ID获取用户
func (r *UserRepository) GetByID(id string) (*model.User, error) { func (r *userRepository) GetByID(id string) (*model.User, error) {
var user model.User var user model.User
err := r.db.First(&user, "id = ?", id).Error err := r.db.First(&user, "id = ?", id).Error
if err != nil { if err != nil {
@@ -34,7 +71,7 @@ func (r *UserRepository) GetByID(id string) (*model.User, error) {
} }
// GetByUsername 根据用户名获取用户 // GetByUsername 根据用户名获取用户
func (r *UserRepository) GetByUsername(username string) (*model.User, error) { func (r *userRepository) GetByUsername(username string) (*model.User, error) {
var user model.User var user model.User
err := r.db.First(&user, "username = ?", username).Error err := r.db.First(&user, "username = ?", username).Error
if err != nil { if err != nil {
@@ -44,7 +81,7 @@ func (r *UserRepository) GetByUsername(username string) (*model.User, error) {
} }
// GetByEmail 根据邮箱获取用户 // GetByEmail 根据邮箱获取用户
func (r *UserRepository) GetByEmail(email string) (*model.User, error) { func (r *userRepository) GetByEmail(email string) (*model.User, error) {
var user model.User var user model.User
err := r.db.First(&user, "email = ?", email).Error err := r.db.First(&user, "email = ?", email).Error
if err != nil { if err != nil {
@@ -54,7 +91,7 @@ func (r *UserRepository) GetByEmail(email string) (*model.User, error) {
} }
// GetByPhone 根据手机号获取用户 // GetByPhone 根据手机号获取用户
func (r *UserRepository) GetByPhone(phone string) (*model.User, error) { func (r *userRepository) GetByPhone(phone string) (*model.User, error) {
var user model.User var user model.User
err := r.db.First(&user, "phone = ?", phone).Error err := r.db.First(&user, "phone = ?", phone).Error
if err != nil { if err != nil {
@@ -64,17 +101,17 @@ func (r *UserRepository) GetByPhone(phone string) (*model.User, error) {
} }
// Update 更新用户 // Update 更新用户
func (r *UserRepository) Update(user *model.User) error { func (r *userRepository) Update(user *model.User) error {
return r.db.Save(user).Error return r.db.Save(user).Error
} }
// Delete 删除用户 // Delete 删除用户
func (r *UserRepository) Delete(id string) error { func (r *userRepository) Delete(id string) error {
return r.db.Delete(&model.User{}, "id = ?", id).Error return r.db.Delete(&model.User{}, "id = ?", id).Error
} }
// List 分页获取用户列表 // 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 users []*model.User
var total int64 var total int64
@@ -87,7 +124,7 @@ func (r *UserRepository) List(page, pageSize int) ([]*model.User, int64, error)
} }
// GetFollowers 获取用户粉丝 // 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 users []*model.User
var total int64 var total int64
@@ -104,7 +141,7 @@ func (r *UserRepository) GetFollowers(userID string, page, pageSize int) ([]*mod
} }
// GetFollowing 获取用户关注 // 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 users []*model.User
var total int64 var total int64
@@ -121,17 +158,17 @@ func (r *UserRepository) GetFollowing(userID string, page, pageSize int) ([]*mod
} }
// CreateFollow 创建关注关系 // CreateFollow 创建关注关系
func (r *UserRepository) CreateFollow(follow *model.Follow) error { func (r *userRepository) CreateFollow(follow *model.Follow) error {
return r.db.Create(follow).Error return r.db.Create(follow).Error
} }
// DeleteFollow 删除关注关系 // 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 return r.db.Where("follower_id = ? AND following_id = ?", followerID, followingID).Delete(&model.Follow{}).Error
} }
// IsFollowing 检查是否关注了某用户 // IsFollowing 检查是否关注了某用户
func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, error) { func (r *userRepository) IsFollowing(followerID, followingID string) (bool, error) {
var count int64 var count int64
err := r.db.Model(&model.Follow{}).Where("follower_id = ? AND following_id = ?", followerID, followingID).Count(&count).Error err := r.db.Model(&model.Follow{}).Where("follower_id = ? AND following_id = ?", followerID, followingID).Count(&count).Error
if err != nil { if err != nil {
@@ -142,7 +179,7 @@ func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, erro
// IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题) // IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题)
// 返回 map[targetUserID]bool // 返回 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) result := make(map[string]bool)
if len(targetUserIDs) == 0 || followerID == "" { if len(targetUserIDs) == 0 || followerID == "" {
return result, nil return result, nil
@@ -171,31 +208,31 @@ func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []str
} }
// IncrementFollowersCount 增加用户粉丝数 // IncrementFollowersCount 增加用户粉丝数
func (r *UserRepository) IncrementFollowersCount(userID string) error { func (r *userRepository) IncrementFollowersCount(userID string) error {
return r.db.Model(&model.User{}).Where("id = ?", userID). return r.db.Model(&model.User{}).Where("id = ?", userID).
UpdateColumn("followers_count", gorm.Expr("followers_count + 1")).Error UpdateColumn("followers_count", gorm.Expr("followers_count + 1")).Error
} }
// DecrementFollowersCount 减少用户粉丝数 // 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). return r.db.Model(&model.User{}).Where("id = ? AND followers_count > 0", userID).
UpdateColumn("followers_count", gorm.Expr("followers_count - 1")).Error UpdateColumn("followers_count", gorm.Expr("followers_count - 1")).Error
} }
// IncrementFollowingCount 增加用户关注数 // IncrementFollowingCount 增加用户关注数
func (r *UserRepository) IncrementFollowingCount(userID string) error { func (r *userRepository) IncrementFollowingCount(userID string) error {
return r.db.Model(&model.User{}).Where("id = ?", userID). return r.db.Model(&model.User{}).Where("id = ?", userID).
UpdateColumn("following_count", gorm.Expr("following_count + 1")).Error UpdateColumn("following_count", gorm.Expr("following_count + 1")).Error
} }
// DecrementFollowingCount 减少用户关注数 // 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). return r.db.Model(&model.User{}).Where("id = ? AND following_count > 0", userID).
UpdateColumn("following_count", gorm.Expr("following_count - 1")).Error UpdateColumn("following_count", gorm.Expr("following_count - 1")).Error
} }
// RefreshFollowersCount 刷新用户粉丝数(通过实际计数) // RefreshFollowersCount 刷新用户粉丝数(通过实际计数)
func (r *UserRepository) RefreshFollowersCount(userID string) error { func (r *userRepository) RefreshFollowersCount(userID string) error {
var count int64 var count int64
err := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Count(&count).Error err := r.db.Model(&model.Follow{}).Where("following_id = ?", userID).Count(&count).Error
if err != nil { if err != nil {
@@ -206,7 +243,7 @@ func (r *UserRepository) RefreshFollowersCount(userID string) error {
} }
// GetPostsCount 获取用户帖子数(实时计算) // GetPostsCount 获取用户帖子数(实时计算)
func (r *UserRepository) GetPostsCount(userID string) (int64, error) { func (r *userRepository) GetPostsCount(userID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.Post{}). err := r.db.Model(&model.Post{}).
Where("user_id = ? AND status = ?", userID, model.PostStatusPublished). Where("user_id = ? AND status = ?", userID, model.PostStatusPublished).
@@ -216,7 +253,7 @@ func (r *UserRepository) GetPostsCount(userID string) (int64, error) {
// GetPostsCountBatch 批量获取用户帖子数(实时计算) // GetPostsCountBatch 批量获取用户帖子数(实时计算)
// 返回 map[userID]postsCount // 返回 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) result := make(map[string]int64)
if len(userIDs) == 0 { if len(userIDs) == 0 {
return result, nil return result, nil
@@ -251,7 +288,7 @@ func (r *UserRepository) GetPostsCountBatch(userIDs []string) (map[string]int64,
} }
// RefreshFollowingCount 刷新用户关注数(通过实际计数) // RefreshFollowingCount 刷新用户关注数(通过实际计数)
func (r *UserRepository) RefreshFollowingCount(userID string) error { func (r *userRepository) RefreshFollowingCount(userID string) error {
var count int64 var count int64
err := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Count(&count).Error err := r.db.Model(&model.Follow{}).Where("follower_id = ?", userID).Count(&count).Error
if err != nil { if err != nil {
@@ -262,7 +299,7 @@ func (r *UserRepository) RefreshFollowingCount(userID string) error {
} }
// IsBlocked 检查拉黑关系是否存在blocker -> blocked // IsBlocked 检查拉黑关系是否存在blocker -> blocked
func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) { func (r *userRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
var count int64 var count int64
err := r.db.Model(&model.UserBlock{}). err := r.db.Model(&model.UserBlock{}).
Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID). Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
@@ -275,7 +312,7 @@ func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
// IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题) // IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题)
// 返回 map[blockedUserID]bool // 返回 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) result := make(map[string]bool)
if len(blockedIDs) == 0 || blockerID == "" { if len(blockedIDs) == 0 || blockerID == "" {
return result, nil return result, nil
@@ -304,7 +341,7 @@ func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (
} }
// IsBlockedEitherDirection 检查是否任一方向存在拉黑 // IsBlockedEitherDirection 检查是否任一方向存在拉黑
func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) { func (r *userRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) {
var count int64 var count int64
err := r.db.Model(&model.UserBlock{}). err := r.db.Model(&model.UserBlock{}).
Where("(blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)", 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 拉黑用户并清理双向关注关系(事务) // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
block := &model.UserBlock{ block := &model.UserBlock{
BlockerID: blockerID, BlockerID: blockerID,
@@ -364,13 +401,13 @@ func (r *UserRepository) BlockUserAndCleanupRelations(blockerID, blockedID strin
} }
// UnblockUser 取消拉黑 // 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). return r.db.Where("blocker_id = ? AND blocked_id = ?", blockerID, blockedID).
Delete(&model.UserBlock{}).Error Delete(&model.UserBlock{}).Error
} }
// GetBlockedUsers 获取用户黑名单列表 // 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 users []*model.User
var total int64 var total int64
@@ -388,7 +425,7 @@ func (r *UserRepository) GetBlockedUsers(blockerID string, page, pageSize int) (
} }
// Search 搜索用户 // 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 users []*model.User
var total int64 var total int64
@@ -421,7 +458,7 @@ func (r *UserRepository) Search(keyword string, page, pageSize int) ([]*model.Us
// GetMutualFollowStatus 批量获取双向关注状态 // GetMutualFollowStatus 批量获取双向关注状态
// 返回 map[userID][isFollowing, isFollowingMe] // 返回 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) result := make(map[string][2]bool)
if len(targetUserIDs) == 0 { if len(targetUserIDs) == 0 {
@@ -465,7 +502,7 @@ func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserI
} }
// AdminList 管理端分页获取用户列表(支持关键词和状态筛选) // 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 users []*model.User
var total int64 var total int64
@@ -501,7 +538,7 @@ func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) (
} }
// GetCommentsCount 获取用户评论数 // GetCommentsCount 获取用户评论数
func (r *UserRepository) GetCommentsCount(userID string) (int64, error) { func (r *userRepository) GetCommentsCount(userID string) (int64, error) {
var count int64 var count int64
err := r.db.Model(&model.Comment{}). err := r.db.Model(&model.Comment{}).
Where("user_id = ?", userID). Where("user_id = ?", userID).
@@ -510,6 +547,6 @@ func (r *UserRepository) GetCommentsCount(userID string) (int64, error) {
} }
// UpdateStatus 更新用户状态 // 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 return r.db.Model(&model.User{}).Where("id = ?", userID).Update("status", status).Error
} }

View File

@@ -7,18 +7,29 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// VoteRepository 投票仓储 // VoteRepository 投票仓储接口
type VoteRepository struct { 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 db *gorm.DB
} }
// NewVoteRepository 创建投票仓储 // NewVoteRepository 创建投票仓储
func NewVoteRepository(db *gorm.DB) *VoteRepository { func NewVoteRepository(db *gorm.DB) VoteRepository {
return &VoteRepository{db: db} return &voteRepository{db: db}
} }
// CreateOptions 批量创建投票选项(使用批量插入,避免 N+1 问题) // 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 { if len(options) == 0 {
return nil return nil
} }
@@ -38,14 +49,14 @@ func (r *VoteRepository) CreateOptions(postID string, options []string) error {
} }
// GetOptionsByPostID 获取帖子的所有投票选项 // GetOptionsByPostID 获取帖子的所有投票选项
func (r *VoteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) { func (r *voteRepository) GetOptionsByPostID(postID string) ([]model.VoteOption, error) {
var options []model.VoteOption var options []model.VoteOption
err := r.db.Where("post_id = ?", postID).Order("sort_order ASC").Find(&options).Error err := r.db.Where("post_id = ?", postID).Order("sort_order ASC").Find(&options).Error
return options, err return options, err
} }
// Vote 用户投票 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 检查用户是否已投票 // 检查用户是否已投票
var existing model.UserVote var existing model.UserVote
@@ -83,7 +94,7 @@ func (r *VoteRepository) Vote(postID, userID, optionID string) error {
} }
// Unvote 取消投票 // 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 { return r.db.Transaction(func(tx *gorm.DB) error {
// 获取用户的投票记录 // 获取用户的投票记录
var userVote model.UserVote var userVote model.UserVote
@@ -112,7 +123,7 @@ func (r *VoteRepository) Unvote(postID, userID string) error {
} }
// GetUserVote 获取用户在指定帖子的投票 // 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 var userVote model.UserVote
err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error
if err != nil { if err != nil {
@@ -125,13 +136,13 @@ func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, er
} }
// UpdateOption 更新选项内容 // 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). return r.db.Model(&model.VoteOption{}).Where("id = ?", optionID).
Update("content", content).Error Update("content", content).Error
} }
// DeleteOptionsByPostID 删除帖子的所有投票选项 // DeleteOptionsByPostID 删除帖子的所有投票选项
func (r *VoteRepository) DeleteOptionsByPostID(postID string) error { func (r *voteRepository) DeleteOptionsByPostID(postID string) error {
return r.db.Transaction(func(tx *gorm.DB) error { return r.db.Transaction(func(tx *gorm.DB) error {
// 删除关联的用户投票记录 // 删除关联的用户投票记录
if err := tx.Where("post_id = ?", postID).Delete(&model.UserVote{}).Error; err != nil { if err := tx.Where("post_id = ?", postID).Delete(&model.UserVote{}).Error; err != nil {

View File

@@ -25,12 +25,12 @@ type AdminCommentService interface {
// adminCommentServiceImpl 管理端评论服务实现 // adminCommentServiceImpl 管理端评论服务实现
type adminCommentServiceImpl struct { type adminCommentServiceImpl struct {
commentRepo *repository.CommentRepository commentRepo repository.CommentRepository
postRepo *repository.PostRepository postRepo repository.PostRepository
} }
// NewAdminCommentService 创建管理端评论服务 // NewAdminCommentService 创建管理端评论服务
func NewAdminCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository) AdminCommentService { func NewAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository) AdminCommentService {
return &adminCommentServiceImpl{ return &adminCommentServiceImpl{
commentRepo: commentRepo, commentRepo: commentRepo,
postRepo: postRepo, postRepo: postRepo,

View File

@@ -28,21 +28,21 @@ type AdminDashboardService interface {
// adminDashboardServiceImpl 管理端仪表盘服务实现 // adminDashboardServiceImpl 管理端仪表盘服务实现
type adminDashboardServiceImpl struct { type adminDashboardServiceImpl struct {
db *gorm.DB db *gorm.DB
userRepo *repository.UserRepository userRepo repository.UserRepository
postRepo *repository.PostRepository postRepo repository.PostRepository
commentRepo *repository.CommentRepository commentRepo repository.CommentRepository
groupRepo repository.GroupRepository groupRepo repository.GroupRepository
activityRepo *repository.UserActivityRepository activityRepo repository.UserActivityRepository
} }
// NewAdminDashboardService 创建管理端仪表盘服务 // NewAdminDashboardService 创建管理端仪表盘服务
func NewAdminDashboardService( func NewAdminDashboardService(
db *gorm.DB, db *gorm.DB,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
commentRepo *repository.CommentRepository, commentRepo repository.CommentRepository,
groupRepo repository.GroupRepository, groupRepo repository.GroupRepository,
activityRepo *repository.UserActivityRepository, activityRepo repository.UserActivityRepository,
) AdminDashboardService { ) AdminDashboardService {
return &adminDashboardServiceImpl{ return &adminDashboardServiceImpl{
db: db, db: db,

View File

@@ -30,13 +30,13 @@ type AdminGroupService interface {
// adminGroupServiceImpl 管理端群组服务实现 // adminGroupServiceImpl 管理端群组服务实现
type adminGroupServiceImpl struct { type adminGroupServiceImpl struct {
groupRepo repository.GroupRepository groupRepo repository.GroupRepository
userRepo *repository.UserRepository userRepo repository.UserRepository
} }
// NewAdminGroupService 创建管理端群组服务 // NewAdminGroupService 创建管理端群组服务
func NewAdminGroupService( func NewAdminGroupService(
groupRepo repository.GroupRepository, groupRepo repository.GroupRepository,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
) AdminGroupService { ) AdminGroupService {
return &adminGroupServiceImpl{ return &adminGroupServiceImpl{
groupRepo: groupRepo, groupRepo: groupRepo,

View File

@@ -34,13 +34,13 @@ type AdminPostService interface {
// adminPostServiceImpl 管理端帖子服务实现 // adminPostServiceImpl 管理端帖子服务实现
type adminPostServiceImpl struct { type adminPostServiceImpl struct {
postRepo *repository.PostRepository postRepo repository.PostRepository
cache cache.Cache cache cache.Cache
logService *LogService logService *LogService
} }
// NewAdminPostService 创建管理端帖子服务 // NewAdminPostService 创建管理端帖子服务
func NewAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) AdminPostService { func NewAdminPostService(postRepo repository.PostRepository, cacheBackend cache.Cache) AdminPostService {
return &adminPostServiceImpl{ return &adminPostServiceImpl{
postRepo: postRepo, postRepo: postRepo,
cache: cacheBackend, cache: cacheBackend,

View File

@@ -20,11 +20,11 @@ type AdminUserService interface {
// adminUserServiceImpl 管理端用户服务实现 // adminUserServiceImpl 管理端用户服务实现
type adminUserServiceImpl struct { type adminUserServiceImpl struct {
userRepo *repository.UserRepository userRepo repository.UserRepository
} }
// NewAdminUserService 创建管理端用户服务 // NewAdminUserService 创建管理端用户服务
func NewAdminUserService(userRepo *repository.UserRepository) AdminUserService { func NewAdminUserService(userRepo repository.UserRepository) AdminUserService {
return &adminUserServiceImpl{ return &adminUserServiceImpl{
userRepo: userRepo, userRepo: userRepo,
} }

View File

@@ -11,7 +11,6 @@ import (
"carrot_bbs/internal/repository" "carrot_bbs/internal/repository"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm"
) )
// AsyncLogConfig 异步日志配置 // AsyncLogConfig 异步日志配置
@@ -31,7 +30,6 @@ type AsyncLogManager struct {
dataChangeChan chan *model.DataChangeLog dataChangeChan chan *model.DataChangeLog
cfg *AsyncLogConfig cfg *AsyncLogConfig
db *gorm.DB
wg sync.WaitGroup wg sync.WaitGroup
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@@ -42,10 +40,9 @@ type AsyncLogManager struct {
// NewAsyncLogManager 创建异步日志管理器 // NewAsyncLogManager 创建异步日志管理器
func NewAsyncLogManager( func NewAsyncLogManager(
db *gorm.DB, operationRepo repository.OperationLogRepository,
operationRepo *repository.OperationLogRepository, loginRepo repository.LoginLogRepository,
loginRepo *repository.LoginLogRepository, dataChangeRepo repository.DataChangeLogRepository,
dataChangeRepo *repository.DataChangeLogRepository,
logger *zap.Logger, logger *zap.Logger,
cfg *AsyncLogConfig, cfg *AsyncLogConfig,
) *AsyncLogManager { ) *AsyncLogManager {
@@ -56,7 +53,6 @@ func NewAsyncLogManager(
loginChan: make(chan *model.LoginLog, cfg.BufferSize), loginChan: make(chan *model.LoginLog, cfg.BufferSize),
dataChangeChan: make(chan *model.DataChangeLog, cfg.BufferSize), dataChangeChan: make(chan *model.DataChangeLog, cfg.BufferSize),
cfg: cfg, cfg: cfg,
db: db,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
logger: logger, logger: logger,
@@ -90,9 +86,9 @@ func NewAsyncLogManager(
// worker 处理协程 // worker 处理协程
func (m *AsyncLogManager) worker( func (m *AsyncLogManager) worker(
id int, id int,
operationRepo *repository.OperationLogRepository, operationRepo repository.OperationLogRepository,
loginRepo *repository.LoginLogRepository, loginRepo repository.LoginLogRepository,
dataChangeRepo *repository.DataChangeLogRepository, dataChangeRepo repository.DataChangeLogRepository,
) { ) {
opBatch := make([]*model.OperationLog, 0, m.cfg.BatchSize) opBatch := make([]*model.OperationLog, 0, m.cfg.BatchSize)
loginBatch := make([]*model.LoginLog, 0, m.cfg.BatchSize) loginBatch := make([]*model.LoginLog, 0, m.cfg.BatchSize)
@@ -164,9 +160,9 @@ func (m *AsyncLogManager) flushBatches(
ops []*model.OperationLog, ops []*model.OperationLog,
logins []*model.LoginLog, logins []*model.LoginLog,
changes []*model.DataChangeLog, changes []*model.DataChangeLog,
operationRepo *repository.OperationLogRepository, operationRepo repository.OperationLogRepository,
loginRepo *repository.LoginLogRepository, loginRepo repository.LoginLogRepository,
dataChangeRepo *repository.DataChangeLogRepository, dataChangeRepo repository.DataChangeLogRepository,
) { ) {
if len(ops) == 0 && len(logins) == 0 && len(changes) == 0 { if len(ops) == 0 && len(logins) == 0 && len(changes) == 0 {
return return
@@ -194,17 +190,17 @@ func (m *AsyncLogManager) flushBatches(
} }
// flushOperationLogs 刷新操作日志 // 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) return repo.BatchCreateOperationLogs(context.Background(), logs)
} }
// flushLoginLogs 刷新登录日志 // 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) return repo.BatchCreateLoginLogs(context.Background(), logs)
} }
// flushDataChangeLogs 刷新数据变更日志 // 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) return repo.BatchCreateDataChangeLogs(context.Background(), logs)
} }

View File

@@ -21,11 +21,11 @@ type ChannelService interface {
} }
type channelServiceImpl struct { type channelServiceImpl struct {
channelRepo *repository.ChannelRepository channelRepo repository.ChannelRepository
cache cache.Cache 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} return &channelServiceImpl{channelRepo: channelRepo, cache: c}
} }

View File

@@ -56,9 +56,8 @@ type ChatService interface {
// chatServiceImpl 聊天服务实现 // chatServiceImpl 聊天服务实现
type chatServiceImpl struct { type chatServiceImpl struct {
db *gorm.DB repo repository.MessageRepository
repo *repository.MessageRepository userRepo repository.UserRepository
userRepo *repository.UserRepository
sensitive SensitiveService sensitive SensitiveService
sseHub *sse.Hub sseHub *sse.Hub
@@ -69,9 +68,8 @@ type chatServiceImpl struct {
// NewChatService 创建聊天服务 // NewChatService 创建聊天服务
func NewChatService( func NewChatService(
db *gorm.DB, repo repository.MessageRepository,
repo *repository.MessageRepository, userRepo repository.UserRepository,
userRepo *repository.UserRepository,
sensitive SensitiveService, sensitive SensitiveService,
sseHub *sse.Hub, sseHub *sse.Hub,
cacheBackend cache.Cache, cacheBackend cache.Cache,
@@ -90,7 +88,6 @@ func NewChatService(
) )
return &chatServiceImpl{ return &chatServiceImpl{
db: db,
repo: repo, repo: repo,
userRepo: userRepo, userRepo: userRepo,
sensitive: sensitive, sensitive: sensitive,
@@ -538,13 +535,9 @@ func (s *chatServiceImpl) GetAllUnreadCount(ctx context.Context, userID string)
// RecallMessage 撤回消息2分钟内 // RecallMessage 撤回消息2分钟内
func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, userID string) error { func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, userID string) error {
// 获取消息 // 获取消息
var message model.Message message, err := s.repo.GetMessageByID(messageID)
err := s.db.First(&message, "id = ?", messageID).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("message not found")
return errors.New("message not found")
}
return fmt.Errorf("failed to get message: %w", err)
} }
// 验证是否是消息发送者 // 验证是否是消息发送者
@@ -563,10 +556,7 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u
} }
// 更新消息状态为已撤回,并清空原始消息内容,仅保留撤回占位 // 更新消息状态为已撤回,并清空原始消息内容,仅保留撤回占位
err = s.db.Model(&message).Updates(map[string]interface{}{ err = s.repo.RecallMessage(messageID, userID)
"status": model.MessageStatusRecalled,
"segments": model.MessageSegments{},
}).Error
if err != nil { if err != nil {
return fmt.Errorf("failed to recall message: %w", err) return fmt.Errorf("failed to recall message: %w", err)
} }
@@ -604,22 +594,15 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u
// DeleteMessage 删除消息(仅对自己可见) // DeleteMessage 删除消息(仅对自己可见)
func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, userID string) error { func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, userID string) error {
// 获取消息 // 获取消息
var message model.Message message, err := s.repo.GetMessageByID(messageID)
err := s.db.First(&message, "id = ?", messageID).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("message not found")
return errors.New("message not found")
}
return fmt.Errorf("failed to get message: %w", err)
} }
// 验证用户是否是会话参与者 // 验证用户是否是会话参与者
_, err = s.getParticipant(ctx, message.ConversationID, userID) _, err = s.getParticipant(ctx, message.ConversationID, userID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("no permission to delete this message")
return errors.New("no permission to delete this message")
}
return fmt.Errorf("failed to get participant: %w", err)
} }
// 对于删除消息,我们使用软删除,但需要确保只对当前用户隐藏 // 对于删除消息,我们使用软删除,但需要确保只对当前用户隐藏
@@ -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 { if err != nil {
return fmt.Errorf("failed to delete message: %w", err) return fmt.Errorf("failed to delete message: %w", err)
} }

View File

@@ -15,8 +15,8 @@ import (
) )
type CommentService struct { type CommentService struct {
commentRepo *repository.CommentRepository commentRepo repository.CommentRepository
postRepo *repository.PostRepository postRepo repository.PostRepository
systemMessageService SystemMessageService systemMessageService SystemMessageService
cache cache.Cache cache cache.Cache
postAIService *PostAIService postAIService *PostAIService
@@ -24,7 +24,7 @@ type CommentService struct {
hookManager *hook.Manager 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{ return &CommentService{
commentRepo: commentRepo, commentRepo: commentRepo,
postRepo: postRepo, postRepo: postRepo,

View File

@@ -106,25 +106,23 @@ type GroupMembersResult struct {
// groupService 群组服务实现 // groupService 群组服务实现
type groupService struct { type groupService struct {
db *gorm.DB
groupRepo repository.GroupRepository groupRepo repository.GroupRepository
userRepo *repository.UserRepository userRepo repository.UserRepository
messageRepo *repository.MessageRepository messageRepo repository.MessageRepository
requestRepo repository.GroupJoinRequestRepository requestRepo repository.GroupJoinRequestRepository
notifyRepo *repository.SystemNotificationRepository notifyRepo repository.SystemNotificationRepository
sseHub *sse.Hub sseHub *sse.Hub
cache cache.Cache cache cache.Cache
} }
// NewGroupService 创建群组服务 // 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{ return &groupService{
db: db,
groupRepo: groupRepo, groupRepo: groupRepo,
userRepo: userRepo, userRepo: userRepo,
messageRepo: messageRepo, messageRepo: messageRepo,
requestRepo: repository.NewGroupJoinRequestRepository(db), requestRepo: requestRepo,
notifyRepo: repository.NewSystemNotificationRepository(db), notifyRepo: notifyRepo,
sseHub: sseHub, sseHub: sseHub,
cache: cacheBackend, cache: cacheBackend,
} }
@@ -263,43 +261,17 @@ func (s *groupService) CreateGroup(ownerID string, name string, description stri
GroupID: &group.ID, GroupID: &group.ID,
} }
// 在事务中创建会话和参与者 // 收集所有参与者
err = s.db.Transaction(func(tx *gorm.DB) error { participants := make([]string, 0, len(memberIDs)+1)
// 创建会话 participants = append(participants, ownerID)
if err := tx.Create(conversation).Error; err != nil { for _, memberID := range memberIDs {
return err if memberID != ownerID {
participants = append(participants, memberID)
} }
}
// 添加群主为会话参与者 // 使用仓储创建会话参与者
ownerParticipant := model.ConversationParticipant{ if err := s.messageRepo.CreateConversationWithParticipants(conversation, participants); err != nil {
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 {
// 记录错误但不影响群组创建成功 // 记录错误但不影响群组创建成功
} }
@@ -418,30 +390,8 @@ func (s *groupService) TransferOwner(userID string, groupID string, newOwnerID s
return ErrNotGroupMember return ErrNotGroupMember
} }
// 在事务中更新 // 使用仓储的 TransferOwner 方法(在事务中更新
return s.db.Transaction(func(tx *gorm.DB) error { return s.groupRepo.TransferOwner(groupID, userID, newOwnerID)
// 更新群组的群主
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
})
} }
// GetUserGroups 获取用户加入的群组列表 // GetUserGroups 获取用户加入的群组列表
@@ -593,9 +543,7 @@ func (s *groupService) collectGroupReviewerIDs(groupID string, ownerID string, s
if ownerID != "" && ownerID != skipUserID { if ownerID != "" && ownerID != skipUserID {
reviewerSet[ownerID] = struct{}{} reviewerSet[ownerID] = struct{}{}
} }
if reviewers, err := s.groupRepo.GetAdmins(groupID); err == nil {
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 {
for _, reviewer := range reviewers { for _, reviewer := range reviewers {
if reviewer.UserID == "" || reviewer.UserID == skipUserID { if reviewer.UserID == "" || reviewer.UserID == skipUserID {
continue continue

View File

@@ -19,7 +19,7 @@ import (
// HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET仅 TopN及 top_idsRedis+本地缓存) // HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET仅 TopN及 top_idsRedis+本地缓存)
type HotRankWorker struct { type HotRankWorker struct {
cfg *config.Config cfg *config.Config
postRepo *repository.PostRepository postRepo repository.PostRepository
c cache.Cache c cache.Cache
mu sync.Mutex mu sync.Mutex
stopOnce sync.Once stopOnce sync.Once
@@ -28,7 +28,7 @@ type HotRankWorker struct {
} }
// NewHotRankWorker 创建热门榜重算 workercache 需为 Redis 实现才有意义) // NewHotRankWorker 创建热门榜重算 workercache 需为 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{ return &HotRankWorker{
cfg: cfg, cfg: cfg,
postRepo: postRepo, postRepo: postRepo,

View File

@@ -30,9 +30,9 @@ type LogCleanupService interface {
// logCleanupServiceImpl 日志清理服务实现 // logCleanupServiceImpl 日志清理服务实现
type logCleanupServiceImpl struct { type logCleanupServiceImpl struct {
operationRepo *repository.OperationLogRepository operationRepo repository.OperationLogRepository
loginRepo *repository.LoginLogRepository loginRepo repository.LoginLogRepository
dataChangeRepo *repository.DataChangeLogRepository dataChangeRepo repository.DataChangeLogRepository
cfg *LogCleanupConfig cfg *LogCleanupConfig
logger *zap.Logger logger *zap.Logger
ctx context.Context ctx context.Context
@@ -41,9 +41,9 @@ type logCleanupServiceImpl struct {
// NewLogCleanupService 创建日志清理服务 // NewLogCleanupService 创建日志清理服务
func NewLogCleanupService( func NewLogCleanupService(
operationRepo *repository.OperationLogRepository, operationRepo repository.OperationLogRepository,
loginRepo *repository.LoginLogRepository, loginRepo repository.LoginLogRepository,
dataChangeRepo *repository.DataChangeLogRepository, dataChangeRepo repository.DataChangeLogRepository,
cfg *LogCleanupConfig, cfg *LogCleanupConfig,
logger *zap.Logger, logger *zap.Logger,
) LogCleanupService { ) LogCleanupService {

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"time" "time"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"carrot_bbs/internal/repository" "carrot_bbs/internal/repository"
) )
@@ -11,7 +12,7 @@ import (
// OperationLogService 操作日志服务接口 // OperationLogService 操作日志服务接口
type OperationLogService interface { type OperationLogService interface {
RecordOperation(log *model.OperationLog) 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) 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) GetOperationLogsByTimeRange(ctx context.Context, startTime, endTime string, page, pageSize int) ([]*model.OperationLog, int64, error)
GetStatistics(ctx context.Context, startTime, endTime string) (*repository.OperationLogStatistics, error) GetStatistics(ctx context.Context, startTime, endTime string) (*repository.OperationLogStatistics, error)
@@ -20,13 +21,13 @@ type OperationLogService interface {
// operationLogServiceImpl 操作日志服务实现 // operationLogServiceImpl 操作日志服务实现
type operationLogServiceImpl struct { type operationLogServiceImpl struct {
asyncManager *AsyncLogManager asyncManager *AsyncLogManager
repo *repository.OperationLogRepository repo repository.OperationLogRepository
} }
// NewOperationLogService 创建操作日志服务 // NewOperationLogService 创建操作日志服务
func NewOperationLogService( func NewOperationLogService(
asyncManager *AsyncLogManager, asyncManager *AsyncLogManager,
repo *repository.OperationLogRepository, repo repository.OperationLogRepository,
) OperationLogService { ) OperationLogService {
return &operationLogServiceImpl{ return &operationLogServiceImpl{
asyncManager: asyncManager, asyncManager: asyncManager,
@@ -40,7 +41,7 @@ func (s *operationLogServiceImpl) RecordOperation(log *model.OperationLog) {
} }
// GetOperationLogs 获取操作日志列表 // 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) return s.repo.GetOperationLogs(ctx, filters, page, pageSize)
} }
@@ -62,7 +63,7 @@ func (s *operationLogServiceImpl) GetStatistics(ctx context.Context, startTime,
// LoginLogService 登录日志服务接口 // LoginLogService 登录日志服务接口
type LoginLogService interface { type LoginLogService interface {
RecordLogin(log *model.LoginLog) 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) GetLoginLogsByUser(ctx context.Context, userID string, page, pageSize int) ([]*model.LoginLog, int64, error)
GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow string) (int64, error) GetFailedLoginsByIP(ctx context.Context, ip string, timeWindow string) (int64, error)
GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error) GetRecentSuccessLogins(ctx context.Context, userID string, limit int) ([]*model.LoginLog, error)
@@ -72,13 +73,13 @@ type LoginLogService interface {
// loginLogServiceImpl 登录日志服务实现 // loginLogServiceImpl 登录日志服务实现
type loginLogServiceImpl struct { type loginLogServiceImpl struct {
asyncManager *AsyncLogManager asyncManager *AsyncLogManager
repo *repository.LoginLogRepository repo repository.LoginLogRepository
} }
// NewLoginLogService 创建登录日志服务 // NewLoginLogService 创建登录日志服务
func NewLoginLogService( func NewLoginLogService(
asyncManager *AsyncLogManager, asyncManager *AsyncLogManager,
repo *repository.LoginLogRepository, repo repository.LoginLogRepository,
) LoginLogService { ) LoginLogService {
return &loginLogServiceImpl{ return &loginLogServiceImpl{
asyncManager: asyncManager, asyncManager: asyncManager,
@@ -92,7 +93,7 @@ func (s *loginLogServiceImpl) RecordLogin(log *model.LoginLog) {
} }
// GetLoginLogs 获取登录日志列表 // 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) return s.repo.GetLoginLogs(ctx, filters, page, pageSize)
} }
@@ -123,7 +124,7 @@ func (s *loginLogServiceImpl) GetStatistics(ctx context.Context, startTime, endT
// DataChangeLogService 数据变更日志服务接口 // DataChangeLogService 数据变更日志服务接口
type DataChangeLogService interface { type DataChangeLogService interface {
RecordDataChange(log *model.DataChangeLog) 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) 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) GetDataChangeLogsByOperator(ctx context.Context, operatorID string, page, pageSize int) ([]*model.DataChangeLog, int64, error)
GetStatistics(ctx context.Context, startTime, endTime string) (*repository.DataChangeLogStatistics, error) GetStatistics(ctx context.Context, startTime, endTime string) (*repository.DataChangeLogStatistics, error)
@@ -132,13 +133,13 @@ type DataChangeLogService interface {
// dataChangeLogServiceImpl 数据变更日志服务实现 // dataChangeLogServiceImpl 数据变更日志服务实现
type dataChangeLogServiceImpl struct { type dataChangeLogServiceImpl struct {
asyncManager *AsyncLogManager asyncManager *AsyncLogManager
repo *repository.DataChangeLogRepository repo repository.DataChangeLogRepository
} }
// NewDataChangeLogService 创建数据变更日志服务 // NewDataChangeLogService 创建数据变更日志服务
func NewDataChangeLogService( func NewDataChangeLogService(
asyncManager *AsyncLogManager, asyncManager *AsyncLogManager,
repo *repository.DataChangeLogRepository, repo repository.DataChangeLogRepository,
) DataChangeLogService { ) DataChangeLogService {
return &dataChangeLogServiceImpl{ return &dataChangeLogServiceImpl{
asyncManager: asyncManager, asyncManager: asyncManager,
@@ -152,7 +153,7 @@ func (s *dataChangeLogServiceImpl) RecordDataChange(log *model.DataChangeLog) {
} }
// GetDataChangeLogs 获取数据变更日志列表 // 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) return s.repo.GetDataChangeLogs(ctx, filters, page, pageSize)
} }

View File

@@ -2,6 +2,8 @@ package service
import ( import (
"errors" "errors"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model" "carrot_bbs/internal/model"
"carrot_bbs/internal/repository" "carrot_bbs/internal/repository"
) )
@@ -21,7 +23,7 @@ type MaterialService interface {
DeleteSubject(id string) error 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) GetMaterialByID(id string) (*model.MaterialFile, error)
GetMaterialByIDWithSubject(id string) (*model.MaterialFile, error) GetMaterialByIDWithSubject(id string) (*model.MaterialFile, error)
GetMaterialsBySubject(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error) GetMaterialsBySubject(subjectID string, page, pageSize int) ([]*model.MaterialFile, int64, error)
@@ -36,14 +38,14 @@ type MaterialService interface {
// materialServiceImpl 学习资料服务实现 // materialServiceImpl 学习资料服务实现
type materialServiceImpl struct { type materialServiceImpl struct {
subjectRepo *repository.MaterialSubjectRepository subjectRepo repository.MaterialSubjectRepository
fileRepo *repository.MaterialFileRepository fileRepo repository.MaterialFileRepository
} }
// NewMaterialService 创建学习资料服务 // NewMaterialService 创建学习资料服务
func NewMaterialService( func NewMaterialService(
subjectRepo *repository.MaterialSubjectRepository, subjectRepo repository.MaterialSubjectRepository,
fileRepo *repository.MaterialFileRepository, fileRepo repository.MaterialFileRepository,
) MaterialService { ) MaterialService {
return &materialServiceImpl{ return &materialServiceImpl{
subjectRepo: subjectRepo, 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) return s.fileRepo.List(params)
} }

View File

@@ -10,7 +10,6 @@ import (
"carrot_bbs/internal/repository" "carrot_bbs/internal/repository"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm"
) )
// 缓存TTL常量 // 缓存TTL常量
@@ -25,10 +24,8 @@ const (
// MessageService 消息服务 // MessageService 消息服务
type MessageService struct { type MessageService struct {
db *gorm.DB
// 基础仓储 // 基础仓储
messageRepo *repository.MessageRepository messageRepo repository.MessageRepository
// 缓存相关字段 // 缓存相关字段
conversationCache *cache.ConversationCache conversationCache *cache.ConversationCache
@@ -40,7 +37,7 @@ type MessageService struct {
} }
// NewMessageService 创建消息服务 // 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) convRepoAdapter := cache.NewConversationRepositoryAdapter(messageRepo)
msgRepoAdapter := cache.NewMessageRepositoryAdapter(messageRepo) msgRepoAdapter := cache.NewMessageRepositoryAdapter(messageRepo)
@@ -54,7 +51,6 @@ func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository, c
) )
return &MessageService{ return &MessageService{
db: db,
messageRepo: messageRepo, messageRepo: messageRepo,
conversationCache: conversationCache, conversationCache: conversationCache,
baseCache: cacheBackend, baseCache: cacheBackend,
@@ -161,7 +157,7 @@ func (s *MessageService) GetConversations(ctx context.Context, userID string, pa
// 生成缓存键 // 生成缓存键
cacheKey := cache.ConversationListKey(userID, page, pageSize) cacheKey := cache.ConversationListKey(userID, page, pageSize)
result, err := cache.GetOrLoadTyped[*ConversationListResult]( result, err := cache.GetOrLoadTyped(
s.baseCache, s.baseCache,
cacheKey, cacheKey,
conversationTTL, conversationTTL,
@@ -252,7 +248,7 @@ func (s *MessageService) GetUnreadCount(ctx context.Context, conversationID stri
// 生成缓存键 // 生成缓存键
cacheKey := cache.UnreadDetailKey(userID, conversationID) cacheKey := cache.UnreadDetailKey(userID, conversationID)
return cache.GetOrLoadTyped[int64]( return cache.GetOrLoadTyped(
s.baseCache, s.baseCache,
cacheKey, cacheKey,
unreadTTL, unreadTTL,

View File

@@ -20,12 +20,12 @@ const (
// NotificationService 通知服务 // NotificationService 通知服务
type NotificationService struct { type NotificationService struct {
notificationRepo *repository.NotificationRepository notificationRepo repository.NotificationRepository
cache cache.Cache cache cache.Cache
} }
// NewNotificationService 创建通知服务 // NewNotificationService 创建通知服务
func NewNotificationService(notificationRepo *repository.NotificationRepository, cacheBackend cache.Cache) *NotificationService { func NewNotificationService(notificationRepo repository.NotificationRepository, cacheBackend cache.Cache) *NotificationService {
return &NotificationService{ return &NotificationService{
notificationRepo: notificationRepo, notificationRepo: notificationRepo,
cache: cacheBackend, cache: cacheBackend,

View File

@@ -73,7 +73,7 @@ type PostService interface {
// postServiceImpl 帖子服务实现 // postServiceImpl 帖子服务实现
type postServiceImpl struct { type postServiceImpl struct {
postRepo *repository.PostRepository postRepo repository.PostRepository
systemMessageService SystemMessageService systemMessageService SystemMessageService
cache cache.Cache cache cache.Cache
postAIService *PostAIService postAIService *PostAIService
@@ -82,7 +82,7 @@ type postServiceImpl struct {
hookManager *hook.Manager 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{ return &postServiceImpl{
postRepo: postRepo, postRepo: postRepo,
systemMessageService: systemMessageService, systemMessageService: systemMessageService,

View File

@@ -62,9 +62,9 @@ type PushService interface {
// pushServiceImpl 推送服务实现 // pushServiceImpl 推送服务实现
type pushServiceImpl struct { type pushServiceImpl struct {
pushRepo *repository.PushRecordRepository pushRepo repository.PushRecordRepository
deviceRepo *repository.DeviceTokenRepository deviceRepo repository.DeviceTokenRepository
messageRepo *repository.MessageRepository messageRepo repository.MessageRepository
sseHub *sse.Hub sseHub *sse.Hub
// 推送队列 // 推送队列
@@ -81,9 +81,9 @@ type pushTask struct {
// NewPushService 创建推送服务 // NewPushService 创建推送服务
func NewPushService( func NewPushService(
pushRepo *repository.PushRecordRepository, pushRepo repository.PushRecordRepository,
deviceRepo *repository.DeviceTokenRepository, deviceRepo repository.DeviceTokenRepository,
messageRepo *repository.MessageRepository, messageRepo repository.MessageRepository,
sseHub *sse.Hub, sseHub *sse.Hub,
) PushService { ) PushService {
return &pushServiceImpl{ return &pushServiceImpl{

View File

@@ -26,22 +26,30 @@ type SystemMessageService interface {
// 发送系统公告 // 发送系统公告
SendSystemAnnouncement(ctx context.Context, userIDs []string, title string, content string) error SendSystemAnnouncement(ctx context.Context, userIDs []string, title string, content string) error
SendBroadcastAnnouncement(ctx context.Context, 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 { type systemMessageServiceImpl struct {
notifyRepo *repository.SystemNotificationRepository notifyRepo repository.SystemNotificationRepository
pushService PushService pushService PushService
userRepo *repository.UserRepository userRepo repository.UserRepository
postRepo *repository.PostRepository postRepo repository.PostRepository
cache cache.Cache cache cache.Cache
} }
// NewSystemMessageService 创建系统消息服务 // NewSystemMessageService 创建系统消息服务
func NewSystemMessageService( func NewSystemMessageService(
notifyRepo *repository.SystemNotificationRepository, notifyRepo repository.SystemNotificationRepository,
pushService PushService, pushService PushService,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
cacheBackend cache.Cache, cacheBackend cache.Cache,
) SystemMessageService { ) SystemMessageService {
return &systemMessageServiceImpl{ return &systemMessageServiceImpl{
@@ -402,6 +410,51 @@ func (s *systemMessageServiceImpl) createNotification(ctx context.Context, userI
return notification, nil 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 获取操作者信息 // getActorInfo 获取操作者信息
func (s *systemMessageServiceImpl) getActorInfo(ctx context.Context, operatorID string) (string, string, error) { func (s *systemMessageServiceImpl) getActorInfo(ctx context.Context, operatorID string) (string, string, error) {
// 从用户仓储获取用户信息 // 从用户仓储获取用户信息

View File

@@ -35,11 +35,11 @@ type UserActivityService interface {
// userActivityService 实现 // userActivityService 实现
type userActivityService struct { type userActivityService struct {
repo *repository.UserActivityRepository repo repository.UserActivityRepository
} }
// NewUserActivityService 创建用户活跃统计服务 // NewUserActivityService 创建用户活跃统计服务
func NewUserActivityService(repo *repository.UserActivityRepository) UserActivityService { func NewUserActivityService(repo repository.UserActivityRepository) UserActivityService {
return &userActivityService{repo: repo} return &userActivityService{repo: repo}
} }

View File

@@ -59,7 +59,7 @@ type UserService interface {
// userServiceImpl 用户服务实现 // userServiceImpl 用户服务实现
type userServiceImpl struct { type userServiceImpl struct {
userRepo *repository.UserRepository userRepo repository.UserRepository
systemMessageService SystemMessageService systemMessageService SystemMessageService
emailCodeService EmailCodeService emailCodeService EmailCodeService
logService *LogService logService *LogService
@@ -67,7 +67,7 @@ type userServiceImpl struct {
// NewUserService 创建用户服务 // NewUserService 创建用户服务
func NewUserService( func NewUserService(
userRepo *repository.UserRepository, userRepo repository.UserRepository,
systemMessageService SystemMessageService, systemMessageService SystemMessageService,
emailService EmailService, emailService EmailService,
cacheBackend cache.Cache, cacheBackend cache.Cache,

View File

@@ -16,8 +16,8 @@ import (
// VoteService 投票服务 // VoteService 投票服务
type VoteService struct { type VoteService struct {
voteRepo *repository.VoteRepository voteRepo repository.VoteRepository
postRepo *repository.PostRepository postRepo repository.PostRepository
cache cache.Cache cache cache.Cache
postAIService *PostAIService postAIService *PostAIService
systemMessageService SystemMessageService systemMessageService SystemMessageService
@@ -25,8 +25,8 @@ type VoteService struct {
// NewVoteService 创建投票服务 // NewVoteService 创建投票服务
func NewVoteService( func NewVoteService(
voteRepo *repository.VoteRepository, voteRepo repository.VoteRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
cache cache.Cache, cache cache.Cache,
postAIService *PostAIService, postAIService *PostAIService,
systemMessageService SystemMessageService, systemMessageService SystemMessageService,

View File

@@ -1,10 +1,8 @@
package wire package wire
import ( import (
"carrot_bbs/internal/cache"
"carrot_bbs/internal/handler" "carrot_bbs/internal/handler"
"carrot_bbs/internal/pkg/sse" "carrot_bbs/internal/pkg/sse"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service" "carrot_bbs/internal/service"
"github.com/google/wire" "github.com/google/wire"
@@ -101,10 +99,8 @@ func ProvideMessageHandler(
// ProvideSystemMessageHandler 提供系统消息处理器 // ProvideSystemMessageHandler 提供系统消息处理器
func ProvideSystemMessageHandler( func ProvideSystemMessageHandler(
systemMsgService service.SystemMessageService, systemMsgService service.SystemMessageService,
systemNotificationRepo *repository.SystemNotificationRepository,
cacheBackend cache.Cache,
) *handler.SystemMessageHandler { ) *handler.SystemMessageHandler {
return handler.NewSystemMessageHandler(systemMsgService, systemNotificationRepo, cacheBackend) return handler.NewSystemMessageHandler(systemMsgService)
} }
// ProvideGroupHandler 提供群组处理器 // ProvideGroupHandler 提供群组处理器

View File

@@ -135,8 +135,8 @@ func ProvideBuiltinHooks(manager *hook.Manager) *hook.BuiltinHooks {
func ProvideModerationHooks( func ProvideModerationHooks(
manager *hook.Manager, manager *hook.Manager,
postAIService *service.PostAIService, postAIService *service.PostAIService,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
commentRepo *repository.CommentRepository, commentRepo repository.CommentRepository,
cfg *config.Config, cfg *config.Config,
) *hook.ModerationHooks { ) *hook.ModerationHooks {
strictMode := cfg.OpenAI.StrictModeration strictMode := cfg.OpenAI.StrictModeration

View File

@@ -19,6 +19,7 @@ var RepositorySet = wire.NewSet(
repository.NewDeviceTokenRepository, repository.NewDeviceTokenRepository,
repository.NewSystemNotificationRepository, repository.NewSystemNotificationRepository,
repository.NewGroupRepository, repository.NewGroupRepository,
repository.NewGroupJoinRequestRepository,
repository.NewStickerRepository, repository.NewStickerRepository,
repository.NewVoteRepository, repository.NewVoteRepository,
repository.NewChannelRepository, repository.NewChannelRepository,
@@ -34,7 +35,7 @@ var RepositorySet = wire.NewSet(
) )
// ProvideUserActivityRepository 提供用户活跃数据仓储 // 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) return repository.NewUserActivityRepository(db, cache)
} }

View File

@@ -86,9 +86,9 @@ func ProvidePostAIService(client openai.Client) *service.PostAIService {
// ProvidePushService 提供推送服务 // ProvidePushService 提供推送服务
func ProvidePushService( func ProvidePushService(
pushRepo *repository.PushRecordRepository, pushRepo repository.PushRecordRepository,
deviceTokenRepo *repository.DeviceTokenRepository, deviceTokenRepo repository.DeviceTokenRepository,
messageRepo *repository.MessageRepository, messageRepo repository.MessageRepository,
sseHub *sse.Hub, sseHub *sse.Hub,
) service.PushService { ) service.PushService {
return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, sseHub) return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, sseHub)
@@ -96,17 +96,17 @@ func ProvidePushService(
// ProvideSystemMessageService 提供系统消息服务 // ProvideSystemMessageService 提供系统消息服务
func ProvideSystemMessageService( func ProvideSystemMessageService(
notifyRepo *repository.SystemNotificationRepository, notifyRepo repository.SystemNotificationRepository,
pushService service.PushService, pushService service.PushService,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
cacheBackend cache.Cache, cacheBackend cache.Cache,
) service.SystemMessageService { ) service.SystemMessageService {
return service.NewSystemMessageService(notifyRepo, pushService, userRepo, postRepo, cacheBackend) return service.NewSystemMessageService(notifyRepo, pushService, userRepo, postRepo, cacheBackend)
} }
func ProvidePostService( func ProvidePostService(
postRepo *repository.PostRepository, postRepo repository.PostRepository,
systemMessageService service.SystemMessageService, systemMessageService service.SystemMessageService,
postAIService *service.PostAIService, postAIService *service.PostAIService,
cacheBackend cache.Cache, cacheBackend cache.Cache,
@@ -119,8 +119,8 @@ func ProvidePostService(
} }
func ProvideCommentService( func ProvideCommentService(
commentRepo *repository.CommentRepository, commentRepo repository.CommentRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
systemMessageService service.SystemMessageService, systemMessageService service.SystemMessageService,
postAIService *service.PostAIService, postAIService *service.PostAIService,
cacheBackend cache.Cache, cacheBackend cache.Cache,
@@ -131,17 +131,16 @@ func ProvideCommentService(
// ProvideMessageService 提供消息服务 // ProvideMessageService 提供消息服务
func ProvideMessageService( func ProvideMessageService(
db *gorm.DB, messageRepo repository.MessageRepository,
messageRepo *repository.MessageRepository,
cacheBackend cache.Cache, cacheBackend cache.Cache,
uploadService *service.UploadService, uploadService *service.UploadService,
) *service.MessageService { ) *service.MessageService {
return service.NewMessageService(db, messageRepo, cacheBackend, uploadService) return service.NewMessageService(messageRepo, cacheBackend, uploadService)
} }
// ProvideNotificationService 提供通知服务 // ProvideNotificationService 提供通知服务
func ProvideNotificationService( func ProvideNotificationService(
notificationRepo *repository.NotificationRepository, notificationRepo repository.NotificationRepository,
cacheBackend cache.Cache, cacheBackend cache.Cache,
) *service.NotificationService { ) *service.NotificationService {
return service.NewNotificationService(notificationRepo, cacheBackend) return service.NewNotificationService(notificationRepo, cacheBackend)
@@ -149,7 +148,7 @@ func ProvideNotificationService(
// ProvideUserService 提供用户服务 // ProvideUserService 提供用户服务
func ProvideUserService( func ProvideUserService(
userRepo *repository.UserRepository, userRepo repository.UserRepository,
systemMessageService service.SystemMessageService, systemMessageService service.SystemMessageService,
emailService service.EmailService, emailService service.EmailService,
cacheBackend cache.Cache, cacheBackend cache.Cache,
@@ -166,8 +165,8 @@ func ProvideStickerService(
// ProvideVoteService 提供投票服务 // ProvideVoteService 提供投票服务
func ProvideVoteService( func ProvideVoteService(
voteRepo *repository.VoteRepository, voteRepo repository.VoteRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
cache cache.Cache, cache cache.Cache,
postAIService *service.PostAIService, postAIService *service.PostAIService,
systemMessageService service.SystemMessageService, systemMessageService service.SystemMessageService,
@@ -175,21 +174,20 @@ func ProvideVoteService(
return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService) 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) return service.NewChannelService(channelRepo, cacheBackend)
} }
// ProvideChatService 提供聊天服务 // ProvideChatService 提供聊天服务
// Note: sensitiveService 传 nil与 main.go 保持一致 // Note: sensitiveService 传 nil与 main.go 保持一致
func ProvideChatService( func ProvideChatService(
db *gorm.DB, messageRepo repository.MessageRepository,
messageRepo *repository.MessageRepository, userRepo repository.UserRepository,
userRepo *repository.UserRepository,
sseHub *sse.Hub, sseHub *sse.Hub,
cacheBackend cache.Cache, cacheBackend cache.Cache,
uploadService *service.UploadService, uploadService *service.UploadService,
) service.ChatService { ) service.ChatService {
return service.NewChatService(db, messageRepo, userRepo, nil, sseHub, cacheBackend, uploadService) return service.NewChatService(messageRepo, userRepo, nil, sseHub, cacheBackend, uploadService)
} }
// ProvideScheduleService 提供日程服务 // ProvideScheduleService 提供日程服务
@@ -210,14 +208,15 @@ func ProvideScheduleSyncService(
// ProvideGroupService 提供群组服务 // ProvideGroupService 提供群组服务
func ProvideGroupService( func ProvideGroupService(
db *gorm.DB,
groupRepo repository.GroupRepository, groupRepo repository.GroupRepository,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
messageRepo *repository.MessageRepository, messageRepo repository.MessageRepository,
requestRepo repository.GroupJoinRequestRepository,
notifyRepo repository.SystemNotificationRepository,
sseHub *sse.Hub, sseHub *sse.Hub,
cacheBackend cache.Cache, cacheBackend cache.Cache,
) service.GroupService { ) service.GroupService {
return service.NewGroupService(db, groupRepo, userRepo, messageRepo, sseHub, cacheBackend) return service.NewGroupService(groupRepo, userRepo, messageRepo, requestRepo, notifyRepo, sseHub, cacheBackend)
} }
// ProvideUploadService 提供上传服务 // ProvideUploadService 提供上传服务
@@ -229,7 +228,7 @@ func ProvideUploadService(
} }
// ProvideUserActivityService 提供用户活跃统计服务 // ProvideUserActivityService 提供用户活跃统计服务
func ProvideUserActivityService(repo *repository.UserActivityRepository) service.UserActivityService { func ProvideUserActivityService(repo repository.UserActivityRepository) service.UserActivityService {
return service.NewUserActivityService(repo) return service.NewUserActivityService(repo)
} }
@@ -242,24 +241,24 @@ func ProvideRoleService(
} }
// ProvideAdminUserService 提供管理端用户服务 // ProvideAdminUserService 提供管理端用户服务
func ProvideAdminUserService(userRepo *repository.UserRepository) service.AdminUserService { func ProvideAdminUserService(userRepo repository.UserRepository) service.AdminUserService {
return service.NewAdminUserService(userRepo) return service.NewAdminUserService(userRepo)
} }
// ProvideAdminPostService 提供管理端帖子服务 // 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) return service.NewAdminPostService(postRepo, cacheBackend)
} }
// ProvideAdminCommentService 提供管理端评论服务 // 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) return service.NewAdminCommentService(commentRepo, postRepo)
} }
// ProvideAdminGroupService 提供管理端群组服务 // ProvideAdminGroupService 提供管理端群组服务
func ProvideAdminGroupService( func ProvideAdminGroupService(
groupRepo repository.GroupRepository, groupRepo repository.GroupRepository,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
) service.AdminGroupService { ) service.AdminGroupService {
return service.NewAdminGroupService(groupRepo, userRepo) return service.NewAdminGroupService(groupRepo, userRepo)
} }
@@ -267,21 +266,20 @@ func ProvideAdminGroupService(
// ProvideAdminDashboardService 提供管理端仪表盘服务 // ProvideAdminDashboardService 提供管理端仪表盘服务
func ProvideAdminDashboardService( func ProvideAdminDashboardService(
db *gorm.DB, db *gorm.DB,
userRepo *repository.UserRepository, userRepo repository.UserRepository,
postRepo *repository.PostRepository, postRepo repository.PostRepository,
commentRepo *repository.CommentRepository, commentRepo repository.CommentRepository,
groupRepo repository.GroupRepository, groupRepo repository.GroupRepository,
activityRepo *repository.UserActivityRepository, activityRepo repository.UserActivityRepository,
) service.AdminDashboardService { ) service.AdminDashboardService {
return service.NewAdminDashboardService(db, userRepo, postRepo, commentRepo, groupRepo, activityRepo) return service.NewAdminDashboardService(db, userRepo, postRepo, commentRepo, groupRepo, activityRepo)
} }
// ProvideAsyncLogManager 提供异步日志管理器 // ProvideAsyncLogManager 提供异步日志管理器
func ProvideAsyncLogManager( func ProvideAsyncLogManager(
db *gorm.DB, operationRepo repository.OperationLogRepository,
operationRepo *repository.OperationLogRepository, loginRepo repository.LoginLogRepository,
loginRepo *repository.LoginLogRepository, dataChangeRepo repository.DataChangeLogRepository,
dataChangeRepo *repository.DataChangeLogRepository,
logger *zap.Logger, logger *zap.Logger,
cfg *config.Config, cfg *config.Config,
) *service.AsyncLogManager { ) *service.AsyncLogManager {
@@ -300,13 +298,13 @@ func ProvideAsyncLogManager(
asyncCfg.FlushInterval = 3 * time.Second asyncCfg.FlushInterval = 3 * time.Second
} }
return service.NewAsyncLogManager(db, operationRepo, loginRepo, dataChangeRepo, logger, asyncCfg) return service.NewAsyncLogManager(operationRepo, loginRepo, dataChangeRepo, logger, asyncCfg)
} }
// ProvideOperationLogService 提供操作日志服务 // ProvideOperationLogService 提供操作日志服务
func ProvideOperationLogService( func ProvideOperationLogService(
asyncManager *service.AsyncLogManager, asyncManager *service.AsyncLogManager,
operationRepo *repository.OperationLogRepository, operationRepo repository.OperationLogRepository,
) service.OperationLogService { ) service.OperationLogService {
return service.NewOperationLogService(asyncManager, operationRepo) return service.NewOperationLogService(asyncManager, operationRepo)
} }
@@ -314,7 +312,7 @@ func ProvideOperationLogService(
// ProvideLoginLogService 提供登录日志服务 // ProvideLoginLogService 提供登录日志服务
func ProvideLoginLogService( func ProvideLoginLogService(
asyncManager *service.AsyncLogManager, asyncManager *service.AsyncLogManager,
loginRepo *repository.LoginLogRepository, loginRepo repository.LoginLogRepository,
) service.LoginLogService { ) service.LoginLogService {
return service.NewLoginLogService(asyncManager, loginRepo) return service.NewLoginLogService(asyncManager, loginRepo)
} }
@@ -322,16 +320,16 @@ func ProvideLoginLogService(
// ProvideDataChangeLogService 提供数据变更日志服务 // ProvideDataChangeLogService 提供数据变更日志服务
func ProvideDataChangeLogService( func ProvideDataChangeLogService(
asyncManager *service.AsyncLogManager, asyncManager *service.AsyncLogManager,
dataChangeRepo *repository.DataChangeLogRepository, dataChangeRepo repository.DataChangeLogRepository,
) service.DataChangeLogService { ) service.DataChangeLogService {
return service.NewDataChangeLogService(asyncManager, dataChangeRepo) return service.NewDataChangeLogService(asyncManager, dataChangeRepo)
} }
// ProvideLogCleanupService 提供日志清理服务 // ProvideLogCleanupService 提供日志清理服务
func ProvideLogCleanupService( func ProvideLogCleanupService(
operationRepo *repository.OperationLogRepository, operationRepo repository.OperationLogRepository,
loginRepo *repository.LoginLogRepository, loginRepo repository.LoginLogRepository,
dataChangeRepo *repository.DataChangeLogRepository, dataChangeRepo repository.DataChangeLogRepository,
logger *zap.Logger, logger *zap.Logger,
cfg *config.Config, cfg *config.Config,
) service.LogCleanupService { ) service.LogCleanupService {
@@ -355,7 +353,7 @@ func ProvideLogService(
} }
// ProvideQRCodeLoginService 提供二维码登录服务 // 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) return service.NewHotRankWorker(cfg, postRepo, cacheBackend)
} }
@@ -372,8 +370,8 @@ func ProvideQRCodeLoginService(
// ProvideMaterialService 提供学习资料服务 // ProvideMaterialService 提供学习资料服务
func ProvideMaterialService( func ProvideMaterialService(
subjectRepo *repository.MaterialSubjectRepository, subjectRepo repository.MaterialSubjectRepository,
fileRepo *repository.MaterialFileRepository, fileRepo repository.MaterialFileRepository,
) service.MaterialService { ) service.MaterialService {
return service.NewMaterialService(subjectRepo, fileRepo) return service.NewMaterialService(subjectRepo, fileRepo)
} }