/** * server.js - API 代理服务主入口 * * 对外暴露 OpenAI / Anthropic 兼容的 API 端点 * 对内通过账号池轮换使用 chataibot.pro 试用额度 * * 启动: node server.js * 端口: config.server.port (默认 9090) */ import http from 'http'; import config from './config.js'; import { AccountPool } from './pool.js'; import { handleChatCompletions } from './openai-adapter.js'; import { handleMessages } from './anthropic-adapter.js'; import { getModelList } from './message-convert.js'; import { getDashboardHTML } from './ui.js'; // ==================== 初始化 ==================== const pool = new AccountPool(); await pool.init(); // ==================== HTTP 服务器 ==================== function parseBody(req, maxSize = 2 * 1024 * 1024) { // 默认 2MB return new Promise((resolve, reject) => { let data = ''; let size = 0; req.on('data', chunk => { size += chunk.length; if (size > maxSize) { req.destroy(); reject(new Error('Request body too large')); return; } data += chunk; }); req.on('end', () => { try { resolve(JSON.parse(data)); } catch { reject(new Error('Invalid JSON body')); } }); req.on('error', reject); }); } function sendJson(res, status, data) { res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }); res.end(JSON.stringify(data)); } function sendError(res, status, message) { sendJson(res, status, { error: { message, type: 'invalid_request_error', code: status }, }); } function sendHtml(res, html) { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' }); res.end(html); } function checkAuth(req) { if (!config.server.apiKey) return true; // Bearer token const auth = req.headers['authorization'] || ''; if (auth.startsWith('Bearer ') && auth.slice(7) === config.server.apiKey) return true; // x-api-key header (Anthropic 风格) if (req.headers['x-api-key'] === config.server.apiKey) return true; return false; } const server = http.createServer(async (req, res) => { const url = new URL(req.url, `http://${req.headers.host}`); const pathname = url.pathname; const method = req.method; // CORS 预检 if (method === 'OPTIONS') { res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version', 'Access-Control-Max-Age': '86400', }); res.end(); return; } // 健康检查 (不需要认证) if (method === 'GET' && pathname === '/health') { sendJson(res, 200, { status: 'ok', uptime: process.uptime() }); return; } // Dashboard 控制台 if (method === 'GET' && (pathname === '/' || pathname === '/ui')) { sendHtml(res, getDashboardHTML()); return; } // 模型列表 (Dashboard 需要免认证访问) if (method === 'GET' && pathname === '/v1/models') { sendJson(res, 200, { object: 'list', data: getModelList() }); return; } // 账号池状态 (Dashboard 需要免认证访问) if (method === 'GET' && pathname === '/pool/status') { sendJson(res, 200, pool.getStatus()); return; } // 注册日志 (Dashboard 需要免认证访问) if (method === 'GET' && pathname === '/pool/logs') { const since = parseInt(url.searchParams.get('since')) || 0; sendJson(res, 200, { logs: pool.getLogs(since) }); return; } // 手动注册账号 (Dashboard 调用) if (method === 'POST' && pathname === '/pool/register') { try { const body = await parseBody(req); const count = Math.min(Math.max(1, body.count || 5), 50); const provider = body.provider || undefined; const concurrency = body.concurrency ? Math.min(Math.max(1, body.concurrency), 20) : undefined; const result = pool.manualRegister(count, provider, concurrency); sendJson(res, 200, { ok: true, message: `已提交 ${result.queued} 个注册任务`, ...result }); } catch (e) { sendJson(res, 200, { ok: false, message: e.message }); } return; } // 认证校验 if (!checkAuth(req)) { sendError(res, 401, 'Invalid API key'); return; } try { // ==================== 路由 ==================== // OpenAI: POST /v1/chat/completions if (method === 'POST' && pathname === '/v1/chat/completions') { const body = await parseBody(req); await handleChatCompletions(body, res, pool); return; } // Anthropic: POST /v1/messages if (method === 'POST' && pathname === '/v1/messages') { const body = await parseBody(req); await handleMessages(body, res, pool); return; } // 404 sendError(res, 404, `Not found: ${method} ${pathname}`); } catch (e) { console.error(`[Server] 请求错误: ${e.message}`); if (!res.headersSent) { sendError(res, 500, 'Internal server error'); } } }); // ==================== 启动 ==================== const { port, host } = config.server; server.listen(port, host, () => { console.log(`\n ┌──────────────────────────────────────────┐`); console.log(` │ ChatAIBot API Proxy │`); console.log(` │ http://${host}:${port} │`); console.log(` ├──────────────────────────────────────────┤`); console.log(` │ Dashboard: http://${host}:${port}/ │`); console.log(` │ OpenAI: POST /v1/chat/completions │`); console.log(` │ Anthropic: POST /v1/messages │`); console.log(` │ Models: GET /v1/models │`); console.log(` │ Pool: GET /pool/status │`); console.log(` │ Health: GET /health │`); console.log(` ├──────────────────────────────────────────┤`); console.log(` │ Auth: ${config.server.apiKey ? 'Bearer ' + config.server.apiKey.substring(0, 8) + '...' : '无 (开放访问)'}${' '.repeat(Math.max(0, 24 - (config.server.apiKey ? 16 : 13)))}│`); console.log(` │ Pool: ${pool.getStatus().active} active / ${pool.getStatus().total} total${' '.repeat(Math.max(0, 22 - String(pool.getStatus().active).length - String(pool.getStatus().total).length))}│`); console.log(` └──────────────────────────────────────────┘\n`); }); // 优雅退出 process.on('SIGINT', () => { console.log('\n[Server] 正在关闭...'); pool.destroy(); server.close(() => process.exit(0)); }); process.on('SIGTERM', () => { pool.destroy(); server.close(() => process.exit(0)); });