id-scanner-lib 1.3.2 → 1.5.0
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 +55 -460
- package/dist/id-scanner-lib.esm.js +4641 -0
- package/dist/id-scanner-lib.esm.js.map +1 -0
- package/dist/id-scanner-lib.js +14755 -0
- package/dist/id-scanner-lib.js.map +1 -0
- package/dist/types/core/base-module.d.ts +44 -0
- package/dist/types/core/camera-manager.d.ts +258 -0
- package/dist/types/core/config.d.ts +88 -0
- package/dist/types/core/errors.d.ts +111 -0
- package/dist/types/core/event-emitter.d.ts +55 -0
- package/dist/types/core/logger.d.ts +277 -0
- package/dist/types/core/module-manager.d.ts +78 -0
- package/dist/types/core/plugin-manager.d.ts +158 -0
- package/dist/types/core/resource-manager.d.ts +246 -0
- package/dist/types/core/result.d.ts +83 -0
- package/dist/types/core/scanner-factory.d.ts +93 -0
- package/dist/types/index.bundle.d.ts +1303 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/interfaces/external-types.d.ts +174 -0
- package/dist/types/interfaces/face-detection.d.ts +293 -0
- package/dist/types/interfaces/scanner-module.d.ts +280 -0
- package/dist/types/modules/face/face-detector.d.ts +170 -0
- package/dist/types/modules/face/index.d.ts +56 -0
- package/dist/types/modules/face/liveness-detector.d.ts +177 -0
- package/dist/types/modules/face/types.d.ts +136 -0
- package/dist/types/modules/id-card/anti-fake-detector.d.ts +170 -0
- package/dist/types/modules/id-card/id-card-detector.d.ts +131 -0
- package/dist/types/modules/id-card/index.d.ts +89 -0
- package/dist/types/modules/id-card/ocr-processor.d.ts +110 -0
- package/dist/types/modules/id-card/ocr-worker.d.ts +31 -0
- package/dist/types/modules/id-card/types.d.ts +181 -0
- package/dist/types/modules/qrcode/index.d.ts +51 -0
- package/dist/types/modules/qrcode/qr-code-scanner.d.ts +64 -0
- package/dist/types/modules/qrcode/types.d.ts +67 -0
- package/dist/types/utils/camera.d.ts +81 -0
- package/dist/types/utils/image-processing.d.ts +176 -0
- package/dist/types/utils/index.d.ts +175 -0
- package/dist/types/utils/performance.d.ts +81 -0
- package/dist/types/utils/resource-manager.d.ts +53 -0
- package/dist/types/utils/types.d.ts +166 -0
- package/dist/types/utils/worker.d.ts +52 -0
- package/dist/types/version.d.ts +7 -0
- package/package.json +76 -77
- package/src/core/base-module.ts +78 -0
- package/src/core/camera-manager.ts +798 -0
- package/src/core/config.ts +268 -0
- package/src/core/errors.ts +174 -0
- package/src/core/event-emitter.ts +110 -0
- package/src/core/logger.ts +549 -0
- package/src/core/module-manager.ts +165 -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 +237 -0
- package/src/index.ts +113 -936
- 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 +931 -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/modules/id-card/anti-fake-detector.ts +732 -0
- package/src/modules/id-card/id-card-detector.ts +474 -0
- package/src/modules/id-card/index.ts +425 -0
- package/src/modules/id-card/ocr-processor.ts +538 -0
- 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 +230 -0
- package/src/modules/qrcode/types.ts +65 -0
- package/src/types/browser-image-compression.d.ts +19 -0
- package/src/types/tesseract.d.ts +280 -0
- package/src/utils/image-processing.ts +432 -49
- package/src/utils/index.ts +426 -0
- package/src/utils/performance.ts +168 -131
- package/src/utils/resource-manager.ts +65 -146
- package/src/utils/types.ts +90 -2
- 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 -11076
- package/dist/id-scanner-core.esm.js.map +0 -1
- package/dist/id-scanner-core.js +0 -11088
- package/dist/id-scanner-core.js.map +0 -1
- package/dist/id-scanner-core.min.js +0 -1
- package/dist/id-scanner-core.min.js.map +0 -1
- package/dist/id-scanner-ocr.esm.js +0 -1802
- package/dist/id-scanner-ocr.esm.js.map +0 -1
- package/dist/id-scanner-ocr.js +0 -1811
- package/dist/id-scanner-ocr.js.map +0 -1
- package/dist/id-scanner-ocr.min.js +0 -1
- package/dist/id-scanner-ocr.min.js.map +0 -1
- package/dist/id-scanner-qr.esm.js +0 -1023
- package/dist/id-scanner-qr.esm.js.map +0 -1
- package/dist/id-scanner-qr.js +0 -1032
- package/dist/id-scanner-qr.js.map +0 -1
- package/dist/id-scanner-qr.min.js +0 -1
- package/dist/id-scanner-qr.min.js.map +0 -1
- package/dist/id-scanner.js +0 -3740
- package/dist/id-scanner.js.map +0 -1
- package/dist/id-scanner.min.js +0 -1
- package/dist/id-scanner.min.js.map +0 -1
- package/src/core.ts +0 -138
- package/src/demo/demo.ts +0 -204
- package/src/id-recognition/anti-fake-detector.ts +0 -317
- package/src/id-recognition/data-extractor.ts +0 -262
- package/src/id-recognition/id-detector.ts +0 -363
- package/src/id-recognition/ocr-processor.ts +0 -334
- 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
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file OCR Worker
|
|
3
|
+
* @description OCR处理的Worker线程实现
|
|
4
|
+
* @module modules/id-card/ocr-worker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { IDCardInfo, IDCardType } from './types';
|
|
8
|
+
import { LoggerMessage } from 'tesseract.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* OCR处理输入参数
|
|
12
|
+
*/
|
|
13
|
+
export interface OCRProcessInput {
|
|
14
|
+
/** 图像Base64数据 */
|
|
15
|
+
imageBase64: string;
|
|
16
|
+
/** Tesseract Worker选项 */
|
|
17
|
+
tessWorkerOptions?: {
|
|
18
|
+
/** 语言 */
|
|
19
|
+
language?: string;
|
|
20
|
+
/** 日志回调 */
|
|
21
|
+
logger?: (message: LoggerMessage) => void;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 在Worker中处理OCR识别
|
|
27
|
+
* @param input OCR处理输入参数
|
|
28
|
+
* @returns OCR处理结果
|
|
29
|
+
*/
|
|
30
|
+
export async function processOCRInWorker(
|
|
31
|
+
input: OCRProcessInput
|
|
32
|
+
): Promise<{ idCardInfo: IDCardInfo; processingTime: number }> {
|
|
33
|
+
const startTime = performance.now();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// 导入Tesseract.js
|
|
37
|
+
const { createWorker } = await import('tesseract.js');
|
|
38
|
+
|
|
39
|
+
// 创建Tesseract Worker
|
|
40
|
+
const worker = createWorker({
|
|
41
|
+
logger: input.tessWorkerOptions?.logger
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 初始化Worker
|
|
45
|
+
await worker.load();
|
|
46
|
+
await worker.loadLanguage('chi_sim');
|
|
47
|
+
await worker.initialize('chi_sim');
|
|
48
|
+
|
|
49
|
+
// 设置识别参数
|
|
50
|
+
await worker.setParameters({
|
|
51
|
+
tessedit_char_whitelist: '0123456789X年月日壹贰叁肆伍陆柒捌玖拾民族汉满回维吾尔藏苗彝壮朝鲜侗瑶白土家哈尼哈萨克傣黎傈僳佤高山拉祜水东乡纳西景颇柯尔克孜达斡尔仫佬羌布朗撒拉毛南仡佬锡伯阿昌普米塔吉克怒乌孜别克俄罗斯鄂温克德昂保安裕固京塔塔尔独龙鄂伦春赫哲门巴珞巴基诺男女住址出生公民身份号码签发机关有效期省市区县乡镇街道号楼单元室ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
|
52
|
+
tessedit_pageseg_mode: 7, // PSM_SINGLE_LINE
|
|
53
|
+
preserve_interword_spaces: '1'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 识别图像
|
|
57
|
+
const { data } = await worker.recognize(input.imageBase64);
|
|
58
|
+
|
|
59
|
+
// 解析身份证信息
|
|
60
|
+
const idCardInfo = parseIDCardText(data.text);
|
|
61
|
+
|
|
62
|
+
// 释放Worker资源
|
|
63
|
+
await worker.terminate();
|
|
64
|
+
|
|
65
|
+
const processingTime = performance.now() - startTime;
|
|
66
|
+
|
|
67
|
+
return { idCardInfo, processingTime };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('OCR处理错误:', error);
|
|
70
|
+
return {
|
|
71
|
+
idCardInfo: {} as IDCardInfo,
|
|
72
|
+
processingTime: performance.now() - startTime
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 解析身份证文本
|
|
79
|
+
* @param text OCR识别的文本
|
|
80
|
+
* @returns 解析后的身份证信息
|
|
81
|
+
*/
|
|
82
|
+
function parseIDCardText(text: string): IDCardInfo {
|
|
83
|
+
const info: IDCardInfo = {};
|
|
84
|
+
|
|
85
|
+
// 预处理文本,清除多余空白
|
|
86
|
+
const processedText = text.replace(/\s+/g, ' ').trim();
|
|
87
|
+
|
|
88
|
+
// 解析身份证号码
|
|
89
|
+
const idNumberRegex = /(\d{17}[\dX])/;
|
|
90
|
+
const idNumberWithPrefixRegex = /公民身份号码[\s\:]*(\d{17}[\dX])/;
|
|
91
|
+
|
|
92
|
+
const basicMatch = processedText.match(idNumberRegex);
|
|
93
|
+
const prefixMatch = processedText.match(idNumberWithPrefixRegex);
|
|
94
|
+
|
|
95
|
+
if (prefixMatch && prefixMatch[1]) {
|
|
96
|
+
info.idNumber = prefixMatch[1];
|
|
97
|
+
} else if (basicMatch && basicMatch[1]) {
|
|
98
|
+
info.idNumber = basicMatch[1];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 解析姓名
|
|
102
|
+
const nameWithLabelRegex = /姓名[\s\:]*([一-龥]{2,4})/;
|
|
103
|
+
const nameMatch = processedText.match(nameWithLabelRegex);
|
|
104
|
+
|
|
105
|
+
if (nameMatch && nameMatch[1]) {
|
|
106
|
+
info.name = nameMatch[1].trim();
|
|
107
|
+
} else {
|
|
108
|
+
// 备用方案:查找短行且内容全是汉字
|
|
109
|
+
const lines = processedText.split('\n').filter(line => line.trim());
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
if (
|
|
112
|
+
line.length >= 2 &&
|
|
113
|
+
line.length <= 5 &&
|
|
114
|
+
/^[一-龥]+$/.test(line) &&
|
|
115
|
+
!/性别|民族|住址|公民|签发|有效/.test(line)
|
|
116
|
+
) {
|
|
117
|
+
info.name = line.trim();
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 解析性别和民族
|
|
124
|
+
const genderAndNationalityRegex = /性别[\s\:]*([男女])[\s ]*民族[\s\:]*([一-龥]+族)/;
|
|
125
|
+
const genderOnlyRegex = /性别[\s\:]*([男女])/;
|
|
126
|
+
const nationalityOnlyRegex = /民族[\s\:]*([一-龥]+族)/;
|
|
127
|
+
|
|
128
|
+
const genderNationalityMatch = processedText.match(genderAndNationalityRegex);
|
|
129
|
+
const genderOnlyMatch = processedText.match(genderOnlyRegex);
|
|
130
|
+
const nationalityOnlyMatch = processedText.match(nationalityOnlyRegex);
|
|
131
|
+
|
|
132
|
+
if (genderNationalityMatch) {
|
|
133
|
+
info.gender = genderNationalityMatch[1];
|
|
134
|
+
info.ethnicity = genderNationalityMatch[2];
|
|
135
|
+
} else {
|
|
136
|
+
if (genderOnlyMatch) info.gender = genderOnlyMatch[1];
|
|
137
|
+
if (nationalityOnlyMatch) info.ethnicity = nationalityOnlyMatch[1];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 根据内容判断身份证类型
|
|
141
|
+
if (processedText.includes('出生') || processedText.includes('公民身份号码')) {
|
|
142
|
+
info.type = IDCardType.FRONT; // 确保类型为枚举值而不是字符串
|
|
143
|
+
} else if (processedText.includes('签发机关') || processedText.includes('有效期')) {
|
|
144
|
+
info.type = IDCardType.BACK; // 确保类型为枚举值而不是字符串
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 解析出生日期
|
|
148
|
+
const birthDateRegex1 = /出生[\s\:]*(\d{4})年(\d{1,2})月(\d{1,2})[日号]/;
|
|
149
|
+
const birthDateRegex2 = /出生[\s\:]*(\d{4})[-\/\.](\d{1,2})[-\/\.](\d{1,2})/;
|
|
150
|
+
const birthDateRegex3 = /出生日期[\s\:]*(\d{4})[-\/\.\u5e74](\d{1,2})[-\/\.\u6708](\d{1,2})[日号]?/;
|
|
151
|
+
|
|
152
|
+
let birthDateMatch =
|
|
153
|
+
processedText.match(birthDateRegex1) ||
|
|
154
|
+
processedText.match(birthDateRegex2) ||
|
|
155
|
+
processedText.match(birthDateRegex3);
|
|
156
|
+
|
|
157
|
+
if (!birthDateMatch && info.idNumber && info.idNumber.length === 18) {
|
|
158
|
+
const year = info.idNumber.substring(6, 10);
|
|
159
|
+
const month = info.idNumber.substring(10, 12);
|
|
160
|
+
const day = info.idNumber.substring(12, 14);
|
|
161
|
+
info.birthDate = `${year}-${month}-${day}`;
|
|
162
|
+
} else if (birthDateMatch) {
|
|
163
|
+
const year = birthDateMatch[1];
|
|
164
|
+
const month = birthDateMatch[2].padStart(2, '0');
|
|
165
|
+
const day = birthDateMatch[3].padStart(2, '0');
|
|
166
|
+
info.birthDate = `${year}-${month}-${day}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 解析地址
|
|
170
|
+
const addressRegex1 = /住址[\s\:]*([\s\S]*?)(?=公民身份|出生|性别|签发)/;
|
|
171
|
+
const addressRegex2 = /住址[\s\:]*([一-龥a-zA-Z0-9\s\.\-]+)/;
|
|
172
|
+
|
|
173
|
+
const addressMatch =
|
|
174
|
+
processedText.match(addressRegex1) || processedText.match(addressRegex2);
|
|
175
|
+
|
|
176
|
+
if (addressMatch && addressMatch[1]) {
|
|
177
|
+
info.address = addressMatch[1]
|
|
178
|
+
.replace(/\s+/g, '')
|
|
179
|
+
.replace(/\n/g, '')
|
|
180
|
+
.trim();
|
|
181
|
+
|
|
182
|
+
if (info.address.length > 70) {
|
|
183
|
+
info.address = info.address.substring(0, 70);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!/[一-龥]/.test(info.address)) {
|
|
187
|
+
info.address = '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 解析签发机关
|
|
192
|
+
const authorityRegex1 = /签发机关[\s\:]*([\s\S]*?)(?=有效|公民|出生|\d{8}|$)/;
|
|
193
|
+
const authorityRegex2 = /签发机关[\s\:]*([一-龥\s]+)/;
|
|
194
|
+
|
|
195
|
+
const authorityMatch =
|
|
196
|
+
processedText.match(authorityRegex1) ||
|
|
197
|
+
processedText.match(authorityRegex2);
|
|
198
|
+
|
|
199
|
+
if (authorityMatch && authorityMatch[1]) {
|
|
200
|
+
info.issueAuthority = authorityMatch[1]
|
|
201
|
+
.replace(/\s+/g, '')
|
|
202
|
+
.replace(/\n/g, '')
|
|
203
|
+
.trim();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 解析有效期限
|
|
207
|
+
const validPeriodRegex1 = /有效期限[\s\:]*(\d{4}[-\.\u5e74\s]\d{1,2}[-\.\u6708\s]\d{1,2}[日\s]*)[-\s]*(至|-)[-\s]*(\d{4}[-\.\u5e74\s]\d{1,2}[-\.\u6708\s]\d{1,2}[日]*|[永久长期]*)/;
|
|
208
|
+
const validPeriodRegex2 = /有效期限[\s\:]*(\d{8})[-\s]*(至|-)[-\s]*(\d{8}|[永久长期]*)/;
|
|
209
|
+
|
|
210
|
+
const validPeriodMatch =
|
|
211
|
+
processedText.match(validPeriodRegex1) ||
|
|
212
|
+
processedText.match(validPeriodRegex2);
|
|
213
|
+
|
|
214
|
+
if (validPeriodMatch) {
|
|
215
|
+
if (validPeriodMatch[1] && validPeriodMatch[3]) {
|
|
216
|
+
const startDate = formatDateString(validPeriodMatch[1]);
|
|
217
|
+
const endDate = /\d/.test(validPeriodMatch[3])
|
|
218
|
+
? formatDateString(validPeriodMatch[3])
|
|
219
|
+
: '长期有效';
|
|
220
|
+
|
|
221
|
+
info.validFrom = startDate;
|
|
222
|
+
info.validTo = endDate;
|
|
223
|
+
info.validPeriod = `${startDate}-${endDate}`;
|
|
224
|
+
} else {
|
|
225
|
+
info.validPeriod = validPeriodMatch[0].replace('有效期限', '').trim();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return info;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 格式化日期字符串
|
|
234
|
+
* @param dateStr 原始日期字符串
|
|
235
|
+
* @returns 格式化后的日期字符串
|
|
236
|
+
*/
|
|
237
|
+
function formatDateString(dateStr: string): string {
|
|
238
|
+
// 提取年月日
|
|
239
|
+
const dateMatch = dateStr.match(
|
|
240
|
+
/(\d{4})[-\.\u5e74\s]*(\d{1,2})[-\.\u6708\s]*(\d{1,2})[日]*/
|
|
241
|
+
);
|
|
242
|
+
if (dateMatch) {
|
|
243
|
+
const year = dateMatch[1];
|
|
244
|
+
const month = dateMatch[2].padStart(2, '0');
|
|
245
|
+
const day = dateMatch[3].padStart(2, '0');
|
|
246
|
+
return `${year}-${month}-${day}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 纯数字格式如 20220101
|
|
250
|
+
if (/^\d{8}$/.test(dateStr)) {
|
|
251
|
+
const year = dateStr.substring(0, 4);
|
|
252
|
+
const month = dateStr.substring(4, 6);
|
|
253
|
+
const day = dateStr.substring(6, 8);
|
|
254
|
+
return `${year}-${month}-${day}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 无法格式化,返回原始字符串
|
|
258
|
+
return dateStr;
|
|
259
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 身份证模块类型定义
|
|
3
|
+
* @description 身份证模块相关的类型和接口定义
|
|
4
|
+
* @module modules/id-card/types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 身份证类型枚举
|
|
9
|
+
*/
|
|
10
|
+
export enum IDCardType {
|
|
11
|
+
/** 第二代居民身份证正面 */
|
|
12
|
+
FRONT = 'front',
|
|
13
|
+
/** 第二代居民身份证背面 */
|
|
14
|
+
BACK = 'back',
|
|
15
|
+
/** 第一代居民身份证 */
|
|
16
|
+
FIRST_GENERATION = 'first_generation',
|
|
17
|
+
/** 临时身份证 */
|
|
18
|
+
TEMPORARY = 'temporary',
|
|
19
|
+
/** 外国人永久居留证 */
|
|
20
|
+
FOREIGN_PERMANENT = 'foreign_permanent',
|
|
21
|
+
/** 港澳台居民居住证 */
|
|
22
|
+
HMT_RESIDENT = 'hmt_resident',
|
|
23
|
+
/** 未知类型 */
|
|
24
|
+
UNKNOWN = 'unknown'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 证件边缘信息
|
|
29
|
+
*/
|
|
30
|
+
export interface IDCardEdge {
|
|
31
|
+
/** 左上角坐标 */
|
|
32
|
+
topLeft: { x: number; y: number };
|
|
33
|
+
/** 右上角坐标 */
|
|
34
|
+
topRight: { x: number; y: number };
|
|
35
|
+
/** 右下角坐标 */
|
|
36
|
+
bottomRight: { x: number; y: number };
|
|
37
|
+
/** 左下角坐标 */
|
|
38
|
+
bottomLeft: { x: number; y: number };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 身份证信息
|
|
43
|
+
*/
|
|
44
|
+
export interface IDCardInfo {
|
|
45
|
+
nationality?: string;
|
|
46
|
+
issuingAuthority?: string;
|
|
47
|
+
validPeriod?: string;
|
|
48
|
+
/** 身份证类型 */
|
|
49
|
+
type?: IDCardType;
|
|
50
|
+
/** 身份证边缘信息 */
|
|
51
|
+
edge?: IDCardEdge;
|
|
52
|
+
/** 姓名 */
|
|
53
|
+
name?: string;
|
|
54
|
+
/** 性别 */
|
|
55
|
+
gender?: string;
|
|
56
|
+
/** 民族 */
|
|
57
|
+
ethnicity?: string;
|
|
58
|
+
/** 出生日期,格式: YYYY-MM-DD */
|
|
59
|
+
birthDate?: string;
|
|
60
|
+
/** 地址 */
|
|
61
|
+
address?: string;
|
|
62
|
+
/** 身份证号码 */
|
|
63
|
+
idNumber?: string;
|
|
64
|
+
/** 签发机关 */
|
|
65
|
+
issueAuthority?: string;
|
|
66
|
+
/** 有效期起始日期,格式: YYYY-MM-DD */
|
|
67
|
+
validFrom?: string;
|
|
68
|
+
/** 有效期截止日期,格式: YYYY-MM-DD */
|
|
69
|
+
validTo?: string;
|
|
70
|
+
/** 相片区域坐标 */
|
|
71
|
+
photoRegion?: {
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
width: number;
|
|
75
|
+
height: number;
|
|
76
|
+
};
|
|
77
|
+
/** 原始身份证图像 */
|
|
78
|
+
image?: ImageData;
|
|
79
|
+
/** 置信度 */
|
|
80
|
+
confidence?: number;
|
|
81
|
+
/** 防伪检测结果 */
|
|
82
|
+
antiFake?: {
|
|
83
|
+
/** 是否通过防伪检测 */
|
|
84
|
+
passed: boolean;
|
|
85
|
+
/** 防伪检测分数 */
|
|
86
|
+
score: number;
|
|
87
|
+
/** 防伪特征检测结果 */
|
|
88
|
+
features?: {
|
|
89
|
+
/** 荧光油墨 */
|
|
90
|
+
fluorescent?: boolean;
|
|
91
|
+
/** 微缩文字 */
|
|
92
|
+
microtext?: boolean;
|
|
93
|
+
/** 光变图案 */
|
|
94
|
+
opticalVariable?: boolean;
|
|
95
|
+
/** 纹理 */
|
|
96
|
+
texture?: boolean;
|
|
97
|
+
/** 暗记 */
|
|
98
|
+
watermark?: boolean;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 身份证模块配置选项
|
|
105
|
+
*/
|
|
106
|
+
export interface IDCardModuleOptions {
|
|
107
|
+
/** 是否启用模块 */
|
|
108
|
+
enabled?: boolean;
|
|
109
|
+
|
|
110
|
+
/** 检测器配置 */
|
|
111
|
+
detector?: {
|
|
112
|
+
/** 最小置信度 */
|
|
113
|
+
minConfidence?: number;
|
|
114
|
+
/** 是否启用OCR识别 */
|
|
115
|
+
enableOCR?: boolean;
|
|
116
|
+
/** 是否启用防伪检测 */
|
|
117
|
+
enableAntiFake?: boolean;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** OCR处理器配置 */
|
|
121
|
+
ocr?: {
|
|
122
|
+
/** 是否使用Web Worker处理OCR */
|
|
123
|
+
useWorker?: boolean;
|
|
124
|
+
/** 最大图像尺寸 */
|
|
125
|
+
maxImageDimension?: number;
|
|
126
|
+
/** 亮度调整 */
|
|
127
|
+
brightness?: number;
|
|
128
|
+
/** 对比度调整 */
|
|
129
|
+
contrast?: number;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** 防伪检测配置 */
|
|
133
|
+
antiFake?: {
|
|
134
|
+
/** 防伪检测灵敏度 */
|
|
135
|
+
sensitivity?: number;
|
|
136
|
+
/** 最小置信度 */
|
|
137
|
+
minConfidence?: number;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 身份证验证结果
|
|
143
|
+
*/
|
|
144
|
+
export interface IDCardVerificationResult {
|
|
145
|
+
/** 是否验证通过 */
|
|
146
|
+
isValid: boolean;
|
|
147
|
+
/** 验证分数 */
|
|
148
|
+
score: number;
|
|
149
|
+
/** 失败原因 */
|
|
150
|
+
failureReason?: string;
|
|
151
|
+
/** 验证详情 */
|
|
152
|
+
details?: {
|
|
153
|
+
/** 身份证号码是否有效 */
|
|
154
|
+
idNumberValid?: boolean;
|
|
155
|
+
/** 签发日期是否有效 */
|
|
156
|
+
issueDateValid?: boolean;
|
|
157
|
+
/** 有效期是否过期 */
|
|
158
|
+
isExpired?: boolean;
|
|
159
|
+
/** 防伪检测是否通过 */
|
|
160
|
+
antiFakePassed?: boolean;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 图像处理配置选项
|
|
166
|
+
*/
|
|
167
|
+
export interface ImageProcessOptions {
|
|
168
|
+
/** 是否进行预处理 */
|
|
169
|
+
preprocess?: boolean;
|
|
170
|
+
/** 是否校正图像 */
|
|
171
|
+
correctPerspective?: boolean;
|
|
172
|
+
/** 是否增强图像 */
|
|
173
|
+
enhance?: boolean;
|
|
174
|
+
/** 是否去噪 */
|
|
175
|
+
denoise?: boolean;
|
|
176
|
+
/** 是否二值化 */
|
|
177
|
+
binarize?: boolean;
|
|
178
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 二维码模块入口
|
|
3
|
+
* @description 提供二维码识别和解析功能的模块入口
|
|
4
|
+
* @module modules/qrcode
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseModule } from '../../core/base-module';
|
|
8
|
+
import { QRCodeResult, QRCodeModuleOptions } from './types';
|
|
9
|
+
import { QRCodeScanner } from './qr-code-scanner';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 二维码模块
|
|
13
|
+
* 提供二维码检测和解析功能
|
|
14
|
+
*/
|
|
15
|
+
export class QRCodeModule extends BaseModule {
|
|
16
|
+
/** 模块名称 */
|
|
17
|
+
public readonly name: string = 'qrcode';
|
|
18
|
+
|
|
19
|
+
/** 模块配置 */
|
|
20
|
+
private options: QRCodeModuleOptions;
|
|
21
|
+
|
|
22
|
+
/** 二维码扫描器 */
|
|
23
|
+
private scanner: QRCodeScanner;
|
|
24
|
+
|
|
25
|
+
/** 最后一次扫描结果 */
|
|
26
|
+
private lastScanResult?: QRCodeResult;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 构造函数
|
|
30
|
+
* @param options 模块配置选项
|
|
31
|
+
*/
|
|
32
|
+
constructor(options: QRCodeModuleOptions = {}) {
|
|
33
|
+
super();
|
|
34
|
+
|
|
35
|
+
this.options = {
|
|
36
|
+
enabled: true,
|
|
37
|
+
scanner: {
|
|
38
|
+
minConfidence: 0.6,
|
|
39
|
+
tryMultipleScan: true,
|
|
40
|
+
returnImage: false,
|
|
41
|
+
...options.scanner
|
|
42
|
+
},
|
|
43
|
+
imageProcess: {
|
|
44
|
+
preprocess: true,
|
|
45
|
+
enhanceContrast: true,
|
|
46
|
+
threshold: 128,
|
|
47
|
+
...options.imageProcess
|
|
48
|
+
},
|
|
49
|
+
...options
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 创建扫描器
|
|
53
|
+
this.scanner = new QRCodeScanner({
|
|
54
|
+
minConfidence: this.options.scanner?.minConfidence,
|
|
55
|
+
returnImage: this.options.scanner?.returnImage,
|
|
56
|
+
imageProcess: this.options.imageProcess
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 初始化模块
|
|
62
|
+
*/
|
|
63
|
+
public async initialize(): Promise<void> {
|
|
64
|
+
if (this._isInitialized) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.logger.debug(this.name, '初始化二维码模块');
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// 初始化扫描器
|
|
72
|
+
await this.scanner.initialize();
|
|
73
|
+
|
|
74
|
+
this._isInitialized = true;
|
|
75
|
+
this.emit('initialized');
|
|
76
|
+
this.logger.debug(this.name, '二维码模块初始化完成');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.logger.error(this.name, '二维码模块初始化失败', error as Error);
|
|
79
|
+
throw new Error(`二维码模块初始化失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 扫描图像中的二维码
|
|
85
|
+
* @param image 图像源
|
|
86
|
+
* @returns 二维码扫描结果
|
|
87
|
+
*/
|
|
88
|
+
public async scan(
|
|
89
|
+
image: ImageData | HTMLImageElement | HTMLCanvasElement
|
|
90
|
+
): Promise<QRCodeResult | undefined> {
|
|
91
|
+
this.ensureInitialized();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// 扫描二维码
|
|
95
|
+
const scanResult = await this.scanner.scan(image);
|
|
96
|
+
|
|
97
|
+
if (scanResult) {
|
|
98
|
+
// 保存最后一次扫描结果
|
|
99
|
+
this.lastScanResult = scanResult;
|
|
100
|
+
|
|
101
|
+
// 触发事件
|
|
102
|
+
this.emit('qrcode:scanned', { result: scanResult });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return scanResult;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.logger.error(this.name, '二维码扫描失败', error as Error);
|
|
108
|
+
throw new Error(`二维码扫描失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取最后一次扫描结果
|
|
114
|
+
*/
|
|
115
|
+
public getLastScanResult(): QRCodeResult | undefined {
|
|
116
|
+
return this.lastScanResult;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 解析二维码数据
|
|
121
|
+
* @param data 二维码数据
|
|
122
|
+
* @returns 解析后的数据对象
|
|
123
|
+
*/
|
|
124
|
+
public parseQRCodeData(data: string): Record<string, any> | string {
|
|
125
|
+
try {
|
|
126
|
+
// 尝试解析为JSON
|
|
127
|
+
return JSON.parse(data);
|
|
128
|
+
} catch {
|
|
129
|
+
// 不是JSON,尝试解析为URL参数
|
|
130
|
+
if (data.includes('=')) {
|
|
131
|
+
try {
|
|
132
|
+
const params: Record<string, string> = {};
|
|
133
|
+
const urlParams = new URLSearchParams(data.includes('?') ? data.split('?')[1] : data);
|
|
134
|
+
|
|
135
|
+
urlParams.forEach((value, key) => {
|
|
136
|
+
params[key] = value;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return params;
|
|
140
|
+
} catch {
|
|
141
|
+
// 解析URL参数失败,返回原始字符串
|
|
142
|
+
return data;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 返回原始字符串
|
|
147
|
+
return data;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 释放模块资源
|
|
153
|
+
*/
|
|
154
|
+
public async dispose(): Promise<void> {
|
|
155
|
+
if (!this._isInitialized) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.logger.debug(this.name, '释放二维码模块资源');
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// 释放扫描器资源
|
|
163
|
+
await this.scanner.dispose();
|
|
164
|
+
|
|
165
|
+
// 调用基类的dispose方法
|
|
166
|
+
await super.dispose();
|
|
167
|
+
} catch (error) {
|
|
168
|
+
this.logger.error(this.name, '二维码模块资源释放失败', error as Error);
|
|
169
|
+
throw new Error(`二维码模块资源释放失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 导出类型
|
|
175
|
+
export * from './types';
|