SpringBoot 参数验证的几种方式

文章目录

  • SpringBoot 参数验证
    • 1、为什么要进行参数验证
    • 2、验证方式
      • 2.1 if 语句判断
      • 2.2 Assert
      • 2.3 Validator
        • 2.3.1 引入依赖
        • 2.3.2 定义参数实体类
        • 2.3.4 定义特定异常全局拦截方法
        • 2.3.5 定义校验类进行测试
        • 2.3.6 测试
      • 2.4 自定义验证注解
        • 2.4.1 定义自定义注解
        • 2.4.2 定义自定义验证器类
        • 2.4.3 应用自定义注解

SpringBoot 参数验证

1、为什么要进行参数验证

在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数进行校验,例如登录的时候需要校验用户名和密码是否为空,添加用户的时候校验用户邮箱地址、手机号码格式是否正确。 靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。

进行参数验证是软件开发中的一个重要环节,其主要原因包括但不限于以下几点:

  1. 数据完整性与准确性:确保接收到的数据是完整且准确的,避免因错误或恶意的数据输入导致系统异常或数据损坏。
  2. 安全防护:防止注入攻击(如SQL注入)、跨站脚本攻击(XSS)等安全威胁,通过验证可以过滤掉非法输入,增强系统安全性。
  3. 性能优化:提前验证参数可以减少不必要的数据库查询或业务逻辑执行,从而提升系统整体性能。
  4. 用户体验:及时向用户提供清晰的错误提示,指导他们正确输入信息,避免提交表单后才告知错误,提高了用户体验。
  5. 代码可维护性:集中处理参数验证逻辑,使得业务逻辑代码更加清晰,易于理解和维护。
  6. 遵循最佳实践:参数验证是编程和Web开发中的一个基本最佳实践,遵循这些原则可以减少错误和漏洞,提升软件质量。
  7. 减少异常处理:通过前端和后端的双重验证,可以减少运行时异常的发生,使得程序更加稳定可靠。
  8. 合规性:对于涉及用户隐私或敏感信息的应用,参数验证也是遵守数据保护法规(如GDPR)的一个重要方面。

因此,参数验证是构建高质量、安全、易用的应用程序不可或缺的一环。

2、验证方式

2.1 if 语句判断

    @PostMapping("/parameterCheck")
    @Operation(summary = "参数校验", description = "嵌套参数校验-测试")
    public CommonResult<TestDto> parameterCheck(@RequestBody TestDto dto) {
        if (dto == null){
            throw new RuntimeException("参数不能为空");
        }
        return CommonResult.SUCCESS(dto);
    }

缺点

  1. 代码可读性和维护性降低:当if条件复杂或嵌套层次过多时,代码可读性大大降低,使得维护和理解代码变得更加困难。开发者可能需要花费更多时间去梳理逻辑关系。
  2. 容易出错:复杂的if条件判断容易出现逻辑错误,比如漏写某个条件分支,或条件判断逻辑失误,导致程序行为不符合预期。
  3. 测试难度增加:if语句尤其是嵌套和多重if的情况下,会生成多个代码路径,这意味着需要编写更多的测试用例来覆盖所有可能的执行路径,增加了测试的复杂度和成本。
  4. 性能影响:虽然现代编译器会对代码进行优化,但在某些情况下,特别是深度嵌套或大量if判断时,可能会对程序的执行效率产生负面影响,尤其是在循环内部或者高频调用的代码块中。
  5. 扩展性差:随着需求变化,频繁修改或增减if条件会使代码结构变得混乱,不利于后期的扩展和修改。
  6. 难以调试:当if逻辑出错时,定位问题可能比较困难,特别是在没有明确错误信息或日志记录的情况下。

因此,在设计代码时,推荐采用诸如策略模式、状态模式等设计模式来替代复杂的if判断,或者使用Switch语句(在适用的情况下)来提高代码的清晰度和可维护性。同时,也可以考虑利用函数式编程的思想,将逻辑分解为更小的、可重用的函数,以提高代码的模块化程度。

2.2 Assert

    @PostMapping("/parameterCheck")
    @Operation(summary = "参数校验", description = "嵌套参数校验-测试")
    public CommonResult<TestDto> parameterCheck(@RequestBody TestDto dto) {
        Assert.isNull(dto.getName(), "姓名不能为空");
        Assert.isNull(dto.getSex(), "性别不能为空");
        return CommonResult.SUCCESS(dto);
    }

使用Assert语句进行参数校验在Java等编程语言中较为常见,尤其是在单元测试中用于验证预期结果。然而,在生产代码中过度依赖Assert进行参数校验也存在一些缺点:

  1. 非异常处理机制Assert主要用于开发阶段的自我检查,它抛出的是AssertionError,这是一种错误而非异常。在默认的Java虚拟机设置下,生产环境通常不启用断言(即-ea标志未设置),这意味着断言不会执行,从而无法起到参数校验的作用。
  2. 用户体验不佳:即便在启用了断言的环境中,AssertionError通常是直接终止程序的,没有被捕获和处理的机制,这会导致程序突然崩溃,给用户带来不友好的体验。
  3. 缺乏灵活性Assert主要用于验证程序内部不变性条件,其信息更多服务于开发者调试,而不能提供丰富的错误信息反馈或自定义错误处理逻辑。
  4. 不利于维护和调试:由于Assert在生产环境中默认不启用,可能导致某些错误在开发阶段未被发现,而在生产环境中因为不同的配置导致问题浮现,增加了问题排查的难度。
  5. 不适用于所有类型的应用程序:对于要求高稳定性和错误处理逻辑复杂的应用,直接使用Assert进行参数校验并不合适,因为它缺乏控制异常流和提供恢复机制的能力。

2.3 Validator

Validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;Validator专门用来进行接口参数校验,例如常见的必填校验,email格式校验,用户名必须位于6到12之间等等。

2.3.1 引入依赖

注意:如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖。我这里使用的SpringBoot版本是3.0.0,因此手动引入了。

        <!-- 如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
2.3.2 定义参数实体类

常见的约束注解如下:

注解功能
@AssertFalse可以为null,如果不为null的话必须为false
@AssertTrue可以为null,如果不为null的话必须为true
@DecimalMax设置不能超过最大值
@DecimalMin设置不能超过最小值
@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future日期必须在当前日期的未来
@Past日期必须在当前日期的过去
@Max最大不得超过此最大值
@Min最大不得小于此最小值
@NotNull不能为null,可以是空
@Null必须为null
@Pattern必须满足指定的正则表达式
@Size集合、数组、map等的size()值必须在指定范围内
@Email必须是email格式
@Length长度必须在指定范围内
@NotBlank字符串不能为null,字符串trim()后也不能等于""
@NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于""
@Range值必须在指定范围内
@URL必须是一个URL
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDto {

    @NotBlank(message = "姓名不能为空")
    @Schema(description = "姓名")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Schema(description = "年龄")
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 200, message = "年龄不能大于200")
    private Integer age;

    //性别只允许为男或女
    @NotBlank(message = "性别不能为空")
    @Pattern(regexp = "^(男|女)$", message = "性别必须为'男'或'女'")
    @Schema(description = "性别")
    private String sex;

    @Valid
    @Schema(description = "嵌套对象")
    private TestDtoObj testDtoObj;
}
import com.example.demo.annotation.PhoneNumber;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {

    @PhoneNumber
    @NotBlank(message = "手机号1不能为空")
    @Schema(description = "手机号1")
    private String phone1;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")
    @NotBlank(message = "手机号不能为空")
    @Schema(description = "手机号2")
    private String phone2;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 16, message = "密码长度必须在6到16个字符之间")
    @Schema(description = "密码")
    private String password;

    @NotBlank(message = "邮箱不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")
    @Schema(description = "邮箱")
    private String email;

    @Digits(integer = 4, fraction = 2, message = "整数位数必须在4位以内小数位数必须在2位以内")
    @Schema(description = "小数")
    private Double num;

    @URL(message = "url格式错误")
    @Schema(description = "地址")
    private String url;

    @Past(message = "日期必须为过去日期")
    @Schema(description = "过去日期")
    private LocalDate pastDate;

    @Future(message = "日期必须为将来日期")
    @Schema(description = "将来日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private LocalDateTime futureDate;

}
2.3.4 定义特定异常全局拦截方法

Validator框架 抛出的特定异常为MethodArgumentNotValidException,该异常会将我们在参数校验注解自定义的message返回到e.getBindingResult().getFieldError().getDefaultMessage()中。

/**
 * 全局异常拦截
 * 
 * @author zyw
 */
@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {

    /**
     * 拦截参数校验异常
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<?> handleGlobalException(MethodArgumentNotValidException e, HttpServletRequest request) {
        log.error("请求地址'{}',发生系统异常'{}'", request.getRequestURI(), e.getBindingResult().getFieldError().getDefaultMessage());
        return CommonResult.ECEPTION(ResultCode.PARAMETER_EXCEPTION, e.getBindingResult().getFieldError().getDefaultMessage());
    }

}
2.3.5 定义校验类进行测试
import com.example.demo.config.CommonResult;
import com.example.demo.model.dto.TestDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@Slf4j
@RequestMapping("knife4j")
@Tag(name = "knife4j测试控制器")
public class Knife4jController {
    @PostMapping("/parameterCheck")
    @Operation(summary = "参数校验", description = "嵌套参数校验-测试")
    public CommonResult<TestDto> parameterCheck(@Validated @RequestBody TestDto dto) {
        return CommonResult.SUCCESS(dto);
    }
}
2.3.6 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 自定义验证注解

在Java项目中,自定义注解是一种强大的功能,允许开发者创建自己的注解类型来满足特定需求,比如验证、日志记录、性能监控等。我们通过自定义注解修饰特定的接口、方法、属性、类,可以实现更加灵活的功能。

2.4.1 定义自定义注解
import com.example.demo.uitls.validator.PhoneNumberValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

/**
 * PhoneNumber : 手机号格式验证注解
 * 用于验证电话号码格式的注解。
 * 该注解可以应用于字段或参数上,以验证其是否为有效的电话号码格式。
 * 默认的错误消息是“无效的手机号码格式”,但可以通过message属性自定义。
 * 可以通过groups和payload属性来支持分组验证和负载信息。
 *
 * @Documented 标记此注解将被包含在文档中。
 * @Constraint 标记此注解为约束注解,并指定PhoneNumberValidator类作为验证器。
 * @Target 指定此注解可以应用于字段和参数上。
 * @Retention 指定此注解在运行时保留。
 * @author zyw
 * @create 2024-05-31  15:38
 */

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
    /**
     * 验证失败时的错误消息,默认为“无效的手机号码格式”。
     * 可以通过将此属性设置为自定义错误消息来更改默认消息。
     *
     * @return 验证失败时的错误消息。
     */
    String message() default "无效的手机号码格式";
    /**
     * 定义验证的分组,默认为空组。
     * 可以通过将此属性设置为一个或多个分组类来指定字段应在哪些分组中进行验证。
     *
     * @return 验证的分组类数组。
     */
    Class<?>[] groups() default {};
    /**
     * 定义验证的负载信息,默认为空负载。
     * 可以通过将此属性设置为一个或多个负载类来携带额外的验证信息。
     *
     * @return 验证的负载信息类数组。
     */
    Class<? extends Payload>[] payload() default {};
}
2.4.2 定义自定义验证器类
import com.example.demo.annotation.PhoneNumber;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * PhoneNumberValidator : 手机号验证器类
 *
 * @author zyw
 * @create 2024-05-31  15:39
 */

public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
    private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$"; // 中国手机号码的简单正则表达式

    @Override
    public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
        return phoneNumber != null && phoneNumber.matches(PHONE_PATTERN);
    }
}
2.4.3 应用自定义注解
import com.example.demo.annotation.PhoneNumber;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * TestDtoObj :
 *
 * @author zyw
 * @create 2024-05-31  15:47
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {

    @PhoneNumber
    @PhoneNumber(message = "手机号格式错误")
    @Schema(description = "手机号1")
    private String phone1;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")
    @NotBlank(message = "手机号不能为空")
    @Schema(description = "手机号2")
    private String phone2;

}

*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {

@PhoneNumber
@PhoneNumber(message = "手机号格式错误")
@Schema(description = "手机号1")
private String phone1;

@Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")
@NotBlank(message = "手机号不能为空")
@Schema(description = "手机号2")
private String phone2;

}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C#操作MySQL从入门到精通(20)——更新数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,本文所说的更新数据就是增删改查的改,改变数据的意思。 本文测试使用的数据库如下: 1、更新一列 所谓更新一列的意思就是只更改一列数据,并且通常要使用where条件,因为不加这个条件的话会导致将所有行的数据进行…

Java | Leetcode Java题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; class Solution {public int singleNumber(int[] nums) {int a 0, b 0;for (int num : nums) {b ~a & (b ^ num);a ~b & (a ^ num);}return b;} }

十大人工智能企业

​​​​​​链接&#xff1a;​​​​​​人工智能品牌排行-人工智能十大公司-人工智能十大品牌-Maigoo品牌榜

Linux--进程间通信(system V共享内存)

目录 1.原理部分 2.系统调用接口 参数说明 返回值 1. 函数原型 2. 参数说明 3. 返回值 4. 原理 5. 注意事项 3.使用一下shmget&#xff08;一段代码&#xff09; 4.一个案例&#xff08;一段代码) 1.简单封装一下 2.使用共享内存 2.1挂接&#xff08;shmat&#x…

2024 年适用于 Linux 的 5 个微软 Word 替代品

对于那些最近由于隐私问题或其他原因而转向 Linux 的用户来说&#xff0c;可能很难替换他们最喜欢的、不在 Linux 操作系统上运行的应用程序。 寻找流行程序的合适替代品可能会成为一项挑战&#xff0c;而且并不是每个人都准备好花费大量时间来尝试弄清楚什么可以与他们在 Win…

新买的移动硬盘无法识别

文章目录 背景解决方案 背景 同事新买的移动硬盘&#xff0c;插在电脑上识别不出来盘符&#xff0c;检查了一下&#xff0c;硬盘没问题应该&#xff0c;是ssk的硬盘盒M.2的SSD&#xff0c;硬盘驱动也是正常的&#xff0c;插拔了几次&#xff0c;都不识别&#xff0c;换了太电脑…

fl studio怎么设置中文及 2024年最新fl studio选购指南

FL Studio让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制。zol提供FL Studio中文版下载。 FL Studio中文版下载软件简介 FL Studio 让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混…

基于实验的电动汽车动力电池SOC

前言 本文为笔者在学习《基于MATLAB的新能源汽车仿真》过程中学习笔记&#xff0c;所涉及的表格数据和公式均为书籍里的。仿真数据是网上找的恒电流放电数据。本文仅作为笔者的入门学习记录。 一、分析动力电池SOC估算方法 SOC是指动力电池按照规定放电条件可以释放的容量占…

java版知识付费saas租户平台:剖析现代知识付费平台的功能架构与运营逻辑

在数字化学习的时代背景下&#xff0c;知识付费平台已经成为教育行业的一颗璀璨明星&#xff0c;以其用户需求为中心&#xff0c;提供便捷高效的学习途径。这些平台汇聚了众多专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需…

安全专业的硬件远控方案 设备无网也能远程运维

在很多行业中&#xff0c;企业的运维工作不仅仅局限在可以联网的IT设备&#xff0c;不能连接外网的特种设备也需要专业的远程运维手段。 这种特种设备在能源、医疗等行业尤其常见&#xff0c;那么我们究竟如何通过远程控制&#xff0c;对这些无网设备实施远程运维&#xff0c;…

[Algorithm][动态规划][01背包问题][目标和][最后一块石头的重量Ⅱ]详细讲解

目录 1.目标和1.题目链接2.算法原理详解3.代码实现 2.最后一块石头的重量 II1.题目链接2.算法原理详解3.代码实现 1.目标和 1.题目链接 目标和 2.算法原理详解 问题转化&#xff1a;在数组中选择一些数&#xff0c;让这些数的和等于a&#xff0c;一共有多少种选法&#xff1f…

推荐4个好用有趣的软件

MyComic——漫画聚合软件 MyComic是一款界面简洁、分类详尽的漫画阅读软件&#xff0c;专为动漫爱好者设计。它提供了丰富的高清漫画资源&#xff0c;支持在线免费阅读&#xff0c;并且可以一键下载到书架&#xff0c;方便随时离线观看&#xff0c;节省流量。用户可以轻松找到喜…

CSAPP Lab01——Data Lab完成思路

陪你把想念的酸拥抱成温暖 陪你把彷徨写出情节来 未来多漫长再漫长还有期待 陪伴你 一直到 故事给说完 ——陪你度过漫长岁月 完整代码见&#xff1a;CSAPP/datalab-handout at main SnowLegend-star/CSAPP (github.com) 01 bitXor 这道题是用~和&计算x^y。 异或是两个…

Android Compose 十:常用组件列表 监听

1 去掉超出滑动区域时的拖拽的阴影 即 overScrollMode 代码如下 CompositionLocalProvider(LocalOverscrollConfiguration provides null) {LazyColumn() {items(list, key {list.indexOf(it)}){Row(Modifier.animateItemPlacement(tween(durationMillis 250))) {Text(text…

接口(API)开发,测试工具-apifox

前言 为什么需要接口&#xff08;API&#xff09;? 因为不同的平台或系统可能使用不同的技术栈、编程语言或数据格式。API提供了一个标准化的方式&#xff0c;使得这些不同的系统可以相互交换数据和功能调用&#xff0c;实现互操作性 在开发日常的项目交互中&#xff0c;不…

英伟达的GPU(4)

更第四篇&#xff0c;上周有点私事&#xff0c;恢复更新 上次的文章 英伟达的GPU(3) (qq.com) 书接前文&#xff0c;我们上章说要更新GPU的内存机制&#xff0c;本次就讲点这个 先做个定义&#xff0c;我们说内存&#xff08;显存&#xff09;&#xff0c;也分物理内存&#…

Linux系统推出VB6开发IDE了?Gambas,Linux脚本编写

第一个Linux程序&#xff0c;加法计算加弹窗对话框,Gambas,linux版的类似VB6的IDE开发环境 一开始想用VB6的Clng函数转成整数&#xff0c;没这函数。 输入3个字母才有智能提示&#xff0c;这点没做好 没有msgbox函数&#xff0c;要用messagebox.warning 如果可以添加函数别名就…

【算法篇】求最长公共前缀JavaScript版本

题目描述 给你一个大小为 n 的字符串数组 strs &#xff0c;其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀&#xff0c;返回这个公共前缀。 数据范围&#xff1a; 数据范围:0<n<5000&#xff0c;0<len(strsi)< 5000 进阶:空间复杂度 O(1)&a…

2024年第三届数据统计与分析竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 详细请查 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024年第三届数据统计与分析竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有…

Unity | Shader基础知识(番外:了解内置Shader-Standard<一>)

目录 前言 一、什么是Standard 二、Standard参数详解 1.了解着色前 2. 着色拆分 3.参数RenderingMode 4.参数Albedo 5.参数Metallic 三、作者的话 前言 有粉丝给我说&#xff0c;感觉自己内部自带的Shader都还不知道怎么用&#xff0c;希望我讲一下内置Shader。 那…