docs(moderation): 举报与反馈系统设计
This commit is contained in:
parent
a1bb302be8
commit
5320eceb32
@ -0,0 +1,883 @@
|
||||
# 举报与反馈系统设计
|
||||
|
||||
> **状态**:设计已确认 ✅
|
||||
> **创建时间**:2026-06-11
|
||||
> **作者**:Claude
|
||||
> **目标**:在 TopFans 平台新增内容举报与用户反馈两大工单系统,支撑运营/审核员通过独立 web 后台对违规内容(数字藏品/用户/AI 描述词)和用户反馈(BUG/咨询/合作/建议)进行审核处理
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [背景与目标](#1-背景与目标)
|
||||
2. [架构概览](#2-架构概览)
|
||||
3. [数据模型](#3-数据模型)
|
||||
4. [状态机](#4-状态机)
|
||||
5. [API 设计](#5-api-设计)
|
||||
6. [关键流程](#6-关键流程)
|
||||
7. [错误码](#7-错误码)
|
||||
8. [通知机制](#8-通知机制)
|
||||
9. [安全与防护](#9-安全与防护)
|
||||
10. [实施步骤](#10-实施步骤)
|
||||
11. [附录](#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` 举报分类
|
||||
|
||||
```sql
|
||||
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` 反馈分类
|
||||
|
||||
```sql
|
||||
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` 举报工单
|
||||
|
||||
```sql
|
||||
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` 举报证据图
|
||||
|
||||
```sql
|
||||
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` 反馈工单
|
||||
|
||||
```sql
|
||||
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` 反馈截图
|
||||
|
||||
```sql
|
||||
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` 审核动作流水
|
||||
|
||||
```sql
|
||||
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` 共享"对象受管控状态"
|
||||
|
||||
```sql
|
||||
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` 管理员操作日志
|
||||
|
||||
```sql
|
||||
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 | 终态 |
|
||||
|
||||
**并发认领防护**:
|
||||
```sql
|
||||
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}` | 查看反馈详情(含回复)|
|
||||
|
||||
**提交举报请求体**:
|
||||
```json
|
||||
{
|
||||
"target_type": "asset",
|
||||
"target_id": 12345,
|
||||
"category_code": "pornographic",
|
||||
"description": "图片含有裸露内容",
|
||||
"is_anonymous": true,
|
||||
"evidence_keys": ["report/2026/06/11/uuid1.png"]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"report_id": 67890,
|
||||
"status": "pending",
|
||||
"auto_hidden": true,
|
||||
"target_hidden": true,
|
||||
"created_at": 1718123456789
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{ "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` | 看板:今日待处理/平均处理时长/分类分布 |
|
||||
|
||||
**执行审核动作请求**:
|
||||
```json
|
||||
{
|
||||
"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 脚本
|
||||
|
||||
```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 脚本生成种子数据(带序列重置)
|
||||
|
||||
### 阶段 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.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/*`
|
||||
|
||||
### 阶段 3:TopFans-Activity 后台 API(FastAPI)
|
||||
- 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\` 藏品查询
|
||||
- 详情/列表 SQL:`LEFT 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 通知模板注册到 `notificationService`(5 个模板)
|
||||
- 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`:
|
||||
```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 | 初始设计 |
|
||||
|
||||
---
|
||||
|
||||
**📝 设计方案已确认,可以开始实现!** ✅
|
||||
Loading…
Reference in New Issue
Block a user