错误处理
RFC 7807 Problem Details。
错误处理
这一课你将能做到
- 设计统一的 API 错误响应格式,不再让调用方面对千奇百怪的错误体
- 实现 RFC 7807 Problem Details 标准,用
application/problem+json返回结构化错误 - 在 Spring Boot 中搭建全局异常处理器,把异常翻译成正确的 HTTP 状态码和错误响应
为什么要学
错误处理是 API 的「售后服务」。调用方拿到 500 和一堆堆栈信息,跟拿到「课程不存在」的明确提示,体验天差地别。好的错误设计让前端能自动处理异常(比如 401 跳登录页),差的错误设计让联调变成猜谜。
先建立图像
错误响应演进:
Level 0:裸异常
HTTP/1.1 500 Internal Server Error
{"timestamp":"...","status":500,"error":"Internal Server Error","trace":"java.lang.NullPointerException..."}
Level 1:统一包装
HTTP/1.1 404 Not Found
{"code":404,"message":"课程不存在","data":null}
Level 2:Problem Details(RFC 7807)
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"type": "https://api.codeforge.com/problems/lesson-not-found",
"title": "课程不存在",
"status": 404,
"detail": "ID 为 123 的课程不存在",
"instance": "/api/lessons/123"
}
跟着做
步骤 1:统一响应包装
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.code = 200;
response.message = "success";
response.data = data;
return response;
}
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.code = code;
response.message = message;
return response;
}
// getter/setter
}
步骤 2:自定义业务异常
public class BusinessException extends RuntimeException {
private final int code;
private final String message;
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
// getter
}
public class LessonNotFoundException extends BusinessException {
public LessonNotFoundException(Long id) {
super(404, "课程不存在: " + id);
}
}
public class SubmissionDeniedException extends BusinessException {
public SubmissionDeniedException() {
super(403, "您没有权限提交此课程的作业");
}
}
步骤 3:全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return ResponseEntity.status(e.getCode())
.body(ApiResponse.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(
MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(400, "参数校验失败: " + message));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleUnknownException(Exception e) {
log.error("系统异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(500, "服务器内部错误,请稍后重试"));
}
}