SpringBoot实战:如何优雅的进行参数校验

引言

在日常的接口开发过程中,为了防治参数对业务造成影响,我们经常需要对接口的参数个数进行验证,例如在登录的时候需要校验用户名和密码是否为空,添加用户的时候检验用户邮箱地址、手机号格式是否正确,如果使用 if … esle…. 进行参数校验则会显得代码太过臃肿,代码可读性差不宜维护,在这种情况下,推荐使用注解来实现参数检验。



一、参数校验的特点

  • 数据完整性与准确性:确保接收到的数据是完整的且准确,避免因为错误或恶意的数据输入导致系统异常或者系统数据损坏。

  • 安全防护:通过进行参数校验机制,能有效防治注入攻击,如SQL注入、跨站脚本攻击(xss)等安全威胁,过滤非法输入,可显著提升系统的安全防护能力。

  • 性能优化:前置参数验证步骤能够减少不必要的数据库查询和冗余业务逻辑执行,从而优化系统资源的利用。

  • 减少异常处理:结合前端与后端的双重验证策略,有效减少了运行时异常的发生,使系统更加稳定可靠,降低了故障排查和维护成本。

  • 合规性:在处理涉及用户隐私或敏感信息的应用时,参数验证是确保符合数据保护法规(如GDPR)要求的关键环节,有助于维护企业法律合规性,保护用户权益


二、常用注解

  1. @NotNull:值不能为null;

  2. @NotEmpty:字符串、集合或数组的值不能为空,即长度大于0;

  3. @NotBlank:字符串的值不能为空白,即不能只包含空格;

  4. @Size:字符串、集合或数组的大小是否在指定范围内;

  5. @Min:数值的最小值;

  6. @Max:数值的最大值;

  7. @DecimalMin:数值的最小值,可以包含小数;

  8. @DecimalMax:数值的最大值,可以包含小数;

  9. @Digits:数值是否符合指定的整数和小数位数;

  10. @Pattern:字符串是否匹配指定的正则表达式;

  11. @Email:字符串是否为有效的电子邮件地址;

  12. @AssertTrue:布尔值是否为true;

  13. @AssertFalse:布尔值是否为false;

  14. @Future:日期是否为将来的日期;

  15. @Past:日期是否为过去的日期;


三、代码示例

3.1 创建项目,添加依赖

<!-- 如果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>

3.2 创建示例实体类

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;

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

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());
    }

}

3.4 定义校验类进行测试

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);
    }
}

3.5 测试

SpringBoot实战:如何优雅的进行参数校验

SpringBoot实战:如何优雅的进行参数校验

SpringBoot实战:如何优雅的进行参数校验

SpringBoot实战:如何优雅的进行参数校验


原文始发于微信公众号(Java技术前沿):SpringBoot实战:如何优雅的进行参数校验

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/299638.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!