Spring validation校验框架
第1步:导入依赖
<!-- 校验框架-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
第2步:为需要校验的参数(实体类或方法中)加上校验规则
常见的校验注解
- @NotNull:确保字段不是null。适用于任何类型。
- @NotEmpty:确保字段既不是null也不是空(对于字符串意味着长度大于0,对于集合意味着至少包含一个元素)。适用于字符串、集合、数组等。
- @NotBlank:确保字符串字段不是null且至少包含一个非空白字符。只能用于字符串类型。
- @Length:字符串长度 max是字符串的最大长度 min是字符串的最小长度(对比于@Size,只能用于字符串)
- @Size(min=, max=):确保字段(字符串、集合、数组)的长度(或大小)在指定的最小值和最大值之间。适用于字符串、集合、数组等。
- @Min(value=) 和 @Max(value=):对数值类型字段设置最小值和最大值。适用于数值类型(Integer、Long等)。
- @Range :可以应用于整型(int、long等)和浮点型(float、double等)。校验数字的大小,min是数字最小值,max是数字的最大值
- @Email:确保字段是有效的电子邮件地址。适用于字符串类型。
- @Pattern(regexp=):确保字符串字段匹配指定的正则表达式。适用于字符串类型。
- @Past 和 @Future:校验日期是否为过去的时间或将来的时间。适用于日期类型(如LocalDate、LocalDateTime等)。
这些注解可以应用于模型类的字段上,以定义字段的验证规则。在Spring MVC的控制器中,可以使用@Valid或@Validated注解来触发这些验证。如果验证失败,Spring将抛出一个异常,可以通过全局异常处理来捕获这个异常并返回适当的响应给客户端。
请注意,@Valid和@Validated注解虽然都用于数据验证,但它们在功能和使用上有所不同。@Valid是JSR 303/JSR 380 Bean Validation API的一部分,而@Validated是Spring的特有注解,支持验证组的概念,允许更灵活地指定在特定情况下应用哪些验证约束。在实际开发中,可以根据需要选择使用哪个注解。
2.1 编辑校验组
实体类中的多个字段被赋予了校验规则,然而在不同的方法中,这些字段不一定是都需要被校验的。
(如下单和取消订单,取消订单需要校验取消订单的原因是否为空,而下单则不需要,但是方法中传的都是order这个实体类,因此区分校验组是必须的)
package com.gmgx.common;import jakarta.validation.groups.Default;public interface ValidGroup extends Default {interface IdAndCancelReason extends ValidGroup {}interface IdAndCancelReasonMember extends ValidGroup {}interface IdAndRejectionReason extends ValidGroup {}interface IdAndRejectCancelReason extends ValidGroup {}interface IdAndShopId extends ValidGroup {}interface ValidOrderSearchDto extends ValidGroup {}interface SubmitNoItems extends ValidGroup {}
}//public interface ValidGroup extends Default {
// interface Crud extends ValidGroup {
// interface create extends Crud {
// }
//
// interface Update extends Crud {
// }
//
// interface Query extends Crud {
// }
//
// interface Delete extends Crud {
// }
// }
//}
创建一个ValidGroup继承Default接口,在里面写接口继承ValidGroup,一个接口即是一个校验组。
2.2 为各种实体类添加校验规则
package com.gmgx.entity;import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.gmgx.common.ValidGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;/*** <p>* 订单表* </p>** @since 2024-09-11*/
@Data
public class Orders implements Serializable {private static final long serialVersionUID = 1L;/*** 主键-订单号*/@NotBlank(groups = {ValidGroup.IdAndShopId.class,ValidGroup.IdAndCancelReason.class, ValidGroup.IdAndRejectCancelReason.class,ValidGroup.IdAndRejectionReason.class, ValidGroup.IdAndCancelReasonMember.class},message = "订单id不能为空")private String id;/*** 订单状态 1待付款,2待派送,3待取餐,4已派送,5已完成,6已取消,7外卖待审批,8堂食待审批*/private Integer status;/*** 下单用户*/private String memberId;/*** 地址id*/private String addressBookId;/*** 下单时间*/// @TableField(fill = FieldFill.INSERT)//新增时自动填充private Date orderTime;/*** 所属店铺*/@NotBlank(groups = {ValidGroup.IdAndShopId.class}, message = "shopId不能为空")private String shopId;/*** 结账时间*/private Date checkoutTime;/*** 支付方式 1微信,2支付宝*/private Integer payMethod;/*** 取餐码(四位数)*/private String pickupCode;/*** 实收金额*/private BigDecimal amount;/*** 备注*/private String remark;/*** 收件人手机号*/private String phone;/*** 收件地址*/private String address;/*** 收货人姓名*/private String consignee;/*** 0-外卖 1-堂食*/private String type;@Length(min = 1, max = 32, groups = ValidGroup.IdAndCancelReason.class,message = "管理后台取消订单的原因不能为空,且不能超过32个字")private String cancelReason;@Length(min = 1, max = 32, groups = ValidGroup.IdAndRejectionReason.class,message = "商家拒单的原因不能为空,且不能超过32个字")private String rejectionReason;@Length(min = 1, max = 32, groups = ValidGroup.IdAndCancelReasonMember.class,message = "用户取消订单的原因不能为空,且不能超过32个字")private String cancelReasonMember;@Length(min = 1, max = 32, groups = ValidGroup.IdAndRejectCancelReason.class,message = "商家拒绝用户取消订单的原因不能为空,且不能超过32个字")private String rejectCancelReason;
}
package com.gmgx.dto;import com.gmgx.common.ValidGroup;
import jakarta.validation.constraints.Size;
import lombok.Data;import java.math.BigDecimal;
import java.util.List;/*** 用户下单 需要传入的参数封装成一个dto*/
@Data
public class SubmitDto {//........省略没有校验规则的字段@Size(min = 1, message = "生成的订单必须至少含有一个商品", groups = ValidGroup.SubmitNoItems.class)private List<ItemDto> items;
}
package com.gmgx.dto;import com.fasterxml.jackson.annotation.JsonFormat;
import com.gmgx.common.ValidGroup;
import jakarta.validation.constraints.Past;
import lombok.Data;import java.util.Date;/*** 封装多字段查询需要的条件dto*/
@Data
public class OrderSearchDto {//开始时间@Past(groups = ValidGroup.ValidOrderSearchDto.class, message = "开始时间必须在当前时间之前")@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//东八区private Date startTime;//结束时间@Past(groups = ValidGroup.ValidOrderSearchDto.class, message = "结束时间必须在当前时间之前")@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date endTime;//..........}
2.3 在控制器中对需要校验参数的方法开启校验
package com.gmgx.controller;import com.gmgx.common.Result;
import com.gmgx.common.ValidGroup;
import com.gmgx.dto.SubmitDto;
import com.gmgx.vo.OrderVo;
import com.gmgx.dto.OrderSearchDto;
import com.gmgx.entity.Orders;
import com.gmgx.exceptions.enumeration.ResponseEnum;
import com.gmgx.service.OrdersService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;@Slf4j
//将@Validated写在类上,开启对所有方法的参数校验
@Validated
@RestController
@RequestMapping("/order")
@Tag(name = "订单控制器")
public class OrdersController {@Resourceprivate OrdersService ordersService;//管理后台/*** 取消订单(管理后台)** @param* @return 订单是否被取消的信息*/@Operation(summary = "后台取消订单", description = "需要传入id,cancelReason")@PutMapping("cancel")public Result<Object> cancel(@RequestBody @Validated(value = ValidGroup.IdAndCancelReason.class) Orders order) {Boolean flag = ordersService.cancel(order);if (flag) {return Result.success("取消订单成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 拒单** @return 订单状态是否被修改为已取消的信息*/@Operation(summary = "商家拒单", description = "需要传入id,rejectionReason")@PutMapping("rejection")public Result<Object> rejection(@RequestBody @Validated(value = ValidGroup.IdAndRejectionReason.class) Orders order) {Boolean flag = ordersService.rejection(order);if (flag) {return Result.success("拒单成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 商家拒绝用户取消订单*/@Operation(summary = "商家拒绝用户取消订单",description = "需要传入id,status(7外卖待审批,8堂食待审批),rejectionCancelReason")@PutMapping("rejectCancel")public Result<Object> rejectCancel(@RequestBody @Validated(ValidGroup.IdAndRejectCancelReason.class) Orders order) {Boolean flag = ordersService.rejectCancel(order);if (flag) {return Result.success("拒单成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 接单* 需要传 id type shopid shopid 用于redis 拿值** @return 订单状态是否被修改为已接单的信息*/@Operation(summary = "接单", description = "需要传入id,type,shopid")@PutMapping("confirm")public Result<Object> confirm(@RequestBody @Validated(ValidGroup.IdAndShopId.class) Orders order) {Boolean flag = ordersService.confirm(order);if (flag) {return Result.success("接单成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 派送订单** @param id* @return*/@Operation(summary = "派送订单", description = "需要传入id")@PutMapping("delivery/{id}")public Result<String> delivery(@PathVariable @NotBlank(message = "订单id不能为空") String id) {Boolean flag = ordersService.delivery(id);if (flag) {return Result.success("修改订单的状态为已派送,请求成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 完成订单** @param id* @return 订单状态是否被修改为已完成的信息*/@Operation(summary = "完成订单", description = "需要传入id")@PutMapping("complete/{id}")public Result<Object> complete(@PathVariable @NotBlank(message = "订单id不能为空") String id) {Boolean flag = ordersService.complete(id);if (flag) {return Result.success("修改订单的状态为完成,请求成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);}/*** 订单搜索** @return*/@Operation(summary = "订单搜索", description = "可选参数:开始,结束时间,订单状态。用户名、订单号、地址是模糊查询")@PostMapping("conditionSearch")//get请求没有请求体public Result<Object> conditionSearch(@RequestBody @Validated(value = ValidGroup.ValidOrderSearchDto.class) OrderSearchDto dto) {log.info("dto:{}", dto);List<Orders> orders = ordersService.conditionSearch(dto);if (orders.size() > 0) {return Result.success(orders);}return Result.error(ResponseEnum.CONDITION_SEARCH_ERROR);}/*** 查询订单详情** @param oId 这是订单id* @return*/@Operation(summary = "查询订单详情(管理端和客户端通用)", description = "传入参数 订单的id")@GetMapping("details/{oId}")public Result<Object> getDetails(@PathVariable @NotBlank(message = "订单id不能为空") String oId) {OrderVo order = ordersService.getDetails(oId);if (order != null) {return Result.success(order);}return Result.error(ResponseEnum.GET_DETAIL_ERROR);}}
第3步:添加拦截器,参数异常时返回统一结果
package com.gmgx.handle;import com.gmgx.common.Result;
import com.gmgx.exceptions.BusinessException;
import com.gmgx.exceptions.enumeration.ResponseEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.List;
import java.util.stream.Collectors;@Slf4j
@RestControllerAdvice
//捕获异常
public class CommonExceptionHandler {//校验参数@ExceptionHandlerpublic Result<List<String>> handleParametersException(MethodArgumentNotValidException e, BindingResult errors) {log.info("参数不合法");e.printStackTrace();List<String> collect = errors.getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());return Result.error(ResponseEnum.INVALID_PARAMETERS, collect);}@ExceptionHandler(Exception.class)public Result<Object> handleException(Exception e) {e.printStackTrace();return Result.error(e.getMessage());}@ExceptionHandler(BusinessException.class)public Result<Object> handleBusinessException(BusinessException e) {e.printStackTrace();ResponseEnum response = e.getResponse();// return Result.error(e.getMessage());return Result.error(response);}}
第4步:clean service 删掉.idea


因为idea有缓存,会导致校验规则不生效,所以要做这两步
测试




单个字段的参数校验可以在test里面测
@Operation(summary = "派送订单", description = "需要传入id")
@PutMapping("delivery/{id}")
public Result<String> delivery(@PathVariable @NotBlank(message = "订单id不能为空") String id) {Boolean flag = ordersService.delivery(id);if (flag) {return Result.success("修改订单的状态为已派送,请求成功");}return Result.error(ResponseEnum.INVALID_ORDER_STATUS_CHANGED);
}
@Test
void testDelivery_noOId(){ordersController.delivery("");
}
结果如下

相关文章:
Spring validation校验框架
第1步:导入依赖 <!-- 校验框架--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId> </dependency> 第2步:为需要校验的参数&…...
UBUNTU20.04安装CH384串口卡驱动
继续上文:统信UOS安装CH384串口卡驱动-CSDN博客 统信UOS系统成功安装CH384串口驱动后,继续在ubuntu20.04下安装驱动,发现一直报错,原因是内核驱动不一致。 解决办法: 1. 下载最新的驱动。CH35XCH384驱动源文件资源-C…...
JWT(JSON Web Tokens) 详细介绍
文章目录 什么是JWT?JWT的组成部分JWT的使用场景优点缺点 Java中如何实现JWT编解码引入JJWT依赖编码JWT解码JWT使用示例 什么是JWT? JWT(JSON Web Tokens)是一种用于双方之间安全传输信息的简洁的、URL安全的令牌标准。JWT可以传递信息,这…...
数据结构练习题————(二叉树)——考前必备合集!
今天在牛客网和力扣上带来了数据结构中二叉树的进阶练习题 1.二叉搜索树与双向链表———二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 2.二叉树遍历————二叉树遍历_牛客题霸_牛客网 (nowcoder.com) 3.二叉树的层序遍历————102. 二叉树的层序遍历 - 力扣&am…...
一天认识一个硬件之鼠标
今天来给大家分享一下鼠标的相关内容,先来分享一下什么是鼠标: 鼠标是一种计算机输入设备,用于控制屏幕上的光标移动和进行各种操作。它最早由道格拉斯恩格尔巴特在1963年发明,并在1968年12月9日制成了世界上第一个鼠标。介绍完了…...
Django 请求配置
http请求配置 请求流程 urls.py配置 from first_app import viewsurlpatterns [path(admin/, admin.site.urls),path(test/,views.first_test), ] views.py配置 from django.shortcuts import render,HttpResponse# Create your views here. def first_test(request):prin…...
轮播图组件更加完善版
依然是基于微博语法开发,使用时请注意标签替换 优化了滑动的效果,默认的索引,滑动距离等, 使用方式和以前一样没变,主要修改了组件内部 <template><wbx-view class"" style"width: 100vw;heig…...
cpu路、核、线程
路:主板插口实际插入的 CPU 个数,也可以理解为主板上支持的CPU的数量。每个CPU插槽可以插入一个物理处理器芯片。例如,一台服务器可能有2路或4路插槽,这意味着它最多可以安装2个或4个物理处理器。 核:单块 CPU 上面能…...
鸿蒙开发(NEXT/API 12)【硬件(注册出行业务事件监听)】车载系统
注册出行业务事件监听,用于接收业务发送事件的通知。 接口说明 接口名描述[on] (type: ‘smartMobilityEvent’, smartMobilityTypes: SmartMobilityType[],callback: Callback): void应用注册出行业务事件监听。 开发步骤 导入Car Kit模块。 import { smartMobi…...
安卓中有main函数吗?
在标准的Android应用程序开发中,并不直接使用类似于传统Java或C程序中的main函数入口点。Android应用程序是基于组件的架构,它依赖于几个关键组件来执行不同的任务,这些组件包括Activity、Service、Broadcast Receiver和Content Provider。 …...
js-17-对数组、对象进行浅拷贝和深拷贝
目录 数组一、浅拷贝1. 展开运算符...2. Array.prototype.slice() 二、深拷贝1. JSON方法2. 递归函数 对象一、浅拷贝1. Object.assign()2. 展开运算符... 二、深拷贝1. JSON方法2. 递归函数 自己总结的一些方法,可能有不到位的地方,欢迎指出 数组 一、…...
Ubuntu24.04中安装Electron
1. 安装Nodejs 使用代理服务从github下载并执行Nodejs安装脚本(假设代理服务器为192.168.2.150:10792) curl -x 192.168.2.150:10792 -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash #注意,Nodejs官网的安装命令少了下面这一行: …...
CPU中也应用到了缓存:CPU3层高速缓存,以及它的缓存一致性问题、MESI协议和Java的一些应用
为什么需要cpu高速缓存? 缓存,一般是为了用来协调两端的数据传输效率差(也可以归纳为性能差),提升响应速度。那么CPU的高速缓存是用来协调什么之间的速度差呢? CPU在处理一条指令的时候,会读写…...
如何使用开发者工具捕获鼠标右键点击事件
要使用浏览器的开发者工具捕获鼠标右键点击事件,请按照以下步骤操作: 打开开发者工具 在大多数浏览器中,您可以按 F12 键或右键单击页面并选择"检查"或"检查元素"。 切换到 Console 标签页 在开发者工具中,找到并点击 “Console” 标签。 添加事件监听器…...
【几何】个人练习-Leetcode-1453. Maximum Number of Darts Inside of a Circular Dartboard
题目链接:https://leetcode.cn/problems/maximum-number-of-darts-inside-of-a-circular-dartboard/description/ 题目大意:给出一系列点和一个圆的半径,(寻找一个圆心)求这个半径的圆最多能覆盖多少个点。 思路&…...
啤酒:从饮品到文化的演变
在人类饮品的长河中,啤酒以其不同的魅力走过了一段漫长的历史旅程。它不仅仅是一种简单的饮品,更是一种文化的象征,一种生活的态度。今天,我们将一起追溯啤酒从单一的饮品到丰富文化的演变过程,并感受Fendi Club精酿啤…...
Java 中 Map 常用类和数据结构详解
1. 引言 在Java编程中,Map是一种重要的数据结构,广泛应用于各种场景,Map实现了键值对(key-value)的存储方式,使得开发者能够快速检索、更新和操作数据。本篇文章将详细讲解Java中常用的Map类及其底层数据结…...
实时监控,动态调整 —— 淘宝商品详情API助力商家实现灵活经营
在讨论实时监控和动态调整的策略时,虽然我不能直接提供淘宝商品详情API的具体代码(因为这通常涉及商业机密和API密钥等敏感信息),但我可以给出一个概念性的示例,说明如何使用这类API来辅助商家实现灵活经营。 概念性示…...
WebGL常用接口和事件
目录 初始化WebGL上下文清除缓冲区缓冲区对象着色器和程序属性指针渲染事件监听错误处理纹理映射...
Golang | Leetcode Golang题解之第429题N叉树的层序遍历
题目: 题解: func levelOrder(root *Node) (ans [][]int) {if root nil {return}q : []*Node{root}for q ! nil {level : []int{}tmp : qq nilfor _, node : range tmp {level append(level, node.Val)q append(q, node.Children...)}ans append(a…...
像素幻梦·创意工坊实操手册:实时HUD状态栏信息读取与调试技巧
像素幻梦创意工坊实操手册:实时HUD状态栏信息读取与调试技巧 1. 认识像素幻梦的HUD状态栏 像素幻梦创意工坊的HUD(Head-Up Display)状态栏位于界面顶部,采用16-bit像素风格设计,为创作者提供实时系统状态反馈。这个看…...
突破跨平台壁垒:Whisky让macOS高效运行Windows程序的颠覆性方案
突破跨平台壁垒:Whisky让macOS高效运行Windows程序的颠覆性方案 【免费下载链接】Whisky A modern Wine wrapper for macOS built with SwiftUI 项目地址: https://gitcode.com/gh_mirrors/wh/Whisky 作为一名独立游戏开发者,李明曾因Mac无法运行…...
【数字逻辑】实战解析:从PLD到FPGA的演进与应用场景
1. 可编程逻辑器件的技术演进之路 第一次接触可编程逻辑器件是在大学实验室里,当时看着老师用一个小芯片就实现了整个数字钟的功能,完全颠覆了我对传统电路板的认知。这种"魔术般"的芯片就是PLD(可编程逻辑器件)&#…...
网络异常排查:快速定位域连接问题
问题描述与初步排查网络位置异常通常表现为计算机无法正确识别当前所在的AD域环境,导致访问域资源受限或登录问题。常见症状包括系统托盘显示“无法访问域”、组策略无法应用、DNS解析失败等。检查计算机是否能够ping通域控制器的主机名和IP地址。使用nslookup命令验…...
【OFDM通信】室内NOMA-OFDM-VLC系统仿真【含Matlab源码 15240期】
💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…...
建筑工地AI监控避坑指南:YOLOv11+PyQt5开发中的7个常见错误
建筑工地AI监控避坑指南:YOLOv11PyQt5开发中的7个常见错误 在建筑工地安全监控领域,AI技术的应用正从概念验证走向规模化落地。YOLOv11作为目标检测领域的新锐算法,配合PyQt5的灵活界面开发能力,确实能构建出高效的安全预警系统。…...
协议数采网关在智慧水务场景中的应用与功能
水资源管理作为生态文明建设的关键组成部分,其重要性不言而喻。在智慧水务建设不断深化的当下,水质监测、水量调度以及设备运维等各个环节,都对智能化水平提出了更为严苛的要求。然而,当前水务行业面临着诸多难题,监测…...
自编码器在异常检测中的实战应用:以金融交易数据为例
自编码器在金融异常检测中的实战指南:从数据清洗到模型部署 金融交易数据中的异常行为检测一直是风险控制的核心环节。传统基于规则的系统难以应对日益复杂的欺诈模式,而自编码器这类无监督学习模型正在改变游戏规则。本文将带您从零构建一个完整的异常检…...
别再只生成exe了:用MSFvenom制作更隐蔽的Windows 11后门(附检测与清除)
Windows 11高级渗透测试:从隐蔽后门构建到防御检测实战 在网络安全攻防演练中,传统的可执行文件Payload已经难以绕过现代终端防护系统。随着Windows 11安全机制的持续强化,红队需要掌握更隐蔽的渗透技术,而蓝队则必须了解这些新型…...
别再让DeepSeek-R1的<think>标签刷屏了!手把手教你用API和Python脚本一键隐藏思考过程
高效隐藏DeepSeek-R1思考过程的工程实践 当你在深夜调试一个集成DeepSeek-R1的客服系统时,终端突然被满屏的<think>标签刷爆——这种场景对开发者来说再熟悉不过了。作为一款强调推理过程的大语言模型,DeepSeek-R1默认会在输出中包含详细的思考步骤…...
