1. 程式人生 > >express 如何上傳文件的原理和實現

express 如何上傳文件的原理和實現

rip .net 文件 note receiving 過濾 console 執行 sage

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:\n\n');

            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('文件上傳成功');


         }
         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');

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

目前再研究中。。。

express 如何上傳文件的原理和實現