Spring Boot 缓存架构:一行配置切换 Caffeine 与 Redis,透明支持多租户隔离
前言在 Spring Boot 项目中直接注入 RedisTemplate 往往会把业务逻辑绑死在 Redis 上当你想切换本地缓存或升级缓存架构时业务代码就需要大面积改动。本文基于 Spring Cache 抽象与条件装配落地一套可插拔缓存方案一行配置即可在 Caffeine 与 Redis 之间切换业务代码零改动并透明支持多租户缓存隔离。同时针对多租户场景下容易被忽略的“数据隔离”问题我们将通过装饰器模式在框架底层透明地解决确保业务开发人员无需操心 Key 的租户前缀。核心概念Spring Cache 抽象Spring Cache 是 Spring 框架提供的缓存抽象层核心接口有两个CacheManager缓存管理器负责创建和管理 Cache 实例。Cache缓存操作接口提供 get、put、evict、clear 等方法。⚠️ 关键点业务代码只依赖 CacheManager 和 Cache 接口不直接引用 Caffeine 或 Redis 的实现类。这是实现无缝切换的基础。整体架构设计切换缓存只需修改配置文件中的 lanjii.cache.type 值yamllanjii: cache: type: LOCAL # 切换为 REDIS 即可启用 Redis 缓存实现步骤第一步引入依赖在缓存框架模块的 pom.xml 中同时引入 Caffeine 和 Redis 依赖xmldependencies !-- 内部依赖 -- dependency groupIdcom.lanjii/groupId artifactIdframework-context/artifactId /dependency !-- Spring Cache -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency !-- Caffeine -- dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId /dependency !-- Jackson (序列化) -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency dependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId /dependency !-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency /dependencies⚠️ 两个依赖同时存在于 classpath但通过条件装配只会激活其中一个 CacheManager。第二步定义缓存配置属性通过 ConfigurationProperties 将 YAML 配置映射为 Java 对象javapackage com.lanjii.framework.cache.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.time.Duration; Data ConfigurationProperties(prefix lanjii.cache) public class LanjiiCacheProperties { /** * 缓存类型LOCALCaffeine或 REDIS */ private CacheType type CacheType.LOCAL; /** * 是否允许缓存 Null 值防止缓存穿透 */ private boolean cacheNullValues true; /** * 默认过期时间默认 1 小时 */ private Duration defaultTtl Duration.ofHours(1); /** * 默认最大容量仅 Local 模式有效默认 1000 */ private long defaultMaxSize 1000L; public enum CacheType { LOCAL, // 本地缓存 (Caffeine) REDIS // 分布式缓存 (Redis) } }各字段说明type核心切换开关LOCAL 表示使用 CaffeineREDIS 表示使用 Redis。默认 LOCAL。cacheNullValues是否允许缓存空值。设为 true 可防止缓存穿透大量请求查询不存在的 key 直接打到数据库。defaultTtl全局默认过期时间。支持 Spring 的 Duration 格式如 1h、30m、7d。defaultMaxSizeCaffeine 本地缓存的最大条目数。超过后按 W-TinyLFU 算法淘汰。Redis 模式下此配置无效。第三步定义缓存元数据CacheDef每个缓存实例都有自己的 TTL过期时间、最大容量、是否需要租户隔离等属性。我们用一个 CacheDef 类来封装这些元数据arduinopackage com.lanjii.framework.cache.core; import java.time.Duration; public class CacheDef { private final String name; // 缓存名称 private final Duration ttl; // 过期时间 private final long maxSize; // 最大容量仅 Local 模式 private final boolean tenantIsolated; // 是否按租户隔离 private CacheDef(String name, Duration ttl, long maxSize, boolean tenantIsolated) { this.name name; this.ttl ttl; this.maxSize maxSize; this.tenantIsolated tenantIsolated; } // 快捷工厂方法 public static CacheDef of(String name, Duration ttl) { return new CacheDef(name, ttl, 1000L, true); } public static CacheDef of(String name, Duration ttl, long maxSize) { return new CacheDef(name, ttl, maxSize, true); } public static CacheDef of(String name, Duration ttl, boolean tenantIsolated) { return new CacheDef(name, ttl, 1000L, tenantIsolated); } public static CacheDef of(String name, Duration ttl, long maxSize, boolean tenantIsolated) { return new CacheDef(name, ttl, maxSize, tenantIsolated); } // getter 省略 }CacheDef 采用静态工厂方法而非 Builder 模式因为缓存定义通常是常量参数固定工厂方法更简洁。第四步缓存注册表CacheRegistryCacheRegistry 作为缓存定义的中央注册表业务模块在启动时将自己的 CacheDef 注册进来arduinopackage com.lanjii.framework.cache.core; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class CacheRegistry { private final MapString, CacheDef cacheDefMap new ConcurrentHashMap(); /** 注册单个缓存定义 */ public void register(CacheDef cacheDef) { cacheDefMap.put(cacheDef.getName(), cacheDef); } /** 批量注册 */ public void registerAll(CacheDef... cacheDefs) { for (CacheDef cacheDef : cacheDefs) { register(cacheDef); } } /** 根据名称获取缓存定义 */ public OptionalCacheDef get(String name) { return Optional.ofNullable(cacheDefMap.get(name)); } /** 获取所有已注册的缓存定义 */ public CollectionCacheDef getAll() { return cacheDefMap.values(); } }使用 ConcurrentHashMap 保证线程安全因为多个业务模块可能在 PostConstruct 阶段并发注册。第五步真正实现“一行切换”的关键——条件装配这是实现一键切换的关键。通过 ConditionalOnProperty 注解Spring Boot 会根据配置值决定创建哪个 CacheManagerkotlinpackage com.lanjii.framework.cache.config; import com.lanjii.framework.cache.core.CacheRegistry; import com.lanjii.framework.cache.properties.LanjiiCacheProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; AutoConfiguration EnableCaching EnableConfigurationProperties(LanjiiCacheProperties.class) public class LanjiiCacheAutoConfiguration { /** 缓存注册表供业务模块注入 */ Bean ConditionalOnMissingBean public CacheRegistry cacheRegistry() { return new CacheRegistry(); } /** LOCAL 模式 —— Caffeine CacheManager默认 */ Bean ConditionalOnProperty(name lanjii.cache.type, havingValue LOCAL, matchIfMissing true) public CacheManager caffeineCacheManager(CacheRegistry cacheRegistry, LanjiiCacheProperties properties) { return new TenantAwareCaffeineCacheManager(cacheRegistry, properties); } }注意 matchIfMissing true如果配置文件中没有写 lanjii.cache.type默认走 LOCAL 模式。这样即使不额外配置缓存类型系统也能先以本地缓存方式跑起来。Redis 的配置放在单独的类中inipackage com.lanjii.framework.cache.config; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.lanjii.framework.cache.core.CacheDef; import com.lanjii.framework.cache.core.CacheRegistry; import com.lanjii.framework.cache.properties.LanjiiCacheProperties; import com.lanjii.framework.context.tenant.TenantContext; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import java.util.HashMap; import java.util.Map; Configuration ConditionalOnProperty(name lanjii.cache.type, havingValue REDIS) public class RedisCacheConfiguration { Bean ConditionalOnClass(RedisConnectionFactory.class) public CacheManager redisCacheManager(CacheRegistry cacheRegistry, LanjiiCacheProperties properties, RedisConnectionFactory connectionFactory) { // 1. 配置 JSON 序列化支持 Java 8 时间类型 类型信息 ObjectMapper objectMapper new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.activateDefaultTyping( objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); GenericJackson2JsonRedisSerializer serializer new GenericJackson2JsonRedisSerializer(objectMapper); // 2. 默认缓存配置 org.springframework.data.redis.cache.RedisCacheConfiguration defaultConfig org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig() .entryTtl(properties.getDefaultTtl()) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer(serializer)) .computePrefixWith(cacheName - { // 多租户前缀tenantId:cacheName:: Long tenantId TenantContext.getTenantId(); String prefix (tenantId ! null) ? tenantId.toString() : 0; return prefix : cacheName ::; }); if (!properties.isCacheNullValues()) { defaultConfig defaultConfig.disableCachingNullValues(); } // 3. 为每个已注册的缓存设置独立的 TTL MapString, org.springframework.data.redis.cache.RedisCacheConfiguration configMap new HashMap(); for (CacheDef cacheDef : cacheRegistry.getAll()) { configMap.put(cacheDef.getName(), defaultConfig.entryTtl(cacheDef.getTtl())); } return RedisCacheManager.builder(connectionFactory) .cacheDefaults(defaultConfig) .withInitialCacheConfigurations(configMap) .build(); } }Redis 配置中的几个关键设计点GenericJackson2JsonRedisSerializer使用 JSON 序列化而非 Java 默认序列化这样 Redis 中的数据人类可读便于调试。同时注册了 JavaTimeModule 以正确序列化 LocalDateTime 等 Java 8 时间类型。activateDefaultTyping在 JSON 中保留类型信息class 字段反序列化时能还原为正确的 Java 类型。computePrefixWithRedis key 格式为 {tenantId}:{cacheName}::{key}通过租户 ID 前缀实现多租户数据隔离。双重条件装配ConditionalOnProperty 确保只在 typeREDIS 时生效ConditionalOnClass 确保 classpath 中有 Redis 依赖时才创建 Bean。序列化陷阱与安全警示⚠️高危预警activateDefaultTyping 是一个“双刃剑”配置。好处它会在 JSON 中自动嵌入 class 字段如 {class:com.example.User, id:1}这使得反序列化时能自动恢复出多态对象或泛型集合开发极其便利。风险如果 Redis 端口对外暴露或者攻击者能控制 Redis 中的数据他们可以构造恶意 JSON如指向 Runtime.exec 的类在反序列化时触发远程代码执行RCE。生产建议内部系统如果 Redis 仅在内网且有密码保护可以使用此方案开发效率最高。公网系统绝对禁止开启 activateDefaultTyping。替代方案是使用 JsonTypeInfo 注解在特定的 DTO 类上显式声明类型信息或者自定义 RedisSerializer 手动处理泛型转换。Redis Key 设计规范本项目采用冒号分隔的命名空间设计css{tenantId}:{cacheName}::{key} # 示例1001:userInfo::admin这样设计的好处在 Redis GUI 工具中可以按写入时间、租户、缓存名称三个维度展开观察排查问题非常直观。第六步难点攻克——本地缓存的多租户隔离这一步是整个方案的技术难点也是市面上大多数缓存教程没有覆盖的地方。问题在哪里Spring 原生的 CaffeineCacheManager 是全局单例的所有租户共享同一批缓存空间。在多租户 SaaS 场景下如果租户 A 和租户 B 的用户恰好有相同的 key如用户名 admin就会发生数据串读——A 租户查到了 B 租户的缓存数据。Redis 模式相对容易处理通过 computePrefixWith 在 key 上添加租户前缀但本地缓存没有类似的官方支持需要自己动手解决。解决思路装饰器模式对 CaffeineCacheManager 返回的 Cache 实例进行包装用 TenantAwareCache 装饰器在每次 key 操作时自动注入租户前缀业务层完全无感知csharp业务层调用 cache.get(admin) ↓ TenantAwareCache 拦截生成 tenantKey 1001:admin ↓ 实际操作底层 CaffeinecaffeineCache.get(1001:admin)本地缓存模式下所有租户数据都在同一个 JVM 内存中需要通过 Key 前缀来隔离这一段代码只需要重点关注 3 件事如何根据 CacheDef 动态创建缓存如何判断当前缓存是否需要租户隔离如何在 key 层自动追加租户前缀typescriptpackage com.lanjii.framework.cache.config; import com.github.benmanes.caffeine.cache.Caffeine; import com.lanjii.framework.cache.core.CacheDef; import com.lanjii.framework.cache.core.CacheRegistry; import com.lanjii.framework.cache.properties.LanjiiCacheProperties; import com.lanjii.framework.context.tenant.TenantContext; import org.springframework.cache.Cache; import org.springframework.cache.caffeine.CaffeineCacheManager; import java.util.concurrent.Callable; public class TenantAwareCaffeineCacheManager extends CaffeineCacheManager { private final CacheRegistry cacheRegistry; private final LanjiiCacheProperties properties; public TenantAwareCaffeineCacheManager(CacheRegistry cacheRegistry, LanjiiCacheProperties properties) { this.cacheRegistry cacheRegistry; this.properties properties; this.setAllowNullValues(properties.isCacheNullValues()); } Override public Cache getCache(String name) { Cache delegate super.getCache(name); // 缓存不存在时根据 CacheRegistry 或默认值动态创建 if (delegate null) { delegate createAndRegisterCache(name); } if (delegate null) { return null; } // 需要租户隔离的缓存包装为 TenantAwareCache CacheDef cacheDef cacheRegistry.get(name).orElse(null); if (cacheDef ! null cacheDef.isTenantIsolated()) { return new TenantAwareCache(delegate); } return delegate; } /** 动态创建并注册缓存 */ private synchronized Cache createAndRegisterCache(String name) { Cache existing super.getCache(name); if (existing ! null) { return existing; // 双重检查 } CacheDef cacheDef cacheRegistry.get(name).orElse(null); CaffeineObject, Object builder Caffeine.newBuilder(); if (cacheDef ! null) { builder.expireAfterWrite(cacheDef.getTtl()) .maximumSize(cacheDef.getMaxSize()); } else { builder.expireAfterWrite(properties.getDefaultTtl()) .maximumSize(properties.getDefaultMaxSize()); } this.registerCustomCache(name, builder.build()); return super.getCache(name); } /** 租户感知的 Cache 装饰器对所有 Key 自动添加租户前缀 */ static class TenantAwareCache implements Cache { private final Cache delegate; TenantAwareCache(Cache delegate) { this.delegate delegate; } private Object createTenantKey(Object key) { Long tenantId TenantContext.getTenantId(); String prefix (tenantId ! null) ? tenantId.toString() : 0; return prefix : key; } Override public String getName() { return delegate.getName(); } Override public Object getNativeCache() { return delegate.getNativeCache(); } Override public ValueWrapper get(Object key) { return delegate.get(createTenantKey(key)); } Override public T T get(Object key, ClassT type) { return delegate.get(createTenantKey(key), type); } Override public T T get(Object key, CallableT valueLoader) { return delegate.get(createTenantKey(key), valueLoader); } Override public void put(Object key, Object value) { delegate.put(createTenantKey(key), value); } Override public void evict(Object key) { delegate.evict(createTenantKey(key)); } Override public boolean evictIfPresent(Object key) { return delegate.evictIfPresent(createTenantKey(key)); } Override public void clear() { delegate.clear(); } Override public boolean invalidate() { return delegate.invalidate(); } Override public ValueWrapper putIfAbsent(Object key, Object value) { return delegate.putIfAbsent(createTenantKey(key), value); } } }这里使用了装饰器模式TenantAwareCache 包装了原始的 Cache在每次操作时自动给 Key 加上 {tenantId}: 前缀从而实现同一缓存空间内的多租户数据隔离。⚠️ 注意 isTenantIsolated 是 CacheDef 中的可选项。对于字典数据、系统配置这类全局共享数据所有租户共用同一份应设置为 false跳过租户前缀包装避免缓存被重复存储 N 份浪费内存。多租户使用注意事项⚠️ 在使用多租户缓存隔离时请务必注意以下几点TenantContext 必须提前设置整套租户隔离机制依赖 TenantContext.getTenantId() 获取当前租户 ID。你需要在请求链路的入口处通常是 Filter 或 Interceptor设置好租户上下文否则所有缓存操作都会落入 0: 默认命名空间导致不同租户的数据混在一起。clear() 会清除所有租户的数据TenantAwareCache.clear() 调用的是底层 Caffeine 缓存的 clear()会清空该缓存名下所有租户的数据而不仅仅是当前租户。如果只想清除当前租户的缓存应该逐个 evict 已知的 key。非多租户项目可跳过如果你的项目不涉及多租户将所有 CacheDef 的 tenantIsolated 设为 false 即可框架会跳过租户前缀逻辑零额外开销。第七步缓存辅助工具CacheHelperCacheHelper 封装了 Spring Cache 接口未覆盖的扩展能力javapackage com.lanjii.framework.cache.helper; import com.lanjii.framework.cache.core.CacheDef; import com.lanjii.framework.cache.core.CacheRegistry; import com.lanjii.framework.context.tenant.TenantContext; import lombok.RequiredArgsConstructor; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Collections; import java.util.stream.Collectors; Component RequiredArgsConstructor public class CacheHelper { private final CacheManager cacheManager; private final CacheRegistry cacheRegistry; /** * 获取指定缓存的所有值仅 Local 模式可用 * Redis 模式需使用 SCAN 命令另行实现 */ SuppressWarnings(unchecked) public T CollectionT getAllValues(String cacheName, ClassT type) { Cache cache cacheManager.getCache(cacheName); if (cache null) return Collections.emptyList(); Object nativeCache cache.getNativeCache(); if (nativeCache instanceof com.github.benmanes.caffeine.cache.Cache) { var caffeineCache (com.github.benmanes.caffeine.cache.CacheObject, Object) nativeCache; CacheDef cacheDef cacheRegistry.get(cacheName).orElse(null); boolean tenantIsolated cacheDef null || cacheDef.isTenantIsolated(); if (tenantIsolated) { Long tenantId TenantContext.getTenantId(); String tenantPrefix (tenantId ! null ? tenantId.toString() : 0) :; return caffeineCache.asMap().entrySet().stream() .filter(entry - entry.getKey().toString().startsWith(tenantPrefix)) .map(entry - (T) entry.getValue()) .collect(Collectors.toList()); } else { return (CollectionT) caffeineCache.asMap().values(); } } throw new UnsupportedOperationException(getAllValues is only supported in LOCAL mode); } /** 清空指定缓存 */ public void clearAll(String cacheName) { Cache cache cacheManager.getCache(cacheName); if (cache ! null) cache.clear(); } /** 获取缓存实例 */ public Cache getCache(String cacheName) { return cacheManager.getCache(cacheName); } }⚠️ getAllValues 方法在 Redis 模式下不可用。如果需要遍历 Redis 缓存数据应使用 RedisTemplate 配合 SCAN 命令实现避免使用 KEYS * 造成阻塞。第八步别忘了让 Spring Boot 识别这套缓存框架在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中注册自动配置类arduinocom.lanjii.framework.cache.config.LanjiiCacheAutoConfigurationSpring Boot 3.x 使用此文件替代了旧版的 spring.factories。业务模块接入框架层搭建完成后业务模块的接入非常简单分为三步。1. 声明缓存常量inipackage com.lanjii.sys.config; import com.lanjii.framework.cache.core.CacheDef; import java.time.Duration; public interface SystemCacheConstants { /** 字典数据缓存全租户共享 */ CacheDef DICT_DATA CacheDef.of(dictData, Duration.ofHours(24), false); /** 系统配置缓存全租户共享 */ CacheDef SYS_CONFIG CacheDef.of(sysConfig, Duration.ofDays(7), false); /** 用户会话缓存全租户共享 */ CacheDef USER_SESSION CacheDef.of(userSession, Duration.ofHours(24), false); /** 用户信息缓存租户隔离 */ CacheDef USER_INFO CacheDef.of(userInfo, Duration.ofHours(24)); /** 验证码缓存租户隔离 */ CacheDef CAPTCHA CacheDef.of(captcha, Duration.ofMinutes(5)); }注意 tenantIsolated 参数的使用false如字典数据、系统配置属于全局共享数据不需要按租户隔离。true默认如用户信息、验证码每个租户的数据相互独立。2. 启动时注册缓存kotlinpackage com.lanjii.sys.config; import com.lanjii.framework.cache.core.CacheRegistry; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; Configuration RequiredArgsConstructor public class SystemCacheConfig { private final CacheRegistry cacheRegistry; PostConstruct public void registerCaches() { cacheRegistry.registerAll( SystemCacheConstants.DICT_DATA, SystemCacheConstants.SYS_CONFIG, SystemCacheConstants.USER_SESSION, SystemCacheConstants.USER_INFO, SystemCacheConstants.CAPTCHA ); } }3. 在 Service 中使用缓存以系统配置服务为例arduinoService(sysConfigService) RequiredArgsConstructor public class SysConfigServiceImpl extends BaseServiceImplSysConfigDao, SysConfig implements SysConfigService { private final CacheManager cacheManager; private final CacheHelper cacheHelper; private Cache getConfigCache() { return cacheManager.getCache(SystemCacheConstants.SYS_CONFIG.getName()); } /** 根据配置键获取配置先查缓存再查数据库 */ public SysConfig getConfigByKey(String configKey) { Cache cache getConfigCache(); // 1. 先从缓存获取 SysConfig config cache.get(configKey, SysConfig.class); if (config ! null) { return config; } // 2. 缓存未命中查询数据库 config getOne(new LambdaQueryWrapperSysConfig() .eq(SysConfig::getConfigKey, configKey)); // 3. 写入缓存 if (config ! null) { cache.put(configKey, config); } return config; } /** 更新配置时清除缓存 */ Override public void updateByIdNew(Long id, SysConfigDTO dto) { SysConfig originalConfig getById(id); // ... 省略校验逻辑 updateById(entity); getConfigCache().evict(originalConfig.getConfigKey()); // 清除旧缓存 } /** 手动清空全部配置缓存 */ Override public void clearCache() { cacheHelper.clearAll(SystemCacheConstants.SYS_CONFIG.getName()); } }可以看到Service 层代码只使用了 CacheManager 和 Cache 接口完全不关心底层是 Caffeine 还是 Redis。切换缓存类型时这里的代码无需做任何修改。架构模式切换这套方案的强大之处在于你可以根据项目的实际部署需求灵活选择架构模式而无需改动代码。模式一轻量级单机模式 (Local)适合单体应用、私有化部署的小型环境或对延迟极度敏感的场景。此时应用完全独立不依赖任何外部缓存组件。yamllanjii: cache: type: LOCAL模式二分布式集群模式 (Redis)适合微服务架构、多实例部署或需要数据持久化的场景。此时通过 Redis 实现多节点间的数据共享。yamllanjii: cache: type: REDIS源码与在线体验完整源码gitee.com/leven2018/l…欢迎 Star ⭐ 和 Fork项目包含本文涉及的所有代码MCP 集成、多模型动态切换、RAG 知识库等。在线体验http://106.54.167.194/admin/index总结通过这套架构我们成功实现了彻底解耦业务代码只认识 CacheManager完全不知道底层是 Caffeine 还是 Redis。一键切换只需改一行配置就能在单元测试Local和生产环境Redis之间无缝切换。多租户安全通过装饰器模式和 Key 前缀策略在本地和分布式环境下都实现了透明的数据隔离。

相关新闻

RexUniNLU参数调优指南:提升模型性能的关键技巧

RexUniNLU参数调优指南:提升模型性能的关键技巧

RexUniNLU参数调优指南:提升模型性能的关键技巧 1. 引言 如果你正在使用RexUniNLU这个强大的中文自然语言理解模型,可能会遇到这样的困惑:为什么同样的模型,别人用起来效果很好,自己用却总觉得差那么一点&#xff1f…

2026/5/17 10:42:31 阅读更多 →
开源技术赋能老旧设备:价值重构与效能优化全指南

开源技术赋能老旧设备:价值重构与效能优化全指南

开源技术赋能老旧设备:价值重构与效能优化全指南 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 价值重构:老旧设备的可持续发展路径 在技术迭代加…

2026/5/17 10:42:30 阅读更多 →
Coze智能客服工作流实战:从架构设计到性能调优

Coze智能客服工作流实战:从架构设计到性能调优

在构建现代智能客服系统时,开发团队常常面临几个核心挑战:如何在高并发下保持对话上下文的连贯性?如何确保意图识别的准确率与响应速度?以及如何设计一个易于维护和扩展的系统架构?传统的基于规则或简单API拼接的方案&…

2026/5/17 10:42:30 阅读更多 →

最新新闻

专业显卡优化工具NVIDIA Profile Inspector:解锁游戏性能的6个关键步骤

专业显卡优化工具NVIDIA Profile Inspector:解锁游戏性能的6个关键步骤

专业显卡优化工具NVIDIA Profile Inspector:解锁游戏性能的6个关键步骤 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款专业的开源显卡驱动配置工具&#xf…

2026/7/6 8:39:52 阅读更多 →
新手入门:5分钟学会使用openEuler迁移助手

新手入门:5分钟学会使用openEuler迁移助手

新手入门:5分钟学会使用openEuler迁移助手 【免费下载链接】migration-assistant Migration assistant helps users migrate business applications from other Linux hairstyles to openEuler OS. 项目地址: https://gitcode.com/openeuler/migration-assistant …

2026/7/6 8:33:50 阅读更多 →
MIL 增量学习实战:基于注意力与原型映射,3数据集F1分数提升2-4%

MIL 增量学习实战:基于注意力与原型映射,3数据集F1分数提升2-4%

MIL增量学习实战:基于注意力与原型映射的性能突破1. 前沿交叉领域的创新实践在动态数据环境下,多示例学习(MIL)与增量学习的结合正成为解决现实问题的关键技术。传统MIL方法在静态封闭环境中表现优异,但当面对持续新增…

2026/7/6 8:33:50 阅读更多 →
Cantian connector for MySQL故障排查手册:常见问题与解决方案

Cantian connector for MySQL故障排查手册:常见问题与解决方案

Cantian connector for MySQL故障排查手册:常见问题与解决方案 【免费下载链接】cantian-connector-mysql Cantian connector for MySQL is a MySQL storage engine plugin. It is capable of forming MySQL instances into a multi-read, multi-write transparent …

2026/7/6 8:31:50 阅读更多 →
如何快速上手warpdrive:10分钟完成硬件加速环境搭建

如何快速上手warpdrive:10分钟完成硬件加速环境搭建

如何快速上手warpdrive:10分钟完成硬件加速环境搭建 【免费下载链接】libwd 项目地址: https://gitcode.com/openeuler/libwd 前往项目官网免费下载:https://ar.openeuler.org/ar/ warpdrive(libwd)是openEuler生态中的硬…

2026/7/6 8:31:50 阅读更多 →
NestOS-Config性能优化指南:从内核配置到系统调优的10个关键技巧

NestOS-Config性能优化指南:从内核配置到系统调优的10个关键技巧

NestOS-Config性能优化指南:从内核配置到系统调优的10个关键技巧 【免费下载链接】nestos-config nestos-config provides base manifest configuration for building NestOS. 项目地址: https://gitcode.com/openeuler/nestos-config 前往项目官网免费下载&…

2026/7/6 8:31:50 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻