node实现文件分片上传之multer篇

 

node实现文件分片上传

前端在做文件上传时,考虑到网速的快慢,如果文件过大的话可能会导致上传时间过长而请求超时,文件上传失败。因此文件过大需要对文件进行分片上传。

那文件分片上传的具体过程是怎样的呢?

进行了许多搜索查找之后,参照众多资源进行修改,得到了自己的简易实现流程。

 

首先列出来node需要用到的模块:

const express = require('express');
var multer = require('multer');
var fs=require('fs');
var path = require('path');
var app = express();
var fse = require('fs-extra');
{
  "name": "fileloader",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "art-template": "^4.13.2",
    "express": "^4.17.1",
    "express-art-template": "^1.0.1",
    "fs-extra": "^9.1.0",
    "jquery": "^3.6.0",
    "multer": "^1.4.2"
  }
}

服务器实例采用express框架快速搭建。配合art-template 和 express-art-template进行页面处理

multer 模块用于处理文件上传

fs-extra模块倒不是必须的,只是参照其他的文章,采用这个模块来删除文件夹较为方便。

 

multer配置介绍

multer用于作为处理文件上传的中间件,可以通过 var upload = multer({dest: '路径'})来实例化multer,然后在路由中配置upload.single('file') 作为中间件。如官方的文档介绍所示:

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })
 
var app = express()
 
app.post('/profile', upload.single('avatar'), function (req, res, next) {
  
})
 
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  
})
 
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])

app.post('/cool-profile', cpUpload, function (req, res, next) {
  
})

官方的示例中,upload实例有三种主要的使用形式,即upload.single()、upload.array()、upload.fileds(),这三者简单来说应该是single用于处理单文件,array和fieds都能处理多文件,由于项目仅演示单文件处理过程,所以这两个不过多赘述,想要了解更多请阅读multer中文翻译文档

.single(filename) --> 接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.file

另外,multer也会向req中添加body字段,如果想要将multer作为body-parser的替代,需要配置类似如下的路由

app.post('/merge',upload.none(),function(req,res){

})

此外,如果前端使用jquery的ajax进行上传,需要配置一些特殊的选项,传递的数据也需要是一个 FormData()对象,我不知道这是不是必须,但是我用这种方法暂时未出现问题,这也是我用multer替代body-parser 出现的问题。

 

当然,这只是简单地配置,如果需要对文件存储路径和文件名进行特殊的设置,需要配置 storage 参数,这是磁盘存储引擎,可以控制文件的存储。

它有两个选项可用,destination 和 filename。他们都是用来确定文件存储位置的函数。

destination 是用来确定上传的文件应该存储在哪个文件夹中。也可以提供一个 string (例如 '/tmp/uploads')。如果没有设置 destination,则使用操作系统默认的临时文件夹。

注意: 如果你提供的 destination 是一个函数,你需要负责创建文件夹。当提供一个字符串,multer 将确保这个文件夹是你创建的。

filename 用于确定文件夹中的文件名的确定。 如果没有设置 filename,每个文件将设置为一个随机文件名,并且是没有扩展名的。

 

代码示例

前端代码示例:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>document</title>
    <style>
        body,div,p,h1,h2,h3,h4,h5,h6,ul,li{margin:0;padding:0;list-style:none;}
        ul,ol{list-style:none;}
        input{outline-style: none;padding: 0;}
        .wrap{
            width: 600px;
            height: 400px;
            background-color: lightblue;
            border-radius: 20px;
            margin: 50px auto;
        }
        label[for="file"]{
            display: inline-block;
            width: 100px;
            height: 100px;
            background-color: lightcoral;
            text-align: center;
            /* vertical-align: middle; */
            line-height: 100px;
        }
        #file{
            opacity: 0;
        }
        input[type="button"]{
            width: 80px;
            height: 32px;
            margin: 10px 10px;
            background-color: rgba(0, 0, blue, 0.5);
            outline-style: none;
        }
    </style>
</head>
<body>
    <div class="wrap">
        <div class="form">
            <label for="file">选择文件</label>
            <input type="file" id="file" name="file" accept="*" >
            <br>
            <input type="button" value="上传" onclick="upload(0)">
        </div>
    </div>
    <script src="/node_modules/jquery/dist/jquery.js"></script>
    <script>
        
       var chunkSize = 1024*1024;

        var fileInput = document.querySelector("#file");

        function upload(index){
            let files = fileInput.files;
            if(!files[0]){
                alert("请选择文件")
                return 
            }
            let file = files[0];// file对象
            //获取文件名和扩展名
            let [fname,fext] = file.name.split('.')

            // 分片
            let start = index * chunkSize
            if(start > file.size){
                merge(file.name);
                return;
            }

            let blob = file.slice(start,start + chunkSize)
            let blobName = `${fname}.${index}.${fext}`;
            let blobFile = new File([blob],blobName);

            // 上传文件
            let formData = new FormData()
            formData.append('file',blobFile);
            $.ajax({
                type: 'post',
                url: '/upload',
                data: formData,
                contentType: false,
                processData: false,
                success: function(res){
                    console.log(res);
                    upload(++index);//递归上传文件分片
                }
            })
        }

        // 合并文件请求
        function merge(filename){
            var formData = new FormData()
            formData.append('name',filename)
            $.ajax({
                type: 'post',
                url:"/merge",
                data: formData,
                contentType: false,
                processData: false,
                dataType: 'json',
                success: function(res){
                    console.log(res);
                }
            })
        }
    </script>
</body>
<html>

通过input选择框,用户选择文件后该元素返回一个File对象,存在input.files中。

File对象继承自Blob对象,拥有slice方法,返回一个blob子对象,是file的一个切片。

得到文件分片后,设置其分片的顺序,并将其添加到一个FormData对象中。

用jquery的ajax进行上传,需要配置两个参数:

                contentType: false,
                processData: false,

不配置可能会报错。

所有分片上传之后,需要发送请求进行文件合并,函数merge即为请求合并文件的函数。

 

 

 

后台代码:

const express = require('express');
var multer = require('multer');
var fs=require('fs');
var path = require('path');
var app = express();
var fse = require('fs-extra');


// 用于检测是否存在用于存放文件的路径,不存在则创建路径
const createFolder = function(folder){
    try{
        fs.accessSync(folder);
    }catch(e){
        fs.mkdirSync(folder);
    }
};
// 文件上传的路径
var uploadFolder = './uploads/';
createFolder(uploadFolder);

// 用于传给multer进行复杂的文件上传配置
var storage = multer.diskStorage({
    destination: function(req,file,cb){
        // 用于进行复杂的路径配置,此处考虑分片上传,先将分片文件保存在临时目录中
        let [fname,index,fext] = file.originalname.split(".");
        let chunkDir = `${uploadFolder}/${fname}`;
        if(!fse.existsSync(chunkDir)){
            fse.mkdirsSync(chunkDir);
        }
        cb(null,chunkDir); //内部提供的回调函数 
    },
    filename: function(req,file,cb){
        // 根据上传的文件名,按分片顺序用分片索引命名,
        // 由于是分片文件,请不要加扩展名,在最后文件合并的时候再添加扩展名
        let fname = file.originalname;
        cb(null,fname.split('.')[1]);

    }
})

var upload = multer({storage: storage});// multer实例

// 配置模板引擎
app.engine('html',require('express-art-template'))
app.set('views',__dirname+'/views')

// 静态资源路由
app.use('/node_modules',express.static('node_modules'))
app.use('/upload',express.static('uploads'))

// 主页路由
app.get('/',function(req,res){
    res.render('index.html')
})

// 上传路由
app.post('/upload',upload.single('file'),function(req,res,next){
    res.end("ok");
})

// 文件合并路由
app.post('/merge',upload.none(),function(req,res){
    let name = req.body.name;
    let fname = name.split('.')[0];
    let chunkDir = path.join(uploadFolder,fname);

    let chunks = fs.readdirSync(chunkDir); // 同步读取以防文件合并顺序混乱

    chunks.sort((a,b)=>a-b).map(chunkPath=>{
        fs.appendFileSync(
            path.join(uploadFolder,name),
            fs.readFileSync(`${chunkDir}/${chunkPath}`)
        )
    })

    fse.removeSync(chunkDir);
    res.send({msg:'合并成功',url:`http://localhost:8080/upload/${name}`});
})




app.listen(8080,()=>{
    console.log("success.....localhost:8080")
})

配置 multer的磁盘存储引擎时, 在destination中设置了保存分片文件的临时文件夹,在filename 中设置了按照分片索引来存储文件,不添加扩展名。

在 merge 路由中进行文件合并,同步读取文件临时目录,用 fs 模块对文件进行排序之后进行文件合并。

 

项目地址:我的gitee仓库

 

 

上一篇:Elementui+iView将图片上传到express服务器


下一篇:nodeJs 使用 Multer 实现本地文件/图片上传到服务器指定目录