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

前言

        在之前的悦享校园的开发中使用了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;
}

请求参数

 响应结果

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

57从零开始学Java之一文详解String字符串的底层实现原理

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在之前的两篇文章中&#xff0c;壹哥给大家介绍了String字符串及其常用的API方法、常用编码、正则表达…

第一讲:BeanFactory和ApplicationContext接口

BeanFactory和ApplicationContext接口 1. 什么是BeanFactory?2. BeanFactory能做什么&#xff1f;3.ApplicationContext对比BeanFactory的额外功能?3.1 MessageSource3.2 ResourcePatternResolver3.3 EnvironmentCapable3.4 ApplicationEventPublisher 4.总结 1. 什么是BeanF…

GuLi商城-前端基础Vue指令-单向绑定双向绑定

什么是指令? 指令 (Directives) 是带有 v- 前缀的特殊特性。 指令特性的预期值是:单个 JavaScript 表达式。 指令的职责是&#xff0c;当表达式的值改变时&#xff0c;将其产生的连带影响&#xff0c;响应式地作用于DOM 例如我们在入门案例中的 v-on&#xff0c;代表绑定事…

vscode + python

序 参考链接&#xff1a; 【教程】VScode中配置Python运行环境_哔哩哔哩_bilibili Python部分 Python Releases for Windows | Python.org vscode部分 Visual Studio Code - Code Editing. Redefined 一路next&#xff0c;全部勾上&#xff1a; 就可以了&#xff1a; 安装插…

盛元广通高校实验室开放预约与综合管理系统LIMS

系统概述&#xff1a; 高校实验室涉及到的课程、老师、学生多&#xff0c;管理起来费时费力&#xff0c;盛元广通高校实验室开放预约与综合管理系统LIMS提供简单易用的账号管理、实验室管理、课程管理、实验项目管理、实验时间设定&#xff1b;为学生提供简单易用的自主实验选…

设计模式篇---抽象工厂(包含优化)

文章目录 概念结构实例优化 概念 抽象工厂&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 工厂方法是有一个类型的产品&#xff0c;也就是只有一个产品的抽象类或接口&#xff0c;而抽象工厂相对于工厂方法来说&#xff0c;是有…

ORB-SLAM2学习笔记7之System主类和多线程

文章目录 0 引言1 整体框架1.1 整体流程 2 System主类2.1 成员函数2.2 成员变量 3 多线程3.1 ORB-SLAM2中的多线程3.2 加锁 0 引言 ORB-SLAM2是一种基于特征的视觉SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;系统&#xff0c;它能够从单个、双目或RBG…

8月16日上课内容 部署LVS-DR群集

本章结构&#xff1a; 数据包流向分析: 数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xf…

追踪工时和控制成本 如何选对工具

研发工作中的工时管理软件是一种用于追踪、记录和分析团队成员在项目中所花费的工作时间的工具。它有助于组织、监控和优化研发项目的进展&#xff0c;确保资源得到有效利用&#xff0c;项目按时完成&#xff0c;并提供数据支持用于决策制定和资源规划。 能够记录团队成员的工…

Visual Studio 2022 你必须知道的实用调试技巧

目录 1、什么是bug&#xff1f; 2.调试是什么&#xff1f;有多重要&#xff1f; 2.1我们是如何写代码的&#xff1f; 2.2又是如何排查出现的问题的呢&#xff1f; ​编辑 2.3 调试是什么&#xff1f; 2.4调试的基本步骤 2.5Debug和Release的介绍 3.Windows环境调试介绍…

[Vue]解决npm run dev报错node:internal/modules/cjs/loader:1031 throw err;

解决: 有2中方法&#xff0c;建议先尝试第一种&#xff0c;不行再第二种 第一种: 重新安装依赖环境 删除项目的node_modules文件夹&#xff0c;重新执行 # 安装依赖环境 npm install# 运行 npm run dev 我只用了第一种方法就可以了 &#xff0c;第二种方法从别的博主那看到…

QT中的按钮控件Buttons介绍

目录 Buttons 按钮控件 1、常用属性介绍 2、按钮介绍 2.1QPushButton 普通按钮 2.2QtoolButton 工具按钮 2.3Radio Button单选按钮 2.4CheckButton复选按钮 2.5Commam Link Button命令链接按钮 2.6Dialog Button Box命令链接按钮 Buttons 按钮控件 在Qt里&#xff0c;…

【JavaEE进阶】SpringMVC

文章目录 一. 简单认识SpringMVC1. 什么是SpringMVC?2. SpringMVC与MVC的关系 二. SpringMVC1. SpringMVC创建和连接2. SpringMVC的简单使用2.1 RequestMapping 注解介绍2.2 RequestMapping支持的请求类型2.3 GetMapping 和 PostMapping 3. 获取参数3.1 传递单个参数3.2 传递对…

【1-3章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第1章 大数据技术概述&#xff08;8节&#xff09; 第三次信息化浪潮&#xff1a;以物联网、云计算、大数据为标志 &#xff08;一&#xff09;大数据 大数据时代到来的原因…

如何编写一个通用的函数?

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 金句分享:…

Java 项目日志实例:Log4j2

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ Apache Log4j 2 是对 Log4j 的升级&#xff0c;与其前身 Log4j 1.x 相比有了显着的改进&#xff0c;并提供了许多 Logback 可用的改进&#xff0c;同时支持 JCL 以及 SLF4J…

Git:本地仓库创建和远程绑定

创建远程仓库 登录git网站&#xff0c;创建一个远程仓库 创建时可以选择仓库属性&#xff0c;公共/私有。仓库命名之类。创建完毕后可以在网站上看到仓库所在网址。 创建本地仓库 打开一个文件夹&#xff0c;鼠标右键Git Bash Here&#xff0c;打开git的命令行 git init//…

AWS security 培训笔记

云计算的好处 Amazon S3 (Storage) Amazon EC2 (Compute) 上图aws 的几个支柱&#xff1a;安全是其中一个啦 其中安全有几个方面 IAMdetection基础架构保护数据保护应急响应 关于云供应商的责任 data center 原来长这样 &#xff0c;据说非常之隐蔽的 如果有天退役了&#xf…

wustojc2010两小时学完C语言

#include <stdio.h> int main() {int a,b,c;scanf("%d%d%d",&a,&b,&c);printf("%d",a-b*c);return 0;}

selenium语法进阶+常用API

目录 浏览器操作 浏览器回退&#xff0c;前进 与刷新 浏览器窗口设置大小 浏览器设置宽高 浏览器窗口最大化 浏览器控制滚动条 信息打印 打印页面的标题和当前页面的URL 定位一组元素 鼠标和键盘事件 键盘 鼠标 下拉框操作 通过索引定位&#xff08;se…