统一响应,自定义校验器,自定义异常,统一异常处理器

文章目录

1.基本准备(构建一个SpringBoot模块)

1.在A_universal_solution模块下创建新的子模块unified-processing

image-20240506125441280

2.pom.xml引入基本依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 继承spring-boot父模块 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- 如果交给父模块统一管理,但是又要继承springboot的父模块,就必须加这个 -->
    </parent>

    <artifactId>unified-processing</artifactId>
    <packaging>jar</packaging>

    <name>unified-processing</name>
    <url>http://maven.apache.org</url>


    <!-- 解决java: -source 1.5 中不支持 diamond 运算符 问题 -->
    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>


    <dependencies>
        <!--validation 参数校验-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.4.5</version>
        </dependency>

        <!-- springboot两个常规配置 -->
        <!-- spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- lombok也是常规配置 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- jupiter测试 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <!-- maven打包插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.编写springboot启动类
package com.sun.solution;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 13:00
 * @Version 1.0
 */
@SpringBootApplication
public class UnifiedApplication {
    public static void main(String[] args) {
        SpringApplication.run(UnifiedApplication.class, args);
    }
}

4.启动测试

image-20240506130251247

2.统一响应处理

1.首先定义一个响应枚举类 RespBeanEnum.java 每个枚举对象都有code和message
package com.sxs.seckill.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

/**
 * Description: 响应枚举类
 *
 * @Author sun
 * @Create 2024/5/5 15:15
 * @Version 1.0
 */

@Getter
@AllArgsConstructor
@ToString
public enum RespBeanEnum {
    // 通用
    SUCCESS(200, "SUCCESS"),
    ERROR(500, "服务端异常"),
    //登录模块
    LOGIN_ERROR(500210, "用户名或者密码错误"),
    MOBILE_ERROR(500211, "手机号码格式不正确"),
    BING_ERROR(500212, "参数绑定异常"),
    MOBILE_NOT_EXIST(500213, "手机号码不存在"),
    PASSWORD_UPDATE_FAIL(500214, "更新密码失败");
    //其他模块。。。

    // 响应码和响应信息
    private final Integer code;
    private final String message;
}

2.然后定义一个响应的Bean RespBean.java ,可以调用响应枚举类,进行响应
package com.sxs.seckill.vo;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * 通用响应数据封装类。
 * 提供了构造函数和静态方法来创建响应对象,支持链式调用来设置属性。
 */
@Getter
@Setter
@Accessors(chain = true) // 支持链式调用
public class RespBean {
    private long code;
    private String message;
    private Object data;

    /**
     * 默认构造函数。
     */
    public RespBean() {}

    /**
     * 构造函数,初始化响应码和消息。
     * @param code 响应码。
     * @param message 响应消息。
     */
    public RespBean(long code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 构造函数,初始化响应码、消息和数据。
     * @param code 响应码。
     * @param message 响应消息。
     * @param data 响应数据。
     */
    public RespBean(long code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * 成功响应,携带数据。
     * @param data 响应数据。
     * @return 生成的成功响应对象。
     */
    public static RespBean success(Object data) {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), data);
    }

    /**
     * 成功响应,不携带数据。
     * @return 生成的成功响应对象。
     */
    public static RespBean success() {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), null);
    }

    /**
     * 错误响应,只携带错误枚举。
     * @param respBeanEnum 错误枚举,包含错误码和消息。
     * @return 生成的错误响应对象。
     */
    public static RespBean error(RespBeanEnum respBeanEnum) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
    }

    /**
     * 错误响应,携带错误枚举和额外数据。
     * @param respBeanEnum 错误枚举,包含错误码和消息。
     * @param data 额外数据。
     * @return 生成的错误响应对象。
     */
    public static RespBean error(RespBeanEnum respBeanEnum, Object data) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), data);
    }

    /**
     * 设置响应数据。
     * @param data 响应数据。
     * @return 当前对象,支持链式调用。
     */
    public RespBean withData(Object data) {
        this.data = data;
        return this;
    }

    /**
     * 设置响应消息。
     * @param message 响应消息。
     * @return 当前对象,支持链式调用。
     */
    public RespBean withMessage(String message) {
        this.message = message;
        return this;
    }
}

3.测试使用
1.目录结构

image-20240506131857665

2.ResponseTest.java
package com.sun.solution.conroller;

import com.sun.solution.unified_response_processing.RespBean;
import com.sun.solution.unified_response_processing.RespBeanEnum;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 13:07
 * @Version 1.0
 */
@RestController
public class ResponseTest {
    /**
     * 成功响应,不携带数据。
     *
     * @return
     */
    @RequestMapping("/success1")
    public RespBean success1() {
        return RespBean.success();
    }

    /**
     * 成功响应,携带数据。
     *
     * @return
     */
    @RequestMapping("/success2")
    public RespBean success2() {
        return RespBean.success("成功响应,携带数据。");
    }


    /**
     * 失败响应,不携带数据。
     *
     * @return
     */
    @RequestMapping("/error1")
    public RespBean error1() {
        return RespBean.error(RespBeanEnum.ERROR);
    }

    /**
     * 失败响应,携带数据。
     *
     * @return
     */
    @RequestMapping("/error2")
    public RespBean error2() {
        return RespBean.error(RespBeanEnum.ERROR, "失败响应,携带数据。");
    }

    /**
     * 测试withData方法
     *
     * @return
     */
    @RequestMapping("/withData")
    public RespBean withData() {
        return RespBean.error(RespBeanEnum.ERROR).withData("测试withData方法");
    }

    /**
     * 测试withMessage方法
     *
     * @return
     */
    @RequestMapping("/withMessage")
    public RespBean withMessage() {
        return RespBean.error(RespBeanEnum.ERROR).withMessage("测试withMessage方法");
    }

}

3.浏览器测试
1.成功响应,不携带数据。

image-20240506132159483

2.成功响应,携带数据。

image-20240506132322584

3.失败响应,不携带数据。

image-20240506132344487

4.失败响应,携带数据。

image-20240506132403115

5.测试withData方法。

image-20240506132427551

6.测试withMessage方法。

image-20240506132447397

3.自定义校验器

1.首先编写一个Bean测试使用,LoginVo.java
package com.sun.solution.vo;

import lombok.Data;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 13:29
 * @Version 1.0
 */
@Data
public class LoginVo {
    private String mobile;
    private String password;
}
2.需求分析
自定义校验器来校验手机号和密码
3.通用校验器模板
1.定义通用校验器接口 GenericValidatorInterface.java
package com.sun.solution.validator;

/**
 * Description: 通用校验器接口,用于重新定义校验方法,使其更加灵活
 *
 * @Author sun
 * @Create 2024/5/6 13:34
 * @Version 1.0
 */

public interface GenericValidatorInterface {
    /**
     * 校验接口
     * @param value 待校验的值,根据情况自定义类型
     * @param annotationValue 注解中的value值,根据情况自定义类型
     * @param required 是否必填
     * @return
     */
    boolean isValid(String value, String annotationValue, boolean required);
}

2.自定义校验注解 GenericValidation.java
package com.sun.solution.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * Description: 自定义校验注解
 *
 * @Author sun
 * @Create 2024/5/6 13:38
 * @Version 1.0
 */
@Documented
@Constraint(validatedBy = GenericValidatorImpl.class) // 1.这里是校验器的实现类
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface GenericValidation {
    // 2.这里是注解的属性
    // message是校验失败时的提示信息
    String message() default "校验失败!";
    // value是注解的值,可以根据情况自定义类型,类型改变则校验器也需要改变
    String value() default "";
    // required是是否必填
    boolean required() default true;

    // 下面这两个属性必须添加,是默认属性
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

3.通用校验器实现类 GenericValidatorImpl.java
package com.sun.solution.genericvalidator;

import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * Description: 通用校验器实现类,ConstraintValidator<TestValidation, String>中的两个参数分别是注解和校验的值类型
 *
 * @Author sun
 * @Create 2024/5/6 13:42
 * @Version 1.0
 */
public class GenericValidatorImpl implements ConstraintValidator<GenericValidation, String>, GenericValidatorInterface {

    // 注解中的value值,根据情况自定义类型
    private String annotationValue;
    // 注解中的required值,表示是否必填
    private boolean required;

    /**
     * 初始化方法,获取注解中的value值和required值
     * @param annotation
     */
    @Override
    public void initialize(GenericValidation annotation) {
        this.annotationValue = annotation.value();
        this.required = annotation.required();
    }

    /**
     * 初始的校验方法
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return isValid(value, annotationValue, required);
    }

    /**
     * 增强的校验方法
     * @param value 待校验的值
     * @param annotationValue 注解中的value值,根据情况自定义类型
     * @param required 是否必填
     * @return
     */
    @Override
    public boolean isValid(String value, String annotationValue, boolean required) {
        // 校验逻辑编写,根据三个参数进行校验
        return false;
    }
}

4.使用方式
1.需求分析

假设,要校验的字段类型是Integer类型,注解中的value是int数组类型

2.将模板复制一份,放到validator包下,准备进行修改

image-20240506142134488

3.要校验的字段为Integer类型,也就是修改value类型

GenericValidatorInterface.java

image-20240506142712265

GenericValidatorImpl.java

image-20240506142756701

4.注解中的value是int数组类型,也就是修改annotationValue的类型

GenericValidation.java

image-20240506143335834

GenericValidatorImpl.java

image-20240506143407645

GenericValidatorInterface.java

image-20240506143434441

5.修改三个类的名字前缀为Test,直接修改然后alter +enter 让IDEA自动修改

image-20240506143656011

6.TestValidatorImpl.java编写校验逻辑
    /**
     * 增强的校验方法
     * @param value 待校验的值
     * @param annotationValue 注解中的value值,根据情况自定义类型
     * @param required 是否必填
     * @return
     */
    @Override
    public boolean isValid(Integer value, int[] annotationValue, boolean required) {
        // 如果不是必填项,且值为空,则直接返回true
        if (!required && value == null) {
            return true;
        }
        // 如果是必填项,且值为空,则直接返回false
        if (required && value == null) {
            return false;
        }
        // 如果注解中的value值不为空,且待校验的值不在value值中,则返回false
        if (annotationValue.length > 0) {
            for (int i : annotationValue) {
                if (value == i) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }
7.修改LoginVo.java 添加测试校验字段
package com.sun.solution.vo;

import com.sun.solution.validator.TestValidation;
import lombok.Data;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 13:29
 * @Version 1.0
 */
@Data
public class LoginVo {
    private String mobile;
    private String password;
    @TestValidation(value = {1, 2, 3}, required = true)
    private Integer test;
}
8.编写controller加上@Valid字段进行校验
package com.sun.solution.conroller;

import com.sun.solution.unified_response_processing.RespBean;
import com.sun.solution.vo.LoginVo;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 14:56
 * @Version 1.0
 */
@RestController
public class ValidationTest {
    @PostMapping("/test")
    public RespBean test(@Valid @RequestBody LoginVo loginVo) {
        return RespBean.success("success!");
    }
}

9.测试

image-20240506150821623

image-20240506150835102

4.统一异常处理器整合自定义校验器

1.编写自定义异常携带响应枚举对象 CustomException.java
package com.sun.solution.exception;

import com.sun.solution.unified_response_processing.RespBeanEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Description: 自定义异常类,具有响应枚举的属性。
 *
 * @Author sun
 * @Create 2024/5/6 15:15
 * @Version 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomException extends RuntimeException{
    private RespBeanEnum respBeanEnum;
}

2.编写全局异常处理器 GlobalExceptionHandler.java
package com.sun.solution.exception;

import com.sun.solution.unified_response_processing.RespBean;
import com.sun.solution.unified_response_processing.RespBeanEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/6 15:16
 * @Version 1.0
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    // 处理所有的异常
    @ExceptionHandler(Exception.class)
    public RespBean exceptionHandler(Exception e) {
        // 日志记录异常信息及堆栈
        log.error("Exception caught: ", e); 
        
        // 如果得到的是自定义异常的对象,那么直接返回这个异常的响应枚举类信息
        if (e instanceof CustomException) {
            CustomException ex = (CustomException) e;
            return RespBean.error(ex.getRespBeanEnum());
        } else if (e instanceof BindException) {
            // 如果是绑定异常,就获取绑定异常的message信息,返回给前端
            // 需要获取改异常 BindException,进行打印
            BindException ex = (BindException) e;
            // 获取绑定异常的信息
            RespBean respBean = RespBean.error(RespBeanEnum.BING_ERROR).withMessage("参数校验异常:" +
                    ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return respBean;
        }
        
        // 如果不是自定义异常,那么返回服务端异常
        return RespBean.error(RespBeanEnum.ERROR);
    }
}

3.最佳实践
1.当需要响应error时直接抛出自定义异常对象,指定响应枚举对象

image-20240506153836780

image-20240506153848112

2.此时当出现参数绑定异常时也会交给统一异常处理解决

image-20240506154042389

3.当出现其他异常时,就会响应服务端异常,控制台也会打印错误信息

image-20240506154219232

image-20240506154225690

相关推荐

  1. SpringBoot实现定义异常+全局异常统一处理

    2024-06-06 00:26:01       35 阅读
  2. 认识异常定义异常

    2024-06-06 00:26:01       7 阅读
  3. Springboot定义全局异常处理

    2024-06-06 00:26:01       32 阅读
  4. springboot全局异常处理定义异常处理

    2024-06-06 00:26:01       40 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-06 00:26:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-06 00:26:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-06 00:26:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-06 00:26:01       18 阅读

热门阅读

  1. 时间步和CLK之间的区别和联系

    2024-06-06 00:26:01       8 阅读
  2. Redis的非关系型数据库

    2024-06-06 00:26:01       7 阅读
  3. 深度学习之学习率调度器Scheduler介绍

    2024-06-06 00:26:01       12 阅读
  4. chap6 RNN

    2024-06-06 00:26:01       8 阅读
  5. FPGA编程与PLC编程的区别:深入解析与对比

    2024-06-06 00:26:01       10 阅读
  6. 解析自动驾驶算法四大模块的问题与后续发展

    2024-06-06 00:26:01       6 阅读
  7. iOS与前端:深入解析两者之间的区别与联系

    2024-06-06 00:26:01       9 阅读
  8. RDK X3(旭日X3派) qt.qpa.xcb: could not connect to display

    2024-06-06 00:26:01       9 阅读
  9. axios的基本使用

    2024-06-06 00:26:01       10 阅读
  10. Python | R 雌雄配对和鱼仔变异马尔可夫链

    2024-06-06 00:26:01       8 阅读
  11. 「前端+鸿蒙」核心技术HTML5+CSS3(八)

    2024-06-06 00:26:01       8 阅读
  12. Web前端开发基础笔记(6)

    2024-06-06 00:26:01       7 阅读
  13. CentOS 8 (stream) 迁移到 其他开源linux almalinux

    2024-06-06 00:26:01       8 阅读
  14. 基于单片机的脉搏测量仪毕业设计

    2024-06-06 00:26:01       7 阅读
  15. Kafka Streams介绍及在idea中的配置

    2024-06-06 00:26:01       12 阅读
  16. python内对sqlite3数据库表删除某几列相同的行index

    2024-06-06 00:26:01       8 阅读
  17. 除visio以外的几款好用流程图绘制工具

    2024-06-06 00:26:01       10 阅读
  18. 用队列实现栈-力扣

    2024-06-06 00:26:01       9 阅读