当前位置: 首页 > 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

http://www.lryc.cn/news/572669.html

相关文章:

  • 大白话说目标检测中的IOU(Intersection over Union)
  • Maven并行构建
  • 单点登录进阶:基于芋道(yudao)授权码模式的单点登录流程、代码实现与安全设计
  • SAP-ABAP:LOOP ... ASSIGNING高效处理内表数据详解
  • pandas polars 数据类型转换
  • 【pdf】Java代码生成PDF
  • lingma(阿里云Ai)结合idea使用
  • uni-app-配合iOS App项目开发apple watch app
  • Python按钮点击事件快速入门
  • vue3 reactive重新赋值
  • VSCode1.101.1Win多语言语言编辑器便携版安装教程
  • 【Dify精讲】第14章:部署架构与DevOps实践
  • 字符编码(UTF-8,16,32 和GBK和ASCLL码)
  • 三维视频融合平台:如何构建动态感知的数字空间
  • 配置Fiori应用时报错
  • 从语音到字幕,视频剪辑效率翻倍方案
  • vtk和opencv和opengl直接的区别是什么?
  • Web Splats
  • 每天一个前端小知识 Day 7 - 现代前端工程化与构建工具体系
  • 设计模式实战指南:从源码解析到Java后端架构的艺术
  • mysql查询使用`_rowid` 虚拟列
  • Apipost 签约锐捷网络:AI赋能,共推 ICT 领域 API 生态智能化升级
  • (链表:哈希表 + 双向链表)146.LRU 缓存
  • 性能测试-jmeter实战3
  • 二十二章 stable diffusion SDXL1.0模型 介绍
  • 期货反向跟单-终止盘手合作原则(二)
  • 原点安全入选 Gartner®“数据安全平台”中国市场指南代表厂商
  • Mac电脑-SSH客户端-Termius
  • JetBrains IDE v2025.1 升级,AI 智能+语言支持齐飞
  • Kafka协议开发总踩坑?3步拆解二进制协议核心