怎样自己制作公司网站上传,杭州网站建设招标,做家教网站赚钱么,天津专门做网站的公司的电话多租户redis缓存分租户处理
那么数据库方面已经做到了拦截#xff0c;但是缓存还是没有分租户#xff0c;还是通通一个文件夹里#xff0c; 想实现上图效果#xff0c;global文件夹里存的是公共缓存。 首先#xff0c;那么就要规定一个俗称#xff0c;缓存名字带有globa…多租户redis缓存分租户处理
那么数据库方面已经做到了拦截但是缓存还是没有分租户还是通通一个文件夹里 想实现上图效果global文件夹里存的是公共缓存。 首先那么就要规定一个俗称缓存名字带有global的为公共缓存其余的为租户缓存
首先先改造springcache的缓存管理器这个是走springcache的也就是说走Cacheable那些时会走这个地方但走了这里就不会走后面的TenantKeyPrefixHandler
public class TenantSpringCacheManager extends PlusSpringCacheManager {public TenantSpringCacheManager() {}Overridepublic Cache getCache(String name) {/*if (CacheUtils.isCommonCache(name)) {return super.getCache(name);}*/if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {return super.getCache(name);}String tenantId TenantHelper.getTentInfo().getTenantId();if (StringUtils.startsWith(name, tenantId)) {// 如果存在则直接返回return super.getCache(name);}return super.getCache(tenantId : name);}}
继承类代码如下
/*** 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.*//*** A {link org.springframework.cache.CacheManager} implementation* backed by Redisson instance.* p* 修改 RedissonSpringCacheManager 源码* 重写 cacheName 处理方法 支持多参数** author Nikita Koksharov**/
SuppressWarnings(unchecked)
public class PlusSpringCacheManager implements CacheManager {private boolean dynamic true;private boolean allowNullValues true;private boolean transactionAware true;MapString, CacheConfig configMap new ConcurrentHashMap();ConcurrentMapString, Cache instanceMap new ConcurrentHashMap();/*** Creates CacheManager supplied by Redisson instance*/public PlusSpringCacheManager() {}/*** Defines possibility of storing {code null} values.* p* Default is codetrue/code** param allowNullValues stores if codetrue/code*/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.* p* Default is codefalse/code** param transactionAware cache is transaction aware if codetrue/code*/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.* p* null parameter setups dynamic mode** param names of caches*/public void setCacheNames(CollectionString 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(MapString, ? extends CacheConfig config) {this.configMap (MapString, CacheConfig) config;}protected CacheConfig createDefaultConfig() {return new CacheConfig();}Overridepublic Cache getCache(String name) {Cache cache instanceMap.get(name);if (cache ! null) {return cache;}if (!dynamic) {return cache;}//去缓存配置Map里查找是否有该缓存 没有就添加一个配置CacheConfig config configMap.get(name);if (config null) {config createDefaultConfig();configMap.put(name, config);}// 重写 cacheName 支持多参数// 重中之重 缓存配置信息 在缓存名中配置 以#号分割 入 sys_cache#时间(毫秒)可以写成xxs的形式#最大空闲时间#最大容量String[] array StringUtils.delimitedListToStringArray(name, #);name array[0];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]));}if (config.getMaxIdleTime() 0 config.getTTL() 0 config.getMaxSize() 0) {return createMap(name, config);}return createMapCache(name, config);}private Cache createMap(String name, CacheConfig config) {RMapObject, Object map RedisUtils.getClient().getMap(name);Cache cache new RedissonCache(map, allowNullValues);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) {RMapCacheObject, Object map RedisUtils.getClient().getMapCache(name);Cache cache new RedissonCache(map, config, allowNullValues);if (transactionAware) {cache new TransactionAwareCacheDecorator(cache);}Cache oldCache instanceMap.putIfAbsent(name, cache);if (oldCache ! null) {cache oldCache;} else {map.setMaxSize(config.getMaxSize());}return cache;}Overridepublic CollectionString getCacheNames() {return Collections.unmodifiableSet(configMap.keySet());}}
这里要提一点假如redis中删除了对应的key值那么此时geCache方法还是能获取对象的不过此时的map为空map 删除前获取的值是有的 删除后获取的对象还有不过值就没有了
改完了springcache之后需要改redis的缓存前缀处理器这个和上面的是两个不同的地方这边是直接拿redis的操作会走这里使用springcache后不会再走这边代码如下
/*** 多租户redis缓存key前缀处理** author Lion Li*/
public class TenantKeyPrefixHandler extends KeyPrefixHandler {public TenantKeyPrefixHandler(String keyPrefix) {super(keyPrefix);}/*** 增加前缀*/Overridepublic String map(String name) {if (StrUtil.isBlank(name)) {return null;}/*if (CacheUtils.isCommonCache(name)) {return super.map(name);}*/if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {return super.map(name);}String tenantId TenantHelper.getTentInfo().getTenantId();if (StringUtils.startsWith(name, tenantId)) {// 如果存在则直接返回return super.map(name);}return super.map(tenantId : name);}/*** 去除前缀*/Overridepublic String unmap(String name) {String unmap super.unmap(name);if (StrUtil.isBlank(unmap)) {return null;}/*if (CacheUtils.isCommonCache(unmap)) {return super.unmap(name);}*/if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {return super.unmap(name);}String tenantId TenantHelper.getTentInfo().getTenantId();if (StringUtils.startsWith(unmap, tenantId)) {// 如果存在则删除return unmap.substring((tenantId :).length());}return unmap;}}继承的类
/*** redis缓存key前缀处理** author ye* date 2022/7/14 17:44* since 4.3.1*/
public class KeyPrefixHandler implements NameMapper {private final String keyPrefix;public KeyPrefixHandler(String keyPrefix) {//前缀为空 则返回空前缀this.keyPrefix StringUtils.isBlank(keyPrefix) ? : keyPrefix :;}/*** 增加前缀*/Overridepublic String map(String name) {if (StringUtils.isBlank(name)) {return null;}if (StringUtils.isNotBlank(keyPrefix) !name.startsWith(keyPrefix)) {return keyPrefix name;}return name;}/*** 去除前缀*/Overridepublic String unmap(String name) {if (StringUtils.isBlank(name)) {return null;}if (StringUtils.isNotBlank(keyPrefix) name.startsWith(keyPrefix)) {return name.substring(keyPrefix.length());}return name;}}然后在redis配置类中添加上述的配置
Slf4j
Configuration
EnableCaching
EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig extends CachingConfigurerSupport {Autowiredprivate RedissonProperties redissonProperties;Autowiredprivate ObjectMapper objectMapper;Beanpublic RedissonAutoConfigurationCustomizer redissonCustomizer() {return config - {TenantKeyPrefixHandler nameMapper new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
...}})/*** 自定义缓存管理器 整合spring-cache*/Beanpublic CacheManager cacheManager() {return new TenantSpringCacheManager();}
}到这里几乎是可以完成了但是还有个关键点就是登录后从登录域里拿租户id那么登录域也是从redis里面拿登录信息的所以token不能放在缓存的租户文件夹里只能放在全局文件夹里。本项目使用的是satoken 先自定义一个satokendao层用于指定
/*** SaToken 认证数据持久层 适配多租户** author Lion Li*/
public class TenantSaTokenDao extends PlusSaTokenDao {Overridepublic String get(String key) {return super.get(GlobalConstants.GLOBAL_REDIS_KEY key);}Overridepublic void set(String key, String value, long timeout) {super.set(GlobalConstants.GLOBAL_REDIS_KEY key, value, timeout);}/*** 修修改指定key-value键值对 (过期时间不变)*/Overridepublic void update(String key, String value) {long expire getTimeout(key);// -2 无此键if (expire NOT_VALUE_EXPIRE) {return;}this.set(key, value, expire);}/*** 删除Value*/Overridepublic void delete(String key) {super.delete(GlobalConstants.GLOBAL_REDIS_KEY key);}/*** 获取Value的剩余存活时间 (单位: 秒)*/Overridepublic long getTimeout(String key) {return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY key);}/*** 修改Value的剩余存活时间 (单位: 秒)*/Overridepublic void updateTimeout(String key, long timeout) {// 判断是否想要设置为永久if (timeout NEVER_EXPIRE) {long expire getTimeout(key);if (expire NEVER_EXPIRE) {// 如果其已经被设置为永久则不作任何处理} else {// 如果尚未被设置为永久那么再次set一次this.set(key, this.get(key), timeout);}return;}RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY key, Duration.ofSeconds(timeout));}/*** 获取Object如无返空*/Overridepublic Object getObject(String key) {return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY key);}/*** 写入Object并设定存活时间 (单位: 秒)*/Overridepublic void setObject(String key, Object object, long timeout) {super.setObject(GlobalConstants.GLOBAL_REDIS_KEY key, object, timeout);}/*** 更新Object (过期时间不变)*/Overridepublic void updateObject(String key, Object object) {long expire getObjectTimeout(key);// -2 无此键if (expire NOT_VALUE_EXPIRE) {return;}this.setObject(key, object, expire);}/*** 删除Object*/Overridepublic void deleteObject(String key) {super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY key);}/*** 获取Object的剩余存活时间 (单位: 秒)*/Overridepublic long getObjectTimeout(String key) {return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY key);}/*** 修改Object的剩余存活时间 (单位: 秒)*/Overridepublic void updateObjectTimeout(String key, long timeout) {// 判断是否想要设置为永久if (timeout NEVER_EXPIRE) {long expire getObjectTimeout(key);if (expire NEVER_EXPIRE) {// 如果其已经被设置为永久则不作任何处理} else {// 如果尚未被设置为永久那么再次set一次this.setObject(key, this.getObject(key), timeout);}return;}RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY key, Duration.ofSeconds(timeout));}/*** 搜索数据*/Overridepublic ListString searchData(String prefix, String keyword, int start, int size, boolean sortType) {return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY prefix, keyword, start, size, sortType);}
}然后在配置类里控制反转 /*** 自定义dao层存储*/Beanpublic SaTokenDao saTokenDao() {
// return new PlusSaTokenDao();return new TenantSaTokenDao();}