feat: 添加举报功能支持
- 新增 Report 数据模型、DTO、Repository、Service 层 - 实现用户端举报 API (POST /api/v1/reports) - 实现管理端举报管理 API (列表、详情、处理、批量处理) - 添加举报自动隐藏机制(达到阈值自动隐藏内容) - 集成系统通知(举报处理结果通知) - 更新路由和 Wire 依赖注入配置 Made-with: Cursor
This commit is contained in:
@@ -244,3 +244,8 @@ casbin:
|
|||||||
- /api/v1/auth/password/reset
|
- /api/v1/auth/password/reset
|
||||||
- /api/v1/auth/refresh
|
- /api/v1/auth/refresh
|
||||||
- /api/v1/admin/setup-super-admin
|
- /api/v1/admin/setup-super-admin
|
||||||
|
|
||||||
|
# 举报配置
|
||||||
|
# 环境变量: APP_REPORT_AUTO_HIDE_THRESHOLD
|
||||||
|
report:
|
||||||
|
auto_hide_threshold: 3 # 自动隐藏阈值,举报次数达到此数值时自动隐藏内容
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type Config struct {
|
|||||||
Encryption EncryptionConfig `mapstructure:"encryption"`
|
Encryption EncryptionConfig `mapstructure:"encryption"`
|
||||||
HotRank HotRankConfig `mapstructure:"hot_rank"`
|
HotRank HotRankConfig `mapstructure:"hot_rank"`
|
||||||
WebRTC WebRTCConfig `mapstructure:"webrtc"`
|
WebRTC WebRTCConfig `mapstructure:"webrtc"`
|
||||||
|
Report ReportConfig `mapstructure:"report"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load 加载配置文件
|
// Load 加载配置文件
|
||||||
@@ -155,6 +156,8 @@ func Load(configPath string) (*Config, error) {
|
|||||||
viper.SetDefault("webrtc.ice_servers", []map[string]interface{}{
|
viper.SetDefault("webrtc.ice_servers", []map[string]interface{}{
|
||||||
{"urls": []string{"stun:stun.l.google.com:19302"}},
|
{"urls": []string{"stun:stun.l.google.com:19302"}},
|
||||||
})
|
})
|
||||||
|
// Report 默认值
|
||||||
|
viper.SetDefault("report.auto_hide_threshold", 3)
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to read config: %w", err)
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
||||||
|
|||||||
7
internal/config/report.go
Normal file
7
internal/config/report.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
// ReportConfig 举报配置
|
||||||
|
type ReportConfig struct {
|
||||||
|
// AutoHideThreshold 自动隐藏阈值(举报次数达到此数值时自动隐藏内容)
|
||||||
|
AutoHideThreshold int `mapstructure:"auto_hide_threshold"`
|
||||||
|
}
|
||||||
187
internal/dto/report.go
Normal file
187
internal/dto/report.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"carrot_bbs/internal/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 举报 DTOs ====================
|
||||||
|
|
||||||
|
// CreateReportRequest 创建举报请求
|
||||||
|
type CreateReportRequest struct {
|
||||||
|
TargetType string `json:"target_type" binding:"required,oneof=post comment message"`
|
||||||
|
TargetID string `json:"target_id" binding:"required"`
|
||||||
|
Reason string `json:"reason" binding:"required,oneof=spam inappropriate harassment misinformation other"`
|
||||||
|
Description string `json:"description" binding:"max=500"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportResponse 举报响应
|
||||||
|
type ReportResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ReporterID string `json:"reporter_id"`
|
||||||
|
TargetType string `json:"target_type"`
|
||||||
|
TargetID string `json:"target_id"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminReportListQuery 管理端举报列表查询参数
|
||||||
|
type AdminReportListQuery struct {
|
||||||
|
Page int `form:"page" binding:"min=1"`
|
||||||
|
PageSize int `form:"page_size" binding:"min=1,max=100"`
|
||||||
|
TargetType string `form:"target_type" binding:"omitempty,oneof=post comment message"`
|
||||||
|
Status string `form:"status" binding:"omitempty,oneof=pending processing resolved rejected"`
|
||||||
|
StartDate string `form:"start_date" binding:"omitempty"`
|
||||||
|
EndDate string `form:"end_date" binding:"omitempty"`
|
||||||
|
Keyword string `form:"keyword" binding:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminReportListResponse 管理端举报列表响应
|
||||||
|
type AdminReportListResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ReporterID string `json:"reporter_id"`
|
||||||
|
Reporter *UserResponse `json:"reporter,omitempty"`
|
||||||
|
TargetType string `json:"target_type"`
|
||||||
|
TargetID string `json:"target_id"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
HandledBy *string `json:"handled_by,omitempty"`
|
||||||
|
Handler *UserResponse `json:"handler,omitempty"`
|
||||||
|
HandledAt *string `json:"handled_at,omitempty"`
|
||||||
|
Result *string `json:"result,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminReportDetailResponse 管理端举报详情响应
|
||||||
|
type AdminReportDetailResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ReporterID string `json:"reporter_id"`
|
||||||
|
Reporter *UserResponse `json:"reporter"`
|
||||||
|
TargetType string `json:"target_type"`
|
||||||
|
TargetID string `json:"target_id"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
HandledBy *string `json:"handled_by,omitempty"`
|
||||||
|
Handler *UserResponse `json:"handler,omitempty"`
|
||||||
|
HandledAt *string `json:"handled_at,omitempty"`
|
||||||
|
Result *string `json:"result,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
|
||||||
|
// 被举报内容详情
|
||||||
|
TargetContent *ReportTargetContent `json:"target_content,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportTargetContent 被举报内容详情
|
||||||
|
type ReportTargetContent struct {
|
||||||
|
Type string `json:"type"` // post, comment, message
|
||||||
|
ID string `json:"id"`
|
||||||
|
Author *UserResponse `json:"author,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"` // 帖子标题
|
||||||
|
Images []string `json:"images,omitempty"` // 图片列表
|
||||||
|
Status string `json:"status,omitempty"` // 内容状态
|
||||||
|
CreatedAt string `json:"created_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleReportRequest 处理举报请求
|
||||||
|
type HandleReportRequest struct {
|
||||||
|
Action string `json:"action" binding:"required,oneof=approve reject"` // approve: 确认违规, reject: 驳回
|
||||||
|
Result string `json:"result" binding:"max=500"` // 处理结果说明
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchHandleReportRequest 批量处理举报请求
|
||||||
|
type BatchHandleReportRequest struct {
|
||||||
|
IDs []string `json:"ids" binding:"required,min=1,max=100"`
|
||||||
|
Action string `json:"action" binding:"required,oneof=approve reject"`
|
||||||
|
Result string `json:"result" binding:"max=500"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminBatchHandleResponse 批量处理响应
|
||||||
|
type AdminBatchHandleResponse struct {
|
||||||
|
SuccessCount int `json:"success_count"`
|
||||||
|
FailedCount int `json:"failed_count"`
|
||||||
|
FailedIDs []string `json:"failed_ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 转换函数 ====================
|
||||||
|
|
||||||
|
// ConvertReportToResponse 将 Report 模型转换为响应
|
||||||
|
func ConvertReportToResponse(report *model.Report) *ReportResponse {
|
||||||
|
return &ReportResponse{
|
||||||
|
ID: report.ID,
|
||||||
|
ReporterID: report.ReporterID,
|
||||||
|
TargetType: string(report.TargetType),
|
||||||
|
TargetID: report.TargetID,
|
||||||
|
Reason: string(report.Reason),
|
||||||
|
Description: report.Description,
|
||||||
|
Status: string(report.Status),
|
||||||
|
CreatedAt: FormatTime(report.CreatedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertReportToAdminListResponse 将 Report 转换为管理端列表响应
|
||||||
|
func ConvertReportToAdminListResponse(report *model.Report, reporter *model.User, handler *model.User) *AdminReportListResponse {
|
||||||
|
resp := &AdminReportListResponse{
|
||||||
|
ID: report.ID,
|
||||||
|
ReporterID: report.ReporterID,
|
||||||
|
TargetType: string(report.TargetType),
|
||||||
|
TargetID: report.TargetID,
|
||||||
|
Reason: string(report.Reason),
|
||||||
|
Description: report.Description,
|
||||||
|
Status: string(report.Status),
|
||||||
|
HandledBy: report.HandledBy,
|
||||||
|
Result: report.Result,
|
||||||
|
CreatedAt: FormatTime(report.CreatedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
if reporter != nil {
|
||||||
|
resp.Reporter = ConvertUserToResponse(reporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler != nil {
|
||||||
|
resp.Handler = ConvertUserToResponse(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if report.HandledAt != nil {
|
||||||
|
handledAt := FormatTime(*report.HandledAt)
|
||||||
|
resp.HandledAt = &handledAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertReportToAdminDetailResponse 将 Report 转换为管理端详情响应
|
||||||
|
func ConvertReportToAdminDetailResponse(report *model.Report, reporter *model.User, handler *model.User, targetContent *ReportTargetContent) *AdminReportDetailResponse {
|
||||||
|
resp := &AdminReportDetailResponse{
|
||||||
|
ID: report.ID,
|
||||||
|
ReporterID: report.ReporterID,
|
||||||
|
TargetType: string(report.TargetType),
|
||||||
|
TargetID: report.TargetID,
|
||||||
|
Reason: string(report.Reason),
|
||||||
|
Description: report.Description,
|
||||||
|
Status: string(report.Status),
|
||||||
|
HandledBy: report.HandledBy,
|
||||||
|
Result: report.Result,
|
||||||
|
CreatedAt: FormatTime(report.CreatedAt),
|
||||||
|
TargetContent: targetContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if reporter != nil {
|
||||||
|
resp.Reporter = ConvertUserToResponse(reporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler != nil {
|
||||||
|
resp.Handler = ConvertUserToResponse(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if report.HandledAt != nil {
|
||||||
|
handledAt := FormatTime(*report.HandledAt)
|
||||||
|
resp.HandledAt = &handledAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
152
internal/handler/admin_report_handler.go
Normal file
152
internal/handler/admin_report_handler.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"carrot_bbs/internal/dto"
|
||||||
|
"carrot_bbs/internal/response"
|
||||||
|
"carrot_bbs/internal/service"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AdminReportHandler 管理端举报处理器
|
||||||
|
type AdminReportHandler struct {
|
||||||
|
adminReportService service.AdminReportService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdminReportHandler 创建管理端举报处理器
|
||||||
|
func NewAdminReportHandler(adminReportService service.AdminReportService) *AdminReportHandler {
|
||||||
|
return &AdminReportHandler{
|
||||||
|
adminReportService: adminReportService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取举报列表
|
||||||
|
func (h *AdminReportHandler) List(c *gin.Context) {
|
||||||
|
// 解析查询参数
|
||||||
|
var query dto.AdminReportListQuery
|
||||||
|
if err := c.ShouldBindQuery(&query); err != nil {
|
||||||
|
response.BadRequest(c, "参数错误: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认分页
|
||||||
|
if query.Page == 0 {
|
||||||
|
query.Page = 1
|
||||||
|
}
|
||||||
|
if query.PageSize == 0 {
|
||||||
|
query.PageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询列表
|
||||||
|
reports, total, err := h.adminReportService.GetReportList(c.Request.Context(), query)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to get report list", zap.Error(err))
|
||||||
|
response.InternalServerError(c, "获取举报列表失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Paginated(c, reports, total, query.Page, query.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detail 获取举报详情
|
||||||
|
func (h *AdminReportHandler) Detail(c *gin.Context) {
|
||||||
|
// 获取举报ID
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
response.BadRequest(c, "举报ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询详情
|
||||||
|
report, err := h.adminReportService.GetReportDetail(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to get report detail", zap.Error(err), zap.String("id", id))
|
||||||
|
response.NotFound(c, "举报不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, report)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 处理举报
|
||||||
|
func (h *AdminReportHandler) Handle(c *gin.Context) {
|
||||||
|
// 获取处理人ID
|
||||||
|
handledBy := c.GetString("user_id")
|
||||||
|
if handledBy == "" {
|
||||||
|
response.Unauthorized(c, "未授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取举报ID
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
response.BadRequest(c, "举报ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求
|
||||||
|
var req dto.HandleReportRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "参数错误: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理举报
|
||||||
|
err := h.adminReportService.HandleReport(c.Request.Context(), id, req.Action, req.Result, handledBy)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to handle report",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("id", id),
|
||||||
|
zap.String("action", req.Action),
|
||||||
|
)
|
||||||
|
response.Error(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "处理成功"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchHandle 批量处理举报
|
||||||
|
func (h *AdminReportHandler) BatchHandle(c *gin.Context) {
|
||||||
|
// 获取处理人ID
|
||||||
|
handledBy := c.GetString("user_id")
|
||||||
|
if handledBy == "" {
|
||||||
|
response.Unauthorized(c, "未授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求
|
||||||
|
var req dto.BatchHandleReportRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "参数错误: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量处理
|
||||||
|
result, err := h.adminReportService.BatchHandle(c.Request.Context(), req.IDs, req.Action, req.Result, handledBy)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to batch handle reports",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Int("count", len(req.IDs)),
|
||||||
|
)
|
||||||
|
response.Error(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePageParams 解析分页参数
|
||||||
|
func parsePageParams(c *gin.Context) (int, int) {
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 || pageSize > 100 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
return page, pageSize
|
||||||
|
}
|
||||||
61
internal/handler/report_handler.go
Normal file
61
internal/handler/report_handler.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"carrot_bbs/internal/dto"
|
||||||
|
"carrot_bbs/internal/response"
|
||||||
|
"carrot_bbs/internal/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportHandler 举报处理器
|
||||||
|
type ReportHandler struct {
|
||||||
|
reportService service.ReportService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReportHandler 创建举报处理器
|
||||||
|
func NewReportHandler(reportService service.ReportService) *ReportHandler {
|
||||||
|
return &ReportHandler{
|
||||||
|
reportService: reportService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建举报
|
||||||
|
func (h *ReportHandler) Create(c *gin.Context) {
|
||||||
|
// 获取用户ID
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
response.Unauthorized(c, "未授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求
|
||||||
|
var req dto.CreateReportRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "参数错误: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建举报
|
||||||
|
report, err := h.reportService.CreateReport(
|
||||||
|
c.Request.Context(),
|
||||||
|
userID,
|
||||||
|
req.TargetType,
|
||||||
|
req.TargetID,
|
||||||
|
req.Reason,
|
||||||
|
req.Description,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to create report",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("target_type", req.TargetType),
|
||||||
|
zap.String("target_id", req.TargetID),
|
||||||
|
)
|
||||||
|
response.Error(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.ConvertReportToResponse(report))
|
||||||
|
}
|
||||||
@@ -134,6 +134,9 @@ func autoMigrate(db *gorm.DB) error {
|
|||||||
&SensitiveWord{},
|
&SensitiveWord{},
|
||||||
&AuditLog{},
|
&AuditLog{},
|
||||||
|
|
||||||
|
// 举报
|
||||||
|
&Report{},
|
||||||
|
|
||||||
// 日志相关
|
// 日志相关
|
||||||
&OperationLog{}, // 操作日志
|
&OperationLog{}, // 操作日志
|
||||||
&LoginLog{}, // 登录日志
|
&LoginLog{}, // 登录日志
|
||||||
|
|||||||
80
internal/model/report.go
Normal file
80
internal/model/report.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportTargetType 举报目标类型
|
||||||
|
type ReportTargetType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportTargetTypePost ReportTargetType = "post" // 帖子
|
||||||
|
ReportTargetTypeComment ReportTargetType = "comment" // 评论
|
||||||
|
ReportTargetTypeMessage ReportTargetType = "message" // 消息
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportStatus 举报处理状态
|
||||||
|
type ReportStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportStatusPending ReportStatus = "pending" // 待处理
|
||||||
|
ReportStatusProcessing ReportStatus = "processing" // 处理中
|
||||||
|
ReportStatusResolved ReportStatus = "resolved" // 已确认违规
|
||||||
|
ReportStatusRejected ReportStatus = "rejected" // 已驳回
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportReason 举报原因类型
|
||||||
|
type ReportReason string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportReasonSpam ReportReason = "spam" // 垃圾广告
|
||||||
|
ReportReasonInappropriate ReportReason = "inappropriate" // 违规内容
|
||||||
|
ReportReasonHarassment ReportReason = "harassment" // 辱骂/攻击
|
||||||
|
ReportReasonMisinformation ReportReason = "misinformation" // 虚假信息
|
||||||
|
ReportReasonOther ReportReason = "other" // 其他
|
||||||
|
)
|
||||||
|
|
||||||
|
// Report 举报实体
|
||||||
|
type Report struct {
|
||||||
|
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||||
|
ReporterID string `json:"reporter_id" gorm:"type:varchar(36);index;index:idx_reports_reporter_status,priority:1;not null"`
|
||||||
|
TargetType ReportTargetType `json:"target_type" gorm:"type:varchar(20);index;index:idx_reports_target_type_id,priority:1;index:idx_reports_target_status,priority:1;not null"`
|
||||||
|
TargetID string `json:"target_id" gorm:"type:varchar(36);index:idx_reports_target_type_id,priority:2;index:idx_reports_target,priority:1;not null"`
|
||||||
|
Reason ReportReason `json:"reason" gorm:"type:varchar(30);not null"`
|
||||||
|
Description string `json:"description" gorm:"type:text"`
|
||||||
|
Status ReportStatus `json:"status" gorm:"type:varchar(20);default:pending;index:idx_reports_status_created,priority:1;index:idx_reports_reporter_status,priority:2;index:idx_reports_target_status,priority:2"`
|
||||||
|
HandledBy *string `json:"handled_by" gorm:"type:varchar(36);index"`
|
||||||
|
HandledAt *time.Time `json:"handled_at" gorm:"type:timestamp"`
|
||||||
|
Result *string `json:"result" gorm:"type:text"`
|
||||||
|
|
||||||
|
// 软删除
|
||||||
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index:idx_reports_status_created,priority:2,sort:desc"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate 创建前生成UUID
|
||||||
|
func (r *Report) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if r.ID == "" {
|
||||||
|
r.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定表名
|
||||||
|
func (Report) TableName() string {
|
||||||
|
return "reports"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportStats 举报统计(用于缓存或查询结果)
|
||||||
|
type ReportStats struct {
|
||||||
|
TargetType ReportTargetType
|
||||||
|
TargetID string
|
||||||
|
ReportCount int
|
||||||
|
IsHidden bool
|
||||||
|
}
|
||||||
@@ -32,6 +32,10 @@ const (
|
|||||||
SysNotifyGroupJoinApply SystemNotificationType = "group_join_apply" // 加群申请待审批
|
SysNotifyGroupJoinApply SystemNotificationType = "group_join_apply" // 加群申请待审批
|
||||||
SysNotifyGroupJoinApproved SystemNotificationType = "group_join_approved" // 加群申请通过
|
SysNotifyGroupJoinApproved SystemNotificationType = "group_join_approved" // 加群申请通过
|
||||||
SysNotifyGroupJoinRejected SystemNotificationType = "group_join_rejected" // 加群申请拒绝
|
SysNotifyGroupJoinRejected SystemNotificationType = "group_join_rejected" // 加群申请拒绝
|
||||||
|
|
||||||
|
// 举报相关
|
||||||
|
SysNotifyReportResolved SystemNotificationType = "report_resolved" // 举报已确认违规
|
||||||
|
SysNotifyReportRejected SystemNotificationType = "report_rejected" // 举报已驳回
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemNotificationExtra 额外数据
|
// SystemNotificationExtra 额外数据
|
||||||
|
|||||||
212
internal/repository/report_repo.go
Normal file
212
internal/repository/report_repo.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"carrot_bbs/internal/dto"
|
||||||
|
"carrot_bbs/internal/model"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportRepository 举报仓储接口
|
||||||
|
type ReportRepository interface {
|
||||||
|
// Create 创建举报
|
||||||
|
Create(report *model.Report) error
|
||||||
|
// CreateWithContext 使用上下文创建举报(支持事务)
|
||||||
|
CreateWithContext(ctx context.Context, report *model.Report) error
|
||||||
|
// FindByID 根据ID查询举报
|
||||||
|
FindByID(id string) (*model.Report, error)
|
||||||
|
// FindByIDWithContext 使用上下文查询举报
|
||||||
|
FindByIDWithContext(ctx context.Context, id string) (*model.Report, error)
|
||||||
|
// List 查询举报列表
|
||||||
|
List(query dto.AdminReportListQuery) ([]*model.Report, int64, error)
|
||||||
|
// GetReportCount 获取内容的举报次数
|
||||||
|
GetReportCount(targetType model.ReportTargetType, targetID string) (int64, error)
|
||||||
|
// HasUserReported 检查用户是否已举报过该内容
|
||||||
|
HasUserReported(reporterID string, targetType model.ReportTargetType, targetID string) (bool, error)
|
||||||
|
// GetPendingReportsByTarget 获取目标内容的待处理举报
|
||||||
|
GetPendingReportsByTarget(targetType model.ReportTargetType, targetID string) ([]*model.Report, error)
|
||||||
|
// UpdateStatus 更新举报状态
|
||||||
|
UpdateStatus(id string, status model.ReportStatus, handledBy string, result string) error
|
||||||
|
// UpdateStatusWithContext 使用上下文更新举报状态
|
||||||
|
UpdateStatusWithContext(ctx context.Context, id string, status model.ReportStatus, handledBy string, result string) error
|
||||||
|
// BatchUpdateStatus 批量更新举报状态
|
||||||
|
BatchUpdateStatus(ids []string, status model.ReportStatus, handledBy string, result string) (int64, error)
|
||||||
|
// BatchUpdateStatusWithContext 使用上下文批量更新举报状态
|
||||||
|
BatchUpdateStatusWithContext(ctx context.Context, ids []string, status model.ReportStatus, handledBy string, result string) (int64, error)
|
||||||
|
// GetByIDsWithContext 使用上下文批量查询举报
|
||||||
|
GetByIDsWithContext(ctx context.Context, ids []string) ([]*model.Report, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reportRepository 举报仓储实现
|
||||||
|
type reportRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReportRepository 创建举报仓储
|
||||||
|
func NewReportRepository(db *gorm.DB) ReportRepository {
|
||||||
|
return &reportRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDB 获取数据库实例(支持事务)
|
||||||
|
func (r *reportRepository) getDB(ctx context.Context) *gorm.DB {
|
||||||
|
if tx := GetTxFromContext(ctx); tx != nil {
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
return r.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建举报
|
||||||
|
func (r *reportRepository) Create(report *model.Report) error {
|
||||||
|
return r.db.Create(report).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWithContext 使用上下文创建举报
|
||||||
|
func (r *reportRepository) CreateWithContext(ctx context.Context, report *model.Report) error {
|
||||||
|
return r.getDB(ctx).Create(report).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByID 根据ID查询举报
|
||||||
|
func (r *reportRepository) FindByID(id string) (*model.Report, error) {
|
||||||
|
var report model.Report
|
||||||
|
err := r.db.Where("id = ?", id).First(&report).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByIDWithContext 使用上下文查询举报
|
||||||
|
func (r *reportRepository) FindByIDWithContext(ctx context.Context, id string) (*model.Report, error) {
|
||||||
|
var report model.Report
|
||||||
|
err := r.getDB(ctx).Where("id = ?", id).First(&report).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 查询举报列表
|
||||||
|
func (r *reportRepository) List(query dto.AdminReportListQuery) ([]*model.Report, int64, error) {
|
||||||
|
var reports []*model.Report
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
db := r.db.Model(&model.Report{})
|
||||||
|
|
||||||
|
// 筛选条件
|
||||||
|
if query.TargetType != "" {
|
||||||
|
db = db.Where("target_type = ?", query.TargetType)
|
||||||
|
}
|
||||||
|
if query.Status != "" {
|
||||||
|
db = db.Where("status = ?", query.Status)
|
||||||
|
}
|
||||||
|
if query.StartDate != "" {
|
||||||
|
db = db.Where("created_at >= ?", query.StartDate)
|
||||||
|
}
|
||||||
|
if query.EndDate != "" {
|
||||||
|
db = db.Where("created_at <= ?", query.EndDate+" 23:59:59")
|
||||||
|
}
|
||||||
|
if query.Keyword != "" {
|
||||||
|
keyword := "%" + query.Keyword + "%"
|
||||||
|
db = db.Where("description LIKE ? OR reason LIKE ?", keyword, keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
offset := (query.Page - 1) * query.PageSize
|
||||||
|
if query.PageSize <= 0 {
|
||||||
|
query.PageSize = 20
|
||||||
|
}
|
||||||
|
if query.Page <= 0 {
|
||||||
|
query.Page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Order("created_at DESC").
|
||||||
|
Offset(offset).
|
||||||
|
Limit(query.PageSize).
|
||||||
|
Find(&reports).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReportCount 获取内容的举报次数
|
||||||
|
func (r *reportRepository) GetReportCount(targetType model.ReportTargetType, targetID string) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&model.Report{}).
|
||||||
|
Where("target_type = ? AND target_id = ?", targetType, targetID).
|
||||||
|
Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasUserReported 检查用户是否已举报过该内容
|
||||||
|
func (r *reportRepository) HasUserReported(reporterID string, targetType model.ReportTargetType, targetID string) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&model.Report{}).
|
||||||
|
Where("reporter_id = ? AND target_type = ? AND target_id = ?", reporterID, targetType, targetID).
|
||||||
|
Count(&count).Error
|
||||||
|
return count > 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingReportsByTarget 获取目标内容的待处理举报
|
||||||
|
func (r *reportRepository) GetPendingReportsByTarget(targetType model.ReportTargetType, targetID string) ([]*model.Report, error) {
|
||||||
|
var reports []*model.Report
|
||||||
|
err := r.db.Where("target_type = ? AND target_id = ? AND status = ?", targetType, targetID, model.ReportStatusPending).
|
||||||
|
Find(&reports).Error
|
||||||
|
return reports, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus 更新举报状态
|
||||||
|
func (r *reportRepository) UpdateStatus(id string, status model.ReportStatus, handledBy string, result string) error {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"handled_by": handledBy,
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
return r.db.Model(&model.Report{}).Where("id = ?", id).Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatusWithContext 使用上下文更新举报状态
|
||||||
|
func (r *reportRepository) UpdateStatusWithContext(ctx context.Context, id string, status model.ReportStatus, handledBy string, result string) error {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"handled_by": handledBy,
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
return r.getDB(ctx).Model(&model.Report{}).Where("id = ?", id).Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchUpdateStatus 批量更新举报状态
|
||||||
|
func (r *reportRepository) BatchUpdateStatus(ids []string, status model.ReportStatus, handledBy string, result string) (int64, error) {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"handled_by": handledBy,
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
resultDB := r.db.Model(&model.Report{}).Where("id IN ?", ids).Updates(updates)
|
||||||
|
return resultDB.RowsAffected, resultDB.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchUpdateStatusWithContext 使用上下文批量更新举报状态
|
||||||
|
func (r *reportRepository) BatchUpdateStatusWithContext(ctx context.Context, ids []string, status model.ReportStatus, handledBy string, result string) (int64, error) {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
"handled_by": handledBy,
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
resultDB := r.getDB(ctx).Model(&model.Report{}).Where("id IN ?", ids).Updates(updates)
|
||||||
|
return resultDB.RowsAffected, resultDB.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByIDsWithContext 使用上下文批量查询举报
|
||||||
|
func (r *reportRepository) GetByIDsWithContext(ctx context.Context, ids []string) ([]*model.Report, error) {
|
||||||
|
var reports []*model.Report
|
||||||
|
err := r.getDB(ctx).Where("id IN ?", ids).Find(&reports).Error
|
||||||
|
return reports, err
|
||||||
|
}
|
||||||
@@ -35,6 +35,8 @@ type Router struct {
|
|||||||
qrcodeHandler *handler.QRCodeHandler
|
qrcodeHandler *handler.QRCodeHandler
|
||||||
materialHandler *handler.MaterialHandler
|
materialHandler *handler.MaterialHandler
|
||||||
callHandler *handler.CallHandler
|
callHandler *handler.CallHandler
|
||||||
|
reportHandler *handler.ReportHandler
|
||||||
|
adminReportHandler *handler.AdminReportHandler
|
||||||
wsHandler *handler.WSHandler
|
wsHandler *handler.WSHandler
|
||||||
logService *service.LogService
|
logService *service.LogService
|
||||||
jwtService *service.JWTService
|
jwtService *service.JWTService
|
||||||
@@ -67,6 +69,8 @@ func New(
|
|||||||
qrcodeHandler *handler.QRCodeHandler,
|
qrcodeHandler *handler.QRCodeHandler,
|
||||||
materialHandler *handler.MaterialHandler,
|
materialHandler *handler.MaterialHandler,
|
||||||
callHandler *handler.CallHandler,
|
callHandler *handler.CallHandler,
|
||||||
|
reportHandler *handler.ReportHandler,
|
||||||
|
adminReportHandler *handler.AdminReportHandler,
|
||||||
logService *service.LogService,
|
logService *service.LogService,
|
||||||
activityService service.UserActivityService,
|
activityService service.UserActivityService,
|
||||||
casbinService service.CasbinService,
|
casbinService service.CasbinService,
|
||||||
@@ -102,6 +106,8 @@ func New(
|
|||||||
qrcodeHandler: qrcodeHandler,
|
qrcodeHandler: qrcodeHandler,
|
||||||
materialHandler: materialHandler,
|
materialHandler: materialHandler,
|
||||||
callHandler: callHandler,
|
callHandler: callHandler,
|
||||||
|
reportHandler: reportHandler,
|
||||||
|
adminReportHandler: adminReportHandler,
|
||||||
logService: logService,
|
logService: logService,
|
||||||
jwtService: jwtService,
|
jwtService: jwtService,
|
||||||
casbinService: casbinService,
|
casbinService: casbinService,
|
||||||
@@ -356,6 +362,15 @@ func (r *Router) setupRoutes() {
|
|||||||
messages.POST("/delete_msg", r.messageHandler.HandleDeleteMsg)
|
messages.POST("/delete_msg", r.messageHandler.HandleDeleteMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 举报路由
|
||||||
|
if r.reportHandler != nil {
|
||||||
|
reports := v1.Group("/reports")
|
||||||
|
reports.Use(authMiddleware)
|
||||||
|
{
|
||||||
|
reports.POST("", r.reportHandler.Create)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 通知路由
|
// 通知路由
|
||||||
notifications := v1.Group("/notifications")
|
notifications := v1.Group("/notifications")
|
||||||
{
|
{
|
||||||
@@ -557,6 +572,14 @@ func (r *Router) setupRoutes() {
|
|||||||
admin.PUT("/materials/:id", r.materialHandler.AdminUpdateMaterial)
|
admin.PUT("/materials/:id", r.materialHandler.AdminUpdateMaterial)
|
||||||
admin.DELETE("/materials/:id", r.materialHandler.AdminDeleteMaterial)
|
admin.DELETE("/materials/:id", r.materialHandler.AdminDeleteMaterial)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 举报管理
|
||||||
|
if r.adminReportHandler != nil {
|
||||||
|
admin.GET("/reports", r.adminReportHandler.List)
|
||||||
|
admin.GET("/reports/:id", r.adminReportHandler.Detail)
|
||||||
|
admin.PUT("/reports/:id/handle", r.adminReportHandler.Handle)
|
||||||
|
admin.POST("/reports/batch-handle", r.adminReportHandler.BatchHandle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
442
internal/service/admin_report_service.go
Normal file
442
internal/service/admin_report_service.go
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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
|
||||||
|
systemNotify SystemNotificationService
|
||||||
|
txManager repository.TransactionManager
|
||||||
|
logService *LogService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdminReportService 创建管理端举报服务
|
||||||
|
func NewAdminReportService(
|
||||||
|
reportRepo repository.ReportRepository,
|
||||||
|
postRepo repository.PostRepository,
|
||||||
|
commentRepo repository.CommentRepository,
|
||||||
|
messageRepo repository.MessageRepository,
|
||||||
|
userRepo repository.UserRepository,
|
||||||
|
systemNotify SystemNotificationService,
|
||||||
|
txManager repository.TransactionManager,
|
||||||
|
logService *LogService,
|
||||||
|
) AdminReportService {
|
||||||
|
return &adminReportServiceImpl{
|
||||||
|
reportRepo: reportRepo,
|
||||||
|
postRepo: postRepo,
|
||||||
|
commentRepo: commentRepo,
|
||||||
|
messageRepo: messageRepo,
|
||||||
|
userRepo: userRepo,
|
||||||
|
systemNotify: systemNotify,
|
||||||
|
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.LogOperation(ctx, "handle_report", "report", id, handledBy, map[string]interface{}{
|
||||||
|
"action": action,
|
||||||
|
"result": result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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.LogOperation(ctx, "batch_handle_reports", "report", "", handledBy, map[string]interface{}{
|
||||||
|
"ids": ids,
|
||||||
|
"action": action,
|
||||||
|
"success_count": response.SuccessCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
content.Content = msg.Content
|
||||||
|
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 = dto.ConvertUserToResponse(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.systemNotify == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取举报人信息
|
||||||
|
reporter, err := s.userRepo.GetByID(report.ReporterID)
|
||||||
|
if err != 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送通知
|
||||||
|
_, err = s.systemNotify.CreateNotification(ctx, &model.SystemNotification{
|
||||||
|
ReceiverID: report.ReporterID,
|
||||||
|
Type: notifyType,
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
ExtraData: &model.SystemNotificationExtra{
|
||||||
|
ActorIDStr: report.ReporterID,
|
||||||
|
ActorName: reporter.Nickname,
|
||||||
|
TargetID: report.TargetID,
|
||||||
|
TargetType: string(report.TargetType),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to send report notification", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
213
internal/service/report_service.go
Normal file
213
internal/service/report_service.go
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -28,6 +28,8 @@ var HandlerSet = wire.NewSet(
|
|||||||
handler.NewQRCodeHandler,
|
handler.NewQRCodeHandler,
|
||||||
handler.NewMaterialHandler,
|
handler.NewMaterialHandler,
|
||||||
handler.NewCallHandler,
|
handler.NewCallHandler,
|
||||||
|
handler.NewReportHandler,
|
||||||
|
handler.NewAdminReportHandler,
|
||||||
|
|
||||||
// 需要特殊处理的 Handler
|
// 需要特殊处理的 Handler
|
||||||
handler.NewUserHandler,
|
handler.NewUserHandler,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var RepositorySet = wire.NewSet(
|
|||||||
repository.NewMaterialSubjectRepository,
|
repository.NewMaterialSubjectRepository,
|
||||||
repository.NewMaterialFileRepository,
|
repository.NewMaterialFileRepository,
|
||||||
repository.NewCallRepository,
|
repository.NewCallRepository,
|
||||||
|
repository.NewReportRepository,
|
||||||
ProvideUserActivityRepository,
|
ProvideUserActivityRepository,
|
||||||
|
|
||||||
// 日志相关仓储
|
// 日志相关仓储
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ var ServiceSet = wire.NewSet(
|
|||||||
ProvideHotRankWorker,
|
ProvideHotRankWorker,
|
||||||
ProvideMaterialService,
|
ProvideMaterialService,
|
||||||
ProvideCallService,
|
ProvideCallService,
|
||||||
|
ProvideReportService,
|
||||||
|
ProvideAdminReportService,
|
||||||
|
|
||||||
// 日志服务
|
// 日志服务
|
||||||
ProvideAsyncLogManager,
|
ProvideAsyncLogManager,
|
||||||
@@ -394,3 +396,32 @@ func ProvideCallService(
|
|||||||
) service.CallService {
|
) service.CallService {
|
||||||
return service.NewCallService(callRepo, wsHub, cfg, db)
|
return service.NewCallService(callRepo, wsHub, cfg, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProvideReportService 提供举报服务
|
||||||
|
func ProvideReportService(
|
||||||
|
reportRepo repository.ReportRepository,
|
||||||
|
postRepo repository.PostRepository,
|
||||||
|
commentRepo repository.CommentRepository,
|
||||||
|
messageRepo repository.MessageRepository,
|
||||||
|
userRepo repository.UserRepository,
|
||||||
|
systemNotify service.SystemNotificationService,
|
||||||
|
txManager repository.TransactionManager,
|
||||||
|
logService *service.LogService,
|
||||||
|
cfg *config.Config,
|
||||||
|
) service.ReportService {
|
||||||
|
return service.NewReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, systemNotify, txManager, logService, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProvideAdminReportService 提供管理端举报服务
|
||||||
|
func ProvideAdminReportService(
|
||||||
|
reportRepo repository.ReportRepository,
|
||||||
|
postRepo repository.PostRepository,
|
||||||
|
commentRepo repository.CommentRepository,
|
||||||
|
messageRepo repository.MessageRepository,
|
||||||
|
userRepo repository.UserRepository,
|
||||||
|
systemNotify service.SystemNotificationService,
|
||||||
|
txManager repository.TransactionManager,
|
||||||
|
logService *service.LogService,
|
||||||
|
) service.AdminReportService {
|
||||||
|
return service.NewAdminReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, systemNotify, txManager, logService)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user