NapCatQQ/packages/napcat-shell/napcat.ts
手瓜一十雪 3e8b575015 Add process restart feature via WebUI
Introduces backend and frontend support for restarting the worker process from the WebUI. Adds API endpoint, controller, and UI button for process management. Refactors napcat-shell to support master/worker process lifecycle and restart logic.
2026-01-17 14:42:07 +08:00

241 lines
7.7 KiB
TypeScript
Raw 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.

import { NCoreInitShell } from './base';
import { NapCatPathWrapper } from '@/napcat-common/src/path';
import { LogWrapper } from '@/napcat-core/helper/log';
import { connectToNamedPipe } from './pipe';
import path from 'path';
import { fileURLToPath } from 'url';
// ES 模块中获取 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 扩展 Process 类型以支持 parentPort
declare global {
namespace NodeJS {
interface Process {
parentPort?: {
on (event: 'message', listener: (e: { data: any; }) => void): void;
postMessage (message: any): void;
};
}
}
}
// 判断是否为子进程(通过环境变量)
const isWorkerProcess = process.env['NAPCAT_WORKER_PROCESS'] === '1';
// 只在主进程中导入 utilityProcess
let utilityProcess: any;
if (!isWorkerProcess) {
// @ts-ignore - electron 运行时存在但类型声明可能缺失
const electron = await import('electron');
utilityProcess = electron.utilityProcess;
}
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
// 存储当前的 worker 进程引用
let currentWorker: any = null;
// 重启 worker 进程的函数
export async function restartWorker () {
logger.log('[NapCat] [UtilityProcess] 正在重启Worker进程...');
if (currentWorker) {
const workerPid = currentWorker.pid;
logger.log(`[NapCat] [UtilityProcess] 准备关闭Worker进程PID: ${workerPid}`);
// 发送关闭信号
currentWorker.postMessage({ type: 'shutdown' });
// 等待进程退出,最多等待 3 秒
await new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
logger.logWarn('[NapCat] [UtilityProcess] Worker进程未在 3 秒内退出,尝试强制终止');
currentWorker.kill();
resolve();
}, 3000);
currentWorker.once('exit', () => {
clearTimeout(timeout);
logger.log('[NapCat] [UtilityProcess] Worker进程已正常退出');
resolve();
});
});
// 检查进程是否真的被杀掉了
if (workerPid) {
logger.log(`[NapCat] [UtilityProcess] 检查进程 ${workerPid} 是否已终止...`);
try {
// 尝试发送信号 0 来检查进程是否存在
process.kill(workerPid, 0);
// 如果没有抛出异常,说明进程还在运行
logger.logWarn(`[NapCat] [UtilityProcess] 进程 ${workerPid} 仍在运行,强制杀掉`);
try {
// Windows 使用 taskkillUnix 使用 SIGKILL
if (process.platform === 'win32') {
const { execSync } = await import('child_process');
execSync(`taskkill /F /PID ${workerPid} /T`, { stdio: 'ignore' });
} else {
process.kill(workerPid, 'SIGKILL');
}
logger.log(`[NapCat] [UtilityProcess] 已强制终止进程 ${workerPid}`);
} catch (killError) {
logger.logError(`[NapCat] [UtilityProcess] 强制终止进程失败:`, killError);
}
} catch (e) {
// 抛出异常说明进程不存在,已经被成功杀掉
logger.log(`[NapCat] [UtilityProcess] 进程 ${workerPid} 已确认终止`);
}
}
// 进程结束后等待 3 秒再启动新进程
logger.log('[NapCat] [UtilityProcess] Worker进程已关闭等待 3 秒后启动新进程...');
await new Promise(resolve => setTimeout(resolve, 3000));
}
// 启动新的 worker 进程
await startWorker();
logger.log('[NapCat] [UtilityProcess] Worker进程重启完成');
}
async function startWorker () {
// 创建 utility 进程
// 根据实际构建产物确定文件扩展名
const workerScript = __filename.endsWith('.mjs')
? path.join(__dirname, 'napcat.mjs')
: path.join(__dirname, 'napcat.js');
const child = utilityProcess.fork(workerScript, [], {
env: {
...process.env,
NAPCAT_WORKER_PROCESS: '1',
},
stdio: 'pipe',
});
currentWorker = child;
logger.log('[NapCat] [UtilityProcess] 已创建Worker进程PID:', child.pid);
// 监听子进程标准输出 - 直接原始输出
if (child.stdout) {
child.stdout.on('data', (data: Buffer) => {
process.stdout.write(data);
});
}
// 监听子进程标准错误 - 直接原始输出
if (child.stderr) {
child.stderr.on('data', (data: Buffer) => {
process.stderr.write(data);
});
}
// 监听子进程消息
child.on('message', (msg: any) => {
logger.log('[NapCat] [UtilityProcess] 收到Worker消息:', msg);
// 处理重启请求
if (msg?.type === 'restart') {
logger.log('[NapCat] [UtilityProcess] 收到重启请求正在重启Worker进程...');
restartWorker().catch(e => {
logger.logError('[NapCat] [UtilityProcess] 重启Worker进程失败:', e);
});
}
});
// 监听子进程退出
child.on('exit', (code: number) => {
if (code !== 0) {
logger.logError(`[NapCat] [UtilityProcess] Worker进程退出退出码: ${code}`);
} else {
logger.log('[NapCat] [UtilityProcess] Worker进程正常退出');
}
// 可选:自动重启工作进程
// logger.log('[NapCat] [UtilityProcess] 正在重启Worker进程...');
// setTimeout(() => restartWorker(), 1000);
});
// 监听子进程生成
child.on('spawn', () => {
logger.log('[NapCat] [UtilityProcess] Worker进程已生成');
});
}
async function startMasterProcess () {
logger.log('[NapCat] [UtilityProcess] Master进程启动PID:', process.pid);
// 连接命名管道,用于输出子进程内容
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
// 启动 worker 进程
await startWorker();
// 优雅关闭处理
const shutdown = (signal: string) => {
logger.log(`[NapCat] [UtilityProcess] 收到${signal}信号,正在关闭...`);
if (currentWorker) {
currentWorker.postMessage({ type: 'shutdown' });
setTimeout(() => {
currentWorker.kill();
process.exit(0);
}, 1000);
} else {
process.exit(0);
}
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
}
async function startWorkerProcess () {
logger.log('[NapCat] [UtilityProcess] Worker进程启动PID:', process.pid);
// 监听来自父进程的消息
process.parentPort?.on('message', (e: { data: any; }) => {
const msg = e.data;
if (msg?.type === 'shutdown') {
logger.log('[NapCat] [UtilityProcess] 收到关闭信号,正在退出...');
process.exit(0);
}
});
// 注册重启进程函数到 WebUI在 Worker 进程中)
const { WebUiDataRuntime } = await import('@/napcat-webui-backend/src/helper/Data');
WebUiDataRuntime.setRestartProcessCall(async () => {
try {
// 向父进程发送重启请求
if (process.parentPort) {
process.parentPort.postMessage({ type: 'restart' });
return { result: true, message: '进程重启请求已发送' };
} else {
return { result: false, message: '无法与主进程通信' };
}
} catch (e) {
logger.logError('[NapCat] [UtilityProcess] 发送重启请求失败:', e);
return { result: false, message: '发送重启请求失败: ' + (e as Error).message };
}
});
// 在子进程中启动NapCat核心
await NCoreInitShell();
}
// 主入口
if (isWorkerProcess) {
// Worker进程
startWorkerProcess().catch((e: Error) => {
logger.logError('[NapCat] [UtilityProcess] Worker进程启动失败:', e);
process.exit(1);
});
} else {
// Master进程
startMasterProcess().catch((e: Error) => {
logger.logError('[NapCat] [UtilityProcess] Master进程启动失败:', e);
process.exit(1);
});
}