diff --git a/axzo-common-autoconfigure/pom.xml b/axzo-common-autoconfigure/pom.xml
index 20704d6..97ef0fc 100644
--- a/axzo-common-autoconfigure/pom.xml
+++ b/axzo-common-autoconfigure/pom.xml
@@ -7,7 +7,7 @@
axzo-framework-commons
cn.axzo.framework
- 1.0.0-SNAPSHOT
+ ${revision}
axzo-common-autoconfigure
@@ -94,5 +94,11 @@
org.springframework.security
spring-security-web
+
+ io.github.openfeign
+ feign-core
+ compile
+ true
+
-
\ No newline at end of file
+
diff --git a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/feign/FeignErrorDecoderAutoConfiguration.java b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/feign/FeignErrorDecoderAutoConfiguration.java
new file mode 100644
index 0000000..a2b46a7
--- /dev/null
+++ b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/feign/FeignErrorDecoderAutoConfiguration.java
@@ -0,0 +1,46 @@
+package cn.axzo.framework.autoconfigure.feign;
+
+import cn.axzo.framework.core.RecordException;
+import feign.Feign;
+import feign.Response;
+import feign.Util;
+import feign.codec.ErrorDecoder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.Servlet;
+import java.io.IOException;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * 为兼容 X-Metadata-Tag 的异常解码器
+ *
+ * @author wangli
+ * @since 2024/6/25 21:55
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnWebApplication
+@ConditionalOnClass({Feign.class, Servlet.class, DispatcherServlet.class})
+public class FeignErrorDecoderAutoConfiguration {
+
+ @Bean
+ public ErrorDecoder feignErrorDecoder() {
+ return (methodKey, response) -> {
+ if (response.status() != 512) {
+ return new ErrorDecoder.Default().decode(methodKey, response);
+ }
+ byte[] body = {};
+ try {
+ if (response.body() != null) {
+ body = Util.toByteArray(response.body().asInputStream());
+ }
+ } catch (IOException ignored) { // NOPMD
+ }
+ return new RecordException(new String(body, UTF_8));
+ };
+ }
+}
diff --git a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java
index fed8d6e..7627737 100644
--- a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java
+++ b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/ExceptionHandlerAutoConfiguration.java
@@ -8,6 +8,10 @@ import cn.axzo.framework.autoconfigure.web.exception.resolver.internal.RequestRe
import cn.axzo.framework.autoconfigure.web.exception.support.GlobalErrorController;
import cn.axzo.framework.autoconfigure.web.exception.support.GlobalExceptionHandler;
import cn.axzo.framework.domain.web.result.Result;
+import cn.axzo.framework.web.feign.FeignRecordExceptionInterceptor;
+import cn.axzo.framework.web.filter.BasicRecordExceptionFilter;
+import feign.RequestInterceptor;
+import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -22,6 +26,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
+import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@@ -184,4 +189,19 @@ public class ExceptionHandlerAutoConfiguration implements WebMvcConfigurer {
return new RequestRejectedExceptionHttpStatusResolver();
}
}
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(RequestInterceptor.class)
+ public static class RecordExceptionConfiguration {
+ @Bean
+ public RequestInterceptor requestInterceptor(){
+ return new FeignRecordExceptionInterceptor();
+ }
+
+ @Bean
+ @ConditionalOnClass(MDC.class)
+ public OncePerRequestFilter oncePerRequestFilter() {
+ return new BasicRecordExceptionFilter();
+ }
+ }
}
diff --git a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java
index b5739d8..df3f6c9 100644
--- a/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java
+++ b/axzo-common-autoconfigure/src/main/java/cn/axzo/framework/autoconfigure/web/exception/support/GlobalExceptionHandler.java
@@ -3,14 +3,22 @@ package cn.axzo.framework.autoconfigure.web.exception.support;
import cn.axzo.framework.autoconfigure.web.exception.handler.ExceptionResultHandler;
import cn.axzo.framework.autoconfigure.web.exception.handler.internal.StandardExceptionResultHandler;
import cn.axzo.framework.core.InternalException;
+import cn.axzo.framework.core.RecordException;
import cn.axzo.framework.core.util.ClassUtil;
import cn.axzo.framework.domain.web.code.BaseCode;
-import cn.axzo.framework.domain.web.result.ApiResult;
+import cn.axzo.framework.domain.web.result.ApiReportResult;
import cn.axzo.framework.domain.web.result.Result;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.google.common.collect.Lists;
+import lombok.Data;
+import lombok.Getter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
import org.springframework.beans.TypeMismatchException;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
@@ -19,6 +27,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Controller;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -34,12 +44,22 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
+import static cn.axzo.framework.domain.web.result.ApiReportResult.err;
+import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_FILTER_PACKAGE_VALUE;
+import static cn.axzo.framework.web.filter.BasicRecordExceptionFilter.MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME;
import static java.lang.String.format;
import static jodd.util.StringUtil.isNotBlank;
+import static org.springframework.http.HttpHeaders.CACHE_CONTROL;
/**
* @Description spring mvc全局异常捕获
@@ -48,6 +68,7 @@ import static jodd.util.StringUtil.isNotBlank;
**/
@RestControllerAdvice(annotations = {Controller.class, RestController.class})
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private final List> handlers;
@@ -55,6 +76,19 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private final ErrorAttributes errorAttributes;
+ public static final String CTX_LOG_ID = "ctxLogId";
+ /**
+ * 多设置一个key = TraceId, value为traceId的变量到MDC. 以兼容目前的logback-spring.xml的配置
+ */
+ public static final String TRACE_ID_IN_MDC = "traceId";
+ private static final String RECORD_FLAG = "debug";
+
+
+ @Value("${axzo.framework.debug:X-Metadata-Tag}")
+ private String recordExceptionHeaderName;
+ @Value("${spring.application.name:}")
+ private String applicationName;
+
public GlobalExceptionHandler(List> handlers,
ErrorAttributes errorAttributes,
ServerProperties properties) {
@@ -85,7 +119,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
}
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
@@ -111,7 +145,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
}
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@@ -136,7 +170,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
}
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@@ -162,7 +196,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
}
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@@ -185,7 +219,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
message = "Required parameter '" + field + "' is not present";
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@@ -230,7 +264,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
}
// 3.响应
- return ApiResult.err(Integer.parseInt(code), message);
+ return err(Integer.parseInt(code), message);
});
return super.handleExceptionInternal(ex, result, headers, status, request);
}
@@ -240,8 +274,16 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
throw e;
}
+ @ExceptionHandler(RecordException.class)
+ public String recordException(HttpServletRequest request, HttpServletResponse response, Throwable e) {
+ return e.getMessage();
+ }
+
@ExceptionHandler(Throwable.class)
public Result handleAllException(HttpServletRequest request, HttpServletResponse response, Throwable e) {
+ if (shouldRecordException(request)) {
+ return buildResult(request, response, e);
+ }
Map attributes = Requests.from(request).getErrorAttributes(errorAttributes, errorProperties);
return _handleException(request, response, e, attributes)
.orElseGet(() -> handlers.stream()
@@ -252,6 +294,16 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
);
}
+ private Result buildResult(HttpServletRequest request, HttpServletResponse response, Throwable e) {
+ response.setHeader(CACHE_CONTROL, "no-store");
+ response.setStatus(512);
+ ApiReportResult