1.背景描述
客户端调接口需要返回另存为弹窗,下载文件,但是遇到的问题是接口调用成功且不报错,浏览器F12查看居然返回一堆json,而没有另存为弹窗;
正确的效果应该是:接口调用成功且浏览器F12不返回任何json,而是弹窗另存为窗口,直接保存文件即可。
2.项目代码
代码说明:
具体的引入或者工具类啥的就不复制粘贴了,就是你可以理解为给你个硬盘地址,然后封装成File,以流输出,或者以poi依赖包的WorkBook输出流都可以。前端js代码
//批量管理-下载批量导入数据
function downloadBatchImportDataTaskActionColumn(taskId) {
var param = {
taskId: taskId
}
$.ajax({
async: true,
url: prefix + "/downloadBatchImportData",
type: 'post',
data: JSON.stringify(param),
dataType: 'json',
contentType: "application/json;charset=UTF-8",
beforeSend:function(){
window.parent.showLoading();
},
success: function (res) {
window.parent.completeLoading();
console.log(res);
},
error:function(){
window.parent.completeLoading();
}
});
}
后端代码
/**
* 下载批量导入数据
* @param req req
* @param response response
*/
@RequestMapping(value = "/downloadBatchImportData")
public void downloadBatchImportData(HttpServletRequest req, @RequestBody QueryTaskRes queryTaskRes, HttpServletResponse response) {
// String taskId = req.getParameter("taskId");
// logger.info("-downloadBatchImportData-taskId:{}", taskId);
OutputStream os = null;
InputStream io = null;
String tempFilePath = TEMP_FILE_PATH;
String fileName = "";
try {
ImpExpTaskDetail impExpTaskDetail = isvcBatchTaskServiceMicro.selectTaskDetailByTaskId(queryTaskRes.getTaskId());
String filedownLink = impExpTaskDetail.getLink();
if (org.springframework.util.StringUtils.isEmpty(filedownLink)) {
fileName = MessageUtils.message("batch.download") + ".xlsx";
} else {
fileName = StringUtils.subscribeNameString(filedownLink);
}
logger.info("-tempFilePath:{},fileName:{}", tempFilePath, fileName);
File file = ResourceUtils.getFile(String.join(File.separator, tempFilePath, fileName));
if (!file.exists()) {
String begin = DateUtil.now();
DateTime beginTime = DateUtil.parse(begin);
long download = HttpUtil.downloadFile(filedownLink, FileUtil.file(tempFilePath, fileName), new StreamProgress() {
@Override
public void start() {
logger.info("开始下载,时间为:" + begin);
}
@Override
public void progress(long progressSize) {
logger.info("已下载:{}", FileUtil.readableFileSize(progressSize));
}
@Override
public void finish() {
String end = DateUtil.now();
DateTime endTime = DateUtil.parse(end);
long between = DateUtil.between(beginTime, endTime, DateUnit.MS);
logger.info("下载完成,用时:" + DateUtil.formatBetween(between, BetweenFormatter.Level.SECOND));
}
});
}
io = new FileInputStream(file);
Workbook wb = new XSSFWorkbook(io);
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
wb.write(response.getOutputStream());
} catch (IOException e) {
logger.error("-downloadBatchImportData error:{}", e.getMessage());
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
logger.error("-OutputStream error:{}", e.getMessage());
}
}
if (io != null) {
try {
io.close();
} catch (IOException e) {
logger.error("-InputStream error:{}", e.getMessage());
}
}
if (Optional.ofNullable(tempFilePath).isPresent()) {
// 强制删除临时文件
boolean isDelete = com.hytalk.util.FileUtil.delFile(new File(tempFilePath));
logger.info("-downloadBatchImportData 强制删除临时文件 , filePath: {} , isDelete : {} ", tempFilePath, isDelete);
}
}
}
3.导致错误原因分析
最终的错误原因就是:
因为使用了ajax发请求,请看下方代码,这里面的dataType和contentType用来设置传参类型及返回类型,只要设置这两个返回的就是json字符串,而不会以文件流输出。但是如果不写这两dataType+contentType值,那么contentType的默认值为application/x-www-form-urlencoded,最终效果也不行而且会报错。报错如图1。
$.ajax({
async: true,
url: prefix + "/downloadBatchImportData",
type: 'post',
data: JSON.stringify(param),
dataType: 'json',
contentType: "application/json;charset=UTF-8",
...
如图1
问题:为啥会报如图1中的错误?
答案:
最终方案:
不采用ajax发送请求,而是采用最普遍的form表单的方式提交就可以实现效果。
前端js代码
//批量管理-下载批量导入数据
function downloadBatchImportDataTaskActionColumn(taskId) {
var url = prefix + "/downloadBatchImportData";
var form = $("<form></form>").attr("action", url).attr("method", "post");
form.append($("<input></input>").attr("type", "hidden").attr("name", "taskId").attr("value", taskId));
form.appendTo('body').submit().remove();
}
后端代码
/**
* 下载批量导入数据
* @param req req
* @param response response
*/
@RequestMapping(value = "/downloadBatchImportData")
public void downloadBatchImportData(HttpServletRequest req, HttpServletResponse response) {
String taskId = req.getParameter("taskId");
logger.info("-downloadBatchImportData-taskId:{}", taskId);
OutputStream os = null;
InputStream io = null;
String tempFilePath = TEMP_FILE_PATH;
String fileName = "";
try {
ImpExpTaskDetail impExpTaskDetail = isvcBatchTaskServiceMicro.selectTaskDetailByTaskId(taskId);
String filedownLink = impExpTaskDetail.getLink();
if (org.springframework.util.StringUtils.isEmpty(filedownLink)) {
fileName = MessageUtils.message("batch.download") + ".xlsx";
} else {
fileName = StringUtils.subscribeNameString(filedownLink);
}
logger.info("-tempFilePath:{},fileName:{}", tempFilePath, fileName);
File file = ResourceUtils.getFile(String.join(File.separator, tempFilePath, fileName));
if (!file.exists()) {
String begin = DateUtil.now();
DateTime beginTime = DateUtil.parse(begin);
long download = HttpUtil.downloadFile(filedownLink, FileUtil.file(tempFilePath, fileName), new StreamProgress() {
@Override
public void start() {
logger.info("开始下载,时间为:" + begin);
}
@Override
public void progress(long progressSize) {
logger.info("已下载:{}", FileUtil.readableFileSize(progressSize));
}
@Override
public void finish() {
String end = DateUtil.now();
DateTime endTime = DateUtil.parse(end);
long between = DateUtil.between(beginTime, endTime, DateUnit.MS);
logger.info("下载完成,用时:" + DateUtil.formatBetween(between, BetweenFormatter.Level.SECOND));
}
});
}
io = new FileInputStream(file);
Workbook wb = new XSSFWorkbook(io);
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
wb.write(response.getOutputStream());
} catch (IOException e) {
logger.error("-downloadBatchImportData error:{}", e.getMessage());
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
logger.error("-OutputStream error:{}", e.getMessage());
}
}
if (io != null) {
try {
io.close();
} catch (IOException e) {
logger.error("-InputStream error:{}", e.getMessage());
}
}
if (Optional.ofNullable(tempFilePath).isPresent()) {
// 强制删除临时文件
boolean isDelete = com.hytalk.util.FileUtil.delFile(new File(tempFilePath));
logger.info("-downloadBatchImportData 强制删除临时文件 , filePath: {} , isDelete : {} ", tempFilePath, isDelete);
}
}
}