背景:
新上线的一个需求,大家都做得比较辛苦。
同时,也收到用户反馈功能功能不好用[石化]
问题描述:
反馈的问题是,在导出的文件名看不懂,一旦导出多了,不好找。如果下载一个用户再自己重新命名一下,又会影响效率。
根据文件名不知道里面的内容
这就很烦了,
不改下名,不好找导出的文件。
改吧,又太麻烦。
用户又能拿这个功能怎么样,只能吐槽这个新功能不好用罢了
这是一个非功能的体验问题。
直接原因:
没有指定下载文件名,浏览器使用了默认的命名策略:将url上的非法字符去掉,然后拼一下。如果得到的字符串太长,还会进行截断处理。
原因分析:
用户执行导出后,后端返回的是一个包含了导出内容的oss地址,也就是一个Url。前端直接把这个url放到<a>标签中。用户点击进行下载
下载时的交互
这种情况下,浏览器下载时展示在状态栏上的名字,浏览器就自由发挥了,目前浏览器的命名规则是将url上的非法字符去掉,然后拼一下。
下载的文件名
优化意见:
方案1:由服务器写入数据流的方式下载,同时由服务器指定一个自定义的文件名。
方案2:服务器返回存放业务数据的oss地址,前端指定一个自定义的文件名。
确定优化方案
最终选定了方案2。
原因是方案2改动最小,并且可以避免下载时导致业务数据缺失的问题。
客户都是用chrome,也规避了方案2的浏览器兼容性问题。
技术方案对比
方案1:
服务器返回数据流的方式
https://www.processon.com/view/6363c4d2e0b34d77dbcd4919
优点:
- 可以由后端灵活自定义浏览器下载时的文件名。没有兼容性问题
- 代码实现简单。代码量少,实现简单
缺点:
- 数据导出过程中如果出现异常,会出现只导出一部分数据的情况,整个下载过程并不会完全中断。用户没有办法分辨是否下载完成。
- 服务器带宽打满后会影响其它功能的使用。服务器写数据到浏览器会占用服务器网卡的总带宽,如果打满,其它功能也用不了。可以把带宽想象成一座桥,大文件就像一个大卡车。
- 影响到服务器的稳定性。大文件生成及传输过程会持续占用服务器内存。服务器的内存是有限的,下载大文件的功能占用了,其它功能就不能正常工作了。
- 分布式环境中,增加了代码的复杂度。Feign或RestTempate在处理字节流时需要特殊的配置,在升级这些http客户组件时,也需要验证对这些已有功能的影响。
方案2:
使用oss作为数据中转站
https://www.processon.com/view/6363c4d2e0b34d77dbcd4919
优点:
- 数据不会丢失。数据上传oss时报错时,整个导出就报错了,不会出现用户只拿到部分数据的情况
- 不会占用服务器出口带宽。返回给前端的一个url,不管导出多大的文件,出口带宽都不会受到影响。
缺点:
1.兼容性问题。
在 HTML5 中,download 属性是 <a> 标签的新属性。
兼容性
定义和用法<a download="filename">filename 规定作为文件名来使用的文本。
该属性也可以设置一个值来规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件 (.img,.xls,.doc,.pdf, .txt, .html, 等等)。
在 <a> 标签中必须设置 href 属性。
2.需要下载的资源是同源的
同源策略(Same origin policy)是基于安全的考虑,是浏览器阻止从一个源加载的文档或脚本获取或设置另一个源加载额文档的属性,是浏览器对JavaScript施加的安全限制,是浏览器最核心也最基本的安全功能。简单地说只要在浏览器里打开页面,就默认遵守同源策略,目的是为了保证用户的隐私安全和数据安全。
那么什么是同源呢?
所谓同源是指两个 URL的"协议+域名+端口"三者都相同
技术方案的详细设计
方案1:
按照http协议的要求,把业务数据转换成数据流写到HttpServletResponse
指定这个数据流的type:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
指定文件名:
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
show me the code
https://github.com/helloworldtang/spring-boot-cookbook/blob/master/learning-demo/src/main/java/com/tangcheng/learning/adapter/openapi/ExportController.java
方案2:
show me the code
https://github.com/helloworldtang/spring-boot-cookbook/blob/master/learning-demo/src/main/resources/templates/jsDownload.html
小结:
导出场景,服务器直接返回数据流到前端,要关注占带宽的情况。
如果是由前端指定下载的下载名,需要考虑兼容性问题。
最佳方案:
服务器返回oss url且指定自定义文件名。
注意事项:要解决文件名相同时会相互覆盖的问题。需要在url上对相同文件名的oss文件在path是进行区分。比如url是这种格式 ip/userId/yyyy/MM/dd/hh/mm/ss/SSS/一个随机数/自定义文件名