Files
lingxi/docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md
T
Stev_Wang 8626996bb1
ci / 质量门禁 (push) Canceled after 0s
docs(plan): A2 最终验收通过并确认完成状态
背景:A2 全部 WBS 已完成,且后续合入了 A1 部门管理补强与角色页 UI 收口;需按当前 main 重新验收并在开发计划确认完成状态。

改动:
- 开发计划 A2 状态行/批次表保持 ✅ 已完成,进度与 §6.3 完成情况更新为最终验收口径;
- 更新最终测试数字:test 331(shared 121 / api 90 / web 120)、test:int 33、A2 e2e 1/1(22.1s);
- 补充收口项(角色管理命名、页头操作右对齐、编辑角色移入页头、移除重复目录抽屉),A2 实施报告同步。

验证:format:check / lint / typecheck / test / test:int 全绿;A2 e2e 最终版 UI 下通过。
2026-11-03 17:10:18 +08:00

134 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 灵犀 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 121 / api 90 / web 120,共 331 |
| 集成 | `pnpm test:int` | ✅ 4 文件 / 33 条(含 A2 roles 13 条) |
| 浏览器 e2e | `A2_E2E=1 pnpm exec playwright test e2e/a2-roles.spec.ts` | ✅ 1 passed(22.1s,最终版 UI 下复跑通过) |
| OpenAPI | `pnpm openapi:export` | ✅ 新增 `/roles`、`/roles/{id}`、`/permissions` 路径 |
> 最终验收日期 2026-11-03。A2 收口后补充的 UI 调整(角色管理命名、页头主操作右对齐、编辑角色移入详情页头、移除重复的权限点目录抽屉)已随同一批提交合入,并在最终版 UI 上重跑 e2e。
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:init # 首次/迁移后:migrate deploy + 幂等种子(不重置数据、不改已有密码)
pnpm dev # API :3000 + Web :5173(日常启动不需要任何 DB 命令)
# 浏览器打开 http://127.0.0.1:5173/login → 管理中心 / 角色管理
```
种子账号:`admin` 超管、`jiang.lan` 平台管理员等 11 个账号;初始密码 `lingxi2026`,首次登录强制修改(仅 `must_change_password=true` 时;改完即持久为 false)。
> 只有需要清空本地库时才执行 `pnpm db:reset`:它会 `migrate reset --force` 并重新 seed,admin 回到 `lingxi2026` + 首登改密标记。`./dev.sh` / `pnpm dev` / `pnpm db:init` 都不会重置账号密码。生产部署只执行 `prisma migrate deploy`,不执行 seed / reset。
---
## 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。