276 lines
9.3 KiB
Go
276 lines
9.3 KiB
Go
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_id(key 由 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))
|
||
}
|