Feign自定义ErrorDecoder错误时返回统一结构
目录Feign自定义ErrorDecoder错误时返回统一结构背景问题解决自定义FeignErrorDecoder在FeignCliet中使用FeignErrorDecoder自定义DefaultErrorAttributes全局异常统一处理[gateway]自定义ErrorHandlerConfiguration[gateway]自定义JsonExceptionHandler小结完整代码Demo
目录
背景
微服务架构项目开发中,API接口都统一使用响应结构。
http状态码统一返回200,状态码根据结构体的code获取。
{
"code": 0,
"message": "success",
"data": {
"name": "kent"
}
}
用户请求时,服务调用流程。
问题
微服务架构中,在正常的情况下,返回的数据结构是按照响应结构体返回的,但服务调用发生异常时,却返回不了code。
例子,在order-service调用product-service,由于库存不足,抛出异常,返回的结果如下:
{
"timestamp": "2020-08-11 13:25:03",
"status": 500,
"error": "Internal Server Error",
"exception": "tech.xproject.common.core.exception.BusinessException",
"message": "not enough stock",
"trace": "tech.xproject.common.core.exception.BusinessException: not enough stock"
}
解决
自定义FeignErrorDecoder、DefaultErrorAttributes、BusinessException对异常进行处理。
自定义FeignErrorDecoder
代码位置:service-api
package tech.xproject.order.config;
import com.alibaba.fastjson.JSON;
import feign.FeignException;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import tech.xproject.common.core.entity.ExceptionInfo;
import tech.xproject.common.core.enums.ResultCodeEnum;
import tech.xproject.common.core.exception.BusinessException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
/**
* @author kent
*/
@Slf4j
@Configuration
public class FeignErrorDecoder extends ErrorDecoder.Default {
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = super.decode(methodKey, response);
// 如果是RetryableException,则返回继续重试
if (exception instanceof RetryableException) {
return exception;
}
try {
// 如果是FeignException,则对其进行处理,并抛出BusinessException
if (exception instanceof FeignException && ((FeignException) exception).responseBody().isPresent()) {
ByteBuffer responseBody = ((FeignException) exception).responseBody().get();
String bodyText = StandardCharsets.UTF_8.newDecoder().decode(responseBody.asReadOnlyBuffer()).toString();
// 将异常信息,转换为ExceptionInfo对象
ExceptionInfo exceptionInfo = JSON.parseObject(bodyText, ExceptionInfo.class);
// 如果excepiton中code不为空,则使用该code,否则使用默认的错误code
Integer code = Optional.ofNullable(exceptionInfo.getCode()).orElse(ResultCodeEnum.ERROR.getCode());
// 如果excepiton中message不为空,则使用该message,否则使用默认的错误message
String message = Optional.ofNullable(exceptionInfo.getMessage()).orElse(ResultCodeEnum.ERROR.getMessage());
return new BusinessException(code, message);
}
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
return exception;
}
}
在FeignClient中使用FeignErrorDecoder
代码位置:service-api
@FeignClient(name = ServiceNameConstant.ORDER_SERVICE, configuration = {FeignErrorDecoder.class})
完整代码示例
package tech.xproject.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import tech.xproject.common.core.constant.ServiceNameConstant;
import tech.xproject.order.config.FeignErrorDecoder;
import tech.xproject.order.pojo.dto.CreateOrderReqDTO;
import tech.xproject.order.pojo.entity.Order;
/**
* @author kent
*/
@FeignClient(name = ServiceNameConstant.ORDER_SERVICE, configuration = {FeignErrorDecoder.class})
public interface RemoteOrderService {
/**
* 创建订单
*
* @param createOrderReqDTO createOrderReqDTO
* @return Order
*/
@PostMapping("/order/create")
Order create(@RequestBody CreateOrderReqDTO createOrderReqDTO);
}
自定义DefaultErrorAttributes
代码位置:service
若不自定义DefaultErrorAttributes,在返回时并不会带上code,需要将自定义的参数加入返回的对象中
package tech.xproject.product.handler;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import tech.xproject.common.core.exception.BusinessException;
import java.util.Map;
/**
* @author kent
*/
@Component
@Primary
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
Throwable error = this.getError(webRequest);
if (error instanceof BusinessException) {
errorAttributes.put("code", ((BusinessException) error).getCode());
}
return errorAttributes;
}
}
全局异常统一处理
代码位置:web
package tech.xproject.web.manager.handler;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import tech.xproject.common.core.entity.R;
import tech.xproject.common.core.enums.ResultCodeEnum;
import tech.xproject.common.core.exception.BusinessException;
import java.util.List;
/**
* @author kent
*/
@Slf4j
@RestControllerAdvice
public class WebGlobalExceptionHandler {
@ExceptionHandler({FeignException.class})
@ResponseBody
public R<?> feignExceptionHandler(FeignException exception) {
log.error(exception.getMessage(), exception);
return R.error(exception.getMessage());
}
@ExceptionHandler({RuntimeException.class})
@ResponseBody
public R<?> runtimeExceptionHandler(RuntimeException exception) {
log.error(exception.getMessage(), exception);
return R.error(exception.getMessage());
}
@ExceptionHandler({Exception.class})
@ResponseBody
public R<?> exceptionHandler(Exception exception) {
log.error(exception.getMessage(), exception);
return R.error(exception.getMessage());
}
@ExceptionHandler({BusinessException.class})
public R<?> businessExceptionHandler(BusinessException exception) {
log.error(exception.getMessage(), exception);
return R.error(exception.getCode(), exception.getMessage());
}
@ExceptionHandler({BindException.class})
@ResponseBody
public R<?> bindExceptionHandler(BindException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
String errorMessage = fieldErrors.get(0).getDefaultMessage();
log.error(errorMessage, exception);
return R.error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage);
}
@ExceptionHandler({MethodArgumentNotValidException.class})
public R<?> validateExceptionHandler(MethodArgumentNotValidException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
String errorMessage = fieldErrors.get(0).getDefaultMessage();
log.error(errorMessage, exception);
return R.error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage);
}
}
[gateway]自定义ErrorHandlerConfiguration
代码位置:gateway
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
// 自定义Json异常处理
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
this.resourceProperties, this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
完整代码示例
package tech.xproject.gateway.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
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 tech.xproject.gateway.handler.JsonExceptionHandler;
import java.util.Collections;
import java.util.List;
/**
* @author kent
*/
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
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.resourceProperties, this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
[gateway]自定义JsonExceptionHandler
代码位置:gateway
使用自定义的结构体返回,code、message、data
/**
* get error attributes
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Throwable error = super.getError(request);
String exMessage = error != null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage();
String message = String.format("request error [%s %s],exception:%s", request.methodName(), request.uri(), exMessage);
Map<String, Object> map = new HashMap<>(3);
map.put("code", ResultCodeEnum.ERROR.getCode());
map.put("message", message);
map.put("data", null);
return map;
}
重写getHttpStatus方法,返回http状态码200
/**
* response http code 200
* the error code need use the code in response content
*
* @param errorAttributes
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return HttpStatus.OK.value();
}
完整代码示例
package tech.xproject.gateway.handler;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
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.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import tech.xproject.common.core.enums.ResultCodeEnum;
import java.util.HashMap;
import java.util.Map;
/**
* @author kent
*/
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* get error attributes
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Throwable error = super.getError(request);
String exMessage = error != null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage();
String message = String.format("request error [%s %s],exception:%s", request.methodName(), request.uri(), exMessage);
Map<String, Object> map = new HashMap<>(3);
map.put("code", ResultCodeEnum.ERROR.getCode());
map.put("message", message);
map.put("data", null);
return map;
}
/**
* render with json
*
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* response http code 200
* the error code need use the code in response content
*
* @param errorAttributes
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return HttpStatus.OK.value();
}
}
小结
在网上查阅各类文章,始终没找到解决方案,只有各类的零散的解决方式,最后通过自己翻代码,断点调试,并结合相关的文章终于解决了。
使用文章跟代码解决方式记录下来,给有需要的人提供参考。
完整代码Demo
feign-custom-exception-code-demo
参考
更多推荐
所有评论(0)