feat(steady): 实现稳态校验任务功能重构

- 添加influxdb配置支持和资源文件打包
- 实现校验任务表格组件和相关工具函数
- 重构校验工作台为任务创建对话框模式
- 实现校验详情面板支持多种异常类型展示
- 更新校验概览表格显示任务基本信息
- 优化校验查询参数和API接口定义
- 实现搜索表单组件化和过滤功能增强
This commit is contained in:
2026-06-11 10:53:02 +08:00
parent 3dff953b8d
commit 8622f25048
25 changed files with 1675 additions and 486 deletions

View File

@@ -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');

View 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')

View 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;

View File

@@ -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');

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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;