Files
backend/internal/service/report_service.go
lafay 5f7b02ee8e
Some checks failed
Build Backend / build (push) Failing after 5m19s
Build Backend / build-docker (push) Has been skipped
feat: 添加举报功能支持
- 新增 Report 数据模型、DTO、Repository、Service 层
- 实现用户端举报 API (POST /api/v1/reports)
- 实现管理端举报管理 API (列表、详情、处理、批量处理)
- 添加举报自动隐藏机制(达到阈值自动隐藏内容)
- 集成系统通知(举报处理结果通知)
- 更新路由和 Wire 依赖注入配置

Made-with: Cursor
2026-03-29 20:18:36 +08:00

213 lines
6.5 KiB
Go

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
}