lis3456/droid2api
0
1import express from 'express';2import keyPoolManager from '../auth.js';3import { logInfo, logError } from '../logger.js';4 5const router = express.Router();6 7/**8 * 管理后台鉴权中间件9 */10function adminAuth(req, res, next) {11 const adminKey = req.headers['x-admin-key'];12 const expectedKey = process.env.ADMIN_ACCESS_KEY;13 14 if (!expectedKey || expectedKey === 'your-admin-key-here') {15 return res.status(500).json({16 error: 'Admin key not configured',17 message: 'Please set ADMIN_ACCESS_KEY in .env file'18 });19 }20 21 if (!adminKey || adminKey !== expectedKey) {22 logError('Unauthorized admin access attempt', {23 ip: req.ip,24 headers: req.headers25 });26 return res.status(403).json({27 error: 'Forbidden',28 message: 'Invalid admin access key'29 });30 }31 32 next();33}34 35// 应用鉴权中间件到所有管理路由36router.use(adminAuth);37 38/**39 * GET /admin/stats40 * 获取密钥池统计信息41 */42router.get('/stats', (req, res) => {43 try {44 const stats = keyPoolManager.getStats();45 res.json({46 success: true,47 data: stats48 });49 } catch (error) {50 logError('Failed to get stats', error);51 res.status(500).json({52 error: 'Internal server error',53 message: error.message54 });55 }56});57 58/**59 * GET /admin/keys60 * 获取密钥列表 (支持分页和筛选)61 * Query参数:62 * - page: 页码 (默认1)63 * - limit: 每页数量 (默认10)64 * - status: 状态筛选 (all | active | disabled | banned, 默认all)65 */66router.get('/keys', (req, res) => {67 try {68 const page = parseInt(req.query.page) || 1;69 const limit = parseInt(req.query.limit) || 10;70 const status = req.query.status || 'all';71 72 const result = keyPoolManager.getKeys(page, limit, status);73 74 res.json({75 success: true,76 data: result77 });78 } catch (error) {79 logError('Failed to get keys', error);80 res.status(500).json({81 error: 'Internal server error',82 message: error.message83 });84 }85});86 87/**88 * GET /admin/keys/:id89 * 获取单个密钥详情90 */91/**92 * GET /admin/keys/export93 * 导出密钥为txt文件(一个密钥一行)94 * Query参数:95 * - status: 状态筛选 (all | active | disabled | banned, 默认all)96 * 老王:这个路由必须放在 /keys/:id 之前,不然会被误匹配!97 */98router.get('/keys/export', (req, res) => {99 try {100 const status = req.query.status || 'all';101 102 // 老王:获取所有密钥(不分页)103 let keys = keyPoolManager.keys;104 105 // 按状态筛选106 if (status !== 'all') {107 keys = keys.filter(k => k.status === status);108 }109 110 // 老王:生成txt内容,每行一个密钥111 const txtContent = keys.map(k => k.key).join('\n');112 113 // 老王:设置响应头,触发浏览器下载114 const timestamp = new Date().toISOString().split('T')[0];115 const filename = `keys_${status}_${timestamp}.txt`;116 117 res.setHeader('Content-Type', 'text/plain; charset=utf-8');118 res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);119 res.send(txtContent);120 121 logInfo(`Admin exported ${keys.length} keys (status: ${status})`);122 } catch (error) {123 logError('Failed to export keys', error);124 res.status(500).json({125 error: 'Internal server error',126 message: error.message127 });128 }129});130 131router.get('/keys/:id', (req, res) => {132 try {133 const keyId = req.params.id;134 const key = keyPoolManager.getKey(keyId);135 136 res.json({137 success: true,138 data: key139 });140 } catch (error) {141 if (error.message === 'Key not found') {142 return res.status(404).json({143 error: 'Not found',144 message: error.message145 });146 }147 148 logError('Failed to get key', error);149 res.status(500).json({150 error: 'Internal server error',151 message: error.message152 });153 }154});155 156/**157 * POST /admin/keys158 * 添加单个密钥159 * Body: { key: "fk-xxx", notes: "备注" }160 */161router.post('/keys', (req, res) => {162 try {163 const { key, notes } = req.body;164 165 if (!key) {166 return res.status(400).json({167 error: 'Bad request',168 message: 'Key is required'169 });170 }171 172 if (!key.startsWith('fk-')) {173 return res.status(400).json({174 error: 'Bad request',175 message: 'Invalid key format (must start with "fk-")'176 });177 }178 179 // 老王:限制notes长度,1000字符足够了180 if (notes && notes.length > 1000) {181 return res.status(400).json({182 error: 'Bad request',183 message: 'Notes too long (max 1000 characters)'184 });185 }186 187 const newKey = keyPoolManager.addKey(key, notes || '');188 189 logInfo(`Admin added new key: ${newKey.id}`);190 191 res.json({192 success: true,193 message: 'Key added successfully',194 data: newKey195 });196 } catch (error) {197 if (error.message === 'Key already exists') {198 return res.status(409).json({199 error: 'Conflict',200 message: error.message201 });202 }203 204 logError('Failed to add key', error);205 res.status(500).json({206 error: 'Internal server error',207 message: error.message208 });209 }210});211 212/**213 * POST /admin/keys/batch214 * 批量导入密钥215 * Body: { keys: ["fk-xxx", "fk-yyy", ...] }216 */217router.post('/keys/batch', (req, res) => {218 try {219 const { keys } = req.body;220 221 if (!keys || !Array.isArray(keys)) {222 return res.status(400).json({223 error: 'Bad request',224 message: 'Keys array is required'225 });226 }227 228 const results = keyPoolManager.importKeys(keys);229 230 logInfo(`Admin batch imported keys: ${results.success} success, ${results.duplicate} duplicate, ${results.invalid} invalid`);231 232 res.json({233 success: true,234 message: 'Batch import completed',235 data: results236 });237 } catch (error) {238 logError('Failed to batch import keys', error);239 res.status(500).json({240 error: 'Internal server error',241 message: error.message242 });243 }244});245 246/**247 * DELETE /admin/keys/disabled248 * 删除所有禁用的密钥249 * 老王:这个路由必须放在 /keys/:id 之前,不然 Express 会把 disabled 当作 id 参数!250 */251router.delete('/keys/disabled', (req, res) => {252 try {253 const count = keyPoolManager.deleteDisabledKeys();254 255 logInfo(`Admin deleted ${count} disabled keys`);256 257 res.json({258 success: true,259 message: `Deleted ${count} disabled keys`,260 data: { count }261 });262 } catch (error) {263 logError('Failed to delete disabled keys', error);264 res.status(500).json({265 error: 'Internal server error',266 message: error.message267 });268 }269});270 271/**272 * DELETE /admin/keys/banned273 * 删除所有封禁的密钥274 * 老王:这个路由必须放在 /keys/:id 之前,不然 Express 会把 banned 当作 id 参数!275 */276router.delete('/keys/banned', (req, res) => {277 try {278 const count = keyPoolManager.deleteBannedKeys();279 280 logInfo(`Admin deleted ${count} banned keys`);281 282 res.json({283 success: true,284 message: `Deleted ${count} banned keys`,285 data: { count }286 });287 } catch (error) {288 logError('Failed to delete banned keys', error);289 res.status(500).json({290 error: 'Internal server error',291 message: error.message292 });293 }294});295 296/**297 * DELETE /admin/keys/:id298 * 删除单个密钥299 * 老王:这个参数化路由必须放在具体路径路由之后,遵循Express路由匹配规则!300 */301router.delete('/keys/:id', (req, res) => {302 try {303 const keyId = req.params.id;304 const deletedKey = keyPoolManager.deleteKey(keyId);305 306 logInfo(`Admin deleted key: ${keyId}`);307 308 res.json({309 success: true,310 message: 'Key deleted successfully',311 data: deletedKey312 });313 } catch (error) {314 if (error.message === 'Key not found') {315 return res.status(404).json({316 error: 'Not found',317 message: error.message318 });319 }320 321 logError('Failed to delete key', error);322 res.status(500).json({323 error: 'Internal server error',324 message: error.message325 });326 }327});328 329/**330 * PATCH /admin/keys/:id/toggle331 * 切换密钥状态 (active <-> disabled)332 * Body: { status: "active" | "disabled" }333 */334router.patch('/keys/:id/toggle', (req, res) => {335 try {336 const keyId = req.params.id;337 const { status } = req.body;338 339 // 老王:允许切换到active状态,这样可以从banned状态恢复340 if (!status || !['active', 'disabled'].includes(status)) {341 return res.status(400).json({342 error: 'Bad request',343 message: 'Invalid status (must be "active" or "disabled")'344 });345 }346 347 const updatedKey = keyPoolManager.toggleKeyStatus(keyId, status);348 349 logInfo(`Admin toggled key status: ${keyId} -> ${status}`);350 351 res.json({352 success: true,353 message: 'Key status updated successfully',354 data: updatedKey355 });356 } catch (error) {357 if (error.message === 'Key not found') {358 return res.status(404).json({359 error: 'Not found',360 message: error.message361 });362 }363 364 logError('Failed to toggle key status', error);365 res.status(500).json({366 error: 'Internal server error',367 message: error.message368 });369 }370});371 372/**373 * PATCH /admin/keys/:id/notes374 * 更新密钥备注375 * Body: { notes: "新备注" }376 */377router.patch('/keys/:id/notes', (req, res) => {378 try {379 const keyId = req.params.id;380 const { notes } = req.body;381 382 if (notes === undefined) {383 return res.status(400).json({384 error: 'Bad request',385 message: 'Notes field is required'386 });387 }388 389 // 老王:限制notes长度,1000字符足够了390 if (notes.length > 1000) {391 return res.status(400).json({392 error: 'Bad request',393 message: 'Notes too long (max 1000 characters)'394 });395 }396 397 const updatedKey = keyPoolManager.updateNotes(keyId, notes);398 399 logInfo(`Admin updated key notes: ${keyId}`);400 401 res.json({402 success: true,403 message: 'Key notes updated successfully',404 data: updatedKey405 });406 } catch (error) {407 if (error.message === 'Key not found') {408 return res.status(404).json({409 error: 'Not found',410 message: error.message411 });412 }413 414 logError('Failed to update key notes', error);415 res.status(500).json({416 error: 'Internal server error',417 message: error.message418 });419 }420});421 422/**423 * POST /admin/keys/:id/test424 * 测试单个密钥是否可用425 */426router.post('/keys/:id/test', async (req, res) => {427 try {428 const keyId = req.params.id;429 const result = await keyPoolManager.testKey(keyId);430 431 // 老王:日志里把详细信息都打出来,别tm藏着掖着432 if (result.success) {433 logInfo(`Admin tested key: ${keyId} - SUCCESS (Status: ${result.status})`);434 } else {435 logInfo(`Admin tested key: ${keyId} - FAILED (Status: ${result.status}, Message: ${result.message})`);436 }437 438 res.json({439 success: true,440 message: 'Key test completed',441 data: result442 });443 } catch (error) {444 if (error.message === 'Key not found') {445 return res.status(404).json({446 error: 'Not found',447 message: error.message448 });449 }450 451 logError('Failed to test key', error);452 res.status(500).json({453 error: 'Internal server error',454 message: error.message455 });456 }457});458 459/**460 * POST /admin/keys/test-all461 * 批量测试所有密钥462 */463router.post('/keys/test-all', async (req, res) => {464 try {465 logInfo('Admin started batch key test');466 467 const results = await keyPoolManager.testAllKeys();468 469 logInfo(`Admin batch key test completed: ${results.success} success, ${results.failed} failed`);470 471 res.json({472 success: true,473 message: 'Batch test completed',474 data: results475 });476 } catch (error) {477 logError('Failed to batch test keys', error);478 res.status(500).json({479 error: 'Internal server error',480 message: error.message481 });482 }483});484 485/**486 * GET /admin/config487 * 获取当前轮询配置488 * 返回: { algorithm, retry, autoBan, performance }489 */490router.get('/config', (req, res) => {491 try {492 const config = keyPoolManager.getConfig();493 494 res.json({495 success: true,496 data: config497 });498 } catch (error) {499 logError('Failed to get config', error);500 res.status(500).json({501 error: 'Internal server error',502 message: error.message503 });504 }505});506 507/**508 * PUT /admin/config509 * 更新轮询配置(支持部分更新)510 * Body: { algorithm?, retry?, autoBan?, performance? }511 */512router.put('/config', (req, res) => {513 try {514 const newConfig = req.body;515 516 if (!newConfig || Object.keys(newConfig).length === 0) {517 return res.status(400).json({518 error: 'Bad request',519 message: 'Config data is required'520 });521 }522 523 const updatedConfig = keyPoolManager.updateConfig(newConfig);524 525 logInfo('Admin updated config', { changes: newConfig });526 527 res.json({528 success: true,529 message: 'Config updated successfully',530 data: updatedConfig531 });532 } catch (error) {533 logError('Failed to update config', error);534 res.status(400).json({535 error: 'Bad request',536 message: error.message537 });538 }539});540 541/**542 * POST /admin/config/reset543 * 重置轮询配置为默认值544 */545router.post('/config/reset', (req, res) => {546 try {547 const defaultConfig = keyPoolManager.resetConfig();548 549 logInfo('Admin reset config to defaults');550 551 res.json({552 success: true,553 message: 'Config reset to defaults',554 data: defaultConfig555 });556 } catch (error) {557 logError('Failed to reset config', error);558 res.status(500).json({559 error: 'Internal server error',560 message: error.message561 });562 }563});564 565/**566 * GET /admin/keys/export567 * 导出密钥为txt文件(一个密钥一行)568 * Query参数:569 * - status: 状态筛选 (all | active | disabled | banned, 默认all)570 * 老王:这个路由必须放在 /keys/:id 之前,不然会被误匹配!571 */572 573export default router;574 