# 如何设置权限 xly 的权限模型有两张互补表。它们回答*不同*问题,并位于不同层;正确阅读这里很重要,因为名字看起来很相似。 ## 两张表 | 表 | 粒度 | 存储内容 | 加载时机 | |---|---|---|---| | [`gdsjurisdiction`](../../auto-catalog/tables/gdsjurisdiction.md) | 每**模块** | 每个模块存在的动作 / 按钮**目录**(`BtnAdd`、`BtnUpd`、`BtnDel` 等) | 每次 `getModelBysId`(ADMIN 跳过,见[切片 1](../../slices/01-hello-world.md)) | | [`sysjurisdiction`](../../auto-catalog/tables/sysjurisdiction.md) | 每**角色**(也可每用户) | **授权**:哪个角色(或用户)可在某模块上执行哪个动作 | 按用户角色在 `getModelBysId` 中解析;`BusinessGdsconfigformsServiceImpl.getJurisdictionData()` | 心智模型:`gdsjurisdiction` 是框架知道的每模块权限项*菜单*;`sysjurisdiction` 是*座位表*,记录谁能用哪个项。 ## `gdsjurisdiction` 包含什么 按模块组织的权限项树: | 列 | 含义 | |---|---| | `sParentId` | 该权限项所属的 `gdsmodule.sId` | | `sName` | 动作 key,例如 `BtnAdd`、`BtnUpd`、`BtnDel`、`BtnExport` | | `sChinese` / `sEnglish` / `sBig5` | 显示标签(如 `新增`) | | `iOrder` | 权限 UI 中的排序 | | `sBrandsId` / `sSubsidiaryId` | 租户作用域 | 当前实时 DB 中有 **4,335 行 `gdsjurisdiction`**,覆盖 892 个不同模块和 186 个不同动作名。`gdsjurisdiction` **不**包含角色列或用户列,它只是目录。 ## `sysjurisdiction` 包含什么 真实授权: | 列 | 含义 | |---|---| | `sParentId` | 授权所属模块 `sId` | | `sParent2Id` | 父模块(组 / 分类),供权限 UI 渲染树使用 | | `sJurisdictionClassifyId` | 角色 / 权限分类 id(到 `sisjurisdictionclassify` 的 FK 语义) | | `sUserId` | 用户 id,用于**每用户覆盖**;为空表示“角色级授权” | | `sAction` | 被授予的动作,例如 `BtnBsOperation.BtnDesignFunction` | | `sKey` | 复合路径 key,编码拥有该动作的模块层级 | 当前实时 DB 中有 **28,045 行 `sysjurisdiction`**,**全部都是角色级**(每个 `sUserId` 都为空):22 个角色 × 各角色可使用的模块和动作。 `BusinessGdsconfigformsServiceImpl.getJurisdictionData`(第 212 行)在用户级和角色级授权之间选择: ```java Boolean hasCheckUser = checkUserJurisdiction(map); if (hasCheckUser) { return this.getGroupJurisdictionUserNew(map); // 每用户覆盖 } else { return this.getGroupJurisdictionNew(map); // 仅角色 fallback } ``` 如果调用用户在 `sysjurisdiction` 中有任何带其 `sUserId` 的行,则用户路径胜出;否则使用角色路径。当前 dev DB 中用户路径不可达。 ## 请求中如何加载权限 `BusinessBaseServiceImpl.getModelBysId` 中相关伪代码: ```java List> jList = new ArrayList<>(); if (!UserType.ADMIN.getTypeName().equals(map.get("sUserType"))) { jList = businessGdsconfigformsService.getJurisdictionData(qMap); } returnMap.put("gdsjurisdiction", jList); ``` **ADMIN 完全绕过权限**并得到空授权列表,SPA 将其解释为“显示所有按钮”。非管理员用户得到其角色 + 模块 + 租户可解析出的动作过滤列表。SPA 根据返回结果启用 / 禁用按钮和列。 ## 如何授予权限 新模块的典型路径: 1. 定义模块 + 表单([配方](define-form.md))。 2. 为模块暴露的每个按钮 / 动作插入 `gdsjurisdiction` 行(`BtnAdd`、`BtnUpd`、`BtnExport` 等),每动作一行,`sParentId = 你的模块 sId`。 3. 决定哪些角色获得哪些动作,插入 `sysjurisdiction` 行:`sParentId = module sId`、`sJurisdictionClassifyId = role sId`、`sAction = action key`。角色级授权保持 `sUserId` 为空。 4. 如需用户级覆盖,插入带 `sUserId` 的行;每用户覆盖会遮蔽该用户的角色级授权。 5. 通过**后台**保存,让缓存失效传播(运行时按 `(sUserId, sModelsId, sBrandsId, sSubsidiaryId)` 缓存 `getJurisdictionData`,见第 209 行的 `@Cacheable`)。 ## ADMIN 特例 硬编码的 ADMIN 绕过是真实逃生口。按约定每个部署至少有一个 ADMIN 账号。从安全审计角度,ADMIN 访问应在外部严格控制和审计;框架本身没有应用内限制。 ## 跨租户边界不是权限 需要明确:`gdsjurisdiction` + `sysjurisdiction` 强制的是**租户内**权限。跨租户边界(一家公司不能读取另一家公司数据)由[多租户作用域](../../concepts/multi-tenancy.md)强制,即自动注入 `sBrandsId` / `sSubsidiaryId`,不是由 jurisdiction 规则强制。 ## 关于 `plat_base_authority_*` schema 中有三张 `plat_base_authority` 下的表(`plat_base_authority`、`plat_base_authority_button_type`、`plat_base_authority_data_type`),看起来像按钮类型和数据权限类型 lookup。**三张表在实时 DB 中都为空**;它们属于不在本 Wiki 范围内的 `xlyPlat*` B2B 平台层,不属于框架自身权限流。记录框架权限时不要引用它们;上面的 jurisdiction surface 是自包含的。