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) }