Springboot接口参数校验

在设计接口时我们通常需要对接口中的非法参数做校验,以降低在程序运行时因为一些非法参数而导致程序发生异常的风险,例如登录的时候需要校验用户名密码是否为空,创建用户的时候需要校验邮件、手机号码格式是否准确。如果在代码中对接口参数一个个硬编码校验的话就太繁琐了,代码可读性极差。这时不妨试试Validator框架。

原生的依赖是validation-api而hibernate-validator是对validation-api的增强,hibernate-validator框架已经集成在spring-boot-starter-web中。

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>5.2.0.Final</version>
</dependency>

但从springboot-2.3开始,校验包被独立成了一个starter组件,所以需要引入validation和web,而springboot-2.3之前的版本只需要引入web依赖就可以了。 

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

部分常见注解如下:

注解功能
@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 lombok.Data;
import org.hibernate.validator.constraints.Length;

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

@Data
public class UserVo {
    @Length(min = 4, max = 12, message = "账号长度必须位于4到10之间")
    private String userCode;
    @NotBlank(message = "用户名不能为空")
    private String userName;
    @Email(message = "邮箱格式有误")
    private String email;
    @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误")
    private String phone;
    /**
     * 0:普通用户;VIP:会员用户;2:超级VIP用户
     */
    @Pattern(regexp = "[012]", message = "用户类型有误,请输入0:普通用户;VIP:会员用户;2:超级VIP用户")
    private String type;
}

在全局异常处理里面加上对参数校验异常的拦截

    @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
    public ResponseVo validatedExceptionHandler(Exception e) {
        log.error("参数校验异常!原因是{}", e);
        if (e instanceof MethodArgumentNotValidException) {
            //BeanValidation exception
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
            return ResponseVo.error(ResponseEnum.BODY_NOT_MATCH.getResultCode(),
                    ex.getBindingResult().getAllErrors().stream()
                            .map(ObjectError::getDefaultMessage)
                            .collect(Collectors.joining("; ")));
        } else if (e instanceof ConstraintViolationException) {
            //BeanValidation GET simple param
            ConstraintViolationException ex = (ConstraintViolationException) e;
            return ResponseVo.error(ResponseEnum.BODY_NOT_MATCH.getResultCode(),
                    ex.getConstraintViolations().stream()
                            .map(ConstraintViolation::getMessage)
                            .collect(Collectors.joining("; ")));
        } else if (e instanceof BindException) {
            //BeanValidation GET object param
            BindException ex = (BindException) e;
            return ResponseVo.error(ResponseEnum.BODY_NOT_MATCH.getResultCode(),
                    ex.getAllErrors().stream()
                            .map(ObjectError::getDefaultMessage)
                            .collect(Collectors.joining("; ")));
        }
        return ResponseVo.error(ResponseEnum.BODY_NOT_MATCH.getResultCode(), ResponseEnum.BODY_NOT_MATCH.getResultMsg());
    }

 定义参数校验的controller

import com.yx.light.element.mybatis.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Email;

@RestController
@RequestMapping(value = "/valid")
@Validated
@Slf4j
public class ValidController {

    /**
     * 单参数校验,单参数需要在controller上加上@Validated配合使用
     * @param email
     * @return
     */
    @PostMapping(value = "/oneParam", produces = "application/json; charset=UTF-8")
    public String oneParam(@Email(message = "邮箱格式有误") String email) {
        log.info("校验邮箱为:{}", email);
        return "邮箱校验成功";
    }

    /**
     * body类型校验
     * @param vo
     * @return
     */
    @PostMapping(value = "/bodyParam", produces = "application/json; charset=UTF-8")
    public String bodyParam(@Validated @RequestBody UserVo vo) {
        log.info("校验body为:{}", vo);
        return "body校验成功";
    }

    /**
     * 表单参数校验
     * @param vo
     * @return
     */
    @PostMapping(value = "/formParam", produces = "application/json; charset=UTF-8")
    public String formParam(@Validated UserVo vo) {
        log.info("校验表单为:{}", vo);
        return "表单校验成功";
    }
}

oneParam接口

bodyParam接口

formParam接口

 二、自定义参数校验

当然你也可以自定义参数校验,出现有些复杂的逻辑基本注解是没办法兼容的,需要我们自己去实现自动校验。

自定义校验注解

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {
    String message() default "用户编码、手机号码、邮箱不允许与现存用户重复";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

 想让自定义验证注解生效,需要实现ConstraintValidator接口。接口的第一个参数是 自定义注解类型,第二个参数是 被注解字段的类。

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.util.function.Predicate;

@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, UserVo> {

    protected Predicate<UserVo> predicate = c -> true;

    @Override
    public boolean isValid(UserVo user, ConstraintValidatorContext constraintValidatorContext) {
        return predicate.test(user);
    }

    /**
     * 校验用户是否唯一
     * 即判断数据库是否存在当前新用户的信息,如用户编码,手机,邮箱
     */
    public static class UniqueUserValidator extends UserValidation<UniqueUser> {
        @Override
        public void initialize(UniqueUser uniqueUser) {
            predicate = c -> !existsUser(c.getUserCode(), c.getEmail(), c.getPhone());
        }

        private boolean existsUser(String userCode, String email, String phone) {
            //TODO 写一个数据库查询判断是否存在相同的用户,省略...
            return true;
        }
    }
}

在controller中添加测试接口

@PostMapping(value = "/addUser", produces = "application/json; charset=UTF-8")
public String addUser(@UniqueUser @Validated @RequestBody UserVo vo) {
	log.info("校验body为:{}", vo);
	return "新增成功";
}

addUser接口 

 三、分组校验

如果你的一个实体中的字段某一些是新增的时候必传,某一些修改时又不用传,那么对于不用传的字段肯定不需要校验的,这时候如果我们共用一个实体作为多个接口参数那肯定存在兼容问题,此时你就可以考虑将参数分组判断。

定义一个分组接口ValidGroup让其继承javax.validation.groups.Default,再在分组接口中定义出多个不同的操作类型,Create,Update,Query,Delete。

import javax.validation.groups.Default;

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 {

        }
    }
}

稍微修改一下原来的vo,给他们加上分组参数groups

import com.yx.light.element.mybatis.validation.ValidGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

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

@Data
public class UserVo {
    @Length(min = 4, max = 12, message = "账号长度必须位于4到10之间", groups = ValidGroup.Crud.Update.class)
    private String userCode;
    @NotBlank(message = "用户名不能为空", groups = ValidGroup.Crud.Create.class)
    private String userName;
    @Email(message = "邮箱格式有误")
    private String email;
    @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误")
    private String phone;
    /**
     * 0:普通用户;VIP:会员用户;2:超级VIP用户
     */
    @Pattern(regexp = "[012]", message = "用户类型有误,请输入0:普通用户;VIP:会员用户;2:超级VIP用户")
    private String type;
}

在controller中添加如下方法,这里我们通过value属性给addUserV2()和updateUser()方法分别指定Create和Update分组。

@PostMapping(value = "/addUserV2", produces = "application/json; charset=UTF-8")
public String addUserV2(@Validated(value = ValidGroup.Crud.Create.class) @RequestBody UserVo vo) {
	log.info("校验body为:{}", vo);
	return "新增成功";
}

@PostMapping(value = "/updateUser", produces = "application/json; charset=UTF-8")
public String updateUser(@Validated(value = ValidGroup.Crud.Update.class) @RequestBody UserVo vo) {
	log.info("校验body为:{}", vo);
	return "更新成功";
}

addUserV2接口

updateUser接口

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

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

相关文章

AOP案例(黑马学习笔记)

需求 需求&#xff1a;将案例中增、删、改相关接口的操作日志记录到数据库表中 ● 就是当访问部门管理和员工管理当中的增、删、改相关功能接口时&#xff0c;需要详细的操作日志&#xff0c;并保存在数据表中&#xff0c;便于后期数据追踪。 操作日志信息包含&#xff1a; ●…

基于HT32的智能家居demo(蓝牙上位机)

参加合泰杯作品的部分展示&#xff0c;基于HT32的智能家居&#xff0c;这里展示灯光的相关控制&#xff0c;是用蓝牙进行的数据透传&#xff0c;参考了一些资料&#xff0c;美化封装了一下之前的上位机界面。 成果展示 点击主界面的蓝牙设置&#xff0c;进行连接&#xff0c;下…

Android和Linux的嵌入式开发差异

最近开始投入Android的怀抱。说来惭愧&#xff0c;08年就听说这东西&#xff0c;当时也有同事投入去看&#xff0c;因为恶心Java&#xff0c;始终对这玩意无感&#xff0c;没想到现在不会这个嵌入式都快要没法搞了。为了不中年失业&#xff0c;所以只能回过头又来学。 首先还是…

编码规则转换

思考&#xff1a; 如何将一个机内码转换为区内码&#xff1f; 只要将机内码减去 A0A0 就可以啦 如果只让我们用加法器来解决呢&#xff1f; 注意我们的数据占用了 32 位&#xff0c;如果想用补码进行减法运算的话&#xff0c;符号位怎么办&#xff1f;&#xff1f;&#xf…

了解Spring中Bean:配置与作用域

作为一名对技术充满热情的学习者&#xff0c;我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代&#xff0c;我远非专家&#xff0c;而是一位不断追求进步的旅行者。通过这篇博客&#xff0c;我想分享我在某个领域的学习经验&#xff0c;与大家共同探讨、共…

Linux和Windows集群中部署HTCondor

目录 1、集群架构 2、HTCondor版本 3、Linux系统安装 3.1、HTCondor安装 3.2、中央管理节点配置 3.3、其他节点配置 4、Windwos系统安装 5、安全配置 6、参考 1、集群架构 操作系统IP地址1*Ubuntu22.04192.168.1.742Ubuntu22.04192.168.1.603Ubuntu22.04192.168.1.6…

python3装饰器

装饰器 它允许你修改函数或类的行为&#xff0c;而不更改其源代码。实质上&#xff0c;装饰器是接受另一个函数作为参数并返回一个包装原始函数的新函数。这样&#xff0c;你可以在不修改原始函数的情况下&#xff0c;添加一些额外的功能或逻辑。 def time_cost(func):"…

Java 数组(详细)

目录 一、数组的概述 1. 数组的理解&#xff1a; 2. 数组相关的概念&#xff1a; 3. 数组的特点&#xff1a; 4. 数组的分类&#xff1a; 5.数据结构&#xff1a; 二、一维数组 1. 一维数组的声明与初始化 2. 一维数组元素的引用&#xff1a; 3. 数组的属性&#xff1…

期货开户金融期货的种类

金融期货概念及其种类有哪些&#xff1f;期货种类分为商品期货、金融期货、和期货期权。金融期货是期货的其中一个种类&#xff0c;它是以证券&#xff1b;货币、汇率&#xff0c;利率等金融产品作为买卖标的的期货品种。金融期货交易产生于本世纪70年代的美国市场&#xff0c;…

项目解决方案: 实时视频拼接方案介绍(中)

目 录 1.实时视频拼接概述 2.适用场景 3.系统介绍 4. 拼接方案介绍 4.1基于4K摄像机的拼接方案 4.2采用1080P平台3.0 横向拼接 4.2.1系统架构 4.2.2系统功能 4.2.3方案特色 4.2.4适用场景 4.2.5设备选型 4.3纵横兼顾&#xff0c;竖屏拼接 4.3.1系统…

从下一代车规MCU厘清存储器的发展(2)

目录 1.概述 2.MCU大厂的选择 2.1 瑞萨自研STT-MRAM 2.2 ST专注PCM 2.3 英飞凌和台积电联手RRAM 2.4 NXP如何计划eNVM 3.小结 1.概述 上篇文章&#xff0c;我们简述了当前主流的存储器技术&#xff0c;现在我们来讲讲各大MCU大厂的技术选择 2.MCU大厂的选择 瑞萨日…

取送货问题(Pickup and Delivery Problem)

取送货问题及其变体 广义取送货问题&#xff08;General Pickup and Delivery Problems&#xff0c;GPDP&#xff09;可以分为两类&#xff1a; Vehicle Routing Problems with Backhauls&#xff0c;VRPB&#xff1a;从配送中心&#xff08;depot&#xff09;取货运输货物到客…

VUE3:省市区联级选择器

一、实现效果 二、代码展示 <template><div class"page"><select v-model"property.province"><option v-for"item in provinces" :key"item">{{ item }}</option></select><select v-model&…

RabbitMQ-消息队列:优先级队列、惰性队列

20、优先级队列 在我们系统中有一个订单催付的场景&#xff0c;我们的客户在天猫下的订单&#xff0c;淘宝会及时将订单推送给我们&#xff0c;如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒&#xff0c;很简单的一个功能对吧。 但是&#xff0c;天猫商家对我…

Stable Cascade-ComfyUI中文生图、图生图、多图融合基础工作流分享

最近 ComfyUI对于Stable Cascade的支持越来越好了一些&#xff0c;官方也放出来一些工作流供参考。 这里简单分享几个比较常用的基础工作流。 &#xff08;如果还没有下载模型&#xff0c;可以先阅读上一篇Stable Cascade升级&#xff0c;现在只需要两个模型&#xff09; &a…

春游和女儿穿这套,超美

质感满满的牛仔套装&#xff0c;简直不要太好看 出游的时候亲子装考虑一下哈哈 经典版型&#xff0c;线条流畅&#xff0c;洋气又舒适 而且用的是防褪色的牛仔面料&#xff0c;耐脏又实穿 日常外出都可以穿&#xff0c;时髦又闲适 上衣做的真口袋实用又美观&#xff0c;版…

实战 | 使用YOLOv8图像分割实现路面坑洞检测(步骤 + 代码)

导 读 本文主要介绍使用YOLOv8图像分割实现路面坑洞检测&#xff08;步骤 代码&#xff09;。 背 景 如上图所示&#xff0c;现实生活中路面坑洞对车辆和驾驶员安全来说存在巨大隐患&#xff0c;本文将介绍如何使用YoloV8图像分割技术来检测路面坑洞&#xff0c;从而提示驾…

【研发日记】Matlab/Simulink技能解锁(三)——在Stateflow编辑窗口Debug

文章目录 前言 State断点 Transition断点 条件断点 按State步进 Watch Data Value Sequence Viewer 分析和应用 总结 前言 见《【研发日记】Matlab/Simulink技能解锁(一)——在Simulink编辑窗口Debug》 见《【研发日记】Matlab/Simulink技能解锁(二)——在Function编辑…

Leetcode3. 无重复字符的最长子串 -hot100

题目&#xff1a; 代码&#xff08;首刷看解析 2024年3月2日&#xff09;&#xff1a; 没能自解的原因是Alpahash.insert(s[right]); 放在for循环开头了&#xff0c;没能处理边界条件。 class Solution { public:int lengthOfLongestSubstring(string s) {// 滑动窗口int left…

overleaf上传到arxiv 参考文献无法引用(?)

记一下overleaf上传到arxiv的bug 参考文献无法引用&#xff08;&#xff1f;&#xff09; 因为需要上传bbl文件而不是bib 用overleaf生成bbl 另外需要将bbl和txt的文件名设置成一样的