文件上传之大文件分块上传

原则:合久必分,分久必合

优势部分:减少了内存占用,可实现断点续传,并发处理,利用带宽,提高效率
不足之处:增加复杂性,增加额外计算存储
应用场景:云存储大文件上传、多媒体平台音视频上传,需断点续传应用
注意事项:合理分块大小,顺序的完整性,异常情况的合理处理

前端

html:


script:

async function upload() {
    const fileInput = document.getElementById('fileInput'); //获取input框
    const file = fileInput.files[0]; // 对input的文件进行获取

    const chunkSize = 1*1024*1024;  //初始化分块的尺寸 每块分块文件大小为1MB(1兆)
    const totalChunks = Math.ceil(file.size / chunkSize); //通过文件尺寸计算出所有的块数
    let currentChunk = 0; //设置块的初始值

    // 通过while循环处理
    while (currentChunk < totalChunks){
        const start = currentChunk * chunkSize;  // 计算当前块的起始位置
        const end = Math.min(start + chunkSize, file.size); // 计算当前块的结束  Math.min:返回一组数值中的最小值
        const chunk = file.slice(start, end); // 切割文件获取当前块

        const formData = new FormData();
        formData.append('file', chunk); // 添加当前块到 FormData 对象
        formData.append('filename', file.name); // 添加文件名到 FormData 对象
        formData.append('totalChunks', totalChunks); // 添加总块数到 FormData 对象
        formData.append('currentChunk', currentChunk); // 添加当前块数到 FormData 对象
    
        try{
            await axios.post('http://localhost:3000/upload',formData,{
                headers:{
                    'Content-Type':'multipart/form-data',
                },
            }); //发送当前块的上传请求
        }catch(error){
            console.error(error);
            return;
        }

        currentChunk++; //增加当前块数,继续下一块的上传(实现循环操作)
    }

    // 当所有分块文件发送完毕,发起合并请求操作
    try{
        const postData = { filename:file.name,totalChunks:totalChunks }; //构造合并请求的数据
        await http.post('http://localhost:3000/merge', postData,{
            headers: {
            'Content-Type': 'application/json'
            }
        }); //发送合并请求
    }catch(error){
        console.error(error);
    }
}

点击上传触发查看

将会向服务器不断发送块级的一个文件内容,将文件以 binary(二进制) 进行数据的传递

文件上传之大文件分块上传_第1张图片

后端(服务器端)

进行后端配置:

const express = require('express');
const cors = require('cors');  // 解决跨域问题
const path = require('path');  // 处理路径问题
const fs = require('fs');  // 文件读取操作
const multer = require('multer');  // 进行文件上传的操作处理
const upload = multer({dest:'uploads/'});  // 设置文件上传地址
const bodyParser = require('body-parser'); // 实现body解析操作
const app = express();
 
app.use(cors());
app.use(bodyParser.json());  // 实现中间件body的应用
app.use(bodyParser.urlencoded({extended:false}));  // 实现文件路径的处理
 
app.post('/upload',(req,res) => {});  // 实现文件上传的请求操作
 
app.listen(3000,() => {
    console.log('Server started on port 3000');
});

upload上传接口:

 upload.single('file'):利用 multer 实现单文件上传的操作处理

app.post('/upload',upload.single('file'),(req,res) => {
    const file = req.file; // 获取上传的文件对象
    const filename = req.body.filename; // 获取文件名
    const totalChunks = parseInt(req.body.totalChunks); // 获取总块数
    const currentChunk = parseInt(req.body.currentChunk); //获取当前块数
    const chunkPath = path.join(
        "uploads/",
        `${filename}-chunk-${currentChunk}`
    ); // 设置当前文件的存储路径
 
    const chunkStream = fs.createReadStream(file.path); //创建读取文件块的可读流
    const writeStream = fs.createWriteStream(chunkPath); //创建写入当前块的可写流
 
    chunkStream.pipe(writeStream); //通过chunkStream.pipe管道操作,将读取的文件块内容通过管道写入当前块的文件
 
    // 对分块上传内容结束以后的事件监听
    chunkStream.on("end", () => {
        fs.unlinkSync(file.path); //读取文件块的流结束后,删除临时文件
        res.sendStatus(200); //响应上传成功的状态
    });
});

切片上传之后,在进行合并操作

merge合并接口:

router.post("/merge", (req, res) => {
    const filename = req.body.filename; //获取文件名
    const totalChunks = parseInt(req.body.totalChunks);  //获取总块数
 
    const mergedPath = path.join("uploads", filename); //生成合并后文件的存储路径
    const writeStream = fs.createWriteStream(mergedPath); //创建写入合并后文件的可写流
 
    // 合并文件块
    const mergeChunks = (index) => {
      if (index === totalChunks) {
        writeStream.end(); //所有块都合并完成后,关闭写入流
        res.sendStatus(200); //响应合并成功的状态
        return;
      }
 
      const chunkPath = path.join("uploads", `${filename}-chunk-${index}`); //获取当前块的存储路径
      const chunk = fs.readFileSync(chunkPath); //同步读取当前块的内容
      fs.unlinkSync(chunkPath); //删除已合并的块文件
 
      writeStream.write(chunk,() => {
        mergeChunks(index + 1); //递归合并下一块
      });
    };
 
    mergeChunks(0); //从第一块开始合并
});

点击上传触发查看

后端产生大量的块级文件

文件上传之大文件分块上传_第2张图片

并且在结束后合并成一个文件

至此,已经实现了大文件上传分块操作,利用合久必分的原则,前端进行看块级的分割,然后向服务器端不断的发送块级文件内容,以便实现服务器端的分久必合操作

然后客户端利用发送以后的处理,通过merge合并请求的发起,回到服务器端利用merge分久必合以后的内容进行一个请求操作,最终形成的是一个完整的上传文件内容

你可能感兴趣的:(文件上传,分块上传)