引入caffeine本地缓存

This commit is contained in:
数据小王子 2024-02-28 14:40:14 +08:00
parent f366e430f1
commit 6d484c0bda
11 changed files with 197 additions and 36 deletions

View File

@ -22,6 +22,7 @@
<satoken.version>1.37.0</satoken.version> <satoken.version>1.37.0</satoken.version>
<HikariCP.version>5.1.0</HikariCP.version> <HikariCP.version>5.1.0</HikariCP.version>
<bitwalker.version>1.21</bitwalker.version> <bitwalker.version>1.21</bitwalker.version>
<caffeine.version>3.1.8</caffeine.version>
<kaptcha.version>2.3.3</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.version>6.1.0</pagehelper.version> <pagehelper.version>6.1.0</pagehelper.version>
<fastjson.version>2.0.43</fastjson.version> <fastjson.version>2.0.43</fastjson.version>
@ -172,6 +173,13 @@
<version>${satoken.version}</version> <version>${satoken.version}</version>
</dependency> </dependency>
<!-- caffeine缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!-- servlet包 --> <!-- servlet包 -->
<dependency> <dependency>
<groupId>jakarta.servlet</groupId> <groupId>jakarta.servlet</groupId>

View File

@ -3,6 +3,7 @@ package com.ruoyi;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/** /**
* 启动程序 * 启动程序
@ -14,7 +15,9 @@ public class RuoYiApplication
{ {
public static void main(String[] args) public static void main(String[] args)
{ {
SpringApplication.run(RuoYiApplication.class, args); SpringApplication application = new SpringApplication(RuoYiApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex-Boot启动成功 ლ(´ڡ`ლ)゙ \n" + System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex-Boot启动成功 ლ(´ڡ`ლ)゙ \n" +
" ███████ ██ ██ ██ ████████ ██ \n" + " ███████ ██ ██ ██ ████████ ██ \n" +
"░██░░░░██ ░░██ ██ ░░ ░██░░░░░ ░██ \n" + "░██░░░░██ ░░██ ██ ░░ ░██░░░░░ ░██ \n" +

View File

@ -46,6 +46,12 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
</dependency> </dependency>
<!-- caffeine缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,45 @@
package com.ruoyi.common.redis.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ruoyi.common.redis.manager.FlexSpringCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.TimeUnit;
/**
* 缓存配置
*
* @author Lion Li
*/
@AutoConfiguration
@EnableCaching
public class CacheConfig {
/**
* caffeine 本地缓存处理器
*/
@Bean
public Cache<Object, Object> caffeine() {
return Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(30, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
}
/**
* 自定义缓存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new FlexSpringCacheManager();
}
}

View File

@ -5,8 +5,8 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.redis.handler.KeyPrefixHandler; import com.ruoyi.common.redis.handler.KeyPrefixHandler;
import com.ruoyi.common.redis.manager.FlexSpringCacheManager;
import com.ruoyi.common.redis.config.properties.RedissonProperties; import com.ruoyi.common.redis.config.properties.RedissonProperties;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.redisson.client.codec.StringCodec; import org.redisson.client.codec.StringCodec;
@ -16,22 +16,17 @@ import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* redis配置 * redis配置
* *
* @author Lion Li * @author Lion Li
*/ */
@Slf4j
@AutoConfiguration @AutoConfiguration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class) @EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig { public class RedisConfig {
private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);
@Autowired @Autowired
private RedissonProperties redissonProperties; private RedissonProperties redissonProperties;
@Resource @Resource
@ -86,14 +81,6 @@ public class RedisConfig {
}; };
} }
/**
* 自定义缓存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new FlexSpringCacheManager();
}
/** /**
* redis集群配置 yml * redis集群配置 yml
* *

View File

@ -0,0 +1,88 @@
package com.ruoyi.common.redis.manager;
import com.ruoyi.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
/**
* Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
*
* @author LionLi
*/
public class CaffeineCacheDecorator implements Cache {
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
CAFFEINE = SpringUtils.getBean("caffeine");
private final Cache cache;
public CaffeineCacheDecorator(Cache cache) {
this.cache = cache;
}
@Override
public String getName() {
return cache.getName();
}
@Override
public Object getNativeCache() {
return cache.getNativeCache();
}
public String getUniqueKey(Object key) {
return cache.getName() + ":" + key;
}
@Override
public ValueWrapper get(Object key) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
return (ValueWrapper) o;
}
@SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
return (T) o;
}
@Override
public void put(Object key, Object value) {
cache.put(key, value);
}
public ValueWrapper putIfAbsent(Object key, Object value) {
return cache.putIfAbsent(key, value);
}
@Override
public void evict(Object key) {
evictIfPresent(key);
}
public boolean evictIfPresent(Object key) {
boolean b = cache.evictIfPresent(key);
if (b) {
CAFFEINE.invalidate(getUniqueKey(key));
}
return b;
}
@Override
public void clear() {
cache.clear();
}
public boolean invalidate() {
return cache.invalidate();
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
return (T) o;
}
}

View File

@ -33,7 +33,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
/** /**
* A {@link org.springframework.cache.CacheManager} implementation * A {@link CacheManager} implementation
* backed by Redisson instance. * backed by Redisson instance.
* <p> * <p>
* 修改 RedissonSpringCacheManager 源码 * 修改 RedissonSpringCacheManager 源码
@ -156,7 +156,7 @@ public class FlexSpringCacheManager implements CacheManager {
private Cache createMap(String name, CacheConfig config) { private Cache createMap(String name, CacheConfig config) {
RMap<Object, Object> map = RedisUtils.getClient().getMap(name); RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
Cache cache = new RedissonCache(map, allowNullValues); Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
if (transactionAware) { if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache); cache = new TransactionAwareCacheDecorator(cache);
} }
@ -170,7 +170,7 @@ public class FlexSpringCacheManager implements CacheManager {
private Cache createMapCache(String name, CacheConfig config) { private Cache createMapCache(String name, CacheConfig config) {
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name); RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
Cache cache = new RedissonCache(map, config, allowNullValues); Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
if (transactionAware) { if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache); cache = new TransactionAwareCacheDecorator(cache);
} }

View File

@ -1 +1,2 @@
com.ruoyi.common.redis.config.RedisConfig com.ruoyi.common.redis.config.RedisConfig
com.ruoyi.common.redis.config.CacheConfig

View File

@ -41,6 +41,12 @@
<artifactId>sa-token-jwt</artifactId> <artifactId>sa-token-jwt</artifactId>
</dependency> </dependency>
<!-- caffeine缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -2,12 +2,16 @@ package com.ruoyi.common.security.core.dao;
import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
import cn.hutool.core.lang.Console;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ruoyi.common.redis.utils.RedisUtils; import com.ruoyi.common.redis.utils.RedisUtils;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
/** /**
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
@ -16,12 +20,23 @@ import java.util.List;
*/ */
public class FlexSaTokenDao implements SaTokenDao { public class FlexSaTokenDao implements SaTokenDao {
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(5, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
/** /**
* 获取Value如无返空 * 获取Value如无返空
*/ */
@Override @Override
public String get(String key) { public String get(String key) {
return RedisUtils.getCacheObject(key); Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
Console.log("caffeine -> key:" + key + ",value:" + o);
return (String) o;
} }
/** /**
@ -38,6 +53,7 @@ public class FlexSaTokenDao implements SaTokenDao {
} else { } else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
} }
CAFFEINE.put(key, value);
} }
/** /**
@ -47,6 +63,7 @@ public class FlexSaTokenDao implements SaTokenDao {
public void update(String key, String value) { public void update(String key, String value) {
if (RedisUtils.hasKey(key)) { if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, value, true); RedisUtils.setCacheObject(key, value, true);
CAFFEINE.put(key, value);
} }
} }
@ -81,7 +98,9 @@ public class FlexSaTokenDao implements SaTokenDao {
*/ */
@Override @Override
public Object getObject(String key) { public Object getObject(String key) {
return RedisUtils.getCacheObject(key); Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
Console.log("caffeine -> key:" + key + ",value:" + o);
return o;
} }
/** /**
@ -98,6 +117,7 @@ public class FlexSaTokenDao implements SaTokenDao {
} else { } else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
} }
CAFFEINE.put(key, object);
} }
/** /**
@ -107,6 +127,7 @@ public class FlexSaTokenDao implements SaTokenDao {
public void updateObject(String key, Object object) { public void updateObject(String key, Object object) {
if (RedisUtils.hasKey(key)) { if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, object, true); RedisUtils.setCacheObject(key, object, true);
CAFFEINE.put(key, object);
} }
} }
@ -139,10 +160,14 @@ public class FlexSaTokenDao implements SaTokenDao {
/** /**
* 搜索数据 * 搜索数据
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) { public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*"); String keyStr = prefix + "*" + keyword + "*";
List<String> list = new ArrayList<>(keys); return (List<String>) CAFFEINE.get(keyStr, k -> {
return SaFoxUtil.searchList(list, start, size, sortType); Collection<String> keys = RedisUtils.keys(keyStr);
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
});
} }
} }

View File

@ -236,16 +236,12 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeMapper, S
* @param separator 分隔符 * @param separator 分隔符
* @return 字典标签 * @return 字典标签
*/ */
@SuppressWarnings("unchecked cast")
@Override @Override
public String getDictLabel(String dictType, String dictValue, String separator) { public String getDictLabel(String dictType, String dictValue, String separator) {
// 优先从本地缓存获取 List<SysDictDataVo> lists = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
List<SysDictDataVo> lists = (List<SysDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
if (ObjectUtil.isNull(lists)) { if (ObjectUtil.isNull(lists)) {
lists = SpringUtils.getAopProxy(this).selectDictDataByType(dictType); return StringUtils.EMPTY;
SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, lists);
} }
Map<String, String> map = StreamUtils.toMap(lists, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel); Map<String, String> map = StreamUtils.toMap(lists, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
if (StringUtils.containsAny(dictValue, separator)) { if (StringUtils.containsAny(dictValue, separator)) {
return Arrays.stream(dictValue.split(separator)) return Arrays.stream(dictValue.split(separator))
@ -264,16 +260,12 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeMapper, S
* @param separator 分隔符 * @param separator 分隔符
* @return 字典值 * @return 字典值
*/ */
@SuppressWarnings("unchecked cast")
@Override @Override
public String getDictValue(String dictType, String dictLabel, String separator) { public String getDictValue(String dictType, String dictLabel, String separator) {
// 优先从本地缓存获取 List<SysDictDataVo> lists = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
List<SysDictDataVo> lists = (List<SysDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
if (ObjectUtil.isNull(lists)) { if (ObjectUtil.isNull(lists)) {
lists = SpringUtils.getAopProxy(this).selectDictDataByType(dictType); return StringUtils.EMPTY;
SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, lists);
} }
Map<String, String> map = StreamUtils.toMap(lists, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue); Map<String, String> map = StreamUtils.toMap(lists, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
if (StringUtils.containsAny(dictLabel, separator)) { if (StringUtils.containsAny(dictLabel, separator)) {
return Arrays.stream(dictLabel.split(separator)) return Arrays.stream(dictLabel.split(separator))