37 KiB
37 KiB
举报与反馈系统设计
状态:设计已确认 ✅ 创建时间:2026-06-11 作者:Claude 目标:在 TopFans 平台新增内容举报与用户反馈两大工单系统,支撑运营/审核员通过独立 web 后台对违规内容(数字藏品/用户/AI 描述词)和用户反馈(BUG/咨询/合作/建议)进行审核处理
目录
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) — 自动隐藏时 UPSERTTopFans-Activity后台 (FastAPI) — 审核员手动下架/封禁/警告时 UPSERT
读取方:
assetService藏品查询/详情/列表:LEFT JOIN moderation_target_status ON target_type='asset' AND target_id=assets.id WHERE COALESCE(is_hidden, false) = falseuserService登录/操作校验: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 客户端 API(uni-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 后台 API(Vue3 后台调用,路径前缀 /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_evidence(OSS 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_hidden:UPSERT 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_statusUPSERT 保留最新状态,历史可查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 脚本生成种子数据(带序列重置)
阶段 2:Go moderationService(topfans)
- 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.protoSubmitReportRequest/ResponseListMyReportsRequest/ResponseGetReportRequest/ResponseGetReportCategoriesRequest/ResponseSubmitFeedbackRequest/ResponseListMyFeedbacksRequest/ResponseGetFeedbackRequest/ResponseGetFeedbackCategoriesRequest/Response
- 2.3
D:\shanghai\topfans\backend\pkg\proto\moderation\编译生成.pb.go - 2.4
D:\shanghai\topfans\backend\pkg\models\moderation.goORM 模型 - 2.5
D:\shanghai\topfans\backend\services\moderationService\repository\moderation_repository.go数据访问 - 2.6
D:\shanghai\topfans\backend\services\moderationService\service\:report_service.gofeedback_service.gocategory_service.goauto_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.goDubbo 注册 - 2.9
D:\shanghai\topfans\backend\go.work加入新服务 - 2.10 Gateway 层:
D:\shanghai\topfans\backend\gateway\controller\moderation_controller.goD:\shanghai\topfans\backend\gateway\dto\moderation_dto.goD:\shanghai\topfans\backend\gateway\dto\moderation_converter.goD:\shanghai\topfans\backend\gateway\router\router.go注册/api/v1/moderation/*
阶段 3:TopFans-Activity 后台 API(FastAPI)
- 3.1
D:\shanghai\TopFans-activity\backend\models\moderation.pySQLAlchemy ORM(与 PG 表结构对应) - 3.2
D:\shanghai\TopFans-activity\backend\schemas\moderation.pyPydantic 模型 - 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.jsaxios 封装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举报分类 CRUDFeedbackCategoryConfig.vue反馈分类 CRUDDashboard.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,成功后提示"已提交"+ 自动隐藏提示
- props:
- 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\藏品查询- 详情/列表 SQL:
LEFT JOIN moderation_target_status ON target_type='asset' AND target_id=assets.id WHERE COALESCE(is_hidden, false) = false
- 详情/列表 SQL:
- 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
- Redis 缓存
阶段 7:通知与测试
- 7.1 通知模板注册到
notificationService(5 个模板) - 7.2 Go 单元测试
report_service_test.go防重复、自动隐藏触发feedback_service_test.go状态机auto_hide_service_test.goLua 脚本逻辑
- 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 | 初始设计 |
📝 设计方案已确认,可以开始实现! ✅