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

简介: 各位可能会有疑问,为什么不使用 `@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"
    }
}
响应:
[银行名称]为必传字段
  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
2月前
通过反射获取方法返回的类型
通过反射获取方法返回的类型
14 1
|
8月前
|
Java 数据库连接 Spring
JavaWeb优雅实现接口参数校验
JavaWeb优雅实现接口参数校验
71 0
|
8月前
|
Serverless 开发工具
ABAP 方法调用的参数传递里,通过引用传递的方式,能修改原始参数值吗?
ABAP 方法调用的参数传递里,通过引用传递的方式,能修改原始参数值吗?
89 0
ABAP 方法调用的参数传递里,通过引用传递的方式,能修改原始参数值吗?
|
4月前
|
存储 C#
C# 方法详解:定义、调用、参数、默认值、返回值、命名参数、方法重载全解析
方法是一段代码,只有在调用时才会运行。 您可以将数据(称为参数)传递给方法。 方法用于执行某些操作,也被称为函数。 为什么使用方法?为了重用代码:定义一次代码,然后多次使用。
44 0
|
10月前
|
Java 数据安全/隐私保护
使用反射实现@RequestBody的参数校验功能
springboot中对实体类参数中属性进行校验一般都是使用javax.validation中提供的注解
接口参数注解验证案例
写作缘由 写接口的时候经常会有请求体里某字段不为null的需求;也有使用一个dto对象,但是插入和修改都想使用这个dto,那这样的话判断条件就不一样,因为修改操作必须有ID,所以参数验证还是挺麻烦的。所以写个demo记录一下,亲测可用。
117 0
|
小程序 前端开发 数据库
小程序__01--后端返回类型是一个object具体类,前端小程序如何提取类中的私有变量
后端返回类型是一个object具体类,前端小程序如何提取类中的私有变量
|
前端开发
GoFrame数据校验之校验结果 | Error接口对象
这篇总结分享:GoFrame数据校验的另外一个知识点:校验结果相关的总结分享。
134 0
GoFrame数据校验之校验对象 | 校验结构体
这篇文章将会为大家介绍GoFrame数据校验中校验对象的知识点,包括:Validator对象常用方法的介绍、单数据校验、校验Map、校验结构体的示例
135 0
GoFrame数据校验之校验对象 | 校验结构体
在请求中传入不定长参数实现不同的逻辑处理的解决办法
在请求中传入不定长参数实现不同的逻辑处理的解决办法