2026/4/18 16:11:57
网站建设
项目流程
wordpress 一小时建站教程,网站做友情链接,网页设计师培训水公司,摄影作品共享网站开发背景视频看了几百小时还迷糊#xff1f;关注我#xff0c;几分钟让你秒懂#xff01;#x1f9e9; 一、为什么需要统一异常处理#xff1f;在没有统一异常处理前#xff0c;你的代码可能是这样的#xff1a;GetMapping(/user/{id})
public ResponseEntity?…视频看了几百小时还迷糊关注我几分钟让你秒懂 一、为什么需要统一异常处理在没有统一异常处理前你的代码可能是这样的GetMapping(/user/{id}) public ResponseEntity? getUser(PathVariable Long id) { try { User user userService.findById(id); return ResponseEntity.ok(user); } catch (UserNotFoundException e) { return ResponseEntity.status(404).body(用户不存在); } catch (Exception e) { log.error(查询用户出错, e); return ResponseEntity.status(500).body(系统繁忙); } }问题很明显每个接口都要写try-catch重复代码多错误信息不统一前端解析困难异常日志分散排查问题难HTTP 状态码混乱有的返回 200 却 body 是错误。✅目标一处定义全局生效结构清晰前后端解耦日志完整便于监控。️ 二、Spring Boot 正确姿势ControllerAdviceExceptionHandler1. 定义统一返回格式import lombok.Data; import java.time.LocalDateTime; Data public class ApiResultT { private int code; private String message; private T data; private LocalDateTime timestamp; public static T ApiResultT success(T data) { ApiResultT result new ApiResult(); result.code 200; result.message success; result.data data; result.timestamp LocalDateTime.now(); return result; } public static T ApiResultT error(int code, String message) { ApiResultT result new ApiResult(); result.code code; result.message message; result.timestamp LocalDateTime.now(); return result; } } 前端只需要判断code 200就知道成功无需解析 HTTP 状态码2. 自定义业务异常类public class BusinessException extends RuntimeException { private final int code; public BusinessException(int code, String message) { super(message); this.code code; } public BusinessException(String message) { this(400, message); // 默认 400 } public int getCode() { return code; } }✅ 业务异常 vs 系统异常BusinessException参数错误、余额不足等可预期错误RuntimeException空指针、数据库连接失败等不可预期错误。3. 全局异常处理器核心import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import java.util.stream.Collectors; RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log LoggerFactory.getLogger(GlobalExceptionHandler.class); // 处理自定义业务异常 ExceptionHandler(BusinessException.class) public ApiResult? handleBusinessException(BusinessException e, HttpServletRequest request) { log.warn(业务异常 | URI: {} | Error: {}, request.getRequestURI(), e.getMessage()); return ApiResult.error(e.getCode(), e.getMessage()); } // 处理参数校验异常JSR-303 ExceptionHandler(MethodArgumentNotValidException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public ApiResult? handleValidationException(MethodArgumentNotValidException e) { String message e.getBindingResult().getFieldErrors().stream() .map(error - error.getField() : error.getDefaultMessage()) .collect(Collectors.joining(; )); log.warn(参数校验失败: {}, message); return ApiResult.error(400, 请求参数错误: message); } // 处理路径变量/请求参数类型转换错误 ExceptionHandler(IllegalArgumentException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public ApiResult? handleIllegalArgumentException(IllegalArgumentException e) { log.warn(非法参数: {}, e.getMessage()); return ApiResult.error(400, 参数格式错误); } // 处理所有未捕获的系统异常兜底 ExceptionHandler(Exception.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiResult? handleSystemException(Exception e, HttpServletRequest request) { // 生产环境不要暴露堆栈给前端 log.error(系统异常 | URI: {} | Error: {}, request.getRequestURI(), e.getMessage(), e); return ApiResult.error(500, 系统繁忙请稍后再试); } } 关键点RestControllerAdviceControllerAdviceResponseBody按异常类型分层处理生产环境绝不返回e.printStackTrace()给前端 三、使用示例1. 业务层抛出异常Service public class UserService { public User findById(Long id) { if (id 0) { throw new BusinessException(用户ID必须大于0); } // ... 查询逻辑 if (user null) { throw new BusinessException(404, 用户不存在); } return user; } }2. Controller 无需 try-catchRestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public ApiResultUser getUser(PathVariable Long id) { User user userService.findById(id); // 可能抛出 BusinessException return ApiResult.success(user); // 成功时直接返回 } }3. 调用效果请求/api/user/0→ 返回{ code: 400, message: 用户ID必须大于0, data: null, timestamp: 2026-01-29T12:00:00 }请求/api/user/999不存在→ 返回{ code: 404, message: 用户不存在, data: null, timestamp: 2026-01-29T12:00:01 }✅ 前端只需判断code无需关心 HTTP 状态码❌ 四、反例 常见错误反例 1在 Controller 里到处写 try-catch// ❌ 重复、难维护、风格不统一 GetMapping(/user/{id}) public Object getUser(PathVariable Long id) { try { // ... } catch (Exception e) { return Map.of(error, e.getMessage()); } }反例 2把系统异常堆栈返回给前端ExceptionHandler(Exception.class) public String handle(Exception e) { return e.toString(); // 前端看到一长串堆栈 } 风险泄露服务器路径、框架版本、数据库结构反例 3所有异常都返回 200// ❌ HTTP 状态码始终是 200违背 RESTful 原则 public MapString, Object error(String msg) { return Map.of(code, 500, msg, msg); }✅ 正确HTTP 状态码 JSON body 双重语义。⚠️ 五、注意事项 最佳实践场景建议敏感信息日志可打 full stack但返回给前端的 message 要脱敏国际化message可根据Accept-Language动态切换监控告警对5xx异常接入 Prometheus AlertManager区分环境开发环境可返回详细错误生产环境必须隐藏异常分类建议定义ClientException4xx和ServerException5xx 六、总结统一异常处理架构图Controller 方法 ↓ 抛出 BusinessException / ValidationException / Exception ↓ GlobalExceptionHandler 拦截 ↓ 按类型返回标准化 ApiResult ↓ 前端统一处理 code 200 ?好处✅ 代码简洁专注业务✅ 错误格式统一前后端协作顺畅✅ 日志完整便于运维✅ 符合 RESTful 规范。视频看了几百小时还迷糊关注我几分钟让你秒懂