feat: 添加举报功能支持
- 新增 Report 数据模型、DTO、Repository、Service 层 - 实现用户端举报 API (POST /api/v1/reports) - 实现管理端举报管理 API (列表、详情、处理、批量处理) - 添加举报自动隐藏机制(达到阈值自动隐藏内容) - 集成系统通知(举报处理结果通知) - 更新路由和 Wire 依赖注入配置 Made-with: Cursor
This commit is contained in:
213
internal/service/report_service.go
Normal file
213
internal/service/report_service.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"carrot_bbs/internal/config"
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ReportService 举报服务接口(用户端)
|
||||
type ReportService interface {
|
||||
// CreateReport 创建举报
|
||||
CreateReport(ctx context.Context, reporterID, targetType, targetID, reason, description string) (*model.Report, error)
|
||||
// HasUserReported 检查用户是否已举报
|
||||
HasUserReported(ctx context.Context, reporterID, targetType, targetID string) (bool, error)
|
||||
}
|
||||
|
||||
// reportServiceImpl 举报服务实现
|
||||
type reportServiceImpl struct {
|
||||
reportRepo repository.ReportRepository
|
||||
postRepo repository.PostRepository
|
||||
commentRepo repository.CommentRepository
|
||||
messageRepo repository.MessageRepository
|
||||
userRepo repository.UserRepository
|
||||
systemNotify SystemNotificationService
|
||||
txManager repository.TransactionManager
|
||||
logService *LogService
|
||||
config *config.ReportConfig
|
||||
}
|
||||
|
||||
// NewReportService 创建举报服务
|
||||
func NewReportService(
|
||||
reportRepo repository.ReportRepository,
|
||||
postRepo repository.PostRepository,
|
||||
commentRepo repository.CommentRepository,
|
||||
messageRepo repository.MessageRepository,
|
||||
userRepo repository.UserRepository,
|
||||
systemNotify SystemNotificationService,
|
||||
txManager repository.TransactionManager,
|
||||
logService *LogService,
|
||||
cfg *config.Config,
|
||||
) ReportService {
|
||||
reportConfig := &cfg.Report
|
||||
if reportConfig.AutoHideThreshold == 0 {
|
||||
reportConfig.AutoHideThreshold = 3
|
||||
}
|
||||
return &reportServiceImpl{
|
||||
reportRepo: reportRepo,
|
||||
postRepo: postRepo,
|
||||
commentRepo: commentRepo,
|
||||
messageRepo: messageRepo,
|
||||
userRepo: userRepo,
|
||||
systemNotify: systemNotify,
|
||||
txManager: txManager,
|
||||
logService: logService,
|
||||
config: reportConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateReport 创建举报
|
||||
func (s *reportServiceImpl) CreateReport(ctx context.Context, reporterID, targetType, targetID, reason, description string) (*model.Report, error) {
|
||||
// 验证举报类型
|
||||
reportTargetType := model.ReportTargetType(targetType)
|
||||
if reportTargetType != model.ReportTargetTypePost &&
|
||||
reportTargetType != model.ReportTargetTypeComment &&
|
||||
reportTargetType != model.ReportTargetTypeMessage {
|
||||
return nil, errors.New("invalid target type")
|
||||
}
|
||||
|
||||
// 验证举报原因
|
||||
reportReason := model.ReportReason(reason)
|
||||
if reportReason != model.ReportReasonSpam &&
|
||||
reportReason != model.ReportReasonInappropriate &&
|
||||
reportReason != model.ReportReasonHarassment &&
|
||||
reportReason != model.ReportReasonMisinformation &&
|
||||
reportReason != model.ReportReasonOther {
|
||||
return nil, errors.New("invalid reason")
|
||||
}
|
||||
|
||||
// 检查目标内容是否存在
|
||||
if err := s.validateTargetExists(ctx, reportTargetType, targetID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查用户是否已举报过
|
||||
hasReported, err := s.reportRepo.HasUserReported(reporterID, reportTargetType, targetID)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to check if user has reported", zap.Error(err))
|
||||
return nil, errors.New("failed to check report status")
|
||||
}
|
||||
if hasReported {
|
||||
return nil, errors.New("you have already reported this content")
|
||||
}
|
||||
|
||||
// 创建举报
|
||||
report := &model.Report{
|
||||
ReporterID: reporterID,
|
||||
TargetType: reportTargetType,
|
||||
TargetID: targetID,
|
||||
Reason: reportReason,
|
||||
Description: description,
|
||||
Status: model.ReportStatusPending,
|
||||
}
|
||||
|
||||
// 使用事务处理
|
||||
var createdReport *model.Report
|
||||
err = s.txManager.RunInTransaction(ctx, func(ctx context.Context) error {
|
||||
// 创建举报记录
|
||||
if err := s.reportRepo.CreateWithContext(ctx, report); err != nil {
|
||||
return fmt.Errorf("failed to create report: %w", err)
|
||||
}
|
||||
|
||||
createdReport = report
|
||||
|
||||
// 检查举报次数是否达到阈值
|
||||
count, err := s.reportRepo.GetReportCount(reportTargetType, targetID)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get report count", zap.Error(err))
|
||||
return nil // 不阻止举报创建
|
||||
}
|
||||
|
||||
// 达到阈值,自动隐藏内容
|
||||
if int(count) >= s.config.AutoHideThreshold {
|
||||
if err := s.hideTargetContent(ctx, reportTargetType, targetID); err != nil {
|
||||
zap.L().Error("failed to hide target content", zap.Error(err))
|
||||
return nil // 不阻止举报创建
|
||||
}
|
||||
zap.L().Info("content auto-hidden due to report threshold",
|
||||
zap.String("target_type", targetType),
|
||||
zap.String("target_id", targetID),
|
||||
zap.Int64("report_count", count),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
if s.logService != nil {
|
||||
s.logService.LogOperation(ctx, "create_report", "report", report.ID, reporterID, map[string]interface{}{
|
||||
"target_type": targetType,
|
||||
"target_id": targetID,
|
||||
"reason": reason,
|
||||
})
|
||||
}
|
||||
|
||||
return createdReport, nil
|
||||
}
|
||||
|
||||
// HasUserReported 检查用户是否已举报
|
||||
func (s *reportServiceImpl) HasUserReported(ctx context.Context, reporterID, targetType, targetID string) (bool, error) {
|
||||
return s.reportRepo.HasUserReported(reporterID, model.ReportTargetType(targetType), targetID)
|
||||
}
|
||||
|
||||
// validateTargetExists 验证目标内容是否存在
|
||||
func (s *reportServiceImpl) validateTargetExists(ctx context.Context, targetType model.ReportTargetType, targetID string) error {
|
||||
switch targetType {
|
||||
case model.ReportTargetTypePost:
|
||||
post, err := s.postRepo.GetByID(targetID)
|
||||
if err != nil || post == nil {
|
||||
return errors.New("post not found")
|
||||
}
|
||||
case model.ReportTargetTypeComment:
|
||||
comment, err := s.commentRepo.GetByID(targetID)
|
||||
if err != nil || comment == nil {
|
||||
return errors.New("comment not found")
|
||||
}
|
||||
case model.ReportTargetTypeMessage:
|
||||
_, err := s.messageRepo.GetMessageByID(targetID)
|
||||
if err != nil {
|
||||
return errors.New("message not found")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hideTargetContent 隐藏目标内容
|
||||
func (s *reportServiceImpl) hideTargetContent(ctx context.Context, targetType model.ReportTargetType, targetID string) error {
|
||||
switch targetType {
|
||||
case model.ReportTargetTypePost:
|
||||
post, err := s.postRepo.GetByID(targetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if post != nil && post.Status != model.PostStatusDeleted {
|
||||
post.Status = model.PostStatusDeleted
|
||||
return s.postRepo.Update(post)
|
||||
}
|
||||
case model.ReportTargetTypeComment:
|
||||
comment, err := s.commentRepo.GetByID(targetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if comment != nil && comment.Status != model.CommentStatusDeleted {
|
||||
comment.Status = model.CommentStatusDeleted
|
||||
return s.commentRepo.Update(comment)
|
||||
}
|
||||
case model.ReportTargetTypeMessage:
|
||||
// 消息隐藏通过更新状态实现
|
||||
return s.messageRepo.UpdateMessageStatus(targetID, model.MessageStatusDeleted)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user