mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 10:36:31 +08:00
commit:升级到vue3,更新最近工作流技术栈,支持sa-token
This commit is contained in:
29
OrangeFormsOpen-MybatisFlex/common/common-redis/pom.xml
Normal file
29
OrangeFormsOpen-MybatisFlex/common/common-redis/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>com.orangeforms</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>common-redis</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.orangeforms</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,263 @@
|
||||
package com.orangeforms.common.redis.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.orangeforms.common.core.cache.DictionaryCache;
|
||||
import com.orangeforms.common.core.constant.ApplicationConstant;
|
||||
import com.orangeforms.common.core.exception.RedisCacheAccessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典数据Redis缓存对象。
|
||||
*
|
||||
* @param <K> 字典表主键类型。
|
||||
* @param <V> 字典表对象类型。
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisDictionaryCache<K, V> implements DictionaryCache<K, V> {
|
||||
|
||||
/**
|
||||
* 字典数据前缀,便于Redis工具分组显示。
|
||||
*/
|
||||
protected static final String DICT_PREFIX = "DICT-TABLE:";
|
||||
/**
|
||||
* redisson客户端。
|
||||
*/
|
||||
protected final RedissonClient redissonClient;
|
||||
/**
|
||||
* 数据存储对象。
|
||||
*/
|
||||
protected final RMap<K, String> dataMap;
|
||||
/**
|
||||
* 字典值对象类型。
|
||||
*/
|
||||
protected final Class<V> valueClazz;
|
||||
/**
|
||||
* 获取字典主键数据的函数对象。
|
||||
*/
|
||||
protected final Function<V, K> idGetter;
|
||||
|
||||
/**
|
||||
* 当前对象的构造器函数。
|
||||
*
|
||||
* @param redissonClient Redisson的客户端对象。
|
||||
* @param dictionaryName 字典表的名称。等同于redis hash对象的key。
|
||||
* @param valueClazz 值对象的Class对象。
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
* @param <K> 字典主键类型。
|
||||
* @param <V> 字典对象类型
|
||||
* @return 实例化后的字典内存缓存对象。
|
||||
*/
|
||||
public static <K, V> RedisDictionaryCache<K, V> create(
|
||||
RedissonClient redissonClient,
|
||||
String dictionaryName,
|
||||
Class<V> valueClazz,
|
||||
Function<V, K> idGetter) {
|
||||
if (idGetter == null) {
|
||||
throw new IllegalArgumentException("IdGetter can't be NULL.");
|
||||
}
|
||||
return new RedisDictionaryCache<>(redissonClient, dictionaryName, valueClazz, idGetter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param redissonClient Redisson的客户端对象。
|
||||
* @param dictionaryName 字典表的名称。等同于redis hash对象的key。确保全局唯一。
|
||||
* @param valueClazz 值对象的Class对象。
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
*/
|
||||
public RedisDictionaryCache(
|
||||
RedissonClient redissonClient,
|
||||
String dictionaryName,
|
||||
Class<V> valueClazz,
|
||||
Function<V, K> idGetter) {
|
||||
this.redissonClient = redissonClient;
|
||||
this.dataMap = redissonClient.getMap(
|
||||
DICT_PREFIX + dictionaryName + ApplicationConstant.DICT_CACHE_NAME_SUFFIX);
|
||||
this.valueClazz = valueClazz;
|
||||
this.idGetter = idGetter;
|
||||
}
|
||||
|
||||
protected RMap<K, String> getDataMap() {
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> getAll() {
|
||||
Collection<String> dataList;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
dataList = getDataMap().readAllValues();
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::getAll] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
if (dataList == null) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
return dataList.stream()
|
||||
.map(data -> JSON.parseObject(data, valueClazz))
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> getInList(Set<K> keys) {
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
Collection<String> dataList;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
dataList = getDataMap().getAll(keys).values();
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::getInList] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
if (dataList == null) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
return dataList.stream()
|
||||
.map(data -> JSON.parseObject(data, valueClazz))
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
String data;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
data = getDataMap().get(id);
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::get] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(data, valueClazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return getDataMap().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K id, V data) {
|
||||
if (id == null || data == null) {
|
||||
return;
|
||||
}
|
||||
String exceptionMessage;
|
||||
try {
|
||||
getDataMap().fastPut(id, JSON.toJSONString(data));
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::put] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(List<V> dataList, boolean force) {
|
||||
String exceptionMessage;
|
||||
try {
|
||||
// 如果不强制刷新,需要先判断缓存中是否存在数据。
|
||||
if (!force && this.getCount() > 0) {
|
||||
return;
|
||||
}
|
||||
Map<K, String> map = null;
|
||||
if (CollUtil.isNotEmpty(dataList)) {
|
||||
map = dataList.stream().collect(Collectors.toMap(idGetter, JSON::toJSONString));
|
||||
}
|
||||
RMap<K, String> localDataMap = getDataMap();
|
||||
localDataMap.clear();
|
||||
if (map != null) {
|
||||
localDataMap.putAll(map);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::reload] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V invalidate(K id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
String data;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
data = getDataMap().remove(id);
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::invalidate] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(data, valueClazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void invalidateSet(Set<K> keys) {
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
Object[] keyArray = keys.toArray(new Object[]{});
|
||||
String exceptionMessage;
|
||||
try {
|
||||
getDataMap().fastRemove((K[]) keyArray);
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::invalidateSet] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateAll() {
|
||||
String exceptionMessage;
|
||||
try {
|
||||
getDataMap().clear();
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"[%s::invalidateAll] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
this.getClass().getSimpleName(), e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.orangeforms.common.redis.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.orangeforms.common.core.constant.ApplicationConstant;
|
||||
import com.orangeforms.common.core.exception.RedisCacheAccessException;
|
||||
import com.google.common.collect.LinkedListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.redisson.api.RListMultimap;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 树形字典数据Redis缓存对象。
|
||||
*
|
||||
* @param <K> 字典表主键类型。
|
||||
* @param <V> 字典表对象类型。
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisTreeDictionaryCache<K, V> extends RedisDictionaryCache<K, V> {
|
||||
|
||||
/**
|
||||
* 树形数据存储对象。
|
||||
*/
|
||||
private final RListMultimap<K, String> allTreeMap;
|
||||
/**
|
||||
* 获取字典父主键数据的函数对象。
|
||||
*/
|
||||
protected final Function<V, K> parentIdGetter;
|
||||
|
||||
/**
|
||||
* 当前对象的构造器函数。
|
||||
*
|
||||
* @param redissonClient Redisson的客户端对象。
|
||||
* @param dictionaryName 字典表的名称。等同于redis hash对象的key。
|
||||
* @param valueClazz 值对象的Class对象。
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
* @param parentIdGetter 获取当前类父主键字段值的函数对象。
|
||||
* @param <K> 字典主键类型。
|
||||
* @param <V> 字典对象类型
|
||||
* @return 实例化后的树形字典内存缓存对象。
|
||||
*/
|
||||
public static <K, V> RedisTreeDictionaryCache<K, V> create(
|
||||
RedissonClient redissonClient,
|
||||
String dictionaryName,
|
||||
Class<V> valueClazz,
|
||||
Function<V, K> idGetter,
|
||||
Function<V, K> parentIdGetter) {
|
||||
if (idGetter == null) {
|
||||
throw new IllegalArgumentException("IdGetter can't be NULL.");
|
||||
}
|
||||
if (parentIdGetter == null) {
|
||||
throw new IllegalArgumentException("ParentIdGetter can't be NULL.");
|
||||
}
|
||||
return new RedisTreeDictionaryCache<>(
|
||||
redissonClient, dictionaryName, valueClazz, idGetter, parentIdGetter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param redissonClient Redisson的客户端对象。
|
||||
* @param dictionaryName 字典表的名称。等同于redis hash对象的key。
|
||||
* @param valueClazz 值对象的Class对象。
|
||||
* @param idGetter 获取当前类主键字段值的函数对象。
|
||||
* @param parentIdGetter 获取当前类父主键字段值的函数对象。
|
||||
*/
|
||||
public RedisTreeDictionaryCache(
|
||||
RedissonClient redissonClient,
|
||||
String dictionaryName,
|
||||
Class<V> valueClazz,
|
||||
Function<V, K> idGetter,
|
||||
Function<V, K> parentIdGetter) {
|
||||
super(redissonClient, dictionaryName, valueClazz, idGetter);
|
||||
this.allTreeMap = redissonClient.getListMultimap(
|
||||
DICT_PREFIX + dictionaryName + ApplicationConstant.TREE_DICT_CACHE_NAME_SUFFIX);
|
||||
this.parentIdGetter = parentIdGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> getListByParentId(K parentId) {
|
||||
List<String> dataList;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
dataList = allTreeMap.get(parentId);
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisTreeDictionaryCache::getListByParentId] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
if (CollUtil.isEmpty(dataList)) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
return dataList.stream().map(data -> JSON.parseObject(data, valueClazz)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(List<V> dataList, boolean force) {
|
||||
String exceptionMessage;
|
||||
try {
|
||||
// 如果不强制刷新,需要先判断缓存中是否存在数据。
|
||||
if (!force && this.getCount() > 0) {
|
||||
return;
|
||||
}
|
||||
dataMap.clear();
|
||||
allTreeMap.clear();
|
||||
if (CollUtil.isEmpty(dataList)) {
|
||||
return;
|
||||
}
|
||||
Map<K, String> map = dataList.stream().collect(Collectors.toMap(idGetter, JSON::toJSONString));
|
||||
// 这里现在本地内存构建树形数据关系,然后再批量存入到Redis缓存。
|
||||
// 以便减少与Redis的交互,同时提升运行时效率。
|
||||
Multimap<K, String> treeMap = LinkedListMultimap.create();
|
||||
for (V data : dataList) {
|
||||
treeMap.put(parentIdGetter.apply(data), JSON.toJSONString(data));
|
||||
}
|
||||
dataMap.putAll(map, 3000);
|
||||
for (Map.Entry<K, Collection<String>> entry : treeMap.asMap().entrySet()) {
|
||||
allTreeMap.putAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisDictionaryCache::reload] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K id, V data) {
|
||||
if (id == null || data == null) {
|
||||
return;
|
||||
}
|
||||
String stringData = JSON.toJSONString(data);
|
||||
K parentId = parentIdGetter.apply(data);
|
||||
String exceptionMessage;
|
||||
try {
|
||||
String oldData = dataMap.put(id, stringData);
|
||||
if (oldData != null) {
|
||||
allTreeMap.remove(parentId, oldData);
|
||||
}
|
||||
allTreeMap.put(parentId, stringData);
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisTreeDictionaryCache::put] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V invalidate(K id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
V data = null;
|
||||
String exceptionMessage;
|
||||
try {
|
||||
String stringData = dataMap.remove(id);
|
||||
if (stringData != null) {
|
||||
data = JSON.parseObject(stringData, valueClazz);
|
||||
K parentId = parentIdGetter.apply(data);
|
||||
allTreeMap.remove(parentId, stringData);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisTreeDictionaryCache::invalidate] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateSet(Set<K> keys) {
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
String exceptionMessage;
|
||||
try {
|
||||
keys.forEach(id -> {
|
||||
if (id != null) {
|
||||
String stringData = dataMap.remove(id);
|
||||
if (stringData != null) {
|
||||
K parentId = parentIdGetter.apply(JSON.parseObject(stringData, valueClazz));
|
||||
allTreeMap.remove(parentId, stringData);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisTreeDictionaryCache::invalidateSet] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateAll() {
|
||||
String exceptionMessage;
|
||||
try {
|
||||
dataMap.clear();
|
||||
allTreeMap.clear();
|
||||
} catch (Exception e) {
|
||||
exceptionMessage = String.format(
|
||||
"Operation of [RedisTreeDictionaryCache::invalidateAll] encountered EXCEPTION [%s] for DICT [%s].",
|
||||
e.getClass().getSimpleName(), valueClazz.getSimpleName());
|
||||
log.warn(exceptionMessage);
|
||||
throw new RedisCacheAccessException(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.orangeforms.common.redis.cache;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.spring.cache.CacheConfig;
|
||||
import org.redisson.spring.cache.RedissonSpringCacheManager;
|
||||
import org.springframework.cache.CacheManager;
|
||||
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 java.util.Map;
|
||||
|
||||
/**
|
||||
* 使用Redisson作为Redis的分布式缓存库。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedissonCacheConfig {
|
||||
|
||||
private static final int DEFAULT_TTL = 3600000;
|
||||
|
||||
/**
|
||||
* 定义cache名称、超时时长(毫秒)。
|
||||
*/
|
||||
public enum CacheEnum {
|
||||
/**
|
||||
* session下上传文件名的缓存(时间是24小时)。
|
||||
*/
|
||||
UPLOAD_FILENAME_CACHE(86400000),
|
||||
/**
|
||||
* session的打印访问令牌缓存(时间是1小时)。
|
||||
*/
|
||||
PRINT_ACCESS_TOKEN_CACHE(3600000),
|
||||
/**
|
||||
* 缺省全局缓存(时间是24小时)。
|
||||
*/
|
||||
GLOBAL_CACHE(86400000);
|
||||
|
||||
/**
|
||||
* 缓存的时长(单位:毫秒)
|
||||
*/
|
||||
private int ttl = DEFAULT_TTL;
|
||||
|
||||
CacheEnum() {
|
||||
}
|
||||
|
||||
CacheEnum(int ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public int getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化缓存配置。
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public CacheManager cacheManager(RedissonClient redissonClient) {
|
||||
Map<String, CacheConfig> config = Maps.newHashMap();
|
||||
for (CacheEnum c : CacheEnum.values()) {
|
||||
config.put(c.name(), new CacheConfig(c.getTtl(), 0));
|
||||
}
|
||||
return new RedissonSpringCacheManager(redissonClient, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.orangeforms.common.redis.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.orangeforms.common.core.object.MyPrintInfo;
|
||||
import com.orangeforms.common.core.object.TokenData;
|
||||
import com.orangeforms.common.core.exception.MyRuntimeException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Session数据缓存辅助类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Component
|
||||
public class SessionCacheHelper {
|
||||
|
||||
@Autowired
|
||||
private CacheManager cacheManager;
|
||||
|
||||
private static final String NO_CACHE_FORMAT_MSG = "No redisson cache [{}]!";
|
||||
|
||||
/**
|
||||
* 缓存当前session内,上传过的文件名。
|
||||
*
|
||||
* @param filename 通常是本地存储的文件名,而不是上传时的原始文件名。
|
||||
*/
|
||||
public void putSessionUploadFile(String filename) {
|
||||
if (filename != null) {
|
||||
Set<String> sessionUploadFileSet = null;
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
if (cache == null) {
|
||||
String msg = StrFormatter.format(NO_CACHE_FORMAT_MSG,
|
||||
RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
throw new MyRuntimeException(msg);
|
||||
}
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper != null) {
|
||||
sessionUploadFileSet = (Set<String>) valueWrapper.get();
|
||||
}
|
||||
if (sessionUploadFileSet == null) {
|
||||
sessionUploadFileSet = new HashSet<>();
|
||||
}
|
||||
sessionUploadFileSet.add(filename);
|
||||
cache.put(TokenData.takeFromRequest().getSessionId(), sessionUploadFileSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存当前Session可以下载的文件集合。
|
||||
*
|
||||
* @param filenameSet 后台服务本地存储的文件名,而不是上传时的原始文件名。
|
||||
*/
|
||||
public void putSessionDownloadableFileNameSet(Set<String> filenameSet) {
|
||||
if (CollUtil.isEmpty(filenameSet)) {
|
||||
return;
|
||||
}
|
||||
Set<String> sessionUploadFileSet = null;
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
if (cache == null) {
|
||||
throw new MyRuntimeException(StrFormatter.format(NO_CACHE_FORMAT_MSG,
|
||||
RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name()));
|
||||
}
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper != null) {
|
||||
sessionUploadFileSet = (Set<String>) valueWrapper.get();
|
||||
}
|
||||
if (sessionUploadFileSet == null) {
|
||||
sessionUploadFileSet = new HashSet<>();
|
||||
}
|
||||
sessionUploadFileSet.addAll(filenameSet);
|
||||
cache.put(TokenData.takeFromRequest().getSessionId(), sessionUploadFileSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数中的文件名,是否有当前session上传。
|
||||
*
|
||||
* @param filename 通常是本地存储的文件名,而不是上传时的原始文件名。
|
||||
* @return true表示该文件是由当前session上传并存储在本地的,否则false。
|
||||
*/
|
||||
public boolean existSessionUploadFile(String filename) {
|
||||
if (filename == null) {
|
||||
return false;
|
||||
}
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
if (cache == null) {
|
||||
String msg = StrFormatter.format(NO_CACHE_FORMAT_MSG,
|
||||
RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
throw new MyRuntimeException(msg);
|
||||
}
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper == null) {
|
||||
return false;
|
||||
}
|
||||
Object cachedData = valueWrapper.get();
|
||||
if (cachedData == null) {
|
||||
return false;
|
||||
}
|
||||
return ((Set<String>) cachedData).contains(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存当前session内,可打印的安全令牌。
|
||||
*
|
||||
* @param token 打印安全令牌。
|
||||
* @param printInfo 打印参数信息。
|
||||
*/
|
||||
public void putSessionPrintTokenAndInfo(String token, MyPrintInfo printInfo) {
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.PRINT_ACCESS_TOKEN_CACHE.name());
|
||||
if (cache == null) {
|
||||
String msg = StrFormatter.format(NO_CACHE_FORMAT_MSG,
|
||||
RedissonCacheConfig.CacheEnum.PRINT_ACCESS_TOKEN_CACHE.name());
|
||||
throw new MyRuntimeException(msg);
|
||||
}
|
||||
Map<String, String> sessionPrintTokenMap = null;
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper != null) {
|
||||
sessionPrintTokenMap = (Map<String, String>) valueWrapper.get();
|
||||
}
|
||||
if (sessionPrintTokenMap == null) {
|
||||
sessionPrintTokenMap = new HashMap<>(4);
|
||||
}
|
||||
sessionPrintTokenMap.put(token, JSON.toJSONString(printInfo));
|
||||
cache.put(TokenData.takeFromRequest().getSessionId(), sessionPrintTokenMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前session中,指定打印令牌所关联的打印信息。
|
||||
*
|
||||
* @param token 打印安全令牌。
|
||||
* @return 当前session中,指定打印令牌所关联的打印信息。不存在返回null。
|
||||
*/
|
||||
public MyPrintInfo getSessionPrintInfoByToken(String token) {
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.PRINT_ACCESS_TOKEN_CACHE.name());
|
||||
if (cache == null) {
|
||||
String msg = StrFormatter.format(NO_CACHE_FORMAT_MSG,
|
||||
RedissonCacheConfig.CacheEnum.PRINT_ACCESS_TOKEN_CACHE.name());
|
||||
throw new MyRuntimeException(msg);
|
||||
}
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper == null) {
|
||||
return null;
|
||||
}
|
||||
Object cachedData = valueWrapper.get();
|
||||
if (cachedData == null) {
|
||||
return null;
|
||||
}
|
||||
String data = ((Map<String, String>) cachedData).get(token);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(data, MyPrintInfo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前session的所有缓存数据。
|
||||
*
|
||||
* @param sessionId 当前会话的SessionId。
|
||||
*/
|
||||
public void removeAllSessionCache(String sessionId) {
|
||||
for (RedissonCacheConfig.CacheEnum c : RedissonCacheConfig.CacheEnum.values()) {
|
||||
Cache cache = cacheManager.getCache(c.name());
|
||||
if (cache != null) {
|
||||
cache.evict(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.orangeforms.common.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.orangeforms.common.core.exception.InvalidRedisModeException;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Redisson配置类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "common-redis.redisson.enabled", havingValue = "true")
|
||||
public class RedissonConfig {
|
||||
|
||||
@Value("${common-redis.redisson.lockWatchdogTimeout}")
|
||||
private Integer lockWatchdogTimeout;
|
||||
|
||||
@Value("${common-redis.redisson.mode}")
|
||||
private String mode;
|
||||
|
||||
/**
|
||||
* 仅仅用于sentinel模式。
|
||||
*/
|
||||
@Value("${common-redis.redisson.masterName:}")
|
||||
private String masterName;
|
||||
|
||||
@Value("${common-redis.redisson.address}")
|
||||
private String address;
|
||||
|
||||
@Value("${common-redis.redisson.timeout}")
|
||||
private Integer timeout;
|
||||
|
||||
@Value("${common-redis.redisson.password:}")
|
||||
private String password;
|
||||
|
||||
@Value("${common-redis.redisson.pool.poolSize}")
|
||||
private Integer poolSize;
|
||||
|
||||
@Value("${common-redis.redisson.pool.minIdle}")
|
||||
private Integer minIdle;
|
||||
|
||||
@Bean
|
||||
public RedissonClient redissonClient() {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
password = null;
|
||||
}
|
||||
Config config = new Config();
|
||||
if ("single".equals(mode)) {
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout)
|
||||
.useSingleServer()
|
||||
.setPassword(password)
|
||||
.setAddress(address)
|
||||
.setConnectionPoolSize(poolSize)
|
||||
.setConnectionMinimumIdleSize(minIdle)
|
||||
.setConnectTimeout(timeout);
|
||||
} else if ("cluster".equals(mode)) {
|
||||
String[] clusterAddresses = StrUtil.splitToArray(address, ',');
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout)
|
||||
.useClusterServers()
|
||||
.setPassword(password)
|
||||
.addNodeAddress(clusterAddresses)
|
||||
.setConnectTimeout(timeout)
|
||||
.setMasterConnectionPoolSize(poolSize)
|
||||
.setMasterConnectionMinimumIdleSize(minIdle);
|
||||
} else if ("sentinel".equals(mode)) {
|
||||
String[] sentinelAddresses = StrUtil.splitToArray(address, ',');
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout)
|
||||
.useSentinelServers()
|
||||
.setPassword(password)
|
||||
.setMasterName(masterName)
|
||||
.addSentinelAddress(sentinelAddresses)
|
||||
.setConnectTimeout(timeout)
|
||||
.setMasterConnectionPoolSize(poolSize)
|
||||
.setMasterConnectionMinimumIdleSize(minIdle);
|
||||
} else if ("master-slave".equals(mode)) {
|
||||
String[] masterSlaveAddresses = StrUtil.splitToArray(address, ',');
|
||||
if (masterSlaveAddresses.length == 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"redis.redisson.address MUST have multiple redis addresses for master-slave mode.");
|
||||
}
|
||||
String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];
|
||||
ArrayUtil.copy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout)
|
||||
.useMasterSlaveServers()
|
||||
.setPassword(password)
|
||||
.setMasterAddress(masterSlaveAddresses[0])
|
||||
.addSlaveAddress(slaveAddresses)
|
||||
.setConnectTimeout(timeout)
|
||||
.setMasterConnectionPoolSize(poolSize)
|
||||
.setMasterConnectionMinimumIdleSize(minIdle);
|
||||
} else {
|
||||
throw new InvalidRedisModeException(mode);
|
||||
}
|
||||
return Redisson.create(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.orangeforms.common.redis.util;
|
||||
|
||||
import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.EnumUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RAtomicLong;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Redis的常用工具方法。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2024-07-02
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CommonRedisUtil {
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
private static final Integer DEFAULT_EXPIRE_SECOND = 300;
|
||||
|
||||
/**
|
||||
* 计算流水号前缀部分。
|
||||
*
|
||||
* @param prefix 前缀字符串。
|
||||
* @param precisionTo 精确到的时间单元,目前仅仅支持 YEAR/MONTH/DAYS/HOURS/MINUTES/SECONDS。
|
||||
* @param middle 日期和流水号之间的字符串。
|
||||
* @return 返回计算后的前缀部分。
|
||||
*/
|
||||
public String calculateTransIdPrefix(String prefix, String precisionTo, String middle) {
|
||||
String key = prefix;
|
||||
if (key == null) {
|
||||
key = "";
|
||||
}
|
||||
DateTime dateTime = new DateTime();
|
||||
String fmt = "yyyy";
|
||||
String fmt2 = fmt + "MMddHH";
|
||||
switch (precisionTo) {
|
||||
case "YEAR":
|
||||
break;
|
||||
case "MONTH":
|
||||
fmt += "MM";
|
||||
break;
|
||||
case "DAYS":
|
||||
fmt = fmt + "MMdd";
|
||||
break;
|
||||
case "HOURS":
|
||||
fmt = fmt2;
|
||||
break;
|
||||
case "MINUTES":
|
||||
fmt = fmt2 + "mm";
|
||||
break;
|
||||
case "SECONDS":
|
||||
fmt = fmt2 + "mmss";
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Only Support YEAR/MONTH/DAYS/HOURS/MINUTES/SECONDS");
|
||||
}
|
||||
key += dateTime.toString(fmt);
|
||||
return middle != null ? key + middle : key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基于时间的流水号方法。
|
||||
*
|
||||
* @param prefix 前缀字符串。
|
||||
* @param precisionTo 精确到的时间单元,目前仅仅支持 YEAR/MONTH/DAYS/HOURS/MINUTES/SECONDS。
|
||||
* @param middle 日期和流水号之间的字符串。
|
||||
* @param idWidth 计算出的流水号宽度,前面补充0。比如idWidth = 3, 输出值为 005/012/123。
|
||||
* 需要注意的是,流水号值超出idWidth指定宽度,低位会被截取。
|
||||
* @return 基于时间的流水号方法。
|
||||
*/
|
||||
public String generateTransId(String prefix, String precisionTo, String middle, int idWidth) {
|
||||
TimeUnit unit = EnumUtil.fromString(TimeUnit.class, precisionTo, null);
|
||||
int unitCount = 1;
|
||||
if (unit == null) {
|
||||
unit = TimeUnit.DAYS;
|
||||
DateTime now = DateTime.now();
|
||||
if (StrUtil.equals(precisionTo, "MONTH")) {
|
||||
DateTime endOfMonthDay = DateUtil.endOfMonth(now);
|
||||
unitCount = endOfMonthDay.getField(DateField.DAY_OF_MONTH) - now.getField(DateField.DAY_OF_MONTH) + 1;
|
||||
} else if (StrUtil.equals(precisionTo, "YEAR")) {
|
||||
DateTime endOfYearDay = DateUtil.endOfYear(now);
|
||||
unitCount = endOfYearDay.getField(DateField.DAY_OF_YEAR) - now.getField(DateField.DAY_OF_YEAR) + 1;
|
||||
}
|
||||
}
|
||||
String key = this.calculateTransIdPrefix(prefix, precisionTo, middle);
|
||||
RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
|
||||
long value = atomicLong.incrementAndGet();
|
||||
if (value == 1L) {
|
||||
atomicLong.expire(unitCount, unit);
|
||||
}
|
||||
return key + StrUtil.padPre(String.valueOf(value), idWidth, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定的键设置流水号的初始值。
|
||||
*
|
||||
* @param key 指定的键。
|
||||
* @param initalValue 初始值。
|
||||
*/
|
||||
public void initTransId(String key, Long initalValue) {
|
||||
RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
|
||||
atomicLong.set(initalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。
|
||||
*
|
||||
* @param key 缓存的键。
|
||||
* @param id 数据Id。
|
||||
* @param f 获取数据的方法。
|
||||
* @param clazz 数据对象类型。
|
||||
* @return 数据对象。
|
||||
*/
|
||||
public <M> M getFromCache(String key, Serializable id, Function<Serializable, M> f, Class<M> clazz) {
|
||||
return this.getFromCache(key, id, f, clazz, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。
|
||||
*
|
||||
* @param key 缓存的键。
|
||||
* @param filter mybatis flex的过滤对象。
|
||||
* @param f 获取数据的方法。
|
||||
* @param clazz 数据对象类型。
|
||||
* @return 数据对象。
|
||||
*/
|
||||
public <N> N getFromCacheWithQueryWrapper(String key, QueryWrapper filter, Function<QueryWrapper, N> f, Class<N> clazz) {
|
||||
N m;
|
||||
RBucket<String> bucket = redissonClient.getBucket(key);
|
||||
if (!bucket.isExists()) {
|
||||
m = f.apply(filter);
|
||||
if (m != null) {
|
||||
bucket.set(JSON.toJSONString(m), DEFAULT_EXPIRE_SECOND, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
m = JSON.parseObject(bucket.get(), clazz);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。
|
||||
*
|
||||
* @param key 缓存的键。
|
||||
* @param filter 过滤对象。
|
||||
* @param f 获取数据的方法。
|
||||
* @param clazz 数据对象类型。
|
||||
* @return 数据对象。
|
||||
*/
|
||||
public <M, N> N getFromCache(String key, M filter, Function<M, N> f, Class<N> clazz) {
|
||||
N m;
|
||||
RBucket<String> bucket = redissonClient.getBucket(key);
|
||||
if (!bucket.isExists()) {
|
||||
m = f.apply(filter);
|
||||
if (m != null) {
|
||||
bucket.set(JSON.toJSONString(m), DEFAULT_EXPIRE_SECOND, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
m = JSON.parseObject(bucket.get(), clazz);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。
|
||||
*
|
||||
* @param key 缓存的键。
|
||||
* @param id 数据Id。
|
||||
* @param f 获取数据的方法。
|
||||
* @param clazz 数据对象类型。
|
||||
* @param seconds 过期秒数。
|
||||
* @return 数据对象。
|
||||
*/
|
||||
public <M> M getFromCache(
|
||||
String key, Serializable id, Function<Serializable, M> f, Class<M> clazz, Integer seconds) {
|
||||
M m;
|
||||
RBucket<String> bucket = redissonClient.getBucket(key);
|
||||
if (!bucket.isExists()) {
|
||||
m = f.apply(id);
|
||||
if (m != null) {
|
||||
if (seconds == null) {
|
||||
seconds = DEFAULT_EXPIRE_SECOND;
|
||||
}
|
||||
bucket.set(JSON.toJSONString(m), seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
m = JSON.parseObject(bucket.get(), clazz);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定Key。
|
||||
*
|
||||
* @param key 键名。
|
||||
*/
|
||||
public void evictFormCache(String key) {
|
||||
redissonClient.getBucket(key).delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.orangeforms.common.redis.config.RedissonConfig
|
||||
Reference in New Issue
Block a user