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.pswp.cn/news/910299.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年计算机应用与神经网络国际会议(CANN 2025)

2025 International Conference on Computer Applications and Neural Networks &#xff08;一&#xff09;会议信息 会议简称&#xff1a;CANN 2025 大会地点&#xff1a;中国重庆 收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Scholar等 &#xff08;二&#x…

振动分析中的低频噪声问题:从理论到实践的完整解决方案

前言 在振动监测和结构健康监测领域&#xff0c;我们经常需要从加速度信号计算速度和位移。然而&#xff0c;许多工程师在实际应用中都会遇到一个令人困扰的问题&#xff1a;通过积分计算得到的速度和位移频谱中低频噪声异常放大。 本文将深入分析这个问题的根本原因&#xf…

ncu学习笔记01——合并访存

全局内存通过缓存实现加载和存储过程。其中&#xff0c;L1为一级缓存&#xff0c;每个SM都有自己的L1&#xff1b;L2为二级缓存&#xff0c;L2则被所有SM共有。 数据从全局内存到SM的传输过程中&#xff0c;会去L1和L2中查询是否有缓存。对全局内存的访问将经过L1&#xff1b;…

2012 - 正方形矩阵

​​​​题目描述 晶晶同学非常喜欢方形&#xff0c;她希望打印出来的字符串也是方形的。老师给了晶晶同学一个字符串"ACM"&#xff0c;晶晶同学突发奇想&#xff0c;如果任意给定义一个整数n&#xff0c;能不能打印出由这个字符串组成的正方形字符串呢&#xff1f;…

C++中set的常见用法

在 C 里&#xff0c;std::set属于标准库容器的一种&#xff0c;其特性是按照特定顺序存储唯一的元素。下面为你详细介绍它的常见使用方法&#xff1a; 1. 头文件引入 要使用std::set&#xff0c;需要在代码中包含相应的头文件&#xff1a; #include <set> 2. 集合的定…

stm32移植freemodbus

1、设置串口 开启串口中断 2、设置定时器 已知在freemodbus中默认定义&#xff1a;当波特率大于19200时&#xff0c;判断一帧数据超时时间固定为1750us&#xff0c;当波特率小于19200时&#xff0c;超时时间为3.5个字符时间。这里移植的是115200&#xff0c;所以一帧数据超时…

鸿蒙next 使用canvas实现ecg动态波形绘制

该代码可在Arkts 与 前端使用&#xff0c;基于canvas 仓库地址&#xff1a;https://gitee.com/harmony_os_example/harmony-os-ecg-waveform.git 代码中的list数组为波形数据&#xff0c;该示例需要根据自己业务替换绘制频率&#xff0c;波形数据&#xff0c;ecg原始数据生成…

基于原生能力的键盘控制

基于原生能力的键盘控制 前言一、进入页面TextInput获焦1、方案2、核心代码 二、点击按钮或其他事件触发TextInput获焦1、方案2、核心代码 三、键盘弹出后只上抬特定的输入组件1、方案2、核心代码 四、监听键盘高度1、方案2、核心代码 五、设置窗口在键盘抬起时的页面避让模式为…

大数据治理域——数据存储与成本管理

摘要 本文主要探讨了数据存储与成本管理的多种策略。介绍了数据压缩技术&#xff0c;如MaxCompute的archive压缩方法&#xff0c;通过RAID file形式存储数据&#xff0c;可有效节省空间&#xff0c;但恢复时间较长&#xff0c;适用于冷备与日志数据。还详细阐述了数据生命周期…

国产Linux银河麒麟操作系统上使用自带openssh远程工具SSH方式登陆华为交换机或服务器

在Windows和Linux Debian系统上我一直使用electerm远程工具访问服务器或交换机&#xff0c; 一、 electerm简介 简介&#xff1a;electerm是一款开源免费的SSH工具&#xff0c;具有良好的跨平台兼容性&#xff0c;适用于Windows、macOS、Linux以及麒麟操作系统。特点&#xf…

Logback 在java中的使用

Logback 是 Java 应用中广泛使用的日志框架&#xff0c;以下是其核心使用方法及最佳实践&#xff1a; 1. 引入依赖 在 Maven 或 Gradle 项目中添加 Logback 及 SLF4J 依赖&#xff1a; <!-- Maven --> <dependency><groupId>ch.qos.logback</groupId>…

Axure应用交互设计:中继器—整行、条件行、当前行赋值

亲爱的小伙伴,如有帮助请订阅专栏!跟着老师每课一练,系统学习Axure交互设计课程! Axure产品经理精品视频课https://edu.csdn.net/course/detail/40420 课程主题:对中继器中:整行、符合某种条件的任意行、当前行的赋值操作 课程视频:

ToolsSet之:TTS及Morse编解码

ToolsSet是微软商店中的一款包含数十种实用工具数百种细分功能的工具集合应用&#xff0c;应用基本功能介绍可以查看以下文章&#xff1a; Windows应用ToolsSet介绍https://blog.csdn.net/BinField/article/details/145898264其中Text菜单中的TTS & Morse可用于将文本转换…

【C++】编码传输:创建零拷贝帧对象4:shared_ptr转unique_ptr给到rtp打包

【C++】编码传输:创建零拷贝帧对象3: dll api转换内部的共享内存根本原因 你想要的是基于 packet 指向的那个已有对象,拷贝(或移动)出一个新的 VideoDataPacket3 实例,因此需要把那个对象本身传进去——也就是 *packet。copilot的原因分析与gpt一致 The issue is with t…

基于UDP的套接字通信

udp是一个面向无连接的&#xff0c;不安全的&#xff0c;报式传输层协议&#xff0c;udp的通信过程默认也是阻塞的。使用UDP进行通信&#xff0c;服务器和客户端的处理步骤比TCP要简单很多&#xff0c;并且两端是对等的 &#xff08;通信的处理流程几乎是一样的&#xff09;&am…

华为CE交换机抓包

capture-packet interface 100GE1/0/5 destination file 001.cap packet-len 64 注&#xff1a;早期版本&#xff08;disp device&#xff09;可能在系统视图下&#xff08;sys&#xff09; 抓完包后可以看到对应文件&#xff08;早期版本在根目录下&#xff09;&#xff1a;…

Python 数据分析与可视化 Day 3 - Pandas 数据筛选与排序操作

&#x1f3af; 今日目标 掌握 DataFrame 的条件筛选&#xff08;布尔索引&#xff09;学会多条件筛选、逻辑运算熟练使用排序&#xff08;sort_values&#xff09;提升数据组织力结合列选择进行数据提取分析 &#x1f9ea; 一、列选择与基本筛选 ✅ 选择单列 / 多列 df[&quo…

Vite项目初始化与配置

下面,我们来系统的梳理关于 Vite 项目初始化与配置 的基本知识点: 一、Vite 核心概念与优势 1.1 什么是 Vite? Vite(法语意为 “快速”)是新一代的前端构建工具,由 Vue.js 作者尤雨溪开发。它解决了传统构建工具(如 Webpack)在开发环境中的性能瓶颈问题。 1.2 Vite …

Transformer中的核心问题 知识点汇总

Transformer架构图 transformer整体架构 1. Transformer 的参数配置 Transformer 的Encoder层和Decoder层都使用6个注意力模块&#xff0c;所有的子网络的输出维度均为512维&#xff0c;多头注意力部分使用了8个注意力头。 2. 归一化的方式 归一化的方式为LayerNorm&#xff0c…

python web开发-Flask数据库集成

Flask 数据库集成完全指南&#xff1a;Flask-SQLAlchemy 实践 1. 引言 数据库是现代Web应用的核心组件&#xff0c;Flask通过Flask-SQLAlchemy扩展提供了强大的数据库集成能力。本文将全面介绍如何在Flask应用中使用Flask-SQLAlchemy进行数据库操作&#xff0c;涵盖从基础配置…