diff --git a/pom.xml b/pom.xml index 493579a..b99fa29 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 4.1.2 2.3 0.9.1 + 3.1.8 @@ -218,6 +219,13 @@ ${ruoyi.version} + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java index 7b97de2..14e8645 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -1,15 +1,20 @@ package com.ruoyi.web.controller.monitor; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.core.domain.AjaxResult; @@ -27,6 +32,9 @@ public class CacheController @Autowired private RedisTemplate redisTemplate; + @Autowired + private CacheManager cacheManager; + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") @GetMapping() public AjaxResult getInfo() throws Exception @@ -50,4 +58,62 @@ public class CacheController result.put("commandStats", pieList); return AjaxResult.success(result); } + + /** + * 获取Spring Cache缓存列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/names") + public AjaxResult getCacheNames() + { + Collection cacheNames = cacheManager.getCacheNames(); + List> result = new ArrayList<>(); + for (String cacheName : cacheNames) + { + Cache cache = cacheManager.getCache(cacheName); + Map cacheInfo = new HashMap<>(); + cacheInfo.put("cacheName", cacheName); + if (cache != null) + { + cacheInfo.put("nativeCache", cache.getNativeCache().getClass().getSimpleName()); + } + result.add(cacheInfo); + } + return AjaxResult.success(result); + } + + /** + * 清空指定缓存 + */ + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clear/{cacheName}") + public AjaxResult clearCache(@PathVariable String cacheName) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + cache.clear(); + return AjaxResult.success("缓存已清空: " + cacheName); + } + return AjaxResult.error("缓存不存在: " + cacheName); + } + + /** + * 清空所有Spring Cache缓存 + */ + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearAll") + public AjaxResult clearAllCache() + { + Collection cacheNames = cacheManager.getCacheNames(); + for (String cacheName : cacheNames) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + cache.clear(); + } + } + return AjaxResult.success("所有缓存已清空"); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/cache/CacheUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/cache/CacheUtils.java new file mode 100644 index 0000000..4943fc4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/cache/CacheUtils.java @@ -0,0 +1,96 @@ +package com.ruoyi.common.core.cache; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +/** + * 缓存工具类 + * + * @author ruoyi + */ +@Component +public class CacheUtils +{ + @Autowired + private CacheManager cacheManager; + + /** + * 获取缓存 + * + * @param cacheName 缓存名称 + * @return Cache对象 + */ + public Cache getCache(String cacheName) + { + return cacheManager.getCache(cacheName); + } + + /** + * 获取缓存值 + * + * @param cacheName 缓存名称 + * @param key 缓存键 + * @return 缓存值 + */ + @SuppressWarnings("unchecked") + public T get(String cacheName, Object key) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + Cache.ValueWrapper wrapper = cache.get(key); + if (wrapper != null) + { + return (T) wrapper.get(); + } + } + return null; + } + + /** + * 设置缓存值 + * + * @param cacheName 缓存名称 + * @param key 缓存键 + * @param value 缓存值 + */ + public void put(String cacheName, Object key, Object value) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + cache.put(key, value); + } + } + + /** + * 删除缓存 + * + * @param cacheName 缓存名称 + * @param key 缓存键 + */ + public void evict(String cacheName, Object key) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + cache.evict(key); + } + } + + /** + * 清空缓存 + * + * @param cacheName 缓存名称 + */ + public void clear(String cacheName) + { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) + { + cache.clear(); + } + } +} diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index 8796c3d..bcd38cc 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -59,6 +59,12 @@ ruoyi-system + + + com.github.ben-manes.caffeine + caffeine + + \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java index 833f219..f2b6c7e 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -1,9 +1,14 @@ package com.ruoyi.framework.config; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.ruoyi.framework.config.FastJson2JsonRedisSerializer; +import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; 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.script.DefaultRedisScript; @@ -14,6 +19,9 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + /** * redis配置 * @@ -76,4 +84,52 @@ public class RedisConfig extends CachingConfigurerSupport "end\n" + "return tonumber(current);"; } + + /** + * 本地缓存管理器(Caffeine) + * 作为默认缓存管理器,适用于高频访问、数据量小的场景 + */ + @Bean + @Primary + public CacheManager cacheManager() + { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + cacheManager.setCaffeine(Caffeine.newBuilder() + .initialCapacity(100) + .maximumSize(1000) + .expireAfterWrite(10, TimeUnit.MINUTES) + .recordStats()); + + // 预定义缓存名称 + cacheManager.setCacheNames(Arrays.asList( + "dict", // 字典缓存 + "config", // 配置缓存 + "user", // 用户缓存 + "menu", // 菜单缓存 + "dept" // 部门缓存 + )); + + return cacheManager; + } + + /** + * Redis 缓存管理器 + * 适用于分布式场景,可通过 @Cacheable(cacheManager = "redisCacheManager") 使用 + */ + @Bean("redisCacheManager") + public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) + { + org.springframework.data.redis.cache.RedisCacheConfiguration config = + org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(java.time.Duration.ofMinutes(30)) + .serializeValuesWith( + org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair + .fromSerializer(new FastJson2JsonRedisSerializer<>(Object.class)) + ) + .disableCachingNullValues(); + + return org.springframework.data.redis.cache.RedisCacheManager.builder(connectionFactory) + .cacheDefaults(config) + .build(); + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java index 6f5e5e4..700a7ac 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -12,6 +12,8 @@ import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.service.ISysConfigService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.Collection; @@ -48,6 +50,7 @@ public class SysConfigServiceImpl implements ISysConfigService */ @Override @DataSource(DataSourceType.MASTER) + @Cacheable(value = "config", key = "#configId", unless = "#result == null") public SysConfig selectConfigById(Long configId) { SysConfig config = new SysConfig(); @@ -62,6 +65,7 @@ public class SysConfigServiceImpl implements ISysConfigService * @return 参数键值 */ @Override + @Cacheable(value = "config", key = "#configKey", unless = "#result == null || #result.isEmpty()") public String selectConfigByKey(String configKey) { String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); @@ -115,6 +119,7 @@ public class SysConfigServiceImpl implements ISysConfigService * @return 结果 */ @Override + @CacheEvict(value = "config", allEntries = true) public int insertConfig(SysConfig config) { int row = configMapper.insertConfig(config); @@ -132,6 +137,7 @@ public class SysConfigServiceImpl implements ISysConfigService * @return 结果 */ @Override + @CacheEvict(value = "config", allEntries = true) public int updateConfig(SysConfig config) { int row = configMapper.updateConfig(config); @@ -149,6 +155,7 @@ public class SysConfigServiceImpl implements ISysConfigService * @return 结果 */ @Override + @CacheEvict(value = "config", allEntries = true) public void deleteConfigByIds(Long[] configIds) { for (Long configId : configIds) @@ -180,6 +187,7 @@ public class SysConfigServiceImpl implements ISysConfigService * 清空参数缓存数据 */ @Override + @CacheEvict(value = "config", allEntries = true) public void clearConfigCache() { Collection keys = redisCache.keys(Constants.SYS_CONFIG_KEY + "*"); diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java index 8e75c4f..a1a392e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -10,6 +10,8 @@ import com.ruoyi.system.mapper.SysDictDataMapper; import com.ruoyi.system.mapper.SysDictTypeMapper; import com.ruoyi.system.service.ISysDictTypeService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; @@ -68,6 +70,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService * @return 字典数据集合信息 */ @Override + @Cacheable(value = "dict", key = "#dictType", unless = "#result == null || #result.isEmpty()") public List selectDictDataByType(String dictType) { List dictDatas = DictUtils.getDictCache(dictType); @@ -79,9 +82,8 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService if (StringUtils.isNotEmpty(dictDatas)) { DictUtils.setDictCache(dictType, dictDatas); - return dictDatas; } - return null; + return dictDatas; } /** @@ -115,6 +117,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService * @return 结果 */ @Override + @CacheEvict(value = "dict", allEntries = true) public void deleteDictTypeByIds(Long[] dictIds) { for (Long dictId : dictIds) @@ -156,6 +159,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService * 重置字典缓存数据 */ @Override + @CacheEvict(value = "dict", allEntries = true) public void resetDictCache() { clearDictCache(); @@ -169,6 +173,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService * @return 结果 */ @Override + @CacheEvict(value = "dict", allEntries = true) public int insertDictType(SysDictType dict) { int row = dictTypeMapper.insertDictType(dict); @@ -186,6 +191,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService * @return 结果 */ @Override + @CacheEvict(value = "dict", allEntries = true) @Transactional public int updateDictType(SysDictType dict) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index 27ef86b..cf5f2eb 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -7,6 +7,8 @@ import javax.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -107,6 +109,7 @@ public class SysUserServiceImpl implements ISysUserService * @return 用户对象信息 */ @Override + @Cacheable(value = "user", key = "#userName", unless = "#result == null") public SysUser selectUserByUserName(String userName) { return userMapper.selectUserByUserName(userName); @@ -119,6 +122,7 @@ public class SysUserServiceImpl implements ISysUserService * @return 用户对象信息 */ @Override + @Cacheable(value = "user", key = "#userId", unless = "#result == null") public SysUser selectUserById(Long userId) { return userMapper.selectUserById(userId); @@ -252,6 +256,7 @@ public class SysUserServiceImpl implements ISysUserService * @return 结果 */ @Override + @CacheEvict(value = "user", allEntries = true) @Transactional public int insertUser(SysUser user) { @@ -283,6 +288,7 @@ public class SysUserServiceImpl implements ISysUserService * @return 结果 */ @Override + @CacheEvict(value = "user", key = "#user.userId") @Transactional public int updateUser(SysUser user) { @@ -476,6 +482,7 @@ public class SysUserServiceImpl implements ISysUserService * @return 结果 */ @Override + @CacheEvict(value = "user", allEntries = true) @Transactional public int deleteUserByIds(Long[] userIds) {