✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥 弹简特 个人主页

❄️ 个人专栏直通车:


靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:


在这里插入图片描述



一、前言

兄弟们,我们前面完成了对于该项目的文档性内容,那么有了文档的参考,从本期开始,我们就可以正式的进入项目实战环节了,本期将会带你搭建环境、自定义状态码+自定义异常,最后封装统一返回结果


二、项目搭建

1、创建工程

在这里插入图片描述


pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.6</version>
        <relativePath/>
    </parent>

    <groupId>com.zhongge</groupId>
    <artifactId>lottery-system</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>lottery-system</name>
    <description>lottery-system</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- RabbitMQ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- redis 服务 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--spring web依赖包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mysql驱动包 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--lombok依赖包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、分层

在这里插入图片描述

3、创建对应的层

在这里插入图片描述


三、状态码和异常

1、自定义状态码

为什么咱们要自定义一些异常状态码呢?

  • 第1点它的明确性,我们有这些以数字为基础的错误码,它就可以明确的表示当前的一个错误状态,可以更好的避免我们一些问题的所在,总比我们返回一堆错误描述好多。

  • 第2点就是对客户端友好,我们作为调用方,我们呢,是希望人家给我们返回的是可以针对这些错误码去进行一些判断。

  • 第3点是维护性,我们这样定一些错误码的话,我们可以更好的去维护系统。

  • 第4点是错误分类。就是对于我们的系统来说,我们是对系统进行分层的,那么我们可以对每一层进行一个错误的分层处理,那这样的话哪一层错误,我们就可以直接就处理到了。

  • 第5点就是容易检索,因为你容易检索,我们定义这样的错误码,它的明确性很高,我们也可以容易检索是什么问题,因为你每一层有啥错误就很容易检索。

那么这些错误码类我们写在哪一层?我们肯定是写在这个common层,因为呢这个错误码它可以是控制层的错误码,也可以是服务层的错误码,也可以是持久层的错误码,所以我们写在那个common通用业务层这样就可以兼顾控制层、服务层、持久层。

在这里插入图片描述


那我们这个错误码类里面应该有哪些信息呢?

首先是有一个错误码,是整型,然后再有一个描述(String类型的描述)。就是一个错误码对应的一个错误描述。

代码:

@Data
public class ErrorCode {
    /**
     * 错误码
     */
    private final Integer code;

    /**
     * 错误描述
     */
    private final String message;

    //构造方法
    public ErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

那定义完错误码之后,我们接下来就需要给我们当前的系统去进行一个错误的分类,比如说我们对分层进行分类:我们有哪些层呢?
有控制层、服务层和持久层,原则上我们要对这三层进行错误的定义。
不过实际上:我们对持久层dao不定义错误码
为什么呢?因为我们的持久层它可能是sql报错,它有许多的错误码,我们定义不过来。因此我们要在service层把dao层的错误给它包住,故这一点可体现我们是必须要在服务层里面去定义这个错误码的。

好,有了以上条件,我们就可以定一个controller和一个service控制层和服务层的两层的错误码。那除此之外,我们还要定义一个全局的错误码,就是它不包含控制层,也不包含服务层,就是其他未知错误,我们可以把它定义成个全局的错误码。

首先我们定义控制层的错误码类
在这里插入图片描述

public interface ControllerErrorCodeConstants {
    //TODO 具体有哪一些错误码 我们写到对应代码的时候再说

    //------人员模块的错误码--------


    //------活动模块的错误码--------


    //------奖品模块的错误码--------



    //------抽奖的错误码--------
}

其次是定义服务层的错误码类
在这里插入图片描述

public interface ServiceErrorCodeConstants {
    //TODO 具体有哪一些错误码 我们写到对应代码的时候再说

    //------人员模块的错误码--------


    //------活动模块的错误码--------


    //------奖品模块的错误码--------



    //------抽奖的错误码--------
}

最后定义未知错误码类
在这里插入图片描述

public interface GlobalErrorCodeConstants {
    // 成功的错误码
    ErrorCode SUCCESS = new ErrorCode(200, "成功! ");

    //系统异常:500
    ErrorCode INTERNAL_SYSTEM_ERROR = new ErrorCode(500, "系统异常! ");

    //未知异常
    ErrorCode UNKNOWN = new ErrorCode(999, "未知错误! ");
}



2、自定义异常类

我们定义异常类的策略是根据分层来定义的。比如我们的看controller层去定一个统一的异常类,对它进行抓取,我们的service层我们去另外统一的搞一个异常类,对service进行抓取。

所以我们开始吧:
先创建一个包exception
在这里插入图片描述
控制层的异常抓取类。
在这里插入图片描述

@Data
@EqualsAndHashCode(callSuper = true)
public class ControllerException extends RuntimeException{

    /**
     * 异常码
     * @see com.zhongge.lotterysystem.common.errorcode.ControllerErrorCodeConstants //参考
     */
    private Integer code;


    /**
     * 异常信息
     */
    private String message;

    //无参构造 是为了 序列化的
    public ControllerException() {

    }


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


    public ControllerException(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.message = errorCode.getMessage();
    }
}

服务层异常抓取类
在这里插入图片描述

@Data
@EqualsAndHashCode(callSuper = true)
public class ServiceException extends RuntimeException{
    /**
     * 异常码
     * @see com.zhongge.lotterysystem.common.errorcode.ServiceErrorCodeConstants //参考
     */
    private Integer code;


    /**
     * 异常信息
     */
    private String message;

    //无参构造 是为了 序列化的
    public ServiceException() {

    }


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


    public ServiceException(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.message = errorCode.getMessage();
    }
}

注意:
在这里插入图片描述
OK,到此我们的异常类就结束了


四、统一返回结果

1、为什么封装统一返回结果

我们接收参数的那一层,也就是我们的控制层,同时也是和我们前端打交道那层,我们要控制层去封装一个统一的返回结果。
这里我们是使用一个泛型,使用泛型的话,它是允许我们返回任何一个类型。
为什么我们要统一封装一个http请求接口调用的返回结果呢?
比如有一个testA接口,返回的是AResult类型。还有一个testB接口访问的是BResult类的型,这两个接口本身是没有问题的,a返回a的,b的b的,互不影响,但是它对前端的处理有问题。
在这里插入图片描述

什么问题呢?
由于我们一般前端拿到结果之后先判断本次调用是否成功。
那是如何判断呢?
一般前端拿到一个结果之后,他会先判断每次调用是否成功,对于testA()接口来说,他会拿到aResult,简写为a, 然后拿到a里面的状态码code来代表本次调用的结果,如果是code是200就代表成功,如果不是200,就表示不成功。

在这里插入图片描述

那对于testB()来说,如果返回值bResult里面封装的不是code的字段,而是b.errorcode,那对于前端来说他要判断a的code。也要判断b的errorcode这样会比较麻烦,此时对于前端来说,如果他们有一个统一的行为来管理后端的结构,到底是成功还是失败,所以对于前端来说,他肯定是希望我们后端无论调什么接口,都给他一个统一的反馈结果。比如说一个统一的叫做commonResult.code。然后使用commonResult.date来获取统一的数据,这样的话,我们的前端就会方便操作很多,因此我们才会设置泛型,让我们后端任意返回我们的数据类型。
在这里插入图片描述


说明白之后,我们来写一下这个统一的返回类型怎么写?

@Data
//因为我们是进行http进行传输的,因此我们可能需要进行一些序列化。所以我们暂时让它实现一下Serializable。
public class commonResult<T> implements Serializable {
    //code表示调用是否成功 返回的错误码
    private Integer code;

    //数据 正常返回的数据
    private T data;

    //消息 错误码描述
    private String message;
    
}

返回成功我们希望封装哪些数据呢?
在这里插入图片描述

返回失败我们希望封装那些数据呢?
在这里插入图片描述

所以我们上述的代码还需要加两个静态方法。
其实返回统计结果它是有那个格式规则,也就是公式,就是属性加两个静态方法,一个成功一个失败,一般是这两种。

@Data
//因为我们是进行http进行传输的,因此我们可能需要进行一些序列化。所以我们暂时让它实现一下Serializable。
public class commonResult<T> implements Serializable {
    //code表示调用是否成功 返回的错误码
    private Integer code;

    //数据 正常返回的数据
    private T data;

    //消息 错误码描述
    private String message;

    //静态方法返回成功的时候
    public static <T> commonResult<T> success(T data){
        commonResult<T> result = new commonResult<>();
        result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
        result.data = data;
        result.message = "";//或者 GlobalErrorCodeConstants.SUCCESS.getMessage()
        return result;
    }

    //静态方法返回失败的时候
    public static <T> commonResult<T> error(Integer code, String message){
        //断言一下 如果传递进来的code是200 即 success 那么就没必要往下走了(这一步是为了做充分的判断)
        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code),
                "code 不是错误的异常");
        //查询走到这里 说明是错误的code
        commonResult<T> result = new commonResult<>();
        result.code = code;
        //result.data = null;//由于是错误的 所以我们就没有必要往这个code里面去塞数据了,默认就是null
        result.message = message;
        return result;
    }

    //静态方法返回失败的时候:通过我们之前返回的ErrorCode
    public static <T> commonResult<T> error(ErrorCode errorCode){
        return error(errorCode.getCode(),errorCode.getMessage());
    }
}

2、代码解释

2.1 属性

在这里插入图片描述


2.2 成功的静态方法

在这里插入图片描述


2.3 失败的静态方法

第一种写法

在这里插入图片描述

第二种写法

第二种写法 就似乎直接传递我们的状态码对象 然后调用一下第一种写法的方法即可

在这里插入图片描述


2.4 对断言代码的解释

Assert.isTrue(
    !GlobalErrorCodeConstants.SUCCESS.getCode().equals(code),
    "code 不是错误的状态码"
);
1). 这行代码的作用

一句话总结:

强制检查:你传入的 code 绝对不能是成功码(比如 200)
如果传了成功码,程序直接报错停止,避免逻辑出错。


2). 拆解每一部分
Assert.isTrue(条件, 错误信息)

这是 Spring 框架提供的断言工具
作用:

  • 如果 条件为 true → 什么都不做,继续往下走
  • 如果 条件为 false直接抛出异常,并显示后面的错误信息

简单说:
我断言这个条件一定成立,不成立就报错!


!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code)

拆成两部分:

  • GlobalErrorCodeConstants.SUCCESS.getCode()
    → 拿到成功状态码,比如 200
  • ! → 取反(不是)

整句意思:

传入的 code 不是成功码(200)


③ 合起来的逻辑
Assert.isTrue(
    传入的code不是成功码,
    "code 不是错误的状态码"
);

翻译成人话:

我要求:你调用 error() 方法时,必须传错误码
如果你不小心传了 200(成功码),我就直接报错提醒你!


3). 为什么要写这行?

因为你的方法叫 error()
它的功能是:创建一个“失败响应”

逻辑上:

  • 失败响应 → 状态码绝对不能是 200
  • 200 是成功,不是失败

如果有人不小心写:

commonResult.error(200, "操作成功");

这就逻辑矛盾了!

这行断言就是为了:
提前拦截这种低级错误,避免bug藏在代码里。


4). 运行效果举例
正确调用(断言通过)
commonResult.error(500, "服务器异常");

500 ≠ 200 → 条件成立 → 正常执行

错误调用(断言报错)
commonResult.error(200, "成功了");

200 == 200 → 条件不成立 → 直接抛异常
提示信息:

code 不是错误的状态码

5). 总结
Assert.isTrue(你必须满足的条件, 不满足时报错的消息);

你这行的意思:

调用错误方法时,状态码不能是成功码,否则报错!


朋友们,我们本期就到这里吧,下一期我们会完成序列化、日志以及加密等等工作,如果有用的老铁点赞👍关注,或者评论区逛一逛,您的支持是我创作的最大动力,咱们下一期见啦~~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐