idcard-reader
Version:
通过二代身份证机具读取二代身份证信息
400 lines (391 loc) • 13.7 kB
JavaScript
/**
* idcard-reader
* 通过二代身份证机具读取二代身份证信息
*
* @version 5.0.0
* @author waiting
* @license MIT
* @link https://github.com/waitingsong/node-idcard-reader#readme
*/
;
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