a标签与Blob下载文件的区别和获取文件下载进度
文件下载的几种方式。
大家都做过文件下载,无非就是通过a标签给定一个href。
用户点击下载按钮。
或者使用Blob的方式进行下载。
这两种是很常见的,也是我们平时做使用最多的方式。
那么我们知道这2种方式有什么区别呢?
如果不清楚,也别着急下面我们一起来探索下:
node + express + cors 搭建环境
// 引入express
const express=require("express");
// 引入路径模块
const path=require("path")
// 处理跨域的插件,需要下载一下这个模块
const cors = require('cors')
// 文件下载相关的接口
const fileListRouter = require('./routes/fileList');
const app=express();
// 处理跨域
app.use(cors())
app.use(express.static(path.join(__dirname, '/public')));
// 下载文件的路由路径
app.use('/fileList', fileListRouter);
//服务端口
app.listen(3000,function () {
console.log("127.0.0.1:3000")
});
文件下载的思路分析
我们首先需要知道这个被下载文件的具体地址。
然后我们需要检查这个文件是否存在(假设存在就具有访问权限)。
如果存在的话,使用异步的方式进行读取。
然后给响应头设置文件类型、文件名、告诉浏览器是是接下载还是展示。
最后一步是把文件内容以二进制的形式写到响应体中,并发送出去。
node提供一个接口实现文件下载
const express = require('express');
const path = require('path');
const fs = require('fs');
const router = express.Router();
router.get('/download', (req, res) => {
// 获取传参对象信息
const queryParams = req.query;
console.log('传递的参数对象', queryParams)
// 获取下载文件的名称,需要注意filename = 文件+文件类型,否者会出现404
const filename = queryParams.fileName
// 从当前所在的目录(__dirname)开始,进入到名为 allFIle 的子目录, 最后定位到名为 filename变量的文件
const filePath = path.join(__dirname, 'allFIle', filename);
console.log('文件路径', filePath)
// 检查文件是否存在
fs.access(filePath, fs.constants.F_OK, (err) => {
// 如果失败,err 是一个对象。成功的话,err是null
if (err) {
return res.status(404).send('File not found');
}
// 'application/octet-stream':一种通用的二进制数据的MIME类型,表示 "任意二进制数据"。
// 当我们不确定文件的MIME类型,或想要强制浏览器将响应作为文件下载而不是直接打开时,通常会使用这个类型。
const mimeType = 'application/octet-stream';
// 用于指示内容该以何种形式展示,直接在浏览器中打开(内联显示)还是作为附件下载 。
// 这里的值 attachment; filename="${filename}"告诉浏览器将响应的内容作为附件下载,
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
// 设置HTTP响应头Content-Type,表示响应的内容是任意二进制数据
res.setHeader('Content-Type', mimeType);
// 创建可读流并传递给响应对象
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
});
});
module.exports = router;
上面代码中的fs.access
在 Node.js 中,fs.access 是一个用于检查文件或目录是否存在
以及是否具有特定权限的异步文件系统方法。
该方法允许我们验证当前进程是否对文件或目录具有读取、写入或执行权限。
fs.access的基本语法
fs.access(path, mode, callback)
path: 文件或目录的路径
mode(可选):要检查的权限,可以使用或 | 运算符进行组合
fs.constants.F_OK:检查文件是否存在。
fs.constants.R_OK:检查是否具有读取权限。
fs.constants.W_OK:检查是否具有写入权限。
fs.constants.X_OK:检查是否具有执行权限。
如果没有提供 mode,则默认检查 fs.constants.F_OK
callback(Function):回调函数,回调函数中有一个参数err。
如果操作成功,err 为 null;
如果操作失败,err 是一个 Error 对象。
简单使用 fs.access
const fs = require('fs');
// 检查这个文件是否存在或者可读。
fs.access('./readme.md', fs.constants.F_OK | fs.constants.R_OK, (err) => {
if (err) {
console.error('文件不存在或不可读');
} else {
console.log('文件存在且可读');
}
});
方式1:前端 a标签下载
<template>
<div>
<el-button>
<a href='http://127.0.0.1:3000/fileList/download?fileName=shipin.mp4'>通过a标签直接下载</a>
</el-button>
</div>
</template>
方式2:通过Blob的方式下载
<template>
<div>
<el-button type="primary" @click="fileDownHandler">
通过blob来下载
</el-button>
</div>
</template>
<script>
import axios from 'axios'
export default {
methods: {
fileDownLoad(fileData, fileName, callBack) {
// 创建Blob实例 fileData 接受的是一个Blob
// 等待服务器把所有的数据都传输到浏览器的内存后,然后再把内存变为 blob 格式
let blob = new Blob([fileData], {
type: 'application/octet-stream',
})
if (!!window.ActiveXObject || 'ActiveXObject' in window) {
window.navigator.msSaveOrOpenBlob(blob, fileName)
callBack()
} else {
// 创建a标签
const link = document.createElement('a')
// 隐藏a标签
link.style.display = 'none'
// 在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 指定源 object的内容
// 或者说(link.href 得到的是一个地址,你可以在浏览器打开。指向的是文件资源)
link.href = URL.createObjectURL(blob)
console.log('link.href指向的是文件资源', link.href)
//设置下载为excel的名称
link.setAttribute('download', fileName)
document.body.appendChild(link)
// 模拟点击事件
link.click()
// 移除a标签
document.body.removeChild(link)
// 回调函数,表示下载成功
callBack()
}
},
fileDownHandler(){
axios.get('http://127.0.0.1:3000/fileList/download?fileName=shipin.mp4', { responseType: 'blob' }).then((response) => {
console.log('文件下载返回来的数据', response)
this.fileDownLoad(response.data, '视频文件', ()=>{
console.log('下载成功')
})
}).catch(function (error) {
console.log(error);
});
}
}
}
</script>
a标签的下载方式:
数据从服务端不断流向浏览器
浏览器会不会等待服务器把这个文件的所有数据都传输完后再触发下载呢?
答案是:不会。等会我们可以通过来验证一下
浏览器只要确认这个响应是成功的。
它就不会去等待全部数据都传输过来,才触发下载。
而是直接去触发下载行为。
这样数据就像流水一般,从服务器经过浏览器流向了文件。
数据从服务器==>经过浏览器 ===>文件。
这个过程浏览器不会保存这些数据
这样的好处:哪怕这个文件有很大(几十或者上百个G)对浏览器的内存都不会造成什么影响。
也就是说:数据经过浏览器流向文件(从网络到磁盘)这一过程对浏览器的内存几乎没怎么占用。
总结:通过a标签的形式来下载大文件是非常友好的。
blob的下载方式
浏览器会等待服务器把所有的数据都传输完成后。
把所有的数据都放入浏览器的内存中
然后生成一个blob对象
然后创建一个本地的url地址
创建a标签,通过a标签,下载保留在浏览器中的所有数据
这样会出现一个问题。
当文件较大的时候,会出现卡死的现象。
出现卡死的现象的原因:
浏览器会等待服务器中把所有的数据都传输完毕后,才进行下载。
a标签和 Blob 下载的区别
1,在下载过程中,离开当前页或者属性页面。
a标签下载不会中断,会继续下载。Blob会中断下载。
2,在下载过程中,览器会不会等待服务器把该文件的所有数据都传输完后,才触发下载?
a标签下载不会等待所有的数据传输完毕后才触发下载,浏览器只要确认这个响应是成功的。就会马上触发下载。
Blob会等待所有的数据都传输完毕后才触发下载。
3,a标签的下载无法携带token进行鉴权(但可以通过cookie鉴权),Blob可以进行鉴权
axios中的onDownloadProgress函数的出场
在做文件下载的时候
如果文件较大产品希望可以显示一个进度条
那么axios支持吗?
通过查看文档,它有一个onDownloadProgress函数
用于获取请求的进度结果
但是需要后端配合,在响应头中设置Content-Length属性,
然后再onDownloadProgress函数中两个非常重要的字段total和loaded
total表示当前文件的大小,单位是byte(字节)
loaded表示当前获取的文件进度
然后我们就能计算出当前文件的下载进度了
如果不设置Content-Length,则total的值是0或者undefined
node提供文件下载的接口并设置Content-Length
router.get('/downFile', (req, res) => {
const filename = 'shipin.mp4'
// 从当前所在的目录(__dirname)开始,进入到名为 allFIle 的子目录, 最后定位到名为 filename变量的文件
const filePath = path.join(__dirname, 'allFIle', filename);
console.log('文件路径', filePath)
// 一个异步方法,用于获取文件或目录的详细信息,包括文件大小、创建时间、修改时间、权限等。
fs.stat(filePath, (err, stats) => {
// 如果失败,err 是一个对象。成功的话,err是null
if (err) {
return res.status(404).send('File not found');
}
const fileSize = stats.size;
console.log('fileSize', fileSize)
// 文件类型
const mimeType = 'video/mp4';
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
// 设置HTTP响应头Content-Type,表示响应的内容是视频
res.setHeader('Content-Type', mimeType);
// 设置文件大小,如果要获取响应进度,这个属性必须设置
res.setHeader('Content-Length', fileSize);
// 创建可读流并传递给响应对象
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
});
});
onDownloadProgress函数的使用
export function fileDownShowProgress(params, callBack) {
return httpRequest({
method: 'get',
url: '/fileList/downFile',
baseURL: 'http://127.0.0.1:3000',
isAbort: true, // 这个属性是我封装的取消请求,可以忽略
params: params,
responseType: 'blob',
onDownloadProgress: function (progressEvent) {
console.log('progressEvent参数:', progressEvent)
// 计算下载进度百分比
const percentNum = Math.round((progressEvent.loaded * 100) / progressEvent.total);
callBack(percentNum)
}
})
}
<template>
<div class="down-page">
<el-button @click="fileDownShowProgressApi">文件下载-显示进度</el-button>
<div class="set-with" v-if="hiddenFlag">
<el-progress :percentage="percentNum"></el-progress>
</div>
</div>
</template>
<script>
import {fileDownShowProgress} from '@/request/api.js'
export default {
data(){
return {
percentNum:0,
hiddenFlag:false
}
},
methods:{
fileDownShowProgressApi(){
fileDownShowProgress({},(percentNum)=>{
console.log('进度值', percentNum)
this.percentNum = percentNum
if(this.percentNum <= 0 || this.percentNum >=100){
this.hiddenFlag = false
}else{
this.hiddenFlag = true
}
}).then(res=>{
console.log('返回来的数据', res)
this.fileDownLoad(res, '视频文件', ()=>{
console.log('下载成功')
})
}).catch(err=>{
// 有可能超时或其他异常情况
this.hiddenFlag= false
this.percentNum = 0
console.log('err:', err)
})
},
fileDownLoad(fileData, fileName, callBack) {
...省略上面有这一部分的代码...
callBack()
}
},
}
}
a标签与Blob下载文件的区别和获取文件下载进度的更多相关文章
- 由于想要实现下载的文件可以进行选择,而不是通过<a>标签写死下载文件的参数,所以一直想要使用JFinal结合ajax实现文件下载,但是ajax实现的文件下载并不能触发浏览器的下载文件弹出框,这里通过模拟表单提交实现同样的效果。
由于想要实现下载的文件可以进行选择,而不是通过<a>标签写死下载文件的参数,所以一直想要使用JFinal结合ajax实现文件下载(这样的话ajax可以传递不同的参数),但是ajax实现的文 ...
- 请求接口类型blob,下载文件
axiosGet () { var This = this var url = '/group1/M00/00/21/MejEvGOX_zOAL2kiAAAAUhB5Iqg138.txt?token= ...
- Struts2学习笔记--使用Response下载文件和Struts2的StreamResult文件下载
使用Response下载文件,servlet中的文件下载是通过流来实现的 我在webRoot文件夹下新建了一个文件夹from,里边放了一张图片,这里就以下载这张图片为例:download.jsp很 ...
- Blob下载文件 & 模拟滚动条实现
1.vue切换路由视图时,事件钩子顺序是 当前模块create-->上一个模块beforeDestroy-->当前模块mounted 因此注册全局事件(比如给window注册事件)应放在m ...
- python官网几个下载文件的区别
进入python官方,下载python编译器,提供了如下几个版本进行选择,这些版本分别是什么意思呢? Python 3.7.1 - 2018-10-20 Download Windows x86 we ...
- 应用HTML5 标签下载文件
使用HTML5 <a>标签可以直接下载文件而不用通过后台action. <a href="/uploadfolder/xxxx.txt">点击下载</ ...
- 下载文件的Restful接口的前端实现
背景介绍 后端是springboot框架,接口是restful风格接口,需求是异步实现一个文件下载,该文件是动态生成的,以流的方式返回给前端.存在的问题是,ajax不支持post方式传参数,并触发浏览 ...
- mvc根据绝对路径下载文件
首先页面需要一个a标签直接指向下载文件的Action并传值:图片地址,以及图片名称(记住要带后缀名的). 然后是Action里面的代码. SiteHelper.DownloadFile(fileUrl ...
- vue下载文件
import fileDownload from 'js-file-download' let params = { ", ", "filename":&quo ...
- Android从网络某个地址下载文件、写入SD卡
首先创建一个HttpDownloader类,获取下载文件的网络地址,将文件下载下来以String流的方式返回: public String download(String urlStr){ //url ...
随机推荐
- JavaScript Library – Svelte
前言 上一回我介绍了 Alpine.js.作为我开发企业网站 draft 版本的 render engine. 用了一阵子后,我觉得它真的非常不好用.所以打算换一个. 前端有好几个 framework ...
- AD域下,环境下办公机系统时间不准确
事件起因: 某部门一同事电脑时间和AD域控时间相差3分钟,虽然说时间相差5分钟内问题不大,但是本着有问题就解决的原则,还是花了点时间去查资料解决. (小小吐槽一下,在我看来域控机是掌管下面所有的办公机 ...
- 10款好用的开源 HarmonyOS 工具库
大家好,我是 V 哥,今天给大家分享10款好用的 HarmonyOS的工具库,在开发鸿蒙应用时可以用下,好用的工具可以简化代码,让你写出优雅的应用来.废话不多说,马上开整. 1. efTool efT ...
- Android应用启动全流程分析(源码深度剖析)
目录 1.前言 2.大纲 3. Input触控事件处理流程 3.1 系统机制分析 3.2 结合Systrace分析 4. 应用进程的创建与启动 4.2 创建应用进程 4.2.1 AMS 发送socke ...
- KPTI——可以缓解“熔断” (Meltdown) 漏洞的内核新特性
Linux 内核修复办法:内核页表隔离KPTl(kernel page table isolation) 每个进程一张页表变成两张:运行在内核态和运行在用户态时分别使用各自分离的页表 Kernel页表 ...
- 墨天轮访谈 | Pika数据库陈磊:云时代下,键值数据库是否会被替代?
分享嘉宾:陈磊 开源数据库Pika项目PMC核心人员 整理:墨天轮社区 导读 大家好,今天我分享的主题是:KV数据库,云时代的文件存储. 随着AI和机器学习等技术的发展,数据演变为了十分宝贵的资源,数 ...
- 树形结构体按照 sort 进行排序先按照字母排序 然后按照数字排序
// 先按照字母排序 然后按照数字排序 function sortListByLetter(arr) { return arr.sort((a, b) => { if (isNaN(a.name ...
- js 时间日期
Date.parse() 把字符串时间转化为时间戳. new Date(时间戳) 转化 时间格式 时间比较大小
- DDD实体和对象
实体 entity 标识符用来唯一定位一个对象,在数据库中我们一般用表的主键来实现标识符,主键和标识符的思考角度不同,就是 id 字段 : 实体:拥有唯一的标识符,标识符的值不会改变,而其他对象的状态 ...
- DirectoryOpus插件:“照得标管理器”-海量照片分类管理好帮手!
照得标管理器 前言 名词解释:"照得标管理器",即:照片得到标签管理器,后文统一简称"照得标管理器"或"照得标". 注:请不要和抖 ...