SpringBoot参数校验@Validated、@Valid

SpringBoot参数校验@Validated、@Valid(javax.validation)

一、应用场景

在实际开发中,前端校验并不安全,任何人都可以通过接口来调用我们的服务,就算加了一层token的校验,有心人总会转空子,来传各式各样错误的参数,如果后端不校验,导致数据库数据混乱、特别是关于金额的数据,可能一个接口把公司都给干倒了

二、原生参数校验

0、返回类(可以不用看)

/**
 * 用于返回
 * @param <T>
 */
@ApiModel("统一返回类")
public class Results<T> {
    public static final String ERROR = "500";
    public static final String SUCCESS = "200";

    /**
     * 返回码
     */
    @ApiModelProperty("返回码,正确码为:200")
    private String resCode ;

    /**
     * 返回消息
     */
    @ApiModelProperty("返回消息")
    private String msg ;

    /**
     * 返回实体
     */
    @ApiModelProperty("返回实体")
    private T obj;

    public static <T> Results<T> success(){
        return success(SUCCESS,"成功",null);
    }

    public static <T> Results<T> success(String msg){
        return success(SUCCESS,msg,null);
    }

    public static <T> Results<T> success(T obj){
        return success(SUCCESS,"成功",obj);
    }

    public static <T> Results<T> success(String msg,T obj){
        return success(SUCCESS,msg,obj);
    }

    public static <T> Results<T> success(String resCode,String msg,T obj){
        Results<T> result = new Results<T>();
        result.setResCode(resCode);
        result.setMsg(msg);
        result.setObj(obj);
        return result;
    }

    public static <T> Results<T> failed() {
        return failed(ERROR,"失败",null);
    }

    public static <T> Results<T> failed(String msg) {
        return failed(ERROR,msg,null);
    }

    public static <T> Results<T> failed(String msg,T obj) {
        return failed(ERROR,msg,obj);
    }

    public static <T> Results<T> failed(String resCode,String msg) {
        return failed(resCode,msg,null);
    }

    public static <T> Results<T> failed(Integer resCode,String msg) {
        return failed(String.valueOf(resCode),msg);
    }

    public static <T> Results<T> failed(String resCode,String msg,T obj) {
        Results<T> result = new Results<T>();
        result.setResCode(resCode);
        result.setMsg(msg);
        result.setObj(obj);
        return result;
    }

    public static <T> Results<T> failedNoPermission() {
        return failed(90005,"没有权限");
    }
    public static <T> Results<T> failedNoPermission(String msg) {
        return failed(90005,msg);
    }


    public static <T> Results<T> failedParameterException() {
        return failed(90004,"参数异常");
    }
    public static <T> Results<T> failedParameterException(String msg) {
        return failed(90004,msg);
    }

    public static <T> Results<T> failedLoginException() {
        return failed(90002,"登录失败");
    }
    public static <T> Results<T> failedLoginException(String msg) {
        return failed(90002,msg);
    }

    public String getResCode() {
        return resCode;
    }

    public void setResCode(String resCode) {
        this.resCode = resCode;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }

    @Override
    public String toString() {
        return "Results{" +
                "resCode='" + resCode + '\'' +
                ", msg='" + msg + '\'' +
                ", obj=" + obj +
                '}';
    }
}

1、实体类

@ApiModel("测试 validation 入参")
@Data
public class TestDto {
    @ApiModelProperty(value = "名字",required = true)
    private String name;
    @ApiModelProperty(value = "年龄",required = true)
    private Integer age;
    @ApiModelProperty(value = "爱好",required = true)
    private List<String> hobbies;
}

2、服务层(为了方便,我直接跟Controller写在一起了)

我们可以看见如果参数过大,要一个一个筛选条件十分浪费时间

@RestController
// lombok 的日志注解
@Slf4j
// swagger 的注解
@Api("测试")
public class TestController {

    @PostMapping("/testValidation")
	// swagger 的注解
    @ApiOperation("测试 validation")
    public Results testValidation(@RequestBody TestDto dto){
        try {
            log.info("test 入参 dto={}",dto);
            // 这要一个一个的塞,很浪费时间
            if (dto.getName() == null || "".equals(dto.getName().trim())){
                return Results.failed("名字不能为空");
            }
            if (dto.getAge() == null){
                return Results.failed("年龄不能为空");
            }
            if (dto.getHobbies() == null || dto.getHobbies().size() == 0){
                return Results.failed("爱好不能为空");
            }
            return Results.success();
        } catch (Exception e) {
            log.error("test 报错",e);
            return Results.failed();
        }
    }
}

三、使用 javax.validation 进行参数校验

1、导包

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

2、全局异常处理类

@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Results MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        BindingResult br = e.getBindingResult();
        if (br.hasFieldErrors()) {
            List<FieldError> fieldErrorList = br.getFieldErrors();
            List<String> errors = new ArrayList<>(fieldErrorList.size());
            for (FieldError error : fieldErrorList) {
                errors.add(error.getField() + ":" + error.getDefaultMessage());
            }
            // 然后提取错误提示信息进行返回
            return Results.failed(errors.toString());
        }
        // 然后提取错误提示信息进行返回
        return Results.failed("校验错误");
    }
 
}
如果不加这个全局处理类,只会给前端返回这样的参数

在这里插入图片描述

加上全局配置类

在这里插入图片描述

3、实体类

@ApiModel("测试 validation 入参")
@Data
public class TestDto {
    
    @ApiModelProperty(value = "名字",required = true)
    // 适用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null 且 trim() 之后 size > 0,必须有实际字符
    @NotBlank(message = "名字不能为空")
    private String name;
    
    @ApiModelProperty(value = "年龄",required = true)
    @NotNull(message = "年龄不能为空")
    // 适用于基本数据类型(Integer,Long,Double等等),当 @NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null(但是可以为 Empty)
    private Integer age;
    
    @ApiModelProperty(value = "爱好",required = true)
    // 适用于 String、Collection集合、Map、数组等等,加了@NotEmpty 注解的参数不能为 Null 或者 长度为 0
    @NotEmpty(message = "年龄不能为空")
    private List<String> hobbies;
}

4、服务层(为了方便,我直接跟Controller写在一起了)

必须要加上 @Valid 或者 @Validated,后续我会讲解这两个有什么不同,目前来说,都可以用,但推荐用 @Validated

@RestController
@Slf4j
@Api("测试")
public class TestController {

    @PostMapping("/testValidation")
    @ApiOperation("测试 validation")
    // 必须要加上 @Valid 或者 @Validated
    public Results testValidation(
        // 必须要加上 @Valid 或者 @Validated
        @Valid @RequestBody TestDto dto){
        try {
            log.info("test 入参 dto={}",dto);
            return Results.success();
        } catch (Exception e) {
            log.error("test 报错",e);
            return Results.failed();
        }
    }
}

6、测试

在这里插入图片描述

四、javax.validation 包下其它常用的校验注解:

这个颜色的是常用的

注解含义
@Null任何类型 必须为null
@NotBlank字符串、字符 字符类不能为null,且去掉空格之后长度大于
@NotNull任何类型 不能为null
@Length(min = 6, max = 8, message = “密码长度为6-8位。”)字符串的大小必须在指定的范围内
@NotEmpty适用于 String、Collection集合、Map、数组等等,加了@NotEmpty 注解的参数不能为 Null 或者 长度为 0
@AssertTrueBoolean、boolean 布尔属性必须是true
@AssertFalseBoolean、boolean 布尔属性必须是false
@Min(10)必须是一个数字,其值必须大于等于指定的最小值(我这填的是10)(整型)
@Max(10)必须是一个数字,其值必须小于等于指定的最大值(我这填的是10)(整型)
@DecimalMin(“10”)必须是一个数字,其值必须大于等于指定的最小值(我这填的是10)(字符串,可以是小数)
@DecimalMax(“10”)必须是一个数字,其值必须小于等于指定的最大值(我这填的是10)(字符串,可以是小数)
@Size(max = 10,min = 1)集合 限定集合大小
@Digits(integer = 3, fraction = 2, message = “请输入有效的数字”)
private double number;
@Digits 用于验证数字的整数位数和小数位数。该注解的 integer 和 fraction 属性分别用于指定整数位数和小数位数的限制。
integer 属性用于指定数字的最大整数位数。它是一个整数值,表示数字允许的最大整数位数。例如,integer = 3 表示数字最多可以有三位整数部分。
fraction 属性用于指定数字的最大小数位数。它是一个整数值,表示数字允许的最大小数位数。例如,fraction = 2 表示数字最多可以有两位小数部分。
@Past时间、日期 必须是一个过去的时间或日期
@Future时期、时间 必须是一个未来的时间或日期
@Email字符串 必须是一个邮箱格式
@Pattern(regexp = “[a-zA-Z]*”, message = “密码不合法”)字符串、字符 正则匹配字符串
@Range(max = 150, min = 1, message = “年龄范围应该在1-150内。”)数字类型(原子和包装) 限定数字范围(长整型)
@URL(protocol=,host=, port=,regexp=, flags=)被注释的字符串必须是一个有效的url
@CreditCardNumber被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
@ScriptAssert(lang=, script=, alias=)要有Java Scripting API 即JSR 223 (“Scripting for the JavaTM Platform”)的实现
@SafeHtml(whitelistType=, additionalTags=)classpath中要有jsoup包

五、@Validated 与 @Valid 比较

1、文字讲解

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。

@Valid属于javax.validation包下,是jdk给提供的 是使用Hibernate validation的时候使用

@Validated是org.springframework.validation.annotation包下的,是spring提供的 是只用Spring Validator校验机制使用

说明:java的JSR303声明了@Valid这类接口,而Hibernate-validator对其进行了实现

@Validation对@Valid进行了二次封装,在使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同,这里主要就这几种情况进行说明。

在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

  1. 分组

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。

  1. 注解地方

@Validated:用在类型、方法和方法参数上。但不能用于成员属性(field)

@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上 所以可以用@Valid实现嵌套验证

总结:

@Valid 和 @Validated 两者都可以对数据进行校验,待校验字段上打的规则注解(@NotNull, @NotEmpty等)都可以对 @Valid 和 @Validated 生效;

@Valid 进行校验的时候,需要用 BindingResult 来做一个校验结果接收。当校验不通过的时候,如果手动不 return ,则并不会阻止程序的执行;

@Validated 进行校验的时候,当校验不通过的时候,程序会抛出400异常,阻止方法中的代码执行,这时需要再写一个全局校验异常捕获处理类,然后返回校验提示。

总体来说,@Validated 使用起来要比 @Valid 方便一些,它可以帮我们节省一定的代码,并且使得方法看上去更加的简洁。

2、代码讲解,groups属性

在开发中,新增、修改两个接口,一般关系就在于新增时ID可以为空,修改时ID不能为空,那我们如果要使用 validation 用于参数校验,创建两个实体类就非常的不划算,这时

①、创建一个update接口

import javax.validation.groups.Default;

public interface Update extends Default {
}
②、修改实体类
@ApiModel("测试 validation 入参")
@Data
public class TestDto {
    @ApiModelProperty(value = "ID",required = true)
    // 新增时ID为空,修改时ID不能为空
    @NotNull(message = "ID不能为空",groups = Update.class)
    private Integer id;
    @ApiModelProperty(value = "名字",required = true)
    @NotBlank(message = "名字不能为空")
    private String name;
}
③、服务层(为了方便,我直接跟Controller写在一起了)
@RestController
@Slf4j
@Api("测试")
public class TestController {

    @PostMapping("/testAdd")
    @ApiOperation("测试 新增")
    public Results testAdd(@Validated @RequestBody TestDto dto){
        try {
            log.info("testAdd 入参 dto={}",dto);
            return Results.success();
        } catch (Exception e) {
            log.error("testAdd 报错",e);
            return Results.failed();
        }
    }

    @PostMapping("/testUpdate")
    @ApiOperation("测试 新增")
    public Results testUpdate(@Validated(Update.class) @RequestBody TestDto dto){
        try {
            log.info("testUpdate 入参 dto={}",dto);
            return Results.success();
        } catch (Exception e) {
            log.error("testUpdate 报错",e);
            return Results.failed();
        }
    }
}
⑤、测试
新增:

在这里插入图片描述

修改:

在这里插入图片描述

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

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

相关文章

链表练习 Leetcode82.删除排序链表中的重复元素 II

题目传送门&#xff1a;Leetcode82 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1…

【欢迎您的到来】这里是开源库get_local_info作者的付费专栏

您好&#xff0c; 我是带剑书生&#xff0c;开源库get_local_info的作者&#xff0c;欢迎您的到来&#xff0c;这里是我的付费专栏&#xff0c;会用更简洁的语言&#xff0c;更通俗的话语&#xff0c;来帮助您更好的学习rust&#xff0c;这里不仅仅讲解Rust在某些应用功能实现上…

Python多线程爬虫——数据分析项目实现详解

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 ChatGPT体验地址 文章目录 前言爬虫获取cookie网站爬取与启动CSDN爬虫爬虫启动将爬取内容存到文件中 多线程爬虫选择要爬取的用户 线程池 爬虫 爬虫是指一种自动化程序&#xff0c;能够模…

ICCV2023 | VL-Match: 使用Token-Level和Instance-Level Matching提升视觉语言预训练

论文标题&#xff1a;VL-Match: Enhancing Vision-Language Pretraining with Token-Level and Instance-Level Matching 代码&#xff1a;None 单位&#xff1a;中国科学院北京计算技术研究所 中国科学院大学 微软 在VLP种&#xff0c;通常采用两种预训练任务&#xff0…

【Leetcode 程序员面试金典 05.01】插入 —— 位运算

面试题 05.01 插入 给定两个整型数字N与M&#xff0c;以及表示比特位置的i与j&#xff08;i < j&#xff0c;且从 0 位开始计算&#xff09;。 编写一种方法&#xff0c;使M对应的二进制数字插入N对应的二进制数字的第i ~ j位区域&#xff0c;不足之处用0补齐。具体插入过…

Shell脚本同时调用#!/bin/bash和#!/usr/bin/expect

如果你想在一个脚本中同时使用bash和expect&#xff0c;你可以将expect部分嵌入到bash脚本中。以下是一个示例&#xff1a; #!/bin/bash# 设置MySQL服务器地址、端口、用户名和密码 MYSQL_HOST"localhost" MYSQL_PORT"3306" MYSQL_USER"your_usernam…

从零实现一套低代码(保姆级教程)【后端服务】 --- 【17】实现页面的增删改查接口

摘要 在上一篇中&#xff0c;我们已经搭建好了后端服务。同时实现了获取全部页面列表的接口以及Swagger文档的配置。 如果这一步没有问题了&#xff0c;我们现在就可以去完成剩下和页面相关的接口了。我们先总体的看一下&#xff0c;我们要实现什么接口。 1.实现新建页面的接…

rust跟我学三:文件时间属性获得方法

图为RUST吉祥物 大家好,我是get_local_info作者带剑书生,这里用一篇文章讲解get_local_info是怎样获得杀毒软件的病毒库时间的。 首先,先要了解get_local_info是什么? get_local_info是一个获取linux系统信息的rust三方库,并提供一些常用功能,目前版本0.2.4。详细介绍地址…

《WebKit 技术内幕》之三(2): WebKit 架构和模块

2.基于 Blink 的 Chrominum 浏览器结构 2.1 Chrominum 浏览器的架构及模块 Chromium也是基于WebKit&#xff08;Blink&#xff09;开发的&#xff0c;并且在WebKit的移植部分中&#xff0c;Chromium也做了很多有趣的事&#xff0c;所以通过Chromium可以了解如何基于WebKit构建浏…

【SpringBoot】Bean 是什么?

感兴趣的话&#xff0c;可以看我另外一篇关于 Bean 的文章&#xff1a;【Java基础】Spring 中 Bean 的理解与使用 一、Bean 定义 Bean 作为 Spring 框架面试中不可或缺的概念&#xff0c;其本质上是指代任何被 Spring 加载生成出来的对象。&#xff08;本质上区别于 Java Bea…

迪文串口屏屏幕界面制作软件T5L_DGUS Tool\\DGUS_V7647的使用

一、概述 使用迪文串口屏要首先用屏幕界面制作软件T5L_DGUS Tool制作界面&#xff0c;然后在直面上设置变量&#xff0c;变量对应有地址。单片机可以使用串口发送数组&#xff0c;数组为迪文屏的控制指令&#xff0c;比如写数据指令&#xff0c;该指令中有变量的地址&#xff0…

k8s创建资源对象过程

我们都知道&#xff0c;K8S中一切皆资源&#xff0c;在使用K8S时&#xff0c;所有的pod或者controller都是通过yaml文件进行创建的。 那么接下来&#xff0c;就和大家一起看一下K8S是如何创建资源的。 创建资源对象的过程 Deployment是一种常见的资源对象。在Kubernetes系统…

低代码-详情页组件设计

效果图 详情页数据结构定义 layout:{// 按钮数据buttonLayout:{headButton:[], // 页头按钮footButton:[] // 页脚按钮},// 详情页表单配置config:{}, // 配置组件列表detailLayout:[]}默认行为 进表单初始化&#xff0c;只展示表单属性&#xff0c;隐藏通用、数据、事件tab项…

springboot第50集:File类,IO流,网络编程,反射机制周刊

image.png FileReader、FileWriter的使用 FileInputStream、FileOutputStream的使用 image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png 服务器内存优化是一个复杂的过程&#xff0c;通常需要综合考虑…

存储系统——Ceph

目录 存储基础 单机存储设备 单机存储的问题 分布式存储&#xff08;软件定义的存储 SDS&#xff09; 分布式存储的类型 Ceph 概述 Ceph 优势 Ceph 架构 RADOS 基础存储系统 LIBRADOS 基础库 高层应用接口 应用层 Ceph 核心组件 OSD&#xff08;Object Storage D…

实现歌词滚动效果

文章目录 需求源码 需求 有一段音频和一个字符串格式的歌词&#xff0c;现欲将二者结合做到歌词随音乐播放歌词滚动的效果&#xff0c;如下图所示 源码 目录结构 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&…

windows PyCharm远程同步Linux服务器上的项目文件,以及远程连接Linux服务器上的python环境

&#xff08;1&#xff09;上传项目文件到Linux服务器和前置说明 &#xff08;1-1&#xff09;本地项目文件地址&#xff1a;D:\Python_Work\XXX &#xff08;1-2&#xff09;阿里云服务器项目文件地址&#xff1a;/home/XXX &#xff08;1-3&#xff09;Pycharm必须是专业版…

杨中科 .NETCORE EFCORE第七部分 一对一,多对多

一对一 一对一关系配置 1、builder.HasOne(o >o.Delivery).WithOne(d>d.Order).HasForeignKey(d>dOrderId); 2、测试插入和获取数据 示例 新建 Order 新建 Delivery DeliveryConfig OrderConfig 执行 迁移命令 查看数据库 测试数据插入 运行查看数据 多对多…

Qt中ComboBox的简单使用

1.相关说明 combobox中item的文字、data、图片设置 2.界面绘制 3.相关主要代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete …

怎么修改或移除WordPress后台仪表盘概览底部的版权信息和主题信息?

前面跟大家分享『WordPress怎么把后台左上角的logo和评论图标移除&#xff1f;』和『WordPress后台底部版权信息“感谢使用 WordPress 进行创作”和版本号怎么修改或删除&#xff1f;』&#xff0c;其实在WordPress后台仪表盘的“概览”底部还有一个WordPress版权信息和所使用的…