提交额外资源

This commit is contained in:
2025-10-16 20:18:20 +08:00
parent 9b02fea0c0
commit 12af3c74f2
9 changed files with 1600 additions and 0 deletions

321
scripts/java-runner.js Normal file
View File

@@ -0,0 +1,321 @@
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<string>} 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<string>} args - Java 程序参数
* @param {Object} options - spawn 选项
* @returns {Promise<number>} 退出代码
*/
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<string>} 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;