const { parentPort } = require("worker_threads"); const koffi = require("koffi"); const path = require("path"); const iconv = require("iconv-lite"); // 定义结构体 const IDCardData = koffi.struct("IDCardData", { Name: koffi.array("char", 32), Sex: koffi.array("char", 6), Nation: koffi.array("char", 20), Born: koffi.array("char", 18), Address: koffi.array("char", 72), IDCardNo: koffi.array("char", 38), GrantDept: koffi.array("char", 32), UserLifeBegin: koffi.array("char", 18), UserLifeEnd: koffi.array("char", 18), reserved: koffi.array("char", 38), PhotoFileName: koffi.array("char", 255), }); let lib = null; let running = false; // 解码 GBK 字符串 function decodeGBK(buffer) { // 找到第一个 null 字节 (0x00) 的位置 let nullIndex = -1; for (let i = 0; i < buffer.length; i++) { if (buffer[i] === 0) { nullIndex = i; break; } } // 如果找到了 null,截取前面的部分;否则使用整个 buffer const validBuffer = nullIndex !== -1 ? buffer.slice(0, nullIndex) : buffer; return iconv.decode(validBuffer, "gbk").trim(); } // 加载 DLL function loadDll() { if (lib) return true; try { const fs = require("fs"); const dllNames = ["Syn_IDCardRead.dll", "SynIDCardRead.dll"]; // 确定 DLL 目录:优先检查生产环境 resources/xzx,否则使用开发环境 resources/xzx let dllDir = path.join(process.cwd(), "resources", "xzx"); if (process.resourcesPath) { const prodDir = path.join(process.resourcesPath, "xzx"); if (fs.existsSync(prodDir)) { dllDir = prodDir; } } let dllPath = null; for (const name of dllNames) { const p = path.join(dllDir, name); if (fs.existsSync(p)) { dllPath = p; break; } } if (!dllPath) { throw new Error(`DLL not found in ${dllDir}`); } parentPort.postMessage({ type: "log", payload: `Loading DLL from: ${dllPath}`, }); lib = koffi.load(dllPath); // 尝试使用 stdcall 约定,这对于 Windows 32位 DLL 很常见 // 如果函数名被修饰(如 _Syn_FindUSBReader@0),__stdcall 会自动处理 const stdcall = "__stdcall"; return { Syn_FindUSBReader: lib.func(`${stdcall} int Syn_FindUSBReader()`), Syn_USBOpenPort: lib.func(`${stdcall} int Syn_USBOpenPort(int)`), Syn_USBClosePort: lib.func(`${stdcall} int Syn_USBClosePort(int)`), Syn_USBStartFindIDCard: lib.func( `${stdcall} int Syn_USBStartFindIDCard(int, uint8*, int)` ), Syn_USBSelectIDCard: lib.func( `${stdcall} int Syn_USBSelectIDCard(int, uint8*, int)` ), Syn_ReadMsg: lib.func( `${stdcall} int Syn_ReadMsg(int, int, _Out_ IDCardData*)` ), }; } catch (err) { parentPort.postMessage({ type: "error", payload: `Failed to load DLL: ${err.message}`, }); return null; } } async function startListen() { if (running) return; running = true; const api = loadDll(); if (!api) { running = false; return; } try { let port = api.Syn_FindUSBReader(); if (port <= 0) port = 1001; const openRes = api.Syn_USBOpenPort(port); if (openRes !== 0) { parentPort.postMessage({ type: "error", payload: `Open port ${port} failed: ${openRes}`, }); running = false; return; } const iin = Buffer.alloc(4); const sn = Buffer.alloc(8); // IDCardData 结构体大小计算: 32+6+20+18+72+38+32+18+18+38+255 = 547 字节 // koffi 会自动处理结构体内存分配 while (running) { // 寻卡 api.Syn_USBStartFindIDCard(port, iin, 0); // 选卡 api.Syn_USBSelectIDCard(port, sn, 0); // 读卡 const data = {}; // koffi 输出对象 const ret = api.Syn_ReadMsg(port, 0, data); if (ret === 0 && data) { const payload = { name: decodeGBK(Buffer.from(data.Name)), sex: decodeGBK(Buffer.from(data.Sex)), nation: decodeGBK(Buffer.from(data.Nation)), born: decodeGBK(Buffer.from(data.Born)), address: decodeGBK(Buffer.from(data.Address)), id_card_no: decodeGBK(Buffer.from(data.IDCardNo)), grant_dept: decodeGBK(Buffer.from(data.GrantDept)), life_begin: decodeGBK(Buffer.from(data.UserLifeBegin)), life_end: decodeGBK(Buffer.from(data.UserLifeEnd)), photo_path: decodeGBK(Buffer.from(data.PhotoFileName)), }; parentPort.postMessage({ type: "data", payload }); // 读到卡后暂停一下,避免重复读取太快 await new Promise((r) => setTimeout(r, 500)); } else { // 没读到卡,稍微等待 await new Promise((r) => setTimeout(r, 150)); } } api.Syn_USBClosePort(port); } catch (err) { parentPort.postMessage({ type: "error", payload: `Worker error: ${err.message}`, }); running = false; } } parentPort.on("message", (msg) => { if (msg === "start") { startListen(); } else if (msg === "stop") { running = false; } });