Kaynağa Gözat

:sparkles: 添加新特性。 扩展spring cache 支持ttl 和自动租户管理

冷冷 6 yıl önce
ebeveyn
işleme
668c62bc73
17 değiştirilmiş dosya ile 717 ekleme ve 88 silme
  1. 49 0
      pigx-common/pigx-common-cache/pom.xml
  2. 275 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/DefaultRedisCacheWriter.java
  3. 72 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisAutoCacheManager.java
  4. 112 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheAutoConfiguration.java
  5. 43 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisCacheManagerConfig.java
  6. 74 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisKeySerializer.java
  7. 54 0
      pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisTemplateConfig.java
  8. 1 0
      pigx-common/pigx-common-cache/src/main/resources/META-INF/spring-devtools.properties
  9. 5 0
      pigx-common/pigx-common-cache/src/main/resources/META-INF/spring.factories
  10. 0 77
      pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/config/RedisConfig.java
  11. 0 1
      pigx-common/pigx-common-core/src/main/resources/META-INF/spring.factories
  12. 1 0
      pigx-common/pom.xml
  13. 6 0
      pigx-upms/pigx-upms-biz/pom.xml
  14. 14 8
      pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/entity/LeaveBill.java
  15. 3 0
      pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/EditorServiceImpl.java
  16. 6 1
      pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/ModelServiceImpl.java
  17. 2 1
      pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/ProcessServiceImpl.java

+ 49 - 0
pigx-common/pigx-common-cache/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~
+  ~      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)
+  ~
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.pig4cloud</groupId>
+		<artifactId>pigx-common</artifactId>
+		<version>${pigx.version}</version>
+	</parent>
+
+	<artifactId>pigx-common-cache</artifactId>
+	<packaging>jar</packaging>
+
+	<description>pigx 缓存工具类</description>
+
+
+	<dependencies>
+		<!--工具类核心包 要从线程中获取租户ID-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pigx-common-security</artifactId>
+			<version>${pigx.version}</version>
+		</dependency>
+		<!--缓存依赖-->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis</artifactId>
+		</dependency>
+	</dependencies>
+</project>

+ 275 - 0
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}. <br />
+ * {@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> T execute(String name, Function<RedisConnection, T> callback) {
+
+		RedisConnection connection = connectionFactory.getConnection();
+		try {
+
+			checkAndPotentiallyWaitUntilUnlocked(name, connection);
+			return callback.apply(connection);
+		} finally {
+			connection.close();
+		}
+	}
+
+	private void executeLockFree(Consumer<RedisConnection> 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);
+	}
+}
+

+ 72 - 0
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
+ * <p>
+ * cachename = xx#ttl
+ */
+@Slf4j
+public class RedisAutoCacheManager extends RedisCacheManager {
+
+	public static final String SPLIT_FLAG = "#";
+
+	public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
+								 Map<String, RedisCacheConfiguration> 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());
+	}
+}

+ 112 - 0
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> 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<String> cacheNames = this.cacheProperties.getCacheNames();
+		Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
+		if (!cacheNames.isEmpty()) {
+			Map<String, RedisCacheConfiguration> 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;
+		}
+	}
+}

+ 43 - 0
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<List<CacheManagerCustomizer<?>>> customizers) {
+		return new CacheManagerCustomizers(customizers.getIfAvailable());
+	}
+}

+ 74 - 0
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序列化为字符串
+ *
+ * <p>
+ *     spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
+ * </p>
+ *
+ * @author L.cm
+ */
+public class RedisKeySerializer implements RedisSerializer<Object> {
+	private final Charset charset;
+	private final ConversionService converter;
+
+	public RedisKeySerializer() {
+		this(StandardCharsets.UTF_8);
+	}
+
+	public RedisKeySerializer(Charset charset) {
+		Assert.notNull(charset, "Charset must not be null!");
+		this.charset = charset;
+		this.converter = DefaultConversionService.getSharedInstance();
+	}
+
+	@Override
+	public Object deserialize(byte[] bytes) {
+		throw new RuntimeException("Used only for serializing key, not for deserialization.");
+	}
+
+	@Override
+	@Nullable
+	public byte[] serialize(@Nullable Object object) {
+		if (object == null) {
+			return null;
+		}
+		String key;
+		if (object instanceof SimpleKey) {
+			key = "";
+		} else if (object instanceof String) {
+			key = (String) object;
+		} else {
+			key = converter.convert(object, String.class);
+		}
+		return key.getBytes(this.charset);
+	}
+
+}

+ 54 - 0
pigx-common/pigx-common-cache/src/main/java/com/pig4cloud/pigx/common/cache/RedisTemplateConfig.java

@@ -0,0 +1,54 @@
+/*
+ *    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 lombok.AllArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+
+/**
+ * RedisTemplate  配置
+ *
+ * @author L.cm
+ */
+@EnableCaching
+@Configuration
+@ConditionalOnBean(RedisConnectionFactory.class)
+@AllArgsConstructor
+public class RedisTemplateConfig {
+	private final RedisConnectionFactory redisConnectionFactory;
+
+	@Bean
+	@ConditionalOnMissingBean(RedisTemplate.class)
+	public RedisTemplate<String, Object> redisTemplate() {
+		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+		redisTemplate.setKeySerializer(new RedisKeySerializer());
+		redisTemplate.setHashKeySerializer(new RedisKeySerializer());
+		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
+		redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+		redisTemplate.setConnectionFactory(redisConnectionFactory);
+		return redisTemplate;
+	}
+
+}

+ 1 - 0
pigx-common/pigx-common-cache/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.pigx-common-log=/pigx-common-cache[\\w-]+\.jar

+ 5 - 0
pigx-common/pigx-common-cache/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,5 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+    com.pig4cloud.pigx.common.cache.RedisTemplateConfig,\
+    com.pig4cloud.pigx.common.cache.RedisCacheManagerConfig,\
+    com.pig4cloud.pigx.common.cache.RedisCacheAutoConfiguration
+

+ 0 - 77
pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/config/RedisConfig.java

@@ -1,77 +0,0 @@
-/*
- *
- *      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.core.config;
-
-import lombok.AllArgsConstructor;
-import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.core.*;
-import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
-
-/**
- * @author lengleng
- * @date 2018/6/23
- * Redis 配置类
- */
-@EnableCaching
-@Configuration
-@AllArgsConstructor
-public class RedisConfig {
-	private final RedisConnectionFactory factory;
-
-	@Bean
-	public RedisTemplate<String, Object> redisTemplate() {
-		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
-		redisTemplate.setKeySerializer(new StringRedisSerializer());
-		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
-		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
-		redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
-		redisTemplate.setConnectionFactory(factory);
-		return redisTemplate;
-	}
-
-	@Bean
-	public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
-		return redisTemplate.opsForHash();
-	}
-
-	@Bean
-	public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
-		return redisTemplate.opsForValue();
-	}
-
-	@Bean
-	public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
-		return redisTemplate.opsForList();
-	}
-
-	@Bean
-	public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
-		return redisTemplate.opsForSet();
-	}
-
-	@Bean
-	public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
-		return redisTemplate.opsForZSet();
-	}
-}

+ 0 - 1
pigx-common/pigx-common-core/src/main/resources/META-INF/spring.factories

@@ -1,6 +1,5 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
   com.pig4cloud.pigx.common.core.config.JacksonConfig,\
-  com.pig4cloud.pigx.common.core.config.RedisConfig,\
   com.pig4cloud.pigx.common.core.config.RestTemplateConfig,\
   com.pig4cloud.pigx.common.core.exception.GlobalExceptionHandler,\
   com.pig4cloud.pigx.common.core.util.SpringContextHolder

+ 1 - 0
pigx-common/pom.xml

@@ -33,6 +33,7 @@
 	<description>pigx 公共聚合模块</description>
 
 	<modules>
+		<module>pigx-common-cache</module>
 		<module>pigx-common-core</module>
 		<module>pigx-common-job</module>
 		<module>pigx-common-log</module>

+ 6 - 0
pigx-upms/pigx-upms-biz/pom.xml

@@ -45,6 +45,12 @@
 			<artifactId>pigx-common-log</artifactId>
 			<version>${pigx.version}</version>
 		</dependency>
+		<!--缓存依赖-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pigx-common-cache</artifactId>
+			<version>${pigx.version}</version>
+		</dependency>
 		<!--swagger-->
 		<dependency>
 			<groupId>com.pig4cloud</groupId>

+ 14 - 8
pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/entity/LeaveBill.java

@@ -24,7 +24,8 @@ import com.baomidou.mybatisplus.annotations.TableName;
 import com.baomidou.mybatisplus.enums.IdType;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-  import java.io.Serializable;
+
+import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
@@ -74,11 +75,16 @@ public class LeaveBill extends Model<LeaveBill> {
 	@TableLogic
 	private String delFlag;
 
-  /**
-   * 主键值
-   */
-  @Override
-  protected Serializable pkVal() {
-    return this.leaveId;
-  }
+	/**
+	 * 租户ID
+	 */
+	private Integer tenantId;
+
+	/**
+	 * 主键值
+	 */
+	@Override
+	protected Serializable pkVal() {
+		return this.leaveId;
+	}
 }

+ 3 - 0
pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/EditorServiceImpl.java

@@ -21,6 +21,7 @@ import cn.hutool.core.util.CharsetUtil;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.pig4cloud.pigx.act.service.EditorService;
+import com.pig4cloud.pigx.common.security.util.SecurityUtils;
 import lombok.AllArgsConstructor;
 import org.activiti.engine.ActivitiException;
 import org.activiti.engine.RepositoryService;
@@ -110,6 +111,8 @@ public class EditorServiceImpl implements EditorService {
 			modelJson.put(MODEL_DESCRIPTION, description);
 			model.setMetaInfo(modelJson.toString());
 			model.setName(name);
+			model.setTenantId(String.valueOf(SecurityUtils.getTenantId()));
+			
 			repositoryService.saveModel(model);
 			repositoryService.addModelEditorSource(model.getId(), jsonXml.getBytes(CharsetUtil.UTF_8));
 			ByteArrayOutputStream outStream = new ByteArrayOutputStream();

+ 6 - 1
pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/ModelServiceImpl.java

@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.pig4cloud.pigx.act.service.ModelService;
 import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
+import com.pig4cloud.pigx.common.security.util.SecurityUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.activiti.bpmn.model.BpmnModel;
@@ -87,6 +88,7 @@ public class ModelServiceImpl implements ModelService {
 			modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, model.getVersion());
 			modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, desc);
 			model.setMetaInfo(modelObjectNode.toString());
+			model.setTenantId(String.valueOf(SecurityUtils.getTenantId()));
 
 			repositoryService.saveModel(model);
 			repositoryService.addModelEditorSource(model.getId(), editorNode.toString().getBytes("utf-8"));
@@ -154,10 +156,13 @@ public class ModelServiceImpl implements ModelService {
 			Deployment deployment = repositoryService
 				.createDeployment().name(model.getName())
 				.addBpmnModel(processName, bpmnModel)
+				.tenantId(String.valueOf(SecurityUtils.getTenantId()))
 				.deploy();
 
 			// 设置流程分类
-			List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
+			List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
+				.deploymentId(deployment.getId())
+				.list();
 
 			list.stream().forEach(processDefinition ->
 				repositoryService.setProcessDefinitionCategory(processDefinition.getId(), model.getCategory()));

+ 2 - 1
pigx-visual/pigx-activiti/src/main/java/com/pig4cloud/pigx/act/service/impl/ProcessServiceImpl.java

@@ -27,6 +27,7 @@ import com.pig4cloud.pigx.act.service.ProcessService;
 import com.pig4cloud.pigx.common.core.constant.enums.EnumProcessStatus;
 import com.pig4cloud.pigx.common.core.constant.enums.EnumResourceType;
 import com.pig4cloud.pigx.common.core.constant.enums.EnumTaskStatus;
+import com.pig4cloud.pigx.common.security.util.SecurityUtils;
 import lombok.AllArgsConstructor;
 import org.activiti.engine.RepositoryService;
 import org.activiti.engine.RuntimeService;
@@ -164,7 +165,7 @@ public class ProcessServiceImpl implements ProcessService {
 		//3: 格式:LeaveBill_id的形式(使用流程变量)
 		String businessKey = key + "_" + leaveBill.getLeaveId();
 		//4:使用流程定义的key,启动流程实例,正在执行的执行对象表中的字段BUSINESS_KEY添加业务数据,同时让流程关联业务
-		runtimeService.startProcessInstanceByKey(key, businessKey);
+		runtimeService.startProcessInstanceByKeyAndTenantId(key, businessKey, String.valueOf(SecurityUtils.getTenantId()));
 		leaveBillMapper.updateById(leaveBill);
 		return Boolean.TRUE;
 	}