llm-chat/backend/app.js

266 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;