参数校验这样写,就不会被劝退了。(代码片段)

author author     2022-12-11     399

关键词:

背景

最近端午好久没有和二胖聚一聚了,于是约了二胖到人民广场去宰他一顿,正好最近他跳槽加薪了。<br/>
:二胖听说你最近跳槽了,并且还是从传统软件公司跳到了互联网公司,工资是不是涨了一点啊,今天你请客哈。<br/>
二胖:别说了,工资是涨了点,但是性价比反而变低了,以前到点就下班,现在下班到家都快12点了。<br/>
:新公司怎么样还适应吗?除了上班时间久点。<br/>
二胖:哎,这个还真稍微有点不适应,这不是刚进去没啥事,leader就给我安排了一个简单的用户保存功能(参数校验),原来以前公司个把小时就做好了的功能,在这新公司硬是折腾了两三天,真是苦不堪言。我改了好几个版本最终leader才满意的点了点头。
技术图片

接口裸奔

  • 按照二胖在以前公司的写法再传统公司反正系统都是服务内部人员的,在后端写参数校验是不存在的事情,完全信赖前端传过来的内容。这不写完代码自测一把发现可以保存数据,就屁颠屁颠的发起代码review了(二胖在以前的公司代码review是不存在的,只要功能实现就好了)。正好leader今天有点时间,看到新同事提交的代码看看写的怎么样。 看着这个裸奔的接口,leader把二胖叫了过去,语重心长的跟二胖说道:"你这个参数校验不写写吗?不怕人家***你的接口吗?这里不校验,直接用,不怕引入sql注入吗?这里不校验下邮箱是否符合格式吗?这个判空也不写,不怕大量的空指针,服务熔断吗?..."。面对leader的拼命十三问,二胖心想试用期怕是有点难过哦?只能低着头回到工位重新按照leader的教育整改起来,然后又重新提交了。

    参数校验if判断

    leader看了看说到:“这次代码比上次好多了,功能基本没啥问题了,但是这一块代码是不是可以在优化下,这样写不是很优雅”

    if(Objects.isNull(user))
            throw new IllegalArgumentException("用户不能为空");
        
        if(StringUtils.isEmpty(user.getUserName()))
            throw new IllegalArgumentException("用户名不能为空");
        
        if(StringUtils.isEmpty(user.getUserName()))
            throw new IllegalArgumentException("用户名不能为空");
        
        if(StringUtils.isEmpty(user.getSex()))
            throw new IllegalArgumentException("用户性别不能为空");
        
        if(Objects.isNull(user.getUserDetail()))
            throw new IllegalArgumentException("用户详细信息不能为空");
        
        if(Objects.isNull(user.getUserDetail().getAddress()))
            throw new IllegalArgumentException("用户地址不能为空");
        
        if(!"M".equals(user.getSex()) && !"F".equals(user.getSex()))
            throw new IllegalArgumentException("用户性别不合法");
        

    二胖也是一阵郁闷,还是怀念以前的公司啊,功能实现就好,代码想怎么写就怎么写。互联网公司就是规矩多,写完代码还要写单测,还要监控一堆破事,活该这群人996.时间都花到这上面去了。抱怨该抱怨但是代码还得改啊。现在疫情期间好不容易找一个工作不能丢啊。
    二狗想到以前不是学过aop吗?再配合下自定义注解,这样代码就应该比较优雅了吧,说干就干。

    自定义注解实现

  • 首先自定义了一个注解因为要校验参数
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD,ElementType.METHOD)
    public @interface ParameterValidator 


- 配置一个切面,解析有`ParameterValidator`注解的方法。
然后通过切面获取所有请求的参数,获取参数之后就解析参数上面的注解。配置切面啥的都比较简单,稍微复杂的就是反射解析参数了,因为要涉及到请求参数的嵌套结构。二胖习惯性的面向百度编程能`copy`别人的代码坚决不去自己写。百度出来的基本上都是单层结构,简单基本类型的对象,没有涉及到是嵌套、级联的类型的情趣参数。最后在`github`(**全球最大的同性交友网站**)找了一圈也没有找到合适的。既然拿来主义没有结果那就只能哼次哼次的自己写了,幸好自己以前学过点反射的知识。花了一个小时通过递归调用写了个粗糙的版本,比较粗糙还有很多场景没有考虑进去。不过基本可以满足条件了部分代码如下:

```java
 public static void checkField(Object object, Class<?> aClass) throws IllegalAccessException 
        boolean primitive = isPrimitive(aClass);
        if (primitive) 
            return;
        
        Field[] declaredFields = filterField(aClass.getDeclaredFields());
        for (Field field : declaredFields) 
            makeAccessible(field);
            // 校验我们自定义注解
            MyNotBlank fieldAnnotation = field.getAnnotation(MyNotBlank.class);
            Object currentObject = field.get(object);
            if (Objects.nonNull(fieldAnnotation)) 
                if (StringUtils.isEmpty(currentObject)) 
                    throw  new IllegalArgumentException(field.getName()+":"+fieldAnnotation.message());
                
            
            if (!isJavaClass(field.getType())) 
                // 递归调用,有级联参数时候
                checkField(currentObject);
             else if (field.getType().isPrimitive()) 

             else if (field.getType().isAssignableFrom(List.class)) 
                // 递归调用,解析list类型
                getList(field, currentObject);
            
        

    

然后赶紧测试一波,还不错基本功能实现了,能够实现判空检验了,也可以实现级联校验了。效果如下:
技术图片
不过这个现在支持类型为基本类型和StringList
后续如果参数类型是数组、或者Map等等还得去解析。 这时候同事二狗从旁边走过,看到二胖这么认真的在敲代码。<br/>
二狗:二胖你又在写什么bug啊。<br/>
二胖:在自己造个轮子,写个通用的参数校验。<br/>
二狗:这个现在市面上不是已经有现成的方案了吗?jsr(Java Specification Requests)可以去了解下哦。<br/>
二胖:好的我马上去查下资料。

jsr(Java Specification Requests) Java 规范提案

  • 说到jsr我们就得先了解下什么是JCP(Java Community Process)

    JCP(Java Community Process) 是一个开放的国际组织,主要由Java开发者以及被授权者组成,职能是发展和更新。

  • JSR又是个什么东东列?

    它是指向JCP提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,(如果你觉得自己牛逼你也可以提交一个)
    以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

    Bean Validation

    Bean Validation 顾名思义是对 java Bean 的校验,目前为止,Java 对 Bean 的校验有3个规范。

  • JSR-303 : Bean Validation
  • JSR 349 : Bean Validation 1.1
  • JSR 380 : Bean Validation 2.0

    Hibernate-Validator

    Hibernate ValidatorBean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint

    代码实现

  • 如果项目的框架是 spring boot 的话,在 spring-boot-starter-web 中已经包含了 Hibernate-validator 的依赖(版本必须是2.3之前)。2.3以后的版本 spring-boot-starter-web已经去除了这个依赖,需要手动引入 Hibernate-validator依赖,详细内容见官网描述

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
  • springboot项目的话直接引入
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
    </dependency>

    代码演示:
    方法前面这个注解@Valid是必须的,否则不生效哦。

    @PostMapping(value = "/save2")
    @ResponseBody
    public ResultViewModel save2(@Valid @RequestBody User user)
        boolean saveUser = saveUser(user);
        if (saveUser) 
            return ResultViewModelUtil.success();
        
        return ResultViewModelUtil.error();
    

    实体类上标上需要校验的规则注解就好了。

//被注释的元素,值必须是一个字符串,不能为null,且调用trim()后,长度必须大于0
@NotBlank(message = "")

//被注释的元素,值不能为null,但可以为"空",用于基本数据类型的非空校验上,而且被其标注的字段可以使用 @size、@Max、@Min 等对字段数值进行大小的控制
@NotNull(message = "")

//被注释的的元素,值不能为null,且长度必须大于0,一般用在集合类上面
@NotEmpty(message = "")

//被注释的元素必须符合指定的正则表达式。
@Pattern(regexp = "", message = "")

//被注释的元素的大小必须在指定的范围内。
@Size(min =, max =)

//被注释的元素,值必须是一个数字,且值必须大于等于指定的最小值
@Min(value = long以内的值, message = "")

//被注释的元素,值必须是一个数字,且值必须小于等于指定的最大值
@Max(value = long以内的值, message = "")

//被注释的元素,值必须是一个数字,其值必须大于等于指定的最小值
@DecimalMin(value = 可以是小数, message = "")

//被注释的元素,值必须是一个数字,其值必须小于等于指定的最大值
@DecimalMax(value = 可以是小数, message = "")

//被注释的元素,值必须为null
@Null(message = "")

//被注释的元素必须是一个数字,其值必须在可接受的范围内
@Digits(integer =, fraction =)

//被注释的元素,值必须为true
@AssertTrue(message = "")

//被注释的元素,值必须为false
@AssertFalse(message = "")

//被注释的元素必须是一个过去的日期
@Past(message = "")

//被注释的元素必须是一个将来的日期
@Future(message = "")

//被注释的元素必须是电子邮件地址
@Email(regexp = "", message = "")
//被注释的元素必须在合适的范围内
@Range(min =, max =, message = "")

//被注释的字符串的大小必须在指定的范围内
@Length(min =, max =, message = "")

唯一需要注意的点就是如果是级联校验的话需要在最外层加上@Validbr/>为什么需要在校验的上一次标上`@Valid`这个注解,里面的校验才会生效列?有知道的或者感兴趣的可以去看看源码给我留言哦。
技术图片
然后在配置一个全局的异常捕获器就好了,由于篇幅原因代码就不贴了,代码上传到了github上。
校验结果:
技术图片

总结

参考
http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted(`官网中文版本贴心吧`)
https://docs.jboss.org/hibernate/validator/6.1/reference/en-US/html_single/#validator-gettingstarted
https://juejin.im/post/5dd8d44c518825734e4cda22
https://www.cnblogs.com/mr-yang-localhost/p/7812038.html


从零开始写博客系统——权限校验(代码片段)

...理。那么基于上面的原理,其实只要在接口字段新增一个参数,这个参数可以任意定义,比如除了常规参数之外,额外增加一个参数叫abcdefg,abcdefg这个参数的值必须是uhgiuhgwohgo,否则就不是本人请求的,服务器拒绝。这么做最... 查看详情

从零开始写博客系统——权限校验(代码片段)

...理。那么基于上面的原理,其实只要在接口字段新增一个参数,这个参数可以任意定义,比如除了常规参数之外,额外增加一个参数叫abcdefg,abcdefg这个参数的值必须是uhgiuhgwohgo,否则就不是本人请求的,服务器拒绝。这么做最... 查看详情

后端参数校验器v1.0(调用一个方法校验所有参数并得到校验结果,且包括错误原因)(代码片段)

一:介绍在写后端时,面对多个参数,比如手机号码、密码等我们常常需要写验证逻辑,当需要验证的参数较多的时候我们会需要写很多的判断语句,这就造成了大量的代码冗余。因此我开发了一套参数验证器,只需要调用参数... 查看详情

mysql这样写update语句,劝退(代码片段)

...9;13245' and owner_name='张三';执行之前的记录是这样的:执行之后的记录是这样的:搜索公众号互联网架构师复“2T”,送你 查看详情

写一个springboot的helloworld(代码片段)

...来特别累,作为入门还是学学Spring吧,然而被Spring的配置劝退了(现在看好像也不是问题,只是没有那种经验,碰到就懵逼),现在改用SpringBoot了,并且学习牛客网上的小demo作为入门参 查看详情

接口安全处理(代码片段)

...们该如何保证接口安全呢?二、接口安全的几种方式数据参数合法性校验接口数据的安全性保证,还需要我们的系统,有个数据合法性校验,简单来说就是参数校验,比如身份证长度,手机号长度,是否是数字等等token授权认证方... 查看详情

java中的参数校验(代码片段)

Java中的参数校验参数校验规则需要进行参数校验不需要进行参数校验JSR303基本概念基本使用@NotBlank统一异常处理JSR303注解@Valid和@Validated@Valid和@Validated比较@Valid高级使用@Valid级联校验@Validated高级使用@V... 查看详情

java中的参数校验(代码片段)

Java中的参数校验参数校验规则需要进行参数校验不需要进行参数校验JSR303基本概念基本使用@NotBlank统一异常处理JSR303注解@Valid和@Validated@Valid和@Validated比较@Valid高级使用@Valid级联校验@Validated高级使用@V... 查看详情

从零开始写博客系统——权限校验(代码片段)

背景如果读者按照前面的文章认真的从头到尾码了代码并且能正常运行,那么其实已经是一个简易的博客系统了,但是这个简易的博客系统还有最后一个问题,那就是我们的新增,修改接口是不能被人随便调用的,否则别人任意... 查看详情

工作3年,还不会写单元测试?新技能get!(代码片段)

...比如调整代码顺序导致bug,取反操作逻辑丢失,参数校验逻辑被误改等。上线前需要花大量时间进行测试和灰度验证。在此过程最大的感受就是:一切没有单测覆盖的重构都是裸奔。经历了没有单测痛苦磨难&#x 查看详情

mysql这样写update语句,劝退

来源:ju.outofmemory.cn/entry/336774最近好几次有开发同学在钉钉上问我,比如下图:问题归纳起来就是:在MySQL里面update一条记录,语法都正确的,但记录并没有被更新...刚遇到这个问题的时候,我拿到这... 查看详情

mysql这样写update语句,劝退

来源:ju.outofmemory.cn/entry/336774最近好几次有开发同学在钉钉上问我,比如下图:问题归纳起来就是:在MySQL里面update一条记录,语法都正确的,但记录并没有被更新...刚遇到这个问题的时候,我拿到这... 查看详情

华为云技术分享刚填了一个奇怪的坑(代码片段)

...,比如一个API,超过一段时间(比如10s)之后,用同样的参数再请求就会被服务器禁掉,无法获取正常数据,这样可以保证数据的安全。怎么增加时效性控制呢?加一个时间的信息就好了,我们可以把时间信息包含在一个APIURL的... 查看详情

程序员新人周一优化一行代码,周三被劝退?

...家一点点参考和启发。相信大家看完后就明白为什么不能这样优化@Transactional注解了,纯属画蛇添足和乱用。事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说MySQL。只要记住这一点,理解... 查看详情

“天天嚷嚷要裸辞,被劝退后傻眼了”(代码片段)

说在前面:友友们晚上好呀,以前见面大家打招呼都是吃了没,现在见面—阳了没?现在,都在谈“羊”,似乎是短短的一段时间内,大家已经不再谈“羊”色变了。而是默默接受,迎接着“羊”&#... 查看详情

unittest参数化(代码片段)

我们在写case的时候,如果用例的操作是一样的,就是参数不同,比如说要测一个登陆的接口,要测正常登陆的、黑名单用户登陆的、账号密码错误的等等,在unittest里面就要写多个case来测试。这样的情况只是调用接口的时候参... 查看详情

flask学习-44.flask-restx请求参数校验reqparse.requestparser()(代码片段)

...希望继续这样做,它不会很快消失。reqparse解析请求参数这是请求解析器的一个简 查看详情

jvm参数校验利器:jacoline

背景最近JDK升级了。原有的JVM调优参数不知道是不是很合适,这是个问题。如果参数设置错误,可能导致启动不了或者性能下降的情况出现。解决方案JaCoLine-InspectyourJavacommandline这个网站可以对你的参数进行校验。可以根... 查看详情