SpringBootでファイルをダウンロードする正しい方法~

Spring Bootとaxiosと連携してファイルダウンロード機能を実現

序文

最近、フロントエンドがポスト経由で圧縮ファイルのダウンロードを要求すると同時に、一部のデータがバックエンドに渡されて圧縮パッケージが生成されるという奇妙な要件に遭遇しました。このとき、バックエンド インターフェイスは、圧縮ファイル ストリームを生成してフロントエンドに出力するだけではありません。エラーを報告し、例外を処理する機能が必要です。つまり、バックエンドがエラーを報告した場合、フロントエンドはファイル ストリームをダウンロードできないはずです。

より一般的な解決策

一般に、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 に直接書き込むため、すべての例外キャプチャがカバーされ、フロントエンドはデータからバイト ストリーム データを直接フェッチできます。

正しい解決策

実はエラー発生時にバックエンドがjsonデータを返す必要があるのですが、正常にダウンロードする場合は直接データを取得してバイトストリームを取得できるのでReturnを使います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, "服务器内部未知错误");
    }

}

上記は解決策の全体的なプロセスですが、少し一方的である可能性があり、実際の純粋に個人的な意見に過ぎません。皆さんの交流と学びを歓迎します!

おすすめ

転載: blog.csdn.net/dubulingbo/article/details/129641709