[Spring Cloud] gateway全局异常捕捉统一返回值

文章目录

    • 处理转发失败的情况
    • 全局参数
    • 同一返回格式
      • 操作消息对象AjaxResult
      • 返回值状态描述对象AjaxStatus
      • 返回值枚举接口层StatusCode
    • 全局异常处理器
    • 自定义通用异常
      • 定一个自定义异常
      • 覆盖默认的异常处理
      • 自定义异常处理工具

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

现在我们需要进一步的处理一些问题,来使得网关更加完善。
本篇文章的完整代码文件已放置在gitee。
杉极简/gateway网关阶段学习

处理转发失败的情况

正常情况下,我们请求了一个接口,并得到了一个结果,如下:
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));
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

比selenium体验更好的ui自动化测试工具: cypress介绍

话说 Cypress is a next generation front end testing tool built for the modern web. And Cypress can test anything that runs in a browser.Cypress consists of a free, open source, locally installed Test Runner and a Dashboard Service for recording your tests.…

leetcode077——排序链表

题目&#xff1a; 给定链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 思路&#xff1a; 1.找链表中点【使用快慢指针 慢指针每次移动一步&#xff0c;快指针每…

基于单片机12864的出租车计价器设计

**单片机设计介绍&#xff0c;基于单片机12864的出租车计价器设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机和12864液晶显示屏的出租车计价器设计&#xff0c;主要是利用单片机的强大控制能力和液晶显示屏的直观显示特性&…

牛客网BC-125 序列中整数去重复(难题讲解)

题目如下 --------------------------------------------------------------------------------------------------------------------------------- 题目讲解&#xff08;思路&#xff09; -------------------------------------------------------------------------------…

单一职责原则

1.1 阅读干吗不直接用手机&#xff1f; 电子阅读器比较专注&#xff0c;而手机功能比较多&#xff0c;影响专注。 1.2 手机不纯粹 手机确实很方便。但是现在的手机就是一台小型智能电脑。它不仅能打电话&#xff0c;还能听音乐、看电影电视、与个人交流、与一群人群聊&#…

基于java+SpringBoot+Vue的大学生入学审核系统设计与实现

基于javaSpringBootVue的大学生入学审核系统设计与实现 开发语言: Java 数据库: MySQL技术: SpringBoot VUE工具: IDEA/Eclipse、Navicat、Maven 系统展示 前台展示 入学办理模块&#xff1a;学生可以提交入学申请并跟踪入学办理进度。 后台展示 学生管理模块&#xff1…

【Docker系列】在 Linux 上安装 Docker Compose 的简明步骤

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

前端学习之DOM编程案例:抽奖案例

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>抽奖案例</title><style>*{margin: 0;padding: 0;}</style> </head> <body><div id"container"&g…

【放假第3天】幻兽帕鲁 雾锁王国 我的世界 游戏云服务器选购指南 附最新价格对比表 新手、小白秒懂

更新日期&#xff1a;4月6日&#xff08;半年档 价格回调&#xff0c;京东云采购季持续进行&#xff09; 本文纯原创&#xff0c;侵权必究 【云服务器推荐】价格对比&#xff01;阿里云 京东云 腾讯云 选购指南视频截图 《最新对比表》已更新在文章头部—腾讯云文档&#xf…

LeetCode 1483.树节点的第 K 个祖先:树上倍增

【LetMeFly】1483.树节点的第 K 个祖先&#xff1a;树上倍增 力扣题目链接&#xff1a;https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/ 给你一棵树&#xff0c;树上有 n 个节点&#xff0c;按从 0 到 n-1 编号。树以父节点数组的形式给出&#xff0c;其中 paren…

redis主从复制与哨兵模式

redis主从复制 Redis主从复制&#xff08;Redis replication&#xff09;是Redis提供的一种数据备份和故障转移机制。通过主从复制&#xff0c;可以将一个Redis服务器&#xff08;主节点&#xff09;的数据复制到一个或多个Redis服务器&#xff08;从节点&#xff09;。这样做…

【面经】interrupt()、interrupted()和isInterrupted()的区别与使用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 interrupt方法 如果打断线程正在sleep&#xff0c;wait&#xff0c;join会导致被打断的线程抛出InterruptedException&#xff0c;并清除打断标记。如…

FebHost:什么是土耳其.TR域名?

当前互联网高速发展,一个国家的顶级域名已成为其网络形象的重要标识。近期,土耳其国家顶级域名”.TR”引起了广泛关注,成为业界热议的话题。 作为代表土耳其共和国的国家顶级域名(ccTLD),.TR域名于1991年首次引入,由土耳其科技和信息技术部负责管理。除了常见的”.com.tr”、”…

画图理解JVM相关内容

文章目录 1. JVM视角下&#xff0c;内存划分2. 类内存分布硬核详解1. 获取堆内存参数2. 扫描堆内存&#xff0c;定位实例3. 查看实例所在地址的数据4. 找到实例所指向的类信息的地址5. 查看class信息6. 结论 3. Java的对象创建流程4. 垃圾判别算法4.1 引用计数法4.2 可达性分析…

GD32F470_光敏电阻光照传感器模块移植手册

2.3 光敏电阻光照传感器 光敏电阻是用硫化隔或硒化隔等半导体材料制成的特殊电阻器&#xff0c;其工作原理是基于内光电效应。随着光照强度的升高&#xff0c;电阻值迅速降低&#xff0c;由于光照产生的载流子都参与导电&#xff0c;在外加电场的作用下作漂移运动&#xff0c;电…

如何从文本数据中提取子列表

提取文本数据中的子列表可以通过各种方式实现&#xff0c;具体取决于文本数据的结构和提取子列表的条件。例如&#xff1a;使用字符串操作和条件判断、使用正则表达式、使用自然语言处理工具、使用自定义解析器等几种模式&#xff0c;那么对于在日常使用中会有那些问题呢 &…

ssm基于HTML5的出租车管理系统论文

摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就很关键。因此出租车信息的管…

知识融合:知识图谱构建的关键技术

目录 一、引言二、知识图谱基础2.1 知识表示三元组属性图 2.2 知识抽取实体抽取关系抽取属性抽取 三、知识融合的核心问题3.1 实体识别与链接实体识别实体链接 3.2 重复实体合并方法示例 3.3 关系融合挑战方法示例 四、知识融合技术深度解析4.1 基于规则的方法规则设计原则规则…

13 - 关于存储器读写的问题

---- 整理自B站UP主 踌躇月光 的视频 1. 存储器存在的问题 前面章节存储器存在问题&#xff0c;读取和写入分开&#xff0c;会造成读写冲突&#xff0c;所以设计要改成写时没法读。 1.1 单字节存储器 片选 CS1&#xff0c;WE1 时&#xff0c;EN0&#xff0c;写入 片选 CS1&am…

CLoVe:在对比视觉语言模型中编码组合语言

CLoVe:在对比视觉语言模型中编码组合语言 摘要引言相关工作CLoVe: A Framework to Increase Compositionality in Contrastive VLMsSynthetic CaptionsHard NegativesModel Patching CLoVe: Encoding Compositional Language inContrastive Vision-Language Models 摘要 近年来…