topfans/docs/superpowers/specs/2026-06-11-moderation-report-feedback-design.md

37 KiB
Raw Blame History

举报与反馈系统设计

状态:设计已确认 创建时间2026-06-11 作者Claude 目标:在 TopFans 平台新增内容举报与用户反馈两大工单系统,支撑运营/审核员通过独立 web 后台对违规内容(数字藏品/用户/AI 描述词和用户反馈BUG/咨询/合作/建议)进行审核处理


目录

  1. 背景与目标
  2. 架构概览
  3. 数据模型
  4. 状态机
  5. API 设计
  6. 关键流程
  7. 错误码
  8. 通知机制
  9. 安全与防护
  10. 实施步骤
  11. 附录

1. 背景与目标

1.1 背景

TopFans 是粉丝/明星数字藏品平台。当前平台缺少:

  • 用户对违规内容(藏品/用户/AI 描述词)的举报通道
  • 用户对平台体验问题的反馈通道
  • 运营/审核员对举报和反馈的审核处理后台

1.2 目标

  • 提供客户端uni-app举报与反馈的提交入口
  • 提供运营 web 后台(基于 D:\shanghai\TopFans-activity)的工单审核界面
  • 支持"举报达到阈值自动隐藏 + 管理员审核动作(标记状态)"的闭环
  • 设计可扩展、易维护、易观测

1.3 范围

包含

  • 客户端提交接口uni-app 端)
  • 审核员后台Vue3 + Element Plus
  • 数据模型与迁移
  • 状态机、自动隐藏、动作流水
  • 通知机制

不包含YAGNI

  • 举报人/被举报人间的对话沟通
  • 申诉/上诉流程
  • 举报人积分/奖励体系
  • 复杂的多级管理员(仅一种角色)
  • 工单 SLA 监控告警

2. 架构概览

2.1 整体架构

┌──────────────────────────────────────────────────────────────────┐
│                       客户端 (uni-app)                           │
│  ┌─────────────────────────┐  ┌──────────────────────────────┐  │
│  │ 举报弹窗 (藏品/用户/描述词)│  │ 我的 → 意见反馈              │  │
│  └────────────┬────────────┘  └────────────┬─────────────────┘  │
└───────────────┼──────────────────────────────┼──────────────────┘
                │ REST                          │
                ▼                              ▼
┌──────────────────────────────────────────────────────────────────┐
│   topfans gateway (Go, 已有)                                    │
│   /api/v1/moderation/*  客户端 API                              │
└───────────────────────────┬──────────────────────────────────────┘
                            │ Dubbo RPC
                            ▼
┌──────────────────────────────────────────────────────────────────┐
│   topfans moderationService (Go, 新建微服务)                     │
│   - ReportService / FeedbackService / CategoryService           │
│   - 写 PostgreSQL reports/feedbacks/categories/状态表            │
│   - Redis 计数 (自动隐藏阈值)                                   │
│   - 触发自动隐藏UPDATE reports + UPSERT moderation_target_status│
└────────┬─────────────────────────────────────────┬───────────────┘
         │ 共享 PG 库                              │
         │                                         │
         ▼                                         ▼
┌────────────────────────────┐  ┌──────────────────────────────────┐
│  PostgreSQL (topfans 库)    │  │  Redis (topfans 已有实例)        │
│  - report_categories       │  │  - mod:report:counter:*          │
│  - feedback_categories     │  │  - mod:report:user:*             │
│  - reports                 │  │  - mod:report:lock:*             │
│  - report_evidence         │  └──────────────────────────────────┘
│  - feedbacks               │
│  - feedback_evidence       │
│  - moderation_target_status│  ← 业务服务读路径 JOIN 此表
│  - moderation_actions      │
│  - admin_audit_logs        │
└────────┬───────────────────┘
         │ 共享 PG 库 (直接读写,不调对方接口)
         ▼
┌──────────────────────────────────────────────────────────────────┐
│   TopFans-Activity 后台 (FastAPI + Vue3, 已有项目)                │
│   D:\shanghai\TopFans-activity                                   │
│   - backend/handlers/moderation_admin.py (后台 API)              │
│   - backend/crud/moderation_crud.py                              │
│   - backend/schemas/moderation.py                                │
│   - frontend/src/views/moderation/ (举报/反馈工单 UI)            │
└──────────────────────────────────────────────────────────────────┘

2.2 模块职责

模块 职责
ReportService (Go) 接收举报、查询我的举报、触发自动隐藏
FeedbackService (Go) 接收反馈、查询我的反馈
CategoryService (Go+FastAPI) 维护分类(动态配置),客户端读取 + 后台 CRUD
AutoHideExecutor (Go) Redis 计数 + 触发自动隐藏(写 reports + 状态表)
后台 Moderation Admin (FastAPI) 审核员认领/动作/分类管理
moderation_target_status 业务服务读路径 JOIN呈现"隐藏/封禁/警告"状态

2.3 复用现有资产

  • OSS 上传:举报证据图、反馈截图复用 assetService 的 OSS 签名接口(GET /api/v1/assets/oss/signature?type=asset
  • 认证:客户端 JWT 用 topfans userService后台 JWT 用 TopFans-activity 已有的 verify_token
  • 通知中心:处理结果通过 topfans notificationService 模板下发(新增 5 个模板)
  • 数据迁移:遵循 topfans 现有 backend/migrations/00x_*.sql 命名规范
  • 序列同步:手动指定 ID 时必须 SELECT setval('table_id_seq', MAX(id))CLAUDE.md 强制规范)

3. 数据模型

3.1 核心表

report_categories      举报分类(动态配置)
feedback_categories    反馈分类(动态配置)
reports                举报工单主表
report_evidence        举报证据图(一对多)
feedbacks              反馈工单主表
feedback_evidence      反馈截图(一对多)
moderation_actions     审核动作流水
moderation_target_status  共享"对象受管控状态"表(业务服务读路径 JOIN
admin_audit_logs       管理员操作日志

3.2 report_categories 举报分类

CREATE TABLE report_categories (
    id BIGSERIAL PRIMARY KEY,
    code VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(50) NOT NULL,
    description VARCHAR(200),
    severity SMALLINT NOT NULL DEFAULT 1,
    enabled BOOLEAN NOT NULL DEFAULT TRUE,
    sort_order INT NOT NULL DEFAULT 0,
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL
);
CREATE INDEX idx_report_categories_enabled ON report_categories(enabled, sort_order);

-- 初始数据
INSERT INTO report_categories (code, name, description, severity, sort_order, created_at, updated_at) VALUES
  ('pornographic', '色情低俗', '含裸露、性暗示内容', 5, 1, EXTRACT(EPOCH FROM NOW())*1000, EXTRACT(EPOCH FROM NOW())*1000),
  ('violence',    '暴力血腥',  '含暴力、血腥画面', 5, 2, ...),
  ('infringing',  '侵权盗版',  '侵犯他人著作权/商标权', 4, 3, ...),
  ('false_info',  '虚假信息',  '虚假、欺骗性内容', 3, 4, ...),
  ('political',   '政治敏感',  '涉及政治敏感话题', 5, 5, ...),
  ('ad_spam',     '广告骚扰',  '垃圾广告、骚扰信息', 2, 6, ...),
  ('other',       '其他',      '其他违规情况', 1, 99, ...);

SELECT setval('report_categories_id_seq', (SELECT MAX(id) FROM report_categories));

3.3 feedback_categories 反馈分类

CREATE TABLE feedback_categories (
    id BIGSERIAL PRIMARY KEY,
    code VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(50) NOT NULL,
    description VARCHAR(200),
    enabled BOOLEAN NOT NULL DEFAULT TRUE,
    sort_order INT NOT NULL DEFAULT 0,
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL
);

INSERT INTO feedback_categories (code, name, description, sort_order, created_at, updated_at) VALUES
  ('bug',        'BUG 报告',  '使用中遇到的问题', 1, ...),
  ('consult',    '使用咨询',  '不知道怎么用', 2, ...),
  ('business',   '内容合作',  '合作/商务联系', 3, ...),
  ('suggestion', '功能建议',  '希望增加什么功能', 4, ...);

SELECT setval('feedback_categories_id_seq', (SELECT MAX(id) FROM feedback_categories));

3.4 reports 举报工单

CREATE TABLE reports (
    id BIGSERIAL PRIMARY KEY,
    reporter_id BIGINT NOT NULL,
    star_id BIGINT,
    target_type VARCHAR(30) NOT NULL,    -- asset | user_profile | description_word
    target_id BIGINT NOT NULL,
    target_snapshot JSONB NOT NULL,      -- 提交时的对象快照
    
    category_code VARCHAR(50) NOT NULL,
    description VARCHAR(500),
    is_anonymous BOOLEAN NOT NULL DEFAULT FALSE,
    
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
        -- pending / reviewing / auto_hidden / resolved / dismissed
    is_auto_hidden BOOLEAN NOT NULL DEFAULT FALSE,
    
    resolved_action VARCHAR(20),         -- takedown | ban | warn | dismissed
    resolved_by BIGINT,
    resolved_at BIGINT,
    resolution_note VARCHAR(500),
    
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL,
    
    CONSTRAINT fk_reports_category FOREIGN KEY (category_code)
        REFERENCES report_categories(code) ON DELETE RESTRICT
);
CREATE INDEX idx_reports_status_created ON reports(status, created_at DESC);
CREATE INDEX idx_reports_target ON reports(target_type, target_id, status);
CREATE INDEX idx_reports_reporter ON reports(reporter_id, created_at DESC);

-- 防重复:同一举报人对同一对象在未结案前只能有一条
CREATE UNIQUE INDEX uk_reports_reporter_target_pending
    ON reports(reporter_id, target_type, target_id)
    WHERE status IN ('pending', 'reviewing', 'auto_hidden');

3.5 report_evidence 举报证据图

CREATE TABLE report_evidence (
    id BIGSERIAL PRIMARY KEY,
    report_id BIGINT NOT NULL,
    oss_key VARCHAR(255) NOT NULL,
    oss_url VARCHAR(500),
    sort_order INT NOT NULL DEFAULT 0,
    created_at BIGINT NOT NULL,
    CONSTRAINT fk_report_evidence_report
        FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
);
CREATE INDEX idx_report_evidence_report ON report_evidence(report_id, sort_order);

3.6 feedbacks 反馈工单

CREATE TABLE feedbacks (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL,
    star_id BIGINT,
    category_code VARCHAR(50) NOT NULL,
    title VARCHAR(100) NOT NULL,
    content TEXT NOT NULL,
    contact VARCHAR(100),
    is_anonymous BOOLEAN NOT NULL DEFAULT FALSE,
    
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
        -- pending / reviewing / replied / closed / archived
    
    replied_by BIGINT,
    replied_at BIGINT,
    reply_content TEXT,
    
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL,
    
    CONSTRAINT fk_feedbacks_category FOREIGN KEY (category_code)
        REFERENCES feedback_categories(code) ON DELETE RESTRICT
);
CREATE INDEX idx_feedbacks_status_created ON feedbacks(status, created_at DESC);
CREATE INDEX idx_feedbacks_user ON feedbacks(user_id, created_at DESC);
CREATE INDEX idx_feedbacks_category ON feedbacks(category_code, status);

3.7 feedback_evidence 反馈截图

CREATE TABLE feedback_evidence (
    id BIGSERIAL PRIMARY KEY,
    feedback_id BIGINT NOT NULL,
    oss_key VARCHAR(255) NOT NULL,
    oss_url VARCHAR(500),
    sort_order INT NOT NULL DEFAULT 0,
    created_at BIGINT NOT NULL,
    CONSTRAINT fk_feedback_evidence_feedback
        FOREIGN KEY (feedback_id) REFERENCES feedbacks(id) ON DELETE CASCADE
);
CREATE INDEX idx_feedback_evidence_feedback ON feedback_evidence(feedback_id, sort_order);

3.8 moderation_actions 审核动作流水

CREATE TABLE moderation_actions (
    id BIGSERIAL PRIMARY KEY,
    report_id BIGINT,
    feedback_id BIGINT,
    admin_id BIGINT NOT NULL,
    action_type VARCHAR(30) NOT NULL,   -- takedown | unhide | ban | warn | dismiss | reply | close | archive
    target_type VARCHAR(30),
    target_id BIGINT,
    note VARCHAR(500),
    success BOOLEAN NOT NULL,
    error_message VARCHAR(500),
    created_at BIGINT NOT NULL,
    CONSTRAINT chk_moderation_actions_xor
        CHECK ((report_id IS NOT NULL) OR (feedback_id IS NOT NULL))
);
CREATE INDEX idx_moderation_actions_report ON moderation_actions(report_id, created_at DESC);
CREATE INDEX idx_moderation_actions_feedback ON moderation_actions(feedback_id, created_at DESC);
CREATE INDEX idx_moderation_actions_admin ON moderation_actions(admin_id, created_at DESC);

3.9 moderation_target_status 共享"对象受管控状态"

CREATE TABLE moderation_target_status (
    id BIGSERIAL PRIMARY KEY,
    target_type VARCHAR(30) NOT NULL,        -- asset | user | description_word
    target_id BIGINT NOT NULL,
    
    is_hidden BOOLEAN NOT NULL DEFAULT FALSE,   -- 隐藏/下架
    is_banned BOOLEAN NOT NULL DEFAULT FALSE,   -- 封禁(仅 user
    is_warned BOOLEAN NOT NULL DEFAULT FALSE,   -- 已警告(仅 user
    
    reason VARCHAR(200),
    source VARCHAR(30) NOT NULL,                -- auto | admin
    source_report_id BIGINT,
    operator_admin_id BIGINT,
    
    created_at BIGINT NOT NULL,
    updated_at BIGINT NOT NULL,
    
    CONSTRAINT uk_moderation_target UNIQUE (target_type, target_id)
);
CREATE INDEX idx_mts_hidden ON moderation_target_status(is_hidden) WHERE is_hidden = TRUE;
CREATE INDEX idx_mts_banned ON moderation_target_status(is_banned) WHERE is_banned = TRUE;

写入方

  • moderationService (Go) — 自动隐藏时 UPSERT
  • TopFans-Activity 后台 (FastAPI) — 审核员手动下架/封禁/警告时 UPSERT

读取方

  • assetService 藏品查询/详情/列表:LEFT JOIN moderation_target_status ON target_type='asset' AND target_id=assets.id WHERE COALESCE(is_hidden, false) = false
  • userService 登录/操作校验:LEFT JOIN ... WHERE COALESCE(is_banned, false) = false
  • AI 描述词服务查询时同样 JOIN

3.10 admin_audit_logs 管理员操作日志

CREATE TABLE admin_audit_logs (
    id BIGSERIAL PRIMARY KEY,
    admin_id BIGINT NOT NULL,
    action VARCHAR(50) NOT NULL,
    resource_type VARCHAR(30),
    resource_id BIGINT,
    ip VARCHAR(50),
    user_agent VARCHAR(500),
    extra JSONB,
    created_at BIGINT NOT NULL
);
CREATE INDEX idx_admin_audit_logs_admin ON admin_audit_logs(admin_id, created_at DESC);
CREATE INDEX idx_admin_audit_logs_resource ON admin_audit_logs(resource_type, resource_id);

3.11 Redis 数据结构

Key 模式 类型 用途 TTL
mod:report:counter:{target_type}:{target_id} String (INCR) 自动隐藏阈值计数(独立用户数) 7 天
mod:report:user:{target_type}:{target_id}:{user_id} String 防重复(仅独立用户才计数) 24h
mod:report:lock:{target_type}:{target_id} String 防并发触发自动隐藏 5s
mod:auto_hide_threshold String (缓存) 自动隐藏阈值 N默认 5 永久

3.12 序列同步规范

遵循 CLAUDE.md 规范:所有手动指定 ID 的 INSERT 末尾必须 SELECT setval('table_id_seq', (SELECT MAX(id) FROM table))

新表起始值:CREATE SEQUENCE table_id_seq START WITH 10000; 预留测试数据空间。


4. 状态机

4.1 举报状态机

                    ┌──────────────────────────────────┐
                    │           pending                │
                    │         (待处理)                  │
                    └─────┬─────────────┬──────────────┘
              管理员认领   │             │  阈值 N 触发
                          ▼             ▼
              ┌──────────────────┐  ┌──────────────────┐
              │    reviewing     │  │   auto_hidden    │
              │    (审核中)       │  │  (已自动隐藏)     │
              └────┬────┬────┬───┘  └────┬─────────────┘
        下架/封禁  │    │驳回│ 解除隐藏  │ 管理员认领
        /警告     │    │    │            ▼
                   ▼    │    ▼    ┌──────────────────┐
              ┌──────┐  │  ┌──────────────┐         │
              │resolved│ │  │  dismissed   │         │
              │(已处理)│ │  │  (已驳回)    │         │
              └──────┘  │  └──────────────┘         │
                         │                          │
                         └────→ resolved / dismissed
状态 含义 进入 退出
pending 新提交待处理 创建 认领 OR 自动隐藏
reviewing 管理员已认领 认领 做出处理动作
auto_hidden 触发自动隐藏 阈值达成 管理员认领并做最终裁决
resolved 已处理 takedown/ban/warn 终态
dismissed 已驳回 dismiss 终态

并发认领防护

UPDATE reports
SET status = 'reviewing', updated_at = ?
WHERE id = ? AND status IN ('pending', 'auto_hidden')

用 affected rows = 1 判断是否抢锁成功。

4.2 反馈状态机

         ┌────────────┐
         │  pending   │
         └─────┬──────┘
               │ 认领
               ▼
       ┌────────────────┐
       │   reviewing    │
       └─┬──────┬────┬──┘
         │      │    │
   已回复 │      │关闭│ 归档
         ▼      ▼    ▼
   ┌────────┐ ┌────────┐ ┌────────┐
   │replied │ │closed  │ │archived│
   └────────┘ └────────┘ └────────┘
状态 进入 备注
pending 创建 初始
reviewing 认领
replied 填写 reply_content 终态,触发用户通知
closed 点击关闭 终态,重复/无效
archived 归档 终态,已处理保留记录

5. API 设计

5.1 客户端 APIuni-app 端调用,路径前缀 /api/v1/moderation

Method & Path 说明
GET /api/v1/moderation/report-categories 获取启用中的举报分类
POST /api/v1/moderation/reports 提交举报
GET /api/v1/moderation/reports?status=&page=&page_size= 我的举报记录
GET /api/v1/moderation/reports/{id} 查看举报进度与结果
GET /api/v1/moderation/feedback-categories 获取反馈分类
POST /api/v1/moderation/feedbacks 提交反馈
GET /api/v1/moderation/feedbacks?status=&page=&page_size= 我的反馈记录
GET /api/v1/moderation/feedbacks/{id} 查看反馈详情(含回复)

提交举报请求体

{
  "target_type": "asset",
  "target_id": 12345,
  "category_code": "pornographic",
  "description": "图片含有裸露内容",
  "is_anonymous": true,
  "evidence_keys": ["report/2026/06/11/uuid1.png"]
}

响应

{
  "code": 200,
  "data": {
    "report_id": 67890,
    "status": "pending",
    "auto_hidden": true,
    "target_hidden": true,
    "created_at": 1718123456789
  }
}

错误响应

{ "code": 50004, "message": "您已举报过该对象,请等待处理" }

证据上传:客户端先调用 GET /api/v1/assets/oss/signature?type=asset 直传 OSS再把返回的 key 提交给本接口。

5.2 后台 APIVue3 后台调用,路径前缀 /api/admin/moderation

Method & Path 说明
GET /api/admin/moderation/reports?status=&category=&target_type=&keyword=&page=&page_size= 举报工单列表
GET /api/admin/moderation/reports/{id} 举报详情
POST /api/admin/moderation/reports/{id}/claim 认领
POST /api/admin/moderation/reports/{id}/release 释放认领
POST /api/admin/moderation/reports/{id}/actions 执行审核动作
GET /api/admin/moderation/feedbacks?status=&category=&keyword=&page=&page_size= 反馈列表
GET /api/admin/moderation/feedbacks/{id} 反馈详情
POST /api/admin/moderation/feedbacks/{id}/claim 认领反馈
POST /api/admin/moderation/feedbacks/{id}/reply 回复(→ replied
POST /api/admin/moderation/feedbacks/{id}/close 关闭
POST /api/admin/moderation/feedbacks/{id}/archive 归档
GET/POST/PUT/DELETE /api/admin/moderation/report-categories 举报分类 CRUD
GET/POST/PUT/DELETE /api/admin/moderation/feedback-categories 反馈分类 CRUD
GET /api/admin/moderation/stats 看板:今日待处理/平均处理时长/分类分布

执行审核动作请求

{
  "action": "takedown",
  "note": "图片含裸露,已下架",
  "send_notification": true
}

6. 关键流程

6.1 用户提交举报(含自动隐藏)

用户点击"举报"
   │
   ▼
uni-app → POST /api/v1/moderation/reports
   │
   ▼
gateway → moderationService.SubmitReport()
   │
   ├─ 1. 验证target_type 合法、分类启用、description ≤ 500 字、evidence ≤ 5 张
   ├─ 2. 验证目标对象存在assetService.GetAssetRPC / userService.GetUserRPC
   ├─ 3. 防重复检查:同 (reporter, target, type) 在 pending/reviewing/auto_hidden 已有?
   │       有 → 返回 50004 错误
   ├─ 4. 写入 reports 表status='pending'snapshot 拿当前对象信息)
   ├─ 5. 写 report_evidenceOSS keys
   ├─ 6. Redis Lua 计数(独立用户才 INCR
   │       首次计数 → 检查 counter >= threshold (默认 5)
   │       └─ 达到 → 触发自动隐藏:
   │             ① UPDATE reports WHERE target=... AND status='pending' 
   │                SET status='auto_hidden', is_auto_hidden=true
   │             ② INSERT INTO moderation_target_status
   │                (asset, is_hidden=true, source='auto', source_report_id=...)
   │             ③ 发通知给被举报方:"您的内容被多人举报已自动隐藏"
   ├─ 7. 返回 {report_id, status, auto_hidden, target_hidden, created_at}

6.2 后台审核流程

审核员登录后台 → 列表页 (筛选 status=pending OR auto_hidden)
   │
   ▼
详情 → GET /api/admin/moderation/reports/{id}
   │
   ▼
认领 → POST /api/admin/moderation/reports/{id}/claim
   │   UPDATE reports SET status='reviewing', updated_at=now
   │   WHERE id=? AND status IN ('pending','auto_hidden')
   │   affected=1 才返回成功
   │
   ▼
查看证据、目标快照、举报人、流水
   │
   ▼
执行动作 → POST /api/admin/moderation/reports/{id}/actions
   │
   ├─ takedown藏品/描述词/用户内容):
   │     UPSERT moderation_target_status (is_hidden=true, source='admin', ...)
   │     UPDATE reports SET status='resolved', resolved_action='takedown', ...
   │     发通知给被举报方
   │
   ├─ ban用户:
   │     UPSERT moderation_target_status (is_banned=true, source='admin', ...)
   │     UPDATE reports SET status='resolved', resolved_action='ban', ...
   │     发通知给被封禁用户
   │
   ├─ warn用户:
   │     UPSERT moderation_target_status (is_warned=true, source='admin', ...)
   │     UPDATE reports SET status='resolved', resolved_action='warn', ...
   │     发站内信给用户
   │
   ├─ dismiss驳回:
   │     如果当前 auto_hiddenUPSERT is_hidden=false解除隐藏
   │     UPDATE reports SET status='dismissed', resolved_action='dismissed', ...
   │     发通知给举报人:"您的举报已驳回"
   │
   └─ 写 moderation_actions 流水 + admin_audit_logs

6.3 反馈流程

用户提交反馈 → POST /api/v1/moderation/feedbacks
   │  moderationService 写 feedbacks (status='pending')
   ▼
后台列表 → GET /api/admin/moderation/feedbacks
   ▼
认领 → POST /api/admin/moderation/feedbacks/{id}/claim
   ▼
回复 → POST /api/admin/moderation/feedbacks/{id}/reply
   │  body: { content: "..." }
   │  UPDATE feedbacks SET status='replied', reply_content=?, replied_by=?, replied_at=now
   │  发通知给反馈人

6.4 自动隐藏 Lua 脚本

-- KEYS[1]=counter, KEYS[2]=user_marker, KEYS[3]=lock
-- ARGV[1]=threshold, ARGV[2]=ttl_counter, ARGV[3]=ttl_marker
local first = redis.call('SET', KEYS[2], '1', 'NX', 'EX', ARGV[3])
if not first then
  return {0, tonumber(redis.call('GET', KEYS[1]) or '0')}
end
local n = redis.call('INCR', KEYS[1])
if n == 1 then
  redis.call('EXPIRE', KEYS[1], ARGV[2])
end
if n >= tonumber(ARGV[1]) then
  return {1, n}
end
return {0, n}

返回 {triggered, count}triggered=1 时上层触发自动隐藏。


7. 错误码

含义 HTTP
50001 举报分类不存在 400
50002 反馈分类不存在 400
50003 目标对象不存在 404
50004 同一对象已举报过(未结案) 429
50005 描述过长(>500 字) 400
50006 证据图超限(>5 张) 400
50007 工单不存在 404
50008 工单已被他人认领 409
50009 工单已结案 400
50010 管理员权限不足 403

8. 通知机制

复用 notificationService 模块,注册 5 个模板:

事件 接收方 模板名 渠道
举报状态变更为 resolved/dismissed 举报人 report_resolved 站内信 + 通知中心
举报成立 + takedown 被举报方 report_takedown_notice 站内信
举报成立 + ban 被举报用户 report_ban_notice 站内信 + 短信
举报成立 + warn 被举报用户 report_warn_notice 站内信
反馈被回复 反馈人 feedback_replied 站内信 + 通知中心

模板字段:{reporter_nickname, target_type, target_id, action, reason, related_url}


9. 安全与防护

9.1 防护机制

机制 实现
防重复举报 DB 局部唯一索引 uk_reports_reporter_target_pending(结案后允许再次举报)
防 Redis 计数重复 Lua 脚本用 user_marker SETNX 保证独立用户才 INCR
防自动隐藏并发 Redis 短锁 mod:report:lock:* 5s
证据图校验 OSS 签名接口限定目录 report/feedback/ 前缀
描述长度 服务端校验 ≤ 500 字DB 字段 VARCHAR(500)
证据图数量 服务端校验 ≤ 5 张
匿名保护 is_anonymous=true 时:reporter_id 仍记录但 API 响应中不返回给被举报方;后台仅对超级管理员可见

9.2 数据隔离

  • 客户端 JWT 仅可看自己的举报/反馈(reporter_id = current_user_id
  • 后台 JWT 走 TopFans-activity 现有 verify_token,所有接口都需 Depends(verify_token)
  • 后续如要分级权限(仅超管可恢复隐藏等),在 JWT payload 加 role 字段

9.3 数据保留

  • reports / feedbacks 永久保留(合规需要)
  • moderation_actions 永久保留(审计)
  • moderation_target_status UPSERT 保留最新状态,历史可查 moderation_actions

10. 实施步骤

阶段 1数据库topfans 库)

  • 1.1 写迁移 D:\shanghai\topfans\backend\migrations\2026_06_11_010_moderation_tables.sql
  • 1.2 包含 8 张表 + 索引 + 默认分类种子数据
  • 1.3 末尾必须 SELECT setval('xxx_id_seq', (SELECT MAX(id) FROM xxx))CLAUDE.md 强制)
  • 1.4 在 backend/scripts/ 加 Go 脚本生成种子数据(带序列重置)

阶段 2Go moderationServicetopfans

  • 2.1 D:\shanghai\topfans\backend\services\moderationService\ 新建目录
    • main.go / config/ / repository/ / service/ / provider/ / client/ / go.mod / start.sh
    • 复用 socialService 类似的分层
  • 2.2 D:\shanghai\topfans\backend\proto\moderation\moderation.proto
    • SubmitReportRequest/Response
    • ListMyReportsRequest/Response
    • GetReportRequest/Response
    • GetReportCategoriesRequest/Response
    • SubmitFeedbackRequest/Response
    • ListMyFeedbacksRequest/Response
    • GetFeedbackRequest/Response
    • GetFeedbackCategoriesRequest/Response
  • 2.3 D:\shanghai\topfans\backend\pkg\proto\moderation\ 编译生成 .pb.go
  • 2.4 D:\shanghai\topfans\backend\pkg\models\moderation.go ORM 模型
  • 2.5 D:\shanghai\topfans\backend\services\moderationService\repository\moderation_repository.go 数据访问
  • 2.6 D:\shanghai\topfans\backend\services\moderationService\service\:
    • report_service.go
    • feedback_service.go
    • category_service.go
    • auto_hide_service.go(含 Redis Lua 脚本)
  • 2.7 D:\shanghai\topfans\backend\services\moderationService\client\:
    • asset_client.go(验证目标存在)
    • user_client.go(验证用户/封禁查询)
  • 2.8 D:\shanghai\topfans\backend\services\moderationService\provider\social_provider.go Dubbo 注册
  • 2.9 D:\shanghai\topfans\backend\go.work 加入新服务
  • 2.10 Gateway 层:
    • D:\shanghai\topfans\backend\gateway\controller\moderation_controller.go
    • D:\shanghai\topfans\backend\gateway\dto\moderation_dto.go
    • D:\shanghai\topfans\backend\gateway\dto\moderation_converter.go
    • D:\shanghai\topfans\backend\gateway\router\router.go 注册 /api/v1/moderation/*

阶段 3TopFans-Activity 后台 APIFastAPI

  • 3.1 D:\shanghai\TopFans-activity\backend\models\moderation.py SQLAlchemy ORM与 PG 表结构对应)
  • 3.2 D:\shanghai\TopFans-activity\backend\schemas\moderation.py Pydantic 模型
  • 3.3 D:\shanghai\TopFans-activity\backend\crud\moderation_crud.py 数据库操作
    • list_reports(filters, page) / get_report_detail(id) / claim_report(id) / execute_action(id, action, ...) / list_categories() / upsert_category() / ...
  • 3.4 D:\shanghai\TopFans-activity\backend\handlers\moderation_admin.py 路由层
    • 复用现有 verify_token 中间件
  • 3.5 D:\shanghai\TopFans-activity\backend\router\__init__.py 注册路由
  • 3.6 数据库迁移(如 ORM 与 PG 表不一致时):
    • D:\shanghai\TopFans-activity\backend\migrations\010_moderation_init.sql(如果用 SQLAlchemy create_all 可省略)

阶段 4后台前端 (Vue3)

  • 4.1 D:\shanghai\TopFans-activity\frontend\src\api\moderation.js axios 封装
    • getReports() / getReportDetail() / claimReport() / executeAction() / ...
    • getReportCategories() / createCategory() / updateCategory() / deleteCategory() / ...
  • 4.2 D:\shanghai\TopFans-activity\frontend\src\views\moderation\
    • ReportList.vue 举报工单列表(筛选:状态/分类/目标类型/关键词)
    • ReportDetail.vue 举报详情(证据图、目标快照、举报人、流水)+ 审核动作按钮
    • FeedbackList.vue 反馈列表
    • FeedbackDetail.vue 反馈详情 + 回复表单
    • ReportCategoryConfig.vue 举报分类 CRUD
    • FeedbackCategoryConfig.vue 反馈分类 CRUD
    • Dashboard.vue 审核看板(可选)
  • 4.3 D:\shanghai\TopFans-activity\frontend\src\router\index.js 加路由
    • /moderation/reports
    • /moderation/reports/:id
    • /moderation/feedbacks
    • /moderation/feedbacks/:id
    • /moderation/categories/report
    • /moderation/categories/feedback
  • 4.4 菜单注册(按 Layout.vue 现成结构)
  • 4.5 Element Plus 组件复用el-table / el-form / el-dialog / el-image / el-tag / el-pagination

阶段 5客户端 uni-app

  • 5.1 D:\shanghai\topfans\frontend\utils\api.js 加:
    • getReportCategoriesApi() / submitReportApi(payload) / getMyReportsApi() / getMyReportDetailApi(id)
    • getFeedbackCategoriesApi() / submitFeedbackApi(payload) / getMyFeedbacksApi() / getMyFeedbackDetailApi(id)
  • 5.2 D:\shanghai\topfans\frontend\components\ReportModal.vue 通用举报弹窗
    • props: targetType, targetId, targetName
    • 表单:分类单选 + 描述 textarea + 证据图上传(最多 5 张)+ 匿名开关
    • submitReportApi,成功后提示"已提交"+ 自动隐藏提示
  • 5.3 在以下位置接入 ReportModal
    • 藏品详情页 (NftDetailModal.vue 等) 长按或"..."菜单
    • 用户主页(粉丝身份下)"..."菜单
    • 描述词展示组件(如果有)的"..."菜单
  • 5.4 D:\shanghai\topfans\frontend\pages\profile\feedback.vue 意见反馈页
    • 复用 ReportModal 的 evidence 上传组件
    • 表单:分类下拉 + 标题 + 内容 + 联系方式(可选) + 截图
  • 5.5 D:\shanghai\topfans\frontend\pages\profile\myReports.vue 我的举报列表
  • 5.6 D:\shanghai\topfans\frontend\pages\profile\myFeedbacks.vue 我的反馈列表
  • 5.7 页面间跳转:从"我的" → 各项入口

阶段 6业务服务读路径加 JOIN按需展开

  • 6.1 D:\shanghai\topfans\backend\services\assetService\repository\ 藏品查询
    • 详情/列表 SQLLEFT JOIN moderation_target_status ON target_type='asset' AND target_id=assets.id WHERE COALESCE(is_hidden, false) = false
  • 6.2 D:\shanghai\topfans\backend\services\userService\repository\ 用户校验
    • 登录/操作校验:LEFT JOIN ... ON target_type='user' AND target_id=users.id WHERE COALESCE(is_banned, false) = false
  • 6.3 描述词相关服务(aiChatService 或新)查询时同样 JOIN
  • 6.4 性能优化(如果热路径压力变大):
    • Redis 缓存 mod:status:cache:{target_type}:{target_id} TTL 60s
    • 命中即跳过 JOIN

阶段 7通知与测试

  • 7.1 通知模板注册到 notificationService5 个模板)
  • 7.2 Go 单元测试
    • report_service_test.go 防重复、自动隐藏触发
    • feedback_service_test.go 状态机
    • auto_hide_service_test.go Lua 脚本逻辑
  • 7.3 FastAPI 单元测试
    • test_moderation_admin.py 列表/详情/认领/动作
    • 鉴权测试(无 token 401
  • 7.4 集成测试
    • 端到端:提交举报 → 后台审核 → 状态变更 → 用户收到通知
    • 自动隐藏5 个独立用户举报 → 自动隐藏 + 状态表写入
  • 7.5 前端联调
    • uni-app → gateway → Go service
    • Vue3 后台 → FastAPI → PG

11. 附录

11.1 配置文件

D:\shanghai\topfans\backend\services\moderationService\config\moderation_config.go

type ModerationConfig struct {
    AutoHideThreshold int           // 默认 5
    CounterTTL        time.Duration // 7 天
    UserMarkerTTL     time.Duration // 24h
    LockTTL           time.Duration // 5s
    MaxDescriptionLen int           // 500
    MaxEvidenceCount  int           // 5
}

11.2 跨服务调用清单

调用方 被调服务 触发时机 失败处理
moderationService assetService.GetAssetRPC 提交举报验证目标 返回 50003
moderationService userService.GetUserRPC 提交举报验证用户 返回 50003
moderationService notificationService 触发自动隐藏后通知 仅日志,不影响主流程
TopFans-Activity (无 — 仅读写 DB) - -
assetService (无 — 仅读 DB) - -
userService (无 — 仅读 DB) - -

11.3 监控指标(建议)

  • moderation.report.submitted.count (counter, tags: target_type, category_code)
  • moderation.report.auto_hidden.count (counter)
  • moderation.report.resolve.duration (histogram, 从 created_at 到 resolved_at)
  • moderation.feedback.reply.duration (histogram)
  • moderation.actions.failed.count (counter, tags: action_type, error)

11.4 变更历史

版本 日期 作者 变更内容
v1.0 2026-06-11 Claude 初始设计

📝 设计方案已确认,可以开始实现!