Koa2 之文件上传下载

news/2024/7/7 21:22:24 标签: 前端, javascript

前言

上传下载在 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>javascript">
	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>javascript">
	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);
})
复制代码

更多文章:lin-xin/blog


http://www.niftyadmin.cn/n/1119909.html

相关文章

我的runtime理解和用法

1.什么是运行时机制 1&#xff09;运行时机制是比较低层的纯c语言的API文档&#xff0c;属于c语言库&#xff0c;包含了许多底层的c语言API 2&#xff09;oc中的对象&#xff0c;方法在运行时才知道它时什么类型的&#xff0c;这个类型是动态生成的像KVO的底层实现就是运用了ru…

.NET Core 2.0 开源Office组件 NPOI

前言 去年 12 月&#xff0c;我移植了大家所熟知 NPOI 到 .NET Core 版本&#xff0c;这里是当时发的博客&#xff0c;当时得到了很多同学的支持&#xff0c;社区反应也很好&#xff0c;在这里非常感谢当时推荐的朋友们。 去年的那个版本是针对于 .NET Core 1.0 的&#xff0c;…

OCP-1Z0-051 补充题库 第43题 子查询

一、原题 Exhibit contains the structure of PRODUCTS table: Evaluate the following query: What would be the outcome of executing the above SQL statement? A. It produces an error B. It shows the names of products whose list price is the second h…

解决“此电脑上没有安装True Speech声音×××”的方法

解决“此电脑上没有安装True Speech声音”的方法很多朋友也许都和我一样同学学习别人的视频来了解和掌握从书本上没有学到的东西。并且很多视频都是屏幕录像专家录制的。当播放时出现“此电脑上没有True Speech声音&#xff0c;无法播放此文件中的声音&#xff0c;可能是你的操…

禅道 bug指向为数字问题解决过程

browse.html.php <td <?php if($bug->assignedTo $this->app->user->account) echo class"red";?>><?php echo zget($users, $bug->assignedTo, $bug->assignedTo);?></td>转载于:https://www.cnblogs.com/cb168/p/5…

列举你能想到的UNIX信号,并说明信号用途

信号是一种软中断&#xff0c;是一种处理异步事件的方法。一般来说&#xff0c;操作系统都支持许多信号。尤其是UNIX&#xff0c;比较重要应用程序一般都会处理信号。 UNIX定义了许多信号&#xff0c;比如SIGINT表示中断字符信号&#xff0c;也就是CtrlC的信号&#xff0c;SIG…

MailBee.NET Objects退回邮件教程(一):SMTP部分

2019独角兽企业重金招聘Python工程师标准>>> MailBee.NET Objects是一款为创建、发送、接收以及处理电子邮件而设计的健壮、功能丰富的.NET控件。几行代码便可为应用程序添加E-Mail支持&#xff0c;简单高效。具备“必需”以及独特的功能&#xff0c;这些控件帮助开…

OCP-1Z0-051 补充题库 第44题 NATURAL JOIN自然连接

一、原题 View the Exhibits and examine the structures of the PRODUCTS, SALES, and CUSTOMERS tables.You issue the following query: SQL>SELECT p.prod_id,prod_name,prod_list_price, quantity_sold,cust_last_name FROM products p NATURAL JOIN sal…