SpringBoot参数校验入门

news2024/11/27 16:39:23

一、添加依赖

<!--参数校验-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

二、校验注解分类

1.空和非空检查

@NotBlank:只能用于字符串不为null和"",并且字符串调用trim()方法后的length要大于0。

@NotNull:不能为null。

@Null:必须为null。

@NotEmpty:集合对象元素不能为0,集合不能为空。

2.数值检查

@DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值。

@DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值。

@Digits(integer,fraction):被注释的元素必须是一个数字,其值必须在可接受的范围内。

@Positive:被注释的元素必须是正数。

@PositiveOrZero:被注释的元素必须是正数或0。

@Negative:被注释的元素必须是负数。

@NegativeOrZero:被注释的元素必须是负数或0。

@Max(value):被注释的元素的值只能小于或等于该值。

@Min(value):被注释的元素的值只能大于或等于该值。

@Range(min,max):被注释的元素必须在min和max之间。

@Length(min,max): 被注释的字符串的长度必须在min和max之间。

3.Boolean 值检查

@AssertTrue:被注释的元素必须为true。

@AssertFalse:被注释的元素必须为false。

4.长度检查

@Size(min,max):判断字符串、集合、数组、Map的长度是否在min和max之间。

5.日期检查

@Past:被注释的元素的日期必须是过去的日期。

@PathOrPresent:被注释的元素的日期必须是过去或现在的日期。

@Future:被注释的元素的日期必须是将来的日期。

@FutureOrPresent:被注释的元素的日期必须是将来或现在的日期。

6.其他

@URL:判断被注释的字符串必须是一个有效的URL。

@SafeHtml:判断提交的HTML是否安全。

三、@Valid和@Validated的区别

@Valid:可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。支持嵌套校验,不支持分组校验。

@Validated:可以添加在类、方法参数、普通方法上,不能用在成员变量上。不支持嵌套校验,支持分组校验。

总的来说,绝大多数场景下,我们使用 @Validated 注解即可。

四、普通测试

1.创建一个用于测试的实体类

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 用户登录请求类
 */
@Data
public class UserLoginRequest {

    /**
     * 账号
     */
    @NotBlank(message = "账号不能为空")
    @Length(min = 2, max = 20, message = "账号的长度为2-20位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号的格式为大小写字母和数字")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码的长度为4-16位")
    private String password;

}

2.创建测试控制器

import com.example.quartzdemo.request.UserLoginRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 测试
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {


    /**
     * 登录
     *
     * @param userLoginRequest 使用@Validated校验输入项
     */
    @PostMapping("/login")
    public String login(@Validated UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "登录验证通过";
    }
}

3.启动程序,进行测试

我们在postman上进行接口的测试

 没有传入password参数,错误异常提示了密码不能为空。

 我们传入的username不是大小写字母或数字,错误异常日志提示了账号格式为大小写字母或数字。

 我们正确填写了账号和密码,验证通过。

五、分组校验测试

1.我们先创建两个分组接口,一个用于新增一个用于修改。

因为新增和修改的需要的参数不一定相同,比如新增的时候不需要ID,但是修改的时候必须要有ID参数,所以需要使用分组来实现我们的需求。

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 新增用户分组
 */
public interface AddUserGroup {
}
/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 修改用户分组
 */
public interface UpdateUserGroup {
}

2.修改用户实体,在实体类中加上新增和修改的分组

import com.example.quartzdemo.interfaces.AddUserGroup;
import com.example.quartzdemo.interfaces.UpdateUserGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 用户登录请求类
 */
@Data
public class UserLoginRequest {


    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空", groups = UpdateUserGroup.class)
    private Long id;

    /**
     * 账号
     */
    @NotBlank(message = "账号不能为空", groups = {AddUserGroup.class, UpdateUserGroup.class})
    @Length(min = 2, max = 20, message = "账号的长度为2-20位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号的格式为大小写字母和数字")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空", groups = {AddUserGroup.class, UpdateUserGroup.class})
    @Length(min = 4, max = 16, message = "密码的长度为4-16位")
    private String password;

}

我们设置ID只能是修改的时候才会判断是否存在,账号和密码在新增和修改的时候都要判断存在。

3.在控制器中测试

import com.example.quartzdemo.interfaces.AddUserGroup;
import com.example.quartzdemo.interfaces.UpdateUserGroup;
import com.example.quartzdemo.request.UserLoginRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 测试
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {


    /**
     * 登录
     *
     * @param userLoginRequest 使用@Validated校验输入项
     */
    @PostMapping("/login")
    public String login(@Validated UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "登录验证通过";
    }

    /**
     * 新增用户
     *
     * @param userLoginRequest 使用@Validated校验输入项 校验的注解中加上了新增分组的参数
     */
    @PostMapping("/add")
    public String add(@Validated(AddUserGroup.class) UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "新增用户验证通过";
    }

    /**
     * 修改用户
     *
     * @param userLoginRequest 使用@Validated校验输入项 校验的注解中加上了修改分组的参数
     */
    @PostMapping("/update")
    public String update(@Validated(UpdateUserGroup.class) UserLoginRequest userLoginRequest) {
        log.info("userLoginRequest:{}", userLoginRequest);
        return "修改用户验证通过";
    }


}

我们继续在postman上进行接口的测试

 可以看到我们在新增的时候没有传入ID参数校验通过了。

接下来我们现在调试修改的接口,没有传递ID参数。

 我们在调试修改接口的时候,没有传递ID参数,验证不能通过了。

最后我们加上ID参数,验证通过。

 六、错误提示的友好处理。

1.创建校验不通过的枚举类

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 业务层异常枚举
 */
public enum ServiceExceptionEnum {
    SUCCESS(0, "成功"),
    ERROR(1, "失败"),
    SYS_ERROR(1000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(1001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(1002, "请求参数不合法");

    private final String message;

    private final int code;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public int getCode() {
        return code;
    }
}

2.统一返回结果实体类

package com.example.quartzdemo.common;

import com.example.quartzdemo.enums.ServiceExceptionEnum;

import java.io.Serializable;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 统一返回结果实体类
 */
public class CommonResult<T> implements Serializable {


    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(ServiceExceptionEnum.SUCCESS.getCode());
        commonResult.setMessage(ServiceExceptionEnum.SUCCESS.getMessage());
        commonResult.setData(data);
        return commonResult;
    }

    /**
     * 失败
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(String message) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(ServiceExceptionEnum.ERROR.getCode());
        commonResult.setMessage(message);
        return commonResult;
    }

    /**
     * 失败
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(int code, String message) {
        CommonResult<T> commonResult = new CommonResult<>();
        commonResult.setCode(code);
        commonResult.setMessage(message);
        return commonResult;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

3.创建全局异常处理类

package com.example.quartzdemo.exception;

import com.example.quartzdemo.common.CommonResult;
import com.example.quartzdemo.enums.ServiceExceptionEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

/**
 * @author qinxun
 * @date 2023-06-14
 * @Descripion: 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理 MissingServletRequestParameterException 异常
     * <p>
     * SpringMVC 参数不正确
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
        log.error("[missingServletRequestParameterExceptionHandler]", ex);
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
        log.error("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
        log.info("========进入了 bindException======");
        log.error("[bindExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
        log.info("-----------------进入了 MethodArgumentNotValidException-----------------");
        log.error("[MethodArgumentNotValidException]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }


    /**
     * 处理其它 Exception 异常
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
        // 记录异常日志
        log.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }
}

我们重新做postman上进行调试

 我们可以看到错误异常的的提示很友好了。

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

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

相关文章

few-shot object counting论文汇总

文章目录 2021OBJECT COUNTING: YOU ONLY NEED TO LOOK AT ONE 2022CounTR: Transformer-based Generalised Visual CountingFew-shot Object Counting with Similarity-Aware Feature Enhancement 2023CAN SAM COUNT ANYTHING? AN EMPIRICAL STUDY ON SAM COUNTING 2021 OBJ…

【MSP432电机驱动学习】TB6612带稳压电机驱动模块、MG310电机、13线霍尔编码器

所用控制板型号&#xff1a;MSP432P401r 今日终于得以继续我的电赛小车速通之路&#xff1a; 苏轼云 “ 素面常嫌粉涴 &#xff0c; 洗妆不褪朱红。 ” 这告诫我们不能只注重在表面粉饰虚伪的自己&#xff0c;要像梅花一样&#xff0c;不断磨砺自己的内在~ 后半句是 “…

广告经济学与垄断竞争分析

产品与广告 产品的分类&#xff1a; 搜寻品&#xff1a;消费者在购买商品之前就可以知道其特征的产品经验品&#xff1a;只能够在使用后才能确认其特征的产品信任品&#xff1a;产品的质量即使在消费之后仍然不能确定&#xff0c;例如医学和法律服务 广告的分类&#xff1a;…

【C++】在线编译器推荐,让你随时随地编写代码

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 支持调试网站Repl.itOnlineGDB 2️⃣ 不支持调试网站Wandboxjson.cnjdoodletutorialspointcppshellideonecoliruonline-ide 3️⃣ 性能分析网站Quick C BenchmarkCompare C Builds 4️⃣ 其它C Insights&#xff08;学习模板、C11…

Java阶段四Day04

Java阶段四Day04 文章目录 Java阶段四Day04关于SLF4j日志框架使用Sql注解关于DAO架构关于Service关于异常 关于SLF4j日志框架 在开发实践中&#xff0c;通常禁止使用System.out.println()这种语句输出信息&#xff0c;主要原因有&#xff1a; 输出效率低下&#xff0c;特别是字…

A fight among three “三国”混战 | 经济学人20230520版社论双语精翻

《经济学人》2023年5月20日封面&#xff08;社论&#xff09;文章精翻&#xff1a;《全球支付系统的“三国”混战》&#xff08;A fight among three&#xff09; A fight among three “三国”混战 The fight over the future of global payments 全球支付的未来之争 Digital …

函数重载分析

函数重载 (Function Overload) 用同一个函数名定义不同的函数 当函数名和不同的参数搭配时函数的含义不同 函数重载至少满足下面的一个条件&#xff1a; 参数个数不同 参数类型不同 参数顺序不同 上面的两个函数可以构成重载函数吗&#xff1f; 当默认参数遇上函数重载会发…

分布式系统概念和设计——分布式多媒体系统

分布式系统概念和设计 分布式多媒体系统 QoS QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;是一种网络管理和控制技术&#xff0c;可以对网络流量进行管理和调度&#xff0c;以确保网络对不同类型的数据流的传输可以提供适当的带宽、传输延迟、抖动和可…

《交通规划》——最短路分配方法

《交通规划》——最短路分配方法 说明&#xff1a;下面内容&#xff0c;将用python、networkx实现刘博航、杜胜品主编的《交通规划》P198页的例题&#xff0c;主要是实现最短路径分配方法。 1. 题目描述如下&#xff1a; 2. networkx构建网络 import networkx as nx import …

Spring6 面向切面(AOP)

文章目录 1、场景模拟1.1、声明接口1.2、创建实现类1.3、创建带日志功能的实现类1.4、隐藏问题 2、代理模式2.1、概念2.2、静态代理2.3、动态代理2.4、测试 3、AOP概念及相关术语3.1、概述3.2、相关术语①横切关注点②通知&#xff08;增强&#xff09;③切面④目标⑤代理⑥连接…

跨模态检索论文阅读:Dissecting Deep Metric Learning Losses for Image-Text Retrieval(GOAL)

Dissecting Deep Metric Learning Losses for Image-Text Retrieval 剖析图像文本检索中的深度度量学习损失 2022.10 视觉语义嵌入&#xff08;VSE&#xff09;是图像-文本检索中的一种流行的应用方法&#xff0c;它通过学习图像和语言模式之间的联合嵌入空间来保留语义的相似性…

2023.06.14 QT day3

用QT实现的简单文本编辑器 头文件widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QTextEdit> #include <QFont> #include <QFontDialog> #include <QColor> #include <QColorDialo…

C语言符合类型之结构篇(结构指针)

结构相关知识总结 什么是结构&#xff1f;结构的声明与简单使用结构的初始化结构中成员变量的访问结构的初始化器结构数组结构数组的声明结构数组的成员标识 结构的嵌套结构指针结构作为参数在函数中传递将结构成员作为参数进行传递将结构地址(指向结构的指针)作为参数进行传递…

leetcode 279.完全平方数

题目描述 给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是完全平方数&#xff0c;而 3 和 11 …

cmu db p4

在lab4中实现一个基于 2PL 的并发控制方式&#xff0c;自动为并发事务执行加锁解锁&#xff0c;提供可串行化能力并实现可重复读、读已提交、读未提交三种隔离度 Lock Manager&#xff1a;锁管理器&#xff0c;利用 2PL 实现并发控制。支持 REPEATABLE_READ、READ_COMMITTED 和…

我五年经验,面试官: “不,你是把一年的工作经验用了五年”

最近看到很多软件测试由于公司裁员而需要重新求职的。他们普遍具有4年甚至更长的工作经验。但求职结果往往都不太理想。 我在与部分软件测试求职者交谈的过程中发现&#xff0c;很多人的工作思路不清晰&#xff0c;技能不扎实&#xff0c;没有持续学习的习惯&#xff0c;但对于…

编程(41) ----------线程池

本篇主要提及线程池的相关内容. 依旧是从最基础的含义开始. 什么是线程池? 在计算机中池的是一个很大的概念, 分为很多种. 但无论是什么池, 其核心都是存取相关数据. 线程池也不例外, 即存放线程的池. 其存在意义与线程异曲同工. 线程产生并使用是因为进程太"重"了…

Andriod开发 ViewPager PageTabStrip

1. ViewPager ViewPager常见于APP的引导页或者产品介绍&#xff0c;左右滑动展示不同页面。 ViewPager用PagerAdapter来绑定数据。PagerAdapter是个抽象类&#xff0c;所以需要写一个子类来实现它。 xml: <?xml version"1.0" encoding"utf-8"?> …

设计模式(十五):行为型之命令模式

设计模式系列文章 设计模式(一)&#xff1a;创建型之单例模式 设计模式(二、三)&#xff1a;创建型之工厂方法和抽象工厂模式 设计模式(四)&#xff1a;创建型之原型模式 设计模式(五)&#xff1a;创建型之建造者模式 设计模式(六)&#xff1a;结构型之代理模式 设计模式…

I2C与I3C

Improved Inter Integrated Circuit &#xff0c;是 MIPI&#xff08;Mobile Industry Processor Interface&#xff09;移动产业处理器接口联盟推出的改进型 i2c (Inter-Integrated Circuit Bus)总线接口。 I3C Introduction I3C &#xff1a;Improved Inter Integrated Cir…