Files
Edu/services/iam/docs/01-understanding.md
SpecialX 0a71b02e04
Some checks failed
CI / quality-ts (push) Failing after 48s
CI / quality-go (push) Failing after 4s
CI / quality-proto (push) Failing after 2s
CI / deploy (push) Has been skipped
fix: code compliance audit and fix across all services
NestJS (6 services): implement @RequirePermission decorator with
SetMetadata+Reflector, register APP_GUARD globally, fix as assertions
to type guards, add explicit return types, fix import type for express,
fix /metrics implicit any, replace native Error with ApplicationError,
remove typeorm remnants, register LifecycleService.

teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward
real userId to downstream, log downstream failures, migrate health
controller to shared/health.

Go (2 services): interface to any, doc comments, CORS dev whitelist,
JWT secret fail-fast, push-gateway internal API auth, metrics and
readyz endpoints, remove dead code.

Python (2 services): lifespan return type, dev_mode to bool, data-ana
APIRouter, ai POST body model, ClickHouse async wrapping.
2026-07-09 17:28:27 +08:00

23 KiB
Raw Blame History

模块理解确认书 — iam

AIai02TS / 身份认证) 阶段:阶段 1 交付物 日期2026-07-09 关联:004 架构影响地图AI 分配方案pending-features P2


1. 我在架构中的位置

  • 层级L5 业务微服务层004 §3.1 六层架构)
  • 业务领域D1 身份认证领域004 §1.1b),独立限界上下文,不归属其他领域
  • 上游(谁调用我)
    • api-gatewayGo/GinHTTP 反向代理 /api/v1/iam/*http://iam:3002/iam/*(已注册,见 api-gateway/main.go 第 76-83 行)
    • teacher-bffNestJSHTTP 调用 /iam/me/iam/viewports 聚合教师身份与视口(见 teacher-bff/teacher.service.ts 第 30/78 行)
    • 未来:student-bffparent-bff(同样走 HTTP 聚合)
  • 下游(我调用谁)
    • MySQL(独占库 iam_db,连接串 DATABASE_URL
    • RedisP2 待启用:权限缓存 + refresh token 黑名单env 已留 REDIS_URL 占位)
    • KafkaP2 待启用:发布 edu.identity.user.* 事件,由 core-edu/msg 消费)
  • 通信方式
    • 入口:HTTP/REST(当前实现,端口 3002
    • 出口MySQLmysql2 连接池 + Drizzle ORM、Redis待接、Kafka待接
    • 演进P3 起对外暴露 gRPCiam.proto 已定义 IamServiceREST 与 gRPC 并存过渡期)
  • 不持有跨服务状态:会话/token 黑名单放 Redis不落本地内存

2. 我的限界上下文

2.1 我负责的聚合 / 实体

聚合根 实体 / 值对象 当前表 职责
User(用户) UserStatus、DataScope iam_users 注册、登录、密码校验、用户信息查询
Role(角色) iam_roles 三层角色模型(系统/组织/临时)的载体
Permission(权限点) resource + action iam_permissions 权限点常量集中表
RolePermission(角色-权限映射) iam_role_permissions 多对多,权限并集来源
UserRole(用户-角色绑定) iam_user_roles 用户拥有的多层角色
RoleViewport(角色-视口配置) componentConfig(JSON) iam_role_viewports 4 层视口模型的 L1 导航 + L3 组件配置
RefreshToken(刷新令牌) tokenHash + revokedAt iam_refresh_tokens refresh token 持久化 + 轮换/撤销

2.2 业务领域归属

  • D1 身份认证领域004 §1.1biam 独占此领域
  • 上游依赖:无(身份认证是整个系统的权限中枢,不依赖其他业务领域)
  • 下游被依赖core-edu消费 UserRegistered 初始化默认班级关联、msg消费 UserRegistered 发欢迎通知)

2.3 我不负责什么(边界外)

  • 班级/学科/年级数据 → core-eduD2 教学组织)
  • 考试/作业/成绩 → core-eduD3 教学核心)
  • 教材/知识点/题库 → contentD4 内容资源)
  • 站内信/通知投递 → msgD5 沟通通知)
  • 学情分析/掌握度计算 → data-anaD6 智能洞察)
  • WebSocket 长连接管理 → push-gateway
  • JWT 公钥校验 → api-gatewayiam 只签发,不校验)
  • 前端权限 Hook → packages/ui-components/hooks/use-permission.ts(前端通过 BFF 拉取,不直连 iam

3. 我与外部的契约

3.1 我消费的 proto message从 shared-proto

  • 当前不消费任何外部 protoiam 是身份认证源头,不反向依赖业务服务)
  • 未来 P3 转 gRPC 时,消费 google/protobuf/timestamp.proto(时间字段标准化)

3.2 我暴露的契约

当前 REST API已实现iam.controller.tsrbac.controller.ts

Method Path 权限 说明
POST /iam/register 公开 注册(自动分配 teacher 角色)
POST /iam/login 公开 登录,返回 accessToken + refreshToken
POST /iam/refresh 公开 刷新令牌
GET /iam/me IAM_USER_READ 当前用户信息(读 x-user-id 头)
GET /iam/viewports IAM_USER_READ 当前用户视口L1 导航,按权限过滤)
GET /iam/permissions/effective IAM_USER_READ 当前用户有效权限(多角色去重)
GET /iam/roles IAM_ROLE_MANAGE 所有角色列表(管理端)
GET /iam/permissions IAM_ROLE_MANAGE 所有权限点列表(管理端)

健康检查(health.controller.ts

Method Path 鉴权 用途
GET /healthz liveness进程存活
GET /readyz readiness校验 DB SELECT 1,失败 503
GET /metrics Prometheus 指标抓取(main.ts 第 23-26 行注册)

Proto 契约(iam.proto

package next_edu_cloud.iam.v1;
service IamService {
  rpc Register(RegisterRequest) returns (AuthResponse);
  rpc Login(LoginRequest) returns (AuthResponse);
  rpc RefreshToken(RefreshTokenRequest) returns (TokenPair);
  rpc GetUserInfo(GetUserInfoRequest) returns (UserInfo);
}

缺口proto 仅定义 4 个 RPCREST 已实现的 viewportspermissions/effectiverolespermissions 未在 proto 中体现。P2 阶段 2 设计文档需补齐 GetViewportsGetEffectivePermissionsListRolesListPermissionsLogoutGetPublicKeyRS256 公钥暴露端点)等 RPC。

我发布的领域事件P2 待实现 Outbox

事件 触发时机 Topic遵循 004 §7.2 消费者
UserRegistered 注册成功 edu.identity.user.created core-edu初始化默认班级关联、msg发欢迎通知
UserUpdated 用户信息变更 edu.identity.user.updated core-edu、msg
UserDeleted / UserDisabled 用户注销/禁用 edu.identity.user.deleted core-edu、msg清理关联
UserRoleChanged 角色绑定变更 edu.identity.user.role_changed 自身缓存失效、msg审计

Topic 命名遵循 004 §7.2edu.identity.user.created / edu.identity.user.updated

我消费的事件

  • 当前:
  • 未来不主动消费业务事件iam 是权限中枢,不订阅其他领域事件)

3.3 错误码前缀

错误码 HTTP 触发条件
IAM_VALIDATION_ERROR 400 Zod 校验失败 / 字段非法
IAM_UNAUTHORIZED 401 未登录、密码错误、token 失效
IAM_PERMISSION_DENIED 403 缺少所需权限点
IAM_NOT_FOUND 404 用户/角色/权限不存在
IAM_CONFLICT 409 邮箱已注册、角色名重复
IAM_BUSINESS_ERROR 422 业务规则违反(如账号禁用)
IAM_DATABASE_ERROR 500 Drizzle 操作失败
IAM_INTERNAL_ERROR 500 未预期异常

全局错误格式{ success: false, error: { code, message, details?, traceId } },由 GlobalErrorFilter 统一兜底traceId 从 x-request-id 头注入。

4. 我的技术栈

维度 选型 版本 备注
语言 TypeScriptESM 5.6+ tsconfig.jsonNodeNext + incremental: false(避免 nest watch 不 emit
框架 NestJS 10.4+ 装饰器 + DI@nestjs/platform-express
ORM Drizzle ORMmysql2 0.31+ getDb() 单例池,无 typeorm DataSource 依赖
数据库 MySQL 8 独占库 iam_db,连接池 connectionLimit=10
缓存 RedisP2 待接) 7 权限缓存 TTL 5min + refresh token 黑名单
密码哈希 bcrypt 5.1+ cost ≥ 12已对齐 project_rules §4
JWT jsonwebtoken 9.0+ 当前 HS256P2 必须切 RS256
日志 pino 9.4+ 结构化 JSONservice: iam base 字段
指标 prom-client 15.1+ iam_requests_total + iam_request_duration_seconds
链路 OpenTelemetry SDK 0.53+ auto-instrumentations + OTLP HTTP exporter
输入校验 Zod 3.23+ Controller 层 schema.parse(body)
测试 Vitest 2.1+ 当前缺失P2 必须补齐
容器 Dockerfile 多阶段 node:20-alpine builder + runtimeEXPOSE 3002

5. 我的阶段归属

  • 阶段P2 身份M4-M6

  • 当前阶段目标pending-features §P2

    1. 已实现骨架:注册/登录/刷新/me/viewports/effective permissions/roles/permissions 列表
    2. RS256 非对称签名(当前仍是 HS256env.JWT_SECRET 单密钥)
    3. refresh_token 轮换 + Redis 黑名单失效(当前只存 hash未实现撤销检查
    4. 权限缓存getEffectivePermissions 结果 Redis 缓存 TTL 5min角色变更主动失效
    5. Outbox 事件发布UserRegistered / UserUpdated / UserRoleChanged
    6. 2FApending-features §P2 提及但优先级低)
    7. 补全数据表parent_student_relationsclass_subject_teacherspending-features §P2 schema 清单)
    8. gRPC 化iam.proto 已定义 4 RPC但服务尚未实现 gRPC server
    9. 测试覆盖classes 有 test/unit/classes.service.test.tsiam 无任何测试)
    10. RBAC CRUD 完整化(当前只有读,缺角色/权限/视口的增删改)
  • 依赖的上游阶段产出

    • P1 api-gateway已注册 /iam/* 路由,已透传 x-user-id/x-user-roles 头)
    • P1 classes 黄金模板(横切关注点模板:错误处理/可观测/健康检查/优雅关闭)
    • P1 shared-protoiam.proto 已定义基础 4 RPC
  • P2 退出标准pending-features §P2 + 004 §14.2

    • 教师登录 → 获取 JWTRS256→ 访问 teacher-portal → 侧边栏按 viewports.L1 渲染 → 看到空白 Dashboard
    • 打 tag v0.2.0-p2

6. 我需要对齐的黄金模板项(对照 classes 服务)

参照 classes 黄金模板 全部源码与 known-issues §2.2 classes 经验。

对齐项 classes 状态 iam 当前状态 iam 待补齐
权限装饰器 @RequirePermission 每个 Handler 都有 ⚠️ Handler 有装饰器,但 PermissionGuard 用本地硬编码 ROLE_PERMISSIONS mapadmin/teacher未走 DB 查询 P2 改为 DB 驱动Guard 调用 IamService.getEffectivePermissions(userId)(从 Redis 缓存读取)
错误码前缀统一 CLASSES_* IAM_*
loggerpino shared/observability/logger.ts 同模板
metricsprom-client *_requests_total + *_request_duration_seconds iam_requests_total + iam_request_duration_seconds
tracerOTel auto-instrumentations + OTLP 同模板
/healthz + /readyz Drizzle SELECT 1 同模板
优雅关闭SIGTERM LifecycleService 关闭 DB 池 同模板 P2 接入 Redis/Kafka 后需补关闭顺序HTTP → Kafka → Redis → DB
测试覆盖率 ≥ 80% test/unit/classes.service.test.ts 完全缺失 P2 必须补齐:test/unit/iam.service.test.tstest/unit/iam.repository.test.tstest/unit/permission.guard.test.ts
Dockerfile 多阶段构建 builder + runtime 同模板
Zod 输入验证 Controller 层 schema.parse(body) 同模板
GlobalErrorFilter 注册到 main.ts 同模板
ESM .js 后缀 相对 import 带 .js 同模板
tsconfig.json incremental: false
AppModule 显式 imports HealthModule
Drizzle getDb() 单例
Controller 读 x-user-id

6.1 当前 iam 与 classes 黄金模板的差异点(需在阶段 2 设计文档中明确处理)

  1. PermissionGuard 数据源classes 用本地 ROLE_PERMISSIONS map够用因为 classes 权限点固定iam 必须改为 DB 驱动——因为 iam 自身就是权限中枢,权限点/RBAC 是动态的,硬编码会导致角色变更不生效。
  2. AuthMiddleware 未注册app.module.ts 只注册了 PermissionGuard 作为 APP_GUARDAuthMiddleware 未在 AppModule.configure() 中消费。Controller 直接从 req.headers['x-user-id'] 读取,这是 known-issues §2.3 记录的决策。P2 设计文档需明确:要么注册 AuthMiddleware要么继续走 header 直读(当前选择后者,与 Gateway 透传策略一致)。
  3. JWT 签名算法classes 不签发 JWT只校验iam 是唯一签发方,必须切 RS256 并暴露公钥端点 /iam/.well-known/jwks.json/iam/public-key
  4. Outbox 模式classes 无 OutboxP3 才引入iam P2 需要引入 Outbox 发布用户事件——需要 coord 在 shared-ts 中提供 Outbox 工具或 iam 自建(参照 core-edu P3 设计)。

7. 服务审计表ai02 自检)

依照 ai-allocation §10 模板,对当前 iam 已实现代码进行审计。

服务 权限装饰器 错误码前缀 logger metrics tracer /healthz /readyz 优雅关闭 测试覆盖率 Dockerfile
iam ⚠️ 0%

说明

  • 权限装饰器 ⚠️:装饰器已挂载到每个 Handler但 Guard 用本地硬编码 ROLE_PERMISSIONS,未走 DB与 iam 作为权限中枢的职责矛盾)
  • 测试覆盖率 0%services/iam/ 下无 test/ 目录Vitest 配置缺失classes 有 vitest.config.ts + test/unit/

8. 待 coord 交叉审查的关键决策点

以下决策需在阶段 2 设计文档中明确,并在 coord 交叉审查时确认:

  1. RS256 密钥管理P2 用本地文件 /opt/edu/keys/iam-private.pem + /opt/edu/keys/iam-public.pem,还是引入 VaultP6 才落地)?建议 P2 用本地文件 + 环境变量 IAM_PRIVATE_KEY_PATHP6 迁 Vault。
  2. 公钥暴露端点:用 GET /iam/.well-known/jwks.jsonJWK Set 标准,支持密钥轮换)还是 GET /iam/public-key(简单 PEM建议前者为未来密钥轮换留余地。
  3. 权限缓存失效策略:角色变更时主动 DEL iam:perms:{userId},还是发 UserRoleChanged 事件让消费者自行失效?建议两者都做:本地服务内主动 DEL同步事件供其他服务感知。
  4. Outbox 实现位置iam 自建 iam_outbox 表 + relay worker还是复用 shared-ts 提供的通用 Outbox 工具?需 coord 确认 shared-ts 是否在 P2 提供 Outbox 工具。若未提供iam 自建轻量 Outbox参照 core-edu P3 设计,但要先于 core-edu 落地)。
  5. gRPC 与 REST 并存策略P2 是否同步实现 gRPC server还是 P2 仅 RESTP3 再补 gRPC建议 P2 仅 REST保证退出标准达成gRPC server 在 P3 随 core-edu 一起引入(此时 shared-ts 也有 gRPC 工具沉淀)。
  6. DataScope 枚举对齐:当前 schema 用 self/class/grade/school/district/all004 §5.3 用 L0-SELF ~ L5-ALL。建议保持 schema 字符串枚举DB 友好),在 API/proto 层用 L0~L5 数值枚举映射,业务代码用语义常量。
  7. parent_student_relations 表归属pending-features §P2 schema 清单把此表放在 iam但 004 §5.4 提到家长场景域由 parent-bff 聚合。需确认:此表是 iam 管理(家长-学生关系是身份关系),还是 core-edu 管理(教学组织关系)?建议归 iam(身份关系优先于教学组织)。
  8. class_subject_teachers 表归属:同上,此表横跨 iam教师身份与 core-edu班级/学科。pending-features §P2 放 iam但 core-edu 的 classes 模块也需要。建议归 core-edu(班级-学科-教师是教学组织数据iam 只存 userId 与角色,不存具体任教关系。

AI Agent: ai02 (iam-module) Branch: main单仓库并行模式见 ai-allocation §9.1 Coordinator: coord-ai