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之对系统的toFixed()方法的修正
May 08 Javascript
jquery 图片预加载 自动等比例缩放插件
Dec 25 Javascript
关于JS判断图片是否加载完成且获取图片宽度的方法
Apr 09 Javascript
详解JavaScript中基于原型prototype的继承特性
May 05 Javascript
基于angular中的重要指令详解($eval,$parse和$compile)
Oct 21 Javascript
JavaScript中数据类型转换总结
Dec 25 Javascript
基于jQuery实现的单行公告活动轮播效果
Aug 23 jQuery
妙用缓存调用链实现JS方法的重载
Apr 30 Javascript
解决vue-quill-editor上传内容由于图片是base64的导致字符太长的问题
Aug 20 Javascript
Vue引用Swiper4插件无法重写分页器样式的解决方法
Sep 27 Javascript
npm qs模块使用详解
Feb 07 Javascript
JavaScript运动原理基础知识详解
Apr 02 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
Discuz! 5.0.0论坛程序中加入一段js代码,让会员点击下载附件前自动弹出提示窗口
2007/04/18 PHP
php 计划任务 检测用户连接状态
2012/03/29 PHP
PHP的伪随机数与真随机数详解
2015/05/27 PHP
php用户密码加密算法分析【Discuz加密算法】
2016/10/12 PHP
PHP+MySQL实现输入页码跳转到指定页面功能示例
2018/06/01 PHP
jquery ajax请求实例深入解析
2012/11/26 Javascript
js图片自动轮播代码分享(js图片轮播)
2014/05/06 Javascript
JavaScript判断textarea值是否为空并给出相应提示
2014/09/04 Javascript
JavaScript设计模式之单件模式介绍
2014/12/28 Javascript
JavaScript实现跑马灯抽奖活动实例代码解析与优化(一)
2016/02/16 Javascript
js生成随机颜色方法代码分享(三种)
2016/12/29 Javascript
jquery实现图片放大点击切换
2017/06/06 jQuery
ES6学习之变量的两种命名方法示例
2017/07/18 Javascript
原生js实现省市区三级联动代码分享
2018/02/12 Javascript
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
Bootstrap 时间日历插件bootstrap-datetimepicker配置与应用小结
2019/05/28 Javascript
基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能
2019/11/12 Javascript
Vue项目配置跨域访问和代理proxy设置方式
2020/09/08 Javascript
[56:45]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第一局
2016/02/28 DOTA
Tensorflow 实现修改张量特定元素的值方法
2018/07/30 Python
Python对象转换为json的方法步骤
2019/04/25 Python
PyQt5重写QComboBox的鼠标点击事件方法
2019/06/25 Python
python定位xpath 节点位置的方法
2019/08/27 Python
如何在Python3中使用telnetlib模块连接网络设备
2020/09/21 Python
python 爬取小说并下载的示例
2020/12/07 Python
CSS3利用text-shadow属性实现多种效果的文字样式展现方法
2016/08/25 HTML / CSS
浅谈cookie和localStorage那些事
2019/08/27 HTML / CSS
英国内衣连锁店:Boux Avenue
2018/01/24 全球购物
俄罗斯美容和健康网上商店:Созвездие Красоты
2019/07/23 全球购物
介绍一下Make? 为什么使用make
2013/12/08 面试题
js正则匹配markdown里的图片标签的实现
2021/03/24 Javascript
十佳班主任事迹材料
2014/01/18 职场文书
竞选纪律委员演讲稿
2014/09/13 职场文书
个人合作协议范本
2015/08/06 职场文书
人民币使用说明书
2019/04/17 职场文书
python tkinter Entry控件的焦点移动操作
2021/05/22 Python