目录
- 反例
- 正确的处理方法
- 第1步:在业务逻辑层定义并抛出“业务异常”
- 第2步:在最外层创建“全局异常处理器”
- 最佳实践
反例
刚开始学习后端开发时做了一个文件管理系统,其中有一个文件删除的API。
文件和目录删除操作 Delete 接口的设计的很粗糙 :HTTP状态码和业务逻辑状态码都返回给请求方了。
业务逻辑状态码:删除操作执行成功逻辑状态码code=200,删除失败逻辑状态码code=500;文件不存在code=FS4004;无管理权限code=FS4003.
http状态码:只要请求成功就返回200。
这就导致了一个问题,因为我的处理逻辑比较复杂,请求方每次读到http状态码为200就认为删除成功了。
但是我们测试删除操作时,并不存在的文件时也显示http code=200,但我们更想给请求方更多的信息。
正确的处理方法
第1步:在业务逻辑层定义并抛出“业务异常”
当删除操作失败时,不要直接返回一个包含错误码的普通对象,而是抛出一个能描述业务错误的自定义异常。
点击查看代码
// 你的文件删除服务 (FileService.java)
public void deleteFile(String fileId, String userId) {File file = fileRepository.findById(fileId);// 业务场景1:文件不存在if (file == null) {// 抛出业务异常,而不是返回一个错误对象throw new BusinessException("FS4004", "文件不存在");}// 业务场景2:权限不足if (!file.getOwnerId().equals(userId)) {throw new BusinessException("FS4003", "无权删除该文件");}// ... 执行删除操作boolean deleted = doDelete(file);// 业务场景3:未知的内部错误导致删除失败if (!deleted) {throw new BusinessException("SYS5000", "删除操作失败,请联系管理员");}
}
第2步:在最外层创建“全局异常处理器”
在你的 trigger 层(通常是 Spring MVC 中),创建一个全局异常处理器。它的职责就是捕获从业务层抛出的 BusinessException,然后把它“翻译”成标准的 HTTP 响应。
点击查看代码
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(BusinessException.class)public ResponseEntity<ApiResponse> handleBusinessException(BusinessException ex) {ApiResponse responseBody = new ApiResponse(ex.getCode(), ex.getMessage(), null);// 根据业务异常的类型,决定返回哪个HTTP状态码switch (ex.getCode()) {case "FS4004": // 文件不存在return new ResponseEntity<>(responseBody, HttpStatus.NOT_FOUND); // HTTP 404case "FS4003": // 无权限return new ResponseEntity<>(responseBody, HttpStatus.FORBIDDEN); // HTTP 403default: // 其他业务失败,如删除失败return new ResponseEntity<>(responseBody, HttpStatus.INTERNAL_SERVER_ERROR); // HTTP 500}}@ExceptionHandler(Exception.class)public ResponseEntity<ApiResponse> handleGenericException(Exception ex) {// 处理所有未被捕获的未知异常ApiResponse responseBody = new ApiResponse("SYS9999", "系统内部未知错误", null);return new ResponseEntity<>(responseBody, HttpStatus.INTERNAL_SERVER_ERROR); // HTTP 500}}
点击查看代码
@RestController
public class FileController {@Autowiredprivate FileService fileService;@DeleteMapping("/files/{id}")public ResponseEntity<ApiResponse> deleteFile(@PathVariable String id) {// 只管调用,成功了就往下走,失败了异常处理器会接管fileService.deleteFile(id, getCurrentUserId());// 如果代码能执行到这里,说明没有任何异常,删除成功ApiResponse responseBody = new ApiResponse("200", "删除成功", null);return new ResponseEntity<>(responseBody, HttpStatus.OK); // HTTP 200}
}
这不仅能让自己的代码更易于维护,也能让所有调用你接口的客户端(前端、其他后端服务等)更容易地进行合作开发。
最佳实践
一个优秀的 API 设计,应该让这两者协同工作,而不是混淆它们。
核心流程:
业务层:只关心业务逻辑,当业务失败时,抛出携带业务状态码的自定义异常(如 AppException )。
表现层(全局异常处理器):作为“翻译官”,捕获业务异常,然后将业务状态码映射到一个最贴切的 HTTP 状态码,并构建包含业务状态码的 JSON 响应体。