毕设利器,教你从零搭建一个有规范的spring boot项目【三】—— 返回结果的处理和统一异常处理

本文介绍了如何在SpringBoot项目中规范返回结果和处理异常,包括使用Fastjson创建统一的响应格式,定义HTTP状态码、错误信息和数据字段。同时,通过自定义异常和全局异常处理器实现业务异常的统一处理,提高前后端交互的友好性。此外,还展示了如何通过自定义异常枚举和接口简化异常处理流程。
摘要由CSDN通过智能技术生成

接上篇毕设利器,教你从零搭建一个有规范的spring boot项目【二】

初步调通了数据库,能够对数据库做基本的增上改查操作,那么接下来,就要注意一些规范和统一的问题了。

拿返回结果的处理来举例子。

上一篇博客中,我们的返回结果格式是这样的:

在这里插入图片描述
前端朋友拿到这个数据了,可能要拿到name这个字段去展示是吧,但要是后台程序出错了怎么办?

出错了,就不会返回name这个字段,前端也就跟着不知所措了。

因此我们还要返回一个字段,告诉前端他是否请求成功。

而这种字段通常是有一定规范的,举个例子,打开bilibili官网,打开右键检查,然后刷新,可以看到B站首页的请求数据的返回:

(吐槽一下这张图,为什么癌症越来越多了,哈哈哈哈哈)

在这里插入图片描述
可以看到,data里就是它首页的数据。

另外你还可以看到code、count、message这三个字段。

一般code为200或者0,就是请求成功并且返回数据正常。

这些都是可以自定义的,所以根据业务需求不同,你可以随意定义,不过大家一般都会有以下三个字段:

  • data:返回的数据,像我们之前查询用户的情况,data里放的就会是用户列表的数据。
  • code:返回码,前端的朋友就是通过字段,来判断返回结果是否正常。
  • message:额外信息,一般只有报错的时候才会有这个字段,要么直接给用户看是哪里出问题了,要么是给前端朋友看的,比如他请求接口的时候少了哪个参数。

上面这么说可能比较抽象,直接跟着做会清晰点。

首先引入fastjson的依赖:

在这里插入图片描述
依赖在下面,可以直接复制,粘完记得点一下上图的右上角刷新一下依赖。

<!-- fastjson-->
<dependency>
	<groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.46</version>
</dependency>

接着新建core.support.http包,注意与controller平级:

在这里插入图片描述
然后把下面的工具类粘在这个包里,其实这工具类爱放哪放哪,放在这个包只是出于个人习惯的归档分类而已,你有自己的习惯的话,完全可以放在你喜欢的地方:

package com.TandK.core.support.http;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

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

public class HttpResponseSupport {
    private HttpResponseSupport() {
    }

    private static JSONObject responseJson;
    private static JSONObject responseErrorJson;

    static {
        responseJson = new JSONObject();
        responseErrorJson = new JSONObject();
    }


    public synchronized static ResponseEntity<Object> success(String msg, String reason) {
        responseErrorJson.clear();
        responseJson.put("code", 0);
        responseJson.put("message", msg);
        responseJson.put("reason", reason);
        responseJson.put("data", msg);
        return obtainResponseEntity(HttpStatus.OK, responseJson);
    }
    public synchronized static ResponseEntity<Object> success(Object obj) {
        responseJson = new JSONObject();
        responseJson.put("code", 0);
        responseJson.put("data", obj);
        return obtainResponseEntity(HttpStatus.OK, responseJson);
    }
    public synchronized static ResponseEntity<Object> success(IPage obj) {
        Map<String, Object> map = new HashMap<String, Object>(2) {{
            put("total",obj.getTotal());
            put("data",obj.getRecords());
        }};

        responseJson = new JSONObject();
        responseJson.put("code", 0);
        responseJson.put("data", map);
        return obtainResponseEntity(HttpStatus.OK, responseJson);
    }

    public synchronized static ResponseEntity<Object> error(HttpStatus httpStatus, String msg, Object obj) {
        JSONObject tempJSON = new JSONObject();
        responseErrorJson.clear();
        responseErrorJson.put("type", httpStatus.getReasonPhrase());
        responseErrorJson.put("message", msg);
        responseErrorJson.put("reason", obj);
        responseErrorJson.put("code", "9999");
        return obtainResponseEntity(httpStatus, responseErrorJson);
    }


    private synchronized static ResponseEntity<Object> obtainResponseEntity(HttpStatus httpStatus, Object response) {
        return new ResponseEntity<>(response, httpStatus);
    }
}

再在core包里新建一个constant包,这个包用来存放各种各样的常量,结构如图:
在这里插入图片描述

建一个常量类,存放各种返回信息的常量:

package com.TandK.core.constant;

/**
 * @author TandK
 */
public final class HttpMessageConstant {

    private HttpMessageConstant() {
    }

    public static final String HTTP_MESSAGE_FAIL = "非法请求";
    public static final String HTTP_MESSAGE_BAG_REQUEST = "请求体不完整";
    public static final String HTTP_MESSAGE_NOT_FOUND = "资源不存在";
    public static final String HTTP_MESSAGE_FORBIDDEN = "请勿重复请求";
    public static final String HTTP_MESSAGE_CONFLICT = "请求资源冲突";
    public static final String HTTP_MESSAGE_PRECONDITION_FAILED = "请求头有误";
    public static final String HTTP_MESSAGE_UNAUTHORIZED = "鉴权失败";
    public static final String HTTP_MESSAGE_INTERNAL_SERVER_ERROR = "服务器内部错误";
    public static final String HTTP_MESSAGE_BAG_REQUEST_EXCEL = "excel error";
    public static final String HTTP_DATE_BAG_REQUEST = "时间格式异常";

}

上面这两个类只是工具类而已,完全可以不仔细看,当然你有阅读别人代码的习惯,那也可以看看。

接下来可以回到我们上次查询用户列表的方法:

在这里插入图片描述
在最后返回的时候,稍做一下修改:
在这里插入图片描述
当然service层和controller层方法声明的返回类型都要改改:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
然后启动一下项目,再次访问用户列表接口,可以看到返回格式改变了:

在这里插入图片描述

这种数据格式是方法正常执行的格式,那么方法要是执行异常怎么办?

别着急,我们先看一下统一异常处理吧。

统一异常处理有两个目的,还是举一点实际情况的例子来说明吧。

假设我们项目中有发送短信的需求,现在发送短信都是借助第三方服务,比如阿里云。

那么调用阿里云的接口,就可能有异常的情况出现,我们可以try-catch捕获,但是通过我们自定义异常,再通过统一异常处理,可以帮助我们快速找到出问题的原因。

这是其一。

另外一个情况,假设在查询用户列表的时候,加了点查询条件,这个时候找不到符合查询条件的数据,那我们怎么办?

返回下面这样的格式给前端同学吗?

{"code":0,"data":[]}

那前端同学给用户展示的就是个空白列表了啊。

假设你们的项目需求就是这样,那也可以。

但如果你们的项目需求是要告诉用户,找不到符合条件的用户,那可能需要给前端同学一点提示了。

前端同学固然可以自己判断data的长度,但我们返回的话可以更友好些。

这个时候我们就可以自定义一个业务异常,通过统一异常处理来告诉前端同学,找不到符合条件的用户。

而处理好统一异常处理后,我们直接抛异常就可以了。可以大大减少我们的工作量。

这是其二。

具体怎么做,跟着往下做就是了。

首先在core包里新建一个exception包,层级结构如图:

在这里插入图片描述
建一个GlobalExceptionHandler类,通过这个类来捕获整个项目的异常:

package com.TandK.core.exception;

import com.TandK.core.constant.HttpMessageConstant;
import com.TandK.core.support.http.HttpResponseSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @author TandK
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 系统异常,返回服务器内部错误
     * @param e
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public ResponseEntity<Object> systemException(Throwable e, HttpServletRequest request) {
        logger.error("出现了 全局Exception异常, 地址是:{},异常是:{}", request.getRequestURI(), e);
        return HttpResponseSupport.error(HttpStatus.INTERNAL_SERVER_ERROR, HttpMessageConstant.HTTP_MESSAGE_INTERNAL_SERVER_ERROR, e.getMessage());
    }

}

说明如下图:

在这里插入图片描述
这样子所有异常——java所有异常都继承于Throwable类,都会进入这个方法——都会进入这个方法。

我们大可以试一试,抛出一个异常:

在这里插入图片描述
可以看到控制台打印了报错信息:
在这里插入图片描述

返回的信息也提示Java端执行的方法出了问题:
在这里插入图片描述

这说明异常被抛出之后,确实地执行了这个方法:

在这里插入图片描述
这样的话,前端同学也能根据返回信息做出相应提示,比如服务器繁忙之类的。

除了Throwable类,我们也可以捕获其他异常。

比如自定义一个业务异常,当上面的用户列表,查询不到的时候的,可以直接抛出一个异常,然后统一进入这个异常处理的方法,告诉前端没有符合条件的查询。

通常来说,我们还可以自定义一个异常枚举,来规范我们的项目。

以业务异常为例,那么上面的思路转化成代码便是如下步骤。

自定义异常枚举:

package com.TandK.core.exception;

import lombok.Getter;

/**
 * @author TandK
 * @since 2021/8/14 17:13
 */
@Getter
public enum BusinessExceptionEumn {

	/**
     * 之后每多一个异常状态,在这里加就行
     */
    THIRD_NET_ERROR(10001, "第三方服务网络异常", "第三方服务网络异常"),
    UPDATE_ERROR(10002, "更新异常", "更新异常"),
    ADD_ERROR(10003, "插入异常", "插入异常"),
    RESULT_NOT_FOUND(10004, "查询结果不存在", "查询结果不存在"),
    SENSITIVE_MESSAGE_CHECK_NOT_PASS(10005, "存在敏感词", "存在敏感词");

    /**
     * code 异常状态码
     */
    private final int code;
    /**
     * msg 异常信息
     */
    private final String msg;
	/**
     * reason 异常原因
     */
    private final String reason;

    BusinessExceptionEumn(int code, String msg, String reason) {
        this.code = code;
        this.msg = msg;
        this.reason = reason;
    }

}

要说明的话都写在注释里了,可以仔细看一下注释。

再自定义一个【自定义异常】接口,以后我们的自定义异常都可以实现这个接口,这么做的好处将在后面体现出来:

package com.TandK.core.exception;

/**
 * @author TandK
 * @since 2021/8/14 21:41
 */
public interface CustomException {

    String getMessage();

    String getReason();

    int getCode();
}

然后新建一个业务异常,实现CustomException接口,继承RuntimeException:

package com.TandK.core.exception;

public class BusinessException extends RuntimeException implements CustomException{

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private static final int DEFCODE = 8600;
    /**
     * exception message
     */
    private String message;

    /**
     * exception reason
     */
    private String reason;

    /**
     * error code
     * default: 500
     */
    private int code = 8600;

    public BusinessException(BusinessExceptionEumn bizCodeEnum) {
        super(bizCodeEnum.getMsg());
        this.message = bizCodeEnum.getMsg();
        this.reason = bizCodeEnum.getReason();
        this.code = bizCodeEnum.getCode();
    }

    public BusinessException(String message) {
        super(message);
        this.code = DEFCODE;
        this.message = message;
        this.reason = message;
    }


    public BusinessException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
        this.reason = message;
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
        this.code = DEFCODE;
        this.message = message;
        this.reason = message;
    }


    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String getReason() {
        return reason;
    }

    @Override
    public int getCode() {
        return code;
    }

}

重点看这个方法,有了业务异常枚举,我们以后抛出异常都是用这个方法抛出:
在这里插入图片描述

上面的代码都是在exception包下新建的,其实你看代码都能知道这一点。

接下来回去修改HttpResponseSupport类,加入一个方法:

    public synchronized static ResponseEntity<Object> error(HttpStatus httpStatus, CustomException e) {
        JSONObject tempJSON = new JSONObject();
        responseErrorJson.clear();
        responseErrorJson.put("type", httpStatus.getReasonPhrase());
        responseErrorJson.put("message", e.getMessage());
        responseErrorJson.put("reason", e.getReason());
        responseErrorJson.put("code", e.getCode());
        return obtainResponseEntity(httpStatus, responseErrorJson);
    }

这个方法能接受我们的自定义异常,并用前面的规范格式返回。

在这里,前面自定义异常接口的好处也体现出来了,之后我们再需要自定义异常,比如支付异常什么的,都可以实现自定义异常接口,然后用这个方法统一处理返回格式。

然后上面这个方法用在下面这个地方:

在这里插入图片描述

插一下具体代码,有需要可以直接复制:

    /**
     * 业务异常
     * @param e
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Object> businessException(BusinessException e, HttpServletRequest request) {
        logger.error("BusinessException, 地址是:{},异常是:{}", request.getRequestURI(), e);
        return HttpResponseSupport.error(HttpStatus.OK, e);
    }

这样,我们在项目中遇到一个问题,比如查询不到符合条件的用户列表,就可以直接抛用户异常了。

抛出来了,会被GlobalExceptionHandler拦截下来,进入对应的异常处理方法。

进入方法后,我们有需要呢,就打印一下,方便我们快速定位问题,还可以封装成统一格式返回给前端。

这就是上面代码的思路和目的,这些准备工作完了之后,接下来我们只需要在有需要的时候,像下面这样抛异常就可以了,甚至不用自己捕获处理。

在这里插入图片描述

做个测试,运行项目,把数据库里的用户删掉:

在这里插入图片描述

再调用接口,看看结果:

在这里插入图片描述

上面的很多代码,很多都是准备工作,做好这些准备工作之后,我们在接下来的编写接口的过程中,对于返回结果和异常的处理其实就是下面两步:

  1. 遇到不正常的情况,直接抛异常:
  2. 正常的情况,就用封装的方法返回:
    在这里插入图片描述

这样用起来就很简单了。

这里只是举了业务异常的例子,有些时候一些异常是不需要打印日志的,比如查不到用户这点屁事,他确实不是真正的异常,那么你可以再弄一个异常,专门处理这些屁事。

我就比较懒了,都塞业务异常了,毕竟还是要看具体项目来,项目不大的话,弄那么自定义异常,花里胡哨的,有这些时间还不如写写业务代码呢,你说是不是?

但要是项目大的话,规范还是很重要的,毕竟几个人分工开发,还要考虑后续维护的问题,有个统一规范还是很重要的。

有什么问题可以留言,看到了都会尽量帮忙解决。


2021-09-26更新:

下篇在这里:《毕设利器,教你从零搭建一个有规范的spring boot项目【四】——参数校验》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TandK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值