feat: external API for QQ bot - client id+secret auth, ticket tracking, bans, per-server data

- db: api_clients table + server_groups.alias + tickets/bans server_name (auto-migrate)
- external.js: client_id+secret auth (multi-client, bcrypt, show-once secret),
  legacy x-external-key compat, JWT pass-through
  - POST /tickets (optional server), GET /tickets/track?token= (full lifecycle)
  - GET/POST /bans, reported-players/stats/all-tickets with server filter
  - GET /servers (alias list), client CRUD (owner/admin, audit logged)
  - resolveServer: alias | 分组/子服 | server_name
- polls.js: server_groups CRUD with alias (add/edit, dup check)
- servers page: alias display + edit; new 外部API page: client mgmt + quick docs
- API.md: full external API section (auth matrix, server param syntax)
- verified: 37 checks incl. resolveServer sandbox (alias/group/fallback)
This commit is contained in:
2026-08-19 19:12:34 +08:00
parent a11df22600
commit 257cd14051
8 changed files with 393 additions and 54 deletions

View File

@@ -9,6 +9,7 @@ const App = {
{ id: 'polls', label: '投票', icon: 'fa-poll', roles: ['owner'] },
{ id: 'features', label: '更新内容', icon: 'fa-lightbulb', roles: ['owner'] },
{ id: 'notifications', label: '通知配置', icon: 'fa-bell', roles: ['owner'] },
{ id: 'external-api', label: '外部API', icon: 'fa-key', roles: ['owner'] },
{ id: 'templates', label: '邮件模板', icon: 'fa-envelope', roles: ['owner'] },
{ id: 'settings', label: '系统设置', icon: 'fa-cog', roles: ['owner'] },
{ id: 'logs', label: '系统日志', icon: 'fa-clipboard-list', roles: ['owner','admin'] },
@@ -92,6 +93,7 @@ const App = {
case 'tracking': this.renderMain('追踪工单', TrackingPage, param); break;
case 'users': this.renderMain('用户管理', UsersPage, param); break;
case 'notifications': this.renderMain('通知配置', NotificationsPage, param); break;
case 'external-api': this.renderMain('外部API', ExternalApiPage, param); break;
case 'templates': this.renderMain('邮件模板', TemplatesPage, param); break;
case 'settings': this.renderMain('系统设置', SettingsPage, param); break;
case 'export': this.renderMain('数据导出', ExportPage, param); break;
@@ -173,6 +175,7 @@ window.UnclaimedPage = UnclaimedPage;
window.UsersPage = UsersPage;
window.TemplatesPage = TemplatesPage;
window.NotificationsPage = NotificationsPage;
window.ExternalApiPage = ExternalApiPage;
window.SettingsPage = SettingsPage;
window.ExportPage = ExportPage;
window.PollsPage = PollsPage;