182 lines
5.2 KiB
JavaScript
182 lines
5.2 KiB
JavaScript
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 很常见
|
||
// koffi 中指定 stdcall 约定需要在函数名前加 __stdcall,但不能作为字符串拼接在类型里
|
||
// 正确语法是 lib.stdcall('函数名', '返回类型', ['参数类型'...]) 或者在字符串定义中使用特定格式
|
||
// 根据 koffi 文档,字符串定义格式为: "ReturnType __stdcall FunctionName(Args...)"
|
||
|
||
return {
|
||
Syn_FindUSBReader: lib.func("int __stdcall Syn_FindUSBReader()"),
|
||
Syn_USBOpenPort: lib.func("int __stdcall Syn_USBOpenPort(int)"),
|
||
Syn_USBClosePort: lib.func("int __stdcall Syn_USBClosePort(int)"),
|
||
Syn_USBStartFindIDCard: lib.func(
|
||
"int __stdcall Syn_USBStartFindIDCard(int, uint8*, int)"
|
||
),
|
||
Syn_USBSelectIDCard: lib.func(
|
||
"int __stdcall Syn_USBSelectIDCard(int, uint8*, int)"
|
||
),
|
||
Syn_ReadMsg: lib.func(
|
||
"int __stdcall 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;
|
||
}
|
||
});
|