diff --git a/SERIALIZE.md b/SERIALIZE.md index 4db64a9f11ae1edd218b07ce43c6549d37acf26e..dc8aa4da6b8bd7bd2383f2d39d7b36a864c38c20 100644 --- a/SERIALIZE.md +++ b/SERIALIZE.md @@ -713,6 +713,10 @@ https://github.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample > https://www.cnblogs.com/xwzp/p/14685452.html +# 解决 redis 序列化 java8 LocalDateTime 问题 + +https://blog.csdn.net/zhuzhoulin/article/details/106758473 + ### 配置文件 ![img.png](img.png) diff --git a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java index 7528f643937cda4c11202115e5d30ca6277835b2..f02b1fe65695f9ac2740ec5297299c44030bdd02 100644 --- a/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java +++ b/app/src/main/java/hxy/dream/app/service/impl/UserServiceImpl.java @@ -8,9 +8,11 @@ import hxy.dream.dao.model.UserModel; import hxy.dream.entity.enums.GenderEnum; import hxy.dream.entity.vo.BaseResponseVO; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; + import java.util.List; /** @@ -40,8 +42,10 @@ public class UserServiceImpl implements UserService { } @Override + @Cacheable(value = "UserService#36000", key = "'getUser'") public UserModel get(String id) { - return userMapper.selectById(id); + UserModel userModel = userMapper.selectById(id); + return userModel; } @Override diff --git a/app/src/main/resources/application-beta.yml b/app/src/main/resources/application-beta.yml index a710040a1ad078eecf1fd7391cbe7565e4ca6a07..7db1d0d39cabc81114e44c11001154249277b523 100755 --- a/app/src/main/resources/application-beta.yml +++ b/app/src/main/resources/application-beta.yml @@ -19,20 +19,26 @@ spring: sql: init: platform: mysql -# redis: -# database: 0 -# host: 119.23.236.94 -# port: 6379 -# password: newpass -# jedis: -# pool: -# max-active: 8 -# cache: -# redis: -# time-to-live: 0s # 缓存过期时间30分钟 -# type: redis -# rabbitmq: -# host: 119.23.236.94 + data: + # redis 配置 + redis: + # 地址 121.36.136.109 localhost + host: 52.131.246.191 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 3 + # 密码 + password: newpass + # 连接超时时间 + timeout: 10s + jedis: + pool: + min-idle: 10 + max-idle: 20 + max-wait: -1ms + max-active: 200 +# ssl: true logging: level: diff --git a/app/src/main/resources/application-test.yml b/app/src/main/resources/application-test.yml index bd590e20a5dc1df8a66ac625cc788a918ac26ac1..04087a2e71a70d05914d76774951d9ae20b51948 100755 --- a/app/src/main/resources/application-test.yml +++ b/app/src/main/resources/application-test.yml @@ -20,20 +20,26 @@ spring: sql: init: platform: mysql -# redis: -# database: 0 -# host: 119.23.236.94 -# port: 6379 -# password: newpass -# jedis: -# pool: -# max-active: 8 -# cache: -# redis: -# time-to-live: 0s # 缓存过期时间30分钟 -# type: redis -# rabbitmq: -# host: 119.23.236.94 + data: + # redis 配置 + redis: + # 地址 121.36.136.109 localhost + host: 52.131.246.191 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 3 + # 密码 + password: newpass + # 连接超时时间 + timeout: 10s + jedis: + pool: + min-idle: 10 + max-idle: 20 + max-wait: -1ms + max-active: 200 +# ssl: true logging: level: diff --git a/common/build.gradle b/common/build.gradle index 0ec3f28adb607034e656e9e853ce472ddd9456fd..7e26b0a08b4ffecbed1a6b56526d8df5e832cf1f 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -18,6 +18,9 @@ dependencies { api 'com.ejlchina:okhttps-jackson:3.4.0' api 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.data:spring-data-redis' + implementation 'redis.clients:jedis' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3' implementation group: 'org.javassist', name: 'javassist', version: '3.27.0-GA' implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.6' diff --git a/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java b/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java index 0a3568d7cdbee1dabe2e89d77e84bb9398664b2c..a356d4dcbb7ac5c9ad5103cddb044c42f48025a1 100644 --- a/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java +++ b/common/src/main/java/hxy/dream/common/configuration/BeanConfig.java @@ -56,7 +56,19 @@ public class BeanConfig { builder.timeZone(TimeZone.getDefault()); ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.registerModule(simpleModule); +/* + // FIXME 解决查询缓存转换异常的问题,这个月应该加在redis自己里面,加在全局,就会操作返回值里面带上类信息。 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 ---- start --- + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + // --end -- + */ // 配置忽略未知属性 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java b/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..44849a18e52023c3beb0d3fe9d0e391a4caa8303 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/DistributedLockHandler.java @@ -0,0 +1,58 @@ +package hxy.dream.common.redis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.concurrent.TimeUnit; + +/** + * 不是很严谨的分布式锁 + */ +@Component +public class DistributedLockHandler { + + private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class); + + + public final static long LOCK_EXPIRE = 30 * 1000L; + public final static long LOCK_TRY_INTERVAL = 30L; + public final static long LOCK_TRY_TIMEOUT = 20 * 1000L; + + @Autowired + private RedisTemplate redisTemplate; + + public boolean getLock(String key, String value) { + try { + if (StringUtils.hasLength(key) && StringUtils.hasLength(value)) { + + long startTime = System.currentTimeMillis(); + do { + if (!redisTemplate.hasKey(key)) { + redisTemplate.opsForValue().set(key, value, LOCK_EXPIRE, TimeUnit.MILLISECONDS); + return true; + } + if (System.currentTimeMillis() - startTime > LOCK_TRY_TIMEOUT) { + // 获取锁超时,直接放弃 + return false; + } + Thread.sleep(LOCK_TRY_INTERVAL); + } while (redisTemplate.hasKey(key)); + } + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + return false; + } + return false; + } + + public void releaseLock(String key) { + if (StringUtils.hasLength(key)) { + redisTemplate.delete(key); + } + } + +} diff --git a/common/src/main/java/hxy/dream/common/redis/RedisConfig.java b/common/src/main/java/hxy/dream/common/redis/RedisConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..81c4e249e27c580068e995eec21c38b06173673b --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/RedisConfig.java @@ -0,0 +1,133 @@ +package hxy.dream.common.redis; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +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.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.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +/** + * redis配置 + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Autowired + RedisConnectionFactory redisConnectionFactory; + + /** + * 这个仅仅针对Redis 序列化问题解决! 不能纳入到全局,否则会造成返回前端带上了类名。 + * + * @return + */ + @Bean + @Primary + public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { + ObjectMapper om = new ObjectMapper(); + // 解决查询缓存转换异常的问题 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 ---- start --- + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + // --end -- + GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); + return genericJackson2JsonRedisSerializer; + } + + @Bean + @Primary + public RedisTemplate redisTemplate() { + + RedisTemplate redisTemplate = new RedisTemplate(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + // 这里 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + GenericJackson2JsonRedisSerializer serializer = genericJackson2JsonRedisSerializer(); + redisTemplate.setValueSerializer(serializer); + // 这里 + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(serializer); + redisTemplate.setEnableTransactionSupport(true); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + + @Bean + @Override + public CacheManager cacheManager() { + + // 设置序列化 + RedisSerializer stringSerializer = new StringRedisSerializer(); + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + + //解决查询缓存转换异常的问题 + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new JavaTimeModule()); + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + jackson2JsonRedisSerializer.setObjectMapper(om); + + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer)) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) + .disableCachingNullValues() + // 缓存过期时间 Duration.ZERO -> eternal + .entryTtl(Duration.ZERO); +// .entryTtl(Duration.ofMinutes(30)); + + boolean ok = false; + if (ok) { + // TODO 这一部分不会报错,但是redis的缓存失效时间就没有实现了 + RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(config) + .transactionAware(); + @SuppressWarnings("serial") + Set cacheNames = new HashSet() { + { + add("codeNameCache"); + } + }; + builder.initialCacheNames(cacheNames); + return builder.build(); + } else { + // FIXME 使用这一部分就会报错 + RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); + return new RedisConfigCacheManager(cacheWriter, config); + } + } +} diff --git a/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java b/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..10e5778ca13e4fb437e3789a56e044cecc6f7161 --- /dev/null +++ b/common/src/main/java/hxy/dream/common/redis/RedisConfigCacheManager.java @@ -0,0 +1,91 @@ +package hxy.dream.common.redis; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.cache.CacheKeyPrefix; +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 org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; + +import java.time.Duration; + +/** + * redis 配置类 + */ +@Slf4j +public class RedisConfigCacheManager extends RedisCacheManager { + + + public RedisConfigCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { + super(cacheWriter, defaultCacheConfiguration); + } + + + private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> cacheName + ":"; + + @Override + protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { + +// GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = SpringUtils.getBean(GenericJackson2JsonRedisSerializer.class); + + RedisSerializationContext.SerializationPair DEFAULT_PAIR = RedisSerializationContext.SerializationPair + .fromSerializer(genericJackson2JsonRedisSerializer()); + + if (name != null && name.length() > 0) { + final int lastIndexOf = name.lastIndexOf('#'); + if (lastIndexOf > -1) { + final String ttl = name.substring(lastIndexOf + 1); + if (isNumeric(ttl)) { + final Duration duration = Duration.ofSeconds(Long.parseLong(ttl)); + cacheConfig = cacheConfig.entryTtl(duration); + //修改缓存key和value值的序列化方式 + cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX) + .serializeValuesWith(DEFAULT_PAIR); + final String cacheName = name.substring(0, lastIndexOf); + return super.createRedisCache(cacheName, cacheConfig); + } else { + log.error("过期时间配置错误!!!#号后面不是数字 => {}", ttl); + } + } + } + //修改缓存key和value值的序列化方式 + cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX) + .serializeValuesWith(DEFAULT_PAIR); + return super.createRedisCache(name, cacheConfig); + } + + public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() { + ObjectMapper om = new ObjectMapper(); + // 解决查询缓存转换异常的问题 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + // 支持 jdk 1.8 日期 ---- start --- + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + // --end -- + GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); + return genericJackson2JsonRedisSerializer; + } + + public static boolean isNumeric(String str) { + for (int i = str.length(); --i >= 0; ) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java index fb6619f6e6f24902e2d0f7cfad3051b491f3bf9f..2e19af980bf46b3eee3a38c4f0d0f7d48e6891d6 100644 --- a/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java +++ b/common/src/main/java/hxy/dream/common/serializer/DefaultInputJsonToEnum.java @@ -29,7 +29,7 @@ public class DefaultInputJsonToEnum { for (BaseEnum value : values) { //因为inputParameter都是string类型的,code转成字符串才能比较 - if (inputParameter.equals(String.valueOf(value.code())) || inputParameter.equals(value.description())) { + if (inputParameter.equals(String.valueOf(value.code())) || inputParameter.equals(value.description())|| inputParameter.equals(value.name())) { baseEnum = value; break; } diff --git a/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java b/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java index cb60938bf0b3b67fb63a35e9ab403fb739b57047..5a2fdc1d41e49d7d9a839578948dd410254345d0 100644 --- a/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java +++ b/entity/src/main/java/hxy/dream/entity/enums/BaseEnum.java @@ -20,4 +20,6 @@ public interface BaseEnum { */ String description(); + String name(); + } \ No newline at end of file