前端在做文件上传时,考虑到网速的快慢,如果文件过大的话可能会导致上传时间过长而请求超时,文件上传失败。因此文件过大需要对文件进行分片上传。
那文件分片上传的具体过程是怎样的呢?
进行了许多搜索查找之后,参照众多资源进行修改,得到了自己的简易实现流程。
首先列出来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用于作为处理文件上传的中间件,可以通过 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
,每个文件将设置为一个随机文件名,并且是没有扩展名的。
前端代码示例:
document
通过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仓库