在现代网站中,越来越多的个性化图片,视频,去展示,因此我们的网站一般都会支持文件上传。
文件上传的方案
大文件上传
:将大文件切分成较小的片段(通常称为分片或块),然后逐个上传这些分片。这种方法可以提高上传的稳定性,因为如果某个分片上传失败,只需要重新上传该分片而不需要重新上传整个文件。同时,分片上传还可以利用多个网络连接并行上传多个分片,提高上传速度。断点续传
:在上传过程中,如果网络中断或上传被中止,断点续传技术可以记录已成功上传的分片信息,以便在恢复上传时继续上传未完成的部分,而不需要重新上传整个文件。这种技术可以大大减少上传失败的影响,并节省时间和带宽。
前端实现
<input id="file" type="file"> <!--用来上传文件-->
监听change事件获取文件 file,实现一个方法 chunkFun
用来切片
const file = document.getElementById('file')
file.addEventListener('change', (event) => {
const file = event.target.files[0] //获取文件信息
const chunks = chunkFun(file)
uploadFile(chunks)
})
文件切片 file 接受文件对象,注意file的底层是继承于blob的因此他可以调用blob的方法,slice进行切片,size就是每个切片的大小,我这里用了4MB
实际可以根据项目情况来
const chunkFun = (file, size = 1024 * 1024 * 4) => {
const chunks = []
for (let i = 0; i < file.size; i += size) {
chunks.push(file.slice(i, i + size))
}
return chunks
}
循环调用接口上传,并且存储一些信息,当前分片的索引
,注意file必须写在最后一个,因为nodejs端的multer
会按照顺序去读的,不然读不到参数, 最后通过promise.all 并发发送请求,等待所有请求发送完成,通知后端合并切片。
const uploadFile = (chunks) => {
const List = []
for (let i = 0; i < chunks.length; i++) {
const formData = new FormData()
formData.append('index', i)
formData.append('total', chunks.length)
formData.append('fileName', 'xiezhen')
formData.append('file', chunks[i])
List.push(fetch('http://127.0.0.1:3000/up', {
method: 'POST',
body: formData
}))
}
Promise.all(List).then(res => {
fetch('http://127.0.0.1:3000/merge',{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body:JSON.stringify({
fileName: 'xiaoManXieZhen',
})
}).then(res => {
console.log(res)
})
})
}
nodejs端实现
安装依赖
- express 帮我们启动服务,并且提供接口
- multer 读取文件,存储
- cors 解决跨域
引入模块
import express from 'express'
import multer from 'multer'
import cors from 'cors'
import fs from 'node:fs'
import path from 'node:path'
提供两个接口
- up 用来存储切片
- merge 合并切片
初始化 multer.diskStorage
- destination 存储的目录
- filename 存储的文件名(我是通过index-文件名存储的你也可以改)
合并逻辑
读取upload目录下面的所有文件 也就是所有的切片
0-xiezhen
1-xiezhen
2-xiezhen
3-xiezhen
读取之后返回的是一个数组,但是读取的时候会乱序,所以从小到大排个序,用了sort,排完序之后,读取每个切片的内容,通过 fs.appendFileSync 合并至一个文件,最后删除合并过的切片 完成。respect
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/')
},
filename: (req, file, cb) => {
cb(null, `${req.body.index}-${req.body.fileName}`)
}
})
const upload = multer({ storage })
const app = express()
app.use(cors())
app.use(express.json())
app.post('/up', upload.single('file'), (req, res) => {
res.send('ok')
})
app.post('/merge', async (req, res) => {
const uploadPath = './uploads'
let files = fs.readdirSync(path.join(process.cwd(), uploadPath))
files = files.sort((a, b) => a.split('-')[0] - b.split('-')[0])
const writePath = path.join(process.cwd(), `video`, `${req.body.fileName}.mp4`)
files.forEach((item) => {
fs.appendFileSync(writePath, fs.readFileSync(path.join(process.cwd(), uploadPath, item)))
fs.unlinkSync(path.join(process.cwd(), uploadPath, item))
})
res.send('ok')
})
app.listen(3000, () => {
console.log('Server is running on port 3000')
})