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

@@ -103,6 +103,7 @@ async function initSchema(connection) {
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
type ENUM('report','suggestion','appeal','result_appeal') NOT NULL,
server_name VARCHAR(100) NOT NULL DEFAULT '',
title VARCHAR(100) NOT NULL,
status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL DEFAULT 'pending',
priority ENUM('low','medium','high','urgent') NOT NULL DEFAULT 'medium',
@@ -250,9 +251,20 @@ async function initSchema(connection) {
id INT AUTO_INCREMENT PRIMARY KEY,
group_name VARCHAR(100) NOT NULL,
server_name VARCHAR(100) NOT NULL,
alias VARCHAR(100) NOT NULL DEFAULT '',
UNIQUE KEY uk_group_server (group_name, server_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('api_clients', `CREATE TABLE api_clients (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
client_id VARCHAR(64) NOT NULL UNIQUE,
secret_hash VARCHAR(255) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_active (active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('user_servers', `CREATE TABLE user_servers (
user_id INT NOT NULL,
group_name VARCHAR(100) NOT NULL,
@@ -265,6 +277,7 @@ async function initSchema(connection) {
id INT AUTO_INCREMENT PRIMARY KEY,
player_name VARCHAR(50) NOT NULL,
player_uid VARCHAR(50),
server_name VARCHAR(100) NOT NULL DEFAULT '',
source VARCHAR(10) DEFAULT '',
reason TEXT,
type ENUM('ban','mute','warn','other') NOT NULL DEFAULT 'ban',
@@ -374,6 +387,10 @@ async function migrateAdditions(db) {
try { await db.execute(`CREATE TABLE IF NOT EXISTS feature_votes (id INT AUTO_INCREMENT PRIMARY KEY, item_id INT NOT NULL, user_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_item_user (item_id, user_id), INDEX idx_item (item_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS feature_comments (id INT AUTO_INCREMENT PRIMARY KEY, item_id INT NOT NULL, user_id INT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_item (item_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS server_groups (id INT AUTO_INCREMENT PRIMARY KEY, group_name VARCHAR(100) NOT NULL, server_name VARCHAR(100) NOT NULL, UNIQUE KEY uk_group_server (group_name, server_name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE server_groups ADD COLUMN alias VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS api_clients (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, client_id VARCHAR(64) NOT NULL UNIQUE, secret_hash VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_active (active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE tickets ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
try { await db.execute("ALTER TABLE bans ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
}
async function seedTemplates(db) {