# 灵犀 A2 角色权限与数据范围 · 实施报告 V0.1 | 项 | 内容 | | --- | --- | | 实施日期 | 2026-11-03 | | 批次 | A2(角色权限与数据范围) | | 状态 | 已完成,门禁通过;未推送 | | 依据 | 开发计划 V0.1 §3.4 H1 门、§6.3、§7.2、§10.3、附录 B;技术架构 V0.4 §5.5 / §6.1–6.3 / §7.1 / §8.7;PRD V1.6 §5.4.3、R-16~R-18 | | 决策记录 | [ADR-001 权限点目录与六类数据范围](../adr/ADR-001-权限点目录与六类数据范围.md) | | 视觉基线 | A0「方向 A · 云白蓝」+ A1 已选定「B 主从分栏」;本轮 UI 门选定 **B 主从分栏** | --- ## 1 范围与完成度 | 任务 | 内容 | 状态 | | --- | --- | --- | | A2-BE-01 | 角色 CRUD / 复制 / 停用 / 删除、系统角色保护、重名校验 | ✅ | | A2-BE-02 | 权限点目录 30 个 / 10 模块代码注册,启动同步 `permissions` 表,`GET /permissions` 树 | ✅ | | A2-BE-03 | 角色权限绑定、特殊权限 6 编码单独授予、特殊权限分桶校验 | ✅ | | A2-BE-04 | 六类数据范围、`data_scopes`、`ScopeContext`、`CustomerDirectory` 端口、fail-closed | ✅(A3 前客户集为空集) | | A2-BE-05 | SoD 三对保存即校验(40304)、变更审计(前后值 / 影响用户数)、outbox 占位通知 | ✅ | | A2-FE-01 | 角色列表、权限点树、六类范围、特殊权限区、保存影响面、SoD 冲突 | ✅ | | A2-FE-02 | 模板 / 复制 / 停用 / 删除 / 系统角色改名 / 绑定用户 / 只读态 | ✅ | | A2-QA-01 | 角色端点权限三态、SoD 三对、即时生效、特殊权限分桶、浏览器 e2e | ✅ | H1 权限门口径(用户确认):权限点保留现有 `read/write` 编码只补缺(30 个 / 10 模块);特殊权限五项 6 编码;SoD 三对按现有编码映射;六类数据范围全链路 + A3 前空客户集;超管隐式全集;A2 审计先结构化日志、A5 落库。详见 ADR-001。 --- ## 2 交付物 ### 2.1 共享契约(`packages/shared`) - `permissions.ts`:30 个权限点常量与元数据目录(`module` / `moduleLabel` / `name` / `description` / `isSpecial` / `sortOrder`)、10 个模块顺序、`SPECIAL_PERMISSIONS`(6)、`SOD_PAIRS`(3)、`PERMISSION_CATALOG`、`permissionsByModule()`、`splitPermissions()`、`findSodConflicts()`、软上限常量。 - `schemas/roles.ts`:`RoleListItem` / `RoleDetail` / `RoleBoundUser` / `RoleSaveResult` / `CreateRoleInput` / `UpdateRoleInput` / `RoleListQuery` / `PermissionTree` / SoD pair;写接口 `.strict()`,特殊权限分桶校验。 - `schemas/identity.ts`:`DataScope` 增 `targetIds` / `customerIds`;`MeResponse.dataScopes: DataScope[]`(多角色并集,超管 `[{type:'all'}]`)。 - 模块与权限点显示名与 `design/a2-ui-v1/brief.md` 逐条对齐;权限点只增不改。 ### 2.2 数据库与种子 - 迁移 `prisma/migrations/20261103025415_a2_role_permissions_data_scopes/`(expand):新增 `permissions`(目录,code 主键)与 `data_scopes`(role_id + scope_type + target_id,复合唯一索引、FK);未删改既有字段。空库与存量库各验证一次。 - `prisma/seed.ts` 幂等更新:30 权限点、10 角色、角色权限映射(普通权限数 24/12/9/4/2/4/3/5/4/5)、data_scopes(含客户负责人 `customer_groups` 空目标行)、特殊权限 8 项授予(超管 6 + 平台管理员 `audit:read` + AI 工程师 `ai:configure`);`prisma/SEED.md` 同步。 - 说明:种子用户沿用 A1 的 11 个示例账号(12 条角色绑定),不扩种原型示例的 28 名用户。 ### 2.3 后端(`apps/api`) - identity 模块:`GET/POST /roles`、`GET/PATCH/DELETE /roles/{id}`、`GET /permissions`;`GET /roles` 默认 `status=active` 兼容 A1 用户页角色多选,同时接受 `role:view` / `user:view`;角色管理页显式传 `status=all`。 - 权限解析:每请求实时读 DB;`is_system` 超管隐式全部权限点、特殊权限与全部数据范围;停用角色即时失效;未登记权限点被丢弃。 - 数据范围:`ScopeGuard` 解析六类范围为 `ScopeContext`;`CustomerDirectory` 端口 + `EmptyCustomerDirectory`(A3 前空集,不降级);`customerScopeWhere` / `isCustomerVisible` / `assertCustomerAccess` / `requireScopeContext` 与单测;多角色并集,任一 `all` 即全部;解析失败 40302。 - SoD:三对互斥保存即校验,命中 40304 并返回 `details.conflicts`;系统角色不可改权限/范围/状态,仅显示名可改;超管只在系统角色自身豁免。 - 审计:角色新建/更新/停用/删除记录前后值、权限 diff、影响用户数、IP/UA/trace_id;`outbox_events` 写 `role.permissions.changed`(含 affectedUserIds)占位通知。 - 测试:`roles.service.spec.ts`、`permission-catalog.service.spec.ts`、`scope.spec.ts`、`roles.int.spec.ts`。 ### 2.4 前端(`apps/web`) - 路由:`/system/roles`(`role:view`,左 400 角色列表 + 右详情)、`/system/roles/:id`(编辑器,Tab:基本信息/权限点/数据范围/特殊权限 + 底部唯一保存)、`/system/roles/new`(`role:manage`,左模板 + 右表单);导航 `/system/roles` 权限由 `ROLE_MANAGE` 调整为 `ROLE_VIEW`;面包屑补角色三页。 - API/Query:`api/roles.ts`、`hooks/queries/use-roles.ts`、`query-keys`、`endpoints`;全部用 shared Zod schema 解析。 - 页面能力:权限树 10 模块/30 点三态 + 搜索 + 展开;特殊权限 6 项二次确认;数据范围六类单选 + 客户组空态;模板预填(特殊不带入);复制/停用/删除/系统角色改名;保存前影响确认;SoD 实时冲突与 40304 回填;未保存离开拦截;权限点目录抽屉;只读态;加载/空/错误态;1024 适配。 - 样式:`styles/roles.css`、`styles/role-editor.css`,复用 `--lx-*` token 与现有 `.lx-*` 组件类;未新增依赖、未内联 SVG。 ### 2.5 UI 原型与决策 - 三套候选原型:`design/a2-ui-v1/`(A 独立编辑器 / B 主从分栏 / C 卡片墙 + 向导),共享口径 `brief.md`,对比页 `index.html` + `style-explorer-*.html`。 - 独立视觉评审:A 8 / B 8 / C 7,均无阻断;推荐 B。用户于 2026-11-03 选定 **B 主从分栏**,实现以 `design/a2-ui-v1/b/` 为准。 - 实现截图:`design/a2-ui-v1/shots/impl/final/`(clean seed);QA e2e 截图:`design/a2-ui-v1/shots/impl/e2e/`(含测试数据)。 --- ## 3 验证结果 | 门禁 | 命令 | 结果 | | --- | --- | --- | | 格式 | `pnpm format:check` | ✅ | | Lint | `pnpm lint` | ✅ 0 warning | | 类型 | `pnpm typecheck` | ✅ 10/10 tasks | | 单测 | `pnpm test` | ✅ shared 119 / api 85 / web 102,共 306 | | 集成 | `pnpm test:int` | ✅ 3 文件 / 27 条(health 1 + identity 13 + roles 13) | | 浏览器 e2e | `A2_E2E=1 pnpm exec playwright test e2e/a2-roles.spec.ts` | ✅ 1 passed(18.9s),复跑 1 passed(19.3s) | | OpenAPI | `pnpm openapi:export` | ✅ 新增 `/roles`、`/roles/{id}`、`/permissions` 路径 | A2-QA-01 覆盖:6 个角色端点权限三态(200/201/204 vs 403 `40301`)、40401、40903、40302 fail-closed;SoD 三对 40304 + `details.conflicts`、失败不落库、系统角色豁免边界;特殊权限分桶与单独授予、复制不带入;同一 access token 的改权限/授特殊/停用/恢复即时生效;outbox 变更审计。 --- ## 4 本地运行 ```bash pnpm check:dev # 确认 PG 5432 / Redis 6379 pnpm db:reset # 迁移 + 种子(admin / lingxi2026,首次登录须改密) pnpm dev # API :3000 + Web :5173 # 浏览器打开 http://127.0.0.1:5173/login → 管理中心 / 角色权限 ``` 种子账号:`admin` 超管、`jiang.lan` 平台管理员等 11 个账号;初始密码 `lingxi2026`,首次登录强制修改。 --- ## 5 已知差异与遗留 | 编号 | 项 | 说明 / 建议 | | --- | --- | --- | | 1 | 客户组 / 负责客户 / 本人创建 | A2 已落解析引擎与 `ScopeContext`,但 A3 前 `CustomerDirectory` 返回空集;「指定客户组」只能先保存范围、A3 后再补授权对象。A3 接入真实客户目录并补范围验收。 | | 2 | 审计落库 | A2 角色变更审计为结构化 Pino 日志 + outbox 事件;审计表落库、检索、导出按 A5。A2-QA-01「越权访问有审计记录」当前以结构化日志 + outbox 为准。 | | 3 | DP-14 两项特殊权限 | 「跨客户复用授权」「批量删除」暂未实现,待 M2/M3 前回写架构并补编码。 | | 4 | 导航模块权限 | 管理中心模块级 permission 仍为 `USER_VIEW`;仅 `role:view` 无 `user:view` 的用户侧栏不可见该模块(路由直达可用)。建议后续统一模块级导航口径。 | | 5 | 面包屑 | 角色编辑器面包屑末级为静态「角色编辑」,未显示角色名;原型为角色名,可在后续接入路由 handle 或上下文数据。 | | 6 | 原型示例数据 | 原型「28 名绑定用户」为 UI 示例;种子实际 11 个账号(12 条绑定)。实现页显示真实种子数据。 | | 7 | OpenAPI schema | 路径已导出,请求/响应 schema 仍缺(Zod 入参无 DTO 类),沿用 A1 遗留;前端直接使用 shared 类型。 | | 8 | e2e 数据 | A2 e2e 每次新增 1 个仅停用的 `qa2.roles.*` 用户(用户不可删除,与 A1 同口径),本地库会累积停用用户;角色在解绑后删除。 | | 9 | 前端可访问性 | Semi Modal 复用 `semi-modal-title` id,关闭动画期间两个弹窗同时挂载可能导致可访问名串台;e2e 已用按钮/文案定位规避,建议后续给 Modal 传唯一 `aria-label`。 | --- ## 6 回滚方式 - 代码:整体回滚本批 commit;前端三页、共享契约、identity 角色接口与测试一并回退。 - 数据库:本批为 expand 迁移,回滚代码不需要删表;如需彻底回退,用 `prisma migrate resolve` 标记后手工 drop `permissions` / `data_scopes`(仅本地)。 - 数据:种子可重跑;角色权限映射幂等重建;`data_scopes` 随角色删除级联。 - 配置:未新增环境变量。 --- ## 7 下一步 1. **A3 客户与行业目录**:实现真实 `CustomerDirectory`、客户组 / 负责客户 / 本人创建范围验收;替换用户停用前客户转交占位。 2. **A5 审计**:`@Audit` 与角色变更审计落库、检索、导出,A2 e2e 增加审计断言。 3. **DP-14**:M2/M3 前回写架构并补「跨客户复用授权」「批量删除」编码。 4. **工程补强**:OpenAPI schema 由 shared Zod 生成;模块级导航权限口径统一;角色编辑器面包屑显示角色名;Semi Modal 唯一 aria-label。