背景:
新上线的一个功能,大家都做得比较辛苦。
同时,也收到用户反馈功能不好用。
问题描述:
反馈的问题是,在导出的文件名看不懂,一旦导出多了,不好找。如果下载一个用户再自己重新命名一下,又会影响效率。
根据文件名不知道里面的内容
这就很烦了,
不改下名,不好找导出的文件。
改吧,又太麻烦。
用户又能拿这个功能怎么样,只能吐槽这个新功能不好用罢了
这是一个非功能的体验问题。
直接原因:
没有指定下载文件名,浏览器使用了默认的命名策略:将url上的非法字符去掉,然后拼一下。如果得到的字符串太长,还会进行截断处理。
原因分析:
用户执行导出后,后端返回的是一个包含了导出内容的oss地址,也就是一个Url。前端直接把这个url放到<a>标签中。用户点击进行下载
下载时的交互
这种情况下,浏览器下载时展示在状态栏上的名字,浏览器就自由发挥了,目前浏览器的命名规则是将url上的非法字符去掉,然后拼一下。
下载的文件名
优化意见:
方案1:服务器返回导出内容的二进制流。由服务器指定浏览器导出的文件名。
代码实现:
/**
* 将二进制流写入HttpServletResponse
*
* @param fileName 浏览器下载时弹出下载框、展示在状态栏中的文件名
* @param response 承载返回给浏览器的对象
* @param workbook POI中定义的excel。承载了excel模型及存放的数据
* @throws IOException
*/
private static void downLoadExcelWithOutputStream(HttpServletResponse response, Workbook workbook, String fileName) throws IOException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("content-Type", "application/vnd.ms-excel");
/**
* 如果文件名是包含中文字符,需要进行编码。否则,浏览器下载时文件名会是乱码
*/
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
ServletOutputStream servletOutputStream = response.getOutputStream();
workbook.write(servletOutputStream);
/**
* 关闭writer,释放内存
*/
workbook.close();
servletOutputStream.flush();
/**
* 此处记得关闭输出Servlet流
*/
IoUtil.close(servletOutputStream);
}
关键代码
方案2:服务器返回存放下载文件的oss Url,前端指定浏览器下载文件的文件名。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>前端自定义下载文件名e</title>
</head>
<body>
待下载的文件:<input type="text" name="fileUrl" id="fileUrl" value='' ><br />
自定义文件名:<input type="text" name="fileName" id="fileName" value=''><br/>
<button id='btn'>下载</button><br />
<span id='status'></span><br />
</body>
<script>
var url=document.getElementById('fileUrl').value;
console.log(url);
document.getElementById('btn').onclick = function() {
document.getElementById('status').innerHTML = '下载中';
fetch(url).then(res => res.blob().then(blob => {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = document.getElementById('fileName').value;
a.click();
window.URL.revokeObjectURL(url);
document.getElementById('status').innerHTML = '下载完成';
}));
};
</script>
</html>
把oss文件以blob的方式下载到当前页,然后再创建a标签
效果:
使用
确定优化方案
最终选定了方案2。
原因是方案2改动最小,可以快速响应及时解决用户关心的问题。
思考
方案1:
优点:
- 可以由后端灵活自定义浏览器下载时的文件名。没有兼容性问题
- 代码实现简单。代码量少,实现简单
缺点:
- 服务器带宽打满后会影响其它功能的使用。服务器写数据到浏览器会占用服务器网卡的总带宽,如果打满,其它功能也用不了。可以把带宽想象成一座桥,大文件就像一个大卡车。
- 影响到服务器的稳定性。大文件生成及传输过程会持续占用服务器内存。服务器的内存是有限的,下载大文件的功能占用了,其它功能就不能正常工作了。
- 分布式环境中,增加了代码的复杂度。Feign或RestTempate在处理字节流时需要特殊的配置,在升级这些http客户组件时,也需要验证对这些已有功能的影响。
方案2: