用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属性

相关文章
相关标签/搜索