From 2e51710e3a6edc873171e017e02432b368c9870c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Thu, 6 Aug 2026 21:21:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(system):=20=E5=AE=9E=E7=8E=B0=E8=B7=A8?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E5=85=8B=E9=9A=86=E6=8E=88=E6=9D=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增RoleMenuMapper和UserRoleMapper跨租户查询方法,支持忽略租户拦截器 - 实现grantCmsPermission方法,支持从参考租户克隆角色、菜单、角色菜单和用户角色到当前租户 - 角色克隆按role_code去重,已存在角色复用,新增角色批量保存 - 菜单克隆按层级复制,避免重复插入,保证父子菜单顺序 - 角色菜单克隆使用映射关系去重,批量保存新增绑定 - 用户角色克隆根据角色代码对齐用户名,避免重复绑定 - 返回克隆后的各资源统计数据,包括新增角色、菜单、角色菜单和用户角色数量 --- .../common/system/mapper/RoleMenuMapper.java | 9 + .../common/system/mapper/UserRoleMapper.java | 9 + .../system/mapper/xml/RoleMenuMapper.xml | 5 + .../system/mapper/xml/UserRoleMapper.xml | 5 + .../service/impl/TenantServiceImpl.java | 217 +++++++++++++----- 5 files changed, 182 insertions(+), 63 deletions(-) diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java index ab60a7e..ded3b01 100644 --- a/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java +++ b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java @@ -36,4 +36,13 @@ public interface RoleMenuMapper extends BaseMapper { @InterceptorIgnore(tenantLine = "true") List listMenuByRoleIds(@Param("roleIds") List roleIds, @Param("menuType") Integer menuType); + /** + * 跨租户查询角色菜单(忽略租户拦截器) + * + * @param tenantId 租户id + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List selectListAll(@Param("tenantId") Integer tenantId); + } diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java index 71efd86..d318fe9 100644 --- a/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java @@ -62,4 +62,13 @@ public interface UserRoleMapper extends BaseMapper { */ List selectListRel(@Param("param") UserRoleParam param); + /** + * 跨租户查询用户角色(忽略租户拦截器) + * + * @param tenantId 租户id + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List selectListAll(@Param("tenantId") Integer tenantId); + } diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml index 377d903..a415eb0 100644 --- a/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml @@ -39,4 +39,9 @@ ORDER BY a.sort_number + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml index 69272a3..6088738 100644 --- a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml @@ -64,4 +64,9 @@ + + + diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java index 655afc2..ef7014e 100644 --- a/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java @@ -10,8 +10,12 @@ import com.gxwebsoft.common.core.utils.DomainUtil; import com.gxwebsoft.common.core.utils.RedisUtil; import com.gxwebsoft.common.system.entity.*; import com.gxwebsoft.common.system.mapper.MenuMapper; +import com.gxwebsoft.common.system.mapper.RoleMapper; +import com.gxwebsoft.common.system.mapper.RoleMenuMapper; +import com.gxwebsoft.common.system.mapper.UserRoleMapper; import com.gxwebsoft.common.system.mapper.TenantMapper; import com.gxwebsoft.common.system.param.MenuParam; +import com.gxwebsoft.common.system.param.RoleParam; import com.gxwebsoft.common.system.service.*; import com.gxwebsoft.common.system.param.TenantParam; import com.gxwebsoft.common.core.web.ApiResult; @@ -26,6 +30,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,10 +42,8 @@ import java.util.stream.Collectors; * @author 科技小王子 * @since 2023-07-17 17:49:53 */ -@Service -public class TenantServiceImpl extends ServiceImpl implements TenantService { - // cms建站权限标识前缀 - private static final String CMS_AUTHORITY_PREFIX = "cms:"; + @Service + public class TenantServiceImpl extends ServiceImpl implements TenantService { @Resource private CompanyService companyService; @@ -49,6 +52,12 @@ public class TenantServiceImpl extends ServiceImpl impleme @Resource private MenuMapper menuMapper; @Resource + private RoleMapper roleMapper; + @Resource + private RoleMenuMapper roleMenuMapper; + @Resource + private UserRoleMapper userRoleMapper; + @Resource private RoleMenuService roleMenuService; @Resource private RoleService roleService; @@ -654,21 +663,70 @@ public class TenantServiceImpl extends ServiceImpl impleme @Override @Transactional(rollbackFor = {Exception.class}) public ApiResult grantCmsPermission(Integer refTenant, Integer curTenant) { - // 1.当前租户的超级管理员角色, 必须带租户条件 - final Role superAdmin = roleService.getOne(new LambdaQueryWrapper() - .eq(Role::getRoleCode, "superAdmin") - .eq(Role::getTenantId, curTenant) - .last("limit 1")); - if (superAdmin == null) { - return new ApiResult<>(Constants.RESULT_ERROR_CODE, "当前租户没有superAdmin角色, 无法授权"); + if (refTenant == null || curTenant == null) { + return new ApiResult<>(Constants.RESULT_ERROR_CODE, "参考租户或当前租户为空"); } - final Integer roleId = superAdmin.getRoleId(); + if (refTenant.equals(curTenant)) { + return new ApiResult<>(Constants.RESULT_ERROR_CODE, "参考租户不能与当前租户相同"); + } + final Map data = new HashMap<>(); + data.put("refTenantId", refTenant); + data.put("tenantId", curTenant); - // 2.跨租户读取参考租户的全部未删除菜单 - final MenuParam refParam = new MenuParam(); - refParam.setTenantId(refTenant); - refParam.setDeleted(0); - final List refMenus = menuMapper.getMenuByClone(refParam); + // ===== 1. 克隆角色 sys_role(按 role_code 去重,superAdmin/admin/user 已存在则复用)===== + final RoleParam refRoleParam = new RoleParam(); + refRoleParam.setTenantId(refTenant); + final List refRoles = roleMapper.selectListAll(refRoleParam); + if (CollectionUtils.isEmpty(refRoles)) { + return new ApiResult<>(Constants.RESULT_ERROR_CODE, "参考租户[" + refTenant + "]没有可复制的角色"); + } + final RoleParam curRoleParam = new RoleParam(); + curRoleParam.setTenantId(curTenant); + // role_code -> Role(当前租户已存在的角色) + final Map curRoleByCode = roleMapper.selectListAll(curRoleParam).stream() + .collect(Collectors.toMap(Role::getRoleCode, r -> r, (a, b) -> a)); + // oldRoleId -> newRoleId 映射 + final Map roleIdMapping = new HashMap<>(); + // 待新增角色(按 role_code 去重) + final Map toCreateByCode = new LinkedHashMap<>(); + for (Role r : refRoles) { + final Role exist = curRoleByCode.get(r.getRoleCode()); + if (exist != null) { + roleIdMapping.put(r.getRoleId(), exist.getRoleId()); + } else if (!toCreateByCode.containsKey(r.getRoleCode())) { + final Role copy = new Role(); + copy.setRoleCode(r.getRoleCode()); + copy.setRoleName(r.getRoleName()); + copy.setComments(r.getComments()); + copy.setSortNumber(r.getSortNumber()); + copy.setTenantId(curTenant); + toCreateByCode.put(r.getRoleCode(), copy); + } + } + int roleCreated = 0; + if (!toCreateByCode.isEmpty()) { + roleService.saveBatch(new ArrayList<>(toCreateByCode.values())); + // 重新读取当前租户角色,回填 oldRoleId -> newRoleId 映射 + final Map refreshed = roleMapper.selectListAll(curRoleParam).stream() + .collect(Collectors.toMap(Role::getRoleCode, r -> r, (a, b) -> a)); + for (Role r : refRoles) { + final Role nr = refreshed.get(r.getRoleCode()); + if (nr != null) { + roleIdMapping.put(r.getRoleId(), nr.getRoleId()); + curRoleByCode.putIfAbsent(r.getRoleCode(), nr); + } + } + roleCreated = toCreateByCode.size(); + } + // 当前租户全部角色:roleId -> Role(含新克隆的),供后续 role_code 对齐使用 + final Map newRoleById = new HashMap<>(); + curRoleByCode.values().forEach(r -> newRoleById.put(r.getRoleId(), r)); + + // ===== 2. 克隆菜单 sys_menu(按层级复制,父先于子,标题/权限去重)===== + final MenuParam refMenuParam = new MenuParam(); + refMenuParam.setTenantId(refTenant); + refMenuParam.setDeleted(0); + final List refMenus = menuMapper.getMenuByClone(refMenuParam); if (CollectionUtils.isEmpty(refMenus)) { return new ApiResult<>(Constants.RESULT_ERROR_CODE, "参考租户[" + refTenant + "]没有可复制的菜单"); } @@ -676,42 +734,19 @@ public class TenantServiceImpl extends ServiceImpl impleme for (Menu menu : refMenus) { refMenuMap.put(menu.getMenuId(), menu); } - - // 3.筛选cms:*子树, 权限标识以cms:开头的节点 + 其全部祖先, 保证父级完整 - final Set cmsMenuIds = new HashSet<>(); - for (Menu menu : refMenus) { - if (menu.getAuthority() == null || !menu.getAuthority().startsWith(CMS_AUTHORITY_PREFIX)) { - continue; - } - Menu current = menu; - // add返回false说明该节点及其祖先已收录, 无需继续向上追溯 - while (current != null && cmsMenuIds.add(current.getMenuId())) { - final Integer parentId = current.getParentId(); - current = (parentId == null || parentId == 0) ? null : refMenuMap.get(parentId); - } - } - if (cmsMenuIds.isEmpty()) { - return new ApiResult<>(Constants.RESULT_ERROR_CODE, "参考租户[" + refTenant + "]没有cms权限菜单"); - } - - // 4.当前租户已有菜单, 用于去重, 保证接口幂等 - final MenuParam curParam = new MenuParam(); - curParam.setTenantId(curTenant); - curParam.setDeleted(0); + final MenuParam curMenuParam = new MenuParam(); + curMenuParam.setTenantId(curTenant); + curMenuParam.setDeleted(0); final Map curMenuKeys = new HashMap<>(); - for (Menu menu : menuMapper.getMenuByClone(curParam)) { + for (Menu menu : menuMapper.getMenuByClone(curMenuParam)) { curMenuKeys.putIfAbsent(getMenuKey(menu), menu.getMenuId()); } - - // 5.按层级复制菜单, 父菜单先于子菜单插入 final List sources = refMenus.stream() - .filter(d -> cmsMenuIds.contains(d.getMenuId())) .sorted(Comparator.comparingInt((Menu d) -> getMenuDepth(d, refMenuMap))) .collect(Collectors.toList()); final Map menuIdMapping = new HashMap<>(); int menuCount = 0; for (Menu source : sources) { - // 已存在的菜单不重复插入, 直接复用其菜单id作为子菜单的父级 final Integer existsMenuId = curMenuKeys.get(getMenuKey(source)); if (existsMenuId != null) { menuIdMapping.put(source.getMenuId(), existsMenuId); @@ -737,32 +772,88 @@ public class TenantServiceImpl extends ServiceImpl impleme menuCount++; } - // 6.绑定cms菜单到超级管理员角色, 已绑定的跳过 - final Set boundMenuIds = roleMenuService.list(new LambdaQueryWrapper() - .eq(RoleMenu::getRoleId, roleId)) - .stream().map(RoleMenu::getMenuId).collect(Collectors.toSet()); - final List roleMenus = new ArrayList<>(); - for (Integer menuId : menuIdMapping.values()) { - if (menuId == null || boundMenuIds.contains(menuId)) { + // ===== 3. 克隆角色菜单 sys_role_menu(roleId/menuId 用映射转换,去重)===== + final Set curRmKeys = roleMenuMapper.selectListAll(curTenant).stream() + .map(rm -> rm.getRoleId() + "_" + rm.getMenuId()) + .collect(Collectors.toSet()); + final List rmsToSave = new ArrayList<>(); + for (RoleMenu rm : roleMenuMapper.selectListAll(refTenant)) { + final Integer newRoleId = roleIdMapping.get(rm.getRoleId()); + final Integer newMenuId = menuIdMapping.get(rm.getMenuId()); + if (newRoleId == null || newMenuId == null) { continue; } - final RoleMenu roleMenu = new RoleMenu(); - roleMenu.setRoleId(roleId); - roleMenu.setMenuId(menuId); - roleMenu.setTenantId(curTenant); - roleMenus.add(roleMenu); + final String key = newRoleId + "_" + newMenuId; + if (curRmKeys.contains(key)) { + continue; + } + final RoleMenu copy = new RoleMenu(); + copy.setRoleId(newRoleId); + copy.setMenuId(newMenuId); + copy.setTenantId(curTenant); + rmsToSave.add(copy); + curRmKeys.add(key); } - if (!roleMenus.isEmpty()) { - roleMenuService.saveBatch(roleMenus); + int roleMenuCreated = 0; + if (!rmsToSave.isEmpty()) { + roleMenuService.saveBatch(rmsToSave); + roleMenuCreated = rmsToSave.size(); } - final Map data = new HashMap<>(); - data.put("refTenantId", refTenant); - data.put("tenantId", curTenant); - data.put("roleId", roleId); + // ===== 4. 克隆用户角色 sys_user_role(按 role_code 对齐当前租户用户,去重)===== + final List curUserRoles = userRoleMapper.selectListAll(curTenant); + final Set curUrKeys = curUserRoles.stream() + .map(ur -> ur.getUserId() + "_" + ur.getRoleId()) + .collect(Collectors.toSet()); + // 当前租户 userId -> 其 role_code 集合 + final Map> userRoleCodes = new HashMap<>(); + for (UserRole ur : curUserRoles) { + final Role role = newRoleById.get(ur.getRoleId()); + if (role == null) { + continue; + } + userRoleCodes.computeIfAbsent(ur.getUserId(), k -> new HashSet<>()).add(role.getRoleCode()); + } + final List ursToSave = new ArrayList<>(); + for (UserRole ur : userRoleMapper.selectListAll(refTenant)) { + final Integer newRoleId = roleIdMapping.get(ur.getRoleId()); + if (newRoleId == null) { + continue; + } + final Role newRole = newRoleById.get(newRoleId); + if (newRole == null) { + continue; + } + final String roleCode = newRole.getRoleCode(); + // 找当前租户中 role_code 相同的用户,建立绑定 + for (Map.Entry> e : userRoleCodes.entrySet()) { + if (!e.getValue().contains(roleCode)) { + continue; + } + final String key = e.getKey() + "_" + newRoleId; + if (curUrKeys.contains(key)) { + continue; + } + final UserRole copy = new UserRole(); + copy.setUserId(e.getKey()); + copy.setRoleId(newRoleId); + copy.setTenantId(curTenant); + ursToSave.add(copy); + curUrKeys.add(key); + } + } + int userRoleCreated = 0; + if (!ursToSave.isEmpty()) { + userRoleService.saveBatch(ursToSave); + userRoleCreated = ursToSave.size(); + } + + data.put("roleTotal", refRoles.size()); + data.put("roleCreated", roleCreated); data.put("menuTotal", menuIdMapping.size()); data.put("menuCreated", menuCount); - data.put("roleMenuCreated", roleMenus.size()); + data.put("roleMenuCreated", roleMenuCreated); + data.put("userRoleCreated", userRoleCreated); return new ApiResult<>(Constants.RESULT_OK_CODE, "授权成功", data); }