const { parentPort } = require("worker_threads"); const koffi = require("koffi"); const path = require("path"); const iconv = require("iconv-lite"); // 定义结构体 (使用原始字节数组,避免对齐问题,手动解析) const IDCardData = koffi.struct("IDCardData", { raw: koffi.array("uint8", 1024), // 分配足够大的空间 }); let lib = null; let apiCache = null; let running = false; // 解码字符串 (GBK) function decodeString(buffer) { // 这里的 buffer 是 uint8 数组 或 Buffer const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer); // 找到第一个 null 字节 (0x00) 的位置 let nullIndex = -1; for (let i = 0; i < buf.length; i++) { if (buf[i] === 0) { nullIndex = i; break; } } const validBuf = nullIndex !== -1 ? buf.slice(0, nullIndex) : buf; return iconv.decode(validBuf, "gbk").trim(); } // 加载 DLL function loadDll() { if (apiCache) return apiCache; 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 很常见 // koffi 2.x 版本中,stdcall 约定需要在函数名前加 __stdcall // 如果字符串解析失败,可以尝试使用 lib.stdcall() 方法 // 辅助函数:尝试加载函数,支持原名和修饰名 const loadFunc = (name, alias, ret, args) => { try { return lib.stdcall(name, ret, args); } catch (e) { try { return lib.stdcall(alias, ret, args); } catch (e2) { throw new Error( `Cannot find function '${name}' or '${alias}' in DLL` ); } } }; try { apiCache = { Syn_OpenPort: loadFunc("Syn_OpenPort", "_Syn_OpenPort@4", "int", [ "int", ]), Syn_ClosePort: loadFunc("Syn_ClosePort", "_Syn_ClosePort@4", "int", [ "int", ]), Syn_StartFindIDCard: loadFunc( "Syn_StartFindIDCard", "_Syn_StartFindIDCard@12", "int", ["int", "uint8*", "int"] ), Syn_SelectIDCard: loadFunc( "Syn_SelectIDCard", "_Syn_SelectIDCard@12", "int", ["int", "uint8*", "int"] ), Syn_ReadMsg: loadFunc("Syn_ReadMsg", "_Syn_ReadMsg@12", "int", [ "int", "int", koffi.out(koffi.pointer(IDCardData)), ]), }; return apiCache; } catch (e) { throw e; } } 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 { // 尝试打开端口,优先尝试 1001 (USB) let port = 1001; let openRes = api.Syn_OpenPort(port); if (openRes !== 0) { // 如果 1001 失败,尝试 1001-1016 for (let p = 1002; p <= 1016; p++) { if (api.Syn_OpenPort(p) === 0) { port = p; openRes = 0; break; } } } if (openRes !== 0) { parentPort.postMessage({ type: "error", payload: `Open port failed (tried 1001-1016)`, }); running = false; return; } parentPort.postMessage({ type: "log", payload: `Port ${port} opened successfully`, }); 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_StartFindIDCard(port, iin, 0); // 选卡 api.Syn_SelectIDCard(port, sn, 0); // 读卡 const data = {}; // koffi 输出对象 const ret = api.Syn_ReadMsg(port, 0, data); if (ret === 0 && data && data.raw) { const rawBuf = Buffer.from(data.raw); // 调试:打印前 64 字节 Hex // parentPort.postMessage({ // type: "log", // payload: `Raw Hex (First 64): ${rawBuf.slice(0, 64).toString("hex")}`, // }); // 手动切片解析 (修正后的偏移) // Name: 0-32 (32) // Sex: 32-36 (4) // Nation: 36-42 (6) // Born: 42-60 (18) // Address: 60-132 (72) // IDCardNo: 132-170 (38) // GrantDept: 170-202 (32) // UserLifeBegin: 202-220 (18) // UserLifeEnd: 220-238 (18) // PhotoFileName: 276-531 (255) const payload = { name: decodeString(rawBuf.slice(0, 32)), sex: decodeString(rawBuf.slice(32, 36)), nation: decodeString(rawBuf.slice(36, 42)), born: decodeString(rawBuf.slice(42, 60)), address: decodeString(rawBuf.slice(60, 132)), id_card_no: decodeString(rawBuf.slice(132, 170)), grant_dept: decodeString(rawBuf.slice(170, 202)), life_begin: decodeString(rawBuf.slice(202, 220)), life_end: decodeString(rawBuf.slice(220, 238)), photo_path: decodeString(rawBuf.slice(276, 531)), }; parentPort.postMessage({ type: "log", payload: `Read IDCard success: ${payload.name} ${payload.id_card_no}`, }); // 打印完整 payload 以便调试 parentPort.postMessage({ type: "log", payload: `Full Payload: ${JSON.stringify(payload)}`, }); parentPort.postMessage({ type: "data", payload }); // 读到卡后暂停一下,避免重复读取太快 await new Promise((r) => setTimeout(r, 500)); } else { // 没读到卡,稍微等待 await new Promise((r) => setTimeout(r, 150)); } } api.Syn_ClosePort(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; } });