From c80d8909ddb1077409025e2005c3e12e3db1687a Mon Sep 17 00:00:00 2001 From: wol <1293433164@qq.com> Date: Mon, 11 Aug 2025 23:49:28 +0800 Subject: [PATCH] common-redis --- agileboot-common/pom.xml | 1 + agileboot-common/wol-common-box/pom.xml | 5 + .../common/core/constant/Constants.java | 127 ++-- .../common/core/constant/HttpStatus.java | 93 +++ .../com/agileboot/common/core/core/R.java | 120 ++++ .../common/core/core/dto/ResponseDTO.java | 59 -- agileboot-common/wol-common-redis/pom.xml | 42 ++ .../redis/config/CacheConfiguration.java | 45 ++ .../redis/config/RedisConfiguration.java | 161 +++++ .../config/properties/RedissonProperties.java | 135 ++++ .../redis/handler/KeyPrefixHandler.java | 50 ++ .../redis/handler/RedisExceptionHandler.java | 30 + .../redis/manager/CaffeineCacheDecorator.java | 97 +++ .../redis/manager/PlusSpringCacheManager.java | 202 ++++++ .../common/redis/utils/CacheUtils.java | 61 ++ .../common/redis/utils/RedisUtils.java | 581 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 2 + pom.xml | 12 + 18 files changed, 1700 insertions(+), 123 deletions(-) create mode 100644 agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/HttpStatus.java create mode 100644 agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/R.java delete mode 100644 agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/dto/ResponseDTO.java create mode 100644 agileboot-common/wol-common-redis/pom.xml create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/CacheConfiguration.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/RedisConfiguration.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/properties/RedissonProperties.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/KeyPrefixHandler.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/RedisExceptionHandler.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/CaffeineCacheDecorator.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/PlusSpringCacheManager.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/CacheUtils.java create mode 100644 agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/RedisUtils.java create mode 100644 agileboot-common/wol-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/agileboot-common/pom.xml b/agileboot-common/pom.xml index 667638c..100a7a6 100644 --- a/agileboot-common/pom.xml +++ b/agileboot-common/pom.xml @@ -14,6 +14,7 @@ wol-common-core wol-common-doc wol-common-mybatis + wol-common-redis pom diff --git a/agileboot-common/wol-common-box/pom.xml b/agileboot-common/wol-common-box/pom.xml index a554f36..20d448b 100644 --- a/agileboot-common/wol-common-box/pom.xml +++ b/agileboot-common/wol-common-box/pom.xml @@ -32,6 +32,11 @@ wol-common-mybatis ${revision} + + com.agileboot + wol-common-redis + ${revision} + diff --git a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/Constants.java b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/Constants.java index 8c096b9..ba882ed 100644 --- a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/Constants.java +++ b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/Constants.java @@ -1,86 +1,85 @@ package com.agileboot.common.core.constant; - /** * 通用常量信息 * - * @author valarchie + * @author ruoyi */ -public class Constants { - private Constants() { - } +public interface Constants { - public static final int KB = 1024; - - public static final int MB = KB * 1024; - - public static final int GB = MB * 1024; - - /** - * http请求 - */ - public static final String HTTP = "http://"; - - /** - * https请求 - */ - public static final String HTTPS = "https://"; - - - public static class Token { - - private Token() { - } - - /** - * 令牌前缀 - */ - public static final String PREFIX = "Bearer "; - - /** - * 令牌前缀 - */ - public static final String LOGIN_USER_KEY = "login_user_key"; - - } - - public static class Captcha { - - private Captcha() { - } - - /** - * 令牌 - */ - public static final String MATH_TYPE = "math"; - - /** - * 令牌前缀 - */ - public static final String CHAR_TYPE = "char"; - - } + int KB = 1024; + int MB = KB * 1024; + int GB = MB * 1024; /** * 资源映射路径 前缀 */ - public static final String RESOURCE_PREFIX = "profile"; + String RESOURCE_PREFIX = "profile"; - public static class UploadSubDir { + /** + * UTF-8 字符集 + */ + String UTF8 = "UTF-8"; - private UploadSubDir() { - } + /** + * GBK 字符集 + */ + String GBK = "GBK"; - public static final String IMPORT_PATH = "import"; + /** + * www主域 + */ + String WWW = "www."; - public static final String AVATAR_PATH = "avatar"; + /** + * http请求 + */ + String HTTP = "http://"; - public static final String DOWNLOAD_PATH = "download"; + /** + * https请求 + */ + String HTTPS = "https://"; - public static final String UPLOAD_PATH = "upload"; + /** + * 通用成功标识 + */ + String SUCCESS = "0"; - } + /** + * 通用失败标识 + */ + String FAIL = "1"; + /** + * 登录成功 + */ + String LOGIN_SUCCESS = "Success"; + /** + * 注销 + */ + String LOGOUT = "Logout"; + + /** + * 注册 + */ + String REGISTER = "Register"; + + /** + * 登录失败 + */ + String LOGIN_FAIL = "Error"; + + /** + * 验证码有效期(分钟) + */ + Integer CAPTCHA_EXPIRATION = 2; + + /** + * 顶级部门id + */ + Long TOP_PARENT_ID = 0L; } + diff --git a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/HttpStatus.java b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/HttpStatus.java new file mode 100644 index 0000000..b5e3583 --- /dev/null +++ b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/constant/HttpStatus.java @@ -0,0 +1,93 @@ +package com.agileboot.common.core.constant; + +/** + * 返回状态码 + * + * @author Lion Li + */ +public interface HttpStatus { + /** + * 操作成功 + */ + int SUCCESS = 200; + + /** + * 对象创建成功 + */ + int CREATED = 201; + + /** + * 请求已经被接受 + */ + int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + int MOVED_PERM = 301; + + /** + * 重定向 + */ + int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + int BAD_REQUEST = 400; + + /** + * 未授权 + */ + int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + int ERROR = 500; + + /** + * 接口未实现 + */ + int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + int WARN = 601; +} diff --git a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/R.java b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/R.java new file mode 100644 index 0000000..d1496c4 --- /dev/null +++ b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/R.java @@ -0,0 +1,120 @@ +package com.agileboot.common.core.core; + +import com.agileboot.common.core.constant.HttpStatus; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author Lion Li + */ +@Data +@NoArgsConstructor +public class R implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 成功 + */ + public static final int SUCCESS = 200; + + /** + * 失败 + */ + public static final int FAIL = 500; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg; + + /** + * 数据对象 + */ + private T data; + + public static R ok() { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(String msg) { + return restResult(null, SUCCESS, msg); + } + + public static R ok(String msg, T data) { + return restResult(data, SUCCESS, msg); + } + + public static R fail() { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(String msg, T data) { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) { + return restResult(null, code, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static R warn(String msg) { + return restResult(null, HttpStatus.WARN, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static R warn(String msg, T data) { + return restResult(data, HttpStatus.WARN, msg); + } + + private static R restResult(T data, int code, String msg) { + R r = new R<>(); + r.setCode(code); + r.setData(data); + r.setMsg(msg); + return r; + } + + public static Boolean isError(R ret) { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) { + return R.SUCCESS == ret.getCode(); + } + +} diff --git a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/dto/ResponseDTO.java b/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/dto/ResponseDTO.java deleted file mode 100644 index a6e4ade..0000000 --- a/agileboot-common/wol-common-core/src/main/java/com/agileboot/common/core/core/dto/ResponseDTO.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.agileboot.common.core.core.dto; - -import com.agileboot.common.core.exception.ApiException; -import com.agileboot.common.core.exception.error.ErrorCode; -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AllArgsConstructor; -import lombok.Data; - -/** - * 响应信息主体 - * - * @author valarchie - */ -@Data -@AllArgsConstructor -public class ResponseDTO { - - private Integer code; - - private String msg; - - @JsonInclude - private T data; - - public static ResponseDTO ok() { - return build(null, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message()); - } - - public static ResponseDTO ok(T data) { - return build(data, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message()); - } - - public static ResponseDTO fail() { - return build(null, ErrorCode.FAILED.code(), ErrorCode.FAILED.message()); - } - - public static ResponseDTO fail(T data) { - return build(data, ErrorCode.FAILED.code(), ErrorCode.FAILED.message()); - } - - public static ResponseDTO fail(ApiException exception) { - return build(null, exception.getErrorCode().code(), exception.getMessage()); - } - - public static ResponseDTO fail(ApiException exception, T data) { - return build(data, exception.getErrorCode().code(), exception.getMessage()); - } - - public static ResponseDTO build(T data, Integer code, String msg) { - return new ResponseDTO<>(code, msg, data); - } - - // 去掉直接填充错误码的方式, 这种方式不能拿到i18n的错误消息 统一通过ApiException来构造错误消息 -// public static ResponseDTO fail(ErrorCodeInterface code, Object... args) { -// return build(null, code, args); -// } - -} - diff --git a/agileboot-common/wol-common-redis/pom.xml b/agileboot-common/wol-common-redis/pom.xml new file mode 100644 index 0000000..4740e61 --- /dev/null +++ b/agileboot-common/wol-common-redis/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + com.agileboot + agileboot-common + 1.0.0 + + + wol-common-redis + + + + + com.agileboot + wol-common-core + + + + org.redisson + redisson-spring-boot-starter + + + + com.baomidou + lock4j-redisson-spring-boot-starter + + + + com.github.ben-manes.caffeine + caffeine + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + \ No newline at end of file diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/CacheConfiguration.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/CacheConfiguration.java new file mode 100644 index 0000000..071bdbe --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/CacheConfiguration.java @@ -0,0 +1,45 @@ +package com.agileboot.common.redis.config; + +import com.agileboot.common.redis.manager.PlusSpringCacheManager; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +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 CacheConfiguration { + + /** + * caffeine 本地缓存处理器 + */ + @Bean + public Cache caffeine() { + return Caffeine.newBuilder() + // 设置最后一次写入或访问后经过固定时间过期 + .expireAfterWrite(30, TimeUnit.SECONDS) + // 初始的缓存空间大小 + .initialCapacity(100) + // 缓存的最大条数 + .maximumSize(1000) + .build(); + } + + /** + * 自定义缓存管理器 整合spring-cache + */ + @Bean + public CacheManager cacheManager() { + return new PlusSpringCacheManager(); + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/RedisConfiguration.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/RedisConfiguration.java new file mode 100644 index 0000000..9e256c4 --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/RedisConfiguration.java @@ -0,0 +1,161 @@ +package com.agileboot.common.redis.config; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.common.redis.config.properties.RedissonProperties; +import com.agileboot.common.redis.handler.KeyPrefixHandler; +import com.agileboot.common.redis.handler.RedisExceptionHandler; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.redisson.client.codec.StringCodec; +import org.redisson.codec.CompositeCodec; +import org.redisson.codec.TypedJsonJacksonCodec; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.core.task.VirtualThreadTaskExecutor; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +/** + * redis配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(RedissonProperties.class) +public class RedisConfiguration { + + @Autowired + private RedissonProperties redissonProperties; + + @Bean + public RedissonAutoConfigurationCustomizer redissonCustomizer() { + return config -> { + JavaTimeModule javaTimeModule = new JavaTimeModule(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + ObjectMapper om = new ObjectMapper(); + om.registerModule(javaTimeModule); + om.setTimeZone(TimeZone.getDefault()); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来 + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); +// LoggerFactory.useSlf4jLogging(true); +// FuryCodec furyCodec = new FuryCodec(); +// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec); + TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om); + // 组合序列化 key 使用 String 内容使用通用 json 格式 + CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec); + config.setThreads(redissonProperties.getThreads()) + .setNettyThreads(redissonProperties.getNettyThreads()) + // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现) + .setUseScriptCache(true) + .setCodec(codec); + if (Threading.VIRTUAL.isActive(SpringUtil.getBean(Environment.class))) { + config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-")); + } + RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + config.useSingleServer() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(singleServerConfig.getTimeout()) + .setClientName(singleServerConfig.getClientName()) + .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) + .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) + .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()); + } + // 集群配置方式 参考下方注释 + RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); + if (ObjectUtil.isNotNull(clusterServersConfig)) { + config.useClusterServers() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(clusterServersConfig.getTimeout()) + .setClientName(clusterServersConfig.getClientName()) + .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize()) + .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize()) + .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize()) + .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize()) + .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize()) + .setReadMode(clusterServersConfig.getReadMode()) + .setSubscriptionMode(clusterServersConfig.getSubscriptionMode()); + } + log.info("初始化 redis 配置"); + }; + } + + /** + * 异常处理器 + */ + @Bean + public RedisExceptionHandler redisExceptionHandler() { + return new RedisExceptionHandler(); + } + + /** + * redis集群配置 yml + * + * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉) + * spring.data: + * redis: + * cluster: + * nodes: + * - 192.168.0.100:6379 + * - 192.168.0.101:6379 + * - 192.168.0.102:6379 + * # 密码 + * password: + * # 连接超时时间 + * timeout: 10s + * # 是否开启ssl + * ssl.enabled: false + * + * redisson: + * # 线程池数量 + * threads: 16 + * # Netty线程池数量 + * nettyThreads: 32 + * # 集群配置 + * clusterServersConfig: + * # 客户端名称 + * clientName: ${ruoyi.name} + * # master最小空闲连接数 + * masterConnectionMinimumIdleSize: 32 + * # master连接池大小 + * masterConnectionPoolSize: 64 + * # slave最小空闲连接数 + * slaveConnectionMinimumIdleSize: 32 + * # slave连接池大小 + * slaveConnectionPoolSize: 64 + * # 连接空闲超时,单位:毫秒 + * idleConnectionTimeout: 10000 + * # 命令等待超时,单位:毫秒 + * timeout: 3000 + * # 发布和订阅连接池大小 + * subscriptionConnectionPoolSize: 50 + * # 读取模式 + * readMode: "SLAVE" + * # 订阅模式 + * subscriptionMode: "MASTER" + */ + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/properties/RedissonProperties.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/properties/RedissonProperties.java new file mode 100644 index 0000000..6fd90f6 --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/config/properties/RedissonProperties.java @@ -0,0 +1,135 @@ +package com.agileboot.common.redis.config.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.redisson.config.ReadMode; +import org.redisson.config.SubscriptionMode; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Redisson 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "redisson") +public class RedissonProperties { + + /** + * redis缓存key前缀 + */ + private String keyPrefix; + + /** + * 线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int threads; + + /** + * Netty线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int nettyThreads; + + /** + * 单机服务配置 + */ + private SingleServerConfig singleServerConfig; + + /** + * 集群服务配置 + */ + private ClusterServersConfig clusterServersConfig; + + @Data + @NoArgsConstructor + public static class SingleServerConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * 最小空闲连接数 + */ + private int connectionMinimumIdleSize; + + /** + * 连接池大小 + */ + private int connectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + } + + @Data + @NoArgsConstructor + public static class ClusterServersConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * master最小空闲连接数 + */ + private int masterConnectionMinimumIdleSize; + + /** + * master连接池大小 + */ + private int masterConnectionPoolSize; + + /** + * slave最小空闲连接数 + */ + private int slaveConnectionMinimumIdleSize; + + /** + * slave连接池大小 + */ + private int slaveConnectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + /** + * 读取模式 + */ + private ReadMode readMode; + + /** + * 订阅模式 + */ + private SubscriptionMode subscriptionMode; + + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/KeyPrefixHandler.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/KeyPrefixHandler.java new file mode 100644 index 0000000..07206c4 --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/KeyPrefixHandler.java @@ -0,0 +1,50 @@ +package com.agileboot.common.redis.handler; + +import io.micrometer.common.util.StringUtils; +import org.redisson.api.NameMapper; + +/** + * redis缓存key前缀处理 + * + * @author ye + * @date 2022/7/14 17:44 + * @since 4.3.0 + */ +public class KeyPrefixHandler implements NameMapper { + + private final String keyPrefix; + + public KeyPrefixHandler(String keyPrefix) { + //前缀为空 则返回空前缀 + this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":"; + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) { + return keyPrefix + name; + } + return name; + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) { + return name.substring(keyPrefix.length()); + } + return name; + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/RedisExceptionHandler.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/RedisExceptionHandler.java new file mode 100644 index 0000000..401202f --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/handler/RedisExceptionHandler.java @@ -0,0 +1,30 @@ +package com.agileboot.common.redis.handler; + +import cn.hutool.http.HttpStatus; +import com.agileboot.common.core.core.R; +import com.baomidou.lock.exception.LockFailureException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * Redis异常处理器 + * + * @author AprilWind + */ +@Slf4j +@RestControllerAdvice +public class RedisExceptionHandler { + + /** + * 分布式锁Lock4j异常 + */ + @ExceptionHandler(LockFailureException.class) + public R handleLockFailureException(LockFailureException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("获取锁失败了'{}',发生Lock4j异常.", requestURI, e); + return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试..."); + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/CaffeineCacheDecorator.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/CaffeineCacheDecorator.java new file mode 100644 index 0000000..846284e --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/CaffeineCacheDecorator.java @@ -0,0 +1,97 @@ +package com.agileboot.common.redis.manager; + +import cn.hutool.extra.spring.SpringUtil; +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 + CAFFEINE = SpringUtil.getBean("caffeine"); + + private final String name; + private final Cache cache; + + public CaffeineCacheDecorator(String name, Cache cache) { + this.name = name; + this.cache = cache; + } + + @Override + public String getName() { + return name; + } + + @Override + public Object getNativeCache() { + return cache.getNativeCache(); + } + + public String getUniqueKey(Object key) { + return name + ":" + key; + } + + @Override + public ValueWrapper get(Object key) { + Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key)); + return (ValueWrapper) o; + } + + @SuppressWarnings("unchecked") + @Override + public T get(Object key, Class type) { + Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type)); + return (T) o; + } + + @Override + public void put(Object key, Object value) { + CAFFEINE.invalidate(getUniqueKey(key)); + cache.put(key, value); + } + + @Override + public ValueWrapper putIfAbsent(Object key, Object value) { + CAFFEINE.invalidate(getUniqueKey(key)); + return cache.putIfAbsent(key, value); + } + + @Override + public void evict(Object key) { + evictIfPresent(key); + } + + @Override + public boolean evictIfPresent(Object key) { + boolean b = cache.evictIfPresent(key); + if (b) { + CAFFEINE.invalidate(getUniqueKey(key)); + } + return b; + } + + @Override + public void clear() { + CAFFEINE.invalidateAll(); + cache.clear(); + } + + @Override + public boolean invalidate() { + return cache.invalidate(); + } + + @SuppressWarnings("unchecked") + @Override + public T get(Object key, Callable valueLoader) { + Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader)); + return (T) o; + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/PlusSpringCacheManager.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/PlusSpringCacheManager.java new file mode 100644 index 0000000..e88925c --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/manager/PlusSpringCacheManager.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.agileboot.common.redis.manager; + +import com.agileboot.common.redis.utils.RedisUtils; +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.spring.cache.CacheConfig; +import org.redisson.spring.cache.RedissonCache; +import org.springframework.boot.convert.DurationStyle; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A {@link CacheManager} implementation + * backed by Redisson instance. + *

+ * 修改 RedissonSpringCacheManager 源码 + * 重写 cacheName 处理方法 支持多参数 + * + * @author Nikita Koksharov + * + */ +@SuppressWarnings("unchecked") +public class PlusSpringCacheManager implements CacheManager { + + private boolean dynamic = true; + + private boolean allowNullValues = true; + + private boolean transactionAware = true; + + Map configMap = new ConcurrentHashMap<>(); + ConcurrentMap instanceMap = new ConcurrentHashMap<>(); + + /** + * Creates CacheManager supplied by Redisson instance + */ + public PlusSpringCacheManager() { + } + + + /** + * Defines possibility of storing {@code null} values. + *

+ * Default is true + * + * @param allowNullValues stores if true + */ + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + /** + * Defines if cache aware of Spring-managed transactions. + * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. + *

+ * Default is false + * + * @param transactionAware cache is transaction aware if true + */ + public void setTransactionAware(boolean transactionAware) { + this.transactionAware = transactionAware; + } + + /** + * Defines 'fixed' cache names. + * A new cache instance will not be created in dynamic for non-defined names. + *

+ * `null` parameter setups dynamic mode + * + * @param names of caches + */ + public void setCacheNames(Collection names) { + if (names != null) { + for (String name : names) { + getCache(name); + } + dynamic = false; + } else { + dynamic = true; + } + } + + /** + * Set cache config mapped by cache name + * + * @param config object + */ + public void setConfig(Map config) { + this.configMap = (Map) config; + } + + protected CacheConfig createDefaultConfig() { + return new CacheConfig(); + } + + @Override + public Cache getCache(String name) { + // 重写 cacheName 支持多参数 + String[] array = StringUtils.delimitedListToStringArray(name, "#"); + name = array[0]; + + Cache cache = instanceMap.get(name); + if (cache != null) { + return cache; + } + if (!dynamic) { + return cache; + } + + CacheConfig config = configMap.get(name); + if (config == null) { + config = createDefaultConfig(); + configMap.put(name, config); + } + + if (array.length > 1) { + config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); + } + if (array.length > 2) { + config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); + } + if (array.length > 3) { + config.setMaxSize(Integer.parseInt(array[3])); + } + int local = 1; + if (array.length > 4) { + local = Integer.parseInt(array[4]); + } + + if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { + return createMap(name, config, local); + } + + return createMapCache(name, config, local); + } + + private Cache createMap(String name, CacheConfig config, int local) { + RMap map = RedisUtils.getClient().getMap(name); + + Cache cache = new RedissonCache(map, allowNullValues); + if (local == 1) { + cache = new CaffeineCacheDecorator(name, cache); + } + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } + return cache; + } + + private Cache createMapCache(String name, CacheConfig config, int local) { + RMapCache map = RedisUtils.getClient().getMapCache(name); + + Cache cache = new RedissonCache(map, config, allowNullValues); + if (local == 1) { + cache = new CaffeineCacheDecorator(name, cache); + } + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } else { + map.setMaxSize(config.getMaxSize()); + } + return cache; + } + + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(configMap.keySet()); + } + + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/CacheUtils.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/CacheUtils.java new file mode 100644 index 0000000..d2bf0ea --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/CacheUtils.java @@ -0,0 +1,61 @@ +package com.agileboot.common.redis.utils; + +import cn.hutool.extra.spring.SpringUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +/** + * 缓存操作工具类 + * + * @author Michelle.Chung + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked"}) +public class CacheUtils { + + private static final CacheManager CACHE_MANAGER = SpringUtil.getBean(CacheManager.class); + + /** + * 获取缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static T get(String cacheNames, Object key) { + Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key); + return wrapper != null ? (T) wrapper.get() : null; + } + + /** + * 保存缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + * @param value 缓存值 + */ + public static void put(String cacheNames, Object key, Object value) { + CACHE_MANAGER.getCache(cacheNames).put(key, value); + } + + /** + * 删除缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static void evict(String cacheNames, Object key) { + CACHE_MANAGER.getCache(cacheNames).evict(key); + } + + /** + * 清空缓存值 + * + * @param cacheNames 缓存组名称 + */ + public static void clear(String cacheNames) { + CACHE_MANAGER.getCache(cacheNames).clear(); + } + +} diff --git a/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/RedisUtils.java b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/RedisUtils.java new file mode 100644 index 0000000..c1bc7c6 --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/java/com/agileboot/common/redis/utils/RedisUtils.java @@ -0,0 +1,581 @@ +package com.agileboot.common.redis.utils; + +import cn.hutool.extra.spring.SpringUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.*; +import org.redisson.api.options.KeysScanOptions; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * redis 工具类 + * + * @author Lion Li + * @version 3.1.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +public class RedisUtils { + + private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class); + + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { + return rateLimiter(key, rateType, rate, rateInterval, 0); + } + + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @param timeout 超时时间 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout)); + if (rateLimiter.tryAcquire()) { + return rateLimiter.availablePermits(); + } else { + return -1L; + } + } + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 发布通道消息 + * + * @param channelKey 通道key + * @param msg 发送数据 + * @param consumer 自定义处理 + */ + public static void publish(String channelKey, T msg, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + consumer.accept(msg); + } + + /** + * 发布消息到指定的频道 + * + * @param channelKey 通道key + * @param msg 发送数据 + */ + public static void publish(String channelKey, T msg) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + } + + /** + * 订阅通道接收消息 + * + * @param channelKey 通道key + * @param clazz 消息类型 + * @param consumer 自定义处理 + */ + public static void subscribe(String channelKey, Class clazz, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.addListener(clazz, (channel, msg) -> consumer.accept(msg)); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public static void setCacheObject(final String key, final T value) { + setCacheObject(key, value, false); + } + + /** + * 缓存基本的对象,保留当前对象 TTL 有效期 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90) + * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案 + */ + public static void setCacheObject(final String key, final T value, final boolean isSaveTtl) { + RBucket bucket = CLIENT.getBucket(key); + if (isSaveTtl) { + try { + bucket.setAndKeepTTL(value); + } catch (Exception e) { + long timeToLive = bucket.remainTimeToLive(); + if (timeToLive == -1) { + bucket.set(value); + } else { + bucket.set(value, Duration.ofMillis(timeToLive)); + } + } + } else { + bucket.set(value); + } + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param duration 时间 + */ + public static void setCacheObject(final String key, final T value, final Duration duration) { + RBucket bucket = CLIENT.getBucket(key); + bucket.set(value, duration); + } + + /** + * 如果不存在则设置 并返回 true 如果存在则返回 false + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @return set成功或失败 + */ + public static boolean setObjectIfAbsent(final String key, final T value, final Duration duration) { + RBucket bucket = CLIENT.getBucket(key); + return bucket.setIfAbsent(value, duration); + } + + /** + * 如果存在则设置 并返回 true 如果存在则返回 false + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @return set成功或失败 + */ + public static boolean setObjectIfExists(final String key, final T value, final Duration duration) { + RBucket bucket = CLIENT.getBucket(key); + return bucket.setIfExists(value, duration); + } + + /** + * 注册对象监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addObjectListener(final String key, final ObjectListener listener) { + RBucket result = CLIENT.getBucket(key); + result.addListener(listener); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final long timeout) { + return expire(key, Duration.ofSeconds(timeout)); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param duration 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final Duration duration) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.expire(duration); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public static T getCacheObject(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.get(); + } + + /** + * 获得key剩余存活时间 + * + * @param key 缓存键值 + * @return 剩余存活时间 + */ + public static long getTimeToLive(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.remainTimeToLive(); + } + + /** + * 删除单个对象 + * + * @param key 缓存的键值 + */ + public static boolean deleteObject(final String key) { + return CLIENT.getBucket(key).delete(); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + */ + public static void deleteObject(final Collection collection) { + RBatch batch = CLIENT.createBatch(); + collection.forEach(t -> { + batch.getBucket(t.toString()).deleteAsync(); + }); + batch.execute(); + } + + /** + * 检查缓存对象是否存在 + * + * @param key 缓存的键值 + */ + public static boolean isExistsObject(final String key) { + return CLIENT.getBucket(key).isExists(); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public static boolean setCacheList(final String key, final List dataList) { + RList rList = CLIENT.getList(key); + return rList.addAll(dataList); + } + + /** + * 追加缓存List数据 + * + * @param key 缓存的键值 + * @param data 待缓存的数据 + * @return 缓存的对象 + */ + public static boolean addCacheList(final String key, final T data) { + RList rList = CLIENT.getList(key); + return rList.add(data); + } + + /** + * 注册List监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addListListener(final String key, final ObjectListener listener) { + RList rList = CLIENT.getList(key); + rList.addListener(listener); + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public static List getCacheList(final String key) { + RList rList = CLIENT.getList(key); + return rList.readAll(); + } + + /** + * 获得缓存的list对象(范围) + * + * @param key 缓存的键值 + * @param form 起始下标 + * @param to 截止下标 + * @return 缓存键值对应的数据 + */ + public static List getCacheListRange(final String key, int form, int to) { + RList rList = CLIENT.getList(key); + return rList.range(form, to); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public static boolean setCacheSet(final String key, final Set dataSet) { + RSet rSet = CLIENT.getSet(key); + return rSet.addAll(dataSet); + } + + /** + * 追加缓存Set数据 + * + * @param key 缓存的键值 + * @param data 待缓存的数据 + * @return 缓存的对象 + */ + public static boolean addCacheSet(final String key, final T data) { + RSet rSet = CLIENT.getSet(key); + return rSet.add(data); + } + + /** + * 注册Set监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addSetListener(final String key, final ObjectListener listener) { + RSet rSet = CLIENT.getSet(key); + rSet.addListener(listener); + } + + /** + * 获得缓存的set + * + * @param key 缓存的key + * @return set对象 + */ + public static Set getCacheSet(final String key) { + RSet rSet = CLIENT.getSet(key); + return rSet.readAll(); + } + + /** + * 缓存Map + * + * @param key 缓存的键值 + * @param dataMap 缓存的数据 + */ + public static void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + RMap rMap = CLIENT.getMap(key); + rMap.putAll(dataMap); + } + } + + /** + * 注册Map监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addMapListener(final String key, final ObjectListener listener) { + RMap rMap = CLIENT.getMap(key); + rMap.addListener(listener); + } + + /** + * 获得缓存的Map + * + * @param key 缓存的键值 + * @return map对象 + */ + public static Map getCacheMap(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(rMap.keySet()); + } + + /** + * 获得缓存Map的key列表 + * + * @param key 缓存的键值 + * @return key列表 + */ + public static Set getCacheMapKeySet(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.keySet(); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public static void setCacheMapValue(final String key, final String hKey, final T value) { + RMap rMap = CLIENT.getMap(key); + rMap.put(hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T getCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.get(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T delCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.remove(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键 + */ + public static void delMultiCacheMapValue(final String key, final Set hKeys) { + RBatch batch = CLIENT.createBatch(); + RMapAsync rMap = batch.getMap(key); + for (String hKey : hKeys) { + rMap.removeAsync(hKey); + } + batch.execute(); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public static Map getMultiCacheMapValue(final String key, final Set hKeys) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(hKeys); + } + + /** + * 设置原子值 + * + * @param key Redis键 + * @param value 值 + */ + public static void setAtomicValue(String key, long value) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + atomic.set(value); + } + + /** + * 获取原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long getAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.get(); + } + + /** + * 递增原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long incrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.incrementAndGet(); + } + + /** + * 递减原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long decrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.decrementAndGet(); + } + + /** + * 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id) + *

+ * limit-设置扫描的限制数量(默认为0,查询全部) + * pattern-设置键的匹配模式(默认为null) + * chunkSize-设置每次扫描的块大小(默认为0,本方法设置为1000) + * type-设置键的类型(默认为null,查询全部类型) + *

+ * @see KeysScanOptions + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public static Collection keys(final String pattern) { + return keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000)); + } + + /** + * 通过扫描参数获取缓存的基本对象列表 + * @param keysScanOptions 扫描参数 + *

+ * limit-设置扫描的限制数量(默认为0,查询全部) + * pattern-设置键的匹配模式(默认为null) + * chunkSize-设置每次扫描的块大小(默认为0) + * type-设置键的类型(默认为null,查询全部类型) + *

+ * @see KeysScanOptions + */ + public static Collection keys(final KeysScanOptions keysScanOptions) { + Stream keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions); + return keysStream.collect(Collectors.toList()); + } + + /** + * 删除缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id) + * + * @param pattern 字符串前缀 + */ + public static void deleteKeys(final String pattern) { + CLIENT.getKeys().deleteByPattern(pattern); + } + + /** + * 检查redis中是否存在key + * + * @param key 键 + */ + public static Boolean hasKey(String key) { + RKeys rKeys = CLIENT.getKeys(); + return rKeys.countExists(key) > 0; + } +} diff --git a/agileboot-common/wol-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/agileboot-common/wol-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..df1a37d --- /dev/null +++ b/agileboot-common/wol-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.agileboot.common.redis.config.CacheConfiguration +com.agileboot.common.redis.config.RedisConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index 742dd34..308f488 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,8 @@ 2.6.5 4.1.2 1.6.8 + 3.50.0 + 2.2.7 @@ -110,7 +112,17 @@ + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + com.baomidou + lock4j-redisson-spring-boot-starter + ${lock4j.version} +