本文目录导读:

核心原理
文件分片上传将大文件分割成多个小片段,分别上传,然后在服务端合并。
前端实现
1 基础分片上传
class FileUploader {
constructor(options = {}) {
this.chunkSize = options.chunkSize || 1 * 1024 * 1024; // 默认1MB
this.threads = options.threads || 3; // 并发数
this.file = null;
this.chunks = [];
this.uploadedChunks = new Set();
}
// 分片处理
createChunks(file) {
this.file = file;
this.chunks = [];
let start = 0;
let index = 0;
while (start < file.size) {
const end = Math.min(start + this.chunkSize, file.size);
const chunk = file.slice(start, end);
this.chunks.push({
file: chunk,
index: index,
start: start,
end: end,
size: chunk.size,
blob: chunk,
name: `${file.name}.part.${index}`,
hash: this.generateHash(chunk, index)
});
start = end;
index++;
}
}
// 生成分片哈希(用于验证)
generateHash(chunk, index) {
return `${this.file.name}_${index}_${chunk.size}_${Date.now()}`;
}
// 上传单个分片
async uploadChunk(chunk) {
const formData = new FormData();
formData.append('file', chunk.file);
formData.append('name', chunk.name);
formData.append('totalChunks', this.chunks.length);
formData.append('chunkIndex', chunk.index);
formData.append('fileName', this.file.name);
formData.append('fileSize', this.file.size);
formData.append('hash', chunk.hash);
try {
const response = await fetch('/upload/chunk', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
this.uploadedChunks.add(chunk.index);
this.updateProgress();
return true;
}
return false;
} catch (error) {
console.error(`Chunk ${chunk.index} upload failed:`, error);
return false;
}
}
// 并发上传所有分片
async upload() {
const uploadPromises = [];
const chunks = [...this.chunks];
// 限制并发数
const queue = [];
while (chunks.length > 0) {
const chunk = chunks.shift();
const promise = this.uploadChunk(chunk).finally(() => {
queue.splice(queue.indexOf(promise), 1);
});
queue.push(promise);
if (queue.length >= this.threads) {
await Promise.race(queue);
}
}
// 等待所有上传完成
await Promise.all(queue);
// 通知服务器合并文件
return this.mergeFile();
}
// 通知服务器合并文件
async mergeFile() {
const response = await fetch('/upload/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: this.file.name,
totalChunks: this.chunks.length,
fileSize: this.file.size
})
});
return response.json();
}
// 更新进度
updateProgress() {
const progress = (this.uploadedChunks.size / this.chunks.length) * 100;
this.onProgress && this.onProgress(progress);
}
// 断点续传(检查已上传的分片)
async checkUploadedChunks(fileName) {
const response = await fetch(`/upload/status?fileName=${fileName}`);
const data = await response.json();
if (data.exists) {
data.uploadedChunks.forEach(chunk => {
this.uploadedChunks.add(chunk);
});
}
}
}
// 使用示例
const uploader = new FileUploader({
chunkSize: 2 * 1024 * 1024, // 2MB
threads: 5
});
uploader.onProgress = (progress) => {
console.log(`上传进度: ${progress.toFixed(2)}%`);
};
// 监听文件选择
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
// 检查已上传的分片(断点续传)
await uploader.checkUploadedChunks(file.name);
// 创建分片并上传
uploader.createChunks(file);
const result = await uploader.upload();
console.log('上传结果:', result);
});
2 带进度条和暂停恢复功能
class AdvancedFileUploader {
constructor(options) {
this.chunkSize = options.chunkSize || 2 * 1024 * 1024;
this.maxRetries = options.maxRetries || 3;
this.paused = false;
this.aborted = false;
}
// 上传带重试机制
async uploadChunkWithRetry(chunk, retryCount = 0) {
if (this.paused || this.aborted) {
return false;
}
try {
const result = await this.uploadChunk(chunk);
return result;
} catch (error) {
if (retryCount < this.maxRetries && !this.aborted) {
await this.delay(Math.pow(2, retryCount) * 1000); // 指数退避
return this.uploadChunkWithRetry(chunk, retryCount + 1);
}
throw error;
}
}
// 暂停上传
pause() {
this.paused = true;
}
// 恢复上传
resume() {
this.paused = false;
this.upload();
}
// 取消上传
abort() {
this.aborted = true;
// 通知服务器取消
fetch('/upload/abort', {
method: 'POST',
body: JSON.stringify({ fileName: this.file.name })
});
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
后端实现 (Node.js)
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs').promises;
const app = express();
const upload = multer({ dest: 'uploads/' });
// 存储分片
app.post('/upload/chunk', upload.single('file'), async (req, res) => {
try {
const { fileName, chunkIndex, totalChunks, hash } = req.body;
const chunkDir = path.join('uploads', fileName);
// 创建临时目录
await fs.mkdir(chunkDir, { recursive: true });
// 移动分片到指定目录
const chunkPath = path.join(chunkDir, `chunk_${chunkIndex}`);
await fs.rename(req.file.path, chunkPath);
res.json({ success: true, message: 'Chunk uploaded' });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// 合并分片
app.post('/upload/merge', async (req, res) => {
try {
const { fileName, totalChunks, fileSize } = req.body;
const chunkDir = path.join('uploads', fileName);
const finalPath = path.join('uploads', 'completed', fileName);
// 确保目标目录存在
await fs.mkdir(path.dirname(finalPath), { recursive: true });
// 合并所有分片
const writeStream = fs.createWriteStream(finalPath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(chunkDir, `chunk_${i}`);
const chunkData = await fs.readFile(chunkPath);
writeStream.write(chunkData);
}
writeStream.end();
// 等待写入完成
await new Promise((resolve, reject) => {
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
// 验证文件大小
const stats = await fs.stat(finalPath);
if (stats.size !== parseInt(fileSize)) {
throw new Error('File size mismatch');
}
// 清理临时分片
await fs.rm(chunkDir, { recursive: true, force: true });
res.json({
success: true,
message: 'File merged successfully',
path: finalPath
});
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// 检查上传状态(断点续传)
app.get('/upload/status', async (req, res) => {
try {
const { fileName } = req.query;
const chunkDir = path.join('uploads', fileName);
try {
const files = await fs.readdir(chunkDir);
const uploadedChunks = files
.filter(f => f.startsWith('chunk_'))
.map(f => parseInt(f.split('_')[1]));
res.json({
exists: true,
uploadedChunks
});
} catch (error) {
res.json({ exists: false, uploadedChunks: [] });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 取消上传
app.post('/upload/abort', async (req, res) => {
try {
const { fileName } = req.body;
const chunkDir = path.join('uploads', fileName);
// 清理临时文件
await fs.rm(chunkDir, { recursive: true, force: true });
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
使用示例
<!DOCTYPE html>
<html>
<head>
<style>
.upload-container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
}
.progress-bar {
width: 100%;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #45a049);
transition: width 0.3s ease;
}
.controls {
display: flex;
gap: 10px;
margin-top: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-warning {
background: #ff9800;
color: white;
}
.btn-danger {
background: #f44336;
color: white;
}
</style>
</head>
<body>
<div class="upload-container">
<h2>文件分片上传</h2>
<input type="file" id="fileInput" />
<div class="progress-bar">
<div class="progress" id="progress" style="width: 0%"></div>
</div>
<div id="status">准备上传...</div>
<div class="controls">
<button class="btn btn-primary" onclick="startUpload()">开始上传</button>
<button class="btn btn-warning" onclick="pauseUpload()">暂停</button>
<button class="btn btn-primary" onclick="resumeUpload()">继续</button>
<button class="btn btn-danger" onclick="cancelUpload()">取消</button>
</div>
</div>
<script>
let uploader = null;
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
uploader = new FileUploader({
chunkSize: 1 * 1024 * 1024, // 1MB
threads: 3
});
uploader.onProgress = (progress) => {
document.getElementById('progress').style.width = progress + '%';
document.getElementById('status').textContent =
`上传进度: ${progress.toFixed(2)}%`;
};
document.getElementById('status').textContent = '准备上传...';
});
async function startUpload() {
if (!uploader) return;
const file = document.getElementById('fileInput').files[0];
uploader.createChunks(file);
document.getElementById('status').textContent = '开始上传...';
const result = await uploader.upload();
if (result.success) {
document.getElementById('status').textContent = '上传完成!';
}
}
function pauseUpload() {
if (uploader && uploader.pause) {
uploader.pause();
document.getElementById('status').textContent = '已暂停';
}
}
function resumeUpload() {
if (uploader && uploader.resume) {
uploader.resume();
document.getElementById('status').textContent = '继续上传...';
}
}
function cancelUpload() {
if (uploader && uploader.abort) {
uploader.abort();
document.getElementById('status').textContent = '已取消';
document.getElementById('progress').style.width = '0%';
}
}
</script>
</body>
</html>
优化建议
1 性能优化
- 动态调整分片大小:根据网络状况调整
- 并发控制:合理设置并发数
- 缓存机制:缓存已上传的分片
2 错误处理
- 重试机制:失败自动重试
- 数据校验:MD5/SHA256校验
- 日志记录:详细错误日志
3 安全性
- 文件类型验证:检查MIME类型
- 大小限制:防止恶意超大文件
- 访问控制:身份验证
这个实现支持大文件断点续传、并发上传、进度显示等功能,可以根据实际需求调整参数。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播