id-scanner-lib 1.1.1 → 1.2.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 +77 -0
- package/dist/id-scanner-core.esm.js +160 -0
- package/dist/id-scanner-core.esm.js.map +1 -1
- package/dist/id-scanner-core.js +160 -0
- package/dist/id-scanner-core.js.map +1 -1
- package/dist/id-scanner-core.min.js +2 -2
- package/dist/id-scanner-core.min.js.map +1 -1
- package/dist/id-scanner-ocr.esm.js +789 -78
- package/dist/id-scanner-ocr.esm.js.map +1 -1
- package/dist/id-scanner-ocr.js +788 -77
- package/dist/id-scanner-ocr.js.map +1 -1
- package/dist/id-scanner-ocr.min.js +2 -2
- package/dist/id-scanner-ocr.min.js.map +1 -1
- package/dist/id-scanner-qr.esm.js +160 -0
- package/dist/id-scanner-qr.esm.js.map +1 -1
- package/dist/id-scanner-qr.js +160 -0
- package/dist/id-scanner-qr.js.map +1 -1
- package/dist/id-scanner-qr.min.js +2 -2
- package/dist/id-scanner-qr.min.js.map +1 -1
- package/dist/id-scanner.js +788 -77
- package/dist/id-scanner.js.map +1 -1
- package/dist/id-scanner.min.js +2 -2
- package/dist/id-scanner.min.js.map +1 -1
- package/package.json +1 -1
- package/src/id-recognition/id-detector.ts +210 -65
- package/src/id-recognition/ocr-processor.ts +142 -21
- package/src/id-recognition/ocr-worker.ts +146 -0
- package/src/utils/image-processing.ts +204 -0
- package/src/utils/performance.ts +208 -0
- package/src/utils/resource-manager.ts +198 -0
- package/src/utils/worker.ts +173 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 资源管理器
|
|
3
|
+
* @description 提供资源自动管理功能,防止内存泄漏
|
|
4
|
+
* @module ResourceManager
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 可释放资源接口
|
|
9
|
+
*/
|
|
10
|
+
export interface Disposable {
|
|
11
|
+
dispose: () => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 资源管理器
|
|
16
|
+
*
|
|
17
|
+
* 用于管理和自动释放资源,防止内存泄漏
|
|
18
|
+
*/
|
|
19
|
+
export class ResourceManager {
|
|
20
|
+
private resources: Map<string, Disposable> = new Map();
|
|
21
|
+
private disposeTimeouts: Map<string, number> = new Map();
|
|
22
|
+
private autoDisposeDelay: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 创建资源管理器
|
|
26
|
+
* @param autoDisposeDelay 自动释放延迟时间(毫秒),默认5分钟
|
|
27
|
+
*/
|
|
28
|
+
constructor(autoDisposeDelay: number = 5 * 60 * 1000) {
|
|
29
|
+
this.autoDisposeDelay = autoDisposeDelay;
|
|
30
|
+
|
|
31
|
+
// 添加页面卸载事件监听,确保在页面关闭时释放所有资源
|
|
32
|
+
if (typeof window !== 'undefined') {
|
|
33
|
+
window.addEventListener('beforeunload', () => {
|
|
34
|
+
this.disposeAll();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 注册资源
|
|
41
|
+
*
|
|
42
|
+
* @param id 资源标识符
|
|
43
|
+
* @param resource 可释放资源
|
|
44
|
+
* @param autoDispose 是否自动释放
|
|
45
|
+
* @returns 注册的资源
|
|
46
|
+
*/
|
|
47
|
+
register<T extends Disposable>(id: string, resource: T, autoDispose: boolean = true): T {
|
|
48
|
+
// 如果已存在同ID资源,先释放它
|
|
49
|
+
if (this.resources.has(id)) {
|
|
50
|
+
this.dispose(id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.resources.set(id, resource);
|
|
54
|
+
|
|
55
|
+
// 如果启用自动释放,设置定时器
|
|
56
|
+
if (autoDispose) {
|
|
57
|
+
this.resetDisposeTimeout(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return resource;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 重置资源自动释放定时器
|
|
65
|
+
*
|
|
66
|
+
* @param id 资源标识符
|
|
67
|
+
*/
|
|
68
|
+
resetDisposeTimeout(id: string): void {
|
|
69
|
+
// 取消现有定时器
|
|
70
|
+
if (this.disposeTimeouts.has(id)) {
|
|
71
|
+
window.clearTimeout(this.disposeTimeouts.get(id)!);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 设置新定时器
|
|
75
|
+
const timeoutId = window.setTimeout(() => {
|
|
76
|
+
this.dispose(id);
|
|
77
|
+
}, this.autoDisposeDelay);
|
|
78
|
+
|
|
79
|
+
this.disposeTimeouts.set(id, timeoutId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 获取资源
|
|
84
|
+
*
|
|
85
|
+
* @param id 资源标识符
|
|
86
|
+
* @returns 资源对象或undefined
|
|
87
|
+
*/
|
|
88
|
+
get<T extends Disposable>(id: string): T | undefined {
|
|
89
|
+
const resource = this.resources.get(id) as T | undefined;
|
|
90
|
+
|
|
91
|
+
// 重置自动释放定时器(仅当资源存在且有定时器时)
|
|
92
|
+
if (resource && this.disposeTimeouts.has(id)) {
|
|
93
|
+
this.resetDisposeTimeout(id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return resource;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 释放单个资源
|
|
101
|
+
*
|
|
102
|
+
* @param id 资源标识符
|
|
103
|
+
* @returns 是否成功释放
|
|
104
|
+
*/
|
|
105
|
+
async dispose(id: string): Promise<boolean> {
|
|
106
|
+
if (!this.resources.has(id)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 取消定时器
|
|
111
|
+
if (this.disposeTimeouts.has(id)) {
|
|
112
|
+
window.clearTimeout(this.disposeTimeouts.get(id)!);
|
|
113
|
+
this.disposeTimeouts.delete(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 释放资源
|
|
117
|
+
try {
|
|
118
|
+
const resource = this.resources.get(id)!;
|
|
119
|
+
const result = resource.dispose();
|
|
120
|
+
|
|
121
|
+
// 处理可能的Promise结果
|
|
122
|
+
if (result instanceof Promise) {
|
|
123
|
+
await result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.resources.delete(id);
|
|
127
|
+
return true;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`释放资源 ${id} 时发生错误:`, error);
|
|
130
|
+
// 尽管出错,仍然从管理器中移除
|
|
131
|
+
this.resources.delete(id);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 释放所有资源
|
|
138
|
+
*/
|
|
139
|
+
async disposeAll(): Promise<void> {
|
|
140
|
+
// 取消所有定时器
|
|
141
|
+
for (const timeoutId of this.disposeTimeouts.values()) {
|
|
142
|
+
window.clearTimeout(timeoutId);
|
|
143
|
+
}
|
|
144
|
+
this.disposeTimeouts.clear();
|
|
145
|
+
|
|
146
|
+
// 并行释放所有资源
|
|
147
|
+
const disposePromises = [];
|
|
148
|
+
|
|
149
|
+
for (const [id, resource] of this.resources.entries()) {
|
|
150
|
+
try {
|
|
151
|
+
const result = resource.dispose();
|
|
152
|
+
if (result instanceof Promise) {
|
|
153
|
+
disposePromises.push(result);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`释放资源 ${id} 时发生错误:`, error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 等待所有异步释放完成
|
|
161
|
+
if (disposePromises.length > 0) {
|
|
162
|
+
await Promise.all(disposePromises);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.resources.clear();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 释放不活跃资源
|
|
170
|
+
*
|
|
171
|
+
* @param maxAge 资源最大年龄(毫秒)
|
|
172
|
+
*/
|
|
173
|
+
disposeInactive(maxAge: number): void {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
|
|
176
|
+
for (const [id, timeoutId] of this.disposeTimeouts.entries()) {
|
|
177
|
+
// 计算资源年龄:自动释放延迟时间 - 剩余时间
|
|
178
|
+
const remainingTime = this.getRemainingTime(timeoutId);
|
|
179
|
+
const age = this.autoDisposeDelay - remainingTime;
|
|
180
|
+
|
|
181
|
+
if (age > maxAge) {
|
|
182
|
+
this.dispose(id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 获取定时器剩余时间
|
|
189
|
+
*
|
|
190
|
+
* @param timeoutId 定时器ID
|
|
191
|
+
* @returns 剩余时间(毫秒)
|
|
192
|
+
*/
|
|
193
|
+
private getRemainingTime(timeoutId: number): number {
|
|
194
|
+
// 注意:这是一个近似实现,因为JavaScript没有提供获取setTimeout剩余时间的API
|
|
195
|
+
// 可以在实际应用中使用更精确的测量方法
|
|
196
|
+
return 0; // 实际应用中应返回真实的剩余时间
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Web Worker辅助工具类
|
|
3
|
+
* @description 提供Worker线程管理功能,用于将计算密集型任务移至后台线程
|
|
4
|
+
* @module WorkerUtils
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 创建Worker线程并处理消息通信
|
|
9
|
+
*
|
|
10
|
+
* @param workerFunction 要在Worker中执行的函数
|
|
11
|
+
* @returns 返回包含发送消息方法的Worker控制对象
|
|
12
|
+
*/
|
|
13
|
+
export function createWorker<T, R>(workerFunction: Function): {
|
|
14
|
+
postMessage: (data: T) => Promise<R>;
|
|
15
|
+
terminate: () => void;
|
|
16
|
+
} {
|
|
17
|
+
// 将函数转换为字符串,然后创建一个Blob URL
|
|
18
|
+
const workerCode = `
|
|
19
|
+
self.onmessage = async function(e) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await (${workerFunction.toString()})(e.data);
|
|
22
|
+
self.postMessage({ success: true, result });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
self.postMessage({
|
|
25
|
+
success: false,
|
|
26
|
+
error: { message: error.message, stack: error.stack }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
33
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
34
|
+
const worker = new Worker(workerUrl);
|
|
35
|
+
|
|
36
|
+
// 创建一个映射来存储待解析的Promise
|
|
37
|
+
const promiseMap = new Map<number, {
|
|
38
|
+
resolve: (value: R) => void;
|
|
39
|
+
reject: (reason: any) => void;
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
let messageCounter = 0;
|
|
43
|
+
|
|
44
|
+
worker.onmessage = (e) => {
|
|
45
|
+
// 释放Blob URL
|
|
46
|
+
if (promiseMap.size === 0) {
|
|
47
|
+
URL.revokeObjectURL(workerUrl);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { id, success, result, error } = e.data;
|
|
51
|
+
|
|
52
|
+
const promiseHandlers = promiseMap.get(id);
|
|
53
|
+
if (promiseHandlers) {
|
|
54
|
+
promiseMap.delete(id);
|
|
55
|
+
|
|
56
|
+
if (success) {
|
|
57
|
+
promiseHandlers.resolve(result);
|
|
58
|
+
} else {
|
|
59
|
+
const workerError = new Error(error.message);
|
|
60
|
+
workerError.stack = error.stack;
|
|
61
|
+
promiseHandlers.reject(workerError);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
postMessage: (data: T): Promise<R> => {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const id = messageCounter++;
|
|
70
|
+
promiseMap.set(id, { resolve, reject });
|
|
71
|
+
worker.postMessage({ id, data });
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
terminate: () => {
|
|
75
|
+
worker.terminate();
|
|
76
|
+
promiseMap.clear();
|
|
77
|
+
URL.revokeObjectURL(workerUrl);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 判断浏览器是否支持Web Workers
|
|
84
|
+
*
|
|
85
|
+
* @returns 是否支持Web Workers
|
|
86
|
+
*/
|
|
87
|
+
export function isWorkerSupported(): boolean {
|
|
88
|
+
return typeof Worker !== 'undefined';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 工作线程池
|
|
93
|
+
* 用于管理和重用Worker线程,避免频繁创建和销毁
|
|
94
|
+
*/
|
|
95
|
+
export class WorkerPool<T, R> {
|
|
96
|
+
private workers: Array<{
|
|
97
|
+
worker: ReturnType<typeof createWorker<T, R>>;
|
|
98
|
+
busy: boolean;
|
|
99
|
+
}> = [];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 创建工作线程池
|
|
103
|
+
*
|
|
104
|
+
* @param workerFunction 要在Worker中执行的函数
|
|
105
|
+
* @param size 池中Worker的数量
|
|
106
|
+
*/
|
|
107
|
+
constructor(private workerFunction: Function, private size: number = navigator.hardwareConcurrency || 4) {
|
|
108
|
+
// 预创建Workers
|
|
109
|
+
for (let i = 0; i < size; i++) {
|
|
110
|
+
this.workers.push({
|
|
111
|
+
worker: createWorker<T, R>(workerFunction),
|
|
112
|
+
busy: false
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 获取一个可用的Worker
|
|
119
|
+
*
|
|
120
|
+
* @returns Worker包装对象
|
|
121
|
+
*/
|
|
122
|
+
private getAvailableWorker(): ReturnType<typeof createWorker<T, R>> {
|
|
123
|
+
// 找到第一个空闲的Worker
|
|
124
|
+
const availableWorker = this.workers.find(w => !w.busy);
|
|
125
|
+
|
|
126
|
+
if (availableWorker) {
|
|
127
|
+
availableWorker.busy = true;
|
|
128
|
+
return availableWorker.worker;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 如果没有空闲Worker,创建一个新的
|
|
132
|
+
const worker = createWorker<T, R>(this.workerFunction);
|
|
133
|
+
this.workers.push({ worker, busy: true });
|
|
134
|
+
return worker;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 执行任务
|
|
139
|
+
*
|
|
140
|
+
* @param data 要处理的数据
|
|
141
|
+
* @returns 处理结果的Promise
|
|
142
|
+
*/
|
|
143
|
+
async execute(data: T): Promise<R> {
|
|
144
|
+
const worker = this.getAvailableWorker();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const result = await worker.postMessage(data);
|
|
148
|
+
// 标记Worker为空闲
|
|
149
|
+
const workerEntry = this.workers.find(w => w.worker === worker);
|
|
150
|
+
if (workerEntry) {
|
|
151
|
+
workerEntry.busy = false;
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// 出错时也标记为空闲
|
|
156
|
+
const workerEntry = this.workers.find(w => w.worker === worker);
|
|
157
|
+
if (workerEntry) {
|
|
158
|
+
workerEntry.busy = false;
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 终止所有Worker
|
|
166
|
+
*/
|
|
167
|
+
terminate(): void {
|
|
168
|
+
this.workers.forEach(({ worker }) => {
|
|
169
|
+
worker.terminate();
|
|
170
|
+
});
|
|
171
|
+
this.workers = [];
|
|
172
|
+
}
|
|
173
|
+
}
|