性能文章>踩了坑:导出功能没做好,差点被投诉>

踩了坑:导出功能没做好,差点被投诉原创

2年前
418547

背景:

新上线的一个需求,研发期间,大家都做得比较辛苦。同时,收到用户反馈功能功能不好用。

问题描述:

反馈的问题是,导出的文件名像是乱码,看不懂。比如多导出几次,导出的文件多了,就不容易找到想要的那个。如果下载一个用户再自己重新命名一下,又会影响效率。
image3.png

根据文件名不知道里面的内容

这就很烦了,不改下名,不好找导出的文件。改吧,又太麻烦。用户又能拿这个功能怎么样,只能吐槽了这是一个非功能的体验问题。

直接原因:

浏览器使用了默认的命名策略,如果没有指定下载文件名那么浏览器会这样这样:

将url上的非法字符去掉,然后拼一下。如果得到的字符串太长,还会进行截断处理。

原因分析:

用户执行导出后,后端返回的是一个包含了导出内容的oss地址,也就是一个Url。前端直接把这个url放到<a>标签中。用户点击进行下载

image5.jpeg

下载时的交互

这种情况下,浏览器下载时展示在状态栏上的名字,浏览器就自由发挥了,目前浏览器的命名规则是将url上的非法字符去掉,然后拼一下。

image6.png

下载的文件名

优化方案:

方案1:由服务器写入数据流的方式下载,同时由服务器指定一个自定义的文件名。

方案2:服务器返回存放业务数据的oss地址,前端指定一个自定义的文件名。

确定优化方案最终选定了方案2。原因是方案2改动最小,并且可以避免下载时导致业务数据缺失的问题。

客户都是用chrome,也规避了方案2的浏览器兼容性问题。

技术方案对比

方案1

image7.png

服务器返回数据流的方式

https://www.processon.com/view/6363c4d2e0b34d77dbcd4919

优点

  1. 可以由后端灵活自定义浏览器下载时的文件名。没有兼容性问题

2.代码实现简单。代码量少,实现简单

缺点

  1. 数据导出过程中如果出现异常,会出现只导出一部分数据的情况,整个下载过程并不会完全中断。用户没有办法分辨是否下载完成。

  2. 服务器带宽打满后会影响其它功能的使用。服务器写数据到浏览器会占用服务器网卡的总带宽,如果打满,其它功能也用不了。可以把带宽想象成一座桥,大文件就像一个大卡车。

  3. 影响到服务器的稳定性。大文件生成及传输过程会持续占用服务器内存。服务器的内存是有限的,下载大文件的功能占用了,其它功能就不能正常工作了。

  4. 分布式环境中,增加了代码的复杂度。Feign或RestTempate在处理字节流时需要特殊的配置,在升级这些http客户组件时,也需要验证对这些已有功能的影响。

方案2

image8.png

使用oss作为数据中转站

https://www.processon.com/view/6363c4d2e0b34d77dbcd4919

优点

  1. 数据不会丢失。数据上传oss时报错时,整个导出就报错了,不会出现用户只拿到部分数据的情况

  2. 不会占用服务器出口带宽。返回给前端的一个url,不管导出多大的文件,出口带宽都不会受到影响。

缺点

1.兼容性问题
在 HTML5 中,download 属性是 <a> 标签的新属性。

image9.png

兼容性

定义和用法<a download=“filename”>filename 规定作为文件名来使用的文本。
该属性也可以设置一个值来规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件 (.img,.xls,.doc,.pdf, .txt, .html, 等等)。
在 <a> 标签中必须设置 href 属性。
https://www.w3school.com.cn/tags/att_a_download.asp

2.需要下载的资源是同源的

同源策略(Same origin policy)是基于安全的考虑,是浏览器对JavaScript施加的安全限制,是浏览器最核心也最基本的安全功能。

简单地说只要在浏览器里打开页面,就默认遵守同源策略,目的是为了保证用户的隐私安全和数据安全。

那么什么是同源呢
所谓同源是指两个 URL的"协议+域名+端口"三者都相同
https://www.runoob.com/tags/att-a-download.html

技术方案的详细设计

方案1

按照http协议的要求,把业务数据转换成数据流写到HttpServletResponse
指定这个数据流的content-type:


response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

指定数据流的打开类型为attachment,文件名filename:

response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

image10.png

https://github.com/helloworldtang/spring-boot-cookbook/blob/master/learning-demo/src/main/java/com/tangcheng/learning/adapter/openapi/ExportController.java

image11.png

方案2

image12.png

https://github.com/helloworldtang/spring-boot-cookbook/blob/master/learning-demo/src/main/resources/templates/jsDownload.html

image13.png

技术方案小结:

导出场景,服务器直接返回数据流到前端,要关注占带宽的情况。
如果是由前端指定下载的下载名,需要考虑兼容性问题。

最佳方案

服务器返回oss url,且指定自定义文件名。

注意事项
要解决文件名相同时,并发操作相互覆盖的问题。
需要通过url对相同文件名的oss数据在path的维度进行区分。
比如url是这种格式
ip/userId/yyyy/MM/dd/hh/mm/ss/SSS/一个随机数/自定义文件名

小结:

用户接触到的地方都是需要精心设计的。用户关注的地方就是有价值的地方,需要投入资源做好。

点赞收藏
bit01
请先登录,查看4条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步
7
4