背景
因為工作原因,需要實現一款讓用戶下載了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"
},
先寫到這
- 寫貼不易,轉帖請標明來源