feat: 添加系统设置中修改前端端口并自动重启功能
- 后端:添加 frontend_port 配置项和重启API - 前端:添加端口设置表单和自动重启交互 - 添加前端服务管理脚本支持启动/停止/重启 - 优化重启流程,避免Network Error提示
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 前端服务管理器
|
||||
* 用于在系统设置中修改端口后自动重启前端服务
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
pid,
|
||||
port,
|
||||
isRunning,
|
||||
portInUse: !portAvailable && !isRunning, // 端口被占用但服务未运行
|
||||
url: `http://localhost:${port}`
|
||||
};
|
||||
};
|
||||
|
||||
// 命令行接口
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user