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 相关文章推荐
Javascript学习笔记7 原型链的原理
Jan 11 Javascript
图片延迟加载的实现代码(模仿懒惰)
Mar 29 Javascript
javascript自定义的addClass()方法
May 28 Javascript
javascript控制在光标位置插入文字适合表情的插入
Jun 09 Javascript
jQuery截取指定长度字符串的实现原理及代码
Jul 01 Javascript
jQuery实现分章节锚点“回到顶部”动画特效代码
Oct 23 Javascript
全面解析Bootstrap排版使用方法(标题)
Nov 30 Javascript
jQuery简单获取键盘事件的方法
Jan 22 Javascript
Javascript中的arguments对象
Jun 20 Javascript
AngularJS与后端php的数据交互方法
Aug 13 Javascript
解决vue路由后界面没有变化,但是链接有的问题
Sep 01 Javascript
Node4-5静态资源服务器实战以及优化压缩文件实例内容
Aug 29 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数组函数序列之array_values() 获取数组元素值的函数与方法
2011/10/30 PHP
PHP中使用cURL实现Get和Post请求的方法
2013/03/13 PHP
phpMyAdmin自动登录和取消自动登录的配置方法
2014/05/12 PHP
php简单实现屏蔽指定ip段用户的访问
2015/04/29 PHP
php str_replace替换指定次数的方法详解
2017/05/05 PHP
PHP配置ZendOpcache插件加速
2019/02/14 PHP
PHP反射实际应用示例
2019/04/03 PHP
TP5(thinkPHP5)框架使用ajax实现与后台数据交互的方法小结
2020/02/10 PHP
jQuery选择没有colspan属性的td的代码
2010/07/06 Javascript
JS中不为人知的五种声明Number的方式简要概述
2013/02/22 Javascript
Jquery图片滚动与幻灯片的实例代码
2013/04/08 Javascript
JavaScript中通过prototype属性共享属性和方法的技巧实例
2015/03/13 Javascript
javascript无刷新评论实现方法
2015/05/13 Javascript
javascript去掉代码里面的注释
2015/07/24 Javascript
JavaScript html5利用FileReader实现上传功能
2020/03/27 Javascript
JS点击缩略图整屏居中放大图片效果
2017/07/04 Javascript
浅谈vue自定义全局组件并通过全局方法 Vue.use() 使用该组件
2017/12/07 Javascript
python二叉树的实现实例
2013/11/21 Python
python三元运算符实现方法
2013/12/17 Python
Python生成随机密码
2015/03/10 Python
使用Python操作MySQL的一些基本方法
2015/08/16 Python
Python采用Django制作简易的知乎日报API
2016/08/03 Python
python3处理含有中文的url方法
2018/05/10 Python
python celery分布式任务队列的使用详解
2019/07/08 Python
Python如何使用内置库matplotlib绘制折线图
2020/02/24 Python
python爬取网易云音乐热歌榜实例代码
2020/08/07 Python
华为的Java面试题
2014/03/07 面试题
商务英语大学生职业生涯规划书范文
2014/01/01 职场文书
校园活动策划书范文
2014/01/10 职场文书
秋季运动会稿件
2014/01/30 职场文书
文明餐桌活动方案
2014/02/11 职场文书
三八妇女节活动总结
2014/05/04 职场文书
高考作弊检讨书1500字
2015/02/16 职场文书
车间统计员岗位职责
2015/04/14 职场文书
毕业证明模板
2015/06/19 职场文书
Python Matplotlib绘制动画的代码详解
2022/05/30 Python