Files
CN_Tool_client/scripts/influxdb-process-manager.js
yexb 81f90ce0f2 feat(auth): 优化权限模块菜单数据处理逻辑
- 添加showMenuList、flatMenuList和breadcrumbList状态字段
- 修改getter方法直接返回缓存的状态数据
- 新增refreshDerivedMenus方法统一处理菜单衍生数据计算
- 在重置授权存储时清理新增的菜单相关状态
- 避免每次路由跳转时重复深拷贝整个菜单树结构

feat(checksquare): 完善校验功能组件和业务逻辑

- 新增测量点对话框组件用于显示监测点详细信息
- 添加校验台账工具函数解析测量点详情
- 实现任务表格删除功能包括确认提示和数据刷新
- 更新任务表格将缺失率字段替换为数据完整性字段
- 重构详情面板使用标签页展示不同类型的校验详情
- 优化摘要表格样式包括紧凑布局和危险颜色标识
- 统一详情对话框尺寸样式保持界面一致性
- 实现数据完整性字段的百分比单位去除处理

refactor(influxdb): 简化数据库启动流程移除命令行包装器

- 直接通过influxd.exe启动InfluxDB服务
- 移除对cmd.exe包装器的依赖和进程ID记录
- 保持进程管理和停止功能的完整性
2026-06-12 08:44:07 +08:00

406 lines
13 KiB
JavaScript
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.

const { spawn, execFile } = require('child_process');
const path = require('path');
const fs = require('fs');
const { resolvePackagedRuntime, resolveRuntimeStrategy } = require('./path-utils');
/**
* InfluxDB 进程管理器。
* 参照 MySQL 绿色包模式,随应用启动本地 InfluxDB退出时只清理本应用记录的进程。
*/
class InfluxDBProcessManager {
constructor(logWindowManager = null) {
const runtime = resolvePackagedRuntime();
const baseDir = runtime.baseDir;
const sourceInfluxdbPath = !runtime.isPackaged
? path.join(baseDir, 'build', 'extraResources', 'influxdb-1.7.0')
: path.join(baseDir, 'influxdb-1.7.0');
const pathStrategy = resolveRuntimeStrategy(baseDir);
this.baseDir = baseDir;
this.pathStrategy = pathStrategy;
this.sourceInfluxdbPath = sourceInfluxdbPath;
this.influxdbPath = pathStrategy.usesSafePaths
? path.join(pathStrategy.safeRuntimeRoot, 'influxdb-1.7.0')
: sourceInfluxdbPath;
this.dataPath = path.join(this.influxdbPath, 'data');
this.metaPath = path.join(this.influxdbPath, 'meta');
this.walPath = path.join(this.influxdbPath, 'wal');
this.configFile = path.join(this.influxdbPath, 'influxdb.runtime.conf');
this.processRecordFile = path.join(this.influxdbPath, '.running-process.json');
this.logWindowManager = logWindowManager;
this.influxdbProcess = null;
this.currentPort = null;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
log(type, message) {
console.log(`[InfluxDB] ${message}`);
if (this.logWindowManager) {
this.logWindowManager.addLog(type, `[InfluxDB] ${message}`);
}
}
normalizeComparablePath(target = '') {
return String(target).replace(/"/g, '').replace(/\//g, '\\').toLowerCase();
}
copyDirectorySync(sourceDir, targetDir) {
if (!fs.existsSync(sourceDir)) {
return;
}
fs.mkdirSync(targetDir, { recursive: true });
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
entries.forEach((entry) => {
const sourcePath = path.join(sourceDir, entry.name);
const targetPath = path.join(targetDir, entry.name);
if (entry.isDirectory()) {
this.copyDirectorySync(sourcePath, targetPath);
return;
}
let shouldCopy = !fs.existsSync(targetPath);
if (!shouldCopy) {
const sourceStat = fs.statSync(sourcePath);
const targetStat = fs.statSync(targetPath);
shouldCopy = sourceStat.size !== targetStat.size || sourceStat.mtimeMs > targetStat.mtimeMs;
}
if (shouldCopy) {
fs.copyFileSync(sourcePath, targetPath);
}
});
}
ensureRuntimeEnvironment() {
if (!fs.existsSync(this.sourceInfluxdbPath)) {
throw new Error(`InfluxDB 目录不存在: ${this.sourceInfluxdbPath}`);
}
if (this.pathStrategy.usesSafePaths) {
this.log('system', '检测到应用路径包含非 ASCII 字符,启用 InfluxDB 英文安全运行目录');
this.log('system', `InfluxDB 源目录: ${this.sourceInfluxdbPath}`);
this.log('system', `InfluxDB 运行目录: ${this.influxdbPath}`);
this.copyDirectorySync(this.sourceInfluxdbPath, this.influxdbPath);
}
fs.mkdirSync(this.dataPath, { recursive: true });
fs.mkdirSync(this.metaPath, { recursive: true });
fs.mkdirSync(this.walPath, { recursive: true });
}
saveProcessRecord(record = {}) {
try {
fs.mkdirSync(path.dirname(this.processRecordFile), { recursive: true });
fs.writeFileSync(this.processRecordFile, JSON.stringify(record, null, 2), 'utf-8');
} catch (error) {
this.log('warn', `写入 InfluxDB 进程标记失败: ${error.message}`);
}
}
getProcessRecord() {
try {
if (!fs.existsSync(this.processRecordFile)) {
return null;
}
return JSON.parse(fs.readFileSync(this.processRecordFile, 'utf-8'));
} catch (error) {
this.log('warn', `读取 InfluxDB 进程标记失败: ${error.message}`);
return null;
}
}
clearProcessRecord() {
try {
if (fs.existsSync(this.processRecordFile)) {
fs.unlinkSync(this.processRecordFile);
}
} catch (error) {
this.log('warn', `清理 InfluxDB 进程标记失败: ${error.message}`);
}
}
execFileCommand(command, args = []) {
return new Promise((resolve, reject) => {
execFile(command, args, { encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
if (error) {
const err = new Error(error.message || stderr || stdout || 'Command execution failed');
err.code = error.code;
err.stdout = stdout;
err.stderr = stderr;
reject(err);
} else {
resolve({ stdout, stderr });
}
});
});
}
async getProcessInfoByPid(pid) {
if (!pid) {
return null;
}
try {
const script = `$proc = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}"; if ($proc) { [PSCustomObject]@{ executablePath = $proc.ExecutablePath; commandLine = $proc.CommandLine; name = $proc.Name } | ConvertTo-Json -Compress }`;
const { stdout } = await this.execFileCommand('powershell.exe', ['-NoProfile', '-Command', script]);
const raw = (stdout || '').trim();
return raw ? JSON.parse(raw) : null;
} catch (error) {
return null;
}
}
async isOwnInfluxDBProcess(pid, record = this.getProcessRecord()) {
const processInfo = await this.getProcessInfoByPid(pid);
if (!processInfo) {
return false;
}
const expectedExe = this.normalizeComparablePath(path.join(this.influxdbPath, 'influxd.exe'));
const expectedConfig = this.normalizeComparablePath((record && record.configFile) || this.configFile);
const executablePath = this.normalizeComparablePath(processInfo.executablePath);
const commandLine = this.normalizeComparablePath(processInfo.commandLine);
return (
executablePath === expectedExe &&
(!expectedConfig || commandLine.includes(expectedConfig))
);
}
async waitForProcessExit(pid, timeoutMs = 3000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const processInfo = await this.getProcessInfoByPid(pid);
if (!processInfo) {
return true;
}
await this.sleep(200);
}
return false;
}
async terminateTrackedProcess(record = this.getProcessRecord(), reason = '正在停止 InfluxDB 进程...') {
const pid = this.influxdbProcess?.pid || record?.pid;
const port = this.currentPort || record?.port;
if (!pid) {
this.log('system', '未找到可清理的 InfluxDB 进程标记');
this.clearProcessRecord();
return false;
}
const isOwnProcess = await this.isOwnInfluxDBProcess(pid, record);
if (!isOwnProcess) {
this.log('warn', `PID ${pid} 不是当前应用的 InfluxDB 进程,跳过清理`);
this.clearProcessRecord();
if (this.influxdbProcess && this.influxdbProcess.pid === pid) {
this.influxdbProcess = null;
}
return false;
}
this.log('system', `${reason} PID=${pid}${port ? `, 端口=${port}` : ''}`);
try {
await this.execFileCommand('taskkill.exe', ['/F', '/T', '/PID', String(pid)]);
await this.waitForProcessExit(pid, 3000);
this.log('success', 'InfluxDB 进程已停止');
} finally {
this.clearProcessRecord();
if (this.influxdbProcess && this.influxdbProcess.pid === pid) {
this.influxdbProcess = null;
}
this.currentPort = null;
}
return true;
}
async checkAndKillOrphanProcess() {
const record = this.getProcessRecord();
if (!record || !record.pid) {
return;
}
const isOwnProcess = await this.isOwnInfluxDBProcess(record.pid, record);
if (!isOwnProcess) {
this.log('warn', `检测到失效的 InfluxDB 进程标记 PID=${record.pid},已跳过清理并移除标记`);
this.clearProcessRecord();
return;
}
this.log('warn', `检测到当前应用上次遗留的 InfluxDB 进程 PID=${record.pid},开始定向清理...`);
await this.terminateTrackedProcess(record, '正在清理当前应用遗留的 InfluxDB 进程...');
}
generateConfig(port) {
const normalizePath = target => target.replace(/\\/g, '/');
const config = `reporting-disabled = true
bind-address = "127.0.0.1:8088"
[meta]
dir = "${normalizePath(this.metaPath)}"
[data]
dir = "${normalizePath(this.dataPath)}"
wal-dir = "${normalizePath(this.walPath)}"
[http]
enabled = true
bind-address = "127.0.0.1:${port}"
auth-enabled = false
log-enabled = true
`;
fs.writeFileSync(this.configFile, config, 'utf-8');
this.log('system', `生成 InfluxDB 配置文件,端口: ${port}`);
}
async startInfluxDBProcess(port) {
return new Promise((resolve, reject) => {
const influxd = path.join(this.influxdbPath, 'influxd.exe');
this.log('system', '正在启动 InfluxDB 进程...');
this.log('system', `可执行文件: ${influxd}`);
this.log('system', `配置文件: ${this.configFile}`);
if (!fs.existsSync(influxd)) {
return reject(new Error(`influxd.exe 不存在: ${influxd}`));
}
if (!fs.existsSync(this.configFile)) {
return reject(new Error(`配置文件不存在: ${this.configFile}`));
}
// 直接启动 influxd.exe进程标记中的 PID 才能精确对应实际 InfluxDB 进程。
const influxdbProcess = spawn(influxd, ['-config', this.configFile], {
cwd: this.influxdbPath,
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true
});
this.influxdbProcess = influxdbProcess;
this.currentPort = port;
this.saveProcessRecord({
pid: influxdbProcess.pid,
port,
executablePath: influxd,
configFile: this.configFile,
influxdbPath: this.influxdbPath,
createdAt: new Date().toISOString()
});
let startupComplete = false;
let startupTimeout = null;
const completeStartup = () => {
if (!startupComplete) {
startupComplete = true;
if (startupTimeout) clearTimeout(startupTimeout);
this.log('success', 'InfluxDB 进程已就绪');
resolve(true);
}
};
const handleOutput = (data) => {
const msg = data.toString().trim();
if (!msg) {
return;
}
if (msg.includes('Listening on HTTP') || msg.includes('Listening for signals')) {
completeStartup();
}
if (!msg.includes('[I]') || msg.includes('Listening') || msg.includes('error')) {
this.log('system', `[influxd] ${msg}`);
}
};
influxdbProcess.stdout.on('data', handleOutput);
influxdbProcess.stderr.on('data', handleOutput);
influxdbProcess.on('error', (err) => {
this.log('error', `InfluxDB 进程启动失败: ${err.message}`);
this.clearProcessRecord();
this.influxdbProcess = null;
reject(err);
});
influxdbProcess.on('exit', (code, signal) => {
this.log('system', `InfluxDB 进程退出,代码: ${code}, 信号: ${signal}`);
const record = this.getProcessRecord();
if (record && record.pid === influxdbProcess.pid) {
this.clearProcessRecord();
}
this.influxdbProcess = null;
this.currentPort = null;
if (!startupComplete) {
reject(new Error(`InfluxDB 进程异常退出,代码: ${code}`));
}
});
startupTimeout = setTimeout(completeStartup, 15000);
});
}
async stopInfluxDBProcess() {
const record = this.getProcessRecord();
if (!this.influxdbProcess && !record) {
this.log('system', 'InfluxDB 进程未运行');
return;
}
await this.terminateTrackedProcess(record, '正在停止当前应用自己的 InfluxDB 进程...');
}
isInfluxDBRunning() {
return this.influxdbProcess !== null && !this.influxdbProcess.killed;
}
async ensureServiceRunning(findAvailablePort, waitForPort) {
this.ensureRuntimeEnvironment();
this.log('system', '开始 InfluxDB 进程检查流程(进程模式)');
this.log('system', `路径策略: ${this.pathStrategy.usesSafePaths ? '英文安全路径模式' : '应用目录直启模式'}`);
this.log('system', `InfluxDB 路径: ${this.influxdbPath}`);
this.log('system', `配置文件: ${this.configFile}`);
if (this.isInfluxDBRunning()) {
const port = this.currentPort || 18086;
this.log('success', `InfluxDB 进程已在运行,端口: ${port}`);
return port;
}
await this.checkAndKillOrphanProcess();
const port = await findAvailablePort(18086, 100);
if (port === -1) {
throw new Error('无法找到可用的 InfluxDB 端口18086-18185 全部被占用)');
}
this.log('system', `找到可用端口: ${port}`);
this.generateConfig(port);
await this.startInfluxDBProcess(port);
this.log('system', `等待端口 ${port} 就绪...`);
const portReady = await waitForPort(port, 30000);
if (!portReady) {
throw new Error(`InfluxDB 端口 ${port} 未能在 30 秒内就绪`);
}
this.log('success', `InfluxDB 进程启动成功,端口: ${port}`);
return port;
}
}
module.exports = InfluxDBProcessManager;