多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
>[info]来源:https://juejin.cn/post/7123091045071454238 案例代码:https://gitee.com/flymini/codes03/tree/master/learn-contrl Controller 层主要有如下职责: 1. 接收请求并解析参数。 2. 调用 Service 执行具体的业务逻辑。 3. 捕获业务逻辑异常并做出反馈。 4. 业务逻辑执行成功后做出响应。 ```java @RestController @RequiredArgsConstructor public class StudentController { final StudentService studentService; @GetMapping("/student/getById") public Student getById(@RequestParam("id") String id) { //参数校验 if (!StringUtils.hasText(id)) { throw new RuntimeException("id不能为空"); } //异常处理 try { Student student = studentService.getById(id); return student; } catch (RuntimeException ex) { throw new RuntimeException(ex); } } } ``` 如果按照上面这样来封装 controller 层,就有如下问题: 1. 参数校验过多耦合了业务代码,违背单一职责。 2. 可能会在多个地方抛出同一个异常,导致代码重复。 3. 各种异常反馈和成功时的响应返回的数据格式不一致,接口对接不友好。 我们可以如下封装 controller 层逻辑,避免上面的问题。 [TOC] # 1. 统一返回结构 >[info]让 controller 层都返回统一的数据结构。 **1. 根据需要封装结果枚举** ```java public enum ResponseEnum { OK(20000, "操作成功"), FAIL(30000, "操作失败"), FAIL_VALITED(30000, "参数校验失败"), FORBIDDEN(30000, "没有权限"); private Integer code; private String message; ResponseEnum(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } ``` **2. 封装统一返回体** ```java @Setter @Getter public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.setCode(ResponseEnum.OK.getCode()); r.setMessage(ResponseEnum.OK.getMessage()); r.setData(data); return r; } public static <T> Result<T> fail(ResponseEnum resEnum) { Result<T> r = new Result<>(); r.setCode(resEnum.getCode()); r.setMessage(resEnum.getMessage()); return r; } } ``` **3. 在 controller 层统一返回 Result** ```java @RestController @RequiredArgsConstructor public class StudentController { final StudentService studentService; @GetMapping("/student/findById") public Result findById(@RequestParam("id") String id) { return Result.ok(studentService.getById(id)); } } ``` <br/> # 2. 参数校验 >[info]参数校验可以借助 Hibernate Validator 框架来完成,参考 https://www.kancloud.cn/king_om/springboot3x/3208554 ```java @PathVariable、@RequestParam 参数校验不通过时抛出异常 ConstraintViolationException。 @RequestBody 参数校验不通过时抛出异常 MethodArgumentNotValidException。 ``` <br/> # 3. 统一拦截异常 >[info]在代码任何地方只要异常没有被捕获,而是被抛出,统一异常拦截器就能够拦截到这个异常,拦截到这个异常后就可以做些自定义处理,然后再响应数据。 **1. 根据需要可以自定义一些异常** ```java public class BusinessException extends RuntimeException { public BusinessException(String message) { super(message); } } public class ForbiddenException extends RuntimeException { public ForbiddenException(String message) { super(message); } } ``` **2. 封装统一异常拦截器** ```java @Slf4j @RestControllerAdvice public class ExceptionAdvice { /** * 捕获BusinessException异常 */ @ExceptionHandler({BusinessException.class}) public Result<?> handleBusinessException(BusinessException ex) { log.error("[handleBusinessException]: {}", ex.getMessage(), ex); return Result.fail(ResponseEnum.FAIL); } /** * 捕获ForbiddenException异常 */ @ExceptionHandler({ForbiddenException.class}) public Result<?> handleForbiddenException(ForbiddenException ex) { log.error("[handleForbiddenException]: {}", ex.getMessage(), ex); return Result.fail(ResponseEnum.FORBIDDEN); } /** * 捕获@RequestBody参数校验不通过时抛出的异常处理 */ @ExceptionHandler({MethodArgumentNotValidException.class}) public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { log.error("[argumentException]: {}", ex.getMessage(), ex); return Result.fail(ResponseEnum.FAIL_VALITED); } /** * 捕获@PathVariable和@RequestParam参数校验不通过时抛出的异常处理 */ @ExceptionHandler({ConstraintViolationException.class}) public Result<?> handleConstraintViolationException(ConstraintViolationException ex) { log.error("[constraintException]: {}", ex.getMessage(), ex); return Result.fail(ResponseEnum.FAIL_VALITED); } /** * 当其他异常无法处理时就由这个方法捕获 */ @ExceptionHandler({Exception.class}) public Result<?> handleException(Exception ex) { log.error("[handleException]: {}", ex.getMessage(), ex); return Result.fail(ResponseEnum.FAIL); } } ``` **3. 测试** ```java @Validated @RestController @RequiredArgsConstructor public class StudentController { final StudentService studentService; @GetMapping("/student/findById") public Result findById(@RequestParam("id") @NotBlank(message = "id不能为空") String id) { Student student = studentService.getById(id); return Result.ok(student); } } ``` 在访问时故意不传入参数`id`,后台就会抛出如下异常: ``` jakarta.validation.ConstraintViolationException: findById.id: id不能为空 at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:138) ~[spring-context-6.0.13.jar:6.0.13] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed ... ``` 客户端得到如下响应结构: ```json {"code":30000,"message":"参数校验失败","data":null} ```