Spring validation校验框架

news2024/11/16 20:52:26

第1步:导入依赖

<!--        校验框架-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

第2步:为需要校验的参数(实体类或方法中)加上校验规则

常见的校验注解

  1. @NotNull:确保字段不是null。适用于任何类型。
  2. @NotEmpty:确保字段既不是null也不是空(对于字符串意味着长度大于0,对于集合意味着至少包含一个元素)。适用于字符串、集合、数组等。
  3. @NotBlank:确保字符串字段不是null且至少包含一个非空白字符。只能用于字符串类型。
  4. @Length:字符串长度 max是字符串的最大长度 min是字符串的最小长度(对比于@Size,只能用于字符串)
  5. @Size(min=, max=):确保字段(字符串、集合、数组)的长度(或大小)在指定的最小值和最大值之间。适用于字符串、集合、数组等。
  6. @Min(value=)@Max(value=):对数值类型字段设置最小值和最大值。适用于数值类型(Integer、Long等)。
  7. @Range :可以应用于整型(int、long等)和浮点型(float、double等)。校验数字的大小,min是数字最小值,max是数字的最大值
  8. @Email:确保字段是有效的电子邮件地址。适用于字符串类型。
  9. @Pattern(regexp=):确保字符串字段匹配指定的正则表达式。适用于字符串类型。
  10. @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 {
    @Resource
    private 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 {

    //校验参数
    @ExceptionHandler
    public 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("");
}

结果如下

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

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

相关文章

Leetcode 739.42. 每日温度 接雨水 单调栈 C++实现

问题&#xff1a;Leetcode 739. 每日温度 算法1&#xff1a;从右到左 栈中记录下一个更大元素的「候选项」。 代码&#xff1a; class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {int n temperatures.size();vector<…

【echarts】报错series.render is required.

总结&#xff1a;就是echarts无法保存renderItem函数到json里&#xff0c;因为renderItem是个封装方法&#xff0c;因此需要初始化加载时重新插入renderItem即可 1.描述&#xff1a;控制台报错series.render is required. 原数据json如下&#xff1a; {type: "bar"…

【数学分析笔记】第3章第4节闭区间上的连续函数(2)

3. 函数极限与连续函数 3.4 闭区间上的连续函数 3.4.4 中间值定理 【定理3.4.4】若 f ( x ) f(x) f(x)在 [ a , b ] [a,b] [a,b]上连续&#xff0c;则它一定能取到最大值 M M M与最小值 m m m之间的任何一个值。 M max ⁡ f ( x ) , x ∈ [ a , b ] , m min ⁡ f ( x ) , …

Vmware 静态ip配置

虚拟机网络设置NAT 查看当前的网络接口 ip addr编辑网络接口配置文件 sudo vi /etc/sysconfig/network-scripts/ifcfg-<接口名>配置静态 IP 地址 Vmware ➡ 编辑 ➡ 虚拟网络编辑器 ➡ Nat设置 参考上图进行配置&#xff0c;千万不要配置宿主机的配置 BOOTPROTOstat…

2023_Spark_实验十一:RDD基础算子操作

一、RDD的练习可以使用两种方式 使用Shell使用IDEA 二、使用Shell练习RDD 当你打开 Spark 的交互式命令行界面&#xff08;也就是 Spark shell&#xff09;的时候&#xff0c;它已经自动为你准备好了一个叫做 sc 的特殊对象&#xff0c;这个对象是用来和 Spark 集群沟通的。你…

女性在网络安全行业崛起,引领行业新风向

1、网络安全自诞生之日起&#xff0c;就与女性有着不解之缘。 ●二战期间&#xff0c;美国雇佣了1万名女性作为“密码女孩”来破译日本人和德国人发送的密信。 ●英国同样雇用了7000多名女性在英国密码分析中心工作&#xff0c;约占全部工作人员的四分之三。 ●世界上的第一…

108.游戏安全项目:信息显示二-剑侠情缘基址分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信…

spring-boot web + vue

依赖的软件 maven 1. 官网下载zip 文件&#xff0c;比如apache-maven-3.9.9-bin.zip 2. 解压到某个盘符&#xff0c;必须保证父亲目录的名字包含英文&#xff0c;数字&#xff0c;破折号&#xff08;-&#xff09; 3. 设置环境变量M2_HOME, 并将%M2_HOME%\bin添加到windown…

openpnp - 散料飞达不要想着做万能版本,能够贴合现有的物料就好

文章目录 openpnp - 散料飞达不要想着做万能版本&#xff0c;能够贴合现有的物料就好概述笔记天真的版本改进的版本物料编带标准物料编带的样式对于散料飞达关心的尺寸不同编带宽度的散料飞达关键尺寸的列表8mm物料编带12mm物料编带16mm物料编带24mm物料编带32mm物料编带44mm物…

【Linux】环境变量(初步认识环境变量)

文章目录 1. 环境变量1.1 基本概念 2. 认识常见环境变量2.1 PATH2.2 HOME2.3 SHELL2.4 PWD2.5 USER 3. 理解环境变量 1. 环境变量 在main函数的命令行参数中&#xff0c;有argc、argv、env三个参数。 argc&#xff1a;命令函参数的个数argc&#xff1a;存放每个参数的具体数值…

FPGA学习(1)-mux2,2选1多路器

目录 1 开发板配套资料 1.1学习网址和资料网址 2.创建工程文件 2.1创建过程 2.2写程序及仿真测试 2.2.1 写程序生成电路 2.2.2仿真 2.2.3 生成执行文件并烧录 3.实验现象 买的小梅哥店铺的开发板&#xff1a;xc7z020clg400 看的小梅哥的视频&#xff1a;03C _基于ZYN…

提取出散射矩阵归一化相位的含义

散射矩阵的值是从图像中获得的&#xff0c;相位角是距离导致的&#xff0c;所以要归一化&#xff0c;VV/HH VV幅度/HH幅度。 VV相位-HH相位

Java-数据结构-Map与Set-(一) ٩(๑>◡<๑)۶

文本目录&#xff1a; ❄️一、搜索树&#xff1a; ☑ 1、概念&#xff1a; ☑ 2、操作-插入&#xff1a; 代码&#xff1a; ☑ 3、操作-查看&#xff1a; 代码&#xff1a; ☑ 4、操作-删除&#xff1a; 代码&#xff1a; ☑ 5、性能分析&#xff1a; ❄️二、搜索&#…

如何在Ubuntu上查看和刷新DNS缓存

DNS缓存是用于DNS查找的临时存储系统&#xff0c;负责将域名转换为IP地址。进行DNS查询时&#xff0c;系统会检查缓存中的相关信息。如果找到了&#xff0c;那么它会加速域名解析的过程。如果DNS缓存中的数据过时或不正确&#xff0c;则需刷新它以确保使用正确的信息。本文主要…

自己掏耳朵怎么弄干净?双十一必买的四大可视挖耳勺分享

我们在掏耳朵时是不是老是觉得要么掏不干净&#xff0c;要么太进去了弄到痛耳朵。因为耳道属于我们一个盲区&#xff0c;在使用棉签或者普通耳勺容易因为操作不当弄伤耳膜。可能还会照成不可逆的后果。所以自己在掏耳勺更加推荐大家使用可视挖耳勺会更加干净和安全。那么&#…

【MATLAB代码】二维环境下的RSSI定位程序,自适应锚点数量,带图像输出、坐标输出、中文注释

程序描述 MATLAB编写的RSSI定位程序&#xff0c;自适应锚点数量&#xff0c;带图像输出、坐标输出、中文注释。 功能概述&#xff1a; 本程序实现了在二维平面上通过接收信号强度指示&#xff08;RSSI&#xff09;进行定位的功能。它使用多个锚节点的信号强度测量来估计未知…

CSS链接

链接是网站的重要组成部分&#xff0c;几乎在每个网页上都能看到不少的链接&#xff0c;合理的设计链接的样式能够给网页的颜值加分。链接有四种不同的状态&#xff0c;分别是 link、visited、active 和 hover&#xff0c;可以通过以下伪类选择器来为链接的四种状态设置不同的样…

CentOS8使用chrony同步网络时间

文章目录 引言I CentOS8使用chrony网络时间同步安装chrony配置间同步服务器地址检查本机的时区设置时区chronyc命令II windows网络时间同步2.1 修改同步服务器2.2 修改同步频率引言 应用场景: 获取服务器时间进行船舶在线率统计 dtos.forEach(item -> {if(item.getDwtime(…

红绿灯倒计时读秒数字识别系统源码分享

红绿灯倒计时读秒数字识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

数据结构——初识树和二叉树

线性结构是一对一的关系&#xff0c;意思就是只有唯一的前驱和唯一的后继&#xff1b; 非线性结构&#xff0c;如树形结构&#xff0c;它可以有多个后继&#xff0c;但只有一个前驱&#xff1b;图形结构&#xff0c;它可以有多个前驱&#xff0c;也可以有多个后继。 树的定义…