当前位置: 首页 > news >正文

【实战案例】JSR303统一校验与SpringBoot项目的整合

前后端分离项目中,当前前端请求后端接口的时候通常需要传输参数,对于参数的校验应该在哪一步进行校验?Controller中还是Service中?答案是都需要校验,只不过负责的板块不一样,Controller中通常校验请求参数的合法性,包括:必填项校验,数据格式校验,比如:是否为空,是否符合一定的日期格式等。Service中要校验的是业务规则相关的内容,比如:课程已经审核通过所以提交失败。Service中根据业务规则去校验通常因为业务各异所以一般无法提取成通用代码,Controller中则可以将校验的代码写成通用代码。
在JavaEE6规范中就定义了参数校验的规范JSR-303,它定义了Bean Validation,即对bean属性进行校验。同时SpringBoot也提供了JSR-303的支持,即spring-boot-starter-validation,底层使用Hibernate Validator,Hibernate Validator是Bean Validation 的参考实现。
接下来在Controller层使用spring-boot-starter-validation完成对请求参数的基本合法性进行校验。

首先在项目所属或依赖工程中添加相应依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

在javax.validation.constraints包下有很多校验注解用于定义校验规则。
在这里插入图片描述

Constraint详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(regex=)被注释的元素必须符合指定的正则表达式
@Valid被注释的元素需要递归验证
@Email被注释的元素必须是电子邮箱地址
@Length(min=下限, max=上限)被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的元素的必须非空并且size大于0
@NotBlank被注释的元素必须不为空且不能全部为’ '(空字符串)
@Range(min=最小值, max=最大值)被注释的元素必须在合适的范围内

例如项目中存在一个接口用于创建课程:

@ApiOperation("新增课程")
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody AddCourseDto addCourseDto) {Long companyId = 10000L;CourseBaseInfoDto courseBase = courseBaseInfoService.createCourseBase(companyId, addCourseDto);return courseBase;
}

此接口使用AddCourseDto模型对象接收参数,可进入AddCourseDto类,在属性上添加校验规则。

package com.gavin.content.model.dto;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.math.BigDecimal;/*** @author Gavin* @description 添加课程dto* @date 2024/10/14*/
@Data
@ApiModel(value = "AddCourseDto", description = "新增课程基本信息")
public class AddCourseDto {@NotEmpty(message = "课程名称不能为空")@ApiModelProperty(value = "课程名称", required = true)private String name;@NotEmpty(message = "适用人群不能为空")@Size(message = "适用人群内容过少", min = 10)@ApiModelProperty(value = "适用人群", required = true)private String users;@ApiModelProperty(value = "课程标签")private String tags;@NotEmpty(message = "课程分类不能为空")@ApiModelProperty(value = "大分类", required = true)private String mt;@NotEmpty(message = "课程分类不能为空")@ApiModelProperty(value = "小分类", required = true)private String st;@NotEmpty(message = "课程等级不能为空")@ApiModelProperty(value = "课程等级", required = true)private String grade;@ApiModelProperty(value = "教学模式(普通,录播,直播等)", required = true)private String teachmode;@ApiModelProperty(value = "课程介绍")private String description;@ApiModelProperty(value = "课程图片", required = true)private String pic;@NotEmpty(message = "收费规则不能为空")@ApiModelProperty(value = "收费规则,对应数据字典", required = true)private String charge;@ApiModelProperty(value = "价格")private Float price;@ApiModelProperty(value = "原价")private Float originalPrice;@ApiModelProperty(value = "qq")private String qq;@ApiModelProperty(value = "微信")private String wechat;@ApiModelProperty(value = "电话")private String phone;@ApiModelProperty(value = "有效期")private Integer validDays;
}

上述程序中@NotEmpty表示属性不能为空,@Size表示限制属性内容的长短。
定义好校验规则还需要开启校验,在controller方法中添加@Validated注解,如下:

@ApiOperation("新增课程")
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated AddCourseDto addCourseDto) {//获取到用户所属机构的idLong companyId = 10000L;CourseBaseInfoDto courseBase = courseBaseInfoService.createCourseBase(companyId, addCourseDto);return courseBase;
}

如果校验出错Spring会抛出MethodArgumentNotValidException异常,为了使得报错信息更加明确,可以在统一异常处理器中捕获异常,解析出异常信息。在全局异常处理器中添加对该类异常的解析处理(全局异常处理类的建立见【实战案例】SpringBoot项目中异常处理通用解决方案),如下:

@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public RestErrorResponse methodArgumentNotValidException(MethodArgumentNotValidException e) {BindingResult bindingResult = e.getBindingResult();List<String> msgList = new ArrayList<>();//将错误信息放在msgListbindingResult.getFieldErrors().stream().forEach(item->msgList.add(item.getDefaultMessage()));//拼接错误信息String msg = StringUtils.join(msgList, ",");log.error("【系统异常】{}",msg);return new RestErrorResponse(msg);
}

经过测试可在不合法的数据参数情况下返回类似如下的异常信息:

{"errMessage": "课程名称不能为空,适用人群内容过少"
}

可见校验器生效。

有时候在同一个属性上设置一个校验规则不能满足要求,比如:订单编号由系统生成,在添加订单时要求订单编号为空,在更新 订单时要求订单编写不能为空。此时就用到了分组校验,同一个属性定义多个校验规则属于不同的分组,比如:添加订单定义@NULL规则属于insert分组,更新订单定义@NotEmpty规则属于update分组,insert和update是分组的名称,是可以修改的。
可以用class类型来表示不同的分组,定义不同的接口类型(空接口)表示不同的分组,如下:

public class ValidationGroups {public interface Insert{};public interface Update{};public interface Delete{};}

定义校验规则时指定分组:

@NotEmpty(groups = {ValidationGroups.Insert.class},message = "添加课程名称不能为空")
@NotEmpty(groups = {ValidationGroups.Update.class},message = "修改课程名称不能为空")
@ApiModelProperty(value = "课程名称", required = true)
private String name;

在Controller方法中启动校验规则指定要使用的分组名:

@ApiOperation("新增课程基础信息")
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated({ValidationGroups.Insert.class}) AddCourseDto addCourseDto){Long companyId = 10000L;return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}

通过上述配置则可实现不同类型的异常信息在不同类型的请求过来使用模型类的时候加以区分。

如果javax.validation.constraints包下的校验规则满足不了需求可通过手写校验代码或自定义校验规则注解。

自定义校验规则注解示例如下:
定义一个自定义注解 @OnlyLetters,用于验证字段是否只包含字母字符:

package com.gavin.validation;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 指定注解的适用范围(字段级别)
@Target({ElementType.FIELD, ElementType.METHOD})
// 指定注解的生命周期(在运行时可用)
@Retention(RetentionPolicy.RUNTIME)
// 关联校验器类
@Constraint(validatedBy = OnlyLettersValidator.class)
public @interface OnlyLetters {// 错误信息String message() default "只能包含字母字符";// 用于分组校验Class<?>[] groups() default {};

实现 OnlyLettersValidator 类,编写具体的校验逻辑:

package com.gavin.validation;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class OnlyLettersValidator implements ConstraintValidator<OnlyLetters, String> {// 初始化方法(可以用来获取注解的属性值)@Overridepublic void initialize(OnlyLetters constraintAnnotation) {// 不需要初始化操作}// 校验逻辑@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// 如果字段为空,不进行验证(可以在其他注解中处理空值校验)if (value == null) {return true;}// 校验字符串是否只包含字母return value.matches("[a-zA-Z]+");}
}

通过上述自定义注解,即可跟前序流程一样进行使用。

相关文章:

【实战案例】JSR303统一校验与SpringBoot项目的整合

前后端分离项目中&#xff0c;当前前端请求后端接口的时候通常需要传输参数&#xff0c;对于参数的校验应该在哪一步进行校验&#xff1f;Controller中还是Service中&#xff1f;答案是都需要校验&#xff0c;只不过负责的板块不一样&#xff0c;Controller中通常校验请求参数的…...

忘记了系统root密码,如何重置root密码?

重置root密码&#xff08;CentOS7&#xff09; 文章目录 重置root密码&#xff08;CentOS7&#xff09;[toc] 1.开启系统时&#xff0c;在引导界面按下字母e。 2.进入到内核界面&#xff0c;找到Linux开头字样一行&#xff0c;然后在最末尾输入参数rd.break&#xff0c;然后按住…...

7-基于国产化FT-M6678+JFM7K325T的6U CPCI信号处理卡

一、板卡概述 本板卡系我公司自主研发&#xff0c;基于6U CPCI的通用高性能信号处理平台。板卡采用一片国产8核DSP FT-C6678和一片国产FPGA JFM7K325T-2FFG900作为主处理器。为您提供了丰富的运算资源。如下图所示&#xff1a; 二、设计参考标准 ● PCIMG 2.0 R3.0 CompactP…...

计算机毕业设计 | SSM超市进销存管理系统(附源码)

1&#xff0c;绪论 1.1 开发背景 世界上第一个购物中心诞生于美国纽约&#xff0c;外国人迈克尔库伦开设了第一家合作商店&#xff0c;为了更好地吸引大量客流量&#xff0c;迈克尔库伦精心设计了低价策略&#xff0c;通过大量进货把商品价格压低&#xff0c;通过商店一次性集…...

手撕数据结构 —— 堆(C语言讲解)

目录 1.堆的认识 什么是堆 堆的性质 2.堆的存储 3.堆的实现 Heap.h中接口总览 具体实现 堆结构的定义 初始化堆 销毁堆 堆的插入 堆的向上调整算法 堆的插入的实现 堆的删除 堆的向下调整算法 堆的删除的实现 使用数组初始化堆 获取堆顶元素 获取堆中的数据…...

TS和JS中,string与String的区别

1. string string 是 TypeScript 的基本类型&#xff0c;用于表示简单的字符串值&#xff0c;同时它是一个原始类型&#xff0c;可直接表示文本数据。 2. String String 是 JavaScript 中的一个全局对象&#xff08;类&#xff09;&#xff0c;用于创建字符串对象&#xff0…...

jna调用c++动态库linux测试

1、 编译代码和运行指令 javac -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest.java VideoAiLibrary.java java -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest javac -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest.java VideoAiLibrary.java -cp 指定c…...

智诊小助手TF卡记录文件导出

若想将TF卡中记录的数据文件导出可按以下的流程进行配置&#xff1a; 点击主界面中的导出选项即可进入到下图中TF卡应用界面点击TF卡应用界面中“查看记录文件”的选项&#xff0c;进入导出文件界面。点击“选择”进入勾选文件的界面 点击“导出”后&#xff0c;点击“确定”即…...

Jetpack-ViewModel+LiveData+DataBinding

1.ViewModel 解决问题&#xff1a; 瞬态数据丢失异步调用内存泄漏类膨胀提高维护难度和测试难度 作用&#xff1a; 介于View视图和Model数据模型之间桥梁使视图和数据能够分离&#xff0c;也能保持通信 public class MainActivity extends AppCompatActivity {private Tex…...

Servlet[springmvc]的Servlet.init()引发异常

报错&#xff1a; 原因之一&#xff1a; web.xml配置文件中监听器导入依赖项错误...

总结:SQL查询变慢,常见原因分析!

文章目录 引言SQL查询慢原因索引失效特殊情况-执行计划中&#xff0c;key有值&#xff0c;还是很慢怎么办&#xff1f; 多表JOIN为什么互联网公司都不建议使用多表join&#xff1f; 索引基数太小不合理查询字段太多表中数据量太大数据库连接数不够为什么乐观锁还会导致大量的锁…...

基于webrtc实现音视频通信

与传统通信方式不同&#xff0c;p2p通信的实现过程不依赖于中间服务器的信息收发&#xff0c;直接通过信令等完成通信过程的建立&#xff1b; 通过websocket实现信令服务器的建立&#xff0c;而通过信令来确定通信双方&#xff1b; webrtc通过 sdp协议来完善通信双方间协议的…...

【多版本并发控制(MVCC)】

并发事务问题&#xff1a; MySQL隔离级别-未提交读&#xff0c;提交读&#xff0c;可重复读&#xff0c;序列化 隔离级别对于并发事务的解决情况 隔离级别脏读不可重复读幻读未提交读不可不可不可读已提交可不可不可可重复读 &#xff08;默认&#xff09;可可不可串行化&…...

常见漏洞及webshell工具的流量特征

常见攻击的流量特征 信息泄露 请求/路径中&#xff0c;包含 特殊文件 或 路径&#xff1b;响应包中&#xff0c;包含敏感信息&#xff08;如&#xff0c;数据结构&#xff0c;用户信息&#xff0c;网络结构等&#xff09; 弱口令爆破 非常规流量&#xff1a;短时间内大量数据…...

python学习-怎么在Pycharm写代码

打开Pycharm&#xff0c;点击文件-新建项目 2.选择pure python-点击箭头 展开 3.选择 Existing interpreter 如果 Existing interpreter 下没有相关环境 &#xff08;1&#xff09;点击**…** &#xff08;2&#xff09;选择python的安装路径 4.可修改文件名称-点击创建 …...

牛客周赛63(C++实现)

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1.小红的好数1.1 题目描述1.2 思路1.3 代码 2.…...

高级英语1第四版教材全解pdf课后答案+课文翻译张汉熙

《高级英语1》是张汉熙教授编著的一本英语教材&#xff0c;广泛用于国内高校英语专业高年级学生的教学。这本书以提高学生的英语综合能力为目标&#xff0c;注重语言知识的系统性和实用性&#xff0c;同时强调跨文化交际能力的培养。书中选材丰富&#xff0c;涵盖了文学、历史、…...

视频去水印软件3款推荐:好用的去水印软件分享!

在处理视频素材时&#xff0c;水印往往是一个令人头疼的问题。幸运的是&#xff0c;市面上有许多优秀的视频编辑软件能够帮助我们快速、有效地去除水印。今天&#xff0c;我将为大家推荐三款功能强大的视频去水印软件&#xff1a;影忆、Final Cut Pro X以及Adobe Premiere Pro&…...

perl文件测试操作符及其意义

perl文件测试操作符及其意义 文件测试操作符意义-r文件或目录&#xff0c;对目前&#xff08;有效的&#xff09;用户或组来说是可读的-w文件或目录&#xff0c;对目前&#xff08;有效的&#xff09;用户或组来说是可写的-x文件或目录&#xff0c;对目前&#xff08;有效的&a…...

NC 单据模板自定义项 设置参照(自定义参照)

NC 单据模板自定义项 设置参照&#xff08;自定义参照&#xff09; 如图下图&#xff0c;NC 单据模板自定义项 设置参照&#xff1a; 1、选择需要设置参照的自定义字段&#xff0c;选择高级属性页签&#xff0c;在类型设置中&#xff0c;数据类型选择参照信息&#xff0c;即bd…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...