import express from 'express'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { generateAssistantResponse, getAvailableModels } from '../api/client.js'; import { generateRequestBody } from '../utils/utils.js'; import logger from '../utils/logger.js'; import config from '../config/config.js'; import adminRoutes, { incrementRequestCount, addLog } from '../admin/routes.js'; import { validateKey, checkRateLimit } from '../admin/key_manager.js'; import idleManager from '../utils/idle_manager.js'; import oauthRoutes from '../oauth/routes.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 确保必要的目录存在 const ensureDirectories = () => { const dirs = ['data', 'uploads']; dirs.forEach(dir => { const dirPath = path.join(process.cwd(), dir); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); logger.info(`创建目录: ${dir}`); } }); }; ensureDirectories(); const app = express(); app.use(express.json({ limit: config.security.maxRequestSize })); // 静态文件服务 - 提供管理控制台页面 app.use(express.static(path.join(process.cwd(), 'client/dist'))); app.use((err, req, res, next) => { if (err.type === 'entity.too.large') { return res.status(413).json({ error: `请求体过大,最大支持 ${config.security.maxRequestSize}` }); } next(err); }); // ... (rest of the file) // 请求日志中间件 app.use((req, res, next) => { // 记录请求活动,管理空闲状态 if (req.path.startsWith('/v1/')) { idleManager.recordActivity(); } const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; logger.request(req.method, req.path, res.statusCode, duration); // 记录到管理日志 if (req.path.startsWith('/v1/')) { incrementRequestCount(); addLog('info', `${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`); } }); next(); }); // API 密钥验证和频率限制中间件 app.use(async (req, res, next) => { if (req.path.startsWith('/v1/')) { const apiKey = config.security?.apiKey; if (apiKey) { const authHeader = req.headers.authorization; const providedKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; // 先检查配置文件中的密钥(不受频率限制) if (providedKey === apiKey) { return next(); } // 再检查数据库中的密钥 const isValid = await validateKey(providedKey); if (!isValid) { logger.warn(`API Key 验证失败: ${req.method} ${req.path}`); await addLog('warn', `API Key 验证失败: ${req.method} ${req.path}`); return res.status(401).json({ error: 'Invalid API Key' }); } // 检查频率限制 const rateLimitCheck = await checkRateLimit(providedKey); if (!rateLimitCheck.allowed) { logger.warn(`频率限制: ${req.method} ${req.path} - ${rateLimitCheck.error}`); await addLog('warn', `频率限制触发: ${providedKey.substring(0, 10)}...`); res.setHeader('X-RateLimit-Limit', rateLimitCheck.limit || 0); res.setHeader('X-RateLimit-Remaining', 0); res.setHeader('X-RateLimit-Reset', rateLimitCheck.resetIn || 0); return res.status(429).json({ error: { message: rateLimitCheck.error, type: 'rate_limit_exceeded', reset_in_seconds: rateLimitCheck.resetIn } }); } // 设置频率限制响应头 if (rateLimitCheck.limit) { res.setHeader('X-RateLimit-Limit', rateLimitCheck.limit); res.setHeader('X-RateLimit-Remaining', rateLimitCheck.remaining); } } } next(); }); // 管理路由 app.use('/admin', adminRoutes); // OAuth 路由 (登录和回调) app.use('/', oauthRoutes); app.get('/v1/models', async (req, res) => { try { const models = await getAvailableModels(); res.json(models); } catch (error) { logger.error('获取模型列表失败:', error.message); res.status(500).json({ error: error.message }); } }); app.post('/v1/chat/completions', async (req, res) => { let { messages, model, stream = true, tools, ...params } = req.body; try { if (!messages) { return res.status(400).json({ error: 'messages is required' }); } // 智能检测:NewAPI测速请求通常消息很简单,强制使用非流式响应 // 检测条件:单条消息 + 内容很短(如 "hi", "test" 等) const isSingleShortMessage = messages.length === 1 && messages[0].content && messages[0].content.length < 20; // 如果检测到可能是测速请求,且未明确要求流式,则使用非流式 if (isSingleShortMessage && req.body.stream === undefined) { stream = false; } const authHeader = req.headers.authorization; const apiKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; const requestBody = generateRequestBody(messages, model, params, tools, apiKey); if (stream) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const id = `chatcmpl-${Date.now()}`; const created = Math.floor(Date.now() / 1000); let hasToolCall = false; await generateAssistantResponse(requestBody, (data) => { if (data.type === 'tool_calls') { hasToolCall = true; res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { tool_calls: data.tool_calls }, finish_reason: null }] })}\n\n`); } else { res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { content: data.content }, finish_reason: null }] })}\n\n`); } }); res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: {}, finish_reason: hasToolCall ? 'tool_calls' : 'stop' }] })}\n\n`); res.write('data: [DONE]\n\n'); res.end(); } else { let fullContent = ''; let toolCalls = []; await generateAssistantResponse(requestBody, (data) => { if (data.type === 'tool_calls') { toolCalls = data.tool_calls; } else { fullContent += data.content; } }); const message = { role: 'assistant', content: fullContent }; if (toolCalls.length > 0) { message.tool_calls = toolCalls; } res.json({ id: `chatcmpl-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, message, finish_reason: toolCalls.length > 0 ? 'tool_calls' : 'stop' }] }); } } catch (error) { logger.error('生成响应失败:', error.message); if (!res.headersSent) { if (stream) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const id = `chatcmpl-${Date.now()}`; const created = Math.floor(Date.now() / 1000); res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: { content: `错误: ${error.message}` }, finish_reason: null }] })}\n\n`); res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] })}\n\n`); res.write('data: [DONE]\n\n'); res.end(); } else { res.status(500).json({ error: error.message }); } } } }); // 所有其他请求返回 index.html (SPA 支持) // Express 5 requires (.*) instead of * for wildcard app.get(/(.*)/, (req, res) => { res.sendFile(path.join(process.cwd(), 'client/dist', 'index.html')); }); const server = app.listen(config.server.port, config.server.host, () => { logger.info(`服务器已启动: ${config.server.host}:${config.server.port}`); }); server.on('error', (error) => { if (error.code === 'EADDRINUSE') { logger.error(`端口 ${config.server.port} 已被占用`); process.exit(1); } else if (error.code === 'EACCES') { logger.error(`端口 ${config.server.port} 无权限访问`); process.exit(1); } else { logger.error('服务器启动失败:', error.message); process.exit(1); } }); const shutdown = () => { logger.info('正在关闭服务器...'); // 清理空闲管理器 idleManager.destroy(); server.close(() => { logger.info('服务器已关闭'); process.exit(0); }); setTimeout(() => process.exit(0), 5000); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown);