feat(oapi): user/modules 新增可选 appCode,支持查询指定应用的模块树

- 传 appCode => 按 application_info.app_code 解析目标应用(供托管型应用代渲染导航)
- 未传 => 保持原行为,按验签 appKey 解析调用方应用
- 应用解析失败一律返回空列表(安全优先,禁止意外泄露其他应用模块)
- 参数用 appCode 而非整数 appId:跨环境稳定,与 form-center 的 /app/{appCode}/ 同键
This commit is contained in:
chenxing
2026-10-21 08:52:42 +08:00
parent 195936236d
commit cb40524b8e
@@ -10,6 +10,7 @@ import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.userbase.common.annotation.OapiLog;
import com.yunhai.userbase.common.validation.ValidationGroups;
import com.yunhai.userbase.config.Config;
import com.yunhai.userbase.entity.ApplicationInfo;
import com.yunhai.userbase.model.sysuser.BatchChangeStatusRequest;
import com.yunhai.userbase.model.sysuser.SysUserChangeStatusRequest;
import com.yunhai.userbase.model.sysuser.SysUserContactRequest;
@@ -91,30 +92,49 @@ public class UserController {
}
/**
* 查询用户在<b>调用方应用</b>下、指定学校(上下文)里的已授权模块列表。<br>
* 查询用户在<b>指定应用</b>下、指定学校(上下文)里的已授权模块列表。<br>
* <br>
* 与管理端「权限一览」返回<b>全应用聚合</b>不同,本方法严格按调用方应用维度过滤:<br>
* ① 通过 {@link AppAuthFilter#APP_AUTH_ATTR} 拿到验签时的 appKey(RequireAppSignFilter
* 已保证非空);<br>
* ② 查 application_info 映射到具体 appId;<br>
* 与管理端「权限一览」返回<b>全应用聚合</b>不同,本方法严格按单一应用维度过滤。应用来源两态:<br>
* ① 传了可选参数 appCode → 按 application_info.app_code 解析<b>目标应用</b>(供托管型应用
* 如 form-center 代快应用渲染导航时查询目标应用的模块清单;查不到返回空列表并 warn,
* 口径同现有兜底);<br>
* ② 未传 appCode → 现行为不变:通过 {@link AppAuthFilter#APP_AUTH_ATTR} 拿到验签时的
* appKey(RequireAppSignFilter 已保证非空),查 application_info 映射到具体 appId;<br>
* ③ 调 Service 专用方法:所有 SQL/集合构建均限定在该 appId 范围内(数据库层面过滤)。<br>
* <br>
* 边界:调用方 appKey 查不到对应应用 → 直接返回空列表(安全优先,禁止意外返回其他应用的模块)。<br>
* 边界:应用解析失败(appCode/appKey 查不到对应应用)→ 直接返回空列表(安全优先,禁止意外
* 返回其他应用的模块)。<br>
* 记账:本参数暴露面=模块名/路径/图标级低敏数据;如需收紧可后续限定仅 BASE 应用调用
* (暂放开,表单中心建设方案 §6.2 已记)。参数用 appCode(字符串)而非整数 appKey:
* 跨环境稳定,与 form-center 分发路由 /app/{appCode}/ 同键。
*
* @param request 用于取 AppAuthFilter 设置的 appKey
* @param request 用于取 AppAuthFilter 设置的 appKey(未传 appCode 时使用)
* @param userId 用户ID(必填)
* @param schId 学校ID/组ID(可选;用于限定「学校上下文」:非空时仅取该学校子树内的真实归属组参与判定,
* 为空时取用户全部真实归属组,与权限一览一致)
* @param appCode 目标应用技术标识(可选;传=查该应用的模块树,不传=按签名调用方解析)
* @return 该应用下用户已授权模块(扁平化;无权限时返回空列表)
*/
@GetMapping("modules")
public List<BoaModule> modules(HttpServletRequest request,
@RequestParam("userId") Integer userId,
@RequestParam(value = "schId", required = false) Integer schId) {
@RequestParam(value = "schId", required = false) Integer schId,
@RequestParam(value = "appCode", required = false) String appCode) {
if (userId == null) {
return Collections.emptyList();
}
Integer appId = resolveCallerAppId(request);
Integer appId;
if (appCode != null && !appCode.trim().isEmpty()) {
ApplicationInfo app = applicationInfoService.getByAppCode(appCode.trim());
if (app == null) {
log.warn("[oapi:user:modules] appCode={} not found in application_info, uri={}",
appCode, request.getRequestURI());
return Collections.emptyList();
}
appId = app.getId();
} else {
appId = resolveCallerAppId(request);
}
if (appId == null) {
return Collections.emptyList();
}