当前位置: 首页 > news >正文

Electron (02)集成 SpringBoot:服务与桌面程序协同启动方案

本篇是关于把springboot生成的jar打到electron里,在生成的桌面程序启动时springboot服务就会自动启动。

虽然之后并不需要这种方案,更好的是部署[一套服务端,多个客户端]...但是既然搭建成功了,也记录一下。

前端文件

1、main.js

const { app, BrowserWindow, ipcMain, Notification, Menu,dialog} = require('electron/main')
const path = require('node:path')
const childProcess = require('child_process');
const fs = require('fs')let win = null;       // Electron主窗口实例
let backendProcess = null;   // Java子进程实例
const BACKEND_PORT = 8080;   // 后端固定端口(可配置)
const JAR_FILENAME = 'helloworld-0.0.1-SNAPSHOT.jar'; // JAR文件名(需与resources目录下的文件一致)function writeFile(_, data) {fs.writeFileSync('D:/hello.txt', data)
}function readFile() {const res = fs.readFileSync('D:/hello.txt').toString();return res
}/*** 获取JAR包路径(兼容开发/生产环境)*/
function getJarPath() {if (app.isPackaged) {// 生产环境:打包后,资源目录为process.resourcesPathreturn path.join(process.resourcesPath, 'resources', JAR_FILENAME);} else {// 开发环境:资源目录为项目根目录的`resources`文件夹return path.join(__dirname, 'resources', JAR_FILENAME);}
}/*** 4. 启动Java子进程(核心逻辑)*/
function startBackend() {const jarPath = getJarPath();// 检查JAR包是否存在(避免启动失败)if (!fs.existsSync(jarPath)) {dialog.showErrorBox('错误', `JAR包不存在:${jarPath}`);app.quit();return;}// 构造Java启动参数(可添加Spring Boot配置,如端口、环境)const args = ['-jar',jarPath,`--server.port=${BACKEND_PORT}`,          // 指定后端端口(避免冲突)`--spring.profiles.active=prod`           // 指定生产环境配置(可选)];// 构造子进程选项(跨平台优化)const options = {windowsHide: true,  // Windows下隐藏命令行窗口(避免弹出黑框)env: { ...process.env }, // 传递环境变量cwd: path.dirname(jarPath) // 设置子进程工作目录(避免相对路径问题)};// 启动子进程(使用spawn,适合长时间运行的进程)backendProcess = childProcess.spawn('java', args, options);// 5. 监听后端输出(调试用)backendProcess.stdout.on('data', (data) => {console.log('[Backend]', data.toString().trim());});// 6. 监听后端错误(如Java未安装、端口冲突)backendProcess.stderr.on('data', (data) => {const errorMsg = data.toString().trim();console.error('[Backend Error]', errorMsg);// 处理端口冲突(示例)if (errorMsg.includes(`Port ${BACKEND_PORT} is already in use`)) {dialog.showErrorBox('错误', `后端端口${BACKEND_PORT}已被占用,请关闭占用程序后重试。`);app.quit();}});// 7. 后端退出事件(如异常崩溃)backendProcess.on('exit', (code) => {console.log('[Backend]', `进程退出,代码:${code}`);backendProcess = null;// 若后端异常退出,关闭Electron应用if (code !== 0 && app.isReady()) {dialog.showErrorBox('错误', '后端进程异常退出,请重启应用。');app.quit();}});
}function createWindow() {const win = new BrowserWindow({width: 1000,height: 800,title: '简单网页',webPreferences: {preload: path.join(__dirname, 'preload.js')}})ipcMain.on('file-save', writeFile)ipcMain.handle('file-read', readFile)// 加载前端页面(兼容开发/生产环境)if (app.isPackaged) {console.log('pro')} else {console.log('dev')}//自定义菜单项let menuTemp = [{label: '文件',submenu: [{label: '打开文件',click() {console.log('打开一个具体的文件')}},{ label: '打开文件夹' },{label: '关于',role: 'about'}]},{ label: '编辑' }]//生成自定义菜单let menu = Menu.buildFromTemplate(menuTemp)Menu.setApplicationMenu(menu)win.loadFile('index.html')// 创建并显示通知const notification = new Notification({title: '主进程通知',body: '恭喜你,学会了求雨之术,风来~雨来~'}).show();// 确保在窗口创建后调用 openDevToolswin.webContents.on('did-finish-load', () => {win.webContents.openDevTools();});// 定时发送时间给渲染进程(每1秒)setInterval(() => {if (win && !win.isDestroyed()) {win.webContents.send('main-time', new Date().toLocaleTimeString());}}, 1000);
}
app.whenReady().then(() => {startBackend();       // 启动后端(先启动后端,再创建窗口)createWindow();   // 创建主窗口})// 应用退出前确保后端进程终止
app.on('will-quit', () => {if (backendProcess) backendProcess.kill();
}); 

2、index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self' http://localhost:8080;">
<link rel="stylesheet" href="styles.css">
</head><body><div id="time">当前时间:加载中...</div><div class="hint">注意:输入内容,可以保存到d:/hello.txt,点击读取,可以读取该文件内容</div><input id="input" type="text"><button id="btn2">向D盘输入hello.txt</button><br><br><hr><button id="btn3">读取D盘hello.txt</button><br><br><hr><button id="sendRequest">点击发送请求</button><div id="result"></div><script type="text/javascript" src="./render.js"></script>
</body></html>

3、render.js

const timeElement = document.getElementById('time');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const btn4 = document.getElementById('sendRequest');
const resultDiv = document.getElementById('result');
const input = document.getElementById('input');btn2.onclick = () => {myAPI.saveFile(input.value)
}btn3.onclick = async () => {let data = await myAPI.readFile()alert(data)
}// 定义常量
const API_URL = 'http://localhost:8080/getcode';
const METHOD = 'GET';// 绑定按钮点击事件
btn4.onclick = async () => {try {// 发送 GET 请求const response = await fetch(API_URL, { method: METHOD });// 检查响应状态if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}// 解析字符串数据const data = await response.text(); // 使用 text() 方法解析字符串// 将数据回显到页面上resultDiv.innerHTML = `<p class="success">请求成功!<br>返回数据:</p><pre>${data}</pre>`;} catch (error) {resultDiv.innerHTML = `<p class="error">请求失败,请检查网络或后端服务是否正常运行!</p>`;}
};// 监听主进程发送的时间消息
myAPI.onMainTime((time) => {timeElement.textContent = `当前时间:${time}`;
});

4、preload.js

const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {saveFile: (data) => {ipcRenderer.send('file-save', data)},readFile: () => {return ipcRenderer.invoke('file-read')},// 监听主进程发送的时间消息onMainTime: (callback) => {ipcRenderer.on('main-time', (event, time) => callback(time))}
})

5、package.json

  "scripts": {"start": "electron .","build": "electron-builder --win --x64","package": "electron-packager . construction --win --out build --arch=x64 --version1.0.0 --overwrite --icon=static/images/128.ico","make": "electron-forge make"},"build": {"appId": "com.xiaoyumao.demo","extraResources": {"from": "resources/helloworld-0.0.1-SNAPSHOT.jar","to": "resources/helloworld-0.0.1-SNAPSHOT.jar"},"win": {"target": [{"target": "nsis","arch": ["x64"]}]},"nsis": {"oneClick": false,"perMachine": true,"allowToChangeInstallationDirectory": true}},

Electron中集成jar

1、先得有jar包

使用springboot技术,快速生成一个web应用。写一个getcode接口,

    @GetMapping("/getcode")public String getcode(){UUID randomUUID = UUID.randomUUID();String uuidWithoutHyphens = randomUUID.toString().replace("-", "");return "随机编码:"+uuidWithoutHyphens;}

在浏览器测试的访问一下

没啥问题后,用maven进行打包,生成可以独立运行的jar


2、child_process启动jar

由Electron主进程(Node环境)创建的独立进程,来启动jar

child_process.spawn()

用于创建一个子进程并实时监听其输入和输出。

java -jar C:\Users\lenovo\electron-basics\resources\helloworld-0.0.1-SNAPSHOT.jar --server.port=8080

3、resource目录

还需要在package.json配置extraResources ,用于在构建 Electron 应用程序时将额外的资源文件打包到最终的应用程序安装包中。它的主要作用是确保应用程序所需的资源文件能够正确地随应用一起发布,而不会丢失。

    "extraResources": {"from": "resources/helloworld-0.0.1-SNAPSHOT.jar","to": "resources/helloworld-0.0.1-SNAPSHOT.jar"},

在 Electron 中,process.resourcesPath 指向的是应用程序的资源目录。

    if (app.isPackaged) {// 生产环境:打包后,资源目录为process.resourcesPathreturn path.join(process.resourcesPath, 'resources', JAR_FILENAME);} 

在这里资源文件都放在了electron本身生成的resources目录中


4、假如没有JAVA_HOME环境

有些情况就是,电脑它没有javahome环境,或者有但是配置的不是我们想要的jdk1.8。。

所以我决定打包的时候把jre环境也打进去,jar启动原理就是下面这样的

C:\Users\lenovo\electron-basics\resources\jre\bin\java -jar C:\Users\lenovo\electron-basics\resources\helloworld-0.0.1-SNAPSHOT.jar --server.port=8080

在resource目录下把jre环境放进去。

package.json就得改变了

    "extraResources": [{"from": "resources/jre","to": "resources/jre"},{"from": "resources/helloworld-0.0.1-SNAPSHOT.jar","to": "resources/helloworld-0.0.1-SNAPSHOT.jar"}],

在main.js中,关于启动jar包的命令、对java环境的检查等都要用xxx\jre\bin\java去检查

在安装软件后,目录是这样的

现在就算没有JAVA_HOME,也照样可以运行


JSP应用

如果项目之前是用jsp写的,那么能不能啥都不改的情况下,直接访问l

相关文章:

  • 回文链表C++
  • 设计模式精讲 Day 8:组合模式(Composite Pattern)
  • Transformer实战——Hugging Face环境配置与应用详解
  • 什么是seata
  • node.js在vscode的配置
  • 多线程八股
  • 【小程序】如何生成特定页面的小程序码
  • 代码审计-fastjson反序列化漏洞
  • .NET基于类名约定的自动依赖注入完整指南
  • python+uniapp基于微信小程序的高校二手商品交易系统
  • NVR的方法多种取决于应用场景
  • PVE使用ubuntu-cloud-24.img创建虚拟机并制作模板
  • 20250620在荣品的PRO-RK3566开发板的Android13系统的uboot阶段就拉高GPIO2C6【driver模式】
  • 第3讲、LangChain性能优化:上下文缓存与流式响应实战指南
  • java面试题03静态修饰类,属性,方法有什么特点?
  • Maven并行构建
  • Anaconda安装env,yml一直卡在Solving environment:不动
  • Spring Boot大文件分块上传:高效解决大文件传输难题
  • React Native【实战范例】同步跟随滚动
  • 【音视频】RTMP协议详解
  • wordpress回收站 恢复/网站优化seo推广服务
  • 西宁网站制作费用是多少/做seo网页价格
  • 在深圳市住房和建设局网站/拉新工作室在哪里接项目
  • 南京做网站建设有哪些内容/广州品牌营销服务
  • 宝安住房和建设局网站电话/怎么做营销
  • 做网站销售的/如何做好品牌推广工作