# ADR-0018: 数据权限按业务模块可配 **Date:** 2026-08-11 **Status:** Accepted ## Context 原数据权限机制为「单一全局档位」:`SysRole.dataScope` 存一个 1-4 的整数,对用户所有受控表统一应用。这意味着一个角色对客户模块和线索模块只能有相同的可见范围(如全部可见或仅本人),无法满足「销售对客户看全部、对线索看本部门」这类差异化需求。 ## Decision 将数据权限从「单一全局档位」改造为「按业务模块分别配置档位」: 1. **模块注册表** — 新增 `sys_data_scope_module` 表(code/name/sort/status/builtin),数据驱动,管理员可 CRUD。 2. **角色×模块档位** — 新增 `sys_role_data_scope(role_id, module_code, data_scope)` 表,一个角色对每个模块各有一档。删除 `SysRole.dataScope` 列。 3. **注解声明** — `@DataScope` 注解新增必填 `module` 属性,引用注册表 code。启动时 `PermissionConfig` 校验 code 在注册表中存在,不存在则拒绝启动。 4. **上下文值对象** — `DataVisibility` 持有 `Map` 替代单个 level;`DataVisibilityContext.currentScope(moduleCode)` 加参数取该模块的 scope。 5. **拦截器顺序调换** — 先解析 SQL 找表 → 查注解(含 module)→ 用 `anno.module()` 从上下文取该模块的 scope → 注入 WHERE。 6. **解析器每模块取最宽** — `PermissionResolverImpl` 查 `sys_role_data_scope` 按 `module_code` 分组,每组取最宽 level(ADR-0008 规则下沉到每模块)。未配模块默认 SELF(最窄档,fail safe)。 7. **删除 CUSTOM(5)** — `DataScopeEnum.CUSTOM` 从枚举中移除。 8. **API 契约** — `RoleController.saveOrUpdate` 用 `moduleScopes` JSON 字符串表单字段替代 `dataScope`;新增 `GET /api/data-scope/modules` 列出可用模块。 9. **迁移策略** — 一次性 SQL 脚本(单事务,CROSS JOIN 旧值,DROP 旧列)。 ## Consequences - 角色配置从「选一个档位」变为「每模块各选一档」,前端角色编辑页需要改造。 - `DataVisibility` API 破坏性变更:`visibilityScope()` → `visibilityScope(String moduleCode)`,所有调用方需加参数。 - 写侧不受影响:`currentOwnership()` 是身份维度(userId/deptId),与档位正交。 - 迁移脚本将旧 `sys_role.data_scope` 值通过 CROSS JOIN 展开到所有注册模块,保留原有行为。