最近在搞spring boot升级,顺便优化及复习了一下http输入输出流及pdf打印的改造和优化。
业务场景:
pdf文件打印,正常打印后台输出二进制流,但是如果在查询数据的过程中遇到配置数据缺失等问题要能够输出responseObject 的json错误提示。
在一个方法里能否同时输出流及json
该场景最初的实现代码如下:
controler:
@ResponseBody
@ApiOperation("账单账单打印")
@RequestMapping(value = "/prinAndExpIpxAccount", method = {RequestMethod.POST})
@SysDataMonitor(title = "账单打印", action = "账单打印")
public ResultObject printAccount(HttpServletResponse response, @RequestBody List<PrintVO> PrintVOs) {
List<PrintVO> PrintVOList = this.getDate(PrintVOs);
// 打印
ResultObject result = FlowService.printAccount(response, iIpxPrintVOList);
return result;
}
service:
@Override
public ResultObject printAccount(HttpServletResponse response, PrintVO PrintVO, String pdfName) {
ResultObject resultObject = new ResultObject();
try {
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "inline;");
ins = new FileInputStream(files[0]);
BufferedInputStream bins = new BufferedInputStream(ins);
OutputStream outs = response.getOutputStream();
BufferedOutputStream bouts = new BufferedOutputStream(outs);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
bouts.write(buffer, 0, bytesRead);
}
bouts.flush();
ins.close();
bins.close();
outs.close();
bouts.close();
baseFile.delete();
fs.close();
} catch (Exception e) {
e.printStackTrace();
try {
fs.close();
} catch (IOException e1) {
e1.printStackTrace();
}
resultObject.setCode("1");
resultObject.setMsg("打印异常");
resultObject.setResult(ResultObject.FAIL);
return resultObject;
}
resultObject.setCode("0");
resultObject.setMsg("打印成功");
resultObject.setResult(ResultObject.SUCCESS);
return resultObject;
}
该方法可以实现需求,但是有瑕疵,当后台调用打印方法时,日志会报错,报错信息如下:
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [XXXXX] with preset Content-Type ‘application/pdf;charset=utf-8’
优化:解决报错问题
该报错主要是同时定义了resultobject 及二进制流,可以将resultObject返回去掉改用void,返回的json通过判断以打印输出(改造成printError方法):
controler:
@ApiOperation("账单打印")
@RequestMapping(value = "/printAccount", method = {RequestMethod.POST})
@SysDataMonitor(title = "账单打印", action = "账单打印")
public void printAccount(HttpServletResponse response, @RequestBody List<PrintVO> PrintVOs) {
ResultObject result = new ResultObject();
List<PrintVO> PrintVOList = this.getDate(PrintVOs);
// 打印
result = FlowService.printAccount(response, iIpxPrintVOList);
printError(result,response);
}
public void printError(ResultObject resultObject,HttpServletResponse response){
try {
response.setContentType("application/json;charset=utf-8");
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild");
response.addHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
response.addHeader("X-Powered-By", "3.2.1");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSON(resultObject));
} catch (IOException e) {
e.printStackTrace();
}
}
输出流的重复输出
此时,会遇到一个报错:
getOutputStream() has already been called for this response
因为已经定义了输出,所以在printError方法中增加:
response.reset();//重置 响应头
此时,遇到了新的报错:
Cannot call reset() after response has been committed
只有在二进制流无法输出的时候才进行json打印输出,有两种方法:
【方法1】通过if 分支判断,控制唯一的输出方法;
【方法2】在第二次输出处增加判断:
采用的第二种方法(改动较小,对逻辑的影响小)controller中增加如下判断:
if(!response.isCommitted()){
printError(result,response);
}
前台的改造
前台接口可以支持多种形式的数据接收,有两种方法:
【方法1】先定义输出类型,在返回值处进行判断。
接口:
print: (params) => {
return adminServer({
url: "/api/printAccount",
responseType: "blob",
method: "post",
data: params,
});
},
通过切出分支,判断是否是json格式,来增加提示。
直接上代码:
try {
this.exportLoading();
let res = await api.print(data);
if (res) {
if (res.data.type === 'application/json') {
const reader = new FileReader()
reader.readAsText(res.data, 'utf-8')
reader.onload = () => {
let data = JSON.parse(reader.result) //这里转json格式
//必须要用箭头函数,否则this.$Msg会报错
this.$alert.warning(data.errorMsg);
}
}else{
const binaryData = [res.data];
let url = window.URL.createObjectURL(
new Blob(binaryData, { type: "application/pdf" })
);
this.pdfUrl = `/pdf/web/viewer.html?file=` + encodeURIComponent(url);
window.open(this.pdfUrl, "_blank");
}
}
} catch (error) {}
this.exportLoad.close();
【方法2】
在接口中不定义接收方法,去掉responseType,在接收中通过返回值关键属性进行判断。
扩展
本文只展示了作为二进制流的一种形式:pdf,改成其他的都是可以的
可见MediaType的类中有具体的类型的定义,可以选择合适的类型进行二进制流的输出: