用Node處理文件上傳

前言

在Web開發中,文件上傳是一個很是常見、很是重要的功能。本文將介紹如何用Node處理上傳的文件。html

需求分析

因爲如今先後端分離很流行,那麼本文也直接採用先後端分離的作法。前端界面以下:
圖片描述前端

用戶從瀏覽器中選擇文件,點擊上傳,將發起http請求到服務器,服務器將接受到的文件存儲在服務器硬盤中。node

前端部分

ajax請求庫採用axios,爲了簡化說明,前端限制上傳的文件類型只能爲圖片,且一次只能上傳一張,有興趣的朋友能夠自行補充,代碼以下:ios

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
  <input type="file" name="file" accept="image/*" onchange="changeImg(event)"/>
  <button onclick="submit()">上傳</button>

  <script>
    let file = ''
    let fileName = ''

    function submit() {
      let data = new FormData()
      data.append('imgName', fileName)
      data.append('img', file)

      axios({
        method: 'post',
        timeout: 2000,
        url: 'http://localhost:3000/postApi',
        data: data
      })
        .then(response => {
          console.log(response.data)
        })
        .catch(error => {
          console.log(error)
        })
    }

    function changeImg(e) {
      file = e.target.files.item(0)
      // 若是不選擇圖片
      if (file === null) {
        return
      }
      fileName = file.name
    }
  </script>
</body>
</html>

後端部分

這是本文要介紹的重點,爲了用高效流暢的方式來解析文件上傳請求,咱們先引入formidable庫:git

npm install formidable --save

formidable的流式解析器讓它成爲了處理文件上傳的絕佳選擇,也就是說它能隨着數據塊的上傳接收它們,解析它們,並吐出特定的部分,相信熟悉流的朋友會很好理解。這種方式不只快,還不會由於須要大量緩衝而致使內存膨脹,即使像視頻這種大型文件,也不會把進程壓垮。
首先,咱們在根目錄下建立myImage文件,用於存放上傳的圖片(注意:若是沒有建立,會致使上傳報錯),接着,咱們建立一個IncomingForm實例form,而且設置存放路徑爲myImage文件夾。代碼以下:github

var http = require('http')
var formidable = require('formidable')

var server = http.createServer(function(req, res){
  // 1 設置cors跨域
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
  res.setHeader('Content-Type', 'application/json')

  // 2
  switch (req.method) {
    case 'OPTIONS':
      res.statusCode = 200
      res.end()
      break
    case 'POST':
      upload(req, res)
      break
  }
})

function upload(req, res) {
  // 1 判斷
  if (!isFormData(req)) {
    res.statusCode = 400
    res.end('錯誤的請求, 請用multipart/form-data格式')
    return
  }

  // 2 處理
  var form = new formidable.IncomingForm()
  form.uploadDir = './myImage'
  form.keepExtensions = true

  form.on('field', (field, value) => {
    console.log(field)
    console.log(value)
  })
  form.on('end', () => {
    res.end('上傳完成!')
  })

  form.parse(req)
}

function isFormData(req) {
  let type = req.headers['content-type'] || ''
  return type.includes('multipart/form-data')
}

server.listen(3000)
console.log('port is on 3000.')

node app開啓http服務器後,在前端頁面中上傳一張kitty.jpg,咱們看到控制檯打印出了前端上傳的imgName屬性:kitty.jpg
圖片描述web

而且,myImage文件夾目錄下多了一張圖片:
圖片描述ajax

打開一看,正是從前端上傳的那張kitty.jpgchrome

文件更名

咱們發現,這個默認的文件名稱並非咱們想要的,咱們想改爲以當前時間戳命名的文件,添加的功能代碼以下:npm

var fs = require('fs')

  form.on('file', (name, file) => {
    // 重命名文件
    let types = file.name.split('.')
    let suffix = types[types.length - 1]
    fs.renameSync(file.path, './myImage/' + new Date().getTime() + '.' + suffix)
  })

再次上傳,發現如今存的照片名稱已經變成咱們想要的格式了。
圖片描述

添加上傳進度

Formidable的progress事件能給出收到的字節數,以及指望收到的字節數。咱們能夠藉助這個作出一個進度條。
咱們爲上面的程序添加下面的代碼,每次有progress事件激發,就會計算百分比並用console.log()輸出:

form.on('progress', (bytesReceived, bytesExpected) => {
    var percent = Math.floor(bytesReceived / bytesExpected * 100)
    console.log(percent)
  })

再次上傳一張圖片,如今控制檯已經會打印出進度顯示了:
圖片描述

固然,通常狀況下,咱們是要把這個進度傳回到用戶的瀏覽器中去,這對於任何想要上傳大型文件的程序來講是個很棒的特性,而且這是個很適合用Node完成的任務。好比說用WebSocket協議,或者像Socket.IO這樣的實時模塊,關於Node中使用websocket,後面我會單獨出一篇文章來介紹。

錯誤處理

任什麼時候候都不要忘了對程序添加錯誤處理,若是你的程序在重要的時候崩掉了,可能輕則被老闆打屁股,重則拉出去祭天。想象一下,若是用戶上傳的圖片很大,而且用戶的網絡還很慢,那麼上傳的時間會超出前端代碼中設置的請求超時時間2s,服務器就會崩掉,不信?咱們來試一下。
首先,我選擇了一張很大的圖片,5M,而且用chrome瀏覽器將瀏覽器網絡環境設置爲slow 3g,設置方法以下:
f12打開開發者工具,在more tools--network conditions
圖片描述

圖片描述

點擊上傳,咱們看見服務端控制檯的信息以下,服務器崩掉了:
圖片描述

因此,最後咱們加上了錯誤處理,代碼以下:

// 加上錯誤處理,防止用戶網絡慢,或者取消上傳,致使服務器崩掉
  form.on('error', err => {
    console.log(err)
    res.statusCode = 500
    res.end('服務器內部錯誤!')
  })

小結

如今,相信你已經學會了如何用Node處理文件上傳了,結合前面的那篇用Node提供靜態文件服務的文章,你是否是可以本身摸索着去嘗試作一些有趣的事情了呢?

參考閱讀

formidable文檔
input的file類型的accept屬性

相關文章
相關標籤/搜索