多租户分缓存处理

多租户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;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.* <p>* Default is <code>true</code>** @param allowNullValues stores if <code>true</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 <code>false</code>** @param transactionAware cache is transaction aware if <code>true</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(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();}@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) {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;}@Overridepublic Collection<String> 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 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层存储*/@Beanpublic SaTokenDao saTokenDao() {
//        return new PlusSaTokenDao();return new TenantSaTokenDao();}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/10284.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库应用:MySQL MHA高可用集群

目录 一、理论 1.MHA 2.MySQL MHA部署准备 3.MySQL MHA部署 二、实验 1.MHA部署 三、问题 1.中英文符兼容报错 2.MHA测试 ssh 无密码认证语法报错 3.MHA测试 ssh 无密码认证log-bin报错 4.MHA测试 mysql 主从连接情况报错slave replication 5.MHA测试 mysql 主从连…

Elasticsearch监控工具Cerebro安装

Elasticsearch监控工具Cerebro安装 1、在windwos下的安装 1.1 下载安装包 https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip 1.2 解压 1.3 修改配置文件 如果需要修改相关信息&#xff0c;编辑C:\zsxsoftware\cerebro-0.9.4\conf\applica…

css3的filter图片滤镜使用

业务介绍 默认&#xff1a;第一个图标为选中状态&#xff0c;其他三个图标事未选中状态 样式&#xff1a;选中状态是深蓝&#xff0c;未选中状体是浅蓝 交互&#xff1a;鼠标放上去选中&#xff0c;其他未选中&#xff0c;鼠标离开时候保持当前选中状态 实现&#xff1a;目前…

Component template should contain exactly one root element

在vue中报错&#xff1a; Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead报错的大致意思是&#xff1a;组件的模板应该只能包含一个根元素&#xff0c;也就是是说作为元素的直…

【每日一题】—— C - (K+1)-th Largest Number (AtCoder Beginner Contest 273)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

【计算机网络 01】说在前面 信息服务 因特网 ISP RFC技术文档 边缘与核心 交换方式 定义与分类 网络性能指标 计算机网络体系结构 章节小结

第一章--概述 说在前面1.1 计算机网络 信息时代作用1.2 因特网概述1.3 三种交换方式1.4 计算机网络 定义与分类1.5 计算机网络的性能指标1.6 计算机网络体系结构1 常见的计算机网络体系结构2 计算机网络体系结构分层的必要性3 计算机网络体系结构分层思想举例4 计算机网络体系结…

本地文件夹上传到Github

本地文件夹上传到Github 步骤1. 下载git步骤2. 在github中新建一个库&#xff08;Repository&#xff09;步骤3. 设置SSH key步骤4. 添加SSH keys步骤5. 本地文件上传到github参考 步骤1. 下载git 下载git客户端&#xff0c;并在本地安装完成。 步骤2. 在github中新建一个库&a…

Install Ansible on CentOS 8

环境准备&#xff1a; 1.至少俩台linux主机&#xff0c;一台是控制节点&#xff0c;一台是受控节点 2.控制节点和受控节点都需要安装Python36 3.控制节点需要安装ansible 4.控制节点需要获得受控节点的普通用户或root用户的权限&#xff0c;控制节点需要ssh客户端&#xff0c;…

[JavaScript游戏开发] 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 文章目录 系列文章目录前言一、本章节效果图二、介绍2.1、准备地图素材2.2、封装地图上…

[OnWork.Tools]系列 01-简介

说明 OnWork.Tools 是基于 Net6 的桌面程序。支持Windows7SP1及以上系统&#xff0c;主要是日常办公或者是开发工作过程中常用的工具集合。界面使用WPF Mvvm模式开发&#xff0c;目的是将开源项目中&#xff0c;好用的项目集成到一起&#xff0c;方便大家使用和学习。 功能 …

React:从 npx开始

使用 npm 来创建第一个 recat 文件&#xff08; react-demo 是文件名&#xff0c;可以自定义&#xff09; npx create-react-app react-demo npx是 npm v5.2 版本新添加的命令&#xff0c;用来简化 npm 中工具包的使用 原始&#xff1a; 全局安装npm i -g create-react-app 2 …

【Matter】基于Ubuntu 22.04 编译chip-tool工具

前言 编译过程有点曲折&#xff0c;做下记录&#xff0c;过程中&#xff0c;有参考别人写的博客&#xff0c;也看github 官方介绍&#xff0c;终于跑通了~ 环境说明&#xff1a; 首先需要稳定的梯子&#xff0c;可以访问“外网”ubuntu 环境&#xff0c;最终成功实验在Ubunt…

Linux 下centos 查看 -std 是否支持 C17

实际工作中&#xff0c;可能会遇到c的一些高级特性&#xff0c;例如std::invoke&#xff0c;此函数是c17才引入的&#xff0c;如何判断当前的gcc是否支持c17呢&#xff0c;这里提供两种办法。 1.根据gcc的版本号来推断 gcc --version&#xff0c;可以查看版本号&#xff0c;笔者…

数据结构【线性表】

数据结构入门级 第二章 线性表 一、线性表的定义和基本操作 线性表的定义&#xff1a;具有相同属性数据类型的数据元素组成的一个有限序列&#xff1b;除第一个元素外的元素都有直接前驱&#xff0c;除最后一个元素外的元素都有直接后继&#xff1b;存在一个唯一被称为“第一个…

从简单线性回归到TensorFlow深度学习

大家好&#xff0c;人工智能近年来变得越来越流行&#xff0c;学习人工智能的需求也随之增加&#xff0c;尤其是许多IT专业人士希望利用机器学习的强大功能&#xff0c;但面临不小的挑战&#xff0c;尤其是在理论和数学上。 步骤1&#xff1a;线性回归 线性回归是一种统计学中…

RNN架构解析——GRU模型

目录 GRU模型实现优点和缺点 GRU模型 实现 优点和缺点

【C语言day06】

逻辑或运算如果前表达式为真&#xff0c;后表达式不计算&#xff0c;第一次循环时i为0&#xff0c;执行i&#xff0c;第二次循环时i为1&#xff0c;是个真值&#xff0c;不再执行i&#xff0c;也就死循环了 在C语言中&#xff0c;一个函数如果不写返回值类型&#xff0c;那么就…

【MyBatis 学习三】子段不一致问题 多表查询 动态SQL

目录 一、解决Java实体类属性与数据库表字段不一致问题 &#x1f337;现象1&#xff1a;显示字段不对应&#xff1a;使用ResultType查询结果为null&#xff1b; &#x1f337;解决办法&#xff1a;字段不对应&#xff1a;使用ResultMap解决。 二、数据库的多表查询 &#…

【Spring Cloud Alibaba】Sentinel运行原理

文章目录 前言1、基本原理2、SphU.entry()2.1、StringResourceWrapper2.2、Entry 3、entry.exit()4、Context 前言 本文基于sentinel-1.8.0版本 Sentinel 是面向分布式服务架构的流量控制组件&#xff0c;主要以流量为切入点&#xff0c;从限流、流量整形、熔断降级、系统负载保…

ajax axios json

目录 一、ajax概述 1. 概念 2. 实现方式 &#xff08;1&#xff09;原生的JS实现方式&#xff08;了解&#xff09; &#xff08;2&#xff09; JQeury实现方式 二、axios 介绍 三、axios使用 1. axios 发送get/post请求 2. axios验证用户名称是否存在 四、json 1. …