如何使用nodejs接收上传文件

工作中遇到了在nodejs服务器中接收文件作为数据导入凭据的需求,开发过程中踩过的坑与大家分享一下

1.准备

·基本的ajax通讯,这里使用JQuery的$.ajax作为请求工具。

·nodejs服务使用express框架,以及nodejs的fs模块

·H5的input[type=“file”]标签作为文件上传的载体

2.基本思路

通过file类型的input标签,选取文件,通过$.ajax调用formData组装的数据进行上传。nodejs下express服务器相应的接口中接收数据,然后将数据保存到服务器指定的目录生成缓存文件,再通过读取缓存文件进行数据的导入。此篇文章仅对导入前的上传进行讲解

3.文件拾取和数据整理

首先我们在html页面中搭建一个input用于文件拾取

1
<input type="file" id="input_file">

然后在脚本中搭建方法框架并将input的选择事件进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 上传文件方法
*/
function uploadFile(file) {

}

/**
* 初始化:注册input事件并获取文件
*/
function init() {
  let fileInput = document.getElementById('input_file');
 
  // 注册input的change事件
  fileInput.addEventListener('change', () => {
    // 注意因为没有指定input的multiple属性,因此只允许用户选择一个文件
    // 对应的FileList里面的第一个成员就是我们上传选择的文件
    let file = fileInput.files[0];
   
    // 这里做一下验证,当change事件对应选择了文件的时候,再调用上传方法
    if(file){
      uploadFile(file);
    }
  }
}

以上我们对input事件进行注册已经完成

4.文件拾取和数据整理

接下来我们对上传文件方法进行整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import * as $ from 'jquery';
/**
* 上传文件方法
*/
function uploadFile(file) {
  // 这里是将要在nodejs服务中提供额文件上传接口地址
  const url = "...demo/upload";
 
  // 创建一个form对象,用来作为文件上传的载体
  let form = new FormData();
  form.set('uploadFile', file);
 
  $.ajax({
    url : url,
    type : post,
    param : form,
    processData : false,  // 使用post请求不需要参数序列化
    contentType : false,  // 注意这里一定要传false,否则默认使用application/x-www-form-urlencoded将无法解析上传的数据
    success : (data) => {
      console.log(data);
    }
  });
}

5.nodejs接收文件

这里是我们这篇文章的重点

对于接收文件,开始的时候认为与普通接口调用一样,post请求参数从request.body里面就可以取出来,但是经过实践发现无论前段通过什么方式发起请求,最终在request.body里面取到的值都是一个空对象:{}。经过查询发现,request.body是针对contentType非application/x-www-form-urlencoded的编码格式

对于这种情况,我们要借助于nodejs插件来进行数据解析

下面对我找到的两个插件进行简单介绍

  • multiparty

npm地址:https://www.npmjs.com/package/multiparty

安装:

1
yarn add multiparty --save
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let fs = require('fs');
let router = require('express').Router();
let multiparty = require('multiparty');

// 上传文件接口
router.post('upload_file', (request, response) => {
  let form = new multiparty.Form();
  form.parse(req, function(err, fields, files) {
      // 这里的files是接收到的文件列表,相当于FileList
      // 对于上传单个文件,取数组里面的第一项
      let file = files[0];
      res.send(file)
    });
});
  • formidable

npm地址:https://www.npmjs.com/package/formidable

安装:

1
yarn add formidable --save
1
2
3
4
5
6
7
8
9
10
11
12
13
let fs = require('fs');
let router = require('express').Router();
let formidable = require('formidable');

// 上传文件接口
router.post('upload_file', (request, response) => {
  let form = new formidable.IncomingForm();
  form.parse(req, function(err, fields, files) {
      // 这里的结构是通过json形式返回,因此要直接取对应上传在formData里面传入的key
      let file = files.upLoadFile;
      res.send(file)
    });
});

通过对比不难看出,两个插件在调用结构上无太大差异。需要注意的是,在回调函数里面的files参数数据类型上的不同

6.nodejs存储文件

以上我们已经掌握了nodejs如何接收上传文件的方法,下面介绍如何将上传的文件进行存储。

通过查询,了解到可以使用fs.rename的方法将文件进行转存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let fs = require('fs');
let path = require('path');
let router = require('express').Router();
let formidable = require('formidable');

/**
* 存储文件方法
*/
function saveFile(file, callback){
  // 定义存储文件地址
  let savePath = path.resolve(__dirname, `../static/${file.name}`)
  let sourcePath = file.path;
 
  // 通过fs.rename方法转存文件
  fs.rename(sourcePath, savePath, (err) => {
    callback(err);
  });
}

// 上传文件接口
router.post('upload_file', (request, response) => {
  let form = new formidable.IncomingForm();
  form.parse(req, function(err, fields, files) {
      // 对于单个文件,这里的files直接是file对象
      let file = files;
      saveFile(file, (err) => {
        res.send(err || '上传成功!');
      }
    });
});

测试的时候发现会抛出异常:

1
cross-device link not permitted

这个异常是由于在本地服务进行测试,上传文件所在的磁盘分区与未来文件不同,导致引起跨区重命名文件的权限问题。因此我们转换一个思路,通过fs创建文件读写流的方式进行文件的存储。

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let fs = require('fs');
let path = require('path');
let router = require('express').Router();
let formidable = require('formidable');

/**
* 存储文件方法
*/
function saveFile(file, callback){
  // 定义存储文件地址
  let savePath = path.resolve(__dirname, `../static/${file.name}`)
  let sourcePath = file.path;
 
  // 创建读写流
  let readStream = fs.createReadStream(sourcePath);
  let writeStream = fs.createWriteStream(savePath);
 
  // 读写进程开始
  readStream.pipe(writeStream);
 
  // 监听读取完成事件
  readStream.on('end', () => {
    // 读取完成后,释放读取源文件链接
    fs.unlinkSync(sourcePath);
    callback();
  });
}

// 上传文件接口
router.post('upload_file', (request, response) => {
  let form = new formidable.IncomingForm();
  form.parse(req, function(err, fields, files) {
      // 对于单个文件,这里的files直接是file对象
      let file = files;
      saveFile(file, (err) => {
        res.send(err || '上传成功!');
      }
    });
});

自此一个完整的文件上传、接收以及存储的功能就完工了!