diff --git a/pigx-common/pigx-common-cache/pom.xml b/pigx-common/pigx-common-cache/pom.xml
new file mode 100644
index 000000000..db9e4e73b
--- /dev/null
+++ b/pigx-common/pigx-common-cache/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pigx-common
+ ${pigx.version}
+
+
+ pigx-common-cache
+ jar
+
+ pigx 缓存工具类
+
+
+
+
+
+ com.pig4cloud
+ pigx-common-security
+ ${pigx.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
diff --git a/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/DefaultRedisCacheWriter.java b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/DefaultRedisCacheWriter.java
new file mode 100644
index 000000000..b8be1f8b6
--- /dev/null
+++ b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/DefaultRedisCacheWriter.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+
+package com.pig4cloud.pigx.common.cache;
+
+import org.springframework.dao.PessimisticLockingFailureException;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
+import org.springframework.data.redis.core.types.Expiration;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * {@link RedisCacheWriter} implementation capable of reading/writing binary data from/to Redis in {@literal standalone}
+ * and {@literal cluster} environments. Works upon a given {@link RedisConnectionFactory} to obtain the actual
+ * {@link RedisConnection}.
+ * {@link DefaultRedisCacheWriter} can be used in
+ * {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking} or
+ * {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking} mode. While
+ * {@literal non-locking} aims for maximum performance it may result in overlapping, non atomic, command execution for
+ * operations spanning multiple Redis interactions like {@code putIfAbsent}. The {@literal locking} counterpart prevents
+ * command overlap by setting an explicit lock key and checking against presence of this key which leads to additional
+ * requests and potential command wait times.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 2.0
+ */
+class DefaultRedisCacheWriter implements RedisCacheWriter {
+
+ private final RedisConnectionFactory connectionFactory;
+ private final Duration sleepTime;
+
+ /**
+ * @param connectionFactory must not be {@literal null}.
+ */
+ DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory) {
+ this(connectionFactory, Duration.ZERO);
+ }
+
+ /**
+ * @param connectionFactory must not be {@literal null}.
+ * @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO}
+ * to disable locking.
+ */
+ private DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
+
+ Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
+ Assert.notNull(sleepTime, "SleepTime must not be null!");
+
+ this.connectionFactory = connectionFactory;
+ this.sleepTime = sleepTime;
+ }
+
+ @Override
+ public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
+
+ Assert.notNull(name, "Name must not be null!");
+ Assert.notNull(key, "Key must not be null!");
+ Assert.notNull(value, "Value must not be null!");
+
+ execute(name, connection -> {
+
+ if (shouldExpireWithin(ttl)) {
+ connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
+ } else {
+ connection.set(key, value);
+ }
+
+ return "OK";
+ });
+ }
+
+
+ @Override
+ public byte[] get(String name, byte[] key) {
+
+ Assert.notNull(name, "Name must not be null!");
+ Assert.notNull(key, "Key must not be null!");
+
+ return execute(name, connection -> connection.get(key));
+ }
+
+
+ @Override
+ public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
+
+ Assert.notNull(name, "Name must not be null!");
+ Assert.notNull(key, "Key must not be null!");
+ Assert.notNull(value, "Value must not be null!");
+
+ return execute(name, connection -> {
+
+ if (isLockingCacheWriter()) {
+ doLock(name, connection);
+ }
+
+ try {
+ if (connection.setNX(key, value)) {
+
+ if (shouldExpireWithin(ttl)) {
+ connection.pExpire(key, ttl.toMillis());
+ }
+ return null;
+ }
+
+ return connection.get(key);
+ } finally {
+
+ if (isLockingCacheWriter()) {
+ doUnlock(name, connection);
+ }
+ }
+ });
+ }
+
+
+ @Override
+ public void remove(String name, byte[] key) {
+
+ Assert.notNull(name, "Name must not be null!");
+ Assert.notNull(key, "Key must not be null!");
+
+ execute(name, connection -> connection.del(key));
+ }
+
+
+ @Override
+ public void clean(String name, byte[] pattern) {
+
+ Assert.notNull(name, "Name must not be null!");
+ Assert.notNull(pattern, "Pattern must not be null!");
+
+ execute(name, connection -> {
+
+ boolean wasLocked = false;
+
+ try {
+
+ if (isLockingCacheWriter()) {
+ doLock(name, connection);
+ wasLocked = true;
+ }
+
+ byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
+ .toArray(new byte[0][]);
+
+ if (keys.length > 0) {
+ connection.del(keys);
+ }
+ } finally {
+
+ if (wasLocked && isLockingCacheWriter()) {
+ doUnlock(name, connection);
+ }
+ }
+
+ return "OK";
+ });
+ }
+
+ /**
+ * Explicitly set a write lock on a cache.
+ *
+ * @param name the name of the cache to lock.
+ */
+ void lock(String name) {
+ execute(name, connection -> doLock(name, connection));
+ }
+
+ /**
+ * Explicitly remove a write lock from a cache.
+ *
+ * @param name the name of the cache to unlock.
+ */
+ void unlock(String name) {
+ executeLockFree(connection -> doUnlock(name, connection));
+ }
+
+ private Boolean doLock(String name, RedisConnection connection) {
+ return connection.setNX(createCacheLockKey(name), new byte[0]);
+ }
+
+ private Long doUnlock(String name, RedisConnection connection) {
+ return connection.del(createCacheLockKey(name));
+ }
+
+ boolean doCheckLock(String name, RedisConnection connection) {
+ return connection.exists(createCacheLockKey(name));
+ }
+
+ /**
+ * @return {@literal true} if {@link RedisCacheWriter} uses locks.
+ */
+ private boolean isLockingCacheWriter() {
+ return !sleepTime.isZero() && !sleepTime.isNegative();
+ }
+
+ private T execute(String name, Function callback) {
+
+ RedisConnection connection = connectionFactory.getConnection();
+ try {
+
+ checkAndPotentiallyWaitUntilUnlocked(name, connection);
+ return callback.apply(connection);
+ } finally {
+ connection.close();
+ }
+ }
+
+ private void executeLockFree(Consumer callback) {
+
+ RedisConnection connection = connectionFactory.getConnection();
+
+ try {
+ callback.accept(connection);
+ } finally {
+ connection.close();
+ }
+ }
+
+ private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
+
+ if (!isLockingCacheWriter()) {
+ return;
+ }
+
+ try {
+
+ while (doCheckLock(name, connection)) {
+ Thread.sleep(sleepTime.toMillis());
+ }
+ } catch (InterruptedException ex) {
+
+ // Re-interrupt current thread, to allow other participants to react.
+ Thread.currentThread().interrupt();
+
+ throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
+ ex);
+ }
+ }
+
+ private static boolean shouldExpireWithin(@Nullable Duration ttl) {
+ return ttl != null && !ttl.isZero() && !ttl.isNegative();
+ }
+
+ private static byte[] createCacheLockKey(String name) {
+ return (name + "~lock").getBytes(StandardCharsets.UTF_8);
+ }
+}
+
diff --git a/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisAutoCacheManager.java b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisAutoCacheManager.java
new file mode 100644
index 000000000..6117dc19b
--- /dev/null
+++ b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisAutoCacheManager.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+
+package com.pig4cloud.pigx.common.cache;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.common.security.util.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.Cache;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+import java.util.Map;
+
+/**
+ * redis cache 扩展cache name自动化配置
+ *
+ * @author L.cm
+ * @author lengleng
+ *
+ * cachename = xx#ttl
+ */
+@Slf4j
+public class RedisAutoCacheManager extends RedisCacheManager {
+
+ public static final String SPLIT_FLAG = "#";
+
+ public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
+ Map initialCacheConfigurations, boolean allowInFlightCacheCreation) {
+ super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
+ }
+
+ @Override
+ protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
+ if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+ String[] cacheArray = name.split(SPLIT_FLAG);
+ if (cacheArray.length < 2) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+ String cacheName = cacheArray[0] + ":" + SecurityUtils.getTenantId();
+ if (cacheConfig != null) {
+ long cacheAge = Long.getLong(cacheArray[1], -1);
+ cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(cacheAge));
+ }
+ return super.createRedisCache(cacheName, cacheConfig);
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ return super.getCache(name + ":" + SecurityUtils.getTenantId());
+ }
+}
diff --git a/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheAutoConfiguration.java b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheAutoConfiguration.java
new file mode 100644
index 000000000..4de9ec321
--- /dev/null
+++ b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheAutoConfiguration.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+
+package com.pig4cloud.pigx.common.cache;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.cache.CacheProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.lang.Nullable;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 扩展redis-cache支持注解cacheName添加超时时间
+ *
+ * @author L.cm
+ */
+@Configuration
+@AutoConfigureAfter({RedisAutoConfiguration.class})
+@ConditionalOnBean({RedisConnectionFactory.class})
+@ConditionalOnMissingBean({CacheManager.class})
+@EnableConfigurationProperties(CacheProperties.class)
+public class RedisCacheAutoConfiguration {
+ private final CacheProperties cacheProperties;
+ private final CacheManagerCustomizers customizerInvoker;
+ @Nullable
+ private final RedisCacheConfiguration redisCacheConfiguration;
+
+ RedisCacheAutoConfiguration(CacheProperties cacheProperties,
+ CacheManagerCustomizers customizerInvoker,
+ ObjectProvider redisCacheConfiguration) {
+ this.cacheProperties = cacheProperties;
+ this.customizerInvoker = customizerInvoker;
+ this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
+ }
+
+ @Bean
+ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
+ DefaultRedisCacheWriter redisCacheWriter = new DefaultRedisCacheWriter(connectionFactory);
+ RedisCacheConfiguration cacheConfiguration = this.determineConfiguration(resourceLoader.getClassLoader());
+ List cacheNames = this.cacheProperties.getCacheNames();
+ Map initialCaches = new LinkedHashMap<>();
+ if (!cacheNames.isEmpty()) {
+ Map cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
+ cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
+ initialCaches.putAll(cacheConfigMap);
+ }
+ boolean allowInFlightCacheCreation = true;
+ boolean enableTransactions = false;
+ RedisAutoCacheManager cacheManager = new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration,
+ initialCaches, allowInFlightCacheCreation);
+ cacheManager.setTransactionAware(enableTransactions);
+ return this.customizerInvoker.customize(cacheManager);
+ }
+
+ private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
+ if (this.redisCacheConfiguration != null) {
+ return this.redisCacheConfiguration;
+ } else {
+ CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
+ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+ config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
+ if (redisProperties.getTimeToLive() != null) {
+ config = config.entryTtl(redisProperties.getTimeToLive());
+ }
+
+ if (redisProperties.getKeyPrefix() != null) {
+ config = config.prefixKeysWith(redisProperties.getKeyPrefix());
+ }
+
+ if (!redisProperties.isCacheNullValues()) {
+ config = config.disableCachingNullValues();
+ }
+
+ if (!redisProperties.isUseKeyPrefix()) {
+ config = config.disableKeyPrefix();
+ }
+
+ return config;
+ }
+ }
+}
diff --git a/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheManagerConfig.java b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheManagerConfig.java
new file mode 100644
index 000000000..8a020cbd7
--- /dev/null
+++ b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheManagerConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+
+package com.pig4cloud.pigx.common.cache;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * CacheManagerCustomizers配置
+ *
+ * @author L.cm
+ */
+@Configuration
+@ConditionalOnMissingBean(CacheManagerCustomizers.class)
+public class RedisCacheManagerConfig {
+
+ @Bean
+ public CacheManagerCustomizers cacheManagerCustomizers(
+ ObjectProvider>> customizers) {
+ return new CacheManagerCustomizers(customizers.getIfAvailable());
+ }
+}
diff --git a/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisKeySerializer.java b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisKeySerializer.java
new file mode 100644
index 000000000..ad914e488
--- /dev/null
+++ b/pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisKeySerializer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+
+package com.pig4cloud.pigx.common.cache;
+import org.springframework.cache.interceptor.SimpleKey;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 将redis key序列化为字符串
+ *
+ *
+ * spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
+ *