2025-08-23 11:12:09 +08:00
|
|
|
|
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
|
|
|
|
|
}));
|
|
|
|
|
|
2025-08-23 15:24:14 +08:00
|
|
|
|
// 设置响应字符集编码
|
|
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-23 11:12:09 +08:00
|
|
|
|
// 解析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;
|