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

View File

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

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

View File

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

View File

@@ -3,33 +3,24 @@ package handler
import (
"strconv"
"carrot_bbs/internal/cache"
"github.com/gin-gonic/gin"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service"
)
// SystemMessageHandler 系统消息处理器
type SystemMessageHandler struct {
systemMsgService service.SystemMessageService
notifyRepo *repository.SystemNotificationRepository
cache cache.Cache
}
// NewSystemMessageHandler 创建系统消息处理器
func NewSystemMessageHandler(
systemMsgService service.SystemMessageService,
notifyRepo *repository.SystemNotificationRepository,
cacheBackend cache.Cache,
) *SystemMessageHandler {
return &SystemMessageHandler{
systemMsgService: systemMsgService,
notifyRepo: notifyRepo,
cache: cacheBackend,
}
}
@@ -53,8 +44,8 @@ func (h *SystemMessageHandler) GetSystemMessages(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
// 获取当前用户的系统通知(从独立表中获取)
notifications, total, err := h.notifyRepo.GetByReceiverID(userID, page, pageSize)
// 获取当前用户的系统通知
notifications, total, err := h.systemMsgService.GetNotifications(userID, page, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get system messages")
return
@@ -83,7 +74,7 @@ func (h *SystemMessageHandler) GetSystemMessagesByCursor(c *gin.Context) {
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
// 获取当前用户的系统通知(使用兼容旧版接口)
notifications, nextCursor, hasMore, err := h.notifyRepo.GetByReceiverIDCursorLegacy(userID, cursorStr, pageSize)
notifications, nextCursor, hasMore, err := h.systemMsgService.GetNotificationsByCursor(userID, cursorStr, pageSize)
if err != nil {
response.InternalServerError(c, "failed to get system messages")
return
@@ -117,7 +108,7 @@ func (h *SystemMessageHandler) GetUnreadCount(c *gin.Context) {
}
// 获取当前用户的未读通知数
unreadCount, err := h.notifyRepo.GetUnreadCount(userID)
unreadCount, err := h.systemMsgService.GetUnreadCount(userID)
if err != nil {
response.InternalServerError(c, "failed to get unread count")
return
@@ -145,12 +136,11 @@ func (h *SystemMessageHandler) MarkAsRead(c *gin.Context) {
}
// 标记为已读
err = h.notifyRepo.MarkAsRead(notificationID, userID)
err = h.systemMsgService.MarkAsRead(notificationID, userID)
if err != nil {
response.InternalServerError(c, "failed to mark as read")
return
}
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "marked as read", nil)
}
@@ -165,12 +155,11 @@ func (h *SystemMessageHandler) MarkAllAsRead(c *gin.Context) {
}
// 标记当前用户所有通知为已读
err := h.notifyRepo.MarkAllAsRead(userID)
err := h.systemMsgService.MarkAllAsRead(userID)
if err != nil {
response.InternalServerError(c, "failed to mark all as read")
return
}
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "all messages marked as read", nil)
}
@@ -192,12 +181,11 @@ func (h *SystemMessageHandler) DeleteSystemMessage(c *gin.Context) {
}
// 删除通知
err = h.notifyRepo.Delete(notificationID, userID)
err = h.systemMsgService.DeleteNotification(notificationID, userID)
if err != nil {
response.InternalServerError(c, "failed to delete notification")
return
}
cache.InvalidateUnreadSystem(h.cache, userID)
response.SuccessWithMessage(c, "notification deleted", nil)
}

View File

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

View File

@@ -69,14 +69,46 @@ func (c *clientImpl) ModeratePost(ctx context.Context, title, content string, im
if !c.IsEnabled() {
return true, "", nil
}
return c.moderateContentInBatches(ctx, fmt.Sprintf("帖子标题:%s\n帖子内容%s", title, content), images)
// 构建帖子提示词,处理标题或内容为空的情况
var prompt strings.Builder
if strings.TrimSpace(title) != "" {
prompt.WriteString("帖子标题:" + title)
}
if strings.TrimSpace(content) != "" {
if prompt.Len() > 0 {
prompt.WriteString("\n")
}
prompt.WriteString("帖子内容:" + content)
}
// 如果标题和内容都为空但有图片,说明是纯图片帖子
if prompt.Len() == 0 && len(normalizeImageURLs(images)) > 0 {
prompt.WriteString("帖子内容:[仅包含图片]")
}
if prompt.Len() == 0 {
prompt.WriteString("帖子内容:[空]")
}
return c.moderateContentInBatches(ctx, prompt.String(), images)
}
func (c *clientImpl) ModerateComment(ctx context.Context, content string, images []string) (bool, string, error) {
if !c.IsEnabled() {
return true, "", nil
}
return c.moderateContentInBatches(ctx, fmt.Sprintf("评论内容:%s", content), images)
// 构建评论提示词,处理纯图片评论的情况
var prompt strings.Builder
if strings.TrimSpace(content) != "" {
prompt.WriteString("评论内容:" + content)
} else if len(normalizeImageURLs(images)) > 0 {
// 评论只有图片没有文字,说明是纯图片评论
prompt.WriteString("评论内容:[仅包含图片]")
} else {
prompt.WriteString("评论内容:[空]")
}
return c.moderateContentInBatches(ctx, prompt.String(), images)
}
func (c *clientImpl) moderateContentInBatches(ctx context.Context, contentPrompt string, images []string) (bool, string, error) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ type GroupRepository interface {
Update(group *model.Group) error
Delete(id string) error
GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error)
TransferOwner(groupID, oldOwnerID, newOwnerID string) error
// 群成员操作
AddMember(member *model.GroupMember) error
@@ -90,6 +91,32 @@ func (r *groupRepository) Update(group *model.Group) error {
return r.db.Save(group).Error
}
// TransferOwner 转让群主(在事务中更新群主和角色)
func (r *groupRepository) TransferOwner(groupID, oldOwnerID, newOwnerID string) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// 更新群组的群主
if err := tx.Model(&model.Group{}).Where("id = ?", groupID).Update("owner_id", newOwnerID).Error; err != nil {
return err
}
// 更新原群主为管理员
if err := tx.Model(&model.GroupMember{}).
Where("group_id = ? AND user_id = ?", groupID, oldOwnerID).
Update("role", model.GroupRoleAdmin).Error; err != nil {
return err
}
// 更新新群主为群主
if err := tx.Model(&model.GroupMember{}).
Where("group_id = ? AND user_id = ?", groupID, newOwnerID).
Update("role", model.GroupRoleOwner).Error; err != nil {
return err
}
return nil
})
}
// Delete 删除群组
func (r *groupRepository) Delete(id string) error {
return r.db.Transaction(func(tx *gorm.DB) error {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,11 +21,11 @@ type ChannelService interface {
}
type channelServiceImpl struct {
channelRepo *repository.ChannelRepository
channelRepo repository.ChannelRepository
cache cache.Cache
}
func NewChannelService(channelRepo *repository.ChannelRepository, c cache.Cache) ChannelService {
func NewChannelService(channelRepo repository.ChannelRepository, c cache.Cache) ChannelService {
return &channelServiceImpl{channelRepo: channelRepo, cache: c}
}

View File

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

View File

@@ -15,8 +15,8 @@ import (
)
type CommentService struct {
commentRepo *repository.CommentRepository
postRepo *repository.PostRepository
commentRepo repository.CommentRepository
postRepo repository.PostRepository
systemMessageService SystemMessageService
cache cache.Cache
postAIService *PostAIService
@@ -24,7 +24,7 @@ type CommentService struct {
hookManager *hook.Manager
}
func NewCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, hookManager *hook.Manager) *CommentService {
func NewCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, hookManager *hook.Manager) *CommentService {
return &CommentService{
commentRepo: commentRepo,
postRepo: postRepo,

View File

@@ -106,25 +106,23 @@ type GroupMembersResult struct {
// groupService 群组服务实现
type groupService struct {
db *gorm.DB
groupRepo repository.GroupRepository
userRepo *repository.UserRepository
messageRepo *repository.MessageRepository
userRepo repository.UserRepository
messageRepo repository.MessageRepository
requestRepo repository.GroupJoinRequestRepository
notifyRepo *repository.SystemNotificationRepository
notifyRepo repository.SystemNotificationRepository
sseHub *sse.Hub
cache cache.Cache
}
// NewGroupService 创建群组服务
func NewGroupService(db *gorm.DB, groupRepo repository.GroupRepository, userRepo *repository.UserRepository, messageRepo *repository.MessageRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService {
func NewGroupService(groupRepo repository.GroupRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository, requestRepo repository.GroupJoinRequestRepository, notifyRepo repository.SystemNotificationRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService {
return &groupService{
db: db,
groupRepo: groupRepo,
userRepo: userRepo,
messageRepo: messageRepo,
requestRepo: repository.NewGroupJoinRequestRepository(db),
notifyRepo: repository.NewSystemNotificationRepository(db),
requestRepo: requestRepo,
notifyRepo: notifyRepo,
sseHub: sseHub,
cache: cacheBackend,
}
@@ -263,43 +261,17 @@ func (s *groupService) CreateGroup(ownerID string, name string, description stri
GroupID: &group.ID,
}
// 在事务中创建会话和参与者
err = s.db.Transaction(func(tx *gorm.DB) error {
// 创建会话
if err := tx.Create(conversation).Error; err != nil {
return err
// 收集所有参与者
participants := make([]string, 0, len(memberIDs)+1)
participants = append(participants, ownerID)
for _, memberID := range memberIDs {
if memberID != ownerID {
participants = append(participants, memberID)
}
}
// 添加群主为会话参与者
ownerParticipant := model.ConversationParticipant{
ConversationID: conversation.ID,
UserID: ownerID,
LastReadSeq: 0,
}
if err := tx.Create(&ownerParticipant).Error; err != nil {
return err
}
// 添加被邀请的成员为会话参与者
for _, memberID := range memberIDs {
if memberID == ownerID {
continue
}
participant := model.ConversationParticipant{
ConversationID: conversation.ID,
UserID: memberID,
LastReadSeq: 0,
}
if err := tx.Create(&participant).Error; err != nil {
// 单个参与者添加失败继续处理其他成员
continue
}
}
return nil
})
if err != nil {
// 使用仓储创建会话参与者
if err := s.messageRepo.CreateConversationWithParticipants(conversation, participants); err != nil {
// 记录错误但不影响群组创建成功
}
@@ -418,30 +390,8 @@ func (s *groupService) TransferOwner(userID string, groupID string, newOwnerID s
return ErrNotGroupMember
}
// 在事务中更新
return s.db.Transaction(func(tx *gorm.DB) error {
// 更新群组的群主
group.OwnerID = newOwnerID
if err := tx.Save(group).Error; err != nil {
return err
}
// 更新原群主为管理员
if err := tx.Model(&model.GroupMember{}).
Where("group_id = ? AND user_id = ?", groupID, userID).
Update("role", model.GroupRoleAdmin).Error; err != nil {
return err
}
// 更新新群主为群主
if err := tx.Model(&model.GroupMember{}).
Where("group_id = ? AND user_id = ?", groupID, newOwnerID).
Update("role", model.GroupRoleOwner).Error; err != nil {
return err
}
return nil
})
// 使用仓储的 TransferOwner 方法(在事务中更新
return s.groupRepo.TransferOwner(groupID, userID, newOwnerID)
}
// GetUserGroups 获取用户加入的群组列表
@@ -593,9 +543,7 @@ func (s *groupService) collectGroupReviewerIDs(groupID string, ownerID string, s
if ownerID != "" && ownerID != skipUserID {
reviewerSet[ownerID] = struct{}{}
}
var reviewers []model.GroupMember
if err := s.db.Where("group_id = ? AND role IN ?", groupID, []string{model.GroupRoleOwner, model.GroupRoleAdmin}).Find(&reviewers).Error; err == nil {
if reviewers, err := s.groupRepo.GetAdmins(groupID); err == nil {
for _, reviewer := range reviewers {
if reviewer.UserID == "" || reviewer.UserID == skipUserID {
continue

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,7 +73,7 @@ type PostService interface {
// postServiceImpl 帖子服务实现
type postServiceImpl struct {
postRepo *repository.PostRepository
postRepo repository.PostRepository
systemMessageService SystemMessageService
cache cache.Cache
postAIService *PostAIService
@@ -82,7 +82,7 @@ type postServiceImpl struct {
hookManager *hook.Manager
}
func NewPostService(postRepo *repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, txManager repository.TransactionManager, hookManager *hook.Manager) PostService {
func NewPostService(postRepo repository.PostRepository, systemMessageService SystemMessageService, postAIService *PostAIService, cacheBackend cache.Cache, txManager repository.TransactionManager, hookManager *hook.Manager) PostService {
return &postServiceImpl{
postRepo: postRepo,
systemMessageService: systemMessageService,

View File

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

View File

@@ -26,22 +26,30 @@ type SystemMessageService interface {
// 发送系统公告
SendSystemAnnouncement(ctx context.Context, userIDs []string, title string, content string) error
SendBroadcastAnnouncement(ctx context.Context, title string, content string) error
// 查询通知(供 Handler 层的 REST API 使用)
GetNotifications(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error)
GetNotificationsByCursor(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error)
GetUnreadCount(receiverID string) (int64, error)
MarkAsRead(notificationID int64, receiverID string) error
MarkAllAsRead(receiverID string) error
DeleteNotification(notificationID int64, receiverID string) error
}
type systemMessageServiceImpl struct {
notifyRepo *repository.SystemNotificationRepository
notifyRepo repository.SystemNotificationRepository
pushService PushService
userRepo *repository.UserRepository
postRepo *repository.PostRepository
userRepo repository.UserRepository
postRepo repository.PostRepository
cache cache.Cache
}
// NewSystemMessageService 创建系统消息服务
func NewSystemMessageService(
notifyRepo *repository.SystemNotificationRepository,
notifyRepo repository.SystemNotificationRepository,
pushService PushService,
userRepo *repository.UserRepository,
postRepo *repository.PostRepository,
userRepo repository.UserRepository,
postRepo repository.PostRepository,
cacheBackend cache.Cache,
) SystemMessageService {
return &systemMessageServiceImpl{
@@ -402,6 +410,51 @@ func (s *systemMessageServiceImpl) createNotification(ctx context.Context, userI
return notification, nil
}
// GetNotifications 获取通知列表(供 Handler 调用)
func (s *systemMessageServiceImpl) GetNotifications(receiverID string, page, pageSize int) ([]*model.SystemNotification, int64, error) {
return s.notifyRepo.GetByReceiverID(receiverID, page, pageSize)
}
// GetNotificationsByCursor 游标分页获取通知列表(供 Handler 调用)
func (s *systemMessageServiceImpl) GetNotificationsByCursor(receiverID string, cursorStr string, pageSize int) ([]*model.SystemNotification, string, bool, error) {
return s.notifyRepo.GetByReceiverIDCursorLegacy(receiverID, cursorStr, pageSize)
}
// GetUnreadCount 获取未读数量(供 Handler 调用)
func (s *systemMessageServiceImpl) GetUnreadCount(receiverID string) (int64, error) {
return s.notifyRepo.GetUnreadCount(receiverID)
}
// MarkAsRead 标记单条为已读(供 Handler 调用)
func (s *systemMessageServiceImpl) MarkAsRead(notificationID int64, receiverID string) error {
err := s.notifyRepo.MarkAsRead(notificationID, receiverID)
if err != nil {
return err
}
cache.InvalidateUnreadSystem(s.cache, receiverID)
return nil
}
// MarkAllAsRead 标记全部为已读(供 Handler 调用)
func (s *systemMessageServiceImpl) MarkAllAsRead(receiverID string) error {
err := s.notifyRepo.MarkAllAsRead(receiverID)
if err != nil {
return err
}
cache.InvalidateUnreadSystem(s.cache, receiverID)
return nil
}
// DeleteNotification 删除通知(供 Handler 调用)
func (s *systemMessageServiceImpl) DeleteNotification(notificationID int64, receiverID string) error {
err := s.notifyRepo.Delete(notificationID, receiverID)
if err != nil {
return err
}
cache.InvalidateUnreadSystem(s.cache, receiverID)
return nil
}
// getActorInfo 获取操作者信息
func (s *systemMessageServiceImpl) getActorInfo(ctx context.Context, operatorID string) (string, string, error) {
// 从用户仓储获取用户信息

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ var RepositorySet = wire.NewSet(
repository.NewDeviceTokenRepository,
repository.NewSystemNotificationRepository,
repository.NewGroupRepository,
repository.NewGroupJoinRequestRepository,
repository.NewStickerRepository,
repository.NewVoteRepository,
repository.NewChannelRepository,
@@ -34,7 +35,7 @@ var RepositorySet = wire.NewSet(
)
// ProvideUserActivityRepository 提供用户活跃数据仓储
func ProvideUserActivityRepository(db *gorm.DB, cache cache.Cache) *repository.UserActivityRepository {
func ProvideUserActivityRepository(db *gorm.DB, cache cache.Cache) repository.UserActivityRepository {
return repository.NewUserActivityRepository(db, cache)
}

View File

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