若依框架改造多租户模式
当前使用版本3.6.4。全是干货
当前使用版本3.6.4
16.20.2 (Currently using 64-bit executable)
https://doc.ruoyi.vip/ruoyi-cloud/document/hjbs.html
一、若依Cloud改为多租户模式
当前使用版本3.6.4,既然要改为多租户模式,多租户重点是什么,数据隔离,那么我们首先要知道若依是如何做数据隔离。
查看DataScopeAspect数据过滤处理的方式发现
现在的系统模块是根据deptId去做的权限,可以把部门理解为一个公司,就是一个租户的意思,那么其实我们根据deptId字段就可以实现多租户模式,但毕竟不是那么规范可读性不高,那我们只需要增加tenantId字段赋值deptId即可,后续新增的模块均可使用tenant_id字段进行数据的过滤。
1、通过自定义注解DataScope+AOP的方式 通过部门和角色的关系进行的数据过滤
2、那么我们需要对部门和角色进行数据隔离,在sys_dept和sys_role表中增加tenant_id字段
3、登录赋值tenant_id返回在token,存在每次请求的线程中,便于获取
这样好像是可以的哦,那么部门和角色的多租户是可以完成的,下面的代码是如何增加tenant_id。
那我们把角色放开给租户的管理员,那菜单的权限和角色的数据权限我们怎么处理呢?这个不要急,下面我会讲到
第一步首先增加字段,自行修改实体类和Mapper文件
ALTER TABLE sys_role ADD COLUMN `tenant_id` bigint NOT NULL COMMENT '租户ID';
ALTER TABLE sys_dept ADD COLUMN `tenant_id` bigint DEFAULT NULL COMMENT '租户ID';
第二步修改DataScopeAspect类的dataScopeFilter()方法
修改后的代码如下
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
{
continue;
}
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.tenant_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.tenant_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.tenant_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
// 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
第三步修改sysLoginService.login(查询到当前部门的tenantId返回)和tokenService.createToken代码,其主要目的是讲tenant_id添加在token中
Long tenantId = loginUser.getSysUser().getTenantId();
claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId);
那我们如何在代码中获取呢
在SecurityUtils类中增加getTenantId方法
public static Long getTenantId()
{
return SecurityContextHolder.getTenantId();
}
SecurityContextHolder类中添加
public static Long getTenantId()
{
return Convert.toLong(get(SecurityConstants.DETAILS_TENANT_ID), 0L);
}
public static void setTenantId(String tenantId) {
set(SecurityConstants.DETAILS_TENANT_ID, tenantId);
}
现在是可以取到了,那在哪里赋值呢在HeaderInterceptor类中这是自定义请求头拦截器,将Header数据封装到线程变量中方便获取,增加代码
SecurityContextHolder.setTenantId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_TENANT_ID));
AuthUtil.verifyLoginUserExpire(loginUser);
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
SecurityContextHolder.set(SecurityConstants.DETAILS_TENANT_ID, loginUser.getSysUser().getTenantId());
SecurityContextHolder.set(SecurityConstants.DETAILS_USERNAME, loginUser.getSysUser().getUserName());
SecurityContextHolder.set(SecurityConstants.DETAILS_USER_ID, loginUser.getSysUser().getUserId());
那么,目前为止我们已经能根据tenantId来进行租户数据区分了,也可以获取和返回tenantId了
那我们什么时候生成tenantId呢?修改insertDept方法进行赋值即可,也就是新增部门时,将部门的id赋值给tenantId即可。
需要进行权限过滤需要使用该注解。需要自行修改代码,roleService的checkRoleNameUnique,checkRoleKeyUnique。这样是为了使角色独立
根据查看菜单的权限是根据当前角色的权限去拿所以不需要增加tenant也可区分,列如,使用管理员账户增加一个角色,租户管理员角色,配置菜单,这个角色只能配置当前角色下的菜单。
但是每一个租户都有不同的角色分配数据权限时,一定要把全部数据权限禁用掉,不然数据就会串。
后续增加的业务模块 新增修改查询一定要增加tenantId字段噢。这样就修改完成了。
更多推荐
所有评论(0)