Files
yunrui_asset/scripts/frontend-manager.js
T

246 lines
6.3 KiB
JavaScript
Raw Normal View History

/**
* 前端服务管理器
* 用于在系统设置中修改端口后自动重启前端服务
*/
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
};