Sfoglia il codice sorgente

feat: 新增启动程序的同时启动额外资源目录中的jar

仅测试了window和mac系统

1. ee 框架ready后,通过命令启动jar(优先试用配置的端口,被占用时则随机端口)
2. did-finish-load 事件通知 fronend 存储java程序的访问地址
3. 程序退出时,通过命令行kill掉程序
4. 提供前端调用 java 接口的示例
zuihou 3 anni fa
parent
commit
d872713d71

+ 12 - 0
electron/config/config.default.js

@@ -83,6 +83,18 @@ module.exports = (appInfo) => {
   };
 
   /**
+   * 内置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服务
    */   
   config.socketServer = {

+ 16 - 1
frontend/src/App.vue

@@ -5,6 +5,8 @@
 </template>
 
 <script>
+import { specialIpcRoute, httpConfig } from '@/api/main';
+
 export default {
   name: 'App',
   components: {},
@@ -12,7 +14,20 @@ export default {
     return {};
   },
   watch: {},
-  methods: {}
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init: ()=>{
+      var  { ipcRenderer: ipc }  = window.require && window.require('electron');
+      ipc.removeAllListeners(specialIpcRoute.javaPort);
+      ipc.on(specialIpcRoute.javaPort, (event, result) => {
+        if (result && result !== '') {
+          httpConfig.baseURL = result;
+        }
+      });
+    }
+  }
 }
 </script>
 <style>

+ 21 - 2
frontend/src/api/main.js

@@ -40,6 +40,7 @@ const ipcApiRoute = {
  */
 const specialIpcRoute = {
   appUpdater: 'app.updater', // 此频道在后端也有相同定义
+  javaPort: 'app.javaPort', // 推送java端口
   window1ToWindow2: 'window1-to-window2', // 窗口之间通信
   window2ToWindow1: 'window2-to-window1', // 窗口之间通信
 }
@@ -63,8 +64,26 @@ const requestHttp = (uri, parameter) => {
   })
 }
 
+const httpConfig = {
+  baseURL: 'http://localhost:11111'
+}
+
+const requestJava = (uri, id) => {
+  // url转换
+  let url = httpConfig.baseURL + uri;
+  console.log('url:', url);
+  return request({
+    url: url,
+    method: 'get',
+    params: { id: id}, // URL 参数
+    timeout: 60000,
+  })
+}
+
 export {
   ipcApiRoute,
   specialIpcRoute,
-  requestHttp
-}
+  requestHttp,
+  requestJava,
+  httpConfig
+}

+ 5 - 0
frontend/src/config/router.config.js

@@ -115,6 +115,11 @@ export const constantRouterMap = [
             name: 'OtherTestIndex',
             component: () => import('@/views/other/test/Index')
           },
+          {
+            path: '/other/java/index',
+            name: 'OtherJavaIndex',
+            component: () => import('@/views/other/java/Index')
+          }
         ] 
       }
     ]

+ 8 - 2
frontend/src/config/subMenu.js

@@ -101,5 +101,11 @@ export default {
 			pageName: 'OtherTestIndex',
 			params: {}
 		},
-	}	
-}
+		'menu_200' : {
+			icon: 'profile',
+			title: 'java',
+			pageName: 'OtherJavaIndex',
+			params: {}
+		},
+	}
+}

+ 44 - 0
frontend/src/views/other/java/Index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div id="app-other">
+    <div class="one-block-1">
+      <span>
+        1. 请求java后台接口
+      </span>
+    </div>  
+    <div class="one-block-2">
+      <a-space>
+        <a-button @click="exec(1111111)"> 点击 </a-button>
+      </a-space>
+    </div>
+  </div>
+</template>
+<script>
+import { requestJava } from '@/api/main'
+
+export default {
+  data() {
+    return {};
+  },
+  methods: {
+    exec (id) {
+      requestJava('/test1/get', id).then(res => {
+        console.log('res:', res)
+      })
+    },
+  }
+};
+</script>
+<style lang="less" scoped>
+#app-other {
+  padding: 0px 10px;
+  text-align: left;
+  width: 100%;
+  .one-block-1 {
+    font-size: 16px;
+    padding-top: 10px;
+  }
+  .one-block-2 {
+    padding-top: 10px;
+  }
+}
+</style>

+ 39 - 0
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();

+ 2 - 1
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"
   }
 }

+ 124 - 0
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;

+ 264 - 0
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;
+}

+ 13 - 0
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');
+    }
+  },
+};