@alibarbar/common 1.0.5 → 1.0.7
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/dist/{chunk-N5TZBSKF.js → chunk-FEBKPX5A.js} +67 -90
- package/dist/chunk-FEBKPX5A.js.map +1 -0
- package/dist/{chunk-5RNJSQYR.mjs → chunk-NJARVI6X.mjs} +67 -91
- package/dist/chunk-NJARVI6X.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -4
- package/dist/index.mjs +1 -1
- package/dist/{upload-StSeeRRa.d.mts → upload-DchqyDBQ.d.mts} +38 -13
- package/dist/{upload-StSeeRRa.d.ts → upload-DchqyDBQ.d.ts} +38 -13
- package/dist/upload.d.mts +1 -1
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +7 -3
- package/dist/upload.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-5RNJSQYR.mjs.map +0 -1
- package/dist/chunk-N5TZBSKF.js.map +0 -1
|
@@ -21,7 +21,6 @@ var ChunkUploader = class {
|
|
|
21
21
|
this.uploadedChunks = /* @__PURE__ */ new Set();
|
|
22
22
|
this.status = "pending" /* PENDING */;
|
|
23
23
|
this.abortController = null;
|
|
24
|
-
this.initResponse = null;
|
|
25
24
|
this.file = file;
|
|
26
25
|
this.options = {
|
|
27
26
|
chunkSize: options.chunkSize || 2 * 1024 * 1024,
|
|
@@ -64,7 +63,7 @@ var ChunkUploader = class {
|
|
|
64
63
|
return response.data;
|
|
65
64
|
}
|
|
66
65
|
/**
|
|
67
|
-
* 计算文件MD5
|
|
66
|
+
* 计算文件MD5
|
|
68
67
|
*/
|
|
69
68
|
async calculateFileMD5() {
|
|
70
69
|
const chunks = chunk7V5UQXIO_js.splitFileIntoChunks(this.file, this.options.chunkSize);
|
|
@@ -87,7 +86,7 @@ var ChunkUploader = class {
|
|
|
87
86
|
}));
|
|
88
87
|
}
|
|
89
88
|
/**
|
|
90
|
-
*
|
|
89
|
+
* 获取已上传的分片列表(用于断点续传)
|
|
91
90
|
*/
|
|
92
91
|
async getUploadedChunks() {
|
|
93
92
|
if (!this.taskId) return [];
|
|
@@ -129,7 +128,7 @@ var ChunkUploader = class {
|
|
|
129
128
|
});
|
|
130
129
|
if (response.code === 200 && response.data.success) {
|
|
131
130
|
this.uploadedChunks.add(chunkInfo.index);
|
|
132
|
-
this.updateProgress();
|
|
131
|
+
await this.updateProgress();
|
|
133
132
|
} else {
|
|
134
133
|
throw new Error(response.message || "\u5206\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
135
134
|
}
|
|
@@ -150,8 +149,6 @@ var ChunkUploader = class {
|
|
|
150
149
|
return;
|
|
151
150
|
}
|
|
152
151
|
let currentIndex = 0;
|
|
153
|
-
const semaphore = new Array(this.options.concurrency).fill(null);
|
|
154
|
-
const allUploadPromises = [];
|
|
155
152
|
const uploadNext = async () => {
|
|
156
153
|
while (this.status === "uploading" /* UPLOADING */) {
|
|
157
154
|
const chunkIndex = currentIndex++;
|
|
@@ -159,26 +156,34 @@ var ChunkUploader = class {
|
|
|
159
156
|
break;
|
|
160
157
|
}
|
|
161
158
|
const chunk = chunksToUpload[chunkIndex];
|
|
162
|
-
|
|
163
|
-
allUploadPromises.push(uploadPromise);
|
|
164
|
-
await uploadPromise;
|
|
159
|
+
await this.uploadChunk(chunk);
|
|
165
160
|
}
|
|
166
161
|
};
|
|
167
|
-
const
|
|
168
|
-
await Promise.all(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
162
|
+
const tasks = Array(this.options.concurrency).fill(null).map(() => uploadNext());
|
|
163
|
+
await Promise.all(tasks);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 更新上传进度(仅用于回调前端显示)
|
|
167
|
+
*/
|
|
168
|
+
async updateProgress() {
|
|
169
|
+
if (!this.taskId) return;
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.request(
|
|
172
|
+
`/api/files/common/progress/${this.taskId}`,
|
|
173
|
+
{
|
|
174
|
+
method: "GET",
|
|
175
|
+
headers: this.options.headers
|
|
176
|
+
}
|
|
176
177
|
);
|
|
178
|
+
if (response.code === 200 && response.data) {
|
|
179
|
+
this.options.onProgress(response.data);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
177
183
|
}
|
|
178
184
|
}
|
|
179
185
|
/**
|
|
180
186
|
* 获取上传进度
|
|
181
|
-
* 当进度达到100%时,会自动调用完成接口
|
|
182
187
|
*/
|
|
183
188
|
async getUploadProgress() {
|
|
184
189
|
if (!this.taskId) {
|
|
@@ -193,9 +198,7 @@ var ChunkUploader = class {
|
|
|
193
198
|
}
|
|
194
199
|
);
|
|
195
200
|
if (response.code === 200 && response.data) {
|
|
196
|
-
|
|
197
|
-
await this.completeUpload();
|
|
198
|
-
}
|
|
201
|
+
console.log("[\u83B7\u53D6\u8FDB\u5EA6]", response.data);
|
|
199
202
|
return response.data;
|
|
200
203
|
}
|
|
201
204
|
} catch (error) {
|
|
@@ -205,27 +208,12 @@ var ChunkUploader = class {
|
|
|
205
208
|
}
|
|
206
209
|
/**
|
|
207
210
|
* 完成上传(调用后端合并分片接口)
|
|
208
|
-
*
|
|
209
|
-
* 只有在进度为 100% 时才会被调用
|
|
210
211
|
*/
|
|
211
212
|
async completeUpload() {
|
|
212
213
|
if (!this.taskId) {
|
|
213
214
|
throw new Error("\u4EFB\u52A1ID\u4E0D\u5B58\u5728");
|
|
214
215
|
}
|
|
215
|
-
|
|
216
|
-
if (this.initResponse?.fileUrl) {
|
|
217
|
-
return {
|
|
218
|
-
taskId: this.taskId,
|
|
219
|
-
fileUrl: this.initResponse.fileUrl,
|
|
220
|
-
fileName: this.file.name,
|
|
221
|
-
fileSize: this.file.size,
|
|
222
|
-
fileMd5: "",
|
|
223
|
-
success: true,
|
|
224
|
-
message: "\u4E0A\u4F20\u5DF2\u5B8C\u6210"
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
throw new Error("\u4E0A\u4F20\u5DF2\u5B8C\u6210\u4F46\u7F3A\u5C11\u6587\u4EF6\u4FE1\u606F");
|
|
228
|
-
}
|
|
216
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u8C03\u7528\u5B8C\u6210\u63A5\u53E3:", `/api/files/common/complete/${this.taskId}`);
|
|
229
217
|
const response = await this.request(
|
|
230
218
|
`/api/files/common/complete/${this.taskId}`,
|
|
231
219
|
{
|
|
@@ -233,37 +221,12 @@ var ChunkUploader = class {
|
|
|
233
221
|
headers: this.options.headers
|
|
234
222
|
}
|
|
235
223
|
);
|
|
224
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u63A5\u53E3\u54CD\u5E94:", response);
|
|
236
225
|
if (response.code !== 200) {
|
|
237
226
|
throw new Error(response.message || "\u5B8C\u6210\u4E0A\u4F20\u5931\u8D25");
|
|
238
227
|
}
|
|
239
|
-
this.status = "completed" /* COMPLETED */;
|
|
240
|
-
this.options.onComplete(response.data);
|
|
241
228
|
return response.data;
|
|
242
229
|
}
|
|
243
|
-
/**
|
|
244
|
-
* 更新上传进度(用于回调前端显示)
|
|
245
|
-
* 每次上传完一个分片后调用
|
|
246
|
-
*/
|
|
247
|
-
async updateProgress() {
|
|
248
|
-
if (!this.taskId) return;
|
|
249
|
-
try {
|
|
250
|
-
const response = await this.request(
|
|
251
|
-
`/api/files/common/progress/${this.taskId}`,
|
|
252
|
-
{
|
|
253
|
-
method: "GET",
|
|
254
|
-
headers: this.options.headers
|
|
255
|
-
}
|
|
256
|
-
);
|
|
257
|
-
if (response.code === 200 && response.data) {
|
|
258
|
-
this.options.onProgress(response.data);
|
|
259
|
-
if (response.data.percentage >= 100) {
|
|
260
|
-
await this.completeUpload();
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
230
|
/**
|
|
268
231
|
* 开始上传
|
|
269
232
|
*/
|
|
@@ -271,13 +234,15 @@ var ChunkUploader = class {
|
|
|
271
234
|
try {
|
|
272
235
|
this.status = "uploading" /* UPLOADING */;
|
|
273
236
|
this.abortController = new AbortController();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
237
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 1. \u521D\u59CB\u5316\u4E0A\u4F20");
|
|
238
|
+
const initResponse = await this.initUpload();
|
|
239
|
+
this.taskId = initResponse.taskId;
|
|
240
|
+
if (initResponse.instantUpload && initResponse.fileUrl) {
|
|
241
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u79D2\u4F20\u6210\u529F");
|
|
277
242
|
this.status = "completed" /* COMPLETED */;
|
|
278
243
|
const result = {
|
|
279
244
|
taskId: this.taskId,
|
|
280
|
-
fileUrl:
|
|
245
|
+
fileUrl: initResponse.fileUrl,
|
|
281
246
|
fileName: this.file.name,
|
|
282
247
|
fileSize: this.file.size,
|
|
283
248
|
fileMd5: "",
|
|
@@ -287,41 +252,48 @@ var ChunkUploader = class {
|
|
|
287
252
|
this.options.onComplete(result);
|
|
288
253
|
return result;
|
|
289
254
|
}
|
|
255
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 2. \u51C6\u5907\u5206\u7247");
|
|
290
256
|
this.prepareChunks();
|
|
257
|
+
const totalChunks = this.chunks.length;
|
|
258
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u5171 ${totalChunks} \u4E2A\u5206\u7247`);
|
|
259
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 3. \u68C0\u67E5\u5DF2\u4E0A\u4F20\u5206\u7247");
|
|
291
260
|
const existingChunks = await this.getUploadedChunks();
|
|
292
261
|
existingChunks.forEach((index) => this.uploadedChunks.add(index));
|
|
262
|
+
console.log(
|
|
263
|
+
`[\u4E0A\u4F20\u6D41\u7A0B] \u5DF2\u4E0A\u4F20 ${existingChunks.length} \u4E2A\u5206\u7247\uFF0C\u8FD8\u9700\u4E0A\u4F20 ${totalChunks - existingChunks.length} \u4E2A`
|
|
264
|
+
);
|
|
265
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 4. \u5F00\u59CB\u4E0A\u4F20\u5206\u7247");
|
|
293
266
|
await this.uploadChunksConcurrently();
|
|
294
|
-
|
|
267
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 5. \u6240\u6709\u5206\u7247\u4E0A\u4F20\u5B8C\u6210");
|
|
295
268
|
const uploadedCount = this.uploadedChunks.size;
|
|
296
269
|
if (uploadedCount < totalChunks) {
|
|
297
|
-
throw new Error(
|
|
298
|
-
`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\uFF0C\u65E0\u6CD5\u5B8C\u6210\u4E0A\u4F20`
|
|
299
|
-
);
|
|
270
|
+
throw new Error(`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247`);
|
|
300
271
|
}
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
fileMd5: "",
|
|
311
|
-
success: true,
|
|
312
|
-
message: "\u4E0A\u4F20\u5B8C\u6210"
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
const finalPercentage = progress?.percentage ?? localPercentage;
|
|
272
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 6. \u9A8C\u8BC1\u901A\u8FC7\uFF1A${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\u5DF2\u4E0A\u4F20`);
|
|
273
|
+
const localPercentage = Math.round(uploadedCount / totalChunks * 100);
|
|
274
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 7. \u672C\u5730\u8FDB\u5EA6\uFF1A${localPercentage}%`);
|
|
275
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 8. \u83B7\u53D6\u670D\u52A1\u7AEF\u8FDB\u5EA6");
|
|
276
|
+
const serverProgress = await this.getUploadProgress();
|
|
277
|
+
const serverPercentage = serverProgress?.percentage ?? 0;
|
|
278
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u670D\u52A1\u7AEF\u8FDB\u5EA6\uFF1A${serverPercentage}%`);
|
|
279
|
+
const finalPercentage = serverProgress?.percentage ?? localPercentage;
|
|
280
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 9. \u6700\u7EC8\u8FDB\u5EA6\uFF1A${finalPercentage}%`);
|
|
316
281
|
if (finalPercentage >= 100) {
|
|
282
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 10. \u2705 \u8FDB\u5EA6\u8FBE\u5230100%\uFF0C\u8C03\u7528\u5B8C\u6210\u63A5\u53E3");
|
|
317
283
|
const result = await this.completeUpload();
|
|
284
|
+
this.status = "completed" /* COMPLETED */;
|
|
285
|
+
this.options.onComplete(result);
|
|
286
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u2705 \u4E0A\u4F20\u5B8C\u6210");
|
|
318
287
|
return result;
|
|
288
|
+
} else {
|
|
289
|
+
console.error(`[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u8FDB\u5EA6\u4E0D\u8DB3100%\uFF1A${finalPercentage}%`);
|
|
290
|
+
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
319
291
|
}
|
|
320
|
-
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
321
292
|
} catch (error) {
|
|
322
293
|
this.status = "failed" /* FAILED */;
|
|
323
294
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
324
295
|
this.options.onError(err);
|
|
296
|
+
console.error("[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u4E0A\u4F20\u5931\u8D25:", err);
|
|
325
297
|
throw err;
|
|
326
298
|
}
|
|
327
299
|
}
|
|
@@ -401,9 +373,14 @@ var ChunkUploader = class {
|
|
|
401
373
|
function createUploader(file, options) {
|
|
402
374
|
return new ChunkUploader(file, options);
|
|
403
375
|
}
|
|
376
|
+
async function uploadFile(file, options) {
|
|
377
|
+
const uploader = createUploader(file, options);
|
|
378
|
+
return uploader.upload();
|
|
379
|
+
}
|
|
404
380
|
|
|
405
381
|
exports.ChunkUploader = ChunkUploader;
|
|
406
382
|
exports.UploadStatus = UploadStatus;
|
|
407
383
|
exports.createUploader = createUploader;
|
|
408
|
-
|
|
409
|
-
//# sourceMappingURL=chunk-
|
|
384
|
+
exports.uploadFile = uploadFile;
|
|
385
|
+
//# sourceMappingURL=chunk-FEBKPX5A.js.map
|
|
386
|
+
//# sourceMappingURL=chunk-FEBKPX5A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/upload.ts","../src/browser/upload.ts"],"names":["UploadStatus","splitFileIntoChunks","calculateBlobMD5"],"mappings":"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","file":"chunk-FEBKPX5A.js","sourcesContent":["/**\n * 文件上传相关类型定义\n */\n\n/**\n * 上传初始化请求参数\n */\nexport interface UploadInitRequest {\n fileName: string;\n fileSize: number;\n fileMd5: string;\n chunkSize?: number;\n}\n\n/**\n * 上传初始化响应\n */\nexport interface UploadInitResponse {\n taskId: string;\n totalChunks: number;\n existingChunks: number[];\n instantUpload: boolean;\n fileUrl?: string;\n chunkSize: number;\n}\n\n/**\n * 分片上传响应\n */\nexport interface ChunkUploadResponse {\n chunkIndex: number;\n success: boolean;\n message: string;\n chunkMd5: string;\n}\n\n/**\n * 完成上传响应\n */\nexport interface CompleteUploadResponse {\n taskId: string;\n fileUrl: string;\n fileName: string;\n fileSize: number;\n fileMd5: string;\n success: boolean;\n message: string;\n}\n\n/**\n * 上传进度信息\n */\nexport interface UploadProgress {\n taskId: string;\n fileName: string;\n fileSize: number;\n totalChunks: number;\n uploadedChunks: number;\n percentage: number;\n status: string;\n uploadedSize: number;\n}\n\n/**\n * API响应包装类型\n */\nexport interface ApiResponse<T> {\n code: number;\n message: string;\n data: T;\n timestamp: number;\n}\n\n/**\n * 上传配置选项\n */\nexport interface UploadOptions {\n /** 分片大小(字节),默认 2MB */\n chunkSize?: number;\n /** 并发上传数量,默认 3 */\n concurrency?: number;\n /** 重试次数,默认 3 */\n retryCount?: number;\n /** 重试延迟(毫秒),默认 1000 */\n retryDelay?: number;\n /** 基础API地址 */\n baseURL?: string;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 上传进度回调 */\n onProgress?: (progress: UploadProgress) => void;\n /** 上传完成回调 */\n onComplete?: (result: CompleteUploadResponse) => void;\n /** 上传错误回调 */\n onError?: (error: Error) => void;\n}\n\n/**\n * 上传状态\n */\nexport enum UploadStatus {\n PENDING = 'pending',\n UPLOADING = 'uploading',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n FAILED = 'failed',\n CANCELLED = 'cancelled',\n}\n\n/**\n * 文件分片信息\n */\nexport interface ChunkInfo {\n index: number;\n start: number;\n end: number;\n blob: Blob;\n md5?: string;\n}\n","/**\n * 文件分片上传工具类\n * 支持分片上传、断点续传、进度追踪、暂停/恢复/取消\n *\n * @example\n * ```typescript\n * import { createUploader } from '@alibarbar/common/upload';\n *\n * const uploader = createUploader(file, {\n * chunkSize: 2 * 1024 * 1024, // 2MB\n * concurrency: 3,\n * baseURL: 'https://api.example.com',\n * onProgress: (progress) => {\n * console.log(`进度:${progress.percentage}%`);\n * },\n * onComplete: (result) => {\n * console.log('上传完成:', result.fileUrl);\n * },\n * onError: (error) => {\n * console.error('上传失败:', error);\n * }\n * });\n *\n * // 开始上传\n * await uploader.upload();\n * ```\n */\n\nimport {\n UploadOptions,\n UploadInitRequest,\n UploadInitResponse,\n ChunkUploadResponse,\n CompleteUploadResponse,\n UploadProgress,\n UploadStatus,\n ChunkInfo,\n ApiResponse,\n} from '../types/upload';\nimport { calculateBlobMD5, splitFileIntoChunks } from './file';\n\n/**\n * 文件上传器类\n */\nexport class ChunkUploader {\n private file: File;\n private options: Required<UploadOptions>;\n private taskId: string | null = null;\n private chunks: ChunkInfo[] = [];\n private uploadedChunks: Set<number> = new Set();\n private status: UploadStatus = UploadStatus.PENDING;\n private abortController: AbortController | null = null;\n\n constructor(file: File, options: UploadOptions = {}) {\n this.file = file;\n this.options = {\n chunkSize: options.chunkSize || 2 * 1024 * 1024, // 默认2MB\n concurrency: options.concurrency || 3,\n retryCount: options.retryCount || 3,\n retryDelay: options.retryDelay || 1000,\n baseURL: options.baseURL || '',\n headers: options.headers || {},\n onProgress: options.onProgress || (() => {}),\n onComplete: options.onComplete || (() => {}),\n onError: options.onError || (() => {}),\n };\n }\n\n /**\n * 初始化上传\n */\n private async initUpload(): Promise<UploadInitResponse> {\n const fileMd5 = await this.calculateFileMD5();\n const request: UploadInitRequest = {\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5,\n chunkSize: this.options.chunkSize,\n };\n\n const response = await this.request<ApiResponse<UploadInitResponse>>('/api/files/common/init', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(request),\n });\n\n if (response.code !== 200) {\n throw new Error(response.message || '初始化上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 计算文件MD5\n */\n private async calculateFileMD5(): Promise<string> {\n const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n const md5Promises = chunks.map((chunk, index) =>\n calculateBlobMD5(chunk).then(md5 => ({ index, md5 }))\n );\n const md5Results = await Promise.all(md5Promises);\n return md5Results.map(r => r.md5).join('');\n }\n\n /**\n * 准备分片\n */\n private prepareChunks(): void {\n const blobChunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n this.chunks = blobChunks.map((blob, index) => ({\n index,\n start: index * this.options.chunkSize,\n end: Math.min((index + 1) * this.options.chunkSize, this.file.size),\n blob,\n }));\n }\n\n /**\n * 获取已上传的分片列表(用于断点续传)\n */\n private async getUploadedChunks(): Promise<number[]> {\n if (!this.taskId) return [];\n\n try {\n const response = await this.request<ApiResponse<number[]>>(\n `/api/files/common/chunks/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200) {\n return response.data || [];\n }\n } catch (error) {\n console.warn('获取已上传分片失败:', error);\n }\n\n return [];\n }\n\n /**\n * 上传单个分片\n */\n private async uploadChunk(chunkInfo: ChunkInfo, retryCount = 0): Promise<void> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n if (this.uploadedChunks.has(chunkInfo.index)) {\n return; // 已上传,跳过\n }\n\n try {\n const chunkMd5 = await calculateBlobMD5(chunkInfo.blob);\n const formData = new FormData();\n formData.append('file', chunkInfo.blob, this.file.name);\n\n const url = `/api/files/common/chunk?taskId=${this.taskId}&chunkIndex=${chunkInfo.index}&chunkMd5=${chunkMd5}`;\n\n const response = await this.request<ApiResponse<ChunkUploadResponse>>(url, {\n method: 'POST',\n headers: this.options.headers,\n body: formData,\n });\n\n if (response.code === 200 && response.data.success) {\n this.uploadedChunks.add(chunkInfo.index);\n // 每上传一个分片,更新进度\n await this.updateProgress();\n } else {\n throw new Error(response.message || '分片上传失败');\n }\n } catch (error) {\n if (retryCount < this.options.retryCount) {\n await this.delay(this.options.retryDelay);\n return this.uploadChunk(chunkInfo, retryCount + 1);\n }\n throw error;\n }\n }\n\n /**\n * 并发上传分片\n */\n private async uploadChunksConcurrently(): Promise<void> {\n const chunksToUpload = this.chunks.filter(chunk => !this.uploadedChunks.has(chunk.index));\n\n if (chunksToUpload.length === 0) {\n return; // 所有分片已上传\n }\n\n let currentIndex = 0;\n const uploadNext = async (): Promise<void> => {\n while (this.status === UploadStatus.UPLOADING) {\n const chunkIndex = currentIndex++;\n if (chunkIndex >= chunksToUpload.length) {\n break;\n }\n const chunk = chunksToUpload[chunkIndex];\n await this.uploadChunk(chunk);\n }\n };\n\n // 启动并发任务\n const tasks = Array(this.options.concurrency)\n .fill(null)\n .map(() => uploadNext());\n await Promise.all(tasks);\n }\n\n /**\n * 更新上传进度(仅用于回调前端显示)\n */\n private async updateProgress(): Promise<void> {\n if (!this.taskId) return;\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n this.options.onProgress(response.data);\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n }\n\n /**\n * 获取上传进度\n */\n private async getUploadProgress(): Promise<UploadProgress | null> {\n if (!this.taskId) {\n return null;\n }\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n console.log('[获取进度]', response.data);\n return response.data;\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n\n return null;\n }\n\n /**\n * 完成上传(调用后端合并分片接口)\n */\n private async completeUpload(): Promise<CompleteUploadResponse> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n console.log('[完成上传] 调用完成接口:', `/api/files/common/complete/${this.taskId}`);\n\n const response = await this.request<ApiResponse<CompleteUploadResponse>>(\n `/api/files/common/complete/${this.taskId}`,\n {\n method: 'POST',\n headers: this.options.headers,\n }\n );\n\n console.log('[完成上传] 接口响应:', response);\n\n if (response.code !== 200) {\n throw new Error(response.message || '完成上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 开始上传\n */\n async upload(): Promise<CompleteUploadResponse> {\n try {\n this.status = UploadStatus.UPLOADING;\n this.abortController = new AbortController();\n\n // 1. 初始化上传\n console.log('[上传流程] 1. 初始化上传');\n const initResponse = await this.initUpload();\n this.taskId = initResponse.taskId;\n\n // 2. 检查是否秒传\n if (initResponse.instantUpload && initResponse.fileUrl) {\n console.log('[上传流程] 秒传成功');\n this.status = UploadStatus.COMPLETED;\n const result: CompleteUploadResponse = {\n taskId: this.taskId,\n fileUrl: initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '文件已存在,秒传成功',\n };\n this.options.onComplete(result);\n return result;\n }\n\n // 3. 准备分片\n console.log('[上传流程] 2. 准备分片');\n this.prepareChunks();\n const totalChunks = this.chunks.length;\n console.log(`[上传流程] 共 ${totalChunks} 个分片`);\n\n // 4. 获取已上传的分片(断点续传)\n console.log('[上传流程] 3. 检查已上传分片');\n const existingChunks = await this.getUploadedChunks();\n existingChunks.forEach(index => this.uploadedChunks.add(index));\n console.log(\n `[上传流程] 已上传 ${existingChunks.length} 个分片,还需上传 ${totalChunks - existingChunks.length} 个`\n );\n\n // 5. 上传分片\n console.log('[上传流程] 4. 开始上传分片');\n await this.uploadChunksConcurrently();\n console.log('[上传流程] 5. 所有分片上传完成');\n\n // 6. 验证所有分片都已上传\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n throw new Error(`上传验证失败:已上传 ${uploadedCount}/${totalChunks} 个分片`);\n }\n console.log(`[上传流程] 6. 验证通过:${uploadedCount}/${totalChunks} 个分片已上传`);\n\n // 7. 计算本地进度\n const localPercentage = Math.round((uploadedCount / totalChunks) * 100);\n console.log(`[上传流程] 7. 本地进度:${localPercentage}%`);\n\n // 8. 获取服务端进度\n console.log('[上传流程] 8. 获取服务端进度');\n const serverProgress = await this.getUploadProgress();\n const serverPercentage = serverProgress?.percentage ?? 0;\n console.log(`[上传流程] 服务端进度:${serverPercentage}%`);\n\n // 9. 计算最终进度(优先使用服务端进度)\n const finalPercentage = serverProgress?.percentage ?? localPercentage;\n console.log(`[上传流程] 9. 最终进度:${finalPercentage}%`);\n\n // 10. 判断是否可以调用完成接口\n if (finalPercentage >= 100) {\n console.log('[上传流程] 10. ✅ 进度达到100%,调用完成接口');\n const result = await this.completeUpload();\n\n this.status = UploadStatus.COMPLETED;\n this.options.onComplete(result);\n\n console.log('[上传流程] ✅ 上传完成');\n return result;\n } else {\n console.error(`[上传流程] ❌ 进度不足100%:${finalPercentage}%`);\n throw new Error(`上传未完成:当前进度 ${finalPercentage.toFixed(2)}%`);\n }\n } catch (error) {\n this.status = UploadStatus.FAILED;\n const err = error instanceof Error ? error : new Error(String(error));\n this.options.onError(err);\n console.error('[上传流程] ❌ 上传失败:', err);\n throw err;\n }\n }\n\n /**\n * 暂停上传\n */\n pause(): void {\n if (this.status === UploadStatus.UPLOADING) {\n this.status = UploadStatus.PAUSED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n }\n\n /**\n * 恢复上传\n */\n async resume(): Promise<CompleteUploadResponse> {\n if (this.status === UploadStatus.PAUSED) {\n return this.upload();\n }\n throw new Error('当前状态无法恢复上传');\n }\n\n /**\n * 取消上传\n */\n async cancel(): Promise<void> {\n if (this.taskId && this.status === UploadStatus.UPLOADING) {\n try {\n await this.request<ApiResponse<null>>(`/api/files/common/cancel/${this.taskId}`, {\n method: 'POST',\n headers: this.options.headers,\n });\n } catch (error) {\n console.warn('取消上传失败:', error);\n }\n }\n\n this.status = UploadStatus.CANCELLED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n\n /**\n * 获取当前状态\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * 获取任务ID\n */\n getTaskId(): string | null {\n return this.taskId;\n }\n\n /**\n * HTTP请求封装\n */\n private async request<T>(url: string, options: RequestInit = {}): Promise<T> {\n const fullUrl = `${this.options.baseURL}${url}`;\n const signal = this.abortController?.signal;\n\n const response = await fetch(fullUrl, {\n ...options,\n signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);\n }\n\n return response.json();\n }\n\n /**\n * 延迟函数\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * 创建文件上传器实例\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns 上传器实例\n */\nexport function createUploader(file: File, options?: UploadOptions): ChunkUploader {\n return new ChunkUploader(file, options);\n}\n\n/**\n * 简单的文件上传函数(Promise风格)\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns Promise<CompleteUploadResponse>\n */\nexport async function uploadFile(\n file: File,\n options?: UploadOptions\n): Promise<CompleteUploadResponse> {\n const uploader = createUploader(file, options);\n return uploader.upload();\n}\n"]}
|
|
@@ -19,7 +19,6 @@ var ChunkUploader = class {
|
|
|
19
19
|
this.uploadedChunks = /* @__PURE__ */ new Set();
|
|
20
20
|
this.status = "pending" /* PENDING */;
|
|
21
21
|
this.abortController = null;
|
|
22
|
-
this.initResponse = null;
|
|
23
22
|
this.file = file;
|
|
24
23
|
this.options = {
|
|
25
24
|
chunkSize: options.chunkSize || 2 * 1024 * 1024,
|
|
@@ -62,7 +61,7 @@ var ChunkUploader = class {
|
|
|
62
61
|
return response.data;
|
|
63
62
|
}
|
|
64
63
|
/**
|
|
65
|
-
* 计算文件MD5
|
|
64
|
+
* 计算文件MD5
|
|
66
65
|
*/
|
|
67
66
|
async calculateFileMD5() {
|
|
68
67
|
const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);
|
|
@@ -85,7 +84,7 @@ var ChunkUploader = class {
|
|
|
85
84
|
}));
|
|
86
85
|
}
|
|
87
86
|
/**
|
|
88
|
-
*
|
|
87
|
+
* 获取已上传的分片列表(用于断点续传)
|
|
89
88
|
*/
|
|
90
89
|
async getUploadedChunks() {
|
|
91
90
|
if (!this.taskId) return [];
|
|
@@ -127,7 +126,7 @@ var ChunkUploader = class {
|
|
|
127
126
|
});
|
|
128
127
|
if (response.code === 200 && response.data.success) {
|
|
129
128
|
this.uploadedChunks.add(chunkInfo.index);
|
|
130
|
-
this.updateProgress();
|
|
129
|
+
await this.updateProgress();
|
|
131
130
|
} else {
|
|
132
131
|
throw new Error(response.message || "\u5206\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
133
132
|
}
|
|
@@ -148,8 +147,6 @@ var ChunkUploader = class {
|
|
|
148
147
|
return;
|
|
149
148
|
}
|
|
150
149
|
let currentIndex = 0;
|
|
151
|
-
const semaphore = new Array(this.options.concurrency).fill(null);
|
|
152
|
-
const allUploadPromises = [];
|
|
153
150
|
const uploadNext = async () => {
|
|
154
151
|
while (this.status === "uploading" /* UPLOADING */) {
|
|
155
152
|
const chunkIndex = currentIndex++;
|
|
@@ -157,26 +154,34 @@ var ChunkUploader = class {
|
|
|
157
154
|
break;
|
|
158
155
|
}
|
|
159
156
|
const chunk = chunksToUpload[chunkIndex];
|
|
160
|
-
|
|
161
|
-
allUploadPromises.push(uploadPromise);
|
|
162
|
-
await uploadPromise;
|
|
157
|
+
await this.uploadChunk(chunk);
|
|
163
158
|
}
|
|
164
159
|
};
|
|
165
|
-
const
|
|
166
|
-
await Promise.all(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
160
|
+
const tasks = Array(this.options.concurrency).fill(null).map(() => uploadNext());
|
|
161
|
+
await Promise.all(tasks);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 更新上传进度(仅用于回调前端显示)
|
|
165
|
+
*/
|
|
166
|
+
async updateProgress() {
|
|
167
|
+
if (!this.taskId) return;
|
|
168
|
+
try {
|
|
169
|
+
const response = await this.request(
|
|
170
|
+
`/api/files/common/progress/${this.taskId}`,
|
|
171
|
+
{
|
|
172
|
+
method: "GET",
|
|
173
|
+
headers: this.options.headers
|
|
174
|
+
}
|
|
174
175
|
);
|
|
176
|
+
if (response.code === 200 && response.data) {
|
|
177
|
+
this.options.onProgress(response.data);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
175
181
|
}
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
178
184
|
* 获取上传进度
|
|
179
|
-
* 当进度达到100%时,会自动调用完成接口
|
|
180
185
|
*/
|
|
181
186
|
async getUploadProgress() {
|
|
182
187
|
if (!this.taskId) {
|
|
@@ -191,9 +196,7 @@ var ChunkUploader = class {
|
|
|
191
196
|
}
|
|
192
197
|
);
|
|
193
198
|
if (response.code === 200 && response.data) {
|
|
194
|
-
|
|
195
|
-
await this.completeUpload();
|
|
196
|
-
}
|
|
199
|
+
console.log("[\u83B7\u53D6\u8FDB\u5EA6]", response.data);
|
|
197
200
|
return response.data;
|
|
198
201
|
}
|
|
199
202
|
} catch (error) {
|
|
@@ -203,27 +206,12 @@ var ChunkUploader = class {
|
|
|
203
206
|
}
|
|
204
207
|
/**
|
|
205
208
|
* 完成上传(调用后端合并分片接口)
|
|
206
|
-
*
|
|
207
|
-
* 只有在进度为 100% 时才会被调用
|
|
208
209
|
*/
|
|
209
210
|
async completeUpload() {
|
|
210
211
|
if (!this.taskId) {
|
|
211
212
|
throw new Error("\u4EFB\u52A1ID\u4E0D\u5B58\u5728");
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
-
if (this.initResponse?.fileUrl) {
|
|
215
|
-
return {
|
|
216
|
-
taskId: this.taskId,
|
|
217
|
-
fileUrl: this.initResponse.fileUrl,
|
|
218
|
-
fileName: this.file.name,
|
|
219
|
-
fileSize: this.file.size,
|
|
220
|
-
fileMd5: "",
|
|
221
|
-
success: true,
|
|
222
|
-
message: "\u4E0A\u4F20\u5DF2\u5B8C\u6210"
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
throw new Error("\u4E0A\u4F20\u5DF2\u5B8C\u6210\u4F46\u7F3A\u5C11\u6587\u4EF6\u4FE1\u606F");
|
|
226
|
-
}
|
|
214
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u8C03\u7528\u5B8C\u6210\u63A5\u53E3:", `/api/files/common/complete/${this.taskId}`);
|
|
227
215
|
const response = await this.request(
|
|
228
216
|
`/api/files/common/complete/${this.taskId}`,
|
|
229
217
|
{
|
|
@@ -231,37 +219,12 @@ var ChunkUploader = class {
|
|
|
231
219
|
headers: this.options.headers
|
|
232
220
|
}
|
|
233
221
|
);
|
|
222
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u63A5\u53E3\u54CD\u5E94:", response);
|
|
234
223
|
if (response.code !== 200) {
|
|
235
224
|
throw new Error(response.message || "\u5B8C\u6210\u4E0A\u4F20\u5931\u8D25");
|
|
236
225
|
}
|
|
237
|
-
this.status = "completed" /* COMPLETED */;
|
|
238
|
-
this.options.onComplete(response.data);
|
|
239
226
|
return response.data;
|
|
240
227
|
}
|
|
241
|
-
/**
|
|
242
|
-
* 更新上传进度(用于回调前端显示)
|
|
243
|
-
* 每次上传完一个分片后调用
|
|
244
|
-
*/
|
|
245
|
-
async updateProgress() {
|
|
246
|
-
if (!this.taskId) return;
|
|
247
|
-
try {
|
|
248
|
-
const response = await this.request(
|
|
249
|
-
`/api/files/common/progress/${this.taskId}`,
|
|
250
|
-
{
|
|
251
|
-
method: "GET",
|
|
252
|
-
headers: this.options.headers
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
if (response.code === 200 && response.data) {
|
|
256
|
-
this.options.onProgress(response.data);
|
|
257
|
-
if (response.data.percentage >= 100) {
|
|
258
|
-
await this.completeUpload();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
228
|
/**
|
|
266
229
|
* 开始上传
|
|
267
230
|
*/
|
|
@@ -269,13 +232,15 @@ var ChunkUploader = class {
|
|
|
269
232
|
try {
|
|
270
233
|
this.status = "uploading" /* UPLOADING */;
|
|
271
234
|
this.abortController = new AbortController();
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
235
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 1. \u521D\u59CB\u5316\u4E0A\u4F20");
|
|
236
|
+
const initResponse = await this.initUpload();
|
|
237
|
+
this.taskId = initResponse.taskId;
|
|
238
|
+
if (initResponse.instantUpload && initResponse.fileUrl) {
|
|
239
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u79D2\u4F20\u6210\u529F");
|
|
275
240
|
this.status = "completed" /* COMPLETED */;
|
|
276
241
|
const result = {
|
|
277
242
|
taskId: this.taskId,
|
|
278
|
-
fileUrl:
|
|
243
|
+
fileUrl: initResponse.fileUrl,
|
|
279
244
|
fileName: this.file.name,
|
|
280
245
|
fileSize: this.file.size,
|
|
281
246
|
fileMd5: "",
|
|
@@ -285,41 +250,48 @@ var ChunkUploader = class {
|
|
|
285
250
|
this.options.onComplete(result);
|
|
286
251
|
return result;
|
|
287
252
|
}
|
|
253
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 2. \u51C6\u5907\u5206\u7247");
|
|
288
254
|
this.prepareChunks();
|
|
255
|
+
const totalChunks = this.chunks.length;
|
|
256
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u5171 ${totalChunks} \u4E2A\u5206\u7247`);
|
|
257
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 3. \u68C0\u67E5\u5DF2\u4E0A\u4F20\u5206\u7247");
|
|
289
258
|
const existingChunks = await this.getUploadedChunks();
|
|
290
259
|
existingChunks.forEach((index) => this.uploadedChunks.add(index));
|
|
260
|
+
console.log(
|
|
261
|
+
`[\u4E0A\u4F20\u6D41\u7A0B] \u5DF2\u4E0A\u4F20 ${existingChunks.length} \u4E2A\u5206\u7247\uFF0C\u8FD8\u9700\u4E0A\u4F20 ${totalChunks - existingChunks.length} \u4E2A`
|
|
262
|
+
);
|
|
263
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 4. \u5F00\u59CB\u4E0A\u4F20\u5206\u7247");
|
|
291
264
|
await this.uploadChunksConcurrently();
|
|
292
|
-
|
|
265
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 5. \u6240\u6709\u5206\u7247\u4E0A\u4F20\u5B8C\u6210");
|
|
293
266
|
const uploadedCount = this.uploadedChunks.size;
|
|
294
267
|
if (uploadedCount < totalChunks) {
|
|
295
|
-
throw new Error(
|
|
296
|
-
`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\uFF0C\u65E0\u6CD5\u5B8C\u6210\u4E0A\u4F20`
|
|
297
|
-
);
|
|
268
|
+
throw new Error(`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247`);
|
|
298
269
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
fileMd5: "",
|
|
309
|
-
success: true,
|
|
310
|
-
message: "\u4E0A\u4F20\u5B8C\u6210"
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
const finalPercentage = progress?.percentage ?? localPercentage;
|
|
270
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 6. \u9A8C\u8BC1\u901A\u8FC7\uFF1A${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\u5DF2\u4E0A\u4F20`);
|
|
271
|
+
const localPercentage = Math.round(uploadedCount / totalChunks * 100);
|
|
272
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 7. \u672C\u5730\u8FDB\u5EA6\uFF1A${localPercentage}%`);
|
|
273
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 8. \u83B7\u53D6\u670D\u52A1\u7AEF\u8FDB\u5EA6");
|
|
274
|
+
const serverProgress = await this.getUploadProgress();
|
|
275
|
+
const serverPercentage = serverProgress?.percentage ?? 0;
|
|
276
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u670D\u52A1\u7AEF\u8FDB\u5EA6\uFF1A${serverPercentage}%`);
|
|
277
|
+
const finalPercentage = serverProgress?.percentage ?? localPercentage;
|
|
278
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 9. \u6700\u7EC8\u8FDB\u5EA6\uFF1A${finalPercentage}%`);
|
|
314
279
|
if (finalPercentage >= 100) {
|
|
280
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 10. \u2705 \u8FDB\u5EA6\u8FBE\u5230100%\uFF0C\u8C03\u7528\u5B8C\u6210\u63A5\u53E3");
|
|
315
281
|
const result = await this.completeUpload();
|
|
282
|
+
this.status = "completed" /* COMPLETED */;
|
|
283
|
+
this.options.onComplete(result);
|
|
284
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u2705 \u4E0A\u4F20\u5B8C\u6210");
|
|
316
285
|
return result;
|
|
286
|
+
} else {
|
|
287
|
+
console.error(`[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u8FDB\u5EA6\u4E0D\u8DB3100%\uFF1A${finalPercentage}%`);
|
|
288
|
+
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
317
289
|
}
|
|
318
|
-
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
319
290
|
} catch (error) {
|
|
320
291
|
this.status = "failed" /* FAILED */;
|
|
321
292
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
322
293
|
this.options.onError(err);
|
|
294
|
+
console.error("[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u4E0A\u4F20\u5931\u8D25:", err);
|
|
323
295
|
throw err;
|
|
324
296
|
}
|
|
325
297
|
}
|
|
@@ -399,7 +371,11 @@ var ChunkUploader = class {
|
|
|
399
371
|
function createUploader(file, options) {
|
|
400
372
|
return new ChunkUploader(file, options);
|
|
401
373
|
}
|
|
374
|
+
async function uploadFile(file, options) {
|
|
375
|
+
const uploader = createUploader(file, options);
|
|
376
|
+
return uploader.upload();
|
|
377
|
+
}
|
|
402
378
|
|
|
403
|
-
export { ChunkUploader, UploadStatus, createUploader };
|
|
404
|
-
//# sourceMappingURL=chunk-
|
|
405
|
-
//# sourceMappingURL=chunk-
|
|
379
|
+
export { ChunkUploader, UploadStatus, createUploader, uploadFile };
|
|
380
|
+
//# sourceMappingURL=chunk-NJARVI6X.mjs.map
|
|
381
|
+
//# sourceMappingURL=chunk-NJARVI6X.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/upload.ts","../src/browser/upload.ts"],"names":["UploadStatus"],"mappings":"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","file":"chunk-NJARVI6X.mjs","sourcesContent":["/**\n * 文件上传相关类型定义\n */\n\n/**\n * 上传初始化请求参数\n */\nexport interface UploadInitRequest {\n fileName: string;\n fileSize: number;\n fileMd5: string;\n chunkSize?: number;\n}\n\n/**\n * 上传初始化响应\n */\nexport interface UploadInitResponse {\n taskId: string;\n totalChunks: number;\n existingChunks: number[];\n instantUpload: boolean;\n fileUrl?: string;\n chunkSize: number;\n}\n\n/**\n * 分片上传响应\n */\nexport interface ChunkUploadResponse {\n chunkIndex: number;\n success: boolean;\n message: string;\n chunkMd5: string;\n}\n\n/**\n * 完成上传响应\n */\nexport interface CompleteUploadResponse {\n taskId: string;\n fileUrl: string;\n fileName: string;\n fileSize: number;\n fileMd5: string;\n success: boolean;\n message: string;\n}\n\n/**\n * 上传进度信息\n */\nexport interface UploadProgress {\n taskId: string;\n fileName: string;\n fileSize: number;\n totalChunks: number;\n uploadedChunks: number;\n percentage: number;\n status: string;\n uploadedSize: number;\n}\n\n/**\n * API响应包装类型\n */\nexport interface ApiResponse<T> {\n code: number;\n message: string;\n data: T;\n timestamp: number;\n}\n\n/**\n * 上传配置选项\n */\nexport interface UploadOptions {\n /** 分片大小(字节),默认 2MB */\n chunkSize?: number;\n /** 并发上传数量,默认 3 */\n concurrency?: number;\n /** 重试次数,默认 3 */\n retryCount?: number;\n /** 重试延迟(毫秒),默认 1000 */\n retryDelay?: number;\n /** 基础API地址 */\n baseURL?: string;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 上传进度回调 */\n onProgress?: (progress: UploadProgress) => void;\n /** 上传完成回调 */\n onComplete?: (result: CompleteUploadResponse) => void;\n /** 上传错误回调 */\n onError?: (error: Error) => void;\n}\n\n/**\n * 上传状态\n */\nexport enum UploadStatus {\n PENDING = 'pending',\n UPLOADING = 'uploading',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n FAILED = 'failed',\n CANCELLED = 'cancelled',\n}\n\n/**\n * 文件分片信息\n */\nexport interface ChunkInfo {\n index: number;\n start: number;\n end: number;\n blob: Blob;\n md5?: string;\n}\n","/**\n * 文件分片上传工具类\n * 支持分片上传、断点续传、进度追踪、暂停/恢复/取消\n *\n * @example\n * ```typescript\n * import { createUploader } from '@alibarbar/common/upload';\n *\n * const uploader = createUploader(file, {\n * chunkSize: 2 * 1024 * 1024, // 2MB\n * concurrency: 3,\n * baseURL: 'https://api.example.com',\n * onProgress: (progress) => {\n * console.log(`进度:${progress.percentage}%`);\n * },\n * onComplete: (result) => {\n * console.log('上传完成:', result.fileUrl);\n * },\n * onError: (error) => {\n * console.error('上传失败:', error);\n * }\n * });\n *\n * // 开始上传\n * await uploader.upload();\n * ```\n */\n\nimport {\n UploadOptions,\n UploadInitRequest,\n UploadInitResponse,\n ChunkUploadResponse,\n CompleteUploadResponse,\n UploadProgress,\n UploadStatus,\n ChunkInfo,\n ApiResponse,\n} from '../types/upload';\nimport { calculateBlobMD5, splitFileIntoChunks } from './file';\n\n/**\n * 文件上传器类\n */\nexport class ChunkUploader {\n private file: File;\n private options: Required<UploadOptions>;\n private taskId: string | null = null;\n private chunks: ChunkInfo[] = [];\n private uploadedChunks: Set<number> = new Set();\n private status: UploadStatus = UploadStatus.PENDING;\n private abortController: AbortController | null = null;\n\n constructor(file: File, options: UploadOptions = {}) {\n this.file = file;\n this.options = {\n chunkSize: options.chunkSize || 2 * 1024 * 1024, // 默认2MB\n concurrency: options.concurrency || 3,\n retryCount: options.retryCount || 3,\n retryDelay: options.retryDelay || 1000,\n baseURL: options.baseURL || '',\n headers: options.headers || {},\n onProgress: options.onProgress || (() => {}),\n onComplete: options.onComplete || (() => {}),\n onError: options.onError || (() => {}),\n };\n }\n\n /**\n * 初始化上传\n */\n private async initUpload(): Promise<UploadInitResponse> {\n const fileMd5 = await this.calculateFileMD5();\n const request: UploadInitRequest = {\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5,\n chunkSize: this.options.chunkSize,\n };\n\n const response = await this.request<ApiResponse<UploadInitResponse>>('/api/files/common/init', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(request),\n });\n\n if (response.code !== 200) {\n throw new Error(response.message || '初始化上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 计算文件MD5\n */\n private async calculateFileMD5(): Promise<string> {\n const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n const md5Promises = chunks.map((chunk, index) =>\n calculateBlobMD5(chunk).then(md5 => ({ index, md5 }))\n );\n const md5Results = await Promise.all(md5Promises);\n return md5Results.map(r => r.md5).join('');\n }\n\n /**\n * 准备分片\n */\n private prepareChunks(): void {\n const blobChunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n this.chunks = blobChunks.map((blob, index) => ({\n index,\n start: index * this.options.chunkSize,\n end: Math.min((index + 1) * this.options.chunkSize, this.file.size),\n blob,\n }));\n }\n\n /**\n * 获取已上传的分片列表(用于断点续传)\n */\n private async getUploadedChunks(): Promise<number[]> {\n if (!this.taskId) return [];\n\n try {\n const response = await this.request<ApiResponse<number[]>>(\n `/api/files/common/chunks/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200) {\n return response.data || [];\n }\n } catch (error) {\n console.warn('获取已上传分片失败:', error);\n }\n\n return [];\n }\n\n /**\n * 上传单个分片\n */\n private async uploadChunk(chunkInfo: ChunkInfo, retryCount = 0): Promise<void> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n if (this.uploadedChunks.has(chunkInfo.index)) {\n return; // 已上传,跳过\n }\n\n try {\n const chunkMd5 = await calculateBlobMD5(chunkInfo.blob);\n const formData = new FormData();\n formData.append('file', chunkInfo.blob, this.file.name);\n\n const url = `/api/files/common/chunk?taskId=${this.taskId}&chunkIndex=${chunkInfo.index}&chunkMd5=${chunkMd5}`;\n\n const response = await this.request<ApiResponse<ChunkUploadResponse>>(url, {\n method: 'POST',\n headers: this.options.headers,\n body: formData,\n });\n\n if (response.code === 200 && response.data.success) {\n this.uploadedChunks.add(chunkInfo.index);\n // 每上传一个分片,更新进度\n await this.updateProgress();\n } else {\n throw new Error(response.message || '分片上传失败');\n }\n } catch (error) {\n if (retryCount < this.options.retryCount) {\n await this.delay(this.options.retryDelay);\n return this.uploadChunk(chunkInfo, retryCount + 1);\n }\n throw error;\n }\n }\n\n /**\n * 并发上传分片\n */\n private async uploadChunksConcurrently(): Promise<void> {\n const chunksToUpload = this.chunks.filter(chunk => !this.uploadedChunks.has(chunk.index));\n\n if (chunksToUpload.length === 0) {\n return; // 所有分片已上传\n }\n\n let currentIndex = 0;\n const uploadNext = async (): Promise<void> => {\n while (this.status === UploadStatus.UPLOADING) {\n const chunkIndex = currentIndex++;\n if (chunkIndex >= chunksToUpload.length) {\n break;\n }\n const chunk = chunksToUpload[chunkIndex];\n await this.uploadChunk(chunk);\n }\n };\n\n // 启动并发任务\n const tasks = Array(this.options.concurrency)\n .fill(null)\n .map(() => uploadNext());\n await Promise.all(tasks);\n }\n\n /**\n * 更新上传进度(仅用于回调前端显示)\n */\n private async updateProgress(): Promise<void> {\n if (!this.taskId) return;\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n this.options.onProgress(response.data);\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n }\n\n /**\n * 获取上传进度\n */\n private async getUploadProgress(): Promise<UploadProgress | null> {\n if (!this.taskId) {\n return null;\n }\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n console.log('[获取进度]', response.data);\n return response.data;\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n\n return null;\n }\n\n /**\n * 完成上传(调用后端合并分片接口)\n */\n private async completeUpload(): Promise<CompleteUploadResponse> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n console.log('[完成上传] 调用完成接口:', `/api/files/common/complete/${this.taskId}`);\n\n const response = await this.request<ApiResponse<CompleteUploadResponse>>(\n `/api/files/common/complete/${this.taskId}`,\n {\n method: 'POST',\n headers: this.options.headers,\n }\n );\n\n console.log('[完成上传] 接口响应:', response);\n\n if (response.code !== 200) {\n throw new Error(response.message || '完成上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 开始上传\n */\n async upload(): Promise<CompleteUploadResponse> {\n try {\n this.status = UploadStatus.UPLOADING;\n this.abortController = new AbortController();\n\n // 1. 初始化上传\n console.log('[上传流程] 1. 初始化上传');\n const initResponse = await this.initUpload();\n this.taskId = initResponse.taskId;\n\n // 2. 检查是否秒传\n if (initResponse.instantUpload && initResponse.fileUrl) {\n console.log('[上传流程] 秒传成功');\n this.status = UploadStatus.COMPLETED;\n const result: CompleteUploadResponse = {\n taskId: this.taskId,\n fileUrl: initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '文件已存在,秒传成功',\n };\n this.options.onComplete(result);\n return result;\n }\n\n // 3. 准备分片\n console.log('[上传流程] 2. 准备分片');\n this.prepareChunks();\n const totalChunks = this.chunks.length;\n console.log(`[上传流程] 共 ${totalChunks} 个分片`);\n\n // 4. 获取已上传的分片(断点续传)\n console.log('[上传流程] 3. 检查已上传分片');\n const existingChunks = await this.getUploadedChunks();\n existingChunks.forEach(index => this.uploadedChunks.add(index));\n console.log(\n `[上传流程] 已上传 ${existingChunks.length} 个分片,还需上传 ${totalChunks - existingChunks.length} 个`\n );\n\n // 5. 上传分片\n console.log('[上传流程] 4. 开始上传分片');\n await this.uploadChunksConcurrently();\n console.log('[上传流程] 5. 所有分片上传完成');\n\n // 6. 验证所有分片都已上传\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n throw new Error(`上传验证失败:已上传 ${uploadedCount}/${totalChunks} 个分片`);\n }\n console.log(`[上传流程] 6. 验证通过:${uploadedCount}/${totalChunks} 个分片已上传`);\n\n // 7. 计算本地进度\n const localPercentage = Math.round((uploadedCount / totalChunks) * 100);\n console.log(`[上传流程] 7. 本地进度:${localPercentage}%`);\n\n // 8. 获取服务端进度\n console.log('[上传流程] 8. 获取服务端进度');\n const serverProgress = await this.getUploadProgress();\n const serverPercentage = serverProgress?.percentage ?? 0;\n console.log(`[上传流程] 服务端进度:${serverPercentage}%`);\n\n // 9. 计算最终进度(优先使用服务端进度)\n const finalPercentage = serverProgress?.percentage ?? localPercentage;\n console.log(`[上传流程] 9. 最终进度:${finalPercentage}%`);\n\n // 10. 判断是否可以调用完成接口\n if (finalPercentage >= 100) {\n console.log('[上传流程] 10. ✅ 进度达到100%,调用完成接口');\n const result = await this.completeUpload();\n\n this.status = UploadStatus.COMPLETED;\n this.options.onComplete(result);\n\n console.log('[上传流程] ✅ 上传完成');\n return result;\n } else {\n console.error(`[上传流程] ❌ 进度不足100%:${finalPercentage}%`);\n throw new Error(`上传未完成:当前进度 ${finalPercentage.toFixed(2)}%`);\n }\n } catch (error) {\n this.status = UploadStatus.FAILED;\n const err = error instanceof Error ? error : new Error(String(error));\n this.options.onError(err);\n console.error('[上传流程] ❌ 上传失败:', err);\n throw err;\n }\n }\n\n /**\n * 暂停上传\n */\n pause(): void {\n if (this.status === UploadStatus.UPLOADING) {\n this.status = UploadStatus.PAUSED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n }\n\n /**\n * 恢复上传\n */\n async resume(): Promise<CompleteUploadResponse> {\n if (this.status === UploadStatus.PAUSED) {\n return this.upload();\n }\n throw new Error('当前状态无法恢复上传');\n }\n\n /**\n * 取消上传\n */\n async cancel(): Promise<void> {\n if (this.taskId && this.status === UploadStatus.UPLOADING) {\n try {\n await this.request<ApiResponse<null>>(`/api/files/common/cancel/${this.taskId}`, {\n method: 'POST',\n headers: this.options.headers,\n });\n } catch (error) {\n console.warn('取消上传失败:', error);\n }\n }\n\n this.status = UploadStatus.CANCELLED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n\n /**\n * 获取当前状态\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * 获取任务ID\n */\n getTaskId(): string | null {\n return this.taskId;\n }\n\n /**\n * HTTP请求封装\n */\n private async request<T>(url: string, options: RequestInit = {}): Promise<T> {\n const fullUrl = `${this.options.baseURL}${url}`;\n const signal = this.abortController?.signal;\n\n const response = await fetch(fullUrl, {\n ...options,\n signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);\n }\n\n return response.json();\n }\n\n /**\n * 延迟函数\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * 创建文件上传器实例\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns 上传器实例\n */\nexport function createUploader(file: File, options?: UploadOptions): ChunkUploader {\n return new ChunkUploader(file, options);\n}\n\n/**\n * 简单的文件上传函数(Promise风格)\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns Promise<CompleteUploadResponse>\n */\nexport async function uploadFile(\n file: File,\n options?: UploadOptions\n): Promise<CompleteUploadResponse> {\n const uploader = createUploader(file, options);\n return uploader.upload();\n}\n"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ export { ParsedUrl, buildUrl, getQueryParams, isAbsoluteUrl, joinUrl, normalizeU
|
|
|
8
8
|
export { HSL, RGB, contrast, darken, hexToRgb, hslToRgb, lighten, mix, rgbToHex, rgbToHsl } from './color.mjs';
|
|
9
9
|
export { CurrencyCode, Locale, TranslationDictionary, createTranslator, formatCurrencyI18n, formatDateI18n, formatNumberI18n, formatRelativeTime, getLocale, pluralize, translate } from './i18n.mjs';
|
|
10
10
|
export { calculateBlobMD5, calculateFileMD5, formatFileSize, getFileExtension, getFileNameWithoutExtension, splitFileIntoChunks } from './file.mjs';
|
|
11
|
-
export { A as ApiResponse, h as ChunkInfo, b as ChunkUploadResponse, C as ChunkUploader, d as CompleteUploadResponse, U as UploadInitRequest, a as UploadInitResponse, f as UploadOptions, e as UploadProgress, g as UploadStatus, c as createUploader } from './upload-
|
|
11
|
+
export { A as ApiResponse, h as ChunkInfo, b as ChunkUploadResponse, C as ChunkUploader, d as CompleteUploadResponse, U as UploadInitRequest, a as UploadInitResponse, f as UploadOptions, e as UploadProgress, g as UploadStatus, c as createUploader, u as uploadFile } from './upload-DchqyDBQ.mjs';
|
|
12
12
|
export { StorageOptions, cookie, getStorageKeyPair, localStorage, sessionStorage, setStorageKeyPair, storage } from './storage.mjs';
|
|
13
13
|
export { checkOnline, downloadFile, fetchWithRetry, fetchWithTimeout, request } from './network.mjs';
|
|
14
14
|
export { $, $$, addClass, copyToClipboard, getElementOffset, getScrollPosition, getStyle, isInViewport, removeClass, scrollTo, setStyle, toggleClass } from './dom.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { ParsedUrl, buildUrl, getQueryParams, isAbsoluteUrl, joinUrl, normalizeU
|
|
|
8
8
|
export { HSL, RGB, contrast, darken, hexToRgb, hslToRgb, lighten, mix, rgbToHex, rgbToHsl } from './color.js';
|
|
9
9
|
export { CurrencyCode, Locale, TranslationDictionary, createTranslator, formatCurrencyI18n, formatDateI18n, formatNumberI18n, formatRelativeTime, getLocale, pluralize, translate } from './i18n.js';
|
|
10
10
|
export { calculateBlobMD5, calculateFileMD5, formatFileSize, getFileExtension, getFileNameWithoutExtension, splitFileIntoChunks } from './file.js';
|
|
11
|
-
export { A as ApiResponse, h as ChunkInfo, b as ChunkUploadResponse, C as ChunkUploader, d as CompleteUploadResponse, U as UploadInitRequest, a as UploadInitResponse, f as UploadOptions, e as UploadProgress, g as UploadStatus, c as createUploader } from './upload-
|
|
11
|
+
export { A as ApiResponse, h as ChunkInfo, b as ChunkUploadResponse, C as ChunkUploader, d as CompleteUploadResponse, U as UploadInitRequest, a as UploadInitResponse, f as UploadOptions, e as UploadProgress, g as UploadStatus, c as createUploader, u as uploadFile } from './upload-DchqyDBQ.js';
|
|
12
12
|
export { StorageOptions, cookie, getStorageKeyPair, localStorage, sessionStorage, setStorageKeyPair, storage } from './storage.js';
|
|
13
13
|
export { checkOnline, downloadFile, fetchWithRetry, fetchWithTimeout, request } from './network.js';
|
|
14
14
|
export { $, $$, addClass, copyToClipboard, getElementOffset, getScrollPosition, getStyle, isInViewport, removeClass, scrollTo, setStyle, toggleClass } from './dom.js';
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var chunkJQZBPAPO_js = require('./chunk-JQZBPAPO.js');
|
|
|
6
6
|
var chunk56W6YECK_js = require('./chunk-56W6YECK.js');
|
|
7
7
|
var chunkY364QIQH_js = require('./chunk-Y364QIQH.js');
|
|
8
8
|
var chunkOX5PLOWB_js = require('./chunk-OX5PLOWB.js');
|
|
9
|
-
var
|
|
9
|
+
var chunkFEBKPX5A_js = require('./chunk-FEBKPX5A.js');
|
|
10
10
|
var chunk7V5UQXIO_js = require('./chunk-7V5UQXIO.js');
|
|
11
11
|
var chunkXVUE53T3_js = require('./chunk-XVUE53T3.js');
|
|
12
12
|
var chunkDYBSRI7V_js = require('./chunk-DYBSRI7V.js');
|
|
@@ -225,15 +225,19 @@ Object.defineProperty(exports, "translate", {
|
|
|
225
225
|
});
|
|
226
226
|
Object.defineProperty(exports, "ChunkUploader", {
|
|
227
227
|
enumerable: true,
|
|
228
|
-
get: function () { return
|
|
228
|
+
get: function () { return chunkFEBKPX5A_js.ChunkUploader; }
|
|
229
229
|
});
|
|
230
230
|
Object.defineProperty(exports, "UploadStatus", {
|
|
231
231
|
enumerable: true,
|
|
232
|
-
get: function () { return
|
|
232
|
+
get: function () { return chunkFEBKPX5A_js.UploadStatus; }
|
|
233
233
|
});
|
|
234
234
|
Object.defineProperty(exports, "createUploader", {
|
|
235
235
|
enumerable: true,
|
|
236
|
-
get: function () { return
|
|
236
|
+
get: function () { return chunkFEBKPX5A_js.createUploader; }
|
|
237
|
+
});
|
|
238
|
+
Object.defineProperty(exports, "uploadFile", {
|
|
239
|
+
enumerable: true,
|
|
240
|
+
get: function () { return chunkFEBKPX5A_js.uploadFile; }
|
|
237
241
|
});
|
|
238
242
|
Object.defineProperty(exports, "calculateBlobMD5", {
|
|
239
243
|
enumerable: true,
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ export { Queue, batch, debounce, memoize, once, retry, throttle, timeout } from
|
|
|
4
4
|
export { Tracker, createTracker, flush, getTracker, initTracker, setCommonParams, setUserInfo, trackClick, trackEvent, trackExposure, trackPageView } from './chunk-PJ7UCTX4.mjs';
|
|
5
5
|
export { contrast, darken, hexToRgb, hslToRgb, lighten, mix, rgbToHex, rgbToHsl } from './chunk-QIOC54LQ.mjs';
|
|
6
6
|
export { createTranslator, formatCurrencyI18n, formatDateI18n, formatNumberI18n, formatRelativeTime, getLocale, pluralize, translate } from './chunk-QIBE7GVN.mjs';
|
|
7
|
-
export { ChunkUploader, UploadStatus, createUploader } from './chunk-
|
|
7
|
+
export { ChunkUploader, UploadStatus, createUploader, uploadFile } from './chunk-NJARVI6X.mjs';
|
|
8
8
|
export { calculateBlobMD5, calculateFileMD5, formatFileSize, getFileExtension, getFileNameWithoutExtension, splitFileIntoChunks } from './chunk-ZVJ6NQUM.mjs';
|
|
9
9
|
export { cookie, getStorageKeyPair, localStorage, sessionStorage, setStorageKeyPair, storage } from './chunk-HME2N3VY.mjs';
|
|
10
10
|
export { base64Decode, base64Encode, exportPrivateKey, exportPublicKey, generateRSAKeyPair, generateRandomString, generateUUID, hash, importPrivateKey, importPublicKey, rsaDecrypt, rsaEncrypt, sha256 } from './chunk-HLDFI7R2.mjs';
|
|
@@ -111,7 +111,30 @@ interface ChunkInfo {
|
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* 文件分片上传工具类
|
|
114
|
-
*
|
|
114
|
+
* 支持分片上传、断点续传、进度追踪、暂停/恢复/取消
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* import { createUploader } from '@alibarbar/common/upload';
|
|
119
|
+
*
|
|
120
|
+
* const uploader = createUploader(file, {
|
|
121
|
+
* chunkSize: 2 * 1024 * 1024, // 2MB
|
|
122
|
+
* concurrency: 3,
|
|
123
|
+
* baseURL: 'https://api.example.com',
|
|
124
|
+
* onProgress: (progress) => {
|
|
125
|
+
* console.log(`进度:${progress.percentage}%`);
|
|
126
|
+
* },
|
|
127
|
+
* onComplete: (result) => {
|
|
128
|
+
* console.log('上传完成:', result.fileUrl);
|
|
129
|
+
* },
|
|
130
|
+
* onError: (error) => {
|
|
131
|
+
* console.error('上传失败:', error);
|
|
132
|
+
* }
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* // 开始上传
|
|
136
|
+
* await uploader.upload();
|
|
137
|
+
* ```
|
|
115
138
|
*/
|
|
116
139
|
|
|
117
140
|
/**
|
|
@@ -125,14 +148,13 @@ declare class ChunkUploader {
|
|
|
125
148
|
private uploadedChunks;
|
|
126
149
|
private status;
|
|
127
150
|
private abortController;
|
|
128
|
-
private initResponse;
|
|
129
151
|
constructor(file: File, options?: UploadOptions);
|
|
130
152
|
/**
|
|
131
153
|
* 初始化上传
|
|
132
154
|
*/
|
|
133
155
|
private initUpload;
|
|
134
156
|
/**
|
|
135
|
-
* 计算文件MD5
|
|
157
|
+
* 计算文件MD5
|
|
136
158
|
*/
|
|
137
159
|
private calculateFileMD5;
|
|
138
160
|
/**
|
|
@@ -140,7 +162,7 @@ declare class ChunkUploader {
|
|
|
140
162
|
*/
|
|
141
163
|
private prepareChunks;
|
|
142
164
|
/**
|
|
143
|
-
*
|
|
165
|
+
* 获取已上传的分片列表(用于断点续传)
|
|
144
166
|
*/
|
|
145
167
|
private getUploadedChunks;
|
|
146
168
|
/**
|
|
@@ -151,22 +173,18 @@ declare class ChunkUploader {
|
|
|
151
173
|
* 并发上传分片
|
|
152
174
|
*/
|
|
153
175
|
private uploadChunksConcurrently;
|
|
176
|
+
/**
|
|
177
|
+
* 更新上传进度(仅用于回调前端显示)
|
|
178
|
+
*/
|
|
179
|
+
private updateProgress;
|
|
154
180
|
/**
|
|
155
181
|
* 获取上传进度
|
|
156
|
-
* 当进度达到100%时,会自动调用完成接口
|
|
157
182
|
*/
|
|
158
183
|
private getUploadProgress;
|
|
159
184
|
/**
|
|
160
185
|
* 完成上传(调用后端合并分片接口)
|
|
161
|
-
*
|
|
162
|
-
* 只有在进度为 100% 时才会被调用
|
|
163
186
|
*/
|
|
164
187
|
private completeUpload;
|
|
165
|
-
/**
|
|
166
|
-
* 更新上传进度(用于回调前端显示)
|
|
167
|
-
* 每次上传完一个分片后调用
|
|
168
|
-
*/
|
|
169
|
-
private updateProgress;
|
|
170
188
|
/**
|
|
171
189
|
* 开始上传
|
|
172
190
|
*/
|
|
@@ -207,5 +225,12 @@ declare class ChunkUploader {
|
|
|
207
225
|
* @returns 上传器实例
|
|
208
226
|
*/
|
|
209
227
|
declare function createUploader(file: File, options?: UploadOptions): ChunkUploader;
|
|
228
|
+
/**
|
|
229
|
+
* 简单的文件上传函数(Promise风格)
|
|
230
|
+
* @param file - 文件对象
|
|
231
|
+
* @param options - 上传配置选项
|
|
232
|
+
* @returns Promise<CompleteUploadResponse>
|
|
233
|
+
*/
|
|
234
|
+
declare function uploadFile(file: File, options?: UploadOptions): Promise<CompleteUploadResponse>;
|
|
210
235
|
|
|
211
|
-
export { type ApiResponse as A, ChunkUploader as C, type UploadInitRequest as U, type UploadInitResponse as a, type ChunkUploadResponse as b, createUploader as c, type CompleteUploadResponse as d, type UploadProgress as e, type UploadOptions as f, UploadStatus as g, type ChunkInfo as h };
|
|
236
|
+
export { type ApiResponse as A, ChunkUploader as C, type UploadInitRequest as U, type UploadInitResponse as a, type ChunkUploadResponse as b, createUploader as c, type CompleteUploadResponse as d, type UploadProgress as e, type UploadOptions as f, UploadStatus as g, type ChunkInfo as h, uploadFile as u };
|
|
@@ -111,7 +111,30 @@ interface ChunkInfo {
|
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* 文件分片上传工具类
|
|
114
|
-
*
|
|
114
|
+
* 支持分片上传、断点续传、进度追踪、暂停/恢复/取消
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* import { createUploader } from '@alibarbar/common/upload';
|
|
119
|
+
*
|
|
120
|
+
* const uploader = createUploader(file, {
|
|
121
|
+
* chunkSize: 2 * 1024 * 1024, // 2MB
|
|
122
|
+
* concurrency: 3,
|
|
123
|
+
* baseURL: 'https://api.example.com',
|
|
124
|
+
* onProgress: (progress) => {
|
|
125
|
+
* console.log(`进度:${progress.percentage}%`);
|
|
126
|
+
* },
|
|
127
|
+
* onComplete: (result) => {
|
|
128
|
+
* console.log('上传完成:', result.fileUrl);
|
|
129
|
+
* },
|
|
130
|
+
* onError: (error) => {
|
|
131
|
+
* console.error('上传失败:', error);
|
|
132
|
+
* }
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* // 开始上传
|
|
136
|
+
* await uploader.upload();
|
|
137
|
+
* ```
|
|
115
138
|
*/
|
|
116
139
|
|
|
117
140
|
/**
|
|
@@ -125,14 +148,13 @@ declare class ChunkUploader {
|
|
|
125
148
|
private uploadedChunks;
|
|
126
149
|
private status;
|
|
127
150
|
private abortController;
|
|
128
|
-
private initResponse;
|
|
129
151
|
constructor(file: File, options?: UploadOptions);
|
|
130
152
|
/**
|
|
131
153
|
* 初始化上传
|
|
132
154
|
*/
|
|
133
155
|
private initUpload;
|
|
134
156
|
/**
|
|
135
|
-
* 计算文件MD5
|
|
157
|
+
* 计算文件MD5
|
|
136
158
|
*/
|
|
137
159
|
private calculateFileMD5;
|
|
138
160
|
/**
|
|
@@ -140,7 +162,7 @@ declare class ChunkUploader {
|
|
|
140
162
|
*/
|
|
141
163
|
private prepareChunks;
|
|
142
164
|
/**
|
|
143
|
-
*
|
|
165
|
+
* 获取已上传的分片列表(用于断点续传)
|
|
144
166
|
*/
|
|
145
167
|
private getUploadedChunks;
|
|
146
168
|
/**
|
|
@@ -151,22 +173,18 @@ declare class ChunkUploader {
|
|
|
151
173
|
* 并发上传分片
|
|
152
174
|
*/
|
|
153
175
|
private uploadChunksConcurrently;
|
|
176
|
+
/**
|
|
177
|
+
* 更新上传进度(仅用于回调前端显示)
|
|
178
|
+
*/
|
|
179
|
+
private updateProgress;
|
|
154
180
|
/**
|
|
155
181
|
* 获取上传进度
|
|
156
|
-
* 当进度达到100%时,会自动调用完成接口
|
|
157
182
|
*/
|
|
158
183
|
private getUploadProgress;
|
|
159
184
|
/**
|
|
160
185
|
* 完成上传(调用后端合并分片接口)
|
|
161
|
-
*
|
|
162
|
-
* 只有在进度为 100% 时才会被调用
|
|
163
186
|
*/
|
|
164
187
|
private completeUpload;
|
|
165
|
-
/**
|
|
166
|
-
* 更新上传进度(用于回调前端显示)
|
|
167
|
-
* 每次上传完一个分片后调用
|
|
168
|
-
*/
|
|
169
|
-
private updateProgress;
|
|
170
188
|
/**
|
|
171
189
|
* 开始上传
|
|
172
190
|
*/
|
|
@@ -207,5 +225,12 @@ declare class ChunkUploader {
|
|
|
207
225
|
* @returns 上传器实例
|
|
208
226
|
*/
|
|
209
227
|
declare function createUploader(file: File, options?: UploadOptions): ChunkUploader;
|
|
228
|
+
/**
|
|
229
|
+
* 简单的文件上传函数(Promise风格)
|
|
230
|
+
* @param file - 文件对象
|
|
231
|
+
* @param options - 上传配置选项
|
|
232
|
+
* @returns Promise<CompleteUploadResponse>
|
|
233
|
+
*/
|
|
234
|
+
declare function uploadFile(file: File, options?: UploadOptions): Promise<CompleteUploadResponse>;
|
|
210
235
|
|
|
211
|
-
export { type ApiResponse as A, ChunkUploader as C, type UploadInitRequest as U, type UploadInitResponse as a, type ChunkUploadResponse as b, createUploader as c, type CompleteUploadResponse as d, type UploadProgress as e, type UploadOptions as f, UploadStatus as g, type ChunkInfo as h };
|
|
236
|
+
export { type ApiResponse as A, ChunkUploader as C, type UploadInitRequest as U, type UploadInitResponse as a, type ChunkUploadResponse as b, createUploader as c, type CompleteUploadResponse as d, type UploadProgress as e, type UploadOptions as f, UploadStatus as g, type ChunkInfo as h, uploadFile as u };
|
package/dist/upload.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as ChunkUploader, c as createUploader } from './upload-
|
|
1
|
+
export { C as ChunkUploader, c as createUploader, u as uploadFile } from './upload-DchqyDBQ.mjs';
|
package/dist/upload.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as ChunkUploader, c as createUploader } from './upload-
|
|
1
|
+
export { C as ChunkUploader, c as createUploader, u as uploadFile } from './upload-DchqyDBQ.js';
|
package/dist/upload.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkFEBKPX5A_js = require('./chunk-FEBKPX5A.js');
|
|
4
4
|
require('./chunk-7V5UQXIO.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "ChunkUploader", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkFEBKPX5A_js.ChunkUploader; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "createUploader", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkFEBKPX5A_js.createUploader; }
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(exports, "uploadFile", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return chunkFEBKPX5A_js.uploadFile; }
|
|
15
19
|
});
|
|
16
20
|
//# sourceMappingURL=upload.js.map
|
|
17
21
|
//# sourceMappingURL=upload.js.map
|
package/dist/upload.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/upload.ts","../src/browser/upload.ts"],"names":["UploadStatus"],"mappings":"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","file":"chunk-5RNJSQYR.mjs","sourcesContent":["/**\n * 文件上传相关类型定义\n */\n\n/**\n * 上传初始化请求参数\n */\nexport interface UploadInitRequest {\n fileName: string;\n fileSize: number;\n fileMd5: string;\n chunkSize?: number;\n}\n\n/**\n * 上传初始化响应\n */\nexport interface UploadInitResponse {\n taskId: string;\n totalChunks: number;\n existingChunks: number[];\n instantUpload: boolean;\n fileUrl?: string;\n chunkSize: number;\n}\n\n/**\n * 分片上传响应\n */\nexport interface ChunkUploadResponse {\n chunkIndex: number;\n success: boolean;\n message: string;\n chunkMd5: string;\n}\n\n/**\n * 完成上传响应\n */\nexport interface CompleteUploadResponse {\n taskId: string;\n fileUrl: string;\n fileName: string;\n fileSize: number;\n fileMd5: string;\n success: boolean;\n message: string;\n}\n\n/**\n * 上传进度信息\n */\nexport interface UploadProgress {\n taskId: string;\n fileName: string;\n fileSize: number;\n totalChunks: number;\n uploadedChunks: number;\n percentage: number;\n status: string;\n uploadedSize: number;\n}\n\n/**\n * API响应包装类型\n */\nexport interface ApiResponse<T> {\n code: number;\n message: string;\n data: T;\n timestamp: number;\n}\n\n/**\n * 上传配置选项\n */\nexport interface UploadOptions {\n /** 分片大小(字节),默认 2MB */\n chunkSize?: number;\n /** 并发上传数量,默认 3 */\n concurrency?: number;\n /** 重试次数,默认 3 */\n retryCount?: number;\n /** 重试延迟(毫秒),默认 1000 */\n retryDelay?: number;\n /** 基础API地址 */\n baseURL?: string;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 上传进度回调 */\n onProgress?: (progress: UploadProgress) => void;\n /** 上传完成回调 */\n onComplete?: (result: CompleteUploadResponse) => void;\n /** 上传错误回调 */\n onError?: (error: Error) => void;\n}\n\n/**\n * 上传状态\n */\nexport enum UploadStatus {\n PENDING = 'pending',\n UPLOADING = 'uploading',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n FAILED = 'failed',\n CANCELLED = 'cancelled',\n}\n\n/**\n * 文件分片信息\n */\nexport interface ChunkInfo {\n index: number;\n start: number;\n end: number;\n blob: Blob;\n md5?: string;\n}\n","/**\n * 文件分片上传工具类\n * 支持分片上传、断点续传、进度追踪\n */\n\nimport {\n UploadOptions,\n UploadInitRequest,\n UploadInitResponse,\n ChunkUploadResponse,\n CompleteUploadResponse,\n UploadProgress,\n UploadStatus,\n ChunkInfo,\n ApiResponse,\n} from '../types/upload';\nimport { calculateBlobMD5, splitFileIntoChunks } from './file';\n\n/**\n * 文件上传器类\n */\nexport class ChunkUploader {\n private file: File;\n private options: Required<UploadOptions>;\n private taskId: string | null = null;\n private chunks: ChunkInfo[] = [];\n private uploadedChunks: Set<number> = new Set();\n private status: UploadStatus = UploadStatus.PENDING;\n private abortController: AbortController | null = null;\n private initResponse: UploadInitResponse | null = null;\n\n constructor(file: File, options: UploadOptions = {}) {\n this.file = file;\n this.options = {\n chunkSize: options.chunkSize || 2 * 1024 * 1024, // 默认2MB\n concurrency: options.concurrency || 3,\n retryCount: options.retryCount || 3,\n retryDelay: options.retryDelay || 1000,\n baseURL: options.baseURL || '',\n headers: options.headers || {},\n onProgress: options.onProgress || (() => {}),\n onComplete: options.onComplete || (() => {}),\n onError: options.onError || (() => {}),\n };\n }\n\n /**\n * 初始化上传\n */\n private async initUpload(): Promise<UploadInitResponse> {\n const fileMd5 = await this.calculateFileMD5();\n const request: UploadInitRequest = {\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5,\n chunkSize: this.options.chunkSize,\n };\n\n const response = await this.request<ApiResponse<UploadInitResponse>>('/api/files/common/init', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(request),\n });\n\n if (response.code !== 200) {\n throw new Error(response.message || '初始化上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 计算文件MD5(简化版,实际应该使用spark-md5等库)\n */\n private async calculateFileMD5(): Promise<string> {\n // 这里使用简化的方法,实际项目中应该使用spark-md5库\n const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n const md5Promises = chunks.map((chunk, index) =>\n calculateBlobMD5(chunk).then(md5 => ({ index, md5 }))\n );\n const md5Results = await Promise.all(md5Promises);\n // 简单拼接,实际应该使用正确的MD5算法\n return md5Results.map(r => r.md5).join('');\n }\n\n /**\n * 准备分片\n */\n private prepareChunks(): void {\n const blobChunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n this.chunks = blobChunks.map((blob, index) => ({\n index,\n start: index * this.options.chunkSize,\n end: Math.min((index + 1) * this.options.chunkSize, this.file.size),\n blob,\n }));\n }\n\n /**\n * 获取已上传的分片列表\n */\n private async getUploadedChunks(): Promise<number[]> {\n if (!this.taskId) return [];\n\n try {\n const response = await this.request<ApiResponse<number[]>>(\n `/api/files/common/chunks/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200) {\n return response.data || [];\n }\n } catch (error) {\n console.warn('获取已上传分片失败:', error);\n }\n\n return [];\n }\n\n /**\n * 上传单个分片\n */\n private async uploadChunk(chunkInfo: ChunkInfo, retryCount = 0): Promise<void> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n if (this.uploadedChunks.has(chunkInfo.index)) {\n return; // 已上传,跳过\n }\n\n try {\n // 计算分片MD5\n const chunkMd5 = await calculateBlobMD5(chunkInfo.blob);\n\n const formData = new FormData();\n formData.append('file', chunkInfo.blob, this.file.name);\n\n const url = `/api/files/common/chunk?taskId=${this.taskId}&chunkIndex=${chunkInfo.index}&chunkMd5=${chunkMd5}`;\n\n const response = await this.request<ApiResponse<ChunkUploadResponse>>(url, {\n method: 'POST',\n headers: this.options.headers,\n body: formData,\n });\n\n if (response.code === 200 && response.data.success) {\n this.uploadedChunks.add(chunkInfo.index);\n this.updateProgress();\n } else {\n throw new Error(response.message || '分片上传失败');\n }\n } catch (error) {\n if (retryCount < this.options.retryCount) {\n await this.delay(this.options.retryDelay);\n return this.uploadChunk(chunkInfo, retryCount + 1);\n }\n throw error;\n }\n }\n\n /**\n * 并发上传分片\n */\n private async uploadChunksConcurrently(): Promise<void> {\n const chunksToUpload = this.chunks.filter(chunk => !this.uploadedChunks.has(chunk.index));\n\n if (chunksToUpload.length === 0) {\n return; // 所有分片已上传\n }\n\n // 使用信号量控制并发数\n let currentIndex = 0;\n const semaphore = new Array(this.options.concurrency).fill(null);\n const allUploadPromises: Promise<void>[] = [];\n\n const uploadNext = async (): Promise<void> => {\n while (this.status === UploadStatus.UPLOADING) {\n // 原子性地获取下一个分片索引\n const chunkIndex = currentIndex++;\n if (chunkIndex >= chunksToUpload.length) {\n break; // 没有更多分片需要上传\n }\n\n const chunk = chunksToUpload[chunkIndex];\n // 创建上传 Promise 并添加到总数组中\n const uploadPromise = this.uploadChunk(chunk);\n allUploadPromises.push(uploadPromise);\n\n // 等待当前分片上传完成(这样可以控制并发数)\n await uploadPromise;\n }\n };\n\n // 启动并发上传任务(最多 concurrency 个并发)\n const concurrencyPromises = semaphore.map(() => uploadNext());\n\n // 等待所有并发任务完成\n await Promise.all(concurrencyPromises);\n\n // 确保所有上传 Promise 都已完成(双重保险)\n await Promise.all(allUploadPromises);\n\n // 最终验证所有分片都已上传\n const totalChunks = this.chunks.length;\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n const missingChunks = this.chunks\n .map((chunk, index) => (!this.uploadedChunks.has(index) ? index : null))\n .filter(index => index !== null);\n throw new Error(\n `上传未完成:已上传 ${uploadedCount}/${totalChunks} 个分片,缺失分片索引: ${missingChunks.join(', ')}`\n );\n }\n }\n\n /**\n * 获取上传进度\n * 当进度达到100%时,会自动调用完成接口\n */\n private async getUploadProgress(): Promise<UploadProgress | null> {\n if (!this.taskId) {\n return null;\n }\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n // 如果进度达到100%,调用完成接口(使用 >= 100 兼容浮点数)\n if (response.data.percentage >= 100) {\n await this.completeUpload();\n }\n return response.data;\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n\n return null;\n }\n\n /**\n * 完成上传(调用后端合并分片接口)\n *\n * 只有在进度为 100% 时才会被调用\n */\n private async completeUpload(): Promise<CompleteUploadResponse> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n // 防止重复调用\n if (this.status === UploadStatus.COMPLETED) {\n // 如果已经完成,返回之前保存的结果\n if (this.initResponse?.fileUrl) {\n return {\n taskId: this.taskId,\n fileUrl: this.initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '上传已完成',\n };\n }\n throw new Error('上传已完成但缺少文件信息');\n }\n\n const response = await this.request<ApiResponse<CompleteUploadResponse>>(\n `/api/files/common/complete/${this.taskId}`,\n {\n method: 'POST',\n headers: this.options.headers,\n }\n );\n\n if (response.code !== 200) {\n throw new Error(response.message || '完成上传失败');\n }\n\n // 更新状态并触发回调\n this.status = UploadStatus.COMPLETED;\n this.options.onComplete(response.data);\n\n return response.data;\n }\n\n /**\n * 更新上传进度(用于回调前端显示)\n * 每次上传完一个分片后调用\n */\n private async updateProgress(): Promise<void> {\n if (!this.taskId) return;\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n // 触发进度回调\n this.options.onProgress(response.data);\n\n // 如果进度达到100%,调用完成接口(使用 >= 100 兼容浮点数)\n if (response.data.percentage >= 100) {\n await this.completeUpload();\n }\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n }\n\n /**\n * 开始上传\n */\n async upload(): Promise<CompleteUploadResponse> {\n try {\n this.status = UploadStatus.UPLOADING;\n this.abortController = new AbortController();\n\n // 1. 初始化上传\n this.initResponse = await this.initUpload();\n this.taskId = this.initResponse.taskId;\n\n // 如果已经完成上传(秒传)\n if (this.initResponse.instantUpload && this.initResponse.fileUrl) {\n this.status = UploadStatus.COMPLETED;\n const result: CompleteUploadResponse = {\n taskId: this.taskId,\n fileUrl: this.initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '文件已存在,秒传成功',\n };\n this.options.onComplete(result);\n return result;\n }\n\n // 2. 准备分片\n this.prepareChunks();\n\n // 3. 获取已上传的分片(断点续传)\n const existingChunks = await this.getUploadedChunks();\n existingChunks.forEach(index => this.uploadedChunks.add(index));\n\n // 4. 上传分片\n await this.uploadChunksConcurrently();\n\n // 5. 本地验证所有分片都已上传\n const totalChunks = this.chunks.length;\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n throw new Error(\n `上传验证失败:已上传 ${uploadedCount}/${totalChunks} 个分片,无法完成上传`\n );\n }\n\n // 6. 本地计算进度\n const localPercentage =\n totalChunks === 0 ? 0 : Math.round((uploadedCount / totalChunks) * 100);\n\n // 7. 获取服务端最终上传进度\n // getUploadProgress内部会在进度为100%时自动调用completeUpload\n const progress = await this.getUploadProgress();\n\n // 8. 保底检查:如果getUploadProgress没有触发完成(比如网络错误或进度未达到100%)\n // 使用本地进度或服务端进度来判断\n const currentStatus = this.status as UploadStatus; // 明确类型,避免TS推断错误\n if (currentStatus === UploadStatus.COMPLETED) {\n // 已经在getUploadProgress或updateProgress中完成了\n return {\n taskId: this.taskId,\n fileUrl: this.initResponse?.fileUrl || '',\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '上传完成',\n };\n }\n\n // 如果状态还不是COMPLETED,使用本地或服务端进度判断\n const finalPercentage = progress?.percentage ?? localPercentage;\n\n if (finalPercentage >= 100) {\n // 进度显示100%但状态还未完成,手动调用完成接口\n const result = await this.completeUpload();\n return result;\n }\n\n // 进度未达到100%,抛出错误\n throw new Error(`上传未完成:当前进度 ${finalPercentage.toFixed(2)}%`);\n } catch (error) {\n this.status = UploadStatus.FAILED;\n const err = error instanceof Error ? error : new Error(String(error));\n this.options.onError(err);\n throw err;\n }\n }\n\n /**\n * 暂停上传\n */\n pause(): void {\n if (this.status === UploadStatus.UPLOADING) {\n this.status = UploadStatus.PAUSED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n }\n\n /**\n * 恢复上传\n */\n async resume(): Promise<CompleteUploadResponse> {\n if (this.status === UploadStatus.PAUSED) {\n return this.upload();\n }\n throw new Error('当前状态无法恢复上传');\n }\n\n /**\n * 取消上传\n */\n async cancel(): Promise<void> {\n if (this.taskId && this.status === UploadStatus.UPLOADING) {\n try {\n await this.request<ApiResponse<null>>(`/api/files/common/cancel/${this.taskId}`, {\n method: 'POST',\n headers: this.options.headers,\n });\n } catch (error) {\n console.warn('取消上传失败:', error);\n }\n }\n\n this.status = UploadStatus.CANCELLED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n\n /**\n * 获取当前状态\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * 获取任务ID\n */\n getTaskId(): string | null {\n return this.taskId;\n }\n\n /**\n * HTTP请求封装\n */\n private async request<T>(url: string, options: RequestInit = {}): Promise<T> {\n const fullUrl = `${this.options.baseURL}${url}`;\n const signal = this.abortController?.signal;\n\n const response = await fetch(fullUrl, {\n ...options,\n signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);\n }\n\n return response.json();\n }\n\n /**\n * 延迟函数\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * 创建文件上传器实例\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns 上传器实例\n */\nexport function createUploader(file: File, options?: UploadOptions): ChunkUploader {\n return new ChunkUploader(file, options);\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/upload.ts","../src/browser/upload.ts"],"names":["UploadStatus","splitFileIntoChunks","calculateBlobMD5"],"mappings":"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","file":"chunk-N5TZBSKF.js","sourcesContent":["/**\n * 文件上传相关类型定义\n */\n\n/**\n * 上传初始化请求参数\n */\nexport interface UploadInitRequest {\n fileName: string;\n fileSize: number;\n fileMd5: string;\n chunkSize?: number;\n}\n\n/**\n * 上传初始化响应\n */\nexport interface UploadInitResponse {\n taskId: string;\n totalChunks: number;\n existingChunks: number[];\n instantUpload: boolean;\n fileUrl?: string;\n chunkSize: number;\n}\n\n/**\n * 分片上传响应\n */\nexport interface ChunkUploadResponse {\n chunkIndex: number;\n success: boolean;\n message: string;\n chunkMd5: string;\n}\n\n/**\n * 完成上传响应\n */\nexport interface CompleteUploadResponse {\n taskId: string;\n fileUrl: string;\n fileName: string;\n fileSize: number;\n fileMd5: string;\n success: boolean;\n message: string;\n}\n\n/**\n * 上传进度信息\n */\nexport interface UploadProgress {\n taskId: string;\n fileName: string;\n fileSize: number;\n totalChunks: number;\n uploadedChunks: number;\n percentage: number;\n status: string;\n uploadedSize: number;\n}\n\n/**\n * API响应包装类型\n */\nexport interface ApiResponse<T> {\n code: number;\n message: string;\n data: T;\n timestamp: number;\n}\n\n/**\n * 上传配置选项\n */\nexport interface UploadOptions {\n /** 分片大小(字节),默认 2MB */\n chunkSize?: number;\n /** 并发上传数量,默认 3 */\n concurrency?: number;\n /** 重试次数,默认 3 */\n retryCount?: number;\n /** 重试延迟(毫秒),默认 1000 */\n retryDelay?: number;\n /** 基础API地址 */\n baseURL?: string;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 上传进度回调 */\n onProgress?: (progress: UploadProgress) => void;\n /** 上传完成回调 */\n onComplete?: (result: CompleteUploadResponse) => void;\n /** 上传错误回调 */\n onError?: (error: Error) => void;\n}\n\n/**\n * 上传状态\n */\nexport enum UploadStatus {\n PENDING = 'pending',\n UPLOADING = 'uploading',\n PAUSED = 'paused',\n COMPLETED = 'completed',\n FAILED = 'failed',\n CANCELLED = 'cancelled',\n}\n\n/**\n * 文件分片信息\n */\nexport interface ChunkInfo {\n index: number;\n start: number;\n end: number;\n blob: Blob;\n md5?: string;\n}\n","/**\n * 文件分片上传工具类\n * 支持分片上传、断点续传、进度追踪\n */\n\nimport {\n UploadOptions,\n UploadInitRequest,\n UploadInitResponse,\n ChunkUploadResponse,\n CompleteUploadResponse,\n UploadProgress,\n UploadStatus,\n ChunkInfo,\n ApiResponse,\n} from '../types/upload';\nimport { calculateBlobMD5, splitFileIntoChunks } from './file';\n\n/**\n * 文件上传器类\n */\nexport class ChunkUploader {\n private file: File;\n private options: Required<UploadOptions>;\n private taskId: string | null = null;\n private chunks: ChunkInfo[] = [];\n private uploadedChunks: Set<number> = new Set();\n private status: UploadStatus = UploadStatus.PENDING;\n private abortController: AbortController | null = null;\n private initResponse: UploadInitResponse | null = null;\n\n constructor(file: File, options: UploadOptions = {}) {\n this.file = file;\n this.options = {\n chunkSize: options.chunkSize || 2 * 1024 * 1024, // 默认2MB\n concurrency: options.concurrency || 3,\n retryCount: options.retryCount || 3,\n retryDelay: options.retryDelay || 1000,\n baseURL: options.baseURL || '',\n headers: options.headers || {},\n onProgress: options.onProgress || (() => {}),\n onComplete: options.onComplete || (() => {}),\n onError: options.onError || (() => {}),\n };\n }\n\n /**\n * 初始化上传\n */\n private async initUpload(): Promise<UploadInitResponse> {\n const fileMd5 = await this.calculateFileMD5();\n const request: UploadInitRequest = {\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5,\n chunkSize: this.options.chunkSize,\n };\n\n const response = await this.request<ApiResponse<UploadInitResponse>>('/api/files/common/init', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(request),\n });\n\n if (response.code !== 200) {\n throw new Error(response.message || '初始化上传失败');\n }\n\n return response.data;\n }\n\n /**\n * 计算文件MD5(简化版,实际应该使用spark-md5等库)\n */\n private async calculateFileMD5(): Promise<string> {\n // 这里使用简化的方法,实际项目中应该使用spark-md5库\n const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n const md5Promises = chunks.map((chunk, index) =>\n calculateBlobMD5(chunk).then(md5 => ({ index, md5 }))\n );\n const md5Results = await Promise.all(md5Promises);\n // 简单拼接,实际应该使用正确的MD5算法\n return md5Results.map(r => r.md5).join('');\n }\n\n /**\n * 准备分片\n */\n private prepareChunks(): void {\n const blobChunks = splitFileIntoChunks(this.file, this.options.chunkSize);\n this.chunks = blobChunks.map((blob, index) => ({\n index,\n start: index * this.options.chunkSize,\n end: Math.min((index + 1) * this.options.chunkSize, this.file.size),\n blob,\n }));\n }\n\n /**\n * 获取已上传的分片列表\n */\n private async getUploadedChunks(): Promise<number[]> {\n if (!this.taskId) return [];\n\n try {\n const response = await this.request<ApiResponse<number[]>>(\n `/api/files/common/chunks/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200) {\n return response.data || [];\n }\n } catch (error) {\n console.warn('获取已上传分片失败:', error);\n }\n\n return [];\n }\n\n /**\n * 上传单个分片\n */\n private async uploadChunk(chunkInfo: ChunkInfo, retryCount = 0): Promise<void> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n if (this.uploadedChunks.has(chunkInfo.index)) {\n return; // 已上传,跳过\n }\n\n try {\n // 计算分片MD5\n const chunkMd5 = await calculateBlobMD5(chunkInfo.blob);\n\n const formData = new FormData();\n formData.append('file', chunkInfo.blob, this.file.name);\n\n const url = `/api/files/common/chunk?taskId=${this.taskId}&chunkIndex=${chunkInfo.index}&chunkMd5=${chunkMd5}`;\n\n const response = await this.request<ApiResponse<ChunkUploadResponse>>(url, {\n method: 'POST',\n headers: this.options.headers,\n body: formData,\n });\n\n if (response.code === 200 && response.data.success) {\n this.uploadedChunks.add(chunkInfo.index);\n this.updateProgress();\n } else {\n throw new Error(response.message || '分片上传失败');\n }\n } catch (error) {\n if (retryCount < this.options.retryCount) {\n await this.delay(this.options.retryDelay);\n return this.uploadChunk(chunkInfo, retryCount + 1);\n }\n throw error;\n }\n }\n\n /**\n * 并发上传分片\n */\n private async uploadChunksConcurrently(): Promise<void> {\n const chunksToUpload = this.chunks.filter(chunk => !this.uploadedChunks.has(chunk.index));\n\n if (chunksToUpload.length === 0) {\n return; // 所有分片已上传\n }\n\n // 使用信号量控制并发数\n let currentIndex = 0;\n const semaphore = new Array(this.options.concurrency).fill(null);\n const allUploadPromises: Promise<void>[] = [];\n\n const uploadNext = async (): Promise<void> => {\n while (this.status === UploadStatus.UPLOADING) {\n // 原子性地获取下一个分片索引\n const chunkIndex = currentIndex++;\n if (chunkIndex >= chunksToUpload.length) {\n break; // 没有更多分片需要上传\n }\n\n const chunk = chunksToUpload[chunkIndex];\n // 创建上传 Promise 并添加到总数组中\n const uploadPromise = this.uploadChunk(chunk);\n allUploadPromises.push(uploadPromise);\n\n // 等待当前分片上传完成(这样可以控制并发数)\n await uploadPromise;\n }\n };\n\n // 启动并发上传任务(最多 concurrency 个并发)\n const concurrencyPromises = semaphore.map(() => uploadNext());\n\n // 等待所有并发任务完成\n await Promise.all(concurrencyPromises);\n\n // 确保所有上传 Promise 都已完成(双重保险)\n await Promise.all(allUploadPromises);\n\n // 最终验证所有分片都已上传\n const totalChunks = this.chunks.length;\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n const missingChunks = this.chunks\n .map((chunk, index) => (!this.uploadedChunks.has(index) ? index : null))\n .filter(index => index !== null);\n throw new Error(\n `上传未完成:已上传 ${uploadedCount}/${totalChunks} 个分片,缺失分片索引: ${missingChunks.join(', ')}`\n );\n }\n }\n\n /**\n * 获取上传进度\n * 当进度达到100%时,会自动调用完成接口\n */\n private async getUploadProgress(): Promise<UploadProgress | null> {\n if (!this.taskId) {\n return null;\n }\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n // 如果进度达到100%,调用完成接口(使用 >= 100 兼容浮点数)\n if (response.data.percentage >= 100) {\n await this.completeUpload();\n }\n return response.data;\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n\n return null;\n }\n\n /**\n * 完成上传(调用后端合并分片接口)\n *\n * 只有在进度为 100% 时才会被调用\n */\n private async completeUpload(): Promise<CompleteUploadResponse> {\n if (!this.taskId) {\n throw new Error('任务ID不存在');\n }\n\n // 防止重复调用\n if (this.status === UploadStatus.COMPLETED) {\n // 如果已经完成,返回之前保存的结果\n if (this.initResponse?.fileUrl) {\n return {\n taskId: this.taskId,\n fileUrl: this.initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '上传已完成',\n };\n }\n throw new Error('上传已完成但缺少文件信息');\n }\n\n const response = await this.request<ApiResponse<CompleteUploadResponse>>(\n `/api/files/common/complete/${this.taskId}`,\n {\n method: 'POST',\n headers: this.options.headers,\n }\n );\n\n if (response.code !== 200) {\n throw new Error(response.message || '完成上传失败');\n }\n\n // 更新状态并触发回调\n this.status = UploadStatus.COMPLETED;\n this.options.onComplete(response.data);\n\n return response.data;\n }\n\n /**\n * 更新上传进度(用于回调前端显示)\n * 每次上传完一个分片后调用\n */\n private async updateProgress(): Promise<void> {\n if (!this.taskId) return;\n\n try {\n const response = await this.request<ApiResponse<UploadProgress>>(\n `/api/files/common/progress/${this.taskId}`,\n {\n method: 'GET',\n headers: this.options.headers,\n }\n );\n\n if (response.code === 200 && response.data) {\n // 触发进度回调\n this.options.onProgress(response.data);\n\n // 如果进度达到100%,调用完成接口(使用 >= 100 兼容浮点数)\n if (response.data.percentage >= 100) {\n await this.completeUpload();\n }\n }\n } catch (error) {\n console.warn('获取上传进度失败:', error);\n }\n }\n\n /**\n * 开始上传\n */\n async upload(): Promise<CompleteUploadResponse> {\n try {\n this.status = UploadStatus.UPLOADING;\n this.abortController = new AbortController();\n\n // 1. 初始化上传\n this.initResponse = await this.initUpload();\n this.taskId = this.initResponse.taskId;\n\n // 如果已经完成上传(秒传)\n if (this.initResponse.instantUpload && this.initResponse.fileUrl) {\n this.status = UploadStatus.COMPLETED;\n const result: CompleteUploadResponse = {\n taskId: this.taskId,\n fileUrl: this.initResponse.fileUrl,\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '文件已存在,秒传成功',\n };\n this.options.onComplete(result);\n return result;\n }\n\n // 2. 准备分片\n this.prepareChunks();\n\n // 3. 获取已上传的分片(断点续传)\n const existingChunks = await this.getUploadedChunks();\n existingChunks.forEach(index => this.uploadedChunks.add(index));\n\n // 4. 上传分片\n await this.uploadChunksConcurrently();\n\n // 5. 本地验证所有分片都已上传\n const totalChunks = this.chunks.length;\n const uploadedCount = this.uploadedChunks.size;\n if (uploadedCount < totalChunks) {\n throw new Error(\n `上传验证失败:已上传 ${uploadedCount}/${totalChunks} 个分片,无法完成上传`\n );\n }\n\n // 6. 本地计算进度\n const localPercentage =\n totalChunks === 0 ? 0 : Math.round((uploadedCount / totalChunks) * 100);\n\n // 7. 获取服务端最终上传进度\n // getUploadProgress内部会在进度为100%时自动调用completeUpload\n const progress = await this.getUploadProgress();\n\n // 8. 保底检查:如果getUploadProgress没有触发完成(比如网络错误或进度未达到100%)\n // 使用本地进度或服务端进度来判断\n const currentStatus = this.status as UploadStatus; // 明确类型,避免TS推断错误\n if (currentStatus === UploadStatus.COMPLETED) {\n // 已经在getUploadProgress或updateProgress中完成了\n return {\n taskId: this.taskId,\n fileUrl: this.initResponse?.fileUrl || '',\n fileName: this.file.name,\n fileSize: this.file.size,\n fileMd5: '',\n success: true,\n message: '上传完成',\n };\n }\n\n // 如果状态还不是COMPLETED,使用本地或服务端进度判断\n const finalPercentage = progress?.percentage ?? localPercentage;\n\n if (finalPercentage >= 100) {\n // 进度显示100%但状态还未完成,手动调用完成接口\n const result = await this.completeUpload();\n return result;\n }\n\n // 进度未达到100%,抛出错误\n throw new Error(`上传未完成:当前进度 ${finalPercentage.toFixed(2)}%`);\n } catch (error) {\n this.status = UploadStatus.FAILED;\n const err = error instanceof Error ? error : new Error(String(error));\n this.options.onError(err);\n throw err;\n }\n }\n\n /**\n * 暂停上传\n */\n pause(): void {\n if (this.status === UploadStatus.UPLOADING) {\n this.status = UploadStatus.PAUSED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n }\n\n /**\n * 恢复上传\n */\n async resume(): Promise<CompleteUploadResponse> {\n if (this.status === UploadStatus.PAUSED) {\n return this.upload();\n }\n throw new Error('当前状态无法恢复上传');\n }\n\n /**\n * 取消上传\n */\n async cancel(): Promise<void> {\n if (this.taskId && this.status === UploadStatus.UPLOADING) {\n try {\n await this.request<ApiResponse<null>>(`/api/files/common/cancel/${this.taskId}`, {\n method: 'POST',\n headers: this.options.headers,\n });\n } catch (error) {\n console.warn('取消上传失败:', error);\n }\n }\n\n this.status = UploadStatus.CANCELLED;\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n\n /**\n * 获取当前状态\n */\n getStatus(): UploadStatus {\n return this.status;\n }\n\n /**\n * 获取任务ID\n */\n getTaskId(): string | null {\n return this.taskId;\n }\n\n /**\n * HTTP请求封装\n */\n private async request<T>(url: string, options: RequestInit = {}): Promise<T> {\n const fullUrl = `${this.options.baseURL}${url}`;\n const signal = this.abortController?.signal;\n\n const response = await fetch(fullUrl, {\n ...options,\n signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);\n }\n\n return response.json();\n }\n\n /**\n * 延迟函数\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * 创建文件上传器实例\n * @param file - 文件对象\n * @param options - 上传配置选项\n * @returns 上传器实例\n */\nexport function createUploader(file: File, options?: UploadOptions): ChunkUploader {\n return new ChunkUploader(file, options);\n}\n"]}
|