feat(steady): 实现稳态校验任务功能重构
- 添加influxdb配置支持和资源文件打包 - 实现校验任务表格组件和相关工具函数 - 重构校验工作台为任务创建对话框模式 - 实现校验详情面板支持多种异常类型展示 - 更新校验概览表格显示任务基本信息 - 优化校验查询参数和API接口定义 - 实现搜索表单组件化和过滤功能增强
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { resolveRuntimeStrategy } = require('./path-utils');
|
||||
const { resolvePackagedRuntime, resolveRuntimeStrategy } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* 配置文件生成器
|
||||
@@ -10,17 +10,15 @@ class ConfigGenerator {
|
||||
constructor() {
|
||||
// 开发环境:项目根目录
|
||||
// 打包后:应用根目录(最终交付目录)
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: path.dirname(process.resourcesPath);
|
||||
const runtime = resolvePackagedRuntime();
|
||||
const baseDir = runtime.baseDir;
|
||||
const pathStrategy = resolveRuntimeStrategy(baseDir);
|
||||
|
||||
// 开发环境:build/extraResources/java
|
||||
// 打包后:resources/extraResources/java
|
||||
this.javaPath = isDev
|
||||
this.javaPath = !runtime.isPackaged
|
||||
? path.join(baseDir, 'build', 'extraResources', 'java')
|
||||
: path.join(process.resourcesPath, 'extraResources', 'java');
|
||||
: path.join(runtime.resourcesPath, 'extraResources', 'java');
|
||||
this.templatePath = path.join(this.javaPath, 'application.yml.template');
|
||||
this.configPath = path.join(this.javaPath, 'application.yml');
|
||||
this.pathStrategy = pathStrategy;
|
||||
@@ -48,6 +46,7 @@ class ConfigGenerator {
|
||||
* 生成配置文件
|
||||
* @param {object} options - 配置选项
|
||||
* @param {number} options.mysqlPort - MySQL 端口
|
||||
* @param {number} options.influxdbPort - InfluxDB 端口
|
||||
* @param {number} options.javaPort - Java 应用端口
|
||||
* @param {number} options.websocketPort - WebSocket 端口
|
||||
* @param {string} options.mysqlPassword - MySQL 密码
|
||||
@@ -77,6 +76,11 @@ class ConfigGenerator {
|
||||
template = template.replace(/\{\{MYSQL_PORT\}\}/g, options.mysqlPort);
|
||||
template = template.replace(/localhost:3306/g, `localhost:${options.mysqlPort}`);
|
||||
}
|
||||
if (options.influxdbPort) {
|
||||
template = template.replace(/\{\{INFLUXDB_PORT\}\}/g, options.influxdbPort);
|
||||
template = template.replace(/127\.0\.0\.1:18086/g, `127.0.0.1:${options.influxdbPort}`);
|
||||
template = template.replace(/localhost:18086/g, `localhost:${options.influxdbPort}`);
|
||||
}
|
||||
if (options.javaPort) {
|
||||
template = template.replace(/port:\s*18092/g, `port: ${options.javaPort}`);
|
||||
}
|
||||
@@ -96,6 +100,7 @@ class ConfigGenerator {
|
||||
console.log('[ConfigGenerator] Path mode:', this.pathStrategy.usesSafePaths ? 'safe-data-root' : 'app-local-data');
|
||||
console.log('[ConfigGenerator] Data path:', this.dataPath);
|
||||
console.log('[ConfigGenerator] MySQL port:', options.mysqlPort || 3306);
|
||||
console.log('[ConfigGenerator] InfluxDB port:', options.influxdbPort || 18086);
|
||||
console.log('[ConfigGenerator] MySQL password:', options.mysqlPassword || 'njcnpqs');
|
||||
console.log('[ConfigGenerator] Java port:', options.javaPort || 18093);
|
||||
console.log('[ConfigGenerator] WebSocket port:', options.websocketPort || 7778);
|
||||
@@ -104,6 +109,7 @@ class ConfigGenerator {
|
||||
configPath: this.configPath,
|
||||
dataPath: this.dataPath,
|
||||
mysqlPort: options.mysqlPort || 3306,
|
||||
influxdbPort: options.influxdbPort || 18086,
|
||||
javaPort: options.javaPort || 18093,
|
||||
websocketPort: options.websocketPort || 7778
|
||||
});
|
||||
@@ -142,15 +148,13 @@ class ConfigGenerator {
|
||||
*/
|
||||
copyBuiltInTemplates() {
|
||||
try {
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: path.dirname(process.resourcesPath);
|
||||
const runtime = resolvePackagedRuntime();
|
||||
const baseDir = runtime.baseDir;
|
||||
|
||||
// 内置模板源路径
|
||||
const templateSource = isDev
|
||||
const templateSource = !runtime.isPackaged
|
||||
? path.join(baseDir, 'build', 'extraResources', 'templates')
|
||||
: path.join(process.resourcesPath, 'extraResources', 'templates');
|
||||
: path.join(runtime.resourcesPath, 'extraResources', 'templates');
|
||||
|
||||
// 目标路径:用户数据目录/template/
|
||||
const templateDest = path.join(this.dataPath, 'template');
|
||||
|
||||
94
scripts/contracts/check-influxdb-startup-contract.mjs
Normal file
94
scripts/contracts/check-influxdb-startup-contract.mjs
Normal file
@@ -0,0 +1,94 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..', '..')
|
||||
|
||||
const read = relativePath => fs.readFileSync(path.join(rootDir, relativePath), 'utf-8')
|
||||
|
||||
const files = {
|
||||
manager: 'scripts/influxdb-process-manager.js',
|
||||
mysqlManager: 'scripts/mysql-process-manager.js',
|
||||
javaRunner: 'scripts/java-runner.js',
|
||||
lifecycle: 'electron/preload/lifecycle.js',
|
||||
configGenerator: 'scripts/config-generator.js',
|
||||
javaTemplate: 'build/extraResources/java/application.yml.template',
|
||||
influxdbBat: 'build/extraResources/influxdb-1.7.0/start-influxdb.bat',
|
||||
startup: 'scripts/startup-manager.js',
|
||||
builder: 'cmd/builder.json'
|
||||
}
|
||||
|
||||
const failures = []
|
||||
|
||||
if (!fs.existsSync(path.join(rootDir, files.manager))) {
|
||||
failures.push('scripts/influxdb-process-manager.js should exist')
|
||||
} else {
|
||||
const source = read(files.manager)
|
||||
const checks = [
|
||||
['manager exports InfluxDBProcessManager', /module\.exports\s*=\s*InfluxDBProcessManager/.test(source)],
|
||||
['manager starts InfluxDB through cmd.exe and start-influxdb.bat', /cmd\.exe/.test(source) && /start-influxdb\.bat/.test(source) && /spawn\(/.test(source)],
|
||||
['manager records process ownership', /\.running-process\.json/.test(source)],
|
||||
['manager can stop tracked process', /stopInfluxDBProcess/.test(source) && /terminateTrackedProcess/.test(source)]
|
||||
]
|
||||
checks.forEach(([message, pass]) => {
|
||||
if (!pass) failures.push(message)
|
||||
})
|
||||
}
|
||||
|
||||
const lifecycleSource = read(files.lifecycle)
|
||||
const configGeneratorSource = read(files.configGenerator)
|
||||
const runtimePathSources = [
|
||||
['InfluxDB process manager', read(files.manager)],
|
||||
['MySQL process manager', read(files.mysqlManager)],
|
||||
['Java runner', read(files.javaRunner)],
|
||||
['config generator', configGeneratorSource]
|
||||
]
|
||||
const javaTemplateSource = read(files.javaTemplate)
|
||||
const startupSource = read(files.startup)
|
||||
const builderSource = read(files.builder)
|
||||
const influxdbBatSource = read(files.influxdbBat)
|
||||
|
||||
const lifecycleChecks = [
|
||||
['lifecycle loads InfluxDBProcessManager', /InfluxDBProcessManager/.test(lifecycleSource)],
|
||||
['lifecycle stores influxdbProcessManager', /this\.influxdbProcessManager/.test(lifecycleSource)],
|
||||
[
|
||||
'lifecycle starts InfluxDB after MySQL before Java port detection',
|
||||
/ensureServiceRunning\([\s\S]*?this\.influxdbPort\s*=\s*await\s*this\.influxdbProcessManager\.ensureServiceRunning[\s\S]*?findAvailablePort\(18093/.test(
|
||||
lifecycleSource
|
||||
)
|
||||
],
|
||||
['lifecycle passes influxdbPort to config generator', /influxdbPort:\s*this\.influxdbPort/.test(lifecycleSource)],
|
||||
['lifecycle stops InfluxDB during cleanup', /stopInfluxDBProcess/.test(lifecycleSource)]
|
||||
]
|
||||
|
||||
const startupChecks = [
|
||||
['startup has InfluxDB port step', /check-influxdb-port/.test(startupSource)],
|
||||
['startup has InfluxDB wait step', /wait-influxdb/.test(startupSource)]
|
||||
]
|
||||
|
||||
const packageChecks = [
|
||||
['windows package includes InfluxDB resources', /build\/extraResources\/influxdb-1\.7\.0/.test(builderSource)]
|
||||
]
|
||||
|
||||
const configChecks = [
|
||||
['config generator replaces InfluxDB port placeholder', /INFLUXDB_PORT/.test(configGeneratorSource)],
|
||||
['java template defines steady InfluxDB url placeholder', /steady:[\s\S]*influxdb:[\s\S]*url:\s*http:\/\/127\.0\.0\.1:\{\{INFLUXDB_PORT\}\}/.test(javaTemplateSource)],
|
||||
['InfluxDB bat accepts runtime config path argument', /%~1/.test(influxdbBatSource) && /influxd\.exe\s+-config\s+/.test(influxdbBatSource)]
|
||||
]
|
||||
|
||||
const runtimePathChecks = runtimePathSources.map(([name, source]) => [
|
||||
`${name} should not treat any process.resourcesPath as packaged runtime`,
|
||||
!/!\s*process\.resourcesPath/.test(source)
|
||||
])
|
||||
|
||||
;[...lifecycleChecks, ...startupChecks, ...configChecks, ...runtimePathChecks, ...packageChecks].forEach(([message, pass]) => {
|
||||
if (!pass) failures.push(message)
|
||||
})
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(`InfluxDB startup contract failed:\n- ${failures.join('\n- ')}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('InfluxDB startup contract passed')
|
||||
410
scripts/influxdb-process-manager.js
Normal file
410
scripts/influxdb-process-manager.js
Normal file
@@ -0,0 +1,410 @@
|
||||
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 expectedBat = this.normalizeComparablePath(path.join(this.influxdbPath, 'start-influxdb.bat'));
|
||||
const expectedConfig = this.normalizeComparablePath((record && record.configFile) || this.configFile);
|
||||
const executablePath = this.normalizeComparablePath(processInfo.executablePath);
|
||||
const commandLine = this.normalizeComparablePath(processInfo.commandLine);
|
||||
|
||||
return (
|
||||
(executablePath === expectedExe || commandLine.includes(expectedBat)) &&
|
||||
(!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');
|
||||
const startBat = path.join(this.influxdbPath, 'start-influxdb.bat');
|
||||
|
||||
this.log('system', '正在启动 InfluxDB 进程...');
|
||||
this.log('system', `可执行文件: ${influxd}`);
|
||||
this.log('system', `启动脚本: ${startBat}`);
|
||||
this.log('system', `配置文件: ${this.configFile}`);
|
||||
|
||||
if (!fs.existsSync(influxd)) {
|
||||
return reject(new Error(`influxd.exe 不存在: ${influxd}`));
|
||||
}
|
||||
if (!fs.existsSync(startBat)) {
|
||||
return reject(new Error(`start-influxdb.bat 不存在: ${startBat}`));
|
||||
}
|
||||
if (!fs.existsSync(this.configFile)) {
|
||||
return reject(new Error(`配置文件不存在: ${this.configFile}`));
|
||||
}
|
||||
|
||||
const influxdbProcess = spawn('cmd.exe', ['/d', '/s', '/c', 'call', startBat, 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;
|
||||
@@ -1,6 +1,7 @@
|
||||
const { spawn, exec, execFile } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { resolvePackagedRuntime } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* Java 运行器 - 用于调用便携式 JRE 运行 Java 程序
|
||||
@@ -10,10 +11,7 @@ class JavaRunner {
|
||||
// 在开发与打包后均可解析到应用根目录下的 jre 目录
|
||||
// 开发环境:项目根目录
|
||||
// 打包后:应用根目录(最终交付目录)
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: path.dirname(process.resourcesPath);
|
||||
const { baseDir } = resolvePackagedRuntime();
|
||||
this.baseDir = baseDir;
|
||||
this.jrePath = path.join(baseDir, 'jre');
|
||||
this.binPath = path.join(this.jrePath, 'bin');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { spawn, exec, execFile } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { resolveRuntimeStrategy } = require('./path-utils');
|
||||
const { resolvePackagedRuntime, resolveRuntimeStrategy } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* MySQL 进程管理器
|
||||
@@ -9,10 +9,7 @@ const { resolveRuntimeStrategy } = require('./path-utils');
|
||||
*/
|
||||
class MySQLProcessManager {
|
||||
constructor(logWindowManager = null) {
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: path.dirname(process.resourcesPath);
|
||||
const { baseDir } = resolvePackagedRuntime();
|
||||
const pathStrategy = resolveRuntimeStrategy(baseDir);
|
||||
|
||||
this.baseDir = baseDir;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* 判断路径中是否包含非 ASCII 字符。
|
||||
@@ -16,6 +17,29 @@ function getDriveRoot(targetPath = '') {
|
||||
return path.parse(resolvedPath).root;
|
||||
}
|
||||
|
||||
function resolvePackagedRuntime() {
|
||||
const resourcesPath = process.resourcesPath;
|
||||
const packagedBaseDir = resourcesPath ? path.dirname(resourcesPath) : '';
|
||||
const hasPackagedResources = resourcesPath
|
||||
&& fs.existsSync(path.join(resourcesPath, 'extraResources'))
|
||||
&& fs.existsSync(path.join(packagedBaseDir, 'mysql'));
|
||||
|
||||
if (hasPackagedResources) {
|
||||
return {
|
||||
isPackaged: true,
|
||||
baseDir: packagedBaseDir,
|
||||
resourcesPath
|
||||
};
|
||||
}
|
||||
|
||||
const baseDir = path.join(__dirname, '..');
|
||||
return {
|
||||
isPackaged: false,
|
||||
baseDir,
|
||||
resourcesPath: path.join(baseDir, 'build', 'extraResources')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析运行期路径策略。
|
||||
* - 安全路径:继续直接使用应用目录
|
||||
@@ -38,5 +62,6 @@ function resolveRuntimeStrategy(baseDir) {
|
||||
module.exports = {
|
||||
hasNonAscii,
|
||||
getDriveRoot,
|
||||
resolvePackagedRuntime,
|
||||
resolveRuntimeStrategy
|
||||
};
|
||||
|
||||
@@ -10,12 +10,14 @@ class StartupManager {
|
||||
this.loadingWindow = null;
|
||||
this.steps = [
|
||||
{ id: 'init', label: '正在初始化应用...', progress: 0 },
|
||||
{ id: 'check-mysql-port', label: '正在检查MySQL服务...', progress: 20 },
|
||||
{ id: 'wait-mysql', label: '确保MySQL服务运行...', progress: 40 },
|
||||
{ id: 'check-java-port', label: '正在检测后端服务端口...', progress: 60 },
|
||||
{ id: 'generate-config', label: '正在生成配置文件...', progress: 70 },
|
||||
{ id: 'start-java', label: '正在启动后端服务...', progress: 80 },
|
||||
{ id: 'wait-java', label: '等待后端服务就绪...', progress: 90 },
|
||||
{ id: 'check-mysql-port', label: '正在检查MySQL服务...', progress: 15 },
|
||||
{ id: 'wait-mysql', label: '确保MySQL服务运行...', progress: 30 },
|
||||
{ id: 'check-influxdb-port', label: '正在检查InfluxDB服务...', progress: 45 },
|
||||
{ id: 'wait-influxdb', label: '确保InfluxDB服务运行...', progress: 55 },
|
||||
{ id: 'check-java-port', label: '正在检测后端服务端口...', progress: 65 },
|
||||
{ id: 'generate-config', label: '正在生成配置文件...', progress: 75 },
|
||||
{ id: 'start-java', label: '正在启动后端服务...', progress: 85 },
|
||||
{ id: 'wait-java', label: '等待后端服务就绪...', progress: 95 },
|
||||
{ id: 'done', label: '启动完成!', progress: 100 }
|
||||
];
|
||||
this.currentStep = 0;
|
||||
|
||||
Reference in New Issue
Block a user