- Added Report and AdminReport handlers to manage report-related functionalities. - Updated router and wire generation to include new report services and handlers. - Enhanced error handling in report and admin report handlers to return appropriate status codes. - Refactored notification logic in admin report service to utilize a dedicated notification repository and push service.
470 lines
13 KiB
Go
470 lines
13 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
|
||
"carrot_bbs/internal/dto"
|
||
"carrot_bbs/internal/model"
|
||
"carrot_bbs/internal/repository"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// AdminReportService 管理端举报服务接口
|
||
type AdminReportService interface {
|
||
// GetReportList 获取举报列表
|
||
GetReportList(ctx context.Context, query dto.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 dto.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)
|
||
} |