From 28cfcbf9a8dd01e655211195c02e1adb7ef588b8 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Sun, 21 Jun 2026 17:17:38 +0800 Subject: [PATCH] refactor(comment): unify status transitions with atomic count maintenance Consolidate comment status changes through transitionStatus() to ensure posts.comments_count and comments.replies_count are atomically maintained within the same transaction, preventing inconsistencies between displayed and stored counts. - Remove separate ApplyPublishedStats() calls, now handled in UpdateModerationStatus - Add cache invalidation for admin moderation operations - Update report auto-hide to use unified status transition --- cmd/server/wire_gen.go | 2 +- internal/repository/comment_repo.go | 176 ++++++++++------------ internal/repository/comment_status.go | 155 +++++++++++++++++++ internal/service/admin_comment_service.go | 45 ++++-- internal/service/comment_service.go | 26 +--- internal/service/report_service.go | 16 +- internal/wire/service.go | 4 +- 7 files changed, 285 insertions(+), 139 deletions(-) create mode 100644 internal/repository/comment_status.go diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 348035f..3f82d92 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -127,7 +127,7 @@ func InitializeApp() (*App, error) { adminUserHandler := handler.NewAdminUserHandler(adminUserService, pushService) adminPostService := wire.ProvideAdminPostService(postRepository, cache, logService) adminPostHandler := handler.NewAdminPostHandler(adminPostService) - adminCommentService := wire.ProvideAdminCommentService(commentRepository, postRepository) + adminCommentService := wire.ProvideAdminCommentService(commentRepository, postRepository, cache) adminCommentHandler := handler.NewAdminCommentHandler(adminCommentService) adminGroupService := wire.ProvideAdminGroupService(groupRepository, userRepository) adminGroupHandler := handler.NewAdminGroupHandler(adminGroupService) diff --git a/internal/repository/comment_repo.go b/internal/repository/comment_repo.go index dc442a3..a154c83 100644 --- a/internal/repository/comment_repo.go +++ b/internal/repository/comment_repo.go @@ -1,6 +1,8 @@ package repository import ( + "errors" + "with_you/internal/model" "with_you/internal/pkg/cursor" "context" @@ -13,9 +15,8 @@ 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, rejectReason string, reviewedBy string) error + UpdateModerationStatus(commentID string, status model.CommentStatus, rejectReason string, reviewedBy string) (*model.Comment, 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) @@ -57,31 +58,72 @@ func (r *commentRepository) GetByID(id string) (*model.Comment, error) { return &comment, nil } -// Update 更新评论 +// Update 更新评论(仅内容字段,如 content / segments / images)。 +// +// 防御性:评论的 status 字段是计数维护的核心,必须经由 UpdateModerationStatus / +// Delete 等状态转换通道变更,否则会破坏 comments_count / replies_count 一致性。 +// 因此本方法显式不更新 status / reviewed_* 等审核字段——即便上层传入的 +// comment 结构体 status 为零值或脏值,也不会污染状态机。 func (r *commentRepository) Update(comment *model.Comment) error { - return r.db.Save(comment).Error -} - -// UpdateModerationStatus 更新评论审核状态 -func (r *commentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus, rejectReason string, reviewedBy string) error { - updates := map[string]any{ - "status": status, - "reviewed_at": gorm.Expr("CURRENT_TIMESTAMP"), - "reviewed_by": reviewedBy, - "reject_reason": rejectReason, - "updated_at": gorm.Expr("updated_at"), - } return r.db.Model(&model.Comment{}). - Where("id = ?", commentID). - UpdateColumns(updates).Error + Where("id = ?", comment.ID). + Select("content", "segments", "images", "updated_at"). + Updates(comment).Error } -// Delete 删除评论(软删除,同时清理关联数据) +// UpdateModerationStatus 更新评论审核状态,并在同一事务内原子地维护 +// 帖子评论数 / 父评论回复数等统计字段。 +// +// 计数维护统一收敛到 transitionStatus(评论状态变更的唯一通道), +// 因此任何调用方(自动审核、管理端单条 / 批量审核、举报自动隐藏等) +// 都能正确维护计数,避免"评论显示 N 条但计数为 N-1"。 +// 同时内置防御:非法状态转换(如 deleted → published)会被拒绝, +// 计数递减使用 CASE WHEN 保证永不为负。 +// +// 返回更新后的评论(含 User 关联),便于调用方做缓存失效、通知等后续处理。 +func (r *commentRepository) UpdateModerationStatus(commentID string, status model.CommentStatus, rejectReason string, reviewedBy string) (*model.Comment, error) { + var updated model.Comment + err := r.db.Transaction(func(tx *gorm.DB) error { + prev, err := transitionStatus(tx, commentID, status) + if err != nil { + return err + } + // 状态未变化或仅元数据需补充(reviewed_at / reviewed_by / reject_reason) + updates := map[string]any{ + "reviewed_at": gorm.Expr("CURRENT_TIMESTAMP"), + "reviewed_by": reviewedBy, + "reject_reason": rejectReason, + "updated_at": gorm.Expr("updated_at"), + } + if prev.Status != status { + updates["status"] = status + } + if err := tx.Model(&model.Comment{}). + Where("id = ?", commentID). + UpdateColumns(updates).Error; err != nil { + return err + } + + // 重新加载(含 User 关联)供调用方使用 + return tx.Preload("User").First(&updated, "id = ?", commentID).Error + }) + if err != nil { + return nil, err + } + return &updated, nil +} + +// Delete 删除评论(软删除,同时清理关联数据)。 +// 删除 = 状态迁移到 deleted,复用 transitionStatus 统一维护计数, +// 保证 Delete 与审核路径的计数行为一致、对称。 func (r *commentRepository) Delete(id string) error { return r.db.Transaction(func(tx *gorm.DB) error { - // 先查询评论获取post_id和parent_id - var comment model.Comment - if err := tx.First(&comment, "id = ?", id).Error; err != nil { + // 统一走状态转换层:published → deleted 会自动 -1 计数;非 published 删除则不调整 + if _, err := transitionStatus(tx, id, model.CommentStatusDeleted); err != nil { + if errors.Is(err, errInvalidStatusTransition) { + // 已删除的评论重复删除:视为幂等成功 + return nil + } return err } @@ -95,54 +137,6 @@ func (r *commentRepository) Delete(id string) error { return err } - // 仅已发布评论才参与统计,避免 pending/rejected 影响计数 - if comment.Status == model.CommentStatusPublished { - // 减少帖子的评论数并同步热度分 - // 评论数属于统计字段,不应影响帖子内容更新时间(updated_at) - if err := tx.Model(&model.Post{}).Where("id = ?", comment.PostID). - UpdateColumns(map[string]any{ - "comments_count": gorm.Expr("comments_count - 1"), - "updated_at": gorm.Expr("updated_at"), - }).Error; err != nil { - return err - } - - // 如果是回复,减少父评论的回复数 - if comment.ParentID != nil && *comment.ParentID != "" { - if err := tx.Model(&model.Comment{}).Where("id = ?", *comment.ParentID). - UpdateColumn("replies_count", gorm.Expr("replies_count - 1")).Error; err != nil { - return err - } - } - } - - return nil - }) -} - -// ApplyPublishedStats 在评论审核通过后更新帖子评论数/回复数 -func (r *commentRepository) ApplyPublishedStats(comment *model.Comment) error { - if comment == nil { - return nil - } - return r.db.Transaction(func(tx *gorm.DB) error { - // 增加帖子的评论数并同步热度分 - // 评论数属于统计字段,不应影响帖子内容更新时间(updated_at) - if err := tx.Model(&model.Post{}).Where("id = ?", comment.PostID). - UpdateColumns(map[string]any{ - "comments_count": gorm.Expr("comments_count + 1"), - "updated_at": gorm.Expr("updated_at"), - }).Error; err != nil { - return err - } - - // 如果是回复,增加父评论的回复数 - if comment.ParentID != nil && *comment.ParentID != "" { - if err := tx.Model(&model.Comment{}).Where("id = ?", *comment.ParentID). - UpdateColumn("replies_count", gorm.Expr("replies_count + 1")).Error; err != nil { - return err - } - } return nil }) } @@ -413,24 +407,26 @@ func (r *commentRepository) GetAdminCommentByID(id string) (*model.Comment, erro return &comment, nil } -// BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题) +// BatchDelete 批量删除评论。 +// 逐条复用 transitionStatus 完成 published → deleted 的状态迁移, +// 统一维护 comments_count 与父评论 replies_count(修复此前批量删除漏减 replies_count 的问题)。 +// 返回失败 ID 列表(当前实现下事务整体回滚,故失败时返回全部 ids)。 func (r *commentRepository) BatchDelete(ids []string) ([]string, error) { if len(ids) == 0 { return nil, nil } - // 先查询所有评论获取 post_id 和状态信息 - type commentInfo struct { - ID string - PostID string - Status model.CommentStatus - } - var comments []commentInfo - if err := r.db.Model(&model.Comment{}).Where("id IN ?", ids).Scan(&comments).Error; err != nil { - return ids, err - } - err := r.db.Transaction(func(tx *gorm.DB) error { + for _, id := range ids { + // 统一走状态转换层:幂等处理已删除评论,已发布评论自动 -1 计数 + 父回复数 + if _, terr := transitionStatus(tx, id, model.CommentStatusDeleted); terr != nil { + if errors.Is(terr, errInvalidStatusTransition) { + continue // 已删除:幂等跳过 + } + return terr + } + } + // 批量删除评论点赞记录 if err := tx.Where("comment_id IN ?", ids).Delete(&model.CommentLike{}).Error; err != nil { return err @@ -440,24 +436,6 @@ func (r *commentRepository) BatchDelete(ids []string) ([]string, error) { if err := tx.Where("id IN ?", ids).Delete(&model.Comment{}).Error; err != nil { return err } - - // 更新帖子的评论数(仅统计已发布评论) - postCountMap := make(map[string]int) - for _, c := range comments { - if c.Status == model.CommentStatusPublished { - postCountMap[c.PostID]++ - } - } - for postID, count := range postCountMap { - if err := tx.Model(&model.Post{}).Where("id = ?", postID). - UpdateColumns(map[string]any{ - "comments_count": gorm.Expr("comments_count - ?", count), - "updated_at": gorm.Expr("updated_at"), - }).Error; err != nil { - return err - } - } - return nil }) diff --git a/internal/repository/comment_status.go b/internal/repository/comment_status.go new file mode 100644 index 0000000..afd2cc1 --- /dev/null +++ b/internal/repository/comment_status.go @@ -0,0 +1,155 @@ +package repository + +import ( + "errors" + + "with_you/internal/model" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// clauseLockUpdate 行级锁子句,供 transitionStatus 加锁读取使用。 +// 集中定义便于在评论仓储内复用。 +var clauseLockUpdate = clause.Locking{Strength: "UPDATE"} + +// 评论状态机与计数维护的统一入口。 +// +// 设计目标: +// 把"评论状态变更"与"聚合计数维护 (posts.comments_count / comments.replies_count)" +// 强绑定到同一个事务内,作为唯一的状态变更通道。任何外部路径 +// (自动审核、管理端审核、批量审核、举报自动隐藏、删除等)都必须经由 +// UpdateModerationStatus / Delete / BatchDelete 走到这里,从而杜绝 +// "调用方忘记维护计数"导致的计数与显示不一致问题(如显示2条但计数为1)。 +// +// 防御性: +// 1. SELECT ... FOR UPDATE 加锁读旧状态,避免并发审核造成计数错乱; +// 2. 非法状态转换(如 deleted → published)直接拒绝,从源头防止脏数据再次污染计数; +// 3. 计数递减使用 CASE WHEN 防止下溢为负,即使历史脏数据导致 count 与实际不一致, +// 也不会产生负值计数; +// 4. 计数规则集中表达:统计字段只反映 published 评论,状态进出 published 时分别 +1 / -1。 + +// errInvalidStatusTransition 非法状态转换 +var errInvalidStatusTransition = errors.New("invalid comment status transition") + +// isCountedStatus 判断该状态的评论是否参与聚合计数 +func isCountedStatus(s model.CommentStatus) bool { + return s == model.CommentStatusPublished +} + +// statusDelta 返回从 oldStatus 迁移到 newStatus 时,聚合计数应当的增量: +// +1:非计数状态 → 计数状态(审核通过 / 恢复发布) +// -1:计数状态 → 非计数状态(已发布评论被驳回/隐藏/删除) +// 0:其余转换(如 pending → rejected,均不参与计数) +func statusDelta(oldStatus, newStatus model.CommentStatus) int { + oldCounted := isCountedStatus(oldStatus) + newCounted := isCountedStatus(newStatus) + switch { + case !oldCounted && newCounted: + return 1 + case oldCounted && !newCounted: + return -1 + default: + return 0 + } +} + +// canTransition 校验状态转换是否合法。 +// deleted 是终态:已软删除的评论不应再被审核(恢复发布等), +// 否则会引入"幽灵计数"——评论已被关联数据清理,却仍试图调整计数。 +func canTransition(oldStatus, newStatus model.CommentStatus) error { + if oldStatus == newStatus { + return nil + } + if oldStatus == model.CommentStatusDeleted { + return errInvalidStatusTransition + } + return nil +} + +// applyStatusDelta 在事务内根据 delta 原子调整帖子评论数与父评论回复数。 +// 使用 CASE WHEN 保证计数永不下溢为负(防御历史脏数据)。 +func applyStatusDelta(tx *gorm.DB, comment *model.Comment, delta int) error { + if delta == 0 { + return nil + } + + // 帖子评论数:delta>0 直接相加;delta<0 用 CASE WHEN 防止下溢 + var postCountExpr any + if delta > 0 { + postCountExpr = gorm.Expr("comments_count + ?", delta) + } else { + postCountExpr = gorm.Expr( + "CASE WHEN comments_count >= ? THEN comments_count + ? ELSE 0 END", + -delta, delta, + ) + } + // 统计字段变更不应影响帖子内容更新时间(updated_at) + if err := tx.Model(&model.Post{}).Where("id = ?", comment.PostID). + UpdateColumns(map[string]any{ + "comments_count": postCountExpr, + "updated_at": gorm.Expr("updated_at"), + }).Error; err != nil { + return err + } + + // 若为回复,同步调整父评论回复数(同样防下溢) + if comment.ParentID != nil && *comment.ParentID != "" { + var replyCountExpr any + if delta > 0 { + replyCountExpr = gorm.Expr("replies_count + ?", delta) + } else { + replyCountExpr = gorm.Expr( + "CASE WHEN replies_count >= ? THEN replies_count + ? ELSE 0 END", + -delta, delta, + ) + } + if err := tx.Model(&model.Comment{}).Where("id = ?", *comment.ParentID). + UpdateColumn("replies_count", replyCountExpr).Error; err != nil { + return err + } + } + return nil +} + +// transitionStatus 在指定事务内将评论状态由当前值迁移到 newStatus, +// 并原子维护聚合计数。这是评论状态变更的唯一计数维护通道。 +// +// 调用方需在外层开启事务(Delete / BatchDelete 等),并传入该事务的 tx。 +// 仅做状态迁移与计数维护,不负责清理点赞、软删除标记等副作用—— +// 那些由调用方在各自事务内完成。 +// +// 返回迁移发生前的旧评论快照(含旧状态),供调用方决策后续清理逻辑。 +func transitionStatus(tx *gorm.DB, commentID string, newStatus model.CommentStatus) (*model.Comment, error) { + // 加锁读取当前状态,避免并发审核造成计数错乱 + var comment model.Comment + if err := tx.Clauses(clauseLockUpdate).First(&comment, "id = ?", commentID).Error; err != nil { + return nil, err + } + oldStatus := comment.Status + if err := canTransition(oldStatus, newStatus); err != nil { + return &comment, err + } + + if oldStatus == newStatus { + // 状态未变化:不写库、不调整计数 + return &comment, nil + } + + updates := map[string]any{ + "status": newStatus, + "updated_at": gorm.Expr("updated_at"), // 状态变更不应刷新内容更新时间 + } + if err := tx.Model(&model.Comment{}). + Where("id = ?", commentID). + UpdateColumns(updates).Error; err != nil { + return nil, err + } + + if err := applyStatusDelta(tx, &comment, statusDelta(oldStatus, newStatus)); err != nil { + return nil, err + } + + comment.Status = newStatus + return &comment, nil +} diff --git a/internal/service/admin_comment_service.go b/internal/service/admin_comment_service.go index c6c6320..2d78d4d 100644 --- a/internal/service/admin_comment_service.go +++ b/internal/service/admin_comment_service.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" + "with_you/internal/cache" "with_you/internal/dto" "with_you/internal/model" "with_you/internal/repository" @@ -24,13 +25,15 @@ type AdminCommentService interface { type adminCommentServiceImpl struct { commentRepo repository.CommentRepository postRepo repository.PostRepository + cache cache.Cache } // NewAdminCommentService 创建管理端评论服务 -func NewAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository) AdminCommentService { +func NewAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, cacheBackend cache.Cache) AdminCommentService { return &adminCommentServiceImpl{ commentRepo: commentRepo, postRepo: postRepo, + cache: cacheBackend, } } @@ -101,29 +104,49 @@ func (s *adminCommentServiceImpl) BatchDeleteComments(ctx context.Context, ids [ } func (s *adminCommentServiceImpl) ModerateComment(ctx context.Context, commentID string, status model.CommentStatus, reason, reviewedBy string) (*dto.AdminCommentDetailResponse, error) { - _, err := s.commentRepo.GetAdminCommentByID(commentID) + if _, err := s.commentRepo.GetAdminCommentByID(commentID); err != nil { + return nil, err + } + + // UpdateModerationStatus 内部已原子地维护帖子评论数/父评论回复数, + // 因此管理端审核与自动审核路径的计数行为完全一致。 + updated, err := s.commentRepo.UpdateModerationStatus(commentID, status, reason, reviewedBy) if err != nil { return nil, err } - if err := s.commentRepo.UpdateModerationStatus(commentID, status, reason, reviewedBy); err != nil { - return nil, err + // 状态变更后失效该帖子相关缓存,确保前端立即看到正确计数 + if s.cache != nil { + cache.InvalidatePostDetail(s.cache, updated.PostID) + cache.InvalidatePostList(s.cache) } - comment, err := s.commentRepo.GetAdminCommentByID(commentID) - if err != nil { - return nil, err - } - - return s.convertCommentToAdminDetailResponse(comment), nil + return s.convertCommentToAdminDetailResponse(updated), nil } func (s *adminCommentServiceImpl) BatchModerateComments(ctx context.Context, ids []string, status model.CommentStatus, reviewedBy string) (*dto.AdminBatchOperationResponse, error) { var failedIDs []string + // 收集受影响的帖子,用于审核完成后统一失效缓存 + affectedPostIDs := make(map[string]struct{}) + for _, id := range ids { - if err := s.commentRepo.UpdateModerationStatus(id, status, "", reviewedBy); err != nil { + // UpdateModerationStatus 内部已原子地维护计数,这里只需收集结果 + updated, err := s.commentRepo.UpdateModerationStatus(id, status, "", reviewedBy) + if err != nil { failedIDs = append(failedIDs, id) + continue } + if updated != nil { + affectedPostIDs[updated.PostID] = struct{}{} + } + } + + // 统一失效受影响帖子的缓存 + if s.cache != nil { + for postID := range affectedPostIDs { + cache.InvalidatePostDetail(s.cache, postID) + } + cache.InvalidatePostList(s.cache) } if len(failedIDs) == 0 { diff --git a/internal/service/comment_service.go b/internal/service/comment_service.go index a71f67f..a755916 100644 --- a/internal/service/comment_service.go +++ b/internal/service/comment_service.go @@ -154,18 +154,13 @@ func (s *commentService) reviewCommentAsync( postOwnerID string, ) { if s.hookManager == nil { - if err := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", "system"); err != nil { + if _, err := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", "system"); err != nil { zap.L().Warn("Failed to publish comment without hook manager", zap.Error(err), ) return } - if err := s.applyCommentPublishedStats(commentID); err != nil { - zap.L().Warn("Failed to apply published stats for comment", - zap.String("commentID", commentID), - zap.Error(err), - ) - } + // 计数维护已在 UpdateModerationStatus 事务内完成,无需再单独调用 s.invalidatePostCaches(postID) s.afterCommentPublished(userID, postID, commentID, parentID, parentUserID, postOwnerID) return @@ -210,31 +205,18 @@ func (s *commentService) reviewCommentAsync( return } - if updateErr := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", result.ReviewedBy); updateErr != nil { + if _, updateErr := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", result.ReviewedBy); updateErr != nil { zap.L().Warn("Failed to publish comment", zap.String("commentID", commentID), zap.Error(updateErr), ) return } - if statsErr := s.applyCommentPublishedStats(commentID); statsErr != nil { - zap.L().Warn("Failed to apply published stats for comment", - zap.String("commentID", commentID), - zap.Error(statsErr), - ) - } + // 计数维护已在 UpdateModerationStatus 事务内完成,无需再单独调用 s.invalidatePostCaches(postID) s.afterCommentPublished(userID, postID, commentID, parentID, parentUserID, postOwnerID) } -func (s *commentService) applyCommentPublishedStats(commentID string) error { - comment, err := s.commentRepo.GetByID(commentID) - if err != nil { - return err - } - return s.commentRepo.ApplyPublishedStats(comment) -} - func (s *commentService) invalidatePostCaches(postID string) { cache.InvalidatePostDetail(s.cache, postID) cache.InvalidatePostList(s.cache) diff --git a/internal/service/report_service.go b/internal/service/report_service.go index 51c47f2..14e0ca4 100644 --- a/internal/service/report_service.go +++ b/internal/service/report_service.go @@ -188,7 +188,11 @@ func (s *reportServiceImpl) validateTargetExists(ctx context.Context, targetType return nil } -// hideTargetContent 隐藏目标内容 +// hideTargetContent 隐藏目标内容。 +// +// 评论隐藏统一走 commentRepo.UpdateModerationStatus(published → deleted), +// 经由仓储内的统一状态转换层维护 posts.comments_count / comments.replies_count, +// 避免此前用裸 Update 改 status 导致举报自动隐藏后计数漏减的问题。 func (s *reportServiceImpl) hideTargetContent(ctx context.Context, targetType model.ReportTargetType, targetID string) error { switch targetType { case model.ReportTargetTypePost: @@ -205,9 +209,13 @@ func (s *reportServiceImpl) hideTargetContent(ctx context.Context, targetType mo if err != nil { return err } - if comment != nil && comment.Status != model.CommentStatusDeleted { - comment.Status = model.CommentStatusDeleted - return s.commentRepo.Update(comment) + // 已是终态(deleted/rejected 等非 published)无需再隐藏,也避免重复调整计数 + if comment == nil || comment.Status != model.CommentStatusPublished { + return nil + } + // published → deleted:统一状态转换层会原子维护计数(永不为负) + if _, err := s.commentRepo.UpdateModerationStatus(targetID, model.CommentStatusDeleted, "举报自动隐藏", "system"); err != nil { + return err } case model.ReportTargetTypeMessage: // 消息隐藏通过更新状态实现 diff --git a/internal/wire/service.go b/internal/wire/service.go index 9bca189..b0b1739 100644 --- a/internal/wire/service.go +++ b/internal/wire/service.go @@ -350,8 +350,8 @@ func ProvideAdminPostService(postRepo repository.PostRepository, cacheBackend ca } // ProvideAdminCommentService 提供管理端评论服务 -func ProvideAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository) service.AdminCommentService { - return service.NewAdminCommentService(commentRepo, postRepo) +func ProvideAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, cacheBackend cache.Cache) service.AdminCommentService { + return service.NewAdminCommentService(commentRepo, postRepo, cacheBackend) } // ProvideAdminGroupService 提供管理端群组服务