如何使用 Electron 和 JavaScript 构建桌面条形码、MRZ 和文档扫描仪
如何使用 Electron 和 JavaScript 构建桌面条形码、MRZ 和文档扫描仪
基于 Web 的视觉 SDK 与 Electron 非常契合:它们完全在 Chromium 渲染器进程中运行,无需任何原生插件,并且可以作为独立的桌面应用程序分发到 Windows、macOS 和 Linux 平台。Dynamsoft Capture Vision (DCV) SDK 底层使用 WebAssembly,这使其成为 Electron 沙盒渲染器的理想之选。
本教程将引导您完成将 Dynamsoft 条形码、MRZ 和文档扫描功能封装到可用于生产环境的 Electron 外壳中的步骤。您将学习 Electron 进程模型的工作原理、如何授予摄像头访问权限、如何放宽 CDN 资源的 Content-Security-Policy 标头,以及如何使用electron-builder.
演示视频:Electron 条形码、MRZ 和文档检测
先决条件
- Node.js 18+和npm 9+
- Dynamsoft免费试用许可证密钥
理解电子过程模型
Electron 将你的应用程序拆分成两种类型的进程:
| 过程 | 角色 | 使用权 |
|---|---|---|
| 主要流程 | Node.js;管理窗口、操作系统 API 和权限 | 完整的 Node.js + Electron API |
| 渲染进程 | Chromium;每个BrowserWindow;渲染 HTML/JS | 仅限 Web API(除非显式桥接) |
Dynamsoft SDK 完全在渲染器内部运行。主进程负责:
- 创建具有正确窗口
webPreferences - 授予
getUserMedia(相机)权限 - 修改响应头以放宽默认内容安全策略 (CSP)。
项目结构
<span><span><span><code>electron/ ├── main.js # Electron main process ├── preload.js # Context bridge – runs before renderer ├── package.json └── src/ ├── index.html # Renderer HTML ├── renderer.js # All scanning / SDK logic ├── utils.js # MRZ helper ├── styles.css └── full.json # DCV MRZ capture template </code></span></span></span>主要进程:浏览器窗口和权限
main.js是 Electron 应用程序的入口点(由"main"`<input>`引用package.json)。创建一个 ` BrowserWindow<input>`contextIsolation: true并nodeIntegration: false保持渲染器沙箱化:
<span><span><span><code><span><em>// main.js</em></span> <span><strong>const</strong></span> { app, BrowserWindow, session } <span>=</span> <span>require</span>('<span>electron</span>'); <span><strong>const</strong></span> path <span>=</span> <span>require</span>('<span>path</span>'); <span><strong>function</strong></span> <span>createWindow</span>() { <span><strong>const</strong></span> win <span>=</span> <span><strong>new</strong></span> <span><strong>BrowserWindow</strong></span>({ <span>width</span>: <span>1280</span>, <span>height</span>: <span>900</span>, <span>webPreferences</span>: { <span>preload</span>: path.<span>join</span>(__dirname, '<span>preload.js</span>'), <span>nodeIntegration</span>: <span><strong>false</strong></span>, <span><em>// best-practice: never expose Node in renderer</em></span> <span>contextIsolation</span>: <span><strong>true</strong></span>, <span><em>// required for contextBridge</em></span> <span>webSecurity</span>: <span><strong>true</strong></span>, }, }); <span><em>// Grant camera and microphone for Dynamsoft Camera Enhancer</em></span> session.defaultSession.<span>setPermissionRequestHandler</span>( (webContents, permission, callback) <span>=></span> { <span>callback</span>(['<span>media</span>', '<span>camera</span>', '<span>microphone</span>'].<span>includes</span>(permission)); } ); win.<span>loadFile</span>(path.<span>join</span>(__dirname, '<span>src</span>', '<span>index.html</span>')); } app.<span>whenReady</span>().<span>then</span>(createWindow); app.<span>on</span>('<span>window-all-closed</span>', () <span>=></span> { <span><strong>if </strong></span>(process.platform <span>!==</span> '<span>darwin</span>') app.<span>quit</span>(); }); app.<span>on</span>('<span>activate</span>', () <span>=></span> { <span><strong>if </strong></span>(BrowserWindow.<span>getAllWindows</span>().length <span>===</span> <span>0</span>) <span>createWindow</span>(); }); </code></span></span></span>安全提示:nodeIntegration: false对于任何加载远程内容的 Electron 应用,建议至少启用“+”contextIsolation: true选项。除非您了解 XSS 的潜在风险,否则请勿禁用这些选项。
上下文隔离和预加载脚本
预加载脚本在特权上下文中执行(在渲染页面之前且 Node.js API 可用之后)。contextBridge.exposeInMainWorld它创建一个安全的、冻结的对象,该对象可window.electronAPI在渲染器中访问,而不会泄露完整的 Node.js API:
<span><span><span><code><span><em>// preload.js</em></span> <span><strong>const</strong></span> { contextBridge } <span>=</span> <span>require</span>('<span>electron</span>'); contextBridge.<span>exposeInMainWorld</span>('<span>electronAPI</span>', { <span>platform</span>: process.platform, <span>versions</span>: { <span>electron</span>: process.versions.electron, <span>node</span>: process.versions.node, <span>chrome</span>: process.versions.chrome, }, }); </code></span></span></span>渲染器现在可以读取window.electronAPI.versions.electron并在用户界面中显示运行时版本——这对于支持和调试非常有用:
<span><span><span><code><span><em><!-- src/index.html – display Electron version in the header --></em></span> <span><strong><p</strong></span> <span>class=</span><span>"electron-badge"</span><span><strong>></strong></span> 🖥️ Desktop App – Electron v<span><strong><span</strong></span> <span>id=</span><span>"electron-version"</span><span><strong>></span></strong></span> <span><strong></p></strong></span> <span><strong><script></strong></span> <span><strong>if </strong></span>(<span>window</span>.electronAPI) { <span>document</span>.<span>getElementById</span>('<span>electron-version</span>').textContent <span>=</span> <span>window</span>.electronAPI.versions.electron; } <span><strong></script></strong></span> </code></span></span></span>渲染器:正在加载 Dynamsoft SDK
DCV 包通过 jsDelivr CDN 加载,并使用<script>标签src/index.html。由于 Electronfile://默认使用 origin,一些浏览器会阻止混合内容请求,但 Electron 的 Chromium 会file://在主进程修改Content-Security-Policy响应头时放宽对 origin 的限制(参见CSP 部分):
<span><span><span><code><span><em><!-- src/index.html --></em></span> <span><strong><script </strong></span><span>src=</span><span>"https://cdn.jsdelivr.net/npm/[email protected]/dist/dcv.bundle.min.js"</span><span><strong>></script></strong></span> <span><strong><script </strong></span><span>src=</span><span>"utils.js"</span><span><strong>></script></strong></span> ... <span><strong><script </strong></span><span>src=</span><span>"renderer.js"</span><span><strong>></script></strong></span> </code></span></span></span>renderer.js它与浏览器版本的原生 JavaScript 应用程序逻辑完全一致。唯一的代码路径差异在于:
- 文件路径(
./full.json)解析为相对于src/index.html--correct 符合 Electronfile://协议。 - 该
save()函数使用了<a download>DOM 技巧;它在 Electron 的 Chromium 中也能正常工作。 showMessage()使用来自utils.js.
在 Electron 中处理摄像头访问
默认情况下,Electron 会session.defaultSession拒绝所有getUserMedia阻止后台静默录制的请求。您的主进程必须显式授予该media权限类型。
setPermissionRequestHandler每当渲染器调用时,都会触发回调函数。Dynamsoft navigator.mediaDevices.getUserMedia()Camera Enhancer 会在内部触发此回调,因此不需要渲染器端的权限代码:
<span><span><span><code><span><em>// main.js – already shown above</em></span> session.defaultSession.<span>setPermissionRequestHandler</span>( (webContents, permission, callback) <span>=></span> { <span>callback</span>(['<span>media</span>', '<span>camera</span>', '<span>microphone</span>'].<span>includes</span>(permission)); } ); </code></span></span></span>生产提示:webContents.getURL()在调用之前,通过检查是否与预期来源匹配来缩小权限授予范围callback(true)。
跨平台相机行为
| 平台 | 行为 |
|---|---|
| macOS | 首次启动时系统会提示是否授予相机权限(macOS隐私权政策要求) |
| 视窗 | 权限由 Electron 处理;默认情况下,桌面应用程序不会出现系统提示。 |
| Linux | V4L2;通常无需提示即可授予访问权限。 |
在 macOS 上,将NSCameraUsageDescription密钥添加到您的(当您在构建选项中设置它时,Info.plist会自动处理)。electron-buildermac.extendInfo
CDN资产的内容安全策略
Electron 添加了一个默认的 CSP,会阻止外部脚本、工作进程和 WASM 线程。由于 Dynamsoft SDK 会在运行时加载工作进程脚本和 WASM 代码块,因此必须放宽此默认策略。
Content-Security-Policy使用以下方式覆盖响应头session.webRequest.onHeadersReceived:
<span><span><span><code><span><em>// main.js</em></span> session.defaultSession.webRequest.<span>onHeadersReceived</span>((details, callback) <span>=></span> { <span>callback</span>({ <span>responseHeaders</span>: { ...details.responseHeaders, '<span>Content-Security-Policy</span>': [ "<span>default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: </span>" <span>+</span> "<span>https://cdn.jsdelivr.net https://*.dynamsoft.com; </span>" <span>+</span> "<span>script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: </span>" <span>+</span> "<span>https://cdn.jsdelivr.net https://*.dynamsoft.com; </span>" <span>+</span> "<span>worker-src 'self' blob:;</span>", ], }, }); }); </code></span></span></span>指令解释:
| 指示 | 目的 |
|---|---|
'unsafe-inline' | 内联<script>,<style>并由 SDK UI 组件使用 |
'unsafe-eval' | instantiate某些 Chromium 配置中的WASM路径 |
blob: | blob:WASM 工作线程以URL 的形式生成。 |
https://cdn.jsdelivr.net | DCV捆绑包的CDN源 |
https://*.dynamsoft.com | Dynamsoft 许可证服务器和模型下载来源 |
生产环境加固:如果您自行托管 DCV 软件包和模型文件,则可以移除 CDN 源'unsafe-eval'。您还可以添加一个nonce或hash来替换'unsafe-inline'内联脚本。
通过 DOM 锚点保存文档
保存修正后的文档图像与浏览器版本的操作完全相同。Electron 的 Chromium 内核会识别该<a download>属性并触发原生保存对话框:
<span><span><span><code><span><em>// src/renderer.js – identical to browser version</em></span> <span><strong>async</strong></span> <span><strong>function</strong></span> <span>save</span>() { <span><strong>const</strong></span> a <span>=</span> <span>document</span>.<span>createElement</span>('<span>a</span>'); a.href <span>=</span> rectifiedImage.src; a.download <span>=</span> <span>`document_</span>${<span>Date</span>.<span>now</span>()}<span>.png`</span>; <span>document</span>.body.<span>appendChild</span>(a); a.<span>click</span>(); <span>document</span>.body.<span>removeChild</span>(a); } </code></span></span></span>在 Electron 中,默认情况下,此操作会打开操作系统文件保存对话框,并指向用户的“下载”文件夹。如果您需要自定义路径(例如,始终保存到特定目录),请使用主进程dialog.showSaveDialogAPI 并通过进程间通信 (IPC) 将路径发送回进程。
使用 electron-builder 进行构建和封装
package.json已预先配置electron-builder。目标:
| 平台 | 格式 | 命令 |
|---|---|---|
| 视窗 | NSIS 安装程序 | npm run dist -- --win |
| macOS | 伤害 | npm run dist -- --mac |
| Linux | AppImage | npm run dist -- --linux |
<span><span><span><code>// package.json (excerpt) { <span>"build"</span>: { <span>"appId"</span>: <span>"com.dynamsoft.visionscanner"</span>, <span>"productName"</span>: <span>"Vision Scanner"</span>, <span>"win"</span>: { <span>"target"</span>: <span>"nsis"</span> }, <span>"mac"</span>: { <span>"target"</span>: <span>"dmg"</span> }, <span>"linux"</span>: { <span>"target"</span>: <span>"AppImage"</span> } } } </code></span></span></span>针对当前平台构建:
<span><span><span><code>npm run dist </code></span></span></span>构建完成后的工件会出现在dist/目录中。Dynamsoft SDK 包会在运行时从 CDN 加载,从而保持分发包体积较小。如需完全离线分发,请将包复制到指定位置src/并<script src>在构建前更新相关设置。

源代码
https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/electron