[feat] 3.0.0版本新增 redis 多数据源插件
This commit is contained in:
parent
8ec03a0b70
commit
b0c7bf5751
25
magic-api-plugins/magic-api-plugin-dynamic-redis/pom.xml
Normal file
25
magic-api-plugins/magic-api-plugin-dynamic-redis/pom.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ssssssss</groupId>
|
||||
<artifactId>magic-api-plugins</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
<artifactId>magic-api-plugin-dynamic-redis</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>magic-api-plugin-dynamic-redis</name>
|
||||
<description>magic-api-plugin-dynamic-redis</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,76 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.ssssssss.magicapi.utils.Assert;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
@DependsOn("redisInstanceFactory")
|
||||
@ConfigurationProperties(prefix = "magic-api.redis")
|
||||
@Import({MagicRedisConfiguration.class,RedisInstanceFactory.class,MagicRedisConfiguration.class})
|
||||
public class MagicDynamicRedisAutoConfig implements InitializingBean {
|
||||
|
||||
@Getter
|
||||
public static String primary;
|
||||
|
||||
@Getter
|
||||
private static Map<String, MagicRedisProperties> dynamic = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.isNotNull(primary, "Redis未设置默认库");
|
||||
dynamic.forEach((redisName, redisProperty) -> {
|
||||
// 构建Redis服务
|
||||
RedisInstanceFactory.getInstance().buildRedisTemplate(redisName, redisProperty);
|
||||
});
|
||||
log.info("[动态Redis]--共创建Redis[{}]个", dynamic.size());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StringRedisTemplate stringRedisTemplate() {
|
||||
MagicRedisProperties redisProperties = dynamic.get(primary);
|
||||
RedisConnectionFactory connectionFactory = RedisInstanceFactory.getInstance().buildLettuceConnectionFactory(primary, redisProperties);
|
||||
StringRedisTemplate template = new StringRedisTemplate();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<Object, Object> redisTemplate() {
|
||||
MagicRedisProperties redisProperties = dynamic.get(primary);
|
||||
RedisConnectionFactory connectionFactory = RedisInstanceFactory.getInstance().buildLettuceConnectionFactory(primary, redisProperties);
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
|
||||
public void setPrimary(String primary) {
|
||||
MagicDynamicRedisAutoConfig.primary = primary;
|
||||
}
|
||||
|
||||
public void setDynamic(Map<String, MagicRedisProperties> dynamic) {
|
||||
MagicDynamicRedisAutoConfig.dynamic = dynamic;
|
||||
}
|
||||
|
||||
public static MagicRedisProperties getPrimaryRedisProp() {
|
||||
return dynamic.get(primary);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.ssssssss.magicapi.core.config.MagicAPIProperties;
|
||||
import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
|
||||
import org.ssssssss.magicapi.core.config.Resource;
|
||||
import org.ssssssss.magicapi.core.model.Plugin;
|
||||
/**
|
||||
* MagicRedisProperties
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
@Configuration
|
||||
public class MagicRedisConfiguration implements MagicPluginConfiguration {
|
||||
|
||||
private final MagicAPIProperties properties;
|
||||
|
||||
public MagicRedisConfiguration(MagicAPIProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Redis存储
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "redis")
|
||||
public org.ssssssss.magicapi.core.resource.Resource magicRedisResource(RedisInstanceFactory redisInstanceFactory) {
|
||||
Resource resource = properties.getResource();
|
||||
StringRedisTemplate redisTemplate = (StringRedisTemplate) redisInstanceFactory.getRedisTemplate(MagicDynamicRedisAutoConfig.getPrimary(), true);
|
||||
return new RedisResource(redisTemplate, resource.getPrefix(), resource.isReadonly());
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入redis模块
|
||||
*/
|
||||
@Bean
|
||||
public RedisDynamicModule redisFunctions(RedisInstanceFactory redisInstanceFactory) {
|
||||
return new RedisDynamicModule(redisInstanceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin plugin() {
|
||||
return new Plugin("dynamicRedis");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.Duration;
|
||||
/**
|
||||
* MagicRedisProperties
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class MagicRedisProperties {
|
||||
|
||||
/**
|
||||
* 连接类型
|
||||
*/
|
||||
private RedisConnectionType type;
|
||||
|
||||
/**
|
||||
* redis主机地址,ip:port,多个用逗号(,)分隔
|
||||
*/
|
||||
private String address = "localhost:6379";
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 数据库(默认0)
|
||||
*/
|
||||
private int database = 0;
|
||||
|
||||
/**
|
||||
* 连接超时时间,默认3S
|
||||
*/
|
||||
private Duration timeout = Duration.ofMillis(3000L);
|
||||
|
||||
/**
|
||||
* 哨兵时需要指定master Name
|
||||
*/
|
||||
private Sentinel sentinel = new Sentinel();
|
||||
|
||||
/**
|
||||
* 默认使用lettuce
|
||||
*/
|
||||
private Lettuce lettuce = new Lettuce();
|
||||
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
private int maxRedirects = 5;
|
||||
|
||||
/**
|
||||
* 哨兵模式
|
||||
*/
|
||||
@Data
|
||||
public static class Sentinel {
|
||||
private String master;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lettuce client配置
|
||||
*/
|
||||
@Data
|
||||
public static class Lettuce {
|
||||
|
||||
/**
|
||||
* Shutdown timeout.
|
||||
*/
|
||||
private Duration shutdownTimeout = Duration.ofMillis(100);
|
||||
|
||||
/**
|
||||
* Lettuce pool configuration.
|
||||
*/
|
||||
private MagicRedisProperties.Pool pool = new Pool();
|
||||
|
||||
/**
|
||||
* setShutdownTimeout
|
||||
*/
|
||||
public void setShutdownTimeout(long shutdownTimeout) {
|
||||
this.shutdownTimeout = Duration.ofMillis(shutdownTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接池属性
|
||||
*/
|
||||
@Data
|
||||
public static class Pool {
|
||||
/**
|
||||
* 最大空闲
|
||||
*/
|
||||
private int maxIdle = 8;
|
||||
|
||||
/**
|
||||
* 最小空闲
|
||||
*/
|
||||
private int minIdle = 0;
|
||||
|
||||
/**
|
||||
* 最大存活
|
||||
*/
|
||||
private int maxActive = 8;
|
||||
|
||||
/**
|
||||
* 最长等待
|
||||
*/
|
||||
private Duration maxWait = Duration.ofMillis(-1);
|
||||
|
||||
/**
|
||||
* long转Duration
|
||||
*/
|
||||
public void setMaxWait(long maxWait) {
|
||||
this.maxWait = Duration.ofMillis(maxWait);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RedisConnectionType {
|
||||
|
||||
/**
|
||||
* 单机部署(默认)
|
||||
*/
|
||||
STANDALONE("standalone", "单机部署"),
|
||||
|
||||
/**
|
||||
* 哨兵部署
|
||||
*/
|
||||
SENTINEL("sentinel", "哨兵部署"),
|
||||
|
||||
/**
|
||||
* 集群部署方式
|
||||
*/
|
||||
CLUSTER("cluster", "集群方式"),
|
||||
|
||||
/**
|
||||
* 主从部署方式
|
||||
*/
|
||||
MASTERSLAVE("masterslave", "主从部署");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String type;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 根据type查询枚举
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 链接类型
|
||||
*/
|
||||
public static RedisConnectionType match(String type) {
|
||||
RedisConnectionType[] values = RedisConnectionType.values();
|
||||
for (RedisConnectionType connectionType : values) {
|
||||
if (connectionType.type.equals(type)) {
|
||||
return connectionType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
public interface RedisConstant {
|
||||
/**
|
||||
* stringRedisTemplate 服务后缀名
|
||||
*/
|
||||
String STRING_REDIS_TEMPLATE = "StringRedisTemplate";
|
||||
|
||||
/**
|
||||
* RedisTemplate 服务后缀名
|
||||
*/
|
||||
String REDIS_TEMPLATE = "RedisTemplate";
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisPipelineException;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.ssssssss.magicapi.core.annotation.MagicModule;
|
||||
import org.ssssssss.magicapi.utils.Assert;
|
||||
import org.ssssssss.script.functions.DynamicMethod;
|
||||
import org.ssssssss.script.reflection.JavaReflection;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* redis 多数据源插件模块
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
@MagicModule("dynamicRedis")
|
||||
@Slf4j
|
||||
public class RedisDynamicModule implements DynamicMethod {
|
||||
|
||||
private final RedisInstanceFactory redisInstanceFactory;
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
private boolean isRedisson;
|
||||
|
||||
public RedisDynamicModule(RedisInstanceFactory redisInstanceFactory) {
|
||||
this.redisInstanceFactory = redisInstanceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化
|
||||
*/
|
||||
public RedisDynamicModule name(String name) {
|
||||
this.redisTemplate = redisInstanceFactory.getRedisTemplate(name, false);
|
||||
Assert.isTrue(redisTemplate==null, "当前没有配置redis " + name);
|
||||
this.isRedisson = Objects.equals("org.redisson.spring.data.connection.RedissonConnectionFactory", this.redisTemplate.getConnectionFactory().getClass().getName());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 序列化
|
||||
*/
|
||||
private byte[] serializer(Object value) {
|
||||
if (value == null || value instanceof String) {
|
||||
return redisTemplate.getStringSerializer().serialize((String) value);
|
||||
}
|
||||
return serializer(value.toString());
|
||||
}
|
||||
|
||||
private Object serializerForRedisson(Object value) {
|
||||
if (value == null || JavaReflection.isPrimitiveAssignableFrom(value.getClass(), value.getClass())) {
|
||||
return value;
|
||||
}
|
||||
return serializer(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object deserialize(Object value) {
|
||||
if (value != null) {
|
||||
if (value instanceof byte[]) {
|
||||
return this.redisTemplate.getStringSerializer().deserialize((byte[]) value);
|
||||
}
|
||||
if (value instanceof List) {
|
||||
List<Object> valueList = (List<Object>) value;
|
||||
List<Object> resultList = new ArrayList<>(valueList.size());
|
||||
for (Object val : valueList) {
|
||||
resultList.add(deserialize(val));
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
Map<Object, Object> map = (Map<Object, Object>) value;
|
||||
LinkedHashMap<Object, Object> newMap = new LinkedHashMap<>(map.size());
|
||||
map.forEach((key, val) -> newMap.put(deserialize(key), deserialize(val)));
|
||||
return newMap;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param methodName 命令名称
|
||||
* @param parameters 命令参数
|
||||
*/
|
||||
@Override
|
||||
public Object execute(String methodName, List<Object> parameters) {
|
||||
return this.redisTemplate.execute(connection -> {
|
||||
Object result;
|
||||
if (isRedisson) {
|
||||
result = executeForRedisson(((DefaultStringRedisConnection) connection).getDelegate(), methodName, parameters);
|
||||
} else {
|
||||
byte[][] params = new byte[parameters.size()][];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
params[i] = serializer(parameters.get(i));
|
||||
}
|
||||
result = connection.execute(methodName, params);
|
||||
}
|
||||
return deserialize(result);
|
||||
}, isRedisson || this.redisTemplate.isExposeConnection());
|
||||
}
|
||||
|
||||
private Object executeForRedisson(RedisConnection connection, String command, List<Object> parameters) {
|
||||
Method[] methods = connection.getClass().getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equalsIgnoreCase(command) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == parameters.size()) {
|
||||
try {
|
||||
Object ret = this.execute(connection, method, parameters);
|
||||
if (ret instanceof String) {
|
||||
return ((String) ret).getBytes();
|
||||
}
|
||||
return ret;
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (connection.isPipelined()) {
|
||||
throw new RedisPipelineException(e);
|
||||
}
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Object execute(RedisConnection connection, Method method, List<Object> parameters) {
|
||||
if (method.getParameterTypes().length > 0 && method.getParameterTypes()[0] == byte[][].class) {
|
||||
return ReflectionUtils.invokeMethod(method, connection, parameters.stream().map(this::serializer).toArray(byte[][]::new));
|
||||
} else if (parameters.size() == 0) {
|
||||
return ReflectionUtils.invokeMethod(method, connection);
|
||||
}
|
||||
return ReflectionUtils.invokeMethod(method, connection, parameters.stream().map(this::serializerForRedisson).toArray());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,396 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.data.redis.connection.*;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.ssssssss.magicapi.utils.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* redis 多数据源插件模块
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedisInstanceFactory implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
* RedisTemplate缓存 取值规则 redisName + “RedisTemplate” 和 redisName + “StringRedisTemplate”
|
||||
*/
|
||||
public final Map<String, RedisTemplate<?, ?>> REDIS_HOLDER = new ConcurrentHashMap<>();
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
private RedisInstanceFactory() {
|
||||
}
|
||||
|
||||
public static class RedisFactoryLoader {
|
||||
private static final RedisInstanceFactory INSTANCE = new RedisInstanceFactory();
|
||||
}
|
||||
|
||||
public static RedisInstanceFactory getInstance() {
|
||||
return RedisFactoryLoader.INSTANCE;
|
||||
}
|
||||
|
||||
public RedisTemplate<?, ?> getRedisTemplate(String redisName, boolean isStringRedisTemplate) {
|
||||
return REDIS_HOLDER.get(getRedisTemplateName(redisName, isStringRedisTemplate));
|
||||
}
|
||||
/**
|
||||
* 生成RedisTemplate服务
|
||||
*
|
||||
* @param redisName redis名称
|
||||
* @param redisProperties redis属性
|
||||
*/
|
||||
public void buildRedisTemplate(String redisName, MagicRedisProperties redisProperties) {
|
||||
RedisConnectionFactory redisConnectionFactory = buildLettuceConnectionFactory(redisName, redisProperties);
|
||||
Assert.isNotNull(redisConnectionFactory, "Redis连接创建失败");
|
||||
RedisTemplate redisTemplate = buildRedisTemplate(redisName, redisConnectionFactory);
|
||||
Assert.isNotNull(redisTemplate, "RedisTemplate创建失败");
|
||||
StringRedisTemplate stringRedisTemplate = buildStringRedisTemplate(redisName, redisConnectionFactory);
|
||||
Assert.isNotNull(stringRedisTemplate, "StringRedisTemplate创建失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ConnectionFactory
|
||||
*/
|
||||
public RedisConnectionFactory buildLettuceConnectionFactory(String redisName, MagicRedisProperties redisProperties) {
|
||||
String redisClientName = getRedisTemplateName(redisName, false);
|
||||
if (applicationContext.containsBean(redisClientName)) {
|
||||
return getBean(redisClientName);
|
||||
}
|
||||
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
|
||||
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
|
||||
// 获取连接工厂
|
||||
RedisConnectionFactory lettuceConnectionFactory = getLettuceConnectionFactory(redisProperties);
|
||||
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(RedisConnectionFactory.class, () -> lettuceConnectionFactory);
|
||||
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
|
||||
// 设置primary bean
|
||||
if (redisName.startsWith(MagicDynamicRedisAutoConfig.primary)) {
|
||||
beanDefinition.setPrimary(true);
|
||||
}
|
||||
beanFactory.registerBeanDefinition(redisClientName, beanDefinition);
|
||||
return getBean(redisClientName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Lettuce连接工厂
|
||||
*
|
||||
* @param redisProperties Redis配置
|
||||
*/
|
||||
public static RedisConnectionFactory getLettuceConnectionFactory(MagicRedisProperties redisProperties) {
|
||||
MagicRedisProperties.Pool pool = redisProperties.getLettuce().getPool();
|
||||
GenericObjectPoolConfig poolConfig = (new PoolBuilderFactory()).getPoolConfig(pool);
|
||||
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
|
||||
.commandTimeout(redisProperties.getTimeout())
|
||||
.poolConfig(poolConfig)
|
||||
.build();
|
||||
LettuceConnectionFactory lettuceConnectionFactory = null;
|
||||
RedisConnectionType connectionType = redisProperties.getType();
|
||||
if (RedisConnectionType.SENTINEL.equals(connectionType)) {
|
||||
lettuceConnectionFactory = new LettuceConnectionFactory(buildSentinelConfig(redisProperties), clientConfig);
|
||||
} else if (RedisConnectionType.CLUSTER.equals(connectionType)) {
|
||||
lettuceConnectionFactory = new LettuceConnectionFactory(buildClusterConfig(redisProperties), clientConfig);
|
||||
} else {
|
||||
lettuceConnectionFactory = new LettuceConnectionFactory(buildStandaloneConfig(redisProperties), clientConfig);
|
||||
}
|
||||
return lettuceConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 StringRedisTemplate
|
||||
*
|
||||
* @param redisName redis名称
|
||||
* @param lettuceConnectionFactory 连接工厂
|
||||
* @return StringRedisTemplate
|
||||
*/
|
||||
private StringRedisTemplate buildStringRedisTemplate(String redisName, RedisConnectionFactory lettuceConnectionFactory) {
|
||||
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
|
||||
constructorArgumentValues.addIndexedArgumentValue(0, lettuceConnectionFactory);
|
||||
|
||||
String beanName = getRedisTemplateName(redisName, true);
|
||||
registerBean(beanName, StringRedisTemplate.class,null, constructorArgumentValues);
|
||||
StringRedisTemplate redisTemplate = getBean(beanName);
|
||||
// 保存RedisTemplate
|
||||
saveRedisBean(beanName, redisTemplate);
|
||||
log.info("[动态Redis] - {}创建成功", beanName);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成RedisTemplate
|
||||
*
|
||||
* @param redisName 缓存名称
|
||||
* @param connectionFactory 连接工厂
|
||||
* @return RedisTemplate
|
||||
*/
|
||||
private RedisTemplate buildRedisTemplate(String redisName, RedisConnectionFactory connectionFactory) {
|
||||
String redisClientName = redisName + RedisConstant.REDIS_TEMPLATE;
|
||||
if (applicationContext.containsBean(redisClientName)) {
|
||||
return getBean(redisClientName);
|
||||
}
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
|
||||
StringRedisSerializer keySerializer = new StringRedisSerializer();
|
||||
|
||||
// 构造参数
|
||||
Map<String, Object> property = new HashMap<>(8);
|
||||
property.put("connectionFactory", connectionFactory);
|
||||
property.put("keySerializer", keySerializer);
|
||||
property.put("hashKeySerializer", keySerializer);
|
||||
property.put("valueSerializer", serializer);
|
||||
property.put("hashValueSerializer", serializer);
|
||||
registerBean(redisClientName, RedisTemplate.class, property,null);
|
||||
// setBean(redisClientName, RedisTemplate.class, property);
|
||||
RedisTemplate redisTemplate = getBean(redisClientName);
|
||||
saveRedisBean(redisClientName, redisTemplate);
|
||||
log.info("[动态Redis] - {}创建成功", redisClientName);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 组装单机配置参数
|
||||
*
|
||||
* @param redisProperties 属性配置
|
||||
* @return 构造Bean参数
|
||||
*/
|
||||
public static RedisStandaloneConfiguration buildStandaloneConfig(MagicRedisProperties redisProperties) {
|
||||
RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration();
|
||||
String[] addresses = redisProperties.getAddress().split(":");
|
||||
standaloneConfig.setHostName(addresses[0]);
|
||||
standaloneConfig.setPort(Integer.parseInt(addresses[1]));
|
||||
String username = redisProperties.getUsername();
|
||||
if (StringUtils.hasText(username)) {
|
||||
standaloneConfig.setUsername(username);
|
||||
}
|
||||
String password = redisProperties.getPassword();
|
||||
if (StringUtils.hasText(password)) {
|
||||
standaloneConfig.setPassword(RedisPassword.of(password));
|
||||
}
|
||||
standaloneConfig.setDatabase(redisProperties.getDatabase());
|
||||
return standaloneConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 组装哨兵配置参数
|
||||
*
|
||||
* @param redisProperties 属性配置
|
||||
* @return 构造Bean参数
|
||||
*/
|
||||
private static RedisSentinelConfiguration buildSentinelConfig(MagicRedisProperties redisProperties) {
|
||||
String[] addresses = redisProperties.getAddress().split(",");
|
||||
Assert.isTrue(addresses.length > 1, "哨兵模式不能配置单节点");
|
||||
List<RedisNode> sentinelNode = new ArrayList<>();
|
||||
for (String address : addresses) {
|
||||
Assert.isTrue(address.contains(","), "地址格式不正确,格式必须是:[地址:端口]");
|
||||
String[] addr = address.split(",");
|
||||
sentinelNode.add(new RedisNode(addr[0].trim(), Integer.parseInt(addr[1])));
|
||||
}
|
||||
String masterName = redisProperties.getSentinel().getMaster();
|
||||
Assert.isNotBlank(masterName, "哨兵模式必须指定主节点名称");
|
||||
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
|
||||
sentinelConfig.setMaster(masterName);
|
||||
sentinelConfig.setDatabase(redisProperties.getDatabase());
|
||||
String username = redisProperties.getUsername();
|
||||
if (StringUtils.hasText(username)) {
|
||||
sentinelConfig.setUsername(username);
|
||||
}
|
||||
String password = redisProperties.getPassword();
|
||||
if (StringUtils.hasText(password)) {
|
||||
sentinelConfig.setPassword(RedisPassword.of(password));
|
||||
}
|
||||
sentinelConfig.setSentinels(sentinelNode);
|
||||
return sentinelConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装集群配置参数
|
||||
*
|
||||
* @param redisProperties 属性配置
|
||||
* @return 构造Bean参数
|
||||
*/
|
||||
private static RedisClusterConfiguration buildClusterConfig(MagicRedisProperties redisProperties) {
|
||||
String[] addresses = redisProperties.getAddress().split(",");
|
||||
Assert.isTrue(addresses.length > 1, "集群模式不能配置单节点");
|
||||
// 集群节点
|
||||
List<RedisNode> clusterNode = new ArrayList<>();
|
||||
for (String address : addresses) {
|
||||
Assert.isTrue(address.contains(","), "地址格式不正确,格式必须是:[地址:端口]");
|
||||
String[] addr = address.split(",");
|
||||
clusterNode.add(new RedisNode(addr[0].trim(), Integer.parseInt(addr[1])));
|
||||
}
|
||||
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
|
||||
clusterConfig.setClusterNodes(clusterNode);
|
||||
// 密码,处理带用户名的场景
|
||||
String username = redisProperties.getUsername();
|
||||
if (StringUtils.hasText(username)) {
|
||||
clusterConfig.setUsername(username);
|
||||
}
|
||||
String password = redisProperties.getPassword();
|
||||
if (StringUtils.hasText(password)) {
|
||||
clusterConfig.setPassword(RedisPassword.of(password));
|
||||
}
|
||||
// 集群默认重试次数
|
||||
int maxRedirects = redisProperties.getMaxRedirects();
|
||||
clusterConfig.setMaxRedirects(maxRedirects <= 0 ? 5 : maxRedirects);
|
||||
return clusterConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Bean
|
||||
*
|
||||
* @param beanName bean名称
|
||||
* @param clazz class
|
||||
* @param constructorArgs 构造函数
|
||||
*/
|
||||
public synchronized void setCosBean(String beanName, Class<?> clazz, ConstructorArgumentValues constructorArgs) {
|
||||
checkApplicationContext();
|
||||
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
|
||||
if (beanFactory.containsBean(beanName)) {
|
||||
return;
|
||||
}
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
//类class
|
||||
definition.setBeanClass(clazz);
|
||||
if (beanName.startsWith(MagicDynamicRedisAutoConfig.primary)) {
|
||||
definition.setPrimary(true);
|
||||
}
|
||||
//属性赋值
|
||||
definition.setConstructorArgumentValues(new ConstructorArgumentValues(constructorArgs));
|
||||
//注册到spring上下文
|
||||
beanFactory.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步方法注册bean到ApplicationContext中
|
||||
*
|
||||
* @param beanName bean name
|
||||
* @param clazz bean class
|
||||
* @param original bean的属性值
|
||||
*/
|
||||
public synchronized void setBean(String beanName, Class<?> clazz, Map<String, Object> original) {
|
||||
checkApplicationContext();
|
||||
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
|
||||
if (beanFactory.containsBean(beanName)) {
|
||||
return;
|
||||
}
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
//类class
|
||||
definition.setBeanClass(clazz);
|
||||
if (beanName.startsWith(MagicDynamicRedisAutoConfig.primary)) {
|
||||
definition.setPrimary(true);
|
||||
}
|
||||
//属性赋值
|
||||
definition.setPropertyValues(new MutablePropertyValues(original));
|
||||
//注册到spring上下文
|
||||
beanFactory.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
|
||||
private String getRedisTemplateName(String redisName, boolean isStringRedisTemplate) {
|
||||
return redisName + (isStringRedisTemplate ? RedisConstant.STRING_REDIS_TEMPLATE : RedisConstant.REDIS_TEMPLATE);
|
||||
}
|
||||
|
||||
private void registerBeanToContext(String beanName, Class<?> clazz, Map<String, Object> properties, ConstructorArgumentValues args) {
|
||||
checkApplicationContext();
|
||||
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
|
||||
if (beanFactory.containsBean(beanName)) {
|
||||
return;
|
||||
}
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(clazz);
|
||||
definition.setPrimary(beanName.startsWith(MagicDynamicRedisAutoConfig.primary));
|
||||
if (args != null) {
|
||||
definition.setConstructorArgumentValues(args);
|
||||
} else {
|
||||
definition.setPropertyValues(new MutablePropertyValues(properties));
|
||||
}
|
||||
beanFactory.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
|
||||
// 合并 setCosBean 和 setBean 方法
|
||||
public synchronized void registerBean(String beanName, Class<?> clazz, Map<String, Object> properties, ConstructorArgumentValues args) {
|
||||
registerBeanToContext(beanName, clazz, properties, args);
|
||||
}
|
||||
/**
|
||||
* 保存RedisBean
|
||||
*
|
||||
* @param beanName redisName名称
|
||||
* @param redisTemplate redisTemplate
|
||||
*/
|
||||
private void saveRedisBean(String beanName, RedisTemplate<?, ?> redisTemplate) {
|
||||
REDIS_HOLDER.put(beanName, redisTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) {
|
||||
checkApplicationContext();
|
||||
if (applicationContext.containsBean(name)) {
|
||||
return (T) applicationContext.getBean(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkApplicationContext() {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalStateException("applicaitonContext未注入,请检查配置");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
RedisInstanceFactory.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程池工程
|
||||
*/
|
||||
private static class PoolBuilderFactory {
|
||||
public GenericObjectPoolConfig getPoolConfig(MagicRedisProperties.Pool properties) {
|
||||
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
|
||||
config.setMaxTotal(properties.getMaxActive());
|
||||
config.setMaxIdle(properties.getMaxIdle());
|
||||
config.setMinIdle(properties.getMinIdle());
|
||||
if (properties.getMaxWait() != null) {
|
||||
config.setMaxWaitMillis(properties.getMaxWait().toMillis());
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
package org.ssssssss.magicapi.redis;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.ssssssss.magicapi.core.resource.KeyValueResource;
|
||||
import org.ssssssss.magicapi.core.resource.Resource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* redis 多数据源插件模块
|
||||
* @since 3.0.0
|
||||
* @author 冰点
|
||||
*/
|
||||
public class RedisResource extends KeyValueResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedisResource.class);
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final Map<String, String> cachedContent = new ConcurrentHashMap<>();
|
||||
|
||||
public RedisResource(StringRedisTemplate redisTemplate, String path, boolean readonly, RedisResource parent) {
|
||||
super(":", path, readonly, parent);
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
public RedisResource(StringRedisTemplate redisTemplate, String path, boolean readonly) {
|
||||
this(redisTemplate, path, readonly, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAll() {
|
||||
List<String> keys = new ArrayList<>(keys());
|
||||
List<String> values = redisTemplate.opsForValue().multiGet(keys);
|
||||
this.cachedContent.entrySet().removeIf(entry -> entry.getKey().startsWith(path));
|
||||
if (values != null) {
|
||||
for (int i = 0, size = keys.size(); i < size; i++) {
|
||||
this.cachedContent.put(keys.get(i), values.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read() {
|
||||
String value = this.cachedContent.get(path);
|
||||
if (value == null) {
|
||||
value = redisTemplate.opsForValue().get(path);
|
||||
if (value != null) {
|
||||
this.cachedContent.put(path, value);
|
||||
}
|
||||
}
|
||||
return value == null ? new byte[0] : value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String content) {
|
||||
this.redisTemplate.opsForValue().set(this.path, content);
|
||||
this.cachedContent.put(this.path, content);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean renameTo(Map<String, String> renameKeys) {
|
||||
renameKeys.forEach(this.redisTemplate::rename);
|
||||
renameKeys.forEach((oldKey, newKey) -> this.cachedContent.put(newKey, this.cachedContent.remove(oldKey)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
if (this.cachedContent.get(this.path) != null) {
|
||||
return true;
|
||||
}
|
||||
return Boolean.TRUE.equals(this.redisTemplate.hasKey(this.path));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean deleteByKey(String key) {
|
||||
if (Boolean.TRUE.equals(this.redisTemplate.delete(key))) {
|
||||
this.cachedContent.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<String, Resource> mappedFunction() {
|
||||
return (it) -> new RedisResource(this.redisTemplate, it, readonly, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> keys() {
|
||||
Set<String> keys = this.redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||
ScanOptions options = ScanOptions.scanOptions()
|
||||
.count(Long.MAX_VALUE)
|
||||
.match((isDirectory() ? this.path : (this.path + separator)) + "*")
|
||||
.build();
|
||||
Set<String> returnKeys = new HashSet<>();
|
||||
try (Cursor<byte[]> cursor = connection.scan(options)) {
|
||||
while (cursor.hasNext()) {
|
||||
returnKeys.add(new String(cursor.next()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("扫描key出错", e);
|
||||
}
|
||||
return returnKeys;
|
||||
});
|
||||
return keys == null ? Collections.emptySet() : keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("redis://%s", getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.redis.MagicDynamicRedisAutoConfig
|
||||
@ -0,0 +1 @@
|
||||
org.ssssssss.magicapi.redis.MagicRedisConfiguration
|
||||
Loading…
Reference in New Issue
Block a user