diff --git a/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/FastjsonCompatibleRedisSerializer.java b/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/FastjsonCompatibleRedisSerializer.java new file mode 100644 index 000000000..6d39a8c62 --- /dev/null +++ b/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/FastjsonCompatibleRedisSerializer.java @@ -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 { + 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; + } + } +} \ No newline at end of file diff --git a/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/RedisConfig.java b/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/RedisConfig.java index bfd62a6c0..b89bb41df 100755 --- a/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/RedisConfig.java +++ b/healthlink-his-server/core-framework/src/main/java/com/core/framework/config/RedisConfig.java @@ -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 redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate 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 valueOperations(RedisTemplate 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"; } } \ No newline at end of file diff --git a/healthlink-his-server/core-framework/src/main/java/com/core/framework/web/service/TokenService.java b/healthlink-his-server/core-framework/src/main/java/com/core/framework/web/service/TokenService.java index bdcac8704..61a552088 100755 --- a/healthlink-his-server/core-framework/src/main/java/com/core/framework/web/service/TokenService.java +++ b/healthlink-his-server/core-framework/src/main/java/com/core/framework/web/service/TokenService.java @@ -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()); } diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V42__add_del_flag_to_sys_audit_log.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V42__add_del_flag_to_sys_audit_log.sql deleted file mode 100644 index 64170363a..000000000 --- a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V42__add_del_flag_to_sys_audit_log.sql +++ /dev/null @@ -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; \ No newline at end of file diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V43__add_hisbase_columns_to_clinical_pathway_execution.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V43__add_hisbase_columns_to_clinical_pathway_execution.sql new file mode 100644 index 000000000..cb11a786a --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V43__add_hisbase_columns_to_clinical_pathway_execution.sql @@ -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; \ No newline at end of file diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V44__add_delete_flag_to_prescription_intercept_log.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V44__add_delete_flag_to_prescription_intercept_log.sql new file mode 100644 index 000000000..6facd6ac9 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V44__add_delete_flag_to_prescription_intercept_log.sql @@ -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; \ No newline at end of file