如何使用反射进行参数效验

简介: 各位可能会有疑问,为什么不使用 `@Valid `注解呢!各位兄弟我也想用啊!但是没办法啊!项目性质导致的。项目会对接各种渠道方,但是所有渠道方都是用同一个实体进行传递的(通用性),但是呢,每个渠道方对字段必传的效验又不一样(用户是上帝)

前言

为什么会有这种功能出现呢!

各位可能会有疑问,为什么不使用 @Valid 注解呢!各位兄弟我也想用啊!但是没办法啊!项目性质导致的。项目会对接各种渠道方,但是所有渠道方都是用同一个实体进行传递的(通用性),但是呢,每个渠道方对字段必传的效验又不一样(用户是上帝)。比如公用实体里有 a、b、c 属性。AA 渠道方只传递 a、b 属性,c 属性没办法给,那我们对 AA 渠道只能给 a、b 属性做必传效验。然后 BB 渠道方给 b、c 属性,那我们对 BB 渠道只能给 b、c 属性做必传效验。也不可能用 if/else一个一个去判断的,字段属性一多,会凉的。基于这种场景就出现了使用 反射 做效验。

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

@Slf4j
public class CheckUtil {

    /**
     * 效验属性类型为 Object
     * @param sourceObject 源对象
     * @param checkFields 需要效验的字段集合
     * @param sourceObject
     * @param checkFields
     * @return
     */
    public static String checkObjNull(Object sourceObject, List<String> checkFields){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        Class<?> objClass = sourceObject.getClass();
        // 获得本类所有属性对象
        Field[] declaredFields = objClass.getDeclaredFields();
        if(null == declaredFields || declaredFields.length == 0){
            return "源对象中没有属性";
        }

        StringBuilder sb = new StringBuilder();
        for (String checkField : checkFields) {
            for (Field field : declaredFields) {
                //关闭程序的安全检测
                field.setAccessible(true);
                // 效验字段与 属性名称相等 则进行效验
                String fieldName = field.getName();
                if(checkField.equals(fieldName)){
                    try {
                        // 获得字段属性值
                        Object fieldValue = field.get(sourceObject);
                        // 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
                        if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
                            sb = sb.append("[").
                                    append(fieldName).
                                    append("]").
                                    append("为必传字段");
                            return sb.toString();
                        }
                    } catch (IllegalAccessException e) {
                       log.error("获取属性值异常",e);
                       return "请检查传递的源对象";
                    }
                }
            }
        }
        // 通过效验
        return null;
    }

    /**
     * 效验属性类型为 List
     * @param sourceObject 源对象
     * @param checkFields 需要效验的字段集合
     * @param listPropertyName 源对象的属性名称
     * @return
     */
    public static String checkArrayNull(Object sourceObject, List<String> checkFields,String listPropertyName){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        if(sourceObject instanceof List){
          List list = (List) sourceObject;
            if(CollectionUtils.isEmpty(list)){
                return listPropertyName + "为必传字段";
            }
            for (Object obj : list) {
                String resultStr = checkObjNull(obj, checkFields);
                return resultStr;
            }
        }
        return null;
    }
}

好了,工具类已经编写好了。那怎么进行使用呢!

使用

导入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

DTO

创建三个类,分别是 UserInfoDTOBankCardDTOIdCardDTO

  • UserInfoDTO:用户信息
@Data
@ApiModel("用户信息")
public class UserInfoDTO {

    @ApiModelProperty("用户唯一号")
    private Long userId;

    @ApiModelProperty("姓名")
    private String name;

    @ApiModelProperty("银行卡列表信息")
    private List<BankCardDTO> cardDTOList;

    @ApiModelProperty("身份证信息")
    private IdCardDTO idCardDTO;
}
  • BankCardDTO:银行卡列表信息
@Data
@ApiModel("银行卡列表")
public class BankCardDTO {

    @ApiModelProperty("卡号")
    private String cardNumber;

    @ApiModelProperty("银行名称")
    private String bankName;

}
  • IdCardDTO:身份证信息
@Data
@ApiModel("身份证信息")
public class IdCardDTO {

    @ApiModelProperty("证件号")
    private String idNumber;
    @ApiModelProperty("有效期")
    private String validity;
}

Controller

然后再创建一个名为TestController的类,提供一个saveUser方法,方法中调用编写的工具类。

 @PostMapping(value = "/saveUser")
    private String saveUser(@RequestBody UserInfoDTO req){
        //效验 用户信息
        String check = CheckUtil.checkObjNull(req, Arrays.asList("userId", "name", "cardDTOList", "idCardDTO"));
        if(StringUtils.hasLength(check)){
            return check;
        }
        //效验 用户信息内的 银行卡列表
        List<BankCardDTO> cardDTOList = req.getCardDTOList();
        String check2 = CheckUtil.checkArrayNull(cardDTOList, Arrays.asList("cardNumber", "bankName"),"cardDTOList");
        if(StringUtils.hasLength(check2)){
            return check2;
        }
        //效验 用户信息内的 身份证信息
        IdCardDTO idCardDTO = req.getIdCardDTO();
        String check3 = CheckUtil.checkObjNull(idCardDTO, Arrays.asList("idNumber", "validity"));
        if(StringUtils.hasLength(check3)){
            return check3;
        }
        return "成功";
    }

调用方法

请求地址:localhost:8080/api/saveUser
{
    "userId": "1",
    "name": "1",
    "cardDTOList": [
        {
            "cardNumber": "5",
            "bankName": ""
        }
    ],
    "idCardDTO": {
        "idNumber": "55",
        "validity": "4"
    }
}
响应:
[bankName]为必传字段

在我们项目组这效果以及足够使用了。因为不直接对接前端,我们只需要将响应给对接方就好了。有些小伙伴可能会直接对接前端,将响应信息展示在前端了。那肯定不行,用户怎么知道 [bankName]为必传字段这是个啥!肯定是需要提示中文的,例如:[银行名称]为必传字段

提示信息优化

如果要提示中文呢!也是有办法的。各位小伙伴可能注意到了,我导入了swagger的依赖,其中有个注解为 @ApiModelProperty,这注解写在了每个属性上,为每个属性提供了描述信息,所以我们可以取这个注解的值。

public static String checkObjNull(Object sourceObject, List<String> checkFields){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        Class<?> objClass = sourceObject.getClass();
        // 获得本类所有属性对象
        Field[] declaredFields = objClass.getDeclaredFields();
        if(null == declaredFields || declaredFields.length == 0){
            return "源对象中没有属性";
        }

        StringBuilder sb = new StringBuilder();
        for (String checkField : checkFields) {
            for (Field field : declaredFields) {
                //关闭程序的安全检测
                field.setAccessible(true);
                // 效验字段与 属性名称相等 则进行效验
                String fieldName = field.getName();
                // 获得指定注解
                ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
                // 获得 value属性 的值
                String value = apiModelProperty.value();
                if(checkField.equals(fieldName)){
                    try {
                        // 获得字段属性值
                        Object fieldValue = field.get(sourceObject);
                        // 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
                        if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
                            sb = sb.append("[").
                                    append(value).
                                    append("]").
                                    append("为必传字段");
                            return sb.toString();
                        }
                    } catch (IllegalAccessException e) {
                        log.error("获取属性值异常",e);
                        return "请检查传递的源对象";
                    }
                }
            }
        }
        // 通过效验
        return null;
    }

再次调用方法

请求地址:localhost:8080/api/saveUser
{
    "userId": "1",
    "name": "1",
    "cardDTOList": [
        {
            "cardNumber": "5",
            "bankName": ""
        }
    ],
    "idCardDTO": {
        "idNumber": "55",
        "validity": "4"
    }
}
响应:
[银行名称]为必传字段
  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
7月前
通过反射获取方法返回的类型
通过反射获取方法返回的类型
|
6月前
|
Java Spring 容器
详解java参数校验之:顺序校验、自定义校验、分组校验(@Validated @GroupSequence)
详解java参数校验之:顺序校验、自定义校验、分组校验(@Validated @GroupSequence)
|
数据安全/隐私保护
fastadmin中写接口是时Validate规则验证自定义如何用
fastadmin中写接口是时Validate规则验证自定义如何用
236 0
|
负载均衡 前端开发 Java
Feign 踩坑指南 (接口返回泛型设置属性为null)
Feign 简介 Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
1936 0
Feign 踩坑指南 (接口返回泛型设置属性为null)
通过普通方式调用和反射方式调用以及关闭检测后,通过反射方式调用的性能对比
通过普通方式调用和反射方式调用以及关闭检测后,通过反射方式调用的性能对比
|
Java 数据安全/隐私保护
使用反射实现@RequestBody的参数校验功能
springboot中对实体类参数中属性进行校验一般都是使用javax.validation中提供的注解
Postman内置动态参数和自定义的动态参数以及断言方式
Postman内置动态参数和自定义的动态参数以及断言方式。每次请求均需手动修改参数时,使用动态参数:内置动态参数/自定义动态参数,解决上述问题
544 0
Postman内置动态参数和自定义的动态参数以及断言方式
接口参数注解验证案例
写作缘由 写接口的时候经常会有请求体里某字段不为null的需求;也有使用一个dto对象,但是插入和修改都想使用这个dto,那这样的话判断条件就不一样,因为修改操作必须有ID,所以参数验证还是挺麻烦的。所以写个demo记录一下,亲测可用。
142 0
|
小程序 前端开发 数据库
小程序__01--后端返回类型是一个object具体类,前端小程序如何提取类中的私有变量
后端返回类型是一个object具体类,前端小程序如何提取类中的私有变量
|
XML SQL JSON
接口自动化测试,一键快速校验接口返回值全部字段
在日常开展自动化测试工作时,为了保证接口测试的有效性,少不了要对接口返回的响应字段进行校验、断言等操作。当接口返回的字段数量本身就很少时,接口断言操作一般都很容易就能实现,但当接口的返回字段特别多,结构特别复杂时,例如响应字段数量达到了成百上千时,如何快速实现全部返回字段的校验?
580 0
接口自动化测试,一键快速校验接口返回值全部字段