背景

因为工作原因,需要实现一款让用户下载了exe,安装后,打开可视化界面即可自动启动java web服务,并将请求到首页的应用。

实现思路

  • 使用springboot做一个web应用,因为其内置了web容器,所以打包成jar后,通过java -jar即可启动web服务
  • 创建一个electron项目,把jar文件还有jre运行环境放进去
  • 通过nodejs的process提供的方法执行启动脚本(java -jar xxx.jar)
  • electron 渲染进程实现服务是否已启动的状态检测,如果已经启动直接访问首页,如果没有启动则执行上一步的启动脚本

1、基于springboot构建的web服务

  • 直接网上找了一个现成的-若依(非前后端分离版本)
  • 然后clone代码,根据文档,完成本地环境的搭建,能实现把服务打成jar(mvn install)
  • 使用java -jar xx.jar命令测试启动是否正常
  • 测试首页能否访问
  • 一切正常

2、创建一个electron项目

  • electron官网有一个electron-quick-star项目,可以参考。
  • clone下来,安装依赖(指定国内镜像地址)
  • 安装依赖前可以把镜像源修改到国内,要不然等待是个很熬人的过程
  • npm install --registry=https://registry.npm.taobao.org // 使用淘宝镜像下载(一次有效)
  • npm config set ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron // 把electron镜像也换一下,我记得有一个100多m的exe文件需要从这里下载。
  • 运行起来(npm start)

3、在项目根目录下创建java应用文件夹

在这里插入图片描述

  • app文件夹下放了业务系统jar文件
  • jre是java1.8的运行环境

4、编写启动命令

  • 启动命令使用了 const { spawn, exec } = require(‘child_process’)
  • electron 不建议在主进程里做业务操作,所以打算把启动命令放到渲染进程里
  • 但是渲染进程已经不支持使用require进行nodejs Api引用了,查了好久,网上给的建议是将nodeIntegration为true,但是根据我的测试,此处修改成true也不行,然后我又测试了一下electron的9.4.4版本,发现可行。看来是最新的版本取消了这个属性的作用,同时官网提供了新的解题思路:
    在这里插入图片描述- 首先在main.js里提供了预加载属性,并且提供了一个preload.js的调用
  • 在preload.js里可以require Node.js APIs
  • preload.js 声明的属性及function可以通过contextBridge暴露给渲染进程
  • 上代码:preload.js:
const { contextBridge } = require('electron')
const { spawn, exec } = require('child_process')
const path = require('path')

contextBridge.exposeInMainWorld('myAPI', {
  startServerForSpawn: () => {
    let path1 = path.join(__dirname, 'app/ruoyi-admin.jar');
    const ls = spawn('java', ['-jar', path1]);
    ls.stdout.on('data', (data) => {
      if(data.toString().indexOf("Started RuoYiApplication") !== -1){
        window.location.href="http://localhost:80";
      }
    });
    ls.stderr.on('data', (data) => {
      console.error(`stderr: ${data}`);
      alert("启动服务异常");
    });
    ls.on('close', (code) => {
      console.log(`child process exited with code ${code}`);
    });
  },
  startServerForbat: () =>{
    const bat = spawn(path.join(__dirname, 'my.bat'));
    bat.stdout.on('data', (data) => {
      console.log(data.toString());
      if(data.toString().indexOf("Started RuoYiApplication") !== -1){
        window.location.href="http://localhost:80";
      }
    });
    bat.stderr.on('data', (data) => {
      console.error(data.toString());
    });
    bat.on('exit', (code) => {
      console.log(`Child exited with code ${code}`);
    });
  }
})
  • 在渲染进程 loading.html 页面里调用(使用window对象调用)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
    <title>loading</title>
  </head>
  <body>
    <h1>Loading</h1>
    <div>正在启动,请等待!</div>
    <script>
      window.myAPI.startServerForbat();
      console.log("-----------hello-------------")
    </script>
  </body>
</html>

  • 在preload.js里有两个方法实现了java -jar命令的执行:startServerForSpawn、startServerForbat
  • 其实都是使用spawn函数进行执行,不过一个是直接执行命令,一个是执行一个bat文件
  • 经过测试我发现第一种在启动了java web服务之后,如果把当前页面跳转到其他页面(window.location.href)后,子进程就自动销毁了,web服务也随之关闭了。
  • 而使用bat就没有这个问题,甭管你是关闭了渲染进程还是主进程都不会影响web服务,在下次启动exe的时候也就不用再重新启动web服务了,直接访问,大大减少了启动时间。
  • my.bat脚本:
cd ./jre/bin
java -jar ../../app/ruoyi-admin.jar

5、编写主页面判断逻辑-index.html

  • 应用起来后先成本地加载index.html文件
  • index.html内通过访问本地服务验证服务是否开启(过期时间200)
  • 如果已开启,直接跳转到服务首页
  • 未启动,则加载上一步的loading页面,启动服务
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="">
    <title>index</title>
  </head>
  <body>
    <!-- We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>. -->
   
    <script src="./static/js/jquery.min.js"> </script>
    <script>
      $(document).ready(function(){
        $.ajax({
          timeout: 200,
          type: 'GET',
          url: 'http://localhost',
          data: {
          },
          success: function(obj){
            window.location.href = "http://localhost"
          },
          error: function(obj){
            window.location.href = "loading.html";
          }
        })
      })
    </script>
  </body>
</html>
  • 这里用的是jquery库发ajax请求验证
  • 本来打算使用nodejs的http模块的get方法进行测试,但是发现如果服务器没有启动,根本走不到回调里去(我在回调了做了statusCode的判断、以及绑定了data、end、error事件,均没有执行)
  • 还试了electron的net模块访问,也是不走回调,所以放弃之。
  • 访问首页的时候发现业务系统的console有报错,好像是对方前端库使用了jquery导致的。所以在main.js里设置了一个属性,解决了问题:contextIsolation:true

6、打包 electron-builder

  • 直接上package.json,贴上就能用,如果本地没有安装electron-builder,npm run dist时先加载依赖
{
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "package": "electron-packager . construction --win --out build --arch=x64 --version1.0.0 --overwrite --icon=static/images/128.ico",
    "dist": "electron-builder --win --x64",
    "win32": "electron-builder --win --ia32"
  },
  "repository": "https://github.com/electron/electron-quick-start",
  "keywords": [
    "Electron",
    "quick",
    "start",
    "tutorial",
    "demo"
  ],
  "author": "GitHub",
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^14.0.0"
  },
  "build": {
    "appId": "com.phil.test",
    "copyright": "https://github.com/phil-cheng",
    "productName": "java打包",
    "asar": false,
    "mac": {
        "target": [
            "dmg",
            "zip"
        ]
    },
    "win": {
        "target": [
            "nsis",
            "zip"
        ],
        "icon": "static/images/256.ico"
    }
}
}

  • 看上述scipts:打windows 64位 exe 执行npm run dist;32位执行npm run win32

注意:

  • 因为第一次上述配置里"asar"默认为true,所以打包会把应用下的代码打包成一个归档文件-asar,如图右边,这就会导致程序在执行bat脚本时找不到本地文件。
    在这里插入图片描述

  • asar严格意义上也不是对代码加密,只是类似于zip一样做了归档处理,通过其对应的命令是可以“解压”出来的

  • 解决办法有两个:

    • 第一个就是把asar设置成false,不归档
    • 第二个是在打包的时候把本地文件copy到包外边(例如:bat、jar、sqllite数据库等本地文件),此办法没有验证过,可以参考这个
"extraResources":  { //把需要访问的文件移动到外层目录
     "from": "template",
     "to": "temp"
 },

先写到这

  • 写贴不易,转帖请标明来源
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐