2025-05-30 21:36:12 +08:00

135 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { randomId } from '../utils/random-id.ts';
import { UploadProgress } from './upload-progress.ts';
export type ConvertOpts = {
appKey?: string;
version?: string;
username?: string;
directory?: string;
isPublic?: boolean;
filename?: string;
/**
* 是否不检查应用文件, 默认 true默认不检测
*/
noCheckAppFiles?: boolean;
};
// createEventSource: (baseUrl: string, searchParams: URLSearchParams) => {
// return new EventSource(baseUrl + '/api/s1/events?' + searchParams.toString());
// },
export type UploadOpts = {
uploadProgress: UploadProgress;
/**
* 创建 EventSource 兼容 nodejs
* @param baseUrl 基础 URL
* @param searchParams 查询参数
* @returns EventSource
*/
createEventSource: (baseUrl: string, searchParams: URLSearchParams) => EventSource;
baseUrl?: string;
token: string;
FormDataFn: any;
};
export const uploadFileChunked = async (file: File, opts: ConvertOpts, opts2: UploadOpts) => {
const { directory, appKey, version, username, isPublic, noCheckAppFiles = true } = opts;
const { uploadProgress, createEventSource, baseUrl = '', token, FormDataFn } = opts2 || {};
return new Promise(async (resolve, reject) => {
const taskId = randomId();
const filename = opts.filename || file.name;
uploadProgress?.start(`${filename} 上传中...`);
const searchParams = new URLSearchParams();
searchParams.set('taskId', taskId);
if (isPublic) {
searchParams.set('public', 'true');
}
if (noCheckAppFiles) {
searchParams.set('noCheckAppFiles', '1');
}
const eventSource = createEventSource(baseUrl + '/api/s1/events', searchParams);
let isError = false;
// 监听服务器推送的进度更新
eventSource.onmessage = function (event) {
console.log('Progress update:', event.data);
const parseIfJson = (data: string) => {
try {
return JSON.parse(data);
} catch (e) {
return data;
}
};
const receivedData = parseIfJson(event.data);
if (typeof receivedData === 'string') return;
const progress = Number(receivedData.progress);
const progressFixed = progress.toFixed(2);
uploadProgress?.set(progress, { ...receivedData, progressFixed, filename, taskId });
};
eventSource.onerror = function (event) {
console.log('eventSource.onerror', event);
isError = true;
reject(event);
};
const chunkSize = 1 * 1024 * 1024; // 1MB
const totalChunks = Math.ceil(file.size / chunkSize);
for (let currentChunk = 0; currentChunk < totalChunks; currentChunk++) {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormDataFn();
formData.append('file', chunk, filename);
formData.append('chunkIndex', currentChunk.toString());
formData.append('totalChunks', totalChunks.toString());
const isLast = currentChunk === totalChunks - 1;
if (directory) {
formData.append('directory', directory);
}
if (appKey && version) {
formData.append('appKey', appKey);
formData.append('version', version);
}
if (username) {
formData.append('username', username);
}
try {
const res = await fetch(baseUrl + '/api/s1/resources/upload/chunk?taskId=' + taskId, {
method: 'POST',
body: formData,
headers: {
'task-id': taskId,
Authorization: `Bearer ${token}`,
},
}).then((response) => response.json());
if (res?.code !== 200) {
console.log('uploadChunk error', res);
uploadProgress?.error(res?.message || '上传失败');
isError = true;
eventSource.close();
uploadProgress?.done();
reject(new Error(res?.message || '上传失败'));
return;
}
if (isLast) {
fetch(baseUrl + '/api/s1/events/close?taskId=' + taskId);
eventSource.close();
uploadProgress?.done();
resolve(res);
}
// console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
} catch (error) {
console.log('Error uploading chunk', error);
fetch(baseUrl + '/api/s1/events/close?taskId=' + taskId);
reject(error);
return;
}
}
// 循环结束
if (!uploadProgress?.end) {
uploadProgress?.done();
}
});
};