id-scanner-lib
Version:
Browser-based ID card, QR code, and face recognition scanner with liveness detection
1,635 lines (1,625 loc) • 176 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tesseract.js'), require('browser-image-compression'), require('jsqr')) :
typeof define === 'function' && define.amd ? define(['exports', 'tesseract.js', 'browser-image-compression', 'jsqr'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IDScannerLib = {}, global.tesseract_js, global.imageCompression, global.jsQR));
})(this, (function (exports, tesseract_js, imageCompression, jsQR) { 'use strict';
/**
* @file 配置管理器 + Scanner 配置
* @description 提供全局配置管理功能 + Scanner v2.0 配置接口
* @module core/config
*/
// ============================================================
// 原有 ConfigManager(保持向后兼容)
// ============================================================
class ConfigManager {
constructor() {
this.config = {};
this.changeCallbacks = new Map();
this.initialized = false;
this._resetDefaults();
}
static getInstance() {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
static resetInstance() {
ConfigManager.instance = undefined;
}
_resetDefaults() {
this.config = {
debug: false,
logLevel: 'info',
modelPath: '/models',
maxRetries: 3,
retryDelay: 1000,
detectionInterval: 100,
face: {
enabled: true,
confidenceThreshold: 0.5,
maxFaces: 10,
},
idCard: {
enabled: false,
},
qr: {
enabled: false,
},
};
this.initialized = true;
}
get(key, defaultValue) {
const keys = key.split('.');
let value = this.config;
for (const k of keys) {
value = value?.[k];
}
return (value !== undefined ? value : defaultValue);
}
set(key, value) {
const keys = key.split('.');
let target = this.config;
for (let i = 0; i < keys.length - 1; i++) {
if (!target[keys[i]])
target[keys[i]] = {};
target = target[keys[i]];
}
const oldValue = target[keys[keys.length - 1]];
target[keys[keys.length - 1]] = value;
const callbacks = this.changeCallbacks.get(key);
if (callbacks) {
callbacks.forEach(cb => cb(value, oldValue));
}
}
onChange(key, callback) {
if (!this.changeCallbacks.has(key)) {
this.changeCallbacks.set(key, []);
}
this.changeCallbacks.get(key).push(callback);
}
/** @deprecated Use onChange instead */
onConfigChange(key, callback) {
this.onChange(key, callback);
}
removeChangeCallback(key, callback) {
const callbacks = this.changeCallbacks.get(key);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1)
callbacks.splice(index, 1);
}
}
reset() {
this.config = {};
this._resetDefaults();
}
}
/**
* @file 日志系统
* @description 提供统一的日志记录与管理功能
* @module core/logger
*/
/**
* 日志级别枚举
*/
exports.LoggerLevel = void 0;
(function (LoggerLevel) {
LoggerLevel["DEBUG"] = "debug";
LoggerLevel["INFO"] = "info";
LoggerLevel["WARN"] = "warn";
LoggerLevel["ERROR"] = "error";
})(exports.LoggerLevel || (exports.LoggerLevel = {}));
/**
* @deprecated 使用 LoggerLevel 代替
*/
const LogLevel = exports.LoggerLevel;
/**
* 控制台日志处理器
* 将日志输出到浏览器控制台
*/
class ConsoleLogHandler {
/**
* 处理日志条目
* @param entry 日志条目
*/
handle(entry) {
const timestamp = new Date(entry.timestamp).toISOString();
const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
// 优先使用 error,其次使用 data
const extra = entry.error || entry.data || '';
switch (entry.level) {
case exports.LoggerLevel.DEBUG:
console.debug(prefix, entry.message, extra);
break;
case exports.LoggerLevel.INFO:
console.info(prefix, entry.message, extra);
break;
case exports.LoggerLevel.WARN:
console.warn(prefix, entry.message, extra);
break;
case exports.LoggerLevel.ERROR:
console.error(prefix, entry.message, extra);
break;
// 输出什么也不做
}
}
}
/**
* 内存日志处理器
* 将日志保存在内存中,用于后续分析或显示
*/
class MemoryLogHandler {
/**
* 构造函数
* @param maxEntries 最大日志条目数,默认为1000
*/
constructor(maxEntries = 1000) {
/** 日志条目数组 */
this.entries = [];
this.maxEntries = maxEntries;
}
/**
* 处理日志条目
* @param entry 日志条目
*/
handle(entry) {
this.entries.push(entry);
// 如果超过最大条目数,移除最老的
if (this.entries.length > this.maxEntries) {
this.entries.shift();
}
}
/**
* 获取所有日志条目
*/
getEntries() {
return [...this.entries];
}
/**
* 根据级别过滤日志条目
* @param level 日志级别
*/
getEntriesByLevel(level) {
return this.entries.filter(entry => entry.level === level);
}
/**
* 根据标签过滤日志条目
* @param tag 日志标签
*/
getEntriesByTag(tag) {
return this.entries.filter(entry => entry.tag === tag);
}
/**
* 清空日志
*/
clear() {
this.entries = [];
}
}
/**
* 远程日志处理器
* 将日志发送到远程服务器
*/
class RemoteLogHandler {
/**
* 构造函数
* @param endpoint 远程服务器URL
* @param maxQueueSize 最大队列长度,默认为100
* @param flushInterval 发送间隔(毫秒),默认为5000
*/
constructor(endpoint, maxQueueSize = 100, flushInterval = 5000) {
/** 批量发送的队列 */
this.queue = [];
/** 定时发送的计时器ID */
this.timerId = null;
/** 当前连续失败计数 */
this.consecutiveFailures = 0;
this.endpoint = endpoint;
this.maxQueueSize = maxQueueSize;
this.flushInterval = flushInterval;
this.isBrowser = typeof window !== 'undefined' && typeof window.addEventListener === 'function';
this.maxConsecutiveFailures = 10;
// 设置定时发送
this.startTimer();
// 页面卸载前尝试发送剩余日志
if (this.isBrowser) {
window.addEventListener('beforeunload', () => {
this.flush();
});
}
}
/**
* 处理日志条目
* @param entry 日志条目
*/
handle(entry) {
// 只处理INFO以上级别的日志
if (entry.level >= exports.LoggerLevel.INFO) {
this.queue.push(entry);
// 如果队列满了,立即发送
if (this.queue.length >= this.maxQueueSize) {
this.flush();
}
}
}
/**
* 发送队列中的日志
*/
flush() {
if (this.queue.length === 0)
return;
// 如果连续失败次数过多,停止发送以防止无限循环
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
console.warn('RemoteLogHandler: Too many consecutive failures, stopping. Clear queue.');
this.queue = [];
this.consecutiveFailures = 0;
return;
}
const entriesToSend = [...this.queue];
this.queue = [];
this.sendLogEntries(entriesToSend);
}
/**
* 发送日志条目到远程服务器
* @param entries 日志条目数组
*/
sendLogEntries(entries) {
if (entries.length === 0)
return;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s 超时
fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(entries),
keepalive: true,
signal: controller.signal
}).then(() => {
clearTimeout(timeoutId);
this.consecutiveFailures = 0; // 发送成功,重置失败计数
}).catch((err) => {
clearTimeout(timeoutId);
console.error('Failed to send logs to remote server:', err);
this.consecutiveFailures++;
// 如果失败次数过多,丢弃日志防止内存泄漏
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
console.warn('RemoteLogHandler: Max consecutive failures exceeded, discarding logs');
this.queue = [];
this.consecutiveFailures = 0;
return;
}
// 失败时把日志放回队列,但防止无限增长
const maxReturn = Math.min(entries.length, this.maxQueueSize - this.queue.length);
if (maxReturn > 0) {
const returnedEntries = entries.slice(0, maxReturn);
this.queue = [...returnedEntries, ...this.queue];
}
});
}
/**
* 开始定时发送
*/
startTimer() {
if (!this.isBrowser)
return;
if (this.timerId !== null)
return;
this.timerId = setInterval(() => {
this.flush();
}, this.flushInterval);
}
/**
* 停止定时发送
*/
stopTimer() {
if (this.timerId !== null) {
clearInterval(this.timerId);
this.timerId = null;
}
}
}
/**
* 日志管理类
* 中央日志管理器,提供统一的日志记录接口
*/
class Logger {
/**
* 私有构造函数,防止直接实例化
*/
constructor() {
/** 日志处理器 */
this.handlers = [];
/** 默认标签 */
this.defaultTag = 'IDScanner';
/** 日志级别 */
this.logLevel = exports.LoggerLevel.INFO;
this.config = ConfigManager.getInstance();
// 默认添加控制台处理器
this.addHandler(new ConsoleLogHandler());
// 监听配置变化
this.config.onConfigChange('logLevel', (level) => {
this.debug('Logger', `Log level changed to ${level}`);
});
}
/**
* 获取单例实例
*/
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
/**
* 重置单例实例(主要用于测试)
*/
static resetInstance() {
Logger.instance = undefined;
}
/**
* 添加日志处理器
* @param handler 日志处理器
*/
addHandler(handler) {
this.handlers.push(handler);
}
/**
* 移除日志处理器
* @param handler 要移除的处理器
*/
removeHandler(handler) {
const index = this.handlers.indexOf(handler);
if (index !== -1) {
this.handlers.splice(index, 1);
}
}
/**
* 移除所有处理器
*/
clearHandlers() {
this.handlers = [];
}
/**
* 设置默认标签
* @param tag 默认标签
*/
setDefaultTag(tag) {
this.defaultTag = tag;
}
/**
* 记录调试级别日志
* @param tag 标签
* @param message 消息
* @param errorOrData 错误对象或结构化数据
*/
debug(tag, message, errorOrData) {
if (errorOrData instanceof Error) {
this.log(exports.LoggerLevel.DEBUG, tag, message, errorOrData);
}
else {
this.logWithData(exports.LoggerLevel.DEBUG, tag, message, errorOrData);
}
}
/**
* 记录信息级别日志
* @param tag 标签
* @param message 消息
* @param errorOrData 错误对象或结构化数据
*/
info(tag, message, errorOrData) {
if (errorOrData instanceof Error) {
this.log(exports.LoggerLevel.INFO, tag, message, errorOrData);
}
else {
this.logWithData(exports.LoggerLevel.INFO, tag, message, errorOrData);
}
}
/**
* 记录警告级别日志
* @param tag 标签
* @param message 消息
* @param errorOrData 错误对象或结构化数据
*/
warn(tag, message, errorOrData) {
if (errorOrData instanceof Error) {
this.log(exports.LoggerLevel.WARN, tag, message, errorOrData);
}
else {
this.logWithData(exports.LoggerLevel.WARN, tag, message, errorOrData);
}
}
/**
* 记录错误级别日志
* @param tag 标签
* @param message 消息
* @param errorOrData 错误对象或结构化数据
*/
error(tag, message, errorOrData) {
if (errorOrData instanceof Error) {
this.log(exports.LoggerLevel.ERROR, tag, message, errorOrData);
}
else {
this.logWithData(exports.LoggerLevel.ERROR, tag, message, errorOrData);
}
}
/**
* 创建标记了特定标签的日志记录器
* @param tag 标签
*/
getTaggedLogger(tag) {
return new TaggedLogger(this, tag);
}
/**
* 记录日志
* @param level 日志级别
* @param tag 标签
* @param message 消息
* @param error 错误
*/
log(level, tag, message, error) {
// 检查日志级别
const levelValue = this.getLevelValue(level);
const currentLevelValue = this.getLevelValue(this.logLevel);
if (levelValue < currentLevelValue) {
return;
}
// 创建日志条目
const entry = {
timestamp: Date.now(),
level: level,
tag: tag || this.defaultTag,
message,
error
};
// 分发到所有处理程序
for (const handler of this.handlers) {
try {
handler.handle(entry);
}
catch (handlerError) {
console.error(`[Logger] 处理程序错误:`, handlerError);
}
}
// 如果没有处理程序,使用控制台
if (this.handlers.length === 0) {
this.consoleOutput(entry);
}
}
/**
* 记录日志(支持结构化数据)
* @param level 日志级别
* @param tag 标签
* @param message 消息
* @param data 结构化数据
*/
logWithData(level, tag, message, data) {
// 检查日志级别
const levelValue = this.getLevelValue(level);
const currentLevelValue = this.getLevelValue(this.logLevel);
if (levelValue < currentLevelValue) {
return;
}
// 创建日志条目
const entry = {
timestamp: Date.now(),
level: level,
tag: tag || this.defaultTag,
message,
data
};
// 分发到所有处理程序
for (const handler of this.handlers) {
try {
handler.handle(entry);
}
catch (handlerError) {
console.error(`[Logger] 处理程序错误:`, handlerError);
}
}
// 如果没有处理程序,使用控制台
if (this.handlers.length === 0) {
this.consoleOutput(entry);
}
}
/**
* 控制台输出
* @param entry 日志条目
*/
consoleOutput(entry) {
const timestamp = new Date(entry.timestamp).toISOString();
const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
// 构造日志内容:错误对象或数据对象
const extra = entry.error || entry.data || '';
switch (entry.level) {
case exports.LoggerLevel.DEBUG:
console.debug(`${prefix} ${entry.message}`, extra);
break;
case exports.LoggerLevel.INFO:
console.info(`${prefix} ${entry.message}`, extra);
break;
case exports.LoggerLevel.WARN:
console.warn(`${prefix} ${entry.message}`, extra);
break;
case exports.LoggerLevel.ERROR:
console.error(`${prefix} ${entry.message}`, extra);
break;
}
}
/**
* 获取日志级别值
* @param level 日志级别
*/
getLevelValue(level) {
switch (level) {
case exports.LoggerLevel.DEBUG:
return 0;
case exports.LoggerLevel.INFO:
return 1;
case exports.LoggerLevel.WARN:
return 2;
case exports.LoggerLevel.ERROR:
return 3;
default:
return 1; // 默认INFO级别
}
}
/**
* 设置日志级别
* @param level 日志级别
*/
setLevel(level) {
if (typeof level === 'string') {
switch (level) {
case 'debug':
this.logLevel = exports.LoggerLevel.DEBUG;
break;
case 'info':
this.logLevel = exports.LoggerLevel.INFO;
break;
case 'warn':
this.logLevel = exports.LoggerLevel.WARN;
break;
case 'error':
this.logLevel = exports.LoggerLevel.ERROR;
break;
default:
this.logLevel = exports.LoggerLevel.INFO;
}
}
else {
this.logLevel = level;
}
this.debug('Logger', `日志级别已设置为 ${this.logLevel}`);
}
/**
* 获取当前日志级别
* @returns 当前日志级别
*/
getLevel() {
return this.logLevel;
}
}
/**
* 带标签的日志记录器
* 提供特定标签的简易日志接口
*/
class TaggedLogger {
/**
* 构造函数
* @param logger 所属的主日志记录器
* @param tag 标签
*/
constructor(logger, tag) {
this.logger = logger;
this.tag = tag;
}
/**
* 记录调试级别日志
* @param message 消息
* @param error 错误
*/
debug(message, error) {
this.logger.debug(this.tag, message, error);
}
/**
* 记录信息级别日志
* @param message 消息
* @param error 错误
*/
info(message, error) {
this.logger.info(this.tag, message, error);
}
/**
* 记录警告级别日志
* @param message 消息
* @param error 错误
*/
warn(message, error) {
this.logger.warn(this.tag, message, error);
}
/**
* 记录错误级别日志
* @param message 消息
* @param error 错误
*/
error(message, error) {
this.logger.error(this.tag, message, error);
}
}
/**
* 日志级别枚举
*/
/**
* @file 事件发射器
* @description 提供基础的事件发射和订阅功能
* @module core/event-emitter
*/
/**
* 事件发射器基类
* 提供基础的事件发射和订阅功能
*/
class EventEmitter {
constructor() {
/** 事件处理器映射 */
this.eventHandlers = new Map();
}
/**
* 订阅事件
* @param eventName 事件名称
* @param handler 事件处理器
*/
on(eventName, handler) {
if (!this.eventHandlers.has(eventName)) {
this.eventHandlers.set(eventName, new Set());
}
this.eventHandlers.get(eventName).add(handler);
}
/**
* 取消订阅事件
* @param eventName 事件名称
* @param handler 事件处理器,如果不提供则移除该事件的所有处理器
*/
off(eventName, handler) {
if (!this.eventHandlers.has(eventName)) {
return;
}
if (handler) {
this.eventHandlers.get(eventName).delete(handler);
// 如果没有处理器了,删除这个事件
if (this.eventHandlers.get(eventName).size === 0) {
this.eventHandlers.delete(eventName);
}
}
else {
// 移除该事件的所有处理器
this.eventHandlers.delete(eventName);
}
}
/**
* 取消订阅事件 (off的别名)
* @param eventName 事件名称
* @param handler 事件处理器
*/
removeListener(eventName, handler) {
this.off(eventName, handler);
}
/**
* 订阅事件,但只触发一次
* @param eventName 事件名称
* @param handler 事件处理器
*/
once(eventName, handler) {
const onceHandler = (data) => {
handler(data);
this.off(eventName, onceHandler);
};
this.on(eventName, onceHandler);
}
/**
* 发射事件
* @param eventName 事件名称
* @param data 事件数据
*/
emit(eventName, data) {
if (!this.eventHandlers.has(eventName)) {
return;
}
for (const handler of this.eventHandlers.get(eventName)) {
try {
handler(data);
}
catch (error) {
console.error(`Error in event handler for "${eventName}":`, error);
}
}
}
/**
* 获取某个事件的处理器数量
* @param eventName 事件名称
*/
listenerCount(eventName) {
return this.eventHandlers.has(eventName) ? this.eventHandlers.get(eventName).size : 0;
}
/**
* 移除所有事件处理器
*/
removeAllListeners() {
this.eventHandlers.clear();
}
/**
* 获取所有事件名称
*/
eventNames() {
return Array.from(this.eventHandlers.keys());
}
}
/**
* @file 版本号文件
* @description 定义库的版本号
* @module Version
*/
// 当前版本号
const VERSION = '1.3.3';
// 构建日期
const BUILD_DATE = new Date().toISOString();
/* eslint-disable */
/**
* @file 模块管理器
* @description 统一管理库的各功能模块,提供模块的注册、初始化和卸载功能
* @module core/module-manager
*/
/**
* 模块管理器类
* 负责管理所有功能模块的生命周期
*/
class ModuleManager extends EventEmitter {
/**
* 获取模块管理器单例
*/
static getInstance() {
if (!ModuleManager.instance) {
ModuleManager.instance = new ModuleManager();
}
return ModuleManager.instance;
}
/**
* 重置单例实例(主要用于测试)
*/
static resetInstance() {
ModuleManager.instance = undefined;
}
/**
* 私有构造函数,确保单例模式
*/
constructor() {
super();
this.modules = new Map();
this.initialized = false;
this.initPromise = null;
this.logger = Logger.getInstance();
this.logger.debug('ModuleManager', `初始化模块管理器 v${VERSION}`);
}
/**
* 注册模块
* @param module 要注册的模块
* @returns 模块管理器实例,支持链式调用
*/
register(module) {
if (this.modules.has(module.name)) {
this.logger.warn('ModuleManager', `模块 "${module.name}" 已经注册,将被覆盖`);
}
this.modules.set(module.name, module);
this.logger.debug('ModuleManager', `注册模块: ${module.name} v${module.version}`);
this.emit('module:registered', { name: module.name });
return this;
}
/**
* 获取模块
* @param name 模块名称
* @returns 模块实例
*/
getModule(name) {
return this.modules.get(name);
}
/**
* 初始化所有注册的模块
*/
async initialize() {
if (this.initialized) {
return;
}
this.logger.debug('ModuleManager', '开始初始化所有模块...');
for (const [name, module] of this.modules.entries()) {
try {
this.logger.debug('ModuleManager', `初始化模块: ${name}`);
await module.initialize();
this.emit('module:initialized', { name });
this.logger.debug('ModuleManager', `模块 ${name} 初始化完成`);
}
catch (error) {
this.logger.error('ModuleManager', `模块 ${name} 初始化失败`, error instanceof Error ? error : undefined);
this.emit('module:error', { name, error });
throw new Error(`模块 ${name} 初始化失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
this.initialized = true;
this.logger.debug('ModuleManager', '所有模块初始化完成');
this.emit('modules:initialized');
}
/**
* 卸载所有模块并释放资源
*/
async dispose() {
this.logger.debug('ModuleManager', '开始释放所有模块资源...');
for (const [name, module] of this.modules.entries()) {
try {
this.logger.debug('ModuleManager', `释放模块资源: ${name}`);
await module.dispose();
this.emit('module:disposed', { name });
}
catch (error) {
this.logger.error('ModuleManager', `模块 ${name} 资源释放失败`, error instanceof Error ? error : undefined);
this.emit('module:error', { name, error });
}
}
this.modules.clear();
this.initialized = false;
this.logger.debug('ModuleManager', '所有模块资源已释放');
this.emit('modules:disposed');
}
/**
* 获取所有已注册的模块名称
*/
getRegisteredModules() {
return Array.from(this.modules.keys());
}
/**
* 检查模块是否已注册
* @param name 模块名称
*/
hasModule(name) {
return this.modules.has(name);
}
}
/**
* @file 基础模块
* @description 提供基础模块实现,作为所有功能模块的基类
* @module core/base-module
*/
/**
* 基础模块类
* 提供模块的基本功能和生命周期管理
*/
class BaseModule extends EventEmitter {
/**
* 构造函数
*/
constructor() {
super();
/** 模块版本 */
this.version = VERSION;
/** 模块是否已初始化 */
this._isInitialized = false;
this.logger = Logger.getInstance();
}
/**
* 获取模块是否已初始化
*/
get isInitialized() {
return this._isInitialized;
}
/**
* 释放模块资源
* 子类可以覆盖此方法以添加额外的资源释放逻辑
*/
async dispose() {
if (!this._isInitialized) {
return;
}
this.logger.debug(this.name, '释放模块资源');
// 重置初始化状态
this._isInitialized = false;
// 删除所有事件监听器
this.removeAllListeners();
this.logger.debug(this.name, '模块资源已释放');
}
/**
* 检查模块是否已初始化,如果未初始化则抛出错误
*/
ensureInitialized() {
if (!this._isInitialized) {
throw new Error(`模块 ${this.name} 尚未初始化`);
}
}
}
/**
* @file 结果包装类
* @description 提供统一的操作结果封装
* @module core/result
*/
/**
* 结果类型
* 用于封装操作的成功或失败结果
*/
class Result {
/**
* 构造函数
* @param success 是否成功
* @param data 结果数据
* @param error 错误对象
* @param meta 元数据
*/
constructor(success, data, error, meta) {
this._success = success;
this._data = data;
this._error = error;
this._meta = meta;
}
/**
* 创建成功结果
* @param data 结果数据
* @param meta 元数据
*/
static success(data, meta) {
return new Result(true, data, undefined, meta);
}
/**
* 创建失败结果
* @param error 错误对象
* @param meta 元数据
*/
static failure(error, meta) {
return new Result(false, undefined, error, meta);
}
/**
* 检查结果是否成功
*/
isSuccess() {
return this._success;
}
/**
* 检查结果是否失败
*/
isFailure() {
return !this._success;
}
/**
* 获取结果数据
*/
getData() {
return this._data;
}
/**
* 获取错误对象
*/
getError() {
return this._error;
}
/**
* 获取元数据
*/
getMeta() {
return this._meta;
}
/**
* 映射结果(如果成功)
* @param fn 映射函数
*/
map(fn) {
if (this.isSuccess() && this._data !== undefined) {
try {
const newData = fn(this._data);
return Result.success(newData, this._meta);
}
catch (error) {
return Result.failure(error instanceof Error ? error : new Error(String(error)), this._meta);
}
}
return Result.failure(this._error, this._meta);
}
/**
* 如果成功,则执行函数
* @param fn 要执行的函数
*/
onSuccess(fn) {
if (this.isSuccess()) {
try {
fn(this._data);
}
catch (error) {
console.error('Error in onSuccess handler:', error);
}
}
return this;
}
/**
* 如果失败,则执行函数
* @param fn 要执行的函数
*/
onFailure(fn) {
if (this.isFailure() && this._error) {
try {
fn(this._error);
}
catch (error) {
console.error('Error in onFailure handler:', error);
}
}
return this;
}
/**
* 无论成功失败,都执行函数
* @param fn 要执行的函数
*/
onFinally(fn) {
try {
fn();
}
catch (error) {
console.error('Error in onFinally handler:', error);
}
return this;
}
/**
* 转换为字符串
*/
toString() {
if (this.isSuccess()) {
return `Success: ${JSON.stringify(this._data)}`;
}
else {
return `Failure: ${this._error?.message || 'Unknown error'}`;
}
}
}
/**
* @file 身份证模块类型定义
* @description 身份证模块相关的类型和接口定义
* @module modules/id-card/types
*/
/**
* 身份证类型枚举
*/
exports.IDCardType = void 0;
(function (IDCardType) {
/** 第二代居民身份证正面 */
IDCardType["FRONT"] = "front";
/** 第二代居民身份证背面 */
IDCardType["BACK"] = "back";
/** 第一代居民身份证 */
IDCardType["FIRST_GENERATION"] = "first_generation";
/** 临时身份证 */
IDCardType["TEMPORARY"] = "temporary";
/** 外国人永久居留证 */
IDCardType["FOREIGN_PERMANENT"] = "foreign_permanent";
/** 港澳台居民居住证 */
IDCardType["HMT_RESIDENT"] = "hmt_resident";
/** 未知类型 */
IDCardType["UNKNOWN"] = "unknown";
})(exports.IDCardType || (exports.IDCardType = {}));
/**
* @file 身份证检测器
* @description 提供身份证检测和解析功能
* @module modules/id-card/id-card-detector
*/
/**
* 身份证检测器类
*/
class IDCardDetector extends EventEmitter {
/**
* 构造函数
* @param options 配置选项
*/
constructor(options = {}) {
super();
this.initialized = false;
this.models = {};
/** 重用的 Canvas 元素,用于减少内存分配 */
this.reusableCanvas = null;
this.reusableContext = null;
this.options = {
enabled: true,
minConfidence: 0.7,
detectType: true,
detectEdge: true,
enableEdgeDetection: false,
enableOCR: true,
cropAndAlign: true,
enableAntiFake: false,
returnImage: false,
modelPath: '/models/id-card',
...options
};
this.logger = Logger.getInstance();
}
/**
* 初始化检测器
*/
async initialize() {
if (this.initialized || !this.options.enabled) {
return;
}
this.logger.debug('IDCardDetector', '初始化身份证检测器');
try {
// 加载检测模型
await this.loadDetectionModel();
// 如果启用OCR,加载OCR模型
if (this.options.enableOCR) {
await this.loadOCRModel();
}
// 如果启用防伪检测,加载防伪模型
if (this.options.enableAntiFake) {
await this.loadAntiFakeModel();
}
this.initialized = true;
this.emit('detector:initialized', {});
this.logger.debug('IDCardDetector', '身份证检测器初始化完成');
}
catch (error) {
this.logger.error('IDCardDetector', '身份证检测器初始化失败', error);
throw error;
}
}
/**
* 加载检测模型
* @private
*/
async loadDetectionModel() {
// 实际项目中,这里应该加载检测模型
this.logger.debug('IDCardDetector', '加载身份证检测模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.detection = {
loaded: true,
name: 'id-card-detection'
};
}
/**
* 加载OCR模型
* @private
*/
async loadOCRModel() {
// 实际项目中,这里应该加载OCR模型
this.logger.debug('IDCardDetector', '加载身份证OCR模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.ocr = {
loaded: true,
name: 'id-card-ocr'
};
}
/**
* 加载防伪模型
* @private
*/
async loadAntiFakeModel() {
// 实际项目中,这里应该加载防伪模型
this.logger.debug('IDCardDetector', '加载身份证防伪模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.antiFake = {
loaded: true,
name: 'id-card-anti-fake'
};
}
/**
* 处理图像
* @param image 图像源(可以是ImageData、HTMLImageElement、HTMLCanvasElement等)
* @param processOptions 图像处理选项
* @returns 处理结果
*/
async processImage(image, processOptions = {}) {
if (!this.initialized) {
return Result.failure(new Error('身份证检测器未初始化'));
}
// 输入验证
if (!image) {
return Result.failure(new Error('图像源不能为空'));
}
// 验证 HTMLImageElement 是否已加载
if (image instanceof HTMLImageElement && !image.complete) {
return Result.failure(new Error('图像尚未加载完成'));
}
// 验证 ImageData 尺寸
if (image instanceof ImageData) {
if (image.width === 0 || image.height === 0) {
return Result.failure(new Error('图像尺寸无效'));
}
}
// 验证 Canvas 尺寸
if (image instanceof HTMLCanvasElement) {
if (image.width === 0 || image.height === 0) {
return Result.failure(new Error('Canvas尺寸无效'));
}
}
try {
this.logger.debug('IDCardDetector', '开始处理图像');
// 预处理图像
const processedImage = await this.preprocessImage(image, processOptions);
// 检测身份证
const detectionResult = await this.detectIDCard(processedImage);
if (!detectionResult || detectionResult.confidence < (this.options.minConfidence || 0.7)) {
return Result.failure(new Error('未检测到身份证或置信度过低'));
}
let idCardInfo = {
type: detectionResult.type,
edge: detectionResult.edge,
confidence: detectionResult.confidence
};
// 如果启用OCR识别,提取文字信息
if (this.options.enableOCR && this.models.ocr) {
// 裁剪并校正图像
const alignedImage = this.options.cropAndAlign ?
await this.cropAndAlign(processedImage, detectionResult.edge) :
processedImage;
// 识别文字
const ocrResult = await this.recognizeText(alignedImage, detectionResult.type);
// 合并结果
idCardInfo = {
...idCardInfo,
...ocrResult
};
}
// 如果启用防伪检测,进行防伪检测
if (this.options.enableAntiFake && this.models.antiFake) {
const antiFakeResult = await this.detectAntiFake(processedImage, detectionResult);
idCardInfo.antiFake = antiFakeResult;
}
// 如果需要返回原始图像
if (this.options.returnImage) {
// 根据图像类型获取ImageData
if (image instanceof ImageData) {
idCardInfo.image = image;
}
else if (image instanceof HTMLCanvasElement) {
const context = image.getContext('2d');
if (context) {
idCardInfo.image = context.getImageData(0, 0, image.width, image.height);
}
}
else if (image instanceof HTMLImageElement && image.complete) {
const canvas = this.getReusableCanvas(image.naturalWidth, image.naturalHeight);
const context = canvas.getContext('2d');
if (context) {
context.drawImage(image, 0, 0);
idCardInfo.image = context.getImageData(0, 0, canvas.width, canvas.height);
}
}
}
this.logger.debug('IDCardDetector', '图像处理完成');
this.emit('detector:result', { result: idCardInfo });
return Result.success(idCardInfo);
}
catch (error) {
this.logger.error('IDCardDetector', '图像处理失败', error);
return Result.failure(error);
}
}
/**
* 获取可重用的 Canvas 元素
* @param width 宽度
* @param height 高度
* @returns CanvasRenderingContext2D
*/
getReusableCanvas(width, height) {
// 如果存在可重用的 canvas 且尺寸匹配,直接返回
if (this.reusableCanvas &&
this.reusableCanvas.width === width &&
this.reusableCanvas.height === height) {
return this.reusableCanvas;
}
// 创建新的 canvas
this.reusableCanvas = document.createElement('canvas');
this.reusableCanvas.width = width;
this.reusableCanvas.height = height;
this.reusableContext = this.reusableCanvas.getContext('2d');
return this.reusableCanvas;
}
/**
* 预处理图像
* @param image 图像源
* @param options 处理选项
* @returns 处理后的图像
* @private
*/
async preprocessImage(image, options) {
this.logger.debug('IDCardDetector', '预处理图像');
// 创建ImageData对象
let imageData;
if (image instanceof ImageData) {
imageData = image;
}
else {
const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
const canvas = this.getReusableCanvas(width, height);
const context = canvas.getContext('2d');
if (!context) {
throw new Error('无法获取Canvas上下文');
}
if (image instanceof HTMLImageElement) {
context.drawImage(image, 0, 0);
}
else {
context.drawImage(image, 0, 0);
}
imageData = context.getImageData(0, 0, width, height);
}
// 应用图像处理选项
// 实际项目中,这里应该根据options进行相应的图像处理
return imageData;
}
/**
* 检测身份证
* @param image 图像数据
* @returns 检测结果
* @private
*/
async detectIDCard(image) {
// 实际项目中,这里应该调用模型进行身份证检测
this.logger.debug('IDCardDetector', '检测身份证');
// 模拟检测结果
// 在实际应用中,这里应该使用机器学习模型进行推理
return {
type: exports.IDCardType.FRONT,
edge: {
topLeft: { x: 10, y: 10 },
topRight: { x: image.width - 10, y: 10 },
bottomRight: { x: image.width - 10, y: image.height - 10 },
bottomLeft: { x: 10, y: image.height - 10 }
},
confidence: 0.95
};
}
/**
* 裁剪并校正图像
* @param image 图像数据
* @param edge 边缘信息
* @returns 校正后的图像
* @private
*/
async cropAndAlign(image, edge) {
this.logger.debug('IDCardDetector', '裁剪并校正图像');
// 设置标准身份证尺寸比例
const standardWidth = 428;
const standardHeight = 270;
const canvas = this.getReusableCanvas(standardWidth, standardHeight);
const context = canvas.getContext('2d');
if (!context) {
throw new Error('无法获取Canvas上下文');
}
// 创建临时Canvas用于源图像
const tempCanvas = document.createElement('canvas');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
const tempContext = tempCanvas.getContext('2d');
if (!tempContext) {
throw new Error('无法获取临时Canvas上下文');
}
// 将ImageData绘制到临时Canvas
tempContext.putImageData(image, 0, 0);
// 在实际应用中,这里应该使用透视变换算法
// 例如使用Canvas的transform或WebGL进行变换
// 简化处理:直接裁剪
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);
return context.getImageData(0, 0, standardWidth, standardHeight);
}
/**
* 识别文字
*
* @note 此方法返回模拟数据,用于框架开发和测试
* 实际使用时需要替换为真实的 OCR 模型集成
*
* @param image 图像数据
* @param type 身份证类型
* @returns 识别结果
* @private
*/
async recognizeText(image, type) {
this.logger.debug('IDCardDetector', '识别文字');
// 模拟OCR结果
// 注意:这是框架的占位实现,真实场景需要接入实际的 OCR 服务
// 可选的方案包括:
// - TensorFlow.js + 自定义 OCR 模型
// - 第三方 OCR API (如百度OCR、腾讯OCR)
// - Tesseract.js WASM 版本
//
if (type === exports.IDCardType.FRONT) {
return {
name: '张三', // TODO: 替换为真实OCR结果
gender: '男', // TODO: 替换为真实OCR结果
ethnicity: '汉', // TODO: 替换为真实OCR结果
birthDate: '1990-01-01', // TODO: 替换为真实OCR结果
address: '北京市朝阳区某某街道某某社区1号楼1单元101', // TODO: 替换为真实OCR结果
idNumber: '110101199001010001', // TODO: 替换为真实OCR结果
photoRegion: {
x: 300,
y: 40,
width: 100,
height: 130
}
};
}
else if (type === exports.IDCardType.BACK) {
return {
issueAuthority: '北京市公安局朝阳分局', // TODO: 替换为真实OCR结果
validFrom: '2015-01-01', // TODO: 替换为真实OCR结果
validTo: '2035-01-01' // TODO: 替换为真实OCR结果
};
}
return {};
}
/**
* 检测防伪特征
*
* @note 此方法返回模拟数据,用于框架开发和测试
* 实际使用时需要替换为真实的防伪检测模型
*
* @param image 图像数据
* @param detectionResult 检测结果
* @returns 防伪检测结果
* @private
*/
async detectAntiFake(image, detectionResult) {
this.logger.debug('IDCardDetector', '检测防伪特征');
// 模拟防伪检测结果
// 注意:这是框架的占位实现,真实场景需要接入实际的防伪检测模型
// 可选的方案包括:
// - 紫外光特征检测
// - 红外光特征检测
// - 微缩文字检测
// - 光学变色特征检测
return {
passed: true,
score: 0.92,
features: {
fluorescent: true, // TODO: 替换为真实检测结果
microtext: true, // TODO: 替换为真实检测结果
opticalVariable: true, // TODO: 替换为真实检测结果
texture: true, // TODO: 替换为真实检测结果
watermark: true // TODO: 替换为真实检测结果
}
};
}
/**
* 释放资源
*/
dispose() {
this.logger.debug('IDCardDetector', '释放资源');
// 清理模型
this.models = {};
this.initialized = false;
// 清理可重用的 Canvas
this.reusableCanvas = null;
this.reusableContext = null;
// 清理事件监听
this.removeAllListeners();
}
}
/**
* 格式化日期字符串为标准格式 (YYYY-MM-DD)
*/
function formatDateString(dateStr) {
const dateMatch = dateStr.match(/(\d{4})[-\.\u5e74\s]*(\d{1,2})[-\.\u6708\s]*(\d{1,2})[日]*/);
if (dateMatch) {
const year = dateMatch[1];
const month = dateMatch[2].padStart(2, "0");
const day = dateMatch[3].padStart(2, "0");
return `${year}-${month}-${day}`;
}
if (/^\d{8}$/.test(dateStr)) {
const year = dateStr.substring(0, 4);
const month = dateStr.substring(4, 6);
const day = dateStr.substring(6, 8);
return `${year}-${month}-${day}`;
}
return dateStr;
}
/**
* IDCardTextParser - 统一解析身份证OCR文本
* 提取 ocr-processor.ts 和 ocr-worker.ts 中的解析逻辑
*/
class IDCardTextParser {
/**
* 解析身份证文本
* @param text OCR识别的原始文本
* @returns 解析后的身份证信息
*/
static parse(text) {
const info = {};
const processedText = text.replace(/\s+/g, " ").trim();
const lines = processedText.split("\n").filter((line) => line.trim());
// 1. 解析身份证号码
const idNumberRegex = /(\d{17}[\dX])/;
const idNumberWithPrefixRegex = /公民身份号码[\s\:]*(\d{17}[\dX])/;
const basicMatch = processedText.match(idNumberRegex);
const prefixMatch = processedText.match(idNumberWithPrefixRegex);
if (prefixMatch && prefixMatch[1]) {
info.idNumber = prefixMatch[1];
}
else if (basicMatch && basicMatch[1]) {
info.idNumber = basicMatch[1];
}
// 2. 解析姓名
const nameWithLabelRegex = /姓名[\s\:]*([一-龥]{2,4})/;
const nameMatch = processedText.match(nameWithLabelRegex);
if (nameMatch && nameMatch[1]) {
info.name = nameMatch[1].trim();
}
else {
for (const line of lines) {
if (line.length >= 2 && line.length <= 5 && /^[一-龥]+$/.test(line) &&
!/性别|民族|住址|公民|签发|有效/.test(line)) {
info.name = line.trim();
break;
}
}
}
// 3. 解析性别和民族
const genderAndNationalityRegex = /性别[\s\:]*([男女])[\s ]*民族[\s\:]*([一-龥]+族)/;
const genderOnlyRegex = /性别[\s\:]*([男女])/;
const nationalityOnlyRegex = /民族[\s\:]*([一-龥]+族)/;
const genderNationalityMatch = processedText.match(genderAndNationalityRegex);
const genderOnlyMatch = processedText.match(genderOnlyRegex);
const nationalityOnlyMatch = processedText.match(nationalityOnlyRegex);
if (genderNationalityMatch) {
info.gender = genderNationalityMatch[1];
info.ethnicity = genderNationalityMatch[2];
}
else {
if (genderOnlyMatch)
info.gender = genderOnlyMatch[1];
if (nationalityOnlyMatch)
info.ethnicity = nationalityOnlyMatch[1];
}
// 4. 判断身份证类型
if (processedText.includes('出生') || processedText.includes('公民身份号码')) {
info.type = exports.IDCardType.FRONT;
}
else if (processedText.includes('签发机关') || processedText.includes('有效期')) {
info.type = exports.IDCardType.BACK;
}
// 5. 解析出生日期
const birthDateRegex1 = /出生[\s\:]*(\d{4})年(\d{1,2})月(\d{1,2})[日号]/;
const birthDateRegex2 = /出生[\s\:]*(\d{4})[-\/\.](\d{1,2})[-\/\.](\d{1,2})/;
const birthDateRegex3 = /出生日期[\s\:]*(\d{4})[-\/\.\u5e74](\d{1,2})[-\/\.\u6708](\d{1,2})[日号]?/;
const birthDateMatch = processedText.match(birthDateRegex1) || processedText.match(birthDateRegex2) || processedText.match(birthDateRegex3);
if (!birthDateMatch && info.idNumber && info.idNumber.length === 18) {
const year = info.idNumber.substring(6, 10);
const month = info.idNumber.substring(10, 12);
const day = info.idNumber.substring(12, 14);
info.birthDate = `${year}-${month}-${day}`;
}
else if (birthDateMatch) {
const year = birthDateMatch[1];
const month = birthDateMatch[2].padStart(2, "0");
const day = birthDateMatch[3].padStart(2, "0");
info.birthDate = `${year}-${month}-${day}`;
}
// 6. 解析地址
const addressRegex1 = /住址[\s\:]*([\s\S]*?)(?=公民身份|出生|性别|签发)/;
const addressRegex2 = /住址[\s\:]*([一-龥a-zA-Z0-9\s\.\-]+)/;
const addressMatch = processedText.match(addressRegex1) || processedText.match(addressRegex2);
if (addressMatch && addressMatch[1]) {
info.address = addressMatch[1].replace(/\s+/g, "").replace(/\n/g, "").trim();
if (info.address.length > 70)
info.address = info.address.substring(0, 70);
if (!/[一-龥]/.test(info.address))
info.address = '';
}
// 7. 解析签发机关
const authorityRegex1 = /签发机关[\s\:]*([\s\S]*?)(?=有效|公民|出生|\d{8}|$)/;
const authorityRegex2 = /签发机关[\s\:]*([一-龥\s]+)/;
const authorityMatch = processedText.match(authorityRegex1) || processedText.match(authorityRegex2);
if (authorityMatch && authorityMatch[1]) {
info.issueAuthority = authorityMatch[1].replace(/\s+/g, "").replace(/\n/g, "").trim();
}
// 8. 解析有效期限
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}[日]*|[永久长期]*)/;
const vali