Explorar el Código

addon java-server

gaoshuaixing hace 3 años
padre
commit
8f2eac0b6e

+ 81 - 0
electron/addon/javaServer/index.js

@@ -0,0 +1,81 @@
+const getPort = require('get-port');
+const server = require("./server");
+const electronApp = require('electron').app;
+
+/**
+ * java server插件
+ * @class
+ */
+class JavaServerAddon {
+
+  constructor(app) {
+    this.app = app;
+    this.cfg = app.config.addons.javaServer;
+    this.javaServer = null;
+  }
+
+  /**
+   * 创建java服务
+   *
+   * @function 
+   * @since 1.0.0
+   */
+  async createServer () {
+    await this.createJavaPorts();
+
+    this.javaServer = new server(this.app);
+    await this.javaServer.create();
+
+    // kill
+    electronApp.on("before-quit", async () => {
+      this.app.logger.info("[addon:javaServer] before-quit: kill-----------");
+      await this.javaServer.kill();
+    });
+
+    return;
+  }
+
+  /**
+   * todo 检查服务是否启动
+   *
+   * @function 
+   * @since 1.0.0
+   */
+  async check () {
+    
+  }
+
+  /**
+   * 创建服务端口
+   *
+   * @function 
+   * @since 1.0.0
+   */
+  async createJavaPorts() {
+    if (!this.cfg.enable) {
+      return;
+    }
+    const javaPort = await getPort({ port: this.cfg.port });
+    process.env.EE_JAVA_PORT = javaPort;
+    this.cfg.port = javaPort;
+
+    // 更新config配置
+    this.app.getCoreDB().setItem("config", this.app.config);
+  }
+
+  /**
+   * 杀掉进程
+   *
+   * @function 
+   * @since 1.0.0
+   */
+  async kill() {
+    if (!this.cfg.enable) {
+      return;
+    }
+    await this.javaServer.kill();
+  }
+}
+
+JavaServerAddon.toString = () => '[class JavaServerAddon]';
+module.exports = JavaServerAddon;

+ 257 - 0
electron/addon/javaServer/ps.js

@@ -0,0 +1,257 @@
+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;
+}

+ 99 - 0
electron/addon/javaServer/server.js

@@ -0,0 +1,99 @@
+const _ = require("lodash");
+const assert = require("assert");
+const fs = require("fs");
+const is = require('electron-is');
+const path = require("path");
+const { exec, execSync } = require("child_process");
+const Utils = require("ee-core").Utils;
+const ps = require("./ps");
+
+/**
+ * java server
+ */
+class JavaServer {
+  constructor (app) {
+    this.app = app;
+    this.options = app.config.addons.javaServer;
+  }
+
+  /**
+   * 创建服务
+   */
+  async create () {
+    if (!this.options.enable) {
+      return;
+    }
+  
+    let port = process.env.EE_JAVA_PORT ? parseInt(process.env.EE_JAVA_PORT) : parseInt(this.options.port);
+    assert(typeof port === "number", "java port required, and must be a number");
+  
+    try {
+      const jarName = this.options.name;
+      let softwarePath = path.join(Utils.getExtraResourcesDir(), jarName);
+      let javaOptStr = this.options.opt;
+      let jrePath = path.join(Utils.getExtraResourcesDir(), this.options.jreVersion);
+      let cmdStr = '';
+      
+      this.app.console.info("[addon:javaServer] jar file path:", softwarePath); 
+      if (!fs.existsSync(softwarePath)) throw new Error('java program does not exist');
+
+      // 替换opt参数
+      javaOptStr = _.replace(javaOptStr, "${port}", port);
+      javaOptStr = _.replace(javaOptStr, "${path}", Utils.getLogDir());
+
+      if (is.windows()) {
+        jrePath = path.join(jrePath, "bin", "javaw.exe");
+        cmdStr = `start ${jrePath} -jar ${javaOptStr} ${softwarePath}`;
+      } else if (is.macOS()) {
+        // 如果提示:不受信任,请执行:  sudo spctl --master-disable
+        jrePath = path.join(jrePath, "Contents", "Home", "bin", "java");
+        //cmdStr = `nohup ${jrePath} -jar ${javaOptStr} ${softwarePath} >/dev/null 2>&1 &`;
+        cmdStr = `${jrePath} -jar ${javaOptStr} ${softwarePath}`;
+      } else {
+        // todo linux
+      }
+
+      this.app.logger.info("[addon:javaServer] cmdStr:", cmdStr);
+      exec(cmdStr);
+
+    } catch (err) {
+      this.app.logger.error('[addon:javaServer] throw error:', err);
+    }
+  }
+
+  /**
+   * 关闭服务
+   */
+  async kill () {
+    const jarName = this.options.name;
+    if (is.windows()) {
+      const resultList = ps.lookup({
+        command: "java",
+        where: 'caption="javaw.exe"',
+        arguments: jarName,
+      });
+  
+      //this.app.logger.info("[addon:javaServer] resultList:", resultList);
+      resultList.forEach((item) => {
+        ps.kill(item.pid, "SIGKILL", (err) => {
+          if (err) {
+            throw new Error(err);
+          }
+          console.info("[addon:javaServer] java程序退出 pid: ", item.pid);
+        });
+      });
+  
+      //   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 if (is.macOS()) {
+      const cmd = `ps -ef | grep java | grep ${jarName} | grep -v grep | awk '{print $2}' | xargs kill -9`;
+      const result = await execSync(cmd);
+      this.app.logger.info("[addon:javaServer] kill:", result != null ? result.toString(): '');
+    } else {
+      // todo linux
+    }
+  }
+}
+
+module.exports = JavaServer;

+ 11 - 14
electron/config/config.default.js

@@ -75,7 +75,7 @@ module.exports = (appInfo) => {
   }
 
   /**
-   * 远程web地址 (可选)
+   * 远程模式-web地址
    */    
   config.remoteUrl = {
     enable: false, // 是否启用
@@ -83,18 +83,6 @@ 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 = {
@@ -190,12 +178,21 @@ module.exports = (appInfo) => {
    * example demo插件
    */
   config.addons = {
+    // 多窗口
     window: {
       enable: true,
     },
+    // java服务
+    javaServer: {
+      enable: true,  // 是否启用,true时,启动程序时,会自动启动 build/extraResources/java-app.jar 下的 java程序
+      port: 18080,    // 端口,端口被占用时随机一个端口,并通知前端修改请求地址。
+      jreVersion: 'jre1.8.0_201', // build/extraResources/目录下 jre 文件夹名称
+      opt: '-server -Xms512M -Xmx512M -Xss512k -Dspring.profiles.active=prod -Dserver.port=${port} -Dlogging.file.path="${path}" ',
+      name: 'java-app.jar' // build/extraResources/目录下 jar 名称
+    },
     example: {
       enable: true, 
-    }
+    },
   };
 
   return {

+ 45 - 0
electron/controller/example.js

@@ -653,6 +653,51 @@ class ExampleController extends Controller {
   }
 
   /**
+   * 启动java项目
+   */ 
+  async startJavaServer () {
+    let data = {
+      code: 0,
+      msg: '',
+      server: ''
+    }
+    const javaCfg = this.app.config.addons.javaServer || {};
+    if (!javaCfg.enable) {
+      data.code = -1;
+      data.msg = 'addon not enabled!';
+      return data;
+    }
+
+    const javaServerAddon = this.app.addon.javaServer;
+    await javaServerAddon.createServer();
+
+    data.server = 'http://localhost:' + javaCfg.port;
+
+    return data;
+  }
+
+  /**
+   * 关闭java项目
+   */ 
+  async closeJavaServer () {
+    let data = {
+      code: 0,
+      msg: '',
+    }
+    const javaCfg = this.app.config.addons.javaServer || {};
+    if (!javaCfg.enable) {
+      data.code = -1;
+      data.msg = 'addon not enabled!';
+      return data;
+    }
+
+    const javaServerAddon = this.app.addon.javaServer;
+    await javaServerAddon.kill();
+
+    return data;
+  }
+
+  /**
    * 测试接口
    */ 
   hello (args) {

+ 1 - 16
frontend/src/App.vue

@@ -5,8 +5,6 @@
 </template>
 
 <script>
-import { specialIpcRoute, httpConfig } from '@/api/main';
-
 export default {
   name: 'App',
   components: {},
@@ -14,20 +12,7 @@ export default {
     return {};
   },
   watch: {},
-  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;
-        }
-      });
-    }
-  }
+  methods: {}
 }
 </script>
 <style>

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

@@ -32,6 +32,8 @@ const ipcApiRoute = {
   ipcSendSyncMsg: 'controller.example.ipcSendSyncMsg',
   ipcSendMsg: 'controller.example.ipcSendMsg',
   getWCid: 'controller.example.getWCid',
+  startJavaServer: 'controller.example.startJavaServer',
+  closeJavaServer: 'controller.example.closeJavaServer',
   hello: 'controller.example.hello',
 }
 
@@ -40,7 +42,6 @@ const ipcApiRoute = {
  */
 const specialIpcRoute = {
   appUpdater: 'app.updater', // 此频道在后端也有相同定义
-  javaPort: 'app.javaPort', // 推送java端口
   window1ToWindow2: 'window1-to-window2', // 窗口之间通信
   window2ToWindow1: 'window2-to-window1', // 窗口之间通信
 }
@@ -64,26 +65,8 @@ 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,
-  requestJava,
-  httpConfig
 }

+ 48 - 9
frontend/src/views/other/java/Index.vue

@@ -7,12 +7,14 @@
     </div>  
     <div class="one-block-2">
       <a-space>
-        <a-button @click="exec(1111111)"> 点击 </a-button>
+        <a-button @click="startServer()"> 启动java项目 </a-button>
+        <a-button @click="sendRequest()"> 测试接口 </a-button>
+        <a-button @click="closeServer()"> 关闭java项目 </a-button>
       </a-space>
     </div>
     <div class="one-block-2">
       <span>
-        1. 修改 electron/config/config.default.js 中 config.javaServer.enable = true <br/>
+        1. 修改 electron/config/config.default.js 中 config.server.enable = true <br/>
         2. 官方下载 jre 并解压到: build/extraResources <br/>
         3. 编译 spring boot 可执行jar到: build/extraResources <br/>
 
@@ -25,8 +27,6 @@
         https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
         <br/>
 
-        <img src="~@/assets/java.png"/>
-
         <br/>
         同时,你可以将18080端口先占用,后启动ee程序,观察请求的端口
 
@@ -35,16 +35,55 @@
   </div>
 </template>
 <script>
-import { requestJava } from '@/api/main'
+import storage from 'store2'
+import { ipcApiRoute } from '@/api/main';
 
 export default {
   data() {
-    return {};
+    return {
+      server: '',
+    };
   },
+  mounted() {
+
+  },  
   methods: {
-    exec (id) {
-      requestJava('/test1/get', id).then(res => {
-        console.log('res:', res)
+    startServer () {
+      this.$ipcInvoke(ipcApiRoute.startJavaServer, {}).then(r => {
+        if (r.code != 0) {
+          this.$message.error(r.msg);
+        }
+        this.$message.info('启动成功');
+        storage.set('javaService', r.server);
+      })
+    },
+
+    closeServer () {
+      this.$ipcInvoke(ipcApiRoute.closeJavaServer, {}).then(r => {
+        if (r.code != 0) {
+          this.$message.error(r.msg);
+        }
+        this.$message.info('服务已关闭');
+        storage.remove('javaService:');
+      })
+    },
+
+    sendRequest () {
+      const server = storage.get('javaService') || '';
+      if (server == '') {
+        this.$message.error('服务未开启');
+        return
+      }
+      let testApi = server + '/test1/get';
+      let params = {
+        url: testApi,
+        method: 'get',
+        params: { id: '1111111'},
+        timeout: 60000,
+      }
+      this.$http(params).then(res => {
+        console.log('res:', res);
+        this.$message.info(`java服务返回: ${res}`, );
       })
     },
   }

+ 0 - 39
main.js

@@ -1,6 +1,4 @@
 const Appliaction = require('ee-core').Appliaction;
-const getPort = require('get-port');
-const { app } = require('electron');
 
 class Main extends Appliaction {
 
@@ -14,26 +12,6 @@ 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");
   }
 
   /**
@@ -56,16 +34,6 @@ 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();
-    });
   }
 
   /**
@@ -75,13 +43,6 @@ 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 - 2
public/lib/javaServer.js

@@ -5,7 +5,7 @@ const assert = require("assert");
 const fs = require("fs");
 const os = require("os");
 const path = require("path");
-const { execSync } = require("child_process");
+const { execSync, exec } = require("child_process");
 const Utils = require("ee-core").Utils;
 const ps = require("./ps");
 
@@ -69,7 +69,7 @@ async function start(app) {
     // 命令行字符串 并 执行
     let cmdStr = `start ${jrePath} -jar ${JAVA_OPT} ${softwarePath}`;
     app.logger.info("[javaServer] cmdStr:", cmdStr);
-    await execSync(cmdStr);
+    exec(cmdStr);
   } else {
     // 不受信任请执行:  sudo spctl --master-disable
     let jrePath = path.join(