问题描述:今天在项目中遇到一个问题,一个报表插件,导出excel数据文件格式,实现原理是将需要下载的数据,按照excel格式(xml数据)内容生成好以后发送之后服务端,然后在服务端生成excel文件,再通过流的形式返回给客户端,完成excel数据下载功能。
在实际运行中,发现在下载文件时,数据内容量小,也就是生成的excel数据内容较少的时候,文件下载正常。但在数据量较大时(我们实际保存文件有3.6M),客户端调用action的方法时,调试发现参数都为null,方法定义如下:
Controller 方法映射如下:
/××
×@param fileName 文件名称
×@param format 文件格式,可以支持excel,csv,html,pdf
×@param content 文件内容,按照需要下载的文件格式,将文件格式与数据一起生成好
××/
@RequestMapping(params = "file=true", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<byte[]> export(String fileName,String format,String content) {
省略写文件细节,主要看方法定义
}
为了测试是什么问题导致的参数都为null,我就自己写了个servlet,servlet实现了doPost方法,读取了post请求的消息体,然后将客户端的下载文件请求发送至servlet上,跟上述大文件下载时一样的数据请求,发现参数其实可以可以接收到的,但使用spring mvc调用方法后参数就都为null了,所以可以判断问题处在spring mvn参数解析上。但因为今天项目bug修改任务比较急,在网上搜索一番后,并没有针对这个问题的解决方案,所以我果断采取在action 方法里自己实现读取body,并解析parameter,具体修改如下:
@RequestMapping(params = "file=true", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<byte[]> export(HttpServletRequest request) {
String body = readyBody(request);
Map<String,String> paramMap = parsePostBodyParam(body);
String filename = null,format=null,content=null;
if(paramMap!=null&&!paramMap.isEmpty()){
filename = paramMap.get("filename");
format = paramMap.get("format");
content = paramMap.get("content");
}
}
private String readyBody(HttpServletRequest req) { StringBuilder body = new StringBuilder(); BufferedReader br =null; String line = null; try { InputStreamReader reader = new InputStreamReader(req.getInputStream()); br = new BufferedReader(reader); while ((line=br.readLine())!=null){ body.append(line); } } catch (IOException e) { e.printStackTrace(); }finally { try { if (br!=null) { br.close(); } } catch (IOException e) { e.printStackTrace(); } } return body.toString(); }
private Map<String,String> parsePostBodyParam(String body){ Map<String,String> paramMap = new HashMap<String,String>(); if(StringUtils.isNotBlank(body)){ String[] params = body.split("&"); if(params!=null&¶ms.length>0){ String[] nameValue = null; for(String param : params){ nameValue = param.split("="); if(nameValue!=null&&nameValue.length==2){ try { paramMap.put(nameValue[0], URLDecoder.decode(nameValue[1],"UTF-8")); System.out.println("post body param key : "+nameValue[0]+" value : "+nameValue[1]); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } } return paramMap; }
在方法参数中直接使用了HttpServletRequest,然后自己读取body中的信息,确认客户端是以content-type 为application/x-www-form-urlencoded方式发送请求,所以自己按照fileName=value&format=value&content=value方式将参数解析称map,然后与之前开发写文件代码结合完成文件下载功能,至此bug修复。
待续:自己后续会研读spring源码,希望可以发现问题的根本原因,并通过解决参数解析实现来解决问题。
方案改进:此文件下载方案性能问题较多,1.数据量大时,客户端浏览器会直接崩溃。2.客户端将数据+文件格式大量数据发送至服务端,服务端再将生成好的文件返回给客户端,这样会增加服务端网络压力。希望结合前段分页数据导出组件,实现将文件生成由服务器段控制,然后将生成好的文件返回给客户端。