随手记
问题背景
前端点击下载,后台逻辑判断,当符合一定条件,则返回文件流,下载文件。
当不符合条件,返回错误信息的json,前端读取json,被展示错误信息。
研究过程
1、第一个想到的是window.open,新开窗口去下载文件,如果有错误,则用response的输出流返回一段html代码,如:
<html>
<head>
<meta charset="utf-8">
</head>
<script>alert(错误信息);window.close();</script>
</html>
只要设置response.setContentType("text/html; charset=utf-8"); 即可解析为html,并且弹出错误信息,可以完成任务。
缺点:无法携带请求头、新开页面显示不友好
2、使用axios。
坑一:require必须设置responseType,并且为blob,如不携带,下载txt格式或许没问题,但是下载excel格式时就会出错。
export function downloadFile(params) {
return request({
url: "/api/xxxxx/xxxxxx?" + params,
method: 'get',
responseType:'blob'
})
}
坑二:如果设置responseType为blob,下载的文件不会乱码了,但是,如果返回的错误信息的json,那么json依旧被当作blob,不是json!这时候需要读取blob的数据,转为json,读取blob的方法如下:
var reader = new FileReader()
reader.addEventListener('loadend', function (e) {
// 输出字符串
console.log(e.target.result)
})
reader.readAsText(blob)
读取方法参考:https://www.cnblogs.com/gerry2019/p/12398025.html
坑三:当使用坑二所使用的方法去读取blob时,不调试则已,一调试,你又落入一个坑了。因为浏览器调试时,读取的结果永远都是undefined!这时候不要慌,直接跳过调试,运行即可,这时候你会发现,读取结果不是undefined了,有值了!
归纳方案
1、后台代码,分情况设置?response的ContentType
/**
* @ProjectName:
* @Package:
* @ClassName: FileDownloadUtil
* @Author: HXH
* @Description: 文件下载工具
* @Date: 2021/9/1 11:30
*/
@Slf4j
public class FileDownloadUtil {
/**
* @param response:
* @param fileFullName: 文件路径(包括文件名)
* @param fileName: 文件名
* @return void
* @author HXH
* @description: 文件下载公用方法,检测文件存在与否,外面不需要检测
* @date 2021/9/1
**/
public static void downLoadFile(HttpServletResponse response, String fileFullName, String fileName) {
response.setCharacterEncoding("UTF-8");
if (StringUtils.isBlank(fileFullName) || StringUtils.isBlank(fileName) || !new File(fileFullName).exists()) {
ApiResult apiResult;
if (StringUtils.isNotBlank(fileFullName)) {
apiResult = ApiResult.fail("【" + fileFullName + "】文件已丢失!");
} else {
apiResult = ApiResult.fail("无文件可下载 或 文件已丢失!");
}
//设置返回json格式,用来做标记
response.setContentType("application/json; charset=utf-8");
try (PrintWriter out = response.getWriter()) {
out.write(JSON.toJSONString(apiResult));
out.flush();
return;
} catch (IOException e) {
}
}
InputStream in = null;
BufferedOutputStream o = null;
File fileLoad;
try {
fileLoad = new File(fileFullName);
long fileLength = fileLoad.length();
String length = String.valueOf(fileLength);
in = new BufferedInputStream(new FileInputStream(fileLoad));
byte[] buffer = new byte[in.available()];
in.read(buffer);
in.close();
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/octet-stream; charset=utf-8;");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content_Length", length);
o = new BufferedOutputStream(response.getOutputStream());
o.write(buffer);
o.flush();
} catch (Exception ex) {
log.error("下载文件错误,详细----->{}", ex.getMessage());
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
if (o != null) {
try {
o.close();
} catch (Exception e) {
}
}
}
}
}
2、前端根据ContentType来判断返回的是文件还是json
import axios from 'axios'
import {Message, MessageBox} from 'element-ui'
import store from '@/store'
import {getToken} from '@/utils/auth'
const BASE_URI = window.location.origin
const service = axios.create({
baseURL: BASE_URI,
timeout: 100000
})
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['X-Token'] = getToken()
}
return config
},
error => {
Promise.reject(error)
}
)
service.interceptors.response.use(
response => {
const contenType = response.headers['content-type'];
//下载处理
if (contenType.indexOf("json") === -1) {
const filename = response.headers["content-disposition"];
const isdownload = contenType.indexOf("download") !== -1 || contenType.indexOf("ms-excel") !== -1
|| contenType.indexOf("octet-stream") !== -1 || contenType.indexOf("force-download") !== -1;
//判断是否是下载
if (filename || isdownload) {
//前端字节流转文件
const blob = new Blob([response.data]);
if (window.navigator.msSaveOrOpenBlob) {
//兼容ie
window.navigator.msSaveBlob(blob, file.filename);
}else{
var downloadElement = document.createElement("a");
var href = window.URL.createObjectURL(blob);
downloadElement.href = href;
downloadElement.download = decodeURIComponent(filename.split("filename=")[1]);
document.body.appendChild(downloadElement);
//此写法兼容火狐
let evt = document.createEvent("MouseEvents");
evt.initEvent("click", false, false);
downloadElement.dispatchEvent(evt);
document.body.removeChild(downloadElement);
window.URL.revokeObjectURL(href);
}
} else {
return response;
}
} else {
//非下载请求
let res = response.data
//判断返回数据格式是否为blob
var isBlob = res instanceof Blob;
if (isBlob){
//非下载请求下,返回数据格式是blob,是下载文件请求,但是后台返回错误信息json的情况
var reader = new FileReader()
reader.addEventListener('loadend', function (e) {
var json = e.target.result;
//把json字符串转为json格式
res = JSON.parse(json);
if (res.code !== 200) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject('error')
} else {
return response.data
}
})
reader.readAsText(res)
}else{
if (res.code !== 200) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject('error')
} else {
return response.data
}
}
}
},
error => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
|