node.js express框架实现文件上传与下载功能实例详解


Posted in Javascript onOctober 15, 2019

本文实例讲述了node.js express框架实现文件上传与下载功能。分享给大家供大家参考,具体如下:

背景

昨天吉视传媒的客户对IPS信息发布系统又提了一个新需求,就是发布端发送消息时需要支持附件的上传,而接收端可以对发布端上传的附件进行下载;接收端回复消息时也需要支持上传附件,发布端可以对所有接收端上传的附件进行打包下载。

功能实现

  • 前台部分

前台使用webUploader插件即可,这是百度开发的一款文件上传组件,具体使用查看它的API即可。这个项目之前开发的时候前台使用了angular.js。

$scope.fileName = "";
  //创建上传附件的对象
  var $list = $("#thelist");
  var uploader = WebUploader.create({
    // 选完文件后,是否自动上传。
    auto: false,
    // swf文件路径
    swf: '../../../lib/webUploader/Uploader.swf',
    // 文件接收服务端。
    server: '/publishUploadFile',
    // 内部根据当前运行是创建,可能是input元素,也可能是flash.
    pick : {
      id : '#filePicker',
      //只能选择一个文件上传
      multiple: false
    },
    // pick :'#filePicker',
    method: 'POST',
  });
  uploader.on('fileQueued', function (file) {
    $scope.fileName = file.name;
    $list.html("");
    $list.html(file.name);
  });

当用户选择文件的时候我创建了文件上传的对象,而在用户真正发送消息的时候我添加了相应的参数并将附件真正的上传上去,符合我这个项目的业务逻辑。

if($scope.fileName){
  //添加参数
  uploader.options.formData.fileId = fileId;
  uploader.options.formData.fileName = $scope.fileName;
  uploader.upload();
}
  • 后台部分

路由就不详细说明了,主要注意的是下载的接口我都是使用的get请求,这样前台在请求的时候直接新打开一个窗口拼接了相应的参数就能下载文件了。下面贴一下action层的代码:

//发布端上传附件
exports.publishUploadFile = function (req, res) {
  messageMng.publishUploadFile(req, function (err, datas) {
    res.json(datas);
  });
};
//下载发布端上传的附件
exports.exportPublishFile = function (req, res) {
  messageMng.exportPublishFile(req, function (err, datas) {
    if (err) {
      res.set({
        "Content-Disposition": "attachment;filename=" + encodeURI("error.txt")
      });
      res.write(err.message);
      res.end();
    } else {
      res.download(datas.path, encodeURI(datas.name));
    }
  });
};
//接收端上传附件
exports.uploadFile = function (req, res) {
  messageMng.uploadFile(req, function (err, datas) {
    res.json(datas);
  });
};
//发布端导出附件
exports.exportFile = function (req, res) {
  messageMng.exportFile(req, function (err, datas) {
    if (err) {
      res.set({
        "Content-Disposition": "attachment;filename=" + encodeURI("error.txt")
      });
      res.write(err.message);
      res.end();
    } else {
      //第一种方式 下载完的zip解压报错
      // res.download(datas.path, datas.name + ".zip");
      //第二种方式
      // var path="D:/maven介绍.ppt";
      var f = fs.createReadStream(datas.path);
      res.writeHead(200, {
        'Content-Type': 'application/force-download',
        'Content-Disposition': 'attachment; filename='+ encodeURI(datas.name) + '.zip'
      });
      f.pipe(res);
    }
  });
};

这里着重说一下下载zip时使用download下载完的压缩包解压会报错,使用第二种方法完美解决。

然后是service层的代码:

/**
 * 发布端上传附件
 * @param req
 * @param fn
 */
MessageManager.prototype.publishUploadFile = function (req, fn) {
  try {
    //消息ID
    var fileId = req.body.fileId;
    var file = req.file;
    //文件上传的目录
    var uploadFolder = path.join(__dirname, '../../upload/publishUploadFile/' + fileId);
    //判断文件夹是否存在 不存在则创建
    toolUtil.mkdirSync(uploadFolder);
    //将上传的文件从临时目录拷贝到指定的目录下
    var fileReadStream = fs.createReadStream(file.path);
    var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + file.originalname);
    fileReadStream.pipe(fileWriteStream);
    fileWriteStream.on('close', function () {
      // 删除临时目录下面的文件
      toolUtil.emptyDir(file.destination);
    });
    fn(null, {"data": "", "message": "上传成功", "error_code": 200});
  } catch (e) {
    fn(e, {"data": "", "message": "上传失败", "error_code": e.message});
  }
};
/**
 * 下载发布端上传的附件
 * @param req
 * @param fn
 */
MessageManager.prototype.exportPublishFile = function (req, fn) {
  try {
    //附件ID
    var id = req.query.id;
    //附件名称或标题
    var name = req.query.name;
    if (id && name) {
      //名称过长的话,截取前25个字符
      if (name.length > 25) {
        name = name.substr(0, 24);
      }
      //将要压缩得文件夹路径
      var filePath = path.join(__dirname, '../../upload/publishUploadFile/' + id + '/' + name);
      if (!fs.existsSync(filePath)) {
        fn(new Error("没有附件!"), null);
      } else {
        fn(null, {"name": name, "path": filePath});
      }
    } else {
      fn(new Error("id或name不能为空"), null);
    }
  } catch (e) {
    fn(new Error(e.message), null);
  }
};
/**
 * 接收端上传附件
 * @param req
 * @param fn
 */
MessageManager.prototype.uploadFile = function (req, fn) {
  try {
    //消息ID
    var msgId = req.body.msgId;
    //消息发送的时间
    var msgSendTime = req.body.msgSendTime.slice(0, 10);
    //消息的标题
    var title = req.body.title;
    var replyId = req.body.replyId;
    var replyName = req.body.replyName;
    var file = req.file;
    //文件上传的目录
    var uploadFolder = path.join(__dirname, '../../upload/messages/' + msgId + '/' + replyName);
    //判断文件夹是否存在 不存在则创建
    toolUtil.mkdirSync(uploadFolder);
    //组装文件的名称 原名称+消息发送时间
    var index = file.originalname.lastIndexOf(".");
    var fileName = file.originalname.substr(0, index) + '-' + msgSendTime + "";
    var suffix = file.originalname.substr(index, file.originalname.length - 1);
    //将上传的文件从临时目录拷贝到指定的目录下
    var fileReadStream = fs.createReadStream(file.path);
    var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + fileName + "." + suffix);
    fileReadStream.pipe(fileWriteStream);
    fileWriteStream.on('close', function () {
      //删除临时目录下面的文件
      toolUtil.emptyDir(file.destination);
    });
    fn(null, {"data": "", "message": "上传成功", "error_code": 200});
  } catch (e) {
    fn(e, {"data": "", "message": "上传失败", "error_code": e.message});
  }
};
/**
 * 导出消息的附件文件
 * @param req
 * @param fn
 */
MessageManager.prototype.exportFile = function (req, fn) {
  try {
    //消息ID
    var id = req.query.id;
    //消息名称或标题
    var name = req.query.name;
    if (id && name) {
      //名称过长的话,截取前25个字符
      if (name.length > 25) {
        name = name.substr(0, 24);
      }
      //将要压缩得文件夹路径
      var messagePath = path.join(__dirname, '../../upload/messages/' + id);
      if (!fs.existsSync(messagePath)) {
        fn(new Error("没有附件!"), null);
      } else {
        //生成得临时zip文件目录
        var zipPath = path.join(__dirname, '../../upload/temp.zip');
        var archive = archiver('zip', {
          // Sets the compression level.
          zlib: {level: 9}
        });
        //创建临时zip文件
        var output = fs.createWriteStream(zipPath);
        archive.pipe(output);
        //设置需要压缩得文件夹目录 以及替换得名称
        archive.directory(messagePath, name);
        archive.finalize();
        archive.on('end', function (err) {
          fn(null, {"name": name, "path": zipPath});
        });
        archive.on('error', function (err) {
          fn(new Error("压缩文件异常"), null);
        });
      }
    } else {
      fn(new Error("id或name不能为空"), null);
    }
  } catch (e) {
    fn(new Error(e.message), null);
  }
};

最后是提出的公共方法toolUtil的代码,这个单独做为一个js文件维护。

const path = require('path');
const fs = require('fs');
/**
 * 创建目录
 * @param dirpath
 */
exports.mkdirSync = function (dirpath){
  if (!fs.existsSync(dirpath)) {
    var pathtmp;
    dirpath.split(path.sep).forEach(function(dirname) {
      if (pathtmp) {
        pathtmp = path.join(pathtmp, dirname);
      }
      else {
        pathtmp = dirname;
      }
      if (!fs.existsSync(pathtmp)) {
        fs.mkdirSync(pathtmp);
      }
    });
  }
};
//删除所有的文件(将所有文件夹置空)
exports.emptyDir = function(dirpath){
  var self = this;
  //读取该文件夹
  var files = fs.readdirSync(dirpath);
  files.forEach(function(file){
    var filePath = dirpath + '/' + file;
    var stats = fs.statSync(filePath);
    if(stats.isDirectory()){
      self.emptyDir(filePath);
    }else{
      fs.unlinkSync(filePath);
    }
  });
};

希望本文所述对大家node.js程序设计有所帮助。

Javascript 相关文章推荐
JS实现添加,替换,删除节点元素的方法
Jun 30 Javascript
JavaScript toUpperCase()方法使用详解
Aug 26 Javascript
jQuery得到多个值只能用取Class ,不能用取ID的方法
Dec 04 Javascript
使用Math.max,Math.min获取数组中的最值实例
Apr 25 Javascript
jQuery Position方法使用和兼容性
Aug 23 jQuery
微信小程序实现团购或秒杀批量倒计时
Nov 01 Javascript
javascript数据结构之多叉树经典操作示例【创建、添加、遍历、移除等】
Aug 01 Javascript
利用原生JS实现data方法示例代码
May 28 Javascript
javascript function(函数类型)使用与注意事项小结
Jun 10 Javascript
vue+iview分页组件的封装
Nov 17 Vue.js
Js利用正则表达式去除字符串的中括号
Nov 23 Javascript
字节飞书面试promise.all实现示例
Jun 16 Javascript
ES6 Promise对象概念及用法实例详解
Oct 15 #Javascript
详解Vue.js 作用域、slot用法(单个slot、具名slot)
Oct 15 #Javascript
vue vantUI实现文件(图片、文档、视频、音频)上传(多文件)
Oct 15 #Javascript
ES6中let、const的区别及变量的解构赋值操作方法实例分析
Oct 15 #Javascript
vue使用@scroll监听滚动事件时,@scroll无效问题的解决方法详解
Oct 15 #Javascript
vue实现树形结构样式和功能的实例代码
Oct 15 #Javascript
谈谈IntersectionObserver懒加载的具体使用
Oct 15 #Javascript
You might like
php开发工具之vs2005图解
2008/01/12 PHP
通过php修改xml文档内容的方法
2015/01/23 PHP
php动态生成缩略图并输出显示的方法
2015/04/20 PHP
PHP SPL标准库之SplFixedArray使用实例
2015/05/12 PHP
thinkPHP5.0框架配置格式、加载解析与读取方法
2017/03/17 PHP
复选框全选与全不选操作实现思路
2013/08/18 Javascript
JavaScript 浏览器对象模型BOM使用介绍
2015/04/13 Javascript
nodejs实现获取某宝商品分类
2015/05/28 NodeJs
jQuery 遍历函数详解
2015/07/05 Javascript
深入理解MVC中的时间js格式化
2016/05/19 Javascript
深入浅析jQuery对象$.html
2016/08/22 Javascript
AngularJs  unit-testing(单元测试)详解
2016/09/02 Javascript
hovertree插件实现二级树形菜单(简单实用)
2016/12/28 Javascript
Angular实现一个简单的多选复选框的弹出框指令实例
2017/04/25 Javascript
微信扫码支付零云插件版实例详解
2017/04/26 Javascript
通过js控制时间,一秒一秒自己动的实例
2017/10/25 Javascript
Vue不能检测到Object/Array更新的情况的解决
2018/06/26 Javascript
微信小程序实现弹出层效果
2020/05/26 Javascript
vue+element tabs选项卡分页效果
2020/06/29 Javascript
vue-iview动态新增和删除的方法
2020/06/17 Javascript
[40:06]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第一场
2018/04/04 DOTA
Python编写简单的HTML页面合并脚本
2016/07/11 Python
Python结合ImageMagick实现多张图片合并为一个pdf文件的方法
2018/04/24 Python
python实现对csv文件的列的内容读取
2018/07/04 Python
Python 从相对路径下import的方法
2018/12/04 Python
详解Python的三种拷贝方式
2020/02/11 Python
amazeui 验证按钮扩展的实现
2020/08/21 HTML / CSS
英国山地公路自行车商店:Tweeks Cycles
2018/03/16 全球购物
新加坡第一的杂货零售商:NTUC FairPrice
2020/12/05 全球购物
生产车间主管岗位职责
2013/12/28 职场文书
旷课检讨书2000字
2014/01/14 职场文书
学习型党组织建设经验材料
2014/05/26 职场文书
公务员四风问题对照检查材料整改措施
2014/09/26 职场文书
golang switch语句的灵活写法介绍
2021/05/06 Golang
解决pytorch 损失函数中输入输出不匹配的问题
2021/06/05 Python
利用python进行数据加载
2021/06/20 Python