Koa2 之文件上传下载的示例代码


Posted in Javascript onMarch 29, 2018

前言

上传下载在 web 应用中还是比较常见的,无论是图片还是其他文件等。在 Koa 中,有很多中间件可以帮助我们快速的实现功能。

文件上传

在前端中上传文件,我们都是通过表单来上传,而上传的文件,在服务器端并不能像普通参数一样通过 ctx.request.body 获取。我们可以用 koa-body 中间件来处理文件上传,它可以将请求体拼到 ctx.request 中。

// app.js
const koa = require('koa');
const app = new koa();
const koaBody = require('koa-body');

app.use(koaBody({
  multipart: true,
  formidable: {
    maxFileSize: 200*1024*1024 // 设置上传文件大小最大限制,默认2M
  }
}));

app.listen(3001, ()=>{
  console.log('koa is listening in 3001');
})

使用中间件后,就可以在 ctx.request.body.files 中获取上传的文件内容。需要注意的就是设置 maxFileSize,不然上传文件一超过默认限制就会报错。

接收到文件之后,我们需要把文件保存到目录中,返回一个 url 给前端。在 node 中的流程为

  1. 创建可读流 const reader = fs.createReadStream(file.path)
  2. 创建可写流 const writer = fs.createWriteStream('upload/newpath.txt')
  3. 可读流通过管道写入可写流 reader.pipe(writer)
const router = require('koa-router')();
const fs = require('fs');

router.post('/upload', async (ctx){
 const file = ctx.request.body.files.file; // 获取上传文件
 const reader = fs.createReadStream(file.path); // 创建可读流
 const ext = file.name.split('.').pop(); // 获取上传文件扩展名
 const upStream = fs.createWriteStream(`upload/${Math.random().toString()}.${ext}`); // 创建可写流
 reader.pipe(upStream); // 可读流通过管道写入可写流
 return ctx.body = '上传成功';
})

该方法适用于上传图片、文本文件、压缩文件等。

文件下载

koa-send 是一个静态文件服务的中间件,可用来实现文件下载功能。

const router = require('koa-router')();
const send = require('koa-send');

router.post('/download/:name', async (ctx){
 const name = ctx.params.name;
 const path = `upload/${name}`;
 ctx.attachment(path);
  await send(ctx, path);
})

在前端进行下载,有两个方法: window.open 和表单提交。这里使用简单一点的 window.open 。

<button onclick="handleClick()">立即下载</button>
<script>
 const handleClick = () => {
 window.open('/download/1.png');
 }
</script>

这里 window.open 默认是开启一个新的窗口,一闪然后关闭,给用户的体验并不好,可以加上第二个参数 window.open('/download/1.png', '_self'); ,这样就会在当前窗口直接下载了。然而这样是将 url 替换当前的页面,则会触发 beforeunload 等页面事件,如果你的页面监听了该事件做一些操作的话,那就有影响了。那么还可以使用一个隐藏的 iframe 窗口来达到同样的效果。

<button onclick="handleClick()">立即下载</button>
<iframe name="myIframe" style="display:none"></iframe>
<script>
 const handleClick = () => {
 window.open('/download/1.png', 'myIframe');
 }
</script>

批量下载

批量下载和单个下载也没什么区别嘛,就多执行几次下载而已嘛。这样也确实没什么问题。如果把这么多个文件打包成一个压缩包,再只下载这个压缩包,是不是体验起来就好一点了呢。

文件打包

archiver 是一个在 Node.js 中能跨平台实现打包功能的模块,支持 zip 和 tar 格式。

const router = require('koa-router')();
const send = require('koa-send');
const archiver = require('archiver');

router.post('/downloadAll', async (ctx){
 // 将要打包的文件列表
 const list = [{name: '1.txt'},{name: '2.txt'}];
 const zipName = '1.zip';
 const zipStream = fs.createWriteStream(zipName);
  const zip = archiver('zip');
  zip.pipe(zipStream);
 for (let i = 0; i < list.length; i++) {
 // 添加单个文件到压缩包
 zip.append(fs.createReadStream(list[i].name), { name: list[i].name })
 }
 await zip.finalize();
 ctx.attachment(zipName);
 await send(ctx, zipName);
})

如果直接打包整个文件夹,则不需要去遍历每个文件 append 到压缩包里

const zipStream = fs.createWriteStream('1.zip');
const zip = archiver('zip');
zip.pipe(zipStream);
// 添加整个文件夹到压缩包
zip.directory('upload/');
zip.finalize();

注意:打包整个文件夹,生成的压缩包文件不可存放到该文件夹下,否则会不断的打包。

中文编码问题

当文件名含有中文的时候,可能会出现一些预想不到的情况。所以上传时,含有中文的话我会对文件名进行 encodeURI() 编码进行保存,下载的时候再进行 decodeURI() 解密。

ctx.attachment(decodeURI(path));
await send(ctx, path);

ctx.attachment 将 Content-Disposition 设置为 “附件” 以指示客户端提示下载。通过解码后的文件名作为下载文件的名字进行下载,这样下载到本地,显示的还是中文名。

然鹅, koa-send 的源码中,会对文件路径进行 decodeURIComponent() 解码:

// koa-send
path = decode(path)

function decode (path) {
 try {
  return decodeURIComponent(path)
 } catch (err) {
  return -1
 }
}

这时解码后去下载含中文的路径,而我们服务器中存放的是编码后的路径,自然就找不到对应的文件了。

要想解决这个问题,那么就别让它去解码。不想动 koa-send 源码的话,可使用另一个中间件 koa-sendfile 代替它。

const router = require('koa-router')();
const sendfile = require('koa-sendfile');

router.post('/download/:name', async (ctx){
 const name = ctx.params.name;
 const path = `upload/${name}`;
 ctx.attachment(decodeURI(path));
  await sendfile(ctx, path);
})

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript Event学习第十章 一些可替换的事件对
Feb 10 Javascript
jQuery中[attribute]选择器用法实例
Dec 31 Javascript
JavaScript字符串删除重复字符的方法
Dec 25 Javascript
基于JavaScript实现网页倒计时自动跳转代码
Dec 28 Javascript
Jquery修改image的src属性,图片不加载问题的解决方法
May 17 Javascript
利用AJAX实现WordPress中的文章列表及评论的分页功能
May 17 Javascript
vue watch深度监听对象实现数据联动效果
Aug 16 Javascript
vue动态设置img的src路径实例
Sep 18 Javascript
vue axios 简单封装以及思考
Oct 09 Javascript
vue-router 起步步骤详解
Mar 26 Javascript
使用Node.js在深度学习中做图片预处理的方法
Sep 18 Javascript
vue利用全局导航守卫作登录后跳转到未登录前指定页面的实例代码
May 19 Javascript
AngularJS下$http服务Post方法传递json参数的实例
Mar 29 #Javascript
Vue 将后台传过来的带html字段的字符串转换为 HTML
Mar 29 #Javascript
利用js实现前后台传送Json的示例代码
Mar 29 #Javascript
vue中Npm run build 根据环境传递参数方法来打包不同域名
Mar 29 #Javascript
JS获取url参数,JS发送json格式的POST请求方法
Mar 29 #Javascript
spirngmvc js传递复杂json参数到controller的实例
Mar 29 #Javascript
Vue.js 表单控件操作小结
Mar 29 #Javascript
You might like
PHP5.4中json_encode中文转码的变化小结
2013/01/30 PHP
PHP中round()函数对浮点数进行四舍五入的方法
2014/11/19 PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
2017/09/20 PHP
PDO::exec讲解
2019/01/28 PHP
PHP实时统计中文字数和区别
2019/02/28 PHP
通过jquery还原含有rowspan、colspan的table的实现方法
2012/02/10 Javascript
利用函数的惰性载入提高javascript代码执行效率
2014/05/05 Javascript
基于NodeJS的前后端分离的思考与实践(一)全栈式开发
2014/09/26 NodeJs
jQuery中$.extend()用法实例
2015/06/24 Javascript
使用jspdf生成pdf报表
2015/07/03 Javascript
JavaScript与HTML的结合方法详解
2015/11/23 Javascript
微信小程序-拍照或选择图片并上传文件
2017/01/06 Javascript
angularjs 的数据绑定实现原理
2018/07/02 Javascript
vue-cli 引入jQuery,Bootstrap,popper的方法
2018/09/03 jQuery
JavaScript实现新年倒计时效果
2018/11/17 Javascript
vue 更改连接后台的api示例
2019/11/11 Javascript
elementui实现预览图片组件二次封装
2020/12/29 Javascript
[02:44]DOTA2英雄基础教程 魅惑魔女
2014/01/07 DOTA
[53:13]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS LGD-GAMING
2014/05/22 DOTA
[07:12]2014DOTA2西雅图国际邀请赛 黑马Liquid专题采访
2014/07/12 DOTA
使用Python对Excel进行读写操作
2017/03/30 Python
Python实现Windows和Linux之间互相传输文件(文件夹)的方法
2017/05/08 Python
Python实现的朴素贝叶斯分类器示例
2018/01/06 Python
Python操作远程服务器 paramiko模块详细介绍
2019/08/07 Python
Python数据存储之 h5py详解
2019/12/26 Python
pytorch梯度剪裁方式
2020/02/04 Python
python访问hdfs的操作
2020/06/06 Python
localstorage和sessionstorage使用记录(推荐)
2017/05/23 HTML / CSS
HTML5 WebSocket实现点对点聊天的示例代码
2018/01/31 HTML / CSS
聪明的粉丝购买门票的地方:TickPick
2018/03/09 全球购物
光电信息专业应届生求职信
2013/10/07 职场文书
工程管理专业个人求职信范文
2013/12/07 职场文书
病人家属写给医院的感谢信
2015/01/23 职场文书
2016年秋季趣味运动会开幕词
2016/03/04 职场文书
联想win10摄像头打不开怎么办?win10笔记本摄像头打不开解决办法
2022/04/08 数码科技
mysql实现将字符串字段转为数字排序或比大小
2022/06/14 MySQL