[feat] 3.0.0版本新增 redis 多数据源插件

This commit is contained in:
冰点 2024-06-17 14:40:06 +08:00
parent 8ec03a0b70
commit b0c7bf5751
11 changed files with 1026 additions and 0 deletions

View 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>

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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主机地址ipport多个用逗号(,)分隔
*/
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);
}
}
}

View File

@ -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;
}
}

View File

@ -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";
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.redis.MagicDynamicRedisAutoConfig