多租户分缓存处理

多租户redis缓存分租户处理

那么数据库方面已经做到了拦截,但是缓存还是没有分租户,还是通通一个文件夹里,多租户分缓存处理_第1张图片
想实现上图效果,global文件夹里存的是公共缓存。
首先,那么就要规定一个俗称,缓存名字带有global的为公共缓存,其余的为租户缓存

首先先改造springcache的缓存管理器,这个是走springcache的,也就是说走@Cacheable那些时会走这个地方,但走了这里就不会走后面的TenantKeyPrefixHandler

public class TenantSpringCacheManager extends PlusSpringCacheManager {

    public TenantSpringCacheManager() {
    }

    @Override
    public 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.
 * 

* 修改 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<String, CacheConfig> configMap = new ConcurrentHashMap<>(); ConcurrentMap<String, Cache> 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<String> 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<String, ? extends CacheConfig> config) { this.configMap = (Map<String, CacheConfig>) config; } protected CacheConfig createDefaultConfig() { return new CacheConfig(); } @Override public 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) { RMap<Object, 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) { RMapCache<Object, 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; } @Override public Collection<String> getCacheNames() { return Collections.unmodifiableSet(configMap.keySet()); } }

这里要提一点,假如redis中删除了对应的key值,那么此时geCache方法还是能获取对象的,不过此时的map为空map
删除前获取的值是有的:
多租户分缓存处理_第2张图片
删除后获取的对象还有,不过值就没有了多租户分缓存处理_第3张图片

改完了springcache之后需要改redis的缓存前缀处理器,这个和上面的是两个不同的地方,这边是直接拿redis的操作会走这里,使用springcache后不会再走这边,代码如下

/**
 * 多租户redis缓存key前缀处理
 *
 * @author Lion Li
 */
public class TenantKeyPrefixHandler extends KeyPrefixHandler {

    public TenantKeyPrefixHandler(String keyPrefix) {
        super(keyPrefix);
    }

    /**
     * 增加前缀
     */
    @Override
    public 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);
    }

    /**
     * 去除前缀
     */
    @Override
    public 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 + ":";
    }

    /**
     * 增加前缀
     */
    @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;
    }

}

然后在redis配置类中添加上述的配置

@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig extends CachingConfigurerSupport {

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
...
		}})
    /**
     * 自定义缓存管理器 整合spring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new TenantSpringCacheManager();
    }
}

到这里几乎是可以完成了,但是还有个关键点,就是登录后从登录域里拿租户id,那么登录域也是从redis里面拿登录信息的,所以token不能放在缓存的租户文件夹里,只能放在全局文件夹里。本项目使用的是satoken
先自定义一个satokendao层,用于指定

/**
 * SaToken 认证数据持久层 适配多租户
 *
 * @author Lion Li
 */
public class TenantSaTokenDao extends PlusSaTokenDao {

    @Override
    public String get(String key) {
        return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    @Override
    public void set(String key, String value, long timeout) {
        super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
    }

    /**
     * 修修改指定key-value键值对 (过期时间不变)
     */
    @Override
    public void update(String key, String value) {
        long expire = getTimeout(key);
        // -2 = 无此键
        if (expire == NOT_VALUE_EXPIRE) {
            return;
        }
        this.set(key, value, expire);
    }

    /**
     * 删除Value
     */
    @Override
    public void delete(String key) {
        super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 获取Value的剩余存活时间 (单位: 秒)
     */
    @Override
    public long getTimeout(String key) {
        return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 修改Value的剩余存活时间 (单位: 秒)
     */
    @Override
    public 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,如无返空
     */
    @Override
    public Object getObject(String key) {
        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 写入Object,并设定存活时间 (单位: 秒)
     */
    @Override
    public void setObject(String key, Object object, long timeout) {
        super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
    }

    /**
     * 更新Object (过期时间不变)
     */
    @Override
    public void updateObject(String key, Object object) {
        long expire = getObjectTimeout(key);
        // -2 = 无此键
        if (expire == NOT_VALUE_EXPIRE) {
            return;
        }
        this.setObject(key, object, expire);
    }

    /**
     * 删除Object
     */
    @Override
    public void deleteObject(String key) {
        super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 获取Object的剩余存活时间 (单位: 秒)
     */
    @Override
    public long getObjectTimeout(String key) {
        return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 修改Object的剩余存活时间 (单位: 秒)
     */
    @Override
    public 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));
    }


    /**
     * 搜索数据
     */
    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
        return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
    }
}

然后在配置类里控制反转

    /**
     * 自定义dao层存储
     */
    @Bean
    public SaTokenDao saTokenDao() {
//        return new PlusSaTokenDao();
        return new TenantSaTokenDao();
    }

你可能感兴趣的:(缓存,spring,redis)