1. 什么是分片上传
分片上传是指将一个完整的文件分割成多个小块,分别上传到服务器,并在服务器上将这些小块按照一定规则合并起来,形成完整的文件。
当需要上传大文件时,如果直接将整个文件一次性上传到服务器,可能会因为网络不稳定或服务器性能原因导致上传失败,而分片上传可以降低整个上传过程的风险。
Node.js作为一种流行的服务器端JavaScript运行时环境,提供了丰富的API和模块,可以实现分片上传。
2. 分片上传的实现步骤
2.1 前端处理
前端负责将文件分割成若干个小块,并逐个上传到服务器。为了方便后续合并文件,每个小块需要记录下标和总块数的信息。
const file = /*获取本地文件*/
const chunkSize = 1024 * 1024 // 每个小块的大小,这里设置为1MB
const chunks = Math.ceil(file.size / chunkSize) // 总块数
const chunkList = [] // 存储每个小块的信息
for(let i = 0; i < chunks; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, file.size)
chunkList.push({
file: file.slice(start, end),
index: i,
total: chunks
})
}
// 逐个上传小块
for(const chunk of chunkList) {
// 将chunk发送到服务器
}
前端也需要在文件上传完成后向服务器发送“文件上传完成”的消息。
2.2 后端处理
后端接收到前端发送的小块后,需要存储这些小块,并等到所有小块上传完成后再合并这些小块为完整的文件。
具体实现可以使用Node.js的fs模块处理文件操作。
const fs = require('fs')
const path = require('path')
function saveChunk(chunk, filePath) {
return new Promise((resolve, reject) => {
const chunkPath = path.join(filePath, chunk.index.toString())
fs.writeFile(chunkPath, chunk.file, err => {
if(err) reject(err)
resolve()
})
})
}
function mergeChunks(chunkList, filePath) {
return new Promise((resolve, reject) => {
const destPath = path.join(filePath, 'output')
const readStreams = []
for(let i = 0; i < chunkList.length; i++) {
const chunkPath = path.join(filePath, i.toString())
readStreams.push(fs.createReadStream(chunkPath))
}
const writeStream = fs.createWriteStream(destPath)
let index = 0
function pipe() {
if(index < readStreams.length) {
readStreams[index].pipe(writeStream, { end: false })
readStreams[index].on('end', () => {
index++
pipe()
})
} else {
writeStream.end()
resolve()
}
}
pipe()
})
}
// 接收小块
app.post('/upload_chunk', async (req, res) => {
const chunk = req.body
const fileId = req.query.fileId
const filePath = path.join(__dirname, 'uploads', fileId)
const chunkSaved = await saveChunk(chunk, filePath)
res.send('ok')
})
// 合并文件
app.post('/merge_file', async (req, res) => {
const fileId = req.body.fileId
const filePath = path.join(__dirname, 'uploads', fileId)
const fileStat = fs.statSync(filePath)
const fileSize = fileStat.size
const chunks = Math.ceil(fileSize / chunkSize)
const chunkList = Array.from({ length: chunks }, (v, i) => i)
.map((index) => path.join(filePath, index.toString()))
.filter((chunkPath) => fs.existsSync(chunkPath))
.map((chunkPath, index) => ({
file: chunkPath,
index,
total: chunks
}))
if(chunkList.length === chunks) {
const mergeCompleted = await mergeChunks(chunkList, filePath)
res.send('ok')
} else {
res.status(400).send('Chunk lost')
}
})
3. 总结
通过前端将文件分割成小块并逐个上传,后端进行存储和合并,分片上传可以大大提高上传大文件的成功率。Node.js提供了fs模块来处理文件操作,可以方便地实现分片上传。
当然,分片上传仍然有一些问题,比如如果存储小块的服务器宕机,或者上传完成的消息丢失等,都会导致文件上传失败。因此,分片上传并不是百分之百可靠的,需要根据实际需求进行权衡和优化。