listenerSet = this.listeners.get(serviceKey);
+ if (listenerSet != null) {
+ listenerSet.remove(listener);
+ if (listenerSet.isEmpty()) {
+ this.listeners.remove(serviceKey);
+ }
+ }
+ }
+
+ public Boolean isEmpty() {
+ return this.listeners.isEmpty();
+ }
+
+ @Override
+ public void onMessage(String key, String msg) {
+ logger.info("sub from redis:" + key + " message:" + msg);
+ String applicationNames = getMappingData(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
+ MappingChangedEvent mappingChangedEvent = new MappingChangedEvent(msg, getAppNames(applicationNames));
+ if (!CollectionUtils.isEmpty(listeners.get(msg))) {
+ for (MappingListener mappingListener : listeners.get(msg)) {
+ mappingListener.onEvent(mappingChangedEvent);
+ }
+ }
+ }
+
+ @Override
+ public void onPMessage(String pattern, String key, String msg) {
+ onMessage(key, msg);
+ }
+
+ @Override
+ public void onPSubscribe(String pattern, int subscribedChannels) {
+ super.onPSubscribe(pattern, subscribedChannels);
+ }
+ }
+
+ /**
+ * Subscribe application names change message.
+ */
+ class MappingDataListener extends Thread {
+
+ private String path;
+
+ private final NotifySub notifySub = new NotifySub();
+ // for test
+ protected volatile boolean running = true;
+
+ public MappingDataListener(String path) {
+ this.path = path;
+ }
+
+ public NotifySub getNotifySub() {
+ return notifySub;
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ if (pool != null) {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.subscribe(notifySub, path);
+ } catch (Throwable e) {
+ String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ } else {
+ try (JedisCluster jedisCluster = new JedisCluster(
+ jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ jedisCluster.subscribe(notifySub, path);
+ } catch (Throwable e) {
+ String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+ }
+ }
+
+ public void shutdown() {
+ try {
+ running = false;
+ notifySub.unsubscribe(path);
+ } catch (Throwable e) {
+ String msg = "Failed to unsubscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ }
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/CustomBeanFactoryPostProcessor.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/CustomBeanFactoryPostProcessor.java
new file mode 100644
index 000000000..a4aefa7c8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/CustomBeanFactoryPostProcessor.java
@@ -0,0 +1,88 @@
+package org.dromara.common.dubbo.config;
+
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.cloud.commons.util.InetUtils;
+import org.springframework.cloud.commons.util.InetUtilsProperties;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.Environment;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/**
+ * dubbo自定义IP注入(避免IP不正确问题)
+ *
+ * @author Lion Li
+ */
+public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered, EnvironmentAware {
+
+ private Environment environment;
+
+ /**
+ * 设置此组件运行的应用环境。
+ * 由 Spring 容器回调注入。
+ *
+ * @param environment 当前应用环境对象
+ */
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ /**
+ * 获取该 BeanFactoryPostProcessor 的顺序,确保它在容器初始化过程中具有最高优先级
+ *
+ * @return 优先级顺序值,越小优先级越高
+ */
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+
+ /**
+ * 在 Spring 容器初始化过程中对 Bean 工厂进行后置处理
+ *
+ * @param beanFactory 可配置的 Bean 工厂
+ * @throws BeansException 如果在处理过程中发生错误
+ */
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ String property = System.getProperty(CommonConstants.DubboProperty.DUBBO_IP_TO_REGISTRY);
+ if (StringUtils.isNotBlank(property)) {
+ return;
+ }
+ // 手动绑定 InetUtilsProperties,避免早期初始化导致配置未注入
+ InetUtilsProperties properties = Binder.get(environment)
+ .bind(InetUtilsProperties.PREFIX, InetUtilsProperties.class)
+ .orElseGet(InetUtilsProperties::new);
+
+ // 创建临时的 InetUtils 实例
+ try (InetUtils inetUtils = new InetUtils(properties)) {
+ String ip = "127.0.0.1";
+ // 获取第一个非回环地址
+ InetAddress address = inetUtils.findFirstNonLoopbackAddress();
+ if (address != null) {
+ if (address instanceof Inet6Address) {
+ // 处理 IPv6 地址
+ String ipv6AddressString = address.getHostAddress();
+ if (ipv6AddressString.contains("%")) {
+ // 去掉可能存在的范围 ID
+ ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
+ }
+ ip = ipv6AddressString;
+ } else {
+ // 处理 IPv4 地址
+ ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
+ }
+ }
+ // 设置系统属性 DUBBO_IP_TO_REGISTRY 为获取到的 IP 地址
+ System.setProperty(CommonConstants.DubboProperty.DUBBO_IP_TO_REGISTRY, ip);
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
new file mode 100644
index 000000000..4a87e22cc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
@@ -0,0 +1,36 @@
+package org.dromara.common.dubbo.config;
+
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.dubbo.handler.DubboExceptionHandler;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * dubbo 配置类
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(DubboCustomProperties.class)
+@PropertySource(value = "classpath:common-dubbo.yml", factory = YmlPropertySourceFactory.class)
+public class DubboConfiguration {
+
+ /**
+ * dubbo自定义IP注入(避免IP不正确问题)
+ */
+ @Bean
+ public BeanFactoryPostProcessor customBeanFactoryPostProcessor() {
+ return new CustomBeanFactoryPostProcessor();
+ }
+
+ /**
+ * 异常处理器
+ */
+ @Bean
+ public DubboExceptionHandler dubboExceptionHandler() {
+ return new DubboExceptionHandler();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/enums/RequestLogEnum.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
similarity index 57%
rename from ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/enums/RequestLogEnum.java
rename to ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
index b8d93b5d8..950114e7e 100644
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/enums/RequestLogEnum.java
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
@@ -1,9 +1,9 @@
-package org.dromara.common.http.log.enums;
+package org.dromara.common.dubbo.enumd;
import lombok.AllArgsConstructor;
/**
- * 请求日志级别.
+ * 请求日志泛型
*
* @author Lion Li
*/
@@ -11,18 +11,18 @@ import lombok.AllArgsConstructor;
public enum RequestLogEnum {
/**
- * 基础信息.
+ * info 基础信息
*/
INFO,
/**
- * 参数信息.
+ * param 参数信息
*/
PARAM,
/**
- * 全量信息.
+ * full 全部
*/
- FULL
+ FULL;
}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
new file mode 100644
index 000000000..5e74c65d2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
@@ -0,0 +1,84 @@
+package org.dromara.common.dubbo.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.rpc.*;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * Dubbo 日志过滤器
+ *
+ * 该过滤器通过实现 Dubbo 的 Filter 接口,在服务调用前后记录日志信息
+ * 可根据配置开关和日志级别输出不同详细程度的日志信息
+ *
+ * 激活条件:
+ * - 在 Provider 和 Consumer 端都生效
+ * - 执行顺序设置为最大值,确保在所有其他过滤器之后执行
+ *
+ * 使用 SpringUtils 获取配置信息,根据配置决定是否记录日志及日志详细程度
+ *
+ * 使用 Lombok 的 @Slf4j 注解简化日志记录
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = Integer.MAX_VALUE)
+public class DubboRequestFilter implements Filter {
+
+ /**
+ * Dubbo Filter 接口实现方法,处理服务调用逻辑并记录日志
+ *
+ * @param invoker Dubbo 服务调用者实例
+ * @param invocation 调用的具体方法信息
+ * @return 调用结果
+ * @throws RpcException 如果调用过程中发生异常
+ */
+ @Override
+ public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
+ DubboCustomProperties properties = SpringUtils.getBean(DubboCustomProperties.class);
+ // 如果未开启请求日志记录,则直接执行服务调用并返回结果
+ if (!properties.getRequestLog()) {
+ return invoker.invoke(invocation);
+ }
+
+ // 判断是 Provider 还是 Consumer
+ String client = CommonConstants.PROVIDER;
+ if (RpcContext.getServiceContext().isConsumerSide()) {
+ client = CommonConstants.CONSUMER;
+ }
+
+ // 构建基础日志信息
+ String baselog = "Client[" + client + "],InterfaceName=[" + invocation.getInvoker().getInterface().getSimpleName() + "],MethodName=[" + invocation.getMethodName() + "]";
+ // 根据日志级别输出不同详细程度的日志信息
+ if (properties.getLogLevel() == RequestLogEnum.INFO) {
+ log.info("DUBBO - 服务调用: {}", baselog);
+ } else {
+ log.info("DUBBO - 服务调用: {},Parameter={}", baselog, invocation.getArguments());
+ }
+
+ // 记录调用开始时间
+ long startTime = System.currentTimeMillis();
+ // 执行接口调用逻辑
+ Result result = invoker.invoke(invocation);
+ // 计算调用耗时
+ long elapsed = System.currentTimeMillis() - startTime;
+ // 如果发生异常且调用的不是泛化服务,则记录异常日志
+ if (result.hasException() && !invoker.getInterface().equals(GenericService.class)) {
+ log.error("DUBBO - 服务异常: {},Exception={}", baselog, result.getException());
+ } else {
+ // 根据日志级别输出服务响应信息
+ if (properties.getLogLevel() == RequestLogEnum.INFO) {
+ log.info("DUBBO - 服务响应: {},SpendTime=[{}ms]", baselog, elapsed);
+ } else if (properties.getLogLevel() == RequestLogEnum.FULL) {
+ log.info("DUBBO - 服务响应: {},SpendTime=[{}ms],Response={}", baselog, elapsed, JsonUtils.toJsonString(new Object[]{result.getValue()}));
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/handler/DubboExceptionHandler.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/handler/DubboExceptionHandler.java
new file mode 100644
index 000000000..5a6149515
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/handler/DubboExceptionHandler.java
@@ -0,0 +1,27 @@
+package org.dromara.common.dubbo.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.rpc.RpcException;
+import org.dromara.common.core.domain.R;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * Dubbo异常处理器
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class DubboExceptionHandler {
+
+ /**
+ * 主键或UNIQUE索引,数据重复异常
+ */
+ @ExceptionHandler(RpcException.class)
+ public R handleDubboException(RpcException e) {
+ log.error("RPC异常: {}", e.getMessage());
+ return R.fail("RPC异常,请联系管理员确认");
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
new file mode 100644
index 000000000..e0df2cd77
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
@@ -0,0 +1,28 @@
+package org.dromara.common.dubbo.properties;
+
+import lombok.Data;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+/**
+ * 自定义配置
+ *
+ * @author Lion Li
+ */
+@Data
+@RefreshScope
+@ConfigurationProperties(prefix = "dubbo.custom")
+public class DubboCustomProperties {
+
+ /**
+ * 是否开启请求日志记录
+ */
+ private Boolean requestLog;
+
+ /**
+ * 日志级别
+ */
+ private RequestLogEnum logLevel;
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
new file mode 100644
index 000000000..6f766ab78
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
@@ -0,0 +1 @@
+dubboRequestFilter=org.dromara.common.dubbo.filter.DubboRequestFilter
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..f60bd3aaf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.dubbo.config.DubboConfiguration
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
new file mode 100644
index 000000000..ddf7f6980
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
@@ -0,0 +1,41 @@
+# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
+dubbo:
+ application:
+ logger: slf4j
+ # 元数据中心 local 本地 remote 远程 这里使用远程便于其他服务获取
+ metadataType: remote
+ # 可选值 interface、instance、all,默认是 all,即接口级地址、应用级地址都注册
+ register-mode: instance
+ service-discovery:
+ # FORCE_INTERFACE,只消费接口级地址,如无地址则报错,单订阅 2.x 地址
+ # APPLICATION_FIRST,智能决策接口级/应用级地址,双订阅
+ # FORCE_APPLICATION,只消费应用级地址,如无地址则报错,单订阅 3.x 地址
+ migration: FORCE_APPLICATION
+ # 注册中心配置
+ registry:
+ address: nacos://${spring.cloud.nacos.server-addr}
+ group: DUBBO_GROUP
+ username: ${spring.cloud.nacos.username}
+ password: ${spring.cloud.nacos.password}
+ parameters:
+ namespace: ${spring.profiles.active}
+ metadata-report:
+ address: redis://${spring.data.redis.host:localhost}:${spring.data.redis.port:6379}
+ group: DUBBO_GROUP
+ username: ${spring.data.redis.username:default}
+ password: ${spring.data.redis.password}
+ parameters:
+ namespace: ${spring.profiles.active}
+ database: ${spring.data.redis.database}
+ timeout: ${spring.data.redis.timeout}
+ # 消费者相关配置
+ consumer:
+ # 结果缓存(LRU算法)
+ # 会有数据不一致问题 建议在注解局部开启
+ cache: false
+ # 支持校验注解
+ validation: jvalidationNew
+ # 调用重试 不包括第一次 0为不需要重试
+ retries: 0
+ # 初始化检查
+ check: false
diff --git a/ruoyi-common/ruoyi-common-http/pom.xml b/ruoyi-common/ruoyi-common-http/pom.xml
deleted file mode 100644
index a7789a572..000000000
--- a/ruoyi-common/ruoyi-common-http/pom.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
- org.dromara
- ruoyi-common
- ${revision}
-
- 4.0.0
-
- ruoyi-common-http
-
-
- ruoyi-common-http 内部 HTTP 远程调用
-
-
-
-
- org.dromara
- ruoyi-common-core
-
-
-
- org.dromara
- ruoyi-common-json
-
-
-
- org.dromara
- ruoyi-common-satoken
-
-
-
- org.springframework.cloud
- spring-cloud-starter-loadbalancer
-
-
-
- org.eclipse.jetty
- jetty-client
-
-
-
-
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/annotation/RemoteServiceController.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/annotation/RemoteServiceController.java
deleted file mode 100644
index c13dab0bd..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/annotation/RemoteServiceController.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.dromara.common.http.annotation;
-
-import org.springframework.core.annotation.AliasFor;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * 内部 HTTP 服务控制器.
- *
- * @author Lion Li
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@RestController
-public @interface RemoteServiceController {
-
- @AliasFor(annotation = RestController.class, attribute = "value")
- String value() default "";
-}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/config/RemoteHttpAutoConfiguration.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/config/RemoteHttpAutoConfiguration.java
deleted file mode 100644
index de6d05170..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/config/RemoteHttpAutoConfiguration.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package org.dromara.common.http.config;
-
-import cn.dev33.satoken.same.SaSameUtil;
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.domain.R;
-import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.core.utils.ServletUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.core.annotation.RemoteHttpService;
-import org.dromara.common.http.annotation.RemoteServiceController;
-import org.dromara.common.http.log.aspect.RemoteHttpProviderLogAspect;
-import org.dromara.common.http.handler.RemoteHttpExceptionHandler;
-import org.dromara.common.http.properties.RemoteHttpProperties;
-import org.dromara.common.http.registrar.RemoteHttpServiceRegistrar;
-import org.dromara.common.http.support.RemoteHttpFallbackProxyPostProcessor;
-import org.dromara.common.http.log.support.LoggingHttpExchangeAdapter;
-import org.dromara.common.http.log.support.RemoteHttpLogSupport;
-import org.dromara.common.json.utils.JsonUtils;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
-import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.Bean;
-import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.http.client.ClientHttpRequestInterceptor;
-import org.springframework.util.StreamUtils;
-import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-/**
- * 内部 HTTP 远程调用配置.
- *
- * 这里把运行时几条链路接起来:
- * 1. Consumer 发请求前透传认证头和 Seata XID
- * 2. 远程非 2xx 响应统一转成 ServiceException
- * 3. 打开请求日志时,为 consumer/provider 两侧挂日志能力
- * 4. 远程代理失败时按接口声明触发 fallback
- *
- * @author Lion Li
- */
-@Slf4j
-@AutoConfiguration
-@Import(RemoteHttpServiceRegistrar.class)
-@EnableConfigurationProperties(RemoteHttpProperties.class)
-public class RemoteHttpAutoConfiguration {
-
- @Bean
- public static BeanFactoryPostProcessor remoteHttpControllerProxyCompatibilityPostProcessor() {
- return new RemoteHttpInfrastructurePostProcessor();
- }
-
- @Bean("remoteHttpHeaderInterceptor")
- public ClientHttpRequestInterceptor remoteHttpHeaderInterceptor() {
- return (request, body, execution) -> {
- HttpHeaders headers = request.getHeaders();
- HttpServletRequest currentRequest = ServletUtils.getRequest();
- if (currentRequest != null) {
- String authorization = currentRequest.getHeader(HttpHeaders.AUTHORIZATION);
- if (StringUtils.isNotBlank(authorization)) {
- headers.set(HttpHeaders.AUTHORIZATION, authorization);
- }
- }
- try {
- // 透传 same-token,保证服务间调用仍然走内网鉴权。
- headers.set(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());
- } catch (Exception ignored) {
- }
- relaySeataXid(headers);
- return execution.execute(request, body);
- };
- }
-
- @Bean
- public RestClientHttpServiceGroupConfigurer remoteHttpServiceGroupConfigurer(
- ClientHttpRequestInterceptor remoteHttpHeaderInterceptor,
- RemoteHttpLogSupport remoteHttpLogSupport) {
- return groups -> groups.forEachGroup((group, clientBuilder, proxyFactoryBuilder) -> {
- clientBuilder.requestInterceptor(remoteHttpHeaderInterceptor)
- // provider 侧远程接口异常会直接映射成非 2xx,这里只按 HTTP 状态处理即可。
- .defaultStatusHandler(HttpStatusCode::isError, (request, response) -> {
- throwServiceException(response.getStatusCode().value(), response.getStatusText(), readResponseBody(response));
- });
- if (remoteHttpLogSupport.isEnabled()) {
- // consumer 侧日志挂在 HttpExchangeAdapter 上,避免碰底层 body 重复读取问题。
- proxyFactoryBuilder.exchangeAdapterDecorator(adapter -> new LoggingHttpExchangeAdapter(adapter, remoteHttpLogSupport));
- }
- });
- }
-
- @Bean
- public RemoteHttpFallbackProxyPostProcessor remoteHttpFallbackProxyPostProcessor() {
- return new RemoteHttpFallbackProxyPostProcessor();
- }
-
- @Bean
- public RemoteHttpLogSupport remoteHttpLogSupport(RemoteHttpProperties properties) {
- return new RemoteHttpLogSupport(properties);
- }
-
- @Bean
- public RemoteHttpProviderLogAspect remoteHttpProviderLogAspect(RemoteHttpLogSupport remoteHttpLogSupport) {
- return new RemoteHttpProviderLogAspect(remoteHttpLogSupport);
- }
-
- @Bean
- public RemoteHttpExceptionHandler remoteHttpExceptionHandler() {
- return new RemoteHttpExceptionHandler();
- }
-
- private void relaySeataXid(HttpHeaders headers) {
- try {
- // 通过反射做可选适配,未引入 Seata 时不强依赖该类。
- Class> rootContextClass = Class.forName("org.apache.seata.core.context.RootContext");
- String xid = (String) rootContextClass.getMethod("getXID").invoke(null);
- if (StringUtils.isBlank(xid)) {
- return;
- }
- String headerName = (String) rootContextClass.getField("KEY_XID").get(null);
- headers.set(headerName, xid);
- } catch (ClassNotFoundException ignored) {
- } catch (Exception e) {
- log.debug("relay seata xid failed", e);
- }
- }
-
- private String readResponseBody(org.springframework.http.client.ClientHttpResponse response) {
- try {
- return StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
- } catch (IOException e) {
- log.debug("read remote response body failed", e);
- return null;
- }
- }
-
- private void throwServiceException(int statusCode, String statusText, String responseBody) {
- if (StringUtils.isNotBlank(responseBody) && JsonUtils.isJsonObject(responseBody)) {
- try {
- // 远程服务如果按 R 返回错误信息,优先还原成更友好的业务异常消息。
- R> result = JsonUtils.parseObject(responseBody, R.class);
- if (result != null && (result.getCode() == 0 || R.isSuccess(result))) {
- return;
- }
- if (result != null && StringUtils.isNotBlank(result.getMsg())) {
- throw new ServiceException(result.getMsg(), result.getCode());
- }
- } catch (ServiceException se) {
- throw se;
- } catch (RuntimeException e) {
- log.debug("parse remote error body failed: {}", responseBody, e);
- }
- }
- String message = StringUtils.firstNonBlank(responseBody, statusText, "远程服务调用失败");
- throw new ServiceException(message, statusCode);
- }
-
- private static final class RemoteHttpInfrastructurePostProcessor implements BeanFactoryPostProcessor {
-
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
- BeanDefinitionRegistry registry = beanFactory instanceof BeanDefinitionRegistry beanDefinitionRegistry
- ? beanDefinitionRegistry : null;
- ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
- for (String beanName : beanFactory.getBeanDefinitionNames()) {
- BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
- preserveRemoteControllerTargetClass(beanDefinition);
- registerFallbackBeanDefinition(registry, beanFactory, beanDefinition, beanClassLoader);
- }
- }
-
- private void preserveRemoteControllerTargetClass(BeanDefinition beanDefinition) {
- if (!(beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition)) {
- return;
- }
- if (!annotatedBeanDefinition.getMetadata().hasAnnotation(RemoteServiceController.class.getName())) {
- return;
- }
- beanDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
- }
-
- private void registerFallbackBeanDefinition(BeanDefinitionRegistry registry,
- ConfigurableListableBeanFactory beanFactory, BeanDefinition beanDefinition, ClassLoader beanClassLoader) {
- if (registry == null) {
- return;
- }
- Class> serviceInterface = resolveRemoteServiceInterface(beanDefinition, beanClassLoader);
- if (serviceInterface == null) {
- return;
- }
- RemoteHttpService remoteHttpService = serviceInterface.getAnnotation(RemoteHttpService.class);
- if (remoteHttpService == null || remoteHttpService.fallback() == void.class) {
- return;
- }
- Class> fallbackClass = remoteHttpService.fallback();
- if (!serviceInterface.isAssignableFrom(fallbackClass)) {
- throw new IllegalStateException("Fallback class must implement remote service interface: "
- + fallbackClass.getName() + " -> " + serviceInterface.getName());
- }
- if (beanFactory.getBeanNamesForType(fallbackClass, false, false).length > 0) {
- return;
- }
- BeanDefinition fallbackBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(fallbackClass)
- .setLazyInit(true)
- .getBeanDefinition();
- // fallback 只给框架内部按具体类型获取使用,不参与业务侧按接口类型自动注入,
- // 否则会和真正的远程代理一起成为 RemoteXxxService 的候选 Bean。
- fallbackBeanDefinition.setAutowireCandidate(false);
- fallbackBeanDefinition.setPrimary(false);
- registry.registerBeanDefinition(fallbackClass.getName(), fallbackBeanDefinition);
- }
-
- private Class> resolveRemoteServiceInterface(BeanDefinition beanDefinition, ClassLoader beanClassLoader) {
- String beanClassName = beanDefinition.getBeanClassName();
- if (beanClassName == null || beanClassLoader == null) {
- return null;
- }
- Class> beanClass = org.springframework.util.ClassUtils.resolveClassName(beanClassName, beanClassLoader);
- if (!beanClass.isInterface() || !beanClass.isAnnotationPresent(RemoteHttpService.class)) {
- return null;
- }
- return beanClass;
- }
- }
-}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/handler/RemoteHttpExceptionHandler.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/handler/RemoteHttpExceptionHandler.java
deleted file mode 100644
index c60037081..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/handler/RemoteHttpExceptionHandler.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package org.dromara.common.http.handler;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.validation.ConstraintViolation;
-import jakarta.validation.ConstraintViolationException;
-import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.constant.HttpStatus;
-import org.dromara.common.core.domain.R;
-import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.core.exception.base.BaseException;
-import org.dromara.common.core.utils.StreamUtils;
-import org.dromara.common.http.annotation.RemoteServiceController;
-import org.springframework.context.MessageSourceResolvable;
-import org.springframework.context.support.DefaultMessageSourceResolvable;
-import org.springframework.core.annotation.Order;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.http.ResponseEntity;
-import org.springframework.http.converter.HttpMessageNotReadableException;
-import org.springframework.validation.BindException;
-import org.springframework.web.HttpRequestMethodNotSupportedException;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.MissingPathVariableException;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-import org.springframework.web.method.annotation.HandlerMethodValidationException;
-import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
-import org.springframework.web.servlet.NoHandlerFoundException;
-
-/**
- * 仅作用于内部远程 HTTP 接口的异常处理器.
- *
- * 远程接口与普通对外 API 分开处理:
- * 1. provider 直接返回非 2xx HTTP 状态,consumer 只按状态码判错
- * 2. 响应体仍保留 R.code / R.msg,方便把业务码继续透传回消费方
- */
-@Slf4j
-@Order(org.springframework.core.Ordered.HIGHEST_PRECEDENCE)
-@RestControllerAdvice(annotations = RemoteServiceController.class)
-public class RemoteHttpExceptionHandler {
-
- @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
- public ResponseEntity> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
- HttpServletRequest request) {
- log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod());
- return buildResponse(HttpStatus.BAD_METHOD, e.getMessage());
- }
-
- @ExceptionHandler(ServiceException.class)
- public ResponseEntity> handleServiceException(ServiceException e) {
- log.error(e.getMessage());
- int code = resolveBusinessCode(e.getCode(), HttpStatus.ERROR);
- return buildResponse(code, e.getMessage());
- }
-
- @ExceptionHandler(ServletException.class)
- public ResponseEntity> handleServletException(ServletException e, HttpServletRequest request) {
- log.error("请求地址'{}',发生未知异常.", request.getRequestURI(), e);
- return buildResponse(HttpStatus.ERROR, e.getMessage());
- }
-
- @ExceptionHandler(BaseException.class)
- public ResponseEntity> handleBaseException(BaseException e) {
- log.error(e.getMessage());
- return buildResponse(HttpStatus.ERROR, e.getMessage());
- }
-
- @ExceptionHandler(MissingPathVariableException.class)
- public ResponseEntity> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
- log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", request.getRequestURI());
- return buildResponse(HttpStatus.BAD_REQUEST, String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
- }
-
- @ExceptionHandler(MethodArgumentTypeMismatchException.class)
- public ResponseEntity> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
- HttpServletRequest request) {
- log.error("请求参数类型不匹配'{}',发生系统异常.", request.getRequestURI());
- String message = String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'",
- e.getName(), e.getRequiredType().getName(), e.getValue());
- return buildResponse(HttpStatus.BAD_REQUEST, message);
- }
-
- @ExceptionHandler(NoHandlerFoundException.class)
- public ResponseEntity> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
- log.error("请求地址'{}'不存在.", request.getRequestURI());
- return buildResponse(HttpStatus.NOT_FOUND, e.getMessage());
- }
-
- @ExceptionHandler(BindException.class)
- public ResponseEntity> handleBindException(BindException e) {
- log.error(e.getMessage());
- String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
- return buildResponse(HttpStatus.BAD_REQUEST, message);
- }
-
- @ExceptionHandler(ConstraintViolationException.class)
- public ResponseEntity> constraintViolationException(ConstraintViolationException e) {
- log.error(e.getMessage());
- String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
- return buildResponse(HttpStatus.BAD_REQUEST, message);
- }
-
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public ResponseEntity> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
- log.error(e.getMessage());
- String message = StreamUtils.join(e.getBindingResult().getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
- return buildResponse(HttpStatus.BAD_REQUEST, message);
- }
-
- @ExceptionHandler(HandlerMethodValidationException.class)
- public ResponseEntity> handlerMethodValidationException(HandlerMethodValidationException e) {
- log.error(e.getMessage());
- String message = StreamUtils.join(e.getAllErrors(), MessageSourceResolvable::getDefaultMessage, ", ");
- return buildResponse(HttpStatus.BAD_REQUEST, message);
- }
-
- @ExceptionHandler(HttpMessageNotReadableException.class)
- public ResponseEntity> handleHttpMessageNotReadableException(HttpMessageNotReadableException e,
- HttpServletRequest request) {
- log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
- return buildResponse(HttpStatus.BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
- }
-
- @ExceptionHandler(RuntimeException.class)
- public ResponseEntity> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
- log.error("请求地址'{}',发生未知异常.", request.getRequestURI(), e);
- return buildResponse(HttpStatus.ERROR, e.getMessage());
- }
-
- @ExceptionHandler(Exception.class)
- public ResponseEntity> handleException(Exception e, HttpServletRequest request) {
- log.error("请求地址'{}',发生系统异常.", request.getRequestURI(), e);
- return buildResponse(HttpStatus.ERROR, e.getMessage());
- }
-
- private ResponseEntity> buildResponse(int code, String message) {
- return ResponseEntity.status(resolveHttpStatus(code))
- .body(R.fail(code, message));
- }
-
- private HttpStatusCode resolveHttpStatus(int code) {
- if (code >= 100 && code <= 599) {
- return HttpStatusCode.valueOf(code);
- }
- return HttpStatusCode.valueOf(HttpStatus.ERROR);
- }
-
- private int resolveBusinessCode(Integer code, int defaultCode) {
- return code == null ? defaultCode : code;
- }
-}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/aspect/RemoteHttpProviderLogAspect.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/aspect/RemoteHttpProviderLogAspect.java
deleted file mode 100644
index 1ca4264d2..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/aspect/RemoteHttpProviderLogAspect.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.dromara.common.http.log.aspect;
-
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.RequiredArgsConstructor;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.dromara.common.core.utils.ServletUtils;
-import org.dromara.common.core.annotation.RemoteHttpService;
-import org.dromara.common.http.log.support.RemoteHttpLogSupport;
-import org.springframework.http.HttpMethod;
-import org.springframework.web.service.annotation.HttpExchange;
-import org.springframework.aop.support.AopUtils;
-import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.util.StringUtils;
-
-import java.lang.reflect.Method;
-
-/**
- * 内部 HTTP Provider 日志切面.
- *
- * Provider 侧日志不直接读原始请求 body,而是等 Spring 完成参数绑定后
- * 直接记录方法入参/返回值,这样可以避免 servlet body 重复读取。
- *
- * @author Lion Li
- */
-@Aspect
-@RequiredArgsConstructor
-public class RemoteHttpProviderLogAspect {
-
- private final RemoteHttpLogSupport logSupport;
-
- @Around("@within(org.dromara.common.http.annotation.RemoteServiceController) && execution(public * *(..))")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- MethodSignature signature = (MethodSignature) joinPoint.getSignature();
- Method method = signature.getMethod();
- Class> targetClass = AopUtils.getTargetClass(joinPoint.getTarget());
- Object[] arguments = joinPoint.getArgs();
- HttpServletRequest request = ServletUtils.getRequest();
- Class> remoteInterface = resolveRemoteInterface(targetClass, method);
- // 真实 HTTP 调用时优先从 servlet 请求拿 method/path;
- // 本地短路调用时再回退到接口上的 @HttpExchange 注解。
- HttpMethod httpMethod = resolveHttpMethod(request, remoteInterface, method);
- String path = resolvePath(request, remoteInterface, method);
- this.logSupport.logRequest(RemoteHttpLogSupport.PROVIDER, httpMethod, path, arguments);
- long startTime = System.currentTimeMillis();
- try {
- Object result = joinPoint.proceed();
- this.logSupport.logResponse(RemoteHttpLogSupport.PROVIDER, httpMethod, path, System.currentTimeMillis() - startTime, result);
- return result;
- } catch (Throwable ex) {
- this.logSupport.logException(RemoteHttpLogSupport.PROVIDER, httpMethod, path, System.currentTimeMillis() - startTime, ex);
- throw ex;
- }
- }
-
- private HttpMethod resolveHttpMethod(HttpServletRequest request, Class> remoteInterface, Method method) {
- if (request != null && StringUtils.hasText(request.getMethod())) {
- return HttpMethod.valueOf(request.getMethod());
- }
- HttpExchange methodExchange = resolveMethodExchange(remoteInterface, method);
- if (methodExchange != null && StringUtils.hasText(methodExchange.method())) {
- return HttpMethod.valueOf(methodExchange.method());
- }
- HttpExchange typeExchange = resolveTypeExchange(remoteInterface);
- if (typeExchange != null && StringUtils.hasText(typeExchange.method())) {
- return HttpMethod.valueOf(typeExchange.method());
- }
- return null;
- }
-
- private String resolvePath(HttpServletRequest request, Class> remoteInterface, Method method) {
- if (request != null) {
- String requestUri = request.getRequestURI();
- if (StringUtils.hasText(requestUri)) {
- String queryString = request.getQueryString();
- if (!StringUtils.hasText(queryString)) {
- return requestUri;
- }
- return requestUri + '?' + queryString;
- }
- }
- String typePath = extractPath(resolveTypeExchange(remoteInterface));
- String methodPath = extractPath(resolveMethodExchange(remoteInterface, method));
- if (!StringUtils.hasText(typePath)) {
- return methodPath;
- }
- if (!StringUtils.hasText(methodPath)) {
- return typePath;
- }
- // 拼出接口级 + 方法级路径,作为本地短路场景下的日志定位信息。
- return combinePath(typePath, methodPath);
- }
-
- private Class> resolveRemoteInterface(Class> targetClass, Method method) {
- for (Class> interfaceType : targetClass.getInterfaces()) {
- if (interfaceType.isAnnotationPresent(RemoteHttpService.class)
- && org.springframework.util.ReflectionUtils.findMethod(interfaceType, method.getName(), method.getParameterTypes()) != null) {
- return interfaceType;
- }
- }
- return null;
- }
-
- private HttpExchange resolveTypeExchange(Class> remoteInterface) {
- if (remoteInterface == null) {
- return null;
- }
- return AnnotatedElementUtils.findMergedAnnotation(remoteInterface, HttpExchange.class);
- }
-
- private HttpExchange resolveMethodExchange(Class> remoteInterface, Method method) {
- if (remoteInterface == null) {
- return null;
- }
- Method interfaceMethod = org.springframework.util.ReflectionUtils.findMethod(remoteInterface, method.getName(), method.getParameterTypes());
- if (interfaceMethod == null) {
- return null;
- }
- return AnnotatedElementUtils.findMergedAnnotation(interfaceMethod, HttpExchange.class);
- }
-
- private String extractPath(HttpExchange exchange) {
- if (exchange == null) {
- return null;
- }
- if (StringUtils.hasText(exchange.url())) {
- return exchange.url();
- }
- if (StringUtils.hasText(exchange.value())) {
- return exchange.value();
- }
- return null;
- }
-
- private String combinePath(String typePath, String methodPath) {
- String normalizedTypePath = trimTrailingSlash(typePath);
- String normalizedMethodPath = trimLeadingSlash(methodPath);
- if (!StringUtils.hasText(normalizedTypePath)) {
- return '/' + normalizedMethodPath;
- }
- if (!StringUtils.hasText(normalizedMethodPath)) {
- return normalizedTypePath;
- }
- return normalizedTypePath + '/' + normalizedMethodPath;
- }
-
- private String trimTrailingSlash(String path) {
- if (!StringUtils.hasText(path)) {
- return path;
- }
- return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
- }
-
- private String trimLeadingSlash(String path) {
- if (!StringUtils.hasText(path)) {
- return path;
- }
- return path.startsWith("/") ? path.substring(1) : path;
- }
-}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/LoggingHttpExchangeAdapter.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/LoggingHttpExchangeAdapter.java
deleted file mode 100644
index 87ed90360..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/LoggingHttpExchangeAdapter.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.dromara.common.http.log.support;
-
-import org.dromara.common.core.exception.ServiceException;
-import org.jspecify.annotations.Nullable;
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.StringUtils;
-import org.springframework.web.service.invoker.HttpExchangeAdapter;
-import org.springframework.web.service.invoker.HttpExchangeAdapterDecorator;
-import org.springframework.web.service.invoker.HttpRequestValues;
-
-import java.net.URI;
-import java.util.Map;
-
-/**
- * 内部 HTTP Consumer 日志装饰器.
- *
- * Consumer 侧日志挂在 HttpServiceProxyFactory 的 exchange adapter 上,
- * 这样可以直接拿到最终请求 method/path 和解码后的返回值,
- * 比直接拦截底层流更稳定,也更容易规避 body 重复读问题。
- *
- * @author Lion Li
- */
-public class LoggingHttpExchangeAdapter extends HttpExchangeAdapterDecorator {
-
- private final RemoteHttpLogSupport logSupport;
-
- public LoggingHttpExchangeAdapter(HttpExchangeAdapter delegate, RemoteHttpLogSupport logSupport) {
- super(delegate);
- this.logSupport = logSupport;
- }
-
- @Override
- public void exchange(HttpRequestValues requestValues) {
- invoke(requestValues, () -> {
- super.exchange(requestValues);
- return null;
- });
- }
-
- @Override
- public HttpHeaders exchangeForHeaders(HttpRequestValues requestValues) {
- return invoke(requestValues, () -> super.exchangeForHeaders(requestValues));
- }
-
- @Override
- public @Nullable T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) {
- return invoke(requestValues, () -> super.exchangeForBody(requestValues, bodyType));
- }
-
- @Override
- public ResponseEntity exchangeForBodilessEntity(HttpRequestValues requestValues) {
- return invoke(requestValues, () -> super.exchangeForBodilessEntity(requestValues));
- }
-
- @Override
- public ResponseEntity exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) {
- return invoke(requestValues, () -> super.exchangeForEntity(requestValues, bodyType));
- }
-
- private T invoke(HttpRequestValues requestValues, ThrowingSupplier supplier) {
- HttpMethod httpMethod = requestValues.getHttpMethod();
- String path = resolvePath(requestValues);
- Object bodyValue = requestValues.getBodyValue();
- Object[] arguments = bodyValue == null ? new Object[0] : bodyValue instanceof Object[] array ? array : new Object[] {bodyValue};
- this.logSupport.logRequest(RemoteHttpLogSupport.CONSUMER, httpMethod, path, arguments);
- long startTime = System.currentTimeMillis();
- try {
- T result = supplier.get();
- this.logSupport.logResponse(RemoteHttpLogSupport.CONSUMER, httpMethod, path,
- System.currentTimeMillis() - startTime, result);
- return result;
- } catch (Throwable ex) {
- this.logSupport.logException(RemoteHttpLogSupport.CONSUMER, httpMethod, path,
- System.currentTimeMillis() - startTime, ex);
- switch (ex) {
- case ServiceException serviceException -> throw serviceException;
- case RuntimeException runtimeException -> throw runtimeException;
- case Error error -> throw error;
- default -> {
- }
- }
- throw new IllegalStateException(ex);
- }
- }
-
- private String resolvePath(HttpRequestValues requestValues) {
- URI uri = requestValues.getUri();
- if (uri != null) {
- // 能拿到最终 URI 时优先打印最终请求地址,便于线上排查。
- return uri.toString();
- }
- String uriTemplate = requestValues.getUriTemplate();
- if (!StringUtils.hasText(uriTemplate)) {
- return null;
- }
- Map uriVariables = requestValues.getUriVariables();
- String path = uriTemplate;
- if (uriVariables != null) {
- for (Map.Entry entry : uriVariables.entrySet()) {
- path = path.replace("{" + entry.getKey() + "}", String.valueOf(entry.getValue()));
- }
- }
- return path;
- }
-
- @FunctionalInterface
- private interface ThrowingSupplier {
-
- T get() throws Throwable;
- }
-}
diff --git a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/RemoteHttpLogSupport.java b/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/RemoteHttpLogSupport.java
deleted file mode 100644
index e9cdd7a4f..000000000
--- a/ruoyi-common/ruoyi-common-http/src/main/java/org/dromara/common/http/log/support/RemoteHttpLogSupport.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.dromara.common.http.log.support;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.http.log.enums.RequestLogEnum;
-import org.dromara.common.http.properties.RemoteHttpProperties;
-import org.dromara.common.json.utils.JsonUtils;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * 内部 HTTP 日志支持.
- *
- * 这里只做两件事:
- * 1. 统一 consumer/provider 的日志格式
- * 2. 对 byte[] 等内容做简单脱敏,避免日志直接刷大块二进制
- *
- * @author Lion Li
- */
-@Slf4j
-@RequiredArgsConstructor
-public class RemoteHttpLogSupport {
-
- public static final String CONSUMER = "CONSUMER";
- public static final String PROVIDER = "PROVIDER";
-
- private final RemoteHttpProperties properties;
-
- public boolean isEnabled() {
- return Boolean.TRUE.equals(properties.getRequestLog());
- }
-
- public boolean isFullLogEnabled() {
- return properties.getLogLevel() == RequestLogEnum.FULL;
- }
-
- public void logRequest(String client, HttpMethod httpMethod, String path, Object[] arguments) {
- if (!isEnabled()) {
- return;
- }
- String baseLog = buildBaseLog(client, httpMethod, path);
- if (properties.getLogLevel() == RequestLogEnum.INFO) {
- log.info("HTTP - 服务调用: {}", baseLog);
- return;
- }
- log.info("HTTP - 服务调用: {},Parameter={}", baseLog, formatArguments(arguments));
- }
-
- public void logResponse(String client, HttpMethod httpMethod, String path, long elapsed, Object response) {
- if (!isEnabled()) {
- return;
- }
- String baseLog = buildBaseLog(client, httpMethod, path);
- if (properties.getLogLevel() == RequestLogEnum.FULL) {
- log.info("HTTP - 服务响应: {},SpendTime=[{}ms],Response={}", baseLog, elapsed, formatValue(unwrapResponse(response)));
- return;
- }
- log.info("HTTP - 服务响应: {},SpendTime=[{}ms]", baseLog, elapsed);
- }
-
- public void logException(String client, HttpMethod httpMethod, String path, long elapsed, Throwable throwable) {
- if (!isEnabled()) {
- return;
- }
- String baseLog = buildBaseLog(client, httpMethod, path);
- log.error("HTTP - 服务异常: {},SpendTime=[{}ms],Exception={}", baseLog, elapsed, throwable.getMessage(), throwable);
- }
-
- private String buildBaseLog(String client, HttpMethod httpMethod, String path) {
- return "Client[" + client + ']' +
- ",HttpMethod[" +
- (httpMethod != null ? httpMethod : "UNKNOWN") +
- ']' +
- ",Path[" +
- (StringUtils.hasText(path) ? path : "UNKNOWN") +
- ']';
- }
-
- private String formatArguments(Object[] arguments) {
- return formatValue(arguments == null ? new Object[0] : arguments);
- }
-
- private Object unwrapResponse(Object response) {
- if (response instanceof ResponseEntity> responseEntity) {
- return responseEntity.getBody();
- }
- return response;
- }
-
- private String formatValue(Object value) {
- try {
- return JsonUtils.toJsonString(sanitizeValue(value));
- } catch (RuntimeException ignored) {
- return String.valueOf(value);
- }
- }
-
- private Object sanitizeValue(Object value) {
- if (value == null) {
- return null;
- }
- if (value instanceof byte[] bytes) {
- // 文件上传这类场景只记录长度,避免二进制内容直接进日志。
- return "byte[" + bytes.length + "]";
- }
- if (value instanceof Object[] array) {
- Object[] sanitized = new Object[array.length];
- for (int i = 0; i < array.length; i++) {
- sanitized[i] = sanitizeValue(array[i]);
- }
- return sanitized;
- }
- if (value instanceof Collection> collection) {
- return collection.stream().map(this::sanitizeValue).toList();
- }
- if (value instanceof Map, ?> map) {
- Map