答应我,Python别再if/else校验请求参数了可以吗

385 阅读8分钟

哎!弹指之间

遥想当年,其实我也特别钟情于if/else连环写法,来校验请求入参,上来就是一顿SAO操作

![](https://pic4.zhimg.com/80/v2-43f0b4ae4a505bc22e8595ca6b71b3e4_720w.jpg)

就现在来说,我们项目都是前后端分离,前后端约定好请求参数,封装成一个对象,前段根据对象来传参,但传入的参数是否为空,怎么判断!

比如举个好理解的简单例子:

请求参数

@Data
@ApiModel(value = "登录请求参数")
public class LoginReqDTO {

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "验证码")
    private String code;
}

响应结果

@Data
public class RespResult<T> {
    private Integer code;
    private String message;
    private T data;
}

当前端调用我们的登录接口时,我们需要 判断请求参数是否为空,这时候 SAO代码 出现了:

@RestController
public class LoginController{

    @ApiOperation(value = "登录接口")
    @PostMapping(value = "/login")
    public RespResult login(LoginReqDTO loginReqDTO) {
        if (StringUtils.isEmpty(loginReqDTO.getMobile)) {//判断手机号是否为空
            return new RespResult(400, "手机号不能为空");
        } else if (StringUtils.isEmpty(loginReqDTO.getPassword)) {//判断密码是否为空
            return new RespResult(400, "密码不能为空");
        } else if (StringUtils.isEmpty(loginReqDTO.getCode)) {//判断验证码是否为空
            return new RespResult(400, "验证码不能为空");
        } else {
            return new RespResult(200, "成功");//我的吗,终于成功了 !
        }
    }
}

卧CAO!现在请求对象里的参数只有三个,当参数有几十个时,那几十个 if/else嵌套,不累吗,那可以说是非常酸爽了……

那么,问题来了:

  • 第一点:你是爽了,别人一阅读(怕是上来就是一 Jao!)
  • 第二点:现在是一个接口,那几十个接口时,那全屏可能只有 if/else了。(哟呵!腻害
  • 第三点:则是以后如果再复杂一点,或者想要再加条件的话,是不是还的整个if/else,极其不好扩展
  • 第四点:最后代码若一改,以前的老功能肯定还得重测,岂不疯了……

所以,如果在不看下文的情况下,你一般会如何去对付这些令人头痛的if/else语句呢?

当然有人会说用 循环语句switch/case 来判断是否会优雅一些呢?答案是:有锤子区别,毛区别都没有

![](https://pic1.zhimg.com/80/v2-700fd8b675ec4dcda832e9ed307709e0_720w.jpg)

接下来简单讲几种改进方式,别再 if/else走天下了

有Boot自带的参数验证为啥不用

大家肯定学过boot吧(没学过也不打紧,没吃过猪肉,还没见过猪跑吗!!),因此,为啥不用boot自带的spring validation,也就是**@Validated注解**,为啥不用?

首先我们在请求参数上加上 @NotBlank注解,并定义为空时的 message

@Data
@ApiModel(value = "登录请求参数")
public class LoginReqDTO {

    @NotBlank(message = "手机号不能为空")
    @ApiModelProperty(value = "手机号")
    private String mobile;

    @NotBlank(message = "密码不能为空")
    @ApiModelProperty(value = "密码")
    private String password;

    @NotBlank(message = "验证码不能为空")
    @ApiModelProperty(value = "验证码")
    private String code;
}

接下来我们将 请求参数是否为空 交给 spring validation 来判断,只需要定义一个全局异常处理器来处理异常:

GlobalExceptionHandler :

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = BindException.class)
    public RespResult violationException(BindException exception) {
        // 不带任何参数访问接口,会抛出 BindException
        // 因此,我们只需捕获这个异常,并返回我们设置的 message 即可
        String message = exception.getAllErrors().get(0).getDefaultMessage();
        return new RespResult(400, message);
    }
}

接下来接口调用就变得非常简单了,加个**@Validated注解**就行了, if/else也灰飞烟灭了:

@RestController
public class LoginController {

    @ApiOperation(value = "登录接口")
    @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
    public RespResult login(@Validated LoginReqDTO loginReqDTO) {
        return new RespResult(200, "成功"); 
    }
}

最后,我们用空的请求参数使用Postman访问下登录接口:localhost:8080/login;结果为:

{
    "code": 400,
    "message": "手机号不能为空",
    "data": null
}

是不是感觉很爽,SAO代码( if/else)也没了。

而且,这样一来,假如以后我想扩充条件,只需要去“请求参数对象中添加一个**@NotBlank(message = “XXX”)注解**”即可,而不是去改以前的代码,这岂不很稳!
example:

@NotBlank(message = "XXX不能为空")
    private String XXX;
![](https://pic4.zhimg.com/80/v2-eb8eb9b4707890f23302811533b6e058_720w.jpg)

稳是稳了,但是,假如请求参数不是对象怎么办?

比如,稍微老一点的项目请求参数可能是非实体,那此时的 if/else 是怎么样被 KO 的呢 !

别慌!此时我们只需要简单改造下GlobalExceptionHandler——全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ConstraintViolationException.class})
    public RespResult violationException(Exception exception) {
        if (exception instanceof ConstraintViolationException) { //使用关键字instanceof 判断 ConstraintViolationException 是否为 Exception 直接或间接子类
            return constraintViolationException((ConstraintViolationException) exception); //调用下面方法,返回结果
        }
        return new RespResult(500, "server error"); // 否则跑出 server error
    }

    // 当我们没有此方法,空参访问localhost:8080/login 会抛出ConstraintViolationException 异常
    public RespResult constraintViolationException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) { //判断是否为空
            StringBuilder sb = new StringBuilder();
            for (ConstraintViolation constraintViolation : constraintViolations) { //遍历 ConstraintViolation
                sb.append(constraintViolation.getMessage()).append(","); // 吧错误信息循环放到sb中, 并以逗号隔开
            }
            String errorMessage = sb.toString(); // 获得异常信息字符串
            return new RespResult(400, errorMessage);
        }
        return new RespResult(500, "server error"); // 否则跑出 server error
    }
}

接下来,我们只需要在请求参数前加上“@NotBlank”注解,并且在接口所在类加上“@Validated” 即可,if/else 同样被 KO 了

@Validated  // 此注解别忘了
@RestController
public class LoginController {

    @ApiOperation(value = "登录接口")
    @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
    public RespResult login(@NotBlank(message = "手机号不能为空") String mobile,
                            @NotBlank(message = "密码不能为空") String password,
                            @NotBlank(message = "验证码不能为空") String code) {
        return new RespResult(200, "成功");
    }
}

我们再次用空的请求参数使用Postman访问下登录接口:localhost:8080/login;结果为:

{
    "code": 400,
    "message": "验证码不能为空,密码不能为空,手机号不能为空,",
    "data": null
}

不难发现,把请求参数中所有为空的参数,都验证出来了,它不香吗!

![](https://picb.zhimg.com/80/v2-713507b13f25be42d038043d70587cf7_720w.jpg)

然并卵,实际开发中,并非 3 + 2 - 5 * 0 这么简单

假如有需求,校验请求“手机号格式是否正确”,怎么办!别慌,自定义注解 登场

有自定义注解为啥不用

首先,我们要自定义注解,肯定的先了解注解的本质。
在「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』。

The common interface extended by all
annotation types
译:所有的注解类型都继承自这个普通的接口(Annotation)

这句话有点抽象,但却说出了注解的本质。
来来来,我们随便看一个JDK内置注解的定义,比如咋们常用的:@Override

@Target(ElementType.METHOD) //注解放置的目标位置
@Retention(RetentionPolicy.SOURCE) //注解在哪个阶段执行
public @interface Override {	//注解
}

不难看出,这是注解 @Override 的定义,其实它本质上就是:

public interface Override extends Annotation{ //继承 Annotation
}    

你没有看错,注解的本质就是一个继承了 Annotation 接口的接口
小编有幸在书上看到这样一端描述:

  • 一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如

注解小编就浅聊到这里了,咋们言归正传,回到使用自定义注解 KO if/else ,也别让 if/else 久等了。

首先,自定一个参数验证注解:@ValidParam

/**
 * Create By CodeCow on 2020/7/21.
 * 自定义注解
 * @Target:注解放置的目标位置
 * @Retention:注解在哪个阶段执行
 * @Constraint:指定此注解的实现, 即:验证器(就是下面的:ParamValidator 类)
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ParamValidator.class)
public @interface ValidParam {

    String message() default "手机号格式不正确";  // 校验的失败的时候返回的信息,可以指定默认值

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

接下来我们 编写一个校验器 ,验证该注解

/**
 * Create By CodeCow on 2020/7/21.
 * 编写校验器 验证该上面我们自定义的注解:@ValidParam
 * 我们需要 实现 ConstraintValidator 接口 并且重写 isValid 方法
 */
public class ParamValidator implements ConstraintValidator<ValidParam, String> {

    @Override
    public void initialize(ValidParam constraintAnnotation) {
        // 初始化
    }

    @Override
    public boolean isValid(String param, ConstraintValidatorContext constraintValidatorContext) {
        // 开始验证
        // 手机号格式(正则语法)
        String mobileFormat = "^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|" +  
                "(18[0-9])|(19[8,9])|(166))[0-9]{8}$";
        Pattern pattern = Pattern.compile(mobileFormat);
        return pattern.matcher(param).matches(); // 手机号格式正确返回 true,否则 false
    }
}

最后,我们修改登录接口,在请求参数前,加上我们自定义的注解(@ValidParam)即可:
同样,请求参数的校验,只需一个注解, if/else就被 KO 了

@RestController
public class LoginController {

    @ApiOperation(value = "登录接口")
    @PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
    public RespResult login(@ValidParam String mobile) {
        return new RespResult(200, "成功");
    }
}

最后,我们再次以空参数使用Postman访问下登录接口:localhost:8080/login;结果为:

{
    "code": 400,
    "message": "手机号格式不正确,",
    "data": null
}

后记

好啦,今就先聊到这里吧,本文仅仅是抛砖引玉而已,使用了现阶段大家比较“钟情的 if/else” 打了个样,不是说用 if/else 不好,只是希望大家在以后的编码中,不要滥用,代码不要太过“冗余”。

其次,在真实项目中,业务场景不可能像上面那么简单,也就像小编前文所提(实际开发,并非 3 + 2 - 5 * 0 这么简单),所以在您握住鼠标的那一刻,还是的多思考一番,考虑这样写是否合理,是否具有扩展性。

关注我,了解更多知识。