Files
yuanhe-checkin-electron/electron/idcard-worker.js
T
2025-11-21 17:59:46 +08:00

248 lines
6.7 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", {
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;
}
});