本人的一个老项目,前端用jquery写的,遇到一个需求,前端上传文件,后台接收到文件后,处理文件中数据并生成文件返回给前端导出,在开发过程中遇到了一些问题,采用曲线救国的方式实现了,记录一下,也希望哪位大神有更好的方式指点一下。
首先,单独写了不接受外部输入,直接一个生成文件返回给前端的测试接口:
后台代码:
@GetMapping("testCalc")
public void testExport(HttpServletResponse response) throws IOException {
//导出数据
OutputStream outputStream = null;
try (HSSFWorkbook wb = calcFeeService.getTestWorkbook()) {
//设置Http响应头告诉浏览器下载这个附件
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;Filename=" + "TEST" + sdf.format(new Date()) + ".xls");
outputStream = response.getOutputStream();
wb.write(outputStream);
} catch (Exception ex) {
throw new ServiceException("导出Excel出现异常,异常信息:" + ex.getMessage());
} finally {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
}
}
开始的时候用jquery写前端,没有找到合适的方法。直接用window.open倒是可以,不过参数需要传入接口的全路径,不同环境域名不一样,每次发布都要修改路径。还有一个弊端,重新打开窗口,若遇到后台抛出业务异常,则无法给出友好的弹框提示,报错信息直接返回到了另一个新的标签页。后面参考网友的一篇文章(https://blog.youkuaiyun.com/zhazha0807/article/details/118734395),改用了原生ajax方式,设置responseType为blob,才成功,代码如下。
前端代码(直接用原生ajax发起请求):
function doTest() {
download('calcFee/testCalc.do', 'test');
}
function download(url,filename) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// xhr.setRequestHeader('token', 'xxxxxxx'); //用来做身份校验的字符串
xhr.responseType = "blob"; // 设置返回类型blob
// 定义请求完成的处理函数,请求前也可以增加 加载框/禁用下载按钮的相关逻辑
xhr.onload = function() {
if (this.status === 200) {
var responseObj = this.response;
var responseType = responseObj.type;
var reader = new FileReader();
reader.readAsDataURL(responseObj);
reader.onload = function(e) {
if(responseType === 'application/json'){//遇业务异常返回,前端弹框提示错误信息
var msg = e.target.result.replace('data:application/json;base64,', '');
layerAlert(JSON.parse(base64Decode(msg)).message,2,"警告");
doGetObjects();
}else{
// 转换完成后创建a标签下载
var a = document.createElement('a');
a.download = filename;
a.href = e.target.result;
$("body").append(a);
a.click();
$(a).remove();
}
}
}
};
xhr.send() //发送ajax请求
}
直接无参数传入后台发起文件导出请求没有问题。然而,业务需求是必须要有输入的,输入内容为excel文件流,无法直接追加到url路径后面,必须用post方式请求。而下载接口必须为get方式请求,无奈之下 ,只能将这个需求拆分成两个接口,一个接口为接收前端文件传入,处理传入的数据,将处理结果暂存。前端调用数据预处理接口成功后,发起文件导出请求。
后台接口1:(接收输入,处理数据)
@PostMapping("doCalcFee")
@ResponseBody
public ResultVO calcFee(MultipartFile mFile) throws Exception{
InputStream is = null;
try{
if(mFile != null){
File tmpDir = new File(AccessController.doPrivileged(new GetPropertyAction("java.io.tmpdir")));
if(!tmpDir.exists()){
tmpDir.mkdirs();
}
File tempFile = File.createTempFile("baseForm", ".xls");
mFile.transferTo(tempFile);
is = new FileInputStream(tempFile);
tempFile.deleteOnExit();
}else{
throw new ServiceException("输入文件不能为空!");
}
calcFeeService.clearCacheData();
calcFeeService.doBusiness(is);
}catch (Exception e){
throw new ServiceException("解析数据文件出现异常,异常信息:" + e.getMessage());
}finally {
if(is != null){
is.close();
}
}
return new ResultVO();
}
后台接口2:(导出结果)
@GetMapping("doExportResult")
public void exportResult(HttpServletResponse response) throws Exception{
if(calcFeeService.cacheIsEmpty()){
throw new ServiceException("数据未准备好,请重新计算!");//全局异常处理类会将错误信息以json格式返回至前端
}
//导出数据
OutputStream outputStream = null;
try (HSSFWorkbook wb = calcFeeService.generateResultWorkbook()) {
//设置Http响应头告诉浏览器下载这个附件
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;Filename=" + sdf.format(new Date()) + ".xls");
outputStream = response.getOutputStream();
wb.write(outputStream);
} catch (Exception ex) {
throw new ServiceException("导出Excel出现异常,异常信息:" + ex.getMessage());
} finally {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
}
}
前端调用时先调用处理数据的post接口,成功后再调用下载结果文件的get接口
function doCalcFee() {
var url = "calcFee/doCalcFee.do";
$("#calcFormId").ajaxSubmit({
type:'post',
url:url,
dataType:'json',
beforeSend:function(){
console.log('loader show')
$("#load").show();
$("#calcBtn").attr('disabled',true);//提交过程中,将计算按钮设置为不可点击
},
success:function(result){
$("#load").hide();
$("#calcBtn").attr('disabled',false);//接口返回成功后,将计算按钮恢复
if(result.state==1){
download('calcFee/doExportResult.do', '费用统计结果' + new Date().Format("yyyyMMddHHmmss"));
doGetObjects();
}else{
layerAlert(result.message,2,"警告");
}
}
});
}
完成!!!
部分内容参考 https://blog.youkuaiyun.com/zhazha0807/article/details/118734395

本文讲述了作者在旧项目中遇到的前端文件上传,后台处理并生成文件返回给前端的需求。通过拆分接口、使用Blob和原生Ajax实现,同时讨论了线程安全问题及改进方法。寻求更高效、一次请求解决的解决方案。
450

被折叠的 条评论
为什么被折叠?



