项目中修改sql以适应多租户之间的数据隔离
多租户之间的数据隔离,用的是什么来标识这条数据是属于哪个租户的?租户Id。因为项目采用共享表的方式,是为了避免数据的冗余,所以并不是尽可能让每张表都有tenant_id这个字段。事物都有两面性,满足一面那么肯定会带来另一面的缺陷,所以大都时候,都只能根据项目和具体情况权衡一个合理的方式。对于主表来说(有tenant_id这个字段),那么无论是查询还是修改删除,那么都可以加上这个tenant_id这个条件,而对于插入来说,那是必须要加上的,查询也必须要加上这个条件,而修改和删除加上tenant_id这个条件呢,可以防止非法参数。对于从表,那么必须进行连表,对于修改和删除则采用exists()这个函数,函数里面进行连表并且带上tenant_id这个字段,判定是否是属于当前这个租户,或者说当前这个租户下是否有这样子的一条数据。进行了没有必要的连表判断,查询肯定会对数据库造成压力的。只是为了可能的安全。其实一般情况下,确实是没有必要的。当然不用对每一个修改和删除进行判断,对自己认为应该要加的还是可以加的。
多租户tenantId的来源
项目分为两个端。一个是管理端,一个是考生端。采用一旦用户登录上系统,则将用户这个对象保存在session中,这样子在这整个会话中都可以取得。
问题:对于有些方法,两个端存在共用的情况,那么这个session应该怎么取?
最开始,并没有意识到这两个端有方法的共用。
发现时,采用判断的方式。无论哪个端登录,必定一次只有一个session存在,另一个必须为null,只有谁不是null,则从session中取值。
修改过程中,发现有一些方法并不能直接判断两端是否共用,项目这么大,难道要一个一个方法跑?而且这样子在很多代码中,都采用同一种方式判断,是很差劲的。
最后,采用共用的方法,这个方法返回一个租户id,无论是那端登录,都可以使用这个静态的方法获取就可以了,当然前提是在两端登录的时候,就必须把tenant_id保存在session中。
多租户实现功能菜单和角色功能菜单
需求是,租户可以实现功能菜单的控制。一般情况下,都是利用角色,并且是给角色分配功能菜单。采用这种方式解决。运营商建立一个租户,则给这个租户建立一个默认租户管理员这个用户,这个用户拥护当前租户的最大权限,它不属于任何角色。因为项目有租户和功能的中间表,所以是可以把这个用户是属于租户,这个租户拥有功能这样子的逻辑做。在后端只需判断当前登录者是一个默认的租户管理员,还是普通的角色用户,在它们对应的tenant-function 和 role-function 表中执行对应的sql获取相应的function就可以了。
考生端登录功能
需求,路径输入租户编码。
最开始,完成功能。无论路径对与错,都会进入到登录页面。在controller中用init()方法,获取一个全局的变量,并赋上可能正确的值。在登录者登录的方法中,进行是否有这个租户存在,存在后判断是永久租户还是普通租户,是普通租户判断是否过期。
发现问题
问题一:controller是一个单例,一个用户登录上这个全局变量被赋值,又一个用户登录上则变量的值将被改变。所以全局变量看似不行了,可以使用SpringMVC的注解或者多线程来保证安全。struts不允许使用全局变量的,当然在controller中使用全局变量,还是很少的,几乎是没有见过类似的程序的。所以,还是把租户id存session中。在login方法中取,执行登录的sql。这里判断是否有租户,根据路径传来的租户编码获取的。
问题二:把判断的逻辑写到longin方法里,感觉不太合理。修改,init()方法进行判断,错误直接跳错误页面,正确跳登录页面。
问题三:对于租户编码一开始规定4位,便把其长度定死在程序中,这样子很不好,因为程序的灵活性很不好,耦合性太强了,如果数据库存租户编码为5位?难道又的打开项目进行源码修改?
问题四:对于字符串的截取,比如String str = "/aaa/bbb/";str.split("/");理解的不够好。这个截取出来数组长度是3,其中0是null,1是aaa,2是bbb。那对于String str = "/qqq/bbb/aa";截取出来长度是4位。
问题五:在考生端,当路径输入错误,也跳到登录界面,是很不好的。
问题六:一开始,对租户的时效判断,并不合理,甚至可以说逻辑不太正确和严密。先是采用运营商的编码是透明的,普通租户编码未知的方式判断,并且把运营商编码写死了,程序不灵活,耦合性太强。修改采用是否拥有永久时效判断运营商还是普通租户,如果是普通租户在sql语句中把时间当成条件查询。
问题七:一开始,提示的信息比较多,什么路径错误,什么租户不存在等,看似很友好,其实是很不好的,没有这个必要去提醒。修改,路径错误跳错误页面,路径正确跳登录页面。路径的正确与否是根据路径传来的租户编码,根据编码查询是否有租户存在。