- 作者:老汪软件技巧
- 发表时间:2024-09-23 10:01
- 浏览量:
前端大文件上传是面试的一道常考题,我也是最近才弄懂,特地来记录一下,加深印象
正文
首先准备一个前端文件夹client和一个后端文件夹server
前端:
这里我使用到的是原生html 和原生js来写的。
首先我们需要拿到文件,原生input 有一个file类型可以让我们从本地获取文件。上传到服务器还需要一个button
"file" id="fileinput">
<button id="btn">上传button>
接下来,我们需要在js中获取到这个文件类型,这里可以使用onchange事件,也可以使用addEventLisner(), 去监听change事件,事件监听的函数中会有个形参e,我们可以通过e.target获取获取到当前监听的元素,在e.target中可以找到一个files属性,里面包含了上传的文件数据,里面只有一条数据,我们可以通过 e.target.files[0]来拿到这条数据,我这里使用了解构赋值const [file] = e.target.files 来获取到数据
这是这部分的代码:
const fileInput = document.querySelector('#fileinput');
const btn = document.getElementById('btn')
let fileObj = null
// 读取本地文件
fileInput.addEventListener('change', handleFileChange);
function handleFileChange(e) {
console.log(e);
const [file] = e.target.files;
fileObj = file
}
获取到数据后 接下来我们就要想办法进行数据的上传了,由于服务器接收一个巨大的文件可能会占用大量的内存资源。所以我们需要把文件分片上传, 那么如何进行分片呢:在js中file类型会有一个slice方法,它可以把文件类型切割成blob类型
有关于blob请看MND文档
这是切片代码:
// 切片
function createChunk(file, size = 5 * 1024 * 1024) {
const chunkList = []
let cur = 0
while(cur < file.size) {
chunkList.push({file: file.slice(cur, cur+size)})
cur += size
}
return chunkList
}
由于各种各样的原因,上传分片的时候可能会造成切片顺序的打乱,所以我们需要去给这些切片去添加一个字段去描述它的顺序。
function handleUpload() {
if(!fileObj) return
const chunkList = createChunk(fileObj)
console.log(chunkList);
const chunks = chunkList.map(({file}, index) => {
return {
file,
size: file.size,
percent: 0,
chunkName: `${fileObj.name}-${index}`,
fileName: fileObj.name,
index
}
})
// 发请求
updateChunks(chunks)
}
这里我添加了chunkName 用名字和下标来描述顺序。
当做完这些事情后,我们就可以进行发送请求了
这里我使用了axios的 CDN
// 请求
function updateChunks(chunks) {
console.log(chunks) // 这个数组中的元素是对此昂,对象中有blob类型的文件对象
const formChunks = chunks.map(({file, fileName, index, chunkName}) => {
const formData = new FormData()
formData.append('file', file)
formData.append('chunkName', chunkName)
formData.append('fileName', fileName)
return {formData, index}
})
const requestList = formChunks.map(({formData, index}) => {
return axios.post('http://localhost:3000/upload', formData,() => {
console.log(index);// 进度条
})
})
// console.log(requestList);
Promise.all(requestList).then(res => {
console.log(res, '所有片段都传输成功');
mergeChunks()
})
}
注意:通过将Blob对象加入到FormData中,可以避免将文件数据转换成字符串或其他格式,从而减少了内存开销和处理时间。
这里我使用了Promise.all()来判断请求是否全部成功(即文件全部上传成功),当全部上传成功且服务端接收后,就可以发送合并请求了:
function mergeChunks(size = 5 * 1024 * 1024) {
axios.post('http://localhost:3000/merge', {
fileName: fileObj.name,
size
})
.then(res => {
console.log(fileObj.name + '合并完成');
})
}
前端到这里就已经大致完成了
后端:
这里我使用了原生node 的http模块和path模块,还使用了第三方库fse去替代fs模块处理文件,然后使用multiparty模块来解析上传文件以及表单数据
const http = require('http');
const path = require('path')
const multiparty = require('multiparty')
const fse = require('fs-extra');
我创建了一个文件夹来存放接收到的切片
// 存放切片
const UPLOAD_DIR = path.resolve(__dirname, '.', 'qiepian')