新手司机翻车实录
"哥,注册接口又被刷爆了!
"某一个周末下午,我接到电话,打开日志一看,NullPointerException堆栈里有38个不同位置的校验逻辑。
原来新人小王在Controller里写满了这样的代码:- // 典型错误示范(转载自某小厂祖传代码)
- public String register(UserDTO user) {
- if (user.getName() == null) {
- return "名字不能为空";
- }
- if (user.getAge() == null) {
- return "年龄不能为空";
- }
- if (user.getAge() < 18) {
- return "年龄不能小于18岁";
- }
- if (!user.getPhone().matches("^1[3-9]\\d{9}$")) {
- return "手机号不合法";
- }
- // ...后续还有20个if...
- }
复制代码 这才是代码界的"九转大肠"——每个入口都让人窒息。
作为一位有很多开发经验的老司机,今天,老夫带你修炼参数校验的6大神功。
希望对你会有所帮助。
第一重:JSR规范基础功
1.1 HibernateValidator瞬炼大法
可以使用Hibernate中Validator框架做参数校验,具体代码如下:- public class UserDTO {
- @NotBlank(message = "名称要填,皮这一下很开心?")
- private String name;
- @NotNull
- @Min(value = 18, message = "未成年禁止入内")
- @Max(60)
- private Integer age;
- @Pattern(regexp = "^1[3-9]\\d{9}$", message = "这手机号是哪国来的?")
- private String phone;
- }
- // Controller层启用校验(新手必知第一步)
- @PostMapping("/register")
- public Result register(@Valid @RequestBody UserDTO user) {
- // 业务代码...
- }
复制代码 技术要点:
- 引入spring-boot-starter-validation依赖(调料包记得加)
- @Valid注解要放在入参侧(别贴在DTO类上)
- 错误信息会进BindingResult(打扫战场需要手动处理)
第二重:全局异常擒龙手
2.1 统一异常拦截器
我们需要对异常进行统一拦截。
这样在出现参数校验异常,比如空指针时,不会把服务的内部错误信息直接输出给用户。
通过@RestControllerAdvice和@ExceptionHandler注解实现统一异常拦截器的功能。
具体代码如下:- @RestControllerAdvice
- public class GlobalExceptionHandler {
-
- // 专治各种不服校验
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public Result handleValidException(MethodArgumentNotValidException e) {
- BindingResult result = e.getBindingResult();
- return Result.fail(result.getFieldError().getDefaultMessage());
- }
- }
- // 返回格式规范(示例)
- public class Result<T> {
- private Integer code;
- private String msg;
- private T data;
-
- public static <T> Result<T> fail(String message) {
- return new Result<>(500, message, null);
- }
- }
复制代码 反爬虫机制:
- 禁止直接暴露字段名给前端(攻击者会利用字段名信息)
- 错误信息字典化管理(后面会教国际化这招)
第三重:自定义校验屠龙技
3.1 手机/邮箱二元校验
有时候,Hibernate Validator框架或者其他校验框架定义的校验不满足需求,我们需要自定义校验规则。
则可以自定义注解,实现ConstraintValidator接口,来实现具体的自定义的校验逻辑。
自定义注解@Contact在字段上使用。
具体代码如下:
[code]@Target({FIELD, PARAMETER})@Retention(RUNTIME)@Constraint(validatedBy = ContactValidator.class)public @interface Contact { String message() default "联系方式格式错误"; Class[] groups() default {}; Class |