Files
backend/internal/service/admin_report_service.go

472 lines
13 KiB
Go
Raw Normal View History

package service
import (
"context"
"errors"
"fmt"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/query"
"with_you/internal/repository"
"go.uber.org/zap"
)
// AdminReportService 管理端举报服务接口
type AdminReportService interface {
// GetReportList 获取举报列表
GetReportList(ctx context.Context, query query.AdminReportListQuery) ([]dto.AdminReportListResponse, int64, error)
// GetReportDetail 获取举报详情
GetReportDetail(ctx context.Context, id string) (*dto.AdminReportDetailResponse, error)
// HandleReport 处理举报
HandleReport(ctx context.Context, id string, action, result, handledBy string) error
// BatchHandle 批量处理举报
BatchHandle(ctx context.Context, ids []string, action, result, handledBy string) (*dto.AdminBatchHandleResponse, error)
}
// adminReportServiceImpl 管理端举报服务实现
type adminReportServiceImpl struct {
reportRepo repository.ReportRepository
postRepo repository.PostRepository
commentRepo repository.CommentRepository
messageRepo repository.MessageRepository
userRepo repository.UserRepository
notifyRepo repository.SystemNotificationRepository
pushService PushService
txManager repository.TransactionManager
logService OperationLogService
}
// NewAdminReportService 创建管理端举报服务
func NewAdminReportService(
reportRepo repository.ReportRepository,
postRepo repository.PostRepository,
commentRepo repository.CommentRepository,
messageRepo repository.MessageRepository,
userRepo repository.UserRepository,
notifyRepo repository.SystemNotificationRepository,
pushService PushService,
txManager repository.TransactionManager,
logService OperationLogService,
) AdminReportService {
return &adminReportServiceImpl{
reportRepo: reportRepo,
postRepo: postRepo,
commentRepo: commentRepo,
messageRepo: messageRepo,
userRepo: userRepo,
notifyRepo: notifyRepo,
pushService: pushService,
txManager: txManager,
logService: logService,
}
}
// GetReportList 获取举报列表
func (s *adminReportServiceImpl) GetReportList(ctx context.Context, query query.AdminReportListQuery) ([]dto.AdminReportListResponse, int64, error) {
// 设置默认分页参数
if query.Page <= 0 {
query.Page = 1
}
if query.PageSize <= 0 {
query.PageSize = 20
}
// 查询举报列表
reports, total, err := s.reportRepo.List(query)
if err != nil {
zap.L().Error("failed to get report list", zap.Error(err))
return nil, 0, errors.New("failed to get report list")
}
// 收集用户ID
userIDs := make(map[string]bool)
for _, report := range reports {
userIDs[report.ReporterID] = true
if report.HandledBy != nil {
userIDs[*report.HandledBy] = true
}
}
// 批量获取用户信息
users, err := s.getUsersByIDs(ctx, userIDs)
if err != nil {
zap.L().Error("failed to get users", zap.Error(err))
}
// 转换响应
responses := make([]dto.AdminReportListResponse, len(reports))
for i, report := range reports {
var reporter, handler *model.User
if user, ok := users[report.ReporterID]; ok {
reporter = user
}
if report.HandledBy != nil {
if user, ok := users[*report.HandledBy]; ok {
handler = user
}
}
responses[i] = *dto.ConvertReportToAdminListResponse(report, reporter, handler)
}
return responses, total, nil
}
// GetReportDetail 获取举报详情
func (s *adminReportServiceImpl) GetReportDetail(ctx context.Context, id string) (*dto.AdminReportDetailResponse, error) {
// 查询举报
report, err := s.reportRepo.FindByID(id)
if err != nil {
zap.L().Error("failed to get report", zap.Error(err), zap.String("id", id))
return nil, errors.New("report not found")
}
// 获取举报人信息
var reporter *model.User
if user, err := s.userRepo.GetByID(report.ReporterID); err == nil {
reporter = user
}
// 获取处理人信息
var handler *model.User
if report.HandledBy != nil {
if user, err := s.userRepo.GetByID(*report.HandledBy); err == nil {
handler = user
}
}
// 获取被举报内容详情
targetContent, err := s.getTargetContent(ctx, report.TargetType, report.TargetID)
if err != nil {
zap.L().Warn("failed to get target content", zap.Error(err))
}
return dto.ConvertReportToAdminDetailResponse(report, reporter, handler, targetContent), nil
}
// HandleReport 处理举报
func (s *adminReportServiceImpl) HandleReport(ctx context.Context, id string, action, result, handledBy string) error {
// 查询举报
report, err := s.reportRepo.FindByID(id)
if err != nil {
return errors.New("report not found")
}
// 检查状态
if report.Status != model.ReportStatusPending && report.Status != model.ReportStatusProcessing {
return errors.New("report has already been handled")
}
// 确定新状态
var newStatus model.ReportStatus
if action == "approve" {
newStatus = model.ReportStatusResolved
} else {
newStatus = model.ReportStatusRejected
}
// 使用事务处理
err = s.txManager.RunInTransaction(ctx, func(ctx context.Context) error {
// 更新举报状态
if err := s.reportRepo.UpdateStatusWithContext(ctx, id, newStatus, handledBy, result); err != nil {
return fmt.Errorf("failed to update report status: %w", err)
}
// 如果确认违规,删除内容
if action == "approve" {
if err := s.deleteTargetContent(ctx, report.TargetType, report.TargetID); err != nil {
zap.L().Error("failed to delete target content", zap.Error(err))
// 不阻止举报处理
}
}
return nil
})
if err != nil {
return err
}
// 发送通知给举报人
s.notifyReporter(ctx, report, action, result)
// 记录操作日志
if s.logService != nil {
s.logService.RecordOperation(&model.OperationLog{
UserID: handledBy,
Operation: "handle_report",
TargetType: "report",
TargetID: id,
Module: "report",
})
}
return nil
}
// BatchHandle 批量处理举报
func (s *adminReportServiceImpl) BatchHandle(ctx context.Context, ids []string, action, result, handledBy string) (*dto.AdminBatchHandleResponse, error) {
// 确定新状态
var newStatus model.ReportStatus
if action == "approve" {
newStatus = model.ReportStatusResolved
} else {
newStatus = model.ReportStatusRejected
}
response := &dto.AdminBatchHandleResponse{
FailedIDs: []string{},
}
// 使用事务处理
err := s.txManager.RunInTransaction(ctx, func(ctx context.Context) error {
// 获取举报列表
reports, err := s.reportRepo.GetByIDsWithContext(ctx, ids)
if err != nil {
return fmt.Errorf("failed to get reports: %w", err)
}
// 过滤出可以处理的举报
var validIDs []string
for _, report := range reports {
if report.Status == model.ReportStatusPending || report.Status == model.ReportStatusProcessing {
validIDs = append(validIDs, report.ID)
} else {
response.FailedIDs = append(response.FailedIDs, report.ID)
response.FailedCount++
}
}
if len(validIDs) == 0 {
return nil
}
// 批量更新状态
affected, err := s.reportRepo.BatchUpdateStatusWithContext(ctx, validIDs, newStatus, handledBy, result)
if err != nil {
return fmt.Errorf("failed to batch update reports: %w", err)
}
response.SuccessCount = int(affected)
// 如果确认违规,批量删除内容
if action == "approve" {
for _, report := range reports {
if containsID(validIDs, report.ID) {
if err := s.deleteTargetContent(ctx, report.TargetType, report.TargetID); err != nil {
zap.L().Error("failed to delete target content",
zap.Error(err),
zap.String("target_type", string(report.TargetType)),
zap.String("target_id", report.TargetID),
)
}
}
}
}
// 发送通知给举报人
for _, report := range reports {
if containsID(validIDs, report.ID) {
s.notifyReporter(ctx, report, action, result)
}
}
return nil
})
if err != nil {
return nil, err
}
// 记录操作日志
if s.logService != nil {
s.logService.RecordOperation(&model.OperationLog{
UserID: handledBy,
Operation: "batch_handle_reports",
TargetType: "report",
TargetID: "",
Module: "report",
})
}
return response, nil
}
// getUsersByIDs 批量获取用户
func (s *adminReportServiceImpl) getUsersByIDs(ctx context.Context, userIDs map[string]bool) (map[string]*model.User, error) {
users := make(map[string]*model.User)
for id := range userIDs {
user, err := s.userRepo.GetByID(id)
if err == nil {
users[id] = user
}
}
return users, nil
}
// getTargetContent 获取被举报内容详情
func (s *adminReportServiceImpl) getTargetContent(ctx context.Context, targetType model.ReportTargetType, targetID string) (*dto.ReportTargetContent, error) {
content := &dto.ReportTargetContent{
Type: string(targetType),
ID: targetID,
}
switch targetType {
case model.ReportTargetTypePost:
post, err := s.postRepo.GetByIDForAdmin(targetID)
if err != nil {
return nil, err
}
content.Title = post.Title
content.Content = post.Content
content.Status = string(post.Status)
content.CreatedAt = dto.FormatTime(post.CreatedAt)
if post.User != nil {
content.Author = dto.ConvertUserToResponse(post.User)
}
if len(post.Images) > 0 {
images := make([]string, len(post.Images))
for i, img := range post.Images {
images[i] = img.URL
}
content.Images = images
}
case model.ReportTargetTypeComment:
comment, err := s.commentRepo.GetByID(targetID)
if err != nil {
return nil, err
}
content.Content = comment.Content
content.Status = string(comment.Status)
content.CreatedAt = dto.FormatTime(comment.CreatedAt)
if comment.User != nil {
content.Author = dto.ConvertUserToResponse(comment.User)
}
case model.ReportTargetTypeMessage:
msg, err := s.messageRepo.GetMessageByID(targetID)
if err != nil {
return nil, err
}
// 从消息段中提取文本内容
contentText := extractTextFromSegments(msg.Segments)
content.Content = contentText
content.Status = string(msg.Status)
content.CreatedAt = dto.FormatTime(msg.CreatedAt)
// 获取发送者信息
if msg.SenderID != "" {
if sender, err := s.userRepo.GetByID(msg.SenderID); err == nil {
content.Author = convertUserToBrief(sender)
}
}
}
return content, nil
}
// deleteTargetContent 删除目标内容
func (s *adminReportServiceImpl) deleteTargetContent(ctx context.Context, targetType model.ReportTargetType, targetID string) error {
switch targetType {
case model.ReportTargetTypePost:
return s.postRepo.Delete(targetID)
case model.ReportTargetTypeComment:
return s.commentRepo.Delete(targetID)
case model.ReportTargetTypeMessage:
return s.messageRepo.UpdateMessageStatus(targetID, model.MessageStatusDeleted)
}
return nil
}
// notifyReporter 通知举报人
func (s *adminReportServiceImpl) notifyReporter(ctx context.Context, report *model.Report, action, result string) {
if s.notifyRepo == nil {
return
}
var title, content string
var notifyType model.SystemNotificationType
if action == "approve" {
notifyType = model.SysNotifyReportResolved
title = "举报处理结果"
content = fmt.Sprintf("您举报的%s已确认违规内容已被删除。", s.getTargetTypeName(report.TargetType))
if result != "" {
content += fmt.Sprintf(" 处理说明:%s", result)
}
} else {
notifyType = model.SysNotifyReportRejected
title = "举报处理结果"
content = fmt.Sprintf("您举报的%s经审核未发现违规举报已驳回。", s.getTargetTypeName(report.TargetType))
if result != "" {
content += fmt.Sprintf(" 处理说明:%s", result)
}
}
// 创建通知
notification := &model.SystemNotification{
ReceiverID: report.ReporterID,
Type: notifyType,
Title: title,
Content: content,
ExtraData: &model.SystemNotificationExtra{
ActorIDStr: report.ReporterID,
TargetID: report.TargetID,
TargetType: string(report.TargetType),
},
}
if err := s.notifyRepo.Create(notification); err != nil {
zap.L().Error("failed to create notification", zap.Error(err))
}
// 推送通知
if s.pushService != nil {
s.pushService.PushSystemNotification(ctx, report.ReporterID, notification)
}
}
// getTargetTypeName 获取目标类型名称
func (s *adminReportServiceImpl) getTargetTypeName(targetType model.ReportTargetType) string {
switch targetType {
case model.ReportTargetTypePost:
return "帖子"
case model.ReportTargetTypeComment:
return "评论"
case model.ReportTargetTypeMessage:
return "消息"
}
return "内容"
}
// containsID 检查ID是否在列表中
func containsID(ids []string, id string) bool {
for _, v := range ids {
if v == id {
return true
}
}
return false
}
// extractTextFromSegments 从消息段中提取文本内容
func extractTextFromSegments(segments model.MessageSegments) string {
var text string
for _, seg := range segments {
if seg.Type == "text" {
if content, ok := seg.Data["text"].(string); ok {
text += content
}
}
}
return text
}
// convertUserToBrief 将用户模型转换为举报用的简要信息
func convertUserToBrief(user *model.User) *dto.UserResponse {
if user == nil {
return nil
}
return dto.ConvertUserToResponse(user)
}