id-scanner-lib 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,30 @@
22
22
  this.config = {};
23
23
  /** 配置变更回调 */
24
24
  this.changeCallbacks = new Map();
25
+ /** 初始化状态 */
26
+ this.initialized = false;
25
27
  // 设置默认配置
28
+ this._resetDefaults();
29
+ }
30
+ /**
31
+ * 获取单例实例
32
+ */
33
+ static getInstance() {
34
+ if (!ConfigManager.instance) {
35
+ ConfigManager.instance = new ConfigManager();
36
+ }
37
+ return ConfigManager.instance;
38
+ }
39
+ /**
40
+ * 重置单例实例(主要用于测试)
41
+ */
42
+ static resetInstance() {
43
+ ConfigManager.instance = undefined;
44
+ }
45
+ /**
46
+ * 重置为默认配置
47
+ */
48
+ _resetDefaults() {
26
49
  this.config = {
27
50
  debug: false,
28
51
  logLevel: 'info',
@@ -38,15 +61,7 @@
38
61
  useCache: true
39
62
  }
40
63
  };
41
- }
42
- /**
43
- * 获取单例实例
44
- */
45
- static getInstance() {
46
- if (!ConfigManager.instance) {
47
- ConfigManager.instance = new ConfigManager();
48
- }
49
- return ConfigManager.instance;
64
+ this.initialized = true;
50
65
  }
51
66
  /**
52
67
  * 获取配置值
@@ -86,7 +101,7 @@
86
101
  */
87
102
  reset() {
88
103
  const oldConfig = { ...this.config };
89
- // 重新创建默认配置
104
+ // 使用私有 reset 方法重建默认配置
90
105
  this.config = {
91
106
  debug: false,
92
107
  logLevel: 'info',
@@ -382,6 +397,16 @@
382
397
  return;
383
398
  const entriesToSend = [...this.queue];
384
399
  this.queue = [];
400
+ // 防止在 fetch 失败时无限重试
401
+ const sendCount = this._sendCount || 0;
402
+ this._sendCount = sendCount + 1;
403
+ // 如果发送次数过多,停止发送以防止无限循环
404
+ if (sendCount > 10) {
405
+ console.warn('RemoteLogHandler: Too many failed sends, stopping. Clear queue.');
406
+ this.queue = [];
407
+ this._sendCount = 0;
408
+ return;
409
+ }
385
410
  try {
386
411
  fetch(this.endpoint, {
387
412
  method: 'POST',
@@ -391,12 +416,17 @@
391
416
  body: JSON.stringify(entriesToSend),
392
417
  // 不等待响应,避免阻塞
393
418
  keepalive: true
394
- }).catch(err => {
419
+ }).catch((err) => {
395
420
  console.error('Failed to send logs to remote server:', err);
396
421
  // 失败时把日志放回队列,但防止无限增长
397
422
  if (this.queue.length < this.maxQueueSize) {
398
- this.queue = [...entriesToSend.slice(0, this.maxQueueSize - this.queue.length), ...this.queue];
423
+ // 限制放回的数量,防止内存溢出
424
+ const maxReturn = Math.min(entriesToSend.length, this.maxQueueSize - this.queue.length);
425
+ const returnedEntries = entriesToSend.slice(0, maxReturn);
426
+ this.queue = [...returnedEntries, ...this.queue];
399
427
  }
428
+ // 重置发送计数,允许后续重试
429
+ this._sendCount = 0;
400
430
  });
401
431
  }
402
432
  catch (error) {
@@ -798,6 +828,7 @@
798
828
  // 构建日期
799
829
  const BUILD_DATE = new Date().toISOString();
800
830
 
831
+ /* eslint-disable */
801
832
  /**
802
833
  * @file 模块管理器
803
834
  * @description 统一管理库的各功能模块,提供模块的注册、初始化和卸载功能
@@ -817,6 +848,12 @@
817
848
  }
818
849
  return ModuleManager.instance;
819
850
  }
851
+ /**
852
+ * 重置单例实例(主要用于测试)
853
+ */
854
+ static resetInstance() {
855
+ ModuleManager.instance = undefined;
856
+ }
820
857
  /**
821
858
  * 私有构造函数,确保单例模式
822
859
  */
@@ -824,6 +861,7 @@
824
861
  super();
825
862
  this.modules = new Map();
826
863
  this.initialized = false;
864
+ this.initPromise = null;
827
865
  this.logger = Logger.getInstance();
828
866
  this.logger.debug('ModuleManager', `初始化模块管理器 v${VERSION}`);
829
867
  }
@@ -1146,6 +1184,9 @@
1146
1184
  super();
1147
1185
  this.initialized = false;
1148
1186
  this.models = {};
1187
+ /** 重用的 Canvas 元素,用于减少内存分配 */
1188
+ this.reusableCanvas = null;
1189
+ this.reusableContext = null;
1149
1190
  this.options = {
1150
1191
  enabled: true,
1151
1192
  minConfidence: 0.7,
@@ -1244,6 +1285,26 @@
1244
1285
  if (!this.initialized) {
1245
1286
  return Result.failure(new Error('身份证检测器未初始化'));
1246
1287
  }
1288
+ // 输入验证
1289
+ if (!image) {
1290
+ return Result.failure(new Error('图像源不能为空'));
1291
+ }
1292
+ // 验证 HTMLImageElement 是否已加载
1293
+ if (image instanceof HTMLImageElement && !image.complete) {
1294
+ return Result.failure(new Error('图像尚未加载完成'));
1295
+ }
1296
+ // 验证 ImageData 尺寸
1297
+ if (image instanceof ImageData) {
1298
+ if (image.width === 0 || image.height === 0) {
1299
+ return Result.failure(new Error('图像尺寸无效'));
1300
+ }
1301
+ }
1302
+ // 验证 Canvas 尺寸
1303
+ if (image instanceof HTMLCanvasElement) {
1304
+ if (image.width === 0 || image.height === 0) {
1305
+ return Result.failure(new Error('Canvas尺寸无效'));
1306
+ }
1307
+ }
1247
1308
  try {
1248
1309
  this.logger.debug('IDCardDetector', '开始处理图像');
1249
1310
  // 预处理图像
@@ -1290,9 +1351,7 @@
1290
1351
  }
1291
1352
  }
1292
1353
  else if (image instanceof HTMLImageElement && image.complete) {
1293
- const canvas = document.createElement('canvas');
1294
- canvas.width = image.naturalWidth;
1295
- canvas.height = image.naturalHeight;
1354
+ const canvas = this.getReusableCanvas(image.naturalWidth, image.naturalHeight);
1296
1355
  const context = canvas.getContext('2d');
1297
1356
  if (context) {
1298
1357
  context.drawImage(image, 0, 0);
@@ -1309,6 +1368,26 @@
1309
1368
  return Result.failure(error);
1310
1369
  }
1311
1370
  }
1371
+ /**
1372
+ * 获取可重用的 Canvas 元素
1373
+ * @param width 宽度
1374
+ * @param height 高度
1375
+ * @returns CanvasRenderingContext2D
1376
+ */
1377
+ getReusableCanvas(width, height) {
1378
+ // 如果存在可重用的 canvas 且尺寸匹配,直接返回
1379
+ if (this.reusableCanvas &&
1380
+ this.reusableCanvas.width === width &&
1381
+ this.reusableCanvas.height === height) {
1382
+ return this.reusableCanvas;
1383
+ }
1384
+ // 创建新的 canvas
1385
+ this.reusableCanvas = document.createElement('canvas');
1386
+ this.reusableCanvas.width = width;
1387
+ this.reusableCanvas.height = height;
1388
+ this.reusableContext = this.reusableCanvas.getContext('2d');
1389
+ return this.reusableCanvas;
1390
+ }
1312
1391
  /**
1313
1392
  * 预处理图像
1314
1393
  * @param image 图像源
@@ -1317,7 +1396,6 @@
1317
1396
  * @private
1318
1397
  */
1319
1398
  async preprocessImage(image, options) {
1320
- // 实际项目中,这里应该对图像进行预处理
1321
1399
  this.logger.debug('IDCardDetector', '预处理图像');
1322
1400
  // 创建ImageData对象
1323
1401
  let imageData;
@@ -1325,11 +1403,9 @@
1325
1403
  imageData = image;
1326
1404
  }
1327
1405
  else {
1328
- const canvas = document.createElement('canvas');
1329
1406
  const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
1330
1407
  const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
1331
- canvas.width = width;
1332
- canvas.height = height;
1408
+ const canvas = this.getReusableCanvas(width, height);
1333
1409
  const context = canvas.getContext('2d');
1334
1410
  if (!context) {
1335
1411
  throw new Error('无法获取Canvas上下文');
@@ -1376,18 +1452,16 @@
1376
1452
  * @private
1377
1453
  */
1378
1454
  async cropAndAlign(image, edge) {
1379
- // 实际项目中,这里应该进行透视变换以校正图像
1380
1455
  this.logger.debug('IDCardDetector', '裁剪并校正图像');
1381
- // 创建Canvas
1382
- const canvas = document.createElement('canvas');
1383
1456
  // 设置标准身份证尺寸比例
1384
- canvas.width = 428;
1385
- canvas.height = 270;
1457
+ const standardWidth = 428;
1458
+ const standardHeight = 270;
1459
+ const canvas = this.getReusableCanvas(standardWidth, standardHeight);
1386
1460
  const context = canvas.getContext('2d');
1387
1461
  if (!context) {
1388
1462
  throw new Error('无法获取Canvas上下文');
1389
1463
  }
1390
- // 创建临时Canvas
1464
+ // 创建临时Canvas用于源图像
1391
1465
  const tempCanvas = document.createElement('canvas');
1392
1466
  tempCanvas.width = image.width;
1393
1467
  tempCanvas.height = image.height;
@@ -1400,29 +1474,37 @@
1400
1474
  // 在实际应用中,这里应该使用透视变换算法
1401
1475
  // 例如使用Canvas的transform或WebGL进行变换
1402
1476
  // 简化处理:直接裁剪
1403
- context.drawImage(tempCanvas, edge.topLeft.x, edge.topLeft.y, edge.topRight.x - edge.topLeft.x, edge.bottomLeft.y - edge.topLeft.y, 0, 0, canvas.width, canvas.height);
1404
- return context.getImageData(0, 0, canvas.width, canvas.height);
1477
+ context.drawImage(tempCanvas, edge.topLeft.x, edge.topLeft.y, edge.topRight.x - edge.topLeft.x, edge.bottomLeft.y - edge.topLeft.y, 0, 0, standardWidth, standardHeight);
1478
+ return context.getImageData(0, 0, standardWidth, standardHeight);
1405
1479
  }
1406
1480
  /**
1407
1481
  * 识别文字
1482
+ *
1483
+ * @note 此方法返回模拟数据,用于框架开发和测试
1484
+ * 实际使用时需要替换为真实的 OCR 模型集成
1485
+ *
1408
1486
  * @param image 图像数据
1409
1487
  * @param type 身份证类型
1410
1488
  * @returns 识别结果
1411
1489
  * @private
1412
1490
  */
1413
1491
  async recognizeText(image, type) {
1414
- // 实际项目中,这里应该调用OCR模型进行文字识别
1415
1492
  this.logger.debug('IDCardDetector', '识别文字');
1416
1493
  // 模拟OCR结果
1417
- // 在实际应用中,这里应该使用OCR模型进行文字识别
1494
+ // 注意:这是框架的占位实现,真实场景需要接入实际的 OCR 服务
1495
+ // 可选的方案包括:
1496
+ // - TensorFlow.js + 自定义 OCR 模型
1497
+ // - 第三方 OCR API (如百度OCR、腾讯OCR)
1498
+ // - Tesseract.js WASM 版本
1499
+ //
1418
1500
  if (type === exports.IDCardType.FRONT) {
1419
1501
  return {
1420
- name: '张三',
1421
- gender: '男',
1422
- ethnicity: '汉',
1423
- birthDate: '1990-01-01',
1424
- address: '北京市朝阳区某某街道某某社区1号楼1单元101',
1425
- idNumber: '110101199001010001',
1502
+ name: '张三', // TODO: 替换为真实OCR结果
1503
+ gender: '男', // TODO: 替换为真实OCR结果
1504
+ ethnicity: '汉', // TODO: 替换为真实OCR结果
1505
+ birthDate: '1990-01-01', // TODO: 替换为真实OCR结果
1506
+ address: '北京市朝阳区某某街道某某社区1号楼1单元101', // TODO: 替换为真实OCR结果
1507
+ idNumber: '110101199001010001', // TODO: 替换为真实OCR结果
1426
1508
  photoRegion: {
1427
1509
  x: 300,
1428
1510
  y: 40,
@@ -1433,34 +1515,42 @@
1433
1515
  }
1434
1516
  else if (type === exports.IDCardType.BACK) {
1435
1517
  return {
1436
- issueAuthority: '北京市公安局朝阳分局',
1437
- validFrom: '2015-01-01',
1438
- validTo: '2035-01-01'
1518
+ issueAuthority: '北京市公安局朝阳分局', // TODO: 替换为真实OCR结果
1519
+ validFrom: '2015-01-01', // TODO: 替换为真实OCR结果
1520
+ validTo: '2035-01-01' // TODO: 替换为真实OCR结果
1439
1521
  };
1440
1522
  }
1441
1523
  return {};
1442
1524
  }
1443
1525
  /**
1444
1526
  * 检测防伪特征
1527
+ *
1528
+ * @note 此方法返回模拟数据,用于框架开发和测试
1529
+ * 实际使用时需要替换为真实的防伪检测模型
1530
+ *
1445
1531
  * @param image 图像数据
1446
1532
  * @param detectionResult 检测结果
1447
1533
  * @returns 防伪检测结果
1448
1534
  * @private
1449
1535
  */
1450
1536
  async detectAntiFake(image, detectionResult) {
1451
- // 实际项目中,这里应该调用防伪模型进行特征检测
1452
1537
  this.logger.debug('IDCardDetector', '检测防伪特征');
1453
1538
  // 模拟防伪检测结果
1454
- // 在实际应用中,这里应该使用机器学习模型检测防伪特征
1539
+ // 注意:这是框架的占位实现,真实场景需要接入实际的防伪检测模型
1540
+ // 可选的方案包括:
1541
+ // - 紫外光特征检测
1542
+ // - 红外光特征检测
1543
+ // - 微缩文字检测
1544
+ // - 光学变色特征检测
1455
1545
  return {
1456
1546
  passed: true,
1457
1547
  score: 0.92,
1458
1548
  features: {
1459
- fluorescent: true,
1460
- microtext: true,
1461
- opticalVariable: true,
1462
- texture: true,
1463
- watermark: true
1549
+ fluorescent: true, // TODO: 替换为真实检测结果
1550
+ microtext: true, // TODO: 替换为真实检测结果
1551
+ opticalVariable: true, // TODO: 替换为真实检测结果
1552
+ texture: true, // TODO: 替换为真实检测结果
1553
+ watermark: true // TODO: 替换为真实检测结果
1464
1554
  }
1465
1555
  };
1466
1556
  }
@@ -1472,6 +1562,9 @@
1472
1562
  // 清理模型
1473
1563
  this.models = {};
1474
1564
  this.initialized = false;
1565
+ // 清理可重用的 Canvas
1566
+ this.reusableCanvas = null;
1567
+ this.reusableContext = null;
1475
1568
  // 清理事件监听
1476
1569
  this.removeAllListeners();
1477
1570
  }
@@ -2390,7 +2483,7 @@
2390
2483
  let messageCounter = 0;
2391
2484
  // 监听Worker消息
2392
2485
  worker.addEventListener('message', (event) => {
2393
- const { messageId, success, result, error } = event.data;
2486
+ const { messageId, success, result, error: _error } = event.data;
2394
2487
  // 查找对应的Promise
2395
2488
  const promiseHandlers = promiseMap.get(messageId);
2396
2489
  if (promiseHandlers) {
@@ -2398,7 +2491,7 @@
2398
2491
  promiseHandlers.resolve(result);
2399
2492
  }
2400
2493
  else {
2401
- promiseHandlers.reject(new Error(error));
2494
+ promiseHandlers.reject(new Error(_error));
2402
2495
  }
2403
2496
  // 删除Promise映射
2404
2497
  promiseMap.delete(messageId);
@@ -2422,8 +2515,8 @@
2422
2515
  // 释放Blob URL
2423
2516
  URL.revokeObjectURL(url);
2424
2517
  // 拒绝所有未完成的Promise
2425
- for (const [, { reject }] of promiseMap) {
2426
- reject(new Error('Worker已终止'));
2518
+ for (const [, { reject: _reject }] of promiseMap) {
2519
+ _reject(new Error('Worker已终止'));
2427
2520
  }
2428
2521
  // 清空Promise映射
2429
2522
  promiseMap.clear();
@@ -3584,6 +3677,7 @@
3584
3677
  }
3585
3678
  }
3586
3679
 
3680
+ /* eslint-disable */
3587
3681
  /**
3588
3682
  * @file 身份证模块入口
3589
3683
  * @description 提供身份证识别和验证功能的模块入口
@@ -4137,6 +4231,7 @@
4137
4231
  }
4138
4232
  }
4139
4233
 
4234
+ /* eslint-disable */
4140
4235
  /**
4141
4236
  * @file 二维码模块入口
4142
4237
  * @description 提供二维码识别和解析功能的模块入口
@@ -4278,6 +4373,7 @@
4278
4373
  }
4279
4374
  }
4280
4375
 
4376
+ /* eslint-disable */
4281
4377
  /**
4282
4378
  * @file 人脸模块入口
4283
4379
  * @description 提供人脸检测、活体检测和人脸比对功能的模块入口
@@ -4726,6 +4822,7 @@
4726
4822
  }
4727
4823
  }
4728
4824
 
4825
+ /* eslint-disable */
4729
4826
  /**
4730
4827
  * @file 主入口文件
4731
4828
  * @description ID Scanner库的主入口点,提供统一的API和模块导出