const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); /** * Java 运行器 - 用于调用便携式 JRE 运行 Java 程序 */ class JavaRunner { constructor() { // 在开发与打包后均可解析到应用根目录下的 jre 目录 // 开发环境:项目根目录 // 打包后:应用根目录(win-unpacked) const isDev = !process.resourcesPath; const baseDir = isDev ? path.join(__dirname, '..') : path.dirname(process.resourcesPath); this.jrePath = path.join(baseDir, 'jre'); this.binPath = path.join(this.jrePath, 'bin'); this.javaExe = path.join(this.binPath, 'java.exe'); } /** * 检查 JRE 是否存在 */ isJREAvailable() { return fs.existsSync(this.javaExe); } /** * 获取 Java 版本 */ getVersion() { return new Promise((resolve, reject) => { if (!this.isJREAvailable()) { reject(new Error('JRE not found at: ' + this.javaExe)); return; } const versionProcess = spawn(this.javaExe, ['-version'], { stdio: ['ignore', 'pipe', 'pipe'] }); let output = ''; let errorOutput = ''; versionProcess.stdout.on('data', (data) => { output += data.toString(); }); versionProcess.stderr.on('data', (data) => { errorOutput += data.toString(); }); versionProcess.on('close', (code) => { if (code === 0 || errorOutput.includes('version')) { // Java -version 输出到 stderr const versionInfo = (output + errorOutput).trim(); resolve(versionInfo); } else { reject(new Error('Failed to get Java version')); } }); }); } /** * 运行 JAR 文件 * @param {string} jarPath - JAR 文件的绝对路径 * @param {Array} args - Java 程序参数 * @param {Object} options - spawn 选项 * @returns {ChildProcess} */ runJar(jarPath, args = [], options = {}) { if (!this.isJREAvailable()) { throw new Error('JRE not found at: ' + this.javaExe); } if (!fs.existsSync(jarPath)) { throw new Error('JAR file not found at: ' + jarPath); } const javaArgs = ['-jar', jarPath, ...args]; const defaultOptions = { cwd: path.dirname(jarPath), stdio: 'inherit' }; const mergedOptions = { ...defaultOptions, ...options }; console.log('Running Java:', this.javaExe, javaArgs.join(' ')); return spawn(this.javaExe, javaArgs, mergedOptions); } /** * 运行 JAR 文件并等待完成 * @param {string} jarPath - JAR 文件的绝对路径 * @param {Array} args - Java 程序参数 * @param {Object} options - spawn 选项 * @returns {Promise} 退出代码 */ runJarAsync(jarPath, args = [], options = {}) { return new Promise((resolve, reject) => { try { const process = this.runJar(jarPath, args, options); process.on('close', (code) => { if (code === 0) { resolve(code); } else { reject(new Error(`Java process exited with code ${code}`)); } }); process.on('error', (error) => { reject(error); }); } catch (error) { reject(error); } }); } /** * 运行 Java 类 * @param {string} className - Java 类名(包含包名) * @param {string} classPath - classpath 路径 * @param {Array} args - 程序参数 * @param {Object} options - spawn 选项 * @returns {ChildProcess} */ runClass(className, classPath, args = [], options = {}) { if (!this.isJREAvailable()) { throw new Error('JRE not found at: ' + this.javaExe); } const javaArgs = ['-cp', classPath, className, ...args]; const defaultOptions = { stdio: 'inherit' }; const mergedOptions = { ...defaultOptions, ...options }; console.log('Running Java:', this.javaExe, javaArgs.join(' ')); return spawn(this.javaExe, javaArgs, mergedOptions); } /** * 运行 Spring Boot JAR 文件 * @param {string} jarPath - JAR 文件的绝对路径 * @param {string} configPath - 配置文件路径 * @param {Object} options - 启动选项(需包含 javaPort) * @returns {ChildProcess} */ runSpringBoot(jarPath, configPath, options = {}) { if (!this.isJREAvailable()) { throw new Error('JRE not found at: ' + this.javaExe); } if (!fs.existsSync(jarPath)) { throw new Error('JAR file not found at: ' + jarPath); } const javaArgs = [ '-Dfile.encoding=UTF-8', // 设置文件编码为UTF-8,解决中文乱码 '-Duser.language=zh', // 设置语言为中文 '-Duser.region=CN', // 设置地区为中国 '-jar', jarPath, `--spring.config.location=${configPath}` ]; const defaultOptions = { cwd: path.dirname(jarPath), stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, JAVA_TOOL_OPTIONS: '-Dfile.encoding=UTF-8' // 额外确保UTF-8编码 } }; const mergedOptions = { ...defaultOptions, ...options }; console.log('Running Spring Boot:', this.javaExe, javaArgs.join(' ')); const javaProcess = spawn(this.javaExe, javaArgs, mergedOptions); // 记录PID和端口用于后续停止 this.springBootProcess = javaProcess; this.currentJavaPort = options.javaPort; // 将Java端口记录到文件,供手动清理脚本使用 if (options.javaPort) { this.recordJavaPort(options.javaPort); } // 进程退出时清理端口记录 javaProcess.on('close', () => { this.cleanupJavaPortFile(); }); return javaProcess; } /** * 停止 Spring Boot 应用 */ stopSpringBoot() { return new Promise((resolve) => { if (this.springBootProcess && !this.springBootProcess.killed) { // 设置3秒超时,如果进程没有正常退出,强制kill const timeout = setTimeout(() => { console.log('[Java] Force killing Spring Boot process'); try { this.springBootProcess.kill('SIGKILL'); } catch (e) { console.error('[Java] Error force killing:', e); } // 清理端口记录文件 this.cleanupJavaPortFile(); resolve(); }, 3000); this.springBootProcess.on('close', () => { clearTimeout(timeout); console.log('[Java] Spring Boot application stopped gracefully'); // 清理端口记录文件 this.cleanupJavaPortFile(); resolve(); }); // 先尝试优雅关闭 console.log('[Java] Sending SIGTERM to Spring Boot'); this.springBootProcess.kill('SIGTERM'); } else { // 即使没有进程引用,也尝试清理端口记录文件 this.cleanupJavaPortFile(); resolve(); } }); } /** * 记录Java端口到文件 */ recordJavaPort(port) { try { const isDev = !process.resourcesPath; const baseDir = isDev ? path.join(__dirname, '..') : path.dirname(process.resourcesPath); const javaDir = path.join(baseDir, 'java'); const portFilePath = path.join(javaDir, '.running-port'); fs.writeFileSync(portFilePath, port.toString(), 'utf-8'); console.log(`[Java] Port ${port} recorded to ${portFilePath}`); } catch (error) { console.warn('[Java] Failed to record port:', error); } } /** * 清理Java端口记录文件 */ cleanupJavaPortFile() { try { const isDev = !process.resourcesPath; const baseDir = isDev ? path.join(__dirname, '..') : path.dirname(process.resourcesPath); const javaDir = path.join(baseDir, 'java'); const portFilePath = path.join(javaDir, '.running-port'); if (fs.existsSync(portFilePath)) { fs.unlinkSync(portFilePath); console.log('[Java] Port record file cleaned up'); } } catch (error) { console.warn('[Java] Failed to cleanup port record:', error); } } /** * 获取记录的Java运行端口 */ getRecordedJavaPort() { try { const isDev = !process.resourcesPath; const baseDir = isDev ? path.join(__dirname, '..') : path.dirname(process.resourcesPath); const javaDir = path.join(baseDir, 'java'); const portFilePath = path.join(javaDir, '.running-port'); if (fs.existsSync(portFilePath)) { const port = fs.readFileSync(portFilePath, 'utf-8').trim(); return parseInt(port); } } catch (error) { console.warn('[Java] Failed to read port record:', error); } return null; } /** * 获取 JRE 路径信息 */ getPathInfo() { return { jrePath: this.jrePath, binPath: this.binPath, javaExe: this.javaExe, available: this.isJREAvailable() }; } } module.exports = JavaRunner;