docs: full API documentation + data compat guard
- docs/API.md: complete API reference (auth/identities/tickets/bans/users/ settings/notifications/polls/features/export/captcha/uploads/external/ install/health) with auth, rate limits, roles, data migration notes - auth.js register: also check user_identities for duplicate game_name (multi-identity compat for old data backfilled into new table) - README: link API docs, multi-identity section
This commit is contained in:
11
README.md
11
README.md
@@ -17,7 +17,12 @@
|
|||||||
| **导出** | CSV(UTF-8 BOM),导出工单/被举报人统计/操作日志(仅服主) |
|
| **导出** | CSV(UTF-8 BOM),导出工单/被举报人统计/操作日志(仅服主) |
|
||||||
| **插件** | Paper 1.20+,统一 `/report` 命令,游戏内注册/登录/举报/追踪 |
|
| **插件** | Paper 1.20+,统一 `/report` 命令,游戏内注册/登录/举报/追踪 |
|
||||||
| **找回密码** | 邮箱验证,1小时有效重置链接 |
|
| **找回密码** | 邮箱验证,1小时有效重置链接 |
|
||||||
| **安装向导** | Web 页面配置 MySQL 连接 + 创建服主账号 |
|
| **多来源身份** | 一个账号可同时绑定网易端 + 皮肤站身份,提交工单时选择身份 |
|
||||||
|
| **安装向导** | Web 页面配置 MySQL 连接 + 创建服主账号(完成后无需重启) |
|
||||||
|
|
||||||
|
## API 文档
|
||||||
|
|
||||||
|
完整接口文档见 **[docs/API.md](docs/API.md)**(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API)。
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -79,7 +84,7 @@ node backend/server.js
|
|||||||
- `closed` 为终态,仅服主可操作
|
- `closed` 为终态,仅服主可操作
|
||||||
- 结果申诉仅一次,关联原工单,仅服主可见
|
- 结果申诉仅一次,关联原工单,仅服主可见
|
||||||
|
|
||||||
## 注册来源
|
## 注册来源与多身份
|
||||||
|
|
||||||
| 来源 | 标识 | UID 含义 |
|
| 来源 | 标识 | UID 含义 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
@@ -88,6 +93,8 @@ node backend/server.js
|
|||||||
|
|
||||||
插件注册默认 `skin`。
|
插件注册默认 `skin`。
|
||||||
|
|
||||||
|
**多身份绑定**:一个账号可同时绑定网易端 + 皮肤站两个身份(控制台「我的身份」或 `POST /api/auth/identities` 管理)。提交工单时可选择使用哪个身份;`user_identities` 表由启动时自动迁移回填,`users` 表主身份字段保持不变(完全兼容旧数据)。
|
||||||
|
|
||||||
## 工单提交流程
|
## 工单提交流程
|
||||||
|
|
||||||
1. 填写标题 + 游戏名 + UID(登录后自动填充)
|
1. 填写标题 + 游戏名 + UID(登录后自动填充)
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ router.post('/register', validateLengths({
|
|||||||
const dup = await getRow('SELECT id FROM users WHERE game_name = ? AND source = ?', [game_name, source || 'netease']);
|
const dup = await getRow('SELECT id FROM users WHERE game_name = ? AND source = ?', [game_name, source || 'netease']);
|
||||||
if (dup) return res.status(400).json({ error: '该游戏名在此来源下已注册' });
|
if (dup) return res.status(400).json({ error: '该游戏名在此来源下已注册' });
|
||||||
}
|
}
|
||||||
|
// 多来源兼容: 该游戏名可能已被其他账号绑定为副身份
|
||||||
|
try {
|
||||||
|
const dupIdent = await getRow('SELECT id FROM user_identities WHERE source = ? AND game_name = ?', [source || 'netease', game_name]);
|
||||||
|
if (dupIdent) return res.status(400).json({ error: '该游戏名已绑定其他账号' });
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const hashed = await bcrypt.hash(password, 10);
|
const hashed = await bcrypt.hash(password, 10);
|
||||||
const r = await query(
|
const r = await query(
|
||||||
|
|||||||
342
docs/API.md
Normal file
342
docs/API.md
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
# MC Report System API 文档
|
||||||
|
|
||||||
|
Base URL: `http://<host>:3100/api`
|
||||||
|
|
||||||
|
## 通用约定
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 认证头 | `Authorization: Bearer <JWT>`(登录后获得) |
|
||||||
|
| API 密钥头 | `x-api-key: <api_key>`(安装时生成,注入前端页面,所有请求需携带) |
|
||||||
|
| 外部密钥头 | `x-external-key: <external_api_key>`(仅 /api/external 插件接口) |
|
||||||
|
| 内容类型 | `application/json`(文件上传用 `multipart/form-data`) |
|
||||||
|
| 错误格式 | `{ "error": "错误信息" }` |
|
||||||
|
| 限流 | 登录 8/min、注册 3/min、工单 500/min、匿名提交 5/min、全局 300/min |
|
||||||
|
|
||||||
|
**角色**:`owner`(服主) / `admin`(管理员) / `player`(玩家)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、认证 Auth
|
||||||
|
|
||||||
|
### POST /auth/send-verify-code
|
||||||
|
发送邮箱验证码(注册前校验邮箱归属)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "email": "player@example.com" }
|
||||||
|
```
|
||||||
|
响应: `{ "code_id": "uuid", "message": "验证码已发送" }`
|
||||||
|
> 若 SMTP 未配置,返回 `{ code_id, code, dev: true }`(开发模式直显验证码)。
|
||||||
|
|
||||||
|
### POST /auth/register
|
||||||
|
注册账号(需先获取邮箱验证码)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "player1", "password": "******", "email": "p@example.com",
|
||||||
|
"game_name": "Steve", "game_uid": "NETEASE_UID_001",
|
||||||
|
"source": "netease", // netease | skin(皮肤站可不填 UID)
|
||||||
|
"captcha_id": "uuid", "captcha_answer": "abcd",
|
||||||
|
"code_id": "uuid", "code": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
响应 201: `{ "message": "注册成功,请登录" }`
|
||||||
|
> 注册时自动在 `user_identities` 中创建主身份记录。
|
||||||
|
|
||||||
|
### POST /auth/login
|
||||||
|
```json
|
||||||
|
{ "username": "player1", "password": "******" }
|
||||||
|
```
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "<JWT>",
|
||||||
|
"user": { "id": 1, "username": "player1", "email": "p@example.com",
|
||||||
|
"game_name": "Steve", "game_uid": "NETEASE_UID_001",
|
||||||
|
"role": "player", "source": "netease" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /auth/me *(认证)*
|
||||||
|
返回当前用户完整信息,含 `identities` 数组(全部已绑定身份)。
|
||||||
|
|
||||||
|
### GET /auth/identities *(认证)*
|
||||||
|
列出当前账号绑定的全部身份(一个账号可同时绑定网易端 + 皮肤站)。
|
||||||
|
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "id": 1, "source": "netease", "game_name": "Steve", "game_uid": "NETEASE_UID_001", "created_at": "..." },
|
||||||
|
{ "id": 2, "source": "skin", "game_name": "Alex", "game_uid": "SKIN_UUID_002", "created_at": "..." }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /auth/identities *(认证)*
|
||||||
|
绑定新来源身份(同来源仅能绑一个,游戏名全局唯一)。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "source": "skin", "game_name": "Alex", "game_uid": "SKIN_UUID_002" }
|
||||||
|
```
|
||||||
|
| 规则 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| source | 必填,`netease` 或 `skin` |
|
||||||
|
| game_name | 必填,不能与其他账号已绑定的游戏名重复 |
|
||||||
|
| game_uid | 网易端必填 |
|
||||||
|
| 同来源重复 | 拒绝(先删除再重新绑定) |
|
||||||
|
| 至少保留一个 | 解绑时若只剩一个身份则拒绝 |
|
||||||
|
|
||||||
|
### DELETE /auth/identities/:id *(认证)*
|
||||||
|
解绑身份。响应: `{ "message": "已解绑" }`
|
||||||
|
|
||||||
|
### PUT /auth/profile *(认证)*
|
||||||
|
更新个人资料(game_name / game_uid / email)。主身份变更会同步到 `user_identities`。
|
||||||
|
|
||||||
|
### PUT /auth/password *(认证)*
|
||||||
|
```json
|
||||||
|
{ "oldPassword": "旧密码", "newPassword": "新密码" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /auth/forgot-password
|
||||||
|
```json
|
||||||
|
{ "email": "p@example.com" }
|
||||||
|
```
|
||||||
|
发送重置链接(使用 `reset_password` 模板,1 小时有效)。
|
||||||
|
|
||||||
|
### POST /auth/reset-password
|
||||||
|
```json
|
||||||
|
{ "token": "<reset_token>", "password": "新密码" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /auth/verify-email
|
||||||
|
验证邮箱(token 或 6 位验证码):
|
||||||
|
```json
|
||||||
|
{ "code": "<verify_token 或 6 位验证码>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、工单 Tickets
|
||||||
|
|
||||||
|
状态机:`pending → processing → awaiting_info → resolved / rejected → closed(7天自动关闭)`;结果申诉 `appealing`。
|
||||||
|
|
||||||
|
### GET /tickets *(认证)*
|
||||||
|
工单列表(带过滤)。Query: `type` / `status` / `priority` / `claimed=0|1` / `search`。
|
||||||
|
- player: 仅自己的工单
|
||||||
|
- admin: 待处理 + 自己接的(建议/投诉管理员/结果申诉不可见)
|
||||||
|
- owner: 全部
|
||||||
|
|
||||||
|
### GET /tickets/stats *(认证)*
|
||||||
|
```json
|
||||||
|
{ "total": 12, "byStatus": { "pending": 3, "processing": 2 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /tickets/unclaimed *(admin/owner)*
|
||||||
|
待认领工单。Query: `type`。
|
||||||
|
|
||||||
|
### GET /tickets/reported-players *(admin/owner)*
|
||||||
|
被举报人聚合统计(次数/处理中/已解决/已驳回/最近时间/常见原因)。
|
||||||
|
|
||||||
|
### GET /tickets/tracking?token=xxx
|
||||||
|
凭追踪标识查工单(匿名追踪用,无需登录)。
|
||||||
|
|
||||||
|
### GET /tickets/:id *(认证)*
|
||||||
|
工单详情(含 responses / attachments / transfers / parent_ticket)。
|
||||||
|
|
||||||
|
### POST /tickets *(可选认证)*
|
||||||
|
提交工单(支持匿名;登录用户可选用任一已绑定身份)。
|
||||||
|
|
||||||
|
`multipart/form-data` 或 JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "report", // report | suggestion | appeal | result_appeal
|
||||||
|
"title": "标题",
|
||||||
|
"reporter_game_name": "Steve", // 登录用户可传自己的任一身份,后端校验归属
|
||||||
|
"reporter_game_uid": "UID",
|
||||||
|
"target_game_name": "Alex", // 举报必填(与 UID 至少一项)
|
||||||
|
"target_game_uid": "UID",
|
||||||
|
"reason": "举报原因", // report/appeal 必填
|
||||||
|
"description": "详细描述", // suggestion/appeal 必填
|
||||||
|
"priority": "medium", // low|medium|high|urgent
|
||||||
|
"is_admin_complaint": 0, // 投诉管理员(仅服主可见)
|
||||||
|
"parent_ticket_id": null // result_appeal 必填(原工单)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
响应 201: `{ "id": 8, "tracking_token": "uuid", "message": "提交成功" }`(Set-Cookie 留存 tracking_token)
|
||||||
|
|
||||||
|
### PUT /tickets/:id *(admin/owner)*
|
||||||
|
更新状态/优先级/处理备注。
|
||||||
|
```json
|
||||||
|
{ "status": "processing", "priority": "high", "claim_note": "内部备注" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /tickets/:id/claim *(admin/owner)*
|
||||||
|
接单认领(建议/投诉管理员/结果申诉仅服主可认领)。
|
||||||
|
```json
|
||||||
|
{ "claim_note": "内部备注", "initial_reply": "给玩家的初始回复" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /tickets/:id/transfer *(admin/owner)*
|
||||||
|
转单。`{ "to_user_id": 2, "reason": "原因" }`
|
||||||
|
|
||||||
|
### POST /tickets/:id/response *(认证)*
|
||||||
|
回复/补充。`{ "content": "内容" }`
|
||||||
|
|
||||||
|
### POST /tickets/:id/anon-response
|
||||||
|
匿名补充(凭 tracking_token Cookie)。
|
||||||
|
|
||||||
|
### POST /tickets/batch *(admin/owner)*
|
||||||
|
批量操作。`{ "ids": [1,2], "action": "delete|status|close", "status": "resolved" }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、封禁 Bans *(owner)*
|
||||||
|
|
||||||
|
### GET /bans、GET /bans/active、GET /bans/min-duration *(认证)*
|
||||||
|
封禁列表 / 生效中 / 最短时长校验。
|
||||||
|
|
||||||
|
### POST /bans *(owner)*
|
||||||
|
```json
|
||||||
|
{ "player_name": "Alex", "player_uid": "UID", "source": "netease|skin",
|
||||||
|
"type": "ban|mute|warn|other", "duration": "7天", "reason": "原因",
|
||||||
|
"ticket_id": null }
|
||||||
|
```
|
||||||
|
|
||||||
|
### PUT /bans/:id *(owner)*
|
||||||
|
更新状态(`active|expired|appealed|lifted`)/原因/时长。
|
||||||
|
|
||||||
|
### DELETE /bans/:id *(owner)*
|
||||||
|
删除记录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、用户管理 Users *(admin/owner)*
|
||||||
|
|
||||||
|
### GET /users *(admin/owner)*、GET /users/:id *(admin/owner)*
|
||||||
|
用户列表 / 详情(含管辖子服务器)。
|
||||||
|
|
||||||
|
### POST /users *(admin/owner)*
|
||||||
|
创建用户(自动写入 user_identities 主身份)。角色 owner 仅服主可建。
|
||||||
|
|
||||||
|
### PUT /users/:id *(admin/owner)*
|
||||||
|
更新邮箱/游戏名/UID/来源/角色/状态/密码/管辖服务器。主身份字段变化自动同步 `user_identities`。
|
||||||
|
|
||||||
|
### GET /users/:id/servers *(admin/owner)*
|
||||||
|
管辖子服务器列表。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、系统设置 Settings *(admin/owner)*
|
||||||
|
|
||||||
|
| 端点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| GET /settings/settings | 全部设置(站点名称/地址/版权/ICP/SMTP) |
|
||||||
|
| PUT /settings/settings | 批量更新设置 |
|
||||||
|
| GET /settings/email-templates | 邮件模板列表 |
|
||||||
|
| GET /settings/email-templates/:code | 模板详情 |
|
||||||
|
| PUT /settings/email-templates/:code | 更新模板(subject/body,支持 HTML 与 {{变量}}) |
|
||||||
|
|
||||||
|
可用模板: `verify_email` / `email_code` / `ticket_created` / `ticket_updated` / `ticket_claimed` / `ticket_transferred` / `reset_password`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、通知 Notifications *(admin/owner)*
|
||||||
|
|
||||||
|
### GET/POST /notifications
|
||||||
|
列表 / 创建 `{ name, type: "webhook|email", webhook_url, events: "all|ticket_created,..." }`
|
||||||
|
|
||||||
|
### PUT/DELETE /notifications/:id
|
||||||
|
更新(名称/地址/类型/启用)/删除。
|
||||||
|
|
||||||
|
### POST /notifications/:id/test
|
||||||
|
发送测试 Webhook。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、投票 Polls
|
||||||
|
|
||||||
|
| 端点 | 认证 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET /polls/groups | 认证 | 分组/子服列表 |
|
||||||
|
| POST /polls/groups | owner | 添加分组 |
|
||||||
|
| DELETE /polls/groups/:id | owner | 删除分组 |
|
||||||
|
| GET /polls/active | 认证 | 当前生效投票(按分组聚合,含票数/我的投票) |
|
||||||
|
| GET/PUT/DELETE /polls/:id | owner | 详情/更新/删除 |
|
||||||
|
| POST /polls | owner | 创建(≥2选项,同子服仅一个进行中) |
|
||||||
|
| POST /polls/:id/vote | 认证 | 投票(管理员 1.5 票权,一人一票) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、更新内容 Features
|
||||||
|
|
||||||
|
| 端点 | 认证 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET /features | 认证 | 列表(状态: 待定/计划中/已完成) |
|
||||||
|
| POST /features | owner | 创建 |
|
||||||
|
| PUT/DELETE /features/:id | owner | 更新/删除 |
|
||||||
|
| POST /features/:id/vote | 认证 | 点赞 |
|
||||||
|
| POST /features/:id/comment | 认证 | 发表评论 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、导出 Export *(owner)*
|
||||||
|
|
||||||
|
| 端点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| GET /export/tickets?type=&status=&start_date=&end_date=&format=csv | 工单导出 |
|
||||||
|
| GET /export/reported-players?format=csv | 被举报人统计导出 |
|
||||||
|
| GET /export/audit-logs?start_date=&end_date=&format=csv | 操作日志导出 |
|
||||||
|
| GET /export/dashboard | 控制台统计(总数/类型/状态/今日/用户数/月度趋势) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、验证码与文件
|
||||||
|
|
||||||
|
| 端点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| GET /captcha | SVG 验证码 `{ id, svg }`(5 分钟有效) |
|
||||||
|
| GET /uploads/:storedName | 附件下载(鉴权;玩家仅可访问自己工单附件) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、外部 API(游戏插件 / Java 服务器)
|
||||||
|
|
||||||
|
认证:`x-external-key: <external_api_key>`(安装时生成)。插件登录后可携带 Bearer JWT。
|
||||||
|
|
||||||
|
| 端点 | 认证 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST /external/auth/register | external-key | 插件注册(邮箱验证链接) |
|
||||||
|
| POST /external/auth/login | external-key | 插件登录,返回 JWT |
|
||||||
|
| GET /external/my-tickets | external-key + Bearer | 我的工单列表(limit ≤50) |
|
||||||
|
| GET /external/my-tickets/:id | external-key + Bearer | 我的工单详情 |
|
||||||
|
| POST /external/tickets | external-key | 游戏内提交工单(自动写主身份) |
|
||||||
|
| GET /external/all-tickets | external-key | 全部工单(分页 page/limit ≤200) |
|
||||||
|
| GET /external/all-tickets/:id | external-key | 工单详情(含回复/附件) |
|
||||||
|
| GET /external/reported-players | external-key | 被举报统计 |
|
||||||
|
| GET /external/stats | external-key | 工单统计 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十二、安装与健康
|
||||||
|
|
||||||
|
| 端点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| GET /install/status | `{ installed: true|false }` |
|
||||||
|
| POST /install/check-db | 测试数据库连接(不存在则自动建库;库名仅字母数字下划线) |
|
||||||
|
| POST /install/complete | 完成安装:建表 + 创建服主 + 生成密钥(完成后无需重启,自动加载业务路由) |
|
||||||
|
| GET /health | `{ status: "ok", installed: true|false }` |
|
||||||
|
|
||||||
|
安装流程(两步):`check-db` → `complete` → 前端自动刷新并跳转登录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据兼容与迁移
|
||||||
|
|
||||||
|
新增多来源身份功能时,`backend/db.js` 的 `migrateAdditions` 自动执行(幂等):
|
||||||
|
|
||||||
|
1. 创建 `user_identities` 表(UNIQUE(user_id, source))
|
||||||
|
2. 从 `users` 表回填主身份:`INSERT IGNORE ... SELECT id, source, game_name, game_uid FROM users`
|
||||||
|
3. 注册/插件注册/管理员创建用户时自动写入主身份记录
|
||||||
|
4. 管理员编辑主身份字段时自动同步 `user_identities`
|
||||||
|
5. 工单提交:登录用户可选择任一已绑定身份(后端校验归属);匿名仍走表单字段
|
||||||
|
|
||||||
|
> 老数据无需手工处理,服务启动时自动迁移;`users` 表原有字段(主身份)保持不变,完全向后兼容。
|
||||||
Reference in New Issue
Block a user