idcard-reader
Version:
通过二代身份证机具读取二代身份证信息
143 lines (142 loc) • 5.33 kB
JavaScript
import { composite, formatBaseData, initialDataBase, initialIDData, parseCompositeOpts, parseDeviceOpts, testWrite, validateDllFile, } from '@waiting/idcard-reader-base';
import { error, info } from '@waiting/log';
import { createFileAsync, fileExists, isFileExists, join, normalize, } from '@waiting/shared-core';
import * as ffi from 'ffi-napi';
import { combineLatest, iif, of, } from 'rxjs';
import { catchError, map, mergeMap, shareReplay, tap, timeout, } from 'rxjs/operators';
import { dllFuncs, dllImgFuncs, } from './config';
import { disconnectDevice, findDeviceList, readDataBase } from './device';
export async function init(options) {
const deviceOpts = parseDeviceOpts(options);
const compositeOpts = parseCompositeOpts(options);
const { debug } = deviceOpts;
if (debug) {
info(compositeOpts);
info(deviceOpts);
}
await validateDllFile(deviceOpts.dllTxt);
// 不允许 未指定照片解码dll
if (compositeOpts.useComposite) {
if (!deviceOpts.dllImage) {
throw new Error('Value of dellImage empty');
}
else if (!await isFileExists(deviceOpts.dllImage)) {
throw new Error('File not exists: ' + deviceOpts.dllImage);
}
}
await 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 */
export function read(device) {
if (device.openPort) {
disconnectDevice(device);
// 读卡获取原始buffer数据
const raw$ = readDataBase(device).pipe(shareReplay(1));
// 生成 base 数据
const base$ = raw$.pipe(tap((raw) => {
if (raw.err) {
throw new Error('读卡失败:code:' + raw.code.toString());
}
}), map((raw) => {
const base = pickFields(raw && raw.text.byteLength ? raw.text.toString('ucs2') : '');
return base;
}), shareReplay(1));
// 解码头像
const imagePath$ = raw$.pipe(mergeMap(raw => retriveAvatar(raw.image, device)), mergeMap((imagePath) => {
return fileExists(imagePath).pipe(tap((path) => {
if (!path) {
error(`解码头像文件失败 path: "${imagePath}"`);
}
}));
}));
// 合成图片
const imgsPath$ = iif(() => !device.compositeOpts.useComposite, of({
compositePath: '',
imagePath: '',
}), combineLatest(base$, imagePath$).pipe(mergeMap(([base, imagePath]) => {
return composite(imagePath, base, device.compositeOpts).pipe(map((compositePath) => {
return {
compositePath,
imagePath,
};
}));
})));
const ret$ = combineLatest(base$, imgsPath$).pipe(tap(() => {
disconnectDevice(device);
}), map(([base, imgsPath]) => {
const ret = {
...initialIDData,
...imgsPath,
base,
};
return ret;
}), timeout(20000), catchError((err) => {
disconnectDevice(device);
throw err;
}));
return ret$.toPromise();
}
else {
throw new Error('设备端口未指定');
}
}
/** pick fields from origin text */
export function pickFields(text) {
const ret = { ...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 formatBaseData(ret);
}
/**
* 解码读取到的头像 Buffer,返回生成的图片路径
*/
export 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 = join(device.deviceOpts.imgSaveDir, _genImageName('idcrimage_'));
const tmpname = name + '.wlt';
if (!device.apii) {
return '';
}
await createFileAsync(tmpname, buf);
// covert wlt file to bmp
const res = device.apii.GetBmp(tmpname, device.useUsb ? 2 : 1);
device.deviceOpts.debug && info(['resolve image res:', res]);
if (res === 1) {
const ipath = 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}`;
}