文章目录

  • Pre
  • 需求
  • 实现三部曲
    • 实体类
    • Step1 搞两个自定义注解
    • Step2 搞自定义校验器
    • Step3 搞验证
  • 小结
  • 源码


Pre

SpringBoot - 优雅的实现【参数校验】高级进阶

SpringBoot - 优雅的实现【自定义参数校验】高级进阶

SpringBoot - 优雅的实现【参数分组校验】高级进阶

SpringBoot - 使用Assert校验让业务代码更简洁

在开发中,为了保证接口的稳定安全,一般需要在接口逻辑中进行校验,比如 上面几篇都是 【参数校验】,一般我们都是使用Bean Validation校验框架。

校验规则 规则说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 验证注解的元素值(日期类型)比当前时间早
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

那【业务规则校验】大部分情况下为了简单都是 if else ,那怎么玩的更优雅一些呢?

Tips: 参考 Bean Validation 的标准方式,借助自定义校验注解进行业务规则校验


需求

  • 新增用户 , 用户名+手机号码+邮箱 唯一
  • 修改用户, 修改后的 【用户名+手机号码+邮箱】不能与库中的用户信息冲突


实现三部曲

当然了, 简单的写就是整个if else return 嘛 查查DB 搞个判断 。 今天晚点看起来有点不一样的


实体类

package com.artisan.bean;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Data
@AllArgsConstructor
@NoArgsConstructor
public class Artisan {private String id;@NotEmpty(message = "Code不能为空")private String code;@NotBlank(message = "名字为必填项")private String name;@Length(min = 8, max = 12, message = "password长度必须位于8到12之间")private String password;@Email(message = "请填写正确的邮箱地址")private String email;private String sex;private String phone;}

Step1 搞两个自定义注解

创建两个自定义注解,用于业务规则校验

package com.artisan.annos;import com.artisan.validate.ArtisanValidator;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;/**** 自定义 "用户唯一" 校验注解 .唯一包含 -----------> 用户名+手机号码+邮箱* @author artisan*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
@Constraint(validatedBy = ArtisanValidator.UniqueArtisanValidator.class)
public @interface UniqueArtisan {String message() default "用户名、手机号码、邮箱不允许与现存用户重复";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
package com.artisan.annos;import com.artisan.validate.ArtisanValidator;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;/***  表示一个用户的信息是无冲突的*  “无冲突”是指该用户的敏感信息与其他用户不重合,比如将一个注册用户的邮箱、手机,修改成与另外一个已存在的注册用户一致的值,这样不行* @author artisan*/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = ArtisanValidator.NotConflictArtisanValidator.class)
public @interface NotConflictArtisan {String message() default "用户名称、邮箱、手机号码与现存用户产生重复";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

Step2 搞自定义校验器

package com.artisan.validate;import com.artisan.annos.NotConflictArtisan;
import com.artisan.annos.UniqueArtisan;
import com.artisan.bean.Artisan;
import com.artisan.repository.ArtisanDao;
import lombok.extern.slf4j.Slf4j;import javax.annotation.Resource;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.function.Predicate;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j
public class ArtisanValidator<T extends Annotation> implements ConstraintValidator<T, Artisan> {protected Predicate<Artisan> predicate = c -> true;@Resourceprotected ArtisanDao artisanDao;@Overridepublic boolean isValid(Artisan artisan, ConstraintValidatorContext constraintValidatorContext) {return artisanDao == null || predicate.test(artisan);}/*** 校验用户是否唯一* 即判断数据库是否存在当前新用户的信息,如用户名,手机,邮箱*/public static class UniqueArtisanValidator extends ArtisanValidator<UniqueArtisan> {@Overridepublic void initialize(UniqueArtisan uniqueArtisan) {predicate = c -> !artisanDao.existsByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone());}}/*** 校验是否与其他用户冲突* 将用户名、邮件、电话改成与现有完全不重复的,或者只与自己重复的,就不算冲突*/public static class NotConflictArtisanValidator extends ArtisanValidator<NotConflictArtisan> {@Overridepublic void initialize(NotConflictArtisan notConflictUser) {predicate = c -> {log.info("user detail is {}", c);Collection<Artisan> collection = artisanDao.findByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone());// 将用户名、邮件、电话改成与现有完全不重复的,或者只与自己重复的,就不算冲突return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));};}}
}

自定义验证注解需要实现 ConstraintValidator 接口。

  • 第一个参数是 自定义注解类型
  • 第二个参数是 被注解字段的类

    因为需要校验多个参数, 直接传入用户对象。

需要提到的一点是 ConstraintValidator 接口的实现类无需添加 @Component 它在启动的时候就已经被加载到容器中了。

使用Predicate函数式接口对业务规则进行判断.


Step3 搞验证

package com.artisan.controller;import com.artisan.annos.NotConflictArtisan;
import com.artisan.annos.UniqueArtisan;
import com.artisan.bean.Artisan;
import com.artisan.repository.ArtisanDao;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;/****/
@RestController
@RequestMapping("/buziVa/artisan")
@Slf4j
@Validated
public class ArtisanController {@Autowiredprivate ArtisanDao artisanDao;// POST 方法@PostMappingpublic Artisan createUser(@UniqueArtisan @Valid Artisan user) {Artisan savedUser = artisanDao.save(user);log.info("save user id is {}", savedUser.getId());return savedUser;}// PUT@SneakyThrows@PutMappingpublic Artisan updateUser(@NotConflictArtisan @Valid @RequestBody Artisan artisan) {Artisan editUser = artisanDao.save(artisan);log.info("update artisan is {}", editUser);return editUser;}}

只需要在方法上加入自定义注解即可,业务逻辑中不需要添加任何业务规则的代码。

小结

通过上面几步操作,业务校验便和业务逻辑就完全分离开来,在需要校验时用@Validated注解自动触发,或者通过代码手动触发执行。

这些注解应用于控制器、服务层、持久层等任何层次的代码之中。

在开发时可以将不带业务含义的格式校验注解放到 Bean 的类定义之上,将带业务逻辑的校验放到 Bean 的类定义的外面。

区别是放在类定义中的注解能够自动运行,而放到类外面则需要明确标出@Validated注解时才会运行。

源码

https://github.com/yangshangwei/boot2

SpringBoot - 优雅的实现【业务校验】高级进阶相关推荐

  1. SpringBoot - 优雅的实现【自定义参数校验】高级进阶

    文章目录 Pre 概述 三部曲 Step1 搞自定义注解 Step2 搞校验逻辑 Step3 使用 Step4 验证 附 int 类型的判断 源码 Pre SpringBoot - 优雅的实现[参数校 ...

  2. SpringBoot - 优雅的实现【参数校验】高级进阶

    文章目录 Pre 概述 参数校验三部曲 Step1 搞依赖 Step2 搞参数校验的实体类 常用的校验注解 Step3 开始验证 存在的问题 使用 统一格式 + 全局异常Handler 优化 源码 P ...

  3. SpringBoot - 优雅的实现【参数分组校验】高级进阶

    文章目录 Pre 需求 实现三部曲 Step1 定义分组接口 Step2 给参数分配分组 Step3 指定分组 Step4 验证 源码 Pre SpringBoot - 优雅的实现[参数校验]高级进阶 ...

  4. java 逻辑校验工具_SpringBoot2.0实战(10)整合fluent-validator优雅业务校验

    相关知识 FluentValidator是一个工具类库,使用流式(Fluent Interface)调用风格让校验跑起来更优雅,代码更简洁,同时验证器(Validator)可以做到开闭原则,实现最大程 ...

  5. SpringBoot 如何进行业务校验,老鸟们都这么玩的~

    大家好,我是飘渺. 今天继续给大家带来 SpringBoot老鸟系列 的第七篇,来聊聊在SpringBoot项目中如何实现业务异常校验Assert. 希望通过今天的文章,咱们能够了解到: 如何使用As ...

  6. Springboot优雅的参数校验(一)

    目录 前言 1. 依赖引入 2. 参数形式 3. 常用到的约束注解 4. 参数基础校验 4.1 @RequestBody参数 4.2 @RequestParam参数/@PathVariable参数 4 ...

  7. SpringBoot 优雅的参数效验!

    引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(UserVO userVO) {if(user ...

  8. SpringBoot 优雅地对接口进行数据加解密

    我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华.如果小伙伴们觉得不错就一键三连吧~ 声明: 原作者:掘金:https://juejin.cn/user/3650034336532824 原文 ...

  9. 二. 微服务的高级进阶

    二. 微服务的高级进阶 1. Ribbon API和负载均衡算法 1. Ribbon API Ribbon 是一个独立的组件,是用来进行远程接口调用的,代码如下 @Slf4j @Service @Sc ...

最新文章

  1. 数据库 Linux下的MySQL数据库管理
  2. 通过ANT实现jmeter批量执行脚本、生成报告、发送邮件全套build.xml文件
  3. 搜狗服务器页面找不到了怎么办,处理搜狗浏览器提示“无法解析服务器的DNS地址”的方法...
  4. 物流的趋势和计算机科技,计算机仿真技术在物流领域的前景分析
  5. java的中文源代码
  6. LMS Amesim 用途及界面汉化
  7. 8个高质量免抠素材网站
  8. 凯悦宣布旗下凯悦悠选品牌首次进入亚太地区;格林酒店发布2021年三季报 | 全球旅报...
  9. android+5.1+root,最新的安卓5.1.1 ROOT教程(不需要刷第三方内核)
  10. zblog mysql修改_zblog数据库批量替换https的方法
  11. python集合中,|与and,or的区分
  12. 电脑重启后IDEA导包报错
  13. php格式用什么能打开,文件格式为.php的文件用什么软件打开?
  14. 最全求职渠道:考公/选调/事业编/国企/私企
  15. JDBC简介(Statement接口)
  16. Facebook营销常犯的错误,看看你中招了吗?
  17. WPS编号后面有很大的空白
  18. #Pythonyyds#python实现——最优化算法
  19. 计算机组成原理第二章练习题(答案详解)
  20. CVPR 2018 | Spotlight论文:单摄像头数秒构建3D人体模型

热门文章

  1. Android:相对布局综合小演练—智能家居,按键快速美化的小技巧
  2. json 反射java 实体_Java 将JSON反射到实体类
  3. opencv各版本链接及opencv_contrib库各版本链接
  4. tensorflow2.0 与tensorflow1.0的性能区别
  5. 118. Leetcode 392. 判断子序列 (动态规划-子序列问题)
  6. 判断二叉树是否是平衡二叉树(dp tree)
  7. 队列的基本原理及实现
  8. 模式识别中Fisher分类器的Matlab实现及测试
  9. 计算机控制电缆敷设长度,计算机控制电缆ZR-DJYPVP-22-6*3*1.0电缆
  10. geo数据差异分析_GeoDiver:GEO数据挖掘分析利器