id-scanner-lib
Version:
Browser-based ID card, QR code, and face recognition scanner with liveness detection
1,621 lines (1,609 loc) • 488 kB
JavaScript
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tesseract.js'), require('browser-image-compression')) :
typeof define === 'function' && define.amd ? define(['exports', 'tesseract.js', 'browser-image-compression'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IDScannerLib = {}, global.Tesseract, global.imageCompression));
})(this, (function (exports, tesseract_js, imageCompression) { 'use strict';
/**
* @file 配置管理器
* @description 提供全局配置管理功能
* @module core/config
*/
/**
* 配置管理器
* 负责存储和管理应用程序的配置
*/
class ConfigManager {
/**
* 私有构造函数
*/
constructor() {
/** 配置存储 */
this.config = {};
/** 配置变更回调 */
this.changeCallbacks = new Map();
// 设置默认配置
this.config = {
debug: false,
logLevel: 'info',
camera: {
resolution: {
width: 1280,
height: 720
},
frameRate: 30,
facingMode: 'environment'
},
performance: {
useCache: true
}
};
}
/**
* 获取单例实例
*/
static getInstance() {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
/**
* 获取配置值
* @param key 配置键,支持点号分隔的路径
* @param defaultValue 默认值
*/
get(key, defaultValue) {
const value = this.getNestedValue(this.config, key);
return (value !== undefined) ? value : defaultValue;
}
/**
* 设置配置值
* @param key 配置键,支持点号分隔的路径
* @param value 配置值
*/
set(key, value) {
const oldValue = this.get(key);
// 如果值相同,不做任何事
if (oldValue === value) {
return;
}
this.setNestedValue(this.config, key, value);
// 触发变更回调
this.triggerChangeCallbacks(key, value, oldValue);
}
/**
* 批量更新配置
* @param config 配置对象
*/
updateConfig(config) {
Object.entries(config).forEach(([key, value]) => {
this.set(key, value);
});
}
/**
* 重置为默认配置
*/
reset() {
const oldConfig = { ...this.config };
// 重新创建默认配置
this.config = {
debug: false,
logLevel: 'info',
camera: {
resolution: {
width: 1280,
height: 720
},
frameRate: 30,
facingMode: 'environment'
},
performance: {
useCache: true
}
};
// 触发所有回调
Object.keys(oldConfig).forEach(key => {
this.triggerChangeCallbacks(key, this.get(key), oldConfig[key]);
});
}
/**
* 注册配置变更回调
* @param key 配置键
* @param callback 回调函数
*/
onConfigChange(key, callback) {
if (!this.changeCallbacks.has(key)) {
this.changeCallbacks.set(key, []);
}
this.changeCallbacks.get(key).push(callback);
}
/**
* 移除配置变更回调
* @param key 配置键
* @param callback 特定回调函数,如不提供则移除所有
*/
offConfigChange(key, callback) {
if (!this.changeCallbacks.has(key)) {
return;
}
if (callback) {
// 移除特定回调
const callbacks = this.changeCallbacks.get(key);
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
// 如果没有回调,删除键
if (callbacks.length === 0) {
this.changeCallbacks.delete(key);
}
}
else {
// 移除所有回调
this.changeCallbacks.delete(key);
}
}
/**
* 获取嵌套值
* @param obj 对象
* @param path 路径
*/
getNestedValue(obj, path) {
// 处理根路径
if (!path) {
return obj;
}
// 处理嵌套路径
const parts = path.split('.');
let current = obj;
for (const part of parts) {
if (current === undefined || current === null) {
return undefined;
}
current = current[part];
}
return current;
}
/**
* 设置嵌套值
* @param obj 对象
* @param path 路径
* @param value 值
*/
setNestedValue(obj, path, value) {
// 处理根路径
if (!path) {
return;
}
// 处理嵌套路径
const parts = path.split('.');
let current = obj;
// 遍历路径,直到倒数第二部分
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
// 如果不存在,创建新对象
if (current[part] === undefined || current[part] === null || typeof current[part] !== 'object') {
current[part] = {};
}
current = current[part];
}
// 设置最终值
current[parts[parts.length - 1]] = value;
}
/**
* 触发变更回调
* @param key 配置键
* @param value 新值
* @param oldValue 旧值
*/
triggerChangeCallbacks(key, value, oldValue) {
// 触发特定键的回调
if (this.changeCallbacks.has(key)) {
const callbacks = this.changeCallbacks.get(key);
callbacks.forEach(callback => {
try {
callback(value, oldValue);
}
catch (error) {
console.error(`Error in config change callback for key ${key}:`, error);
}
});
}
// 触发父路径的回调
const parts = key.split('.');
while (parts.length > 1) {
parts.pop();
const parentKey = parts.join('.');
if (this.changeCallbacks.has(parentKey)) {
const parentValue = this.get(parentKey);
this.changeCallbacks.get(parentKey).forEach(callback => {
try {
callback(parentValue, parentValue);
}
catch (error) {
console.error(`Error in config change callback for parent key ${parentKey}:`, error);
}
});
}
}
}
}
/**
* @file 日志系统
* @description 提供统一的日志记录与管理功能
* @module core/logger
*/
/**
* 日志级别枚举
*/
exports.LogLevel = void 0;
(function (LogLevel) {
LogLevel["DEBUG"] = "debug";
LogLevel["INFO"] = "info";
LogLevel["WARN"] = "warn";
LogLevel["ERROR"] = "error";
})(exports.LogLevel || (exports.LogLevel = {}));
/**
* 控制台日志处理器
* 将日志输出到浏览器控制台
*/
class ConsoleLogHandler {
/**
* 处理日志条目
* @param entry 日志条目
*/
handle(entry) {
const timestamp = new Date(entry.timestamp).toISOString();
const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
switch (entry.level) {
case exports.LogLevel.DEBUG:
console.debug(prefix, entry.message, entry.error || '');
break;
case exports.LogLevel.INFO:
console.info(prefix, entry.message, entry.error || '');
break;
case exports.LogLevel.WARN:
console.warn(prefix, entry.message, entry.error || '');
break;
case exports.LogLevel.ERROR:
console.error(prefix, entry.message, entry.error || '');
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.endpoint = endpoint;
this.maxQueueSize = maxQueueSize;
this.flushInterval = flushInterval;
// 设置定时发送
this.startTimer();
// 页面卸载前尝试发送剩余日志
window.addEventListener('beforeunload', () => {
this.flush();
});
}
/**
* 处理日志条目
* @param entry 日志条目
*/
handle(entry) {
// 只处理INFO以上级别的日志
if (entry.level >= exports.LogLevel.INFO) {
this.queue.push(entry);
// 如果队列满了,立即发送
if (this.queue.length >= this.maxQueueSize) {
this.flush();
}
}
}
/**
* 发送队列中的日志
*/
flush() {
if (this.queue.length === 0)
return;
const entriesToSend = [...this.queue];
this.queue = [];
try {
fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(entriesToSend),
// 不等待响应,避免阻塞
keepalive: true
}).catch(err => {
console.error('Failed to send logs to remote server:', err);
// 失败时把日志放回队列,但防止无限增长
if (this.queue.length < this.maxQueueSize) {
this.queue = [...entriesToSend.slice(0, this.maxQueueSize - this.queue.length), ...this.queue];
}
});
}
catch (error) {
console.error('Error sending logs:', error);
}
}
/**
* 开始定时发送
*/
startTimer() {
if (this.timerId !== null)
return;
this.timerId = window.setInterval(() => {
this.flush();
}, this.flushInterval);
}
/**
* 停止定时发送
*/
stopTimer() {
if (this.timerId !== null) {
window.clearInterval(this.timerId);
this.timerId = null;
}
}
}
/**
* 日志管理类
* 中央日志管理器,提供统一的日志记录接口
*/
class Logger {
/**
* 私有构造函数,防止直接实例化
*/
constructor() {
/** 日志处理器 */
this.handlers = [];
/** 默认标签 */
this.defaultTag = 'IDScanner';
/** 日志级别 */
this.logLevel = exports.LogLevel.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;
}
/**
* 添加日志处理器
* @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 error 错误
*/
debug(tag, message, error) {
this.log(exports.LogLevel.DEBUG, tag, message, error);
}
/**
* 记录信息级别日志
* @param tag 标签
* @param message 消息
* @param error 错误
*/
info(tag, message, error) {
this.log(exports.LogLevel.INFO, tag, message, error);
}
/**
* 记录警告级别日志
* @param tag 标签
* @param message 消息
* @param error 错误
*/
warn(tag, message, error) {
this.log(exports.LogLevel.WARN, tag, message, error);
}
/**
* 记录错误级别日志
* @param tag 标签
* @param message 消息
* @param error 错误
*/
error(tag, message, error) {
this.log(exports.LogLevel.ERROR, tag, message, error);
}
/**
* 创建标记了特定标签的日志记录器
* @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 entry 日志条目
*/
consoleOutput(entry) {
const timestamp = new Date(entry.timestamp).toISOString();
const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
switch (entry.level) {
case exports.LogLevel.DEBUG:
console.debug(`${prefix} ${entry.message}`, entry.error || '');
break;
case exports.LogLevel.INFO:
console.info(`${prefix} ${entry.message}`, entry.error || '');
break;
case exports.LogLevel.WARN:
console.warn(`${prefix} ${entry.message}`, entry.error || '');
break;
case exports.LogLevel.ERROR:
console.error(`${prefix} ${entry.message}`, entry.error || '');
break;
}
}
/**
* 获取日志级别值
* @param level 日志级别
*/
getLevelValue(level) {
switch (level) {
case exports.LogLevel.DEBUG:
return 0;
case exports.LogLevel.INFO:
return 1;
case exports.LogLevel.WARN:
return 2;
case exports.LogLevel.ERROR:
return 3;
default:
return 1; // 默认INFO级别
}
}
/**
* 设置日志级别
* @param level 日志级别
*/
setLevel(level) {
if (typeof level === 'string') {
switch (level) {
case 'debug':
this.logLevel = exports.LogLevel.DEBUG;
break;
case 'info':
this.logLevel = exports.LogLevel.INFO;
break;
case 'warn':
this.logLevel = exports.LogLevel.WARN;
break;
case 'error':
this.logLevel = exports.LogLevel.ERROR;
break;
default:
this.logLevel = exports.LogLevel.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);
}
}
/**
* 订阅事件,但只触发一次
* @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.5.0';
// 构建日期
const BUILD_DATE = new Date().toISOString();
/**
* @file 模块管理器
* @description 统一管理库的各功能模块,提供模块的注册、初始化和卸载功能
* @module core/module-manager
*/
/**
* 模块管理器类
* 负责管理所有功能模块的生命周期
*/
class ModuleManager extends EventEmitter {
/**
* 获取模块管理器单例
*/
static getInstance() {
if (!ModuleManager.instance) {
ModuleManager.instance = new ModuleManager();
}
return ModuleManager.instance;
}
/**
* 私有构造函数,确保单例模式
*/
constructor() {
super();
this.modules = new Map();
this.initialized = false;
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) {
const errorObj = error instanceof Error ? error : new Error(String(error));
this.logger.error('ModuleManager', `模块 ${name} 初始化失败`, errorObj);
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) {
const errorObj = error instanceof Error ? error : new Error(String(error));
this.logger.error('ModuleManager', `模块 ${name} 资源释放失败`, errorObj);
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;
}
/**
* 获取结果数据
*/
get data() {
return this._data;
}
/**
* 获取错误对象
*/
get error() {
return this._error;
}
/**
* 获取元数据
*/
get meta() {
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 = {};
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('身份证检测器未初始化'));
}
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 = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = 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);
}
}
/**
* 预处理图像
* @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 canvas = document.createElement('canvas');
const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
canvas.width = width;
canvas.height = 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', '裁剪并校正图像');
// 创建Canvas
const canvas = document.createElement('canvas');
// 设置标准身份证尺寸比例
canvas.width = 428;
canvas.height = 270;
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, canvas.width, canvas.height);
return context.getImageData(0, 0, canvas.width, canvas.height);
}
/**
* 识别文字
* @param image 图像数据
* @param type 身份证类型
* @returns 识别结果
* @private
*/
async recognizeText(image, type) {
// 实际项目中,这里应该调用OCR模型进行文字识别
this.logger.debug('IDCardDetector', '识别文字');
// 模拟OCR结果
// 在实际应用中,这里应该使用OCR模型进行文字识别
if (type === exports.IDCardType.FRONT) {
return {
name: '张三',
gender: '男',
ethnicity: '汉',
birthDate: '1990-01-01',
address: '北京市朝阳区某某街道某某社区1号楼1单元101',
idNumber: '110101199001010001',
photoRegion: {
x: 300,
y: 40,
width: 100,
height: 130
}
};
}
else if (type === exports.IDCardType.BACK) {
return {
issueAuthority: '北京市公安局朝阳分局',
validFrom: '2015-01-01',
validTo: '2035-01-01'
};
}
return {};
}
/**
* 检测防伪特征
* @param image 图像数据
* @param detectionResult 检测结果
* @returns 防伪检测结果
* @private
*/
async detectAntiFake(image, detectionResult) {
// 实际项目中,这里应该调用防伪模型进行特征检测
this.logger.debug('IDCardDetector', '检测防伪特征');
// 模拟防伪检测结果
// 在实际应用中,这里应该使用机器学习模型检测防伪特征
return {
passed: true,
score: 0.92,
features: {
fluorescent: true,
microtext: true,
opticalVariable: true,
texture: true,
watermark: true
}
};
}
/**
* 释放资源
*/
dispose() {
this.logger.debug('IDCardDetector', '释放资源');
// 清理模型
this.models = {};
this.initialized = false;
// 清理事件监听
this.removeAllListeners();
}
}
/**
* @file 图像处理工具类
* @description 提供图像预处理功能,用于提高OCR识别率
* @module ImageProcessor
* @version 1.3.2
*/
/**
* 图像处理工具类
*
* 提供各种图像处理功能,用于优化识别效果
*/
class ImageProcessor {
/**
* 将ImageData转换为Canvas元素
*
* @param {ImageData} imageData - 要转换的图像数据
* @returns {HTMLCanvasElement} 包含图像的Canvas元素
*/
static imageDataToCanvas(imageData) {
const canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.putImageData(imageData, 0, 0);
}
return canvas;
}
/**
* 将Canvas转换为ImageData
*
* @param {HTMLCanvasElement} canvas - 要转换的Canvas元素
* @returns {ImageData|null} Canvas的图像数据,如果获取失败则返回null
*/
static canvasToImageData(canvas) {
const ctx = canvas.getContext("2d");
return ctx ? ctx.getImageData(0, 0, canvas.width, canvas.height) : null;
}
/**
* 调整图像亮度和对比度
*
* @param imageData 原始图像数据
* @param brightness 亮度调整值 (-100到100)
* @param contrast 对比度调整值 (-100到100)
* @returns 处理后的图像数据
*/
static adjustBrightnessContrast(imageData, brightness = 0, contrast = 0) {
// 将亮度和对比度范围限制在 -100 到 100 之间
brightness = Math.max(-100, Math.min(100, brightness));
contrast = Math.max(-100, Math.min(100, contrast));
// 将范围转换为适合计算的值
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
const briAdjust = (brightness / 100) * 255;
const data = imageData.data;
const length = data.length;
for (let i = 0; i < length; i += 4) {
// 分别处理 RGB 三个通道
for (let j = 0; j < 3; j++) {
// 应用亮度和对比度调整公式
const newValue = factor * (data[i + j] + briAdjust - 128) + 128;
data[i + j] = Math.max(0, Math.min(255, newValue));
}
// Alpha 通道保持不变
}
return imageData;
}
/**
* 将图像转换为灰度图
*
* @param imageData 原始图像数据
* @returns 灰度图像数据
*/
static toGrayscale(imageData) {
const data = imageData.data;
const length = data.length;
for (let i = 0; i < length; i += 4) {
// 使用加权平均法将 RGB 转换为灰度值
const gray = data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11;
data[i] = data[i + 1] = data[i + 2] = gray;
}
return imageData;
}
/**
* 锐化图像
*
* @param imageData 原始图像数据
* @param amount 锐化程度,默认为2
* @returns 锐化后的图像数据
*/
static sharpen(imageData, amount = 2) {
if (!imageData || !imageData.data)
return imageData;
const width = imageData.width;
const height = imageData.height;
const data = imageData.data;
const outputData = new Uint8ClampedArray(data.length);
// 锐化卷积核
const kernel = [
0,
-amount,
0,
-amount,
1 + 4 * amount,
-amount,
0,
-amount,
0,
];
// 应用卷积
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const pos = (y * width + x) * 4;
// 对每个通道应用卷积
for (let c = 0; c < 3; c++) {
let val = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const kernelPos = (ky + 1) * 3 + (kx + 1);
const dataPos = ((y + ky) * width + (x + kx)) * 4 + c;
val += data[dataPos] * kernel[kernelPos];
}
}
outputData[pos + c] = Math.max(0, Math.min(255, val));
}
outputData[pos + 3] = data[pos + 3]; // 保持透明度不变
}
}
// 处理边缘像素
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {
const pos = (y * width + x) * 4;
outputData[pos] = data[pos];
outputData[pos + 1] = data[pos + 1];
outputData[pos + 2] = data[pos + 2];
outputData[pos + 3] = data[pos + 3];
}
}
}
// 创建新的ImageData对象
return new ImageData(outputData, width, height);
}
/**
* 对图像应用阈值操作,增强对比度
*