如何使用 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。
先决条件
- 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)。
项目结构
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
主要进程:浏览器窗口和权限
main.js 是 Electron 应用程序的入口点(由 "main" 引用 package.json)。创建一个 BrowserWindow contextIsolation: true 并 nodeIntegration: false 保持渲染器沙箱化:
// main.js
const { app, BrowserWindow, session } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 900,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
// best-practice: never expose Node in renderer
contextIsolation: true,
// required for contextBridge
webSecurity: true,
},
});
// Grant camera and microphone for Dynamsoft Camera Enhancer
session.defaultSession.setPermissionRequestHandler(
(webContents, permission, callback) => {
callback(['media', 'camera', 'microphone'].includes(permission));
}
);
win.loadFile(path.join(__dirname, 'src', 'index.html'));
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
安全提示: nodeIntegration: false 对于任何加载远程内容的 Electron 应用,建议至少启用 + contextIsolation: true 选项。除非您了解 XSS 的潜在风险,否则请勿禁用这些选项。
上下文隔离和预加载脚本
预加载脚本在特权上下文中执行(在渲染页面之前且 Node.js API 可用之后)。contextBridge.exposeInMainWorld 它创建一个安全的、冻结的对象,该对象可 window.electronAPI 在渲染器中访问,而不会泄露完整的 Node.js API:
// preload.js
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
platform: process.platform,
versions: {
electron: process.versions.electron,
node: process.versions.node,
chrome: process.versions.chrome,
},
});
渲染器现在可以读取 window.electronAPI.versions.electron 并在用户界面中显示运行时版本——这对于支持和调试非常有用:
<!-- src/index.html – display Electron version in the header -->
<p class="electron-badge">🖥️ Desktop App – Electron v<span id="electron-version"></span></p>
<script>
if (window.electronAPI) {
document.getElementById('electron-version').textContent = window.electronAPI.versions.electron;
}
</script>
渲染器:正在加载 Dynamsoft SDK
DCV 包通过 jsDelivr CDN 加载,并使用
<!-- src/index.html -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dcv.bundle.min.js"></script>
<script src="utils.js"></script>
...
<script src="renderer.js"></script>
renderer.js 它与浏览器版本的原生 JavaScript 应用程序逻辑完全一致。唯一的代码路径差异在于:
- 文件路径(./full.json)解析为相对于 src/index.html--correct 符合 Electron file:// 协议。
- 该 save() 函数使用了 DOM 技巧;它在 Electron 的 Chromium 中也能正常工作。
- showMessage() 使用来自 utils.js.
在 Electron 中处理摄像头访问
默认情况下,Electron 会 session.defaultSession 拒绝所有 getUserMedia 阻止后台静默录制的请求。您的主进程必须显式授予该 media 权限类型。
setPermissionRequestHandler 每当渲染器调用时,都会触发回调函数。Dynamsoft navigator.mediaDevices.getUserMedia() Camera Enhancer 会在内部触发此回调,因此不需要渲染器端的权限代码:
// main.js – already shown above
session.defaultSession.setPermissionRequestHandler(
(webContents, permission, callback) => {
callback(['media', 'camera', 'microphone'].includes(permission));
}
);
生产提示: webContents.getURL() 在调用之前,通过检查是否与预期来源匹配来缩小权限授予范围 callback(true)。
跨平台相机行为
| 平台 | 行为 |
|---|---|
| macOS | 首次启动时系统会提示是否授予相机权限(macOS 隐私权政策要求) |
| 视窗 | 权限由 Electron 处理;默认情况下,桌面应用程序不会出现系统提示。 |
| Linux | V4L2;通常无需提示即可授予访问权限。 |
在 macOS 上,将 NSCameraUsageDescription 密钥添加到您的 Info.plist(当您在构建选项中设置它时,electron-builder mac.extendInfo 会自动处理)。
CDN 资产的内容安全策略
Electron 添加了一个默认的 CSP,会阻止外部脚本、工作进程和 WASM 线程。由于 Dynamsoft SDK 会在运行时加载工作进程脚本和 WASM 代码块,因此必须放宽此默认策略。
Content-Security-Policy 使用以下方式覆盖响应头 session.webRequest.onHeadersReceived:
// main.js
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: " +
"https://cdn.jsdelivr.net https://*.dynamsoft.com; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: " +
+
,
],
},
});
});
保存修正后的文档图像与浏览器版本的操作完全相同。Electron 的 Chromium 内核会识别该 属性并触发原生保存对话框:
// src/renderer.js – identical to browser version
async function save() {
const a = document.createElement('a');
a.href = rectifiedImage.src;
a.download = `document_${Date.now()}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
在 Electron 中,默认情况下,此操作会打开操作系统文件保存对话框,并指向用户的'下载'文件夹。如果您需要自定义路径(例如,始终保存到特定目录),请使用主进程 dialog.showSaveDialog API 并通过进程间通信 (IPC) 将路径发送回进程。
使用 electron-builder 进行构建和封装
package.json 已预先配置 electron-builder。目标:
| 平台 | 格式 | 命令 |
|---|---|---|
| 视窗 | NSIS 安装程序 | npm run dist -- --win |
| macOS | dmg | npm run dist -- --mac |
| Linux | AppImage | npm run dist -- --linux |
// package.json (excerpt)
{
"build": {
"appId": "com.dynamsoft.visionscanner",
"productName": "Vision Scanner",
"win": {
https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/electron


