前提
背景:前端使用react,后端使用springboot
需求:前台点击按钮,实现后台查询数据库生成报表返回给浏览器下载,使用post请求
代码
为了使用方便,对文件下载这一类请求进行封装
前端代码
request.js
import axios from 'axios'
const requestFile = axios.create({
// 设置接收为二进制数据(文件)
responseType: 'blob'
})
requestFile.interceptors.response.use(
response => {
if (response.status >= 200 && response.status < 300) {
// 请求成功,调用下载逻辑
// 从请求头content-disposition中获取文件名,由于编码格式为ISO8859-1,所以使用escape转码字符串成通用十六进制,使用decodeURI解码成UTF-8
// 如果后端直接使用URLEncoder将字符串编码,则前端直接使用decodeURIComponent解码即可!!详看文末踩坑
let filename = decodeURI(escape(response.headers['content-disposition'].split('filename=')[1]))
if (filename == '' || filename == undefined) { filename = '文件' }
// 使用a标签来模拟下载,接收二进制数据流
let link = document.createElement('a');
link.href = window.URL.createObjectURL(new Blob([response.data]))
// 前端这里new Blob时也可以设置请求接受的格式,但是由于后端是发送主体,所以在后端设置好比较规范:new Blob([response.data], {type: 'application/vnd.ms-excel'});
link.download = filename
link.style.display = none
document.body.appendChild(link)
link.click()
// 使用完移除
link.remove()
return "ok"
}
notification.error({
message: `请求错误 ${response.status}:${response.config.url}`,
description: response.statusText
})
const error = new Error(response.statusText)
error.response = response
throw error
},
err => {
if(axios.isCancel(err)) return Promise.reject(err)
if(err.code){
notification.error({
message: err.name,
description: err.message
})
}else if('stack' in err && 'message' in err){
notification.error({
description: err.message
})
}
return Promise.reject(err);
});
export { requestFile }
main.js
import {requestFile} from '@/utils/request.js'
// 请求下载文件
requestFile.post(url,data)
后端代码
这里是excel文件,所以需要引入依赖:
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
Controller将查询到的结果传给下载的方法:
public void downloadFile(List<Map<String,String>> data,HttpServletResponse response){
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet st = wb.createSheet();
// 生成标题
HSSFRow title = st.createRow(0);
title.createCell(0).setCellValue('检查时间');
title.createCell(1).setCellValue('检查人');
title.createCell(2).setCellValue('检查对象');
title.createCell(3).setCellValue('资源类型');
// 遍历插入内容
if(data.size()==0){
log.info("数据长度为0")
}else{
for(int i=0;i<data.size();i++){
HSSFRow row = st.createRow(i+1);
row.createCell(0).setCellValue('aaa');
row.createCell(1).setCellValue('bbb');
row.createCell(2).setCellValue('ccc');
row.createCell(3).setCellValue('ddd');
}
}
// 将文件写出到response
try{
// 将返回重置
response.reset()
// 也可以使用URLEncoder对中文编码
String filename = new String("检查表.xls".getBytes(),StandardCharsets.ISO_8859_1);
// 设置请求头
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.addHeader("Content-Disposition","attachement;filename="+filename);
OutputStream ops = new BufferedOutputStream(response.getOutputStream());
wb.write(ops);
ops.flush()
// ops.close() // 不晓得为啥,关流会报错,可能springboot自动维护了这个流
}catch(IOException e){
log.error("下载失败 {} {}",e.getMessage(),e.getCause());
}
}
特别注意
坑挺多的,我碰到的坑:前端文件名乱码、不弹出下载框、下载内容乱码
明确几点:
- 前端文件名如果想在浏览器的网络请求里看到中文,后端必须将文件名以
ISO8859-1
保存到请求头中 - 前端需要实现下载必须保证ajax请求的类型为
responseType:'blob'
,否则不会出现下载框 - 下载内容如果按照2进行设置一般不会乱码,如果出现乱码,请明确:
charset=utf-8
在请求头中设置
2021/11/29 更新
又踩坑了,想着直接设置编码格式为UTF-8,然后在前段获取解析出来:String filename = new String("检查表.xls".getBytes(),StandardCharsets.UTF_8);
结果一直报502 bad gateway
解决方法:使用URLEncoder
代替原来的ISO编码对中文进行编码:
String filename = URLEncoder.encode("下载.et");
response.addHeader("Content-Disposition","attachment;filename="+filename);
// 为了简化前段获取文件名难度,设置一个filename字段
response.addHeader("filename",filename);
前端:
// 请求
import Axios from 'axios'
...
Axios({url:xxx,method:'post',data:{},responseType:'blob'}).then(res=>{
console.log(decodeURIComponent(res.headers.filename))
...
}