关于http前后端输出流:request/response等配置详解

最近在搞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的类中有具体的类型的定义,可以选择合适的类型进行二进制流的输出:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值