refactor(redis): 重构Redis配置以兼容fastjson格式
- 移除Jackson多态类型验证器配置 - 使用FastjsonCompatibleRedisSerializer替代GenericJacksonJsonRedisSerializer - 添加Primary注解优化Bean注入 - 移除不必要的ValueOperations Bean定义 - 更新限流脚本中的变量名提高可读性 - 在TokenService中添加对多种缓存格式的兼容性支持 - 创建FastjsonCompatibleRedisSerializer类处理不同数据格式的反序列化 - 添加数据库迁移脚本为相关表增加基础字段和删除标识
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package com.core.framework.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Jackson Redis序列化器 - 兼容fastjson旧格式
|
||||
*
|
||||
* 新数据: 纯JSON (无类型包装),调用方用 convertValue 转换
|
||||
* 旧fastjson: 去除L后缀后按JSON解析
|
||||
* 旧Jackson activateDefaultTyping: 解包 ["className",{data}] 后取data部分
|
||||
*/
|
||||
public class FastjsonCompatibleRedisSerializer implements RedisSerializer<Object> {
|
||||
private static final Logger log = LoggerFactory.getLogger(FastjsonCompatibleRedisSerializer.class);
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/** 全局ObjectMapper,供外部调用方做 convertValue 转换 */
|
||||
private static final ObjectMapper sharedMapper = createMapper();
|
||||
|
||||
public FastjsonCompatibleRedisSerializer() { log.info("[INIT] FastjsonCompatibleRedisSerializer loaded - plain JSON mode");
|
||||
this.objectMapper = createMapper();
|
||||
}
|
||||
|
||||
private static ObjectMapper createMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/** 获取共享ObjectMapper,供 DictUtils / TokenService 等做 convertValue */
|
||||
public static ObjectMapper getSharedMapper() {
|
||||
return sharedMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Object object) throws SerializationException {
|
||||
if (object == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsBytes(object);
|
||||
} catch (Exception e) {
|
||||
throw new SerializationException("Redis序列化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) throws SerializationException {
|
||||
if (bytes == null || bytes.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
String json = new String(bytes, StandardCharsets.UTF_8);
|
||||
// 移除fastjson特有的Long L后缀: 123L -> 123
|
||||
String cleaned = json.replaceAll("(\\d+)L", "$1");
|
||||
try {
|
||||
// 处理旧Jackson activateDefaultTyping格式: ["className", {data}]
|
||||
if (cleaned.startsWith("[\"") && cleaned.length() > 10) {
|
||||
com.fasterxml.jackson.databind.JsonNode node = objectMapper.readTree(cleaned);
|
||||
if (node.isArray() && node.size() >= 2 && node.get(0).isTextual()) {
|
||||
// 取data部分(第2个元素),忽略className
|
||||
return objectMapper.treeToValue(node.get(1), Object.class);
|
||||
}
|
||||
}
|
||||
return objectMapper.readValue(cleaned, Object.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Redis数据反序列化失败(已忽略): {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,51 +4,24 @@ 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.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.serializer.GenericJacksonJsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import tools.jackson.databind.DeserializationFeature;
|
||||
import tools.jackson.databind.DatabindContext;
|
||||
import tools.jackson.databind.JavaType;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig extends CachingConfigurerSupport {
|
||||
|
||||
private static final PolymorphicTypeValidator ALLOW_ALL = new PolymorphicTypeValidator() {
|
||||
@Override
|
||||
public Validity validateBaseType(DatabindContext ctxt, JavaType baseType) {
|
||||
return Validity.ALLOWED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Validity validateSubClassName(DatabindContext ctxt, JavaType baseType, String subClassName) {
|
||||
return Validity.ALLOWED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Validity validateSubType(DatabindContext ctxt, JavaType baseType, JavaType subType) {
|
||||
return Validity.ALLOWED;
|
||||
}
|
||||
};
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@SuppressWarnings(value = {"unchecked", "rawtypes"})
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
tools.jackson.databind.ObjectMapper objectMapper = JsonMapper.builder()
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.activateDefaultTyping(ALLOW_ALL, tools.jackson.databind.DefaultTyping.NON_FINAL)
|
||||
.build();
|
||||
|
||||
GenericJacksonJsonRedisSerializer serializer = new GenericJacksonJsonRedisSerializer(objectMapper);
|
||||
FastjsonCompatibleRedisSerializer serializer = new FastjsonCompatibleRedisSerializer();
|
||||
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
@@ -68,16 +41,18 @@ public class RedisConfig extends CachingConfigurerSupport {
|
||||
return redisScript;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ValueOperations<Object, Object> valueOperations(RedisTemplate<Object, Object> redisTemplate) {
|
||||
return redisTemplate.opsForValue();
|
||||
}
|
||||
|
||||
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);";
|
||||
return "local key = KEYS[1]\n" +
|
||||
"local count = tonumber(ARGV[1])\n" +
|
||||
"local ttl = tonumber(ARGV[2])\n" +
|
||||
"local current = redis.call('get', KEYS[1]);\n" +
|
||||
"if current and tonumber(current) > count then\n" +
|
||||
" return tonumber(current);\n" +
|
||||
"end\n" +
|
||||
"current = redis.call('incr', KEYS[1]);\n" +
|
||||
"if tonumber(current) == 1 then\n" +
|
||||
" redis.call('expire', KEYS[1], ttl);\n" +
|
||||
"end\n" +
|
||||
"return tonumber(current);\n";
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,28 @@ public class TokenService {
|
||||
// 解析对应的权限以及用户信息
|
||||
String uuid = (String)claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser user = redisCache.getCacheObject(userKey);
|
||||
return user;
|
||||
Object cached = redisCache.getCacheObject(userKey);
|
||||
if (cached instanceof LoginUser) {
|
||||
return (LoginUser) cached;
|
||||
}
|
||||
// 兼容旧Jackson activateDefaultTyping格式: ["className",{data}]
|
||||
if (cached instanceof java.util.List<?> list && list.size() >= 2 && list.get(0) instanceof String) {
|
||||
Object data = list.get(1);
|
||||
if (data instanceof java.util.Map) {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper.convertValue(data, LoginUser.class);
|
||||
}
|
||||
}
|
||||
// 兼容纯JSON格式: LinkedHashMap -> LoginUser
|
||||
if (cached instanceof java.util.Map) {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper.convertValue(cached, LoginUser.class);
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息异常'{}'", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
-- 为 sys_audit_log 添加 del_flag 列以匹配 HisBaseEntity 逻辑删除
|
||||
ALTER TABLE sys_audit_log ADD COLUMN IF NOT EXISTS del_flag CHAR(1) DEFAULT '0';
|
||||
COMMENT ON COLUMN sys_audit_log.del_flag IS '删除标识(0=正常,1=删除)';
|
||||
UPDATE sys_audit_log SET del_flag = '0' WHERE del_flag IS NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 为 clinical_pathway_execution 添加 HisBaseEntity 所需的基础字段
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_by VARCHAR(64);
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_by VARCHAR(64);
|
||||
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 为 prescription_intercept_log 添加 delete_flag 列以匹配 HisBaseEntity 默认映射
|
||||
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS delete_flag CHAR(1) DEFAULT '0';
|
||||
COMMENT ON COLUMN prescription_intercept_log.delete_flag IS '删除标识(0=正常,1=删除)';
|
||||
UPDATE prescription_intercept_log SET delete_flag = '0' WHERE delete_flag IS NULL;
|
||||
Reference in New Issue
Block a user