昨天给大家介绍了 ElectronEgg
这款开源桌面应用开发框架。那么我们能不能搭配使用Python语言开发一套自己的工具箱呢!
这个毋庸置疑,接下来我们看看通过 ElectronEgg&Python
实现一个PDF工具箱。
前言
我们经常在使用一些好用的工具上,体验不是特别友好,很多采用命令行的方式。想有一个 GUI
界面,这样操作更方便,也便于其他不懂技术的同事使用。
现在市面上完全免费的PDF工具较少,要么收费,要么功能有诸多限制,在线的pdf工具也是一样的情况,有的在线是免费的,但是有些文档不便于上传到其他的服务器(安全敏感)。
预期实现的功能
- PDF合并
- PDF分割
- PDF提取图片
- PDF提取文本
- PDF转图片
- PDF加密
- PDF解密
- PDF添加水印
- PDF删除页面
技术选型
后端Python:
首先Python操作PDF的第三方API采用的是 pymupdf
库实现的。这个具体使用方法可以自行搜索,或者后续小编另写一篇文章进行介绍。
当然有条件有能力的朋友也可以使用纯Python独有的GUI库实现,比如近期文章中介绍的nicegui
开发一个界面很方便,不依赖ElectronEgg框架也是可以的。
使用Python实现功能之后,然后通过 nuitka
将 Python
文件打包成一个可执行文件,通过 ElectronEgg
进行调用。
前端ElectronEgg
前端GUI页面主要使用 Electron
+ Vue3
实现,前端和后端的通信主要使用 Node
中的 child_process
实现, 前端页面的通信使用 IPC
实现。
主要使用技术有:
- nodejs
- electron
- vue
- vue-router
- pinia
- naiveui
- python
- pymupdf
- nuitkia 打包工具
IPC通信
在 ElectronEgg
中为了安全问题,不可以直接在渲染进程中调用 Nodejs
中的相关操作。 默认情况下,渲染器进程没有权限访问 Node.js 和 Electron 模块。 作为应用开发者,您需要使用 contextBridge
API 来选择要从预加载脚本中暴露哪些 API。
例:通过系统的默认应用打开浏览PDF文件
渲染器进程到主进程(单向)
单向通信只是渲染进程发消息到主进程,不需要主进程的返回信息。
1、在主线程中通过 ipcMain.on
监听事件
index.ts 主进程中加载脚本,设置事件监听
import { app, dialog, ipcMain, shell } from 'electron'; function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } const openLocalPath = async (path:string) => { shell.openPath(path); }; app.whenReady().then(() => { ipcMain.on('shell:openPath', openLocalPath) createWindow() })
2、通过预加载脚本暴露 ipcRenderer.send
要将消息发送到上面创建的监听器,您可以使用 ipcRenderer.send
API。在您的预加载脚本中添加以下代码,向渲染器进程暴露一个全局的 window.electronAPI
变量。
const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { openLocalPath: (path) => ipcRenderer.send('openLocalPath', path) })
3、在渲染进程中调用
const setButton = document.getElementById('btn') setButton.addEventListener('click', () => { window.electronAPI.openLocalPath(path) })
例:打开文件夹获取里面的文件
渲染器进程到主进程(双向)
1、在主线程中通过 ipcMain.handle
监听事件
const openDirectory = async (): Promise<string> => { const { canceled, filePaths } = await dialog.showOpenDialog() if (!canceled) { return filePaths[0] } }; ipcMain.handle(IPC_EVENT.EVENT_DIALOG_OPENFILE, async () => openDirectory(type));
2、通过预加载脚本暴露 ipcRenderer.invoke
contextBridge.exposeInMainWorld('electronAPI', { openDirectory: async (): Promise<string> => ipcRenderer.invoke(IPC_EVENT.EVENT_DIALOG_OPENFILE) })
3、在渲染进程中调用
const handleOpenFile = async () => { const res = await window.electronAPI.openDirectory() // 获取打开的文件夹路径 }
例:主进程的错误信息发送给渲染进程,通过界面显示出来
主进程到渲染器进程
将消息从主进程发送到渲染器进程时,消息需要通过其 WebContents
实例发送到渲染器进程。 此 WebContents
实例包含一个 send
方法,其使用方式与 ipcRenderer.send
相同。
1、在主线程中通过 mainWindow.webContents.send
发送事件
// 发送主进程的错误信息给渲染进程 mainWindow.webContents.send(IPC_EVENT.EVENT_PROCESS_ERROR, result)
2、通过预加载脚本暴露 ipcRenderer.on
export const listenError = (callback: (e: IpcRendererEvent, result: ProcessResult) => void) => ipcRenderer.on(IPC_EVENT.EVENT_PROCESS_ERROR, callback);
3、在渲染进程中设置监听
onBeforeMount(() => { listenSuccess((__: IpcRendererEvent, _: ProcessResult) => { store.updateLoading(false); notification.success({ duration: 1500, content: '操作成功' }); }); })
后端 和 前端 通信
通信的格式主要使用 json
字符串,通过 Nodejs
中的 child_process
调用 命令行,监听命令行的控制台的输出信息
import { spawn } from 'child_process'; //获取命令行的路径 const resourceUrl = join(dirname(app.getPath('exe')), '/resources/toolkit/'); //调用命令,传递相关的参数 child = spawn('toolkit', [cmd, config_json], { cwd: resourceUrl }); //设置监听 child.stdout.on('data', (data) => { // 处理返回的数据 }) child.stderr.on('data', (data) => { // 错误信息 }) child.on('exit', (code) => { // 退出信息 })
在 python
中主要向控制台输出信息
def process_done(cmd): print(json.dumps({'cmd': cmd, 'status': 'done'}))
打包可执行文件
主要是使用 nuitka
工具把 Python
文件打包成一个可执行文件
nuitka --standalone --output-dir=static toolkit.pyt
自动更新功能实现
软件的自动更新主要使用 electron-updater
,主要的逻辑代码,在打包的配置文件中设置自己的更新服务器,将打包之后的文件放在自己的服务器中。
publish: [ { provider: 'generic', url: 'https://www.examle.com/apps/pdf-toolkit', }, ],
自动更新主要的监听事件
export const initUpdate = (win: BrowserWindow) => { autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = false; // 主进程监听检查更新事件 ipcMain.on(IPC_EVENT.EVENT_UPDATE_CHECKFORUPDATE, () => { autoUpdater.checkForUpdates(); }); // 主进程监听开始下载事件 ipcMain.on(IPC_EVENT.EVENT_UPDATE_DOWNLOADUPDATE, () => { autoUpdater.downloadUpdate(); }); // 检测到有可用的更新 autoUpdater.on(IPC_EVENT.EVENT_UPDATE_UPDATEAVAILABLE, (info: UpdateInfo) => { win.webContents.send(IPC_EVENT.EVENT_UPDATE_UPDATEAVAILABLE, info); }); // 下载更新进度 autoUpdater.on(IPC_EVENT.EVENT_UPDATE_DOWNLOADPROGRESS, (progressObj: ProgressInfo) => { win.webContents.send(IPC_EVENT.EVENT_UPDATE_DOWNLOADPROGRESS, progressObj); }); // 下载完成并安装 autoUpdater.on(IPC_EVENT.EVENT_UPDATE_UPDATEDOWNLOADED, () => { autoUpdater.quitAndInstall(); win.webContents.send(IPC_EVENT.EVENT_UPDATE_UPDATEDOWNLOADED); }); };
效果展示