topfans/backend/gateway/controller/moderation_controller.go
2026-06-22 17:19:48 +08:00

276 lines
9.3 KiB
Go
Raw Permalink 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 controller
import (
"errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
pbModeration "github.com/topfans/backend/pkg/proto/moderation"
modSvc "github.com/topfans/backend/services/moderationService/service"
"github.com/topfans/backend/gateway/dto"
"github.com/topfans/backend/gateway/pkg/response"
)
// modSpecErrCode 举报/反馈业务错误码spec §7,50001-50019
// 配套 api.js 的 REPORT_ERROR_MSG 映射表使用
const (
modCodeCategoryInvalid = 50001 // 举报分类不存在或已停用
modCodeFeedbackCatInvalid = 50002 // 反馈分类不存在或已停用
modCodeTargetNotFound = 50003 // 目标对象不存在
modCodeDuplicateReport = 50004 // 已举报过
modCodeDescriptionTooLong = 50005 // 描述过长
modCodeTooManyEvidence = 50006 // 证据图超限
modCodeReportNotFound = 50007 // 工单不存在
modCodeReportClaimed = 50008 // 工单已被他人认领
modCodeReportClosed = 50009 // 工单已结案
modCodeSelfReport = 50011 // 不能举报自己
modCodeRateLimited = 50012 // 提交过于频繁
modCodeInvalidStateMig = 50014 // 状态机非法迁移
modCodeTargetTypeUnsupported = 50016 // target_type 不支持
)
// modErrMapping 错误 → 业务错误码的映射表
// 与 modSpecErrCode 常量集中维护
var modErrMapping = map[error]int{
modSvc.ErrCategoryInvalid: modCodeCategoryInvalid,
modSvc.ErrFeedbackCatInvalid: modCodeFeedbackCatInvalid,
modSvc.ErrTargetNotFound: modCodeTargetNotFound,
modSvc.ErrDuplicateReport: modCodeDuplicateReport,
modSvc.ErrDescriptionTooLong: modCodeDescriptionTooLong,
modSvc.ErrTooManyEvidence: modCodeTooManyEvidence,
modSvc.ErrReportNotFound: modCodeReportNotFound,
modSvc.ErrReportClaimed: modCodeReportClaimed,
modSvc.ErrReportClosed: modCodeReportClosed,
modSvc.ErrSelfReport: modCodeSelfReport,
modSvc.ErrRateLimited: modCodeRateLimited,
modSvc.ErrInvalidStateMigration: modCodeInvalidStateMig,
modSvc.ErrTargetTypeUnsupported: modCodeTargetTypeUnsupported,
}
// modSpecErrMsg 业务错误码 → spec 文案spec §7 中文版)
// 与 api.js 的 REPORT_ERROR_MSG 同源;后端优先返回这里的中文,前端做兼容兜底
var modSpecErrMsg = map[int]string{
modCodeCategoryInvalid: "举报分类不存在或已停用",
modCodeFeedbackCatInvalid: "反馈分类不存在或已停用",
modCodeTargetNotFound: "目标对象不存在",
modCodeDuplicateReport: "您已举报过该对象",
modCodeDescriptionTooLong: "描述过长(最多 500 字)",
modCodeTooManyEvidence: "证据图超限(最多 5 张)",
modCodeReportNotFound: "工单不存在",
modCodeReportClaimed: "工单已被他人认领",
modCodeReportClosed: "工单已结案,无法再操作",
modCodeSelfReport: "不能举报自己",
modCodeRateLimited: "提交过于频繁,请稍后再试",
modCodeInvalidStateMig: "工单状态不允许此操作",
modCodeTargetTypeUnsupported: "举报对象类型不支持",
}
// mapModError 把 service 层错误翻译为 (业务错误码, 中文 message)。
// 命中则返回 (code, msg, true);未命中返回 (500, "", false) 由调用方走 500 兜底
func mapModError(err error) (int, string, bool) {
if err == nil {
return 0, "", false
}
for target, code := range modErrMapping {
if errors.Is(err, target) {
return code, modSpecErrMsg[code], true
}
}
return http.StatusInternalServerError, "", false
}
// ModerationController 举报反馈控制器spec §5.1 客户端 API
type ModerationController struct {
modService pbModeration.ModerationService
}
// NewModerationController 创建举报反馈控制器service 由 gateway 注入)
func NewModerationController() *ModerationController {
return &ModerationController{}
}
// SetService 由 gateway 注入
func (ctrl *ModerationController) SetService(svc pbModeration.ModerationService) {
ctrl.modService = svc
}
// getUserID 从 gin context 提取 user_idkey 由 auth middleware 写入)
func getUserID(c *gin.Context) int64 {
if v, ok := c.Get("user_id"); ok {
if id, ok := v.(int64); ok {
return id
}
}
return 0
}
// GetReportCategories 获取举报分类
func (ctrl *ModerationController) GetReportCategories(c *gin.Context) {
resp, err := ctrl.modService.GetReportCategories(
c.Request.Context(), &pbModeration.GetReportCategoriesRequest{},
)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewReportCategoryList(resp.Categories))
}
// GetFeedbackCategories 获取反馈分类
func (ctrl *ModerationController) GetFeedbackCategories(c *gin.Context) {
resp, err := ctrl.modService.GetFeedbackCategories(
c.Request.Context(), &pbModeration.GetFeedbackCategoriesRequest{},
)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewFeedbackCategoryList(resp.Categories))
}
// SubmitReport 提交举报
func (ctrl *ModerationController) SubmitReport(c *gin.Context) {
var req dto.SubmitReportRequestDTO
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "参数错误: "+err.Error())
return
}
userID := getUserID(c)
resp, err := ctrl.modService.SubmitReport(c.Request.Context(), &pbModeration.SubmitReportRequest{
ReporterId: userID,
TargetType: req.TargetType,
TargetId: req.TargetID,
CategoryCode: req.CategoryCode,
Description: req.Description,
IsAnonymous: req.IsAnonymous,
EvidenceKeys: req.EvidenceKeys,
ClientIp: c.ClientIP(),
DeviceFp: c.GetHeader("X-Device-Fingerprint"),
})
if err != nil {
if code, msg, ok := mapModError(err); ok {
response.ErrorWithCode(c, code, msg)
return
}
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewSubmitReportResponse(resp))
}
// ListMyReports 我的举报列表
func (ctrl *ModerationController) ListMyReports(c *gin.Context) {
userID := getUserID(c)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
resp, err := ctrl.modService.ListMyReports(c.Request.Context(), &pbModeration.ListMyReportsRequest{
ReporterId: userID,
Status: c.Query("status"),
Page: int32(page),
PageSize: int32(pageSize),
})
if err != nil {
if code, msg, ok := mapModError(err); ok {
response.ErrorWithCode(c, code, msg)
return
}
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewListMyReportsResponse(resp))
}
// GetReportDetail 举报详情
func (ctrl *ModerationController) GetReportDetail(c *gin.Context) {
userID := getUserID(c)
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "无效的 ID")
return
}
resp, err := ctrl.modService.GetReport(c.Request.Context(), &pbModeration.GetReportRequest{
ReporterId: userID, Id: id,
})
if err != nil {
if code, msg, ok := mapModError(err); ok {
response.ErrorWithCode(c, code, msg)
return
}
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewGetReportResponse(resp))
}
// SubmitFeedback 提交反馈
func (ctrl *ModerationController) SubmitFeedback(c *gin.Context) {
var req dto.SubmitFeedbackRequestDTO
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "参数错误: "+err.Error())
return
}
userID := getUserID(c)
resp, err := ctrl.modService.SubmitFeedback(c.Request.Context(), &pbModeration.SubmitFeedbackRequest{
UserId: userID,
StarId: req.StarID,
CategoryCode: req.CategoryCode,
Title: req.Title,
Content: req.Content,
Contact: req.Contact,
IsAnonymous: req.IsAnonymous,
EvidenceKeys: req.EvidenceKeys,
})
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewSubmitFeedbackResponse(resp))
}
// ListMyFeedbacks 我的反馈列表
func (ctrl *ModerationController) ListMyFeedbacks(c *gin.Context) {
userID := getUserID(c)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
resp, err := ctrl.modService.ListMyFeedbacks(c.Request.Context(), &pbModeration.ListMyFeedbacksRequest{
UserId: userID,
Status: c.Query("status"),
Page: int32(page),
PageSize: int32(pageSize),
})
if err != nil {
if code, msg, ok := mapModError(err); ok {
response.ErrorWithCode(c, code, msg)
return
}
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewListMyFeedbacksResponse(resp))
}
// GetFeedbackDetail 反馈详情
func (ctrl *ModerationController) GetFeedbackDetail(c *gin.Context) {
userID := getUserID(c)
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "无效的 ID")
return
}
resp, err := ctrl.modService.GetFeedback(c.Request.Context(), &pbModeration.GetFeedbackRequest{
UserId: userID, Id: id,
})
if err != nil {
if code, msg, ok := mapModError(err); ok {
response.ErrorWithCode(c, code, msg)
return
}
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.NewGetFeedbackResponse(resp))
}