Skip to content

acuity-cache-starter缓存处理类

天源云的key命名规则

WARNING

规定了key的命名规则为:[前缀]:[租户ID]:[服务模块名:]业务类型:[业务字段]:[value类型]:[业务值]

例如: lc:1:base:base_dict: id:String:BASE_ORG_TYPE

​ lc(老崔)前缀:1是企业id:base是基础模块:字典表名:id字段:id类型是string:字典键值

本模块将 分布式缓存redis内存缓存caffeine 的一些基础共有方法抽象出来,达到平滑切换缓存的目的。业务系统使用时,注入 CacheOps 类即可。但这样做又有几个缺陷: redis 的一些特殊方法将无法使用 list、set、hash 高级功能, 因此特意新增加一个 CacheOpsPlus 类支持 redis 全部接口。

建议

在 caffeine 实现是为了解决开发环境没有部署 redis 时应急使用, 生产环境请使用 redis 实现.

使用 acuity-cache-starter 后想用 redis 的 特殊方法 怎么用?

配置文件配置 acuity.cache.type=redis 后,注入 CacheOpsPlus 即可, 只要你能保证系统必须依赖 redis 即可。 但 CacheOpsPlus 基于内存等实现不能保证代码的正确性!!! (开发环境没有部署 redis 时,将配置调整成 caffeine 应急使用)

为啥要在封装一次?

  • 项目比较小(基本都是 CRUD 功能),而且团队中会优雅使用 redis 的比较少,而且会频繁的复制代码到 N 个项目,每个项目随时都可能会重新部署或者迁移一套环境用于演示,这里就是想让一些部署去演示的项目,直接用内存缓存即可,少部署一个 redis。
  • 开发电脑配置比较低,启动太多中间件会很卡,对于专心编码的开发来说,少启动一个中间件,对开发的体验比较好
  • 本地没有部署 redis, 又无测试 redis 环境可用, 基于启动项目开发功能, 将配置调整成 caffeine 即可

本模块核心 API

  • CacheOps : 基础缓存操作类
  • CachePlusOps : 增强缓存操作类, 包含了 redis 常用的方法
  • CacheKey (为了解耦, 已经移动到你 acuity-core 模块下) : 封装了缓存 key 和 过期时间的 缓存 Key 对象
  • RedisDistributedLock : 分布式锁的简单实现

注意事项

  1. 本模块虽然实现了 SpringCache 管理器重写, 但不建议在项目中使用. (原因: this 调用时, @Cache 不生效)
  2. CAFFEINE 模式 请勿在生产使用, 仅仅支持本地环境没有 redis 环境时,应急使用.
  3. 通过 acuity.cache.cacheNullVal 全局配置是否缓存空值
  4. 通过 acuity.cache.serializerType 配置 redis 类型的缓存序列化的方式。 (注意,修改序列化类型后,需要先清空 redis 的所有缓存,并重启项目。 生产环境请勿轻易、经常切换序列化类型!)
yaml
acuity:
  cache:
    type: REDIS
    cacheNullVal: true #是否缓存空值
    serializerType: ProtoStuff # 序列化类型 支持:JACK_SON、ProtoStuff、JDK
    # def:   # 不推荐使用。 用来配置Spring提供的@Cache的,自行阅读源码
    # configs:  # 不推荐使用。 用来配置Spring提供的@Cache的,自行阅读源码
acuity:
  cache:
    type: REDIS
    cacheNullVal: true #是否缓存空值
    serializerType: ProtoStuff # 序列化类型 支持:JACK_SON、ProtoStuff、JDK
    # def:   # 不推荐使用。 用来配置Spring提供的@Cache的,自行阅读源码
    # configs:  # 不推荐使用。 用来配置Spring提供的@Cache的,自行阅读源码
  1. serializerType 使用 JACK_SON 类型时,直接存储 Long 类型的值,从 redis 中取出数据时,需要手动强制转换成 Long 才行,否则会报错!!!
  2. acuity-cloud 不能使用内存缓存,否则无法登录

如何新项目如何接入

1, 在 pom.xml 中加依赖

xml
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-cache-starter</artifactId>
</dependency>
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-cache-starter</artifactId>
</dependency>
  1. 在 redis.yml 中加入配置
yaml
acuity:
  redis:
    ip: 127.0.0.1
    port: 16379
    password: 12345678
    database: 0
  cache:
    type: REDIS # CAFFEINE
    cacheNullVal: true # 是否缓存null值
    serializerType: ProtoStuff # 序列化类型 支持:JACK_SON、ProtoStuff、JDK
spring:
  redis:
    host: ${acuity.redis.ip}
    password: ${acuity.redis.password}
    port: ${acuity.redis.port}
    database: ${acuity.redis.database}
acuity:
  redis:
    ip: 127.0.0.1
    port: 16379
    password: 12345678
    database: 0
  cache:
    type: REDIS # CAFFEINE
    cacheNullVal: true # 是否缓存null值
    serializerType: ProtoStuff # 序列化类型 支持:JACK_SON、ProtoStuff、JDK
spring:
  redis:
    host: ${acuity.redis.ip}
    password: ${acuity.redis.password}
    port: ${acuity.redis.port}
    database: ${acuity.redis.database}
  1. 若缓存想使用 CAFFEINE 模式, 需要在依赖中排除 redis
xml
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-cache-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-cache-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  1. 为需要缓存的数据创建 CacheKeyBuilder ,如 ApplicationCacheKeyBuilder
java
public class ApplicationCacheKeyBuilder implements CacheKeyBuilder {
    @Override
    public String getPrefix() {
        return CacheKeyDefinition.APPLICATION;   //key前缀
    }
    @Override
    public Duration getExpire() {
        return Duration.ofHours(24);  // 有效期
    }
}
public class ApplicationCacheKeyBuilder implements CacheKeyBuilder {
    @Override
    public String getPrefix() {
        return CacheKeyDefinition.APPLICATION;   //key前缀
    }
    @Override
    public Duration getExpire() {
        return Duration.ofHours(24);  // 有效期
    }
}
  1. 需要操作缓存的地方注入 CacheOps
java
@Autowired  // 普通缓存功能
private CacheOps cacheOps;
@Autowired   // 增强功能
private CachePlusOps cachePlusOps;
public void teset(){
    CacheKey cacheKey = new VerificationCodeCacheKeyBuilder().key(data.getType().name(), data.getMobile());
    cacheOps.set(cacheKey, code);
}
@Autowired  // 普通缓存功能
private CacheOps cacheOps;
@Autowired   // 增强功能
private CachePlusOps cachePlusOps;
public void teset(){
    CacheKey cacheKey = new VerificationCodeCacheKeyBuilder().key(data.getType().name(), data.getMobile());
    cacheOps.set(cacheKey, code);
}

原理

为什么在 pom 中引入依赖 acuity-cache-starter 后,在项目中就能注入使用了?

java
@Autowired
private CacheOps cacheOps;
@Autowired
private CacheOpsPlus cacheOpsPlus;
@Autowired
private CacheOps cacheOps;
@Autowired
private CacheOpsPlus cacheOpsPlus;

1, acuity-cache-starter/src/main/resources/META-INF/spring.factories 中有如下配置,该配置会在项目启动时,自动加载 CacheAutoConfigure 类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    top.acuity.commons.cache.CacheAutoConfigure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    top.acuity.commons.cache.CacheAutoConfigure

2, 这个类通过注解启用了缓存, 并导入 Caffeine 自动配置类和 Redis 自动配置类

java
@EnableCaching
@Import({
        CaffeineAutoConfigure.class, RedisAutoConfigure.class
})
public class CacheAutoConfigure {
//省略...
}
@EnableCaching
@Import({
        CaffeineAutoConfigure.class, RedisAutoConfigure.class
})
public class CacheAutoConfigure {
//省略...
}

3,CaffeineAutoConfigure 会读取 yml 里面的配置, 当 acuity.cache.type=CAFFEINE 时生效,CaffeineAutoConfigure 类内部实例化了 CaffeineOpsImpl。 RedisAutoConfigure 在 acuity.cache.type=REDIS 时生效,acuity.cache.type 没有配置时,默认使用 redis 的配置,RedisAutoConfigure 内部实例化了 RedisOpsImpl。

所以,在项目中注入 CacheOps 或者 CacheOpsPlus 即可。

java
@Slf4j
@ConditionalOnProperty(name = "acuity.cache.type", havingValue = "CAFFEINE")
@EnableConfigurationProperties({CustomCacheProperties.class})
public class CaffeineAutoConfigure {
    @Bean
    @ConditionalOnMissingBean
    public CacheOps cacheOps() {
        return new CaffeineOpsImpl();
    }
    @Bean
    @ConditionalOnMissingBean
    public CachePlusOps cachePlusOps() {
        return new CaffeineOpsImpl();
    }
    }

@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "acuity.cache.type", havingValue = "REDIS", matchIfMissing = true)
@EnableConfigurationProperties({RedisProperties.class, CustomCacheProperties.class})
public class RedisAutoConfigure {
    @Bean
    @ConditionalOnMissingBean
    public CacheOps cacheOps(RedisOps redisOps) {
        return new RedisOpsImpl(redisOps);
    }
    @Bean
    @ConditionalOnMissingBean
    public CachePlusOps cachePlusOps(RedisOps redisOps) {
        return new RedisOpsImpl(redisOps);
    }
}
@Slf4j
@ConditionalOnProperty(name = "acuity.cache.type", havingValue = "CAFFEINE")
@EnableConfigurationProperties({CustomCacheProperties.class})
public class CaffeineAutoConfigure {
    @Bean
    @ConditionalOnMissingBean
    public CacheOps cacheOps() {
        return new CaffeineOpsImpl();
    }
    @Bean
    @ConditionalOnMissingBean
    public CachePlusOps cachePlusOps() {
        return new CaffeineOpsImpl();
    }
    }

@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "acuity.cache.type", havingValue = "REDIS", matchIfMissing = true)
@EnableConfigurationProperties({RedisProperties.class, CustomCacheProperties.class})
public class RedisAutoConfigure {
    @Bean
    @ConditionalOnMissingBean
    public CacheOps cacheOps(RedisOps redisOps) {
        return new RedisOpsImpl(redisOps);
    }
    @Bean
    @ConditionalOnMissingBean
    public CachePlusOps cachePlusOps(RedisOps redisOps) {
        return new RedisOpsImpl(redisOps);
    }
}

缓存的Key

CacheKey

k-v类型缓存的key,封装缓存的key和key的有效期。

CacheHashKey

hash类型缓存的key,封装缓存的key、field以及key的有效期。

CacheKeyBuilder

key的构造器,用来生成key并构造CacheKey。生成key的规则如下

java
/**
 * 根据动态参数 拼接key
 * <p>
 * key命名规范:[租户编码:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
 *
 * @param uniques 动态参数
 * @return
 */
default String getKey(Object... uniques) {
    ArrayList<String> regionList = new ArrayList<>();
    String prefix = this.getPrefix();
    if (StrUtil.isNotEmpty(prefix)) {
        regionList.add(prefix);
    }

    String tenant = this.getTenant();
    // 租户编码:存储默认库的全局缓存,可以重写getTenant并返回null
    if (StrUtil.isNotEmpty(tenant)) {
        regionList.add(tenant);
    }
    // 服务模块名
    String modular = getModular();
    if (StrUtil.isNotEmpty(modular)) {
        regionList.add(modular);
    }
    // 业务类型
    String table = this.getTable();
    ArgumentAssert.notEmpty(table, "缓存业务类型不能为空");
    regionList.add(table);
    // 业务字段
    String field = getField();
    if (StrUtil.isNotEmpty(field)) {
        regionList.add(field);
    }
    // value类型
    ValueType valueType = getValueType();
    if (valueType != null) {
        regionList.add(valueType.name());
    }

    // 业务值
    for (Object unique : uniques) {
        if (ObjectUtil.isNotEmpty(unique)) {
            regionList.add(String.valueOf(unique));
        }
    }
    return CollUtil.join(regionList, StrPool.COLON);
}
/**
 * 根据动态参数 拼接key
 * <p>
 * key命名规范:[租户编码:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
 *
 * @param uniques 动态参数
 * @return
 */
default String getKey(Object... uniques) {
    ArrayList<String> regionList = new ArrayList<>();
    String prefix = this.getPrefix();
    if (StrUtil.isNotEmpty(prefix)) {
        regionList.add(prefix);
    }

    String tenant = this.getTenant();
    // 租户编码:存储默认库的全局缓存,可以重写getTenant并返回null
    if (StrUtil.isNotEmpty(tenant)) {
        regionList.add(tenant);
    }
    // 服务模块名
    String modular = getModular();
    if (StrUtil.isNotEmpty(modular)) {
        regionList.add(modular);
    }
    // 业务类型
    String table = this.getTable();
    ArgumentAssert.notEmpty(table, "缓存业务类型不能为空");
    regionList.add(table);
    // 业务字段
    String field = getField();
    if (StrUtil.isNotEmpty(field)) {
        regionList.add(field);
    }
    // value类型
    ValueType valueType = getValueType();
    if (valueType != null) {
        regionList.add(valueType.name());
    }

    // 业务值
    for (Object unique : uniques) {
        if (ObjectUtil.isNotEmpty(unique)) {
            regionList.add(String.valueOf(unique));
        }
    }
    return CollUtil.join(regionList, StrPool.COLON);
}

Key的命名规则

阿里开发手册中,key命名风格:

  • Redis key命名需具有可读性以及可管理性,不该使用含义不清的key以及特别长的key名;
  • 以英文字母开头,命名中只能出现小写字母、数字、英文点号(.)和英文半角冒号( :;
  • 不要包含特殊字符,如下划线、空格、换行、单双引号以及其他转义字符;

天源云的key命名规则

规定了key的命名规则为:

[前缀:][租户ID:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]

  • 前缀

    可选。 用来区分不同项目或不同环境。 如:区分acuity-cloud 或 acuity-boot、acuity-cloud的dev、test或pro环境

  • 租户ID

    可选。 用来区分不同租户数据缓存。 如: 租户A和租户B分别取ContextUtil.getTenantId()中的值来区分。 若某个缓存需要无需区分租户,可以不设置租户ID

  • 服务模块名

    可选。 用来区分不同服务或功能模块的缓存。 如: 仅在base服务使用和仅在system服务使用的缓存分别用base和system来区分,多个服务共用的缓存可以不设置或者设置为common。

  • 业务类型

    必填。 用来区分不同业务类型的数据,建议设置为表名。 当同一key涉及多个业务时, 可以使用英文半角点号 (.)分割,用来表示一个完整的语义。如: lc:1:base:user.activity:id.id:number:1.1 -> [奖品id1、奖品id2] 租户ID=1,base服务,用户ID=1,活动ID=1的奖品为奖品1(数字类型)、奖品2 lc:2:base:user.activity:id.id:obj:1.2 -> [奖品id1、奖品id2] 租户ID=2,base服务,用户ID=1,活动ID=2的奖品为奖品1(数字类型)、奖品2

  • 业务字段

    可选。用来区分key通过那个字段来区分,通常设置为字段名。 当同一key涉及多个业务时, 可以使用英文半角点号 (.)分割,用来对应不同业务类型的字段。如:业务类型为 user.activity 表示用户的活动,则应该用 id.id 跟 user.activity 对应,表示key中包user的id 和 activity的id

  • value类型

    可选。 用来区分value存储的是完整对象、数字还是字符串。Redis key命名包含key所代表的value类型,以提高可读性。如: obj 表示value存储对象,number表示value存储数字, string 表示value存储字符串。

  • 业务值

    可选。 用来区分同一业务类型的不同行的数据缓存。如: 用户表id为1和i2的缓存,分别将该值设置为1和2。

Key的定义

  • CacheKey

封装redis中String、List、Set等类型的key和过期时间

  • CacheHashKey

封装redis中Hash类型的key、field和过期时间

java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CacheKey {
    /**
     * redis key
     */
    @NonNull
    private String key;
    /**
     * 超时时间 秒
     */
    private Duration expire;

    public CacheKey(final @NonNull String key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "key=" + key + " , expire=" + expire;
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CacheKey {
    /**
     * redis key
     */
    @NonNull
    private String key;
    /**
     * 超时时间 秒
     */
    private Duration expire;

    public CacheKey(final @NonNull String key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "key=" + key + " , expire=" + expire;
    }
}
java
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class CacheHashKey extends CacheKey {
    /**
     * redis hash field
     */
    private Object field;

    public CacheHashKey(@NonNull String key, final Object field) {
        super(key);
        this.field = field;
    }

    public CacheHashKey(@NonNull String key, final Object field, Duration expire) {
        super(key, expire);
        this.field = field;
    }

    public CacheKey tran() {
        return new CacheKey(StrUtil.join(StrPool.COLON, getKey(), getField()), getExpire());
    }
}
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class CacheHashKey extends CacheKey {
    /**
     * redis hash field
     */
    private Object field;

    public CacheHashKey(@NonNull String key, final Object field) {
        super(key);
        this.field = field;
    }

    public CacheHashKey(@NonNull String key, final Object field, Duration expire) {
        super(key, expire);
        this.field = field;
    }

    public CacheKey tran() {
        return new CacheKey(StrUtil.join(StrPool.COLON, getKey(), getField()), getExpire());
    }
}

如何使用

提示

如果你觉得key的命名过长,完全可以将非必填的部分置空,通过CacheKeyBuilder#getKey方法生成出来的key就会短一些。

操作缓存步骤如下:

  • 定义缓存Key构造器:CacheKeyBuilder

    构造器

    Key构造器,又名CacheKeyBuilder。为了方便生成CacheKey,可以使用CacheKeyBuilder来构造CacheKey。

java
@FunctionalInterface
public interface CacheKeyBuilder {

    /**
     * 缓存前缀,用于区分项目,环境等等
     *
     * @return 缓存前缀
     */
    default String getPrefix() {
        return null;
    }

    /**
     * 租户编码,用于区分租户
     * <p>
     * 非租户模式设置成空字符串
     *
     * @return 租户编码
     */
    default String getTenant() {
        return ContextUtil.getTenantId() != null ? String.valueOf(ContextUtil.getTenantId()) : null;
    }

    /**
     * 设置企业id
     *
     * @param tenantId 企业id
     * @return
     */
    default CacheKeyBuilder setTenantId(Long tenantId) {
        return this;
    }

    /**
     * 服务模块名,用于区分后端服务、前端模块等
     *
     * @return 服务模块名
     */
    default String getModular() {
        return null;
    }


    /**
     * key的业务类型, 用于区分表
     *
     * @return 通常是表名
     */
    @NonNull
    String getTable();

    /**
     * key的字段名, 用于区分字段
     *
     * @return 通常是key的字段名
     */
    default String getField() {
        return null;
    }

    /**
     * 缓存的value存储的类型
     *
     * @return value类型
     */
    default ValueType getValueType() {
        return ValueType.obj;
    }

    /**
     * 缓存自动过期时间
     *
     * @return 缓存自动过期时间
     */
    @Nullable
    default Duration getExpire() {
        return null;
    }

    /**
     * 获取通配符
     *
     * @return key 前缀
     */
    default String getPattern() {
        return StrUtil.format("*:{}:*", getTable());
    }

    /**
     * 构建通用KV模式 的 cache key
     * 兼容 redis caffeine
     *
     * @param uniques 参数
     * @return cache key
     */
    default CacheKey key(Object... uniques) {
        String key = getKey(uniques);
        ArgumentAssert.notEmpty(key, "key 不能为空");
        return new CacheKey(key, getExpire());
    }

    /**
     * 构建 redis 类型的 hash cache key
     *
     * @param field   field
     * @param uniques 动态参数
     * @return cache key
     */
    default CacheHashKey hashFieldKey(@NonNull Object field, Object... uniques) {
        String key = getKey(uniques);

        ArgumentAssert.notEmpty(key, "key 不能为空");
        ArgumentAssert.notNull(field, "field 不能为空");
        return new CacheHashKey(key, field, getExpire());
    }

    /**
     * 构建 redis 类型的 hash cache key (无field)
     *
     * @param uniques 动态参数
     * @return
     */
    default CacheHashKey hashKey(Object... uniques) {
        String key = getKey(uniques);

        ArgumentAssert.notEmpty(key, "key 不能为空");
        return new CacheHashKey(key, null, getExpire());
    }

    /**
     * 根据动态参数 拼接key
     * <p>
     * key命名规范:[前缀:][租户ID:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
     *
     * @param uniques 动态参数
     * @return
     */
    default String getKey(Object... uniques) {
        ArrayList<String> regionList = new ArrayList<>();
        String prefix = this.getPrefix();
        if (StrUtil.isNotEmpty(prefix)) {
            regionList.add(prefix);
        }

        String tenant = this.getTenant();
        // 租户编码:存储默认库的全局缓存,可以重写getTenant并返回null
        if (StrUtil.isNotEmpty(tenant)) {
            regionList.add(tenant);
        }
        // 服务模块名
        String modular = getModular();
        if (StrUtil.isNotEmpty(modular)) {
            regionList.add(modular);
        }
        // 业务类型
        String table = this.getTable();
        ArgumentAssert.notEmpty(table, "缓存业务类型不能为空");
        regionList.add(table);
        // 业务字段
        String field = getField();
        if (StrUtil.isNotEmpty(field)) {
            regionList.add(field);
        }
        // value类型
        ValueType valueType = getValueType();
        if (valueType != null) {
            regionList.add(valueType.name());
        }

        // 业务值
        for (Object unique : uniques) {
            if (ObjectUtil.isNotEmpty(unique)) {
                regionList.add(String.valueOf(unique));
            }
        }
        return CollUtil.join(regionList, StrPool.COLON);
    }

    enum ValueType {
        obj, string, number,
    }
}
@FunctionalInterface
public interface CacheKeyBuilder {

    /**
     * 缓存前缀,用于区分项目,环境等等
     *
     * @return 缓存前缀
     */
    default String getPrefix() {
        return null;
    }

    /**
     * 租户编码,用于区分租户
     * <p>
     * 非租户模式设置成空字符串
     *
     * @return 租户编码
     */
    default String getTenant() {
        return ContextUtil.getTenantId() != null ? String.valueOf(ContextUtil.getTenantId()) : null;
    }

    /**
     * 设置企业id
     *
     * @param tenantId 企业id
     * @return
     */
    default CacheKeyBuilder setTenantId(Long tenantId) {
        return this;
    }

    /**
     * 服务模块名,用于区分后端服务、前端模块等
     *
     * @return 服务模块名
     */
    default String getModular() {
        return null;
    }


    /**
     * key的业务类型, 用于区分表
     *
     * @return 通常是表名
     */
    @NonNull
    String getTable();

    /**
     * key的字段名, 用于区分字段
     *
     * @return 通常是key的字段名
     */
    default String getField() {
        return null;
    }

    /**
     * 缓存的value存储的类型
     *
     * @return value类型
     */
    default ValueType getValueType() {
        return ValueType.obj;
    }

    /**
     * 缓存自动过期时间
     *
     * @return 缓存自动过期时间
     */
    @Nullable
    default Duration getExpire() {
        return null;
    }

    /**
     * 获取通配符
     *
     * @return key 前缀
     */
    default String getPattern() {
        return StrUtil.format("*:{}:*", getTable());
    }

    /**
     * 构建通用KV模式 的 cache key
     * 兼容 redis caffeine
     *
     * @param uniques 参数
     * @return cache key
     */
    default CacheKey key(Object... uniques) {
        String key = getKey(uniques);
        ArgumentAssert.notEmpty(key, "key 不能为空");
        return new CacheKey(key, getExpire());
    }

    /**
     * 构建 redis 类型的 hash cache key
     *
     * @param field   field
     * @param uniques 动态参数
     * @return cache key
     */
    default CacheHashKey hashFieldKey(@NonNull Object field, Object... uniques) {
        String key = getKey(uniques);

        ArgumentAssert.notEmpty(key, "key 不能为空");
        ArgumentAssert.notNull(field, "field 不能为空");
        return new CacheHashKey(key, field, getExpire());
    }

    /**
     * 构建 redis 类型的 hash cache key (无field)
     *
     * @param uniques 动态参数
     * @return
     */
    default CacheHashKey hashKey(Object... uniques) {
        String key = getKey(uniques);

        ArgumentAssert.notEmpty(key, "key 不能为空");
        return new CacheHashKey(key, null, getExpire());
    }

    /**
     * 根据动态参数 拼接key
     * <p>
     * key命名规范:[前缀:][租户ID:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
     *
     * @param uniques 动态参数
     * @return
     */
    default String getKey(Object... uniques) {
        ArrayList<String> regionList = new ArrayList<>();
        String prefix = this.getPrefix();
        if (StrUtil.isNotEmpty(prefix)) {
            regionList.add(prefix);
        }

        String tenant = this.getTenant();
        // 租户编码:存储默认库的全局缓存,可以重写getTenant并返回null
        if (StrUtil.isNotEmpty(tenant)) {
            regionList.add(tenant);
        }
        // 服务模块名
        String modular = getModular();
        if (StrUtil.isNotEmpty(modular)) {
            regionList.add(modular);
        }
        // 业务类型
        String table = this.getTable();
        ArgumentAssert.notEmpty(table, "缓存业务类型不能为空");
        regionList.add(table);
        // 业务字段
        String field = getField();
        if (StrUtil.isNotEmpty(field)) {
            regionList.add(field);
        }
        // value类型
        ValueType valueType = getValueType();
        if (valueType != null) {
            regionList.add(valueType.name());
        }

        // 业务值
        for (Object unique : uniques) {
            if (ObjectUtil.isNotEmpty(unique)) {
                regionList.add(String.valueOf(unique));
            }
        }
        return CollUtil.join(regionList, StrPool.COLON);
    }

    enum ValueType {
        obj, string, number,
    }
}
  • 编写代码操作缓存
    • 实现SuperCacheManager
    • 自行调用CacheOps
    • 自行调用CacheHashOps
    • 自行调用RedisOps

欢迎使用天源云Saas快速开发系统