【实战案例】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 | 被注释的元素需要递归验证 |
被注释的元素必须是电子邮箱地址 | |
@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项目的整合
前后端分离项目中,当前前端请求后端接口的时候通常需要传输参数,对于参数的校验应该在哪一步进行校验?Controller中还是Service中?答案是都需要校验,只不过负责的板块不一样,Controller中通常校验请求参数的…...

忘记了系统root密码,如何重置root密码?
重置root密码(CentOS7) 文章目录 重置root密码(CentOS7)[toc] 1.开启系统时,在引导界面按下字母e。 2.进入到内核界面,找到Linux开头字样一行,然后在最末尾输入参数rd.break,然后按住…...

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

计算机毕业设计 | SSM超市进销存管理系统(附源码)
1,绪论 1.1 开发背景 世界上第一个购物中心诞生于美国纽约,外国人迈克尔库伦开设了第一家合作商店,为了更好地吸引大量客流量,迈克尔库伦精心设计了低价策略,通过大量进货把商品价格压低,通过商店一次性集…...

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

TS和JS中,string与String的区别
1. string string 是 TypeScript 的基本类型,用于表示简单的字符串值,同时它是一个原始类型,可直接表示文本数据。 2. String String 是 JavaScript 中的一个全局对象(类),用于创建字符串对象࿰…...

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卡中记录的数据文件导出可按以下的流程进行配置: 点击主界面中的导出选项即可进入到下图中TF卡应用界面点击TF卡应用界面中“查看记录文件”的选项,进入导出文件界面。点击“选择”进入勾选文件的界面 点击“导出”后,点击“确定”即…...

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

Servlet[springmvc]的Servlet.init()引发异常
报错: 原因之一: web.xml配置文件中监听器导入依赖项错误...

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

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

【多版本并发控制(MVCC)】
并发事务问题: MySQL隔离级别-未提交读,提交读,可重复读,序列化 隔离级别对于并发事务的解决情况 隔离级别脏读不可重复读幻读未提交读不可不可不可读已提交可不可不可可重复读 (默认)可可不可串行化&…...

常见漏洞及webshell工具的流量特征
常见攻击的流量特征 信息泄露 请求/路径中,包含 特殊文件 或 路径;响应包中,包含敏感信息(如,数据结构,用户信息,网络结构等) 弱口令爆破 非常规流量:短时间内大量数据…...

python学习-怎么在Pycharm写代码
打开Pycharm,点击文件-新建项目 2.选择pure python-点击箭头 展开 3.选择 Existing interpreter 如果 Existing interpreter 下没有相关环境 (1)点击**…** (2)选择python的安装路径 4.可修改文件名称-点击创建 …...

牛客周赛63(C++实现)
🌈个人主页:Yui_ 🌈Linux专栏:Linux 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 🌈C专栏:C 文章目录 1.小红的好数1.1 题目描述1.2 思路1.3 代码 2.…...

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

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

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

NC 单据模板自定义项 设置参照(自定义参照)
NC 单据模板自定义项 设置参照(自定义参照) 如图下图,NC 单据模板自定义项 设置参照: 1、选择需要设置参照的自定义字段,选择高级属性页签,在类型设置中,数据类型选择参照信息,即bd…...

Element-ui官方示例(Popover 弹出框)
Element-ui官方示例(Popover 弹出框),好用的弹出框。 使用 vue-cli3 我们为新版的 vue-cli 准备了相应的Element 插件,你可以用它们快速地搭建一个基于 Element 的项目。 使用 Starter Kit 我们提供了通用的项目模版&#…...

Bootstrap 5 练习 - 显示工具提示
文章目录 引言准备工作创建HTML文件导入Bootstrap 5框架编写页面代码编写JavaScript脚本浏览网页注意事项结束语 引言 大家好,今天我们将一起学习如何在Bootstrap 5中创建一个简单的工具提示(Tooltip)。工具提示是一个非常实用的用户界面元素…...

【p2p、分布式,区块链笔记 Torrent】: WebTorrent GitTorrent bittorrent-dht
bittorrent-dht模块 BitTorrent DHT 通过 DHT 网络广播值,允许其他用户通过 DHT 来发现和获取这些数据。 1. 导入依赖 var DHT require(bittorrent-dht)2. 创建实例 var dht new DHT({bootstrap: config.dht.bootstrap }) dht.listen(config.dht.listen)new D…...

【Next.js 项目实战系列】05-删除 Issue
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】04-修改 Issue 删除 Issue 添加删除 Button 本节代码链接 这里我们主要关注布局…...

Springboot api http并发测试请求
pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> 线程发起请求 package com.example.demo;import org.springframework.http.HttpEntity; import org…...

Qt的websocket客户端和服务器测试(非安全版本)
测试内容: 客户端: 1 连接服务器 2 发送数据 3 处理错误信号 4 监听断开信号 5 接收服务器的数据 服务器: 1 监听等待客户端连接 2 向指定的客户端发送数据 4 监听断开信号 5 接收客户端的数据 测试界面 工程文件.pro添加的内容:…...

Prometheus运维监控平台之监控指标注册到consul脚本开发、自定义监控项采集配置调试(三)
系列文章目录 运维监控平台搭建 运维监控平台监控标签 golang_Consul代码实现Prometheus监控目标的注册以及动态发现与配置V1版本 文章目录 系列文章目录目的一、监控指标注册到consul的golang脚本开发1、修改settings.yaml文件2、修改config/ocnsul,go文件3、修改core/consul…...

C语言——数组
1.数组的概念 数组是一组相同类型元素的集合; 数组中可以存放1个或多个元素,但数组元素个数不能为0。 同时数组可以分为一维数组和多维数组,多维数组一般常见 是二维数组。 2.一维数组的创建和初始化 一维数组的创建的基本语法: …...

MySQL-09.DDL-表结构操作-查询修改删除
一.查询 二.修改 三.删除 -- DDL:查看表结构 -- 查看:当前数据库下的表show tables ;-- 查看:查看指定的表结构 desc tb_emp;-- 查看:数据库的建表语句 show create table tb_emp;-- DDL:修改表结构 -- 修改:为表tb…...

WileyNJDv5_Template模板无法编译生成pdf文件
文章目录 问题解决办法结果 问题 使用WileyNJDv5_Template模板时,修改tex文件里的内容,按F6编译后,并没有报错,但是编辑后的pdf文件没有生成,因为看文件夹里的pdf文件日期还是以前的日期,所以是旧版的pdf文…...