故事背景:
使用【electron】开发的软件S中集成了一个新的功能A;新功能需要一个【单独的后端服务BackA】支持。评审决定BackA的启动需要纳入【权限系统】中。也就是说BackA需要根据网络请求的结果【动态启动】;本文记录了实现此需求的过程,以及实现过程中踩的坑。
痛点问题:
1. electron中如何发送【网络请求】
2. 如何启动或者关闭一个后端服务
3. 当软件【异常退出】的时候,动态启动的BackA如何关闭
4. BackA的【信息】如何保存
解决方案:
1. 针对electron发送网络请求的问题,讨论之后最终决定使用axios实现。
2. 使用nodejs中内置的child_process实现进程的动态启停
3. 将BackA作为软件S的子进程运行,这样当S异常退出的时候BackA也能自行关闭;此外在动态启动BackA之前根据BackA的进程名获取进程号,然后逐个关闭。
4. 建立独立的日志系统保存BackA服务信息,用到了winston库。
相关代码:
1. axios实现网络请求
const axios = require('axios');
axios({
method: 'get',
url: backAUrl,
})
.then((data) => {...})
2. 使用child_process库中的spawn方法和execSync方法完成对服务的动态启停:
辅助函数:
// 获取backA的启动程序名称
const getBackA = () => 'BackA.exe';
// 获取backA启动程序目录
const getBackADir = () =>
process.env.NODE_ENV === 'development'
? '***'
: path.resolve(__dirname, '***');
// 获取backA的启动位置
const getBackAPath = () => {
return path.resolve(getBackADir(), getBackA());
};
启动服务:
try {
const { spawn } = require('child_process');
// 开启子进程
const childProcess = spawn(getBackAPath(), []);
// 保存此子进程的pid
backaId = childProcess.pid;
childProcess.stdout.on('data', (data) => {
// 这里的vLogger是记录backA信息的日志对象
vLogger.log('info', `子进程输出: ${data}`);
});
// 处理错误
childProcess.on('error', (error) => {
console.error('error', `子进程错误: ${error}`);
throw new Error('launch voice assistant failed');
});
// // 断线通知
// childProcess.on('close', (code) => {
// console.log('info', `子进程退出,退出码: ${code}`);
// });
} catch (e: unknown) {
console.log('error', 'launch backA failed');
}
关闭服务:
const exec = require('child_process').execSync;
try {
// 这里的backaId是backA进程的pid
exec(`taskkill /F /PID ${backaId}`);
} catch (e: unknown) {
console.log('Error:删除backA进程失败!');
}
3. 启动backA之前关闭已经挂住的服务
const processName = getBackA ();
const command = 'wmic process where "name=\'' + processName + '\'" get ProcessId /VALUE';
try {
const output = exec(command).toString('utf8');
const pidLine = output
.trim()
.split('=')
.filter((v: string) => v !== 'ProcessId');
if (pidLine?.forEach) {
pidLine.forEach((pid: string) => exec(`taskkill /F /PID ${parseInt(pid, 10)}`));
}
} catch (e: unknown) {
return;
}
4. 记录backA服务的日志
const { createLogger, transports, format } = require('winston');
const vLoggerName = path.resolve(
getBackADir(),
'Log',
getBackA() + '_' + new Date().toLocaleString().replace(/[:/ ]/g, '_') + '_runtime.log',
);
const vLogger = createLogger({
level: 'info', // 设置日志输出级别
format: format.combine(
format.timestamp(), // 添加时间戳
format.simple(), // 使用简单的文本格式输出日志信息
),
transports: [
// new transports.Console(), // 输出到控制台
new transports.File({
// 输出到文件
filename: vLoggerName,
}),
],
});
执行时机:
1. 是否动态启动backA的请求R在app.whenReady()的回调中执行;
2. 动态启动backA服务在R的回调中执行;
3. 清除已经挂住的backA服务在启动backA之前进行,同样也是在R的回调中进行;
4. 关闭backA服务在主进程退出之前进行;
欢迎各位大佬批评指正~