文件下载接口的优化实践

文件下载接口的需求:

  • 接口可以正常下载时, 响应的content-type为application/octet-stream;charset=UTF-8. 返回的是一个文件;
  • 接口出现异常时, 响应的content-type为application/json, 返回的是包含错误码、错误信息的json格式;

过程一: 文件写入response.outputStream, 方法返回值为void

@RequestMapping("/downloadFile")
    public void downloadFile(@RequestParam("file") String fileName, HttpServletResponse response) throws IOException {
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.setContentType("application/octet-stream;charset=UTF-8");// 设置response内容的类型
        downloadService.downloadFile(fileName, response);

    }

代码缺陷:

在接口出现异常时无错误信息返回.

在controller的AOP切面中, 通常对切入点这样处理: 会对返回值做一个全局报文封装

CommonResp resp = new CommonResp(ErrorCode.SERVER_ERROR, null);

try {

            resp = (CommonResp) point.proceed();
            return resp;
        } catch (BusinessException ex) {
            resp.setCode(ex.getErrorCode());
            resp.setMessage(ex.getErrorMessage());

            return resp;
        } catch (Exception ex) {
            LOGGER.error("服务端异常:{}", new Object[]{point.getTarget().getClass().getName(), point.getSignature().getName()}, ex);
            return resp;
        } finally {
            // 设置通用返回值
            resp.setRequestId(requestId);
            resp.setTimestamp(now);
        }

因此, 这样就会出现一个隐式约定: controller的方法必须有返回值而且是通用返回值CommonResp.

如果文件下载接口方法返回值为void, 会导致在AOP切面中对resp的后续处理出现NPE:

  • 在接口正常情况下, 文件已经写入输出流, 因为servlet的机制, 接口依然可以获得返回, AOP切面中产生的NPE会在tomcat的StandardWrapperValve中捕获, 并写入tomcat日志
  • 在接口异常情况下, 文件没有写入输出流, 在AOP切面中捕获了接口异常, 返回的resp为NULL, 在请求接口处得不到任何返回, 在AOP的finally也会因为resp为NULL产生NPE.

过程二: 将文件写入response.outputStream, 同时方法也有返回值, 但是没有设置contentType和没有主动关闭输出流

@RequestMapping("/downloadFile")
    public CommonResp downloadFile(@RequestParam("file") String fileName, HttpServletResponse response) throws IOException {

        //response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        //response.setContentType("application/octet-stream;charset=UTF-8");// 设置response内容的类型
        try {
            downloadService.downloadFile(fileName, response);
        } catch (BusinessException e) {
            //response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
            return new CommonResp<>(e.getErrorCode(), e.getErrorMessage());
        }
        return CommonResp.ok();

    }

因为没有主动关闭输出流,  会导致在返回的内容中, 追加了方法的返回值信息. 类似这样

(文件内容:)XXXXXXXXX{"code":0,"message":"success","timestamp":1635334499414,"request_id":"bb35896b15dd41e8a9d019bb727a547d"}

过程三: 将文件写入response.outputStream, 同时方法也有返回值, 没有设置contentType, 主动关闭输出流

@RequestMapping("/downloadFile")
    public CommonResp downloadFile(@RequestParam("file") String fileName, HttpServletResponse response) throws IOException {

        //response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        //response.setContentType("application/octet-stream;charset=UTF-8");// 设置response内容的类型
        try {
            downloadService.downloadFile(fileName, response);
            response.getOutputStream().close();
        } catch (BusinessException e) {
            //response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
            return new CommonResp<>(e.getErrorCode(), e.getErrorMessage());
        }
        return CommonResp.ok();

    }

返回的内容不再追加方法返回值信息. 

过程四: 将文件写入response.outputStream, 同时方法也有返回值, 设置contentType, 无需再主动关闭输出流

@RequestMapping("/downloadFile")
    public CommonResp downloadFile(@RequestParam("file") String fileName, HttpServletResponse response) throws IOException {

        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.setContentType("application/octet-stream;charset=UTF-8");// 设置response内容的类型
        try {
            downloadService.downloadFile(fileName, response);
        } catch (BusinessException e) {
            response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
            return new CommonResp<>(e.getErrorCode(), e.getErrorMessage());
        }
        return CommonResp.ok();

    }

设置contentType后, 接口以文件的形式返回. 因为spring的机制, 无需主动关闭输出流, 文件中不会追加方法返回值信息.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值