express 如何上传文件的原理和实现

摘要:
res)=>res.end(“伪访问”);inputtype=“text”name=“name”>formaction=“/cos/upload”enctype=“multipart/form-data”method=“post”>inputtype=“text”name=“name”>br>提交<
express 上传文件的原理和实现
  1. 原理
  2. formidable
  3. multer
  4. COS

1.原理

1.1 要想了解express上传 我们先看看 nodejs原生上传是怎么实现的

let server = require('http').Server(app);
server.listen(3000);

首先为了让express拥有原始http模块的一些功能

请不要使用 bodyParser 之类的中间件 因为不会next到这里 保持尽量原生。

app.post('/upload', async(req, res)=>{
  var postData =  '';
  req.on("data", function(postDataChunk) {  // 有新的数据包到达就执行
      postData += postDataChunk;

  });

  req.on("end", function() {  // 数据传输完毕
      console.log(postData);
      res.end('up success');

     // console.log('post data finish receiving: ' + postData );
  });

接下来前端模拟一个post请求 ajax form表单都行 假设我们传了个

<form action="/cos/upload"
      enctype="application/x-www-form-urlencoded"
      method="post">
    <p>
What is your name? <input type="text" name="name"><br>
<button   type="submit" >提交</button>
    </p>
</form>

在 post的 content-type="application/x-www-form-urlencoded" 下 后台 console.log` 的 postData 应该是 name=xxx
这时候通过一些 querystring 之类的中间件解析出 key value数值 以方便我们操作
bodyParser这个中间件也是封装简化了流程 只要直接去req.body里面取就行。但有个弱点 对于file提交 就不能这么玩了! 那我们继续看看为什么

enctype="application/x-www-form-urlencoded" 改成 enctype= multipart/form-data另外增加个文件提交 再准备1个zip文件

<form action="/cos/upload"
      enctype="multipart/form-data"
      method="post">
    <p>
What is your name? <input type="text" name="name"><br>
What files are you sending? <input type="file" name="file" id="file"><br>
<button   type="submit" >提交</button>
    </p>
</form>

上传zip文件 和填写名称后 后台 console.logpostData 大概长这样

------WebKitFormBoundaryXKLAlaggDlZOVroE
Content-Disposition: form-data; name="name"

xxxx
------WebKitFormBoundaryXKLAlaggDlZOVroE
Content-Disposition: form-data; name="file"; filename="wrap.zip"
Content-Type: application/zip

PK�����bL�y��}������wrap.jpgUT	��]�Z�ϟZux���������PS��?�HBKh�:	ŀ�	�zG"  一堆乱码

真的很吓人格式 其实除了乱码 还是有点规律 都通过WebKitFormBoundary 分割 还有一些头信息描述 和 文件内容 如果您想了解这些代表什么 请看 传送门

让我们改进下代码 取消掉name字段 end 里生成文件

req.on("end", function() {  // 数据传输完毕

       fs.writeFile('./upload/sss.zip',postData,'binary',function(err){  //path为本地路径例如public/logo.png
           if(err){
             console.log('保存出错!')
               res.end('up failed');

           }else{
               console.log('保存成功!')
               res.end('up success');
           }
       })


      // console.log('post data finish receiving: ' + postData );
   });

这段代码的目的是 接受前端的zip包 然后保存为服务器端的upload文件下sss.zip。 很显然 运行是成功了 也生成了 但双击解压时候就愣逼了 无法打开。 看了下2个zip的大小 很明显已经发送了变化 。原因就出在一些其他的东西也进入了数据体(就是刚才乱码部分) 如果name没取消 那么信息将会更混乱 怎么解决呢?其实也很简单 只是少了个解析中间件 当然也有大神手写 根据一定规则 split啊 正则啊

2.formidable

formidable 就是一款解析软件 npm install 下 和 var formidable = require('formidable')

app.post('/upload', async(req, res)=>{

    var form = new formidable.IncomingForm();
    form.uploadDir = "./upload";
    //form.keepExtensions = true; 是否保持上传什么后缀 保存什么后缀 默认没有后缀

    form.parse(req, function(err, fields, files) {
            res.writeHead(200, {'content-type': 'text/plain'});
            res.write('received upload:

');

            var tmpPath=files.file.path; //临时保存路径
            var fileName=files.file.name;
            fs.rename(tmpPath,path.resolve(form.uploadDir,fileName),function (err) {

                if(err){
                    console.log('保存出错!')
                }
                else{
                    console.log('保存成功!')
                }
                res.end();
            })
        });

然后打开生成的zip文件。这次能正常解压 说明数据没发生破坏。 formidable 也有很多 事件驱动api 如 progresserror 等 具体看文档

这里我原名保存了上传文件 按实际情况定义。

3.Multer

multer 就是一款express官方推荐的上传中间件 。和formidable 差不多 但是官方送的最基础的上传 如果你想扩展功能 如上传进度条,断点续传 阿里oss 腾讯cos 等内容仓库 还要你自己想办法 融进去。github上已经有部分写好的 可以去看看 这里给出我的配置 可以参考参考

let setMulter = require('../util/myMulter');

//文件上传服务
router.post('/upload',  function (req, res, next)  {

  //这是自己写的一个封装势力 为了想以后扩展
  var upload=setMulter('file',1);

  upload(req, res, function (err) {

         try {
              if (err) throw err;
              if(req.files.length==0) throw new  Error("不能上传空文件"); //官方没有空文件的办法 再这里添加
                
              res.send(`文件上传成功,地址:${req.files[0].url}`);


         }
         catch (err) {
                 console.log(err.message);
                 res.send(`文件上传失败,原因:${err.message}`);
         }
   });
});

myMulter.js

let path=require('path');
let multer = require('multer');
//api https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md


//定义上传目录
let dir=path.resolve(__dirname,'../upload');

//定义mimes-type
const mimes = {
    '.png': 'image/png',
    '.gif': 'image/gif',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.zip':'application/zip'
};


//定义仓库
const storage = multer.diskStorage({

    //Note:如果你传递的是一个函数,你负责创建文件夹,如果你传递的是一个字符串,multer会自动创建
    destination: function (req, file, cb) {
        cb(null, dir);
    },
    //获取文件MD5,重命名,添加后缀,文件重复会直接覆盖
    filename: function (req, file, cb) {

        var ext =(file.originalname).split('.').pop();
        cb(null, `${file.fieldname}-${Date.now()}.${ext}`);
    }
});

//定义过滤器
const fileFilter =function  (req, file, cb) {

    // 指示是否应接受该文件
    var test = Object.values(mimes).filter(function(type) {
        return type===file.mimetype;
    })


    // 接受这个文件,使用`true`,像这样:
    if(test){
        cb(null, true);
    }else{ // 拒绝这个文件,使用`false`,像这样:

        cb(new Error('file mimes not allow!'), false);
    }


}

//定义限制
const limits={
    fileSize:1024 * 1024 * 30
};


module.exports=function(opt) {

    return  multer({
        storage: storage,
        fileFilter:fileFilter,
        limits: limits,
    }).array(opt);
};

如果你想添加进度条 请参考 这个

4.COS

通常来说 一般上传文件会结合云服务器商的存储空间 以方便cdn加速 和数据备份等 操作 还可以支持分片上传。 multer也支持 但必须要自己写 可以参考multer/storage/disk.js
必须要掌握 stream 的概念。 _handleFile就是对流的处理 。特别注意的是 multer 送的 file.stream可读流是 stream.Readable 基础上自己改出来的 . fs.createReadStream()方法读不出来 所以你必须自己要做tmp文件然后读它 再删它。
我用的是腾讯云COS 如果有兴趣的话 看 https://github.com/lanbosm/multer-COS

4.1 安装npm包
npm install multer-cos -S

4.2 制作自己的multer
myMulter.js

let path=require('path');
let multer = require('multer');
let multerCOS = require('multer-cos');
require( 'dotenv' ).config();
/**
 * multer
 * https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md
 * cos
 * https://cloud.tencent.com/document/product/436/12264#options-object
 */


//定义临时文件
let dir=path.resolve(__dirname,'./tmp');

//定义mimes-type
const mimes = {
    '.png': 'image/png',
    '.gif': 'image/gif',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.zip':'application/zip',
    '.txt':'text/plain'
};


const cosConfig={
    //id和key是必须

    //SecretId: AKIXXXXXXXXXXX,
    //SecretKey:XXXXXXXXXXXXXX,
    //Bucket:test-bucket-125XXXXXXXXX
    //Region=ap-shanghai
    // 可选参数
    FileParallelLimit: 3,    // 控制文件上传并发数
    ChunkParallelLimit: 3,   // 控制单个文件下分片上传并发数
    ChunkSize: 1024 * 1024,  // 控制分片大小,单位 B
    domain:'static.dorodoro-lab.com', //cos域名
    dir:'upload',                     //cos文件路径
    onProgress:function(progressData){//进度回调函数,回调是一个对象,包含进度信息
        //console.log(progressData);
    }

};


//定义仓库
const storage = multerCOS({
    cos:cosConfig,
    //Note:如果你传递的是一个函数,你负责创建文件夹,如果你传递的是一个字符串,multer会自动创建 如果什么都不传 系统自己会生成tmp目录
    destination: function (req, file, cb) {
        cb(null, dir);
    },
    //自己会生成个随机16字母的文件名和后缀
    filename:'auto'
});

//测试cos
//storage.test();


//定义过滤器
const fileFilter =function  (req, file, cb) {

    // 指示是否应接受该文件
    let test = Object.values(mimes).filter(type=>type===file.mimetype);

    // 接受这个文件,使用`true`,像这样:
    if(test.length>0){
        cb(null, true);
    }else{ // 拒绝这个文件,使用`false`,像这样:
        cb(new Error('file mimes not allow!'), false);
    }
};

//定义限制
const limits={
    fileSize:1024 * 1024 * 30
};


module.exports=function(opt) {
    return  multer({
        storage: storage,
        fileFilter:fileFilter,
        limits: limits,
    }).array(opt);
};

4.3 和官方版一样用就行了 记得在控制台配置好相关api密钥

免责声明:文章转载自《express 如何上传文件的原理和实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇VMware vCenter Server6.5安装及群集配置介绍Windows开启telnet服务 + 连接失败处理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

前端工程打开速度优化的循序渐进总结

优化的重要指标: 页面打开速度(Fully Loaded) 网站首页(或列表页)之 First View :打开速度应在 3秒+0.5秒 内; 对 Repeat View 时的各项指标暂不作要求; 首屏打开时间(Start Render) 网站首页(或列表页) 之 First View :首屏渲染速度应在 1秒+0.5秒 内; 文档解析完毕时间(Do...

Fastdfs文件系统删除重复的文件

环境:centos、fastdfs Fastdfs文件系统删除重复的文件 问题:fastdfs文件系统磁盘空间疯狂扩展。 原因:fastdfs产生了很多的文件备份,要找到重复文件,排除在使用的文件删除其他的文件。 根源可能是程序反复上传原因,开发进行跟中 删除不需要文件思路: 1、查看文件重复情况 2、列出所有文件系统文件,和从数据库找到所有在用的文件名称...

JAVA读取yml配置文件指定key下的所有内容

先引入需要的依赖 <!--读取yml文件--> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId>...

springBoot读取properties文件乱码

在使用idea进行springBoot测试时,读取properties文件里面的内容会中文乱码,可以设置文件的编码格式为utf-8  有时候可能全都设置完utf-8后还不好使,网上教程说可以清理一下idea缓存  但是测试后仍没有用 最后在properties文件上添加相关属性,比如springBoot的 spring.http.encoding.en...

phpBB3导入版面的Python脚本

关联的数据表 在phpBB3中导入版面时, 需要处理的有两张表, 一个是 forums, 一个是 acl_groups.  如果是干净的论坛, 可以不保留安装时填入的默认分区和版面, 直接用以下语句初始化: -- 清空 forums 表 TRUNCATE phpbb_forums; -- 清空 acl_groups 表 TRUNCATE ph...

vant上传文件到后端

最近在做手机版页面,采用的vant框架,这个上传控件和以前用iview、element有点不一样,iview、element都是直接提供后端接口文件会自动发送到后端,vant需要自己负责发送文件到后端,对于我这种面向百度编程人员还是有点难度。特意记一下,能帮到其他面向百度编程人员 代码 很简单,基本是使用文件构建FormData参数,如下: html代...