大文件上传组件支持分片上传、断点续传、进度追踪等功能,可以高效地上传大文件。
拖拽文件到此处或
import { FileUploader } from '@enterprise-ui/react19';
<FileUploader
uploadUrl="/api/upload"
onProgress={(progress, file) => {
console.log(`${file.name}: ${progress}%`);
}}
onSuccess={(file, response) => {
console.log('上传成功:', response);
}}
onError={(file, error) => {
console.error('上传失败:', error);
}}
/>可以通过 chunkSize 和 concurrency 属性配置分片大小和并发数量。
拖拽文件到此处或
<FileUploader
uploadUrl="/api/upload"
chunkSize={5 * 1024 * 1024} // 5MB 分片
concurrency={5} // 5个并发
/>可以通过 maxSize 属性限制文件大小。
拖拽文件到此处或
<FileUploader
uploadUrl="/api/upload"
maxSize={100 * 1024 * 1024} // 最大 100MB
/>设置 multiple 属性支持多文件上传。
拖拽文件到此处或
<FileUploader
uploadUrl="/api/upload"
multiple // 支持多文件
/>可以通过 accept 属性限制文件类型。
拖拽文件到此处或
<FileUploader
uploadUrl="/api/upload"
accept="image/*" // 只接受图片
/>大文件上传的核心思想是将文件分割成多个小分片,每个分片独立上传。这样可以:
// 1. 计算分片数量
const chunkSize = 2 * 1024 * 1024; // 2MB
const totalChunks = Math.ceil(file.size / chunkSize);
// 2. 创建分片数组
const chunks: ChunkInfo[] = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
chunks.push({
index: i,
blob: file.slice(start, end), // Blob.slice API
uploaded: false,
});
}Math.min() 确保不超出文件大小每个文件需要唯一标识,用于服务端识别和断点续传。使用 SHA-256 哈希算法生成。
async function generateFileId(file: File): Promise<string> {
// 使用文件名 + 大小 + 修改时间生成唯一标识
const data = `${file.name}-${file.size}-${file.lastModified}`;
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
// SHA-256 哈希
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
// 转换为十六进制字符串
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}并发控制是平衡上传速度和服务器压力的关键。使用队列 + Promise 实现。
// 并发上传实现
async function uploadFile(file: File, chunks: ChunkInfo[]) {
const concurrency = 3; // 并发数
const uploadQueue: Promise<void>[] = [];
let uploadedCount = 0;
// 递归上传函数
const uploadNext = async () => {
// 找到下一个未上传的分片
const pendingChunk = chunks.find(chunk => !chunk.uploaded);
if (!pendingChunk) return;
try {
// 上传分片
await uploadChunk(file, pendingChunk);
pendingChunk.uploaded = true;
uploadedCount++;
// 更新进度
updateProgress((uploadedCount / chunks.length) * 100);
// 继续上传下一个
if (uploadedCount < chunks.length) {
await uploadNext();
} else {
// 所有分片上传完成,合并文件
await mergeChunks(file);
}
} catch (error) {
// 错误重试(简化版)
await uploadNext();
}
};
// 启动并发上传
for (let i = 0; i < Math.min(concurrency, chunks.length); i++) {
uploadQueue.push(uploadNext());
}
// 等待所有上传完成
await Promise.all(uploadQueue);
}断点续传的核心是:服务端记录已上传的分片,客户端只上传未完成的分片。
// 1. 生成文件唯一标识
const fileId = await generateFileId(file);
// 2. 检查已上传分片(服务端接口)
const response = await fetch(`/api/upload/check?fileId=${fileId}`);
const { uploadedChunks } = await response.json();
// uploadedChunks: [0, 1, 2, 5, 6] // 已上传的分片索引
// 3. 标记已上传的分片
chunks.forEach((chunk, index) => {
if (uploadedChunks.includes(index)) {
chunk.uploaded = true;
}
});
// 4. 只上传未完成的分片
const chunksToUpload = chunks.filter(chunk => !chunk.uploaded);
// 5. 继续上传
await uploadChunks(chunksToUpload);进度计算需要考虑分片上传的异步特性,确保进度准确反映实际上传情况。
// 精确的进度计算
let uploadedBytes = 0;
const totalBytes = file.size;
chunks.forEach((chunk, index) => {
if (chunk.uploaded) {
uploadedBytes += chunk.blob.size;
}
});
// 计算百分比
const progress = Math.round((uploadedBytes / totalBytes) * 100);
// 或者使用分片数量(更简单)
const progress = Math.round((uploadedCount / totalChunks) * 100);// 指数退避重试
async function uploadChunkWithRetry(
chunk: ChunkInfo,
maxRetries = 3
): Promise<void> {
let retries = 0;
while (retries < maxRetries) {
try {
await uploadChunk(chunk);
return; // 成功则返回
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw error; // 达到最大重试次数,抛出错误
}
// 指数退避:1s, 2s, 4s
const delay = Math.pow(2, retries) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}所有分片上传完成后,需要通知服务端合并文件。
// 1. 所有分片上传完成
if (uploadedCount === totalChunks) {
// 2. 通知服务端合并
const response = await fetch('/api/upload/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileId: fileId,
fileName: file.name,
totalChunks: totalChunks,
}),
});
// 3. 服务端合并逻辑(伪代码)
// - 按索引顺序读取所有分片
// - 合并成完整文件
// - 保存到最终位置
// - 删除临时分片
// - 返回文件访问 URL
}大文件场景下,文件哈希(MD5/SHA)、分片时的读取与计算等属于 CPU 密集操作, 若在主线程执行会阻塞 UI,导致进度条卡顿、页面无响应。使用 Web Worker 把这类计算放到子线程,主线程只负责 UI 更新和网络请求,体验更流畅。
file.size 和 chunkSize 计算分片索引、起止位置等纯计算,可放 Worker 减少主线程压力(若与 hash 同处可一并做)。fetch/XMLHttpRequest 在主线程更易与现有上传逻辑配合。File.slice() 得到 Blob 通常在主线程即可;若后端要求「按分片先算 hash 再上传」,则 hash 在 Worker 算,主线程只拿结果去请求。ArrayBuffer(FileReader.readAsArrayBuffer 读出的分片)或分片索引 + 配置,由 Worker 计算该分片的 hash。{ chunkIndex, hash } 或全量 hash;主线程把 hash 填入上传参数或用于秒传判断。Worker 的环境(如部分老旧浏览器):可在主线程同步计算 hash(仅建议小文件),或跳过 hash 仅用分片上传 + 合并,并提示「秒传与完整性校验不可用」。window、document,只做纯计算;哈希库需选支持 Worker 的(如 Web Crypto API 或可在 Worker 里跑的 MD5/SHA 实现)。// 主线程:读取分片,交给 Worker 算 hash
const buffer = await file.slice(start, end).arrayBuffer();
worker.postMessage({ type: 'hash', chunkIndex: i, buffer }, [buffer]);
// Worker 内:计算 hash 后回传
self.onmessage = async (e) => {
const { chunkIndex, buffer } = e.data;
const hash = await computeHash(buffer); // 使用 Web Crypto 或 MD5/SHA 库
self.postMessage({ chunkIndex, hash });
};
// 主线程:收到 hash 后上传该分片(或先汇总再上传)
worker.onmessage = (e) => {
const { chunkIndex, hash } = e.data;
uploadChunkWithHash(chunkIndex, hash);
};总结:大文件上传文档里把「Web Worker」写清楚,能体现你在性能与体验上的考虑:哈希与重计算不阻塞主线程,进度条和交互保持流畅,同时为秒传和校验留好扩展点。
大文件会被分割成多个小分片(默认 2MB),每个分片独立上传。
通过队列控制同时上传的分片数量,避免服务器压力过大。默认并发数为 3。
每个文件都有唯一标识(基于文件名、大小、修改时间生成),服务端记录已上传的分片。 上传失败后,只上传未完成的分片。
根据已上传的分片数量计算整体进度,实时更新进度条。
组件需要服务端提供以下接口:
POST /api/upload
FormData:
- file: Blob (分片文件)
- chunkIndex: number (分片索引)
- totalChunks: number (总分片数)
- fileId: string (文件唯一标识)
- fileName: string (文件名)
- fileSize: number (文件大小)
Response:
{
success: boolean,
chunkIndex: number
}POST /api/upload/merge
Body:
{
fileId: string,
fileName: string,
totalChunks: number
}
Response:
{
success: boolean,
url: string // 文件访问地址
}| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| uploadUrl | 文件上传地址 | string | - |
| chunkSize | 分片大小(字节) | number | 2 * 1024 * 1024 |
| concurrency | 并发上传数量 | number | 3 |
| multiple | 是否支持多文件 | boolean | false |
| accept | 接受的文件类型 | string | - |
| maxSize | 最大文件大小(字节) | number | - |
| onProgress | 上传进度回调 | (progress: number, file: File) => void | - |
| onSuccess | 上传成功回调 | (file: File, response: any) => void | - |
| onError | 上传失败回调 | (file: File, error: Error) => void | - |
| className | 自定义类名 | string | - |
chunkSize 和 concurrency'use client',需要在客户端组件中使用