UNPKG

idcard-reader

Version:

通过二代身份证机具读取二代身份证信息

400 lines (391 loc) 13.7 kB
/** * idcard-reader * 通过二代身份证机具读取二代身份证信息 * * @version 5.0.0 * @author waiting * @license MIT * @link https://github.com/waitingsong/node-idcard-reader#readme */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var sharedCore = require('@waiting/shared-core'); var idcardReaderBase = require('@waiting/idcard-reader-base'); var log = require('@waiting/log'); var ffi = require('ffi-napi'); var rxjs = require('rxjs'); var operators = require('rxjs/operators'); const config = { appDir: '', tmpDir: sharedCore.join(sharedCore.tmpdir(), 'idcard-reader'), }; const dllFuncs = { /** 查找设备端口 */ SDT_OpenPort: ['int', ['int']], /** 关闭端口 */ SDT_ClosePort: ['int', ['int']], /** 找卡 port,0,0 */ SDT_StartFindIDCard: ['int', ['int', 'pointer', 'int']], /** 选卡 */ SDT_SelectIDCard: ['int', ['int', 'pointer', 'int']], /** 读取基础信息 */ SDT_ReadBaseMsg: ['int', ['int', 'pointer', 'pointer', 'pointer', 'pointer', 'int']], /** 对 SAM 进行状态检测 */ SDT_GetSAMStatus: ['int', ['int', 'int']], /** 读取SAM_V的编号 返回值0x90-成功,其他-失败 */ SDT_GetSAMIDToStr: ['int', ['int', 'pointer', 'int']], /** 读取追加信息 */ SDT_ReadNewAppMsg: ['int', ['int', 'pointer', 'pointer', 'int']], // SDT_ReadAllAppMsg: ['int', ['int', 'pointer', 'pointer', 'int'] ], /** 重置SAM */ SDT_ResetSAM: ['int', ['int', 'int']], }; const dllImgFuncs = { /** 解码头像 */ GetBmp: ['int', ['string', 'int']], }; function connectDevice(device, port) { if (device && device.inUse) { device.deviceOpts.debug && log.info('Cautiton: connectDevice() device in use'); return 0; } const openRet = device.apib.SDT_OpenPort(port); device.deviceOpts.debug && log.info(`open device port ret: ${openRet}`); return openRet === 144 ? port : 0; } function disconnectDevice(device) { const ret = device.apib.SDT_ClosePort(device.openPort); device.deviceOpts.debug && log.info(`disconnectDevice at port: ${device.openPort}, ret: ${ret} `); device.inUse = false; return ret === 144; } function resetDevice(device, port) { if (port && port > 0) { const ret = device.apib.SDT_ResetSAM(port, 0); log.info(`reset ${port} ret: ${ret}`); } else { for (let i = 1; i <= 16; i += 1) { const ret = device.apib.SDT_ResetSAM(i, 0); log.info(`reset ${i} ret: ${ret}`); } for (let i = 1001; i <= 1016; i += 1) { const ret = device.apib.SDT_ResetSAM(i, 0); log.info(`reset ${i} ret: ${ret}`); } } device.deviceOpts.debug && log.info(`reset device at port: ${device.openPort}`); device.inUse = false; } function findDeviceList(deviceOpts, compositeOpts, apib) { const arr = []; if (deviceOpts.port > 0) { // 仅USB接口 const device = findDevice(deviceOpts.port, deviceOpts, compositeOpts, apib, true); if (device.openPort > 0) { arr.push(device); } } else { // 必须先检测usb端口 for (let i = 1001; i <= 1016; i += 1) { const device = findDevice(i, deviceOpts, compositeOpts, apib, true); if (device.openPort > 0) { // device.simid = getSamid(device) arr.push(device); if (!deviceOpts.searchAll) { break; } } } if (!deviceOpts.searchAll && arr.length) { return arr; } // 检测串口 for (let i = 1; i <= 16; i += 1) { const device = findDevice(i, deviceOpts, compositeOpts, apib, false); if (device.openPort > 0) { arr.push(device); if (!deviceOpts.searchAll) { break; } } } } return arr; } function findDevice(openPort, deviceOpts, compositeOpts, apib, useUsb) { const device = { apib, apii: null, deviceOpts, compositeOpts, inUse: false, openPort: 0, useUsb, }; const port = connectDevice(device, openPort); if (port > 0) { device.inUse = true; device.openPort = port; deviceOpts.debug && log.info(`Found device at serial/usb port: ${port}`); disconnectDevice(device); } return device; } /** 读取二代证基础信息 */ function readDataBase(device) { const path = sharedCore.dirname(device.deviceOpts.dllTxt); process.env.PATH = `${process.env.PATH};${path}`; // const srcDir = path.replace(/\\/g, '/') + '/' // const targetPath = normalize(device.deviceOpts.imgSaveDir + '/').replace(/\\/g, '/') if (device.deviceOpts.debug) { log.info('starting reading readCard '); // info('IDCard_GetInformation() src path:' + srcDir) // info('IDCard_GetInformation() target path:' + targetPath) } const open = connectDevice(device, device.openPort); if (!open) { throw new Error(`打开端口失败 readDataBase() port: ${device.openPort}`); } const cardReady$ = findCard(device).pipe(operators.mergeMap((found) => { if (found) { return rxjs.of(selectCard(device)).pipe(operators.timeout(1500)); } else { throw new Error('findCard() 未能找到指定设备'); } }), operators.tap((ready) => { if (!ready) { throw new Error('二代证无效,请确保证件处于机具读卡区域内'); } })); const ret$ = cardReady$.pipe(operators.map(() => readCard(device)), operators.tap((raw) => { if (device.deviceOpts.debug) { // info(`readDataBase bufLen: ${buf.byteLength}`) log.info('readDataBase ret'); log.info(raw); } })); return ret$; } /** 检测卡是否可读取状态 */ function findCard(device) { const { findCardRetryTimes } = device.deviceOpts; const findRet$ = rxjs.range(0, findCardRetryTimes > 0 ? findCardRetryTimes + 1 : 1).pipe(operators.concatMap((_, index) => { if (index > 0 && index >= findCardRetryTimes) { throw new Error(`findCard fail over ${findCardRetryTimes} times`); } // 移动中读取到卡 延迟执行选卡 const delay$ = rxjs.timer(index === 0 ? 0 : 1000); return delay$.pipe(operators.mergeMap(() => rxjs.of(_findCard(device)))); })); const ret$ = findRet$.pipe(operators.tap((ret) => { device.deviceOpts.debug && log.info(`findStatus: ${ret}`); }), operators.filter(ret => ret === 159), operators.take(1), operators.defaultIfEmpty(0), operators.map(ret => ret > 0)); return ret$; } function _findCard(device) { try { const buf = Buffer.alloc(4); return device.apib.SDT_StartFindIDCard(device.openPort, buf, 1); } catch (ex) { device.deviceOpts.debug && log.error(ex); return 0; } } /** 选卡 */ function selectCard(device) { const buf = Buffer.alloc(4); const res = device.apib.SDT_SelectIDCard(device.openPort, buf, 1); return res === 144; } function readCard(device) { const opts = { pucCHMsg: Buffer.alloc(1024), puiCHMsgLen: Buffer.from([1024]), pucPHMsg: Buffer.alloc(1024), puiPHMsgLen: Buffer.from([1024]), }; // console.log(opts) const data = { err: 1, code: 0, text: opts.pucCHMsg, image: opts.pucPHMsg, imagePath: '', }; try { data.code = device.apib.SDT_ReadBaseMsg(device.openPort, opts.pucCHMsg, opts.puiCHMsgLen, opts.pucPHMsg, opts.puiPHMsgLen, 1); } catch (ex) { console.error(ex); } if (data.code === 144) { data.err = 0; } else { resetDevice(device, device.openPort); } return data; } async function init(options) { const deviceOpts = idcardReaderBase.parseDeviceOpts(options); const compositeOpts = idcardReaderBase.parseCompositeOpts(options); const { debug } = deviceOpts; if (debug) { log.info(compositeOpts); log.info(deviceOpts); } await idcardReaderBase.validateDllFile(deviceOpts.dllTxt); // 不允许 未指定照片解码dll if (compositeOpts.useComposite) { if (!deviceOpts.dllImage) { throw new Error('Value of dellImage empty'); } else if (!await sharedCore.isFileExists(deviceOpts.dllImage)) { throw new Error('File not exists: ' + deviceOpts.dllImage); } } await idcardReaderBase.testWrite(deviceOpts.imgSaveDir); const apib = ffi.Library(deviceOpts.dllTxt, dllFuncs); const devices = findDeviceList(deviceOpts, compositeOpts, apib); if (devices && devices.length) { debug && console.info('Found devices:', devices); return devices; } else { throw new Error('未找到读卡设备'); } } /** Read card data */ function read(device) { if (device.openPort) { disconnectDevice(device); // 读卡获取原始buffer数据 const raw$ = readDataBase(device).pipe(operators.shareReplay(1)); // 生成 base 数据 const base$ = raw$.pipe(operators.tap((raw) => { if (raw.err) { throw new Error('读卡失败:code:' + raw.code.toString()); } }), operators.map((raw) => { const base = pickFields(raw && raw.text.byteLength ? raw.text.toString('ucs2') : ''); return base; }), operators.shareReplay(1)); // 解码头像 const imagePath$ = raw$.pipe(operators.mergeMap(raw => retriveAvatar(raw.image, device)), operators.mergeMap((imagePath) => { return sharedCore.fileExists(imagePath).pipe(operators.tap((path) => { if (!path) { log.error(`解码头像文件失败 path: "${imagePath}"`); } })); })); // 合成图片 const imgsPath$ = rxjs.iif(() => !device.compositeOpts.useComposite, rxjs.of({ compositePath: '', imagePath: '', }), rxjs.combineLatest(base$, imagePath$).pipe(operators.mergeMap(([base, imagePath]) => { return idcardReaderBase.composite(imagePath, base, device.compositeOpts).pipe(operators.map((compositePath) => { return { compositePath, imagePath, }; })); }))); const ret$ = rxjs.combineLatest(base$, imgsPath$).pipe(operators.tap(() => { disconnectDevice(device); }), operators.map(([base, imgsPath]) => { const ret = { ...idcardReaderBase.initialIDData, ...imgsPath, base, }; return ret; }), operators.timeout(20000), operators.catchError((err) => { disconnectDevice(device); throw err; })); return ret$.toPromise(); } else { throw new Error('设备端口未指定'); } } /** pick fields from origin text */ function pickFields(text) { const ret = { ...idcardReaderBase.initialDataBase }; if (!text || !text.length) { return ret; } ret.name = text.slice(0, 15).trim(); ret.gender = +text.slice(15, 16); ret.nation = text.slice(16, 18); // 民族 ret.birth = text.slice(18, 26); // 16 ret.address = text.slice(26, 61).trim(); // 70 ret.idc = text.slice(61, 79); // 身份证号 ret.regorg = text.slice(79, 94).trim(); // 签发机关 ret.startdate = text.slice(94, 102); ret.enddate = text.slice(102, 110); return idcardReaderBase.formatBaseData(ret); } /** * 解码读取到的头像 Buffer,返回生成的图片路径 */ function retriveAvatar(image, device) { const opts = device.deviceOpts; device.apii = ffi.Library(opts.dllImage, dllImgFuncs); return decodeImage(device, image); } async function decodeImage(device, buf) { // console.log(buf.slice(0, 10)) const name = sharedCore.join(device.deviceOpts.imgSaveDir, _genImageName('idcrimage_')); const tmpname = name + '.wlt'; if (!device.apii) { return ''; } await sharedCore.createFileAsync(tmpname, buf); // covert wlt file to bmp const res = device.apii.GetBmp(tmpname, device.useUsb ? 2 : 1); device.deviceOpts.debug && log.info(['resolve image res:', res]); if (res === 1) { const ipath = sharedCore.normalize(name + '.bmp'); return ipath; } else { return ''; } } function _genImageName(prefix) { const dd = new Date(); const mon = dd.getMonth(); const day = dd.getDate(); const rstr = Math.random().toString().slice(-8); return `${prefix}${dd.getFullYear()}${mon > 9 ? mon : '0' + mon.toString()}${day > 9 ? day : '0' + day.toString()}_${rstr}`; } // base directory of this module config.appDir = sharedCore.join(__dirname, '/..'); Object.defineProperty(exports, 'initialCompositeOpts', { enumerable: true, get: function () { return idcardReaderBase.initialCompositeOpts; } }); Object.defineProperty(exports, 'initialOpts', { enumerable: true, get: function () { return idcardReaderBase.initialOpts; } }); Object.defineProperty(exports, 'nationMap', { enumerable: true, get: function () { return idcardReaderBase.nationMap; } }); exports.init = init; exports.pickFields = pickFields; exports.read = read; exports.retriveAvatar = retriveAvatar; //# sourceMappingURL=index.cjs.js.map