@file-ud.js/core 0.1.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 +382 -0
- package/README_DOWNLOADER.md +334 -0
- package/README_UPLOADER.md +369 -0
- package/dist/downloader/index.d.mts +1 -0
- package/dist/downloader/index.d.ts +1 -0
- package/dist/downloader/index.js +8 -0
- package/dist/downloader/index.mjs +1 -0
- package/dist/index.d.mts +1856 -0
- package/dist/index.d.ts +1856 -0
- package/dist/index.js +8413 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8389 -0
- package/dist/index.mjs.map +1 -0
- package/dist/uploader/index.d.mts +1 -0
- package/dist/uploader/index.d.ts +1 -0
- package/dist/uploader/index.js +8 -0
- package/dist/uploader/index.mjs +1 -0
- package/dist/utils/index.d.mts +2048 -0
- package/dist/utils/index.d.ts +2048 -0
- package/dist/utils/index.js +1609 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +1567 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,2048 @@
|
|
|
1
|
+
import PQueue from 'p-queue';
|
|
2
|
+
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
3
|
+
|
|
4
|
+
declare class EventEmitter {
|
|
5
|
+
events?: Map<EventName, Set<Function>>;
|
|
6
|
+
/** 确保 events Map 已初始化(兼容 Object.create 不执行字段初始化器) */
|
|
7
|
+
private ensureEvents;
|
|
8
|
+
on<K extends EventName>(eventName: K, callback: EventCallback<K>): this;
|
|
9
|
+
emit<K extends EventName>(eventName: K, ...args: Parameters<EventCallback<K>>): boolean;
|
|
10
|
+
once<K extends EventName>(eventName: K, callback: EventCallback<K>): this;
|
|
11
|
+
off<K extends EventName>(eventName: K, callback?: EventCallback<K>): this;
|
|
12
|
+
listenerCount(eventName: EventName): number;
|
|
13
|
+
removeAllListeners(eventName?: EventName): this;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 分片管理器基类
|
|
18
|
+
*
|
|
19
|
+
* 提取上传/下载分片管理器的所有公共逻辑:
|
|
20
|
+
* - 并发分片传输调度
|
|
21
|
+
* - 暂停/恢复/取消
|
|
22
|
+
* - 重试机制(含 AbortController + 超时)
|
|
23
|
+
* - 进度计算 & 速率统计
|
|
24
|
+
* - 分片完成/失败后的事件触发与状态聚合
|
|
25
|
+
*
|
|
26
|
+
* 子类只需实现以下抽象方法:
|
|
27
|
+
* - {@link getTag} 日志标签
|
|
28
|
+
* - {@link getTransferFile} 关联的传输文件实例
|
|
29
|
+
* - {@link doInit} 初始化传输(获取已传分片列表等)
|
|
30
|
+
* - {@link doChunkTransfer} 执行单个分片的实际传输
|
|
31
|
+
* - {@link doMergeChunks} 合并所有分片
|
|
32
|
+
*/
|
|
33
|
+
declare abstract class ChunkManager<T extends TransferFile<T> = TransferFile<any, any>> {
|
|
34
|
+
chunkSize: number;
|
|
35
|
+
maxConcurrent: number;
|
|
36
|
+
/** 最大速率限制(bytes/秒),0 = 不限制。下载场景由 maxDownloadSpeed 设置 */
|
|
37
|
+
maxSpeed: number;
|
|
38
|
+
chunkIndex: number;
|
|
39
|
+
retries: number | null;
|
|
40
|
+
retryDelay: number;
|
|
41
|
+
timeout: number;
|
|
42
|
+
chunk: Blob | null;
|
|
43
|
+
totalChunks: number;
|
|
44
|
+
chunks: boolean[];
|
|
45
|
+
chunkEndTime: number;
|
|
46
|
+
completedChunks: number;
|
|
47
|
+
totalChunkTime: number;
|
|
48
|
+
response: any;
|
|
49
|
+
queue: PQueue | null;
|
|
50
|
+
chunkStartTime: number;
|
|
51
|
+
chunkStatsInfos: {
|
|
52
|
+
totalTime: number;
|
|
53
|
+
fileSize: number;
|
|
54
|
+
completedChunks: number | undefined;
|
|
55
|
+
averageSpeed: number;
|
|
56
|
+
};
|
|
57
|
+
totalChunkSize: number;
|
|
58
|
+
config: ChunkOptions;
|
|
59
|
+
countedChunks: Set<number>;
|
|
60
|
+
fileHash: string;
|
|
61
|
+
failedChunks: number[];
|
|
62
|
+
retryCountMap: Map<number, number>;
|
|
63
|
+
isPaused: boolean;
|
|
64
|
+
isCancelled: boolean;
|
|
65
|
+
pauseResolves: Array<() => void>;
|
|
66
|
+
activeUploads: Set<Promise<void>>;
|
|
67
|
+
abortControllers: AbortController[];
|
|
68
|
+
lastUpdateTime: number;
|
|
69
|
+
lastChunkBytes: number;
|
|
70
|
+
/** 每次 start() 递增的代数计数器,防止旧 run 的异步回调污染新 run 的状态 */
|
|
71
|
+
private _startGeneration;
|
|
72
|
+
/** 速率限制:累计已传输字节数(用于限速计算) */
|
|
73
|
+
private _throttleBytes;
|
|
74
|
+
/** 速率限制:起始时间戳 */
|
|
75
|
+
private _throttleStartTime;
|
|
76
|
+
chunkStats: {
|
|
77
|
+
averageTime: number;
|
|
78
|
+
maxTime: number;
|
|
79
|
+
minTime: number;
|
|
80
|
+
} | null;
|
|
81
|
+
/** 是否秒传/秒下(无需实际传输,文件已在目标端) */
|
|
82
|
+
isInstantTransfer: boolean;
|
|
83
|
+
/** 关联的传输文件(UploadFile / DownloadFile) */
|
|
84
|
+
protected transferFile: T;
|
|
85
|
+
/** 日志标签,如 "uploadChunkManager" / "DownloadChunkManager" */
|
|
86
|
+
protected abstract getTag(): string;
|
|
87
|
+
/** 计算文件唯一标识:上传=本地MD5,下载=服务端Hash / 指纹 */
|
|
88
|
+
protected abstract computeFileIdentifier(): Promise<string>;
|
|
89
|
+
/** 返回关联的传输文件实例 */
|
|
90
|
+
protected getTransferFile(): T;
|
|
91
|
+
/** 初始化传输:检查已上传/已下载分片(断点续传) */
|
|
92
|
+
protected abstract doInit(): Promise<any>;
|
|
93
|
+
/** 执行单个分片的实际传输(上传/下载) */
|
|
94
|
+
protected abstract doChunkTransfer(chunkIndex: number, signal?: AbortSignal): Promise<{
|
|
95
|
+
data: any;
|
|
96
|
+
chunkSize: number;
|
|
97
|
+
}>;
|
|
98
|
+
/** 合并所有分片 */
|
|
99
|
+
protected abstract doMergeChunks(): Promise<any>;
|
|
100
|
+
/** start() 重置后的额外清理,如下载场景清空 downloadedChunks */
|
|
101
|
+
protected doAfterStartReset(): Promise<void>;
|
|
102
|
+
/** 分片传输成功后保存结果数据(上传存 response,下载存 blob 到 Map / 流式写入磁盘) */
|
|
103
|
+
protected doSaveChunkResult(_chunkIndex: number, _data: any): Promise<void>;
|
|
104
|
+
/** onSuccess 之前的额外操作,如下载场景调用 saveBlob */
|
|
105
|
+
protected doBeforeOnSuccess(_mergeResult: any): Promise<void>;
|
|
106
|
+
/** start() 发现所有分片已完成时的处理(上传需区分秒传/断点续传) */
|
|
107
|
+
protected doOnAllChunksAlreadyDone(_initResult: any): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* 标记所有分片为已完成(秒传/秒下、服务端已存在全部分片)
|
|
110
|
+
*/
|
|
111
|
+
protected applyAllChunksComplete(fileSize: number): void;
|
|
112
|
+
/**
|
|
113
|
+
* 断点续传恢复:从 initResult.chunks 标记已完成的分片
|
|
114
|
+
* @param initResult - 服务端返回的初始化结果,包含 chunks 数组
|
|
115
|
+
*/
|
|
116
|
+
protected applyBreakpointResume(initResult: {
|
|
117
|
+
chunks?: number[] | null;
|
|
118
|
+
}): void;
|
|
119
|
+
constructor(ChunkOptions: ChunkOptions, file: T);
|
|
120
|
+
/**
|
|
121
|
+
* 暂停传输(上传/下载统一实现)
|
|
122
|
+
*/
|
|
123
|
+
pause(): void;
|
|
124
|
+
/**
|
|
125
|
+
* 恢复传输(上传/下载统一实现)
|
|
126
|
+
*/
|
|
127
|
+
resume(): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* 取消传输(上传/下载统一实现)
|
|
130
|
+
*/
|
|
131
|
+
cancel(): void;
|
|
132
|
+
/**
|
|
133
|
+
* 是否处于暂停状态
|
|
134
|
+
*/
|
|
135
|
+
getPaused(): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* 获取失败分片的数量
|
|
138
|
+
*/
|
|
139
|
+
getFailedChunksCount(): number;
|
|
140
|
+
/**
|
|
141
|
+
* 更新传输进度百分比
|
|
142
|
+
*/
|
|
143
|
+
updateProgress(): void;
|
|
144
|
+
/**
|
|
145
|
+
* 计算并触发全局速率更新
|
|
146
|
+
*/
|
|
147
|
+
protected calculateAndUpdateSpeed(currentTransferredBytes: number): void;
|
|
148
|
+
/**
|
|
149
|
+
* 计算传输统计信息
|
|
150
|
+
*/
|
|
151
|
+
protected calculateStats(): void;
|
|
152
|
+
/**
|
|
153
|
+
* 统一的 entry:启动分片传输
|
|
154
|
+
*/
|
|
155
|
+
start(): Promise<void>;
|
|
156
|
+
/** 子类覆写以返回不同的开始事件名 */
|
|
157
|
+
protected getStartEventName(): string;
|
|
158
|
+
/**
|
|
159
|
+
* 重试失败的分片
|
|
160
|
+
*/
|
|
161
|
+
retryFailedChunks(): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* 带超时 & 重试机制的单个分片传输
|
|
164
|
+
*/
|
|
165
|
+
protected chunkWithRetry(chunkIndex: number): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* 分片传输成功后的统一处理
|
|
168
|
+
*/
|
|
169
|
+
protected handleChunkSuccess(chunkIndex: number, result: {
|
|
170
|
+
data: any;
|
|
171
|
+
chunkSize: number;
|
|
172
|
+
}): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* 分片传输失败后的统一处理
|
|
175
|
+
*/
|
|
176
|
+
protected handleChunkError(chunkIndex: number, error: any): void;
|
|
177
|
+
/**
|
|
178
|
+
* 并发传输所有分片
|
|
179
|
+
*
|
|
180
|
+
* @param startGen - 当前 start() 调用的代数,用于过滤上一轮残留的异步回调
|
|
181
|
+
*/
|
|
182
|
+
protected transferWithConcurrency(startGen?: number): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* 检查统计信息并触发合并或失败处理
|
|
185
|
+
*/
|
|
186
|
+
protected checkStatistics(): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* 处理所有分片成功
|
|
189
|
+
*/
|
|
190
|
+
protected handleAllChunksSuccess(): Promise<void>;
|
|
191
|
+
/**
|
|
192
|
+
* 处理失败分片
|
|
193
|
+
*/
|
|
194
|
+
protected handleFailedChunks(): Promise<void>;
|
|
195
|
+
/**
|
|
196
|
+
* 设置文件状态为失败
|
|
197
|
+
*/
|
|
198
|
+
protected setFileStatusToFail(): void;
|
|
199
|
+
/**
|
|
200
|
+
* 等待恢复(暂停/恢复机制)
|
|
201
|
+
*/
|
|
202
|
+
protected waitForResume(): Promise<void>;
|
|
203
|
+
/**
|
|
204
|
+
* 下载速率限流:在传输每个分片前检查是否需要延迟
|
|
205
|
+
*
|
|
206
|
+
* 采用平均速率算法:累计已传输字节数 / 已用时间 必须 ≤ maxSpeed。
|
|
207
|
+
* 如果当前速率超过限制,sleep 差值后继续。
|
|
208
|
+
*
|
|
209
|
+
* @param chunkSize - 待传输分片的预计大小(字节)
|
|
210
|
+
*/
|
|
211
|
+
protected throttleBeforeChunk(chunkSize: number): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* 从活跃控制器列表中移除指定的 AbortController
|
|
214
|
+
*/
|
|
215
|
+
protected removeAbortController(controller: AbortController): void;
|
|
216
|
+
/**
|
|
217
|
+
* 合并完成后的收尾(可分片数量完整时直接走合并流程)
|
|
218
|
+
*/
|
|
219
|
+
protected completeMerge(): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
declare class TransferFile<T extends TransferFile<T, any>, D = any> {
|
|
223
|
+
/**
|
|
224
|
+
* 上传速率统计信息
|
|
225
|
+
* 包含瞬时速度、平均速度及其格式化字符串
|
|
226
|
+
* 通过 Proxy 自动触发全局速率聚合
|
|
227
|
+
*/
|
|
228
|
+
speed: speedInfo;
|
|
229
|
+
/**
|
|
230
|
+
* 上传下载时间统计信息
|
|
231
|
+
* 记录文件从开始到结束的完整耗时
|
|
232
|
+
* 通过 Proxy 自动触发 UI 更新
|
|
233
|
+
*/
|
|
234
|
+
transferTime: TimeInfo;
|
|
235
|
+
/** 文件唯一标识符 */
|
|
236
|
+
fileId: string;
|
|
237
|
+
/** Proxy 代理对象,用于实现响应式更新 */
|
|
238
|
+
proxy: T;
|
|
239
|
+
/** 文件预览 URL (Object URL) */
|
|
240
|
+
url: string;
|
|
241
|
+
/** 文件名称 */
|
|
242
|
+
fileName: string;
|
|
243
|
+
/** 原始 File 对象 */
|
|
244
|
+
File: File;
|
|
245
|
+
/** 上传进度百分比 (0-100) */
|
|
246
|
+
percent: number | undefined;
|
|
247
|
+
/** 所属的 Uploader 实例 */
|
|
248
|
+
transfer: Transfer;
|
|
249
|
+
/** 是否正在上传 */
|
|
250
|
+
loading: boolean;
|
|
251
|
+
/** 文件传输状态 */
|
|
252
|
+
status: IFile["status"];
|
|
253
|
+
isCancel: boolean;
|
|
254
|
+
/** 文件扩展名 */
|
|
255
|
+
extension: string | undefined;
|
|
256
|
+
hashPercent: number;
|
|
257
|
+
hashLoading: boolean;
|
|
258
|
+
/** 格式化后的文件大小,如 "5.23 MB" */
|
|
259
|
+
formatSize: string | undefined;
|
|
260
|
+
/** 当前文件已上传的大小(格式化字符串),如 "45.23 MB" */
|
|
261
|
+
transferFormatSize: string;
|
|
262
|
+
context: any;
|
|
263
|
+
/** 表单数据对象,用于携带上传参数 */
|
|
264
|
+
formData: FormData | null;
|
|
265
|
+
chunkManager: ChunkManager | null;
|
|
266
|
+
/** 是否处于重试状态 */
|
|
267
|
+
isRetry?: boolean;
|
|
268
|
+
size: number;
|
|
269
|
+
/** Promise resolve 回调引用 */
|
|
270
|
+
resolve: ((value: any) => void | undefined) | undefined;
|
|
271
|
+
/** Promise reject 回调引用 */
|
|
272
|
+
reject: ((reason?: any) => void | undefined) | undefined;
|
|
273
|
+
/** 文件在队列中的索引 */
|
|
274
|
+
index?: number;
|
|
275
|
+
/** 插件可写入的自定义元数据 */
|
|
276
|
+
metadata?: Record<string, any>;
|
|
277
|
+
sub: T | null;
|
|
278
|
+
/** 取消上传的函数 */
|
|
279
|
+
abort: IFile["abort"];
|
|
280
|
+
/** 标记是否已计入总字节数(避免重试时重复累加) */
|
|
281
|
+
__hasCountedTotalBytes__: boolean;
|
|
282
|
+
/** 当前文件已上传的字节数(用于普通上传计算总进度) */
|
|
283
|
+
__transferBytes__: number;
|
|
284
|
+
type: string;
|
|
285
|
+
/** 上次计算速率的时间戳 (毫秒) */
|
|
286
|
+
protected lastUpdateTime: number;
|
|
287
|
+
/** 上次已传输的字节数 */
|
|
288
|
+
protected lastTransferBytes: number;
|
|
289
|
+
/** 传输开始的时间戳 (毫秒) */
|
|
290
|
+
protected transferStartTime: number;
|
|
291
|
+
constructor(file: IFile, transfer: Transfer);
|
|
292
|
+
isUploader(): boolean;
|
|
293
|
+
isDownloader(): boolean;
|
|
294
|
+
/**
|
|
295
|
+
* 开始传输
|
|
296
|
+
*
|
|
297
|
+
* 统一的传输入口,自动根据配置选择分片传输或普通传输:
|
|
298
|
+
* - **分片传输**: 调用 chunkManager.start()
|
|
299
|
+
* - **普通传输**: 由子类实现具体逻辑
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* // 手动传输模式
|
|
304
|
+
* const file = uploader.addFile(fileObject);
|
|
305
|
+
*
|
|
306
|
+
* // 用户点击按钮时
|
|
307
|
+
* button.onclick = () => {
|
|
308
|
+
* file.start();
|
|
309
|
+
* };
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* - 如果文件已经在传输中,会忽略此次调用
|
|
314
|
+
* - 如果文件已传输成功,会抛出警告
|
|
315
|
+
* - 这是异步操作,建议在 UI 上显示加载状态
|
|
316
|
+
*/
|
|
317
|
+
start(chunkManager: ChunkManager | null): Promise<D>;
|
|
318
|
+
/**
|
|
319
|
+
* 取消当前传输
|
|
320
|
+
*/
|
|
321
|
+
cancel(fn: ((next: () => void) => void) | void): void;
|
|
322
|
+
/**
|
|
323
|
+
* 暂停传输(仅分片传输支持)
|
|
324
|
+
*/
|
|
325
|
+
pause(): void;
|
|
326
|
+
/**
|
|
327
|
+
* 恢复传输(仅分片传输支持)
|
|
328
|
+
*/
|
|
329
|
+
resume(): Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* 重试传输
|
|
332
|
+
*/
|
|
333
|
+
retry(): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* 执行普通传输的重试(模板方法,由子类覆写)
|
|
336
|
+
* - UploadFile: 重试上传 → 调用 upload()
|
|
337
|
+
* - DownloadFile: 重试下载 → 调用 download()
|
|
338
|
+
*/
|
|
339
|
+
protected doRetryTransfer(): Promise<any>;
|
|
340
|
+
onSuccess(res: D): void;
|
|
341
|
+
onError(err: any): void;
|
|
342
|
+
initInterceptor(): void;
|
|
343
|
+
/**
|
|
344
|
+
* 处理传输进度事件(供 XHRInterceptor 回调)
|
|
345
|
+
*
|
|
346
|
+
* 统一流程:
|
|
347
|
+
* 1. 调用子类的 {@link updateLocalProgress} 更新当前文件的本体进度/字节数
|
|
348
|
+
* 2. 更新格式化后的已传输大小
|
|
349
|
+
* 3. 调用子类的 {@link calculateGlobalProgress} 计算全局统计
|
|
350
|
+
* 4. 调用 {@link calculateSpeed} 计算并更新速率
|
|
351
|
+
* 5. 发射 progress 事件
|
|
352
|
+
*
|
|
353
|
+
* @param event - XHR 进度事件对象
|
|
354
|
+
*/
|
|
355
|
+
handleProgress(event: ProgressEvent): void;
|
|
356
|
+
/**
|
|
357
|
+
* 更新当前文件的进度和已传输字节数(模板方法,由子类实现)
|
|
358
|
+
*
|
|
359
|
+
* - **普通传输**:从 event.loaded/event.total 直接计算百分比
|
|
360
|
+
* - **分片传输**:由分片管理器独立维护进度,此处无需额外操作
|
|
361
|
+
*
|
|
362
|
+
* @param event - XHR 进度事件对象
|
|
363
|
+
*/
|
|
364
|
+
protected updateLocalProgress(_event: ProgressEvent): void;
|
|
365
|
+
/**
|
|
366
|
+
* 获取文件对应的分片管理器(模板方法,由子类实现)
|
|
367
|
+
* - UploadFile 返回 uploadChunkManager
|
|
368
|
+
* - DownloadFile 返回 downloadChunkManager
|
|
369
|
+
*/
|
|
370
|
+
protected getChunkManager(): ChunkManager | null;
|
|
371
|
+
/**
|
|
372
|
+
* 获取文件大小(字节数),兼容上传(有 File 对象)和下载(无 File 对象)场景
|
|
373
|
+
*/
|
|
374
|
+
getFileSize(): number;
|
|
375
|
+
/**
|
|
376
|
+
* 计算全局传输进度(统一实现,子类只需覆写 getChunkManager / getFileSize)
|
|
377
|
+
*
|
|
378
|
+
* 汇总所有活动文件的传输进度,更新 transfer 实例上的全局统计
|
|
379
|
+
*
|
|
380
|
+
* @param _event - XHR 进度事件对象
|
|
381
|
+
*/
|
|
382
|
+
protected calculateGlobalProgress(_event: ProgressEvent): void;
|
|
383
|
+
/**
|
|
384
|
+
* 计算传输速率(带防抖优化)
|
|
385
|
+
*
|
|
386
|
+
* 采用最小时间间隔采样策略,避免高频进度回调导致数值剧烈抖动。
|
|
387
|
+
* 瞬时速度基于最近两个有效采样点计算,平均速度基于总耗时和总字节数计算。
|
|
388
|
+
*
|
|
389
|
+
* @param loadedBytes - 当前已传输的字节数
|
|
390
|
+
*/
|
|
391
|
+
protected calculateSpeed(loadedBytes: number): void;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
declare class Transfer<T extends TransferFile<T> = TransferFile<any>, D = any> extends EventEmitter {
|
|
395
|
+
/** 当前传输任务的文件列表 */
|
|
396
|
+
files: T[];
|
|
397
|
+
/** 当前正在上传或下载任务的文件列表 用来判断当前文件是否全部上传完成 */
|
|
398
|
+
activeFiles: T[];
|
|
399
|
+
/** 总字节数 */
|
|
400
|
+
totalBytes: number;
|
|
401
|
+
/** 已传输的总字节数 */
|
|
402
|
+
transferredBytes: number;
|
|
403
|
+
/** 总的已传输的总字节数 */
|
|
404
|
+
totalTransferredBytes: number;
|
|
405
|
+
/** 全局已传输的总大小(格式化字符串),如 "125.50 MB" */
|
|
406
|
+
transferredFormatSize: string;
|
|
407
|
+
lastLoadedMap: Map<any, any>;
|
|
408
|
+
plugins: IUDPlugin<T>[];
|
|
409
|
+
/** 插件间共享数据(如 MD5 计算结果等),由插件自行存取 */
|
|
410
|
+
pluginSharedData: Map<string, any>;
|
|
411
|
+
/** 全局待传输的总大小(格式化字符串),如 "256.80 MB" */
|
|
412
|
+
totalFormatSize: string;
|
|
413
|
+
/** 全局传输速率信息 (瞬时速度、平均速度等) */
|
|
414
|
+
speed: speedInfo;
|
|
415
|
+
/** 全局传输进度百分比 (0-100) */
|
|
416
|
+
totalPercent: number;
|
|
417
|
+
/** 是否处于加载/传输状态 */
|
|
418
|
+
loading: boolean;
|
|
419
|
+
/** 传输时间统计信息 (开始时间、结束时间、耗时) */
|
|
420
|
+
transferTime: TimeInfo;
|
|
421
|
+
/** 内部定时器 ID,用于防抖更新 */
|
|
422
|
+
__updateTimer__: number | undefined;
|
|
423
|
+
updateCallback: UpdateCallBack<T> | null | undefined;
|
|
424
|
+
onInitChunkCallback: onInitChunkCallback<T> | null;
|
|
425
|
+
OnMergeChunkCallBack: OnMergeChunkCallBack | null;
|
|
426
|
+
beforeTransferCallback: beforeTransferCallBack<T> | null | undefined;
|
|
427
|
+
static onError: ErrorCallBack;
|
|
428
|
+
successCallback: successCallback<D>;
|
|
429
|
+
/**
|
|
430
|
+
* 触发更新回调(requestAnimationFrame 防抖)
|
|
431
|
+
*
|
|
432
|
+
* 用 rAF 替代 setTimeout,确保同一帧内多次调用只执行一次,
|
|
433
|
+
* 多文件并发场景下所有文件的 Proxy set 共享同一个 rAF 调度。
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
triggerUpdate(): void;
|
|
437
|
+
/**
|
|
438
|
+
* 更新全局统计信息(内部方法)
|
|
439
|
+
* @private
|
|
440
|
+
*/
|
|
441
|
+
updateGlobalStats(): void;
|
|
442
|
+
/**
|
|
443
|
+
* 重置上状态为初始值(统一重置逻辑)
|
|
444
|
+
* @private
|
|
445
|
+
*/
|
|
446
|
+
resetState(): void;
|
|
447
|
+
set onUpdate(callback: UpdateCallBack<T>);
|
|
448
|
+
set onInitChunk(callback: onInitChunkCallback<T>);
|
|
449
|
+
set onMergeChunk(callback: OnMergeChunkCallBack);
|
|
450
|
+
set onSuccess(callback: successCallback<D>);
|
|
451
|
+
set onbeforeTransfer(callback: beforeTransferCallBack<T>);
|
|
452
|
+
/**
|
|
453
|
+
* 注册插件
|
|
454
|
+
* @param plugin 插件实例
|
|
455
|
+
* @param options 插件配置
|
|
456
|
+
*/
|
|
457
|
+
use(plugin: IUDPlugin<T> | IUDPlugin<T>[], options?: any): this;
|
|
458
|
+
/**
|
|
459
|
+
* 移除插件
|
|
460
|
+
* @param name 插件名称
|
|
461
|
+
*/
|
|
462
|
+
unuse(name: string): this;
|
|
463
|
+
/**
|
|
464
|
+
* 获取插件
|
|
465
|
+
* @param name 插件名称
|
|
466
|
+
*/
|
|
467
|
+
getPlugin(name?: string): IUDPlugin<T> | IUDPlugin<T>[] | undefined;
|
|
468
|
+
/**
|
|
469
|
+
* 执行插件钩子
|
|
470
|
+
* @param hook 钩子名称
|
|
471
|
+
* @param args 参数
|
|
472
|
+
*/
|
|
473
|
+
runHook<K extends keyof IUDPlugin>(hook: K, ...args: any[]): Promise<any[]>;
|
|
474
|
+
/**
|
|
475
|
+
* 计算全局上传速率(所有文件的聚合)
|
|
476
|
+
* @returns 全局速率对象
|
|
477
|
+
* @private
|
|
478
|
+
*/
|
|
479
|
+
private calculateGlobalUploadSpeed;
|
|
480
|
+
/**
|
|
481
|
+
* 获取文件的已上传字节数(统一获取逻辑)
|
|
482
|
+
* @param file 文件实例
|
|
483
|
+
* @returns 已上传字节数
|
|
484
|
+
* @private
|
|
485
|
+
*/
|
|
486
|
+
private getFileBytes;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
declare class UploadChunkManager extends ChunkManager {
|
|
490
|
+
uploadFile: UploadFile;
|
|
491
|
+
uploadErrorFileCallBack: (() => void)[];
|
|
492
|
+
/**
|
|
493
|
+
* 初始化上传任务
|
|
494
|
+
*/
|
|
495
|
+
constructor(ChunkOptions: ChunkOptions, file: UploadFile);
|
|
496
|
+
protected getTag(): string;
|
|
497
|
+
protected computeFileIdentifier(): Promise<string>;
|
|
498
|
+
protected doInit(): Promise<any>;
|
|
499
|
+
protected doChunkTransfer(chunkIndex: number, signal?: AbortSignal): Promise<{
|
|
500
|
+
data: any;
|
|
501
|
+
chunkSize: number;
|
|
502
|
+
}>;
|
|
503
|
+
protected doMergeChunks(): Promise<any>;
|
|
504
|
+
/**
|
|
505
|
+
* 计算文件MD5哈希值
|
|
506
|
+
* 优化: 使用与分片上传相同的 chunkSize,减少重复I/O
|
|
507
|
+
*/
|
|
508
|
+
private computeFileHash;
|
|
509
|
+
private getFileHash;
|
|
510
|
+
/**
|
|
511
|
+
* 初始化上传
|
|
512
|
+
*/
|
|
513
|
+
private initUpload;
|
|
514
|
+
/**
|
|
515
|
+
* 带重试机制的分片上传
|
|
516
|
+
*/
|
|
517
|
+
private uploadChunkWithRetry;
|
|
518
|
+
/**
|
|
519
|
+
* 设置文件状态为失败(统一处理,避免重复代码)
|
|
520
|
+
*/
|
|
521
|
+
protected setFileStatusToFail(): void;
|
|
522
|
+
/**
|
|
523
|
+
* 上传单个分片
|
|
524
|
+
*/
|
|
525
|
+
private uploadChunk;
|
|
526
|
+
/**
|
|
527
|
+
* 合并所有分片
|
|
528
|
+
*/
|
|
529
|
+
private mergeChunks;
|
|
530
|
+
/**
|
|
531
|
+
* 计算上传统计信息(统一处理,避免重复代码)
|
|
532
|
+
*/
|
|
533
|
+
private calculateUploadStats;
|
|
534
|
+
/**
|
|
535
|
+
* 检查统计信息并触发合并
|
|
536
|
+
*/
|
|
537
|
+
protected checkStatistics(): Promise<void>;
|
|
538
|
+
/**
|
|
539
|
+
* 处理所有分片成功的情况
|
|
540
|
+
*/
|
|
541
|
+
protected handleAllChunksSuccess(): Promise<void>;
|
|
542
|
+
/**
|
|
543
|
+
* 处理失败分片的情况(自动重试)
|
|
544
|
+
*/
|
|
545
|
+
protected handleFailedChunks(): Promise<void>;
|
|
546
|
+
/**
|
|
547
|
+
* 处理重试失败的情况(统一错误处理)
|
|
548
|
+
*/
|
|
549
|
+
private handleRetryFailure;
|
|
550
|
+
/**
|
|
551
|
+
* 重试失败的分片
|
|
552
|
+
*/
|
|
553
|
+
retryFailedChunks(): Promise<void>;
|
|
554
|
+
/**
|
|
555
|
+
* 取消上传
|
|
556
|
+
*
|
|
557
|
+
* 取消所有正在进行的分片上传任务,并清理相关资源。
|
|
558
|
+
* 与 pause 不同,cancel 会完全停止上传,无法恢复。
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* file.cancel();
|
|
563
|
+
* console.log(file.status); // "cancelled"
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
cancelUpload(): void;
|
|
567
|
+
/**
|
|
568
|
+
* 暂停上传
|
|
569
|
+
*
|
|
570
|
+
* 暂停所有正在进行的分片上传,但保持当前进度。
|
|
571
|
+
* 会等待当前活跃的分片完成后暂停新分片的启动。
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```typescript
|
|
575
|
+
* file.pause();
|
|
576
|
+
* console.log(file.status); // "paused"
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
pause(): void;
|
|
580
|
+
/**
|
|
581
|
+
* 恢复上传
|
|
582
|
+
*
|
|
583
|
+
* 从暂停的位置继续上传分片。
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```typescript
|
|
587
|
+
* file.resume();
|
|
588
|
+
* console.log(file.status); // "UDLoading"
|
|
589
|
+
* ```
|
|
590
|
+
*/
|
|
591
|
+
resume(): Promise<void>;
|
|
592
|
+
/**
|
|
593
|
+
* 检查是否处于暂停状态
|
|
594
|
+
* @returns 是否暂停
|
|
595
|
+
*/
|
|
596
|
+
getPaused(): boolean;
|
|
597
|
+
/**
|
|
598
|
+
* 获取失败分片的数量
|
|
599
|
+
* @returns 失败分片数量
|
|
600
|
+
*/
|
|
601
|
+
getFailedChunksCount(): number;
|
|
602
|
+
/**
|
|
603
|
+
* 等待恢复(用于暂停/恢复机制)
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
606
|
+
protected waitForResume(): Promise<void>;
|
|
607
|
+
/**
|
|
608
|
+
* 更新上传进度
|
|
609
|
+
*
|
|
610
|
+
* ✅ 简单直接的进度计算:completedChunks / totalChunks * 100%
|
|
611
|
+
* completedChunks 只在分片真正上传成功时才累加
|
|
612
|
+
*/
|
|
613
|
+
updateProgress(): void;
|
|
614
|
+
/**
|
|
615
|
+
* 计算并更新单个文件的上传速度
|
|
616
|
+
*
|
|
617
|
+
* 🔑 内联实现,每次分片完成时直接写入 proxy.speed,不做首次跳过也不做防抖。
|
|
618
|
+
* 分片上传本身是低频事件(间隔取决于分片大小/网速),不需要防抖反而会导致
|
|
619
|
+
* 相邻调用间的 speed 值振荡。(calculateSpeed 的 100ms 防抖适用于 XHR progress
|
|
620
|
+
* 高频回调场景,但对分片上传反而有害。)
|
|
621
|
+
*
|
|
622
|
+
* 💡 瞬时速度在首次调用时为 0(无上次采样可比较),从第二次起有真实值。
|
|
623
|
+
* 平均速度基于 chunkStartTime,首轮即有效(虽然包含了 hash 开销会偏低,
|
|
624
|
+
* 但这在几次分片后即趋于稳定,比跳变好得多)。
|
|
625
|
+
*/
|
|
626
|
+
private updateChunkUploadSpeed;
|
|
627
|
+
startUpload(): Promise<void>;
|
|
628
|
+
/**
|
|
629
|
+
* 使用信号量控制并发上传
|
|
630
|
+
*/
|
|
631
|
+
protected uploadWithConcurrency(): Promise<void>;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* XHR 拦截器配置
|
|
636
|
+
*/
|
|
637
|
+
interface XHRInterceptorConfig<T> {
|
|
638
|
+
/** 拦截器名称(用于日志) */
|
|
639
|
+
name: string;
|
|
640
|
+
/** 获取文件队列的方法 */
|
|
641
|
+
getFileQueue: () => T[];
|
|
642
|
+
/** 获取全局 headers 的方法 */
|
|
643
|
+
getGlobalHeaders: (file: T) => Record<string, string> | undefined;
|
|
644
|
+
/** 处理进度事件的方法 */
|
|
645
|
+
onProgress: (file: T, event: ProgressEvent) => void;
|
|
646
|
+
/** 设置 abort 方法(可选) */
|
|
647
|
+
setupAbort?: (file: T, xhr: XMLHttpRequest) => void;
|
|
648
|
+
/** 标记文件已分配(可选) */
|
|
649
|
+
markFileAsAssigned?: (file: T) => void;
|
|
650
|
+
/** 从 XHR 获取文件实例的方法 */
|
|
651
|
+
getFileFromXHR?: (xhr: XMLHttpRequest) => T | null;
|
|
652
|
+
/** 设置 XHR 与文件的映射 */
|
|
653
|
+
setFileToXHR?: (xhr: XMLHttpRequest, file: T) => void;
|
|
654
|
+
/** 设置 abort 方法 */
|
|
655
|
+
onAbort?: (file: T, xhr: XMLHttpRequest) => void;
|
|
656
|
+
/** 取消标记文件 */
|
|
657
|
+
unmarkFileAsAssigned: (file: T) => void;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* 全局 XHR 拦截器管理器
|
|
661
|
+
*/
|
|
662
|
+
declare class XHRInterceptor<T extends TransferFile<T>> {
|
|
663
|
+
private config;
|
|
664
|
+
private static instances;
|
|
665
|
+
private isInstalled;
|
|
666
|
+
private originalXHR;
|
|
667
|
+
private xhrToFileMap;
|
|
668
|
+
private assignedFiles;
|
|
669
|
+
constructor(config: XHRInterceptorConfig<T>);
|
|
670
|
+
/**
|
|
671
|
+
* 获取或创建拦截器实例(单例模式)
|
|
672
|
+
*/
|
|
673
|
+
static getInstance<T extends TransferFile<T>>(name: string, config: XHRInterceptorConfig<T>): XHRInterceptor<T>;
|
|
674
|
+
/**
|
|
675
|
+
* 添加文件到已分配集合
|
|
676
|
+
*/
|
|
677
|
+
private markAsAssigned;
|
|
678
|
+
/**
|
|
679
|
+
* 从 XHR 获取关联的文件
|
|
680
|
+
*/
|
|
681
|
+
private getFileFromXHR;
|
|
682
|
+
/**
|
|
683
|
+
* 设置 XHR 与文件的映射
|
|
684
|
+
*/
|
|
685
|
+
private setFileToXHR;
|
|
686
|
+
/**
|
|
687
|
+
* 安装拦截器(全局只安装一次)
|
|
688
|
+
*/
|
|
689
|
+
install(): void;
|
|
690
|
+
/**
|
|
691
|
+
* 恢复原始 XHR
|
|
692
|
+
*/
|
|
693
|
+
restore(): void;
|
|
694
|
+
/**
|
|
695
|
+
* 清理文件(文件完成时调用)
|
|
696
|
+
*/
|
|
697
|
+
cleanupFile(file: T): void;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* 文件传输实例类
|
|
702
|
+
* 负责单个文件的上传逻辑、状态管理、速率计算和生命周期控制
|
|
703
|
+
*
|
|
704
|
+
* @template T - 上传成功后的响应数据类型
|
|
705
|
+
*/
|
|
706
|
+
declare class UploadFile<T = any> extends TransferFile<UploadFile, T> {
|
|
707
|
+
up: Uploader<T>;
|
|
708
|
+
/**
|
|
709
|
+
* 🔑 文件创建时保存 action 快照,防止切换模式后重试时用到错误的 action
|
|
710
|
+
*/
|
|
711
|
+
private _action;
|
|
712
|
+
/**
|
|
713
|
+
* 分片管理器实例
|
|
714
|
+
* 每个文件拥有独立的 uploadChunkManager,实现并发控制、断点续传和失败隔离
|
|
715
|
+
*/
|
|
716
|
+
uploadChunkManager: UploadChunkManager | null;
|
|
717
|
+
static interceptor: XHRInterceptor<UploadFile>;
|
|
718
|
+
/**
|
|
719
|
+
* 全局共享的 WeakMap
|
|
720
|
+
* 用于存储 XHR 实例与文件上下文的映射关系,支持拦截器透明注入
|
|
721
|
+
*/
|
|
722
|
+
private static xhrToFileMap;
|
|
723
|
+
/**
|
|
724
|
+
* 全局共享的当前上传文件队列
|
|
725
|
+
* 用于在拦截器中查找匹配的 FormData
|
|
726
|
+
*/
|
|
727
|
+
private static currentQueue;
|
|
728
|
+
/**
|
|
729
|
+
* 跟踪已分配到 XHR 的文件集合
|
|
730
|
+
* 用于按顺序分配 XHR 实例,避免冲突
|
|
731
|
+
*/
|
|
732
|
+
private static assignedFiles;
|
|
733
|
+
constructor(file: IFile, transfer: Transfer);
|
|
734
|
+
/**
|
|
735
|
+
* 从回显数据初始化 uploadChunkManager 状态
|
|
736
|
+
* 用于后端接口返回的文件列表回显场景
|
|
737
|
+
*
|
|
738
|
+
* @param file - 包含分片上传信息的文件对象
|
|
739
|
+
*/
|
|
740
|
+
private initChunkManagerFromRestore;
|
|
741
|
+
/**
|
|
742
|
+
* 执行普通上传的重试(模板方法覆写)
|
|
743
|
+
*/
|
|
744
|
+
protected doRetryTransfer(): Promise<T>;
|
|
745
|
+
remove(): void;
|
|
746
|
+
setFile(file: File | Blob, formData: FormData, index?: number): void;
|
|
747
|
+
/**
|
|
748
|
+
* 上传文件(支持普通上传和分片上传)
|
|
749
|
+
*
|
|
750
|
+
* @param onChunkComplete - 分片上传完成时的回调函数
|
|
751
|
+
* @param signal - AbortSignal,用于取消请求(超时控制)
|
|
752
|
+
* @param chunkFormData - 分片上传的 FormData(可选,优先使用此参数而不是 this.formData)
|
|
753
|
+
* @returns Promise<T>
|
|
754
|
+
*/
|
|
755
|
+
upload(onChunkComplete?: (res: T) => void, signal?: AbortSignal, chunkFormData?: FormData): Promise<T>;
|
|
756
|
+
/**
|
|
757
|
+
* 返回上传的分片管理器
|
|
758
|
+
*/
|
|
759
|
+
protected getChunkManager(): ChunkManager | null;
|
|
760
|
+
/**
|
|
761
|
+
* 获取文件大小(上传场景:直接用 File.size)
|
|
762
|
+
*/
|
|
763
|
+
getFileSize(): number;
|
|
764
|
+
/**
|
|
765
|
+
* 更新当前文件的上传进度和已传输字节数(模板方法)
|
|
766
|
+
*
|
|
767
|
+
* 根据上传类型采用不同的进度计算策略:
|
|
768
|
+
* - 普通上传:从 event.loaded/event.total 直接计算百分比和字节数
|
|
769
|
+
* - 分片上传:由 uploadChunkManager 独立维护进度,此处无需额外操作
|
|
770
|
+
*/
|
|
771
|
+
protected updateLocalProgress(event: ProgressEvent): void;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
declare class Uploader<T = any> extends Transfer<UploadFile, T> {
|
|
775
|
+
inputHTML: HTMLInputElement | null;
|
|
776
|
+
static objectUrls: any[];
|
|
777
|
+
static baseConfig: UploaderConfig;
|
|
778
|
+
config: UploaderConfig | null;
|
|
779
|
+
static instances: Uploader | null;
|
|
780
|
+
static uploadFile: UploadFile | null;
|
|
781
|
+
static isInterceptorInstalled: boolean;
|
|
782
|
+
totalPercent: number;
|
|
783
|
+
static fileIndex: number;
|
|
784
|
+
static originalXHR?: typeof XMLHttpRequest | null;
|
|
785
|
+
static interceptorActive: boolean;
|
|
786
|
+
private static defaultPlugins;
|
|
787
|
+
openCallBack: OpenFileCallback;
|
|
788
|
+
selectCallback: SelectCallBack | null | undefined;
|
|
789
|
+
clearFiles(): void;
|
|
790
|
+
/**
|
|
791
|
+
* 设置全局默认插件(影响之后创建的所有实例)
|
|
792
|
+
*/
|
|
793
|
+
static setDefaultPlugins(plugins: IUDPlugin<UploadFile>[]): void;
|
|
794
|
+
/**
|
|
795
|
+
* 设置文件列表(用于回显已上传的文件)
|
|
796
|
+
* @param files - 要回显的文件列表,可以是 UploadFile 实例或文件信息对象
|
|
797
|
+
* @example
|
|
798
|
+
* ```typescript
|
|
799
|
+
* //从服务端获取文件列表后回显
|
|
800
|
+
* const savedFiles = await fetchSavedFiles();
|
|
801
|
+
* uploader.setFiles(savedFiles.map(fileInfo => ({
|
|
802
|
+
* fileId: fileInfo.id,
|
|
803
|
+
* fileName: fileInfo.name,
|
|
804
|
+
* url: fileInfo.url,
|
|
805
|
+
* size: fileInfo.size,
|
|
806
|
+
* percent: 100,
|
|
807
|
+
* status: 'success',
|
|
808
|
+
* formatSize: formatFileSize(fileInfo.size)
|
|
809
|
+
* })));
|
|
810
|
+
*
|
|
811
|
+
*/
|
|
812
|
+
setFiles(files: IFile[]): void;
|
|
813
|
+
constructor(config?: UploaderConfig);
|
|
814
|
+
/**
|
|
815
|
+
* @description: 打开文件
|
|
816
|
+
* @param {OpenFileCallback} fn
|
|
817
|
+
* @return {*}
|
|
818
|
+
*/
|
|
819
|
+
open(fn?: OpenFileCallback): any;
|
|
820
|
+
private init;
|
|
821
|
+
private createInput;
|
|
822
|
+
create(config?: UploaderConfig): this;
|
|
823
|
+
updateConfig(config: Partial<UploaderConfig>): void;
|
|
824
|
+
set onbeforeTransfer(callback: beforeTransferCallBack<UploadFile>);
|
|
825
|
+
set onSelect(callback: SelectCallBack);
|
|
826
|
+
remObjectUrls(url: string): void;
|
|
827
|
+
cancelAll(): void;
|
|
828
|
+
pauseAll(): void;
|
|
829
|
+
resumeAll(): void;
|
|
830
|
+
retryAll(): void;
|
|
831
|
+
/**
|
|
832
|
+
* @description: 手动提交上传所有文件(当 autoUpload 为 false 时)
|
|
833
|
+
* @return {Promise<void>}
|
|
834
|
+
*/
|
|
835
|
+
submit(): Promise<void>;
|
|
836
|
+
/**
|
|
837
|
+
* 验证文件是否符合上传条件(统一验证逻辑)
|
|
838
|
+
* @param file 原始文件对象
|
|
839
|
+
* @param uploadFileInstance 上传文件实例
|
|
840
|
+
* @returns 验证结果 { valid: boolean, error?: any }
|
|
841
|
+
* @private
|
|
842
|
+
*/
|
|
843
|
+
private validateFile;
|
|
844
|
+
/**
|
|
845
|
+
* 处理文件选择(内部方法)
|
|
846
|
+
*/
|
|
847
|
+
private processSelectedFiles;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* 错误码枚举(所有可能的错误)
|
|
852
|
+
*/
|
|
853
|
+
declare enum ErrorCode {
|
|
854
|
+
/** 未知错误 */
|
|
855
|
+
UNKNOWN = 1000,
|
|
856
|
+
/** 操作已中止 */
|
|
857
|
+
ABORTED = 1001,
|
|
858
|
+
/** 请求超时 */
|
|
859
|
+
TIMEOUT = 1002,
|
|
860
|
+
/** 网络错误 */
|
|
861
|
+
NETWORK = 1003,
|
|
862
|
+
/** 文件过大 */
|
|
863
|
+
FILE_TOO_LARGE = 2000,
|
|
864
|
+
/** 文件过小 */
|
|
865
|
+
FILE_TOO_SMALL = 2001,
|
|
866
|
+
/** 文件类型无效 */
|
|
867
|
+
INVALID_TYPE = 2002,
|
|
868
|
+
/** 文件数量超限 */
|
|
869
|
+
FILE_LIMIT_EXCEEDED = 2003,
|
|
870
|
+
/** 重复的文件 */
|
|
871
|
+
DUPLICATE_FILE = 2004,
|
|
872
|
+
/** 文件为空 */
|
|
873
|
+
FILE_EMPTY = 2005,
|
|
874
|
+
/** 文件已损坏 */
|
|
875
|
+
FILE_CORRUPTED = 2006,
|
|
876
|
+
/** 文件过于空泛(可能缺少必要内容) */
|
|
877
|
+
FILE_TOO_EMPTY = 2007,
|
|
878
|
+
/** 图片宽度不符合要求 */
|
|
879
|
+
IMAGE_WIDTH_INVALID = 2100,
|
|
880
|
+
/** 图片高度不符合要求 */
|
|
881
|
+
IMAGE_HEIGHT_INVALID = 2101,
|
|
882
|
+
/** 图片宽高比不符合要求 */
|
|
883
|
+
IMAGE_ASPECT_RATIO_INVALID = 2102,
|
|
884
|
+
/** 图片分辨率不符合要求 */
|
|
885
|
+
IMAGE_RESOLUTION_INVALID = 2103,
|
|
886
|
+
/** 图片不是正方形 */
|
|
887
|
+
IMAGE_NOT_SQUARE = 2104,
|
|
888
|
+
/** 不支持动态图片 */
|
|
889
|
+
IMAGE_ANIMATED = 2105,
|
|
890
|
+
/** 视频时长不符合要求 */
|
|
891
|
+
VIDEO_DURATION_INVALID = 2200,
|
|
892
|
+
/** 视频宽度不符合要求 */
|
|
893
|
+
VIDEO_WIDTH_INVALID = 2201,
|
|
894
|
+
/** 视频高度不符合要求 */
|
|
895
|
+
VIDEO_HEIGHT_INVALID = 2202,
|
|
896
|
+
/** 视频比特率不符合要求 */
|
|
897
|
+
VIDEO_BITRATE_INVALID = 2203,
|
|
898
|
+
/** 视频编码格式不支持 */
|
|
899
|
+
VIDEO_CODEC_INVALID = 2204,
|
|
900
|
+
/** 上传失败 */
|
|
901
|
+
UPLOAD_FAILED = 3000,
|
|
902
|
+
/** 分片上传失败 */
|
|
903
|
+
CHUNK_UPLOAD_FAILED = 3001,
|
|
904
|
+
/** 合并分片失败 */
|
|
905
|
+
MERGE_FAILED = 3002,
|
|
906
|
+
/** 服务器错误 */
|
|
907
|
+
SERVER_ERROR = 3003,
|
|
908
|
+
/** 未授权访问 */
|
|
909
|
+
UNAUTHORIZED = 3004,
|
|
910
|
+
/** 禁止访问 */
|
|
911
|
+
FORBIDDEN = 3005,
|
|
912
|
+
/** 资源不存在 */
|
|
913
|
+
NOT_FOUND = 3006,
|
|
914
|
+
/** 下载失败 */
|
|
915
|
+
DOWNLOAD_FAILED = 4000,
|
|
916
|
+
/** 分片下载失败 */
|
|
917
|
+
CHUNK_DOWNLOAD_FAILED = 4001,
|
|
918
|
+
/** 插件错误 */
|
|
919
|
+
PLUGIN_ERROR = 5000,
|
|
920
|
+
/** 插件初始化失败 */
|
|
921
|
+
PLUGIN_INIT_FAILED = 5001,
|
|
922
|
+
/** 插件执行失败 */
|
|
923
|
+
PLUGIN_EXECUTION_FAILED = 5002
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* 错误级别
|
|
927
|
+
*/
|
|
928
|
+
declare enum ErrorLevel {
|
|
929
|
+
INFO = "info",// 提示信息,不影响流程
|
|
930
|
+
WARNING = "warn",// 警告,但继续执行
|
|
931
|
+
ERROR = "error",// 错误,中断当前操作
|
|
932
|
+
CRITICAL = "critical"
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* 错误上下文(包含现场信息)
|
|
936
|
+
*/
|
|
937
|
+
interface ErrorContext {
|
|
938
|
+
/** 发生时间 */
|
|
939
|
+
timestamp?: number;
|
|
940
|
+
/** 插件名称 */
|
|
941
|
+
plugin?: string;
|
|
942
|
+
/** 上传器实例 */
|
|
943
|
+
uploader?: Uploader;
|
|
944
|
+
options?: Record<string, any>;
|
|
945
|
+
/** 文件名 */
|
|
946
|
+
fileName?: string;
|
|
947
|
+
/** 文件大小 */
|
|
948
|
+
fileSize?: number;
|
|
949
|
+
/** 分片索引 */
|
|
950
|
+
chunkIndex?: number;
|
|
951
|
+
/** HTTP 状态码 */
|
|
952
|
+
httpStatus?: number;
|
|
953
|
+
/** 重试次数 */
|
|
954
|
+
retryCount?: number;
|
|
955
|
+
/** 原始错误 */
|
|
956
|
+
originalError?: any;
|
|
957
|
+
/** 自定义字段 */
|
|
958
|
+
[key: string]: any;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* 错误配置
|
|
962
|
+
*/
|
|
963
|
+
interface ErrorOptions {
|
|
964
|
+
/** 是否可恢复 */
|
|
965
|
+
recoverable?: boolean;
|
|
966
|
+
/** 是否自动重试 */
|
|
967
|
+
retryable?: boolean;
|
|
968
|
+
/** 建议操作 */
|
|
969
|
+
suggestion?: string;
|
|
970
|
+
/** 是否显示给用户 */
|
|
971
|
+
userVisible?: boolean;
|
|
972
|
+
/** 国际化key */
|
|
973
|
+
i18nKey?: string;
|
|
974
|
+
}
|
|
975
|
+
interface FileUDErrorJSON {
|
|
976
|
+
name: string;
|
|
977
|
+
code: ErrorCode;
|
|
978
|
+
level: ErrorLevel;
|
|
979
|
+
message: string;
|
|
980
|
+
context: ErrorContext;
|
|
981
|
+
options: ErrorOptions;
|
|
982
|
+
stack?: string;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* 文件下载器类 (Downloader)
|
|
987
|
+
*
|
|
988
|
+
* 继承自 Transfer,专门处理文件下载相关的业务逻辑。
|
|
989
|
+
* 回调属性(successCallback / updateCallback / onInitChunkCallback / OnMergeChunkCallBack /
|
|
990
|
+
* beforeTransferCallback)及其 setter 全部由 Transfer 基类提供,此处不再重复声明。
|
|
991
|
+
*/
|
|
992
|
+
declare class Downloader<T = any> extends Transfer<DownloadFile, T> {
|
|
993
|
+
/** 全局基础配置 */
|
|
994
|
+
static baseConfig: DownloaderConfig;
|
|
995
|
+
/** 当前实例的配置信息 */
|
|
996
|
+
config: DownloaderConfig | null;
|
|
997
|
+
static instances: Downloader | null;
|
|
998
|
+
static fileIndex: number;
|
|
999
|
+
/** 静态默认插件(影响之后创建的所有实例) */
|
|
1000
|
+
private static defaultPlugins;
|
|
1001
|
+
constructor(config?: DownloaderConfig);
|
|
1002
|
+
/**
|
|
1003
|
+
* 动态更新配置
|
|
1004
|
+
* @param config 部分配置项
|
|
1005
|
+
*/
|
|
1006
|
+
updateConfig(config: Partial<DownloaderConfig>): void;
|
|
1007
|
+
/**
|
|
1008
|
+
* 创建下载器实例(多例模式支持)
|
|
1009
|
+
* @param config 下载器配置
|
|
1010
|
+
* @returns Downloader 实例
|
|
1011
|
+
*/
|
|
1012
|
+
create(config?: DownloaderConfig): this;
|
|
1013
|
+
/**
|
|
1014
|
+
* 设置全局默认插件(影响之后创建的所有实例)
|
|
1015
|
+
*/
|
|
1016
|
+
static setDefaultPlugins(plugins: IUDPlugin<DownloadFile>[]): void;
|
|
1017
|
+
/**
|
|
1018
|
+
* 初始化实例插件列表(继承全局默认插件)
|
|
1019
|
+
*/
|
|
1020
|
+
private init;
|
|
1021
|
+
/**
|
|
1022
|
+
* 🔑 文件名 → FileSystemFileHandle 缓存
|
|
1023
|
+
*
|
|
1024
|
+
* 用途:避免重复调用 showSaveFilePicker() 截断已存在的文件。
|
|
1025
|
+
* Chrome 的 showSaveFilePicker() 在用户选择覆盖同名文件时会直接截断文件为 0 字节,
|
|
1026
|
+
* 导致"秒下"检测失败。缓存句柄后,二次下载同一文件直接复用句柄。
|
|
1027
|
+
*
|
|
1028
|
+
* ⚠️ 挂载在 window 上而非 static Map:Vite HMR 会重置模块级静态变量,
|
|
1029
|
+
* 但 window 上的属性不受影响,保证开发模式下缓存不丢失。
|
|
1030
|
+
* 页面全量刷新后缓存自然清空,功能不受影响(回退到正常下载)。
|
|
1031
|
+
*/
|
|
1032
|
+
private static get fileHandleCache();
|
|
1033
|
+
/**
|
|
1034
|
+
* 静态方法:打开系统"另存为"对话框,获取流式写入 FileHandle
|
|
1035
|
+
*
|
|
1036
|
+
* 使用 File System Access API(Chrome 86+, Edge 86+)。
|
|
1037
|
+
* 如果 API 不可用,返回 undefined(调用方应回退到 Blob 下载模式)。
|
|
1038
|
+
*
|
|
1039
|
+
* 🔑 如果当前会话中已下载过同名文件,直接复用缓存的 handle,
|
|
1040
|
+
* 避免 showSaveFilePicker() 截断已有文件。
|
|
1041
|
+
*
|
|
1042
|
+
* @param suggestedName 建议的文件名
|
|
1043
|
+
* @returns FileHandle(用户选择后),null(用户取消),undefined(API 不可用)
|
|
1044
|
+
*/
|
|
1045
|
+
static pickSaveFile(suggestedName?: string): Promise<FileSystemFileHandle | null | undefined>;
|
|
1046
|
+
/**
|
|
1047
|
+
* 添加下载任务并立即开始下载
|
|
1048
|
+
* @param file 下载文件配置
|
|
1049
|
+
* @param fileHandle 可选,File System Access API 的 FileHandle(流式保存用)
|
|
1050
|
+
* @returns DownloadFile 实例
|
|
1051
|
+
*/
|
|
1052
|
+
downloadFile(file: UDFile, fileHandle?: FileSystemFileHandle): DownloadFile<T>;
|
|
1053
|
+
/**
|
|
1054
|
+
* 提交所有待下载文件(自动调用所有文件 start 方法)
|
|
1055
|
+
*/
|
|
1056
|
+
submit(): void;
|
|
1057
|
+
/**
|
|
1058
|
+
* 取消所有下载任务
|
|
1059
|
+
*/
|
|
1060
|
+
cancelAll(): void;
|
|
1061
|
+
/**
|
|
1062
|
+
* 暂停所有下载任务
|
|
1063
|
+
*/
|
|
1064
|
+
pauseAll(): void;
|
|
1065
|
+
/**
|
|
1066
|
+
* 恢复所有下载任务
|
|
1067
|
+
*/
|
|
1068
|
+
resumeAll(): Promise<void>;
|
|
1069
|
+
/**
|
|
1070
|
+
* 移除所有下载任务(取消进行中的下载并清空列表)
|
|
1071
|
+
*/
|
|
1072
|
+
removeAll(): void;
|
|
1073
|
+
/**
|
|
1074
|
+
* 重试所有失败/取消的下载任务
|
|
1075
|
+
*/
|
|
1076
|
+
retryAll(): Promise<void>;
|
|
1077
|
+
/**
|
|
1078
|
+
* 保存 Blob 到本地(触发浏览器下载对话框)
|
|
1079
|
+
* @param fileName 文件名
|
|
1080
|
+
* @param data Blob 数据或可转为 Blob 的数据
|
|
1081
|
+
*/
|
|
1082
|
+
static saveBlob(fileName: string, data: any): void;
|
|
1083
|
+
/**
|
|
1084
|
+
* 通过 URL 下载并保存文件到本地(触发浏览器下载对话框)
|
|
1085
|
+
* @param fileName 文件名
|
|
1086
|
+
* @param url 下载链接
|
|
1087
|
+
*/
|
|
1088
|
+
static saveFile(fileName: string, url: string): Promise<void>;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* 文件传输接受类型定义
|
|
1093
|
+
* 支持常见的 MIME 类型、通配符和文件后缀名
|
|
1094
|
+
*/
|
|
1095
|
+
type AcceptFileType = "image/*" | "image/jpeg" | "image/png" | "image/gif" | "image/webp" | "image/svg+xml" | "image/bmp" | "image/tiff" | "video/*" | "video/mp4" | "video/webm" | "video/ogg" | "video/quicktime" | "audio/*" | "audio/mp3" | "audio/wav" | "audio/ogg" | "audio/aac" | "audio/flac" | "application/pdf" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.ms-powerpoint" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/zip" | "application/x-rar-compressed" | "application/x-7z-compressed" | "text/plain" | "text/csv" | "text/html" | "text/css" | "text/javascript" | "application/json" | "application/xml" | "font/woff" | "font/woff2" | "font/ttf" | "font/otf" | ".jpg" | ".jpeg" | ".png" | ".gif" | ".webp" | ".svg" | ".bmp" | ".tiff" | ".ico" | ".heic" | ".heif" | ".raw" | ".psd" | ".ai" | ".eps" | ".mp4" | ".webm" | ".ogg" | ".avi" | ".mov" | ".wmv" | ".flv" | ".mkv" | ".m4v" | ".mpeg" | ".mpg" | ".3gp" | ".rmvb" | ".rm" | ".mp3" | ".wav" | ".flac" | ".aac" | ".wma" | ".m4a" | ".ape" | ".opus" | ".aiff" | ".mid" | ".midi" | ".pdf" | ".doc" | ".docx" | ".xls" | ".xlsx" | ".ppt" | ".pptx" | ".txt" | ".md" | ".csv" | ".rtf" | ".odt" | ".ods" | ".odp" | ".wps" | ".et" | ".dps" | ".json" | ".xml" | ".html" | ".htm" | ".css" | ".scss" | ".sass" | ".less" | ".js" | ".jsx" | ".ts" | ".tsx" | ".vue" | ".py" | ".java" | ".c" | ".cpp" | ".h" | ".hpp" | ".go" | ".rs" | ".php" | ".rb" | ".swift" | ".kt" | ".yaml" | ".yml" | ".toml" | ".ini" | ".sql" | ".sh" | ".bat" | ".ps1" | ".zip" | ".rar" | ".7z" | ".tar" | ".gz" | ".bz2" | ".xz" | ".tgz" | ".iso" | ".ttf" | ".otf" | ".woff" | ".woff2" | ".eot" | ".apk" | ".ipa" | ".exe" | ".msi" | ".dmg" | ".pkg";
|
|
1096
|
+
interface ChunkOptions {
|
|
1097
|
+
maxConcurrent?: number;
|
|
1098
|
+
chunkSize?: number;
|
|
1099
|
+
retries?: number | null;
|
|
1100
|
+
retryDelay?: number;
|
|
1101
|
+
timeout?: number;
|
|
1102
|
+
enableFileCache?: boolean;
|
|
1103
|
+
cacheRetentionDays?: number;
|
|
1104
|
+
}
|
|
1105
|
+
type FileConfig = {
|
|
1106
|
+
data: File | Blob;
|
|
1107
|
+
uploadFile: UploadFile;
|
|
1108
|
+
formData: FormData;
|
|
1109
|
+
chunkIndex?: number;
|
|
1110
|
+
};
|
|
1111
|
+
interface UDConfig<T = any> {
|
|
1112
|
+
chunkOptions?: ChunkOptions | null;
|
|
1113
|
+
axiosInstance?: AxiosInstance;
|
|
1114
|
+
limit?: number;
|
|
1115
|
+
maxSize?: number;
|
|
1116
|
+
headers?: Record<string, any>;
|
|
1117
|
+
/** 最大同时传输文件数,0 或不设置表示不限制 */
|
|
1118
|
+
maxFileConcurrent?: number;
|
|
1119
|
+
}
|
|
1120
|
+
interface UploaderConfig extends UDConfig<UploadFile> {
|
|
1121
|
+
multiple?: boolean;
|
|
1122
|
+
accept?: AcceptFileType[] | string[];
|
|
1123
|
+
show?: false;
|
|
1124
|
+
elementId?: string;
|
|
1125
|
+
autoUpload?: boolean;
|
|
1126
|
+
/**
|
|
1127
|
+
* 文件传输下载地址,可以是字符串或者promise函数
|
|
1128
|
+
* @return {Promise}
|
|
1129
|
+
*/
|
|
1130
|
+
action: string | ((formData: FormData, transferFile: UploadFile) => string | Promise<any>);
|
|
1131
|
+
file?: string | ((FileConfig: FileConfig) => void);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* 下载器配置
|
|
1135
|
+
*/
|
|
1136
|
+
interface DownloaderConfig extends UDConfig<DownloadFile> {
|
|
1137
|
+
/** 默认超时时间 */
|
|
1138
|
+
timeout?: number;
|
|
1139
|
+
/**
|
|
1140
|
+
* 文件传输下载地址,可以是字符串或者promise函数
|
|
1141
|
+
* @return {Promise}
|
|
1142
|
+
*/
|
|
1143
|
+
action: string | ((transferFile: DownloadFile) => string | Promise<any>);
|
|
1144
|
+
axiosOptions?: AxiosRequestConfig;
|
|
1145
|
+
/** 下载最大速率限制(bytes/秒),0 或不设置表示不限制 */
|
|
1146
|
+
maxDownloadSpeed?: number;
|
|
1147
|
+
}
|
|
1148
|
+
interface PluginContext<T extends TransferFile<any, any> = TransferFile<any, any>> {
|
|
1149
|
+
/** 传输器实例(Uploader 或 Downloader) */
|
|
1150
|
+
transfer: Transfer<T>;
|
|
1151
|
+
/** 当前文件 */
|
|
1152
|
+
file?: T;
|
|
1153
|
+
/** 表单数据(上传专用) */
|
|
1154
|
+
formData?: FormData;
|
|
1155
|
+
/** 配置信息 */
|
|
1156
|
+
config?: any;
|
|
1157
|
+
/** 插件共享数据 */
|
|
1158
|
+
shared: Map<string, any>;
|
|
1159
|
+
/** 当前操作状态(用于区分MD5计算、上传中等不同阶段) */
|
|
1160
|
+
status?: IFile["status"];
|
|
1161
|
+
/** 状态描述信息(可选,用于显示给用户) */
|
|
1162
|
+
message?: string;
|
|
1163
|
+
}
|
|
1164
|
+
interface IUDPlugin<T extends TransferFile<any, any> = TransferFile<any, any>> {
|
|
1165
|
+
/** 插件名称 */
|
|
1166
|
+
name: string;
|
|
1167
|
+
/** 插件版本 */
|
|
1168
|
+
version?: string;
|
|
1169
|
+
desc?: string;
|
|
1170
|
+
/** 插件优先级(数字越小越先执行) */
|
|
1171
|
+
priority?: number;
|
|
1172
|
+
/** 插件初始化(注册时调用一次) */
|
|
1173
|
+
install?: (transfer: Transfer<T>, options?: any) => void | Promise<void>;
|
|
1174
|
+
created?: (transfer: Transfer<T>) => void | Promise<void>;
|
|
1175
|
+
/** 文件选择后触发 */
|
|
1176
|
+
onFileSelect?: (file: T, context: PluginContext<T>) => Promise<T | void | false> | T | void;
|
|
1177
|
+
/** 传输前触发 */
|
|
1178
|
+
beforeTransfer?: (file: T, context: PluginContext<T>) => Promise<boolean | void> | boolean | void;
|
|
1179
|
+
/** 传输进度触发 */
|
|
1180
|
+
onProgress?: (percent: number, file: T, context: PluginContext<T>) => void;
|
|
1181
|
+
/** 传输成功触发 */
|
|
1182
|
+
onSuccess?: (response: any, file: T, context: PluginContext<T>) => void;
|
|
1183
|
+
/** 传输失败触发 */
|
|
1184
|
+
onError?: (error: Error, file: T, context: PluginContext<T>) => void;
|
|
1185
|
+
/** 插件销毁时调用 */
|
|
1186
|
+
destroy?: () => void;
|
|
1187
|
+
}
|
|
1188
|
+
interface UDFile {
|
|
1189
|
+
fileId?: string;
|
|
1190
|
+
url: string;
|
|
1191
|
+
fileName: string;
|
|
1192
|
+
size?: number;
|
|
1193
|
+
fileHandle?: FileSystemFileHandle;
|
|
1194
|
+
status?: "pending" | "UDLoading" | "paused" | "success" | "fail" | "error" | "cancelled" | "merging" | "hashing";
|
|
1195
|
+
}
|
|
1196
|
+
interface IFile extends UDFile {
|
|
1197
|
+
percent?: number;
|
|
1198
|
+
extension?: string;
|
|
1199
|
+
File?: File;
|
|
1200
|
+
loading?: boolean;
|
|
1201
|
+
hashLoading?: boolean;
|
|
1202
|
+
hashPercent?: number;
|
|
1203
|
+
isCancel?: boolean;
|
|
1204
|
+
formatSize?: string;
|
|
1205
|
+
abort?: () => void;
|
|
1206
|
+
isRetry?: boolean;
|
|
1207
|
+
index?: number;
|
|
1208
|
+
metadata?: Record<string, any>;
|
|
1209
|
+
speed?: speedInfo;
|
|
1210
|
+
totalChunks?: number;
|
|
1211
|
+
completedChunks?: number;
|
|
1212
|
+
chunkIndexes?: number[];
|
|
1213
|
+
fileHash?: string;
|
|
1214
|
+
uploadId?: string;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* 下载文件接口
|
|
1218
|
+
*/
|
|
1219
|
+
interface IDownloadFile extends Omit<IFile, "transfer"> {
|
|
1220
|
+
saveFileName?: string;
|
|
1221
|
+
useBlob?: boolean;
|
|
1222
|
+
__downloader__?: Downloader;
|
|
1223
|
+
}
|
|
1224
|
+
type ErrorCallBack = (errors: FileUDErrorJSON) => void;
|
|
1225
|
+
type beforeTransferCallBack<T extends TransferFile<any, any>> = (file: T) => boolean | Promise<Boolean> | undefined | null | {
|
|
1226
|
+
[key: string]: any;
|
|
1227
|
+
};
|
|
1228
|
+
type successCallback<T> = (response: T, file: TransferFile<any, any>) => void;
|
|
1229
|
+
type UpdateCallBack<T = any> = (file: T[]) => void;
|
|
1230
|
+
type onInitChunkCallback<T> = (file: T, totalChunks: number, fileHash: string) => Promise<{
|
|
1231
|
+
fileHash: string;
|
|
1232
|
+
chunks?: number[] | null | undefined;
|
|
1233
|
+
isInstantUpload?: boolean;
|
|
1234
|
+
isInstantDownload?: boolean;
|
|
1235
|
+
url?: string;
|
|
1236
|
+
shouldRemove?: boolean;
|
|
1237
|
+
}> | undefined;
|
|
1238
|
+
type OnMergeChunkCallBack = (chunkManager: UploadChunkManager) => Promise<any>;
|
|
1239
|
+
type OpenFileCallback = (uploadFile: UploadFile) => void;
|
|
1240
|
+
type SelectCallBack = (file: File) => boolean | Promise<Boolean> | undefined | null;
|
|
1241
|
+
/**
|
|
1242
|
+
* 分片事件数据辅助类型(上传/下载共用)
|
|
1243
|
+
*/
|
|
1244
|
+
interface ChunkStartData<T = TransferFile<any, any>> {
|
|
1245
|
+
file: T;
|
|
1246
|
+
totalChunks: number;
|
|
1247
|
+
chunkSize: number;
|
|
1248
|
+
}
|
|
1249
|
+
interface ChunkSuccessData<T = TransferFile<any, any>> {
|
|
1250
|
+
chunkIndex: number;
|
|
1251
|
+
totalChunks: number;
|
|
1252
|
+
completedChunks: number;
|
|
1253
|
+
percent: number;
|
|
1254
|
+
file: T;
|
|
1255
|
+
}
|
|
1256
|
+
interface ChunkErrorData<T = TransferFile<any, any>> {
|
|
1257
|
+
chunkIndex: number;
|
|
1258
|
+
totalChunks: number;
|
|
1259
|
+
error: string;
|
|
1260
|
+
file: T;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* 基础传输事件接口(上传/下载共用)
|
|
1264
|
+
*
|
|
1265
|
+
* 注意:error 和 update 未纳入基类,因为上传/下载的签名完全不同:
|
|
1266
|
+
* - Uploader.error = (errors: FileUDErrorJSON) => void
|
|
1267
|
+
* - Downloader.error = (error: Error, file: DownloadFile) => void
|
|
1268
|
+
*/
|
|
1269
|
+
interface BaseTransferEvents<T = TransferFile<any, any>> {
|
|
1270
|
+
change: (file: T) => void;
|
|
1271
|
+
progress: (percent: number) => void;
|
|
1272
|
+
pause: (file: T) => void;
|
|
1273
|
+
resume: (file: T) => void;
|
|
1274
|
+
cancel: (file: T) => void;
|
|
1275
|
+
retry: (file: T) => void;
|
|
1276
|
+
remove: (file: T) => void;
|
|
1277
|
+
"files-start": (files: T[]) => void;
|
|
1278
|
+
"files-complete": (files: TransferFile<any, any>[]) => void;
|
|
1279
|
+
"chunk-success": (data: ChunkSuccessData<T>) => void;
|
|
1280
|
+
"chunk-error": (data: ChunkErrorData<T>) => void;
|
|
1281
|
+
merging: (data: {
|
|
1282
|
+
file: T;
|
|
1283
|
+
completedChunks: number;
|
|
1284
|
+
totalChunks: number;
|
|
1285
|
+
}) => void;
|
|
1286
|
+
"merge-success": (data: {
|
|
1287
|
+
file: T;
|
|
1288
|
+
response?: any;
|
|
1289
|
+
}) => void;
|
|
1290
|
+
"merge-error": (data: {
|
|
1291
|
+
file: T;
|
|
1292
|
+
error: string;
|
|
1293
|
+
}) => void;
|
|
1294
|
+
}
|
|
1295
|
+
interface UploaderEvents extends BaseTransferEvents<UploadFile> {
|
|
1296
|
+
select: (file: File) => boolean;
|
|
1297
|
+
update: (uploaderFile: UploadFile[]) => void;
|
|
1298
|
+
error: ErrorCallBack;
|
|
1299
|
+
"chunk-upload-start": (data: ChunkStartData<UploadFile>) => void;
|
|
1300
|
+
"instant-upload": (data: {
|
|
1301
|
+
file: UploadFile;
|
|
1302
|
+
reason: string;
|
|
1303
|
+
}) => void;
|
|
1304
|
+
}
|
|
1305
|
+
type EventName = keyof UploaderEvents;
|
|
1306
|
+
type EventCallback<T extends EventName> = UploaderEvents[T];
|
|
1307
|
+
/**
|
|
1308
|
+
* 上传速率统计信息接口
|
|
1309
|
+
* 用于描述单个文件或全局的上传速度指标
|
|
1310
|
+
*/
|
|
1311
|
+
interface speedInfo {
|
|
1312
|
+
/** 当前瞬时速度 (bytes/s) - 基于最近两个采样点计算 */
|
|
1313
|
+
currentSpeed: number;
|
|
1314
|
+
/** 平均速度 (bytes/s) - 基于总耗时和总字节数计算 */
|
|
1315
|
+
averageSpeed: number;
|
|
1316
|
+
/** 格式化后的当前速度,如 "15.23 MB/s"、"256 KB/s" */
|
|
1317
|
+
currentSpeedFormatted: string;
|
|
1318
|
+
/** 格式化后的平均速度,如 "12.45 MB/s"、"128 KB/s" */
|
|
1319
|
+
averageSpeedFormatted: string;
|
|
1320
|
+
/** 预计剩余时间(秒),-1 表示无法计算,0 表示即将完成 */
|
|
1321
|
+
estimatedTimeRemaining: number;
|
|
1322
|
+
/** 格式化后的预计剩余时间,如 "3m 20s"、"12s"、"即将完成" */
|
|
1323
|
+
estimatedTimeFormatted: string;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* 上传时间统计信息接口
|
|
1327
|
+
* 记录文件传输的生命周期时间节点
|
|
1328
|
+
*/
|
|
1329
|
+
interface TimeInfo {
|
|
1330
|
+
/** 开始时间戳 (毫秒) */
|
|
1331
|
+
startTime: number;
|
|
1332
|
+
/** 结束时间戳 (毫秒) */
|
|
1333
|
+
endTime: number;
|
|
1334
|
+
/** 总耗时 (毫秒) */
|
|
1335
|
+
duration: number;
|
|
1336
|
+
/** 格式化后的耗时,如 "5.23s"、"1m 30s"、"2h 15m" */
|
|
1337
|
+
durationFormatted: string;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
declare class DownloadChunkManager extends ChunkManager {
|
|
1341
|
+
private downloadFile;
|
|
1342
|
+
/** 存储每个分片的 Blob 数据(流式写入不可用时的内存兜底) */
|
|
1343
|
+
private chunkBlobs;
|
|
1344
|
+
/** 流式写入的 FileHandle(File System Access API) */
|
|
1345
|
+
private streamFileHandle;
|
|
1346
|
+
/** 流式写入的可写流 */
|
|
1347
|
+
private writable;
|
|
1348
|
+
/** 待完成写入的 Promise 列表(fire-and-forget,不阻塞 PQueue worker) */
|
|
1349
|
+
private pendingWrites;
|
|
1350
|
+
/** 🔑 已确认落盘的分片索引(仅流式模式使用,内存模式不写此 Set) */
|
|
1351
|
+
private _diskWrittenChunks;
|
|
1352
|
+
/** 🔑 doAfterStartReset() 保存 IndexedDB 时的安全快照。
|
|
1353
|
+
* ensureWritableClosed() 的 pending writes 会追加新分片到 _diskWrittenChunks,
|
|
1354
|
+
* 若 close() 失败 → abort() → 磁盘数据丢失,需要回滚 IndexedDB 到这个安全状态,
|
|
1355
|
+
* 而不是全量删除。已 settle 的 writes 在磁盘上是安全的。 */
|
|
1356
|
+
private _safeDiskChunks;
|
|
1357
|
+
/** 上次写入 IndexedDB 进度的时间戳(节流用) */
|
|
1358
|
+
private _lastProgressSaveTime;
|
|
1359
|
+
constructor(chunkOptions: ChunkOptions, file: TransferFile<any, any>);
|
|
1360
|
+
protected getTag(): string;
|
|
1361
|
+
/**
|
|
1362
|
+
* 计算文件唯一标识
|
|
1363
|
+
*
|
|
1364
|
+
* - 若 this.fileHash 已设置(服务端返回的真实 MD5),直接返回
|
|
1365
|
+
* - 否则兜底用 fileName-fileSize,仅作为 onInitChunk → check-file 的临时传参
|
|
1366
|
+
*/
|
|
1367
|
+
protected computeFileIdentifier(): Promise<string>;
|
|
1368
|
+
/**
|
|
1369
|
+
* 初始化下载任务(含断点续传 / 秒下检查)
|
|
1370
|
+
*
|
|
1371
|
+
* 1. 调用 onInitChunkCallback 检查已下载分片(断点续传 / 秒下)
|
|
1372
|
+
* 2. 如果用户提供了 fileHandle 直接使用;否则尝试调用 showSaveFilePicker
|
|
1373
|
+
* 让用户选择保存位置,实现流式写入磁盘(内存峰值 = 并发数 × 分片大小)。
|
|
1374
|
+
* 3. 若 API 不可用或用户取消,回退到内存累积模式。
|
|
1375
|
+
*/
|
|
1376
|
+
protected doInit(): Promise<any>;
|
|
1377
|
+
/**
|
|
1378
|
+
* 标记为秒下(instant download),设置所有状态为已完成
|
|
1379
|
+
*/
|
|
1380
|
+
private markInstantDownload;
|
|
1381
|
+
/**
|
|
1382
|
+
* 下载单个分片(Range 请求)
|
|
1383
|
+
*/
|
|
1384
|
+
protected doChunkTransfer(chunkIndex: number, signal?: AbortSignal): Promise<{
|
|
1385
|
+
data: Blob;
|
|
1386
|
+
chunkSize: number;
|
|
1387
|
+
}>;
|
|
1388
|
+
/**
|
|
1389
|
+
* 合并所有已下载分片
|
|
1390
|
+
*
|
|
1391
|
+
* - 流式模式:等待所有 fire-and-forget 写入完成后关闭 writable
|
|
1392
|
+
* - 内存模式:将 chunkBlobs Map 中的分片按顺序拼接为单一 Blob
|
|
1393
|
+
*/
|
|
1394
|
+
protected doMergeChunks(): Promise<Blob | FileSystemFileHandle>;
|
|
1395
|
+
/**
|
|
1396
|
+
* 保存每个分片的 Blob 数据 + 自动缓存下载进度到 IndexedDB
|
|
1397
|
+
*
|
|
1398
|
+
* - 流式模式:fire-and-forget 写入(不 await,PQueue worker 立即继续下载下一个分片)
|
|
1399
|
+
* - 内存模式:存储到 chunkBlobs Map 中,待合并时一次性拼接
|
|
1400
|
+
*
|
|
1401
|
+
* 🔑 IndexedDB 安全性:仅当磁盘写入确认完成后才保存进度。避免 cancel 时
|
|
1402
|
+
* write 尚未 flush → close() 未保住数据 → abort() 丢弃 → IndexedDB 却标记"已完成"
|
|
1403
|
+
* → 重试时跳过这些分片 → 文件出现空洞损坏。
|
|
1404
|
+
*/
|
|
1405
|
+
protected doSaveChunkResult(chunkIndex: number, data: any): Promise<void>;
|
|
1406
|
+
/**
|
|
1407
|
+
* 将下载进度存入 IndexedDB(节流,最多每 2 秒写一次)
|
|
1408
|
+
*
|
|
1409
|
+
* 🔑 流式模式:仅保存 _diskWrittenChunks 中已确认落盘的分片,防止
|
|
1410
|
+
* cancel→abort 丢弃数据后 IndexedDB 仍标记"已完成"→重试跳过→文件损坏。
|
|
1411
|
+
* 内存模式:chunkBlobs Map 存于内存,cancel 不丢数据,直接用 this.chunks[]。
|
|
1412
|
+
*/
|
|
1413
|
+
private saveProgressToCacheSoft;
|
|
1414
|
+
/**
|
|
1415
|
+
* 合并完成后保存文件
|
|
1416
|
+
*
|
|
1417
|
+
* - 流式模式:文件已在写入流关闭时落盘,无需额外操作
|
|
1418
|
+
* - 内存模式:如果有 fileHandle 则流式写入,否则触发浏览器下载对话框
|
|
1419
|
+
*/
|
|
1420
|
+
protected doBeforeOnSuccess(mergeResult: any): Promise<void>;
|
|
1421
|
+
/**
|
|
1422
|
+
* start() 重置后清理之前的 Blob 缓存和流式写入状态
|
|
1423
|
+
*
|
|
1424
|
+
* 🔑 在此处将 _diskWrittenChunks 保存到 IndexedDB,但 NOT clear()。
|
|
1425
|
+
* clear() 推迟到 ensureWritableClosed() 的最终保存完成后执行:
|
|
1426
|
+
* - doAfterStartReset() 保存时,_diskWrittenChunks 含已 settle 的分片
|
|
1427
|
+
* - ensureWritableClosed() 等待 pending writes → 新 settle 的分片被
|
|
1428
|
+
* .then() 加入 _diskWrittenChunks → 最终保存含完整集合 → 覆盖写入 ✅
|
|
1429
|
+
* - 如果 doAfterStartReset() 就 clear(),最终保存只剩几个零碎分片 → 覆盖掉
|
|
1430
|
+
* 刚刚保存的完整记录 → 进度倒退 ❌
|
|
1431
|
+
*/
|
|
1432
|
+
protected doAfterStartReset(): Promise<void>;
|
|
1433
|
+
/**
|
|
1434
|
+
* 异步清理:关闭上次的 writable,释放文件锁。
|
|
1435
|
+
*
|
|
1436
|
+
* ⚠️ 必须 await 调用,确保 writable 完全关闭、文件锁释放后,
|
|
1437
|
+
* doInit() 才能安全调用 createWritable(),否则会因锁冲突抛 InvalidStateError。
|
|
1438
|
+
*
|
|
1439
|
+
* 🔑 竞态修复:先置 null this.writable,再异步 close。
|
|
1440
|
+
* 否则旧 PQueue 残留的 doSaveChunkResult 回调会在 close() 进行中
|
|
1441
|
+
* 检查 if (this.writable) → 仍为非 null → writable.write() 写入正在关闭的流
|
|
1442
|
+
* → 浏览器抛出 "Cannot write to a closing writable stream"。
|
|
1443
|
+
*
|
|
1444
|
+
* 🔑 数据安全:先等待 pendingWrites 全部 settle → write.then() 中会更新
|
|
1445
|
+
* _diskWrittenChunks → 再 close() flush 缓冲区 → 磁盘数据完整。
|
|
1446
|
+
* 若 close() 失败退化为 abort(),磁盘数据丢失,必须同步清理 IndexedDB。
|
|
1447
|
+
*/
|
|
1448
|
+
ensureWritableClosed(): Promise<void>;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* 文件下载实例类
|
|
1453
|
+
* 负责单个文件的下载逻辑、状态管理、速率计算和生命周期控制
|
|
1454
|
+
*
|
|
1455
|
+
* @template T - 下载成功后的响应数据类型
|
|
1456
|
+
*/
|
|
1457
|
+
declare class DownloadFile<T = any> extends TransferFile<DownloadFile, T> {
|
|
1458
|
+
/**
|
|
1459
|
+
* 分片管理器实例
|
|
1460
|
+
* 每个文件拥有独立的 downloadChunkManager,实现并发控制、断点续传和失败隔离
|
|
1461
|
+
*/
|
|
1462
|
+
downloadChunkManager: DownloadChunkManager | null;
|
|
1463
|
+
dl: Downloader<T>;
|
|
1464
|
+
/**
|
|
1465
|
+
* 🔑 文件创建时保存 action 快照,防止切换模式后重试时用到错误的 action
|
|
1466
|
+
*/
|
|
1467
|
+
private _action;
|
|
1468
|
+
/**
|
|
1469
|
+
* 流式保存的 FileHandle(File System Access API)
|
|
1470
|
+
* 如果设置了 fileHandle,下载完成后通过流式写入文件,不触发浏览器下载对话框
|
|
1471
|
+
*/
|
|
1472
|
+
fileHandle: FileSystemFileHandle | null | undefined;
|
|
1473
|
+
/**
|
|
1474
|
+
* 分片下载专用 headers 队列(如 Range)
|
|
1475
|
+
* 并发分片各自 push,拦截器 shift 取走,避免并发覆盖导致 Range 头错乱
|
|
1476
|
+
* 函数 action 下,用户不需要手动处理分片 headers
|
|
1477
|
+
*/
|
|
1478
|
+
_chunkHeadersQueue: Record<string, string>[];
|
|
1479
|
+
static interceptor: XHRInterceptor<DownloadFile>;
|
|
1480
|
+
/**
|
|
1481
|
+
* 全局共享的 WeakMap
|
|
1482
|
+
* 用于存储 XHR 实例与文件上下文的映射关系,支持拦截器透明注入
|
|
1483
|
+
*/
|
|
1484
|
+
private static xhrToFileMap;
|
|
1485
|
+
/**
|
|
1486
|
+
* 全局共享的当前下载文件队列
|
|
1487
|
+
* 用于在拦截器中查找匹配的 XHR
|
|
1488
|
+
*/
|
|
1489
|
+
private static currentQueue;
|
|
1490
|
+
/**
|
|
1491
|
+
* 跟踪已分配到 XHR 的文件集合
|
|
1492
|
+
* 用于按顺序分配 XHR 实例,避免冲突
|
|
1493
|
+
*/
|
|
1494
|
+
private static assignedFiles;
|
|
1495
|
+
constructor(file: IDownloadFile, transfer: Transfer);
|
|
1496
|
+
/**
|
|
1497
|
+
* 执行普通下载的重试(模板方法覆写)
|
|
1498
|
+
*/
|
|
1499
|
+
protected doRetryTransfer(): Promise<T>;
|
|
1500
|
+
/**
|
|
1501
|
+
* 从回显数据初始化 downloadChunkManager 状态
|
|
1502
|
+
*/
|
|
1503
|
+
private initChunkManagerFromRestore;
|
|
1504
|
+
/**
|
|
1505
|
+
* 执行下载
|
|
1506
|
+
*/
|
|
1507
|
+
download(): Promise<T>;
|
|
1508
|
+
/**
|
|
1509
|
+
* 更新当前文件的下载进度和已传输字节数(模板方法)
|
|
1510
|
+
*
|
|
1511
|
+
* - 普通下载:从 event.loaded/event.total 直接计算百分比和字节数
|
|
1512
|
+
* - 分片下载:由 downloadChunkManager 独立维护进度,此处无需额外操作
|
|
1513
|
+
*
|
|
1514
|
+
* 当响应没有 Content-Length 头(event.total === 0)时:
|
|
1515
|
+
* - __transferBytes__ 使用 event.loaded 记录实际已下载字节数
|
|
1516
|
+
* - 百分比无法精确计算,使用基于 loaded 的模拟进度(0~99%),完成时跳到 100%
|
|
1517
|
+
*/
|
|
1518
|
+
protected updateLocalProgress(event: ProgressEvent): void;
|
|
1519
|
+
/**
|
|
1520
|
+
* 返回下载的分片管理器
|
|
1521
|
+
*/
|
|
1522
|
+
protected getChunkManager(): ChunkManager | null;
|
|
1523
|
+
/**
|
|
1524
|
+
* 下载单个分片(Range 请求)
|
|
1525
|
+
*
|
|
1526
|
+
* - 字符串 action:直接走 axios,Range 头在 overrides.headers 中显式传入
|
|
1527
|
+
* - 函数 action:通过 _chunkHeadersQueue 告知拦截器注入 Range 头,
|
|
1528
|
+
* 最终由 cfg.action(this) 调用用户的 axios 函数
|
|
1529
|
+
*
|
|
1530
|
+
* @param start - 分片起始字节偏移
|
|
1531
|
+
* @param end - 分片结束字节偏移(含)
|
|
1532
|
+
* @param signal - AbortSignal,用于超时/取消
|
|
1533
|
+
* @returns 分片的 Blob 数据
|
|
1534
|
+
*/
|
|
1535
|
+
downloadChunk(start: number, end: number, signal?: AbortSignal): Promise<Blob>;
|
|
1536
|
+
/**
|
|
1537
|
+
* 统一 HTTP 请求方法(download() 和 downloadChunk() 共用)
|
|
1538
|
+
*
|
|
1539
|
+
* action 不区分普通/分片,只按类型分发:
|
|
1540
|
+
* - 字符串 action → 始终走 axios(分片时 headers 里带 Range,普通时不带)
|
|
1541
|
+
* - 函数 action → 始终调 cfg.action(this)(分片时 _chunkHeadersQueue 已 push,拦截器自动注入 Range)
|
|
1542
|
+
*/
|
|
1543
|
+
private _doHttpRequest;
|
|
1544
|
+
/**
|
|
1545
|
+
* 获取文件大小(下载场景:无 File 对象,fallback 到 size 属性)
|
|
1546
|
+
*/
|
|
1547
|
+
getFileSize(): number;
|
|
1548
|
+
/**
|
|
1549
|
+
* 通过 FileHandle 流式写入 Blob 数据到磁盘
|
|
1550
|
+
*
|
|
1551
|
+
* 使用 File System Access API,直接写入用户选择的文件位置。
|
|
1552
|
+
* 绕过浏览器内存限制,适合大文件下载。
|
|
1553
|
+
*
|
|
1554
|
+
* @param fileHandle - 文件句柄
|
|
1555
|
+
* @param data - 要写入的数据(Blob 或可转为 Blob)
|
|
1556
|
+
*/
|
|
1557
|
+
/**
|
|
1558
|
+
* 从下载列表中移除当前文件,取消进行中的下载
|
|
1559
|
+
*/
|
|
1560
|
+
remove(): void;
|
|
1561
|
+
static writeToFileHandle(fileHandle: FileSystemFileHandle, data: Blob | any): Promise<void>;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* 统一日志工具模块
|
|
1566
|
+
*
|
|
1567
|
+
* 提供环境感知的日志输出,支持级别控制和格式化
|
|
1568
|
+
*
|
|
1569
|
+
* @example
|
|
1570
|
+
* ```typescript
|
|
1571
|
+
* import { logger } from './utils/logger';
|
|
1572
|
+
*
|
|
1573
|
+
* logger.info('uploadChunkManager', '开始上传分片');
|
|
1574
|
+
* logger.debug('uploadChunkManager', '分片详情', { index: 0, size: 1024 });
|
|
1575
|
+
* logger.warn('uploadChunkManager', '更新状态失败:', error);
|
|
1576
|
+
* logger.error('uploadChunkManager', '上传失败', error);
|
|
1577
|
+
* ```
|
|
1578
|
+
*/
|
|
1579
|
+
/**
|
|
1580
|
+
* 日志级别枚举
|
|
1581
|
+
*/
|
|
1582
|
+
declare enum LogLevel {
|
|
1583
|
+
DEBUG = 0,// 调试信息(仅开发环境)
|
|
1584
|
+
INFO = 1,// 一般信息
|
|
1585
|
+
WARN = 2,// 警告信息
|
|
1586
|
+
ERROR = 3
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* 日志配置选项
|
|
1590
|
+
*/
|
|
1591
|
+
interface LoggerOptions {
|
|
1592
|
+
/** 是否启用日志输出(默认 true) */
|
|
1593
|
+
enabled?: boolean;
|
|
1594
|
+
/** 最低日志级别 */
|
|
1595
|
+
level?: LogLevel;
|
|
1596
|
+
/** 是否显示时间戳 */
|
|
1597
|
+
showTimestamp?: boolean;
|
|
1598
|
+
/** 是否启用颜色输出 */
|
|
1599
|
+
enableColors?: boolean;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* 日志条目接口
|
|
1603
|
+
*/
|
|
1604
|
+
interface LogEntry {
|
|
1605
|
+
/** 时间戳 */
|
|
1606
|
+
timestamp: number;
|
|
1607
|
+
/** 日志级别 */
|
|
1608
|
+
level: LogLevel;
|
|
1609
|
+
/** 模块名称 */
|
|
1610
|
+
module: string;
|
|
1611
|
+
/** 消息内容 */
|
|
1612
|
+
message: string;
|
|
1613
|
+
/** 额外参数 */
|
|
1614
|
+
args?: any[];
|
|
1615
|
+
/** 错误堆栈(如果是 Error 对象) */
|
|
1616
|
+
stack?: string;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* 日志收集器回调函数类型
|
|
1620
|
+
*/
|
|
1621
|
+
type LogCollectorCallback = (entry: LogEntry) => void | Promise<void>;
|
|
1622
|
+
/**
|
|
1623
|
+
* 初始化日志配置
|
|
1624
|
+
* @param options 日志配置选项
|
|
1625
|
+
*/
|
|
1626
|
+
declare function initLogger(options?: LoggerOptions): void;
|
|
1627
|
+
/**
|
|
1628
|
+
* 动态设置日志级别
|
|
1629
|
+
* @param level 日志级别
|
|
1630
|
+
*/
|
|
1631
|
+
declare function setLogLevel(level: LogLevel): void;
|
|
1632
|
+
/**
|
|
1633
|
+
* 注册日志收集器
|
|
1634
|
+
* @param callback 收集器回调函数
|
|
1635
|
+
* @returns 取消注册的函数
|
|
1636
|
+
*
|
|
1637
|
+
* @example
|
|
1638
|
+
* ```typescript
|
|
1639
|
+
* // 注册收集器
|
|
1640
|
+
* const unsubscribe = addLogCollector((entry) => {
|
|
1641
|
+
* if (entry.level === LogLevel.ERROR) {
|
|
1642
|
+
* // 上报到监控平台
|
|
1643
|
+
* reportToSentry(entry);
|
|
1644
|
+
* }
|
|
1645
|
+
* });
|
|
1646
|
+
*
|
|
1647
|
+
* // 取消注册
|
|
1648
|
+
* unsubscribe();
|
|
1649
|
+
* ```
|
|
1650
|
+
*/
|
|
1651
|
+
declare function addLogCollector(callback: LogCollectorCallback): () => void;
|
|
1652
|
+
/**
|
|
1653
|
+
* 清除所有日志收集器
|
|
1654
|
+
*/
|
|
1655
|
+
declare function clearLogCollectors(): void;
|
|
1656
|
+
/**
|
|
1657
|
+
* 日志工具对象
|
|
1658
|
+
*/
|
|
1659
|
+
declare const logger: {
|
|
1660
|
+
/**
|
|
1661
|
+
* 调试日志(仅开发环境)
|
|
1662
|
+
* @param module 模块名称
|
|
1663
|
+
* @param message 消息内容
|
|
1664
|
+
* @param args 额外参数
|
|
1665
|
+
*/
|
|
1666
|
+
debug: (module: string, message: string, ...args: any[]) => void;
|
|
1667
|
+
/**
|
|
1668
|
+
* 信息日志
|
|
1669
|
+
* @param module 模块名称
|
|
1670
|
+
* @param message 消息内容
|
|
1671
|
+
* @param args 额外参数
|
|
1672
|
+
*/
|
|
1673
|
+
info: (module: string, message: string, ...args: any[]) => void;
|
|
1674
|
+
/**
|
|
1675
|
+
* 警告日志
|
|
1676
|
+
* @param module 模块名称
|
|
1677
|
+
* @param message 消息内容
|
|
1678
|
+
* @param args 额外参数
|
|
1679
|
+
*/
|
|
1680
|
+
warn: (module: string, message: string, ...args: any[]) => void;
|
|
1681
|
+
/**
|
|
1682
|
+
* 错误日志
|
|
1683
|
+
* @param module 模块名称
|
|
1684
|
+
* @param message 消息内容
|
|
1685
|
+
* @param args 额外参数
|
|
1686
|
+
*/
|
|
1687
|
+
error: (module: string, message: string, ...args: any[]) => void;
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* 保存 File 对象到 IndexedDB
|
|
1692
|
+
*
|
|
1693
|
+
* @param fileHash - 文件哈希值(作为主键)
|
|
1694
|
+
* @param file - File 对象
|
|
1695
|
+
* @returns Promise<void>
|
|
1696
|
+
*/
|
|
1697
|
+
declare function saveFileToCache(fileHash: string, file: File): Promise<void>;
|
|
1698
|
+
/**
|
|
1699
|
+
* 从 IndexedDB 恢复 File 对象
|
|
1700
|
+
*
|
|
1701
|
+
* @param fileHash - 文件哈希值
|
|
1702
|
+
* @returns Promise<File | null> - 如果找到则返回 File 对象,否则返回 null
|
|
1703
|
+
*/
|
|
1704
|
+
declare function restoreFileFromCache(fileHash: string): Promise<File | null>;
|
|
1705
|
+
/**
|
|
1706
|
+
* 删除指定文件的缓存
|
|
1707
|
+
*
|
|
1708
|
+
* @param fileHash - 文件哈希值
|
|
1709
|
+
* @returns Promise<void>
|
|
1710
|
+
*/
|
|
1711
|
+
declare function removeFileFromCache(fileHash: string): Promise<void>;
|
|
1712
|
+
/**
|
|
1713
|
+
* 清空所有文件缓存
|
|
1714
|
+
*
|
|
1715
|
+
* @returns Promise<void>
|
|
1716
|
+
*/
|
|
1717
|
+
declare function clearAllFileCache(): Promise<void>;
|
|
1718
|
+
/**
|
|
1719
|
+
* 获取缓存统计信息
|
|
1720
|
+
*
|
|
1721
|
+
* @returns Promise<{ count: number; totalSize: number }>
|
|
1722
|
+
*/
|
|
1723
|
+
declare function getCacheStats(): Promise<{
|
|
1724
|
+
count: number;
|
|
1725
|
+
totalSize: number;
|
|
1726
|
+
}>;
|
|
1727
|
+
/**
|
|
1728
|
+
* 清理过期缓存(超过指定天数的缓存)
|
|
1729
|
+
*
|
|
1730
|
+
* @param days - 保留天数,默认 7 天
|
|
1731
|
+
* @returns Promise<number> - 删除的记录数
|
|
1732
|
+
*/
|
|
1733
|
+
declare function cleanExpiredCache(days?: number): Promise<number>;
|
|
1734
|
+
/**
|
|
1735
|
+
* 将 FileSystemFileHandle 持久化到 IndexedDB(跨页面刷新复用)
|
|
1736
|
+
*
|
|
1737
|
+
* 关键是避免重复调用 showSaveFilePicker() 导致 Chrome 截断已有文件为 0 字节。
|
|
1738
|
+
*/
|
|
1739
|
+
declare function saveFileHandle(fileName: string, fileHandle: FileSystemFileHandle): Promise<void>;
|
|
1740
|
+
/**
|
|
1741
|
+
* 从 IndexedDB 加载持久化的 FileSystemFileHandle
|
|
1742
|
+
* @returns FileSystemFileHandle(未找到或已失效则返回 null)
|
|
1743
|
+
*/
|
|
1744
|
+
declare function loadFileHandle(fileName: string): Promise<FileSystemFileHandle | null>;
|
|
1745
|
+
/**
|
|
1746
|
+
* 删除指定文件的句柄记录
|
|
1747
|
+
*/
|
|
1748
|
+
declare function removeFileHandle(fileName: string): Promise<void>;
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* 上传性能监控模块
|
|
1752
|
+
*
|
|
1753
|
+
* 提供上传成功率、耗时统计、错误分析等监控功能
|
|
1754
|
+
*
|
|
1755
|
+
* @example
|
|
1756
|
+
* ```typescript
|
|
1757
|
+
* import { uploadMonitor } from '@core/utils';
|
|
1758
|
+
*
|
|
1759
|
+
* // 获取实时统计数据
|
|
1760
|
+
* console.log(uploadMonitor.getStats());
|
|
1761
|
+
*
|
|
1762
|
+
* // 重置统计数据
|
|
1763
|
+
* uploadMonitor.reset();
|
|
1764
|
+
* ```
|
|
1765
|
+
*/
|
|
1766
|
+
/**
|
|
1767
|
+
* 单个文件的上传记录
|
|
1768
|
+
*/
|
|
1769
|
+
interface UploadRecord {
|
|
1770
|
+
/** 文件唯一标识 */
|
|
1771
|
+
fileId: string;
|
|
1772
|
+
/** 文件名 */
|
|
1773
|
+
fileName: string;
|
|
1774
|
+
/** 文件大小(字节) */
|
|
1775
|
+
fileSize: number;
|
|
1776
|
+
/** 开始时间戳 */
|
|
1777
|
+
startTime: number;
|
|
1778
|
+
/** 结束时间戳 */
|
|
1779
|
+
endTime?: number;
|
|
1780
|
+
/** 是否成功 */
|
|
1781
|
+
success: boolean;
|
|
1782
|
+
/** 失败原因(如果失败) */
|
|
1783
|
+
error?: string;
|
|
1784
|
+
/** 重试次数 */
|
|
1785
|
+
retryCount: number;
|
|
1786
|
+
/** 分片总数(如果是分片上传) */
|
|
1787
|
+
totalChunks?: number;
|
|
1788
|
+
/** 成功分片数 */
|
|
1789
|
+
chunks?: number;
|
|
1790
|
+
/** 失败分片数 */
|
|
1791
|
+
failedChunks?: number;
|
|
1792
|
+
/** ✅ 各模块的错误次数统计 */
|
|
1793
|
+
errorModules?: Map<string, number>;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* 上传统计数据
|
|
1797
|
+
*/
|
|
1798
|
+
interface UploadStats {
|
|
1799
|
+
/** 总上传数 */
|
|
1800
|
+
totalUploads: number;
|
|
1801
|
+
/** 成功上传数 */
|
|
1802
|
+
successfulUploads: number;
|
|
1803
|
+
/** 失败上传数 */
|
|
1804
|
+
failedUploads: number;
|
|
1805
|
+
/** 成功率(0-100) */
|
|
1806
|
+
successRate: number;
|
|
1807
|
+
/** 平均耗时(毫秒) */
|
|
1808
|
+
averageDuration: number;
|
|
1809
|
+
/** 最短耗时(毫秒) */
|
|
1810
|
+
minDuration: number;
|
|
1811
|
+
/** 最长耗时(毫秒) */
|
|
1812
|
+
maxDuration: number;
|
|
1813
|
+
/** 中位数耗时(毫秒) */
|
|
1814
|
+
medianDuration: number;
|
|
1815
|
+
/** 平均文件大小(字节) */
|
|
1816
|
+
averageFileSize: number;
|
|
1817
|
+
/** 总上传数据量(字节) */
|
|
1818
|
+
totalBytesUploaded: number;
|
|
1819
|
+
/** 平均上传速度(字节/秒) */
|
|
1820
|
+
averageSpeed: number;
|
|
1821
|
+
/** 按错误类型统计 */
|
|
1822
|
+
errorsByType: Map<string, number>;
|
|
1823
|
+
/** 按模块统计错误 */
|
|
1824
|
+
errorsByModule: Map<string, number>;
|
|
1825
|
+
/** 平均重试次数 */
|
|
1826
|
+
averageRetries: number;
|
|
1827
|
+
/** 分片上传统计 */
|
|
1828
|
+
chunkUploadStats?: {
|
|
1829
|
+
/** 总分片数 */
|
|
1830
|
+
totalChunks: number;
|
|
1831
|
+
/** 成功分片数 */
|
|
1832
|
+
successfulChunks: number;
|
|
1833
|
+
/** 失败分片数 */
|
|
1834
|
+
failedChunks: number;
|
|
1835
|
+
/** 分片成功率 */
|
|
1836
|
+
chunkSuccessRate: number;
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* 上传性能监控类
|
|
1841
|
+
*/
|
|
1842
|
+
declare class UploadMonitor {
|
|
1843
|
+
/** 上传记录列表 */
|
|
1844
|
+
private records;
|
|
1845
|
+
/** 当前活跃的上传任务 */
|
|
1846
|
+
private activeUploads;
|
|
1847
|
+
/** 最大保留记录数 */
|
|
1848
|
+
private readonly MAX_RECORDS;
|
|
1849
|
+
constructor();
|
|
1850
|
+
/**
|
|
1851
|
+
* 初始化日志收集器
|
|
1852
|
+
*/
|
|
1853
|
+
private init;
|
|
1854
|
+
/**
|
|
1855
|
+
* 处理日志条目
|
|
1856
|
+
*/
|
|
1857
|
+
private processLog;
|
|
1858
|
+
/**
|
|
1859
|
+
* 完成记录并添加到历史
|
|
1860
|
+
*/
|
|
1861
|
+
private finalizeRecord;
|
|
1862
|
+
/**
|
|
1863
|
+
* 从日志条目中提取 fileId
|
|
1864
|
+
*/
|
|
1865
|
+
private extractFileId;
|
|
1866
|
+
/**
|
|
1867
|
+
* 从日志条目中提取文件名
|
|
1868
|
+
*/
|
|
1869
|
+
private extractFileName;
|
|
1870
|
+
/**
|
|
1871
|
+
* 从日志条目中提取文件大小
|
|
1872
|
+
*/
|
|
1873
|
+
private extractFileSize;
|
|
1874
|
+
/**
|
|
1875
|
+
* 从日志条目中提取分片总数
|
|
1876
|
+
*/
|
|
1877
|
+
private extractTotalChunks;
|
|
1878
|
+
/**
|
|
1879
|
+
* 获取上传统计数据
|
|
1880
|
+
*/
|
|
1881
|
+
getStats(): UploadStats;
|
|
1882
|
+
/**
|
|
1883
|
+
* 分类错误类型
|
|
1884
|
+
*/
|
|
1885
|
+
private classifyError;
|
|
1886
|
+
/**
|
|
1887
|
+
* 获取最近 N 条上传记录
|
|
1888
|
+
*/
|
|
1889
|
+
getRecentRecords(count?: number): UploadRecord[];
|
|
1890
|
+
/**
|
|
1891
|
+
* 获取活跃上传任务
|
|
1892
|
+
*/
|
|
1893
|
+
getActiveUploads(): Map<string, UploadRecord>;
|
|
1894
|
+
/**
|
|
1895
|
+
* 重置所有统计数据
|
|
1896
|
+
*/
|
|
1897
|
+
reset(): void;
|
|
1898
|
+
/**
|
|
1899
|
+
* 导出统计数据为 JSON
|
|
1900
|
+
*/
|
|
1901
|
+
exportToJSON(): string;
|
|
1902
|
+
/**
|
|
1903
|
+
* 打印统计报告到控制台
|
|
1904
|
+
*/
|
|
1905
|
+
printReport(): void;
|
|
1906
|
+
}
|
|
1907
|
+
declare const uploadMonitor: UploadMonitor;
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* 网络状态检查工具
|
|
1911
|
+
*
|
|
1912
|
+
* 提供上传前的网络状态检查功能
|
|
1913
|
+
*/
|
|
1914
|
+
/**
|
|
1915
|
+
* 网络检查结果
|
|
1916
|
+
*/
|
|
1917
|
+
interface NetworkCheckResult {
|
|
1918
|
+
/** 是否在线 */
|
|
1919
|
+
online: boolean;
|
|
1920
|
+
/** 检查时间戳 */
|
|
1921
|
+
timestamp: number;
|
|
1922
|
+
/** 错误信息(如果离线) */
|
|
1923
|
+
error?: string;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* 检查网络连接状态
|
|
1927
|
+
* @returns 网络检查结果
|
|
1928
|
+
*/
|
|
1929
|
+
declare function checkNetworkStatus(): NetworkCheckResult;
|
|
1930
|
+
/**
|
|
1931
|
+
* 异步检查网络连通性(通过实际请求测试)
|
|
1932
|
+
* @param testUrl 测试用的 URL(可选,默认为当前域名)
|
|
1933
|
+
* @param timeout 超时时间(毫秒),默认 5000ms
|
|
1934
|
+
* @returns Promise<NetworkCheckResult>
|
|
1935
|
+
*/
|
|
1936
|
+
declare function checkNetworkConnectivity(testUrl?: string, timeout?: number): Promise<NetworkCheckResult>;
|
|
1937
|
+
/**
|
|
1938
|
+
* 监听网络状态变化
|
|
1939
|
+
* @param callback 网络状态变化回调
|
|
1940
|
+
* @returns 取消监听的函数
|
|
1941
|
+
*/
|
|
1942
|
+
declare function watchNetworkStatus(callback: (online: boolean) => void): () => void;
|
|
1943
|
+
|
|
1944
|
+
/**
|
|
1945
|
+
* Web Worker 内联 MD5 计算 — 将 CPU 密集型哈希计算移出主线程,避免 UI 卡顿。
|
|
1946
|
+
*
|
|
1947
|
+
* 策略:
|
|
1948
|
+
* - 文件 ≤ 50MB:通过 Blob URL Worker 异步计算,主线程零阻塞
|
|
1949
|
+
* - 文件 > 50MB 或 Worker 不可用:回退到主线程 SparkMD5 流式计算
|
|
1950
|
+
* - 小文件 (<5MB):直接用主线程,省去 Worker 创建开销
|
|
1951
|
+
*/
|
|
1952
|
+
/**
|
|
1953
|
+
* 使用 Web Worker 异步计算文件 MD5。
|
|
1954
|
+
*
|
|
1955
|
+
* 仅在上传端使用(有 File 对象时);下载端正用此函数的地方
|
|
1956
|
+
* 传入的是 FileSystemFileHandle.getFile() 返回的 File,同样可用。
|
|
1957
|
+
*
|
|
1958
|
+
* @returns MD5 hex 字符串
|
|
1959
|
+
*/
|
|
1960
|
+
declare function calculateFileMD5InWorker(file: File, signal?: AbortSignal): Promise<string>;
|
|
1961
|
+
/**
|
|
1962
|
+
* 全局清理:释放缓存的 Blob URL(SPA 卸载时调用)
|
|
1963
|
+
*/
|
|
1964
|
+
declare function disposeMD5Worker(): void;
|
|
1965
|
+
|
|
1966
|
+
declare function generateFileId(): string;
|
|
1967
|
+
declare function mergeObjects<T extends Record<string, any>>(target: T, source?: Partial<T>): T;
|
|
1968
|
+
declare function handleFile(file: File): Promise<string>;
|
|
1969
|
+
declare function computeTransferTime(transferTime: TimeInfo): {
|
|
1970
|
+
start(): void;
|
|
1971
|
+
end(): void;
|
|
1972
|
+
};
|
|
1973
|
+
declare function getFileExtension(filename?: string): string;
|
|
1974
|
+
/**
|
|
1975
|
+
* 判断文件是否处于活跃上传状态(统一判断逻辑)
|
|
1976
|
+
* @param file 文件实例
|
|
1977
|
+
* @returns 是否为活跃上传状态
|
|
1978
|
+
* @private
|
|
1979
|
+
*/
|
|
1980
|
+
declare function isFileActive(file: TransferFile<any>): boolean;
|
|
1981
|
+
/**
|
|
1982
|
+
* 将文件大小转换为易读的格式
|
|
1983
|
+
* @param {number} bytes - 文件大小的字节数
|
|
1984
|
+
* @param {number} [decimals=2] - 保留的小数位数,默认为2
|
|
1985
|
+
* @returns {string} 转换后的文件大小字符串
|
|
1986
|
+
*/
|
|
1987
|
+
declare function formatFileSize(bytes: any, decimals?: number): string;
|
|
1988
|
+
declare const validator: {
|
|
1989
|
+
limit(limit: number, length: number): boolean;
|
|
1990
|
+
size(fileSize: number, maxSize: number): boolean;
|
|
1991
|
+
type(accept: string[], file: TransferFile<any>): boolean;
|
|
1992
|
+
};
|
|
1993
|
+
/**
|
|
1994
|
+
* 创建响应式的 UploadFile 实例
|
|
1995
|
+
* @param uploadFile 原始的 UploadFile 实例
|
|
1996
|
+
* @param uploader Uploader 实例
|
|
1997
|
+
* @returns Proxy 包装后的 UploadFile
|
|
1998
|
+
*/
|
|
1999
|
+
declare function createReactiveUploadFile(file: UploadFile, uploader: Transfer): UploadFile;
|
|
2000
|
+
/**
|
|
2001
|
+
* 创建响应式的 DownloadFile 实例
|
|
2002
|
+
* @param file 原始的 DownloadFile 实例
|
|
2003
|
+
* @param downloader Downloader 实例
|
|
2004
|
+
* @returns Proxy 包装后的 DownloadFile
|
|
2005
|
+
*/
|
|
2006
|
+
declare function createReactiveDownloadFile(file: DownloadFile, downloader: Transfer): DownloadFile;
|
|
2007
|
+
/**
|
|
2008
|
+
* 提取函数中的路径
|
|
2009
|
+
* @param str 函数字符串
|
|
2010
|
+
* @returns 提取到的路径,如果没有找到返回 null
|
|
2011
|
+
*/
|
|
2012
|
+
declare function extractPathFromFunction(str: string): string | null;
|
|
2013
|
+
/** 判断一个值是否为纯对象(非 null、非数组、typeof === "object") */
|
|
2014
|
+
declare function isPlainObject(value: any): value is Record<string, any>;
|
|
2015
|
+
/**
|
|
2016
|
+
* 格式化上传/下载速度显示
|
|
2017
|
+
* @param bytesPerSecond 每秒字节数
|
|
2018
|
+
* @returns 格式化后的速度字符串
|
|
2019
|
+
*/
|
|
2020
|
+
declare function formatSpeed(bytesPerSecond: number): string;
|
|
2021
|
+
/**
|
|
2022
|
+
* 格式化时间 duration 显示
|
|
2023
|
+
* @param milliseconds 毫秒数
|
|
2024
|
+
* @returns 格式化后的时间字符串
|
|
2025
|
+
*/
|
|
2026
|
+
declare function formatDuration(milliseconds: number): string;
|
|
2027
|
+
/**
|
|
2028
|
+
* 睡眠工具函数
|
|
2029
|
+
* @param ms 毫秒数
|
|
2030
|
+
* @returns Promise
|
|
2031
|
+
*/
|
|
2032
|
+
declare function sleep(ms: number): Promise<void>;
|
|
2033
|
+
/**
|
|
2034
|
+
* 计算文件的 MD5 哈希值
|
|
2035
|
+
*
|
|
2036
|
+
* 策略(自动选择最优路径):
|
|
2037
|
+
* - 文件 > 50MB:Worker 路径(完整读取至 Worker 计算,主线程零阻塞)
|
|
2038
|
+
* - 文件 5~50MB:Worker 路径,无进度回调时优先
|
|
2039
|
+
* - 文件 ≤ 5MB 或有 onProgress 回调:主线程 SparkMD5 流式计算
|
|
2040
|
+
* - Worker 不可用:回退到主线程 SparkMD5
|
|
2041
|
+
*
|
|
2042
|
+
* @param file 文件对象
|
|
2043
|
+
* @param onProgress 可选的进度回调函数,接收当前进度百分比 (0-100)
|
|
2044
|
+
* @returns MD5 哈希字符串
|
|
2045
|
+
*/
|
|
2046
|
+
declare function calculateFileMD5(file: File, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<string>;
|
|
2047
|
+
|
|
2048
|
+
export { type LogCollectorCallback, type LogEntry, LogLevel, type LoggerOptions, type NetworkCheckResult, type UploadRecord, type UploadStats, addLogCollector, calculateFileMD5, calculateFileMD5InWorker, checkNetworkConnectivity, checkNetworkStatus, cleanExpiredCache, clearAllFileCache, clearLogCollectors, computeTransferTime, createReactiveDownloadFile, createReactiveUploadFile, disposeMD5Worker, extractPathFromFunction, formatDuration, formatFileSize, formatSpeed, generateFileId, getCacheStats, getFileExtension, handleFile, initLogger, isFileActive, isPlainObject, loadFileHandle, logger, mergeObjects, removeFileFromCache, removeFileHandle, restoreFileFromCache, saveFileHandle, saveFileToCache, setLogLevel, sleep, uploadMonitor, validator, watchNetworkStatus };
|