在controller层接收前端传过来的数据时,我们需要对参数进行统一检验,比如登陆的时候检查手机号是否为空、格式是否正确,同时还需要返回校验信息,那么这一部分该怎么设计呢?
针对这个问题,Java开发者在Java API规范 (JSR303) 定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们介绍Spring Validation的使用。
@Valid
- 第一步:添加依赖
<!-- hibernate 验证框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 第二步:封装参数
将前端传过来用户的参数封装到自定义实体RegistLoginBO中,对每个参数字段添加注解约束和message
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class RegistLoginBO {
@NotBlank(message = "手机号不能为空")
@Length(min = 11, max = 11, message = "手机长度不正确")
private String mobile;
@NotBlank(message = "验证码不能为空")
private String verifyCode;
}
- 第三步:Controller获取参数
通过@Valid @RequestBody RegistLoginBO registLoginBO
校验参数,如果参数不对抛出MethodArgumentNotValidException
,由@ControllerAdvice + @ExceptionHandler(MethodArgumentNotValidException.class)
统一捕获异常,参数校验的值放在BindingResult中,可通过e.getBindingResult()获得。
@PostMapping("login")
public GraceJSONResult login(@Valid @RequestBody RegistLoginBO registLoginBO) throws Exception {
String mobile = registLoginBO.getMobile();
String code = registLoginBO.getSmsCode();
// 1. 从redis中获得验证码进行校验是否匹配
String redisCode = redis.get(MOBILE_SMSCODE + ":" + mobile);
if (StringUtils.isBlank(redisCode) || !redisCode.equalsIgnoreCase(code)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
// 2. 查询数据库,判断用户是否存在
Users user = userService.queryMobileIsExist(mobile);
if (user == null) {
// 2.1 如果用户为空,表示没有注册过,则为null,需要注册信息入库
user = userService.createUser(mobile);
}
// 3. 如果不为空,可以继续下方业务,可以保存用户会话信息
String uToken = UUID.randomUUID().toString();
redis.set(REDIS_USER_TOKEN + ":" + user.getId(), uToken);
// 4. 用户登录注册成功以后,删除redis中的短信验证码
redis.del(MOBILE_SMSCODE + ":" + mobile);
// 5. 返回用户信息,包含token令牌
UsersVO usersVO = new UsersVO();
BeanUtils.copyProperties(user, usersVO);
usersVO.setUserToken(uToken);
return GraceJSONResult.ok(usersVO);
}
- 校验结果
@Validated
如果对于某一个参数UserParam,它可以作为两个方法的参数,其中一个允许为null,另一个要求不为null,这时候就需要@Validated分组校验了。
@Validated
提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制;可以用在类型、方法和方法参数上,但是不能用在成员属性上。
@Valid
作为标准JSR-303规范,还没有吸收分组的功能;可以用在方法、构造函数、方法参数和成员属性上。
- 定义分组(无需实现接口)
public interface AddValidationGroup {
}
public interface EditValidationGroup {
}
- 校验字段添加分组
@Data
@Builder
@ApiModel(value = "User", subTypes = {
AddressParam.class})
public class UserParam implements Serializable {
private static final long serialVersionUID = 1L;
@NotEmpty(message = "{user.msg.userId.notEmpty}", groups = {
EditValidationGroup.class}) // 这里
private String userId;
}
- controller中的接口使用校验时使用分组
@Slf4j
@Api(value = "User Interfaces", tags = "User Interfaces")
@RestController
@RequestMapping("/user")
public class UserController {
/**
* http://localhost:8080/user/add .
*
* @param userParam user param
* @return user
*/
@ApiOperation("Add User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("add")
public ResponseEntity<UserParam> add(@Validated(AddValidationGroup.class) @RequestBody UserParam userParam) {
return ResponseEntity.ok(userParam);
}
/**
* http://localhost:8080/user/add .
*
* @param userParam user param
* @return user
*/
@ApiOperation("Edit User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("edit")
public ResponseEntity<UserParam> edit(@Validated(EditValidationGroup.class) @RequestBody UserParam userParam) {
return ResponseEntity.ok(userParam);
}
}
自定义validation
如何按照自定的规则进行校验?
- 自定义注解
package tech.pdai.springboot.validation.group.validation.custom;
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;
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {
TelephoneNumberValidator.class}) // 指定校验器
public @interface TelephoneNumber {
String message() default "Invalid telephone number";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
}
- 定义校验器
public class TelephoneNumberValidator implements ConstraintValidator<TelephoneNumber, String> {
private static final String REGEX_TEL = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
try {
return Pattern.matches(REGEX_TEL, s);
} catch (Exception e) {
return false;
}
}
}
下面就可以正常使用添加校验约束了。