Merge remote-tracking branch 'yudao-gtee/master' into origin-master
commit
ff1d1ea393
|
|
@ -33,7 +33,6 @@
|
|||
<redisson.version>3.18.0</redisson.version>
|
||||
<dm8.jdbc.version>8.1.2.141</dm8.jdbc.version>
|
||||
<!-- RPC 相关 -->
|
||||
<dubbo.version>2.7.18</dubbo.version>
|
||||
<!-- Config 配置中心相关 -->
|
||||
<apollo.version>1.9.2</apollo.version>
|
||||
<!-- Job 定时任务相关 -->
|
||||
|
|
@ -301,21 +300,6 @@
|
|||
</dependency>
|
||||
|
||||
<!-- RPC 远程调用相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-common</artifactId> <!-- 兜底,保证在不引入 spring-cloud-starter-dubbo 时,注解等不报错 -->
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-cluster</artifactId> <!-- 兜底,保证在不引入 spring-cloud-starter-dubbo 时,注解等不报错 -->
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-rpc-api</artifactId> <!-- 兜底,保证在不引入 spring-cloud-starter-dubbo 时,注解等不报错 -->
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-rpc</artifactId>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ public class BannerApplicationRunner implements ApplicationRunner {
|
|||
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
||||
// 商城
|
||||
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
||||
// 支付
|
||||
System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -227,8 +227,7 @@ public class OperateLogAspect {
|
|||
private static void fillMethodFields(OperateLog operateLogObj,
|
||||
ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
LocalDateTime startTime, Object result, Throwable exception) {
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
LocalDateTime startTime, Object result, Throwable exception) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
operateLogObj.setJavaMethod(methodSignature.toString());
|
||||
if (operateLog == null || operateLog.logArgs()) {
|
||||
operateLogObj.setJavaMethodArgs(obtainMethodArgs(joinPoint));
|
||||
|
|
@ -258,6 +257,11 @@ public class OperateLogAspect {
|
|||
if (operateLog != null) {
|
||||
return operateLog.enable();
|
||||
}
|
||||
// Cloud 专属逻辑:如果是 RPC 请求,则必须 @OperateLog 注解,才会记录操作日志
|
||||
String className = joinPoint.getSignature().getDeclaringType().getName();
|
||||
if (WebFrameworkUtils.isRpcRequest(className)) {
|
||||
return false;
|
||||
}
|
||||
// 没有 @ApiOperation 注解的情况下,只记录 POST、PUT、DELETE 的情况
|
||||
return obtainFirstLogRequestMethod(obtainRequestMethod(joinPoint)) != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -68,6 +70,8 @@ public class PayClientFactoryImpl implements PayClientFactory {
|
|||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
// 其它支付
|
||||
case MOCK: return (AbstractPayClient<Config>) new MockPayClient(channelId, (MockPayClientConfig) config);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 模拟支付的 PayClient 实现类
|
||||
*
|
||||
* 模拟支付返回结果都是成功,方便大家日常流畅
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
|
||||
|
||||
private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
|
||||
|
||||
public MockPayClient(Long channelId, MockPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.MOCK.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
return PayOrderRespDTO.successOf("MOCK-P-" + reqDTO.getOutTradeNo(), "", LocalDateTime.now(),
|
||||
reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doGetOrder(String outTradeNo) {
|
||||
return PayOrderRespDTO.successOf("MOCK-P-" + outTradeNo, "", LocalDateTime.now(),
|
||||
outTradeNo, MOCK_RESP_SUCCESS_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
return PayRefundRespDTO.successOf("MOCK-R-" + reqDTO.getOutRefundNo(), LocalDateTime.now(),
|
||||
reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
|
||||
return PayRefundRespDTO.successOf("MOCK-R-" + outRefundNo, LocalDateTime.now(),
|
||||
outRefundNo, MOCK_RESP_SUCCESS_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
|
||||
throw new UnsupportedOperationException("模拟支付无退款回调");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
|
||||
throw new UnsupportedOperationException("模拟支付无支付回调");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Validator;
|
||||
|
||||
/**
|
||||
* 模拟支付的 PayClientConfig 实现类
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Data
|
||||
public class MockPayClientConfig implements PayClientConfig {
|
||||
|
||||
/**
|
||||
* 配置名称
|
||||
*
|
||||
* 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public void validate(Validator validator) {
|
||||
// 模拟支付配置无需校验
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.pay.core.enums.channel;
|
|||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
|
@ -26,7 +27,9 @@ public enum PayChannelEnum {
|
|||
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
|
||||
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
|
||||
|
||||
MOCK("mock", "模拟支付", MockPayClientConfig.class);
|
||||
|
||||
/**
|
||||
* 编码
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.redis;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 多租户拓展的 RedisKeyDefine 实现类
|
||||
*
|
||||
* 由于 Redis 不同于 MySQL 有 column 字段,无法通过类似 WHERE tenant_id = ? 的方式过滤
|
||||
* 所以需要通过在 Redis Key 上增加后缀的方式,进行租户之间的隔离。具体的步骤是:
|
||||
* 1. 假设 Redis Key 是 user:%d,示例是 user:1;对应到多租户的 Redis Key 是 user:%d:%d,
|
||||
* 2. 在 Redis DAO 中,需要使用 {@link #formatKey(Object...)} 方法,进行 Redis Key 的格式化
|
||||
*
|
||||
* 注意,大多数情况下,并不用使用 TenantRedisKeyDefine 实现。主要的使用场景,还是 Redis Key 可能存在冲突的情况。
|
||||
* 例如说,租户 1 和 2 都有一个手机号作为 Key,则他们会存在冲突的问题
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TenantRedisKeyDefine extends RedisKeyDefine {
|
||||
|
||||
/**
|
||||
* 多租户的 KEY 模板
|
||||
*/
|
||||
private static final String KEY_TEMPLATE_SUFFIX = ":%d";
|
||||
|
||||
public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
|
||||
super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeout);
|
||||
}
|
||||
|
||||
public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
|
||||
super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeoutType);
|
||||
}
|
||||
|
||||
private static String buildKeyTemplate(String keyTemplate) {
|
||||
return keyTemplate + KEY_TEMPLATE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatKey(Object... args) {
|
||||
args = ArrayUtil.append(args, TenantContextHolder.getRequiredTenantId());
|
||||
return super.formatKey(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TenantRedisKeyDefineTest {
|
||||
|
||||
@Test
|
||||
public void testFormatKey() {
|
||||
Long tenantId = 30L;
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
// 准备参数
|
||||
TenantRedisKeyDefine define = new TenantRedisKeyDefine("", "user:%d:%d", RedisKeyDefine.KeyTypeEnum.HASH,
|
||||
Object.class, RedisKeyDefine.TimeoutTypeEnum.FIXED);
|
||||
Long userId = 10L;
|
||||
Integer userType = 1;
|
||||
|
||||
// 调用
|
||||
String key = define.formatKey(userId, userType);
|
||||
// 断言
|
||||
assertEquals("user:10:1:30", key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.captcha.config;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
|
||||
import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
|
||||
import com.xingyuv.captcha.properties.AjCaptchaProperties;
|
||||
import com.xingyuv.captcha.service.CaptchaCacheService;
|
||||
|
|
@ -15,12 +13,6 @@ import javax.annotation.Resource;
|
|||
@AutoConfiguration
|
||||
public class YudaoCaptchaConfiguration {
|
||||
|
||||
static {
|
||||
// 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
|
||||
// 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
|
||||
ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName());
|
||||
}
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.captcha.core.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import com.xingyuv.captcha.model.vo.PointVO;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
|
||||
|
||||
/**
|
||||
* 验证码 Redis Key 枚举类
|
||||
*
|
||||
|
|
@ -14,12 +7,22 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
|
|||
*/
|
||||
public interface CaptchaRedisKeyConstants {
|
||||
|
||||
RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流",
|
||||
"AJ.CAPTCHA.REQ.LIMIT-%s-%s",
|
||||
STRING, Integer.class, Duration.ofSeconds(60)); // 例如说:验证失败 5 次,get 接口锁定
|
||||
/**
|
||||
* 验证码的请求限流
|
||||
*
|
||||
* KEY 格式:AJ.CAPTCHA.REQ.LIMIT-%s-%s
|
||||
* VALUE 数据类型:String // 例如说:验证失败 5 次,get 接口锁定
|
||||
* 过期时间:60 秒
|
||||
*/
|
||||
String AJ_CAPTCHA_REQ_LIMIT = "AJ.CAPTCHA.REQ.LIMIT-%s-%s";
|
||||
|
||||
RedisKeyDefine AJ_CAPTCHA_RUNNING = new RedisKeyDefine("验证码的坐标",
|
||||
"RUNNING:CAPTCHA:%s", // AbstractCaptchaService.REDIS_CAPTCHA_KEY
|
||||
STRING, PointVO.class, Duration.ofSeconds(120)); // {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
|
||||
/**
|
||||
* 验证码的坐标
|
||||
*
|
||||
* KEY 格式:RUNNING:CAPTCHA:%s // AbstractCaptchaService.REDIS_CAPTCHA_KEY
|
||||
* VALUE 数据类型:String // PointVO.class {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
|
||||
* 过期时间:120 秒
|
||||
*/
|
||||
String AJ_CAPTCHA_RUNNING = "RUNNING:CAPTCHA:%s";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,15 +56,6 @@
|
|||
<artifactId>feign-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-cluster</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 注册中心相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
|
|
|
|||
|
|
@ -13,16 +13,14 @@ import static cn.iocoder.yudao.framework.env.core.util.EnvUtils.HOST_NAME_VALUE;
|
|||
|
||||
/**
|
||||
* 多环境的 {@link EnvEnvironmentPostProcessor} 实现类
|
||||
* 将 yudao.env.tag 设置到 dubbo、nacos 等组件对应的 tag 配置项,当且仅当它们不存在时
|
||||
* 将 yudao.env.tag 设置到 nacos 等组件对应的 tag 配置项,当且仅当它们不存在时
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class EnvEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private static final Set<String> TARGET_TAG_KEYS = SetUtils.asSet(
|
||||
"spring.cloud.nacos.discovery.metadata.tag", // Nacos 注册中心
|
||||
"dubbo.provider.tag", // Dubbo 服务提供者的 tag
|
||||
"dubbo.consumer.tag" // Dubbo 服务消费者的 tag
|
||||
"spring.cloud.nacos.discovery.metadata.tag" // Nacos 注册中心
|
||||
// MQ TODO
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
|||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
|
||||
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
|
|
@ -24,24 +25,10 @@ public class YudaoEnvRpcAutoConfiguration {
|
|||
|
||||
// ========== Feign 相关 ==========
|
||||
|
||||
// TODO @芋艿:由于 loadBalancerClientFactoryBeanPostProcessor 拦截不到 LoadBalancerClientFactory,所以采用 loadBalancerClientFactory 实现
|
||||
// @Bean
|
||||
// public BeanPostProcessor loadBalancerClientFactoryBeanPostProcessor(LoadBalancerClientsProperties properties) {
|
||||
// return new BeanPostProcessor() {
|
||||
// @Override
|
||||
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
// if (!(bean instanceof LoadBalancerClientFactory)) {
|
||||
// return bean;
|
||||
// }
|
||||
// return bean;
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* 创建 {@link EnvLoadBalancerClientFactory} Bean
|
||||
*
|
||||
* 参考 {@link org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration#loadBalancerClientFactory(LoadBalancerClientsProperties)} 方法
|
||||
* 参考 {@link LoadBalancerAutoConfiguration#loadBalancerClientFactory(LoadBalancerClientsProperties)} 方法
|
||||
*/
|
||||
@Bean
|
||||
public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,
|
||||
|
|
@ -56,6 +43,4 @@ public class YudaoEnvRpcAutoConfiguration {
|
|||
return new EnvRequestInterceptor();
|
||||
}
|
||||
|
||||
// ========== Dubbo 相关 ==========
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.env.core.dubbo;
|
||||
|
||||
import cn.iocoder.yudao.framework.env.core.context.EnvContextHolder;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Consumer 方,在调用 Provider 时,将 {@link EnvContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
|
||||
*
|
||||
* 完整逻辑说明,见 {@link DubboProviderRouterTagFilter}
|
||||
*
|
||||
* 注意,这里需要设置到 order = 1 的原因,是需要保证排在 ConsumerContextClusterInterceptor 之后
|
||||
*/
|
||||
@Activate(group = CommonConstants.CONSUMER, order = 1)
|
||||
public class DubboConsumerRouterTagClusterInterceptor implements ClusterInterceptor {
|
||||
|
||||
@Override
|
||||
public void before(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
|
||||
// 设置 Dubbo Tag 到 Dubbo 隐式传参
|
||||
String tag = EnvContextHolder.getTag();
|
||||
if (StringUtils.hasText(tag)) {
|
||||
invocation.setAttachment(CommonConstants.TAG_KEY, tag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
|
||||
// 清空 Dubbo Tag 的隐式传参
|
||||
invocation.setAttachment(CommonConstants.TAG_KEY, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.env.core.dubbo;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.env.core.context.EnvContextHolder;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.*;
|
||||
import org.apache.dubbo.rpc.cluster.router.tag.TagRouter;
|
||||
|
||||
/**
|
||||
* 基于 Dubbo 标签路由规则(http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html),实现如下功能:
|
||||
* 1. 本地开发调试时,在带有 Dubbo Tag 的情况下,优先调用指定 Tag 的服务提供者。这样,我们可以将本地启动的服务提供者打上相应的 Tag,即可优先调用本地;
|
||||
* 并且,前端在调用开发环境上的 Dubbo 服务时,因为不带有 Dubbo Tag,所以不会调用到后端开发本地启动的 Dubbo 服务提供者;
|
||||
* 2. TODO 优化点:蓝绿发布、灰度发布
|
||||
*
|
||||
* 实现逻辑为:
|
||||
* 1. 对于 Consumer 方,在调用 Provider 时,{@link DubboConsumerRouterTagClusterInterceptor} 会将 {@link EnvContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
|
||||
* 同时,Dubbo 自带 {@link TagRouter},会根据该参数,会选择符合该 Tag 的 Provider。
|
||||
* 2. 对于 Provider 方,在通过 Dubbo 隐式传参获得到 Tag 时,会设置到 {@link EnvContextHolder} 中。
|
||||
* 这样,在 Provider 作为 Consumer 角色时,调用其它 Provider 时,可以继续实现标签路由的功能。
|
||||
*/
|
||||
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
|
||||
public class DubboProviderRouterTagFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
// 情况一,没有 tag 时,直接调用即可
|
||||
String tag = invocation.getAttachment(CommonConstants.TAG_KEY);
|
||||
if (StrUtil.isEmpty(tag)) {
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
|
||||
// 情况二,有 tag 时,从 Dubbo 隐式传参获得 Dubbo Tag
|
||||
EnvContextHolder.setTag(tag);
|
||||
// 继续调用
|
||||
try {
|
||||
return invoker.invoke(invocation);
|
||||
} finally {
|
||||
EnvContextHolder.removeTag();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
package cn.iocoder.yudao.framework.env.core.util;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.env.config.EnvProperties;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
|
@ -44,8 +43,14 @@ public class EnvUtils {
|
|||
requestTemplate.header(HEADER_TAG, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 hostname 主机名
|
||||
*
|
||||
* @return 主机名
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String getHostName() {
|
||||
return StrUtil.blankToDefault(NetUtil.getLocalHostName(), IdUtil.fastSimpleUUID());
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
dubboProviderRouterTagFilter=cn.iocoder.yudao.framework.env.core.dubbo.DubboProviderRouterTagFilter
|
||||
|
|
@ -1 +0,0 @@
|
|||
dubboConsumerRouterTagClusterInterceptor=cn.iocoder.yudao.framework.env.core.dubbo.DubboConsumerRouterTagClusterInterceptor
|
||||
|
|
@ -103,8 +103,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
|
|||
update(update, new QueryWrapper<>());
|
||||
}
|
||||
|
||||
default void updateBatch(Collection<T> entities) {
|
||||
Db.updateBatchById(entities);
|
||||
}
|
||||
|
||||
default void updateBatch(Collection<T> entities, int size) {
|
||||
Db.updateBatchById(entities, size);
|
||||
}
|
||||
|
||||
default void saveOrUpdateBatch(Collection<T> collection) {
|
||||
Db.saveOrUpdateBatch(collection);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link AES} 实现
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现
|
||||
* 可通过 jasypt.encryptor.password 配置项,设置密钥
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package cn.iocoder.yudao.framework.idempotent.core.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
|
||||
|
||||
/**
|
||||
* 幂等 Redis DAO
|
||||
*
|
||||
|
|
@ -16,9 +13,14 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
|
|||
@AllArgsConstructor
|
||||
public class IdempotentRedisDAO {
|
||||
|
||||
private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
|
||||
"idempotent:%s", // 参数为 uuid
|
||||
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
|
||||
/**
|
||||
* 幂等操作
|
||||
*
|
||||
* KEY 格式:idempotent:%s // 参数为 uuid
|
||||
* VALUE 格式:String
|
||||
* 过期时间:不固定
|
||||
*/
|
||||
private static final String IDEMPOTENT = "idempotent:%s";
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ public class IdempotentRedisDAO {
|
|||
}
|
||||
|
||||
private static String formatKey(String key) {
|
||||
return String.format(IDEMPOTENT.getKeyTemplate(), key);
|
||||
return String.format(IDEMPOTENT, key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,13 @@
|
|||
package cn.iocoder.yudao.framework.lock4j.config;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
|
||||
import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
|
||||
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration(before = LockAutoConfiguration.class)
|
||||
public class YudaoLock4jConfiguration {
|
||||
|
||||
static {
|
||||
// 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
|
||||
// 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
|
||||
ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultLockFailureStrategy lockFailureStrategy() {
|
||||
return new DefaultLockFailureStrategy();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.lock4j.core;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import org.redisson.api.RLock;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
|
||||
|
||||
/**
|
||||
* Lock4j Redis Key 枚举类
|
||||
*
|
||||
|
|
@ -12,8 +7,13 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.H
|
|||
*/
|
||||
public interface Lock4jRedisKeyConstants {
|
||||
|
||||
RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
|
||||
"lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类
|
||||
HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
/**
|
||||
* 分布式锁
|
||||
*
|
||||
* KEY 格式:lock4j:%s // 参数来自 DefaultLockKeyBuilder 类
|
||||
* VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
* 过期时间:不固定
|
||||
*/
|
||||
String LOCK4J = "lock4j:%s";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@
|
|||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
|
@ -7,8 +9,15 @@ import org.springframework.cache.annotation.EnableCaching;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
|
||||
|
||||
/**
|
||||
* Cache 配置类,基于 Redis 实现
|
||||
|
|
@ -20,15 +29,19 @@ public class YudaoCacheAutoConfiguration {
|
|||
|
||||
/**
|
||||
* RedisCacheConfiguration Bean
|
||||
*
|
||||
* <p>
|
||||
* 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
|
||||
// 设置使用 JSON 序列化方式
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
|
||||
// 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格
|
||||
// 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
|
||||
config = config.computePrefixWith(cacheName -> cacheName + StrUtil.COLON);
|
||||
// 设置使用 JSON 序列化方式
|
||||
config = config.serializeValuesWith(
|
||||
RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
|
||||
|
||||
// 设置 CacheProperties.Redis 的属性
|
||||
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
|
||||
|
|
@ -47,4 +60,14 @@ public class YudaoCacheAutoConfiguration {
|
|||
return config;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
|
||||
RedisCacheConfiguration redisCacheConfiguration) {
|
||||
// 创建 RedisCacheWriter 对象
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
|
@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration {
|
|||
template.setKeySerializer(RedisSerializer.string());
|
||||
template.setHashKeySerializer(RedisSerializer.string());
|
||||
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
|
||||
template.setValueSerializer(RedisSerializer.json());
|
||||
template.setHashValueSerializer(RedisSerializer.json());
|
||||
template.setValueSerializer(buildRedisSerializer());
|
||||
template.setHashValueSerializer(buildRedisSerializer());
|
||||
return template;
|
||||
}
|
||||
|
||||
public static RedisSerializer<?> buildRedisSerializer() {
|
||||
RedisSerializer<Object> json = RedisSerializer.json();
|
||||
// 解决 LocalDateTime 的序列化
|
||||
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
|
||||
objectMapper.registerModules(new JavaTimeModule());
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.redis.core;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis Key 定义类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class RedisKeyDefine {
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum KeyTypeEnum {
|
||||
|
||||
STRING("String"),
|
||||
LIST("List"),
|
||||
HASH("Hash"),
|
||||
SET("Set"),
|
||||
ZSET("Sorted Set"),
|
||||
STREAM("Stream"),
|
||||
PUBSUB("Pub/Sub");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
@JsonValue
|
||||
private final String type;
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TimeoutTypeEnum {
|
||||
|
||||
FOREVER(1), // 永不超时
|
||||
DYNAMIC(2), // 动态超时
|
||||
FIXED(3); // 固定超时
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
@JsonValue
|
||||
private final Integer type;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Key 模板
|
||||
*/
|
||||
private final String keyTemplate;
|
||||
/**
|
||||
* Key 类型的枚举
|
||||
*/
|
||||
private final KeyTypeEnum keyType;
|
||||
/**
|
||||
* Value 类型
|
||||
*
|
||||
* 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型
|
||||
*/
|
||||
private final Class<?> valueType;
|
||||
/**
|
||||
* 超时类型
|
||||
*/
|
||||
private final TimeoutTypeEnum timeoutType;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private final Duration timeout;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private final String memo;
|
||||
|
||||
private RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType,
|
||||
TimeoutTypeEnum timeoutType, Duration timeout) {
|
||||
this.memo = memo;
|
||||
this.keyTemplate = keyTemplate;
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.timeout = timeout;
|
||||
this.timeoutType = timeoutType;
|
||||
// 添加注册表
|
||||
RedisKeyRegistry.add(this);
|
||||
}
|
||||
|
||||
public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
|
||||
this(memo, keyTemplate, keyType, valueType, TimeoutTypeEnum.FIXED, timeout);
|
||||
}
|
||||
|
||||
public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
|
||||
this(memo, keyTemplate, keyType, valueType, timeoutType, Duration.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Key
|
||||
*
|
||||
* 注意,内部采用 {@link String#format(String, Object...)} 实现
|
||||
*
|
||||
* @param args 格式化的参数
|
||||
* @return Key
|
||||
*/
|
||||
public String formatKey(Object... args) {
|
||||
return String.format(keyTemplate, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.redis.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link RedisKeyDefine} 注册表
|
||||
*/
|
||||
public class RedisKeyRegistry {
|
||||
|
||||
/**
|
||||
* Redis RedisKeyDefine 数组
|
||||
*/
|
||||
private static final List<RedisKeyDefine> DEFINES = new ArrayList<>();
|
||||
|
||||
public static void add(RedisKeyDefine define) {
|
||||
DEFINES.add(define);
|
||||
}
|
||||
|
||||
public static List<RedisKeyDefine> list() {
|
||||
return DEFINES;
|
||||
}
|
||||
|
||||
public static int size() {
|
||||
return DEFINES.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package cn.iocoder.yudao.framework.redis.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* 支持自定义过期时间的 {@link RedisCacheManager} 实现类
|
||||
*
|
||||
* 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间,单位为秒
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TimeoutRedisCacheManager extends RedisCacheManager {
|
||||
|
||||
private static final String SPLIT = "#";
|
||||
|
||||
public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
|
||||
super(cacheWriter, defaultCacheConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
|
||||
if (StrUtil.isEmpty(name)) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
// 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间
|
||||
String[] names = StrUtil.splitToArray(name, SPLIT);
|
||||
if (names.length != 2) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
|
||||
// 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
|
||||
if (cacheConfig != null) {
|
||||
// 移除 # 后面的 : 以及后面的内容,避免影响解析
|
||||
names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
|
||||
// 解析时间
|
||||
Duration duration = DurationStyle.detectAndParse(names[1], ChronoUnit.SECONDS);
|
||||
cacheConfig = cacheConfig.entryTtl(duration);
|
||||
}
|
||||
return super.createRedisCache(names[0], cacheConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
OpenFeign:提供 RESTful API 的调用
|
||||
Dubbo:提供 Dubbo RPC 的调用
|
||||
</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
|
|
@ -34,20 +33,6 @@
|
|||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-common</artifactId> <!-- 兜底,保证在不引入 spring-cloud-starter-dubbo 时,注解等不报错 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-rpc-api</artifactId> <!-- 兜底,保证在不引入 spring-cloud-starter-dubbo 时,注解等不报错 -->
|
||||
</dependency>
|
||||
<!-- -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.cloud</groupId>-->
|
||||
<!-- <artifactId>spring-cloud-starter-dubbo</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- 工具相关 -->
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.rpc.core.dubbo;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServerException;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
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 javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
|
||||
|
||||
@Activate(group = CommonConstants.PROVIDER) // TODO 优化点:设置下顺序
|
||||
@Slf4j
|
||||
public class DubboProviderExceptionFilter implements Filter, Filter.Listener {
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
|
||||
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
|
||||
try {
|
||||
// 1. 转换异常
|
||||
Throwable exception = appResponse.getException();
|
||||
// 1.1 参数校验异常
|
||||
if (exception instanceof ConstraintViolationException) {
|
||||
exception = this.constraintViolationExceptionHandler((ConstraintViolationException) exception);
|
||||
// 1. ServiceException 业务异常,因为不会有序列化问题,所以无需处理
|
||||
} else if (exception instanceof ServiceException) {
|
||||
// 1.3 其它异常,转换成 GlobalException 全局异常,避免可能存在的反序列化问题
|
||||
} else {
|
||||
exception = this.defaultExceptionHandler(exception, invocation);
|
||||
assert exception != null;
|
||||
}
|
||||
// 2. 根据不同的方法 schema 返回结果
|
||||
// 2.1 如果是 ServiceException 异常,并且返回参数类型是 CommonResult 的情况,则将转换成 CommonResult 返回
|
||||
if (isReturnCommonResult(invocation) && exception instanceof ServiceException) {
|
||||
appResponse.setException(null); // 一定要清空异常
|
||||
appResponse.setValue(CommonResult.error((ServiceException) exception));
|
||||
// 2.2 如果是 GlobalException 全局异常,则直接抛出
|
||||
} else {
|
||||
// TODO 优化点:尝试修改成 RpcException
|
||||
appResponse.setException(exception);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
|
||||
log.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
private boolean isReturnCommonResult(Invocation invocation) {
|
||||
if (!(invocation instanceof RpcInvocation)) {
|
||||
return false;
|
||||
}
|
||||
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
|
||||
Type[] returnTypes = rpcInvocation.getReturnTypes();
|
||||
if (returnTypes.length == 0) {
|
||||
return false;
|
||||
}
|
||||
Type returnType = returnTypes[0];
|
||||
if (!(returnType instanceof Class)) {
|
||||
return false;
|
||||
}
|
||||
Class<?> returnClass = (Class<?>) returnType;
|
||||
return returnClass == CommonResult.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Validator 校验不通过产生的异常
|
||||
*/
|
||||
private ServiceException constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
||||
log.warn("[constraintViolationExceptionHandler]", ex);
|
||||
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
||||
return new ServiceException(BAD_REQUEST.getCode(),
|
||||
String.format("请求参数不正确:%s", constraintViolation.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统异常,兜底处理所有的一切
|
||||
*/
|
||||
private ServerException defaultExceptionHandler(Throwable exception, Invocation invocation) {
|
||||
log.error("[defaultExceptionHandler][service({}) method({}) params({}) 执行异常]",
|
||||
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), invocation.getArguments(), exception);
|
||||
// 如果已经是 GlobalException 全局异常,直接返回即可
|
||||
if (exception instanceof ServerException) {
|
||||
return (ServerException) exception;
|
||||
}
|
||||
return new ServerException(INTERNAL_SERVER_ERROR).setMessage(this.buildDetailMessage(exception, invocation));
|
||||
}
|
||||
|
||||
private String buildDetailMessage(Throwable exception, Invocation invocation) {
|
||||
return String.format("Service(%s) Method(%s) 发生异常(%s)",
|
||||
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), ExceptionUtil.getRootCauseMessage(exception));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* OpenFeign:提供 RESTful API 的调用
|
||||
* Dubbo:提供 Dubbo RPC 的调用
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
dubboExceptionFilter=cn.iocoder.yudao.framework.rpc.core.dubbo.DubboProviderExceptionFilter
|
||||
|
|
@ -1 +0,0 @@
|
|||
<http://www.iocoder.cn/Spring-Boot/Dubbo/?yudao>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<http://www.iocoder.cn/Spring-Cloud-Alibaba/Dubbo/?yudao>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.web.core.util;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
|
|
@ -136,4 +135,16 @@ public class WebFrameworkUtils {
|
|||
return request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 RPC 请求
|
||||
*
|
||||
* 约定大于配置,只要以 Api 结尾,都认为是 RPC 接口
|
||||
*
|
||||
* @param className 类名
|
||||
* @return 是否为 RPC 请求
|
||||
*/
|
||||
public static boolean isRpcRequest(String className) {
|
||||
return className.endsWith("Api");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,13 @@ spring:
|
|||
- Path=/app-api/pay/**
|
||||
filters:
|
||||
- RewritePath=/app-api/pay/v3/api-docs, /v3/api-docs
|
||||
|
||||
## mp-server 服务
|
||||
- id: mp-admin-api # 路由的编号
|
||||
uri: grayLb://mp-server
|
||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
||||
- Path=/admin-api/mp/**
|
||||
filters:
|
||||
- RewritePath=/admin-api/mp/v3/api-docs, /v3/api-docs
|
||||
x-forwarded:
|
||||
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
||||
|
||||
|
|
@ -85,3 +91,6 @@ knife4j:
|
|||
- name: pay-server
|
||||
service-name: pay-server
|
||||
url: /admin-api/pay/v3/api-docs
|
||||
- name: mp-server
|
||||
service-name: mp-server
|
||||
url: /admin-api/mp/v3/api-docs
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
spring:
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
|
||||
|
||||
# Servlet 配置
|
||||
servlet:
|
||||
|
|
@ -69,14 +69,6 @@ mybatis-plus:
|
|||
type-aliases-package: ${yudao.info.base-package}.dal.dataobject
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
dubbo:
|
||||
scan:
|
||||
base-packages: ${yudao.info.base-package}.api # 指定 Dubbo 服务实现类的扫描基准包
|
||||
protocol:
|
||||
name: dubbo # 协议名称
|
||||
port: -1 # 协议端口,-1 表示自增端口,从 20880 开始
|
||||
registry:
|
||||
address: spring-cloud://localhost # 设置使用 Spring Cloud 注册中心
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
|
|
|
|||
|
|
@ -3,20 +3,14 @@ package cn.iocoder.yudao.module.infra.api.file;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.file.dto.FileCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileService;
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class FileApiImpl implements FileApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,14 @@ package cn.iocoder.yudao.module.infra.api.logger;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.infra.service.logger.ApiAccessLogService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class ApiAccessLogApiImpl implements ApiAccessLogApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
package cn.iocoder.yudao.module.infra.api.logger;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.infra.service.logger.ApiAccessLogService;
|
||||
import cn.iocoder.yudao.module.infra.service.logger.ApiErrorLogService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class ApiErrorLogApiImpl implements ApiErrorLogApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,3 @@
|
|||
GET {{baseUrl}}/infra/redis/get-monitor-info
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
|
||||
### 请求 /infra/redis/get-key-list 接口 => 成功
|
||||
GET {{baseUrl}}/infra/redis/get-key-list
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,20 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.redis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyRegistry;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyValueRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
|
||||
import cn.iocoder.yudao.module.infra.convert.redis.RedisConvert;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.data.redis.connection.RedisServerCommands;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.Properties;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
|
|
@ -47,66 +40,4 @@ public class RedisController {
|
|||
return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats));
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-define-list")
|
||||
@Operation(summary = "获得 Redis Key 模板列表")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<List<RedisKeyDefineRespVO>> getKeyDefineList() {
|
||||
List<RedisKeyDefine> keyDefines = RedisKeyRegistry.list();
|
||||
return success(RedisConvert.INSTANCE.convertList(keyDefines));
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-list")
|
||||
@Operation(summary = "获得 Redis keys 键名列表")
|
||||
@Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Set<String>> getKeyDefineList(@RequestParam("keyTemplate") String keyTemplate) {
|
||||
return success(getKeyDefineList0(keyTemplate));
|
||||
}
|
||||
|
||||
private Set<String> getKeyDefineList0(String keyTemplate) {
|
||||
// key 格式化
|
||||
String key = StrUtil.replace(keyTemplate, "%[s|c|b|d|x|o|f|a|e|g]", parameter -> "*");
|
||||
// scan 扫描 key
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||
try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(100).build())) {
|
||||
cursor.forEachRemaining(value -> keys.add(StrUtil.utf8Str(value)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return keys;
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-value")
|
||||
@Operation(summary = "获得 Redis key 内容")
|
||||
@Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<RedisKeyValueRespVO> getKeyValue(@RequestParam("key") String key) {
|
||||
String value = stringRedisTemplate.opsForValue().get(key);
|
||||
return success(new RedisKeyValueRespVO(key, value));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-key")
|
||||
@Operation(summary = "删除 Redis Key")
|
||||
@Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Boolean> deleteKey(@RequestParam("key") String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-keys")
|
||||
@Operation(summary = "删除 Redis Key 根据模板")
|
||||
@Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Boolean> deleteKeys(@RequestParam("keyTemplate") String keyTemplate) {
|
||||
Set<String> keys = getKeyDefineList0(keyTemplate);
|
||||
if (CollUtil.isNotEmpty(keys)) {
|
||||
stringRedisTemplate.delete(keys);
|
||||
}
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Schema(description = "管理后台 - Redis Key 信息 Response VO")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class RedisKeyDefineRespVO {
|
||||
|
||||
@Schema(description = "Key 模板", requiredMode = Schema.RequiredMode.REQUIRED, example = "login_user:%s")
|
||||
private String keyTemplate;
|
||||
|
||||
@Schema(description = "Key 类型的枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
|
||||
private RedisKeyDefine.KeyTypeEnum keyType;
|
||||
|
||||
@Schema(description = "Value 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "java.lang.String")
|
||||
private Class<?> valueType;
|
||||
|
||||
@Schema(description = "超时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private RedisKeyDefine.TimeoutTypeEnum timeoutType;
|
||||
|
||||
@Schema(description = "过期时间,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Duration timeout;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "啦啦啦啦~")
|
||||
private String memo;
|
||||
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Schema(description = "管理后台 - Redis Key 信息 Response VO")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class RedisKeyRespVO {
|
||||
|
||||
@Schema(description = "login_user:%s", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
|
||||
private String keyTemplate;
|
||||
|
||||
@Schema(description = "Key 类型的枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
|
||||
private RedisKeyDefine.KeyTypeEnum keyType;
|
||||
|
||||
@Schema(description = "Value 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "java.lang.String")
|
||||
private Class<?> valueType;
|
||||
|
||||
@Schema(description = "超时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private RedisKeyDefine.TimeoutTypeEnum timeoutType;
|
||||
|
||||
@Schema(description = "过期时间,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Duration timeout;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "啦啦啦啦~")
|
||||
private String memo;
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 单个 Redis Key Value Response VO")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class RedisKeyValueRespVO {
|
||||
|
||||
@Schema(description = "c5f6990767804a928f4bb96ca249febf", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
|
||||
private String key;
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
|
||||
private String value;
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
package cn.iocoder.yudao.module.infra.convert.redis;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@Mapper
|
||||
|
|
@ -29,6 +26,4 @@ public interface RedisConvert {
|
|||
return respVO;
|
||||
}
|
||||
|
||||
List<RedisKeyDefineRespVO> convertList(List<RedisKeyDefine> list);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
|||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
|
||||
|
|
@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
|
|||
.orderByDesc(FileConfigDO::getId));
|
||||
}
|
||||
|
||||
@Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
|
||||
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.mq.consumer.file;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link FileConfigRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class FileConfigRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private FileConfigService fileConfigService;
|
||||
|
||||
@EventListener
|
||||
public void execute(FileConfigRefreshMessage message) {
|
||||
log.info("[execute][收到 FileConfig 刷新消息]");
|
||||
fileConfigService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 消息队列的消费者
|
||||
*/
|
||||
package cn.iocoder.yudao.module.infra.mq.consumer;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.mq.message.file;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 文件配置数据刷新 Message
|
||||
*/
|
||||
@Data
|
||||
public class FileConfigRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public FileConfigRefreshMessage() {
|
||||
}
|
||||
|
||||
public FileConfigRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 消息队列的消息
|
||||
*/
|
||||
package cn.iocoder.yudao.module.infra.mq.message;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.infra.mq.producer.file;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 文件配置相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class FileConfigProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link FileConfigRefreshMessage} 消息
|
||||
*/
|
||||
public void sendFileConfigRefreshMessage() {
|
||||
publishEvent(new FileConfigRefreshMessage(this, selfDestinationService(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 消息队列的生产者
|
||||
*/
|
||||
package cn.iocoder.yudao.module.infra.mq.producer;
|
||||
|
|
@ -16,11 +16,6 @@ import javax.validation.Valid;
|
|||
*/
|
||||
public interface FileConfigService {
|
||||
|
||||
/**
|
||||
* 初始化文件客户端
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建文件配置
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package cn.iocoder.yudao.module.infra.service.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
import cn.iocoder.yudao.framework.file.core.client.FileClient;
|
||||
|
|
@ -15,20 +17,20 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
|
|||
import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
||||
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Validator;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
|
||||
|
|
@ -46,6 +48,12 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
|
||||
@Resource
|
||||
private FileClientFactory fileClientFactory;
|
||||
|
||||
/**
|
||||
* 文件配置的缓存
|
||||
*/
|
||||
@Getter
|
||||
private List<FileConfigDO> fileConfigCache;
|
||||
/**
|
||||
* Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
|
||||
*/
|
||||
|
|
@ -55,13 +63,9 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
@Resource
|
||||
private FileConfigMapper fileConfigMapper;
|
||||
|
||||
@Resource
|
||||
private FileConfigProducer fileConfigProducer;
|
||||
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
|
|
@ -76,6 +80,27 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
masterFileClient = fileClientFactory.getFileClient(config.getId());
|
||||
}
|
||||
});
|
||||
this.fileConfigCache = configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过定时任务轮询,刷新缓存
|
||||
*
|
||||
* 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
|
||||
*/
|
||||
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
|
||||
public void refreshLocalCache() {
|
||||
// 情况一:如果缓存里没有数据,则直接刷新缓存
|
||||
if (CollUtil.isEmpty(fileConfigCache)) {
|
||||
initLocalCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
|
||||
LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
|
||||
if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
|
||||
initLocalCache();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -85,9 +110,9 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
.setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
|
||||
.setMaster(false); // 默认非 master
|
||||
fileConfigMapper.insert(fileConfig);
|
||||
// 发送刷新配置的消息
|
||||
fileConfigProducer.sendFileConfigRefreshMessage();
|
||||
// 返回
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
return fileConfig.getId();
|
||||
}
|
||||
|
||||
|
|
@ -99,8 +124,9 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
|
||||
.setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
|
||||
fileConfigMapper.updateById(updateObj);
|
||||
// 发送刷新配置的消息
|
||||
fileConfigProducer.sendFileConfigRefreshMessage();
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -112,15 +138,9 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
|
||||
// 更新
|
||||
fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
|
||||
// 发送刷新配置的消息
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
fileConfigProducer.sendFileConfigRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
|
||||
|
|
@ -139,12 +159,13 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
// 校验存在
|
||||
FileConfigDO config = validateFileConfigExists(id);
|
||||
if (Boolean.TRUE.equals(config.getMaster())) {
|
||||
throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
|
||||
throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
|
||||
}
|
||||
// 删除
|
||||
fileConfigMapper.deleteById(id);
|
||||
// 发送刷新配置的消息
|
||||
fileConfigProducer.sendFileConfigRefreshMessage();
|
||||
|
||||
// 刷新缓存
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
private FileConfigDO validateFileConfigExists(Long id) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
spring:
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
|
||||
|
||||
# Servlet 配置
|
||||
servlet:
|
||||
|
|
@ -60,16 +60,6 @@ mybatis-plus:
|
|||
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
dubbo:
|
||||
scan:
|
||||
base-packages: ${yudao.info.base-package}.api # 指定 Dubbo 服务实现类的扫描基准包
|
||||
protocol:
|
||||
name: dubbo # 协议名称
|
||||
port: -1 # 协议端口,-1 表示自增端口,从 20880 开始
|
||||
registry:
|
||||
address: spring-cloud://localhost # 设置使用 Spring Cloud 注册中心
|
||||
application:
|
||||
id: infra-server # TODO 一定要写么?
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigP
|
|||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
||||
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
|
@ -42,10 +41,10 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link FileConfigServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link FileConfigServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(FileConfigServiceImpl.class)
|
||||
public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
|
|
@ -55,8 +54,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private FileConfigMapper fileConfigMapper;
|
||||
|
||||
@MockBean
|
||||
private FileConfigProducer fileConfigProducer;
|
||||
@MockBean
|
||||
private Validator validator;
|
||||
@MockBean
|
||||
|
|
@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
|
||||
eq(configDO2.getStorage()), eq(configDO2.getConfig()));
|
||||
assertSame(masterFileClient, fileConfigService.getMasterFileClient());
|
||||
// 断言 fileConfigCache 缓存
|
||||
assertEquals(2, fileConfigService.getFileConfigCache().size());
|
||||
assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0));
|
||||
assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
assertFalse(fileConfig.getMaster());
|
||||
assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
|
||||
assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
|
||||
// verify 调用
|
||||
verify(fileConfigProducer).sendFileConfigRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
assertPojoEquals(reqVO, fileConfig, "config");
|
||||
assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
|
||||
assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
|
||||
// verify 调用
|
||||
verify(fileConfigProducer).sendFileConfigRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
// 断言数据
|
||||
assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
|
||||
assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
|
||||
// verify 调用
|
||||
verify(fileConfigProducer).sendFileConfigRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -172,10 +167,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
// 调用
|
||||
fileConfigService.deleteFileConfig(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(fileConfigMapper.selectById(id));
|
||||
// verify 调用
|
||||
verify(fileConfigProducer).sendFileConfigRefreshMessage();
|
||||
// 校验数据不存在了
|
||||
assertNull(fileConfigMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -201,30 +194,30 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetFileConfigPage() {
|
||||
// mock 数据
|
||||
FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码")
|
||||
.setStorage(FileStorageEnum.LOCAL.getStorage());
|
||||
dbFileConfig.setCreateTime(LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN));// 等会查询到
|
||||
fileConfigMapper.insert(dbFileConfig);
|
||||
// 测试 name 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码")));
|
||||
// 测试 storage 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage())));
|
||||
// 测试 createTime 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(LocalDateTimeUtil.parse("2020-11-23", DatePattern.NORM_DATE_PATTERN))));
|
||||
// 准备参数
|
||||
FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
|
||||
reqVO.setName("芋道");
|
||||
reqVO.setStorage(FileStorageEnum.LOCAL.getStorage());
|
||||
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2020, 1, 1),
|
||||
buildTime(2020, 1, 24)}));
|
||||
// mock 数据
|
||||
FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码")
|
||||
.setStorage(FileStorageEnum.LOCAL.getStorage());
|
||||
dbFileConfig.setCreateTime(LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN));// 等会查询到
|
||||
fileConfigMapper.insert(dbFileConfig);
|
||||
// 测试 name 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码")));
|
||||
// 测试 storage 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage())));
|
||||
// 测试 createTime 不匹配
|
||||
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(LocalDateTimeUtil.parse("2020-11-23", DatePattern.NORM_DATE_PATTERN))));
|
||||
// 准备参数
|
||||
FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
|
||||
reqVO.setName("芋道");
|
||||
reqVO.setStorage(FileStorageEnum.LOCAL.getStorage());
|
||||
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2020, 1, 1),
|
||||
buildTime(2020, 1, 24)}));
|
||||
|
||||
// 调用
|
||||
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
|
|||
return selectOne(MpAccountDO::getAppId, appId);
|
||||
}
|
||||
|
||||
@Select("SELECT COUNT(*) FROM pay_account WHERE update_time > #{maxUpdateTime}")
|
||||
@Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}")
|
||||
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
spring:
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
|
||||
|
||||
# Servlet 配置
|
||||
servlet:
|
||||
|
|
@ -71,6 +71,7 @@ wx:
|
|||
type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
|
||||
key-prefix: wx # Redis Key 的前缀
|
||||
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
|
||||
app-id: null # 避免 weixin-java-mp starter 报错
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
|
|
@ -81,6 +82,9 @@ yudao:
|
|||
web:
|
||||
admin-ui:
|
||||
url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
|
||||
security:
|
||||
permit-all_urls:
|
||||
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
|
||||
swagger:
|
||||
title: 管理后台
|
||||
description: 提供管理员管理的所有功能
|
||||
|
|
|
|||
|
|
@ -5,18 +5,14 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
|||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@Service
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class PayOrderApiImpl implements PayOrderApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,14 @@ import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
|||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@Service
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class PayRefundApiImpl implements PayRefundApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
|
|
@ -82,8 +83,8 @@ public class PayNotifyController {
|
|||
@PermitAll
|
||||
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||
public String notifyRefund(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||
|
|
@ -118,6 +119,9 @@ public class PayNotifyController {
|
|||
@PreAuthorize("@ss.hasPermission('pay:notify:query')")
|
||||
public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
|
||||
PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
// 拼接返回
|
||||
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
|
||||
return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
package cn.iocoder.yudao.module.pay.dal.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import org.redisson.api.RLock;
|
||||
|
||||
/**
|
||||
* 支付 Redis Key 枚举类
|
||||
*
|
||||
|
|
@ -10,9 +7,14 @@ import org.redisson.api.RLock;
|
|||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
|
||||
"pay_notify:lock:%d", // 参数来自 DefaultLockKeyBuilder 类
|
||||
RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
/**
|
||||
* 通知任务的分布式锁
|
||||
*
|
||||
* KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类
|
||||
* VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
* 过期时间:不固定
|
||||
*/
|
||||
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
|
||||
|
||||
/**
|
||||
* 支付序号的缓存
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class PayNotifyLockRedisDAO {
|
|||
}
|
||||
|
||||
private static String formatKey(Long id) {
|
||||
return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
|
||||
return String.format(PAY_NOTIFY_LOCK, id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
spring:
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
|
||||
|
||||
# Servlet 配置
|
||||
servlet:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
spring:
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
|
||||
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
|
||||
|
||||
# Servlet 配置
|
||||
servlet:
|
||||
|
|
@ -60,16 +60,6 @@ mybatis-plus:
|
|||
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
dubbo:
|
||||
scan:
|
||||
base-packages: ${yudao.info.base-package}.api # 指定 Dubbo 服务实现类的扫描基准包
|
||||
protocol:
|
||||
name: dubbo # 协议名称
|
||||
port: -1 # 协议端口,-1 表示自增端口,从 20880 开始
|
||||
registry:
|
||||
address: spring-cloud://localhost # 设置使用 Spring Cloud 注册中心
|
||||
application:
|
||||
id: report-server # TODO 一定要写么?
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.system.api.logger;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class SystemServerApplication {
|
|||
|
||||
public static void main(String[] args) {
|
||||
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
|
||||
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
|
||||
// 如果你碰到 启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
|
||||
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
|
||||
|
||||
SpringApplication.run(SystemServerApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
|||
import cn.iocoder.yudao.module.system.convert.dept.DeptConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -14,10 +13,8 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class DeptApiImpl implements DeptApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.system.api.dept;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.service.dept.PostService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -11,10 +9,8 @@ import javax.annotation.Resource;
|
|||
import java.util.Collection;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class PostApiImpl implements PostApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
|
|||
import cn.iocoder.yudao.module.system.convert.dict.DictDataConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -13,10 +12,8 @@ import javax.annotation.Resource;
|
|||
import java.util.Collection;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class DictDataApiImpl implements DictDataApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|||
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeAutoGenerateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeRespDTO;
|
||||
import cn.iocoder.yudao.module.system.service.errorcode.ErrorCodeService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -13,10 +12,8 @@ import java.time.LocalDateTime;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class ErrorCodeApiImpl implements ErrorCodeApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,14 @@ package cn.iocoder.yudao.module.system.api.logger;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class LoginLogApiImpl implements LoginLogApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,14 @@ package cn.iocoder.yudao.module.system.api.logger;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class OperateLogApiImpl implements OperateLogApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,15 @@ package cn.iocoder.yudao.module.system.api.mail;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.mail.MailSendService;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class MailSendApiImpl implements MailSendApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|||
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
|
||||
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -8,17 +8,14 @@ import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class OAuth2TokenApiImpl implements OAuth2TokenApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.api.permission;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -11,11 +10,9 @@ import javax.annotation.Resource;
|
|||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class PermissionApiImpl implements PermissionApi {
|
||||
|
||||
|
|
@ -24,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
|
|||
|
||||
@Override
|
||||
public CommonResult<Set<Long>> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
|
||||
return success(permissionService.getUserRoleIdListByRoleIds(roleIds));
|
||||
return success(permissionService.getUserRoleIdListByRoleId(roleIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.system.api.permission;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.service.permission.RoleService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -11,10 +9,8 @@ import javax.annotation.Resource;
|
|||
import java.util.Collection;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class RoleApiImpl implements RoleApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.system.api.sensitiveword;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -11,10 +9,8 @@ import javax.annotation.Resource;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class SensitiveWordApiImpl implements SensitiveWordApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
package cn.iocoder.yudao.module.system.api.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsCodeService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class SmsCodeApiImpl implements SmsCodeApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,14 @@ package cn.iocoder.yudao.module.system.api.sms;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class SmsSendApiImpl implements SmsSendApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class SocialUserApiImpl implements SocialUserApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.api.tenant;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -10,10 +9,8 @@ import javax.annotation.Resource;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class TenantApiImpl implements TenantApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
|||
import cn.iocoder.yudao.module.system.convert.user.UserConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -16,10 +14,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
|
||||
@Validated
|
||||
public class AdminUserApiImpl implements AdminUserApi {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
POST {{baseUrl}}/system/auth/login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenentId}}
|
||||
tag: Yunai.local
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
|
|
@ -14,6 +15,7 @@ tenant-id: {{adminTenentId}}
|
|||
POST {{baseUrl}}/system/auth/login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenentId}}
|
||||
tag: Yunai.local
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
|
||||
|
|
@ -12,8 +11,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.MenuService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.RoleService;
|
||||
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
|
||||
|
|
@ -34,9 +33,9 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
@Tag(name = "管理后台 - 认证")
|
||||
@RestController
|
||||
|
|
@ -52,6 +51,8 @@ public class AuthController {
|
|||
@Resource
|
||||
private RoleService roleService;
|
||||
@Resource
|
||||
private MenuService menuService;
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
@Resource
|
||||
private SocialUserService socialUserService;
|
||||
|
|
@ -91,33 +92,24 @@ public class AuthController {
|
|||
@GetMapping("/get-permission-info")
|
||||
@Operation(summary = "获取登录用户的权限信息")
|
||||
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
|
||||
// 获得用户信息
|
||||
// 1.1 获得用户信息
|
||||
AdminUserDO user = userService.getUser(getLoginUserId());
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
// 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
|
||||
// 获得菜单列表
|
||||
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
|
||||
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
|
||||
// 拼接结果返回
|
||||
return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
|
||||
}
|
||||
|
||||
@GetMapping("/list-menus")
|
||||
@Operation(summary = "获得登录用户的菜单列表")
|
||||
public CommonResult<List<AuthMenuRespVO>> getMenuList() {
|
||||
// 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 获得用户拥有的菜单列表
|
||||
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
|
||||
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
|
||||
// 转换成 Tree 结构返回
|
||||
return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
|
||||
// 1.2 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
|
||||
List<RoleDO> roles = roleService.getRoleList(roleIds);
|
||||
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
|
||||
|
||||
// 1.3 获得菜单列表
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
|
||||
List<MenuDO> menuList = menuService.getMenuList(menuIds);
|
||||
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
|
||||
|
||||
// 2. 拼接结果返回
|
||||
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
|
||||
}
|
||||
|
||||
// ========== 短信登录相关 ==========
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
|
||||
|
|
@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
|
|||
@Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Set<String> permissions;
|
||||
|
||||
@Schema(description = "菜单树", required = true)
|
||||
private List<MenuVO> menus;
|
||||
|
||||
@Schema(description = "用户信息 VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
|
|
@ -42,4 +46,48 @@ public class AuthPermissionInfoRespVO {
|
|||
|
||||
}
|
||||
|
||||
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class MenuVO {
|
||||
|
||||
@Schema(description = "菜单名称", required = true, example = "芋道")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "父菜单 ID", required = true, example = "1024")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "菜单名称", required = true, example = "芋道")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
|
||||
private String component;
|
||||
|
||||
@Schema(description = "组件名", example = "SystemUser")
|
||||
private String componentName;
|
||||
|
||||
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "是否可见", required = true, example = "false")
|
||||
private Boolean visible;
|
||||
|
||||
@Schema(description = "是否缓存", required = true, example = "false")
|
||||
private Boolean keepAlive;
|
||||
|
||||
@Schema(description = "是否总是显示", example = "false")
|
||||
private Boolean alwaysShow;
|
||||
|
||||
/**
|
||||
* 子路由
|
||||
*/
|
||||
private List<MenuVO> children;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ public class PermissionController {
|
|||
|
||||
@Operation(summary = "获得角色拥有的菜单编号")
|
||||
@Parameter(name = "roleId", description = "角色编号", required = true)
|
||||
@GetMapping("/list-role-resources")
|
||||
@GetMapping("/list-role-menus")
|
||||
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
|
||||
public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
|
||||
return success(permissionService.getRoleMenuIds(roleId));
|
||||
public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {
|
||||
return success(permissionService.getRoleMenuListByRoleId(roleId));
|
||||
}
|
||||
|
||||
@PostMapping("/assign-role-menu")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -27,14 +28,16 @@ public interface AuthConvert {
|
|||
|
||||
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
|
||||
return AuthPermissionInfoRespVO.builder()
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname())
|
||||
.avatar(user.getAvatar()).build())
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
|
||||
.roles(convertSet(roleList, RoleDO::getCode))
|
||||
// 权限标识信息
|
||||
.permissions(convertSet(menuList, MenuDO::getPermission))
|
||||
// 菜单树
|
||||
.menus(buildMenuTree(menuList))
|
||||
.build();
|
||||
}
|
||||
|
||||
AuthMenuRespVO convertTreeNode(MenuDO menu);
|
||||
AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
|
||||
|
||||
/**
|
||||
* 将菜单列表,构建成菜单树
|
||||
|
|
@ -42,20 +45,23 @@ public interface AuthConvert {
|
|||
* @param menuList 菜单列表
|
||||
* @return 菜单树
|
||||
*/
|
||||
default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
|
||||
default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
|
||||
// 移除按钮
|
||||
menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
|
||||
// 排序,保证菜单的有序性
|
||||
menuList.sort(Comparator.comparing(MenuDO::getSort));
|
||||
|
||||
// 构建菜单树
|
||||
// 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
|
||||
Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
|
||||
Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
|
||||
menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
|
||||
// 处理父子关系
|
||||
treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
|
||||
// 获得父节点
|
||||
AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
|
||||
AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
|
||||
if (parentNode == null) {
|
||||
LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
|
||||
childNode.getId(), childNode.getParentId());
|
||||
childNode.getId(), childNode.getParentId());
|
||||
return;
|
||||
}
|
||||
// 将自己添加到父节点中
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
|
|
@ -25,4 +26,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
|
|||
return selectCount(DeptDO::getParentId, parentId);
|
||||
}
|
||||
|
||||
default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {
|
||||
return selectList(DeptDO::getParentId, parentIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
|
|||
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
|
||||
}
|
||||
|
||||
default List<MenuDO> selectListByPermission(String permission) {
|
||||
return selectList(MenuDO::getPermission, permission);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
|
|||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
|
@ -13,14 +11,18 @@ import java.util.List;
|
|||
@Mapper
|
||||
public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
|
||||
|
||||
@Repository
|
||||
class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
|
||||
return selectList(RoleMenuDO::getRoleId, roleId);
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
|
||||
return selectList(RoleMenuDO::getRoleId, roleIds);
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByMenuId(Long menuId) {
|
||||
return selectList(RoleMenuDO::getMenuId, menuId);
|
||||
}
|
||||
|
||||
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
|
||||
delete(new LambdaQueryWrapper<RoleMenuDO>()
|
||||
.eq(RoleMenuDO::getRoleId, roleId)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
|
|||
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
|
|||
return selectOne(SensitiveWordDO::getName, name);
|
||||
}
|
||||
|
||||
@Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
|
||||
Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
|||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
|
||||
|
|
@ -18,4 +21,7 @@ public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
|
|||
.orderByDesc(SmsChannelDO::getId));
|
||||
}
|
||||
|
||||
@Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
|
||||
Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.dal.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
|
||||
|
||||
/**
|
||||
* System Redis Key 枚举类
|
||||
*
|
||||
|
|
@ -14,16 +9,93 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
|
|||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
|
||||
"captcha_code:%s", // 参数为 uuid
|
||||
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
|
||||
/**
|
||||
* 指定部门的所有子部门编号数组的缓存
|
||||
* <p>
|
||||
* KEY 格式:dept_children_ids:{id}
|
||||
* VALUE 数据类型:String 子部门编号集合
|
||||
*/
|
||||
String DEPT_CHILDREN_ID_LIST = "dept_children_ids";
|
||||
|
||||
RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
|
||||
"oauth2_access_token:%s", // 参数为访问令牌 token
|
||||
STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
|
||||
/**
|
||||
* 角色的缓存
|
||||
* <p>
|
||||
* KEY 格式:role:{id}
|
||||
* VALUE 数据类型:String 角色信息
|
||||
*/
|
||||
String ROLE = "role";
|
||||
|
||||
RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
|
||||
"social_auth_state:%s", // 参数为 state
|
||||
STRING, String.class, Duration.ofHours(24)); // 值为 state
|
||||
/**
|
||||
* 用户拥有的角色编号的缓存
|
||||
* <p>
|
||||
* KEY 格式:user_role_ids:{userId}
|
||||
* VALUE 数据类型:String 角色编号集合
|
||||
*/
|
||||
String USER_ROLE_ID_LIST = "user_role_ids";
|
||||
|
||||
/**
|
||||
* 拥有指定菜单的角色编号的缓存
|
||||
* <p>
|
||||
* KEY 格式:user_role_ids:{menuId}
|
||||
* VALUE 数据类型:String 角色编号集合
|
||||
*/
|
||||
String MENU_ROLE_ID_LIST = "menu_role_ids";
|
||||
|
||||
/**
|
||||
* 拥有权限对应的菜单编号数组的缓存
|
||||
* <p>
|
||||
* KEY 格式:permission_menu_ids:{permission}
|
||||
* VALUE 数据类型:String 菜单编号数组
|
||||
*/
|
||||
String PERMISSION_MENU_ID_LIST = "permission_menu_ids";
|
||||
|
||||
/**
|
||||
* OAuth2 客户端的缓存
|
||||
* <p>
|
||||
* KEY 格式:user:{id}
|
||||
* VALUE 数据类型:String 客户端信息
|
||||
*/
|
||||
String OAUTH_CLIENT = "oauth_client";
|
||||
|
||||
/**
|
||||
* 访问令牌的缓存
|
||||
* <p>
|
||||
* KEY 格式:oauth2_access_token:{token}
|
||||
* VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO}
|
||||
* <p>
|
||||
* 由于动态过期时间,使用 RedisTemplate 操作
|
||||
*/
|
||||
String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
|
||||
|
||||
/**
|
||||
* 站内信模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:notify_template:{code}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String NOTIFY_TEMPLATE = "notify_template";
|
||||
|
||||
/**
|
||||
* 邮件账号的缓存
|
||||
* <p>
|
||||
* KEY 格式:sms_template:{id}
|
||||
* VALUE 数据格式:String 账号信息
|
||||
*/
|
||||
String MAIL_ACCOUNT = "mail_account";
|
||||
|
||||
/**
|
||||
* 邮件模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:mail_template:{code}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String MAIL_TEMPLATE = "mail_template";
|
||||
|
||||
/**
|
||||
* 短信模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:sms_template:{id}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String SMS_TEMPLATE = "sms_template";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public class OAuth2AccessTokenRedisDAO {
|
|||
}
|
||||
|
||||
private static String formatKey(String accessToken) {
|
||||
return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
|
||||
return String.format(OAUTH2_ACCESS_TOKEN, accessToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.auth;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link OAuth2ClientRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OAuth2ClientRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientService oauth2ClientService;
|
||||
|
||||
@EventListener
|
||||
public void execute(OAuth2ClientRefreshMessage message) {
|
||||
log.info("[execute][收到 OAuth2Client 刷新消息]");
|
||||
oauth2ClientService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue