简述

在上一篇章时我们有了一个简单的gateway网关
[Spring Cloud] gateway简单搭建与请求转发-CSDN博客
现在我们需要根据这个网关进行一部分的改进

现在我们需要进一步的处理一些问题,来使得网关更加完善。

本文涉及代码已开源

本文网关gateway,微服务,vue已开源到gitee
杉极简/gateway网关阶段学习
https://gitee.com/dong-puen/gateway-stages

Fir Cloud 完整项目

该内容完整项目如下
Fir Cloud v1.0.0
https://gitee.com/dong-puen/fir-cloud
https://github.com/firLucky/fir-cloud在这里插入图片描述

处理转发失败的情况

正常情况下,我们请求了一个接口,并得到了一个结果,如下:
image.png
但是,我们依然要考虑,如果访问到一个不存在的接口,会得到什么样的结果?
如果不做任何修改的时候,我们会得到以下的结果:
image.png
但这不是我们想要的。
我们想要如下返回结果。
image.png
因此我们需要配置一个全局异常处理器来处理。
为达到这个目的,我们开始构建一些基础的功能,描述如下。

全局参数

我们需要去创建一个全部配置,如下所示:
这里统一存放着我们需要读取的配置,主要为全局使用的参数。具体配置方式如下所示:
在网关项目中创建一个config文件夹,用于存放网关的相关配置。(当然,能想做网关的人,应该都熟练的会使用Spring Boot了,在Spring Boot项目中,这都是一些较为基础的内容)
此时,我们先配置一个全局异常捕捉-打印堆栈异常的参数,用于本文的一些功能当中。
image.png

global:
  # 全局异常捕捉-打印堆栈异常
  printStackTrace: true
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


/**
 * @author fir
 * @date 2023/7/28 17:53
 */
@Data
@Component
@ConfigurationProperties(prefix = "global")
public class GlobalConfig {

    /**
     * 全局异常捕捉-打印堆栈异常
     */
    private boolean printStackTrace;
}

image.png

同一返回格式

创建一个result文件夹,用于存放统一返回值的相关配置
该部分比较基础,暂时只说明配置方式与代码。
image.png

操作消息对象AjaxResult

import java.io.Serializable;
import java.util.HashMap;


/**
 * 操作消息-JSON
 *
 * @author fir
 */
public class AjaxResult extends HashMap<String, Object> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";


    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }


    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  状态描述
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }


    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  状态描述
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (data != null) {
            super.put(DATA_TAG, data);
        }
    }


    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return AjaxResult.success(success.getCode(), success.getMsg());
    }


    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return AjaxResult.success(success.getMsg(), data);
    }


    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(AjaxStatus success) {

        return AjaxResult.success(success.getMsg(), new HashMap<>(0));
    }


    /**
     * 返回成功消息
     *
     * @param code 状态吗
     * @param msg  状态描述
     * @return 消息体
     */
    public static AjaxResult success(Integer code, String msg) {
        return new AjaxResult(code, msg);
    }


    /**
     * 返回成功消息
     *
     * @param msg  状态描述
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return new AjaxResult(success.getCode(), msg, data);
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @param data       数据对象
     * @return 请求结果
     */
    public static AjaxResult success(StatusCode statusCode, Object data) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);
    }


    /**
     * 返回错误消息
     *
     * @return 警告消息
     */
    public static AjaxResult error() {
        return AjaxResult.error(AjaxStatus.LOSE_OPERATION.getMsg());
    }


    /**
     * 返回错误消息
     *
     * @param msg 状态描述
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, new HashMap<>(0));
    }


    /**
     * 返回错误消息
     *
     * @param msg  状态描述
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        AjaxStatus loseEfficacy = AjaxStatus.LOSE_EFFICACY;
        return new AjaxResult(loseEfficacy.getCode(), msg, data);
    }


    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  状态描述
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, new HashMap<>(0));
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @param data       数据对象
     * @return 请求结果
     */
    public static AjaxResult error(StatusCode statusCode, Object data) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @return 请求结果
     */
    public static AjaxResult error(StatusCode statusCode) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), new HashMap<>(0));
    }
}

返回值状态描述对象AjaxStatus

import lombok.Getter;


/**
 * 返回值状态与描述
 * 
 * @author fir
 */

@Getter
public enum AjaxStatus implements StatusCode {
    /**
     * 请求成功
     */
    SUCCESS(200, "请求成功"),
    /**
     * 登录成功
     */
    SUCCESS_LOGIN(200, "登录成功"),
    /**
     * 登出成功
     */
    SUCCESS_LOGOUT(200, "登出成功"),
    /**
     * 启动成功
     */
    SUCCESS_FLOW_START(200, "启动成功"),
    /**
     * 暂无数据
     */
    NO_DATA(200, "暂无数据"),
    /**
     * 错误请求
     */
    BAD_REQUEST(400, "错误请求"),
    /**
     * 登录过期
     */
    EXPIRATION_TOKEN(401, "登录过期"),
    /**
     * 服务不存在
     */
    FAILED_SERVICE_DOES(404, "服务不存在"),
    /**
     * 账号或密码为空
     */
    NULL_LOGIN_DATA(480, "账号或密码为空"),
    /**
     * 建立通信-通信建立失败
     */
    FAILED_COMMUNICATION(481, "通信建立失败"),
    /**
     * 接口不存在
     */
    NULL_API(404, "接口不存在"),
    /**
     * 建立通信-非法请求
     */
    ILLEGAL_REQUEST(482, "非法请求"),
    /**
     * 请求失败
     */
    LOSE_EFFICACY(490, "请求失败"),
    /**
     * 操作失败
     */
    LOSE_OPERATION(491, "操作失败"),
    /**
     * 请求失败
     */
    FAILED(500, "请求失败"),
    /**
     * 服务不可用(gateway网关总定义-所有未定义处理的异常都返回该异常)
     */
    SERVICE_UNAVAILABLE(500, "服务不可用"),
    /**
     * 请求整体加密-无效会话
     */
    SESSION_INVALID(601, "无效会话"),
    /**
     * 请求整体加密-会话过期
     */
    SESSION_EXPIRE(602, "会话过期"),
    /**
     * 防重放校验失败
     */
    ANTI_REPLAY_VERIFY_FAILED(701, "防重放校验失败"),
    /**
     * 防重放校验失败
     */
    INTEGRITY_VERIFY_FAILED(801, "完整性校验失败"),
    /**
     * 预留
     */
    PASS(1000, "请求失败");

    private final int code;
    private final String msg;

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

返回值枚举接口层StatusCode

package com.fir.gateway.config.result;

/**
 * 返回值枚举接口层
 * @author 18714
 */
public interface StatusCode {


    /**
     * 获取code信息
     *
     * @return code码
     */
     int getCode();


    /**
     * 获取msg信息
     *
     * @return msg描述
     */
     String getMsg();
}

全局异常处理器

做两步

  1. 覆盖默认的异常处理。
  2. 自定义异常处理。

自定义通用异常

通常的处理过程为抛出异常->全局异常捕捉->返回前端
通常在代码中,对于某个特定的条件,我们抛出一个自定义异常,并携带特定的状态码与状态描述

if (session == null) {
    throw new CustomException(AjaxStatus.SESSION_INVALID);
}

而此时,我们在开发环境中通常需要显示堆栈异常,但是生成环境中,大多数是不需要的,此时我们就是用了全局参数配置在自定义异常工具这个类中,我们配置了定义异常默认不打印堆栈异常

定一个自定义异常

import com.fir.gateway.config.result.AjaxStatus;
import lombok.Getter;

/**
 * 自定义通用异常
 * 抛出异常->全局异常捕捉->返回前端
 *
 * @author fir
 */
@Getter
public class CustomException extends RuntimeException {

    /**
     * code状态码
     */
    private final int code;

    /**
     * 错误状态码
     */
    private final AjaxStatus ajaxStatus;


    public CustomException(AjaxStatus ajaxStatus) {
        super(ajaxStatus.getMsg());
        this.code = ajaxStatus.getCode();
        this.ajaxStatus = ajaxStatus;
    }

}

覆盖默认的异常处理

此时我们覆盖默认的异常处理,并使用自定义的异常处理工具JsonExceptionHandler

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;


/**
 * 覆盖默认的异常处理
 * @author fir
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, WebProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final WebProperties webProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     WebProperties webProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.webProperties = webProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.webProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }

}

自定义异常处理工具

解决三个问题

  1. 处理自定义异常
  2. 处理不存在的接口
  3. 对于其他的异常,统一返回一个指定的错误描述AjaxStatus.SERVICE_UNAVAILABLE
package com.fir.gateway.config.exception;


import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.result.AjaxResult;
import com.fir.gateway.config.result.AjaxStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;


/**
 * 自定义异常处理工具
 * 异常时用JSON代替HTML异常信息
 *
 * @author fir
 */
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {


    /**
     * 网关参数配置
     */
    @Resource
    private GlobalConfig globalConfig;


    public JsonExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, webProperties.getResources(), errorProperties, applicationContext);
        log.info(String.valueOf(errorProperties));
        log.info(String.valueOf(errorAttributes));
    }

    /**
     * 重构方法,设置返回属性格式
     */
    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Throwable errorThrowable = getError(request);
        // 自定义异常默认不打印堆栈异常
        // 决定是否打印堆栈异常
        boolean printStackTrace = globalConfig.isPrintStackTrace();
        if (printStackTrace) {
            errorThrowable.printStackTrace();
        }
        // 打印全局异常
        log.error(errorThrowable.getMessage());

        Class<?> errorClass = errorThrowable.getClass();
        String simpleName = errorClass.getSimpleName();
        AjaxStatus ajaxStatus;
        switch (simpleName) {
            case "CustomException":
                // 处理自定义异常
                CustomException customException = (CustomException) errorThrowable;
                ajaxStatus = customException.getAjaxStatus();
                break;
            case "NotFoundException":
            case "ResponseStatusException":
                // 处理404
                ajaxStatus = AjaxStatus.NULL_API;
                break;
            default:
                // 统一返回一个服务错误描述
                ajaxStatus = AjaxStatus.SERVICE_UNAVAILABLE;
                break;
        }

        AjaxResult result = AjaxResult.error(ajaxStatus);
        return ServerResponse.status(200).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(result));
    }
}
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐