266 lines
6.5 KiB
JavaScript
266 lines
6.5 KiB
JavaScript
const express = require('express');
|
||
const cors = require('cors');
|
||
const helmet = require('helmet');
|
||
const compression = require('compression');
|
||
const dotenv = require('dotenv');
|
||
|
||
// 导入中间件
|
||
const { requestLogger, consoleLogger, errorLogger, apiStats } = require('./middleware/logger');
|
||
const { notFoundHandler, globalErrorHandler, gracefulShutdown } = require('./middleware/errorHandler');
|
||
const { globalRateLimit, chatRateLimit } = require('./middleware/rateLimit');
|
||
const { sessionAuth, contentTypeCheck } = require('./middleware/auth');
|
||
|
||
// 导入路由
|
||
const apiRoutes = require('./routes');
|
||
|
||
// 导入数据库
|
||
const database = require('./models/database');
|
||
|
||
// 导入配置验证
|
||
const { validateConfig } = require('./config/llm');
|
||
|
||
// 加载环境变量
|
||
dotenv.config();
|
||
|
||
class App {
|
||
constructor() {
|
||
this.app = express();
|
||
this.port = process.env.PORT || 3000;
|
||
this.env = process.env.NODE_ENV || 'development';
|
||
|
||
// 初始化应用
|
||
this.initializeMiddlewares();
|
||
this.initializeRoutes();
|
||
this.initializeErrorHandling();
|
||
this.initializeGracefulShutdown();
|
||
}
|
||
|
||
/**
|
||
* 初始化中间件
|
||
*/
|
||
initializeMiddlewares() {
|
||
// 安全相关中间件
|
||
this.app.use(helmet({
|
||
contentSecurityPolicy: false, // 禁用CSP以避免开发时的问题
|
||
crossOriginEmbedderPolicy: false
|
||
}));
|
||
|
||
// 启用 trust proxy,用于正确获取客户端IP
|
||
this.app.set('trust proxy', 1);
|
||
|
||
// CORS配置
|
||
const corsOptions = {
|
||
origin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:5173'],
|
||
credentials: true,
|
||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||
allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'x-session-id']
|
||
};
|
||
this.app.use(cors(corsOptions));
|
||
|
||
// 压缩响应
|
||
this.app.use(compression());
|
||
|
||
// 解析JSON请求体
|
||
this.app.use(express.json({
|
||
limit: '10mb',
|
||
strict: true
|
||
}));
|
||
|
||
// 设置响应字符集编码
|
||
this.app.use((req, res, next) => {
|
||
const originalJson = res.json;
|
||
res.json = function(data) {
|
||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||
return originalJson.call(this, data);
|
||
};
|
||
next();
|
||
});
|
||
|
||
// 解析URL编码的请求体
|
||
this.app.use(express.urlencoded({
|
||
extended: true,
|
||
limit: '10mb'
|
||
}));
|
||
|
||
// 日志中间件
|
||
this.app.use(consoleLogger);
|
||
this.app.use(requestLogger);
|
||
this.app.use(errorLogger);
|
||
|
||
// API统计中间件
|
||
this.app.use(apiStats);
|
||
|
||
// 会话认证中间件
|
||
this.app.use(sessionAuth);
|
||
|
||
// 内容类型检查中间件
|
||
this.app.use('/api', contentTypeCheck());
|
||
|
||
// 全局速率限制
|
||
this.app.use('/api', globalRateLimit);
|
||
|
||
// 聊天API专用速率限制
|
||
this.app.use('/api/chat', chatRateLimit);
|
||
}
|
||
|
||
/**
|
||
* 初始化路由
|
||
*/
|
||
initializeRoutes() {
|
||
// 根路径
|
||
this.app.get('/', (req, res) => {
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
name: 'LLM Chat Website Backend',
|
||
version: '1.0.0',
|
||
environment: this.env,
|
||
timestamp: new Date().toISOString()
|
||
},
|
||
message: '欢迎使用大语言模型聊天网站后端服务'
|
||
});
|
||
});
|
||
|
||
// API路由
|
||
this.app.use('/api', apiRoutes);
|
||
|
||
// 静态文件服务(如果需要)
|
||
this.app.use('/static', express.static('public'));
|
||
}
|
||
|
||
/**
|
||
* 初始化错误处理
|
||
*/
|
||
initializeErrorHandling() {
|
||
// 404处理
|
||
this.app.use(notFoundHandler);
|
||
|
||
// 全局错误处理
|
||
this.app.use(globalErrorHandler);
|
||
}
|
||
|
||
/**
|
||
* 初始化优雅关闭
|
||
*/
|
||
initializeGracefulShutdown() {
|
||
gracefulShutdown();
|
||
}
|
||
|
||
/**
|
||
* 初始化数据库
|
||
*/
|
||
async initializeDatabase() {
|
||
try {
|
||
await database.initialize();
|
||
console.log('数据库初始化完成');
|
||
return true;
|
||
} catch (error) {
|
||
console.error('数据库初始化失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证配置
|
||
*/
|
||
validateConfiguration() {
|
||
console.log('验证配置...');
|
||
|
||
// 验证LLM配置
|
||
const llmConfigValid = validateConfig();
|
||
if (!llmConfigValid) {
|
||
console.warn('LLM配置可能不完整,请检查环境变量');
|
||
}
|
||
|
||
// 验证必要的环境变量
|
||
const requiredEnvVars = ['PORT'];
|
||
const missingEnvVars = requiredEnvVars.filter(envVar => !process.env[envVar]);
|
||
|
||
if (missingEnvVars.length > 0) {
|
||
console.warn('缺少环境变量:', missingEnvVars.join(', '));
|
||
}
|
||
|
||
console.log('配置验证完成');
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 启动服务器
|
||
*/
|
||
async start() {
|
||
try {
|
||
console.log('启动应用程序...');
|
||
|
||
// 验证配置
|
||
this.validateConfiguration();
|
||
|
||
// 初始化数据库
|
||
const dbInitialized = await this.initializeDatabase();
|
||
if (!dbInitialized) {
|
||
throw new Error('数据库初始化失败');
|
||
}
|
||
|
||
// 启动HTTP服务器
|
||
const server = this.app.listen(this.port, () => {
|
||
console.log(`
|
||
🚀 服务器启动成功!
|
||
📝 环境: ${this.env}
|
||
🌐 端口: ${this.port}
|
||
🔗 本地地址: http://localhost:${this.port}
|
||
📊 API文档: http://localhost:${this.port}/api
|
||
🏥 健康检查: http://localhost:${this.port}/api/health
|
||
`);
|
||
});
|
||
|
||
// 设置服务器超时
|
||
server.timeout = 30000; // 30秒
|
||
|
||
// 处理服务器错误
|
||
server.on('error', (error) => {
|
||
if (error.code === 'EADDRINUSE') {
|
||
console.error(`❌ 端口 ${this.port} 已被占用`);
|
||
process.exit(1);
|
||
} else {
|
||
console.error('❌ 服务器启动失败:', error);
|
||
process.exit(1);
|
||
}
|
||
});
|
||
|
||
// 优雅关闭处理
|
||
const gracefulShutdown = () => {
|
||
console.log('📝 正在关闭服务器...');
|
||
server.close(async () => {
|
||
console.log('🔒 HTTP服务器已关闭');
|
||
|
||
try {
|
||
await database.close();
|
||
console.log('🔒 数据库连接已关闭');
|
||
} catch (error) {
|
||
console.error('关闭数据库连接失败:', error);
|
||
}
|
||
|
||
console.log('👋 应用程序已安全退出');
|
||
process.exit(0);
|
||
});
|
||
};
|
||
|
||
process.on('SIGTERM', gracefulShutdown);
|
||
process.on('SIGINT', gracefulShutdown);
|
||
|
||
return server;
|
||
|
||
} catch (error) {
|
||
console.error('❌ 应用程序启动失败:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取Express应用实例
|
||
*/
|
||
getApp() {
|
||
return this.app;
|
||
}
|
||
}
|
||
|
||
module.exports = App; |