[Spring Cloud] (2)gateway全局异常捕捉统一返回值
通常的处理过程为抛出异常->全局异常捕捉->返回前端通常在代码中,对于某个特定的条件,我们抛出一个自定义异常,并携带特定的状态码与状态描述。
文章目录
简述
在上一篇章时我们有了一个简单的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
处理转发失败的情况
正常情况下,我们请求了一个接口,并得到了一个结果,如下:
但是,我们依然要考虑,如果访问到一个不存在的接口,会得到什么样的结果?
如果不做任何修改的时候,我们会得到以下的结果:
但这不是我们想要的。
我们想要如下返回结果。
因此我们需要配置一个全局异常处理器来处理。
为达到这个目的,我们开始构建一些基础的功能,描述如下。
全局参数
我们需要去创建一个全部配置,如下所示:
这里统一存放着我们需要读取的配置,主要为全局使用的参数。具体配置方式如下所示:
在网关项目中创建一个config文件夹,用于存放网关的相关配置。(当然,能想做网关的人,应该都熟练的会使用Spring Boot了,在Spring Boot项目中,这都是一些较为基础的内容)
此时,我们先配置一个全局异常捕捉-打印堆栈异常的参数,用于本文的一些功能当中。
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;
}
同一返回格式
创建一个result文件夹,用于存放统一返回值的相关配置
该部分比较基础,暂时只说明配置方式与代码。
操作消息对象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();
}
全局异常处理器
做两步
- 覆盖默认的异常处理。
- 自定义异常处理。
自定义通用异常
通常的处理过程为抛出异常->全局异常捕捉->返回前端
通常在代码中,对于某个特定的条件,我们抛出一个自定义异常,并携带特定的状态码与状态描述
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;
}
}
自定义异常处理工具
解决三个问题
- 处理自定义异常
- 处理不存在的接口
- 对于其他的异常,统一返回一个指定的错误描述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));
}
}
更多推荐
所有评论(0)