OrangeTV/production.js

179 lines
4.8 KiB
JavaScript
Raw Permalink 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.

/**
* 生产模式下的服务器入口
* 使用 NODE_ENV=production node production.js 来启动
*/
process.env.NODE_ENV = 'production';
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const path = require('path');
const http = require('http');
const { createWebSocketServer } = require('./websocket');
// 调用 generate-manifest.js 生成 manifest.json
function generateManifest() {
console.log('Generating manifest.json for Docker deployment...');
try {
const generateManifestScript = path.join(
__dirname,
'scripts',
'generate-manifest.js'
);
require(generateManifestScript);
} catch (error) {
console.error('❌ Error calling generate-manifest.js:', error);
throw error;
}
}
// 生成manifest
generateManifest();
const hostname = process.env.HOSTNAME || '0.0.0.0';
const port = process.env.PORT || 3000;
// 在生产模式下初始化 Next.js
const app = next({
dev: false,
hostname,
port
});
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
// 检查是否是WebSocket升级请求如果是则跳过Next.js处理
const upgrade = req.headers.upgrade;
if (upgrade && upgrade.toLowerCase() === 'websocket') {
// 不处理WebSocket升级请求让upgrade事件处理器处理
return;
}
// 使用Next.js处理所有非WebSocket请求
const parsedUrl = parse(req.url, true);
await handle(req, res, parsedUrl);
} catch (err) {
console.error('处理请求时出错:', req.url, err);
res.statusCode = 500;
res.end('内部服务器错误');
}
});
// 初始化 WebSocket 服务器
const wss = createWebSocketServer();
// 将 WebSocket 服务器实例存储到全局对象中,供 API 路由使用
global.wss = wss;
// 使用WeakSet来跟踪已处理的socket避免重复处理
const handledSockets = new WeakSet();
// 处理 WebSocket 升级请求
server.on('upgrade', (request, socket, head) => {
// 如果socket已经被处理过直接返回
if (handledSockets.has(socket)) {
return;
}
const pathname = parse(request.url).pathname;
if (pathname === '/ws') {
console.log('处理 WebSocket 升级请求:', pathname);
// 标记socket已被处理
handledSockets.add(socket);
// 处理WebSocket连接
try {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
} catch (error) {
console.error('WebSocket升级错误:', error);
socket.destroy();
}
} else {
console.log('未知的升级请求路径:', pathname);
// 不销毁socket让它自然关闭
}
});
// 启动服务器
server.listen(port, (err) => {
if (err) throw err;
console.log(`> 服务已启动 (生产模式): http://${hostname}:${port}`);
console.log(`> WebSocket 服务已启动: ws://${hostname}:${port}/ws`);
// 设置服务器启动后的任务
setupServerTasks();
});
});
// 设置服务器启动后的任务
function setupServerTasks() {
// 每 1 秒轮询一次,直到请求成功
const TARGET_URL = `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}/login`;
const intervalId = setInterval(() => {
console.log(`Fetching ${TARGET_URL} ...`);
const req = http.get(TARGET_URL, (res) => {
// 当返回 2xx 状态码时认为成功,然后停止轮询
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
console.log('Server is up, stop polling.');
clearInterval(intervalId);
setTimeout(() => {
// 服务器启动后,立即执行一次 cron 任务
executeCronJob();
}, 3000);
// 然后设置每小时执行一次 cron 任务
setInterval(() => {
executeCronJob();
}, 60 * 60 * 1000); // 每小时执行一次
}
});
req.setTimeout(2000, () => {
req.destroy();
});
}, 1000);
}
// 执行 cron 任务的函数
function executeCronJob() {
const cronUrl = `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}/api/cron`;
console.log(`Executing cron job: ${cronUrl}`);
const req = http.get(cronUrl, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
console.log('Cron job executed successfully:', data);
} else {
console.error('Cron job failed:', res.statusCode, data);
}
});
});
req.on('error', (err) => {
console.error('Error executing cron job:', err);
});
req.setTimeout(30000, () => {
console.error('Cron job timeout');
req.destroy();
});
}