diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java index edf31f24a..59d6dbcec 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java @@ -31,6 +31,7 @@ public interface GlobalErrorCodeConstants { ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启"); ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项"); + ErrorCode FEIGN_ERROR = new ErrorCode(555, "rpc远程调用异常"); // ========== 自定义错误段 ========== ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求 diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/YudaoCloudRpcAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/YudaoCloudRpcAutoConfiguration.java new file mode 100644 index 000000000..aba609546 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/YudaoCloudRpcAutoConfiguration.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.framework.rpc.config; + +import cn.iocoder.yudao.framework.rpc.core.fallback.YudaoFeignBuilder; +import feign.Feign; +import feign.RequestInterceptor; +import jakarta.annotation.Resource; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; + +import java.util.List; + +/** + * @Description Rpc配置 + * @Author tpz + * @Date 2024/10/30 14:27 + */ +@AutoConfiguration +public class YudaoCloudRpcAutoConfiguration { + + @Resource + private List requestInterceptors; + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + public Feign.Builder feignSentinelBuilder() { + return new YudaoFeignBuilder().requestInterceptors(requestInterceptors); + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/package-info.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/package-info.java index 516acc537..53e81f2ef 100644 --- a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/package-info.java +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/config/package-info.java @@ -1,4 +1,6 @@ /** - * 占坑 TODO + * open-feign的自动配置 + * + * @author tpz */ package cn.iocoder.yudao.framework.rpc.config; diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFallbackFactory.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFallbackFactory.java new file mode 100644 index 000000000..7d283b207 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFallbackFactory.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.framework.rpc.core.fallback; + +import feign.Target; +import lombok.AllArgsConstructor; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cloud.openfeign.FallbackFactory; + +/** + * 默认 FallbackFactory,用户不自定义FallbackFactory的情况加统一走这个FallbackFactory + * tips:返回的对象不实现任何接口故使用CGLIB的Enhancer+MethodInterceptor代理 + * @author tpz + */ +@AllArgsConstructor +public class YudaoFallbackFactory implements FallbackFactory { + + // 代理对象 + private final Target target; + + @SuppressWarnings("unchecked") + @Override + public T create(Throwable cause) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(target.type()); + enhancer.setUseCache(true); + enhancer.setCallback(new YudaoFeignFallback<>(target.type(), target.name(), cause)); + return (T) enhancer.create(); + } +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignBuilder.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignBuilder.java new file mode 100644 index 000000000..3d3a3c64f --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignBuilder.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.framework.rpc.core.fallback; + +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.Target; +import lombok.SneakyThrows; +import org.springframework.beans.BeansException; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.FeignClientFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @Description 扩展Feign Builder 代理获取统一fallback 避免每一个peign接口都需要写一个fallback类 + * @Author tpz + * @Date 2024/10/30 14:37 + */ +public class YudaoFeignBuilder extends Feign.Builder implements ApplicationContextAware { + + public String CONTEXT_FALL_BACK = "fallback"; + public String CONTEXT_FALL_BACK_FACTORY = "fallbackFactory"; + + private FeignClientFactory feignClientFactoryBean; + + @Override + public Feign internalBuild() { + super.invocationHandlerFactory(new InvocationHandlerFactory() { + + @SneakyThrows + @Override + public InvocationHandler create(Target target, Map dispatch) { + + // 获取 FeignClient 注解信息 + FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class); + assert feignClient != null; + Class fallback = feignClient.fallback(); + Class fallbackFactory = feignClient.fallbackFactory(); + String contextId = feignClient.contextId(); + + if (!StringUtils.hasText(contextId)) { + contextId = feignClient.name(); + } + + Object fallbackInstance; + FallbackFactory fallbackFactoryInstance; + + // 如果有自定义fallback使用自定义fallback + if (void.class != fallback) { + fallbackInstance = getFromContext(contextId, CONTEXT_FALL_BACK, fallback, target.type()); + return new YudaoInvocationHandler(target, dispatch, new FallbackFactory.Default<>(fallbackInstance)); + } + + // 如果有自定义fallbackFactory使用自定义fallbackFactory + if (void.class != fallbackFactory) { + fallbackFactoryInstance = (FallbackFactory) getFromContext(contextId, CONTEXT_FALL_BACK_FACTORY, fallbackFactory, FallbackFactory.class); + return new YudaoInvocationHandler(target, dispatch, fallbackFactoryInstance); + } + + // 默认的FallbackFactory + YudaoFallbackFactory defaultFallbackFactory = new YudaoFallbackFactory<>(target); + return new YudaoInvocationHandler(target, dispatch, defaultFallbackFactory); + } + + private Object getFromContext(String name, String type, Class fallbackType, Class targetType) { + Object fallbackInstance = feignClientFactoryBean.getInstance(name, fallbackType); + if (fallbackInstance == null) { + throw new IllegalStateException(String.format("【rpc 配置错误】类型未找到 name:[{%s}] fallbackType:[{%s}]", name, fallbackType)); + } + if (!targetType.isAssignableFrom(fallbackType)) { + throw new IllegalStateException(String.format("【rpc 配置错误】实例不兼容 name:[{%s}]", name)); + } + return fallbackInstance; + } + }); + + super.contract(contract); + return super.internalBuild(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + feignClientFactoryBean = applicationContext.getBean(FeignClientFactory.class); + } +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignFallback.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignFallback.java new file mode 100644 index 000000000..2e4ab6392 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoFeignFallback.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.framework.rpc.core.fallback; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.lang.Nullable; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 统一的远程调用fallback返回信息处理 + * 1 在调用端打印错误日志方便后期排查 + * 2 处理返回类型 + * @author tpz + */ +@Slf4j +@AllArgsConstructor +public class YudaoFeignFallback implements MethodInterceptor { + + // 代理类 + private final Class targetType; + private final String targetName; + private final Throwable cause; + + /** + * 统一的远程调用fallback返回信息处理 + * @author tpz + * @date 2024/10/31 9:23 + **/ + @Nullable + @Override + public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { + String errorMessage = cause.getMessage(); + log.error("【rpc调用异常】被调服务:[{}] 错误信息:[{}]", targetName, errorMessage); + + // 否则是在调用的过程中发生的异常 + if (cause instanceof ServiceException) { + Integer code = ((ServiceException) cause).getCode(); + // 如果是系统异常直接返回feign错误编码 否则返回业务异常的编码 + return CommonResult.error(Objects.equals(code, GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()) ? + GlobalErrorCodeConstants.FEIGN_ERROR.getCode() : code, errorMessage); + } + + // 其他情况统一返回feign的错误编码 + return CommonResult.error(GlobalErrorCodeConstants.FEIGN_ERROR.getCode(), errorMessage); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + YudaoFeignFallback that = (YudaoFeignFallback) o; + return targetType.equals(that.targetType); + } + + @Override + public int hashCode() { + return Objects.hash(targetType); + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoInvocationHandler.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoInvocationHandler.java new file mode 100644 index 000000000..6d999eb79 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/YudaoInvocationHandler.java @@ -0,0 +1,152 @@ +package cn.iocoder.yudao.framework.rpc.core.fallback; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import feign.InvocationHandlerFactory; +import feign.Target; +import org.springframework.cloud.openfeign.FallbackFactory; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.LinkedHashMap; +import java.util.Map; + +import static feign.Util.checkNotNull; + +/** + * 远程调用发生异常自动走统一fallback代理类处理 + * @author tpz + */ +public class YudaoInvocationHandler implements InvocationHandler { + + private final Target target; + + private final Map dispatch; + + private FallbackFactory fallbackFactory; + + private Map fallbackMethodMap; + + public YudaoInvocationHandler(Target target, Map dispatch, + FallbackFactory fallbackFactory) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + this.fallbackFactory = fallbackFactory; + this.fallbackMethodMap = toFallbackMethod(dispatch); + } + + public YudaoInvocationHandler(Target target, Map dispatch) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + // 其他方法直接执行 + Object result = handleNormalMethod(method, args); + if (result != null) return result; + // 获取当前执行代理的方法 + InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method); + // 只代理feign的HardCodedTarget + if (target instanceof Target.HardCodedTarget) { + try { + // 执行远程调用 + result = methodHandler.invoke(args); + // 执行错误的情况 + handleErrResult(result); + } catch (Throwable ex) { + // fallback handle + if (fallbackFactory != null) { + try { + // 根据方法名称使用过程创建代理对象 + return fallbackMethodMap.get(method) + .invoke(fallbackFactory.create(ex), args); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e.getCause()); + } + } else { + // 没有fallback就报错 + throw ex; + } + } + } else { + // 直接执行 + result = methodHandler.invoke(args); + } + + return result; + } + + /** + * 其他方法直接执行 + * + * @author tpz + * @date 2024/10/30 16:41 + **/ + private Object handleNormalMethod(Method method, Object[] args) { + if ("equals".equals(method.getName())) { + try { + Object otherHandler = args.length > 0 && args[0] != null + ? Proxy.getInvocationHandler(args[0]) : null; + return equals(otherHandler); + } catch (IllegalArgumentException e) { + return false; + } + } else if ("hashCode".equals(method.getName())) { + return hashCode(); + } else if ("toString".equals(method.getName())) { + return toString(); + } + return null; + } + + /** + * 错误的情况抛出异常继续走fallback + * + * @author tpz + * @date 2024/10/30 18:08 + **/ + private void handleErrResult(Object result) { + if (result instanceof CommonResult) { + // 如果请远程调用报错会抛出遗异常 + ((CommonResult) result).getCheckedData(); + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof YudaoInvocationHandler) { + YudaoInvocationHandler other = (YudaoInvocationHandler) obj; + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + + /** + * @author tpz + * @date 2024/10/30 16:42 + **/ + static Map toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap<>(); + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + return result; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/package-info.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/package-info.java new file mode 100644 index 000000000..646110fa5 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/fallback/package-info.java @@ -0,0 +1,6 @@ +/** + * 使用代理模式提供默认的fallback实现,避免反复写fallback类 + * + * @author tpz + */ +package cn.iocoder.yudao.framework.rpc.core.fallback; diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/package-info.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/package-info.java index e8e494628..e240f52ad 100644 --- a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/package-info.java +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/core/package-info.java @@ -1,4 +1,6 @@ /** - * 占坑 TODO + * feign自动配置的核心类 + * + * @author tpz */ package cn.iocoder.yudao.framework.rpc.core; diff --git a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/package-info.java b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/package-info.java index 4441019dd..721e3203e 100644 --- a/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/package-info.java +++ b/yudao-framework/yudao-spring-boot-starter-rpc/src/main/java/cn/iocoder/yudao/framework/rpc/package-info.java @@ -1,6 +1,6 @@ /** - * OpenFeign:提供 RESTful API 的调用 + * feign远程调用的相关配置 * - * @author 芋道源码 + * @author tpz */ package cn.iocoder.yudao.framework.rpc;