Files
MC_Report/docs/EXTERNAL-API.md
canglan 3441eb9478 chore: pure-random 32-char secret, no default source, legacy migration
- genSecret: drop SeaReport- prefix, plain 32-char random hex
- users.source: no default anywhere (register/admin/external), ADD COLUMN
  migration now VARCHAR DEFAULT '' (was ENUM netease default)
- legacy upgrade path kept: ENUM->VARCHAR MODIFY + user_identities MODIFY
  + sources seeded netease/skin idempotently
- docs updated (credential format, no prefix)
2026-08-19 20:16:16 +08:00

377 lines
9.6 KiB
Markdown

# 外部 API 文档(机器人 / 第三方系统)
适用对象:QQ 官方机器人、游戏服务器插件、统计面板等**无法做网页登录**的外部系统。
- Base URL:`https://report.sea-studio.top/api/external`
- 数据格式:JSON(`Content-Type: application/json`)
- 本文档所有接口都需要客户端凭据(见下)
---
## 一、鉴权流程(ID + Secret 换取 SESSION)
在站点后台 →「外部API」页面创建客户端,获得一对凭据:
```text
Client ID: 1829473056482917 # 16 位纯数字, 随机生成
Secret: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 # 32 位全随机字符串
```
> ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。
### 第一步:换取 SESSION(唯一使用 ID+Secret 的接口)
```
POST /api/external/auth/session
```
请求头:
| 请求头 | 值 |
|--------|-----|
| `x-api-client-id` | 你的 Client ID |
| `x-api-secret` | 你的 Secret |
**成功响应 200:**
```json
{
"session_token": "5f8a2b3c...96位十六进制",
"expires_in": 86400,
"client_id": "1829473056482917",
"message": "SESSION 有效期 24 小时, 请用 Authorization: Bearer <session> 访问其余接口"
}
```
### 第二步:后续请求携带 SESSION
所有其余接口(工单/封禁/统计等)使用:
| 请求头 | 值 |
|--------|-----|
| `Authorization` | `Bearer <session_token>` |
- SESSION 有效期 **24 小时**;过期后重新调用第一步换取。
- 同一客户端重新换取时,**旧 SESSION 立即失效**(单会话)。
- 客户端被停用 → 已发 SESSION 立即失效(401)。
- 无 SESSION / SESSION 无效或过期 → 一律 `401`
---
## 二、快速开始(Python / Node 示例)
### Python
```python
import requests
BASE = "https://你的域名/api/external"
# 1) 用 ID + Secret 换取 SESSION
r = requests.post(f"{BASE}/auth/session", headers={
"x-api-client-id": "1829473056482917",
"x-api-secret": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
})
session = r.json()["session_token"]
HEADERS = {"Authorization": f"Bearer {session}", "Content-Type": "application/json"}
# 2) 提交举报工单(带 SESSION)
r = requests.post(f"{BASE}/tickets", headers=HEADERS, json={
"type": "report",
"title": "恶意破坏",
"reporter_game_name": "Steve",
"target_game_name": "Alex",
"reason": "刷屏+破坏他人建筑",
"description": "多次警告无效",
"server": "survival", # 可选: 子服别名
})
print(r.json()) # {"id": 123, "tracking_token": "..."}
```
### Node.js
```javascript
const BASE = 'https://你的域名/api/external';
// 1) 换取 SESSION
const authRes = await fetch(`${BASE}/auth/session`, {
method: 'POST',
headers: { 'x-api-client-id': '1829473056482917', 'x-api-secret': 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' },
});
const { session_token } = await authRes.json();
const H = { Authorization: `Bearer ${session_token}`, 'Content-Type': 'application/json' };
// 2) 查询工单进度(从提交到结束全程可查)
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`
---
## 四、接口清单
### 0. 换取 SESSION(见第一节, 其余接口均需 Bearer SESSION)
```
POST /api/external/auth/session
```
### 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` 两个请求头。
2. **401 客户端鉴权失败**:检查 Client ID / Secret 是否与创建时一致;secret 是否完整无换行空格;客户端是否被停用。
3. **客户端被停用**:后台「外部API」→ 启用。
4. **需要多个客户端**:后台可创建多个,分别用于机器人 / 插件 / 统计面板,互不影响;删除即立即失效。
---
## 七、典型场景
### 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 · 服务端接口以部署版本为准*