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

463e545e5f8d004412a7e2847d802d77.jpg

文:答应我,别再if/else校验请求参数了可以吗
注:中国传统文化,先仔细看,若有用,再点赞, 给自己一点思考的时间
注:微信搜索:CodeCow,关注这个非常 SAO 的程序员

​哎!弹指之间

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

image

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

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

请求参数

@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 来判断是否会优雅一些呢?答案是:有锤子区别,毛区别都没有!

image

接下来简单讲几种改进方式,别再 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;
image

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

比如,稍微老一点的项目请求参数可能是非实体,那此时的 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
}

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

image

然并卵,实际开发中,并非 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 这么简单),所以在您握住鼠标的那一刻,还是的多思考一番,考虑这样写是否合理,是否具有扩展性。

好文推荐

image

小声BB

  • 中国传统文化,先仔细看,若有用,再点赞, 给自己一点思考的时间
  • 更多幽默、风趣好文,尽在“ CodeCow ” 程序牛公众号
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339