diff --git a/backend/middleware/security.js b/backend/middleware/security.js index 078387a..ad4c9a2 100644 --- a/backend/middleware/security.js +++ b/backend/middleware/security.js @@ -154,7 +154,7 @@ const captchaLimiter = rateLimit({ function apiKeyGuard(req, res, next) { const p = req.originalUrl; - if (p === '/api/health' || p.startsWith('/api/install')) return next(); + if (p === '/api/health' || p.startsWith('/api/install') || p.startsWith('/api/polls/server-list')) return next(); const key = getApiKey(); if (!key) return next(); if (!req.headers['x-api-key'] || req.headers['x-api-key'].length !== key.length) return res.status(401).json({ error: '无效的 API 密钥' }); diff --git a/public/index.html b/public/index.html index b10ebd1..67b2a7e 100644 --- a/public/index.html +++ b/public/index.html @@ -70,6 +70,7 @@ + diff --git a/public/js/app.js b/public/js/app.js index 53355cf..53372e6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -49,6 +49,7 @@ const App = { } if (window.__SITE_NAME__) { document.getElementById('site-title').textContent = window.__SITE_NAME__; + document.getElementById('logo-text').textContent = window.__SITE_NAME__; document.getElementById('sidebar-title').textContent = window.__SITE_NAME__ || '举报系统'; } this._footerLoaded = false; @@ -129,6 +130,7 @@ const App = { case 'users': this.renderMain('用户管理', UsersPage, param); break; case 'notifications': this.renderMain('通知配置', NotificationsPage, param); break; case 'external-api': this.renderMain('外部API', ExternalApiPage, param); break; + case 'external-api-docs': this.renderMain('外部API文档', ExternalApiDocsPage, param); break; case 'sources': this.renderMain('来源管理', SourcesPage, param); break; case 'templates': this.renderMain('邮件模板', TemplatesPage, param); break; case 'settings': this.renderMain('系统设置', SettingsPage, param); break; diff --git a/public/js/pages/external-api-docs.js b/public/js/pages/external-api-docs.js new file mode 100644 index 0000000..0903c62 --- /dev/null +++ b/public/js/pages/external-api-docs.js @@ -0,0 +1,122 @@ +/* + * MC Report System + * Copyright (C) 2026 Sea Network Technology Studio + * Author: CangLan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* 外部API 全量文档页: 仅展示, 无数据请求 */ +const ExternalApiDocsPage = { + async render() { + return ` +
+ +
鉴权流程
+
    +
  1. 在「外部API」页面创建客户端, 获得 Client ID(16位数字)与 Secret(32位随机字符串), Secret 仅创建时显示一次。
  2. +
  3. POST /api/external/auth/session, 请求头携带 x-api-client-idx-api-secret, 换取 SESSION(24小时有效, 单会话, 重复换取使旧会话失效)。
  4. +
  5. 之后所有接口请求头使用 Authorization: Bearer <SESSION>。除换取 SESSION 外, 所有接口均需 Bearer SESSION。
  6. +
+

SESSION 有效期 24 小时; 客户端被停用后 SESSION 立即失效。所有响应为 JSON; 错误响应形如 {"error":"..."}

+
+ +
接口一览
+ + + + + + + + + + + +
方法路径鉴权说明
POST/api/external/auth/sessionID+Secret换取 SESSION(唯一使用 ID+Secret 的接口)
POST/api/external/auth/registerSESSION插件代玩家注册账号(需邮箱)
POST/api/external/ticketsSESSION提交工单(举报/建议/申诉), 返回 id + tracking_token; 可带 server(别名或「分组/子服」)
GET/api/external/tickets/track?token=xxxSESSION按追踪码查工单状态 + 回复, 从提交到结束全程可查
GET/api/external/all-tickets?server=&status=&type=&page=&limit=SESSION工单列表(可按子服过滤, 分页)
GET/api/external/all-tickets/:idSESSION工单详情(含回复/附件)
GET/api/external/bans?server=&status=&player=SESSION拉取封禁列表
POST/api/external/bansSESSION新增封禁(可带 server)
GET/api/external/reported-players?server=SESSION被举报人统计
GET/api/external/serversSESSION服务器列表(含别名)
GET/api/external/stats?server=SESSION工单统计
+ +
1. 换取 SESSION
+
POST /api/external/auth/session
+Headers: x-api-client-id: 1234567890123456
+         x-api-secret: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
+

响应:

+
{ "session_token": "xxxx...", "expires_in": 86400 }
+
+ +
2. 插件注册账号(可选)
+
POST /api/external/auth/register
+Authorization: Bearer <SESSION>
+{ "username": "player1", "password": "123456", "email": "p1@example.com", "game_name": "Steve" }
+

响应: 201 { "message": "注册成功!已自动激活,请登录" }。注册后游戏UID由系统自动生成。

+
+ +
3. 提交工单
+
POST /api/external/tickets
+Authorization: Bearer <SESSION>
+{
+  "type": "report",
+  "title": "玩家破坏建筑",
+  "reporter_game_name": "Steve",
+  "target_game_name": "Alex",
+  "target_game_uid": "123456789",
+  "reason": "使用外挂",
+  "description": "在生存服主城附近...",
+  "server": "生存服"          // 可选: 别名或「分组/子服」
+}
+

type 取值: report(举报) / suggestion(建议) / appeal(申诉)。

+

响应:

+
201 { "id": 42, "tracking_token": "uuid-...", "server": "生存服" }
+
+ +
4. 查询工单状态
+
GET /api/external/tickets/track?token=uuid-...
+

响应(含全部回复):

+
{ "id": 42, "status": "processing", "replies": [ { "content": "...", "is_staff": true, "created_at": "..." } ] }
+
+ +
5. 封禁同步
+
GET /api/external/bans?server=生存服&status=active
+POST /api/external/bans
+Authorization: Bearer <SESSION>
+{ "player_name": "Alex", "player_uid": "123456789", "reason": "外挂", "type": "ban", "duration": "7d", "server": "生存服" }
+
+ +
6. 其他数据接口
+
GET /api/external/all-tickets?server=生存服&status=pending&page=1&limit=20
+GET /api/external/all-tickets/42
+GET /api/external/reported-players?server=生存服
+GET /api/external/servers
+GET /api/external/stats?server=生存服
+
+ +
插件接入示例(Node.js)
+
const API = 'https://你的站点/api/external';
+// 1. 换取 SESSION
+const s = await fetch(API + '/auth/session', { method: 'POST', headers: {
+  'x-api-client-id': '你的ClientID', 'x-api-secret': '你的Secret' } });
+const { session_token } = await s.json();
+// 2. 提交工单
+await fetch(API + '/tickets', { method: 'POST', headers: {
+  'Authorization': 'Bearer ' + session_token, 'Content-Type': 'application/json' },
+  body: JSON.stringify({ type: 'report', title: '...', reporter_game_name: 'Steve', reason: '...' }) });
+

完整 Java(Bukkit/Paper) 示例见仓库 docs/PLUGIN-GUIDE.md

+
+ +
`; + }, + async mount() { + document.getElementById('page-title').textContent = '外部API文档'; + document.getElementById('page-actions').innerHTML = ''; + } +}; diff --git a/public/js/pages/external-api.js b/public/js/pages/external-api.js index fa523fa..96b88cd 100644 --- a/public/js/pages/external-api.js +++ b/public/js/pages/external-api.js @@ -22,7 +22,9 @@ const ExternalApiPage = { async mount() { document.getElementById('page-title').textContent = '外部API'; - document.getElementById('page-actions').innerHTML = ''; + document.getElementById('page-actions').innerHTML = ` + + `; await this.load(); }, @@ -44,24 +46,8 @@ const ExternalApiPage = { `).join('')}`} -
鉴权流程: 先调 POST /api/external/auth/sessionx-api-client-id + x-api-secret 换取 SESSION(24小时有效), 之后所有接口请求头用 Authorization: Bearer <SESSION>。secret 仅创建时显示一次,请立即保存。
+
使用流程: 创建客户端后, 用 Client ID + SecretPOST /api/external/auth/session 换取 SESSION, 之后所有接口用 Authorization: Bearer <SESSION>。完整说明见右上角「接口文档」。
- -
接口速览(除换取SESSION外, 均需 Bearer SESSION)
- - - - - - - - - - - - -
方法路径说明
POST/api/external/auth/sessionID+Secret 换取 SESSION(唯一使用 ID+Secret 的接口)
POST/api/external/auth/register插件注册账号(需 SESSION)
POST/api/external/tickets提交工单(举报/建议/申诉),返回 id + tracking_token;可带 server(别名或「分组/子服」)
GET/api/external/tickets/track?token=xxx按追踪码查工单状态 + 回复,从提交到结束全程可查
GET/api/external/all-tickets?server=&status=&type=&page=&limit=工单列表(可按子服过滤)
GET/api/external/all-tickets/:id工单详情(含回复/附件)
GET/api/external/bans?server=&status=&player=拉取封禁列表
POST/api/external/bans新增封禁(可带 server)
GET/api/external/reported-players?server=被举报人统计
GET/api/external/servers服务器列表(含别名)
GET/api/external/stats?server=工单统计
POST/api/external/clients创建客户端(需登录态, 本页操作)
-

完整请求/响应示例见 docs/API.md 外部API章节。

`; } catch (e) { ct.innerHTML = `
${e.message}
`; } },