spring 参数校验

有参数传递的地方都少不了参数校验。在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。

试想一下,如果在controller层中没有经过任何校验的参数通过service层、dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,

严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果。

因此,对于请求参数,一般上都需要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,并且当参数较多的时候,代码回显的很臃肿,例如:

 

这是一个 bad practise !

所以java的JSR 303: Bean Validation规范就是解决这个问题的。

 

JSR 303只是个规范,并没有具体的实现,目前通常都是由hibernate-validator进行统一参数校验。

 

JSR303定义的基础校验类型:

Constraint

详细信息

@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

 

Hibernate Validator 中附加的 constraint :

Constraint

详细信息

@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内

 

下面通过一个例子来使用一下:

需要校验的参数DTO

@Data

@ApiModel("回帖风控放行后发短消息提醒")

public class PostCheckPassDTO {

    @NotNull(

        message = "回帖用户puid不能为空"

    )

    @ApiModelProperty("回帖用户puid,必传")

    private Long puid;

 

    @NotNull(

        message = "回帖用户username不能为空"

    )

    @ApiModelProperty("回帖用户username,必传")

    private String username;

 

    @NotNull(

        message = "帖子id不能为空"

    )

    @ApiModelProperty("帖子id,必传")

    private Long tid;

 

    @NotNull(

        message = "回帖id不能为空"

    )

    @ApiModelProperty("回帖id")

    private Long pid;

 

    @NotNull(

        message = "回帖内容不能为空"

    )

    @ApiModelProperty("回帖内容")

    private String content;

 

    @ApiModelProperty("回帖引用回帖id")

    private Long quote;

 

}

 

要注意加上@Valid注解开启校验

controller入口方法

@PostMapping("sendRemindForCheckPassPosts")

public DataResult<Boolean> sendRemindForCheckPassPosts(@Valid @RequestBody  PostCheckPassDTO postCheckPassDTO){

    userBbsWriteService.sendRemindForCheckPassPosts(postCheckPassDTO);

    return DataResult.ok(true);

}

 

这时候我们把参数里面的username去掉,请求一下接口,响应结果如下:

 

 

 

看到在校验不通过时,返回的异常信息是不友好的,此时可利用统一异常处理,对校验异常进行特殊处理,

特别说明下,对于异常处理类,共有以下几种情况(被@RequestBody和@RequestParam注解的请求实体,校验异常类是不同的),上代码:

全局异常处理器

@Slf4j

@ControllerAdvice

public class GlobalExceptionHandler {

 

 

    /**

     * 异常处理

     * @param e

     * @return

     */

    @ResponseBody

    @ExceptionHandler(value = Exception.class)

    public DataResult exceptionHandler(Exception e) {

        log.error("GlobalExceptionHandler.exceptionHandler , 异常信息",e);

        /**cat error 上报*/

        Cat.logErrorWithCategory("GlobalExceptionHandler","global error",e);

        return DataResult.fail(e.getMessage());

    }

 

 

    /**

     * 业务异常

     * @param e

     * @return

     */

    @ResponseBody

    @ExceptionHandler(value = BplCommonException.class)

    public DataResult bplCommonExceptionHandler(BplCommonException e) {

        log.warn("",e);

        return DataResult.fail(e.getMessage());

    }

 

    /**

     * 处理所有RequestBody注解参数验证异常

     * @param e

     * @return

     */

    @ExceptionHandler(MethodArgumentNotValidException.class)

    @ResponseBody

    public DataResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e){

        log.warn(e.getMessage());

        return DataResult.fail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());

    }

 

 

    /**

     * 处理所有RequestParam注解数据验证异常

     * @param ex

     * @return

     */

    @ExceptionHandler(BindException.class)

    public DataResult handleBindException(BindException ex) {

        FieldError fieldError = ex.getBindingResult().getFieldError();

        log.warn("必填校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());

        return DataResult.fail(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());

    }

}

 

添加全局异常处理器后,这时我们再次请求接口,看看相应结果:

 

 

上面是最通用也是最常见的使用方式,可以解决绝大部分参数校验的问题。还有剩下的小部分,我们可以通过自定义校验注解的方式解决, 比如我们定义一个检查版本号的注解:

自定义注解

@Documented

//指定注解的处理类

@Constraint(validatedBy = {VersionValidatorHandler.class })

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })

@Retention(RUNTIME)

public @interface ConstantVersion {

 

   String message() default "{constraint.default.const.message}";

 

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

 

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

 

   String value();

 

}

 

注解处理类   VersionValidatorHandler

注解处理类

public class VersionValidatorHandler implements ConstraintValidator<Constant, String> {

 

    private String constant;

 

    @Override

    public void initialize(Constant constraintAnnotation) {

        //获取设置的字段值

        this.constant = constraintAnnotation.value();

    }

 

    @Override

    public boolean isValid(String value, ConstraintValidatorContext context) {

        //判断参数是否等于设置的字段值,返回结果

        return constant.equals(value);

    }

 

}

注解的使用:

自定义注解使用

@ConstantVersion (message = "verson只能为1.0.0",value="1.0.0")

String version;

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值