在平常的开发中,参数校验是很是重要的一个环节,严格参数校验会减小不少出bug的几率,增长接口的安全性。在此以前写过一篇SpringBoot统一参数校验主要介绍了一些简单的校验方法。而这篇则是介绍一些进阶的校验方式。好比说:在某个接口编写的过程当中确定会遇到,当xxType值为A,paramA值必传。xxType值为B,paramB值必须传。对于这样的,一般的作法就是在controller加上各类if判断。显然这样的代码是不够优雅的,而分组校验及自定义参数校验,就是来解决这个问题的。前端
Restful的接口,在如今来说应该是比较常见的了,经常使用的地址栏的参数,咱们都是这样校验的。java
/** * 获取电话号码信息 */ @GetMapping("/phoneInfo/{phone}") public ResultVo phoneInfo(@PathVariable("phone") String phone){ // 验证电话号码是否有效 String pattern = "^[1][3,4,5,7,8][0-9]{9}$"; boolean isValid = Pattern.matches(pattern, phone); if(isValid){ // 执行相应逻辑 return ResultVoUtil.success(phone); } else { // 返回错误信息 return ResultVoUtil.error("手机号码无效"); } }
很显然上面的代码不够优雅,因此咱们能够在参数后面,添加对应的正则表达式phone:正则表达式
来进行验证。这样就省去了在controller编写校验代码了。正则表达式
/** * 获取电话号码信息 */ @GetMapping("/phoneInfo/{phone:^[1][3,4,5,7,8][0-9]{9}$}") public ResultVo phoneInfo(@PathVariable("phone") String phone){ return ResultVoUtil.success(phone); }
虽然这样处理后代码更精简了。可是若是传入的手机号码,不符合规则会直接返回404。而不是提示手机号码错误。错误信息以下:spring
咱们以校验手机号码为例,虽然validation
提供了@Pattern
这个注解来使用正则表达式进行校验。若是被使用在多处,一旦正则表达式发生更改,则须要一个一个的进行修改。很显然为了不作这样的无用功,自定义校验注解
就是你的好帮手。数据库
@Data public class PhoneForm { /** * 电话号码 */ @Pattern(regexp = "^[1][3,4,5,7,8][0-9]{9}$" , message = "电话号码有误") private String phone; }
要实现一个自定义校验注解,主要是有两步。一是注解自己,二是校验逻辑实现类。segmentfault
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { String message() default "手机号码格式有误"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
public class PhoneValidator implements ConstraintValidator<Phone, Object> { @Override public boolean isValid(Object telephone, ConstraintValidatorContext constraintValidatorContext) { String pattern = "^1[3|4|5|7|8]\\d{9}$"; return Pattern.matches(pattern, telephone.toString()); } }
@Data public class CustomForm { /** * 电话号码 */ @Phone private String phone; }
@PostMapping("/customTest") public ResultVo customTest(@RequestBody @Validated CustomForm form){ return ResultVoUtil.success(form.getPhone()); }
注解是指定当前自定义注解可使用在哪些地方,这里仅仅让他可使用属性上。但还可使用在更多的地方,好比说方法上、构造器上等等。安全
指定当前注解保留到运行时。保留策略有下面三种:app
指定了当前注解使用哪一个校验类来进行校验。jvm
@Data public class UserForm { /** * id */ @Null(message = "新增时id必须为空", groups = {Insert.class}) @NotNull(message = "更新时id不能为空", groups = {Update.class}) private String id; /** * 类型 */ @NotEmpty(message = "姓名不能为空" , groups = {Insert.class}) private String name; /** * 年龄 */ @NotEmpty(message = "年龄不能为空" , groups = {Insert.class}) private String age; }
public interface Insert { }
public interface Update { }
/** * 添加用户 */ @PostMapping("/addUser") public ResultVo addUser(@RequestBody @Validated({Insert.class}) UserForm form){ // 选择对应的分组进行校验 return ResultVoUtil.success(form); } /** * 更新用户 */ @PostMapping("/updateUser") public ResultVo updateUser(@RequestBody @Validated({Update.class}) UserForm form){ // 选择对应的分组进行校验 return ResultVoUtil.success(form); }
@GroupSequence
在@GroupSequence
内能够指定,分组校验的顺序。好比说@GroupSequence({Insert.class, Update.class, UserForm.class})
先执行Insert
校验,而后执行Update
校验。若是Insert
分组,校验失败了,则不会进行Update
分组的校验。ide
@Data @GroupSequence({Insert.class, Update.class, UserForm.class}) public class UserForm { /** * id */ @Null(message = "新增时id必须为空", groups = {Insert.class}) @NotNull(message = "更新时id不能为空", groups = {Update.class}) private String id; /** * 类型 */ @NotEmpty(message = "姓名不能为空" , groups = {Insert.class}) private String name; /** * 年龄 */ @NotEmpty(message = "年龄不能为空" , groups = {Insert.class}) private String age; }
/** * 编辑用户 */ @PostMapping("/editUser") public ResultVo editUser(@RequestBody @Validated UserForm form){ return ResultVoUtil.success(form); }
哈哈哈,测试结果实际上是个死循环,无论你咋输入都会报错,小伙伴能够尝试一下哦。上面的例子只是个演示,在实际中仍是别这样作了,须要根据具体逻辑进行校验。
对于以前提到了当xxType值为A,paramA值必传。xxType值为B,paramB值必须传这样的场景。单独使用分组校验和分组序列是没法实现的。须要使用@GroupSequenceProvider
才行。
@Data @GroupSequenceProvider(value = CustomSequenceProvider.class) public class CustomGroupForm { /** * 类型 */ @Pattern(regexp = "[A|B]" , message = "类型没必要须为 A|B") private String type; /** * 参数A */ @NotEmpty(message = "参数A不能为空" , groups = {WhenTypeIsA.class}) private String paramA; /** * 参数B */ @NotEmpty(message = "参数B不能为空", groups = {WhenTypeIsB.class}) private String paramB; /** * 分组A */ public interface WhenTypeIsA { } /** * 分组B */ public interface WhenTypeIsB { } }
public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> { @Override public List<Class<?>> getValidationGroups(CustomGroupForm form) { List<Class<?>> defaultGroupSequence = new ArrayList<>(); defaultGroupSequence.add(CustomGroupForm.class); if (form != null && "A".equals(form.getType())) { defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class); } if (form != null && "B".equals(form.getType())) { defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class); } return defaultGroupSequence; } }
/** * 自定义分组 */ @PostMapping("/customGroup") public ResultVo customGroup(@RequestBody @Validated CustomGroupForm form){ return ResultVoUtil.success(form); }
GroupSequence
注解是一个标准的Bean认证注解。正如以前,它可以让你静态的从新定义一个类的,默认校验组顺序。然而GroupSequenceProvider
它可以让你动态的定义一个校验组的顺序。
SpringBoot 2.3.x 移除了validation
依赖须要手动引入依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
我的的一些小经验,参数的非空判断,这个应该是校验的第一步了,除了非空校验,咱们还须要作到下面这几点:
type
的值是【0|1|2】这样的。Id
好比说 userId
、merchantId
,对于这样的参数,都须要进行真实性校验,判断系统内是有含有,而且对应的状态是否正常。参数校验越严格越好,严格的校验规则不只能减小接口出错的几率,同时还能避免出现脏数据,从而来保证系统的安全性和稳定性。
错误的提醒信息须要友好一点哦,防止等下被前端大哥吐槽哦。
若是以为对你有帮助,能够多多评论,多多点赞哦,也能够到个人主页看看,说不定有你喜欢的文章,也能够随手点个关注哦,谢谢。
我是不同的科技宅,天天进步一点点,体验不同的生活。咱们下期见!