化繁为简,使用Hibernate Validator实现参数校验

news2025/1/22 17:04:37

前言

        在之前的悦享校园的开发中使用了SSM框架,由于当时并没有使用参数参数校验工具,方法的入参判断使用了大量的if else语句,代码十分臃肿,因此最近在重构代码时,将框架改为SpringBoot后,引入了Hibernate Validator校验工具对参数进行优雅校验(SSM同样可用)。本文将通过实例来演示如何使用该框架。

环境配置

JDK 1.8

Spring Boot 2.7.12

导入依赖

        <!--SpringBoot 2.3开始,校验包单独组件,需要手动引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

基础校验

在正式开始之前,需要先对以下的内容作以了解,方便后续的学习。

校验注解

Hibernate Validator,提供了丰富的校验注解来供我们使用,这里列举一些比较常用的:

注解解释
@Null

验证对象是否为空

@NotNull

验证对象是否为非空

@NotBlank

用于String对象,验证字符串不为空且trim()后长度大于0

@Length

验证对象的长度是否符合

@Size

验证对象长度是否在给定的范围之内(用于Array,Collection,Map,String)

@Min

入参对象的值不能小于该值(用于Number和String)

@Max

入参对象的值不能大于该值(用于Number和String)

@Digits

验证number和string的构成是否合法

@Past

验证date和calendar对象是否在当前时间之前

@Future

验证date和calendar对象是否在当前时间之后

@Pattern

验证是否符合正则表达式规则

@Email验证邮箱
@Positive

验证输入的对象是否非负数

@Range

验证输入的对象的值是否在指定范围内

参数绑定

@PathVariable

该注解用于接收具有Restful风格的参数,如/api/v1/1001,最终userId的值为1001

如下代码中,使用name属性可以指定GetMapping中的id名称与之对应,从而可以自定义参数名称userId,而不是使用默认名称id

@GetMapping("/v1/{id}")
public void getMsg(@PathVariable(name = "id") Integer userId){}

@RequestParam

该注解用于接收查询参数,如/api/v1/product?user="123",则user的值为123。该注解也可用于接收form-data类型的数据。

当在参数前使用@RequireParam时,当请求该方法时,对应的参数必须存在,否则会引发异常,可使用@RequireParam(required = false)指明该参数非必须,该注解在入参为null时可提供默认值。

@GetMapping("/v1/product")
public void getMsg(@RequestParam String user){}

@RequestBody

该注解用于接收JSON格式的数据,如请求为{"name":123,"age":18},需要有对应的实体类作为映射。

@PostMapping("/v1/user")
public void getMsg(@RequestBody User user){}

@Validated

该注解可以作用于类、方法、参数上,用于开启参数校验。

@Valid

该注解可以作用于方法、参数、字段、构造器上,同样可以用于参数校验。

单参校验

将@Validated注解用于类上,可以不用在每个方法的参数上重复开启校验,使用前面提的基础校验即可对参数作约束。 

校验方法

@RestController
@RequestMapping("/v1/hello")
@Slf4j
@Validated
public class HelloController {

    /**
     * GetMapping 请求
     * @param name 用户名称
     * @param uid  用户uid
     * @return
     */
    @GetMapping("/{id}")
    public ResultDataVO getMsg(@RequestParam String name,
                               @Max(value = 10,message = "最大值不能超过10")
                               @PathVariable(name = "id") int uid) {
        log.info("访问hello下的msg方法。");
        String result = "Hello,"+name+" id "+uid;

        return ResultDataVO.success(result);
    }
}

请求参数​​​​​​​ 响应结果

 由于我们没有给String参数赋值,因此默认为空字符串。但由于在uid上使用了@Max注解,因此当入参为100时便会出现异常,而返回的JSON格式为全局的响应处理,在后续的文章会提到。

对象校验

当我们在使用中,往往会遇到多个参数的情况,由于get请求对参数长度有限制,且参数会拼接在URL中, 因此对于此场景我们一般使用对象的方式来接收参数,而对于我们的参数也将传入也将用JSON格式。

由于使用JSON格式传参,将使用@RequestBody注解,除此之外还要使用@Validated或@Valid注解,关于两者的选择,只需要记住分组校验使用@Validated,否则二者均可。

即使在类上添加了@Validated注解,此处仍然需要以上两个注解之一,否则参数校验将无效。

校验方法

@RestController
@RequestMapping("/v1/hello")
@Slf4j
@Validated
public class HelloController {

    /**
     * 对象校验示例
     * @param user
     * @return 
     */
    @PostMapping("/msg")
    public ResultDataVO(@RequestBody @Valid UserDTO user){
        return ResultDataVO.success(user.toString());
    }
}

UserDTO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {

    /**
     * id
     */
    @Min(value = 1,message = "id值不合法")
    @Positive
    private Integer id;
    /**
     * 昵称
     */
    @NotBlank(message = "用户名不能为空")
    @Length(min = 4,max=10,message = "用户名必须在4-10个字符之间")
    private String username;
    /**
     * 年龄
     */
    @Range(min = 1,max=120,message = "年龄不在合法范围内")
    @NotNull(message = "age不能为空")
    private Integer age;
}

上述代码中,参数为UserDTO对象,其中包含三个待校验的字段,由于未涉及到分组校验,因此校验方法中参数前使用@Valid注解(@Validated注解亦可),注意入参的字段名称需要和UserDTO中的名称一致。

请求参数

 响应结果

分组校验

假设有这样的场景,在用户登录和修改密码中,两者分别需要如下参数,登录时需要用户名、密码,而修改时需要密码、新密码,为了方便管理,只使用了一个DTO类来存放上述的字段,此时由于这些参数需要在不同的场景下校验,而加上基础校验的注解会导致所有参数均被校验,面对此问题,我们将使用分组校验来解决。

可以看到如何代码中,均是使用了@Validate注解,且注解中含有一个分组接口,使用该接口可以帮助我们告知程序该对那些参数进行校验。

校验方法

@RestController
@Slf4j
@RequestMapping("/local/auth")
@Validated
public class LocalAuthController {

    @Autowired
    private LocalAuthService localAuthService;

    /**
     * 用户登录接口
     * @param localAuthDTO 账号对象
     * @return
     */
    @PostMapping("/login")
    LocalAuthVO loginCheck(@RequestBody @Validated(AuthCheckSequence.class) LocalAuthDTO localAuthDTO){
        return localAuthService.userLoginCheck(localAuthDTO);

    }

    /**
     * 用户修改密码接口
     * @param localAuthDTO 账号对象
     * @return
     */
    @PutMapping("/password")
    ResultDataVO userChangePassword(@RequestBody
                                    @Validated(AuthChangeSequence.class)
                                    LocalAuthDTO localAuthDTO){
        return localAuthService.userModifyPassword(localAuthDTO);
    }
}

分组接口

AuthChangeSequence 

/**
 * 分组校验,用户登录
 */

public interface AuthCheckSequence {

}

AuthCheckSequence 

/**
 * 分组校验,密码修改
 */
public interface AuthChangeSequence {

}

LocalAuthDTO 

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LocalAuthDTO {

    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空",groups = AuthCheckSequence.class)
    @Pattern(regexp = "^[a-zA-Z0-9]{3,8}$", message = "用户名必须由3-8个字母或数字组成",
    groups = AuthCheckSequence.UsernameCheck.class)
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空",groups = AuthCheckSequence.class)
    @Size(min = 6,max = 18, message = "密码长度必须在6到18位之间",groups = AuthCheckSequence.PasswordCheck.class)
    private String password;

    /**
     * 新密码,修改密码时使用
     */
    @NotBlank(message = "新密码不能为空",groups = AuthChangeSequence.class)
    @Size(min = 6,max = 18, message = "新密码长度必须在6到18位之间",groups = AuthChangeSequence.ChangePassword.class)
    private String newPassword;
}

请求参数

响应结果

从上图所示请求结果可以看出,使用@Validated注解+分组校验接口后,将只对该参数中标明了对应分组接口的字段进行校验,并不会对全部参数进行校验。 ​​​​​​​

顺序校验

由于用户人数增多,我们将在对用户进行分组,将在LocalAuthDTO中引入新的字段,而我们所期望的是能够按照用户类型,用户名,密码的顺序来校验,而不是像之前对象校验最终显示的结果那样,随机进行校验并返回结果,因此需要使用在分组校验的基础上做一些改进,使其可以按照我们所指定的顺序执行校验,以前面登录为例:

校验方法

@RestController
@Slf4j
@RequestMapping("/local/auth")
@Validated
public class LocalAuthController {

    @Autowired
    private LocalAuthService localAuthService;

    /**
     * 用户登录接口
     * @param localAuthDTO 账号对象
     * @return
     */
    @PostMapping("/login")
    LocalAuthVO loginCheck(@RequestBody @Validated(AuthCheckSequence.class) LocalAuthDTO localAuthDTO){
        return localAuthService.userLoginCheck(localAuthDTO);

    }
}

 AuthCheckSequence

通过在该分组接口中新建接口,并使用@GroupSequence注解中参数的顺序来实现校验参数的顺序。如下将标识使用AuthCheckSequence分组的参数将按照用户类型、用户名​​​​​​​、密码的顺序进行校验。

@GroupSequence({AuthCheckSequence.UserTypeCheck.class, AuthCheckSequence.UsernameCheck.class, AuthCheckSequence.PasswordCheck.class})
public interface AuthCheckSequence {

    /**
     * 用户类型
     */
    interface UserTypeCheck {};

    /**
     * 用户名校验
     */
    interface UsernameCheck {};

    /**
     * 密码校验
     */
    interface PasswordCheck {};
}

LocalAuthDTO

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LocalAuthDTO {

     /**
     * 用户类型 1.用户 2.商家 3.管理员,目前仅支持用户和商家类型注册
     */
    @NotNull(message = "用户类型不能为空",groups = AuthCheckSequence.UserTypeCheck.class)
    @Range(min = 1,max = 3,message = "用户类型不合法",groups =         
 AuthCheckSequence.UserTypeCheck.class)
    private Integer userType;

    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空",groups = AuthCheckSequence.UsernameCheck.class)
    @Pattern(regexp = "^[a-zA-Z0-9]{3,8}$", message = "用户名必须由3-8个字母或数字组成",
    groups = AuthCheckSequence.UsernameCheck.class)
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空",groups = AuthCheckSequence.PasswordCheck.class)
    @Size(min = 6,max = 18, message = "密码长度必须在6到18位之间",groups = AuthCheckSequence.PasswordCheck.class)
    private String password;
}

请求参数

 响应结果

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/883541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HCIP MPLS实验

MPLS实验 拓扑MPLS VPN配置PE与PE间建立MP-BPG邻居关系R7访问R2,R3,R4环回 拓扑 MPLS VPN配置 首先可以用OSPF协议将R2,R3,R4跑通&#xff0c;然后配置MPLS [R2]mpls lsr-id 2.2.2.2 定义MPLS的router-id&#xff0c;要为本地设备的真实ip地址&#xff0c;且邻居可达&#…

机器学习算法之-逻辑回归(2)

为什么需要逻辑回归 拟合效果太好 特征与标签之间的线性关系极强的数据&#xff0c;比如金融领域中的 信用卡欺诈&#xff0c;评分卡制作&#xff0c;电商中的营销预测等等相关的数据&#xff0c;都是逻辑回归的强项。虽然现在有了梯度提升树GDBT&#xff0c;比逻辑回归效果更…

【学习FreeRTOS】第8章——FreeRTOS列表和列表项

1.列表和列表项的简介 列表是 FreeRTOS 中的一个数据结构&#xff0c;概念上和链表有点类似&#xff0c;列表被用来跟踪 FreeRTOS中的任务。列表项就是存放在列表中的项目。 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS 中的列表是一个双向环形链表列表的…

【大数据之Kafka】三、Kafka生产者之消息发送流程及同步异步发送API

将外部传送给过来的数据发送到kafka集群。 1 发送原理 &#xff08;1&#xff09;创建main()线程&#xff0c;创建producer对象&#xff0c;调用send方法&#xff0c;经过拦截器&#xff08;可选&#xff09;、序列化器、分区器。 &#xff08;2&#xff09;分区器将数据发送…

java面试题(16):Mysql一致性视图是啥时候建立的

1 演示错误案例 先给大家来一个错误演示。 我们打开两个会话窗口&#xff0c;默认情况下隔离级别是可重复读&#xff0c;我们来看下&#xff1a; 首先在 A 会话中查看当前 user 表&#xff0c;查看完成后开启事务&#xff1a; 可以看到id3的数据sex是男。 接下来在 B 会话中…

场景入门12----关卡切换和流送

在游戏时往往需要切换关卡&#xff0c;有两种方法&#xff0c;关卡切换和推送。关卡切换的方法是进入了一个新的地图&#xff0c;这时人物的值都复原了&#xff0c;一般都是在一个地图上推送关卡。 关卡切换 首先&#xff0c;文件新建一个新关卡&#xff0c;命名。找到之前制…

地址解析协议-ARP

ARP协议 无论网络层使用何种协议&#xff0c;在实际网络的链路上传输数据帧时&#xff0c;最终必须使用硬件地址 地址解析协议&#xff08;Address Resolution Protocol&#xff0c;ARP&#xff09;&#xff1a;完成IP地址到MAC地址的映射&#xff0c;每个主机都有一个ARP高速缓…

C语言刷题训练DAY.4

1.计算体重指数 解题思路&#xff1a; 这里我们只需要按照他的要求写出公式。 注意&#xff1a;身高要换算成米&#xff0c;打印的结构是个浮点数&#xff0c;打印的格式要相对应 解题代码&#xff1a; #include<stdio.h> int main() {int weight 0;int height 0;…

Python自动化小技巧18——自动化资产月报(word设置字体表格样式,查找替换文字)

案例背景 每月都要写各种月报&#xff0c;经营管理月报&#xff0c;资产月报.....这些报告文字目标都是高度相似的&#xff0c;只是需要替换为每个月的实际数据就行&#xff0c;如下&#xff1a; (打码是怕信息泄露.....) 可以看到&#xff0c;这个报告的都是高度模板化&…

浏览器 - 事件循环机制详解

目录 1&#xff0c;浏览器进程模型进程线程浏览器的进程和线程1&#xff0c;浏览器进程2&#xff0c;网络进程3&#xff0c;渲染进程 2&#xff0c;渲染主线程事件循环异步同步 JS 为什么会阻塞渲染任务优先级 3&#xff0c;常见面试题1&#xff0c;如何理解 js 的异步2&#x…

时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 基于KNN K近邻的时间序列预测-递归预测未来(多指标评价) …

数据集成与流动优化:解锁企业数据的无限潜力

在当今数字化时代&#xff0c;企业拥有海量数据&#xff0c;这些数据散落在不同部门、系统和平台之间&#xff0c;形成了所谓的“数据孤岛”。要想实现数据的最大化价值&#xff0c;就必须解决数据集成与流动的挑战。本文将深入探讨数据集成与流动优化的重要性&#xff0c;以及…

833-字符串中查找与替换

题目描述&#xff1a; 你会得到一个字符串 s (索引从 0 开始)&#xff0c;你必须对它执行 k 个替换操作。替换操作以三个长度均为 k 的并行数组给出&#xff1a;indices, sources, targets。 要完成第 i 个替换操作: 检查 子字符串 sources[i] 是否出现在 原字符串 s 的索…

POSTGRESQL 关于安装中自动启动的问题 详解

开头还是介绍一下群&#xff0c;如果感兴趣Polardb ,mongodb ,MySQL ,Postgresql ,redis &#xff0c;SQL SERVER ,ORACLE,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加 liuaustin3微信号 &…

使用SpringBoot + Thymeleaf 完成简单的用户登录

&#x1f600;前言 本篇博文是关于Thymeleaf 的综合案例&#xff0c; 使用SpringBoot Thymeleaf 完成简单的用户登录-列表功能&#xff0c;希望你能够喜欢&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨…

数据结构--最短路径 Floyd算法

数据结构–最短路径 Floyd算法 F l o y d 算法&#xff1a;求出每⼀对顶点之间的最短路径 \color{red}Floyd算法&#xff1a;求出每⼀对顶点之间的最短路径 Floyd算法&#xff1a;求出每⼀对顶点之间的最短路径 使⽤动态规划思想&#xff0c;将问题的求解分为多个阶段 对于n个顶…

re学习(31)BUUCTF-xx(多层加密)

参考文章&#xff1a;【BUUCTF逆向 [2019红帽杯]xx】_nb_What_DG的博客-CSDN博客 re学习笔记&#xff08;26&#xff09;BUUCTF-re-[2019红帽杯]xx_Forgo7ten的博客-CSDN博客 还有B站 水番正文 IDA64位载入 shiftF12查看字符串 交叉引用找到关键代码 使用findcrypt插件找到…

H13-922题库 HCIP-GaussDB-OLAP V1.5

**H13-922 V1.5 GaussDB(DWS) OLAP题库 华为认证GaussDB OLAP数据库高级工程师HCIP-GaussDB-OLAP V1.0自2019年10月18日起&#xff0c;正式在中国区发布。当前版本V1.5 考试前提&#xff1a; 掌握基本的数据库基础知识、掌握数据仓库运维的基础知识、掌握基本Linux运维知识、…

互联网发展历程:速度与效率,交换机的登场

互联网的演进就像一场追求速度与效率的竞赛&#xff0c;每一次的技术升级都为我们带来更快、更高效的网络体验。然而&#xff0c;在网络的初期阶段&#xff0c;人们面临着数据传输速度不够快的问题。一项关键的技术应运而生&#xff0c;那就是“交换机”。 速度不足的困境&…

SpringBoot + Mybatis多数据源

一、配置文件 spring: # datasource: # username: root # password: 123456 # url: jdbc:mysql://127.0.0.1:3306/jun01?characterEncodingutf-8&serverTimezoneUTC # driver-class-name: com.mysql.cj.jdbc.Driverdatasource:# 数据源1onedata:jdbc-url: j…