[重大更新] 使用 spring 新特性 HttpServiceClient 替代 Dubbo 降低框架使用难度(半成本 数据权限不好使)

This commit is contained in:
疯狂的狮子Li
2026-03-20 19:56:09 +08:00
parent 9cd198d99d
commit b6d2274b53
127 changed files with 1894 additions and 1496 deletions

View File

@@ -74,7 +74,7 @@
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<profiles.active>public</profiles.active>
<nacos.server>127.0.0.1:8848</nacos.server>
<nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
<nacos.config.group>DEFAULT_GROUP</nacos.config.group>

View File

@@ -1,7 +1,13 @@
package org.dromara.resource.api;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.resource.api.domain.RemoteFile;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.List;
@@ -10,6 +16,8 @@ import java.util.List;
*
* @author Lion Li
*/
@RemoteHttpService(value = "ruoyi-resource", fallback = RemoteFileServiceFallback.class)
@HttpExchange("/remote/file")
public interface RemoteFileService {
/**
@@ -18,7 +26,9 @@ public interface RemoteFileService {
* @param file 文件信息
* @return 结果
*/
RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) throws ServiceException;
@PostExchange("/upload")
RemoteFile upload(@RequestParam String name, @RequestParam String originalFilename,
@RequestParam String contentType, @RequestBody byte[] file) throws ServiceException;
/**
* 通过ossId查询对应的url
@@ -26,7 +36,8 @@ public interface RemoteFileService {
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
String selectUrlByIds(String ossIds);
@GetExchange("/select-url-by-ids")
String selectUrlByIds(@RequestParam String ossIds);
/**
* 通过ossId查询列表
@@ -34,5 +45,6 @@ public interface RemoteFileService {
* @param ossIds ossId串逗号分隔
* @return 列表
*/
List<RemoteFile> selectByIds(String ossIds);
@GetExchange("/select-by-ids")
List<RemoteFile> selectByIds(@RequestParam String ossIds);
}

View File

@@ -7,12 +7,12 @@ import org.dromara.resource.api.domain.RemoteFile;
import java.util.List;
/**
* 文件服务(降级处理)
* 文件服务熔断降级.
*
* @author Lion Li
*/
@Slf4j
public class RemoteFileServiceMock implements RemoteFileService {
public class RemoteFileServiceFallback implements RemoteFileService {
/**
* 上传文件

View File

@@ -1,12 +1,18 @@
package org.dromara.resource.api;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
/**
* 邮件服务
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-resource")
@HttpExchange("/remote/mail")
public interface RemoteMailService {
/**
@@ -16,6 +22,7 @@ public interface RemoteMailService {
* @param subject 标题
* @param text 内容
*/
void send(String to, String subject, String text) throws ServiceException;
@PostExchange("/send")
void send(@RequestParam String to, @RequestParam String subject, @RequestParam String text) throws ServiceException;
}

View File

@@ -1,5 +1,11 @@
package org.dromara.resource.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.List;
/**
@@ -7,6 +13,8 @@ import java.util.List;
*
* @author Lion Li
*/
@RemoteHttpService(value = "ruoyi-resource", fallback = RemoteMessageServiceFallback.class)
@HttpExchange("/remote/message")
public interface RemoteMessageService {
/**
@@ -15,12 +23,14 @@ public interface RemoteMessageService {
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
void publishMessage(List<Long> sessionKey, String message);
@PostExchange("/publish-message")
void publishMessage(@RequestBody List<Long> sessionKey, @RequestParam String message);
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
void publishAll(String message);
@PostExchange("/publish-all")
void publishAll(@RequestParam String message);
}

View File

@@ -0,0 +1,35 @@
package org.dromara.resource.api;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* 消息服务熔断降级.
*
* @author Lion Li
*/
@Slf4j
public class RemoteMessageServiceFallback implements RemoteMessageService {
/**
* 发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
@Override
public void publishMessage(List<Long> sessionKey, String message) {
log.warn("消息服务调用失败, 已触发熔断降级");
}
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
@Override
public void publishAll(String message) {
log.warn("消息服务调用失败, 已触发熔断降级");
}
}

View File

@@ -1,47 +0,0 @@
package org.dromara.resource.api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* 消息服务
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
public class RemoteMessageServiceStub implements RemoteMessageService {
private final RemoteMessageService remoteMessageService;
/**
* 发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
*/
@Override
public void publishMessage(List<Long> sessionKey, String message) {
try {
remoteMessageService.publishMessage(sessionKey, message);
} catch (Exception e) {
log.warn("推送功能未开启或服务未找到");
}
}
/**
* 发布订阅的消息(群发)
*
* @param message 消息内容
*/
@Override
public void publishAll(String message) {
try {
remoteMessageService.publishAll(message);
} catch (Exception e) {
log.warn("推送功能未开启或服务未找到");
}
}
}

View File

@@ -1,6 +1,13 @@
package org.dromara.resource.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.resource.api.domain.RemoteSms;
import org.dromara.resource.api.domain.RemoteSmsBatch;
import org.dromara.resource.api.domain.RemoteSmsDelayBatch;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.LinkedHashMap;
import java.util.List;
@@ -10,6 +17,8 @@ import java.util.List;
*
* @author Feng
*/
@RemoteHttpService("ruoyi-resource")
@HttpExchange("/inner/remote/resource/sms")
public interface RemoteSmsService {
/**
@@ -19,7 +28,8 @@ public interface RemoteSmsService {
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, String message);
@PostExchange("/send-text")
RemoteSms sendMessage(@RequestParam String phone, @RequestParam String message);
/**
* 同步方法:发送固定消息模板多模板参数短信
@@ -28,7 +38,8 @@ public interface RemoteSmsService {
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, LinkedHashMap<String, String> messages);
@PostExchange("/send-vars")
RemoteSms sendMessage(@RequestParam String phone, @RequestBody LinkedHashMap<String, String> messages);
/**
* 同步方法:使用自定义模板发送短信
@@ -38,7 +49,9 @@ public interface RemoteSmsService {
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
@PostExchange("/send-template")
RemoteSms sendMessage(@RequestParam String phone, @RequestParam String templateId,
@RequestBody LinkedHashMap<String, String> messages);
/**
* 同步方法:群发固定模板短信
@@ -47,7 +60,8 @@ public interface RemoteSmsService {
* @param message 短信内容
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms messageTexting(List<String> phones, String message);
@PostExchange("/message-texting")
RemoteSms messageTexting(@RequestBody List<String> phones, @RequestParam String message);
/**
* 同步方法:使用自定义模板群发短信
@@ -57,7 +71,19 @@ public interface RemoteSmsService {
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @return 封装了短信发送结果的 RemoteSms 对象
*/
RemoteSms messageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
@PostExchange("/message-texting-template")
default RemoteSms messageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
return messageTextingTemplate(new RemoteSmsBatch(phones, templateId, messages));
}
/**
* 使用自定义模板群发短信.
*
* @param request 群发模板短信请求
* @return 封装了短信发送结果的 RemoteSms 对象
*/
@PostExchange("/message-texting-template")
RemoteSms messageTextingTemplate(@RequestBody RemoteSmsBatch request);
/**
* 异步方法:发送固定消息模板短信
@@ -65,7 +91,8 @@ public interface RemoteSmsService {
* @param phone 目标手机号
* @param message 短信内容
*/
void sendMessageAsync(String phone, String message);
@PostExchange("/send-async-text")
void sendMessageAsync(@RequestParam String phone, @RequestParam String message);
/**
* 异步方法:使用自定义模板发送短信
@@ -74,7 +101,9 @@ public interface RemoteSmsService {
* @param templateId 短信模板ID
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
*/
void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages);
@PostExchange("/send-async-template")
void sendMessageAsync(@RequestParam String phone, @RequestParam String templateId,
@RequestBody LinkedHashMap<String, String> messages);
/**
* 延迟发送:发送固定消息模板短信
@@ -83,7 +112,8 @@ public interface RemoteSmsService {
* @param message 短信内容
* @param delayedTime 延迟发送时间(毫秒)
*/
void delayMessage(String phone, String message, Long delayedTime);
@PostExchange("/delay-text")
void delayMessage(@RequestParam String phone, @RequestParam String message, @RequestParam Long delayedTime);
/**
* 延迟发送:使用自定义模板发送定时短信
@@ -93,7 +123,9 @@ public interface RemoteSmsService {
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间(毫秒)
*/
void delayMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
@PostExchange("/delay-template")
void delayMessage(@RequestParam String phone, @RequestParam String templateId,
@RequestBody LinkedHashMap<String, String> messages, @RequestParam Long delayedTime);
/**
* 延迟群发:群发延迟短信
@@ -102,7 +134,8 @@ public interface RemoteSmsService {
* @param message 短信内容
* @param delayedTime 延迟发送时间(毫秒)
*/
void delayMessageTexting(List<String> phones, String message, Long delayedTime);
@PostExchange("/delay-message-texting")
void delayMessageTexting(@RequestBody List<String> phones, @RequestParam String message, @RequestParam Long delayedTime);
/**
* 延迟群发:使用自定义模板发送群体延迟短信
@@ -112,34 +145,50 @@ public interface RemoteSmsService {
* @param messages 短信模板参数,使用 LinkedHashMap 以保持参数顺序
* @param delayedTime 延迟发送时间(毫秒)
*/
void delayMessageTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
@PostExchange("/delay-message-texting-template")
default void delayMessageTexting(List<String> phones, String templateId,
LinkedHashMap<String, String> messages, Long delayedTime) {
delayMessageTextingTemplate(new RemoteSmsDelayBatch(phones, templateId, messages, delayedTime));
}
/**
* 延迟群发模板短信.
*
* @param request 延迟群发模板短信请求
*/
@PostExchange("/delay-message-texting-template")
void delayMessageTextingTemplate(@RequestBody RemoteSmsDelayBatch request);
/**
* 加入黑名单
*
* @param phone 手机号
*/
void addBlacklist(String phone);
@PostExchange("/add-blacklist-one")
void addBlacklist(@RequestParam String phone);
/**
* 加入黑名单
*
* @param phones 手机号列表
*/
void addBlacklist(List<String> phones);
@PostExchange("/add-blacklist-list")
void addBlacklist(@RequestBody List<String> phones);
/**
* 移除黑名单
*
* @param phone 手机号
*/
void removeBlacklist(String phone);
@PostExchange("/remove-blacklist-one")
void removeBlacklist(@RequestParam String phone);
/**
* 移除黑名单
*
* @param phones 手机号
*/
void removeBlacklist(List<String> phones);
@PostExchange("/remove-blacklist-list")
void removeBlacklist(@RequestBody List<String> phones);
}

View File

@@ -0,0 +1,16 @@
package org.dromara.resource.api.domain;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 群发模板短信请求.
*
* @author Lion Li
*/
public record RemoteSmsBatch(
List<String> phones,
String templateId,
LinkedHashMap<String, String> messages
) {
}

View File

@@ -0,0 +1,17 @@
package org.dromara.resource.api.domain;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 延迟群发模板短信请求.
*
* @author Lion Li
*/
public record RemoteSmsDelayBatch(
List<String> phones,
String templateId,
LinkedHashMap<String, String> messages,
Long delayedTime
) {
}

View File

@@ -1,12 +1,18 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
/**
* 客户端服务
*
* @author Michelle.Chung
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/client")
public interface RemoteClientService {
/**
@@ -15,6 +21,7 @@ public interface RemoteClientService {
* @param clientId 客户端id
* @return 客户端对象
*/
RemoteClientVo queryByClientId(String clientId);
@GetExchange("/query-by-client-id")
RemoteClientVo queryByClientId(@RequestParam String clientId);
}

View File

@@ -2,6 +2,11 @@ package org.dromara.system.api;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import java.math.BigDecimal;
import java.util.List;
@@ -11,12 +16,15 @@ import java.util.List;
*
* @author Michelle.Chung
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/config")
public interface RemoteConfigService {
/**
* 获取注册开关
* @return true开启false关闭
*/
@GetExchange("/select-register-enabled")
boolean selectRegisterEnabled();
/**
@@ -25,7 +33,8 @@ public interface RemoteConfigService {
* @param configKey 参数 key
* @return 参数值
*/
String getConfigValue(String configKey);
@GetExchange("/get-config-value")
String getConfigValue(@RequestParam String configKey);
/**
* 根据参数 key 获取布尔值
@@ -73,7 +82,9 @@ public interface RemoteConfigService {
* @param configKey 参数 key
* @return Dict 对象,如果配置为空或无法解析,返回空 Dict
*/
Dict getConfigMap(String configKey);
default Dict getConfigMap(String configKey) {
return JsonUtils.parseMap(getConfigValue(configKey));
}
/**
* 根据参数 key 获取 Map 类型的配置列表
@@ -81,7 +92,9 @@ public interface RemoteConfigService {
* @param configKey 参数 key
* @return Dict 列表,如果配置为空或无法解析,返回空列表
*/
List<Dict> getConfigArrayMap(String configKey);
default List<Dict> getConfigArrayMap(String configKey) {
return JsonUtils.parseArrayMap(getConfigValue(configKey));
}
/**
* 根据参数 key 获取指定类型的配置对象
@@ -91,7 +104,9 @@ public interface RemoteConfigService {
* @param <T> 目标对象泛型
* @return 对象实例,如果配置为空或无法解析,返回 null
*/
<T> T getConfigObject(String configKey, Class<T> clazz);
default <T> T getConfigObject(String configKey, Class<T> clazz) {
return JsonUtils.parseObject(getConfigValue(configKey), clazz);
}
/**
* 根据参数 key 获取指定类型的配置列表
@@ -101,6 +116,8 @@ public interface RemoteConfigService {
* @param <T> 元素类型泛型
* @return 指定类型列表,如果配置为空或无法解析,返回空列表
*/
<T> List<T> getConfigArray(String configKey, Class<T> clazz);
default <T> List<T> getConfigArray(String configKey, Class<T> clazz) {
return JsonUtils.parseArray(getConfigValue(configKey), clazz);
}
}

View File

@@ -1,10 +1,17 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
/**
* 数据权限服务
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/data-scope")
public interface RemoteDataScopeService {
/**
@@ -13,7 +20,8 @@ public interface RemoteDataScopeService {
* @param roleId 角色ID
* @return 返回角色的自定义权限语句,如果没有找到则返回 null
*/
String getRoleCustom(Long roleId);
@GetExchange("/role-custom")
String getRoleCustom(@RequestParam Long roleId);
/**
* 获取部门和下级权限语句
@@ -21,6 +29,7 @@ public interface RemoteDataScopeService {
* @param deptId 部门ID
* @return 返回部门及其下级的权限语句,如果没有找到则返回 null
*/
String getDeptAndChild(Long deptId);
@GetExchange("/dept-and-child")
String getDeptAndChild(@RequestParam Long deptId);
}

View File

@@ -1,6 +1,12 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.vo.RemoteDeptVo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.Collection;
import java.util.List;
@@ -11,6 +17,8 @@ import java.util.Map;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/dept")
public interface RemoteDeptService {
/**
@@ -19,7 +27,8 @@ public interface RemoteDeptService {
* @param deptIds 部门ID串逗号分隔
* @return 部门名称串逗号分隔
*/
String selectDeptNameByIds(String deptIds);
@GetExchange("/select-dept-name-by-ids")
String selectDeptNameByIds(@RequestParam String deptIds);
/**
* 根据部门ID查询部门负责人
@@ -27,13 +36,15 @@ public interface RemoteDeptService {
* @param deptId 部门ID用于指定需要查询的部门
* @return 返回该部门的负责人ID
*/
Long selectDeptLeaderById(Long deptId);
@GetExchange("/select-dept-leader-by-id")
Long selectDeptLeaderById(@RequestParam Long deptId);
/**
* 查询部门
*
* @return 部门列表
*/
@GetExchange("/select-depts-by-list")
List<RemoteDeptVo> selectDeptsByList();
/**
@@ -42,6 +53,7 @@ public interface RemoteDeptService {
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(Collection<Long> deptIds);
@PostExchange("/select-dept-names-by-ids")
Map<Long, String> selectDeptNamesByIds(@RequestBody Collection<Long> deptIds);
}

View File

@@ -1,7 +1,11 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.vo.RemoteDictDataVo;
import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import java.util.List;
@@ -10,6 +14,8 @@ import java.util.List;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/dict")
public interface RemoteDictService {
/**
@@ -18,7 +24,8 @@ public interface RemoteDictService {
* @param dictType 字典类型
* @return 字典类型
*/
RemoteDictTypeVo selectDictTypeByType(String dictType);
@GetExchange("/select-dict-type-by-type")
RemoteDictTypeVo selectDictTypeByType(@RequestParam String dictType);
/**
* 根据字典类型查询字典数据
@@ -26,6 +33,7 @@ public interface RemoteDictService {
* @param dictType 字典类型
* @return 字典数据集合信息
*/
List<RemoteDictDataVo> selectDictDataByType(String dictType);
@GetExchange("/select-dict-data-by-type")
List<RemoteDictDataVo> selectDictDataByType(@RequestParam String dictType);
}

View File

@@ -1,13 +1,19 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.bo.RemoteLoginInfoBo;
import org.dromara.system.api.domain.bo.RemoteOperLogBo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
/**
* 日志服务
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/log")
public interface RemoteLogService {
/**
@@ -15,13 +21,15 @@ public interface RemoteLogService {
*
* @param sysOperLog 日志实体
*/
void saveLog(RemoteOperLogBo sysOperLog);
@PostExchange("/save-log")
void saveLog(@RequestBody RemoteOperLogBo sysOperLog);
/**
* 保存访问记录
*
* @param sysLoginInfo 访问实体
*/
void saveLoginInfo(RemoteLoginInfoBo sysLoginInfo);
@PostExchange("/save-login-info")
void saveLoginInfo(@RequestBody RemoteLoginInfoBo sysLoginInfo);
}

View File

@@ -1,5 +1,10 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import java.util.Set;
/**
@@ -7,6 +12,8 @@ import java.util.Set;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/permission")
public interface RemotePermissionService {
/**
@@ -15,7 +22,8 @@ public interface RemotePermissionService {
* @param userId 用户id
* @return 角色权限信息
*/
Set<String> getRolePermission(Long userId);
@GetExchange("/role-permission")
Set<String> getRolePermission(@RequestParam Long userId);
/**
* 获取菜单数据权限
@@ -23,6 +31,7 @@ public interface RemotePermissionService {
* @param userId 用户id
* @return 菜单权限信息
*/
Set<String> getMenuPermission(Long userId);
@GetExchange("/menu-permission")
Set<String> getMenuPermission(@RequestParam Long userId);
}

View File

@@ -1,7 +1,11 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
@@ -9,6 +13,8 @@ import java.util.Map;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/post")
public interface RemotePostService {
/**
@@ -17,6 +23,7 @@ public interface RemotePostService {
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(Collection<Long> postIds);
@PostExchange("/select-post-names-by-ids")
Map<Long, String> selectPostNamesByIds(@RequestBody Collection<Long> postIds);
}

View File

@@ -1,7 +1,11 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
@@ -9,6 +13,8 @@ import java.util.Map;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/role")
public interface RemoteRoleService {
/**
@@ -17,6 +23,7 @@ public interface RemoteRoleService {
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(Collection<Long> roleIds);
@PostExchange("/select-role-names-by-ids")
Map<Long, String> selectRoleNamesByIds(@RequestBody Collection<Long> roleIds);
}

View File

@@ -1,7 +1,13 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.bo.RemoteSocialBo;
import org.dromara.system.api.domain.vo.RemoteSocialVo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.List;
@@ -10,6 +16,8 @@ import java.util.List;
*
* @author Michelle.Chung
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/social")
public interface RemoteSocialService {
/**
@@ -18,28 +26,32 @@ public interface RemoteSocialService {
* @param authId 认证id
* @return 授权信息
*/
List<RemoteSocialVo> selectByAuthId(String authId);
@GetExchange("/select-by-auth-id")
List<RemoteSocialVo> selectByAuthId(@RequestParam String authId);
/**
* 查询列表
*
* @param bo 社会化关系业务对象
*/
List<RemoteSocialVo> queryList(RemoteSocialBo bo);
@PostExchange("/query-list")
List<RemoteSocialVo> queryList(@RequestBody RemoteSocialBo bo);
/**
* 保存社会化关系
*
* @param bo 社会化关系业务对象
*/
void insertByBo(RemoteSocialBo bo);
@PostExchange("/insert-by-bo")
void insertByBo(@RequestBody RemoteSocialBo bo);
/**
* 更新社会化关系
*
* @param bo 社会化关系业务对象
*/
void updateByBo(RemoteSocialBo bo);
@PostExchange("/update-by-bo")
void updateByBo(@RequestBody RemoteSocialBo bo);
/**
* 删除社会化关系
@@ -47,6 +59,7 @@ public interface RemoteSocialService {
* @param socialId 社会化关系ID
* @return 结果
*/
Boolean deleteWithValidById(Long socialId);
@PostExchange("/delete-with-valid-by-id")
Boolean deleteWithValidById(@RequestParam Long socialId);
}

View File

@@ -1,13 +1,19 @@
package org.dromara.system.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.bo.RemoteTaskAssigneeBo;
import org.dromara.system.api.domain.vo.RemoteTaskAssigneeVo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
/**
* 工作流设计器获取任务执行人
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/task-assignee")
public interface RemoteTaskAssigneeService {
/**
@@ -16,7 +22,8 @@ public interface RemoteTaskAssigneeService {
* @param taskQuery 查询条件
* @return 办理人
*/
RemoteTaskAssigneeVo selectRolesByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
@PostExchange("/select-roles")
RemoteTaskAssigneeVo selectRolesByTaskAssigneeList(@RequestBody RemoteTaskAssigneeBo taskQuery);
/**
* 查询岗位并返回任务指派的列表,支持分页
@@ -24,7 +31,8 @@ public interface RemoteTaskAssigneeService {
* @param taskQuery 查询条件
* @return 办理人
*/
RemoteTaskAssigneeVo selectPostsByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
@PostExchange("/select-posts")
RemoteTaskAssigneeVo selectPostsByTaskAssigneeList(@RequestBody RemoteTaskAssigneeBo taskQuery);
/**
* 查询部门并返回任务指派的列表,支持分页
@@ -32,7 +40,8 @@ public interface RemoteTaskAssigneeService {
* @param taskQuery 查询条件
* @return 办理人
*/
RemoteTaskAssigneeVo selectDeptsByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
@PostExchange("/select-depts")
RemoteTaskAssigneeVo selectDeptsByTaskAssigneeList(@RequestBody RemoteTaskAssigneeBo taskQuery);
/**
* 查询用户并返回任务指派的列表,支持分页
@@ -40,6 +49,7 @@ public interface RemoteTaskAssigneeService {
* @param taskQuery 查询条件
* @return 办理人
*/
RemoteTaskAssigneeVo selectUsersByTaskAssigneeList(RemoteTaskAssigneeBo taskQuery);
@PostExchange("/select-users")
RemoteTaskAssigneeVo selectUsersByTaskAssigneeList(@RequestBody RemoteTaskAssigneeBo taskQuery);
}

View File

@@ -2,10 +2,16 @@ package org.dromara.system.api;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.system.api.domain.bo.RemoteUserBo;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.system.api.model.LoginUser;
import org.dromara.system.api.model.XcxLoginUser;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.Collection;
import java.util.List;
@@ -16,6 +22,8 @@ import java.util.Map;
*
* @author Lion Li
*/
@RemoteHttpService("ruoyi-system")
@HttpExchange("/remote/user")
public interface RemoteUserService {
/**
@@ -24,7 +32,8 @@ public interface RemoteUserService {
* @param username 用户名
* @return 结果
*/
LoginUser getUserInfo(String username) throws UserException;
@GetExchange("/get-by-username")
LoginUser getUserInfo(@RequestParam String username) throws UserException;
/**
* 通过用户id查询用户信息
@@ -32,7 +41,8 @@ public interface RemoteUserService {
* @param userId 用户id
* @return 结果
*/
LoginUser getUserInfo(Long userId) throws UserException;
@GetExchange("/get-by-id")
LoginUser getUserInfo(@RequestParam Long userId) throws UserException;
/**
* 通过手机号查询用户信息
@@ -40,7 +50,8 @@ public interface RemoteUserService {
* @param phonenumber 手机号
* @return 结果
*/
LoginUser getUserInfoByPhonenumber(String phonenumber) throws UserException;
@GetExchange("/get-by-phonenumber")
LoginUser getUserInfoByPhonenumber(@RequestParam String phonenumber) throws UserException;
/**
* 通过邮箱查询用户信息
@@ -48,7 +59,8 @@ public interface RemoteUserService {
* @param email 邮箱
* @return 结果
*/
LoginUser getUserInfoByEmail(String email) throws UserException;
@GetExchange("/get-by-email")
LoginUser getUserInfoByEmail(@RequestParam String email) throws UserException;
/**
* 通过openid查询用户信息
@@ -56,7 +68,8 @@ public interface RemoteUserService {
* @param openid openid
* @return 结果
*/
XcxLoginUser getUserInfoByOpenid(String openid) throws UserException;
@GetExchange("/get-by-openid")
XcxLoginUser getUserInfoByOpenid(@RequestParam String openid) throws UserException;
/**
* 注册用户信息
@@ -64,7 +77,8 @@ public interface RemoteUserService {
* @param remoteUserBo 用户信息
* @return 结果
*/
Boolean registerUserInfo(RemoteUserBo remoteUserBo) throws UserException, ServiceException;
@PostExchange("/register-user-info")
Boolean registerUserInfo(@RequestBody RemoteUserBo remoteUserBo) throws UserException, ServiceException;
/**
* 通过userId查询用户账户
@@ -72,7 +86,8 @@ public interface RemoteUserService {
* @param userId 用户id
* @return 结果
*/
String selectUserNameById(Long userId);
@GetExchange("/select-username-by-id")
String selectUserNameById(@RequestParam Long userId);
/**
* 通过用户ID查询用户昵称
@@ -80,7 +95,8 @@ public interface RemoteUserService {
* @param userId 用户ID
* @return 用户昵称
*/
String selectNicknameById(Long userId);
@GetExchange("/select-nickname-by-id")
String selectNicknameById(@RequestParam Long userId);
/**
* 通过用户ID查询用户昵称
@@ -88,7 +104,8 @@ public interface RemoteUserService {
* @param userIds 用户ID 多个用逗号隔开
* @return 用户昵称
*/
String selectNicknameByIds(String userIds);
@GetExchange("/select-nickname-by-ids")
String selectNicknameByIds(@RequestParam String userIds);
/**
* 通过用户ID查询用户手机号
@@ -96,7 +113,8 @@ public interface RemoteUserService {
* @param userId 用户id
* @return 用户手机号
*/
String selectPhonenumberById(Long userId);
@GetExchange("/select-phonenumber-by-id")
String selectPhonenumberById(@RequestParam Long userId);
/**
* 通过用户ID查询用户邮箱
@@ -104,7 +122,8 @@ public interface RemoteUserService {
* @param userId 用户id
* @return 用户邮箱
*/
String selectEmailById(Long userId);
@GetExchange("/select-email-by-id")
String selectEmailById(@RequestParam Long userId);
/**
* 更新用户信息
@@ -112,7 +131,8 @@ public interface RemoteUserService {
* @param userId 用户ID
* @param ip IP地址
*/
void recordLoginInfo(Long userId, String ip);
@PostExchange("/record-login-info")
void recordLoginInfo(@RequestParam Long userId, @RequestParam String ip);
/**
* 通过用户ID查询用户列表
@@ -120,7 +140,8 @@ public interface RemoteUserService {
* @param userIds 用户ids
* @return 用户列表
*/
List<RemoteUserVo> selectListByIds(Collection<Long> userIds);
@PostExchange("/select-list-by-ids")
List<RemoteUserVo> selectListByIds(@RequestBody Collection<Long> userIds);
/**
* 通过角色ID查询用户ID
@@ -128,7 +149,8 @@ public interface RemoteUserService {
* @param roleIds 角色ids
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(Collection<Long> roleIds);
@PostExchange("/select-user-ids-by-role-ids")
List<Long> selectUserIdsByRoleIds(@RequestBody Collection<Long> roleIds);
/**
* 通过角色ID查询用户
@@ -136,7 +158,8 @@ public interface RemoteUserService {
* @param roleIds 角色ids
* @return 用户
*/
List<RemoteUserVo> selectUsersByRoleIds(Collection<Long> roleIds);
@PostExchange("/select-users-by-role-ids")
List<RemoteUserVo> selectUsersByRoleIds(@RequestBody Collection<Long> roleIds);
/**
* 通过部门ID查询用户
@@ -144,7 +167,8 @@ public interface RemoteUserService {
* @param deptIds 部门ids
* @return 用户
*/
List<RemoteUserVo> selectUsersByDeptIds(Collection<Long> deptIds);
@PostExchange("/select-users-by-dept-ids")
List<RemoteUserVo> selectUsersByDeptIds(@RequestBody Collection<Long> deptIds);
/**
* 通过岗位ID查询用户
@@ -152,7 +176,8 @@ public interface RemoteUserService {
* @param postIds 岗位ids
* @return 用户
*/
List<RemoteUserVo> selectUsersByPostIds(Collection<Long> postIds);
@PostExchange("/select-users-by-post-ids")
List<RemoteUserVo> selectUsersByPostIds(@RequestBody Collection<Long> postIds);
/**
* 根据用户 ID 列表查询用户昵称映射关系
@@ -160,6 +185,7 @@ public interface RemoteUserService {
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户昵称
*/
Map<Long, String> selectUserNicksByIds(Collection<Long> userIds);
@PostExchange("/select-user-nicks-by-ids")
Map<Long, String> selectUserNicksByIds(@RequestBody Collection<Long> userIds);
}

View File

@@ -1,8 +1,14 @@
package org.dromara.workflow.api;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.workflow.api.domain.RemoteCompleteTask;
import org.dromara.workflow.api.domain.RemoteStartProcess;
import org.dromara.workflow.api.domain.RemoteStartProcessReturn;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import java.util.List;
import java.util.Map;
@@ -13,6 +19,8 @@ import java.util.Map;
* @Author ZETA
* @Date 2024/6/3
*/
@RemoteHttpService(value = "ruoyi-workflow", fallback = RemoteWorkflowServiceFallback.class)
@HttpExchange("/remote/workflow")
public interface RemoteWorkflowService {
/**
@@ -21,7 +29,8 @@ public interface RemoteWorkflowService {
* @param businessIds 业务id
* @return 结果
*/
boolean deleteInstance(List<String> businessIds);
@PostExchange("/delete-instance")
boolean deleteInstance(@RequestBody List<String> businessIds);
/**
* 获取当前流程状态
@@ -29,7 +38,8 @@ public interface RemoteWorkflowService {
* @param taskId 任务id
* @return 状态
*/
String getBusinessStatusByTaskId(Long taskId);
@GetExchange("/business-status-by-task-id")
String getBusinessStatusByTaskId(@RequestParam Long taskId);
/**
* 获取当前流程状态
@@ -37,7 +47,8 @@ public interface RemoteWorkflowService {
* @param businessId 业务id
* @return 状态
*/
String getBusinessStatus(String businessId);
@GetExchange("/business-status")
String getBusinessStatus(@RequestParam String businessId);
/**
* 设置流程变量
@@ -45,14 +56,16 @@ public interface RemoteWorkflowService {
* @param instanceId 流程实例id
* @param variable 流程变量
*/
void setVariable(Long instanceId, Map<String, Object> variable);
@PostExchange("/set-variable")
void setVariable(@RequestParam Long instanceId, @RequestBody Map<String, Object> variable);
/**
* 获取流程变量
*
* @param instanceId 流程实例id
*/
Map<String, Object> instanceVariable(Long instanceId);
@GetExchange("/instance-variable")
Map<String, Object> instanceVariable(@RequestParam Long instanceId);
/**
* 按照业务id查询流程实例id
@@ -60,7 +73,8 @@ public interface RemoteWorkflowService {
* @param businessId 业务id
* @return 结果
*/
Long getInstanceIdByBusinessId(String businessId);
@GetExchange("/instance-id-by-business-id")
Long getInstanceIdByBusinessId(@RequestParam String businessId);
/**
* 启动流程
@@ -68,7 +82,8 @@ public interface RemoteWorkflowService {
* @param startProcess 参数
* @return 结果
*/
RemoteStartProcessReturn startWorkFlow(RemoteStartProcess startProcess);
@PostExchange("/start-workflow")
RemoteStartProcessReturn startWorkFlow(@RequestBody RemoteStartProcess startProcess);
/**
* 办理任务
@@ -76,7 +91,8 @@ public interface RemoteWorkflowService {
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(RemoteCompleteTask completeTask);
@PostExchange("/complete-task")
boolean completeTask(@RequestBody RemoteCompleteTask completeTask);
/**
@@ -86,7 +102,8 @@ public interface RemoteWorkflowService {
* @param message 办理意见
* @return 结果
*/
boolean completeTask(Long taskId, String message);
@PostExchange("/complete-task-simple")
boolean completeTask(@RequestParam Long taskId, @RequestParam String message);
/**
* 启动流程并办理第一个任务
@@ -94,6 +111,7 @@ public interface RemoteWorkflowService {
* @param startProcess 参数
* @return 结果
*/
boolean startCompleteTask(RemoteStartProcess startProcess);
@PostExchange("/start-complete-task")
boolean startCompleteTask(@RequestBody RemoteStartProcess startProcess);
}

View File

@@ -9,12 +9,12 @@ import java.util.List;
import java.util.Map;
/**
* 工作流服务(降级处理)
* 工作流服务熔断降级.
*
* @author Lion Li
*/
@Slf4j
public class RemoteWorkflowServiceMock implements RemoteWorkflowService {
public class RemoteWorkflowServiceFallback implements RemoteWorkflowService {
@Override
public boolean deleteInstance(List<String> businessIds) {

View File

@@ -53,6 +53,11 @@
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-http</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
@@ -63,11 +68,6 @@
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-seata</artifactId>

View File

@@ -1,6 +1,5 @@
package org.dromara.auth;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@@ -10,7 +9,6 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
*
* @author ruoyi
*/
@EnableDubbo
@SpringBootApplication
public class RuoYiAuthApplication {
public static void main(String[] args) {

View File

@@ -8,7 +8,6 @@ import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.RegisterBody;
import org.dromara.auth.form.SocialLoginBody;
@@ -53,13 +52,9 @@ public class TokenController {
private final SysLoginService sysLoginService;
private final ScheduledExecutorService scheduledExecutorService;
@DubboReference
private final RemoteConfigService remoteConfigService;
@DubboReference
private final RemoteClientService remoteClientService;
@DubboReference
private final RemoteSocialService remoteSocialService;
@DubboReference(stub = "true")
private final RemoteMessageService remoteMessageService;
/**

View File

@@ -8,7 +8,6 @@ import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.MessageUtils;
@@ -35,10 +34,8 @@ import java.time.Duration;
@Slf4j
public class UserActionListener implements SaTokenListener {
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
private RemoteMessageService remoteMessageService;
private final RemoteUserService remoteUserService;
private final RemoteMessageService remoteMessageService;
/**
* 每次登录时触发

View File

@@ -10,7 +10,6 @@ import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.form.RegisterBody;
import org.dromara.auth.properties.CaptchaProperties;
import org.dromara.auth.properties.UserPasswordProperties;
@@ -50,10 +49,8 @@ import java.util.function.Supplier;
@Slf4j
public class SysLoginService {
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
private RemoteSocialService remoteSocialService;
private final RemoteUserService remoteUserService;
private final RemoteSocialService remoteSocialService;
@Autowired
private UserPasswordProperties userPasswordProperties;

View File

@@ -4,7 +4,6 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.EmailLoginBody;
import org.dromara.auth.service.IAuthStrategy;
@@ -36,8 +35,7 @@ public class EmailAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {

View File

@@ -5,7 +5,6 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.PasswordLoginBody;
import org.dromara.auth.properties.CaptchaProperties;
@@ -41,8 +40,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {

View File

@@ -4,7 +4,6 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.SmsLoginBody;
import org.dromara.auth.service.IAuthStrategy;
@@ -36,8 +35,7 @@ public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {

View File

@@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.SocialLoginBody;
import org.dromara.auth.service.IAuthStrategy;
@@ -37,10 +36,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
private final SocialProperties socialProperties;
@DubboReference
private RemoteSocialService remoteSocialService;
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteSocialService remoteSocialService;
private final RemoteUserService remoteUserService;
/**
* 登录-第三方授权登录

View File

@@ -11,7 +11,6 @@ import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.auth.domain.vo.LoginVo;
import org.dromara.auth.form.XcxLoginBody;
import org.dromara.auth.service.IAuthStrategy;
@@ -37,8 +36,7 @@ public class XcxAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {

View File

@@ -20,9 +20,9 @@
<module>ruoyi-common-security</module>
<module>ruoyi-common-satoken</module>
<module>ruoyi-common-web</module>
<module>ruoyi-common-http</module>
<module>ruoyi-common-mybatis</module>
<module>ruoyi-common-job</module>
<module>ruoyi-common-dubbo</module>
<module>ruoyi-common-seata</module>
<module>ruoyi-common-loadbalancer</module>
<module>ruoyi-common-oss</module>

View File

@@ -18,8 +18,6 @@
<spring-cloud-alibaba.version>2025.1.0.0</spring-cloud-alibaba.version>
<seata.version>2.5.0</seata.version>
<nacos.client.version>3.1.1</nacos.client.version>
<dubbo.version>3.3.6</dubbo.version>
<dubbo-extensions.version>3.3.1</dubbo-extensions.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -60,30 +58,6 @@
<version>${seata.version}</version>
</dependency>
<!-- Apache Dubbo 配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo.extensions</groupId>
<artifactId>dubbo-metadata-report-redis</artifactId>
<version>${dubbo-extensions.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -82,6 +82,13 @@
<version>${revision}</version>
</dependency>
<!-- 内部HTTP远程调用 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-http</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库服务 -->
<dependency>
<groupId>org.dromara</groupId>
@@ -96,13 +103,6 @@
<version>${revision}</version>
</dependency>
<!-- RPC服务 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
<version>${revision}</version>
</dependency>
<!-- 分布式事务 -->
<dependency>
<groupId>org.dromara</groupId>

View File

@@ -0,0 +1,38 @@
package org.dromara.common.core.annotation;
import org.springframework.core.annotation.AliasFor;
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 Service 所属服务.
*
* @author Lion Li
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RemoteHttpService {
/**
* 服务名.
*/
@AliasFor("serviceId")
String value() default "";
/**
* 服务名.
*/
@AliasFor("value")
String serviceId() default "";
/**
* 远程调用失败时的 fallback 实现.
*/
Class<?> fallback() default void.class;
}

View File

@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-dubbo</artifactId>
<description>
ruoyi-common-dubbo RPC服务
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo.extensions</groupId>
<artifactId>dubbo-metadata-report-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dubbo3</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,538 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.store.redis;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigItem;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.*;
import org.apache.dubbo.metadata.MappingChangedEvent;
import org.apache.dubbo.metadata.MappingListener;
import org.apache.dubbo.metadata.MetadataInfo;
import org.apache.dubbo.metadata.ServiceNameMapping;
import org.apache.dubbo.metadata.report.identifier.*;
import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
import org.apache.dubbo.rpc.RpcException;
import redis.clients.jedis.*;
import redis.clients.jedis.params.SetParams;
import redis.clients.jedis.util.JedisClusterCRC16;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.*;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RESPONSE;
import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
/**
* RedisMetadataReport
*/
public class RedisMetadataReport extends AbstractMetadataReport {
private static final String REDIS_DATABASE_KEY = "database";
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RedisMetadataReport.class);
// protected , for test
protected JedisPool pool;
private Set<HostAndPort> jedisClusterNodes;
private int timeout;
private String username;
private String password;
private final String root;
private final ConcurrentHashMap<String, MappingDataListener> mappingDataListenerMap = new ConcurrentHashMap<>();
private SetParams jedisParams = SetParams.setParams();
public RedisMetadataReport(URL url) {
super(url);
timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
username = url.getUsername();
password = url.getPassword();
this.root = url.getGroup(DEFAULT_ROOT);
if (url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
// ttl default is twice the cycle-report time
jedisParams.px(ONE_DAY_IN_MILLISECONDS * 2);
}
if (url.getParameter(CLUSTER_KEY, false)) {
jedisClusterNodes = new HashSet<>();
List<URL> urls = url.getBackupUrls();
for (URL tmpUrl : urls) {
jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
}
} else {
int database = url.getParameter(REDIS_DATABASE_KEY, 0);
pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, username, password, database);
}
}
@Override
protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
this.storeMetadata(providerMetadataIdentifier, serviceDefinitions, true);
}
@Override
protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) {
this.storeMetadata(consumerMetadataIdentifier, value, true);
}
@Override
protected void doSaveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier, URL url) {
this.storeMetadata(serviceMetadataIdentifier, URL.encode(url.toFullString()), false);
}
@Override
protected void doRemoveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier) {
this.deleteMetadata(serviceMetadataIdentifier);
}
@Override
protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
String content = getMetadata(metadataIdentifier);
if (StringUtils.isEmpty(content)) {
return Collections.emptyList();
}
return new ArrayList<>(Arrays.asList(URL.decode(content)));
}
@Override
protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
this.storeMetadata(subscriberMetadataIdentifier, urlListStr, false);
}
@Override
protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
return this.getMetadata(subscriberMetadataIdentifier);
}
@Override
public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
return this.getMetadata(metadataIdentifier);
}
private void storeMetadata(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
if (pool != null) {
storeMetadataStandalone(metadataIdentifier, v, ephemeral);
} else {
storeMetadataInCluster(metadataIdentifier, v, ephemeral);
}
}
private void storeMetadataInCluster(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
if (ephemeral) {
jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v, jedisParams);
} else {
jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v);
}
} catch (Throwable e) {
String msg =
"Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void storeMetadataStandalone(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
try (Jedis jedis = pool.getResource()) {
if (ephemeral) {
jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v, jedisParams);
} else {
jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v);
}
} catch (Throwable e) {
String msg = "Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
deleteMetadataStandalone(metadataIdentifier);
} else {
deleteMetadataInCluster(metadataIdentifier);
}
}
private void deleteMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.del(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
jedis.del(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
return getMetadataStandalone(metadataIdentifier);
} else {
return getMetadataInCluster(metadataIdentifier);
}
}
private String getMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.get(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* Store class and application names using Redis hashes
* key: default 'dubbo:mapping'
* field: class (serviceInterface)
* value: application_names
* @param serviceInterface field(class)
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @param newConfigContent new application_names
* @param ticket previous application_names
* @return
*/
@Override
public boolean registerServiceAppMapping(
String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
try {
if (null != ticket && !(ticket instanceof String)) {
throw new IllegalArgumentException("redis publishConfigCas requires stat type ticket");
}
String pathKey = buildMappingKey(defaultMappingGroup);
return storeMapping(pathKey, serviceInterface, newConfigContent, (String) ticket);
} catch (Exception e) {
logger.warn(TRANSPORT_FAILED_RESPONSE, "", "", "redis publishConfigCas failed.", e);
return false;
}
}
private boolean storeMapping(String key, String field, String value, String ticket) {
if (pool != null) {
return storeMappingStandalone(key, field, value, ticket);
} else {
return storeMappingInCluster(key, field, value, ticket);
}
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingInCluster(String key, String field, String value, String ticket) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
Jedis jedis = new Jedis(jedisCluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)));
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedisCluster.publish(buildPubSubKey(), field);
return true;
}
} else {
jedis.unwatch();
}
jedis.close();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingStandalone(String key, String field, String value, String ticket) {
try (Jedis jedis = pool.getResource()) {
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedis.publish(buildPubSubKey(), field);
return true;
}
}
jedis.unwatch();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* build mapping key
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @return
*/
private String buildMappingKey(String defaultMappingGroup) {
return this.root + GROUP_CHAR_SEPARATOR + defaultMappingGroup;
}
/**
* build pub/sub key
*/
private String buildPubSubKey() {
return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
}
/**
* get content and use content to complete cas
* @param serviceKey class
* @param group {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
*/
@Override
public ConfigItem getConfigItem(String serviceKey, String group) {
String key = buildMappingKey(group);
String content = getMappingData(key, serviceKey);
return new ConfigItem(content, content);
}
/**
* get current application_names
*/
private String getMappingData(String key, String field) {
if (pool != null) {
return getMappingDataStandalone(key, field);
} else {
return getMappingDataInCluster(key, field);
}
}
private String getMappingDataInCluster(String key, String field) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMappingDataStandalone(String key, String field) {
try (Jedis jedis = pool.getResource()) {
return jedis.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* remove listener. If have no listener,thread will dead
*/
@Override
public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
MappingDataListener mappingDataListener = mappingDataListenerMap.get(buildPubSubKey());
if (null != mappingDataListener) {
NotifySub notifySub = mappingDataListener.getNotifySub();
notifySub.removeListener(serviceKey, listener);
if (notifySub.isEmpty()) {
mappingDataListener.shutdown();
}
}
}
/**
* Start a thread and subscribe to {@link this#buildPubSubKey()}.
* Notify {@link MappingListener} if there is a change in the 'application_names' message.
*/
@Override
public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
MappingDataListener mappingDataListener =
ConcurrentHashMapUtils.computeIfAbsent(mappingDataListenerMap, buildPubSubKey(), k -> {
MappingDataListener dataListener = new MappingDataListener(buildPubSubKey());
dataListener.start();
return dataListener;
});
mappingDataListener.getNotifySub().addListener(serviceKey, listener);
return this.getServiceAppMapping(serviceKey, url);
}
@Override
public Set<String> getServiceAppMapping(String serviceKey, URL url) {
String key = buildMappingKey(DEFAULT_MAPPING_GROUP);
return getAppNames(getMappingData(key, serviceKey));
}
@Override
public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
String content = this.getMetadata(identifier);
return JsonUtils.toJavaObject(content, MetadataInfo.class);
}
@Override
public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.storeMetadata(identifier, metadataInfo.getContent(), false);
}
@Override
public void unPublishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.deleteMetadata(identifier);
}
// for test
public MappingDataListener getMappingDataListener() {
return mappingDataListenerMap.get(buildPubSubKey());
}
/**
* Listen for changes in the 'application_names' message and notify the listener.
*/
class NotifySub extends JedisPubSub {
private final Map<String, Set<MappingListener>> listeners = new ConcurrentHashMap<>();
public void addListener(String key, MappingListener listener) {
Set<MappingListener> listenerSet = listeners.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
listenerSet.add(listener);
}
public void removeListener(String serviceKey, MappingListener listener) {
Set<MappingListener> 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);
}
}
}
}

View File

@@ -1,88 +0,0 @@
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);
}
}
}

View File

@@ -1,36 +0,0 @@
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();
}
}

View File

@@ -1,84 +0,0 @@
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 日志过滤器
* <p>
* 该过滤器通过实现 Dubbo 的 Filter 接口,在服务调用前后记录日志信息
* 可根据配置开关和日志级别输出不同详细程度的日志信息
* <p>
* 激活条件:
* - 在 Provider 和 Consumer 端都生效
* - 执行顺序设置为最大值,确保在所有其他过滤器之后执行
* <p>
* 使用 SpringUtils 获取配置信息,根据配置决定是否记录日志及日志详细程度
* <p>
* 使用 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;
}
}

View File

@@ -1,27 +0,0 @@
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<Void> handleDubboException(RpcException e) {
log.error("RPC异常: {}", e.getMessage());
return R.fail("RPC异常请联系管理员确认");
}
}

View File

@@ -1,28 +0,0 @@
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;
}

View File

@@ -1 +0,0 @@
dubboRequestFilter=org.dromara.common.dubbo.filter.DubboRequestFilter

View File

@@ -1,41 +0,0 @@
# 内置配置 不允许修改 如需修改请在 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

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-http</artifactId>
<description>
ruoyi-common-http 内部 HTTP 远程调用
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
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 "";
}

View File

@@ -0,0 +1,233 @@
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;
}
}
}

View File

@@ -0,0 +1,151 @@
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<R<Void>> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod());
return buildResponse(HttpStatus.BAD_METHOD, e.getMessage());
}
@ExceptionHandler(ServiceException.class)
public ResponseEntity<R<Void>> handleServiceException(ServiceException e) {
log.error(e.getMessage());
int code = resolveBusinessCode(e.getCode(), HttpStatus.ERROR);
return buildResponse(code, e.getMessage());
}
@ExceptionHandler(ServletException.class)
public ResponseEntity<R<Void>> handleServletException(ServletException e, HttpServletRequest request) {
log.error("请求地址'{}',发生未知异常.", request.getRequestURI(), e);
return buildResponse(HttpStatus.ERROR, e.getMessage());
}
@ExceptionHandler(BaseException.class)
public ResponseEntity<R<Void>> handleBaseException(BaseException e) {
log.error(e.getMessage());
return buildResponse(HttpStatus.ERROR, e.getMessage());
}
@ExceptionHandler(MissingPathVariableException.class)
public ResponseEntity<R<Void>> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", request.getRequestURI());
return buildResponse(HttpStatus.BAD_REQUEST, String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<R<Void>> 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<R<Void>> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
log.error("请求地址'{}'不存在.", request.getRequestURI());
return buildResponse(HttpStatus.NOT_FOUND, e.getMessage());
}
@ExceptionHandler(BindException.class)
public ResponseEntity<R<Void>> 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<R<Void>> 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<R<Void>> 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<R<Void>> 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<R<Void>> handleHttpMessageNotReadableException(HttpMessageNotReadableException e,
HttpServletRequest request) {
log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
return buildResponse(HttpStatus.BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<R<Void>> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("请求地址'{}',发生未知异常.", request.getRequestURI(), e);
return buildResponse(HttpStatus.ERROR, e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<R<Void>> handleException(Exception e, HttpServletRequest request) {
log.error("请求地址'{}',发生系统异常.", request.getRequestURI(), e);
return buildResponse(HttpStatus.ERROR, e.getMessage());
}
private ResponseEntity<R<Void>> 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;
}
}

View File

@@ -0,0 +1,162 @@
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;
}
}

View File

@@ -1,9 +1,9 @@
package org.dromara.common.dubbo.enumd;
package org.dromara.common.http.log.enums;
import lombok.AllArgsConstructor;
/**
* 请求日志泛型
* 请求日志级别.
*
* @author Lion Li
*/
@@ -11,18 +11,18 @@ import lombok.AllArgsConstructor;
public enum RequestLogEnum {
/**
* info 基础信息
* 基础信息.
*/
INFO,
/**
* param 参数信息
* 参数信息.
*/
PARAM,
/**
* full 全部
* 全量信息.
*/
FULL;
FULL
}

View File

@@ -0,0 +1,114 @@
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 <T> @Nullable T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return invoke(requestValues, () -> super.exchangeForBody(requestValues, bodyType));
}
@Override
public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues requestValues) {
return invoke(requestValues, () -> super.exchangeForBodilessEntity(requestValues));
}
@Override
public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return invoke(requestValues, () -> super.exchangeForEntity(requestValues, bodyType));
}
private <T> T invoke(HttpRequestValues requestValues, ThrowingSupplier<T> 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<String, String> uriVariables = requestValues.getUriVariables();
String path = uriTemplate;
if (uriVariables != null) {
for (Map.Entry<String, String> entry : uriVariables.entrySet()) {
path = path.replace("{" + entry.getKey() + "}", String.valueOf(entry.getValue()));
}
}
return path;
}
@FunctionalInterface
private interface ThrowingSupplier<T> {
T get() throws Throwable;
}
}

View File

@@ -0,0 +1,132 @@
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<Object, Object> sanitized = new LinkedHashMap<>(map.size());
map.forEach((key, item) -> sanitized.put(key, sanitizeValue(item)));
return sanitized;
}
if (ObjectUtils.isArray(value)) {
return ObjectUtils.nullSafeToString(value);
}
return value;
}
}

View File

@@ -0,0 +1,26 @@
package org.dromara.common.http.properties;
import lombok.Data;
import org.dromara.common.http.log.enums.RequestLogEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 内部 HTTP 调用配置.
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "remote.http")
public class RemoteHttpProperties {
/**
* 是否开启请求日志.
*/
private Boolean requestLog = Boolean.FALSE;
/**
* 日志级别.
*/
private RequestLogEnum logLevel = RequestLogEnum.INFO;
}

View File

@@ -0,0 +1,217 @@
package org.dromara.common.http.registrar;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.dromara.common.http.annotation.RemoteServiceController;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.env.Environment;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.registry.AbstractHttpServiceRegistrar;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 按接口声明自动注册远程 HTTP Service.
*
* 这个注册器负责把“接口声明”转成 Spring HTTP Service Client 代理,
* 同时保留一个和 Dubbo 类似的优化:
* 当前服务自己就提供了该接口实现时,不再注册远程代理,直接走本地 Bean。
*
* @author Lion Li
*/
public class RemoteHttpServiceRegistrar extends AbstractHttpServiceRegistrar
implements EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {
private Environment environment;
private ResourceLoader resourceLoader;
private ClassLoader beanClassLoader;
private static final String SCAN_PACKAGES_PROPERTY = "remote.http.scan-packages";
private static final AntPathMatcher PACKAGE_MATCHER = new AntPathMatcher(".");
@Override
public void setEnvironment(Environment environment) {
super.setEnvironment(environment);
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
super.setResourceLoader(resourceLoader);
this.resourceLoader = resourceLoader;
}
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
super.setBeanClassLoader(beanClassLoader);
this.beanClassLoader = beanClassLoader;
}
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata importingClassMetadata) {
Set<String> scanPackagePatterns = new LinkedHashSet<>(resolveConfiguredScanPackages());
if (scanPackagePatterns.isEmpty()) {
return;
}
Set<String> scanBasePackages = resolveScanBasePackages(scanPackagePatterns);
if (scanBasePackages.isEmpty()) {
return;
}
// 先找出当前服务自己已经提供的远程接口,后面这些接口不再注册 HTTP client。
Set<String> localServiceTypes = resolveLocalServiceTypes(scanBasePackages, scanPackagePatterns);
MultiValueMap<String, String> groupedServices = resolveRemoteHttpServices(scanBasePackages, scanPackagePatterns, localServiceTypes);
groupedServices.forEach((serviceId, classNames) ->
registry.forGroup(serviceId).registerTypeNames(classNames.toArray(String[]::new)));
}
private MultiValueMap<String, String> resolveRemoteHttpServices(Set<String> basePackages, Set<String> scanPackagePatterns,
Set<String> localServiceTypes) {
MultiValueMap<String, String> groupedServices = new LinkedMultiValueMap<>();
for (AnnotatedBeanDefinition beanDefinition : scanCandidateComponents(basePackages, RemoteHttpService.class)) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
if (!metadata.isInterface() || !hasHttpExchange(metadata)) {
continue;
}
String serviceTypeName = metadata.getClassName();
if (!matchesConfiguredPackage(serviceTypeName, scanPackagePatterns)) {
continue;
}
// 同服务场景直接依赖本地 provider不再生成 HTTP 代理。
if (localServiceTypes.contains(serviceTypeName)) {
continue;
}
groupedServices.add(resolveServiceId(metadata), serviceTypeName);
}
return groupedServices;
}
private Set<String> resolveLocalServiceTypes(Set<String> basePackages, Set<String> scanPackagePatterns) {
MultiValueMap<String, String> localServiceTypes = new LinkedMultiValueMap<>();
for (AnnotatedBeanDefinition beanDefinition : scanCandidateComponents(basePackages, RemoteServiceController.class)) {
String className = beanDefinition.getMetadata().getClassName();
Class<?> beanClass = ClassUtils.resolveClassName(className, this.beanClassLoader);
for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(beanClass, this.beanClassLoader)) {
if (interfaceType.isAnnotationPresent(RemoteHttpService.class)
&& matchesConfiguredPackage(interfaceType.getName(), scanPackagePatterns)) {
localServiceTypes.add(interfaceType.getName(), className);
}
}
}
// 同一个远程接口只允许一个本地 provider否则本地短路目标不明确。
localServiceTypes.forEach((serviceTypeName, providerClassNames) -> {
if (providerClassNames.size() > 1) {
throw new IllegalStateException("Multiple local RemoteServiceController beans found for "
+ serviceTypeName + ": " + providerClassNames);
}
});
return new LinkedHashSet<>(localServiceTypes.keySet());
}
private List<AnnotatedBeanDefinition> scanCandidateComponents(Set<String> basePackages,
Class<? extends Annotation> annotationType) {
ClassPathScanningCandidateComponentProvider scanner = createScanner(annotationType);
List<AnnotatedBeanDefinition> beanDefinitions = new ArrayList<>();
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : scanner.findCandidateComponents(basePackage)) {
if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
beanDefinitions.add(annotatedBeanDefinition);
}
}
}
return beanDefinitions;
}
private ClassPathScanningCandidateComponentProvider createScanner(Class<? extends Annotation> annotationType) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isIndependent();
}
};
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
if (this.environment != null) {
scanner.setEnvironment(this.environment);
}
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
return scanner;
}
private String resolveServiceId(AnnotationMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(RemoteHttpService.class.getName());
String serviceId = attributes != null ? String.valueOf(attributes.get("serviceId")) : StringUtils.EMPTY;
if (StringUtils.isBlank(serviceId)) {
throw new IllegalStateException("RemoteHttpService serviceId must not be blank: " + metadata.getClassName());
}
return serviceId;
}
private boolean hasHttpExchange(AnnotationMetadata metadata) {
return metadata.isAnnotated(HttpExchange.class.getName()) || metadata.hasAnnotatedMethods(HttpExchange.class.getName());
}
private List<String> resolveConfiguredScanPackages() {
if (this.environment == null) {
return Collections.emptyList();
}
return Binder.get(this.environment).bind(SCAN_PACKAGES_PROPERTY, org.springframework.boot.context.properties.bind.Bindable.listOf(String.class))
.orElseGet(Collections::emptyList)
.stream()
.filter(StringUtils::isNotBlank)
.distinct()
.toList();
}
private Set<String> resolveScanBasePackages(Set<String> scanPackagePatterns) {
Set<String> basePackages = new LinkedHashSet<>();
for (String packagePattern : scanPackagePatterns) {
String basePackage = resolveScanBasePackage(packagePattern);
if (StringUtils.isNotBlank(basePackage)) {
basePackages.add(basePackage);
}
}
return basePackages;
}
private String resolveScanBasePackage(String packagePattern) {
int wildcardIndex = packagePattern.indexOf('*');
if (wildcardIndex < 0) {
return packagePattern;
}
String packagePrefix = packagePattern.substring(0, wildcardIndex);
packagePrefix = StringUtils.substringBeforeLast(packagePrefix, ".");
return StringUtils.defaultString(packagePrefix);
}
private boolean matchesConfiguredPackage(String className, Set<String> scanPackagePatterns) {
if (scanPackagePatterns.isEmpty()) {
return true;
}
String packageName = ClassUtils.getPackageName(className);
for (String packagePattern : scanPackagePatterns) {
if (PACKAGE_MATCHER.match(packagePattern, packageName)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,165 @@
package org.dromara.common.http.support;
import org.aopalliance.intercept.MethodInterceptor;
import org.dromara.common.core.annotation.RemoteHttpService;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
/**
* 远程 HTTP 代理 fallback 包装器.
*
* <p>仅包装注册器生成的远程 HTTP 代理 Bean。代理调用报错时
* 按接口上声明的 fallback 实现兜底,不处理本地 provider Bean。
*
* <p>这里故意保持和之前 mock/stub 类似的简单约束:
* fallback 必须实现接口本身,且方法签名与接口保持一致。</p>
*
* @author Lion Li
*/
public class RemoteHttpFallbackProxyPostProcessor
implements BeanPostProcessor, BeanFactoryAware, BeanClassLoaderAware {
private static final String HTTP_SERVICE_GROUP_NAME_ATTRIBUTE = "httpServiceGroupName";
private static final String FALLBACK_WRAPPED_ATTRIBUTE = "remoteHttpFallbackWrapped";
private ConfigurableListableBeanFactory beanFactory;
private ClassLoader beanClassLoader;
@Override
public void setBeanFactory(org.springframework.beans.factory.BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof FallbackDecoratedProxy) {
return bean;
}
Class<?> serviceInterface = resolveRemoteServiceInterface(beanName, bean);
if (serviceInterface == null) {
return bean;
}
RemoteHttpService remoteHttpService = serviceInterface.getAnnotation(RemoteHttpService.class);
if (remoteHttpService == null || remoteHttpService.fallback() == void.class) {
return bean;
}
Class<?> fallbackClass = remoteHttpService.fallback();
if (!serviceInterface.isAssignableFrom(fallbackClass)) {
throw new IllegalStateException("Fallback class must implement remote service interface: "
+ fallbackClass.getName() + " -> " + serviceInterface.getName());
}
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.setInterfaces(ClassUtils.getAllInterfacesForClass(bean.getClass(), this.beanClassLoader));
proxyFactory.addInterface(FallbackDecoratedProxy.class);
proxyFactory.addAdvice((MethodInterceptor) invocation -> {
Method method = invocation.getMethod();
if (method.getDeclaringClass() == Object.class) {
return invocation.proceed();
}
try {
return invocation.proceed();
} catch (Throwable ex) {
return invokeFallback(serviceInterface, fallbackClass, method, invocation.getArguments(), ex);
}
});
markWrapped(beanName);
return proxyFactory.getProxy(this.beanClassLoader);
}
private Class<?> resolveRemoteServiceInterface(String beanName, Object bean) {
if (this.beanFactory == null || !this.beanFactory.containsBeanDefinition(beanName)) {
return null;
}
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(beanName);
if (beanDefinition.getAttribute(HTTP_SERVICE_GROUP_NAME_ATTRIBUTE) == null) {
return null;
}
if (Boolean.TRUE.equals(beanDefinition.getAttribute(FALLBACK_WRAPPED_ATTRIBUTE))) {
return null;
}
Class<?> beanClass = resolveBeanClass(beanDefinition);
if (beanClass != null && beanClass.isInterface() && beanClass.isAnnotationPresent(RemoteHttpService.class)) {
return beanClass;
}
for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(bean.getClass(), this.beanClassLoader)) {
if (interfaceType.isAnnotationPresent(RemoteHttpService.class)) {
return interfaceType;
}
}
return null;
}
private Class<?> resolveBeanClass(BeanDefinition beanDefinition) {
String beanClassName = beanDefinition.getBeanClassName();
return beanClassName == null ? null : ClassUtils.resolveClassName(beanClassName, this.beanClassLoader);
}
private Object invokeFallback(Class<?> serviceInterface, Class<?> fallbackClass, Method method, Object[] args, Throwable ex)
throws Throwable {
Object fallbackInstance = instantiateFallback(fallbackClass);
Method fallbackMethod = ReflectionUtils.findMethod(fallbackClass, method.getName(), method.getParameterTypes());
if (fallbackMethod == null) {
throw unwrap(ex);
}
ReflectionUtils.makeAccessible(fallbackMethod);
return invokeMethod(fallbackInstance, fallbackMethod, args);
}
private Object instantiateFallback(Class<?> fallbackClass) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized for remote fallback: " + fallbackClass.getName());
}
return this.beanFactory.getBean(fallbackClass);
}
private void markWrapped(String beanName) {
if (this.beanFactory == null || !this.beanFactory.containsBeanDefinition(beanName)) {
return;
}
this.beanFactory.getBeanDefinition(beanName).setAttribute(FALLBACK_WRAPPED_ATTRIBUTE, true);
}
private Object invokeMethod(Object target, Method method, Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException ex) {
throw unwrap(ex.getTargetException());
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Could not invoke remote fallback method: " + method, ex);
} catch (UndeclaredThrowableException ex) {
throw unwrap(ex);
} catch (RuntimeException ex) {
throw unwrap(ex);
}
}
private Throwable unwrap(Throwable throwable) {
Throwable current = throwable;
while (current instanceof InvocationTargetException invocationTargetException && invocationTargetException.getTargetException() != null) {
current = invocationTargetException.getTargetException();
}
while (current instanceof UndeclaredThrowableException undeclaredThrowableException && undeclaredThrowableException.getUndeclaredThrowable() != null) {
current = undeclaredThrowableException.getUndeclaredThrowable();
}
return current;
}
private interface FallbackDecoratedProxy {
}
}

View File

@@ -0,0 +1 @@
org.dromara.common.http.config.RemoteHttpAutoConfiguration

View File

@@ -20,12 +20,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -1,25 +0,0 @@
package org.dromara.common.loadbalance.config;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* dubbo自定义负载均衡配置注入
*
* @author Lion Li
*/
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.setProperty("dubbo.consumer.loadbalance", "customDubboLoadBalancer");
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -1,30 +0,0 @@
package org.dromara.common.loadbalance.core;
import cn.hutool.core.net.NetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* 自定义 Dubbo 负载均衡算法
*
* @author Lion Li
*/
@Slf4j
public class CustomDubboLoadBalancer extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
for (Invoker<T> invoker : invokers) {
if (NetUtil.localIpv4s().contains(invoker.getUrl().getHost())) {
return invoker;
}
}
return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
}
}

View File

@@ -1 +0,0 @@
customDubboLoadBalancer=org.dromara.common.loadbalance.core.CustomDubboLoadBalancer

View File

@@ -1,2 +0,0 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
org.dromara.common.loadbalance.config.CustomEnvironmentPostProcessor

View File

@@ -27,12 +27,6 @@
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -5,8 +5,8 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -27,12 +27,11 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class LogEventListener {
@DubboReference
private RemoteLogService remoteLogService;
@DubboReference
private RemoteClientService remoteClientService;
private final RemoteLogService remoteLogService;
private final RemoteClientService remoteClientService;
/**
* 保存系统日志记录

View File

@@ -21,12 +21,6 @@
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>

View File

@@ -17,6 +17,7 @@ import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.dromara.common.mybatis.service.SysDataScopeService;
import org.dromara.system.api.RemoteDataScopeService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -117,8 +118,8 @@ public class MybatisPlusConfiguration {
* 数据权限处理实现
*/
@Bean("sdss")
public SysDataScopeService sysDataScopeService() {
return new SysDataScopeService();
public SysDataScopeService sysDataScopeService(RemoteDataScopeService remoteDataScopeService) {
return new SysDataScopeService(remoteDataScopeService);
}
/**

View File

@@ -1,28 +0,0 @@
package org.dromara.common.mybatis.filter;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
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 java.util.Map;
/**
* dubbo 数据权限参数传递
*
* @author Lion Li
*/
@Slf4j
@Activate(group = {CommonConstants.CONSUMER})
public class DubboDataPermissionFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
RpcServiceContext context = RpcContext.getServiceContext();
Map<String, Object> dataPermissionContext = DataPermissionHelper.getContext();
context.setObjectAttachment(DataPermissionHelper.DATA_PERMISSION_KEY, dataPermissionContext);
return invoker.invoke(invocation);
}
}

View File

@@ -1,6 +1,6 @@
package org.dromara.common.mybatis.service;
import org.apache.dubbo.config.annotation.DubboReference;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.system.api.RemoteDataScopeService;
import org.springframework.cache.annotation.Cacheable;
@@ -15,10 +15,10 @@ import org.springframework.stereotype.Service;
* @author Lion Li
*/
@Service("sdss")
@RequiredArgsConstructor
public class SysDataScopeService {
@DubboReference
private RemoteDataScopeService remoteDataScopeService;
private final RemoteDataScopeService remoteDataScopeService;
/**
* 获取角色自定义权限语句

View File

@@ -1 +0,0 @@
dubboDataPermissionFilter=org.dromara.common.mybatis.filter.DubboDataPermissionFilter

View File

@@ -12,7 +12,7 @@ import org.springframework.context.annotation.Bean;
public class RateLimiterConfig {
@Bean
public RateLimiterAspect rateLimiterAspect() {
public RateLimiterAspect plusRateLimiterAspect() {
return new RateLimiterAspect();
}

View File

@@ -22,12 +22,6 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo.extensions</groupId>
<artifactId>dubbo-filter-seata</artifactId>
<version>3.3.1</version>
</dependency>
<!-- SpringBoot Seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
@@ -37,10 +31,6 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo.extensions</groupId>
<artifactId>dubbo-filter-seata</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>

View File

@@ -28,11 +28,5 @@
<artifactId>ruoyi-api-system</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -2,14 +2,13 @@ package org.dromara.common.core.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.github.benmanes.caffeine.cache.Cache;
import org.apache.dubbo.config.annotation.DubboReference;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.api.RemoteDictService;
import org.dromara.system.api.domain.vo.RemoteDictDataVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@@ -21,13 +20,12 @@ import java.util.stream.Collectors;
* @author Lion Li
*/
@Service
@RequiredArgsConstructor
public class DictServiceImpl implements DictService {
@Autowired
private Cache<Object, Object> ceffeine;
private final Cache<Object, Object> ceffeine;
@DubboReference
private RemoteDictService remoteDictService;
private final RemoteDictService remoteDictService;
/**
* 根据字典类型和字典值获取字典标签

View File

@@ -1,6 +1,6 @@
package org.dromara.common.core.service.impl;
import org.apache.dubbo.config.annotation.DubboReference;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.service.PermissionService;
import org.dromara.system.api.RemotePermissionService;
import org.springframework.stereotype.Service;
@@ -13,10 +13,10 @@ import java.util.Set;
* @author Lion Li
*/
@Service
@RequiredArgsConstructor
public class PermissionServiceImpl implements PermissionService {
@DubboReference
private RemotePermissionService remotePermissionService;
private final RemotePermissionService remotePermissionService;
@Override
public Set<String> getRolePermission(Long userId) {

View File

@@ -27,11 +27,6 @@
<artifactId>ruoyi-common-service-impl</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-api-resource</artifactId>

View File

@@ -8,6 +8,7 @@ import org.dromara.common.translation.core.handler.TranslationBeanSerializerModi
import org.dromara.common.translation.core.handler.TranslationHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ser.SerializerFactory;
@@ -42,13 +43,4 @@ public class TranslationConfig {
TranslationHandler.TRANSLATION_MAPPER.putAll(map);
}
@Bean
public JsonMapperBuilderCustomizer translationInitCustomizer() {
return builder -> {
SerializerFactory serializerFactory = builder.serializerFactory();
serializerFactory = serializerFactory.withSerializerModifier(new TranslationBeanSerializerModifier());
builder.serializerFactory(serializerFactory);
};
}
}

View File

@@ -0,0 +1,29 @@
package org.dromara.common.translation.config;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ser.SerializerFactory;
/**
* 翻译模块额外修改jackson配置
*
* @author Lion Li
*/
@Slf4j
@AutoConfiguration(before = JacksonAutoConfiguration.class)
public class TranslationJacksonConfig {
@Bean
public JsonMapperBuilderCustomizer translationInitCustomizer() {
return builder -> {
SerializerFactory serializerFactory = builder.serializerFactory();
serializerFactory = serializerFactory.withSerializerModifier(new TranslationBeanSerializerModifier());
builder.serializerFactory(serializerFactory);
};
}
}

View File

@@ -5,7 +5,6 @@ import org.dromara.common.translation.constant.TransConstant;
import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.system.api.RemoteDeptService;
import lombok.AllArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
/**
* 部门翻译实现
@@ -16,8 +15,7 @@ import org.apache.dubbo.config.annotation.DubboReference;
@TranslationType(type = TransConstant.DEPT_ID_TO_NAME)
public class DeptNameTranslationImpl implements TranslationInterface<String> {
@DubboReference
private RemoteDeptService remoteDeptService;
private final RemoteDeptService remoteDeptService;
@Override
public String translation(Object key, String other) {

View File

@@ -2,7 +2,6 @@ package org.dromara.common.translation.core.impl;
import cn.hutool.core.convert.Convert;
import lombok.AllArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.CacheUtils;
@@ -23,8 +22,7 @@ import java.util.List;
@TranslationType(type = TransConstant.USER_ID_TO_NICKNAME)
public class NicknameTranslationImpl implements TranslationInterface<String> {
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public String translation(Object key, String other) {

View File

@@ -5,7 +5,6 @@ import org.dromara.common.translation.constant.TransConstant;
import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.resource.api.RemoteFileService;
import lombok.AllArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
/**
* OSS翻译实现
@@ -16,8 +15,7 @@ import org.apache.dubbo.config.annotation.DubboReference;
@TranslationType(type = TransConstant.OSS_ID_TO_URL)
public class OssUrlTranslationImpl implements TranslationInterface<String> {
@DubboReference(mock = "true")
private RemoteFileService remoteFileService;
private final RemoteFileService remoteFileService;
@Override
public String translation(Object key, String other) {

View File

@@ -9,7 +9,6 @@ import org.dromara.common.translation.constant.TransConstant;
import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.system.api.RemoteUserService;
import lombok.AllArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
/**
* 用户名翻译实现
@@ -20,8 +19,7 @@ import org.apache.dubbo.config.annotation.DubboReference;
@TranslationType(type = TransConstant.USER_ID_TO_NAME)
public class UserNameTranslationImpl implements TranslationInterface<String> {
@DubboReference
private RemoteUserService remoteUserService;
private final RemoteUserService remoteUserService;
@Override
public String translation(Object key, String other) {

View File

@@ -1,4 +1,5 @@
org.dromara.common.translation.config.TranslationConfig
org.dromara.common.translation.config.TranslationJacksonConfig
org.dromara.common.translation.core.impl.DeptNameTranslationImpl
org.dromara.common.translation.core.impl.DictTypeTranslationImpl
org.dromara.common.translation.core.impl.OssUrlTranslationImpl

View File

@@ -52,24 +52,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-metrics</artifactId>
<version>2.7.6</version>
<exclusions>
<exclusion>
<groupId>net.dreamlu</groupId>
<artifactId>mica-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-core</artifactId>
<version>2.7.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -67,7 +67,7 @@ public class GlobalExceptionHandler {
*/
@ResponseStatus(org.springframework.http.HttpStatus.UNAUTHORIZED)
@ExceptionHandler(SseException.class)
public String handleNotLoginException(SseException e, HttpServletRequest request) {
public String handleSseException(SseException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.debug("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return JsonUtils.toJsonString(R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"));
@@ -140,7 +140,7 @@ public class GlobalExceptionHandler {
* sse 连接超时异常 不需要处理
*/
@ExceptionHandler(AsyncRequestTimeoutException.class)
public void handleRuntimeException(AsyncRequestTimeoutException e) {
public void handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {
}
/**

View File

@@ -1,4 +1,3 @@
org.dromara.common.web.config.FilterConfig
org.dromara.common.web.config.I18nConfig
org.dromara.common.web.config.UndertowConfig
org.dromara.common.web.config.ResourcesConfig

View File

@@ -47,11 +47,6 @@
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>

View File

@@ -62,7 +62,7 @@ public class AuthFilter implements WebMvcConfigurer {
}
})))
.addPathPatterns("/**")
.excludePathPatterns("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse");
.excludePathPatterns("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse" , "/error");
}
/**

View File

@@ -2,13 +2,19 @@ package org.dromara.gateway.handler;
import cn.dev33.satoken.exception.NotLoginException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.domain.R;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 网关统一异常处理
*
@@ -19,23 +25,34 @@ import org.springframework.web.server.ResponseStatusException;
public class GatewayExceptionHandler {
@ExceptionHandler(NotLoginException.class)
public R<Void> handleNotLogin(HttpServletRequest request, NotLoginException ex) {
public void handleNotLogin(HttpServletRequest request, HttpServletResponse response, NotLoginException ex) throws IOException {
log.warn("[网关认证失败]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage());
return R.fail(HttpStatus.UNAUTHORIZED, ex.getMessage());
writeJson(response, HttpStatus.UNAUTHORIZED, ex.getMessage());
}
@ExceptionHandler(Throwable.class)
public R<Void> handle(HttpServletRequest request, Throwable ex) {
public void handle(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
int code;
String msg;
if ("NotFoundException".equals(ex.getClass().getSimpleName())) {
code = HttpStatus.NOT_FOUND;
msg = "服务未找到";
} else if (ex instanceof ResponseStatusException responseStatusException) {
code = responseStatusException.getStatusCode().value();
msg = responseStatusException.getMessage();
} else {
code = HttpStatus.ERROR;
msg = "内部服务器错误";
}
log.error("[网关异常处理]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage(), ex);
return R.fail(msg);
writeJson(response, code, msg);
}
private void writeJson(HttpServletResponse response, int code, String msg) throws IOException {
response.setStatus(code);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(JsonUtils.toJsonString(R.fail(code, msg)));
}
}

View File

@@ -51,15 +51,14 @@
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
<artifactId>ruoyi-common-http</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>

View File

@@ -1,6 +1,5 @@
package org.dromara.gen;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@@ -10,7 +9,6 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
*
* @author ruoyi
*/
@EnableDubbo
@SpringBootApplication
public class RuoYiGenApplication {
public static void main(String[] args) {

View File

@@ -35,7 +35,7 @@
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
<artifactId>ruoyi-common-http</artifactId>
</dependency>
<dependency>

View File

@@ -1,6 +1,5 @@
package org.dromara.job;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@@ -10,7 +9,6 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
*
* @author Lion Li
*/
@EnableDubbo
@SpringBootApplication
public class RuoYiJobApplication {

View File

@@ -29,12 +29,12 @@
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId>
<artifactId>ruoyi-common-seata</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-seata</artifactId>
<artifactId>ruoyi-common-http</artifactId>
</dependency>
<dependency>

View File

@@ -1,6 +1,5 @@
package org.dromara.resource;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@@ -11,7 +10,6 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
*
* @author Lion Li
*/
@EnableDubbo
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RuoYiResourceApplication {
public static void main(String[] args) {

View File

@@ -3,7 +3,7 @@ package org.dromara.resource.dubbo;
import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.http.annotation.RemoteServiceController;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -17,7 +17,6 @@ import org.dromara.resource.domain.SysOssExt;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssVo;
import org.dromara.resource.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -28,9 +27,8 @@ import java.util.List;
* @author Lion Li
*/
@Slf4j
@Service
@RequiredArgsConstructor
@DubboService
@RemoteServiceController
public class RemoteFileServiceImpl implements RemoteFileService {
private final ISysOssService sysOssService;

View File

@@ -2,11 +2,10 @@ package org.dromara.resource.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.http.annotation.RemoteServiceController;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.resource.api.RemoteMailService;
import org.springframework.stereotype.Service;
/**
* 邮件服务
@@ -15,8 +14,7 @@ import org.springframework.stereotype.Service;
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
@RemoteServiceController
public class RemoteMailServiceImpl implements RemoteMailService {
/**

View File

@@ -2,11 +2,10 @@ package org.dromara.resource.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.http.annotation.RemoteServiceController;
import org.dromara.common.sse.dto.SseMessageDTO;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.resource.api.RemoteMessageService;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -17,8 +16,7 @@ import java.util.List;
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
@RemoteServiceController
public class RemoteMessageServiceImpl implements RemoteMessageService {
/**

Some files were not shown because too many files have changed in this diff Show More