最近遇到一个奇怪的需求,前端通过post请求下载压缩文件,同时会传给后端一些数据,用于生成压缩包。此时后端接口就不仅仅是生成压缩文件流输出给前端。而必须要有报错能力与异常处理能力。即如果后端报错,前端应该是下载不了文件流。
一般而言,Spring Boot生成文件流供前端下载,会直接将文件流写入到 HttpServletResponse.getOutputStream()
,然而这样会有一个问题,无论后端如何报错,前端都能成功下载文件,因为 status=200
。即如下写法:
@PostMapping(value = "/project/code/download")public void downloadCode(@RequestBody TProjectInfo pf, HttpServletResponse response) { // 1.生成源码文件 // 2.压缩文件 // 3.设置回复的一些参数 // 4.将压缩文件写入网络流 log.info("request param: {}", pf); OutputStream os = null; try { // 配置文件下载 // 下载文件能正常显示中文 String filename = pf.getProjectName() + System.currentTimeMillis() + ".zip"; response.setHeader("Content-Disposition", "attachment;filename=" + filename); response.setHeader("Content-Type", "application/octet-stream"); response.setContentType("application/octet-stream; charset=UTF-8"); os = response.getOutputStream(); projectService.generateCode(pf, os); log.info("Download successfully!"); } catch (Exception e) { log.error("Download failed: {}", e.getMessage()); throw new OptErrorException(OptStatus.FAIL.code, "文件下载失败"); } finally { if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } }}
分析原因
因为后端直接向OutputStream写入,会覆盖所有异常捕获,因此前端直接向data下取字节流数据即可。
其实就是要后端能在错误时返回json数据,正确下载时直接取data下取字节流即可,所以使用 ResponseEntity
返回即可。
@PostMapping(value = "/project/code/download")public ResponseEntity<InputStreamResource> downloadCode(@RequestBody TProjectInfo pf) { log.info("request param: {}", pf); String filename = pf.getProjectName() + System.currentTimeMillis() + ".zip"; byte[] bytes = projectService.generateCode(pf); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)); return ResponseEntity.ok() .headers(headers) .contentType(MediaType.parseMediaType("application/octet-stream")) .body(new InputStreamResource(bais));}
这里的异常,使用RestExceptionAdvice统一处理:
@RestControllerAdvicepublic class ExceptionAdvice { private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class); @ExceptionHandler(value = BindException.class) public ResponseEntity<Result<Object>> handleValidateException(BindException bindException) { logger.error("数据绑定异常,{}", bindException.getMessage(), bindException); return of(HttpStatus.BAD_REQUEST, "数据绑定异常"); } @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) public ResponseEntity<Result<Object>> handleMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) { if (exception != null) { logger.error("Http 405, {}", exception.getMessage(), exception); } return of(HttpStatus.METHOD_NOT_ALLOWED, "方法不被支持"); } private ResponseEntity<Result<Object>> of(HttpStatus status, String msg) { return ResponseEntity.status(status).body(Result.builder().code(OptStatus.FAIL.code).msg(msg).build()); } @ExceptionHandler(value = OptErrorException.class) public ResponseEntity<Result<Object>> handleOptErrorException(OptErrorException exception) { return of(HttpStatus.INTERNAL_SERVER_ERROR, exception.getOptMsg()); } @ExceptionHandler(value = Throwable.class) public ResponseEntity<Result<Object>> handle500(Throwable e) { if (e != null) { logger.error("Http 500, {}", e.getMessage(), e); } return of(HttpStatus.INTERNAL_SERVER_ERROR, "服务器内部未知错误"); }}
以上就是解决的全过程,可能写得有点片面,纯属一点个人实践中的见解。欢迎各位交流学习!
来源地址:https://blog.csdn.net/dubulingbo/article/details/129641709