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

简介: 各位可能会有疑问,为什么不使用 `@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"
    }
}
响应:
[银行名称]为必传字段
  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
算法
【Simulink】极值搜索控制 Extremum Seeking Control(无模型控制)
【Simulink】极值搜索控制 Extremum Seeking Control(无模型控制)
1000 0
|
XML 网络协议 安全
|
7月前
|
安全 数据安全/隐私保护
.RememberMe简介
RememberMe并非简单保存用户名密码,而是服务器端实现的持久登录机制。传统Session在关闭浏览器后失效,而RememberMe通过安全令牌等方式,使用户重新打开浏览器后仍保持登录状态,提升体验的同时需防范潜在安全风险。
|
机器学习/深度学习 人工智能 自然语言处理
《驾驭随机梯度下降:C++ 实现与优化算法全解析》
本文深入探讨了随机梯度下降(SGD)及其变种优化算法在C++中的实现与应用。从SGD的基础动力,到动量法、Adagrad、RMSProp、Adadelta及Adam等算法的优化升级,文章详细解析了各算法的工作原理、实现方法及其在图像识别、自然语言处理等领域的实践案例。强调了在C++实现中,内存管理、计算效率、超参数调优及代码可扩展性的关键考量,为开发高效、精准的人工智能应用提供了坚实保障。
551 33
|
存储 JSON 安全
从入门到精通:Python中的OAuth与JWT,打造无懈可击的认证体系🔒
【8月更文挑战第4天】构建现代Web和移动应用时,用户认证与授权至关重要。Python集成OAuth和JWT技术,能轻松实现安全认证。本文从OAuth基础入手,介绍如何使用`requests-oauthlib`库简化流程,再到JWT进阶应用,利用`PyJWT`库生成及验证令牌。最后,探讨如何结合两者,创建无缝认证体验。通过代码示例,由浅入深地引导读者掌握构建坚固应用认证体系的方法。
574 2
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
417 1
|
Java 文件存储 Spring
轻松实现Spring Boot与FastDFS的无缝整合
家人们啦!,上篇文章了,我们讲了如何使用docker-compose快速部署fastdfs,在今天的文章中,我将向大家介绍如何将Spring Boot与FastDFS进行无缝整合,以便高效地管理和操作文件存储。通过这个整合,你将能够轻松地在Spring Boot应用程序中实现文件的上传和下载等功能。让我们开始吧
999 0
轻松实现Spring Boot与FastDFS的无缝整合
|
NoSQL Linux Redis
在CentOS上安装和配置Redis
在CentOS上安装和配置Redis
3406 3
|
Web App开发 安全 前端开发
X-Frame-Options响应头防点击劫持
X-Frame-Options响应头防点击劫持
1299 6
|
算法 大数据 数据处理
【软件设计师备考 专题 】IO控制方式(中断系统、DMA、IO处理机方式)
【软件设计师备考 专题 】IO控制方式(中断系统、DMA、IO处理机方式)
816 1

热门文章

最新文章