SpringBoot下载文件的正确解法方式

 更新时间:2023年08月09日 09:24:39   作者:habitplus007  
这篇文章主要给大家介绍了关于SpringBoot下载文件的正确解法方式,SpringBoot是一款流行的框架,用于开发Web应用程序,在使用SpringBoot构建Web应用程序时,可能需要实现文件下载的功能,需要的朋友可以参考下

前言

最近遇到一个奇怪的需求,前端通过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统一处理:

@RestControllerAdvice
public class ExceptionAdvice {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
    /**
     * 处理数据绑定异常
     *
     * @param bindException 数据绑定异常
     * @return 统一响应基本结果
     */
    @ExceptionHandler(value = BindException.class)
    public ResponseEntity<Result<Object>> handleValidateException(BindException bindException) {
        logger.error("数据绑定异常,{}", bindException.getMessage(), bindException);
        return of(HttpStatus.BAD_REQUEST, "数据绑定异常");
    }
    /**
     * 统一处理 405 异常
     *
     * @param exception 请求方法不支持异常
     * @return 统一响应结果
     */
    @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());
    }
    /**
     * 统一处理自定义操作错误异常
     *
     * @param exception 操作错误异常
     * @return 统一响应结果
     */
    @ExceptionHandler(value = OptErrorException.class)
    public ResponseEntity<Result<Object>> handleOptErrorException(OptErrorException exception) {
        return of(HttpStatus.INTERNAL_SERVER_ERROR, exception.getOptMsg());
    }
    /**
     * 统一处理 服务器内部 异常
     *
     * @param e 异常
     * @return 统一响应结果
     */
    @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, "服务器内部未知错误");
    }
}

以上就是解决的全过程,可能写得有点片面,纯属一点个人实践中的见解。

总结

到此这篇关于SpringBoot下载文件的正确解法方式的文章就介绍到这了,更多相关SpringBoot下载文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一步步教你写一个SpringMVC框架

    一步步教你写一个SpringMVC框架

    现在主流的Web MVC框架除了Struts这个主力外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,这篇文章主要给大家介绍了关于如何一步步写一个SpringMVC框架的相关资料,需要的朋友可以参考下
    2022-03-03
  • java使用定时器实现监听数据变化

    java使用定时器实现监听数据变化

    这篇文章主要为大家详细介绍了Java如何使用定时器监听数据变化,当满足某个条件时(例如没有数据更新)自动执行某项任务,有兴趣的可以了解下
    2023-11-11
  • MyBatis Mapper接受参数的四种方式代码解析

    MyBatis Mapper接受参数的四种方式代码解析

    这篇文章主要介绍了MyBatis Mapper接受参数的四种方式代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • springboot+jwt+微信小程序授权登录获取token的方法实例

    springboot+jwt+微信小程序授权登录获取token的方法实例

    本文主要介绍了springboot+jwt+微信小程序授权登录获取token的方法实例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • java中get()方法和set()方法的作用浅析

    java中get()方法和set()方法的作用浅析

    这篇文章主要给大家介绍了关于java中get()方法和set()方法的作用,set是是对数据进行设置,而get是对数据进行获取,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • 浅谈Java中GuavaCache返回Null的注意事项

    浅谈Java中GuavaCache返回Null的注意事项

    Guava在实际的Java后端项目中应用的场景还是比较多的,比如限流,缓存,容器操作之类的,本文主要介绍了GuavaCache返回Null的注意事项,感兴趣的可以了解一下
    2021-10-10
  • Java后端之俯瞰数据接收的三种方式

    Java后端之俯瞰数据接收的三种方式

    在前后端分离的开发项目中,前后端联调的时候会出现这样那样的问题,尤其是在调取数据的程序上面,有时候前端给的前端给到后端的明明是正确的但就是无法拿到正确的数据,下面小千就来给大家详解一下常见的三种数据传输方式
    2021-10-10
  • Java扑克牌速算24的方法

    Java扑克牌速算24的方法

    这篇文章主要为大家详细介绍了Java扑克牌速算24的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • Sentinel整合Feign流程详细讲解

    Sentinel整合Feign流程详细讲解

    要想整合Feign,首先要了解Feign的使用以及执行过程,然后看 Sentinel如何整合进去,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 举例讲解Java编程中this关键字与super关键字的用法

    举例讲解Java编程中this关键字与super关键字的用法

    这篇文章主要介绍了Java编程中this关键字与super关键字的用法示例,super是this的父辈,在继承过程中两个关键字经常被用到,需要的朋友可以参考下
    2016-03-03

最新评论