学习记录:DAY32

Electron 开发之旅:从入门到实践

前言


接续上一篇 blog,这篇的内容主要和 Electron 有关。

课设不是特别想做下去了,实际核心代码大概只有 3,4 百行左右,比较水......

或许会把 Docker 的部署也做一做(权当是练习了)。

日程


现在是晚上 7 点 40 分,希望 9 点之前能把应用打包好,这样今天还能匀点时间用来复习。

困困困困困困困困困困困困困困困困困困

Electron 的水比我想象的深,搞不懂所以用 bat 了

过 0 点了,都不睡觉是吧,我和你们爆了😡😭😡😭

学习内容


《省流》

  1. Electron 安装与入门
  2. 预加载脚本与进程通信
  3. 打包应用
  4. bat 脚本启动前后端

1. Electron 安装与入门

先放个官方的安装教程:安装指导 | Electron

基本上参照官方的教程安装即可。因为 Electron 的安装过程分为两部分:

  • 下载 npm 包 (镜像)
  • 下载 Electron 二进制文件(GitHub 下载)

所以国服玩家最好设置一下国内的镜像源:

bash 复制代码
# 设置淘宝镜像源
npm config set registry https://registry.npmmirror.com

# 从国内镜像下载二进制包
npm install electron --save-dev --electron-mirror=https://npmmirror.com/mirrors/electron/

接着是入门程序:

官方 api 文档:app | Electron

javascript 复制代码
// main.js
const { app, BrowserWindow } = require('electron')
function createWindow() {
    const win = new BrowserWindow({ // 创建窗口对象
        width: 800,
        height: 600,
        autoHideMenuBar: true, // 隐藏菜单栏
    })
    win.loadURL('https://www.github.com') // 加载网页
}
app.on('ready', () => { // 启动钩子
    createWindow()
})

如果要启动本地的网页资源:

javascript 复制代码
win.loadFile('./pages/index.html')

通常还需要设置内容安全策略来控制资源的访问:

html 复制代码
<!-- index.html -->
<meta
    http-equiv="X-Content-Security-Policy"
    content="default-src 'self'; script-src 'self'"
/>

关于 CSP 的详细说明请参考:内容安全策略(CSP) - HTTP | MDN

窗口默认行为:

javascript 复制代码
// windows 习惯:关闭所有窗口后退出程序
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})
// Mac 习惯
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

配置自动重启:

这个能保证在编写代码后 electron 应用能自动更新

bash 复制代码
# 安装 nodemon
npm i nodemon -D

# 配置 nodemon.json
{
    "ignore": [
        "node_modules",
        "dist"
    ],
    "restartable": "r",
    "watch": ["*.*"],
    "ext": "html,js,css",
    "delay": 10000
}

# 配置 package.json
"start": "nodemon --exec electron .",

2. 预加载脚本与进程通信

首先要了解 Electron 的流程模型:

由一个入口 主进程(main.js)和多个渲染进程(render.js [即 index.html] 的 js 脚本)组成

预加载脚本(preload.js),作为沟通两者的桥梁

javascript 复制代码
// preload.js
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', { // 形成全局上下文键值对
    version: process.version,
})

在 main.js 中声明

javascript 复制代码
const path = require('path')
function createWindow() {
    const win = new BrowserWindow({ 
        ...
        webPreferences: {
            preload: path.resolve(__dirname, "./preload.js")
        }
    })
}

在 render.js 中调用

javascript 复制代码
const btn1 = document.getElementById('btn1')
btn1.onclick = () => {
    alert(myAPI.version)
}

进程通信(IPC)是 Electron 中的一个重要概念,实现进程之间的通信

1)单向通信

在 main.js 中导入 ipc 模块:

javascript 复制代码
const { ipcMain } = require('electron')
function createWindow() {
    .....
    ipcMain.on('file-save', writeFile)
}
function writeFile(_, data) {
    fs.writeFileSync(
        path.join(__dirname, 'output.txt'),
        data
    )
}

在 preload.js 中设置信道方法:

javascript 复制代码
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
    version: process.version,
    saveFile: (data) => {
        ipcRenderer.send('file-save', data)
    }
})

在 render.js 中触发方法,将信息 单向 向主进程通信

javascript 复制代码
btn2.onclick = () => {
    myAPI.saveFile(input.value)
}

2)双向通信

实现原理和单向类似,只是方法细节上有所区别

javascript 复制代码
// main.js
ipcMain.handle('file-read', readFile)
....
function readFile() {
    return fs.readFileSync(
        path.join(__dirname, 'output.txt')
    ).toString()
}

// preload.js
readFile: () => {
    return ipcRenderer.invoke('file-read')
}

// render.js
btn3.onclick = async () => {
    let data = await myAPI.readFile()
    alert(data)
}

!需要注意一个 axios 上的细节:

Electron 的进程间通信是异步的

invoke() 设计就是返回 Promise 来处理这种异步性

Promise 链延续:

  • 如果不 return,Promise 链会在 preload 层中断
  • 加了 return 才能让 Promise 链延续到渲染进程

实际调用示例对比

能正常工作的版本:

javascript 复制代码
// preload.js
contextBridge.exposeInMainWorld('myAPI', {
    readFile: () => {
        return ipcRenderer.invoke('file-read') // 有 return
    }
})
// 渲染进程
const data = await myAPI.readFile() // 能获取到数据

不能工作的版本:

javascript 复制代码
// preload.js
contextBridge.exposeInMainWorld('myAPI', {
    readFile: () => {
        ipcRenderer.invoke('file-read') // 无 return
    }
})
// 渲染进程
const data = await myAPI.readFile() // data 将是 undefined

建议:永远 return IPC 调用的结果:无论是 invoke() 还是 send() 的响应

3. 打包应用

这里用到的打包方法是 electron-builder,配置麻烦,但是自由度比较高

bash 复制代码
# 安装 electron-builder
npm install electron-builder -D

完整 package.json

json 复制代码
"build": {
    // 应用程序的唯一标识符(反向域名格式)
    "appId": "com.kacat.testapp",
    
    // Windows 平台特定配置
    "win": {
        // 应用程序图标的路径(建议使用 256x256 像素的.ico文件)
        "icon": "./logo.ico",
        
        // 构建目标配置
        "target": [
            {
                // 使用 NSIS 制作安装程序(.exe)
                "target": "nsis",
                // 目标架构(仅 x64 位)
                "arch": ["x64"]
            }
        ]
    },
    
    // NSIS 安装程序配置
    "nsis": {
        // 是否一键安装(false 表示显示安装向导)
        "oneClick": false,
        // 是否为所有用户安装
        "perMachine": true,
        // 是否允许用户更改安装目录
        "allowToChangeInstallationDirectory": true
    },
    
    // Electron 下载镜像配置(使用国内淘宝镜像加速下载)
    "electronDownload": {
        "mirror": "https://npmmirror.com/mirrors/electron/"
    }
}
bash 复制代码
# 执行打包
npm run build

4. bat 脚本启动前后端

bash 复制代码
@echo off
chcp 65001 > nul
title kat-watermarking

set JAR_PATH=kat-watermarking.jar
set NGINX_PATH=nginx
set NGINX_EXEC=nginx.exe
set NGINX_URL=http://localhost:10086
set JDK_DIR=jdk-17.0.2
set JAVA_CMD="%JDK_DIR%\bin\java"
if errorlevel 1 (
    echo 错误: bat 配置有误,请检查
    pause
    exit /b 1
)

echo 正在启动 Java 应用程序...
start "JavaApp" /min cmd /c "%JAVA_CMD% -jar %JAR_PATH% && pause"
if errorlevel 1 (
    echo 错误: 启动 Java 应用程序失败
    pause
    exit /b 1
)

echo 正在启动 Nginx 服务器...

tasklist /fi "imagename eq nginx.exe" | find /i "nginx.exe" > nul
if not errorlevel 1 (
    echo 检测到已有 Nginx 正在运行,正在终止旧进程...
    taskkill /f /im nginx.exe > nul 2>&1
    timeout /t 1 /nobreak > nul
)

cd "%NGINX_PATH%"
start "" "%NGINX_EXEC%"
if errorlevel 1 (
    echo 错误: 启动 Nginx 服务器失败
    pause
    exit /b 1
)
cd ..

timeout /t 2 /nobreak > nul 

echo 正在打开 Nginx 网页...
start "" "%NGINX_URL%"
if errorlevel 1 (
    echo 警告: 无法打开浏览器,请手动访问 %NGINX_URL%
)

echo 所有服务已启动完成!
pause

taskkill /f /im nginx.exe > nul 2>&1

exit /b 0

其实没有什么技术含量,挑一下有用的讲讲吧:

  • @echo off 关闭命令本身的回显,只显示输出结果(即 echo 指明的字段)
  • chcp 65001 设置控制台字符集为 utf-8
  • > nul 重定向输出到 null(即不显示输出)
  • if errorlevel 1 () 检查上一个命令的退出代码, >=1 为执行错误
  • tasklist /fi "字段 运算符 值" 列出进程列表并条件过滤
  • | 管道符,用于将一个命令的输出传递给另一个命令作为输入
  • find /i "字符串" 查找字符串,不区分大小写
  • taskkill /f /im nginx.exe /f 强制终止 /im 按进程名称终止
  • timeout /t 1 /nobreak 暂停脚本执行 1 秒钟 /nobreak 禁止用户按键跳过等待

结语


事实证明困距离不得不睡觉有很长一段距离。

如果能习惯这个感觉还能正常工作,🤤

相关推荐
恰薯条的屑海鸥1 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习
喜欢吃燃面2 小时前
C++刷题:日期模拟(1)
c++·学习·算法
蓝婷儿4 小时前
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
开发语言·python·学习
叶子2024224 小时前
学习使用YOLO的predict函数使用
人工智能·学习·yolo
jackson凌4 小时前
【Java学习笔记】SringBuffer类(重点)
java·笔记·学习
黑客老李6 小时前
JavaSec | SpringAOP 链学习分析
java·运维·服务器·开发语言·学习·apache·memcached
海的诗篇_6 小时前
移除元素-JavaScript【算法学习day.04】
javascript·学习·算法
傍晚冰川7 小时前
FreeRTOS任务调度过程vTaskStartScheduler()&任务设计和划分
开发语言·笔记·stm32·单片机·嵌入式硬件·学习
月初,7 小时前
MongoDB学习和应用(高效的非关系型数据库)
学习·mongodb·nosql