场景:

jeecg boot提供了多租户的配置,但是并没有完整实现该功能,此文就原系统表的菜单、部门、角色、用户为例实现多租户功能实现方案。

  1. 修改菜单表:sys_permission 增加两个字段tenant_id(租户ID),并且设置tenant_id的初始值(注意实体需加对应字段)
ALTER TABLE `sys_permission`
ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `internal_or_external`;
update sys_permission set tenant_id = 1;
  1. 修改部门表(注意实体需加字段)
ALTER TABLE `sys_depart`
ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `update_time`;
update sys_depart set tenant_id = 1;
  1. 修改角色表(注意实体需加字段)
ALTER TABLE `sys_role`
ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `update_time`;
update sys_role set tenant_id = 1;
  1. 配置基础菜单json, 做多租户的时候,需要将部分系统管理的菜单复制多分分配给不同租户角色,所以需要配置一个json文件,详细见补丁文件
    在这里插入图片描述

  2. 修改代码:全项目搜索注解@RequiresRoles @RequiresPermissions,删除,没找到忽略

修改SysDepartMapper.xml增加代码

<!-- 根据父ID查询同级部门 -->
<select id="querySameLevelDepart" parameterType="String" resultType="org.jeecg.modules.system.entity.SysDepart">
	select * from sys_depart
    <choose>
        <when test="pid != null and pid != ''">
            where parent_id = #{pid,jdbcType=VARCHAR}
        </when>
        <otherwise>
            where parent_id is null or parent_id=''
        </otherwise>
    </choose>
    order by org_code desc
</select>

修改SysDepartMapper增加代码:

  @InterceptorIgnore(tenantLine = "true")
	List<SysDepart> querySameLevelDepart(@Param("pid")String pid);

接口ISysDepartService新增代码如下:

List<SysDepart> querySameLevelDepart(String pid);

实现类SysDepartServiceImpl新增:

@Override
	public List<SysDepart> querySameLevelDepart(String pid) {
		return baseMapper.querySameLevelDepart(pid);
	}

还是修改SysDepartServiceImpl: 将注解@Cacheable(value = CacheConstant.SYS_DEPARTS_CACHE) @Cacheable(value = CacheConstant.SYS_DEPART_IDS_CACHE)去掉

修改SysPermissionTree代码:

/**
 * 租户ID
 */
private java.lang.Integer tenantId;

/**
 * 是否是基础菜单 1是0否  如果是1 新增租户的时候需要复制
 */
private java.lang.Boolean baseFlag;

修改OrgCodeRule代码:
在这里插入图片描述

  1. 修改租户表:sys_tenant 增加一个字段 pre_code(角色编码前缀)

  2. 修改新增租户的逻辑 租户接口:

public interface ISysTenantService extends IService<SysTenant> {

    /**
     * 保存
     * @param sysTenant
     */
    void saveSysTenant(SysTenant sysTenant);

}

实现类SysTenantServiceImpl:

package org.jeecg.modules.system.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysPermissionMapper;
import org.jeecg.modules.system.mapper.SysTenantMapper;
import org.jeecg.modules.system.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

@Service
@Slf4j
public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant> implements ISysTenantService {

    @Autowired
    ISysPermissionService sysPermissionService;

    @Autowired
    ISysUserService sysUserService;

    @Autowired
    ISysRoleService sysRoleService;

    @Autowired
    ISysUserRoleService sysUserRoleService;

    @Autowired
    ISysRolePermissionService sysRolePermissionService;

    private List<SysPermission> getPermissionList(){
        //  如果设置了BaseFlag字段配置 可以读取数据库
/*      LambdaQueryWrapper<SysPermission> query = new LambdaQueryWrapper<SysPermission>();
        query.eq(SysPermission::getBaseFlag, true);
        query.eq(SysPermission::getTenantId, 1);
        List<SysPermission> ls = sysPermissionService.list(query);*/
        // 读取json  需要自己提前在baseRoute.json文件里配置菜单信息
        String jsonPath = "static/system/baseRoute.json";
        ClassPathResource classPathResource = new ClassPathResource(jsonPath);
        byte[] bytes = new byte[0];
        try {
            bytes = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        String json = new String(bytes);
        JSONArray array = JSON.parseArray(json);
        List<SysPermission> ls = array.toJavaList(SysPermission.class);
        return ls;
    }

    @Override
    @Transactional
    public void saveSysTenant(SysTenant sysTenant) {
        this.save(sysTenant);
        int tenantId = sysTenant.getId();
        List<SysPermission> ls = getPermissionList();
        Collection<String> menuIds = setPermissionTenant(ls, tenantId);
        sysPermissionService.saveBatch(ls);

        // 修改admin用户的租户
        SysUser user = sysUserService.getUserByName("admin");
        String refTenantIds = user.getRelTenantIds();
        if(oConvertUtils.isEmpty(refTenantIds)){
            user.setRelTenantIds(String.valueOf(tenantId));
        }else{
            user.setRelTenantIds(refTenantIds+","+tenantId);
        }
        sysUserService.updateById(user);

        // 添加admin角色
        SysRole role = new SysRole();
        role.setRoleCode(sysTenant.getPreCode()+"_admin");
        role.setRoleName("管理员");
        role.setTenantId(tenantId);
        sysRoleService.save(role);

        // 添加角色 用户关系
        SysUserRole sysUserRole = new SysUserRole();
        // TODO is ok?
        sysUserRole.setRoleId(role.getId());
        sysUserRole.setUserId(user.getId());
        sysUserRoleService.save(sysUserRole);

        // 添加角色 菜单关系
        List<SysRolePermission> list = new ArrayList<>();
        for(String menuId: menuIds){
            SysRolePermission sp = new SysRolePermission();
            sp.setPermissionId(menuId);
            sp.setRoleId(role.getId());
            list.add(sp);
        }
        sysRolePermissionService.saveBatch(list);

    }

    private String randomId(){
        long id = IdWorker.getId();
        return String.valueOf(id);
    }

    private Collection<String> setPermissionTenant(List<SysPermission> ls, int tenantId){
        // 循环两次 第一次设置ID和tenantId 第二次设置pid
        Map<String, String> map = new HashMap<>();
        for(SysPermission p: ls){
            String oldId = p.getId();
            String newId = randomId();
            map.put(oldId, newId);
            p.setId(newId);
            p.setTenantId(tenantId);
            p.setCreateBy(null);
            p.setCreateTime(null);
            p.setUpdateBy(null);
            p.setUpdateTime(null);
        }
        for(SysPermission p: ls){
            String oldPid = p.getParentId();
            if(oConvertUtils.isNotEmpty(oldPid)){
                String newPid = map.get(oldPid);
                if(oConvertUtils.isNotEmpty(newPid)){
                    p.setParentId(newPid);
                }else{
                    // TODO 一般情况下这个newPid是肯定有值的  如果没有值 说明当前节点的父节点 没有设置为基础路由  那么 需要递归获取 所有父级节点 挨个设置一下即可
                }
            }
        }
        return map.values();
    }
}

系统做成多租户后,新增租户的时候菜单会复制多份,如果这个时候想再切回来,那么多余的数据需要被清除,可以执行下面的sql:

delete from sys_role_permission where permission_id in (select id from sys_permission where tenant_id <> 1);
delete from sys_role_permission where role_id in (select id from sys_role where tenant_id <> 1);
delete from sys_permission where tenant_id <> 1;

delete from sys_user_role where role_id in (select id from sys_role where tenant_id <> 1);
delete from sys_role where tenant_id <> 1;

delete from sys_user_depart where dep_id in (select id from sys_depart where tenant_id <> 1);
delete from sys_depart where tenant_id <> 1;

代码升级文件:链接:https://pan.baidu.com/s/1uwj2khoE6t_IYqfxa57ssQ 提取码:6v7h

===================================
上文多租户要求配置一个基础路由的json文件,里面的数据如果不想手动编写可以在菜单表添加一个特殊标识 ,然后查询需要的数据,将数据以json格式输出,拿到json后配置到baseRoute.json文件即可:

LambdaQueryWrapper<SysPermission> query = new LambdaQueryWrapper<SysPermission>();

// 这里的baseFlag就是一个特殊标识,说明该菜单需要被多租户复制分配

        query.eq(SysPermission::getBaseFlag, true);
        query.eq(SysPermission::getTenantId, 1);
        List<SysPermission> ls = sysPermissionService.list(query);
JSONArray array = new JSONArray();
        for(SysPermission p: ls){
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("name", p.getName());
            map.put("id", p.getId());
            map.put("parentId", p.getParentId());
            map.put("component", p.getComponent());
            map.put("componentName", p.getComponentName());
            map.put("url", p.getUrl());
            map.put("route", p.isRoute());
            map.put("delFlag", p.getDelFlag());
            map.put("sortNo", p.getSortNo());
            map.put("icon", p.getIcon());
            map.put("alwaysShow", p.isAlwaysShow());
            map.put("hidden", p.isHidden());
            map.put("internalOrExternal", p.isInternalOrExternal());
            map.put("keepAlive", p.isKeepAlive());
            map.put("leaf", p.isLeaf());
            map.put("menuType", p.getMenuType());
            map.put("ruleFlag", p.getRuleFlag());
            JSONObject json = new JSONObject(map);
            array.add(json);
        }
        System.out.println("======================");
        System.out.println(array.toJSONString());
        System.out.println("======================");

其他:用户列表的saas实现

不建议直接把用户表加到配置中,建议单独做一个列表,针对租户ID配置通过配置数据权限的方式实现。
因为用户逻辑代码太多了,改动成本太高,单独做一个菜单给只自己看自己租户的数据即可。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐