Files
backend/internal/service/admin_report_service.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

472 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}