【谷粒商城之JSR303数据校验和集中异常处理】

news2024/11/25 4:35:44

本笔记内容为尚硅谷谷粒商城JSR303数据校验和集中异常处理部分

目录

一、简介

二、SR303数据校验使用步骤

1、引入依赖

2、给参数对象添加校验注解

常见的注解

3、接口参数前增加@Valid 开启校验

三、异常的统一处理

四、分组解决校验

1、创建Groups 

2、添加分组 

五、自定义校验注解

1、创建约束规则

2、创建约束校验器

3、自定义校验注解校验失败后的返回信息


一、简介


JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。

Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

后端在处理前端传过来的数据时,尽管前端表单已经加了校验逻辑,但是作为严谨考虑,在后端对接口传输的数据做校验也必不可少。 

二、SR303数据校验使用步骤


1、引入依赖

使用spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖

2、给参数对象添加校验注解

常见的注解

Bean Validation 中内置的 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(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

示例: 

 

3、接口参数前增加@Valid 开启校验

Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。

/**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid  @RequestBody BrandEntity brand, BindingResult result){
        if (result.hasErrors())
        {
            Map<String,String> map=new HashMap<>();
            //1.获取校验错误结果
            result.getFieldErrors().forEach((item)->{
                //获取到错误提示
                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();
    }

三、异常的统一处理


参数校验不通过时,会抛出 BingBindException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。

可以在common中新建一个枚举用于存放我们异常类型

例:

package com.atguigu.common.exception;

/**
 * @Description: 错误状态码枚举
 *
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *      002:短信验证码频率太高
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 *  15:用户
 *
 *
 *
 **/

public enum BizCodeEnum {

    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败"),
    TO_MANY_REQUEST(10002,"请求流量过大,请稍后再试"),
    SMS_CODE_EXCEPTION(10002,"验证码获取频率太高,请稍后再试"),
    PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
    USER_EXIST_EXCEPTION(15001,"存在相同的用户"),
    PHONE_EXIST_EXCEPTION(15002,"存在相同的手机号"),
    NO_STOCK_EXCEPTION(21000,"商品库存不足"),
    LOGINACCT_PASSWORD_EXCEPTION(15003,"账号或密码错误"),
    ;

    private Integer code;

    private String message;

    BizCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

在exception包下创建一个异常类

例:

package com.atguigu.gulimall.product.exception;

import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * Description: 集中处理异常
 *
 */
@Slf4j
/*
@RestController
@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
*/
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControlleAdvice {

    //捕获异常指定异常 MethodArgumentNotValidException
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handValidException(MethodArgumentNotValidException e){
         log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();

        Map<String,String> map=new HashMap<>();
        bindingResult.getFieldErrors().forEach((item)->{
           map.put(item.getField(),item.getDefaultMessage());
        });
        return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(), BizCodeEnum.VAILD_EXCEPTION.getMessage()).put("data",map);
    }

    //任意异常类型
    @ExceptionHandler(value = Throwable.class)
    public R handException(Throwable throwable){

        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMessage());
    }
}

 然后只需要校验的参数Bean前添加 @Valid 开启校验功能就可以了

例:

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

四、分组解决校验


 新增和修改对于实体的校验规则是不同的,例如id是自增的时,新增时id要为空,修改则必须不为空;新增和修改,若用的恰好又是同一种实体,那就需要用到分组校验。

校验注解都有一个groups属性,可以将校验注解分组。

1、创建Groups 

从源码可以看出 groups 是一个Class<?>类型的数组,那么就可以创建一个Groups

 或

package com.xxxx.common.validator.group;

/**
 * 新增数据 Group
 */
public interface AddGroup {
}
package com.xxxx.common.validator.group;

/**
 * 更新数据 Group
 *
 */

public interface UpdateGroup {

}

2、添加分组 

给参数对象的校验注解添加分组

例:

package com.atguigu.gulimall.product.entity;

import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.atguigu.common.valid.UpdateStatusGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 * 
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必须指定品牌ID",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定ID",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message = "logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
	@ListValue(vals = {0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty(groups = {AddGroup.class})
	//@Pattern自定义可以传入正则表达式
	@Pattern(regexp = "^[a-zA-Z]$",message = "检索字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups = {AddGroup.class})
	@Min(value = 0,message = "排序必须大于等于0",groups = {AddGroup.class,UpdateGroup.class})
	private Integer sort;

}

Controller 中原先的@Valid不能指定分组 ,需要替换成@Validated

@RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(/*@Valid*/ @Validated({AddGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/){
        /*if (result.hasErrors())
        {
            Map<String,String> map=new HashMap<>();
            //1.获取校验错误结果
            result.getFieldErrors().forEach((item)->{
                //获取到错误提示
                String message = item.getDefaultMessage();
                //获取到错误的属性名
                String field=item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交数据不合法").put("data",map);
        }else{
            brandService.save(brand);
        }*/
        brandService.save(brand);
        return R.ok();
    }

五、自定义校验注解


虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要自定义校验注解。

假设一个字段规定使用0和1两种状态

点进去@NotNull查看 ,发现这些接口都有这些相同的信息

1、创建约束规则

我们可以跟着创建约束规则ListValue 

/**
 * @Description: 自定义注解
 **/

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class }) /*关联注解器-可以指定多个*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
   
    // 默认会找ValidationMessages.properties
    String message() default "{com.atguigu.common.valid.ListValue.message}";

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

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

     // 可以指定数据只能是vals数组指定的值
    int[] vals() default { };

}

2、创建约束校验器

新建自定义校验器ListValuaConstraintValidator,并实现ConstraintValidator


/**
 * @Description: 校验器
 **/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();

    /**
     * 初始化方法
     * @param constraintAnnotation
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {

        int[] vals = constraintAnnotation.vals();

        for (int val : vals) {
            set.add(val);
        }

    }

    /**
     * 判断是否效验成功
     * @param value 需要效验的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {

        //判断是否有包含的值
        boolean contains = set.contains(value);

        return contains;
    }

}

3、自定义校验注解校验失败后的返回信息

新建配置文件ValidationMessages.properties,定义我们自定义校验注解校验失败后的返回信息

com.xxx.common.valid.ListValue.message =  必须提交指定参数

 例:

com.atguigu.common.valid.ListValue.message=必须提交指定的值

记得在刚刚编写好的自定义注解ListVaue中关联我们自定义的检验器

最后我们在使用@ListValue时就可以固定设定只能接收哪些值了

测试

六、总结


JSR303
*   1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
*   2)、开启校验功能@Valid
*      效果:校验错误以后会有默认的响应;
*   3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
*   4)、分组校验(多场景的复杂校验)
*         1)、  @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
*          给校验注解标注什么情况需要进行校验
*         2)、@Validated({AddGroup.class})
*         3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
*
*   5)、自定义校验
*      1)、编写一个自定义的校验注解
*      2)、编写一个自定义的校验器 ConstraintValidator
*      3)、关联自定义的校验器和自定义的校验注解
*      @Documented
* @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
* @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
* @Retention(RUNTIME)
* public @interface ListValue {
统一的异常处理
* @ControllerAdvice
*  1)、编写异常处理类,使用@ControllerAdvice。
*  2)、使用@ExceptionHandler标注方法可以处理的异常。

 结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL数据库之表的增删改查(进阶)

目录1. 数据库约束1.1 约束类型1.2 NULL约束1.3 UNIQUE&#xff1a;唯一约束1.4 DEFAULT&#xff1a;默认值约束1.5 PRIMARY KEY&#xff1a;主键约束1.6 FOREIGN KEY&#xff1a;外键约束1.7 CHECK约束2 表之间的关系2.1 一对一2.2 一对多2.3 多对多3 新增4 查询4.1 聚合查询4…

Redis一致性问题

&#xff08;1&#xff09;何为一致性&#xff1f; 1、定义&#xff1a; 指系统中各节点数据保持一致。 分布式系统中&#xff0c;可以理解为多个节点中的数据是一致的。 2、分类&#xff1a; 强一致性&#xff1a;写进去的数据是什么&#xff0c;读出来的数据就是什么。弱一…

DeepSpeed-Chat:最强ChatGPT训练框架,一键完成RLHF训练!

https://github.com/microsoft/DeepSpeedExamples/tree/master/applications/DeepSpeed-Chat 一个快速、负担得起、可扩展和开放的系统框架&#xff0c;用于实现端到端强化学习人类反馈 (RLHF) 培训体验&#xff0c;以生成各种规模的高质量 ChatGPT 样式模型。 目录 &#x…

计算机体系结构-体系结构基础与流水线原理

计算机体系结构&#xff1a;体系结构基础与流水线原理 ​ 计算机体系结构&#xff1a;量化设计与分析一书以RISC-V为例介绍计算机体系结构。本文为第一部分&#xff0c;介绍体系结构的基本知识和流水线原理。笔记内容为原书的第一章&#xff0c;附录A、B、C。 第一章 量化设计…

练习Tomcat

文章目录1. 简述静态网页和动态网页的区别。2. 简述 Webl.0 和 Web2.0 的区别。3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。1. 简述静态网页和动态网页的区别。 静态网页&#xff1a; &#xff08;1&#xff09;静态网页不能简单地理解成静止不…

SCADE Display(OpenGL)软件设计文档生成工具的设计考虑

SCADE Display&#xff08;OpenGL&#xff09;软件设计文档生成工具的设计考虑 2018年6月 1 引言 本文档描述在SCADE Display软件设计文档生成工具&#xff08;以下简称为SDYSDDGEN&#xff09;的设计过程中考虑到的一些问题及其解决方案。 2 目标 SDYSDDGEN的目标设定为&…

面向对象程序设计 C++总结笔记(1)

面向对象程序设计 学习方法 理解基本原理掌握程序设计方法加强动手实践 课程目标 理解面向对象程序设计的基本原理&#xff0c;掌握面向对象技术的基本概念和封装性、继承性和多态性&#xff0c;能够具有面向对象程序设计思想。掌握C语言面向对象的基本特性和C语言基础知识&…

就在20号!袋鼠云春季生长大会邀您共观数智生机,我们云上见

如今&#xff0c;数字经济正逐步走向深化应用、规范发展、普惠共享的新阶段&#xff0c;数字经济与实体经济深度融合、基础软件国产化替代成为数字时代主潮流。 「 2023 袋鼠云春季生长大会」乘风而起&#xff0c;带您走近大数据基础软件——数栈&#xff0c;低代码数字孪生世界…

Hadoop之Yarn篇

目录 ​编辑 Yarn的工作机制&#xff1a; 全流程作业&#xff1a; Yarn的调度器与调度算法&#xff1a; FIFO调度器&#xff08;先进先出&#xff09;&#xff1a; 容量调度器&#xff08;Capacity Scheduler&#xff09;&#xff1a; 容量调度器资源分配算法&#xff1…

【面试题】对 JSON.stringify()与JSON.parse() 理解

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 重新学习这两个API的起因 在本周五有线上的项目&#xff0c;16:30开始验证线上环境。 开始…

【数据挖掘与商务智能决策】第十一章 AdaBoost与GBDT模型

11.1 AdaBoost模型简单代码实现 1.AdaBoost分类模型演示 from sklearn.ensemble import AdaBoostClassifier X [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]] y [0, 0, 0, 1, 1]model AdaBoostClassifier(random_state123) model.fit(X, y)print(model.predict([[5, 5]]))[0…

使用 Urch 让 Ubuntu 原生远程控制功能稳定可靠

有些时候&#xff0c;使用远程控制能够简化不少运维和操作的事情。 本篇文章分享如何通过开源工具 “Urch&#xff08;Ubuntu Remote Control Helper&#xff09;” 让 Ubuntu 原生的远程控制&#xff08;远程桌面&#xff09;功能稳定可靠。 方案已经经过 Ubuntu 22.04 LTS …

JVM之低延迟垃圾收集器

目录 低延迟垃圾收集器 概要 各款收集器的并发情况 Shenandoah收集器 Shenandoah相比G1的改进之处 链接矩阵 定义 优点 Shenandoah收集器的工作过程 Brooks Pointer 转发指针技术 转发指针的优缺点 Shenandoah 性能测试 Shenandoah 总结 ZGC 收集器 ZGC的Region的…

编译原理第一章

编译原理笔记 文章目录编译原理笔记day1什么是编译&#xff1f;编译器的结构词法分析概述词法分析的主要任务语法分析概述主要目的主要任务具体实例语义分析概述主要目的主要任务中间代码生成和编译器后端常用的中间表示形式目标代码生成器代码优化器day1 什么是编译&#xff…

Mysql 学习(一)基础知识(待更新)

文章目录服务端处理客户端请求启动项系统变量启动项和系统变量的区别常见的字符集字符集比较规则服务端处理客户端请求 客户端进程向服务器进程发送一段文本&#xff08;MySQL语句&#xff09;&#xff0c;服务器进程处理后再向客户端进程发送一段文本&#xff08;处理结果&am…

Idea使用样式主题

目的 花里胡哨的idea显示主题 安装插件 在preferences>plugins中搜索“Material Theme”安装两个中的一个 重启>设置>选择主题 对比度&#xff08;多选&#xff09; Contrast Mode:对比度模式&#xff0c;目录结构&#xff0c;选项卡等非文本选择前后的颜色对比度。…

Docker部署RabbitMQ(单机,集群,仲裁队列)

RabbitMQ部署指南 1.单机部署 我们在Centos7虚拟机中使用Docker来安装。 1.1.下载镜像 方式一&#xff1a;在线拉取 docker pull rabbitmq:3-management方式二&#xff1a;从本地加载 在课前资料已经提供了镜像包&#xff1a; 上传到虚拟机中后&#xff0c;使用命令加载镜…

【论文阅读】(20230410-20230416)论文阅读简单记录和汇总

&#xff08;20230410-20230416&#xff09;论文阅读简单记录和汇总 2023/04/09&#xff1a;很久没有动笔写东西了&#xff0c;这两周就要被抓着汇报了&#xff0c;痛苦啊呜呜呜呜呜 目录 (CVPR 2023): Temporal Interpolation Is All You Need for Dynamic Neural Radiance …

RPC 漫谈:序列化问题

RPC 漫谈&#xff1a;序列化问题 何为序列 对于计算机而言&#xff0c;一切数据皆为二进制序列。但编程人员为了以人类可读可控的形式处理这些二进制数据&#xff0c;于是发明了数据类型和结构的概念&#xff0c;数据类型用以标注一段二进制数据的解析方式&#xff0c;数据结…

echarts formatter如何自定义百分比小数位置,比如取整数。{b} {d}%

echarts formatter如何自定义百分比小数位置&#xff0c;比如取整数。{b} {d}% 一、现状 我有一个 pie 的图表&#xff0c;option 中的 formatter 是这样的&#xff1a; label: {show: true,position: outside,fontSize: 12,formatter: {b} {d}% },图表数据是这样的 二、需…