id-scanner-lib 1.3.3 → 1.6.2
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.
- package/README.md +324 -410
- package/dist/id-scanner-lib.esm.js +4826 -0
- package/dist/id-scanner-lib.esm.js.map +1 -0
- package/dist/id-scanner-lib.js +4858 -0
- package/dist/id-scanner-lib.js.map +1 -0
- package/dist/types/browser-image-compression.d.ts +19 -0
- package/dist/types/tesseract.d.ts +280 -0
- package/package.json +89 -78
- package/src/core/base-module.ts +78 -0
- package/src/core/camera-manager.ts +813 -0
- package/src/core/config.ts +305 -0
- package/src/core/errors.ts +174 -0
- package/src/core/event-emitter.test.ts +42 -0
- package/src/core/event-emitter.ts +110 -0
- package/src/core/loading-state.test.ts +67 -0
- package/src/core/loading-state.ts +156 -0
- package/src/core/logger.test.ts +49 -0
- package/src/core/logger.ts +549 -0
- package/src/core/module-manager.ts +163 -0
- package/src/core/plugin-manager.ts +429 -0
- package/src/core/resource-manager.ts +762 -0
- package/src/core/result.ts +163 -0
- package/src/core/scanner-factory.ts +236 -0
- package/src/index.ts +117 -939
- package/src/interfaces/external-types.ts +200 -0
- package/src/interfaces/face-detection.ts +309 -0
- package/src/interfaces/scanner-module.ts +384 -0
- package/src/modules/face/face-detector.ts +988 -0
- package/src/modules/face/index.ts +208 -0
- package/src/modules/face/liveness-detector.ts +908 -0
- package/src/modules/face/types.ts +133 -0
- package/src/{id-recognition → modules/id-card}/anti-fake-detector.ts +274 -240
- package/src/modules/id-card/id-card-detector.ts +474 -0
- package/src/modules/id-card/index.ts +425 -0
- package/src/{id-recognition → modules/id-card}/ocr-processor.ts +149 -92
- package/src/modules/id-card/ocr-worker.ts +259 -0
- package/src/modules/id-card/types.ts +178 -0
- package/src/modules/qrcode/index.ts +175 -0
- package/src/modules/qrcode/qr-code-scanner.ts +231 -0
- package/src/modules/qrcode/types.ts +169 -0
- package/src/types/common.test.ts +99 -0
- package/src/types/common.ts +166 -0
- package/src/types/tesseract.d.ts +265 -22
- package/src/utils/camera.test.ts +30 -0
- package/src/utils/camera.ts +4 -1
- package/src/utils/error-handler.test.ts +137 -0
- package/src/utils/error-handler.ts +110 -0
- package/src/utils/image-processing.ts +68 -49
- package/src/utils/index.test.ts +186 -0
- package/src/utils/index.ts +429 -0
- package/src/utils/performance.ts +168 -131
- package/src/utils/resource-manager.ts +65 -146
- package/src/utils/retry.test.ts +142 -0
- package/src/utils/retry.ts +282 -0
- package/src/utils/types.ts +90 -2
- package/src/utils/utils.test.ts +171 -0
- package/src/utils/worker.ts +123 -84
- package/src/version.ts +11 -0
- package/tools/scaffold.js +543 -0
- package/dist/id-scanner-core.esm.js +0 -11349
- package/dist/id-scanner-core.js +0 -11361
- package/dist/id-scanner-core.min.js +0 -1
- package/dist/id-scanner-ocr.esm.js +0 -2319
- package/dist/id-scanner-ocr.js +0 -2328
- package/dist/id-scanner-ocr.min.js +0 -1
- package/dist/id-scanner-qr.esm.js +0 -1296
- package/dist/id-scanner-qr.js +0 -1305
- package/dist/id-scanner-qr.min.js +0 -1
- package/dist/id-scanner.js +0 -4561
- package/dist/id-scanner.min.js +0 -1
- package/src/core.ts +0 -138
- package/src/demo/demo.ts +0 -204
- package/src/id-recognition/data-extractor.ts +0 -262
- package/src/id-recognition/id-detector.ts +0 -510
- package/src/id-recognition/ocr-worker.ts +0 -156
- package/src/index-umd.ts +0 -477
- package/src/ocr-module.ts +0 -187
- package/src/qr-module.ts +0 -179
- package/src/scanner/barcode-scanner.ts +0 -251
- package/src/scanner/qr-scanner.ts +0 -167
package/src/index.ts
CHANGED
|
@@ -1,989 +1,167 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
3
|
-
* @description
|
|
4
|
-
* @module
|
|
5
|
-
* @version 1.3.0
|
|
6
|
-
* @license MIT
|
|
2
|
+
* @file 主入口文件
|
|
3
|
+
* @description ID Scanner库的主入口点,提供统一的API和模块导出
|
|
4
|
+
* @module index
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import type { QRScannerOptions } from "./scanner/qr-scanner"
|
|
19
|
-
import type { BarcodeScannerOptions } from "./scanner/barcode-scanner"
|
|
20
|
-
|
|
21
|
-
// 导入防伪检测器
|
|
22
|
-
import {
|
|
23
|
-
AntiFakeDetector,
|
|
24
|
-
AntiFakeDetectionResult,
|
|
25
|
-
} from "./id-recognition/anti-fake-detector"
|
|
7
|
+
import { ModuleManager } from './core/module-manager';
|
|
8
|
+
import { IDCardModule } from './modules/id-card';
|
|
9
|
+
import { QRCodeModule } from './modules/qrcode';
|
|
10
|
+
import { FaceModule } from './modules/face';
|
|
11
|
+
import { VERSION, BUILD_DATE } from './version';
|
|
12
|
+
import { Logger, LogLevel } from './core/logger';
|
|
13
|
+
import { IDCardModuleOptions } from './modules/id-card/types';
|
|
14
|
+
import { QRCodeModuleOptions } from './modules/qrcode/types';
|
|
15
|
+
import { FaceModuleOptions } from './modules/face/types';
|
|
26
16
|
|
|
27
17
|
/**
|
|
28
|
-
* IDScanner
|
|
18
|
+
* IDScanner配置选项
|
|
29
19
|
*/
|
|
30
20
|
export interface IDScannerOptions {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
/** 日志级别 */
|
|
22
|
+
logLevel?: LogLevel;
|
|
23
|
+
/** 是否启用身份证识别模块 */
|
|
24
|
+
enableIDCard?: boolean;
|
|
25
|
+
/** 是否启用二维码识别模块 */
|
|
26
|
+
enableQRCode?: boolean;
|
|
27
|
+
/** 是否启用人脸识别模块 */
|
|
28
|
+
enableFace?: boolean;
|
|
29
|
+
/** 身份证模块配置 */
|
|
30
|
+
idCard?: IDCardModuleOptions;
|
|
31
|
+
/** 二维码模块配置 */
|
|
32
|
+
qrCode?: QRCodeModuleOptions;
|
|
33
|
+
/** 人脸识别模块配置 */
|
|
34
|
+
face?: FaceModuleOptions;
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
/**
|
|
43
|
-
* IDScanner
|
|
44
|
-
*
|
|
45
|
-
* 整合二维码、条形码扫描和身份证识别功能,提供统一的接口
|
|
46
|
-
* 使用动态导入实现按需加载
|
|
38
|
+
* IDScanner类
|
|
39
|
+
* 提供整合的身份证、二维码和人脸识别功能
|
|
47
40
|
*/
|
|
48
41
|
export class IDScanner {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
private isAntiFakeModuleLoaded: boolean = false
|
|
62
|
-
|
|
42
|
+
/** 版本号 */
|
|
43
|
+
public static readonly VERSION = VERSION;
|
|
44
|
+
/** 构建日期 */
|
|
45
|
+
public static readonly BUILD_DATE = BUILD_DATE;
|
|
46
|
+
|
|
47
|
+
/** 模块管理器 */
|
|
48
|
+
private moduleManager: ModuleManager;
|
|
49
|
+
/** 是否已经初始化 */
|
|
50
|
+
private initialized = false;
|
|
51
|
+
/** 日志工具 */
|
|
52
|
+
private logger: Logger;
|
|
53
|
+
|
|
63
54
|
/**
|
|
64
55
|
* 构造函数
|
|
65
56
|
* @param options 配置选项
|
|
66
57
|
*/
|
|
67
|
-
constructor(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* 初始化模块
|
|
73
|
-
* 根据需要初始化OCR引擎和防伪检测模块
|
|
74
|
-
*/
|
|
75
|
-
async initialize(): Promise<void> {
|
|
76
|
-
try {
|
|
77
|
-
// 预加载OCR模块但不初始化
|
|
78
|
-
if (!this.isOCRModuleLoaded) {
|
|
79
|
-
// 动态导入OCR模块
|
|
80
|
-
const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
|
|
81
|
-
this.ocrModule = new OCRModule({
|
|
82
|
-
cameraOptions: this.options.cameraOptions,
|
|
83
|
-
onIDCardScanned: this.options.onIDCardScanned,
|
|
84
|
-
onError: this.options.onError,
|
|
85
|
-
})
|
|
86
|
-
this.isOCRModuleLoaded = true
|
|
87
|
-
|
|
88
|
-
// 初始化OCR模块
|
|
89
|
-
await this.ocrModule.initialize()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 初始化防伪检测模块
|
|
93
|
-
if (!this.isAntiFakeModuleLoaded) {
|
|
94
|
-
this.antiFakeDetector = new AntiFakeDetector()
|
|
95
|
-
this.isAntiFakeModuleLoaded = true
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
console.log("IDScanner初始化完成")
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error("初始化失败:", error)
|
|
101
|
-
this.handleError(error as Error)
|
|
102
|
-
throw error
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 初始化OCR模块
|
|
108
|
-
*/
|
|
109
|
-
private async initOCRModule(): Promise<void> {
|
|
110
|
-
if (this.isOCRModuleLoaded) return
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
// 动态导入OCR模块
|
|
114
|
-
const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
|
|
115
|
-
this.ocrModule = new OCRModule({
|
|
116
|
-
cameraOptions: this.options.cameraOptions,
|
|
117
|
-
onIDCardScanned: this.options.onIDCardScanned,
|
|
118
|
-
onError: this.options.onError,
|
|
119
|
-
})
|
|
120
|
-
this.isOCRModuleLoaded = true
|
|
121
|
-
|
|
122
|
-
// 初始化OCR模块
|
|
123
|
-
await this.ocrModule.initialize()
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error("OCR模块初始化失败:", error)
|
|
126
|
-
throw error
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 启动二维码扫描
|
|
132
|
-
* @param videoElement HTML视频元素
|
|
133
|
-
*/
|
|
134
|
-
async startQRScanner(videoElement: HTMLVideoElement): Promise<void> {
|
|
135
|
-
this.stop()
|
|
136
|
-
this.videoElement = videoElement
|
|
137
|
-
this.scanMode = "qr"
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
// 动态加载二维码模块
|
|
141
|
-
if (!this.isQRModuleLoaded) {
|
|
142
|
-
const ScannerModule = await import("./qr-module").then(
|
|
143
|
-
(m) => m.ScannerModule
|
|
144
|
-
)
|
|
145
|
-
this.qrModule = new ScannerModule({
|
|
146
|
-
cameraOptions: this.options.cameraOptions,
|
|
147
|
-
qrScannerOptions: this.options.qrScannerOptions,
|
|
148
|
-
barcodeScannerOptions: this.options.barcodeScannerOptions,
|
|
149
|
-
onQRCodeScanned: this.options.onQRCodeScanned,
|
|
150
|
-
onBarcodeScanned: this.options.onBarcodeScanned,
|
|
151
|
-
onError: this.options.onError,
|
|
152
|
-
})
|
|
153
|
-
this.isQRModuleLoaded = true
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
await this.qrModule.startQRScanner(videoElement)
|
|
157
|
-
} catch (error) {
|
|
158
|
-
this.handleError(error as Error)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 启动条形码扫描
|
|
164
|
-
* @param videoElement HTML视频元素
|
|
165
|
-
*/
|
|
166
|
-
async startBarcodeScanner(videoElement: HTMLVideoElement): Promise<void> {
|
|
167
|
-
this.stop()
|
|
168
|
-
this.videoElement = videoElement
|
|
169
|
-
this.scanMode = "barcode"
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
// 动态加载二维码模块
|
|
173
|
-
if (!this.isQRModuleLoaded) {
|
|
174
|
-
const ScannerModule = await import("./qr-module").then(
|
|
175
|
-
(m) => m.ScannerModule
|
|
176
|
-
)
|
|
177
|
-
this.qrModule = new ScannerModule({
|
|
178
|
-
cameraOptions: this.options.cameraOptions,
|
|
179
|
-
qrScannerOptions: this.options.qrScannerOptions,
|
|
180
|
-
barcodeScannerOptions: this.options.barcodeScannerOptions,
|
|
181
|
-
onQRCodeScanned: this.options.onQRCodeScanned,
|
|
182
|
-
onBarcodeScanned: this.options.onBarcodeScanned,
|
|
183
|
-
onError: this.options.onError,
|
|
184
|
-
})
|
|
185
|
-
this.isQRModuleLoaded = true
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
await this.qrModule.startBarcodeScanner(videoElement)
|
|
189
|
-
} catch (error) {
|
|
190
|
-
this.handleError(error as Error)
|
|
58
|
+
constructor(options: IDScannerOptions = {}) {
|
|
59
|
+
// 配置日志级别
|
|
60
|
+
this.logger = Logger.getInstance();
|
|
61
|
+
if (options.logLevel !== undefined) {
|
|
62
|
+
this.logger.setLevel(options.logLevel);
|
|
191
63
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
async startIDCardScanner(videoElement: HTMLVideoElement): Promise<void> {
|
|
199
|
-
this.stop()
|
|
200
|
-
this.videoElement = videoElement
|
|
201
|
-
this.scanMode = "idcard"
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
// 检查OCR模块是否已加载,若未加载则自动初始化
|
|
205
|
-
if (!this.isOCRModuleLoaded) {
|
|
206
|
-
await this.initialize()
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
await this.ocrModule.startIDCardScanner(videoElement)
|
|
210
|
-
} catch (error) {
|
|
211
|
-
this.handleError(error as Error)
|
|
64
|
+
|
|
65
|
+
this.moduleManager = ModuleManager.getInstance();
|
|
66
|
+
|
|
67
|
+
// 注册模块
|
|
68
|
+
if (options.enableIDCard !== false) {
|
|
69
|
+
this.moduleManager.register(new IDCardModule(options.idCard));
|
|
212
70
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* 停止扫描
|
|
217
|
-
*/
|
|
218
|
-
stop(): void {
|
|
219
|
-
if (this.scanMode === "qr" || this.scanMode === "barcode") {
|
|
220
|
-
if (this.qrModule) {
|
|
221
|
-
this.qrModule.stop()
|
|
222
|
-
}
|
|
223
|
-
} else if (this.scanMode === "idcard") {
|
|
224
|
-
if (this.ocrModule) {
|
|
225
|
-
this.ocrModule.stop()
|
|
226
|
-
}
|
|
71
|
+
|
|
72
|
+
if (options.enableQRCode !== false) {
|
|
73
|
+
this.moduleManager.register(new QRCodeModule(options.qrCode));
|
|
227
74
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
* 处理错误
|
|
232
|
-
*/
|
|
233
|
-
private handleError(error: Error): void {
|
|
234
|
-
if (this.options.onError) {
|
|
235
|
-
this.options.onError(error)
|
|
236
|
-
} else {
|
|
237
|
-
console.error("IDScanner错误:", error)
|
|
75
|
+
|
|
76
|
+
if (options.enableFace !== false) {
|
|
77
|
+
this.moduleManager.register(new FaceModule(options.face));
|
|
238
78
|
}
|
|
239
79
|
}
|
|
240
80
|
|
|
241
81
|
/**
|
|
242
|
-
*
|
|
82
|
+
* 初始化库
|
|
243
83
|
*/
|
|
244
|
-
async
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
// 释放OCR资源
|
|
248
|
-
if (this.isOCRModuleLoaded && this.ocrModule) {
|
|
249
|
-
await this.ocrModule.terminate()
|
|
250
|
-
this.ocrModule = null
|
|
251
|
-
this.isOCRModuleLoaded = false
|
|
84
|
+
public async initialize(): Promise<void> {
|
|
85
|
+
if (this.initialized) {
|
|
86
|
+
return;
|
|
252
87
|
}
|
|
253
88
|
|
|
254
|
-
|
|
255
|
-
if (this.isQRModuleLoaded && this.qrModule) {
|
|
256
|
-
this.qrModule = null
|
|
257
|
-
this.isQRModuleLoaded = false
|
|
258
|
-
}
|
|
89
|
+
this.logger.info('IDScanner', `初始化 IDScanner v${VERSION}`);
|
|
259
90
|
|
|
260
|
-
// 释放防伪检测资源
|
|
261
|
-
if (this.antiFakeDetector) {
|
|
262
|
-
this.antiFakeDetector.dispose()
|
|
263
|
-
this.antiFakeDetector = null
|
|
264
|
-
this.isAntiFakeModuleLoaded = false
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 处理图片中的二维码
|
|
270
|
-
* @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
|
|
271
|
-
* @returns 返回Promise,解析为扫描结果
|
|
272
|
-
*/
|
|
273
|
-
async processQRCodeImage(
|
|
274
|
-
imageSource: HTMLImageElement | HTMLCanvasElement | string | File
|
|
275
|
-
): Promise<string> {
|
|
276
91
|
try {
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
const ScannerModule = await import("./qr-module").then(
|
|
280
|
-
(m) => m.ScannerModule
|
|
281
|
-
)
|
|
282
|
-
this.qrModule = new ScannerModule({
|
|
283
|
-
qrScannerOptions: this.options.qrScannerOptions,
|
|
284
|
-
onQRCodeScanned: this.options.onQRCodeScanned,
|
|
285
|
-
onError: this.options.onError,
|
|
286
|
-
})
|
|
287
|
-
this.isQRModuleLoaded = true
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// 处理不同类型的图片源
|
|
291
|
-
let imageElement: HTMLImageElement
|
|
292
|
-
|
|
293
|
-
if (imageSource instanceof File) {
|
|
294
|
-
// 如果是File对象,创建新的Image元素并加载图片
|
|
295
|
-
imageElement = new Image()
|
|
296
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
297
|
-
const url = URL.createObjectURL(imageSource)
|
|
298
|
-
await new Promise((resolve, reject) => {
|
|
299
|
-
imageElement.onload = resolve
|
|
300
|
-
imageElement.onerror = reject
|
|
301
|
-
imageElement.src = url
|
|
302
|
-
})
|
|
303
|
-
// 使用后释放URL对象
|
|
304
|
-
URL.revokeObjectURL(url)
|
|
305
|
-
} else if (typeof imageSource === "string") {
|
|
306
|
-
// 如果是URL字符串,创建新的Image元素并加载图片
|
|
307
|
-
imageElement = new Image()
|
|
308
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
309
|
-
await new Promise((resolve, reject) => {
|
|
310
|
-
imageElement.onload = resolve
|
|
311
|
-
imageElement.onerror = reject
|
|
312
|
-
imageElement.src = imageSource
|
|
313
|
-
})
|
|
314
|
-
} else if (imageSource instanceof HTMLImageElement) {
|
|
315
|
-
// 如果已经是Image元素,直接使用
|
|
316
|
-
imageElement = imageSource
|
|
317
|
-
} else if (imageSource instanceof HTMLCanvasElement) {
|
|
318
|
-
// 如果是Canvas元素,创建Image并从Canvas获取数据
|
|
319
|
-
imageElement = new Image()
|
|
320
|
-
imageElement.src = imageSource.toDataURL()
|
|
321
|
-
await new Promise((resolve) => {
|
|
322
|
-
imageElement.onload = resolve
|
|
323
|
-
})
|
|
324
|
-
} else {
|
|
325
|
-
throw new Error("不支持的图片源类型")
|
|
326
|
-
}
|
|
92
|
+
// 初始化所有模块
|
|
93
|
+
await this.moduleManager.initialize();
|
|
327
94
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
canvas.width = imageElement.naturalWidth
|
|
331
|
-
canvas.height = imageElement.naturalHeight
|
|
332
|
-
const ctx = canvas.getContext("2d")
|
|
333
|
-
|
|
334
|
-
if (!ctx) {
|
|
335
|
-
throw new Error("无法创建Canvas上下文")
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
ctx.drawImage(imageElement, 0, 0)
|
|
339
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
340
|
-
|
|
341
|
-
// 使用QR模块处理图像
|
|
342
|
-
return this.qrModule.processQRCodeImage(imageData)
|
|
95
|
+
this.initialized = true;
|
|
96
|
+
this.logger.info('IDScanner', 'IDScanner初始化完成');
|
|
343
97
|
} catch (error) {
|
|
344
|
-
this.
|
|
345
|
-
throw error
|
|
98
|
+
this.logger.error('IDScanner', 'IDScanner初始化失败', error as Error);
|
|
99
|
+
throw new Error(`IDScanner初始化失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
346
100
|
}
|
|
347
101
|
}
|
|
348
102
|
|
|
349
103
|
/**
|
|
350
|
-
*
|
|
351
|
-
* @
|
|
352
|
-
* @returns 返回Promise,解析为扫描结果
|
|
104
|
+
* 获取身份证模块实例
|
|
105
|
+
* @returns 身份证模块
|
|
353
106
|
*/
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
): Promise<string> {
|
|
357
|
-
try {
|
|
358
|
-
// 动态加载二维码模块
|
|
359
|
-
if (!this.isQRModuleLoaded) {
|
|
360
|
-
const ScannerModule = await import("./qr-module").then(
|
|
361
|
-
(m) => m.ScannerModule
|
|
362
|
-
)
|
|
363
|
-
this.qrModule = new ScannerModule({
|
|
364
|
-
barcodeScannerOptions: this.options.barcodeScannerOptions,
|
|
365
|
-
onBarcodeScanned: this.options.onBarcodeScanned,
|
|
366
|
-
onError: this.options.onError,
|
|
367
|
-
})
|
|
368
|
-
this.isQRModuleLoaded = true
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// 处理不同类型的图片源
|
|
372
|
-
let imageElement: HTMLImageElement
|
|
373
|
-
|
|
374
|
-
if (imageSource instanceof File) {
|
|
375
|
-
// 如果是File对象,创建新的Image元素并加载图片
|
|
376
|
-
imageElement = new Image()
|
|
377
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
378
|
-
const url = URL.createObjectURL(imageSource)
|
|
379
|
-
await new Promise((resolve, reject) => {
|
|
380
|
-
imageElement.onload = resolve
|
|
381
|
-
imageElement.onerror = reject
|
|
382
|
-
imageElement.src = url
|
|
383
|
-
})
|
|
384
|
-
// 使用后释放URL对象
|
|
385
|
-
URL.revokeObjectURL(url)
|
|
386
|
-
} else if (typeof imageSource === "string") {
|
|
387
|
-
// 如果是URL字符串,创建新的Image元素并加载图片
|
|
388
|
-
imageElement = new Image()
|
|
389
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
390
|
-
await new Promise((resolve, reject) => {
|
|
391
|
-
imageElement.onload = resolve
|
|
392
|
-
imageElement.onerror = reject
|
|
393
|
-
imageElement.src = imageSource
|
|
394
|
-
})
|
|
395
|
-
} else if (imageSource instanceof HTMLImageElement) {
|
|
396
|
-
// 如果已经是Image元素,直接使用
|
|
397
|
-
imageElement = imageSource
|
|
398
|
-
} else if (imageSource instanceof HTMLCanvasElement) {
|
|
399
|
-
// 如果是Canvas元素,创建Image并从Canvas获取数据
|
|
400
|
-
imageElement = new Image()
|
|
401
|
-
imageElement.src = imageSource.toDataURL()
|
|
402
|
-
await new Promise((resolve) => {
|
|
403
|
-
imageElement.onload = resolve
|
|
404
|
-
})
|
|
405
|
-
} else {
|
|
406
|
-
throw new Error("不支持的图片源类型")
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// 获取图像数据
|
|
410
|
-
const canvas = document.createElement("canvas")
|
|
411
|
-
canvas.width = imageElement.naturalWidth
|
|
412
|
-
canvas.height = imageElement.naturalHeight
|
|
413
|
-
const ctx = canvas.getContext("2d")
|
|
414
|
-
|
|
415
|
-
if (!ctx) {
|
|
416
|
-
throw new Error("无法创建Canvas上下文")
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
ctx.drawImage(imageElement, 0, 0)
|
|
420
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
421
|
-
|
|
422
|
-
// 使用Barcode模块处理图像
|
|
423
|
-
return this.qrModule.processBarcodeImage(imageData)
|
|
424
|
-
} catch (error) {
|
|
425
|
-
this.handleError(error as Error)
|
|
426
|
-
throw error
|
|
427
|
-
}
|
|
107
|
+
public getIDCardModule(): IDCardModule | undefined {
|
|
108
|
+
return this.moduleManager.getModule<IDCardModule>('id-card');
|
|
428
109
|
}
|
|
429
|
-
|
|
110
|
+
|
|
430
111
|
/**
|
|
431
|
-
*
|
|
432
|
-
* @
|
|
433
|
-
* @returns 返回Promise,解析为身份证信息
|
|
112
|
+
* 获取二维码模块实例
|
|
113
|
+
* @returns 二维码模块
|
|
434
114
|
*/
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
): Promise<IDCardInfo> {
|
|
438
|
-
if (!this.isOCRModuleLoaded) {
|
|
439
|
-
await this.initOCRModule()
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
// 处理不同类型的图片源
|
|
444
|
-
let imageElement: HTMLImageElement
|
|
445
|
-
|
|
446
|
-
if (imageSource instanceof File) {
|
|
447
|
-
// 如果是File对象,先进行压缩
|
|
448
|
-
const compressedFile = await ImageProcessor.compressImage(imageSource, {
|
|
449
|
-
maxSizeMB: 2, // 最大2MB
|
|
450
|
-
maxWidthOrHeight: 1800, // 最大尺寸
|
|
451
|
-
useWebWorker: true,
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
// 创建新的Image元素并加载图片
|
|
455
|
-
imageElement = new Image()
|
|
456
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
457
|
-
const url = URL.createObjectURL(compressedFile)
|
|
458
|
-
await new Promise((resolve, reject) => {
|
|
459
|
-
imageElement.onload = resolve
|
|
460
|
-
imageElement.onerror = reject
|
|
461
|
-
imageElement.src = url
|
|
462
|
-
})
|
|
463
|
-
// 使用后释放URL对象
|
|
464
|
-
URL.revokeObjectURL(url)
|
|
465
|
-
} else if (typeof imageSource === "string") {
|
|
466
|
-
// 如果是URL字符串,创建新的Image元素并加载图片
|
|
467
|
-
imageElement = new Image()
|
|
468
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
469
|
-
await new Promise((resolve, reject) => {
|
|
470
|
-
imageElement.onload = resolve
|
|
471
|
-
imageElement.onerror = reject
|
|
472
|
-
imageElement.src = imageSource
|
|
473
|
-
})
|
|
474
|
-
} else if (imageSource instanceof HTMLImageElement) {
|
|
475
|
-
// 如果已经是Image元素,直接使用
|
|
476
|
-
imageElement = imageSource
|
|
477
|
-
} else if (imageSource instanceof HTMLCanvasElement) {
|
|
478
|
-
// 如果是Canvas元素,创建Image并从Canvas获取数据
|
|
479
|
-
imageElement = new Image()
|
|
480
|
-
imageElement.src = imageSource.toDataURL()
|
|
481
|
-
await new Promise((resolve) => {
|
|
482
|
-
imageElement.onload = resolve
|
|
483
|
-
})
|
|
484
|
-
} else {
|
|
485
|
-
throw new Error("不支持的图片源类型")
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// 获取图像数据
|
|
489
|
-
const canvas = document.createElement("canvas")
|
|
490
|
-
canvas.width = imageElement.naturalWidth
|
|
491
|
-
canvas.height = imageElement.naturalHeight
|
|
492
|
-
const ctx = canvas.getContext("2d")
|
|
493
|
-
|
|
494
|
-
if (!ctx) {
|
|
495
|
-
throw new Error("无法创建Canvas上下文")
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
ctx.drawImage(imageElement, 0, 0)
|
|
499
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
500
|
-
|
|
501
|
-
// 对图像进行预处理,提高识别率
|
|
502
|
-
const enhancedImageData = ImageProcessor.batchProcess(imageData, {
|
|
503
|
-
brightness: 10,
|
|
504
|
-
contrast: 15,
|
|
505
|
-
sharpen: true,
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
// 使用OCR模块处理图像
|
|
509
|
-
const idInfo = await this.ocrModule.processIDCard(enhancedImageData)
|
|
510
|
-
|
|
511
|
-
// 进行防伪检测并将结果添加到身份证信息中
|
|
512
|
-
if (this.isAntiFakeModuleLoaded && this.antiFakeDetector) {
|
|
513
|
-
try {
|
|
514
|
-
const result = await this.antiFakeDetector.detect(enhancedImageData)
|
|
515
|
-
// 将防伪检测结果添加到身份证信息对象中
|
|
516
|
-
const extendedInfo = idInfo as any
|
|
517
|
-
extendedInfo.antiFakeResult = result
|
|
518
|
-
|
|
519
|
-
// 触发防伪检测回调
|
|
520
|
-
if (this.options.onAntiFakeDetected) {
|
|
521
|
-
this.options.onAntiFakeDetected(result)
|
|
522
|
-
}
|
|
523
|
-
} catch (error) {
|
|
524
|
-
console.warn("身份证防伪检测失败:", error)
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return idInfo
|
|
529
|
-
} catch (error) {
|
|
530
|
-
this.handleError(error as Error)
|
|
531
|
-
throw error
|
|
532
|
-
}
|
|
115
|
+
public getQRCodeModule(): QRCodeModule | undefined {
|
|
116
|
+
return this.moduleManager.getModule<QRCodeModule>('qrcode');
|
|
533
117
|
}
|
|
534
|
-
|
|
118
|
+
|
|
535
119
|
/**
|
|
536
|
-
*
|
|
537
|
-
* @
|
|
538
|
-
* @param options 图像处理选项
|
|
539
|
-
* @param outputFormat 输出格式,'imagedata'或'file'
|
|
540
|
-
* @returns 返回Promise,解析为处理后的ImageData或File
|
|
120
|
+
* 获取人脸识别模块实例
|
|
121
|
+
* @returns 人脸识别模块
|
|
541
122
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
options: ImageProcessorOptions = {},
|
|
545
|
-
outputFormat: "imagedata" | "file" = "imagedata"
|
|
546
|
-
): Promise<ImageData | File> {
|
|
547
|
-
try {
|
|
548
|
-
// 处理不同类型的图片源
|
|
549
|
-
let imageData: ImageData
|
|
550
|
-
|
|
551
|
-
if (imageSource instanceof File) {
|
|
552
|
-
// 如果是File对象,先进行压缩
|
|
553
|
-
const compressedFile = await ImageProcessor.compressImage(imageSource, {
|
|
554
|
-
maxSizeMB: 2, // 最大2MB
|
|
555
|
-
maxWidthOrHeight: 1920, // 最大尺寸
|
|
556
|
-
useWebWorker: true,
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
// 从File创建ImageData
|
|
560
|
-
imageData = await ImageProcessor.createImageDataFromFile(compressedFile)
|
|
561
|
-
} else if (typeof imageSource === "string") {
|
|
562
|
-
// 如果是URL字符串,创建新的Image元素并加载图片
|
|
563
|
-
const imageElement = new Image()
|
|
564
|
-
imageElement.crossOrigin = "anonymous" // 处理跨域图片
|
|
565
|
-
await new Promise((resolve, reject) => {
|
|
566
|
-
imageElement.onload = resolve
|
|
567
|
-
imageElement.onerror = reject
|
|
568
|
-
imageElement.src = imageSource
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
// 获取图像数据
|
|
572
|
-
const canvas = document.createElement("canvas")
|
|
573
|
-
canvas.width = imageElement.naturalWidth
|
|
574
|
-
canvas.height = imageElement.naturalHeight
|
|
575
|
-
const ctx = canvas.getContext("2d")
|
|
576
|
-
|
|
577
|
-
if (!ctx) {
|
|
578
|
-
throw new Error("无法创建Canvas上下文")
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
ctx.drawImage(imageElement, 0, 0)
|
|
582
|
-
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
583
|
-
} else if (imageSource instanceof HTMLImageElement) {
|
|
584
|
-
// 如果是Image元素,从它创建ImageData
|
|
585
|
-
const canvas = document.createElement("canvas")
|
|
586
|
-
canvas.width = imageSource.naturalWidth
|
|
587
|
-
canvas.height = imageSource.naturalHeight
|
|
588
|
-
const ctx = canvas.getContext("2d")
|
|
589
|
-
|
|
590
|
-
if (!ctx) {
|
|
591
|
-
throw new Error("无法创建Canvas上下文")
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
ctx.drawImage(imageSource, 0, 0)
|
|
595
|
-
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
596
|
-
} else if (imageSource instanceof HTMLCanvasElement) {
|
|
597
|
-
// 如果是Canvas元素,直接获取其ImageData
|
|
598
|
-
const ctx = imageSource.getContext("2d")
|
|
599
|
-
|
|
600
|
-
if (!ctx) {
|
|
601
|
-
throw new Error("无法获取Canvas上下文")
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
imageData = ctx.getImageData(
|
|
605
|
-
0,
|
|
606
|
-
0,
|
|
607
|
-
imageSource.width,
|
|
608
|
-
imageSource.height
|
|
609
|
-
)
|
|
610
|
-
} else {
|
|
611
|
-
throw new Error("不支持的图片源类型")
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// 进行图像处理
|
|
615
|
-
const processedImageData = ImageProcessor.batchProcess(imageData, options)
|
|
616
|
-
|
|
617
|
-
// 根据需要的输出格式返回结果
|
|
618
|
-
if (outputFormat === "file") {
|
|
619
|
-
// 将ImageData转换为File
|
|
620
|
-
const file = await ImageProcessor.imageDataToFile(
|
|
621
|
-
processedImageData,
|
|
622
|
-
"processed_image.jpg",
|
|
623
|
-
"image/jpeg",
|
|
624
|
-
0.85
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
// 触发回调
|
|
628
|
-
if (this.options.onImageProcessed) {
|
|
629
|
-
this.options.onImageProcessed(file)
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return file
|
|
633
|
-
} else {
|
|
634
|
-
// 触发回调
|
|
635
|
-
if (this.options.onImageProcessed) {
|
|
636
|
-
this.options.onImageProcessed(processedImageData)
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
return processedImageData
|
|
640
|
-
}
|
|
641
|
-
} catch (error) {
|
|
642
|
-
this.handleError(error as Error)
|
|
643
|
-
throw error
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* 压缩图片
|
|
649
|
-
* @param file 要压缩的图片文件
|
|
650
|
-
* @param options 压缩选项
|
|
651
|
-
* @returns 返回Promise,解析为压缩后的文件
|
|
652
|
-
*/
|
|
653
|
-
async compressImage(
|
|
654
|
-
file: File,
|
|
655
|
-
options?: ImageCompressionOptions
|
|
656
|
-
): Promise<File> {
|
|
657
|
-
try {
|
|
658
|
-
return await ImageProcessor.compressImage(file, options)
|
|
659
|
-
} catch (error) {
|
|
660
|
-
this.handleError(error as Error)
|
|
661
|
-
throw error
|
|
662
|
-
}
|
|
123
|
+
public getFaceModule(): FaceModule | undefined {
|
|
124
|
+
return this.moduleManager.getModule<FaceModule>('face');
|
|
663
125
|
}
|
|
664
|
-
|
|
126
|
+
|
|
665
127
|
/**
|
|
666
|
-
*
|
|
667
|
-
* @param imageSource 图片源
|
|
668
|
-
* @returns 防伪检测结果
|
|
128
|
+
* 释放所有资源
|
|
669
129
|
*/
|
|
670
|
-
async
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (!this.isAntiFakeModuleLoaded || !this.antiFakeDetector) {
|
|
674
|
-
await this.initialize()
|
|
675
|
-
if (!this.antiFakeDetector) {
|
|
676
|
-
throw new Error("防伪检测模块初始化失败")
|
|
677
|
-
}
|
|
130
|
+
public async dispose(): Promise<void> {
|
|
131
|
+
if (!this.initialized) {
|
|
132
|
+
return;
|
|
678
133
|
}
|
|
679
|
-
|
|
134
|
+
|
|
135
|
+
this.logger.info('IDScanner', '释放IDScanner资源');
|
|
136
|
+
|
|
680
137
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
// 处理URL或Base64
|
|
686
|
-
const img = new Image()
|
|
687
|
-
await new Promise<void>((resolve, reject) => {
|
|
688
|
-
img.onload = () => resolve()
|
|
689
|
-
img.onerror = () => reject(new Error("图像加载失败"))
|
|
690
|
-
img.src = imageSource
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
const canvas = document.createElement("canvas")
|
|
694
|
-
canvas.width = img.width
|
|
695
|
-
canvas.height = img.height
|
|
696
|
-
const ctx = canvas.getContext("2d")
|
|
697
|
-
if (!ctx) {
|
|
698
|
-
throw new Error("无法创建Canvas上下文")
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
ctx.drawImage(img, 0, 0)
|
|
702
|
-
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
703
|
-
} else if (imageSource instanceof File) {
|
|
704
|
-
// 处理文件
|
|
705
|
-
imageData = await ImageProcessor.createImageDataFromFile(imageSource)
|
|
706
|
-
} else if (imageSource instanceof HTMLImageElement) {
|
|
707
|
-
// 处理Image元素
|
|
708
|
-
const canvas = document.createElement("canvas")
|
|
709
|
-
canvas.width = imageSource.width
|
|
710
|
-
canvas.height = imageSource.height
|
|
711
|
-
const ctx = canvas.getContext("2d")
|
|
712
|
-
if (!ctx) {
|
|
713
|
-
throw new Error("无法创建Canvas上下文")
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
ctx.drawImage(imageSource, 0, 0)
|
|
717
|
-
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
718
|
-
} else {
|
|
719
|
-
// 处理Canvas元素
|
|
720
|
-
const ctx = (imageSource as HTMLCanvasElement).getContext("2d")
|
|
721
|
-
if (!ctx) {
|
|
722
|
-
throw new Error("无法获取Canvas上下文")
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
imageData = ctx.getImageData(
|
|
726
|
-
0,
|
|
727
|
-
0,
|
|
728
|
-
imageSource.width,
|
|
729
|
-
imageSource.height
|
|
730
|
-
)
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// 执行防伪检测
|
|
734
|
-
const result = await this.antiFakeDetector.detect(imageData)
|
|
735
|
-
|
|
736
|
-
// 触发回调
|
|
737
|
-
if (this.options.onAntiFakeDetected) {
|
|
738
|
-
this.options.onAntiFakeDetected(result)
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
return result
|
|
138
|
+
await this.moduleManager.dispose();
|
|
139
|
+
|
|
140
|
+
this.initialized = false;
|
|
141
|
+
this.logger.info('IDScanner', 'IDScanner资源已释放');
|
|
742
142
|
} catch (error) {
|
|
743
|
-
this.
|
|
744
|
-
throw error
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// 导出工具类和类型
|
|
750
|
-
export { Camera, CameraOptions } from "./utils/camera"
|
|
751
|
-
export {
|
|
752
|
-
ImageProcessor,
|
|
753
|
-
ImageProcessorOptions,
|
|
754
|
-
ImageCompressionOptions,
|
|
755
|
-
} from "./utils/image-processing"
|
|
756
|
-
export { IDCardInfo, DetectionResult } from "./utils/types"
|
|
757
|
-
|
|
758
|
-
// 导出防伪检测相关类型和类
|
|
759
|
-
export { AntiFakeDetector, AntiFakeDetectionResult }
|
|
760
|
-
|
|
761
|
-
// 为了向后兼容,我们创建一个演示类
|
|
762
|
-
export class IDScannerDemo {
|
|
763
|
-
private scanner: IDScanner
|
|
764
|
-
private currentMode: "qr" | "idcard" = "qr"
|
|
765
|
-
private videoElement: HTMLVideoElement
|
|
766
|
-
private resultElement: HTMLElement
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* 创建演示类实例
|
|
770
|
-
* @param videoElementId 视频元素ID
|
|
771
|
-
* @param resultElementId 结果显示元素ID
|
|
772
|
-
* @param switchButtonId 切换按钮ID
|
|
773
|
-
* @param imageInputId 图片输入元素ID
|
|
774
|
-
*/
|
|
775
|
-
constructor(
|
|
776
|
-
videoElementId: string,
|
|
777
|
-
resultElementId: string,
|
|
778
|
-
switchButtonId?: string,
|
|
779
|
-
imageInputId?: string
|
|
780
|
-
) {
|
|
781
|
-
this.videoElement = document.getElementById(
|
|
782
|
-
videoElementId
|
|
783
|
-
) as HTMLVideoElement
|
|
784
|
-
this.resultElement = document.getElementById(resultElementId) as HTMLElement
|
|
785
|
-
|
|
786
|
-
// 创建扫描器实例
|
|
787
|
-
this.scanner = new IDScanner({
|
|
788
|
-
onQRCodeScanned: (result) => this.handleScanResult(result),
|
|
789
|
-
onIDCardScanned: (info) => this.handleIDCardResult(info),
|
|
790
|
-
onError: (error) => this.handleError(error),
|
|
791
|
-
})
|
|
792
|
-
|
|
793
|
-
// 设置切换按钮事件
|
|
794
|
-
if (switchButtonId) {
|
|
795
|
-
const switchButton = document.getElementById(switchButtonId)
|
|
796
|
-
if (switchButton) {
|
|
797
|
-
switchButton.addEventListener("click", () => this.toggleMode())
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// 设置图片输入事件
|
|
802
|
-
if (imageInputId) {
|
|
803
|
-
const imageInput = document.getElementById(
|
|
804
|
-
imageInputId
|
|
805
|
-
) as HTMLInputElement
|
|
806
|
-
if (imageInput) {
|
|
807
|
-
imageInput.addEventListener("change", (e) => this.handleImageInput(e))
|
|
808
|
-
}
|
|
809
|
-
}
|
|
143
|
+
this.logger.error('IDScanner', 'IDScanner资源释放失败', error as Error);
|
|
144
|
+
throw new Error(`IDScanner资源释放失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
810
145
|
}
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* 初始化扫描器
|
|
814
|
-
*/
|
|
815
|
-
async initialize(): Promise<void> {
|
|
816
|
-
try {
|
|
817
|
-
// 初始化身份证识别引擎
|
|
818
|
-
await this.scanner.initialize()
|
|
819
|
-
|
|
820
|
-
// 默认启动二维码扫描
|
|
821
|
-
await this.startQRMode()
|
|
822
|
-
} catch (error) {
|
|
823
|
-
this.handleError(error as Error)
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
/**
|
|
828
|
-
* 切换扫描模式
|
|
829
|
-
*/
|
|
830
|
-
async toggleMode(): Promise<void> {
|
|
831
|
-
try {
|
|
832
|
-
this.scanner.stop()
|
|
833
|
-
|
|
834
|
-
if (this.currentMode === "qr") {
|
|
835
|
-
this.currentMode = "idcard"
|
|
836
|
-
await this.startIDCardMode()
|
|
837
|
-
} else {
|
|
838
|
-
this.currentMode = "qr"
|
|
839
|
-
await this.startQRMode()
|
|
840
|
-
}
|
|
841
|
-
} catch (error) {
|
|
842
|
-
this.handleError(error as Error)
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* 启动二维码扫描模式
|
|
848
|
-
*/
|
|
849
|
-
private async startQRMode(): Promise<void> {
|
|
850
|
-
try {
|
|
851
|
-
this.updateResultDisplay("等待扫描二维码...")
|
|
852
|
-
await this.scanner.startQRScanner(this.videoElement)
|
|
853
|
-
} catch (error) {
|
|
854
|
-
this.handleError(error as Error)
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/**
|
|
859
|
-
* 启动身份证扫描模式
|
|
860
|
-
*/
|
|
861
|
-
private async startIDCardMode(): Promise<void> {
|
|
862
|
-
try {
|
|
863
|
-
this.updateResultDisplay("等待扫描身份证...")
|
|
864
|
-
await this.scanner.startIDCardScanner(this.videoElement)
|
|
865
|
-
} catch (error) {
|
|
866
|
-
this.handleError(error as Error)
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* 处理图片输入
|
|
872
|
-
*/
|
|
873
|
-
private async handleImageInput(event: Event): Promise<void> {
|
|
874
|
-
try {
|
|
875
|
-
const input = event.target as HTMLInputElement
|
|
876
|
-
|
|
877
|
-
if (!input.files || input.files.length === 0) {
|
|
878
|
-
return
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const file = input.files[0]
|
|
882
|
-
this.updateResultDisplay("正在处理图片...")
|
|
883
|
-
|
|
884
|
-
// 根据当前模式处理图片
|
|
885
|
-
if (this.currentMode === "qr") {
|
|
886
|
-
const result = await this.scanner.processQRCodeImage(file)
|
|
887
|
-
this.handleScanResult(result)
|
|
888
|
-
} else {
|
|
889
|
-
const info = await this.scanner.processIDCardImage(file)
|
|
890
|
-
this.handleIDCardResult(info)
|
|
891
|
-
}
|
|
892
|
-
} catch (error) {
|
|
893
|
-
this.handleError(error as Error)
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* 处理扫描结果
|
|
899
|
-
*/
|
|
900
|
-
private handleScanResult(result: string): void {
|
|
901
|
-
this.updateResultDisplay(`
|
|
902
|
-
<h3>扫描结果:</h3>
|
|
903
|
-
<p>${result}</p>
|
|
904
|
-
`)
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* 处理身份证识别结果
|
|
909
|
-
*/
|
|
910
|
-
private handleIDCardResult(info: IDCardInfo): void {
|
|
911
|
-
// 格式化显示身份证信息
|
|
912
|
-
const infoHtml = Object.entries(info)
|
|
913
|
-
.filter(([key, value]) => value && key !== "antiFakeResult") // 过滤掉空值和防伪结果
|
|
914
|
-
.map(([key, value]) => {
|
|
915
|
-
// 转换键名为中文显示
|
|
916
|
-
const keyMap: { [key: string]: string } = {
|
|
917
|
-
name: "姓名",
|
|
918
|
-
gender: "性别",
|
|
919
|
-
nationality: "民族",
|
|
920
|
-
birthDate: "出生日期",
|
|
921
|
-
address: "地址",
|
|
922
|
-
idNumber: "身份证号",
|
|
923
|
-
issuingAuthority: "签发机关",
|
|
924
|
-
validPeriod: "有效期限",
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
const displayKey = keyMap[key] || key
|
|
928
|
-
return `<div><strong>${displayKey}:</strong> ${value}</div>`
|
|
929
|
-
})
|
|
930
|
-
.join("")
|
|
931
|
-
|
|
932
|
-
// 检查是否有防伪检测结果
|
|
933
|
-
let antiFakeHtml = ""
|
|
934
|
-
const anyInfo = info as any
|
|
935
|
-
if (anyInfo.antiFakeResult) {
|
|
936
|
-
const antiFakeResult = anyInfo.antiFakeResult
|
|
937
|
-
antiFakeHtml = `
|
|
938
|
-
<h3>防伪检测结果:</h3>
|
|
939
|
-
<div style="color: ${antiFakeResult.isAuthentic ? "green" : "red"}">
|
|
940
|
-
${
|
|
941
|
-
antiFakeResult.isAuthentic
|
|
942
|
-
? "✓ 身份证真实"
|
|
943
|
-
: "⚠ 警告:可能为伪造证件"
|
|
944
|
-
}
|
|
945
|
-
</div>
|
|
946
|
-
<div>置信度: ${(antiFakeResult.confidence * 100).toFixed(1)}%</div>
|
|
947
|
-
<div>检测到的特征: ${
|
|
948
|
-
antiFakeResult.detectedFeatures.join(", ") || "无"
|
|
949
|
-
}</div>
|
|
950
|
-
<div>${antiFakeResult.message}</div>
|
|
951
|
-
`
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
this.updateResultDisplay(`
|
|
955
|
-
<h3>身份证信息:</h3>
|
|
956
|
-
${infoHtml}
|
|
957
|
-
${antiFakeHtml}
|
|
958
|
-
`)
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* 处理错误
|
|
963
|
-
*/
|
|
964
|
-
private handleError(error: Error): void {
|
|
965
|
-
console.error("识别错误:", error)
|
|
966
|
-
this.updateResultDisplay(`
|
|
967
|
-
<div class="error">
|
|
968
|
-
<h3>错误:</h3>
|
|
969
|
-
<p>${error.message}</p>
|
|
970
|
-
</div>
|
|
971
|
-
`)
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* 更新结果显示
|
|
976
|
-
*/
|
|
977
|
-
private updateResultDisplay(html: string): void {
|
|
978
|
-
if (this.resultElement) {
|
|
979
|
-
this.resultElement.innerHTML = html
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
/**
|
|
984
|
-
* 停止扫描
|
|
985
|
-
*/
|
|
986
|
-
stop(): void {
|
|
987
|
-
this.scanner.stop()
|
|
988
146
|
}
|
|
989
147
|
}
|
|
148
|
+
|
|
149
|
+
// 导出核心模块
|
|
150
|
+
export * from './core/loading-state';
|
|
151
|
+
export * from './core/module-manager';
|
|
152
|
+
export * from './core/logger';
|
|
153
|
+
export * from './core/errors';
|
|
154
|
+
|
|
155
|
+
// 导出功能模块 (明确导出以避免命名冲突)
|
|
156
|
+
export { IDCardModule } from './modules/id-card';
|
|
157
|
+
export { QRCodeModule } from './modules/qrcode';
|
|
158
|
+
export { FaceModule } from './modules/face';
|
|
159
|
+
|
|
160
|
+
// 导出类型
|
|
161
|
+
export * from './utils/types';
|
|
162
|
+
export { IDCardModuleOptions, IDCardInfo, IDCardType, IDCardVerificationResult } from './modules/id-card/types';
|
|
163
|
+
export { QRCodeModuleOptions, QRCodeResult, BarcodeFormat, DEFAULT_FORMATS } from './modules/qrcode/types';
|
|
164
|
+
export { FaceModuleOptions, FaceDetectionResult, FaceComparisonResult } from './modules/face/types';
|
|
165
|
+
|
|
166
|
+
// 默认导出IDScanner类
|
|
167
|
+
export default IDScanner;
|