135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
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();
|
||
}
|
||
});
|
||
};
|