131 lines
3.9 KiB
JavaScript
131 lines
3.9 KiB
JavaScript
const { spawn } = require('child_process');
|
||
const path = require('path');
|
||
const express = require('express');
|
||
const fs = require('fs');
|
||
|
||
// 加载生产环境配置文件
|
||
require('dotenv').config({ path: path.join(__dirname, '.env.production') });
|
||
|
||
// 生产环境配置 - 宝塔面板兼容
|
||
const API_PORT = process.env.PORT || 4001;
|
||
const FRONTEND_PORT = process.env.FRONTEND_PORT || 4002;
|
||
const NODE_ENV = 'production';
|
||
const DOMAIN = process.env.DOMAIN || 'ggl.xi.plus';
|
||
|
||
// 验证端口范围 (4001-4010)
|
||
if (API_PORT < 4001 || API_PORT > 4010) {
|
||
console.warn(`警告: API端口 ${API_PORT} 不在推荐范围 4001-4010 内`);
|
||
}
|
||
if (FRONTEND_PORT < 4001 || FRONTEND_PORT > 4010) {
|
||
console.warn(`警告: 前端端口 ${FRONTEND_PORT} 不在推荐范围 4001-4010 内`);
|
||
}
|
||
|
||
// 设置环境变量
|
||
process.env.NODE_ENV = NODE_ENV;
|
||
process.env.PORT = API_PORT;
|
||
process.env.DOMAIN = DOMAIN;
|
||
|
||
console.log(`=== 梦回高句丽 - 生产环境启动 ===`);
|
||
console.log(`域名: ${DOMAIN}`);
|
||
console.log(`后端API端口: ${API_PORT}`);
|
||
console.log(`前端静态文件端口: ${FRONTEND_PORT}`);
|
||
console.log(`工作目录: ${process.cwd()}`);
|
||
console.log(`Node.js版本: ${process.version}`);
|
||
console.log(`启动时间: ${new Date().toLocaleString('zh-CN')}`);
|
||
|
||
// 检查dist目录是否存在
|
||
const distPath = path.join(__dirname, 'dist');
|
||
if (!fs.existsSync(distPath)) {
|
||
console.error(`错误: dist目录不存在 (${distPath})`);
|
||
console.log('请先运行 npm run build 构建前端项目');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 启动前端静态文件服务
|
||
const app = express();
|
||
|
||
// 安全头设置
|
||
app.use((req, res, next) => {
|
||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||
res.setHeader('X-Frame-Options', 'DENY');
|
||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||
next();
|
||
});
|
||
|
||
// 静态文件服务
|
||
app.use(express.static(distPath, {
|
||
maxAge: '1y',
|
||
etag: true,
|
||
lastModified: true
|
||
}));
|
||
|
||
// SPA路由处理
|
||
app.get('*', (req, res) => {
|
||
res.sendFile(path.join(distPath, 'index.html'));
|
||
});
|
||
|
||
const frontendServer = app.listen(FRONTEND_PORT, '0.0.0.0', () => {
|
||
console.log(`✅ 前端静态文件服务已启动`);
|
||
console.log(` - 端口: ${FRONTEND_PORT}`);
|
||
console.log(` - 访问地址: http://${DOMAIN}:${FRONTEND_PORT}`);
|
||
});
|
||
|
||
// 检查API服务器文件是否存在
|
||
const apiServerPath = path.join(__dirname, 'api', 'server.ts');
|
||
if (!fs.existsSync(apiServerPath)) {
|
||
console.error(`错误: API服务器文件不存在 (${apiServerPath})`);
|
||
process.exit(1);
|
||
}
|
||
|
||
// 启动后端API服务器
|
||
console.log(`🚀 启动后端API服务器...`);
|
||
const apiServer = spawn('node', ['--import', 'tsx/esm', 'api/server.ts'], {
|
||
stdio: 'inherit',
|
||
env: {
|
||
...process.env,
|
||
NODE_ENV: NODE_ENV,
|
||
PORT: API_PORT,
|
||
DOMAIN: DOMAIN
|
||
},
|
||
cwd: __dirname
|
||
});
|
||
|
||
// 处理API服务器进程退出
|
||
apiServer.on('close', (code) => {
|
||
console.log(`❌ API服务器进程退出,退出码: ${code}`);
|
||
frontendServer.close();
|
||
process.exit(code);
|
||
});
|
||
|
||
// 处理API服务器错误
|
||
apiServer.on('error', (err) => {
|
||
console.error('❌ 启动API服务器时发生错误:', err);
|
||
frontendServer.close();
|
||
process.exit(1);
|
||
});
|
||
|
||
// API服务器启动成功提示
|
||
setTimeout(() => {
|
||
console.log(`✅ 后端API服务已启动`);
|
||
console.log(` - 端口: ${API_PORT}`);
|
||
console.log(` - API地址: http://${DOMAIN}:${API_PORT}/api`);
|
||
console.log(`\n🌐 服务器已完全启动,可通过宝塔面板管理`);
|
||
}, 2000);
|
||
|
||
// 优雅关闭
|
||
process.on('SIGINT', () => {
|
||
console.log('\n收到 SIGINT 信号,正在关闭服务器...');
|
||
frontendServer.close();
|
||
apiServer.kill('SIGINT');
|
||
});
|
||
|
||
process.on('SIGTERM', () => {
|
||
console.log('\n收到 SIGTERM 信号,正在关闭服务器...');
|
||
frontendServer.close();
|
||
apiServer.kill('SIGTERM');
|
||
});
|
||
|
||
// 处理前端服务器错误
|
||
frontendServer.on('error', (err) => {
|
||
console.error('前端静态文件服务器错误:', err);
|
||
}); |