应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

大型SaaS系统的数据范围权限设计与实现!

  前言
  多租户(MultiTenant)是SaaS中的一个重要概念,它是一种软件架构技术,在多个租户的环境下,共享同一套系统实例,并且租户之间的数据具有隔离性,也就是说一个租户不能去访问其他租户的数据。基于不同的隔离级别,通常具有下面三种实现方案:每个租户使用独立DataBase,隔离级别高,性能好,但成本大租户之间共享DataBase,使用独立的Schema租户之间共享Schema,在表上添加租户字段,共享数据程度最高,隔离级别最低。数据库设计
  Mybatisplus在第3层隔离级别上,提供了基于分页插件的多租户的解决方案,我们对此来进行介绍。在正式开始前,首先做好准备工作创建两张表,在基础字段后都添加租户字段tenantid:CREATETABLEuser(idbigint(20)NOTNULL,namevarchar(20)DEFAULTNULL,phonevarchar(11)DEFAULTNULL,addressvarchar(64)DEFAULTNULL,tenantidbigint(20)DEFAULTNULL,PRIMARYKEY(id))CREATETABLEdept(idbigint(20)NOTNULL,deptnamevarchar(64)DEFAULTNULL,commentvarchar(128)DEFAULTNULL,tenantidbigint(20)DEFAULTNULL,PRIMARYKEY(id))引入依赖
  在项目中导入需要的依赖:dependencygroupIdcom。baomidougroupIdmybatisplusbootstarterartifactIdversion3。3。2versiondependencydependencygroupIdcom。github。jsqlparsergroupIdjsqlparserartifactIdversion3。1versiondependency实现
  Mybatisplus配置类:EnableTransactionManagement(proxyTargetClasstrue)ConfigurationpublicclassMybatisPlusConfig{BeanpublicPaginationInterceptorpaginationInterceptor(){PaginationInterceptorpaginationInterceptornewPaginationInterceptor();ListISqlParsersqlParserListnewArrayList();TenantSqlParsertenantSqlParsernewTenantSqlParser();tenantSqlParser。setTenantHandler(newTenantHandler(){OverridepublicExpressiongetTenantId(booleanselect){StringtenantId3;returnnewStringValue(tenantId);}OverridepublicStringgetTenantIdColumn(){returntenantid;}OverridepublicbooleandoTableFilter(StringtableName){returnfalse;}});sqlParserList。add(tenantSqlParser);paginationInterceptor。setSqlParserList(sqlParserList);returnpaginationInterceptor;}}
  这里主要实现的功能:创建SQL解析器集合创建租户SQL解析器设置租户处理器,具体处理租户逻辑
  这里暂时把租户的id固定写成3,来进行测试。测试执行全表语句:publicListUsergetUserList(){returnuserMapper。selectList(newLambdaQueryWrapperUser()。isNotNull(User::getId));}
  使用插件解析执行的SQL语句,可以看到自动在查询条件后加上了租户过滤条件:
  那么在实际的项目中,怎么将租户信息传给租户处理器呢,根据情况我们可以从缓存或者请求头中获取,以从Request请求头获取为例:OverridepublicExpressiongetTenantId(booleanselect){ServletRequestAttributesattributes(ServletRequestAttributes)RequestContextHolder。getRequestAttributes();HttpServletRequestrequestattributes。getRequest();StringtenantIdrequest。getHeader(tenantId);returnnewStringValue(tenantId);}
  前端在发起http请求时,在Header中加入tenantId字段,后端在处理器中获取后,设置为当前这次请求的租户过滤条件。
  如果是基于请求头携带租户信息的情况,那么在使用中可能会遇到一个坑,如果当使用多线程的时候,新开启的异步线程并不会自动携带当前线程的Request请求。OverridepublicListUsergetUserListByFuture(){CallablegetUser()userMapper。selectList(newLambdaQueryWrapperUser()。isNotNull(User::getId));FutureTaskListUserfuturenewFutureTask(getUser);newThread(future)。start();try{returnfuture。get();}catch(Exceptione){e。printStackTrace();}returnnull;}
  执行上面的方法,可以看出是获取不到当前的Request请求的,因此无法获得租户id,会导致后续报错空指针异常:
  修改的话也非常简单,开启RequestAttributes的子线程共享,修改上面的代码:OverridepublicListUsergetUserListByFuture(){ServletRequestAttributessra(ServletRequestAttributes)RequestContextHolder。getRequestAttributes();CallablegetUser(){RequestContextHolder。setRequestAttributes(sra,true);returnuserMapper。selectList(newLambdaQueryWrapperUser()。isNotNull(User::getId));};FutureTaskListUserfuturenewFutureTask(getUser);newThread(future)。start();try{returnfuture。get();}catch(Exceptione){e。printStackTrace();}returnnull;}
  这样修改后,在异步线程中也能正常的获取租户信息了。
  那么,有的小伙伴可能要问了,在业务中并不是所有的查询都需要过滤租户条件啊,针对这种情况,有两种方式来进行处理。
  1、如果整张表的所有SQL操作都不需要针对租户进行操作,那么就对表进行过滤,修改doTableFilter方法,添加表的名称:OverridepublicbooleandoTableFilter(StringtableName){ListStringIGNORETENANTTABLESArrays。asList(dept);returnIGNORETENANTTABLES。stream()。anyMatch(ee。equalsIgnoreCase(tableName));}
  这样,在dept表的所有查询都不进行过滤:
  2、如果有一些特定的SQL语句不想被执行租户过滤,可以通过SqlParser注解的形式开启,注意注解只能加在Mapper接口的方法上:SqlParser(filtertrue)Select(selectfromuserwherename{name})UserselectUserByName(Param(valuename)Stringname);
  或在分页拦截器中指定需要过滤的方法:BeanpublicPaginationInterceptorpaginationInterceptor(){PaginationInterceptorpaginationInterceptornewPaginationInterceptor();paginationInterceptor。setSqlParserFilter(metaObject{MappedStatementmsSqlParserHelper。getMappedStatement(metaObject);对应Mapper、dao中的方法if(com。cn。tenant。dao。UserMapper。selectUserByPhone。equals(ms。getId())){returntrue;}returnfalse;});。。。}
  上面这两种方式实现的功能相同,但是如果需要过滤的SQL语句很多,那么第二种方式配置起来会比较麻烦,因此建议通过注解的方式进行过滤。
  除此之外,还有一个比较容易踩的坑就是在复制Bean时,不要复制租户id字段,否则会导致SQL语句报错:publicvoidcreateSnapshot(LonguserId){UseruseruserMapper。selectOne(newLambdaQueryWrapperUser()。eq(User::getId,userId));UserSnapshotuserSnapshotnewUserSnapshot();BeanUtil。copyProperties(user,userSnapshot);userSnapshotMapper。insert(userSnapshot);}
  查看报错可以看出,本身Bean的租户字段不为空的情况下,SQL又自动添加一次租户查询条件,因此导致了报错:
  我们可以修改复制Bean语句,手动忽略租户id字段,这里使用的是hutool的BeanUtil工具类,可以添加忽略字段。BeanUtil。copyProperties(user,userSnapshot,tenantId);
  在忽略了租户id的拷贝后,查询可以正常执行。
  最后,再来看一下对联表查询的支持,首先看一下包含子查询的SQL:Select(selectfromuserwhereidin(selectidfromusersnapshot))ListUserselectSnapshot();
  查看执行结果,可以看见,在子查询的内部也自动添加的租户查询条件:
  再来看一下使用Join进行联表查询:Select(selectu。fromuseruleftjoinusersnapshotusonu。idus。id)ListUserselectSnapshot();
  同样,会在左右两张表上都添加租户的过滤条件:
  再看一下不使用Join的普通联表查询:Select(selectu。fromuseru,usersnapshotus,deptdwhereu。idus。idandd。idisnotnull)ListUserselectSnapshot();
  查看执行结果,可以看见在这种情况下,只在FROM关键字后面的第一张表上添加了租户的过滤条件,因此如果使用这种查询方式,需要额外注意,用户需要手动在SQL语句中添加租户过滤。
  来源:https:mp。weixin。qq。comsSREXWGeYvtoCbabBBWrg

人民日报每日金句摘抄1新的春天,拿出拼的精神、闯的劲头、实的作风。2抓创新就是抓发展,谋创新就是谋未来。3我们靠实干创造了辉煌的过去,还要靠实干开创更加美好的未来。4事非经过不知……生不逢时报国无门的巴西球员组成的阵容巴西足球素来以足球王国著称,优秀的球星数不胜数。许多在俱乐部发挥神勇,无奈同位置上优秀球员太多,还有像埃尔伯这种因为踢球风格与桑巴足球特点不符,以及德贾明哈这种个性十足、脾气火……国家植物园展出一角硬币图案铝兰花真身元宵节临近,国家植物园将在园区内举办多项展览活动,让市民游客欢度佳节。2月4号到5号,国家植物园元宵兰花展在北园热带展览温室内集中亮相。200多种一万多株兰花及年宵花汇聚一堂,……昆明老城区步行街上的这个牌坊为谁而立的?在昆明最中心的闹市区,南屏步行街十字口,大家都会看到一个很大的古牌坊,百大新天地和柏联广场之间。上面有忠爱二字,知道这个牌坊的来历吗?忠爱坊始建于明朝初年,系为纪念元代政……中年之后最深的领悟格局大了,你的事业就顺了头条创作挑战赛先添柴,火炉才会烧起来。作者:洞见Leyla你是否有过这样的体验:工作中遇到芝麻绿豆大的事情,也会激起情绪上的波澜;事业上碰到一丁点……打造真正属于自己的数字化资产,还要从区块链说起【数字化资产】:是将现实存在的各种实物或非实物资产进行数字化标识,使资产在网络空间也具备线下各种属性,如权属、流通等。例如付费音乐、视频、书籍等。上升到企业:一种新的机制……318家国家5A级旅游景区,你去过几家?(河北省11家)头条创作挑战赛国家AAAAA级旅游景区,即5A级景区,为中华人民共和国旅游景区质量等级划分的景区级别,共分为五级,从高到低依次为AAAAA、AAAA、AAA、AA、A级五……对不起,我不知道大家都来长沙过年了!春节的长沙,到处都是人从众!顶流长沙又一次红得发紫、火上热搜,长沙的各大景点都是人山人海,吃饭的地方都排起了长长长长长的队伍,乌泱泱的人群从早到晚不见减少甚至有人问:是全……恩佐英超处子秀12次对抗9次成功抢断全场最高今天凌晨结束的一场202223赛季英超联赛第22轮中,切尔西主场00闷平富勒姆蓝军在冬窗截止日豪掷1。21亿欧元违约金从本菲卡签下的阿根廷天才中场恩佐费尔南德斯在加盟后仅仅3天……免税业复苏观察大热三年,离岛免税还能继续热下去吗?受益于离岛免税新政及疫情背景下国人海外免税消费快速回流,离岛免税2020年来迅速崛起。根据FrostSullivan数据,我国离岛免税销售额2019年2021年CAGR达83,……7。56万件!银行投诉曝光,信用卡投诉居高不下,兴业平安和浦中国经济周刊经济网讯(记者郑扬波)近日,银保监会发布2022年第三季度银行业的投诉情况。银行信用卡业务是银行投诉的重灾区,兴业银行和交通银行的投诉数量排名多次靠前。银保监……你知道吗?2023年春节是21世纪第二早春节春节的脚步日益临近,1月22日,我们将迎来2023年春节。天文科普专家介绍,2023年春节是21世纪这100年里的第二早春节,比21世纪最早春节只晚了一天。春节是每年农历……
仅用8年!比亚迪成功突破高端芯片封锁,王传福只给中国企业用时间回溯至2019年,全球市场迎来了一场新能源风暴:背靠上海超级工厂,特斯拉强势突围,一跃成为世界级巨头。而同样一幕,三年后在东方大国上演:老冤家比亚迪迎来里程碑时刻以半年领先……因为售价,iPhone14再次冲上热搜大家还记得上周有人粗略的统计下在近90天的时间之内,关于苹果和iPhone14系列的热搜高达920个以上,平均每天有十个以上。在iPhone14系列即将发布前夕,有这么多的热搜……警惕!这几种蔬菜千万别生吃!必须煮熟食用很多人喜欢生吃蔬菜,认为能减肥或避免蔬菜营养流失,但不是所有蔬菜都能生吃。有些蔬菜若没有煮熟就食用,容易造成肠胃不适。7类蔬菜要煮熟才能吃有些蔬菜只需简单调味或腌渍……原神须弥传送点开启攻略,山洞水底草鸡和雷树须弥开图,大部分传送点都比较好开启,唯独有5个点比较难的,我拿出来说说,以下图标记的是大致地点,下面再详细说明。1、第一个传送点,在掣电树附近,从左边下方的七天神像走过去……雪上加霜!罗马连损大将遭多次争议判罚,穆帅用10连胜强势回应连续损兵折将,并且遭遇多次判罚,穆里尼奥用进球和胜利对裁判作出回应!斯莫林高高跃起头球破门,并且展示25号球衣,祝福维纳尔杜姆早日康复,而穆帅第一时间提醒球员保持专注,因为,穆……价格598元!华为Mate50专属5G手机壳遭曝光,网友必要不可否认,只有华为具有全场景端到端的最佳5G解决方案和5G全生命周期服务能力,这句话看似夸张,但实际上来说确实如此,也得到了很多用户的认可。尤其是信号方面,无论是4G信号……文旅部公布第二批国家级夜间文化和旅游消费集聚区名单央视网消息:文化和旅游部网站8月25日公布第二批国家级夜间文化和旅游消费集聚区名单,北京市王府井、欢乐谷等123个集聚区入选,至此全国夜间文化和旅游消费集聚区已达243个。……夏天孕妇去产检这样穿最方便产检要穿得好看还是穿得方便?答案当然是怎么方便怎么穿啦。作为一个二胎宝妈,产检经验还算丰富,我负责任的告诉大家,去产检不建议只穿裙子,原因有两个,一是产检时经常要把上衣拉……和秦plusdmi美好的一下午!南山根公园一日游!眼瞅着夏季快过去了,不去一次南山根公园可惜了。有了小秦,开近点的地方用电就可以行驶了,也不担心油耗!我们很快到达目的地,门口是大大的喷泉。警……遇见黄姚,遇见最美古镇作者:极客行天下百年老宅爆改民宿,半山露台可赏日落黄姚古镇圣旨府酒店这次来黄姚,住在带龙桥附近古巷中的一间民宿,酒店拥有一个十分高傲的名字:圣旨府。这是一处明……见证历史!沙特石油用人民币结算,霸权动摇,美元将如何反击?我们正在见证历史。8月11日英国《卫报》报道,在沙特首都利雅得,工人们正在把五星红旗和欢迎横幅悬挂在街道两侧,欢迎即将到来的中国贵客。至于是谁来?目前两国官方并未证……2022平价投影仪推荐,学生党租房党投影仪选择攻略如今眼瞅着暑假的假期快要结束了,相比很多的学生也是要准备开学了吧;那么开学了为自己的宿舍增添些什么东西呢?独居生活的你害怕寂寞买些什么东西解除寂寞呢?一款千元以内的投影仪绝对是……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网