您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# JavaScript之分片上传,断点续传的方法
## 一、背景与需求分析
在现代Web应用中,大文件上传是一个常见需求。传统单次上传方式存在以下问题:
- 网络波动导致失败需重新上传
- 大文件占用服务器内存过高
- 无法实时展示上传进度
分片上传(Chunked Upload)和断点续传(Resumable Upload)通过将文件切割为多个片段分别上传,有效解决了这些问题。
## 二、核心实现原理
### 1. 分片上传流程
1. 前端将文件切割为固定大小(如5MB)的片段
2. 为每个片段生成唯一标识
3. 并行/串行上传分片
4. 服务端接收并存储分片
5. 所有分片上传完成后触发合并操作
### 2. 断点续传实现
- 基于文件hash建立唯一标识
- 服务端记录已上传分片信息
- 中断后重新上传时先查询缺失分片
## 三、前端实现代码示例
### 1. 文件分片处理
```javascript
// 生成文件分片
function createFileChunks(file, chunkSize = 5 * 1024 * 1024) {
const chunks = []
let cur = 0
while (cur < file.size) {
chunks.push({
chunk: file.slice(cur, cur + chunkSize),
filename: `${file.name}-${cur}`
})
cur += chunkSize
}
return chunks
}
// 生成文件hash(使用SparkMD5库)
async function calculateHash(chunks) {
return new Promise(resolve => {
const spark = new SparkMD5.ArrayBuffer()
let count = 0
const loadNext = index => {
const reader = new FileReader()
reader.readAsArrayBuffer(chunks[index].chunk)
reader.onload = e => {
spark.append(e.target.result)
count++
if (count === chunks.length) {
resolve(spark.end())
} else {
loadNext(count)
}
}
}
loadNext(0)
})
}
async function uploadFile(file) {
const chunks = createFileChunks(file)
const fileHash = await calculateHash(chunks)
// 检查已上传分片
const { uploadedList } = await checkServerChunks(fileHash)
const requests = chunks.map((chunk, index) => {
// 跳过已上传分片
if (uploadedList.includes(`${fileHash}-${index}`)) {
return Promise.resolve()
}
const formData = new FormData()
formData.append('chunk', chunk.chunk)
formData.append('hash', `${fileHash}-${index}`)
formData.append('fileHash', fileHash)
return axios.post('/upload', formData, {
onUploadProgress: createProgressHandler(chunk)
})
})
await Promise.all(requests)
await mergeFileRequest(fileHash, file.name)
}
// 接收分片
app.post('/upload', (req, res) => {
const { hash, fileHash } = req.body
const chunk = req.files.chunk
// 存储分片到临时目录
const chunkDir = path.resolve('temp', fileHash)
if (!fs.existsSync(chunkDir)) {
fs.mkdirSync(chunkDir)
}
fs.writeFileSync(path.resolve(chunkDir, hash), chunk.data)
res.send({ code: 0 })
})
// 合并分片
app.post('/merge', async (req, res) => {
const { fileHash, filename } = req.body
const chunkDir = path.resolve('temp', fileHash)
const chunks = fs.readdirSync(chunkDir)
// 按序号排序后合并
chunks.sort((a, b) => a.split('-')[1] - b.split('-')[1])
await Promise.all(
chunks.map(chunkPath =>
fs.promises.appendFile(
path.resolve('uploads', filename),
fs.readFileSync(path.resolve(chunkDir, chunkPath))
)
)
// 删除临时目录
fs.rmdirSync(chunkDir, { recursive: true })
res.send({ code: 0 })
})
async function parallelUpload(tasks, limit = 3) {
const results = []
const executing = []
for (const task of tasks) {
const p = Promise.resolve().then(task)
results.push(p)
const e = p.then(() => executing.splice(executing.indexOf(e), 1))
executing.push(e)
if (executing.length >= limit) {
await Promise.race(executing)
}
}
return Promise.all(results)
}
function createProgressHandler(chunk) {
return progressEvent => {
const percent = Math.round(
(progressEvent.loaded / chunk.size) * 100
)
// 更新对应分片的进度
updateChunkProgress(chunk.filename, percent)
// 计算整体进度
const total = chunks.reduce((sum, c) => {
return sum + (c.progress || 0) * c.size / file.size
}, 0)
console.log(`总进度: ${total.toFixed(2)}%`)
}
}
通过上述方法,开发者可以构建健壮的大文件上传系统,显著提升用户体验。实际应用中还需结合具体业务场景进行调整优化。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。