多租户过程记录四(修改代码中)

项目中修改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语句中把时间当成条件查询。

          问题七:一开始,提示的信息比较多,什么路径错误,什么租户不存在等,看似很友好,其实是很不好的,没有这个必要去提醒。修改,路径错误跳错误页面,路径正确跳登录页面。路径的正确与否是根据路径传来的租户编码,根据编码查询是否有租户存在。

 

 

 

 

 

 

 

 

 

 

 

 

 

租户模块是指企业或个人在系统的账户信息及其相关信息管理模块。在C语言租户模块的开发代码可以包括以下几个部分: 1. 定义租户结构体:定义租户结构体,包括租户ID、租户名称、联系方式等基本信息。 2. 租户信息管理函数:开发一些租户信息管理函数,包括新增租户修改租户信息、查询租户信息等。 3. 租户权限管理函数:开发一些租户权限管理函数,包括授权、取消授权、修改权限等。 4. 租户日志记录函数:开发一些租户日志记录函数,记录租户的操作行为以及相关信息。 以下是一个简单的C语言租户模块开发代码示例: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义租户结构体 typedef struct { int tenant_id; // 租户ID char tenant_name[50]; // 租户名称 char contact[50]; // 联系方式 } Tenant; // 新增租户 void add_tenant(Tenant *tenants, int *count) { Tenant tenant; printf("请输入新租户ID:"); scanf("%d", &tenant.tenant_id); printf("请输入新租户名称:"); scanf("%s", tenant.tenant_name); printf("请输入新租户联系方式:"); scanf("%s", tenant.contact); tenants[*count] = tenant; (*count)++; printf("新增成功!\n"); } // 修改租户信息 void modify_tenant(Tenant *tenants, int count) { int tenant_id, index = -1; printf("请输入要修改租户ID:"); scanf("%d", &tenant_id); for (int i = 0; i < count; i++) { if (tenants[i].tenant_id == tenant_id) { index = i; break; } } if (index == -1) { printf("找不到该租户!\n"); return; } Tenant tenant; printf("请输入新租户名称(原名称:%s):", tenants[index].tenant_name); scanf("%s", tenant.tenant_name); printf("请输入新租户联系方式(原联系方式:%s):", tenants[index].contact); scanf("%s", tenant.contact); tenants[index] = tenant; printf("修改成功!\n"); } // 查询租户信息 void query_tenant(Tenant *tenants, int count) { int tenant_id, index = -1; printf("请输入要查询的租户ID:"); scanf("%d", &tenant_id); for (int i = 0; i < count; i++) { if (tenants[i].tenant_id == tenant_id) { index = i; break; } } if (index == -1) { printf("找不到该租户!\n"); return; } printf("租户ID:%d,租户名称:%s,联系方式:%s\n", tenants[index].tenant_id, tenants[index].tenant_name, tenants[index].contact); } // 授权 void authorize(Tenant *tenants, int count) { int tenant_id, index = -1; printf("请输入要授权的租户ID:"); scanf("%d", &tenant_id); for (int i = 0; i < count; i++) { if (tenants[i].tenant_id == tenant_id) { index = i; break; } } if (index == -1) { printf("找不到该租户!\n"); return; } printf("授权成功!\n"); } // 取消授权 void revoke(Tenant *tenants, int count) { int tenant_id, index = -1; printf("请输入要取消授权的租户ID:"); scanf("%d", &tenant_id); for (int i = 0; i < count; i++) { if (tenants[i].tenant_id == tenant_id) { index = i; break; } } if (index == -1) { printf("找不到该租户!\n"); return; } printf("取消授权成功!\n"); } // 记录日志 void log(Tenant *tenants, int count, char *action) { int tenant_id, index = -1; printf("请输入操作的租户ID:"); scanf("%d", &tenant_id); for (int i = 0; i < count; i++) { if (tenants[i].tenant_id == tenant_id) { index = i; break; } } if (index == -1) { printf("找不到该租户!\n"); return; } printf("操作记录租户ID:%d,操作:%s\n", tenants[index].tenant_id, action); } int main() { Tenant tenants[100]; int count = 0; while (1) { int option; printf("\n请选择操作:\n"); printf("1. 新增租户\n"); printf("2. 修改租户信息\n"); printf("3. 查询租户信息\n"); printf("4. 授权\n"); printf("5. 取消授权\n"); printf("6. 记录日志\n"); printf("7. 退出\n"); scanf("%d", &option); switch (option) { case 1: add_tenant(tenants, &count); break; case 2: modify_tenant(tenants, count); break; case 3: query_tenant(tenants, count); break; case 4: authorize(tenants, count); break; case 5: revoke(tenants, count); break; case 6: log(tenants, count, "操作"); break; case 7: exit(0); default: printf("无效操作!\n"); break; } } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值