- 检测是否为生产环境(通过检查dist目录) - 生产环境显示Nginx配置提示,不自动重启 - 开发环境保持自动重启功能 - 区分显示生产环境和开发环境状态
246 lines
6.3 KiB
JavaScript
246 lines
6.3 KiB
JavaScript
/**
|
|
* 前端服务管理器
|
|
* 用于在系统设置中修改端口后自动重启前端服务
|
|
*/
|
|
|
|
const { exec } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const net = require('net');
|
|
|
|
// 配置文件路径
|
|
const CONFIG_FILE = path.join(__dirname, '../frontend/.frontend-port');
|
|
const PID_FILE = path.join(__dirname, '../frontend/.frontend-pid');
|
|
|
|
// 检查端口是否被占用
|
|
const checkPort = (port) => {
|
|
return new Promise((resolve) => {
|
|
const server = net.createServer();
|
|
server.once('error', () => {
|
|
resolve(false); // 端口被占用
|
|
});
|
|
server.once('listening', () => {
|
|
server.close();
|
|
resolve(true); // 端口可用
|
|
});
|
|
server.listen(port);
|
|
});
|
|
};
|
|
|
|
// 获取当前配置的端口
|
|
const getConfiguredPort = () => {
|
|
try {
|
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
const port = parseInt(fs.readFileSync(CONFIG_FILE, 'utf-8').trim(), 10);
|
|
if (!isNaN(port) && port > 0 && port < 65536) {
|
|
return port;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('读取端口配置失败:', e.message);
|
|
}
|
|
return 3000; // 默认端口
|
|
};
|
|
|
|
// 保存PID到文件
|
|
const savePid = (pid) => {
|
|
try {
|
|
fs.writeFileSync(PID_FILE, pid.toString(), 'utf-8');
|
|
} catch (e) {
|
|
console.error('保存PID失败:', e.message);
|
|
}
|
|
};
|
|
|
|
// 读取PID文件
|
|
const getSavedPid = () => {
|
|
try {
|
|
if (fs.existsSync(PID_FILE)) {
|
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
return isNaN(pid) ? null : pid;
|
|
}
|
|
} catch (e) {
|
|
console.warn('读取PID文件失败:', e.message);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// 清除PID文件
|
|
const clearPid = () => {
|
|
try {
|
|
if (fs.existsSync(PID_FILE)) {
|
|
fs.unlinkSync(PID_FILE);
|
|
}
|
|
} catch (e) {
|
|
console.warn('清除PID文件失败:', e.message);
|
|
}
|
|
};
|
|
|
|
// 检查进程是否存在
|
|
const isProcessRunning = (pid) => {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// 停止前端服务
|
|
const stopFrontend = async () => {
|
|
return new Promise((resolve) => {
|
|
const pid = getSavedPid();
|
|
|
|
if (pid && isProcessRunning(pid)) {
|
|
console.log(`正在停止前端服务 (PID: ${pid})...`);
|
|
|
|
// Windows 使用 taskkill
|
|
const killCmd = process.platform === 'win32'
|
|
? `taskkill /PID ${pid} /T /F`
|
|
: `kill -9 ${pid}`;
|
|
|
|
exec(killCmd, (error) => {
|
|
if (error) {
|
|
console.warn('停止服务警告:', error.message);
|
|
} else {
|
|
console.log('前端服务已停止');
|
|
}
|
|
clearPid();
|
|
resolve();
|
|
});
|
|
} else {
|
|
console.log('没有找到运行中的前端服务');
|
|
clearPid();
|
|
resolve();
|
|
}
|
|
});
|
|
};
|
|
|
|
// 启动前端服务
|
|
const startFrontend = async () => {
|
|
const port = getConfiguredPort();
|
|
|
|
// 检查端口是否可用
|
|
const isAvailable = await checkPort(port);
|
|
if (!isAvailable) {
|
|
throw new Error(`端口 ${port} 已被占用,请更换端口或关闭占用该端口的程序`);
|
|
}
|
|
|
|
console.log(`正在启动前端服务,端口: ${port}...`);
|
|
|
|
const frontendDir = path.join(__dirname, '../frontend');
|
|
|
|
// 使用 exec 启动服务(Windows 兼容)
|
|
const isWindows = process.platform === 'win32';
|
|
const cmd = isWindows
|
|
? `set FRONTEND_PORT=${port} && npm run dev`
|
|
: `FRONTEND_PORT=${port} npm run dev`;
|
|
|
|
const child = exec(cmd, {
|
|
cwd: frontendDir,
|
|
windowsHide: true // Windows 隐藏窗口
|
|
});
|
|
|
|
// 保存PID
|
|
savePid(child.pid);
|
|
console.log(`前端服务已启动 (PID: ${child.pid}),端口: ${port}`);
|
|
|
|
return { pid: child.pid, port };
|
|
};
|
|
|
|
// 重启前端服务
|
|
const restartFrontend = async () => {
|
|
console.log('正在重启前端服务...');
|
|
await stopFrontend();
|
|
|
|
// 等待1秒确保端口释放
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const result = await startFrontend();
|
|
console.log(`前端服务重启完成,访问地址: http://localhost:${result.port}`);
|
|
return result;
|
|
};
|
|
|
|
// 获取前端服务状态
|
|
const getStatus = async () => {
|
|
const pid = getSavedPid();
|
|
const port = getConfiguredPort();
|
|
const isRunning = pid ? isProcessRunning(pid) : false;
|
|
|
|
// 检查端口实际占用情况
|
|
const portAvailable = await checkPort(port);
|
|
|
|
// 检测是否为生产环境(通过检查是否存在 dist 目录)
|
|
const frontendDir = path.join(__dirname, '../frontend');
|
|
const distDir = path.join(frontendDir, 'dist');
|
|
const isProduction = fs.existsSync(distDir);
|
|
|
|
// 生产环境:只要端口被占用就认为服务在运行(Nginx/其他服务器)
|
|
// 开发环境:通过 PID 判断
|
|
const isServiceRunning = isProduction ? !portAvailable : isRunning;
|
|
|
|
return {
|
|
pid,
|
|
port,
|
|
isRunning: isServiceRunning,
|
|
portInUse: !portAvailable && !isServiceRunning, // 端口被占用但服务未运行
|
|
url: `http://localhost:${port}`,
|
|
isProduction // 是否为生产环境
|
|
};
|
|
};
|
|
|
|
// 命令行接口
|
|
const main = async () => {
|
|
const command = process.argv[2];
|
|
|
|
try {
|
|
switch (command) {
|
|
case 'start':
|
|
await startFrontend();
|
|
break;
|
|
case 'stop':
|
|
await stopFrontend();
|
|
break;
|
|
case 'restart':
|
|
await restartFrontend();
|
|
break;
|
|
case 'status':
|
|
const status = await getStatus();
|
|
console.log('前端服务状态:');
|
|
console.log(` PID: ${status.pid || '无'}`);
|
|
console.log(` 端口: ${status.port}`);
|
|
console.log(` 运行中: ${status.isRunning ? '是' : '否'}`);
|
|
console.log(` 访问地址: ${status.url}`);
|
|
if (status.portInUse) {
|
|
console.log(' 警告: 端口被其他程序占用');
|
|
}
|
|
break;
|
|
default:
|
|
console.log('用法: node frontend-manager.js [start|stop|restart|status]');
|
|
console.log('');
|
|
console.log('命令:');
|
|
console.log(' start - 启动前端服务');
|
|
console.log(' stop - 停止前端服务');
|
|
console.log(' restart - 重启前端服务');
|
|
console.log(' status - 查看服务状态');
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
console.error('错误:', error.message);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// 如果是直接运行此脚本
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
// 导出模块供其他文件使用
|
|
module.exports = {
|
|
startFrontend,
|
|
stopFrontend,
|
|
restartFrontend,
|
|
getStatus,
|
|
getConfiguredPort
|
|
};
|