web开发中导出一个文件应该怎么做呢?
在web开发中,如果你想让用户下载或者导出一个文件,应该怎么做呢?
传统的做法是在后端存储或者即时生成一个文件来提供下载功能,这样的优势是可以做权限控制、数据二次处理,但缺点是需要额外发起请求、增大服务端压力、下载速度慢。
但随着HTML5的标准发布,我们已经能够做到只前端来下载各种文件了。
后端响应式下载
在常规的HTTP应答中js权限控制,Content-Disposition 消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
在HTTP场景中,第一个参数或者是inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将filename的值预填为下载后的文件名)。
我们在后端响应头中只要设置该头部信息,即可下载为文件,而不是请求并展示:
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">Content-Type: text/html; charset=utf-8
Content-Disposition: attachment; filename="cool.html"
</pre></p>
但需要注意的是,如果想要用这种方式下载文件,不能使用AJAX的方式,而是应该新建一个标签,模拟点击下载。原因为处于安全性考虑,JavaScript无法与磁盘进行交互,因此AJAX得到的内容将被保留在内存中,而不是磁盘上。
Nginx添加header头下载
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">location ~ .(jpg|jpeg|png|bmp|ico|gif|swf)$ {
add_header Content-Disposition 'attachment; filename="cool.html"';
}
</pre></p>
和后端一样的原理,只不过头部信息通过Nginx统一添加。
前端下载:标签的download属性
此属性指示浏览器下载URL而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么它将作为下载的文件名使用。此属性对允许的值没有限制,但是/和会被转换为下划线。
此属性仅适用于同源 URLs。
尽管HTTP URL需要位于同一源中,但是可以使用 blob: URLs 和 data: URLs ,以方便用户下载 JavaScript 方式生成的内容(例如使用在线绘图的Web应用创建的照片)。
常规的标签,用于链接的跳转,如新的页面,那么如果我们给标签加上download属性,就能很简单的让用户保存新的html页面。
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">PHP实现并发请求
</pre></p>
生成并下载字符串文件
首先我们需要了解一个特殊的数据格式:Blob。
Blob数据
Blob(Binary Large Object,二进制类型的大对象),表示一个不可变的原始数据的类文件对象,我们上传文件时常用的File对象就继承于Blob,并进行了扩展用于支持用户系统上的文件。
我们只能通过Blob()构造函数来创建一个新的Blob对象:
Blob(blobParts[, options])
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">// 创建一个json类型的Blob对象,支持传入同类型数据的一个数组
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)],
{type : 'application/json'});
// 此时blob的值
// Blob(22) {size: 22, type: 'application/json'}
</pre></p>
Blob对象存在两个只读属性:
size: Blob 对象中所包含数据的大小(字节)。
type: 一个字符串,表明该Blob对象所包含数据的MIME类型。如果类型未知,则该值为空字符串。
URL对象和下载字符串文件
URL 接口是一个用来创建 URLs 的对象,包含两个静态方法:
objectURL = URL.createObjectURL(blob)
创建一个 URL(DOMString),包含一个唯一的blob链接(该链接协议为以blob:,后跟唯一标识浏览器中的对象的掩码)。这个 URL 的生命周期和创建它的窗口中的 document 绑定。
URL.revokeObjectURL(objectURL)
销毁之前使用URL.createObjectURL()方法创建的URL实例。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">var url = URL.createObjectURL(blob);
// 此时url的值,跟document绑定,所以每个页面创建的字符串均不同
// blob:https://developer.mozilla.org/defe53c2-2882-43c6-b275-db2a57959789
</pre></p>
此时,我们在页面中创建一个新标签,点击即可下载我们想要的文件:
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">下载文件链接
</pre></p>
FileReader读取Blob数据
想要读取Blob数据的唯一方法是FileReader。
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
其中File对象可以是来自用户在一个元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。
该对象包含3个属性:
FileReader.error
一个DOMException,表示在读取文件时发生的错误 。
FileReader.readyState
表示FileReader状态的数字。取值如下:
常量名 值 描述
EMPTY 0 还没有加载任何数据.
LOADING 1 数据正在被加载.
DONE 2 已完成全部的读取请求.
FileReader.result
文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。
包含6个事件处理:onabort,onerror,onload,onloadstart,onloadend,onprogress,这些不再详细说明,因为 FileReader 继承自EventTarget,所以所有这些事件也可以通过addEventListener方法使用。
包含5个方法:
FileReader.abort()
中止读取操作。在返回时,readyState属性为DONE。
FileReader.readAsArrayBuffer()
开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.
FileReader.readAsBinaryString()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。
FileReader.readAsDataURL()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容。
FileReader.readAsText()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
因此我们可以直接读取Blob对象的数据:
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">var reader = new FileReader();
reader.addEventListener("loadend", function() {
console.log(reader.result);
});
reader.readAsDataURL(blob);
// 此时result的值
// data:application/json;base64,ewogICJoZWxsbyI6ICJ3b3JsZCIKfQ==
reader.readAsText(blob);
// 此时result的值
// {
// "hello": "world"
// }
</pre></p>
下载图片
除了下载手动生成的字符串或对象,我们还能提供下载图片的功能,一方面能用于支持Canvas绘图的保存功能,一方面能提供批量下载图片等高级功能。
除了浏览器自带的右键保存,我们还可以这么做来下载图片:
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">// 通过src获取图片的blob对象
function getImageBlob(url, cb) {
var xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "blob";
xhr.onload = function() {
if (this.status == 200) {
cb(this.response);
}
};
xhr.send();
}
let reader = new FileReader();
reader.addEventListener("loadend", function() {
console.log(reader.result);
});
getImageBlob('https://cdn.segmentfault.com/v-5c4ec07f/global/img/user-64.png', function(blob){
// 读取来看下下载的内容
reader.readAsDataURL(blob);
// 最终生成的字符串
// data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAA...
// 生成下载用的URL对象
let url = URL.createObjectURL(blob);
// 生成一个a标签,并模拟点击,即可下载,批量下载同理
let aDom = aDom = document.createElement('a');
aDom.href = url;
aDom.download = 'download.json';
aDom.text = '下载文件';
document.getElementsByTagName('body')[0].appendChild(aDom);
aDom.click();
});
</pre></p>
下载excel文件等
如果你明白了下载的原理,那么所有的内容都能够理解,只不过是转换成对应的格式而已,当然,复杂格式的文档不需要你自己去配置,可以引入第三方库,在excel文档方面我选择用 tableExport库(#javascript):
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">// 引入CDN文件
'https://cdn.bootcss.com/xlsx/0.14.1/xlsx.core.min.js',
'https://cdn.bootcss.com/FileSaver.js/2014-11-29/FileSaver.min.js',
'https://cdn.bootcss.com/TableExport/5.2.0/js/tableexport.min.js'
// 绑定下载事件,这个是我自己的场景下代码,可能不适合大家,具体的参考官方文档
const tableDom = $('#table');
$('.table-exportBtn', tableDom).on('click', function () {
const tableExport = tableDom.tableExport({
formats: ['xlsx', 'txt'],
filename: '表格下载',
exportButtons: false
});
const type = $(this).data().type;
const exportData = tableExport.getExportData()[tableDom[0].id][type];
const {data, mimeType, filename, fileExtension, merges, RTL, sheetname} = exportData;
// 源码里才能看到完整参数,官方文档没有写全,导致下载的文件格式错误
tableExport.export2file(data, mimeType, filename, fileExtension, merges, RTL, sheetname);
});
</pre></p>
默认的方法会自动生成下载按钮,但如果你想自定义下载功能,参考 exportButtons: false 设置() 一节,但这个文档有问题,export2file参数不完整,导致下载的xlsx文件一直格式错误js权限控制,通过查看源码,需要写全参数才可以,上面的示例里已经写出。
tableExport库源码
我们可以看下tableExport导出文件的核心代码,其导出为excel格式比较复杂,由xlsx.core.min.js来完成:
<pre class="" style="margin-top: 0.5em;margin-bottom: 0.5em;padding: 1em;max-width: 100%;overflow-wrap: normal;border-width: 0px;border-style: initial;border-color: initial;overflow: auto;background: rgb(45, 45, 45);color: rgb(204, 204, 204);font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;word-spacing: normal;word-break: normal;line-height: 1.5;tab-size: 4;hyphens: none;box-sizing: border-box !important;"><p class="" style="margin-top: 3px;margin-bottom: 3px;max-width: 100%;overflow-wrap: break-word;min-height: 1em;border-width: 0px;border-style: initial;border-color: initial;line-height: 16px;font-size: 12px;text-align: justify;word-break: break-all;box-sizing: border-box !important;white-space: nowrap !important;">
export2file: function(data, mime, name, extension, merges, RTL, sheetname) {
var format = extension.slice(1);
data = this.getRawData(data, extension, name, merges, RTL, sheetname);
if (_isMobile && (format === _FORMAT.CSV || format === _FORMAT.TXT)) {
// 拼凑指定格式的data:类型 URI
var dataURI = "data:" + mime + ";" + this.charset + "," + data;
this.downloadDataURI(dataURI, name, extension);
} else {
// TODO: error and fallback when saveAs
not available
saveAs(new Blob([data], { type: mime + ";" + this.charset }), name + extension, true);
}
},
// 先创建标签,然后提供href和download属性,并模拟点击
downloadDataURI: function(dataURI, name, extension) {
var encodedUri = encodeURI(dataURI);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", name + extension);
document.body.appendChild(link);
link.click();
},
</pre></p>
xlsx文件导出导出
还没有仔细研究,感兴趣的可以查看其js-xlsx Github项目()
第三方库
上面我们主要讲了下载背后的原理,你可以自己封装,也可以使用现成的第三方库,如 download.js() ,这个能提供大部分常用数据的下载;但如果你是要下载表格数据为excel格式,还是推荐 tableExport.js(#javascript) 及其依赖组件。
参考资料
MDN-a: ()
MDN-blob: ()
掘金-细说Web API中的Blob:()
MDN-URL: ()
MDN-FileReader: ()
博客园-js 获取图片url的Blob值并预览:()
tableExport文档:(#javascript)
感谢 @Oliveryoung 提供的其他解决方案
MDN-Content-Disposition: ()
Ajax请求无法下载文件的原因: ()
Github-download.js:()
发表评论
热门文章
Spimes主题专为博客、自媒体、资讯类的网站设计....
一款个人简历主题,可以简单搭建一下,具体也比较简单....
仿制主题,Typecho博客主题,昼夜双版设计,可....
用于作品展示、资源下载,行业垂直性网站、个人博客,....
热评文章
最新评论
Z.
11月29日
博主你好,Deng插件,这个点击不进去,提示这个(Warning: require_once(/www/wwwroot/w.zzy2020.com/usr/plugins/Deng/Deng/html/profile.php): failed to open stream: No such file or directory in /www/wwwroot/w.zzy2020.com/Fresh/extending.php on line 26
Fatal error: require_once(): Failed opening required '/www/wwwroot/w.zzy2020.com/usr/plugins/Deng/Deng/html/profile.php' (include_path='.:/www/server/php/72/lib/php') in /www/wwwroot/w.zzy2020.com/Fresh/extending.php on line 26)
蔑视的士
4天前
支持