利用koa进行前后端实现文件下载
前言:这篇文章讲述的是在日常开发中前后端如何进行下载文件功能的实现。
相关文章:(会不时更新,有兴趣掘友可以点个关注)
该篇文章是基于# 手把手教你入门koa,让你成为crud工程师的第一步这篇文章的代码来继续编写的。
下载文件
下载文件相对来说比较简单。基本流程就是前端请求下载接口,后端直接返回一个二进制文件流或文件网络地址,前端创建a标签进行href
属性直接赋值,添加download
属性然后代码触发点击,最后消除该标签即可。
前置工作: 做法跟前面类似,在controller文件夹创建一个download.js
文件作为下载模块的代码编写,并作为路由暴露出去。然后在router.js
里面声明该路由。
这里我们还需要用到一个中间件koa-static
,koa-static是静态资源请求中间件,静态资源例如html、js、css、jpg、png等等。这个中间可以让我们有能力去访问服务器的静态资源。
npm install koa-static
//app.js
//新增以下代码
const static = require("koa-static")
//__dirname为文件所在的当前目录,app.js所在就是为根目录,这样我们可以直接localhost:3001/文件名,以此来读取文件
app.use(static(__dirname + '/dowload', {
index: false, // 默认为true 访问的文件为index.html 可以修改为别的文件名或者false
hidden: false, // 是否同意传输隐藏文件
defer: true // 如果为true,则在返回next()之后进行服务,从而允许后续中间件先进行响应
}))
文件网络地址
后端实现代码:
后端设置下载请求的响应头content-disposition
attachment
表示让浏览器强制下载filename
用于设置下载弹出框里预填的文件名
<script>
const Router = require("@koa/router");
const fs = require("fs");
const router = new Router();
router.get("/url", async (ctx, next) => {
let id = ctx.request.query.id;
//这里应该是根据标识来对数据库进行查询然后返回对应的url,这里偷懒直接写死一个链接
// let url = "http://localhost:3001/1.mp4"//测试MP4格式
// let url = "http://localhost:3001/2.webp"//测试webp格式
// let url = "http://localhost:3001/2.gif"//测试gif格式
// let url = "http://localhost:3001/js.jpeg";//测试jpeg格式
let url = "http://localhost:3001/5.png";
//响应首部 Access-Control-Expose-Headers 就是控制“暴露”的开关,它列出了哪些首部可以作为响应的一部分暴露给外部。
ctx.set("Access-Control-Expose-Headers","content-disposition");
ctx.set('content-disposition', 'attachment;filename=' + encodeURIComponent('test.mp4'))
ctx.body = { code: 200, msg: "操作成功", url: url };
});
module.exports = router.routes();
<script>
前端实现代码:
1.通过a标签直接下载
<a href="http://localhost:3001/5.png?response-content-type=application/octet-stream" download="1.jpg">下载文件</a>
该方法一般来说,下载通过以上写法可以实现下载。但是download
属性本身存在一些限制或者系统的限制,会导致该属性失效,转而改为打开文件。
- 仅适用于同源URL(同协议,同端口,同域名)也就是说
href
属性的地址必须是非跨域的地址,如果引用的是第三方的网站或者说是前后端分离的项目(调用后台的接口),这时download
就会不起作用 - 如果是非同源url,可以使用
blob: URLs
和data: URLs
网上说在地址后面加上?response-content-type=application/octet-stream
,使用这个contentType的话访问页面的时候浏览器就会开启下载框对其内容进行下载。(已测试过,无效)
如果是下载浏览器无法解析的文件,例如.exe,.xlsx..
那么浏览器会自动下载,但是如果使用浏览器可以解析的文件,比如.txt,.png,.pdf....
2.通过canvas进行下载,仅限于静态图片格式,如png,jpg,webp这些静态图片,gif格式会下载为第一帧的静态图片。canvas这个东西只能渲染静态图片,不能渲染动图。
<template>
<div>
<el-button @click="downLoadUrl">下载</el-button>
</div>
</template>
<script setup>
//返回url链接进行下载
const downLoadUrl = async () => {
//传递参数根据前后端一起定义,根据什么可以让后端传回链接
const res = await axios.get('http://localhost:3001/download/url', { params: { id: 1 } })
console.log(res.data.url)
downloadIamge(res.data.url, '测试')
}
const downloadIamge = (url, name) => {
var image = new Image()
// 解决跨域 Canvas 污染问题 解决跨域问题-并很差使,须要改对应的请求服务器
image.setAttribute('crossOrigin', 'anonymous')
image.onerror = function (err) {
console.log('err', err)
}
image.onload = function () {
var canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
var context = canvas.getContext('2d')
context.drawImage(image, 0, 0, image.width, image.height)
var url = canvas.toDataURL(url)
// 生成一个a元素
var a = document.createElement('a')
// 建立一个单击事件
var event = new MouseEvent('click')
// 将a的download属性设置为咱们想要下载的图片名称,若name不存在则使用‘下载图片名称’做为默认名称
a.download = name || '下载图片名称'
// 将生成的URL设置为a.href属性
a.href = url
console.log('下载图片')
// 触发a的单击事件
a.dispatchEvent(event)
}
image.src=url
}
</script>
由上面的各个格式测试可知canvas
只适用于静态图片的下载,对于其他格式均不支持,局限性太大。一般来说也不建议使用该方法
3.通过链接转换为二进制数据进行下载
//修改downLoadUrl方法即可
//返回url链接进行下载
const downLoadUrl = async () => {
//传递参数根据前后端一起定义,根据什么可以让后端传回链接
const res = await axios.get('http://localhost:3001/download/url', { params: { id: 1 } })
let url = res.data.url
console.log(res.data.url)
let fileName = res.headers['content-disposition'].split(';')[1].split('filename=')[1]
console.log(fileName)
changeBlob(url).then((res) => {
const blob = new Blob([res])
console.log('blob',blob)
if ('download' in document.createElement('a')) {
// 非IE下载
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = window.URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL对象
document.body.removeChild(elink)
} else {
// IE10+ 下载
navigator.MsSaveBlob(blob, name)
}
})
}
// 地址转文件
const changeBlob = (url) => {
//模拟发送http请求,将文件链接转换成文件流
return new Promise((resolve) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'blob'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
}
}
xhr.send()
})
}
文件流
这种方式对于前端来说就是少了将网络地址转换为二进制数据这一步骤,对于后端来说是如果数据库存储的是文件相对地址要从数据库读到对应的相对地址,然后后端进行域名+地址拼接,利用fs
模块进行文件读取,然后将二进制数据传递给前端。如果数据库存储oss则通过oss的api来操作,如果是存储二进制数据或者base64格式(不推荐,会对造成数据库压力)则直接返回给前端
后端实现代码
router.get("/file",async(ctx,next)=>{
let id = ctx.request.query.id;
//模拟从数据库中取出了对应的地址,括号应该是数据库查询出来的相对路径,然后拼接上服务器地址组成绝对路径进行读取操作,将二进制数据返回给前端
let filePath = path.resolve('./dowload/1.mp4')
let fileName = 'test.mp4'
console.log(filePath)
const file = fs.readFileSync(filePath)
console.log('file',file)
ctx.set("Access-Control-Expose-Headers","content-disposition");
ctx.set(
"Content-disposition",
"attachment;filename=" + encodeURIComponent(fileName)
);
ctx.body = file; //返回文件流
})
前端实现代码
//返回二进制文件流下载
const downLoadFile = async() => {
const res = await axios.get('http://localhost:3001/download/file', { params: { id: 1 } })
let url = res.data.url
let fileName = res.headers['content-disposition'].split(';')[1].split('filename=')[1]
const blob = new Blob([url])
if ('download' in document.createElement('a')) {
// 非IE下载
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = window.URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL对象
document.body.removeChild(elink)
} else {
// IE10+ 下载
navigator.MsSaveBlob(blob, fileName)
}
}
总结:关于一般业务中所用的下载功能基本就这些了。希望这篇文章能对你有所帮助,如果可以麻烦给个赞,谢谢(* ̄︶ ̄)
转载自:https://juejin.cn/post/7187665292159287351