SpringBoot如何优雅的进行参数校验

一、传统参数校验

虽然往事不堪回首,但还是得回忆一下我们传统参数校验的痛点。

下面是我们传统校验用户名和邮箱是否合法的代码

if (username == null || username.isEmpty()) {
    throw new IllegalArgumentException("用户名不能为空");
}
 
if (isValidEmail(email)) {
    throw new IllegalArgumentException("邮箱格式不正确");
}
 
public boolean isValidEmail(String email) {
    String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
    Pattern pattern = Pattern.compile(emailRegex);
    Matcher matcher = pattern.matcher(email);
    return matcher.matches();
}

这样的代码不仅冗长,而且难以维护,尤其是在多个地方重复使用时,容易出错。

面对上面的痛点,我们就得解放双手,利用框架来完成校验。

它只需要通过简单的注解来定义校验规则,让框架来帮助我们处理校验逻辑,让我们代码变得更加的优雅。

二、几个名词

问题①:JSR是什么?

JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解。

我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!

问题②:Bean Validation是什么?

Bean Validation是一个抽象的框架,它定义了验证规则,而不会涉及具体的业务逻辑

问题③:Hibernate Validator是什么?

Bean Validation的实现,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现

三、所需依赖

Spring boot 2.3以前版本,Springbootspring-boot-starter-web默认内置了Hibernate-Validator

这些版本直接引入spring-boot-starter-web即可,后面的版本需要单独引入

在后面的测试中会用到lombokSpringBootwebtest等基础依赖,这里就不一一给出

四、注解及作用

看到这些注解后,大家可能会对【@NotNul@NotEmpty@NotBlank】这三个注解有点不理解,这里稍作解释

  • @NotNull:任何对象的value不能为null。

  • @NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null。

  • @NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0。

五、快速入门

5.1 新增加一个一个User 实体类
@Data
public class User {
    //姓名
    @NotBlank(message = "用户名不能为空")  //注解确保姓名不为空
    private  String name;
 
    //性别
    @NotBlank(message = "性别不能为空")   //注解确保性别不为空
    private String sex;
 
    //年龄
    @NotNull(message = "年龄不能为空")  //注解确保年龄不为空
    @Max(value = 120,message = "年龄不能大于120")  //注解确保年龄必须小于等于120
    @Min(value = 18,message = "年龄不能小于18")   //注解确保年龄必须大于等于18
    private  Integer age;
 
    //邮箱
    @Email(message = "邮箱格式不正确")    //注解确保邮箱格式正确
    @NotBlank(message = "邮箱不能为空")
    private String email;
}

上述代码说明:

  • @NotBlank: 此注解确保字符串不为空并且不能为空字符串,且去掉前后空格后的长度必须大于 0。它常用于字符串字段验证。message 属性用于指定提示信息;

  • @NotNull: 此注解确保整数类型不能为 null

  • @Min 和 @Max: 这两个注解用于验证数字值是否在指定的范围内。例如,在上面的示例中,我们想要确保 age 的值在 18 到 120 之间;

  • @Email: 此注解用于验证字符串值是否是有效的电子邮件地址格式。

5.2 Controller层参数校验

下图是controller层校验流程

Controller层校验流程

@RestController
public class ValidatorController {
    //测试参数校验
    @RequestMapping("/testValidator")
    public ResponseEntity<String> testValidator(@Valid @RequestBody User  user, BindingResult bindingResult){
 
        // 是否存在校验错误
        if (bindingResult.hasErrors()) {
            // 获取校验不通过字段的提示信息
            String errorMsg = bindingResult.getFieldErrors()
                    .stream()
                    .map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(", "));
 
            return ResponseEntity.badRequest().body(errorMsg);
        }
        return ResponseEntity.ok("参数校验成功");
    }
 
}

解释一下上面代码:

  • @Validated: 告诉 Spring 需要对 User 对象执行校验; 这个一定不要忘记加上

  • BindingResult : 该类包含校验不通过时的异常信息,校验不通过时,我们通过这个对象来获取注解中message="xxx"中的内容

注意:当注解校验不通过时,直接将异常信息返回给前端其实并不友好,我们可以将异常包装一下再丢给前端

5.3 测试校验结果

这里我们使用postman工具测试一下参数校验是否成功

① 入参正确情况

{
    "name":"小凡",
    "sex":"男",
    "age":18,
    "email":"xiezhr@qq.com"
}

入参正确

入参不正确的情况

{
    "name":null,
    "sex":"",
    "age":17,
    "email":"xiezhrqq.com"
}

入参不正确

通过上面的入门小案例,你学会了么?

上面的返回结果看起来可能不是那么优雅,那么怎么封装统一返回结果呢,

传送门在此优雅的封装返回结果

六、单个参数校验

上面快速入门中我们说了实体参数校验,这小节,我们来看看单个参数的校验

6.1 controller层校验代码

@RequestMapping("/testSingleParmaValidator")
public ResponseEntity<String> testSingleParmaValidator(@NotBlank(message = "姓名不能为空") String name,
                                                       @Min(value = 18,message = "年龄不能小于18")
                                                       @Max(value = 120,message = "年龄不能大于120") Integer age
 
                                                      ){
 
    // 参数校验
    return ResponseEntity.ok("参数校验成功");
}

6.2 全局异常捕获

当参数校验不通过会发生如下异常信息

异常信息

这里我们不能像上面一样通过BindingResult 来获取异常信息,需要添加全局异常捕获校验失败异常,具体代码如下

@RestControllerAdvice
public class GlobalExceptionHandler {
    //处理ValidationException异常
    @ExceptionHandler(ValidationException.class)
    //返回状态码为400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<String> handleValidationExceptions(ValidationException  ex) {
        String  message = "";
        //判断异常类型
        if(ex instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) ex;
            //获取验证不通过的信息
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            //遍历验证不通过的信息
            for (ConstraintViolation<?> item : violations) {
                //将验证不通过的信息拼接到message中
                message+=item.getMessage()+",";
            }
        }
        //返回错误信息
        return ResponseEntity.badRequest().body(message);
    }
 
}

6.3 测试校验结果

入参正确情况

http://localhost:8080/testSingleParmaValidator?name=小凡&age=18

入参正确的情况

入参不正确情况

http://localhost:8080/testSingleParmaValidator?name=&age=17

入参不正确情况

八、参数校验分组

在实际开发中,我们会遇到这样的情况:同一个实体类可能会在多个接口中使用,但每次的校验场景又不一样。

例如:新增用户和修改用户接口,参数都是User 实体,在新增用户的时候ID字段 可以为空,但name字段 不能为空

在修改用户的是由ID字段不能为空,这种时候就可以使用参数分组来实现。

8.1 定义验证分组接口

定义两个分组接口CreateUserGroup(用户创建组),UpdateUserGroup(用户更新组),

分别继承javax.validation.groups.Default,标识不同的业务场景

public interface CreateUserGroup extends Default {
}
 
public interface UpdateUserGroup extends Default {
}

注:继承Default并不是必须的。只是说,如果继承了Default,那么@Validated(value = Create.class)的校验范畴就为【Create】和【Default】;如果没继承Default,那么@Validated(value = Create.class)的校验范畴只为【Create】,而@Validated(value = {Create.class, Default.class})的校验范畴才为【Create】和【Default】

8.2 分组校验的使用

① 在实体中添加groups 属性

@Data
public class User {
 
    //用户ID
    @NotNull(message = "用户ID不能为空",groups = UpdateUserGroup.class)  //用户更新接口必须传递用户ID
    private Integer id;
 
    //姓名
    @NotBlank(message = "用户名不能为空",groups = CreateUserGroup.class)  //用户创建接口必须传递用户名
    private  String name;
 
    //性别
    @NotBlank(message = "性别不能为空")   //注解确保性别不为空
    private String sex;
 
    //年龄
    @NotNull(message = "年龄不能为空")  //注解确保年龄不为空
    @Max(value = 120,message = "年龄不能大于120")  //注解确保年龄必须小于等于120
    @Min(value = 18,message = "年龄不能小于18")   //注解确保年龄必须大于等于18
    private  Integer age;
 
    //邮箱
    @Email(message = "邮箱格式不正确")    //注解确保邮箱格式正确
    @NotBlank(message = "邮箱不能为空")
    private String email;
}

②在接口中使用分组

使用 @Validated 注解,并指定要执行的验证组。

//添加用户
@PostMapping("/addUser")
public ResponseEntity<User> addUser(@Validated(value= CreateUserGroup.class) @RequestBody User user){
 
    return ResponseEntity.ok(user);
}
//更新用户
@PutMapping("/updateUser")
public ResponseEntity<User> updateUserUser(@Validated(value= UpdateUserGroup.class) @RequestBody User user){
 
    return ResponseEntity.ok(user);
}

我们指定create接口指定CreateUserGroup分组,update接口指定UpdateUserGroup

8.3 测试一下接口

接口入参

{
    "name":"小凡",
    "sex":"男",
    "age":18,
    "email":"xiezhr@qq.com"
}

addUser接口添加用户,不需要id,验证通过

添加用户

updateUser接口修改用户,需要传入id,校验不通过

修改用户

九、嵌套对象校验

9.1 构造一个员工信息表
@Data
public class Emp {
    @NotBlank(message = "员工编号不能为空")
    private  String empNo;
    @NotBlank(message = "员工姓名不能为空")
    private  String empName;
    @NotBlank(message = "员工职位不能为空")
    private  String job;
    @Valid                  //这里必须使用@Valid注解
    private  Dept dept;
}

@Data
public class Dept {
    @NotBlank(message = "部门编号不能为空")
    private String  deptNo;
    @NotBlank(message = "部门名称不能为空")
    private String  deptName;
}

在这个示例中, Dept 类包含三个字段需要校验: deptNo 和``deptName字段,通过在Dept类中的每个字段上添加相应的校验注解,然后在Emp类中的dept字段上添加@Valid` 注解,可以实现对嵌套对象中多个字段进行参数校验。

9.2 嵌套对象的使用
@PostMapping("/emp")
public ResponseEntity<String> createOrder(@Valid @RequestBody Emp emp) {
    return ResponseEntity.ok("参数校验成功");
}

9.3 测试一下

① 正确入参情况

{
    "empNo":"10001",
    "empName":"小凡",
    "job":"程序员",
    "dept":{
        "deptNo":"20001",
        "deptName":"研发部111"
    }
}

正确入参情况

② 不正确入参情况

{
    "empNo":"10001",
    "empName":"",
    "job":"程序员",
    "dept":{
        "deptNo":"20001",
        "deptName":""
    }
}

不正确入参情况

十、自定义参数校验

SpringBoot 提供的注解校验功能可以满足大多数的验证需求,但如果在系统中需要实现一些特殊的校验功能时,

我们可以根据规则自定义校验

下面我们来手把手教你自定义一个字符串校验,校验字符串必须为大写或小写

10.1 自定义注解类

我们要自定义验证功能,需要首先自定义注解,以便我们在实体类中使用它,代码如下

①定义一个枚举类 CaseMode :

public enum CaseMode {
    UPPER,
    LOWER;
}

②创建一个自定义的校验注解 @CheckCase

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
public @interface CheckCase {
    String message() default "字符串必须是大写或小写";
 
    Class<?>[] groups() default {};
 
    Class<? extends Payload>[] payload() default {};
 
    CaseMode value();
}

10.2 自定义验证业务逻辑类
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
 
    private CaseMode caseMode;
 
    @Override
    public void initialize(CheckCase constraintAnnotation) {
        // 获取约束注解的值
        this.caseMode = constraintAnnotation.value();
    }
 
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 如果值为空,则返回true
        if (value == null) {
            return true;
        }
 
        // 根据caseMode的值,判断value是否需要转换大小写
        if (caseMode == CaseMode.UPPER) {
            return value.equals(value.toUpperCase());
        } else {
            return value.equals(value.toLowerCase());
        }
    }
}

10.3 自定义校验注解使用

①在Car实体类上添加注解

@Data
public class Car {
    //车牌号
    @CheckCase(value = CaseMode.UPPER,message = "车牌号必须为大写")
    private  String brand;
    //颜色
    @CheckCase(value = CaseMode.LOWER,message = "颜色必须为小写")
    private  String color;
}

②在controller 中校验参数

@GetMapping ("/car")
public ResponseEntity<String> validatorCar(@Valid @RequestBody Car car) {
    return ResponseEntity.ok("参数校验成功");
}

10.4 测试一下

①入参正确情况

{
    "brand":"云A.888888",
    "color":"red"
}

入参正确情况

②入参错误情况

{
    "brand":"云a.888888",
    "color":"RED"
}

入参错误情况

文章转载自:xiezhr

原文链接:https://www.cnblogs.com/xiezhr/p/18093602

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

如何使用PHP和RabbitMQ实现延迟队列(方式一)?

前言 今天我们来做个小试验&#xff0c;用PHP和RabbitMQ实现消息队列的延迟功能。 前期准备&#xff0c;需要安装好docker、docker-compose的运行环境。 需要安装RabbitMQ的可以看下面这篇文章。 如何使用PHP和RabbitMQ实现消息队列&#xff1f;-CSDN博客 一、安装RabbitM…

抖音视频关键词批量采集工具|无水印视频爬虫提取软件

抖音视频关键词批量采集工具&#xff1a; 我们很高兴地介绍最新推出的抖音视频关键词批量采集工具&#xff0c;该工具集成了多项强大功能&#xff0c;让您轻松实现视频内容的批量提取和下载。以下是详细的功能解析和操作说明&#xff1a; 主要功能&#xff1a; 关键词批量提取…

【GIS前沿技术】推荐几款高大上的在线地图,地理学者的福音!

文章目录 一、https://zoom.earth/二、https://www.ventusky.com/三、https://earth.nullschool.net四、https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer?fjsapi 一、https://zoom.earth/ 卫星&#xff1a; 雷达&#xff1a; 降水&#xff…

[深度学习]yolov8+pyqt5搭建精美界面GUI设计源码实现五

【简单介绍】 依托先进的目标检测算法YOLOv8与灵活的PyQt5界面开发框架&#xff0c;我们倾力打造出了一款集直观、易用与功能强大于一体的目标检测GUI界面软件。通过深度融合YOLOv8在目标识别领域的出色性能与PyQt5的精美界面设计&#xff0c;我们成功推出了一款高效且稳定的软…

Element UI中日期选择日(date-picker)等其他选择器下拉显示错位、位置错误解决

省流版 给选择器加上唯一key&#xff08;下面的想看就看&#xff09; 问题复现 需求是用一个下拉切换时间维度的选择&#xff0c;分别为年度、季度、月度&#xff0c;但是开发的时候发现&#xff0c;当切换的时候&#xff0c;视图可正常切换&#xff0c;但点击选择时却发现选…

数据分析面试题(41~50)

41、lstm的原理、lstm和rnn的区别 ①LSTM是一种常用于处理序列数据的循环神经网络&#xff08;RNN&#xff09;架构&#xff0c;特别适用于长序列的建模。其主要特点是通过门控机制来控制信息的流动&#xff0c;从而有效地解决了传统RNN在处理长序列时的梯度消失或爆炸的问题。…

Linux(centos7)部署hadoop集群

部署环境要求:已完成JDK环境部署、配置完成固定IP、SSH免费登录、防火墙关闭等。 1、下载、上传主机 官网:https://hadoop.apache.org 2、解压缩、创建软连接 解压: tar -zxvf hadoop-3.3.6.tar.gz软连接: ln -s /usr/local/apps/hadoop-3.3.6 hadoop3、文件配置 hadoo…

MQTT协议介绍

基本概念 MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。 基本设计思想是轻巧、开放、简单、规范&#xff0c;易于实现。 这些特点使得它对很多场景来说都是很好的选择&#xff0c;特别是对于受限的环境如机器与机器的通信&#xff08;M2M&#xff09;以及物联网…

设计模式之抽象工厂模式精讲

概念&#xff1a;为创建一组相关或相互依赖的对象提供一个接口&#xff0c;而且无须指定他们的具体类。 抽象工厂模式是工厂方法模式的升级版本。在存在多个业务品种或分类时&#xff0c;抽象工厂模式是一种更好的解决方式。 抽象工厂模式的UML类图如下&#xff1a; 可以看…

基于XGBoost和数据预处理的电动汽车车型预测

基于XGBoost和数据预处理的电动汽车车型预测 文章目录 基于XGBoost和数据预处理的电动汽车车型预测1、前言2、导入数据3、各县电动汽车采用情况条形图4、电动车类型饼图5、前5最欢迎的电动车制造商6、XGBoost模型6.1 字符串列的标识6.2 删除不相关的列6.3 编码分类变量6.4 电动…

大数据分析案例-基于决策树算法构建大学毕业生薪资预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

渐变色x轴换行柱状图

// 系统上云率const optionBar {title: {text: 系统上云率,left: left,textStyle: {color: "#fff",fontSize: 14,fontWeight: 650,align: "center",},},color: [#32C5FF, #00F766, #EECB5F],grid: {top: 40,bottom: 0,},legend: { // 控制图例组件show: …

C语言中其他运算符介绍

除了算术运算符和位运算符外&#xff0c;C语言还提供了一些其他类型的运算符&#xff0c;包括逗号运算符、条件运算符、sizeof运算符、指针运算符等。这些运算符在C语言中具有特定的功能和用途&#xff0c;对于编写复杂的程序和实现各种算法非常有用。本文将深入介绍C语言中的这…

是德科技keysight DSOX3104A示波器

181/2461/8938产品概述&#xff1a; Keysight(原Agilent) InfiniiVision DSOX3104A 的价位较低&#xff0c;能够在满足您苛刻预算要求的情况下提供卓越性能&#xff0c;以及可选功能。是德(原安捷伦)突破性技术可以在相同的预算条件下提供更多更出色的示波器功能 Keysight(原A…

Spring Boot 整合分布式搜索引擎 Elastic Search 实现 自动补全功能

文章目录 ⛄引言一、分词器⛅拼音分词器⚡自定义分词器 二、自动补全查询三、自动补全⌚业务需求⏰实现酒店搜索自动补全 四、效果图⛵小结 ⛄引言 本文参考黑马 分布式Elastic search Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;…

zabbix监控vmware esxi

一、zabbix服务端配置 修改zabbix_server.conf配置文件 vim /etc/zabbix/zabbix_server.conf #######zabbix_server.conf底部第二行开始添加下面配置######## StartVMwareCollectors5 #StartVMwareCollectors - 预先启动Vmware collector收集器实例的数量。此值取决于要监控的…

深入了解 Postman 请求头的使用方法

当你在使用 Postman 发送请求时&#xff0c;请求头&#xff08;Headers&#xff09;是你可以包含在 HTTP 请求中的重要部分之一。请求头包含了关于请求的元数据信息&#xff0c;这些信息对于服务器来处理请求是非常重要的。下面是一份详细的图文介绍&#xff0c;说明了如何在 P…

算法(6)KMP+trie

KMP&#xff1a; 最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili 该视频使用python书写代码&#xff0c;不会python的小伙伴也可以看看了解kmp的大致思路。 问题描述&#xff1a; kmp&#xff1a;字符串匹配算法&#xff0c;用来找一个长字符串中出现了几次小字符串&#xf…

ubuntu中使用docker对配置文件进行挂载

目录 1.什么是挂载&#xff1f; 2.挂载的好处 3.挂载的方法 4.运行 5.查看 1.什么是挂载&#xff1f; 挂载通常指的是使操作系统能够访问到文件系统的过程。当一个文件系统被挂载到一个目录&#xff08;称为挂载点&#xff09;后&#xff0c;从该目录及其子目录下就可以访…

游戏本续航@控制中心的省电模式效果如何

文章目录 节能模式长续航模式&#x1f47a;相关工具 节能模式长续航模式&#x1f47a; 蓝天模具Control Center中的模式 根据我的试验,以及软件的提示,可以发现 Power Saving是最省电的,儿Quiet模式并不省电,它会启用独立显卡,只不过风扇的转速不像娱乐模式和性能模式那么积极而…