Jeecg-boot 2.4.6+ 多租户改造方案(涉及菜单部门角色等基础模块)
Posted JEECG官方博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jeecg-boot 2.4.6+ 多租户改造方案(涉及菜单部门角色等基础模块)相关的知识,希望对你有一定的参考价值。
场景:
jeecg boot提供了多租户的配置,但是并没有完整实现该功能,此文就原系统表的菜单、部门、角色、用户为例实现多租户功能实现方案。
- 修改菜单表:
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;
- 修改部门表(注意实体需加字段)
ALTER TABLE `sys_depart`
ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `update_time`;
update sys_depart set tenant_id = 1;
- 修改角色表(注意实体需加字段)
ALTER TABLE `sys_role`
ADD COLUMN `tenant_id` int(1) NULL COMMENT '租户ID' AFTER `update_time`;
update sys_role set tenant_id = 1;
-
配置基础菜单json, 做多租户的时候,需要将部分系统管理的菜单复制多分分配给不同租户角色,所以需要配置一个json文件,详细见补丁文件
-
修改代码:全项目搜索注解
@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代码:
-
修改租户表:
sys_tenant
增加一个字段pre_code
(角色编码前缀) -
修改新增租户的逻辑 租户接口:
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配置通过配置数据权限的方式实现。
因为用户逻辑代码太多了,改动成本太高,单独做一个菜单给只自己看自己租户的数据即可。
以上是关于Jeecg-boot 2.4.6+ 多租户改造方案(涉及菜单部门角色等基础模块)的主要内容,如果未能解决你的问题,请参考以下文章
JeecgBoot开发多租户SAAS数据隔离,查询数据库,改造多租户后 Mybatis-plus 查询数据库的SQL语句tenant-id[租户Id] 一直为0
JeecgBoot开发多租户SAAS数据隔离,查询数据库,改造多租户后 Mybatis-plus 查询数据库的SQL语句tenant-id[租户Id] 一直为0