// 加载环境变量配置 require('dotenv').config(); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const path = require('path'); const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); const { initDB, getDB, dbConfig } = require('./database'); const { logger } = require('./config/logger'); const { errorHandler, notFound } = require('./middleware/errorHandler'); const fs = require('fs'); const app = express(); const PORT = process.env.PORT || 3000; // 确保日志目录存在 const logDir = path.join(__dirname, 'logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } // 安全中间件 app.use(helmet({ contentSecurityPolicy: false, // 为了支持前端应用 crossOriginEmbedderPolicy: false, crossOriginOpenerPolicy: false, // 禁用 COOP 头部以避免非 HTTPS 环境的警告 originAgentCluster: false // 禁用Origin-Agent-Cluster头部 })); // 中间件配置 // CORS配置 - 允许前端访问静态资源 app.use(cors({ origin: [ 'http://localhost:5173', 'http://localhost:5176', 'http://localhost:5175', 'http://localhost:5174', 'http://localhost:3001', 'https://www.zrbjr.com', 'https://zrbjr.com' ], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] })); app.use(bodyParser.json({ limit: '10mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); // 请求日志中间件 app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; // 只记录非正常状态码的请求日志(过滤掉200、304等正常返回) if (res.statusCode >= 400 || res.statusCode < 200) { logger.info('HTTP Request', { method: req.method, url: req.originalUrl, statusCode: res.statusCode, duration: `${duration}ms`, ip: req.ip, userAgent: req.get('User-Agent') }); } }); next(); }); // 限流中间件 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 1000, // 限制每个IP 15分钟内最多100个请求 message: { success: false, error: { code: 'RATE_LIMIT_EXCEEDED', message: '请求过于频繁,请稍后再试' } } }); app.use('/api', limiter); // 静态文件服务 - 必须在API路由之前 // 为uploads路径配置CORS和静态文件服务 app.use('/uploads', express.static(path.join(__dirname, 'uploads'), { setHeaders: (res, filePath) => { // 设置CORS头部 res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); // 设置缓存和内容类型 if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) { res.setHeader('Content-Type', 'image/jpeg'); } else if (filePath.endsWith('.png')) { res.setHeader('Content-Type', 'image/png'); } else if (filePath.endsWith('.gif')) { res.setHeader('Content-Type', 'image/gif'); } else if (filePath.endsWith('.webp')) { res.setHeader('Content-Type', 'image/webp'); } res.setHeader('Cache-Control', 'public, max-age=86400'); // 1天缓存 } })); // 处理vite.svg请求 app.get('/vite.svg', (req, res) => { const referer = req.get('Referer'); if (referer && referer.includes('/admin')) { // 为admin页面提供logo.svg res.setHeader('Content-Type', 'image/svg+xml'); res.sendFile(path.join(__dirname, 'admin/dist/logo.svg')); } else { // 前端页面没有vite.svg,返回404 res.status(404).send('File not found'); } }); // 静态文件服务配置 // 专门处理admin路径下的assets app.use('/admin/assets', express.static(path.join(__dirname, 'admin/dist/assets'), { setHeaders: (res, filePath) => { res.removeHeader('Origin-Agent-Cluster'); if (filePath.endsWith('.css')) { res.setHeader('Content-Type', 'text/css; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } else if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } } })); // 为admin页面的assets提供服务(当从admin页面访问/assets/时) app.use('/assets', (req, res, next) => { // 检查referer来判断是否来自admin页面 const referer = req.get('Referer'); if (referer && referer.includes('/admin')) { // 如果来自admin页面,从admin/dist/assets提供文件 express.static(path.join(__dirname, 'admin/dist/assets'), { setHeaders: (res, filePath) => { res.removeHeader('Origin-Agent-Cluster'); if (filePath.endsWith('.css')) { res.setHeader('Content-Type', 'text/css; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } else if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } } })(req, res, next); } else { // 否则从frontend/dist/assets提供文件 express.static(path.join(__dirname, 'frontend/dist/assets'), { setHeaders: (res, filePath) => { res.removeHeader('Origin-Agent-Cluster'); if (filePath.endsWith('.css')) { res.setHeader('Content-Type', 'text/css; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } else if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); } } })(req, res, next); } }); app.use('/admin', express.static(path.join(__dirname, 'admin/dist'), { setHeaders: (res, filePath) => { // 移除Origin-Agent-Cluster头部以避免冲突 res.removeHeader('Origin-Agent-Cluster'); if (filePath.endsWith('.css')) { res.setHeader('Content-Type', 'text/css; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 } else if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 } else if (filePath.endsWith('.html')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // HTML文件不缓存 res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); } else if (filePath.endsWith('.svg')) { res.setHeader('Content-Type', 'image/svg+xml'); } } })); app.use(express.static(path.join(__dirname, 'frontend/dist'), { setHeaders: (res, filePath) => { // 移除Origin-Agent-Cluster头部以避免冲突 res.removeHeader('Origin-Agent-Cluster'); if (filePath.endsWith('.css')) { res.setHeader('Content-Type', 'text/css; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 } else if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 } else if (filePath.endsWith('.html')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // HTML文件不缓存 res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); } else if (filePath.endsWith('.svg')) { res.setHeader('Content-Type', 'image/svg+xml'); } } })); // 引入数据库初始化模块 const { initDatabase } = require('./config/database-init'); // API路由 app.use('/api/auth', require('./routes/auth')); app.use('/api/users', require('./routes/users')); app.use('/api/user', require('./routes/users')); // 添加单数形式的路由映射 app.use('/api/products', require('./routes/products')); app.use('/api/specifications', require('./routes/specifications')); app.use('/api/orders', require('./routes/orders')); app.use('/api/points', require('./routes/points')); app.use('/api/captcha', require('./routes/captcha')); // 验证码路由 app.use('/api/sms', require('./routes/sms')); // 短信验证路由 app.use('/api/upload', require('./routes/upload')); app.use('/api/transfers', require('./routes/transfers')); app.use('/api/matching', require('./routes/matching')); app.use('/api/admin/matching', require('./routes/matchingAdmin')); app.use('/api/system', require('./routes/system')); app.use('/api/risk', require('./routes/riskManagement')); app.use('/api/agents', require('./routes/agents')); app.use('/api/admin/agents', require('./routes/agents/agents')); app.use('/api/admin/withdrawals', require('./routes/agents/withdrawals')); app.use('/api/agent-withdrawals', require('./routes/agent-withdrawals')); app.use('/api/regions', require('./routes/regions')); app.use('/api/addresses', require('./routes/addresses')); app.use('/api/address-labels', require('./routes/address-labels')); app.use('/api/cart', require('./routes/cart')); app.use('/api/announcements', require('./routes/announcements')); // 通知公告路由 app.use('/api/wechat-pay', require('./routes/wechatPay')); // 只保留微信支付 app.use('/api/payment', require('./routes/payment')); // 商城后台相关接口 app.use('/api/shopbackend', require('./routes/shopbackend')); // 前端路由 - 必须在最后,作为fallback app.get('/', (req, res) => { res.removeHeader('Origin-Agent-Cluster'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); }); app.get('/admin*', (req, res) => { res.removeHeader('Origin-Agent-Cluster'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); res.sendFile(path.join(__dirname, 'admin/dist/index.html')); }); app.get('/frontend*', (req, res) => { res.removeHeader('Origin-Agent-Cluster'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); }); // SPA fallback - 处理前端路由 app.get('*', (req, res) => { // 如果请求的是静态资源但找不到,返回404(不返回JSON) if (req.path.includes('.')) { return res.status(404).send('File not found'); } // 否则返回前端应用的index.html res.removeHeader('Origin-Agent-Cluster'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); }); // 404处理 app.use(notFound); // 全局错误处理中间件 app.use(errorHandler); // 导出数据库连接供路由使用 module.exports = { get db() { return getDB(); } }; // 启动服务器 app.listen(PORT, async () => { try { logger.info('Server starting', { port: PORT }); console.log(`服务器运行在 http://localhost:${PORT}`); await initDatabase(); // global.sqlReq = mysql.createConnection() // 启动转账超时检查服务 const timeoutService = require('./services/timeoutService'); timeoutService.startTimeoutChecker(); console.log('转账超时检查服务已启动'); // 启动数据库连接监控 // const dbMonitor = require('./db-monitor'); // dbMonitor.startMonitoring(60000); // 每分钟监控一次 // console.log('数据库连接监控已启动'); global.captchaStore = new Map(); logger.info('Server started successfully', { port: PORT, environment: process.env.NODE_ENV || 'development' }); } catch (error) { logger.error('Failed to start server', { error: error.message }); process.exit(1); } }); // 优雅关闭 process.on('SIGTERM', async () => { logger.info('SIGTERM received, shutting down gracefully'); try { const { closeDB } = require('./database'); await closeDB(); } catch (error) { logger.error('Error closing database', { error: error.message }); } process.exit(0); }); process.on('SIGINT', async () => { logger.info('SIGINT received, shutting down gracefully'); try { const { closeDB } = require('./database'); await closeDB(); } catch (error) { logger.error('Error closing database', { error: error.message }); } process.exit(0); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection', { reason, promise }); }); process.on('uncaughtException', (error) => { logger.error('Uncaught Exception', { error: error.message, stack: error.stack }); process.exit(1); });