Idea+maven+spring-cloud项目搭建系列--14 整合请求参数校验

news2024/10/7 11:32:03

前言:当我们在进行web 项目的开发时,对于前端传入的参数,都需要进行一些非空必填等的验证,然后在进行业务逻辑的处理,如果写一堆的if 判断很不优雅,那么有没有好的方式来帮忙处理,本文通过hibernate-validator 及jakarta.validation 结合的方式来完成请求参数的验证;

整合开始:
1 : 引入验证框架所需要的jar 包:

 <!--validation 核心jar 内部引入了hibernate-validator 及jakarta.validation -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
   <!-- web 请求依赖jar -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
 <!--lombok 插件便于生成get set 方法 -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
  </dependency>
   <!-- 测试依赖 -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>
   <!-- 阿里json 格式化工具-->
  <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.19</version>
  </dependency>
   <!--处理jdbc报错依赖jar  -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
  </dependency>

2 controller 使用:


import com.example.springvalidate.dto.UserReqDto;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.*;

@Validated
@RestController
@RequestMapping("user/")
public class UserController {

    /**
     * Get 请求参数验证
     * @param id
     * 设置id 非必须, 以便 AbstractNamedValueMethodArgumentResolver 的 resolveArgument 方法对参数解析不报错
     * 在使用 NotNull进行非空校验
     * @return
     */
    @GetMapping("getName")
    public Object getName(@RequestParam(value = "id",required = false) @NotNull(message = "id 不能为空") @Min(value = 1) Integer id ){
        return "1";
    }

    /**
     * post
     * @param reqDto
     * @return
     */
    @PostMapping("getName1")
    public String getName1(@RequestBody  @Valid  UserReqDto reqDto){

        return "1";
    }
    /**
     * put
     * @param reqDto
     * @return
     */
    @PutMapping("getName2")
    public String getName2(@RequestBody  @Valid  UserReqDto reqDto){

        return "1";
    }

    /**
     * delete
     * @param id
     * @return
     */
    @DeleteMapping("del/{id}")
    public String del(@PathVariable("id") @Min(message = "id 不能小于1",value = 1) Integer id ){
        return "1";
    }
    /** 分组
     * group1
     */
    @PostMapping("group1")
    public String group1(@RequestBody  @Validated(UserReqDto.ModifyAge.class)  UserReqDto reqDto){

        return "1";
    }
    /** 分组
     * group2
     */
    @PostMapping("group2")
    public String group2(@RequestBody   @Validated({UserReqDto.ModifyEmail.class})  UserReqDto reqDto){

        return "1";
    }
}

UserReqDto 请求参数接收类:


import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;

public class UserReqDto {

    //特殊用于修改名字 标记使用 灵活放置位置

    /**
     * 分组验证+ 基本验证
     * 此处会验证ModifyAge 的分组 及验证 userId不能为空
     */
    public interface ModifyAge extends Default {
    }

    public interface ModifyEmail {
    }

    @NotNull(message = "id dto 不能为空")
    private Integer userId;

    //自定义一个正则
    @NotBlank(groups = {UserReqDto.ModifyAge.class},
            message = "失败,请填写name"
    )
    private String name;

    @NotBlank(groups = {UserReqDto.ModifyEmail.class},
            message = "失败,请填写email"
    )
    private String email;
}

3 简单解释:

项目中我们使用了 spring 中的 @Validated 和 javax.validation 中的 @Valid ,那么它们之间的联系是什么;

3.1 @Validated 注解,可以用在类,方法,和参数级别:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    Class<?>[] value() default {};
}

3.2 @Valid 注解,可以用在类,方法,和参数,类中的属性:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}

也就是说我们在使用@RequestBody 接收body 中的请求参数,参数类内部对于属性的校验可以使用 javax.validation 的方法:
在这里插入图片描述
这样我们就可以在controller 类中,对类使用@Validated 进行修饰,然后 对于get,delete 方法 ,通过url 获取参数的我们可以使用javax.validation 的验证方法完成验证,在对于post ,put 通过body 获取参数的可以对其使用 @Validated ,在参数接收类的内部使用javax.validation 的验证方法完成验证;

3.3 通过使用group 完成参数分组,解决不同场景的参数验证:
如在新增用户的时候可能不需要id ,但是在修改名字的时候需要使用id 来找到改用户,可以通过继承Default 使得没有分组的属性也进行校验(如本例中的ModifyAge 会验证userId 和name);
在这里插入图片描述

controller 中设置分组:
在这里插入图片描述

3.4 对于从url 中获取参数来说,我们会发现当使用@RequestParam 接收参数时,发起的http 请求会直接报错,那是因为spring 本身参数值获取逻辑抛出了异常:
AbstractNamedValueMethodArgumentResolver 类中的 resolveArgument 方法:

@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();
    Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    } else {
    	// 获取指定参数名称的值
        Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
      		  // 如果对应参数的默认值不是空,那么就使用默认值
            if (namedValueInfo.defaultValue != null) {
                arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            } 
            // 否则,查看这个参数的值是否是必须设置的,如果必须设置,但是没有对应的值,那么按错误处理
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }

            arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } 
		// 如果为空字符串,但是有设定的默认值则取默认值
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException var11) {
                throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
            } catch (TypeMismatchException var12) {
                throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
            }
			// 如果参数必填,且参数为空,则报错
            if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
            }
        }
		// 获取参数
        this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
}

当我们使用@RequestParam 接收参数时可以看到参数默认是必须存在的:
在这里插入图片描述
所以它在发现参数没有且没有默认值,并且是必填的,则在进行valide 验证矿建之前就抛出了异常;所以我们在使用@RequestParam 接收并进行 @NotNull 判断时 ,可以设置,required = false,通过spring 的参数获取,然后交由后面的验证框架进行 非空的验证;

这里我们可以看出,对于@RequestParam的使用而言,其值的解析会有这么几个分支处理:

  • 如果这个参数对应的值存在,就直接返回。
  • 如果这个参数对应的值不存在,那么先看这个参数是否设置了默认值,若有,则返回默认值。
  • 如果默认值也没有,那么看这个参数是否是required(默认为true),以及该参数本身是否可选isOptional()。如果满足这俩条件,但是没有可选的值,就报错。
  • 否则按照null处理。

4 经常使用的参数验证方法:

javax.validation.constraints包下提供的注解:
在这里插入图片描述

hibernate也扩展了很多注解,位于org.hibernate.validator.constraints包下:
在这里插入图片描述
5 增加异常的统一处理:

参考:SpringBoot工具篇–统一数据结构及返回(controller & exception)

6 参考:

6.1 spring-boot-starter-validation开启参数校验使用详解;
6.2 Spring常见问题解决 - @RequestParam和@PathVariable的区别以及400报错问题;

项目git 地址:https://codeup.aliyun.com/61cd21816112fe9819da8d9c/spring-validate.git

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

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

相关文章

光电隔离转换器 直流信号放大器 导轨安装DIN11 IPO OC系列

概述&#xff1a; 导轨安装DIN11 IPO OC系列模拟信号隔离放大器是一种将输入信号隔离放大、转换成按比例输出的直流信号混合集成厚模电路。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等需要直流信号隔离测控的行业。此系列产品内部采用了线性光电隔离技术相…

DSP中定点与浮点运算

一、定点数及其定标 在定点DSP芯片中&#xff0c;采用的是定点数据数值运算&#xff0c;其操作数一般采用整形数来表示。一个整形数的最大表示范围由DSP芯片给定字长决定。字长越长&#xff0c;表示的范围越大&#xff0c;精度越高。 举例16位字长 每个16数位用1个符号位表示正…

九龙证券|这一刻,资本市场进入全新时代!

2023年4月10日&#xff0c;第一批10家主板注册制企业上市鸣锣敲钟&#xff0c;奏响了触及本钱商场灵魂深处革新的序曲。 动能切换中的我国对于高效资源配置的渴望&#xff0c;与革新进行时的本钱商场对于全面注册制的探究&#xff0c;一起凝集成一股连绵有力之暖流&#xff0c;…

学习安全攻防技能30讲-开篇|别说你没有被安全困扰过

文章目录学习安全攻防技能30讲-开篇|别说你没有被安全困扰过研读开篇安全重要吗&#xff1f;安全难学吗&#xff1f;学习安全攻防技能30讲-开篇|别说你没有被安全困扰过 研读开篇 文中说到一个竞赛叫CTF&#xff0c;这个之前从来没有听过的&#xff0c;作为开发人员涨知识了。…

Java封装

Java封装\huge{Java封装}Java封装 JavaJavaJava的三大特征之一。 作用 告知如何设计对象的属性和方法&#xff0c;将对象完全独立起来。 ❗❗封装原则 对象代表什么&#xff0c;就要封装对应的数据&#xff0c;并且提供对应数据的行为。&#xff08;尤其是后半句非常重要&a…

012:Mapbox GL显示弹窗Popup

第012个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中显示弹窗。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共70行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:https://xiaozhuan…

PHP医院安全(不良)事件管理系统源码,十多种不良事件类型,上百种报告内容数据表

医院不良事件上报系统源码&#xff0c;PHP医院安全&#xff08;不良&#xff09;事件管理系统源码 技术架构&#xff1a;前后端分离&#xff0c;仓储模式&#xff0c; 开发语言&#xff1a;PHP 开发工具&#xff1a;vscode 前端框架&#xff1a;vue2element 后端框架&…

2023年mathorcupB题城市轨道交通列车时刻表思路分析

B 题 城市轨道交通列车时刻表优化问题 列车时刻表优化问题是轨道交通领域行车组织方式的经典问题之一。 列车时刻表规定了列车在每个车站的到达和出发(或通过)时刻&#xff0c;其在实 际运用过程中&#xff0c;通常用列车运行图来表示。图 1 为某一运行图的示例&#xff0c;图…

文件操作【上篇】

文章目录&#x1f5c3;️1.为什么使用文件&#x1f5c3;️2.什么是文件&#x1f4c1;2.1.程序文件&#x1f4c1;2.2.数据文件&#x1f4c1;2.3.文件名&#x1f5c3;️3.文件的打开和关闭&#x1f4c1;3.1.文件指针&#x1f4c1;3.2.文件的打开和关闭&#x1f5c3;️4.文件的顺序…

I-型糖尿病患者的福音,皮下燃料电池将多余的血糖转化为电能产生胰岛素

I-型糖尿病患者体内不产生胰岛素&#xff0c;患者必须从外部获得激素来调节血糖水平。当前&#xff0c;患者主要通过将胰岛素泵直接连接到身体以获得胰岛素。这些胰岛素泵以及其他医疗器械&#xff08;如起搏器&#xff09;需要可靠的能源供应&#xff0c;主要通过一次性或可充…

#mvn 打包ik分词器报错#

场景&#xff1a;在window上安装ik分词器&#xff0c;需要先mvn打包&#xff0c;结果报错 原因&#xff1a;由于jdk版本的问题导致 解决过程 1&#xff1a;打包流程 git clone https://github.com/medcl/elasticsearch-analysis-ik #git clone https://gitcode.net/mirrors/me…

线性分类算法:逻辑回归和Softmax回归

目录&#xff08;一 &#xff09;逻辑回归1.1 逻辑回归概述&#xff1a;1.2 逻辑回归的作用与Sigmoid 函数&#xff1a;1.2.1 Sigmoid 函数作用1.3 指数族分布1.4 逻辑回归的损失函数1.5逻辑回归如何求解得到最优解模型方法1.6 逻辑回归鸢尾花分类1.7 逻辑回归做多分类&#xf…

拉取gradle项目报错Could not find method compile() for arguments

拉取gradle项目, 依赖拉不下来, 报错如下: Could not find method compile() for arguments XXXXXX on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. 找了很多篇文章都没有解决,后来在stack overflow上面找到了答案: Note t…

Linux中快速搭建RabbitMQ

目录一、简介1、关于RabbitMQ2、RabbitMQ主要端口介绍二、RabbitMQ安装1、安装依赖项socat2、下载Erlang和RabbitMQ(1) Erlang和RabbitMQ版本关系要求(2) 下载操作系统支持的Erlang和RabbitMQ版本(3) 安装Erlang和RabbitMQ三、启动和关闭1、启动服务2、查看状态3、停止服务4、设…

核心业务3:借款人借款申请

核心业务3&#xff1a;借款人借款申请 1.借款人借款申请业务流程图 2.借款项数据库绑定 ---------------------借款申请流程---------------------- 3.借款申请流程 4.前端代码逻辑 5.后端代码逻辑 ---------------------借款申请流程---------------------- 核心业务3&…

用SSH登陆Centos系统时,命令行最前面显示“的提示符[root@www myapp]”是什么意思?

用SSH登陆Centos系统时&#xff0c;命令行最前面显示“的提示符[rootwww myapp]”是什么意思&#xff1f; 在SSH登录到CentOS系统时&#xff0c;提示符 [rootwww myapp] 中的 www 表示当前登录的主机名&#xff08;hostname&#xff09;&#xff0c;也就是指当前运行的CentOS系…

基于html+css的盒子展示2

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

蓝桥杯web备赛——Node.js

node.js之前只能说是略有了解&#xff0c;这次好好了解一下吧&#xff01; 东西还是比较多的。 目前来看就了解比赛会用到的http模块就可以了&#xff0c;其他的暂且不做了解 const http require("http");//1.引入http模块const app http.createServer();//2.创建…

【网络安全】--xss漏洞

xss漏洞xss漏洞介绍危害防御方法xss测试语句xss攻击语句1. 反射性xss2.存储型xss3.DOM型xssxss漏洞介绍 定义&#xff1a;XSS 攻击全称跨站脚本攻击&#xff0c;是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆&#xff0c;故将跨站脚本攻击缩写为 XSS&#xff0c…

线段树笔记草稿

一个左节点u << 1和右节点u << 1 | 1 的证明 区间修改部分 1.批量等值修改 前提条件 是要区间修改&#xff0c;区间查询&#xff0c;且修改操作修改的值是相同的 情景 一般是要对一个数组执行k次操作&#xff0c;每次改变其中一个区间内所有元素的值&#x…