> 文章列表 > Controller层代码就该这么写,简洁又优雅!

Controller层代码就该这么写,简洁又优雅!

Controller层代码就该这么写,简洁又优雅!

文章目录

  • 1、统一返回结构
    • 1.1、定义返回数据结构
    • 1.2、统一包装返回结构
  • 2、参数校验
    • 2.1、@PathVariable 和 @RequestParam 参数校验,
    • 2.2、@RequestBody 参数校验
    • 2.3、自定义校验规则
  • 3、自定义异常与统一拦截异常

1、统一返回结构

使用一个状态码、状态信息就能清楚地了解接口调用情况:

1.1、定义返回数据结构

public interface IResult {Integer getCode();String getMessage();
}public enum ResultEnum implements IResult {OK(200, "OK"),BAD_REQUEST(400, "Bad Request"),UNAUTHORIZED(401, "Unauthorized"),FORBIDDEN(403, "Forbidden"),NOT_FOUND(404, "Not Found"),INTERNAL_SERVER_ERROR(500, "Internal Server Error");private Integer code;private String message;ResultEnum(Integer code, String message) {this.code = code;this.message = message;}@Overridepublic Integer getCode() {return code;}@Overridepublic String getMessage() {return message;}
}

统一返回数据结构

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {private Integer code;private String message;private T data;public static <T> Result<T> success(T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);}public static <T> Result<T> success(String message, T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);}public static Result<?> failed() {return new Result<>(ResultEnum.INTERNAL_SERVER_ERROR.getCode(), ResultEnum.INTERNAL_SERVER_ERROR.getMessage(), null);}public static Result<?> failed(String message) {return new Result<>(ResultEnum.INTERNAL_SERVER_ERROR.getCode(), message, null);}public static Result<?> failed(IResult errorResult) {return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);}public static <T> Result<T> instance(Integer code, String message, T data) {Result<T> result = new Result<>();result.setCode(code);result.setMessage(message);result.setData(data);return result;}
}

正常情况下,我们这样返回结果即可

@RestController
public class TestController {@Autowiredprivate UserMapper userMapper;@GetMapping("/test")public Result test(){UserPo userPo = userMapper.selectById(1);return  Result.success(userPo);}
}输出结果
{"code": 200,"message": "OK","data": {"id": 1,"name": "Jone","age": 18,"email": "test1@baomidou.com"}
}

扩展:
正确的结果,我们可以预期自己封装Result响应结构,如果是有些预期的返回结果,我们有没有全局封装的方法,他们返回结果自动封装成Result响应结构

1.2、统一包装返回结构

Spring 中提供了一个类 ResponseBodyAdvice ,能帮助我们实现上述需求:

@ControllerAdvice
public class CommonResponseAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof Result) {return body;}Result<Object> responseData = new Result();responseData.setCode(HttpStatus.OK.value());responseData.setMessage("请封装Response格式");responseData.setData(body);return responseData;}
}

经过上面的封装,此时我们调用controller,如果返回结果没有封装成Result结构,则直接返回Result的错误提示信息

@RestController
public class UserController {@GetMapping("/users/{id}")public User getUser(@PathVariable Long id) {// 查询用户信息User user = userService.getUser(id);return user;}
}返回结果
{"code": 200,"message": "请封装Response格式","data": {"id": 1,"name": "Jone","age": 18,"email": "test1@baomidou.com"}
}

2、参数校验

2.1、@PathVariable 和 @RequestParam 参数校验,

  • Get 请求的参数接收一般依赖这两个注解
@RestController(value = "prettyTestController")
@RequestMapping("/pretty")
public class TestController {private TestService testService;@GetMapping("/{num}")public Integer detail(@PathVariable("num") @Min(1) @Max(20) Integer num) {return num * num;}@GetMapping("/getByEmail")public TestDTO getByAccount(@RequestParam @NotBlank @Email String email) {TestDTO testDTO = new TestDTO();testDTO.setEmail(email);return testDTO;}@Autowiredpublic void setTestService(TestService prettyTestService) {this.testService = prettyTestService;}
}

2.2、@RequestBody 参数校验

  • Post、Put 请求的参数推荐使用 @RequestBody 请求体参数。
  • 对 @RequestBody 参数进行校验需要在 DTO 对象中加入校验条件后,再搭配 @Validated 即可完成自动校验。
//DTO
@Data
public class TestDTO {@NotBlankprivate String userName;@NotBlank@Length(min = 6, max = 20)private String password;@NotNull@Emailprivate String email;
}//Controller
@RestController(value = "prettyTestController")
@RequestMapping("/pretty")
public class TestController {private TestService testService;@PostMapping("/test-validation")public void testValidation(@RequestBody @Validated TestDTO testDTO) {this.testService.save(testDTO);}@Autowiredpublic void setTestService(TestService testService) {this.testService = testService;}
}

2.3、自定义校验规则

有些时候 JSR303 标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则。

自定义校验规则需要做两件事情:

  • 自定义注解类,定义错误信息和一些其他需要的内容
  • 注解校验器,定义判定规则
//自定义注解类
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {/*** 是否允许为空*/boolean required() default true;/*** 校验不通过返回的提示信息*/String message() default "不是一个手机号码格式";/*** Constraint要求的属性,用于分组校验和扩展,留空就好*/Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}//注解校验器
public class MobileValidator implements ConstraintValidator<Mobile, CharSequence> {private boolean required = false;private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$"); // 验证手机号/*** 在验证开始前调用注解里的方法,从而获取到一些注解里的参数** @param constraintAnnotation annotation instance for a given constraint declaration*/@Overridepublic void initialize(Mobile constraintAnnotation) {this.required = constraintAnnotation.required();}/*** 判断参数是否合法** @param value   object to validate* @param context context in which the constraint is evaluated*/@Overridepublic boolean isValid(CharSequence value, ConstraintValidatorContext context) {if (this.required) {// 验证return isMobile(value);}if (StringUtils.hasText(value)) {// 验证return isMobile(value);}return true;}private boolean isMobile(final CharSequence str) {Matcher m = pattern.matcher(str);return m.matches();}
}

自动校验参数真的是一项非常必要、非常有意义的工作。JSR303 提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。

3、自定义异常与统一拦截异常

  • 自定义异常可以让我们更加清晰地了解程序发生了什么错误,以及错误的具体原因。通过自定义异常,我们可以将不同的异常类型进行分类,方便我们在程序中对不同的异常类型进行不同的处理。

  • 统一拦截异常可以帮助我们在程序出现异常时,统一处理异常信息,而不需要在每个可能出现异常的地方都进行处理。这样可以减少代码的重复性,提高代码的可维护性和可读性。同时,统一拦截异常也可以方便我们对异常信息进行记录和分析,以便于后续的排查和修复。

//自定义异常
public class ForbiddenException extends RuntimeException {public ForbiddenException(String message) {super(message);}
}//自定义异常
public class BusinessException extends RuntimeException {public BusinessException(String message) {super(message);}
}//统一拦截异常
@RestControllerAdvice(basePackages = "com.example.demo")
public class ExceptionAdvice {/*** 捕获 {@code BusinessException} 异常*/@ExceptionHandler({BusinessException.class})public Result<?> handleBusinessException(BusinessException ex) {return Result.failed(ex.getMessage());}/*** 捕获 {@code ForbiddenException} 异常*/@ExceptionHandler({ForbiddenException.class})public Result<?> handleForbiddenException(ForbiddenException ex) {return Result.failed(ResultEnum.FORBIDDEN);}/*** {@code @RequestBody} 参数校验不通过时抛出的异常处理*/@ExceptionHandler({MethodArgumentNotValidException.class})public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {BindingResult bindingResult = ex.getBindingResult();StringBuilder sb = new StringBuilder("校验失败:");for (FieldError fieldError : bindingResult.getFieldErrors()) {sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");}String msg = sb.toString();if (StringUtils.hasText(msg)) {return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), msg);}return Result.failed(ResultEnum.VALIDATE_FAILED);}/*** {@code @PathVariable} 和 {@code @RequestParam} 参数校验不通过时抛出的异常处理*/@ExceptionHandler({ConstraintViolationException.class})public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {if (StringUtils.hasText(ex.getMessage())) {return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), ex.getMessage());}return Result.failed(ResultEnum.VALIDATE_FAILED);}/*** 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用*/@ExceptionHandler({Exception.class})public Result<?> handle(Exception ex) {return Result.failed(ex.getMessage());}}