Files
lingxi/docs/开发计划/灵犀_A1身份认证与用户管理_实施报告_V0.1.md
T
Stev_Wang 981757b76c
ci / 质量门禁 (push) Canceled after 0s
docs(plan): 回写 A1 部门管理补强
背景:
- 部门管理已实现(独立页面 /system/departments、只停用不删除、
  有成员/启用子部门禁止停用),需要同步计划、规范与实施报告。

改动:
- 开发计划:新增 WBS A1-BE-05(部门接口)与 A1-FE-04(部门管理页),
  A1 进度更新为 10/10、测试计数与「补强」说明;附录 C 增加
  /system/departments,§7.1 页面/接口清单更新;版本记录增补。
- A1 实施报告:范围表、契约/后端/前端交付物、测试计数与验证表补充
  部门管理;新增 §2.5 部门管理补强(口径、规则、覆盖范围)与遗留项。
- 11-前端路由与目录规范:管理中心模块表补 /system/departments
  (pages/system/departments/),版本 V1.1。

影响面:
- 仅文档;与已提交的代码行为一致,不改变其它批次范围。

回滚方式:
- 回滚本提交即恢复文档原状;不影响代码。

验证证据:
- pnpm format:check 通过;
- 文档中的测试计数、接口与页面路径与 feat(identity) 提交一致。
2026-11-03 15:02:14 +08:00

12 KiB
Raw Blame History

灵犀 A1 身份认证与用户管理 · 实施报告 V0.1

项 内容
实施日期 2026-11-02
批次 A1(身份认证与用户管理)
状态 已完成,门禁通过;未提交
依据 开发计划 V0.1 §6.2 / §7.1、技术架构 V0.4 §5.4.1 / §6.3、工程规范 01–10
视觉基线 A0「方向 A · 云白蓝」+ 选定方案 B(主从分栏 + 分屏登录)

1 范围与完成度

任务 内容 状态
A1-BE-01 部门 / 用户 CRUD、停用启用、角色绑定、客户转交占位 ✅
A1-BE-02 Argon2id 登录、JWT access + refresh 旋转、jti 吊销、登出、失败锁定、密码策略、SSO 接口预留 ✅(SSO 仅预留)
A1-BE-03 Jwt → Permission → SpecialPermission → Scope 守卫链、@Audit 与请求上下文 ✅(Scope 骨架)
A1-BE-04 /me 用户 / 角色 / 权限 / 数据范围 / 可见客户;权限即时生效 ✅(dataScope 仅 all / 本部门及下级,六类留 A2)
A1-FE-01 登录页、token 存储刷新、登出、过期跳转、路由回跳 ✅
A1-FE-02 用户列表(游标分页 / 筛选 / 排序)、新建 / 编辑、停用确认、部门筛选、角色多选、客户转交入口 ✅
A1-FE-03 /me 缓存、usePermission / <Can>、路由与按钮级控制、403 页 ✅
A1-BE-05 部门管理补强:树(含停用)、新建 / 改名 / 负责人 / 排序、停用 / 启用、停用拦截 ✅(2026-11-03 补强)
A1-FE-04 部门管理页 /system/departments:两级树 + 详情/编辑 + 停用启用确认 ✅(2026-11-03 补强)
A1-QA-01 登录 / 刷新 / 登出 / 停用 / 角色变更 / 越权 6 类用例 ✅(接口 int + 浏览器 e2e)

口径确认(用户拍板):初始密码由管理员设定、首次登录强制修改;手机号列表掩码、详情全号仅 user:manage 可见;路由按开发计划附录 C 使用 /system/users(2026-11-03 路由规范调整后)。


2 交付物

2.1 契约(packages/shared)

  • 新增 src/schemas/identity.ts(+ spec):UserSummary(phoneMasked,永不含全号)、UserDetail(phone? 仅授权时返回)、UserListQuery(cursor/limit/keyword/department_id/role_id/status/sort/order)、CreateUserInput、UpdateUserInput、DisableUserInput、TransferCustomersInput、LoginInput、RefreshInput、LoginResponse = tokenPair + me、MeResponse、DepartmentNode(status / sortOrder / memberCount / directMemberCount)、CreateDepartmentInput、UpdateDepartmentInput、DepartmentTreeQuery、RoleSummary。
  • permissions.ts 新增 USER_VIEW = 'user:view';保留 USER_MANAGE。
  • 写 body camelCase、query snake_case;写接口 .strict();错误码沿用 A0 统一表。

2.2 后端(apps/api + prisma)

  • 依赖:@nestjs/jwt@12.0.3、argon2@0.45.1(计划 A1-BE-02 已批准,版本精确锁定)。
  • 迁移:prisma/migrations/20261102143519_a1_identity(expand):departments、users、roles、user_roles、role_permissions、refresh_tokens。
  • 种子(幂等):8 角色 + 角色权限映射、3 个一级部门 + 6 个小组、11 个用户(admin 超管 + 10 位示例成员),统一初始密码 lingxi2026、must_change_password=true。
  • identity 模块(apps/api/src/modules/identity):
    • POST /auth/login|refresh|logout|change-password、GET /me、GET/POST /users、GET/PATCH /users/{id}、POST /users/{id}/disable|enable|transfer-customers、GET /departments/tree?include_disabled=、POST/PATCH /departments、POST /departments/{id}/disable|enable、GET /roles。
    • 部门:两级组织树;新建 / 改名 / 负责人 / 排序;只停用不删除;有在职成员、启用中的下级小组或下级在职成员时禁止停用(42201 带数量);启用时上级必须启用;负责人必须是启用用户。
    • refresh token 旋转:jti + token_hash + family,旧 token 原子吊销;重放整族吊销。
    • 登录失败 Redis 计数,达阈值锁定 15 分钟(42901)。
    • 权限每请求从 DB 解析,角色变更下一次请求生效。
    • 未修改初始密码时除白名单接口外统一 40903。
    • 手机号:列表只 phoneMasked;详情仅调用者具 user:manage 时附带 phone。
  • 安全与上下文(apps/api/src/common/security):@Public、@RequirePermissions、@CurrentUser、@Audit、AsyncLocalStorage 请求上下文;@Audit 先写 Pino 结构化日志(A5 落库预留)。
  • env:JWT_ACCESS_SECRET / JWT_REFRESH_SECRET(≥32 位)/ JWT_ACCESS_TTL_SECONDS=900 / JWT_REFRESH_TTL_SECONDS=604800 / AUTH_MAX_LOGIN_FAILURES=5 / AUTH_LOCK_MINUTES=15;.env.example 已登记,本地 .env.local 已配置。

2.3 前端(apps/web)

  • 路由:/system/users、/system/users/new、/system/users/:id、/403(旧 /users* 已加重定向);RequireAuth + RequirePermission;未登录跳 /login?redirect=(含 sanitizeRedirect 防开放重定向);无权限进 403;停用账号回登录并给出停用说明。
  • 权限:usePermission / usePermissions + <Can>,无权限按钮不渲染;/me 走 TanStack Query 缓存。
  • 登录:方案 B 左右分屏(左 560 品牌 + 能力 + CSS 规格缩略,右表单卡);真实 POST /auth/login;错误 / loading / 锁定态;首次登录强改在登录页内完成,成功后清会话要求用新密码重登。
  • 用户列表 / 详情:左 400 列表 + 右 760 详情(基本资料 / 角色权限 / 负责客户 / 登录与安全 / 操作记录);搜索、部门、角色、状态、排序、游标分页;停用 / 启用 / 客户转交 / 调整角色 / 超管授予均「对象卡 → 后果 → 勾选」二次确认;编辑资料弹窗;空 / 加载 / 错误三态;1024 / 1280 适配。
  • 新建用户:单栏三段(账号信息 / 组织与角色 / 初始设置),Semi Form + 共享 Zod 同源校验,部门两级联动、角色多选、超管二次确认。
  • 部门管理(补强):/system/departments,左两级组织树(人数 / 负责人 / 状态、显示已停用、?id= 选中)+ 右详情/内联编辑(名称、上级只读、负责人、排序、直属/含下级人数、下级小组);新建部门/小组、停用/启用二次确认;无 user:manage 只读;树/表单/接口封装均有单测。
  • 样式:--lx-* token + Semi 组件 + styles/users.css;未引入其他组件库/图标库。

2.4 测试(e2e)

  • e2e/a1-identity.spec.ts:6 条浏览器端到端用例(A1_E2E=1 门控,CI 无依赖时 skip),真实 API + 真实 PG/Redis;测试数据自建自清。
  • e2e/README.md:运行前置、环境变量、CORS 说明。

2.5 A1 补强:部门管理(2026-11-03)

  • 口径:独立页面 /system/departments;只停用不删除;有在职成员 / 启用中的下级小组 / 下级在职成员时禁止停用;负责人/排序可改;上级关系创建后不可移动;权限复用 user:view / user:manage,不新增权限点。
  • 后端:GET /departments/tree?include_disabled、POST/PATCH /departments、POST /departments/{id}/disable|enable;无 DELETE;@Audit 覆盖写操作;成员统计只算在职。
  • 前端:管理中心新增「部门管理」;两级树 + 详情/编辑 + 新建/停用启用确认;停用前本地预判 + 服务端文案兜底。
  • 测试:shared 契约(节点字段/新建/更新 strict);api 单测 + int 覆盖两级/重名/负责人/三类停用拦截/启用联动/DELETE 404;web 单测覆盖树、约束、表单、接口封装;真实账号 + 真实 API 手工联调通过(新建部门/小组、改名、停用空小组、重名提示、截图 70-departments.png)。

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 files / 33 tests(真实 PG + Redis,含部门 6 例)
OpenAPI pnpm openapi:export ✅ identity 路径(含部门 5 条)导出,apps/web/src/api/generated/openapi.d.ts 已更新
浏览器 e2e A1_E2E=1 pnpm exec playwright test e2e/a1-identity.spec.ts ✅ 6/6(登录+强改、刷新旋转、登出、停用、角色变更、越权 403)
部门管理联调 真实 API + 浏览器(jiang.lan) ✅ 新建一级/小组、改名、停用空小组、重名 40903、树/详情/停用确认均正常

联调截图:design/a1-ui-v1/shots/impl/(登录、强改、用户列表、详情、角色 Tab、停用弹窗、命中真实接口);e2e 截图:design/a1-ui-v1/shots/impl/e2e/。


4 本地运行

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(超级管理员);示例成员 chen.mo / lin.qing / zhou.an / zhao.yinuo / gao.cen / su.he / shen.zhiyuan / jiang.lan;停用示例 tang.jia / he.an。初始密码统一 lingxi2026,首次登录强制修改。

只有需要清空本地库时才执行 pnpm db:reset(会重建并恢复种子密码);./dev.sh / pnpm dev / pnpm db:init 不会重置已有账号密码。


5 已知差异与遗留

编号 项 说明 / 建议
1 数据范围六类 当前 /me 只区分 all 与 department_and_children;六类范围与 ScopeContext 注入按计划由 A2 完成,ScopeGuard 骨架已就位。
2 客户转交 POST /users/{id}/transfer-customers 现返回 42201「客户模块(A3)提供」;负责客户 Tab 暂为空态。A3 落地后替换。
3 审计落库 @Audit 目前写结构化 Pino 日志;A5 建审计表后接入持久化与检索/导出。
4 OpenAPI schema 路径已导出,但请求/响应 schema 仍缺(Zod 入参无 DTO 类);建议后续用 shared Zod 生成 JSON Schema 并补 @ApiBody/@ApiOkResponse。前端当前直接用 shared 类型。
5 记住我 契约暂无 rememberMe,登录页保留交互;如需真正 7 天会话需扩展 refresh TTL 策略。
6 SSO 仅预留适配器接口,未实现(计划 M3-3)。
7 对比度 辅助文字 #8F959E 白底约 2.8:1 属 A0 全局 token 问题,本轮未改;建议作为一次全局 token 调整统一处理。
8 CORS / e2e API WEB_ORIGIN 仅允许 http://127.0.0.1:5173;e2e 跑在 dev 5173。若改用 preview 4173,需把该 origin 加进 WEB_ORIGIN。
9 本地账号状态 联调过程中曾修改本地 admin/jiang.lan 口令并触发锁定;交付前已 pnpm db:reset 恢复种子(lingxi2026)。
10 客户转交必填 PRD 要求离职停用前必须转交名下客户;当前因 A3 未落地,停用弹窗的转交为可选项,A3 之后应收紧为有客户时必填。
11 部门管理 e2e 部门管理暂无独立浏览器 e2e,由 api int(6 例)+ web 单测 + 真实账号手工联调覆盖;后续补 e2e 时建议覆盖「有成员禁止停用」与「停用后不可再选」。
12 部门删除 按口径不提供删除,仅停用;如后续确需删除空部门,需先补 PRD/接口口径。

6 回滚方式

  • 代码:整体回滚本批 commit(未提交前可直接 git restore 上述路径)。
  • 数据库:本批为 expand 迁移,回滚代码不需要删表;如需彻底回退,用 prisma migrate resolve 标记后手工 drop 新增表(仅本地)。
  • 配置:删除 .env.local 中新增的 JWT / 认证变量,API 启动会因校验失败而阻止启动(有意的 fail-fast)。

7 下一步

  1. A2 角色权限与数据范围:角色 CRUD、权限点树、六类数据范围、SoD 校验、ScopeContext 全链路注入(当前 identity 已留接口与守卫骨架)。
  2. A3 客户与行业目录:替换客户转交占位、负责客户 Tab 接真实数据、停用前转交必填。
  3. A5 审计:@Audit 落库、检索与导出,A1 e2e 增加审计断言。
  4. 工程补强:OpenAPI schema 由 shared Zod 生成;全局对比度 token 决策;rememberMe 与 refresh TTL 策略。