当前使用版本3.6.4
16.20.2 (Currently using 64-bit executable)
https://doc.ruoyi.vip/ruoyi-cloud/document/hjbs.html

一、若依Cloud改为多租户模式

当前使用版本3.6.4,既然要改为多租户模式,多租户重点是什么,数据隔离,那么我们首先要知道若依是如何做数据隔离。
image.png
查看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()方法
image.png
修改后的代码如下

 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。这样是为了使角色独立
image.pngimage.png
根据查看菜单的权限是根据当前角色的权限去拿所以不需要增加tenant也可区分,列如,使用管理员账户增加一个角色,租户管理员角色,配置菜单,这个角色只能配置当前角色下的菜单。
但是每一个租户都有不同的角色分配数据权限时,一定要把全部数据权限禁用掉,不然数据就会串。
image.png
后续增加的业务模块 新增修改查询一定要增加tenantId字段噢。这样就修改完成了。

Logo

快速构建 Web 应用程序

更多推荐