1. 修改菜单表:sys_permission, 增加一个字段tenant_id(租户ID),并且设置tenant_id的初始值。

      1. ALTER TABLE `sys_permission`
      2. ADD COLUMN `tenant_id` int(5) NULL COMMENT '租户ID' AFTER `internal_or_external`;
      3. update sys_permission set tenant_id = 1;
    2. 修改org.jeecg.modules.system.entity.SysPermission,增加tenantId属性。如下: ```java /**

    • 外链菜单打开方式 0/内部打开 1/外部打开 / private boolean internalOrExternal; /update_end author:wuxianquan date:20190908 for:实体增加字段 */

    /**

    • 租户id */ private Integer tenantId; ```
    1. 修改部门表: sys_depart,增加一个字段tenant_id(租户ID),并且设置tenant_id的初始值。

      1. ALTER TABLE `sys_depart`
      2. ADD COLUMN `tenant_id` int(5) NULL COMMENT '租户ID' AFTER `update_time`;
      3. update sys_depart set tenant_id = 1;
    2. 修改org.jeecg.modules.system.entity.SysDepart,增加tenantId属性。如下: ```java

      /**

      • 租户id */ private Integer tenantId;
    1. 5. 修改角色表:sys_role,增加一个字段tenant_id(租户ID),并且设置tenant_id的初始值。
    2. ```sql
    3. ALTER TABLE `sys_role`
    4. ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `update_time`;
    5. update sys_role set tenant_id = 1;
    1. 修改org.jeecg.modules.system.entity.SysRole,增加tenantId属性。如下: ```java /**
      • 租户id */ private Integer tenantId;
    1. 7. 修改代码:全项目搜索注解@RequiresRoles @RequiresPermissions,删除,没找到忽略。自己新增的业务模块,如果用到了这两个注解,可以参考使用。这里只是去除系统功能模块中的注解。3.0版本其实已经都删除了。
    2. 7. 修改SysDepartMapper.xml,增加“根据父ID查询同级部门”的代码。
    3. ```xml
    4. <!-- 根据父ID查询同级部门 -->
    5. <select id="querySameLevelDepart" parameterType="String" resultType="org.jeecg.modules.system.entity.SysDepart">
    6. select * from sys_depart
    7. <choose>
    8. <when test="pid != null and pid != ''">
    9. where parent_id = #{pid,jdbcType=VARCHAR}
    10. </when>
    11. <otherwise>
    12. where parent_id is null or parent_id=''
    13. </otherwise>
    14. </choose>
    15. order by org_code desc
    16. </select>
    17. <!-- 根据username查询所拥有的部门 -->
    1. 修改org.jeecg.modules.system.mapper.SysDepartMapper,增加代码: ```java /**
      • 根据父ID查询同级部门 *
      • @param pid
      • @return */ @InterceptorIgnore(tenantLine = “true”) List querySameLevelDepart(@Param(“pid”) String pid);
    1. 10. 接口org.jeecg.modules.system.service.ISysDepartService新增代码如下:
    2. ```java
    3. /**
    4. * 根据父ID查询同级部门
    5. *
    6. * @param pid
    7. * @return
    8. */
    9. List<SysDepart> querySameLevelDepart(String pid);
    1. 实现类org.jeecg.modules.system.service.impl.SysDepartServiceImpl新增: ```java /**
      • 根据父ID查询同级部门 *
      • @param pid
      • @return */ @Override public List querySameLevelDepart(String pid) { return baseMapper.querySameLevelDepart(pid); }
    1. 12. 修改org.jeecg.modules.system.service.impl.SysDepartServiceImpl
    2. 将注解@Cacheable(value = CacheConstant.SYS_DEPARTS_CACHE) @Cacheable(value = CacheConstant.SYS_DEPART_IDS_CACHE)去掉
    3. 13. 修改org.jeecg.modules.system.model.SysPermissionTree代码
    4. ```java
    5. /**
    6. * 租户ID
    7. */
    8. private java.lang.Integer tenantId;
    9. public Integer getTenantId() {
    10. return tenantId;
    11. }
    12. public void setTenantId(Integer tenantId) {
    13. this.tenantId = tenantId;
    14. }
    1. 修改OrgCodeRule代码,原来的代码如下:

      1. if (StringUtil.isNullOrEmpty(parentId)) {
      2. // 线判断数据库中的表是否为空,空则直接返回初始编码
      3. query1.eq(SysDepart::getParentId, "").or().isNull(SysDepart::getParentId);
      4. query1.orderByDesc(SysDepart::getOrgCode);
      5. departList = sysDepartService.list(query1);
      6. if (departList == null || departList.size() == 0) {
      7. strArray[0] = YouBianCodeUtil.getNextYouBianCode(null);
      8. strArray[1] = "1";
      9. return strArray;
      10. } else {
      11. SysDepart depart = departList.get(0);
      12. oldOrgCode = depart.getOrgCode();
      13. orgType = depart.getOrgType();
      14. newOrgCode = YouBianCodeUtil.getNextYouBianCode(oldOrgCode);
      15. }
      16. } else {//反之则查询出所有同级的部门,获取结果后有两种情况,有同级和没有同级
      17. // 封装查询同级的条件
      18. query.eq(SysDepart::getParentId, parentId);
      19. // 降序排序
      20. query.orderByDesc(SysDepart::getOrgCode);
      21. // 查询出同级部门的集合
      22. List<SysDepart> parentList = sysDepartService.list(query);
      23. // 查询出父级部门
      24. SysDepart depart = sysDepartService.getById(parentId);
      25. // 获取父级部门的Code
      26. String parentCode = depart.getOrgCode();
      27. // 根据父级部门类型算出当前部门的类型
      28. orgType = String.valueOf(Integer.valueOf(depart.getOrgType()) + 1);
      29. // 处理同级部门为null的情况
      30. if (parentList == null || parentList.size() == 0) {
      31. // 直接生成当前的部门编码并返回
      32. newOrgCode = YouBianCodeUtil.getSubYouBianCode(parentCode, null);
      33. } else { //处理有同级部门的情况
      34. // 获取同级部门的编码,利用工具类
      35. String subCode = parentList.get(0).getOrgCode();
      36. // 返回生成的当前部门编码
      37. newOrgCode = YouBianCodeUtil.getSubYouBianCode(parentCode, subCode);
      38. }
      39. }

      修改后的代码如下:

      1. //如果是最高级,则查询出同级的org_code, 调用工具类生成编码并返回
      2. if (StringUtil.isNullOrEmpty(parentId)) {
      3. // 线判断数据库中的表是否为空,空则直接返回初始编码
      4. //query1.eq(SysDepart::getParentId, "").or().isNull(SysDepart::getParentId);
      5. //query1.orderByDesc(SysDepart::getOrgCode);
      6. //departList = sysDepartService.list(query1);
      7. departList = sysDepartService.querySameLevelDepart(parentId);
      8. if (departList == null || departList.size() == 0) {
      9. strArray[0] = YouBianCodeUtil.getNextYouBianCode(null);
      10. strArray[1] = "1";
      11. return strArray;
      12. } else {
      13. SysDepart depart = departList.get(0);
      14. oldOrgCode = depart.getOrgCode();
      15. orgType = depart.getOrgType();
      16. newOrgCode = YouBianCodeUtil.getNextYouBianCode(oldOrgCode);
      17. }
      18. } else {//反之则查询出所有同级的部门,获取结果后有两种情况,有同级和没有同级
      19. // 封装查询同级的条件
      20. //query.eq(SysDepart::getParentId, parentId);
      21. // 降序排序
      22. //query.orderByDesc(SysDepart::getOrgCode);
      23. // 查询出同级部门的集合
      24. //List<SysDepart> parentList = sysDepartService.list(query);
      25. List<SysDepart> parentList = sysDepartService.querySameLevelDepart(parentId);
      26. // 查询出父级部门
      27. SysDepart depart = sysDepartService.getById(parentId);
      28. // 获取父级部门的Code
      29. String parentCode = depart.getOrgCode();
      30. // 根据父级部门类型算出当前部门的类型
      31. orgType = String.valueOf(Integer.valueOf(depart.getOrgType()) + 1);
      32. // 处理同级部门为null的情况
      33. if (parentList == null || parentList.size() == 0) {
      34. // 直接生成当前的部门编码并返回
      35. newOrgCode = YouBianCodeUtil.getSubYouBianCode(parentCode, null);
      36. } else { //处理有同级部门的情况
      37. // 获取同级部门的编码,利用工具类
      38. String subCode = parentList.get(0).getOrgCode();
      39. // 返回生成的当前部门编码
      40. newOrgCode = YouBianCodeUtil.getSubYouBianCode(parentCode, subCode);
      41. }
      42. }
    2. 修改租户表:sys_tenant 增加一个字段 pre_code(角色编码前缀) ``sql ALTER TABLEsys_tenantADD COLUMNpre_codevarchar(100) NULL COMMENT '角色编码前缀' AFTERstatus`; alter table sys_tenant add constraint sys_tenant_pre_code_uindex

      1. primary key (pre_code);
    1. 16. 修改org.jeecg.modules.system.entity.SysTenant,增加如下内容:
    2. ```java
    3. /**
    4. * 角色编码前缀
    5. */
    6. private String preCode;
    1. 修改新增租户的逻辑。

      租户接口org.jeecg.modules.system.service.ISysTenantService: ```java /**

      • 保存
      • @param sysTenant */ void saveSysTenant(SysTenant sysTenant);
    1. 18. 修改sys_permission表。增加base_flag字段,base_flag用来设置基础菜单标志,0-不可复制,1-可复制;默认 0
    2. ```sql
    3. ALTER TABLE `sys_permission`
    4. ADD COLUMN `base_flag` int(1) default 0 not NULL COMMENT '基础菜单标志' AFTER `tenant_id`;
    5. update sys_permission set base_flag = 0;
    1. 手动修改sys_permission表,找出需要复制给其他租户的菜单数据,把这些菜单的base_flag字段设置成1。
    2. 修改org.jeecg.modules.system.entity.SysPermission,增加baseFlag属性。如下: ```java /**

      • 租户id */ private Integer tenantId;

      /**

      • 设置基础菜单标志,0-不可复制,1-可复制;默认 0
      • 创建新租户时,复制baseFlag为1的菜单为新租户可用。 */ private Integer baseFlag;
    1. 21. 修改org.jeecg.modules.system.controller.SysTenantControlleradd方法修改如下:
    2. ```java
    3. /**
    4. * 添加
    5. * @param
    6. * @return
    7. */
    8. @RequestMapping(value = "/add", method = RequestMethod.POST)
    9. public Result<SysTenant> add(@RequestBody SysTenant sysTenant) {
    10. Result<SysTenant> result = new Result();
    11. if(sysTenantService.getById(sysTenant.getId())!=null){
    12. return result.error500("该编号已存在!");
    13. }
    14. LambdaQueryWrapper<SysTenant> query = new LambdaQueryWrapper<SysTenant>();
    15. query.eq(SysTenant::getPreCode, sysTenant.getPreCode());
    16. if(sysTenantService.count(query) > 0){
    17. return result.error500("该角色前缀编号已存在!");
    18. }
    19. try {
    20. //sysTenantService.save(sysTenant);
    21. sysTenantService.saveSysTenant(sysTenant);
    22. result.success("添加成功!");
    23. } catch (Exception e) {
    24. log.error(e.getMessage(), e);
    25. result.error500("操作失败");
    26. }
    27. return result;
    28. }
    1. 实现类org.jeecg.modules.system.service.impl.SysTenantServiceImpl,增加如下方法: ```java package org.jeecg.modules.system.service.impl;

    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.exception.JeecgBootException; import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.system.entity.; import org.jeecg.modules.system.mapper.SysTenantMapper; import org.jeecg.modules.system.service.; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;

    import java.io.Serializable; import java.util.*;

    @Service(“sysTenantServiceImpl”) @Slf4j public class SysTenantServiceImpl extends ServiceImpl implements ISysTenantService {

    @Autowired ISysPermissionService sysPermissionService;

    @Autowired ISysUserService sysUserService;

    @Autowired ISysRoleService sysRoleService;

    @Autowired ISysUserRoleService sysUserRoleService;

    @Autowired ISysRolePermissionService sysRolePermissionService;

    @Override public List queryEffectiveTenant(Collection idList) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(SysTenant::getId, idList); queryWrapper.eq(SysTenant::getStatus, CommonConstant.STATUS_1); //此处查询忽略时间条件 return super.list(queryWrapper); }

    @Override public int countUserLinkTenant(String id) { LambdaQueryWrapper userQueryWrapper = new LambdaQueryWrapper<>(); userQueryWrapper.eq(SysUser::getRelTenantIds, id); userQueryWrapper.or().like(SysUser::getRelTenantIds, “%,” + id); userQueryWrapper.or().like(SysUser::getRelTenantIds, id + “,%”); userQueryWrapper.or().like(SysUser::getRelTenantIds, “%,” + id + “,%”); // 查找出已被关联的用户数量 return sysUserService.count(userQueryWrapper); }

    @Override public boolean removeTenantById(String id) { // 查找出已被关联的用户数量 int userCount = this.countUserLinkTenant(id); if (userCount > 0) { throw new JeecgBootException(“该租户已被引用,无法删除!”); } return this.removeById(id); }

    @Override public boolean removeById(Serializable id) { return super.removeById(id); }

    @Override public boolean save(SysTenant entity) { return super.save(entity); }

    /**

    • 保存 *
    • @param sysTenant */ @Override @Transactional public void saveSysTenant(SysTenant sysTenant) { this.save(sysTenant); int tenantId = sysTenant.getId(); List ls = getPermissionList(); Collection 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);

      // 添加角色 用户关系—给admin管理员绑定新创建的租户角色 SysUserRole sysUserRole = new SysUserRole(); // TODO is ok? sysUserRole.setRoleId(role.getId()); sysUserRole.setUserId(user.getId()); sysUserRoleService.save(sysUserRole);

      // 添加角色 菜单关系 List 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 List getPermissionList() { //如果设置了BaseFlag字段配置 可以读取数据库 LambdaQueryWrapper query = new LambdaQueryWrapper(); query.eq(SysPermission::getBaseFlag, 1); query.eq(SysPermission::getTenantId, 1); List ls = sysPermissionService.list(query); for(SysPermission sp : ls){ sp.setBaseFlag(0); } // 读取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 ls = array.toJavaList(SysPermission.class); return ls; }

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

    private Collection setPermissionTenant(List ls, int tenantId) { // 循环两次 第一次设置ID和tenantId 第二次设置pid Map 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(); }

    }

    1. 23. 修改租户前端界面TenantForm.vue,在租户编号后面增加“角色编码前缀”输入框。
    2. ```javascript
    3. <a-col :span="24">
    4. <a-form-model-item label="租户编号" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="id">
    5. <a-input-number style="width: 100%" :min="1" v-model="model.id" placeholder="请输入租户编号" :disabled="disabledId"></a-input-number>
    6. </a-form-model-item>
    7. </a-col>
    8. <a-col :span="24">
    9. <a-form-model-item label="角色编码前缀" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="preCode">
    10. <a-input style="width: 100%" v-model="model.preCode" placeholder="请输入角色编码前缀"></a-input>
    11. </a-form-model-item>
    12. </a-col>
    1. 租户前端界面TenantList.vue,在租户编号后面增加“角色编码前缀”,如下: ```javascript
      1. {
      2. title:'租户编号',
      3. align:"center",
      4. dataIndex: 'id'
      5. },
      6. {
      7. title:'角色编码前缀',
      8. align:"center",
      9. dataIndex: 'preCode'
      10. },
    1. 25. 修改org.jeecg.config.mybatis.MybatisPlusSaasConfig,增加角色、菜单、部门的表名加入tenantTable列表。
    2. ```java
    3. static {
    4. tenantTable.add("demo");
    5. //角色、菜单、部门
    6. tenantTable.add("sys_role");
    7. tenantTable.add("sys_permission");
    8. tenantTable.add("sys_depart");
    9. }

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

    1. delete from sys_role_permission where permission_id in (select id from sys_permission where tenant_id <> 1);
    2. delete from sys_role_permission where role_id in (select id from sys_role where tenant_id <> 1);
    3. delete from sys_permission where tenant_id <> 1;
    4. delete from sys_user_role where role_id in (select id from sys_role where tenant_id <> 1);
    5. delete from sys_role where tenant_id <> 1;
    6. delete from sys_user_depart where dep_id in (select id from sys_depart where tenant_id <> 1);
    7. delete from sys_depart where tenant_id <> 1;

    此番修改后,原系统中的管理员角色和管理员都属于id为1的租户。新建租户的时候会把租户1的管理员赋予新租户。同时系统会给新租户创建一个管理员角色,名字用preCode+admin的方式生成。

    参考:Jeecg-boot 2.4.6+ 多租户改造方案(涉及菜单部门角色等基础模块)