谷粒商城_06_JSR303校验+Elasticsearch

Posted 早上真起不来!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谷粒商城_06_JSR303校验+Elasticsearch相关的知识,希望对你有一定的参考价值。

谷粒商城_01_环境搭建
谷粒商城_02_Nacos、网关
谷粒商城_03_前端基础
谷粒商城_04_商品CRUD
谷粒商城_05_阿里云OSS和前端校验

JSR303校验

问题引入:填写form时应该有前端校验,后端也应该有校验

前端的校验是element-ui表单验证:https://element.eleme.cn/#/zh-CN/component/form

  • Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator

  • 使用自定义校验规则可以解决字母限制、密码不一致、非整数限制等等的问题

  • 基本使用

    // dataForm:表单的数据、dataRule:所有检验的规则
    <el-form :model="dataForm" :rules="dataRule"/>
        
    // 表单的某一项用prop指定校验的目标即可使用
    <el-form-item label="密码" prop="checkPass">
    
    // 
    data() 
    	return 
        	dataRule: 
                // 密码校验的字段checkPass,validator: validatePass指向校验的具体方法,既可以将其替换成方法使用
            	checkPass: [
                 validator: validatePass, trigger: 'blur' 
            	],
            
        
    
    var validatePass = (rule, value, callback) => 
        if (value === '') 
            callback(new Error('请再次输入密码'));
            // dataForm表单的数据
         else if (value !== this.dataForm.pass) 
            callback(new Error('两次输入密码不一致!'));
         else 
            callback();
        
    ;
    
  • 后端:@NotNull、@NotBank、@URL等等

普通校验

1、使用校验注解

在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints、org.hibernate.validator.constraints”包中,提供了如@Email、@NotNull、@URL等注解。

  • 主要看注解源码文档来了解其中使用
<!--jsr3参数校验器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.6.1</version>
</dependency>

2、@Valid内置异常

这里内置异常的意思是发生异常时返回的json不是我们的R对象,而是mvc的内置类

在controller类上加:@Validated,方法参数中加校验注解:@Valid,开启校验,

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand)
    brandService.save(brand);

    return R.ok();

测试: http://localhost:88/api/product/brand/save,在postman种发送上面的请求,发现返回的是json对象,但不是我们的R对象

 // 其中
"defaultMessage": "不能为空",

“defaultMessage”: “不能为空”,这些错误消息定义在“hibernate-validator”的“\\org\\hibernate\\validator\\ValidationMessages_zh_CN.properties”文件中。在该文件中定义了很多的错误规则:

javax.validation.constraints.AssertFalse.message     = 只能为false
javax.validation.constraints.AssertTrue.message      = 只能为true
javax.validation.constraints.DecimalMax.message      = 必须小于或等于value
javax.validation.constraints.DecimalMin.message      = 必须大于或等于value
javax.validation.constraints.Digits.message          = 数字的值超出了允许范围(只允许在integer位整数和fraction位小数范围内)
javax.validation.constraints.Email.message           = 不是一个合法的电子邮件地址
javax.validation.constraints.Future.message          = 需要是一个将来的时间
javax.validation.constraints.FutureOrPresent.message = 需要是一个将来或现在的时间
javax.validation.constraints.Max.message             = 最大不能超过value
javax.validation.constraints.Min.message             = 最小不能小于value
javax.validation.constraints.Negative.message        = 必须是负数
javax.validation.constraints.NegativeOrZero.message  = 必须是负数或零
javax.validation.constraints.NotBlank.message        = 不能为空
javax.validation.constraints.NotEmpty.message        = 不能为空
javax.validation.constraints.NotNull.message         = 不能为null
javax.validation.constraints.Null.message            = 必须为null
javax.validation.constraints.Past.message            = 需要是一个过去的时间
javax.validation.constraints.PastOrPresent.message   = 需要是一个过去或现在的时间
javax.validation.constraints.Pattern.message         = 需要匹配正则表达式"regexp"
javax.validation.constraints.Positive.message        = 必须是正数
javax.validation.constraints.PositiveOrZero.message  = 必须是正数或零
javax.validation.constraints.Size.message            = 个数必须在minmax之间

org.hibernate.validator.constraints.CreditCardNumber.message        = 不合法的信用卡号码
org.hibernate.validator.constraints.Currency.message                = 不合法的货币 (必须是value其中之一)
org.hibernate.validator.constraints.EAN.message                     = 不合法的type条形码
org.hibernate.validator.constraints.Email.message                   = 不是一个合法的电子邮件地址
org.hibernate.validator.constraints.Length.message                  = 长度需要在minmax之间
org.hibernate.validator.constraints.CodePointLength.message         = 长度需要在minmax之间
org.hibernate.validator.constraints.LuhnCheck.message               = $validatedValue的校验码不合法, Luhn10校验和不匹配
org.hibernate.validator.constraints.Mod10Check.message              = $validatedValue的校验码不合法,10校验和不匹配
org.hibernate.validator.constraints.Mod11Check.message              = $validatedValue的校验码不合法,11校验和不匹配
org.hibernate.validator.constraints.ModCheck.message                = $validatedValue的校验码不合法, $modType校验和不匹配
org.hibernate.validator.constraints.NotBlank.message                = 不能为空
org.hibernate.validator.constraints.NotEmpty.message                = 不能为空
org.hibernate.validator.constraints.ParametersScriptAssert.message  = 执行脚本表达式"script"没有返回期望结果
org.hibernate.validator.constraints.Range.message                   = 需要在minmax之间
org.hibernate.validator.constraints.Safehtml.message                = 可能有不安全的HTML内容
org.hibernate.validator.constraints.ScriptAssert.message            = 执行脚本表达式"script"没有返回期望结果
org.hibernate.validator.constraints.URL.message                     = 需要是一个合法的URL

org.hibernate.validator.constraints.time.DurationMax.message        = 必须小于$inclusive == true ? '或等于' : ''$days == 0 ? '' : days += '天'$hours == 0 ? '' : hours += '小时'$minutes == 0 ? '' : minutes += '分钟'$seconds == 0 ? '' : seconds += '秒'$millis == 0 ? '' : millis += '毫秒'$nanos == 0 ? '' : nanos += '纳秒'
org.hibernate.validator.constraints.time.DurationMin.message        = 必须大于$inclusive == true ? '或等于' : ''$days == 0 ? '' : days += '天'$hours == 0 ? '' : hours += '小时'$minutes == 0 ? '' : minutes += '分钟'$seconds == 0 ? '' : seconds += '秒'$millis == 0 ? '' : millis += '毫秒'$nanos == 0 ? '' : nanos += '纳秒'

想要自定义错误消息,可以覆盖默认的错误提示信息,如@NotBlank的默认message是

public @interface NotBlank 
	String message() default "javax.validation.constraints.NotBlank.message";

可以在添加注解的时候,修改message:

@NotBlank(message = "品牌名必须非空")
private String name;

当再次发送请求时,得到的错误提示信息:

"defaultMessage": "品牌名必须非空",

但是返回的错误不是R对象,影响接收端的接收,我们可以通过局部异常处理或者统一一次处理解决

局部异常处理BindResult

  • 给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand,
              BindingResult result) // 手动处理异常

    if( result.hasErrors())
        Map<String,String> map=new HashMap<>();
        //1.获取错误的校验结果
        result.getFieldErrors().forEach((item)->
            //获取发生错误时的message,提示信息
            String message = item.getDefaultMessage();
            //获取发生错误的属性的名字
            String field = item.getField();
            map.put(field,message);
        );
        return R.error(400,"提交的数据不合法").put("data",map);
    else 

    
    brandService.save(brand);
    return R.ok();

这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。

统一异常处理

@ExceptionHandler

  • @ ExceptionHandler 需要进行异常处理的方法必须与出错的方法在同一个Controller里面。那么当代码加入了 @ControllerAdvice,则不需要必须在同一个 controller 中了。这也是 Spring 3.2 带来的新特性。 也就是说,@controlleradvice + @ ExceptionHandler 也可以实现全局的异常捕捉。

1、抽取一个异常处理类

  • @ControllerAdvice标注在类上,通过“basePackages”能够说明处理哪些路径下的异常。
  • @ExceptionHandler(value = 异常类型.class)标注在方法上
  • 这里的ConstraintViolationException异常我和项目中不一样,但是可以通过源码定位到里面的属性,然后获取异常信息放入map
/**
 * @author ljy
 * @version 1.0.0
 * @Description 集中处理所有异常
 * @createTime 2021年12月14日 19:22:00
 */
@Slf4j
// @RestControllerAdvice和@ControllerAdvice的关系类似于@RestController
@RestControllerAdvice(basePackages = "com.liu.gulimall.product.controller")// 管理的controller
public class GulimallExceptionControllerAdvice 

    @ExceptionHandler(value = ConstraintViolationException.class) // 感知精确异常
    // 也可以返回ModelAndView
    // exception是controller中的异常,精确匹配ConstraintViolationException这个异常
    // MethodArgumentNotValidException
    public R handleValidException(ConstraintViolationException exception)
        Map<String,String> map=new HashMap<>();
        // 获取数据校验的错误结果,和之前一样BindingResult
        exception.getConstraintViolations().forEach(item->
            String message = item.getMessage();
            Path propertyPath = item.getPropertyPath();
            map.put(propertyPath.toString(),message);
        );

        log.error("数据校验出现问题,异常类型",exception.getMessage(),exception.getClass());

        return R.error(400,"数据校验出现问题").put("data",map);
    

    @ExceptionHandler(value = Throwable.class)//异常的范围更大
    public R handleException(Throwable throwable)
        log.error("未知异常,异常类型",
                throwable.getMessage(),
                throwable.getClass());
        return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
                BizCodeEnum.UNKNOW_EXEPTION.getMsg());
    


(2)测试: http://localhost:9999/product/brand/save

(3)默认异常处理

@ExceptionHandler(value = Throwable.class)// 异常的范围更大
public R handleException(Throwable throwable)
    log.error("未知异常,异常类型",
              throwable.getMessage(),
              throwable.getClass());
    return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
                   BizCodeEnum.UNKNOW_EXEPTION.getMsg());

(4)错误状态码

正规开发过程中,错误状态码有着严格的定义规则,在项目中我们的错误状态码定义

上面的用法主要是通过@Controller+@ExceptionHandler来进行异常拦截处理,单独定义一个枚举类,用来存储这些错误状态码

/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:10001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
/**
 * @author ljy
 * @version 1.0.0
 * @Description TODO
 * @createTime 2021年12月14日 19:23:00
 */
public enum BizCodeEnum 

    UNKNOW_EXEPTION(10000,"系统未知异常"),

    VALID_EXCEPTION( 10001,"参数格式校验失败");

    private int code;
    private String msg;

    BizCodeEnum(int code, String msg) 
        this.code = code;
        this.msg = msg;
    

    public int getCode() 
        return code;
    

    public String getMsg() 
        return msg;
    

分组校验功能(多场景校验)

前面解决了统一异常处理,但是同一实体比如id,修改的时候不能为空,添加的时候为空,那怎么来校验呢?

我们采取分组的方式

1、定义分组接口,A(添加分组)、B(删除分组),接口只是标识

2、在controller方法中添加的方法中使用 @Validated(A.class)

// 表示添加的时候采用A分组策略
public R save(@Validated(A.class) @RequestBody BrandEntity brand)

3、在实体属性上,比如id,@NotNull(message = “修改id不为空”,groups = B.class)、@Null(message = “新增id必须为空”,groups = A.class)

@NotNull(message = "修改必须定制品牌id", groups = UpdateGroup.class)
@Null(message = "新增不能指定id", groups = AddGroup.class)
@TableId
private Long brandId;

在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。

  • controller接收到之后,根据@Validated表明的分组信息,然后确定品牌对应的校验注解。

总结:

package com.liu.common.valid;

/**
 * @author ljy
 * @version 1.0.0
 * @Description TODO
 * @createTime 2021年12月14日 20:49:00
 */
public interface addGroup 

========================
/**
 * 保存
 */
@RequestMapping("/save")
public R save(@Validated(addGroup.class) @RequestBody BrandEntity brand, BindingResult result)
    brandService.save(brand);
    return R.ok();

========================
/**
 * 品牌名
 */
@NotBlank(message = "品牌名不能为空", groups  以上是关于谷粒商城_06_JSR303校验+Elasticsearch的主要内容,如果未能解决你的问题,请参考以下文章

谷粒商城-JSR303分组校验

谷粒商城-品牌管理-JSR303数据校验

第184天学习打卡(项目 谷粒商城 26统一异常管理 JSR303分组校验)

第185天学习打卡(项目 谷粒商城27 JSR303自定义校验注解 SPU SKU 属性分组效果前端组件抽取 父子组件交互)

商城项目10_JSR303常用注解在项目中如何使用统一处理异常分组校验功能自定义校验注解

商城项目10_JSR303常用注解在项目中如何使用统一处理异常分组校验功能自定义校验注解