一, 介绍与需求
1.1, 介绍
XMLHttpRequest 用于在后台与服务器交换数据. 这意味着可以在不重新加载整个网页的情况下, 对网页的某部分进行更新.
问题: Chrome 会自动调用内置的 PDF 阅读器打开
1.2, 需求
在谷歌 (Chrome) 浏览器中, 使用 a 标签属性 download 下载 PDF 链接文件, 如果是相同域时, 可以直接下载; 但是如果域不同, 则不是下载, 而是直接打开页面预览文件. 但是需求是直接点击下载文件, 而不是打开预览; 以及下载后台返回的文件流.
二, 下载文件
2.1, 思路
通过 a 标签的 download 属性, 我们可以直接下载后台接口返回的数据流文件; 故此, 我们是否可以模拟发送 http 请求, 将文件链接转换成文件流来使用 a 标签 download 下载. 以下主要介绍链接文件转文件流下载的思路与方法
2.2, 文件路径转文件流
1, 先校验是否是路径链接
使用正则表达式校验 url 是否合法
- let reg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\/])+$/;
- if (!reg.test(url)) {
- throw new Error("传入参数不合法, 不是标准的链接");
- }
2, 创建 XMLHttpRequest 对象
模拟发送 http 请求, 获取文件流
- let xhr = new XMLHttpRequest();// 创建 XMLHttpRequest 对象
- xhr.open('get', 'http://url', true);// 规定请求的类型, URL 以及是否异步处理请求. 三个参数分别是 method: 请求的类型; GET 或 POST url: 文件在服务器上的位置 async:true(异步)或 false(同步)
- xhr.setRequestHeader('Content-Type', `application/pdf`);// 设置请求头
- xhr.responseType = "blob";// 返回的数据类型 这儿需要 blob 对象
- xhr.onload = function () {// 请求成功回调函数
- if (this.status == 200) {
- // 接受二进制文件流
- var blob = this.response;
- }
- }
- xhr.send();// 将请求发送到服务器
3, 完整方法
- /**
- * 文件链接转文件流下载 -- 主要针对 PDF 解决谷歌浏览器 a 标签下载 PDF 直接打开的问题
- * @param url : 文件链接
- * @param fileName : 文件名;
- * @param type : 文件类型;
- */
- function fileLinkToStreamDownload(url, fileName, type) {
- let reg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\/])+$/;
- if (!reg.test(url)) {
- throw new Error("传入参数不合法, 不是标准的文件链接");
- } else {
- let xhr = new XMLHttpRequest();
- xhr.open('get', url, true);
- xhr.setRequestHeader('Content-Type', `application/${type}`);
- xhr.responseType = "blob";
- xhr.onload = function () {
- if (this.status == 200) {
- // 接受二进制文件流
- var blob = this.response;
- downloadExportFile(blob, fileName, type)
- }
- }
- xhr.send();
- }
- }
2.3, 下载文件
1, 创建下载链接
- let downloadElement = document.createElement('a');
- let href = blob;
- if (typeof blob == 'string') {
- downloadElement.target = '_blank';// 如果是链接, 打开新标签页下载
- } else {
- href = Windows.URL.createObjectURL(blob); // 创建下载的链接
- }
- downloadElement.href = href;// 下载链接
2, 模拟点击下载链接
- downloadElement.download = tagFileName + moment(new Date().getTime()).format('YYYYMMDDhhmmss') + '.' + fileType; // 下载后文件名
- document.body.appendChild(downloadElement);
- downloadElement.click(); // 点击下载
3, 下载完成后释放资源
- document.body.removeChild(downloadElement); // 下载完成移除元素
- if (typeof blob != 'string') {
- Windows.URL.revokeObjectURL(href); // 释放掉 blob 对象
- }
4, 完成方法
- /**
- * 下载导出文件
- * @param blob : 返回数据的 blob 对象或链接
- * @param tagFileName : 下载后文件名标记
- * @param fileType : 文件类 Word(docx) Excel(xlsx) ppt 等
- */
- function downloadExportFile(blob, tagFileName, fileType) {
- let downloadElement = document.createElement('a');
- let href = blob;
- if (typeof blob == 'string') {
- downloadElement.target = '_blank';
- } else {
- href = Windows.URL.createObjectURL(blob); // 创建下载的链接
- }
- downloadElement.href = href;
- downloadElement.download = tagFileName + moment(new Date().getTime()).format('YYYYMMDDhhmmss') + '.' + fileType; // 下载后文件名
- document.body.appendChild(downloadElement);
- downloadElement.click(); // 点击下载
- document.body.removeChild(downloadElement); // 下载完成移除元素
- if (typeof blob != 'string') {
- Windows.URL.revokeObjectURL(href); // 释放掉 blob 对象
- }
- }
2.4,base64 对象转文件对象
主要针对图片, 不过其他文件也可
- /**
- * base64 对象转文件对象
- * @param urlData : 数据的 base64 对象
- * @param type : 类型 image/PNG;
- * @returns {Blob}:Blob 文件对象
- */
- function base64ToBlob(urlData, type) {
- let arr = urlData.split(',');
- let array = arr[0].match(/:(.*?);/)
- let mime = (array && array.length> 1 ? array[1] : type) || type;
- // 去掉 url 的头, 并转化为 byte
- let bytes = Windows.atob(arr[1]);
- // 处理异常, 将 ascii 码小于 0 的转换为大于 0
- let ab = new ArrayBuffer(bytes.length);
- // 生成视图(直接针对内存):8 位无符号整数, 长度 1 个字节
- let ia = new Uint8Array(ab);
- for (let i = 0; i <bytes.length; i++) {
- ia[i] = bytes.charCodeAt(i);
- }
- return new Blob([ab], {
- type: mime
- });
- }
2.5, 使用实例
1, 文件链接转文件流下载
1 fileLinkToStreamDownload('http://127.0.0.1/download.pdf', '下载文件实例', 'pdf')
2,base64 对象转文件对象下载
- let blob = base64ToBlob('data:image/png;base64,iVBORw0KGgo=...','image/png')// 获取图片的文件流
- downloadExportFile(blob, 'download', 'png')
问题记录: 浏览器缓存问题
由于浏览器的缓存机制, 当我们使用 XMLHttpRequest 发出请求的时候, 浏览器会将请求的地址与缓存中的地址进行比较, 如果存在相同记录则根据不向服务器发出请求而直接返回与上一次请求相同内容.
解决这类缓存问题的办法:
1, 时间戳方法 - 即在每次请求的 url 后面加上当前时间的字符串或其他类似的不会重复的随机字符串, 这样浏览器每次发出的是不同的 url, 即会当做不同的请求来处理, 而不会从缓存中读取.
- if(url.indexOf("?")>=0){// 判断 url 中是否已经带有参数
- url = url + "&t=" + (new Date()).valueOf();
- }else{
- url = url + "?t=" + (new Date()).valueOf();
- }
2, 在 XMLHttpRequest 发送请求之前加上:
加 If-Modified-Since 头
- xhr.setRequestHeader("If-Modified-Since","0");
- xhr.send(null);
来源: https://www.cnblogs.com/jackson-yqj/p/11321275.html