require('dotenv').config() const express = require('express') const cors = require('cors') const helmet = require('helmet') const compression = require('compression') const morgan = require('morgan') const rateLimit = require('express-rate-limit') const connectDB = require('./config/database') const initRoutes = require('./routes') const logger = require('./utils/logger') // 创建 Express 应用 const app = express() // 数据库连接 connectDB() // 中间件配置 app.use(helmet()) // 安全头部 app.use(cors()) // 跨域支持 app.use(compression()) // Gzip 压缩 app.use(express.json({ limit: '10mb' })) // JSON 解析 app.use(express.urlencoded({ extended: true, limit: '10mb' })) // URL 编码解析 // 日志中间件 if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')) } else { app.use(morgan('combined')) } // 速率限制 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 分钟 max: 1000, // 每个 IP 最多 1000 次请求 message: { success: false, error: '请求过于频繁,请稍后再试' } }) app.use('/api/', limiter) // 健康检查 app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime() }) }) // API 路由 initRoutes(app) // 404 处理 app.use((req, res) => { res.status(404).json({ success: false, error: '接口不存在', path: req.path }) }) // 全局错误处理 app.use((err, req, res, next) => { logger.error('服务器错误:', err) res.status(err.status || 500).json({ success: false, error: process.env.NODE_ENV === 'production' ? '服务器内部错误' : err.message, stack: process.env.NODE_ENV === 'development' ? err.stack : undefined }) }) // 启动服务器(仅在直接运行时启动,被测试 import 时不启动) const PORT = process.env.PORT || 3000 let server = null if (require.main === module) { server = app.listen(PORT, () => { logger.info(`🚀 服务器启动成功`) logger.info(`📍 端口: ${PORT}`) logger.info(`🌐 环境: ${process.env.NODE_ENV || 'development'}`) logger.info(`📊 健康检查: http://localhost:${PORT}/health`) }) } // 优雅关闭 process.on('SIGTERM', () => { logger.info('📴 接收到 SIGTERM 信号,正在关闭服务器...') if (server) { server.close(() => { logger.info('✅ 服务器已关闭') process.exit(0) }) } else { process.exit(0) } }) module.exports = app