diff --git a/electron/config/config.default.js b/electron/config/config.default.js
index 5b866c3..a3fa67b 100644
--- a/electron/config/config.default.js
+++ b/electron/config/config.default.js
@@ -82,6 +82,18 @@ module.exports = (appInfo) => {
url: 'https://discuz.chat/' // Any web url
};
+ /**
+ * 内置java服务 默认关闭
+ */
+ config.javaServer = {
+ enable: false, // 是否启用,true时,启动程序时,会自动启动 build/extraResources/app.jar 下的 java程序
+ port: 18080, // 端口,端口被占用时随机一个端口,并通知前端修改请求地址。
+ jreVersion: 'jre1.8.0_201', // build/extraResources/目录下 jre 文件夹名称
+ // java 启动参数,该参数可以根据自己需求自由发挥
+ opt: '-server -Xms512M -Xmx512M -Xss512k -Dspring.profiles.active=prod -Dserver.port=${port} -Dlogging.file.path="${path}" ',
+ name: 'app.jar' // build/extraResources/目录下 jar 名称
+ }
+
/**
* 内置socket服务
*/
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index e904022..6b84c49 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -5,6 +5,8 @@
diff --git a/main.js b/main.js
index 0994e20..3467626 100644
--- a/main.js
+++ b/main.js
@@ -1,4 +1,6 @@
const Appliaction = require('ee-core').Appliaction;
+const getPort = require('get-port');
+const { app } = require('electron');
class Main extends Appliaction {
@@ -12,6 +14,26 @@ class Main extends Appliaction {
*/
async ready () {
// do some things
+
+ await this.createJavaPorts();
+ await this.startJava();
+ }
+
+ async createJavaPorts() {
+ if (this.config.javaServer.enable) {
+ const javaPort = await getPort({ port: this.config.javaServer.port });
+ process.env.EE_JAVA_PORT = javaPort;
+ this.config.javaServer.port = javaPort;
+ }
+ // 更新config配置
+ this.getCoreDB().setItem("config", this.config);
+ }
+
+ async startJava() {
+ this.logger.info("[main] startJava start");
+ const javaServer = require("./public/lib/javaServer");
+ javaServer.start(this);
+ this.logger.info("[main] startJava end");
}
/**
@@ -34,6 +56,16 @@ class Main extends Appliaction {
win.show();
})
}
+
+ const self = this;
+ this.electron.mainWindow.webContents.on("did-finish-load", () => {
+ const updateFrontend = require('./public/lib/updateFrontend');
+ updateFrontend.install(self);
+ });
+
+ app.on("before-quit", async () => {
+ await this.killJava();
+ });
}
/**
@@ -43,6 +75,13 @@ class Main extends Appliaction {
// do some things
}
+
+ async killJava() {
+ if (this.config.javaServer.enable) {
+ const javaServer = require("./public/lib/javaServer");
+ await javaServer.kill(this);
+ }
+ }
}
new Main();
diff --git a/package.json b/package.json
index ba381ed..1cee481 100644
--- a/package.json
+++ b/package.json
@@ -116,6 +116,7 @@
"dayjs": "^1.10.7",
"ee-core": "^1.4.0",
"electron-is": "^3.0.0",
- "lodash": "^4.17.21"
+ "lodash": "^4.17.21",
+ "table-parser": "^0.1.3"
}
}
diff --git a/public/lib/javaServer.js b/public/lib/javaServer.js
new file mode 100644
index 0000000..aa6e53f
--- /dev/null
+++ b/public/lib/javaServer.js
@@ -0,0 +1,124 @@
+"use strict";
+
+const _ = require("lodash");
+const assert = require("assert");
+const fs = require("fs");
+const os = require("os");
+const path = require("path");
+const { execSync } = require("child_process");
+const Utils = require("ee-core").Utils;
+const ps = require("./ps");
+
+function getCoreDB() {
+ const Storage = require("ee-core").Storage;
+ return Storage.JsonDB.connection("system");
+}
+
+function getJavaPort() {
+ const cdb = getCoreDB();
+ const port = cdb.getItem("config").javaServer.port;
+ return port;
+}
+
+function getJarName() {
+ const cdb = getCoreDB();
+ return cdb.getItem("config").javaServer.name;
+}
+
+function getOpt(port, path) {
+ const cdb = getCoreDB();
+ const opt = cdb.getItem("config").javaServer.opt;
+ let javaOpt = _.replace(opt, "${port}", port);
+ return _.replace(javaOpt, "${path}", path);
+}
+
+function getJreVersion() {
+ const cdb = getCoreDB();
+ return cdb.getItem("config").javaServer.jreVersion;
+}
+
+async function start(app) {
+ const options = app.config.javaServer;
+ if (!options.enable) {
+ return;
+ }
+
+ let port = process.env.EE_JAVA_PORT ? parseInt(process.env.EE_JAVA_PORT) : parseInt(getJavaPort());
+ assert(typeof port === "number", "java port required, and must be a number");
+ app.logger.info("[javaServer] java server port is:", port);
+
+ const jarName = getJarName();
+ let softwarePath = path.join(Utils.getExtraResourcesDir(), jarName);
+ app.logger.info("[javaServer] jar存放路径:", softwarePath);
+
+ const logPath = Utils.getLogDir()
+
+ // 检查程序是否存在
+ if (!fs.existsSync(softwarePath)) {
+ app.logger.info("[javaServer] java程序不存在", softwarePath);
+ }
+
+ const JAVA_OPT = getOpt(port, logPath);
+ if (os.platform() === "win32") {
+ let jrePath = path.join(
+ Utils.getExtraResourcesDir(),
+ getJreVersion(),
+ "bin",
+ "javaw.exe"
+ );
+ // 命令行字符串 并 执行
+ let cmdStr = `start ${jrePath} -jar ${JAVA_OPT} ${softwarePath}`;
+ app.logger.info("[javaServer] cmdStr:", cmdStr);
+ await execSync(cmdStr);
+ } else {
+ // 不受信任请执行: sudo spctl --master-disable
+ let jrePath = path.join(
+ Utils.getExtraResourcesDir(),
+ getJreVersion(),
+ "Contents",
+ "Home",
+ "bin",
+ "java"
+ );
+ // 命令行字符串 并 执行
+ // let cmdStr = `${jrePath} -jar ${JAVA_OPT} ${softwarePath} > /Users/tangyh/Downloads/app.log`;
+ let cmdStr = `nohup ${jrePath} -jar ${JAVA_OPT} ${softwarePath} >/dev/null 2>&1 &`;
+ app.logger.info("[javaServer] cmdStr:", cmdStr);
+ await execSync(cmdStr);
+ }
+}
+
+async function kill(app) {
+ const port = getJavaPort();
+ const jarName = getJarName();
+ app.logger.info("[javaServer] kill port: ", port);
+
+ if (os.platform() === "win32") {
+ const resultList = ps.lookup({
+ command: "java",
+ where: 'caption="javaw.exe"',
+ arguments: jarName,
+ });
+
+ app.logger.info("[javaServer] resultList:", resultList);
+ resultList.forEach((item) => {
+ ps.kill(item.pid, "SIGKILL", (err) => {
+ if (err) {
+ throw new Error(err);
+ }
+ app.logger.info("[javaServer] 已经退出后台程序: %O", item);
+ });
+ });
+
+ // const cmd = `for /f "tokens=1-5" %i in ('netstat -ano ^| findstr ":${port}"') do taskkill /F /T /PID %m`;
+ // const a = await execSync(cmd, {encoding: 'utf-8'});
+ // app.logger.info("[javaServer] kill:", a);
+ } else {
+ const cmd = `ps -ef | grep java | grep ${jarName} | grep -v grep | awk '{print $2}' | xargs kill -9`;
+ const result = await execSync(cmd);
+ app.logger.info("[javaServer] kill:", result != null ? result.toString(): '');
+ }
+}
+
+module.exports.start = start;
+module.exports.kill = kill;
diff --git a/public/lib/ps.js b/public/lib/ps.js
new file mode 100644
index 0000000..ad096c9
--- /dev/null
+++ b/public/lib/ps.js
@@ -0,0 +1,264 @@
+/**
+ * 本文件修改至 https://github.com/neekey/ps
+ * 原因是:
+ * 1. lookup 只提供了异步方式
+ * 2. lookup 性能太差
+ */
+
+var ChildProcess = require("child_process");
+var IS_WIN = process.platform === "win32";
+var TableParser = require("table-parser");
+/**
+ * End of line.
+ * Basically, the EOL should be:
+ * - windows: \r\n
+ * - *nix: \n
+ * But i'm trying to get every possibilities covered.
+ */
+var EOL = /(\r\n)|(\n\r)|\n|\r/;
+var SystemEOL = require("os").EOL;
+
+/**
+ * Execute child process
+ * @type {Function}
+ * @param {String[]} args
+ * @param {String} where
+ * @param {Function} callback
+ * @param {Object=null} callback.err
+ * @param {Object[]} callback.stdout
+ */
+var Exec = function (args, where) {
+ var spawnSync = ChildProcess.spawnSync;
+ var execSync = ChildProcess.execSync;
+
+ // on windows, if use ChildProcess.exec(`wmic process get`), the stdout will gives you nothing
+ // that's why I use `cmd` instead
+ if (IS_WIN) {
+ const cmd = `wmic process where ${where} get ProcessId,ParentProcessId,CommandLine \n`;
+ const result = execSync(cmd);
+ if (!result) {
+ throw new Error(result);
+ }
+
+ var stdout = result.toString();
+
+ var beginRow;
+ stdout = stdout.split(EOL);
+
+ // Find the line index for the titles
+ stdout.forEach(function (out, index) {
+ if (
+ out &&
+ typeof beginRow == "undefined" &&
+ out.indexOf("CommandLine") === 0
+ ) {
+ beginRow = index;
+ }
+ });
+
+ // get rid of the start (copyright) and the end (current pwd)
+ stdout.splice(stdout.length - 1, 1);
+ stdout.splice(0, beginRow);
+
+ return stdout.join(SystemEOL) || false;
+ } else {
+ if (typeof args === "string") {
+ args = args.split(/\s+/);
+ }
+ const result = spawnSync("ps", args);
+ if (result.stderr && !!result.stderr.toString()) {
+ throw new Error(result.stderr);
+ } else {
+ return result.stdout.toString();
+ }
+ }
+};
+
+/**
+ * Query Process: Focus on pid & cmd
+ * @param query
+ * @param {String|String[]} query.pid
+ * @param {String} query.command RegExp String
+ * @param {String} query.arguments RegExp String
+ * @param {String|array} query.psargs
+ * @param {String|array} query.where where 条件
+ * @param {Function} callback
+ * @param {Object=null} callback.err
+ * @param {Object[]} callback.processList
+ * @return {Object}
+ */
+
+exports.lookup = function (query) {
+ /**
+ * add 'lx' as default ps arguments, since the default ps output in linux like "ubuntu", wont include command arguments
+ */
+ var exeArgs = query.psargs || ["lx"];
+ var where = query.where || 'name="javaw.exe"';
+ var filter = {};
+ var idList;
+
+ // Lookup by PID
+ if (query.pid) {
+ if (Array.isArray(query.pid)) {
+ idList = query.pid;
+ } else {
+ idList = [query.pid];
+ }
+
+ // Cast all PIDs as Strings
+ idList = idList.map(function (v) {
+ return String(v);
+ });
+ }
+
+ if (query.command) {
+ filter["command"] = new RegExp(query.command, "i");
+ }
+
+ if (query.arguments) {
+ filter["arguments"] = new RegExp(query.arguments, "i");
+ }
+
+ if (query.ppid) {
+ filter["ppid"] = new RegExp(query.ppid);
+ }
+
+ const result = Exec(exeArgs, where);
+
+ var processList = parseGrid(result);
+ var resultList = [];
+
+ processList.forEach(function (p) {
+ var flt;
+ var type;
+ var result = true;
+
+ if (idList && idList.indexOf(String(p.pid)) < 0) {
+ return;
+ }
+
+ for (type in filter) {
+ flt = filter[type];
+ result = flt.test(p[type]) ? result : false;
+ }
+
+ if (result) {
+ resultList.push(p);
+ }
+ });
+
+ return resultList;
+};
+
+/**
+ * Kill process
+ * @param pid
+ * @param {Object|String} signal
+ * @param {String} signal.signal
+ * @param {number} signal.timeout
+ * @param next
+ */
+
+exports.kill = function (pid, signal, next) {
+ //opts are optional
+ if (arguments.length == 2 && typeof signal == "function") {
+ next = signal;
+ signal = undefined;
+ }
+
+ var checkTimeoutSeconds = (signal && signal.timeout) || 30;
+
+ if (typeof signal === "object") {
+ signal = signal.signal;
+ }
+
+ try {
+ process.kill(pid, signal);
+ } catch (e) {
+ return next && next(e);
+ }
+
+ var checkConfident = 0;
+ var checkTimeoutTimer = null;
+ var checkIsTimeout = false;
+
+ function checkKilled(finishCallback) {
+ exports.lookup({ pid: pid }, function (err, list) {
+ if (checkIsTimeout) return;
+
+ if (err) {
+ clearTimeout(checkTimeoutTimer);
+ finishCallback && finishCallback(err);
+ } else if (list.length > 0) {
+ checkConfident = checkConfident - 1 || 0;
+ checkKilled(finishCallback);
+ } else {
+ checkConfident++;
+ if (checkConfident === 5) {
+ clearTimeout(checkTimeoutTimer);
+ finishCallback && finishCallback();
+ } else {
+ checkKilled(finishCallback);
+ }
+ }
+ });
+ }
+
+ next && checkKilled(next);
+
+ checkTimeoutTimer =
+ next &&
+ setTimeout(function () {
+ checkIsTimeout = true;
+ next(new Error("Kill process timeout"));
+ }, checkTimeoutSeconds * 1000);
+};
+
+/**
+ * Parse the stdout into readable object.
+ * @param {String} output
+ */
+
+function parseGrid(output) {
+ if (!output) {
+ return [];
+ }
+ return formatOutput(TableParser.parse(output));
+}
+
+/**
+ * format the structure, extract pid, command, arguments, ppid
+ * @param data
+ * @return {Array}
+ */
+
+function formatOutput(data) {
+ var formatedData = [];
+ data.forEach(function (d) {
+ var pid =
+ (d.PID && d.PID[0]) || (d.ProcessId && d.ProcessId[0]) || undefined;
+ var cmd = d.CMD || d.CommandLine || d.COMMAND || undefined;
+ var ppid =
+ (d.PPID && d.PPID[0]) ||
+ (d.ParentProcessId && d.ParentProcessId[0]) ||
+ undefined;
+
+ if (pid && cmd) {
+ var command = cmd[0];
+ var args = "";
+
+ if (cmd.length > 1) {
+ args = cmd.slice(1);
+ }
+
+ formatedData.push({
+ pid: pid,
+ command: command,
+ arguments: args,
+ ppid: ppid,
+ });
+ }
+ });
+
+ return formatedData;
+}
diff --git a/public/lib/updateFrontend.js b/public/lib/updateFrontend.js
new file mode 100644
index 0000000..34ee1c7
--- /dev/null
+++ b/public/lib/updateFrontend.js
@@ -0,0 +1,13 @@
+/** 修改前端配置 */
+module.exports = {
+ install(eeApp) {
+ if (eeApp.config.javaServer.enable) {
+ let javaServerPrefix = `http://localhost:${eeApp.config.javaServer.port}`;
+
+ const mainWindow = eeApp.electron.mainWindow;
+ const channel = "app.javaPort";
+ mainWindow.webContents.send(channel, javaServerPrefix);
+ console.log('send');
+ }
+ },
+};