2026-04-23 22:29:34 +08:00
|
|
|
|
package service
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-07-08 01:47:48 +08:00
|
|
|
|
"errors"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
"fmt"
|
2026-04-23 22:29:34 +08:00
|
|
|
|
"log"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
"strings"
|
|
|
|
|
|
|
2026-04-22 16:01:59 +08:00
|
|
|
|
"with_you/internal/cache"
|
2026-04-23 22:29:34 +08:00
|
|
|
|
"with_you/internal/dto"
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
apperrors "with_you/internal/errors"
|
2026-04-22 16:01:59 +08:00
|
|
|
|
"with_you/internal/model"
|
|
|
|
|
|
"with_you/internal/pkg/cursor"
|
|
|
|
|
|
"with_you/internal/pkg/hook"
|
|
|
|
|
|
"with_you/internal/repository"
|
2026-03-17 00:47:17 +08:00
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
// CommentService 评论服务接口
|
|
|
|
|
|
type CommentService interface {
|
|
|
|
|
|
Create(ctx context.Context, postID, userID, content string, segments model.MessageSegments, parentID *string, images string, imageURLs []string) (*model.Comment, error)
|
|
|
|
|
|
GetByID(ctx context.Context, id string) (*model.Comment, error)
|
|
|
|
|
|
GetByPostID(ctx context.Context, postID string, page, pageSize int) ([]*model.Comment, int64, error)
|
|
|
|
|
|
GetRepliesByRootID(ctx context.Context, rootID string, page, pageSize int) ([]*model.Comment, int64, error)
|
|
|
|
|
|
GetReplies(ctx context.Context, parentID string) ([]*model.Comment, error)
|
|
|
|
|
|
Update(ctx context.Context, userID string, comment *model.Comment) error
|
|
|
|
|
|
Delete(ctx context.Context, userID string, id string) error
|
|
|
|
|
|
Like(ctx context.Context, commentID, userID string) error
|
|
|
|
|
|
Unlike(ctx context.Context, commentID, userID string) error
|
|
|
|
|
|
IsLiked(ctx context.Context, commentID, userID string) bool
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type commentService struct {
|
2026-03-26 18:14:16 +08:00
|
|
|
|
commentRepo repository.CommentRepository
|
|
|
|
|
|
postRepo repository.PostRepository
|
2026-03-09 21:28:58 +08:00
|
|
|
|
systemMessageService SystemMessageService
|
2026-03-10 12:58:23 +08:00
|
|
|
|
cache cache.Cache
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
postAIService PostAIService
|
2026-03-15 02:25:10 +08:00
|
|
|
|
logService *LogService
|
2026-03-20 12:23:28 +08:00
|
|
|
|
hookManager *hook.Manager
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func NewCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, systemMessageService SystemMessageService, postAIService PostAIService, cacheBackend cache.Cache, hookManager *hook.Manager, logService *LogService) CommentService {
|
|
|
|
|
|
return &commentService{
|
2026-03-09 21:28:58 +08:00
|
|
|
|
commentRepo: commentRepo,
|
|
|
|
|
|
postRepo: postRepo,
|
|
|
|
|
|
systemMessageService: systemMessageService,
|
2026-03-13 09:38:18 +08:00
|
|
|
|
cache: cacheBackend,
|
2026-03-09 21:28:58 +08:00
|
|
|
|
postAIService: postAIService,
|
2026-03-28 07:03:21 +08:00
|
|
|
|
logService: logService,
|
2026-03-20 12:23:28 +08:00
|
|
|
|
hookManager: hookManager,
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create 创建评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) Create(ctx context.Context, postID, userID, content string, segments model.MessageSegments, parentID *string, images string, imageURLs []string) (*model.Comment, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if s.postAIService != nil {
|
|
|
|
|
|
// 采用异步审核,前端先立即返回
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取帖子信息用于发送通知
|
|
|
|
|
|
post, err := s.postRepo.GetByID(postID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
comment := &model.Comment{
|
|
|
|
|
|
PostID: postID,
|
|
|
|
|
|
UserID: userID,
|
|
|
|
|
|
Content: content,
|
2026-04-23 22:29:34 +08:00
|
|
|
|
Segments: segments,
|
2026-03-09 21:28:58 +08:00
|
|
|
|
ParentID: parentID,
|
|
|
|
|
|
Images: images,
|
|
|
|
|
|
Status: model.CommentStatusPending,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有父评论,设置根评论ID
|
|
|
|
|
|
var parentUserID string
|
|
|
|
|
|
if parentID != nil {
|
|
|
|
|
|
parent, err := s.commentRepo.GetByID(*parentID)
|
|
|
|
|
|
if err == nil && parent != nil {
|
|
|
|
|
|
if parent.RootID != nil {
|
|
|
|
|
|
comment.RootID = parent.RootID
|
|
|
|
|
|
} else {
|
|
|
|
|
|
comment.RootID = parentID
|
|
|
|
|
|
}
|
|
|
|
|
|
parentUserID = parent.UserID
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = s.commentRepo.Create(comment)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 02:25:10 +08:00
|
|
|
|
// 记录操作日志
|
|
|
|
|
|
if s.logService != nil {
|
|
|
|
|
|
s.logService.OperationLog.RecordOperation(&model.OperationLog{
|
|
|
|
|
|
UserID: userID,
|
|
|
|
|
|
Operation: string(model.OpCommentCreate),
|
|
|
|
|
|
Module: "comment",
|
|
|
|
|
|
TargetType: "comment",
|
|
|
|
|
|
TargetID: comment.ID,
|
|
|
|
|
|
Status: "success",
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 重新查询以获取关联的 User
|
|
|
|
|
|
comment, err = s.commentRepo.GetByID(comment.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 22:29:34 +08:00
|
|
|
|
// 异步处理 @提及通知
|
|
|
|
|
|
if len(segments) > 0 {
|
|
|
|
|
|
bgCtx := context.WithoutCancel(ctx)
|
|
|
|
|
|
go s.processCommentMentionNotifications(bgCtx, userID, postID, post.Title, segments)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
go s.reviewCommentAsync(comment.ID, userID, postID, content, imageURLs, parentID, parentUserID, post.UserID)
|
|
|
|
|
|
|
|
|
|
|
|
return comment, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 22:29:34 +08:00
|
|
|
|
// processCommentMentionNotifications 处理评论中 @提及的通知
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) processCommentMentionNotifications(ctx context.Context, authorID, postID, postTitle string, segments model.MessageSegments) {
|
2026-04-23 22:29:34 +08:00
|
|
|
|
if s.systemMessageService == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mentionedUserIDs := dto.ExtractMentionedUsers(segments)
|
|
|
|
|
|
if len(mentionedUserIDs) == 0 {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, targetUserID := range mentionedUserIDs {
|
|
|
|
|
|
if targetUserID == authorID {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := s.systemMessageService.SendMentionNotification(ctx, targetUserID, authorID, postID); err != nil {
|
|
|
|
|
|
log.Printf("[WARN] Failed to send comment mention notification to user %s: %v", targetUserID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) reviewCommentAsync(
|
2026-03-09 21:28:58 +08:00
|
|
|
|
commentID, userID, postID, content string,
|
|
|
|
|
|
imageURLs []string,
|
|
|
|
|
|
parentID *string,
|
|
|
|
|
|
parentUserID string,
|
|
|
|
|
|
postOwnerID string,
|
|
|
|
|
|
) {
|
2026-03-20 12:23:28 +08:00
|
|
|
|
if s.hookManager == nil {
|
2026-06-21 17:17:38 +08:00
|
|
|
|
if _, err := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", "system"); err != nil {
|
2026-03-20 12:23:28 +08:00
|
|
|
|
zap.L().Warn("Failed to publish comment without hook manager",
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-21 17:17:38 +08:00
|
|
|
|
// 计数维护已在 UpdateModerationStatus 事务内完成,无需再单独调用
|
2026-03-10 12:58:23 +08:00
|
|
|
|
s.invalidatePostCaches(postID)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
s.afterCommentPublished(userID, postID, commentID, parentID, parentUserID, postOwnerID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 12:23:28 +08:00
|
|
|
|
var authorID uint
|
|
|
|
|
|
fmt.Sscanf(userID, "%d", &authorID)
|
|
|
|
|
|
|
|
|
|
|
|
result := &hook.ModerationResult{}
|
2026-07-08 01:47:48 +08:00
|
|
|
|
triggerErr := s.hookManager.TriggerWithMetadata(context.Background(), hook.HookCommentPreModerate, authorID, &hook.CommentModerateHookData{
|
2026-03-20 12:23:28 +08:00
|
|
|
|
CommentID: commentID,
|
|
|
|
|
|
PostID: postID,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Images: imageURLs,
|
|
|
|
|
|
AuthorID: authorID,
|
|
|
|
|
|
ParentID: parentID,
|
2026-03-30 04:49:35 +08:00
|
|
|
|
}, map[string]any{
|
2026-03-20 12:23:28 +08:00
|
|
|
|
"result": result,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-08 01:47:48 +08:00
|
|
|
|
// Fallback-approve when a moderation hook times out or is cancelled, so a
|
|
|
|
|
|
// slow moderation API never silently rejects an otherwise-fine comment.
|
|
|
|
|
|
if triggerErr != nil && (errors.Is(triggerErr, context.DeadlineExceeded) || errors.Is(triggerErr, context.Canceled)) {
|
|
|
|
|
|
zap.L().Warn("Comment moderation hook timed out or was cancelled, fallback approve",
|
|
|
|
|
|
zap.String("comment_id", commentID),
|
|
|
|
|
|
zap.Error(triggerErr),
|
|
|
|
|
|
)
|
|
|
|
|
|
result.Approved = true
|
|
|
|
|
|
result.ReviewedBy = "system"
|
|
|
|
|
|
result.RejectReason = ""
|
|
|
|
|
|
result.NeedsReview = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 12:23:28 +08:00
|
|
|
|
s.hookManager.Trigger(context.Background(), hook.HookCommentModerated, authorID, &hook.CommentModeratedHookData{
|
|
|
|
|
|
CommentID: commentID,
|
|
|
|
|
|
PostID: postID,
|
|
|
|
|
|
AuthorID: authorID,
|
|
|
|
|
|
Approved: result.Approved,
|
|
|
|
|
|
RejectReason: result.RejectReason,
|
|
|
|
|
|
ReviewedBy: result.ReviewedBy,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if !result.Approved {
|
2026-04-11 04:23:24 +08:00
|
|
|
|
if result.NeedsReview {
|
|
|
|
|
|
s.invalidatePostCaches(postID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-03-20 12:23:28 +08:00
|
|
|
|
if delErr := s.commentRepo.Delete(commentID); delErr != nil {
|
|
|
|
|
|
zap.L().Warn("Failed to delete rejected comment",
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.String("commentID", commentID),
|
2026-03-20 12:23:28 +08:00
|
|
|
|
zap.Error(delErr),
|
2026-03-17 00:47:17 +08:00
|
|
|
|
)
|
2026-03-10 12:58:23 +08:00
|
|
|
|
}
|
2026-03-20 12:23:28 +08:00
|
|
|
|
s.notifyCommentModerationRejected(userID, result.RejectReason)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-21 17:17:38 +08:00
|
|
|
|
if _, updateErr := s.commentRepo.UpdateModerationStatus(commentID, model.CommentStatusPublished, "", result.ReviewedBy); updateErr != nil {
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Warn("Failed to publish comment",
|
|
|
|
|
|
zap.String("commentID", commentID),
|
|
|
|
|
|
zap.Error(updateErr),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-21 17:17:38 +08:00
|
|
|
|
// 计数维护已在 UpdateModerationStatus 事务内完成,无需再单独调用
|
2026-03-10 12:58:23 +08:00
|
|
|
|
s.invalidatePostCaches(postID)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
s.afterCommentPublished(userID, postID, commentID, parentID, parentUserID, postOwnerID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) invalidatePostCaches(postID string) {
|
2026-03-10 12:58:23 +08:00
|
|
|
|
cache.InvalidatePostDetail(s.cache, postID)
|
|
|
|
|
|
cache.InvalidatePostList(s.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) afterCommentPublished(userID, postID, commentID string, parentID *string, parentUserID, postOwnerID string) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 发送系统消息通知
|
|
|
|
|
|
if s.systemMessageService != nil {
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
if parentID != nil && parentUserID != "" {
|
|
|
|
|
|
// 回复评论,通知被回复的人
|
|
|
|
|
|
if parentUserID != userID {
|
|
|
|
|
|
notifyErr := s.systemMessageService.SendReplyNotification(context.Background(), parentUserID, userID, postID, *parentID, commentID)
|
|
|
|
|
|
if notifyErr != nil {
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Error("Error sending reply notification",
|
|
|
|
|
|
zap.Error(notifyErr),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 评论帖子,通知帖子作者
|
|
|
|
|
|
if postOwnerID != userID {
|
|
|
|
|
|
notifyErr := s.systemMessageService.SendCommentNotification(context.Background(), postOwnerID, userID, postID, commentID)
|
|
|
|
|
|
if notifyErr != nil {
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Error("Error sending comment notification",
|
|
|
|
|
|
zap.Error(notifyErr),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) notifyCommentModerationRejected(userID, reason string) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if s.systemMessageService == nil || strings.TrimSpace(userID) == "" {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
content := "您发布的评论未通过AI审核,请修改后重试。"
|
|
|
|
|
|
if strings.TrimSpace(reason) != "" {
|
|
|
|
|
|
content = fmt.Sprintf("您发布的评论未通过AI审核,原因:%s。请修改后重试。", reason)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
if err := s.systemMessageService.SendSystemAnnouncement(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
[]string{userID},
|
|
|
|
|
|
"评论审核未通过",
|
|
|
|
|
|
content,
|
|
|
|
|
|
); err != nil {
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Warn("Failed to send comment moderation reject notification",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByID 根据ID获取评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetByID(ctx context.Context, id string) (*model.Comment, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.GetByID(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetByPostID 获取帖子评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetByPostID(ctx context.Context, postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 使用带回复的查询,默认加载前3条回复
|
|
|
|
|
|
return s.commentRepo.GetByPostIDWithReplies(postID, page, pageSize, 3)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRepliesByRootID 根据根评论ID分页获取回复
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetRepliesByRootID(ctx context.Context, rootID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.GetRepliesByRootID(rootID, page, pageSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetReplies 获取回复
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetReplies(ctx context.Context, parentID string) ([]*model.Comment, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.GetReplies(parentID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update 更新评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) Update(ctx context.Context, userID string, comment *model.Comment) error {
|
2026-04-30 12:26:25 +08:00
|
|
|
|
if comment.UserID != userID {
|
|
|
|
|
|
return apperrors.ErrForbidden
|
|
|
|
|
|
}
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.Update(comment)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Delete 删除评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) Delete(ctx context.Context, userID string, id string) error {
|
2026-03-10 12:58:23 +08:00
|
|
|
|
comment, err := s.commentRepo.GetByID(id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-04-30 12:26:25 +08:00
|
|
|
|
if comment.UserID != userID {
|
|
|
|
|
|
return apperrors.ErrForbidden
|
|
|
|
|
|
}
|
2026-03-10 12:58:23 +08:00
|
|
|
|
if err := s.commentRepo.Delete(id); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
s.invalidatePostCaches(comment.PostID)
|
|
|
|
|
|
return nil
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Like 点赞评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) Like(ctx context.Context, commentID, userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 获取评论信息用于发送通知
|
|
|
|
|
|
comment, err := s.commentRepo.GetByID(commentID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = s.commentRepo.Like(commentID, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送评论/回复点赞通知(只有不是给自己点赞时才发送)
|
|
|
|
|
|
if s.systemMessageService != nil && comment.UserID != userID {
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
var notifyErr error
|
|
|
|
|
|
if comment.ParentID != nil {
|
|
|
|
|
|
notifyErr = s.systemMessageService.SendLikeReplyNotification(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
comment.UserID,
|
|
|
|
|
|
userID,
|
|
|
|
|
|
comment.PostID,
|
|
|
|
|
|
commentID,
|
|
|
|
|
|
comment.Content,
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
notifyErr = s.systemMessageService.SendLikeCommentNotification(
|
|
|
|
|
|
context.Background(),
|
|
|
|
|
|
comment.UserID,
|
|
|
|
|
|
userID,
|
|
|
|
|
|
comment.PostID,
|
|
|
|
|
|
commentID,
|
|
|
|
|
|
comment.Content,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notifyErr != nil {
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Error("Error sending like notification",
|
|
|
|
|
|
zap.Error(notifyErr),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unlike 取消点赞评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) Unlike(ctx context.Context, commentID, userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.Unlike(commentID, userID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsLiked 检查是否已点赞
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) IsLiked(ctx context.Context, commentID, userID string) bool {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.commentRepo.IsLiked(commentID, userID)
|
|
|
|
|
|
}
|
feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval.
Changes:
- Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement)
- Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq)
- Add cursor pagination handlers that maintain backward compatibility with existing offset pagination
- Add new API routes for cursor-based endpoints (/cursor suffix)
- Add helper converter functions for pointer slice types in DTO conversions
2026-03-20 23:03:23 +08:00
|
|
|
|
|
|
|
|
|
|
// GetCommentsByCursor 游标分页获取帖子评论
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval.
Changes:
- Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement)
- Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq)
- Add cursor pagination handlers that maintain backward compatibility with existing offset pagination
- Add new API routes for cursor-based endpoints (/cursor suffix)
- Add helper converter functions for pointer slice types in DTO conversions
2026-03-20 23:03:23 +08:00
|
|
|
|
return s.commentRepo.GetCommentsByCursor(ctx, postID, req)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRepliesByCursor 游标分页获取根评论的回复
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *commentService) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval.
Changes:
- Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement)
- Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq)
- Add cursor pagination handlers that maintain backward compatibility with existing offset pagination
- Add new API routes for cursor-based endpoints (/cursor suffix)
- Add helper converter functions for pointer slice types in DTO conversions
2026-03-20 23:03:23 +08:00
|
|
|
|
return s.commentRepo.GetRepliesByCursor(ctx, rootID, req)
|
|
|
|
|
|
}
|