Appearance
参数校验是后端请求的第一道防线,不符合条件的请求,越在前面拦截掉,消耗的资源越少。 对参数进行校验,我们可能会出现如下类似代码:
@RestController@RequestMapping("/user")public classUserControllerextendsBaseController{ @PostMapping("/add") public ApiResultaddUser(@RequestBodyUseruser) { if(user == null) { returnApiResult.fail(" ==对象不能为空==
"); } if(StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) { returnApiResult.fail(" ==账号、密码或邮箱不能为空==
"); } if(user.getAccount().length() < 6|| user.getAccount().length() > 11) { returnApiResult.fail(" ==账号长度必须是==6-11==个字符==
"); } if(user.getPassword().length() < 6|| user.getPassword().length() > 16) { returnApiResult.fail(" ==密码长度必须是==6-16==个字符==
"); } if(!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$", user.getEmail())) { returnApiResult.fail(" ==邮箱格式不正确==
"); } // 新增用户操作
returnApiResult.success(); }} ==复制代码== 这样实现并没有什么错,但是看起来实在不够优雅。 下面使用Spring Validator对该代码进行优化。 具体实现Validator + BindResult 首先在对象上通过注解的方式定义校验规则,并指定校验失败后的信息,如下:
package com.minchengjianshe.organizationalandpersonal.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.minchengjianshe.organizationalandpersonal.common.Common; import com.minchengjianshe.organizationalandpersonal.validator.Phone; import lombok.Getter; import lombok.Setter; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @TableName("sys_user") @Getter @Setter public class User extends Common { @TableId(type = IdType.AUTO) private Long id; @NotNull(message = " 用户账号不能为空
") @Size(min = 6, max = 11, message = " 账号长度必须是6-11个字符
") private String account; @NotNull(message = " 用户密码不能为空
") @Size(min = 6, max = 11, message = " 密码长度必须是6-16个字符
") private String password; @Size(max = 40, message = " 用户昵称不能超过40个字符
") private String nickname; @Email(message = " 邮箱格式不正确
") private String email; @Phone(message = " 手机号格式不正确
") private String phone; } 校验规则定义完后,在接口上添加@Vaild注解和BindResult参数即可完成校验,如下:
package com.minchengjianshe.organizationalandpersonal.controller; import com.minchengjianshe.organizationalandpersonal.common.ApiResult; import com.minchengjianshe.organizationalandpersonal.entity.User; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; 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.Valid; @RestController @RequestMapping("/user") public class UserController extends BaseController { @PostMapping("/add") public ApiResult addUser(@RequestBody @Valid User user, BindingResult bindingResult) { // 参数校验失败,会将错误信息封装成在
BindingResult for (ObjectError error : bindingResult.getAllErrors()) { return ApiResult.fail(error.getDefaultMessage()); } // 新增用户操作
return ApiResult.success(); } } 当我们在访问接口时,未填写用户账户,则会返回如下结果:
{"code":500,"data":null,"message":" ==用户账号不能为空==
"} ==复制代码== 使用该方式已经是非常方便的进行参数校验方式了。 但是不难发现,当有多个接口需要进行参数验证时,就需要在每个接口中添加参数BindingResult。 重复写如下代码: // 参数校验失败,会将错误信息封装成在
BindingResultfor(ObjectError error: bindingResult.getAllErrors()) { returnApiResult.fail(error.getDefaultMessage());} 程序员可不是只会Ctrl+C和Ctrl+V的,我们可以通过异常统一处理来解决这个问题。 Validator + 异常统一处理 如果在接口中不添加参数BindingResult,校验失败则会抛出MethodArgumentNotValidException异常,在异常中包含校验失败的信息。
所以只要统一处理MethodArgumentNotValidException异常即可。 在【异常统一处理的三种方式】这一篇文章中, 介绍了三种异常统一处理的方式,这里使用@ControllerAdvice + @ExceptionHandler的方式
@Slf4j@ControllerAdvicepublic class GlobalExceptionHandler { /** * MethodArgumentNotValidException 异常处理
 */@ResponseBody@ExceptionHandler(MethodArgumentNotValidException.class) public ApiResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { BindingResultbindingResult= e.getBindingResult(); for(ObjectError error : bindingResult.getAllErrors()) { // 返回检验信息
returnApiResult.fail(error.getDefaultMessage()); } returnApiResult.fail(" ==服务异常,请稍后重试==
"); }} 在对异常进行统一处理后,在接口中只需添加注解@Vaild即可,相当的简洁。
package com.minchengjianshe.organizationalandpersonal.controller; import com.minchengjianshe.organizationalandpersonal.common.ApiResult; import com.minchengjianshe.organizationalandpersonal.entity.User; 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.Valid; @RestController @RequestMapping("/user") public class UserController extends BaseController { @PostMapping("/add") public ApiResult addUser(@RequestBody @Valid User user) { // 新增用户操作
returnApiResult.success() return ApiResult.success(); } } 至此,已经能很优雅的使用参数校验。 但是别以为这样就万事大吉了。虽然默认提供的注解能够校验绝大部分的情况,但是还有一些特殊的情况,比如校验用户手机号,这没有现成的Validator。 这时,就需要自定义Validator。 自定义Validator 需要自定义注解和实现校验逻辑。 这里定义注解@Phone,来校验手机号格式:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)@Documented@Constraint(validatedBy = {PhoneValidator.class})public @interfacePhone { Stringmessage() default" ==手机号格式不正确==
"; Class<?>[]groups() default{}; Class<? extendsPayload>[]payload() default{};} ==复制代码== @Constraint(validatedBy = {PhoneValidator.class}) 用来指定校验类为PhoneValidator。
publicclassPhoneValidatorimplementsConstraintValidator<Phone, String> { @OverridepublicbooleanisValid(Stringphone, ConstraintValidatorContext context) { // 校验手机格式
returnPattern.matches("^1[3-9]\d{9}", phone); }} ==复制代码== 这样就能够通过@Phone来校验手机号格式。
publicclassUser{ @NotNull(message = " ==用户手机号不能为空==
") @Phone(message = " ==手机号格式不正确==
") private String phone;} ==复制代码== 总结 通过Validator和异常统一处理,很优雅的实现了参数校验。并通过自定义Validator,可以实现各种复杂的校验。 感谢阅读,如果感觉有帮助的话,不妨随手点个赞! 源码
github.com/zhuqianchan
… 往期 > 来自
<https://juejin.cn/post/6844904165806718984\>
