序文
最近、フロントエンドがポスト経由で圧縮ファイルのダウンロードを要求すると同時に、一部のデータがバックエンドに渡されて圧縮パッケージが生成されるという奇妙な要件に遭遇しました。このとき、バックエンド インターフェイスは、圧縮ファイル ストリームを生成してフロントエンドに出力するだけではありません。エラーを報告し、例外を処理する機能が必要です。つまり、バックエンドがエラーを報告した場合、フロントエンドはファイル ストリームをダウンロードできないはずです。
より一般的な解決策
一般に、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, "服务器内部未知错误");
}
}
上記は解決策の全体的なプロセスですが、少し一方的である可能性があり、実際の純粋に個人的な意見に過ぎません。皆さんの交流と学びを歓迎します!