diff --git a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java index dacc9b4..52e265d 100644 --- a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java +++ b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java @@ -1,22 +1,24 @@ package com.yunhai.userbase.controller.oapi; +import com.yunhai.platform.appauth.AppAuthFilter; +import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.model.BoaGroup; import com.yunhai.platform.utils.model.BoaModule; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userbase.config.Config; -import com.yunhai.userbase.model.groupauthz.UserPermissionAppVo; -import com.yunhai.userbase.model.groupauthz.UserPermissionCategoryVo; -import com.yunhai.userbase.model.groupauthz.UserPermissionModuleVo; +import com.yunhai.userbase.entity.ApplicationInfo; +import com.yunhai.userbase.repository.ApplicationInfoRepository; import com.yunhai.userbase.service.IGroupAuthzService; import com.yunhai.userbase.service.impl.UserCenterService; -import org.springframework.beans.BeanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; +import javax.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; @@ -34,12 +36,17 @@ import java.util.List; @RequestMapping(Config.OPEN_API_PATH + "user/") public class UserController { + private static final Logger log = LoggerFactory.getLogger(UserController.class); + @Autowired private UserCenterService userCenterService; @Autowired private IGroupAuthzService groupAuthzService; + @Autowired + private ApplicationInfoRepository applicationInfoRepository; + @GetMapping("byId") public BoaUser byId(@RequestParam("id") Integer id) { return userCenterService.getUserInfoWithDeptById(id); @@ -62,60 +69,72 @@ public class UserController { } /** - * 查询用户在指定学校下的已授权模块列表。
- * 若 {@code schId} 为 null 时,自动取用户的主学校;若仍无主学校信息,退化为取用户主归属组(groups 第 0 个)。 + * 查询用户在调用方应用下、指定学校(上下文)里的已授权模块列表。
+ *
+ * 与管理端「权限一览」返回全应用聚合不同,本方法严格按调用方应用维度过滤:
+ * ① 通过 {@link AppAuthFilter#APP_AUTH_ATTR} 拿到验签时的 appKey(RequireAppSignFilter + * 已保证非空);
+ * ② 查 application_info 映射到具体 appId;
+ * ③ 调 Service 专用方法:所有 SQL/集合构建均限定在该 appId 范围内(数据库层面过滤)。
+ *
+ * 边界:调用方 appKey 查不到对应应用 → 直接返回空列表(安全优先,禁止意外返回其他应用的模块)。
* - * @param userId 用户ID(必填) - * @param schId 学校ID/组ID(可选;为 null 时按上述规则推断) - * @return 扁平化的已授权模块列表(含基础平台统一 BoaModule 模型);无权限时返回空列表 + * @param request 用于取 AppAuthFilter 设置的 appKey + * @param userId 用户ID(必填) + * @param schId 学校ID/组ID(可选;为 null 时按"主学校→主归属组"规则推断) + * @return 该应用下用户已授权模块(扁平化;无权限时返回空列表) */ @GetMapping("modules") - public List modules(@RequestParam("userId") Integer userId, + public List modules(HttpServletRequest request, + @RequestParam("userId") Integer userId, @RequestParam(value = "schId", required = false) Integer schId) { if (userId == null) { return Collections.emptyList(); } - Integer effectiveGroupId = resolveEffectiveGroupId(userId, schId); - // 注意:超管即使没有学校/归属组,service.findUserPermissionTree 内部也会走超管短路返回全部权限, - // 因此这里不能因 effectiveGroupId 为 null 直接返回空,否则超管在 oapi/user/modules 永远拿不到权限; - // 普通用户无组/无学校时,findUserPermissionTree 同样会返回空列表,行为一致。 - List tree = groupAuthzService.findUserPermissionTree(effectiveGroupId, userId); - if (tree == null || tree.isEmpty()) { + Integer appId = resolveCallerAppId(request); + if (appId == null) { return Collections.emptyList(); } - List result = new ArrayList<>(); - for (UserPermissionCategoryVo category : tree) { - if (category.getApps() == null) - continue; - for (UserPermissionAppVo appVo : category.getApps()) { - String icon = appVo.getPcIcon(); - collectModules(appVo.getModules(), icon, result); - } - } - return result; + return groupAuthzService.findUserModulesForApp(appId, schId, userId); } /** - * 递归收集已授权模块树到扁平列表,并把应用 PC 图标作为模块的 icon 兜底(BoaModule.icon)。 + * 从请求 attribute 中解析调用方 appKey,并映射为 application_info 的主键 id。
+ * 映射失败返回 null(Controller 层统一返回空列表;异常只打 warn 日志,不给前端 500)。 */ - private void collectModules(List modules, - String appIcon, - List collector) { - if (modules == null || modules.isEmpty()) { - return; + private Integer resolveCallerAppId(HttpServletRequest request) { + Object attr = request.getAttribute(AppAuthFilter.APP_AUTH_ATTR); + if (attr == null) { + log.warn("[oapi:user:modules] no AppAuth attr (should be blocked by RequireAppSignFilter), uri={}", + request.getRequestURI()); + return null; } - for (UserPermissionModuleVo m : modules) { - BoaModule bm = new BoaModule(); - BeanUtils.copyProperties(m, bm); - bm.setIcon(appIcon); - collector.add(bm); - collectModules(m.getChildren(), appIcon, collector); + String appKeyStr = (attr instanceof String) ? (String) attr : String.valueOf(attr); + if (appKeyStr.trim().isEmpty()) { + log.warn("[oapi:user:modules] AppAuth attr is blank, uri={}", request.getRequestURI()); + return null; } + Integer appKey; + try { + appKey = Integer.parseInt(appKeyStr.trim()); + } catch (NumberFormatException e) { + log.warn("[oapi:user:modules] invalid appKey format={}, uri={}", appKeyStr, request.getRequestURI()); + return null; + } + ApplicationInfo app = applicationInfoRepository.findByAppKey(appKey); + if (app == null) { + log.warn("[oapi:user:modules] appKey={} not found in application_info, uri={}", appKey, + request.getRequestURI()); + return null; + } + return app.getId(); } /** - * 按「schId 为空时」解析出用户主学校/主组作为模块查询的上下文组ID。 + * 按「schId 为空时」解析出用户主学校/主组作为模块查询的上下文组ID。
+ * 保留方法(逻辑已下沉到 Service.resolveEffectiveGroupId 复用),不再被 modules() 使用。 */ + @Deprecated private Integer resolveEffectiveGroupId(Integer userId, Integer schId) { if (schId != null) { return schId; diff --git a/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java b/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java index d12a66c..09086d7 100644 --- a/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java +++ b/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java @@ -115,6 +115,23 @@ public interface IGroupAuthzService { */ List findUserGroupTree(Integer userId); + /** + * 开放接口专用:查询用户在指定调用方应用下的已授权模块(扁平化 BoaModule 列表)。
+ * 与 {@link #findUserPermissionTree(Integer, Integer)} 的核心差别:
+ * - 后者返回用户在全部已开通应用下的权限聚合(供管理端权限一览使用);
+ * - 本方法只返回 {@code appId} 对应这一个应用下的模块,所有 SQL/集合构建均限定该应用(数据库层面过滤)。
+ *
+ * 上下文组(schId)语义与开放接口一致:schId 非空=只认该组链路;schId 为空=取用户主学校组(若仍空=用户主归属组 id)。
+ * 超级管理员:不做组/继承判定,直接返回该应用下全部模块(但仍只返回该应用,不会跨应用返回)。
+ * 角色授权并集:最终权限 = 组授权模块 ∪ 启用角色下属于该应用的模块(逻辑与权限一览保持一致)。 + * + * @param appId 调用方应用ID(必填;查不到或该应用无开通=返回空列表) + * @param schId 学校/组上下文(可选;为 null 时按"主学校→主归属组"规则推断) + * @param userId 用户ID(必填) + * @return 该应用下用户有权限的模块(扁平化;无权限时返回空列表) + */ + List findUserModulesForApp(Integer appId, Integer schId, Integer userId); + // ===================== 新增:树节点式增量授权(供左侧树 + 右侧 appAuthzList 的勾选交互) ===================== /** diff --git a/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java b/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java index fc4c51d..471df81 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java @@ -1,6 +1,9 @@ package com.yunhai.userbase.service.impl; import com.yunhai.platform.utils.exception.DException; +import com.yunhai.platform.utils.model.BoaGroup; +import com.yunhai.platform.utils.model.BoaModule; +import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userbase.entity.*; import com.yunhai.userbase.enums.GroupTypeEnum; import com.yunhai.userbase.enums.IEnum; @@ -32,6 +35,7 @@ import com.yunhai.userbase.repository.SysUserAppRoleRelRepository; import com.yunhai.userbase.repository.SysUserGroupRelRepository; import com.yunhai.userbase.repository.SysUserRepository; import com.yunhai.userbase.service.IGroupAuthzService; +import com.yunhai.userbase.service.impl.UserCenterService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; @@ -100,6 +104,9 @@ public class GroupAuthzService implements IGroupAuthzService { @Autowired private SysUserAppRoleRelRepository sysUserAppRoleRelRepository; + @Autowired + private UserCenterService userCenterService; + // ===================== 新增:沿祖先链收集「本组+所有祖先组开通的应用ID并集」(应用池联动GetSchoolAppId) // ===================== @@ -493,6 +500,200 @@ public class GroupAuthzService implements IGroupAuthzService { return Collections.singletonList(moduleId); } + // ===================== 开放接口专用:单应用维度模块过滤 ===================== + + @Override + public List findUserModulesForApp(Integer appId, Integer schId, Integer userId) { + Objects.requireNonNull(appId, "应用ID不能为空"); + Objects.requireNonNull(userId, "用户ID不能为空"); + // 1. 调用方应用本身存在性校验(数据库过滤第一层:appId 必须真实存在,否则直接空) + ApplicationInfo app = applicationInfoRepository.findById(appId).orElse(null); + if (app == null) { + return Collections.emptyList(); + } + String appIcon = app.getPcIcon(); + SysUser user = sysUserRepository.findById(userId).orElse(null); + if (user == null) { + return Collections.emptyList(); + } + // 2. 超级管理员短路:不再返回全应用模块=仅返回该应用下全部模块(数据库过滤第二层:appId 限定) + if (isSuperAdmin(user.getType(), userId)) { + List allModules = appModuleRepository.findByAppIdIn(Collections.singletonList(appId)); + return flattenToBoaModules(allModules, appIcon); + } + // 3. 收集该用户启用角色下=属于目标应用的模块ID(角色授权并集,不要求组先开通应用) + Set roleModuleIdsInApp = collectEnabledRoleModuleIdsForApp(userId, appId); + // 4. 上下文组解析(schId 非空=只认这一条链路;schId 空=按"主学校→主归属组"规则推断) + Integer effectiveGroupId = resolveEffectiveGroupId(userId, schId); + // 5. 若上下文组有效=构建其祖先链,并判定:该祖先链开通池里是否包含目标 appId(应用开通闸门) + // 上下文组无效(用户无学校/无组)时,仍可能因角色授权而返回模块,不能直接返回空 + Set selfGroupIds = new LinkedHashSet<>(); + Map allGroupMap = buildAllGroupMap(); + Map> chainMap = new HashMap<>(); + Set allChainIds = new LinkedHashSet<>(); + Map> groupToApps = new HashMap<>(); + boolean appOpened = false; + if (effectiveGroupId != null) { + selfGroupIds.add(effectiveGroupId); + List chain = ancestorChain(effectiveGroupId, allGroupMap); + chainMap.put(effectiveGroupId, chain); + allChainIds.addAll(chain); + Set openedApps = collectAppIdsForAuthzScope(effectiveGroupId); + groupToApps.put(effectiveGroupId, openedApps); + if (openedApps.contains(appId)) { + appOpened = true; + } + } + // 6. 数据库过滤第三层:只加载目标应用下的模块 + List appModules = appModuleRepository.findByAppIdIn(Collections.singletonList(appId)); + if (appModules.isEmpty()) { + return (roleModuleIdsInApp.isEmpty()) ? Collections.emptyList() + : flattenToBoaModulesById(roleModuleIdsInApp, appIcon, buildAppModuleMap(appModules)); + } + Set appModuleIds = new LinkedHashSet<>(); + for (AppModule m : appModules) { + appModuleIds.add(m.getId()); + } + // 7. 数据库过滤第四层:只查「目标应用模块 × 祖先链」上的组授权记录 + Map> modulePermMap = new HashMap<>(); + Map> groupConfiguredApps = new HashMap<>(); + if (appOpened && !appModuleIds.isEmpty() && !allChainIds.isEmpty()) { + List perms = sysGroupModulePermRepository.findByModuleIdInAndGroupIdIn( + new ArrayList<>(appModuleIds), new ArrayList<>(allChainIds)); + for (SysGroupModulePerm perm : perms) { + if (perm.getModuleId() == null || perm.getGroupId() == null) { + continue; + } + modulePermMap.computeIfAbsent(perm.getModuleId(), k -> new HashMap<>()) + .put(perm.getGroupId(), perm); + groupConfiguredApps.computeIfAbsent(perm.getGroupId(), k -> new HashSet<>()).add(appId); + } + } + // 8. 权限判定合并:组授权 ∪ 角色授权(均属于该应用) + Set permittedModuleIds = new LinkedHashSet<>(); + for (Integer mid : roleModuleIdsInApp) { + if (appModuleIds.contains(mid)) { + permittedModuleIds.add(mid); + } + } + if (appOpened) { + for (AppModule m : appModules) { + if (permittedModuleIds.contains(m.getId())) { + continue; + } + if (hasUserModulePermission(m.getId(), m.getAppId(), selfGroupIds, chainMap, groupToApps, + modulePermMap, allGroupMap, groupConfiguredApps)) { + permittedModuleIds.add(m.getId()); + } + } + } + if (permittedModuleIds.isEmpty()) { + return Collections.emptyList(); + } + return flattenToBoaModulesById(permittedModuleIds, appIcon, buildAppModuleMap(appModules)); + } + + /** + * 收集用户启用角色下=且属于目标应用 appId 的模块ID集合(角色授权并集专用的"应用过滤版")。
+ * 与 {@link #collectEnabledRoleModuleIds(Integer)} 的区别:后者返回全部角色模块; + * 本方法在其之上再按模块.appId == appId 过滤一次(不额外发 SQL,遍历两次即可)。 + */ + private Set collectEnabledRoleModuleIdsForApp(Integer userId, Integer appId) { + Set allRoleModuleIds = collectEnabledRoleModuleIds(userId); + if (allRoleModuleIds.isEmpty() || appId == null) { + return Collections.emptySet(); + } + Set result = new LinkedHashSet<>(); + for (AppModule m : appModuleRepository.findAllById(allRoleModuleIds)) { + if (Objects.equals(m.getAppId(), appId) && m.getId() != null) { + result.add(m.getId()); + } + } + return result; + } + + /** + * 上下文组解析(与开放接口 modules 语义严格一致): + * - schId 非空:直接用其作为唯一 selfGroupId; + * - schId 为空:取用户主学校组(BoaUser.school.id); + * - 仍空:取用户主归属组 id(groups 第 0 个); + * - 全空:返回 null(此时只能靠角色授权拿模块)。 + */ + private Integer resolveEffectiveGroupId(Integer userId, Integer schId) { + if (schId != null) { + return schId; + } + BoaUser boa = userCenterService.getUserInfoWithDeptById(userId); + if (boa == null) { + return null; + } + BoaGroup school = boa.getSchool(); + if (school != null && school.getId() != null) { + return school.getId(); + } + List groups = boa.getGroups(); + if (groups != null && !groups.isEmpty()) { + BoaGroup main = groups.get(0); + return main == null ? null : main.getId(); + } + return null; + } + + private static Map buildAppModuleMap(List appModules) { + Map map = new HashMap<>(); + for (AppModule m : appModules) { + if (m.getId() != null) { + map.put(m.getId(), m); + } + } + return map; + } + + private static List flattenToBoaModules(List modules, String icon) { + if (modules == null || modules.isEmpty()) { + return Collections.emptyList(); + } + modules.sort(moduleOrder()); + List result = new ArrayList<>(modules.size()); + for (AppModule m : modules) { + result.add(toBoaModule(m, icon)); + } + return result; + } + + private static List flattenToBoaModulesById(Set moduleIds, String icon, + Map moduleMap) { + if (moduleIds == null || moduleIds.isEmpty()) { + return Collections.emptyList(); + } + List list = new ArrayList<>(moduleIds.size()); + for (Integer mid : moduleIds) { + AppModule m = moduleMap.get(mid); + if (m != null) { + list.add(m); + } + } + return flattenToBoaModules(list, icon); + } + + private static BoaModule toBoaModule(AppModule m, String icon) { + BoaModule bm = new BoaModule(); + BeanUtils.copyProperties(m, bm); + bm.setIcon(icon); + return bm; + } + + private static Comparator moduleOrder() { + return (a, b) -> { + int sa = a.getSortNo() == null ? 999 : a.getSortNo(); + int sb = b.getSortNo() == null ? 999 : b.getSortNo(); + if (sa != sb) { + return Integer.compare(sa, sb); + } + return Integer.compare(a.getId() == null ? 0 : a.getId(), b.getId() == null ? 0 : b.getId()); + }; + } + // ===================== 权限一览 ===================== @Override @@ -605,14 +806,14 @@ public class GroupAuthzService implements IGroupAuthzService { Set roleModuleIds = collectEnabledRoleModuleIds(userId); // 1. 归属组集合:区分「组上下文模式」和「用户全局模式」 - // A) 接口显式传了 groupId(= 管理员在"某个组的授权面板"点用户权限一览,是典型的组视角查询): - // 以传入的 groupId 作为唯一 selfGroupId,仅沿「该组 -> 祖先 -> 第一个学校组(含)」链路判定; - // 【关键】不再把用户真实加入的其他组(例如用户还属于学校组)并入判断。 - // 原因:如果允许用户归属的"学校组"作为独立 selfGroupId 参与本组显式判定, - // 则子组未设置的模块会直接命中学校组自己的显式记录(permMap.get(学校组)!=null), - // 导致"子组设置的覆盖限制"完全失效=父级所有模块都拿到,即你观察到的"本组继承没生效/还是父级全量"。 - // B) 未传 groupId(全局查用户权限/开放接口 modules 未传schId以外的其他上下文): - // 正常取用户所有真实归属组并集。 + // A) 接口显式传了 groupId(= 管理员在"某个组的授权面板"点用户权限一览,是典型的组视角查询): + // 以传入的 groupId 作为唯一 selfGroupId,仅沿「该组 -> 祖先 -> 第一个学校组(含)」链路判定; + // 【关键】不再把用户真实加入的其他组(例如用户还属于学校组)并入判断。 + // 原因:如果允许用户归属的"学校组"作为独立 selfGroupId 参与本组显式判定, + // 则子组未设置的模块会直接命中学校组自己的显式记录(permMap.get(学校组)!=null), + // 导致"子组设置的覆盖限制"完全失效=父级所有模块都拿到,即你观察到的"本组继承没生效/还是父级全量"。 + // B) 未传 groupId(全局查用户权限/开放接口 modules 未传schId以外的其他上下文): + // 正常取用户所有真实归属组并集。 List userRels = sysUserGroupRelRepository.findByUserId(userId); Set selfGroupIds = new LinkedHashSet<>(); if (groupId != null) { @@ -884,12 +1085,15 @@ public class GroupAuthzService implements IGroupAuthzService { /** * 就近原则:在 groupId 的祖先链上,找到离它最近的、对目标应用有显式模块配置的组(含自身)。 * - *

链首即自身({@link #ancestorChain} 由自身向根排列),天然「就近优先」;遍历时第一个 + *

+ * 链首即自身({@link #ancestorChain} 由自身向根排列),天然「就近优先」;遍历时第一个 * 「groupConfiguredApps 中包含该应用」的组即作为该应用在本组范围内的唯一权限来源返回。 * - *

学校截止:遇到学校组(无论是否已配置)即作为继承上限,不再向教育局/根越级,避免跨学校继承到未开通的应用。 + *

+ * 学校截止:遇到学校组(无论是否已配置)即作为继承上限,不再向教育局/根越级,避免跨学校继承到未开通的应用。 * - *

找不到返回 null(该应用在此范围内没有任何组的显式配置,对应模块不许可)。 + *

+ * 找不到返回 null(该应用在此范围内没有任何组的显式配置,对应模块不许可)。 */ private Integer findNearestAuthorityGroup(Integer groupId, List chain, Integer appId, Map> groupConfiguredApps, @@ -913,7 +1117,8 @@ public class GroupAuthzService implements IGroupAuthzService { /** * 子组继承闸门:判定「来源组 authority 对 moduleId 的记录」是否对本组 selfGroupId 生效。 * - *

规则(修复「父级未勾子组继承却仍被继承」的 bug): + *

+ * 规则(修复「父级未勾子组继承却仍被继承」的 bug): * ① 来源组 == 本组:本组自身勾选的模块直接生效——{@code inheritFlag} 只约束是否下放到子组,不影响本组自己; * ② 来源组为祖先(往上回溯得来):必须勾选了「子组继承」({@code inheritFlag=true}) 才会下放到本组, * 否则该模块不继承。即「只过滤勾选了子组继承的模块;若来源组对该应用所有模块都没勾子组继承,则本组对该应用返回空」。 @@ -933,7 +1138,8 @@ public class GroupAuthzService implements IGroupAuthzService { /** * 用户维度权限判定:用户对指定模块是否有权限(就近原则 + 学校截止 + 角色授权并集 + 超管兜底)。 * - *

就近原则(统一了「本组覆盖」与「最近祖先继承」两种情形,修复两类合并 bug): + *

+ * 就近原则(统一了「本组覆盖」与「最近祖先继承」两种情形,修复两类合并 bug): * 沿 selfGroupId 的祖先链(含自身)向上,找到「离它最近、且对 appId 有任意显式模块配置」的组作为 * 该应用的唯一权限来源;该模块被许可,当且仅当这个来源组对它自身有显式 {@code sys_group_module_perm} 记录。 * 这样: @@ -941,13 +1147,16 @@ public class GroupAuthzService implements IGroupAuthzService { * ② 本组无配置(如英语组为空)→ 取最近的有配置祖先(如教师组)为来源,不再越级到更远的学校组; * ③ 来源组的模块集合即最终集合,更上层同应用模块(如学校组多勾的模块8)一律不合并。 * - *

子组继承闸门(修复「父级未勾子组继承仍被继承」的 bug):来源组若是本组,本组勾选的模块直接生效; + *

+ * 子组继承闸门(修复「父级未勾子组继承仍被继承」的 bug):来源组若是本组,本组勾选的模块直接生效; * 来源组若是祖先,必须该模块记录 {@code inheritFlag=true}(勾选子组继承)才下放到本组,否则不继承—— * 即「只过滤勾选了子组继承的模块;若来源组对该应用所有模块都没勾子组继承,则本组对该应用返回空」。 * - *

学校截止:祖先链只查到第一个学校组(含)为止,不跨学校到教育局/根。 + *

+ * 学校截止:祖先链只查到第一个学校组(含)为止,不跨学校到教育局/根。 * - *

其余:归属组需已开通该模块所属应用(双保险,防越权)。 + *

+ * 其余:归属组需已开通该模块所属应用(双保险,防越权)。 */ private boolean hasUserModulePermission(Integer moduleId, Integer appId, Set selfGroupIds, @@ -1235,7 +1444,7 @@ public class GroupAuthzService implements IGroupAuthzService { return false; } // 4. 一次批量查询「该应用下所有模块」在全部祖先链节点上的权限记录 - // (要判定就近来源,需要知道链上哪些组对该应用有显式配置,不能只看单模块) + // (要判定就近来源,需要知道链上哪些组对该应用有显式配置,不能只看单模块) List appModules = appModuleRepository.findByAppIdIn(Collections.singletonList(appId)); Set appModuleIds = new LinkedHashSet<>(); for (AppModule m : appModules) { @@ -1256,7 +1465,7 @@ public class GroupAuthzService implements IGroupAuthzService { } } // 5. 就近原则判定:对每个归属组,取离它最近的、对该应用有显式配置的组为唯一权限来源; - // 该来源组对目标模块有记录即通过(多个归属组取并集)。 + // 该来源组对目标模块有记录即通过(多个归属组取并集)。 for (Integer selfGroupId : selfGroupIds) { List chain = chainMap.get(selfGroupId); Integer authority = findNearestAuthorityGroup(selfGroupId, chain, appId, groupConfiguredApps, allGroupMap);