zoukankan      html  css  js  c++  java
  • express 如何上传文件的原理和实现

    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 class="btn-save" 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 class="btn-save" 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 如 progress error 等 具体看文档

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

    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密钥

  • 相关阅读:
    战争迷雾Fog Of War
    [UE4]运行时UMG组件跟随鼠标的逻辑:拖拽UMG组件(蓝图)
    [UE4]FString常用API
    用PNG作为Texture创建Material
    [UE4]C++代码操作SplineMesh
    [UE4]Visual Studio的相关插件安装:UE4.natvis和UnrealVS Extension
    TSubobjectPtr和C++传统指针的区别
    组件Slate教程 & UMG widget构造初始化函数中获取其内部组件
    设置UMG的ComboBox(String)字体大小
    UMG设置组件自适应居中或靠边
  • 原文地址:https://www.cnblogs.com/lanbosm/p/8531862.html
Copyright © 2011-2022 走看看