@zxhsure/file-uploader
Version:
A chunked file uploader with customizable upload functions
1,067 lines (869 loc) • 32.3 kB
Markdown
一个功能强大的大文件切片上传 JavaScript 库,支持断点续传、并发控制、进度监控和真实内容 Hash 生成。
- 🚀 **大文件切片上传** - 支持任意大小文件的分片上传
- 🎯 **可自定义配置** - 灵活配置chunk大小、并发数等参数
- 🔧 **自定义上传函数** - 完全自定义上传逻辑,适配任何后端API
- 📊 **实时进度监控** - 详细的上传进度回调
- 🛡️ **真实内容Hash** - 基于SHA-256的文件块内容hash,确保数据完整性
- 🔄 **断点续传支持** - 通过hash值支持断点续传逻辑
- 🚦 **并发控制** - 内置信号量机制控制并发上传数量
- 📦 **零依赖** - 纯原生JavaScript实现,无外部依赖
- 🌐 **浏览器兼容** - 支持现代浏览器的Web Crypto API
```bash
npm install @zxhsure/file-uploader
```
在开始使用之前,请注意以下关键点:
1. **📤 上传函数配置**:
- 库提供了默认的上传函数,但仅用于演示
- **生产环境必须配置自定义的 `uploadFunction`** 来适配你的后端 API
- 默认函数会向 `/api/upload/chunk` 发送 POST 请求
2. **🔗 文件合并**:
- `mergeChunks()` 方法是否必需取决于你的后端实现
- 如果后端需要明确的合并指令,则必须调用此方法
- 建议即使后端自动合并也调用此方法来确认状态
3. **🌐 API 兼容性**:
- 需要现代浏览器支持 Web Crypto API (用于生成真实 hash)
- 如果不支持,会自动降级到简单 hash 生成
## 🚀 快速开始
```javascript
import FileUploader from '@zxhsure/file-uploader';
const uploader = new FileUploader({
chunkSize: 2 * 1024 * 1024, // 2MB chunks
concurrent: 5, // 5个并发上传
// ⚠️ 重要:必须配置适合你后端API的上传函数
uploadFunction: async (chunkData) => {
const formData = new FormData();
formData.append('file', chunkData.chunk);
formData.append('index', chunkData.index);
formData.append('hash', chunkData.hash);
formData.append('fileName', chunkData.fileName);
formData.append('totalChunks', chunkData.totalChunks);
const response = await fetch('https://your-api.com/upload/chunk', {
method: 'POST',
headers: {
'Authorization': 'Bearer your-token' // 根据需要添加认证
},
body: formData
});
if (!response.ok) {
throw new Error(`上传失败: ${response.statusText}`);
}
return await response.json();
},
onProgress: (info) => {
console.log(`上传进度: ${info.progress.toFixed(2)}%`);
},
onComplete: (info) => {
console.log(`文件 ${info.fileName} 上传完成!`);
}
});
// 上传文件
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await uploader.upload(file);
console.log('上传成功:', result);
// 📝 如果后端需要合并步骤,在这里调用合并
// await uploader.mergeChunks(file.name, result.results.length, mergeFn);
} catch (error) {
console.error('上传失败:', error);
}
}
});
```
> **🔍 注意事项:**
> - **uploadFunction 是关键**:默认实现仅用于演示,生产环境必须自定义
> - **API 适配**:根据你的后端 API 调整 FormData 字段名和请求结构
> - **认证处理**:添加必要的认证头或参数
> - **错误处理**:确保正确处理 HTTP 错误状态
创建一个新的文件上传器实例。
**参数 (options):**
| 参数 | 类型 | 默认值 | 必填 | 描述 |
|------|------|--------|------|------|
| `chunkSize` | `number` | `1024 * 1024` (1MB) | ❌ | 每个文件块的大小(字节) |
| `concurrent` | `number` | `3` | ❌ | 同时上传的块数量 |
| `uploadFunction` | `function` | `defaultUploadFunction` | ⚠️ | 自定义上传函数,**强烈建议自定义** |
| `onProgress` | `function` | `() => {}` | ❌ | 进度更新回调函数 |
| `onError` | `function` | `() => {}` | ❌ | 错误处理回调函数 |
| `onComplete` | `function` | `() => {}` | ❌ | 上传完成回调函数 |
> **⚠️ 重要提示:**
> - **uploadFunction**: 虽然有默认实现,但默认函数只是向 `/api/upload/chunk` 发送 POST 请求,**实际使用中几乎总是需要根据你的后端 API 进行自定义**
> - **默认上传函数**:仅用于测试和演示,生产环境请务必提供自定义的上传函数
**示例:**
```javascript
const uploader = new FileUploader({
chunkSize: 5 * 1024 * 1024, // 5MB
concurrent: 3,
// 自定义上传函数
uploadFunction: async (chunkData) => {
const formData = new FormData();
formData.append('file', chunkData.chunk);
formData.append('index', chunkData.index);
formData.append('hash', chunkData.hash);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return await response.json();
},
// 进度回调
onProgress: ({ progress, completedChunks, totalChunks }) => {
const progressBar = document.getElementById('progressBar');
progressBar.style.width = `${progress}%`;
progressBar.textContent = `${completedChunks}/${totalChunks} chunks`;
},
// 错误处理
onError: ({ error, chunk, fileName }) => {
console.error(`文件 ${fileName} 的第 ${chunk.index} 块上传失败:`, error);
// 可以在这里实现重试逻辑
},
// 完成回调
onComplete: ({ fileName, fileSize, totalChunks, results }) => {
console.log(`文件 ${fileName} 上传完成!`);
console.log(`文件大小: ${fileSize} bytes`);
console.log(`总块数: ${totalChunks}`);
// 触发合并请求等后续操作
}
});
```
---
将文件分割成多个块,并为每个块生成基于内容的SHA-256哈希值。
**参数:**
- `file` (`File`): 要分割的文件对象
**返回值:**
```javascript
Promise<{
chunks: Array<{
index: number, // 块索引 (从0开始)
chunk: Blob, // 文件块数据
start: number, // 在原文件中的起始位置
end: number, // 在原文件中的结束位置
size: number, // 块大小
hash: string // SHA-256哈希值 (格式: fileName_fileSize_index_sha256hex)
}>,
totalChunks: number, // 总块数
fileInfo: {
name: string, // 文件名
size: number, // 文件大小
type: string, // 文件类型
lastModified: number // 最后修改时间
}
}>
```
**使用场景:**
1. **预分析文件结构** - 在正式上传前分析文件将被分成多少块
2. **断点续传准备** - 生成文件块信息,检查哪些块已经上传
3. **分离关注点** - 将文件分块逻辑与上传逻辑分开
**示例:**
```javascript
// 场景1: 预分析文件,显示将要上传的块信息
async function analyzeFile(file) {
try {
const { chunks, totalChunks, fileInfo } = await uploader.generateChunks(file);
console.log(`文件: ${fileInfo.name}`);
console.log(`大小: ${fileInfo.size} bytes`);
console.log(`将分成 ${totalChunks} 块上传`);
// 显示每个块的信息
chunks.forEach((chunk, i) => {
console.log(`块 ${i}: ${chunk.size} bytes, hash: ${chunk.hash.slice(-8)}`);
});
return { chunks, totalChunks, fileInfo };
} catch (error) {
console.error('文件分析失败:', error);
}
}
// 场景2: 断点续传 - 检查已上传的块
async function checkUploadedChunks(file) {
const { chunks, fileInfo } = await uploader.generateChunks(file);
// 向服务器查询已上传的块
const response = await fetch(`/api/upload/status/${fileInfo.name}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: fileInfo.name,
fileSize: fileInfo.size,
hashes: chunks.map(c => c.hash)
})
});
const { uploadedHashes } = await response.json();
// 过滤出未上传的块
const remainingChunks = chunks.filter(chunk =>
!uploadedHashes.includes(chunk.hash)
);
console.log(`还需上传 ${remainingChunks.length} 个块`);
return remainingChunks;
}
// 场景3: 批量处理多个文件
async function batchAnalyzeFiles(files) {
const analysisResults = [];
for (const file of files) {
try {
const result = await uploader.generateChunks(file);
analysisResults.push({
file: file.name,
...result
});
} catch (error) {
console.error(`分析文件 ${file.name} 失败:`, error);
}
}
// 计算总上传量
const totalSize = analysisResults.reduce((sum, r) => sum + r.fileInfo.size, 0);
const totalChunks = analysisResults.reduce((sum, r) => sum + r.totalChunks, 0);
console.log(`总共 ${files.length} 个文件`);
console.log(`总大小: ${totalSize} bytes`);
console.log(`总块数: ${totalChunks}`);
return analysisResults;
}
```
---
执行文件的分块上传,包含进度监控和错误处理。
**参数:**
- `file` (`File`): 要上传的文件对象
**返回值:**
```javascript
Promise<{
success: boolean, // 上传是否成功
fileInfo: {
name: string, // 文件名
size: number, // 文件大小
type: string, // 文件类型
lastModified: number // 最后修改时间
},
results: Array<any> // 每个块的上传结果数组
}>
```
**使用场景:**
1. **标准文件上传** - 最常见的使用场景
2. **大文件上传** - 处理GB级别的大文件
3. **批量文件上传** - 按顺序或并行上传多个文件
**示例:**
```javascript
// 场景1: 标准文件上传
async function uploadSingleFile(file) {
try {
const result = await uploader.upload(file);
if (result.success) {
console.log(`文件 ${result.fileInfo.name} 上传成功!`);
console.log(`文件大小: ${result.fileInfo.size} bytes`);
console.log(`上传结果:`, result.results);
// 通知服务器合并文件
await mergeFile(result.fileInfo.name, result.results.length);
}
} catch (error) {
console.error('上传失败:', error.message);
// 可以在这里实现重试逻辑
}
}
// 场景2: 大文件上传 (带详细进度显示)
async function uploadLargeFile(file) {
// 创建专门的大文件上传器
const largeFileUploader = new FileUploader({
chunkSize: 10 * 1024 * 1024, // 10MB chunks for large files
concurrent: 2, // 降低并发避免网络拥塞
onProgress: ({ progress, completedChunks, totalChunks, currentChunk }) => {
const progressElement = document.getElementById('progress');
const statusElement = document.getElementById('status');
progressElement.style.width = `${progress}%`;
statusElement.textContent =
`上传中: ${progress.toFixed(1)}% (${completedChunks}/${totalChunks} 块)`;
// 估算剩余时间
const elapsed = Date.now() - uploadStartTime;
const estimatedTotal = elapsed / (progress / 100);
const remaining = estimatedTotal - elapsed;
document.getElementById('timeRemaining').textContent =
`预计剩余: ${Math.round(remaining / 1000)}秒`;
}
});
const uploadStartTime = Date.now();
try {
const result = await largeFileUploader.upload(file);
const uploadTime = (Date.now() - uploadStartTime) / 1000;
const speed = file.size / uploadTime / 1024 / 1024; // MB/s
console.log(`大文件上传完成! 用时: ${uploadTime.toFixed(2)}秒`);
console.log(`平均速度: ${speed.toFixed(2)} MB/s`);
return result;
} catch (error) {
console.error('大文件上传失败:', error);
throw error;
}
}
// 场景3: 批量文件上传 (串行)
async function uploadMultipleFiles(files) {
const results = [];
let totalProgress = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
console.log(`开始上传第 ${i + 1}/${files.length} 个文件: ${file.name}`);
try {
// 为每个文件创建独立的进度跟踪
const fileUploader = new FileUploader({
onProgress: ({ progress }) => {
const overallProgress =
(totalProgress + progress / files.length);
updateOverallProgress(overallProgress);
}
});
const result = await fileUploader.upload(file);
results.push({ file: file.name, success: true, result });
totalProgress += 100 / files.length;
} catch (error) {
console.error(`文件 ${file.name} 上传失败:`, error);
results.push({ file: file.name, success: false, error: error.message });
}
}
console.log('批量上传完成:', results);
return results;
}
// 场景4: 并行文件上传 (限制并发数)
async function uploadFilesInParallel(files, maxConcurrent = 3) {
const semaphore = new Semaphore(maxConcurrent);
const uploadPromises = files.map(async (file) => {
const release = await semaphore.acquire();
try {
console.log(`开始上传: ${file.name}`);
const result = await uploader.upload(file);
console.log(`上传完成: ${file.name}`);
return { file: file.name, success: true, result };
} catch (error) {
console.error(`上传失败: ${file.name}`, error);
return { file: file.name, success: false, error: error.message };
} finally {
release();
}
});
const results = await Promise.all(uploadPromises);
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(`并行上传完成: 成功 ${successful}, 失败 ${failed}`);
return results;
}
```
---
合并服务器上已上传的文件块,通常在所有块上传完成后调用。
> **📝 关于文件合并:**
> - **是否必需**:取决于你的后端实现
> - 如果后端在接收每个块后自动合并,则此方法**不是必需的**
> - 如果后端需要明确的合并指令,则此方法**是必需的**
> - **推荐做法**:即使后端自动合并,也建议调用此方法来确认合并状态和获取最终文件信息
> - **典型场景**:大多数分片上传实现都需要最后的合并步骤
**参数:**
- `fileName` (`string`): 要合并的文件名
- `totalChunks` (`number`): 总块数
- `mergeFunction` (`function`): 自定义合并函数
**mergeFunction 参数:**
```javascript
{
fileName: string, // 文件名
totalChunks: number // 总块数
}
```
**返回值:**
`Promise<any>` - 合并函数的返回结果
**使用场景:**
1. **标准文件合并** - 上传完成后合并文件
2. **验证合并** - 合并前验证所有块完整性
3. **批处理合并** - 批量合并多个文件
**示例:**
```javascript
// 场景1: 标准文件合并
async function mergeUploadedFile(fileName, totalChunks) {
try {
const result = await uploader.mergeChunks(
fileName,
totalChunks,
async ({ fileName, totalChunks }) => {
const response = await fetch('/api/upload/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName,
totalChunks,
timestamp: Date.now()
})
});
if (!response.ok) {
throw new Error(`合并失败: ${response.statusText}`);
}
return await response.json();
}
);
console.log('文件合并成功:', result);
return result;
} catch (error) {
console.error('文件合并失败:', error);
throw error;
}
}
// 场景2: 带验证的文件合并
async function mergeWithValidation(fileName, totalChunks, expectedHash) {
const result = await uploader.mergeChunks(
fileName,
totalChunks,
async ({ fileName, totalChunks }) => {
// 第一步: 验证所有块都已上传
const validationResponse = await fetch('/api/upload/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileName, totalChunks })
});
const validation = await validationResponse.json();
if (!validation.valid) {
throw new Error(`文件块验证失败: ${validation.message}`);
}
// 第二步: 执行合并
const mergeResponse = await fetch('/api/upload/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName,
totalChunks,
expectedHash
})
});
const mergeResult = await mergeResponse.json();
// 第三步: 验证合并后文件的完整性
if (expectedHash && mergeResult.actualHash !== expectedHash) {
throw new Error('合并后文件hash不匹配');
}
return mergeResult;
}
);
console.log('验证合并完成:', result);
return result;
}
// 场景3: 批量文件合并
async function batchMergeFiles(fileInfos) {
const mergeResults = [];
for (const fileInfo of fileInfos) {
try {
console.log(`开始合并文件: ${fileInfo.fileName}`);
const result = await uploader.mergeChunks(
fileInfo.fileName,
fileInfo.totalChunks,
async ({ fileName, totalChunks }) => {
const response = await fetch('/api/upload/batch-merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName,
totalChunks,
batchId: fileInfo.batchId,
priority: fileInfo.priority || 'normal'
})
});
return await response.json();
}
);
mergeResults.push({
fileName: fileInfo.fileName,
success: true,
result
});
console.log(`文件 ${fileInfo.fileName} 合并完成`);
} catch (error) {
console.error(`文件 ${fileInfo.fileName} 合并失败:`, error);
mergeResults.push({
fileName: fileInfo.fileName,
success: false,
error: error.message
});
}
}
const successful = mergeResults.filter(r => r.success).length;
console.log(`批量合并完成: ${successful}/${fileInfos.length} 个文件成功`);
return mergeResults;
}
// 场景4: 带重试的合并
async function mergeWithRetry(fileName, totalChunks, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`尝试合并文件 ${fileName} (第 ${attempt}/${maxRetries} 次)`);
const result = await uploader.mergeChunks(
fileName,
totalChunks,
async ({ fileName, totalChunks }) => {
const response = await fetch('/api/upload/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Retry-Attempt': attempt.toString()
},
body: JSON.stringify({
fileName,
totalChunks,
retryAttempt: attempt
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
console.log(`文件 ${fileName} 在第 ${attempt} 次尝试中合并成功`);
return result;
} catch (error) {
lastError = error;
console.warn(`第 ${attempt} 次合并尝试失败:`, error.message);
if (attempt < maxRetries) {
// 指数退避策略
const delay = Math.pow(2, attempt) * 1000;
console.log(`等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`合并失败,已重试 ${maxRetries} 次: ${lastError.message}`);
}
```
---
```html
<!DOCTYPE html>
<html>
<head>
<title>File Uploader Demo</title>
<style>
.progress-container {
width: 100%;
background-color:
border-radius: 5px;
margin: 10px 0;
}
.progress-bar {
width: 0%;
height: 30px;
background-color:
border-radius: 5px;
text-align: center;
line-height: 30px;
color: white;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<input type="file" id="fileInput" multiple>
<div class="progress-container">
<div class="progress-bar" id="progressBar">0%</div>
</div>
<div id="status">请选择文件</div>
<div id="results"></div>
<script type="module">
import FileUploader from './src/file-upload.js';
const uploader = new FileUploader({
chunkSize: 2 * 1024 * 1024, // 2MB
concurrent: 3,
// 自定义上传函数
uploadFunction: async (chunkData) => {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 这里替换为你的实际API
const formData = new FormData();
formData.append('file', chunkData.chunk);
formData.append('chunkIndex', chunkData.index);
formData.append('fileName', chunkData.fileName);
formData.append('chunkHash', chunkData.hash);
formData.append('totalChunks', chunkData.totalChunks);
const response = await fetch('/api/upload/chunk', {
method: 'POST',
body: formData
});
return await response.json();
},
// 进度更新
onProgress: ({ progress, completedChunks, totalChunks }) => {
const progressBar = document.getElementById('progressBar');
const status = document.getElementById('status');
progressBar.style.width = `${progress}%`;
progressBar.textContent = `${progress.toFixed(1)}%`;
status.textContent = `上传中: ${completedChunks}/${totalChunks} 块完成`;
},
// 错误处理
onError: ({ error, chunk, fileName }) => {
const status = document.getElementById('status');
status.textContent = `错误: ${error.message}`;
status.style.color = 'red';
},
// 上传完成
onComplete: ({ fileName, fileSize, totalChunks }) => {
const status = document.getElementById('status');
const results = document.getElementById('results');
status.textContent = `上传完成: ${fileName}`;
status.style.color = 'green';
results.innerHTML += `
<div>
<strong>${fileName}</strong>
(${(fileSize / 1024 / 1024).toFixed(2)} MB, ${totalChunks} 块)
</div>
`;
}
});
// 文件选择处理
document.getElementById('fileInput').addEventListener('change', async (event) => {
const files = Array.from(event.target.files);
for (const file of files) {
try {
await uploader.upload(file);
} catch (error) {
console.error(`文件 ${file.name} 上传失败:`, error);
}
}
});
</script>
</body>
</html>
```
```javascript
class AdvancedUploader {
constructor() {
this.uploader = new FileUploader({
chunkSize: 5 * 1024 * 1024, // 5MB
concurrent: 3,
uploadFunction: this.uploadChunk.bind(this),
onProgress: this.handleProgress.bind(this),
onError: this.handleError.bind(this),
onComplete: this.handleComplete.bind(this)
});
this.uploadState = new Map(); // 存储上传状态
}
// 检查断点续传状态
async checkResumeStatus(file) {
const { chunks, fileInfo } = await this.uploader.generateChunks(file);
const hashes = chunks.map(c => c.hash);
try {
const response = await fetch('/api/upload/status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: fileInfo.name,
fileSize: fileInfo.size,
hashes
})
});
const { uploadedHashes = [] } = await response.json();
// 过滤出未上传的块
const remainingChunks = chunks.filter(chunk =>
!uploadedHashes.includes(chunk.hash)
);
return {
total: chunks.length,
completed: chunks.length - remainingChunks.length,
remaining: remainingChunks,
canResume: remainingChunks.length < chunks.length
};
} catch (error) {
console.warn('无法检查续传状态,将进行完整上传:', error);
return {
total: chunks.length,
completed: 0,
remaining: chunks,
canResume: false
};
}
}
// 断点续传上传
async resumeUpload(file) {
const resumeStatus = await this.checkResumeStatus(file);
if (resumeStatus.canResume) {
console.log(`检测到断点续传: 已完成 ${resumeStatus.completed}/${resumeStatus.total} 块`);
}
// 这里需要修改uploader以支持只上传特定块
// 实际实现中可能需要扩展FileUploader类
return await this.uploader.upload(file);
}
async uploadChunk(chunkData) {
// 实现带重试的上传逻辑
const maxRetries = 3;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const formData = new FormData();
formData.append('file', chunkData.chunk);
formData.append('index', chunkData.index);
formData.append('hash', chunkData.hash);
formData.append('fileName', chunkData.fileName);
formData.append('totalChunks', chunkData.totalChunks);
const response = await fetch('/api/upload/chunk', {
method: 'POST',
body: formData,
headers: {
'Upload-Attempt': attempt.toString()
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
lastError = error;
console.warn(`块 ${chunkData.index} 第 ${attempt} 次上传失败:`, error.message);
if (attempt < maxRetries) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}
throw lastError;
}
handleProgress(progressInfo) {
const { progress, completedChunks, totalChunks, currentChunk } = progressInfo;
console.log(`进度: ${progress.toFixed(2)}% (${completedChunks}/${totalChunks})`);
// 更新UI
this.updateProgressUI(progress, completedChunks, totalChunks);
// 保存进度状态
this.saveProgressState(progressInfo);
}
handleError(errorInfo) {
const { error, chunk, fileName } = errorInfo;
console.error(`文件 ${fileName} 块 ${chunk.index} 上传失败:`, error);
// 记录失败的块,用于重试
this.recordFailedChunk(fileName, chunk, error);
}
handleComplete(completeInfo) {
const { fileName, fileSize, totalChunks } = completeInfo;
console.log(`文件上传完成: ${fileName}`);
// 触发文件合并
this.mergeFile(fileName, totalChunks);
}
async mergeFile(fileName, totalChunks) {
try {
const result = await this.uploader.mergeChunks(
fileName,
totalChunks,
async ({ fileName, totalChunks }) => {
const response = await fetch('/api/upload/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileName, totalChunks })
});
return await response.json();
}
);
console.log(`文件合并完成: ${fileName}`, result);
return result;
} catch (error) {
console.error(`文件合并失败: ${fileName}`, error);
throw error;
}
}
// 辅助方法
updateProgressUI(progress, completed, total) {
// 实现进度UI更新
}
saveProgressState(progressInfo) {
// 实现进度状态保存(如localStorage)
}
recordFailedChunk(fileName, chunk, error) {
// 记录失败块信息,用于重试
}
}
// 使用高级上传器
const advancedUploader = new AdvancedUploader();
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
try {
await advancedUploader.resumeUpload(file);
} catch (error) {
console.error('上传失败:', error);
}
}
});
```
上传函数接收包含以下属性的 `chunkData` 对象:
```javascript
{
index: number, // 块索引
chunk: Blob, // 文件块
start: number, // 起始位置
end: number, // 结束位置
size: number, // 块大小
hash: string, // 块哈希值
fileName: string, // 文件名
totalChunks: number, // 总块数
fileInfo: { // 文件信息
name: string,
size: number,
type: string,
lastModified: number
}
}
```
```javascript
{
progress: number, // 进度百分比 (0-100)
completedChunks: number, // 已完成块数
totalChunks: number, // 总块数
currentChunk: number // 当前上传的块索引
}
```
```javascript
{
error: Error, // 错误对象
chunk: Object, // 失败的块信息
fileName: string // 文件名
}
```
```javascript
{
fileName: string, // 文件名
fileSize: number, // 文件大小
totalChunks: number, // 总块数
results: Array<any> // 所有块的上传结果
}
```
库内置了多层错误处理机制:
1. **网络错误**: 自动重试机制
2. **文件读取错误**: 降级处理
3. **Hash生成错误**: 回退到简单hash
4. **并发控制**: 防止资源耗尽
- Chrome 37+
- Firefox 34+
- Safari 7.1+
- Edge 79+
需要支持的Web APIs:
- File API
- Blob API
- Web Crypto API (用于hash生成)
- Promise
- async/await
## 📈 性能建议
1. **chunk大小**:
- 小文件: 1-2MB
- 大文件: 5-10MB
- 超大文件: 10-50MB
2. **并发数**:
- 快速网络: 5-8
- 普通网络: 3-5
- 慢速网络: 1-2
3. **内存优化**:
- 避免同时处理过多大文件
- 及时清理已完成的chunk引用
## 📄 许可证
MIT License - 详见 [LICENSE](LICENSE) 文件