【实战案例】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…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
