| |
| |
| |
| |
|
|
| import express from 'express'; |
| import cors from 'cors'; |
| import path from 'path'; |
| import { closeRequester } from '../api/client.js'; |
| import logger from '../utils/logger.js'; |
| import config from '../config/config.js'; |
| import memoryManager from '../utils/memoryManager.js'; |
| import { getPublicDir, getRelativePath } from '../utils/paths.js'; |
| import { MEMORY_CHECK_INTERVAL } from '../constants/index.js'; |
| import { errorHandler } from '../utils/errors.js'; |
| import { getChunkPoolSize, clearChunkPool } from './stream.js'; |
|
|
| |
| import adminRouter from '../routes/admin.js'; |
| import sdRouter from '../routes/sd.js'; |
| import openaiRouter from '../routes/openai.js'; |
| import geminiRouter from '../routes/gemini.js'; |
| import claudeRouter from '../routes/claude.js'; |
|
|
| const publicDir = getPublicDir(); |
|
|
| logger.info(`静态文件目录: ${getRelativePath(publicDir)}`); |
|
|
| const app = express(); |
|
|
| |
| memoryManager.setThreshold(config.server.memoryThreshold); |
| memoryManager.start(MEMORY_CHECK_INTERVAL); |
|
|
| |
| app.use(cors()); |
| app.use(express.json({ limit: config.security.maxRequestSize })); |
|
|
| |
| app.use('/images', express.static(path.join(publicDir, 'images'))); |
| app.use(express.static(publicDir)); |
|
|
| |
| app.use('/admin', adminRouter); |
|
|
| |
| app.use(errorHandler); |
|
|
| |
| app.use((req, res, next) => { |
| const ignorePaths = [ |
| '/images', '/favicon.ico', '/.well-known', |
| '/sdapi/v1/options', '/sdapi/v1/samplers', '/sdapi/v1/schedulers', |
| '/sdapi/v1/upscalers', '/sdapi/v1/latent-upscale-modes', |
| '/sdapi/v1/sd-vae', '/sdapi/v1/sd-modules' |
| ]; |
| |
| const fullPath = req.originalUrl.split('?')[0]; |
| if (!ignorePaths.some(p => fullPath.startsWith(p))) { |
| const start = Date.now(); |
| res.on('finish', () => { |
| logger.request(req.method, fullPath, res.statusCode, Date.now() - start); |
| }); |
| } |
| next(); |
| }); |
|
|
| |
| app.use('/sdapi/v1', sdRouter); |
|
|
| |
| app.use((req, res, next) => { |
| if (req.path.startsWith('/v1/')) { |
| const apiKey = config.security?.apiKey; |
| if (apiKey) { |
| const authHeader = req.headers.authorization || req.headers['x-api-key']; |
| const providedKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; |
| if (providedKey !== apiKey) { |
| logger.warn(`API Key 验证失败: ${req.method} ${req.path} (提供的Key: ${providedKey ? providedKey.substring(0, 10) + '...' : '无'})`); |
| return res.status(401).json({ error: 'Invalid API Key' }); |
| } |
| } |
| } else if (req.path.startsWith('/v1beta/')) { |
| const apiKey = config.security?.apiKey; |
| if (apiKey) { |
| const providedKey = req.query.key || req.headers['x-goog-api-key']; |
| if (providedKey !== apiKey) { |
| logger.warn(`API Key 验证失败: ${req.method} ${req.path} (提供的Key: ${providedKey ? providedKey.substring(0, 10) + '...' : '无'})`); |
| return res.status(401).json({ error: 'Invalid API Key' }); |
| } |
| } |
| } |
| next(); |
| }); |
|
|
| |
|
|
| |
| app.use('/v1', openaiRouter); |
|
|
| |
| app.use('/v1beta', geminiRouter); |
|
|
| |
| app.use('/v1', claudeRouter); |
|
|
| |
|
|
| |
| app.get('/v1/memory', (req, res) => { |
| const usage = process.memoryUsage(); |
| res.json({ |
| heapUsed: usage.heapUsed, |
| heapTotal: usage.heapTotal, |
| rss: usage.rss, |
| external: usage.external, |
| arrayBuffers: usage.arrayBuffers, |
| pressure: memoryManager.getCurrentPressure(), |
| poolSizes: memoryManager.getPoolSizes(), |
| chunkPoolSize: getChunkPoolSize() |
| }); |
| }); |
|
|
| |
| app.get('/health', (req, res) => { |
| res.json({ status: 'ok', uptime: process.uptime() }); |
| }); |
|
|
| |
| 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('正在关闭服务器...'); |
| |
| |
| memoryManager.stop(); |
| logger.info('已停止内存管理器'); |
| |
| |
| closeRequester(); |
| logger.info('已关闭子进程请求器'); |
| |
| |
| clearChunkPool(); |
| logger.info('已清理对象池'); |
| |
| server.close(() => { |
| logger.info('服务器已关闭'); |
| process.exit(0); |
| }); |
| |
| |
| setTimeout(() => { |
| logger.warn('服务器关闭超时,强制退出'); |
| process.exit(0); |
| }, 5000); |
| }; |
|
|
| process.on('SIGINT', shutdown); |
| process.on('SIGTERM', shutdown); |
|
|
| |
| process.on('uncaughtException', (error) => { |
| logger.error('未捕获异常:', error.message); |
| |
| }); |
|
|
| process.on('unhandledRejection', (reason, promise) => { |
| logger.error('未处理的 Promise 拒绝:', reason); |
| }); |
|
|