docs: standalone external API doc for bots/third-party

- docs/EXTERNAL-API.md: auth matrix (ID+Secret / legacy key / JWT),
  Python+Node quickstart, server alias syntax, 9 endpoints with
  request/response examples, status flow, error codes, troubleshooting,
  typical scenarios (QQ bot ticket flow, plugin ban sync)
- README + API.md link to the standalone doc
- verified: 24 checks, endpoints cross-checked doc vs code
This commit is contained in:
2026-08-19 19:40:25 +08:00
parent 257cd14051
commit 4dfc30ce89
3 changed files with 353 additions and 1 deletions

View File

@@ -22,7 +22,8 @@
## API 文档 ## API 文档
完整接口文档见 **[docs/API.md](docs/API.md)**认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API)。 - **完整接口文档**:[docs/API.md](docs/API.md)(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API)
- **第三方外部 API(机器人/插件)**:[docs/EXTERNAL-API.md](docs/EXTERNAL-API.md)(ID+Secret 鉴权、提交工单、进度追踪、封禁拉取、按子服拉数据)
## 快速开始 ## 快速开始

View File

@@ -1,5 +1,7 @@
# MC Report System API 文档 # MC Report System API 文档
> 面向**机器人 / 第三方系统**的免登录 API(鉴权方式、请求示例、子服定位语法)见独立文档 **[EXTERNAL-API.md](EXTERNAL-API.md)**。
Base URL: `http://<host>:3100/api` Base URL: `http://<host>:3100/api`
## 通用约定 ## 通用约定

349
docs/EXTERNAL-API.md Normal file
View File

@@ -0,0 +1,349 @@
# 外部 API 文档(机器人 / 第三方系统)
适用对象:QQ 官方机器人、游戏服务器插件、统计面板等**无法做网页登录**的外部系统。
- Base URL:`https://<你的域名>/api/external`
- 数据格式:JSON(`Content-Type: application/json`)
- 本文档所有接口**不需要用户登录**,只需要客户端凭据(见下)
---
## 一、鉴权(三种方式,任选其一)
### 方式 A:ID + Secret(推荐,多客户端)
在站点后台 →「外部API」页面创建客户端,获得一对凭据:
```text
Client ID: c_1a2b3c4d5e6f7a8b9c0d1e2f
Secret: s_9f8e7d6c5b4a39281726354a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e
```
> ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。
调用时在请求头携带:
| 请求头 | 值 |
|--------|-----|
| `x-api-client-id` | 你的 Client ID |
| `x-api-secret` | 你的 Secret |
### 方式 B:旧版单 Key(兼容)
| 请求头 | 值 |
|--------|-----|
| `x-external-key` | 部署时配置的 external_api_key |
### 方式 C:用户 JWT(仅限插件)
| 请求头 | 值 |
|--------|-----|
| `Authorization` | `Bearer <JWT>`(来自 `POST /api/external/auth/login`) |
---
## 二、快速开始(Python / Node 示例)
### Python
```python
import requests
BASE = "https://你的域名/api/external"
HEADERS = {
"x-api-client-id": "c_1a2b3c4d5e6f7a8b9c0d1e2f",
"x-api-secret": "s_9f8e7d6c...",
"Content-Type": "application/json",
}
# 提交举报工单
r = requests.post(f"{BASE}/tickets", headers=HEADERS, json={
"type": "report",
"title": "恶意破坏",
"reporter_game_name": "Steve",
"reporter_game_uid": "df273bda10b94aa18db345574d5a1e1d",
"target_game_name": "Alex",
"target_game_uid": "SKIN_UUID_002",
"reason": "刷屏+破坏他人建筑",
"description": "多次警告无效",
"server": "survival", # 可选: 子服别名
})
print(r.json()) # {"id": 123, "tracking_token": "..."}
```
### Node.js
```javascript
const BASE = 'https://你的域名/api/external';
const H = {
'x-api-client-id': 'c_1a2b3c...',
'x-api-secret': 's_9f8e7d6c...',
'Content-Type': 'application/json',
};
// 查询工单进度(从提交到结束全程可查)
const res = await fetch(`${BASE}/tickets/track?token=你的tracking_token`, { headers: H });
console.log(await res.json());
```
---
## 三、子服务器定位(`server` 参数)
子服可在后台「服务器管理」配置**外部别名(alias)**。所有带 `server` 的接口支持三种写法:
| 写法 | 示例 | 说明 |
|------|------|------|
| 别名(推荐) | `survival` | 服务器改名不影响 |
| 分组/子服 | `网易服务器/生存服` | URL 编码或 JSON 直接传 |
| 子服名 | `生存服` | 同名时取第一个 |
查询服务器列表(含别名):`GET /api/external/servers`
---
## 四、接口清单
### 1. 提交工单
```
POST /api/external/tickets
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| type | string | ✅ | `report` / `suggestion` / `appeal` |
| title | string | ✅ | 标题 |
| reporter_game_name | string | ✅ | 提交人游戏名 |
| reporter_game_uid | string | ✅ | 提交人 UID / UUID |
| target_game_name | string | 条件 | 被举报人游戏名(report 建议填) |
| target_game_uid | string | 条件 | 被举报人 UID |
| reason | string | 条件 | 举报原因(report/appeal 必填) |
| description | string | 条件 | 详情(suggestion/appeal 必填) |
| server | string | 否 | 子服(别名/分组/子服名) |
限制:同一提交人**待处理工单最多 5 个**。
**成功响应 201:**
```json
{
"id": 123,
"tracking_token": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"server": "生存服"
}
```
> `tracking_token` 是查询进度的唯一凭证,请妥善保存。
### 2. 查询工单进度(提交 → 处理 → 结束)
```
GET /api/external/tickets/track?token=<tracking_token>
```
**成功响应:**
```json
{
"id": 123,
"type": "report",
"title": "恶意破坏",
"status": "processing",
"priority": "medium",
"reporter_game_name": "Steve",
"target_game_name": "Alex",
"reason": "刷屏+破坏他人建筑",
"assigned_to": "管理员",
"claim_note": "已受理",
"server_name": "生存服",
"created_at": "2026-08-19T10:00:00.000Z",
"updated_at": "2026-08-19T11:30:00.000Z",
"responses": [
{ "content": "游戏内提交...", "is_staff": 0, "created_at": "..." },
{ "content": "正在核实,请耐心等待", "is_staff": 1, "created_at": "..." }
]
}
```
**工单状态流转:**
```
pending(待处理) → processing(处理中) → resolved(已解决)
↘ awaiting_info(待补充信息) → processing / rejected(驳回)
↘ closed(关闭) appealed(申诉中) → resolved / rejected
```
### 3. 工单列表
```
GET /api/external/all-tickets?type=&status=&server=&page=&limit=
```
| 参数 | 说明 |
|------|------|
| type | `report` / `suggestion` / `appeal` |
| status | `pending` / `processing` / `awaiting_info` / `appealing` / `resolved` / `rejected` / `closed` |
| server | 子服(别名/分组/子服名) |
| page / limit | 分页,limit ≤ 200,默认 page=1 limit=50 |
### 4. 工单详情(含回复与附件)
```
GET /api/external/all-tickets/:id
```
### 5. 拉取封禁列表
```
GET /api/external/bans?server=&status=&player=&page=&limit=
```
| 参数 | 说明 |
|------|------|
| server | 子服过滤(可选) |
| status | `active` / `expired` / `appealed` / `lifted`(默认不滤) |
| player | 按玩家名或 UID 精确匹配 |
| page / limit | 分页,limit ≤ 500 |
**成功响应:**
```json
[
{
"id": 7,
"player_name": "Alex",
"player_uid": "SKIN_UUID_002",
"source": "netease",
"reason": "使用外挂",
"type": "ban",
"duration": "7d",
"server_name": "生存服",
"status": "active",
"created_at": "2026-08-19T10:00:00.000Z",
"expires_at": "2026-08-26T10:00:00.000Z"
}
]
```
### 6. 新增封禁
```
POST /api/external/bans
```
```json
{
"player_name": "Alex",
"player_uid": "SKIN_UUID_002",
"source": "netease",
"reason": "使用外挂",
"type": "ban",
"duration": "7d",
"expires_at": "2026-08-26T10:00:00.000Z",
"server": "survival",
"status": "active"
}
```
| 字段 | 必填 | 说明 |
|------|------|------|
| player_name | ✅ | 玩家名 |
| type | 否 | `ban` / `mute` / `warn` / `other`,默认 `ban` |
| status | 否 | `active` / `expired` / `appealed` / `lifted`,默认 `active` |
| expires_at | 否 | ISO 时间或 `null`(永久) |
**响应 201:** `{ "id": 7 }`
### 7. 被举报人统计
```
GET /api/external/reported-players?server=
```
**响应:**
```json
[
{
"target_game_name": "Alex",
"target_game_uid": "SKIN_UUID_002",
"total": 5,
"active": 2,
"resolved": 3,
"last_report": "2026-08-19T10:00:00.000Z"
}
]
```
### 8. 服务器列表(含别名)
```
GET /api/external/servers
```
**响应:**
```json
[
{ "group_name": "网易服务器", "server_name": "生存服", "alias": "survival" },
{ "group_name": "网易服务器", "server_name": "空岛服", "alias": "skyblock" }
]
```
### 9. 工单统计
```
GET /api/external/stats?server=
```
**响应:**
```json
{
"total": 128,
"byType": [ { "type": "report", "c": 95 }, { "type": "suggestion", "c": 20 } ],
"byStatus": [ { "status": "pending", "c": 12 }, { "status": "resolved", "c": 100 } ]
}
```
---
## 五、错误码
| HTTP | 说明 |
|------|------|
| 400 | 参数错误(缺少必填字段 / 类型不正确 / 待处理工单超上限) |
| 401 | 鉴权失败(凭据错误或客户端已停用) |
| 404 | 工单 / 封禁不存在 |
| 500 | 服务器内部错误 |
错误响应统一为 `{ "error": "描述信息" }`
---
## 六、鉴权失败排查
1. **401 未授权**:检查 `x-api-client-id` / `x-api-secret` 是否与创建时一致;客户端是否被停用;secret 是否完整无换行空格。
2. **客户端被停用**:后台「外部API」→ 启用。
3. **需要多个客户端**:后台可创建多个,分别用于机器人 / 插件 / 统计面板,互不影响;删除即立即失效。
---
## 七、典型场景
### QQ 机器人:玩家提交举报
1. 玩家在 QQ 群发送举报信息 → 机器人调 `POST /tickets` 提交(reporter 为玩家 QQ 绑定的游戏名/UID)
2. 机器人把返回的 `tracking_token` 发给玩家 → 玩家随时发「查进度」→ 机器人调 `GET /tickets/track?token=...`
3. 处理结果(状态变化)可通过工单详情轮询,或后台「通知配置」设置 Webhook 推送
### 服务器插件:同步封禁
1. 插件启动时调 `GET /bans?status=active&server=生存服` 拉取本服封禁
2. 服内封禁新玩家时调 `POST /bans` 同步到系统
3. 定期轮询 `GET /bans?status=active` 保持同步
---
*协议版本 v1 · 服务端接口以部署版本为准*