package cn.hobbystocks.auc.common.config; import io.lettuce.core.ClientOptions; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisSentinelConfiguration; 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.script.DefaultRedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; /** * redis配置 * * @author ruoyi */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @ConditionalOnProperty(name = "hobbystocks.redis.sentinel.nodes") public LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties, @Value("${hobbystocks.redis.sentinel.nodes:}") String hosts) { // 创建 Redis 哨兵配置 RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master(redisProperties.getSentinel().getMaster()); // 设置主节点名称,确保与哨兵配置一致 for (String host : hosts.split(",")) { sentinelConfig.sentinel(host.split(":")[0], Integer.parseInt(host.split(":")[1])); } // 如果 Redis 服务器设置了密码,则可以在此配置 sentinelConfig.setPassword(redisProperties.getPassword()); sentinelConfig.setDatabase(redisProperties.getDatabase()); // 配置连接池参数 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive()); // 最大连接数 poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle()); // 最大空闲连接数 poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle()); // 最小空闲连接数 // 配置客户端选项,启用自动重连 ClientOptions clientOptions = ClientOptions.builder() .autoReconnect(true) .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) .build(); // 创建 Lettuce 连接池配置 LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .poolConfig(poolConfig) .clientOptions(clientOptions) .clientOptions(ClientOptions.builder() .pingBeforeActivateConnection(true) // 在激活连接前发送 ping .build()) .build(); // 创建并配置 Lettuce 连接工厂 return new LettuceConnectionFactory(sentinelConfig, clientConfig); } @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); serializer.setObjectMapper(mapper); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } @Bean public DefaultRedisScript limitScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(limitScriptText()); redisScript.setResultType(Long.class); return redisScript; } /** * 限流脚本 */ private String limitScriptText() { return "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local time = tonumber(ARGV[2])\n" + "local current = redis.call('get', key);\n" + "if current and tonumber(current) > count then\n" + " return tonumber(current);\n" + "end\n" + "current = redis.call('incr', key)\n" + "if tonumber(current) == 1 then\n" + " redis.call('expire', key, time)\n" + "end\n" + "return tonumber(current);"; } }