• 作者:老汪软件技巧
  • 发表时间: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')