Files
Edu/services/iam/docs/nextstep-v2.md
SpecialX ad39a3bb0f feat(iam): v2 用户管理 RPC + F12 httpOnly Cookie
admin-portal §2.3 P1 阻塞项补齐:CreateUser/UpdateUser/DeleteUser 3 RPC

iam.repository/service/grpc.controller 实现 3 用户管理方法(含 bcrypt + 审计)

iam.controller 新增 POST /v1/iam/users + DELETE /v1/iam/users/:id(@RequirePermission(IAM_USER_MANAGE))

iam.dto 新增 createUserSchema Zod 校验

F12 httpOnly Cookie:refresh_token 改为 httpOnly+Secure+SameSite=Strict Cookie 下发

extractRefreshToken 优先读 cookie 回退 body + buildRefreshTokenCookie/buildClearCookie
2026-07-14 22:59:28 +08:00

284 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# iam 下一步工作与上下游依赖Next Steps v2
> 模块iam身份与访问管理服务端口 3002 HTTP + 50052 gRPC
> 更新日期2026-07-14
> 关联文档:
>
> - [nextstep.md](./nextstep.md)v1 基线)
> - [02-architecture-design.md](./02-architecture-design.md)
> - [iam_contract.md](../../../docs/architecture/issues/contracts/iam_contract.md)
---
## 1. v2 迭代已完成工作
### 1.1 admin-portal §2.3 P1 阻塞项补齐(用户管理 RPC
| # | 工作项 | 状态 |
| --- | ----------------------------------------------------------------------------------------------------------------------------- | ---- |
| 1 | `iam.proto` 新增 `CreateUserRequest` / `UpdateUserRequest` / `DeleteUserRequest` / `DeleteUserResponse` 4 个 message | ✅ |
| 2 | `iam.proto` `service IamService` 声明 `CreateUser` / `UpdateUser` / `DeleteUser` 3 个 RPC | ✅ |
| 3 | `iam.repository.ts` 实现 `createUser` / `updateUser` / `deleteUser` 数据访问 | ✅ |
| 4 | `iam.service.ts` 实现 `createUser` / `updateUser` / `deleteUser` 业务逻辑(含 bcrypt + 审计) | ✅ |
| 5 | `iam.grpc.controller.ts` 新增 3 个 `@GrpcMethod` 实现 | ✅ |
| 6 | `iam.controller.ts` 新增 `POST /v1/iam/users` + `DELETE /v1/iam/users/:id` REST 端点(`@RequirePermission(IAM_USER_MANAGE)` | ✅ |
| 7 | `iam.dto.ts` 新增 `createUserSchema` Zod 校验 | ✅ |
### 1.2 F12 httpOnly Cookie 模式refresh_token 安全增强)
依据架构裁决 F12refresh_token 不再通过响应体明文返回,改为 httpOnly + Secure + SameSite=Strict Cookie 下发。
| # | 工作项 | 状态 |
| --- | --------------------------------------------------------------------------------- | ---- |
| 1 | `iam.controller.ts` 新增 `extractRefreshToken(req)`:优先读 cookie回退 body | ✅ |
| 2 | `iam.controller.ts` 新增 `buildRefreshTokenCookie(token, maxAge)` 构造 Set-Cookie | ✅ |
| 3 | `iam.controller.ts` 新增 `buildClearCookie()` 构造清除 Cookie | ✅ |
| 4 | `POST /v1/iam/register` 下发 Set-Cookie | ✅ |
| 5 | `POST /v1/iam/login` 下发 Set-Cookie | ✅ |
| 6 | `POST /v1/iam/refresh` 优先从 cookie 读取 + 轮换 Cookie | ✅ |
| 7 | `POST /v1/iam/logout` 清除 CookieMax-Age=0 | ✅ |
Cookie 属性:`HttpOnly; SameSite=Strict; Max-Age=604800; Path=/; Secure`
### 1.3 Docker 构建修复bcrypt 原生模块)
| # | 工作项 | 状态 |
| --- | --------------------------------------------------------------------------------------------- | ---- |
| 1 | Dockerfile 改用 `pnpm install --no-frozen-lockfile --filter @edu/iam-service...` 过滤安装策略 | ✅ |
| 2 | 不再使用 `--ignore-scripts`,让 bcrypt install script 正常执行原生编译 | ✅ |
| 3 | 仅安装 iam + workspace 依赖,避免 arch-scan 的 better-sqlite3 原生编译 | ✅ |
| 4 | 镜像 `edu/iam:test` 构建成功660MBbcrypt `.node` 文件存在 | ✅ |
### 1.4 本地 Docker 端到端验证2026-07-14
测试环境:`edu-iam-test` 容器NODE_ENV=production, DEV_MODE=true接入 `edu-full_default` 网络,直连 edu-mysql / edu-redis / edu-kafka。
JWT RS256 密钥对挂载在 `edu-iam-keys` volume`/app/keys/private.pem` + `/app/keys/public.pem`)。
| 验证项 | 状态 | 说明 |
| ---------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------- |
| `GET /healthz` | ✅ | 200 `{"status":"ok","service":"iam"}` |
| `GET /readyz` | ✅ | 2005 项依赖全 okdatabase / redis / kafka / jwks / grpc |
| `GET /v1/iam/.well-known/jwks.json` | ✅ | 200返回 RSA 公钥kid: iam-rs256-v1, alg: RS256, kty: RSA |
| `POST /v1/iam/register` | ✅ | 201 Created + `Set-Cookie: refresh_token=...; HttpOnly; SameSite=Strict; Max-Age=604800; Path=/; Secure` |
| `POST /v1/iam/login` | ✅ | 201 Created + Set-Cookie 下发 |
| `POST /v1/iam/refresh`cookie 优先) | ✅ | 201 Created + 轮换 Set-Cookie新 jti |
| `POST /v1/iam/logout` | ✅ | 201 Created + `Set-Cookie: refresh_token=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Secure`(清除) |
| `POST /v1/iam/users`createUser | ✅ | 201 Created + 返回新用户(含 dataScope |
| `DELETE /v1/iam/users/:id`deleteUser | ✅ | 200 OK + `{"success":true,"data":{"success":true}}` |
---
## 2. 上游依赖iam 依赖谁)
iam 作为身份基础设施服务,运行时依赖以下组件(与 v1 一致,无新增):
### 2.1 基础设施
| 组件 | 端点 | 用途 | 状态 |
| -------------- | ----------------------------------------------------------------------- | ---------------------------------------- | ---- |
| MySQL | `mysql://edu:changeme@edu-mysql:3306/next_edu_cloud` | 用户/角色/权限/视口/审计/Outbox 表持久化 | ✅ |
| Redis | `redis://edu-redis:6379` | 权限缓存TTL 5min+ token 黑名单 | ✅ |
| Kafka | `kafka:29092` | Outbox 事件投递 | ✅ |
| JWT RS256 密钥 | `/app/keys/private.pem` + `/app/keys/public.pem`edu-iam-keys volume | RS256 签发 + JWKS 暴露公钥 | ✅ |
### 2.2 共享包(协调 AI 维护)
| 包 | 依赖内容 | 状态 |
| ------------ | ---------------------------------------------------------------- | ---- |
| shared-proto | `packages/shared-proto/proto/iam.proto`15 RPC + 全部 message | ✅ |
| shared-ts | `@edu/shared-ts/outbox` OutboxModule + OutboxService | ✅ |
---
## 3. 下游依赖(谁依赖 iam
### 3.1 api-gatewayai01 负责)— P0
| # | 依赖项 | 用途 | 状态 |
| --- | ----------------------------------- | ---------------------------------------------------- | --------- |
| 1 | `GET /v1/iam/.well-known/jwks.json` | RS256 公钥集JWKSTTL 5min 缓存 | ✅ 已就绪 |
| 2 | RS256 JWT 签发 | api-gateway 用 JWKS 公钥校验 access_token | ✅ 已就绪 |
| 3 | `GET /healthz` 端点 | /readyz 下游健康检查 | ✅ 已就绪 |
| 4 | JWT claims 含 role/data_scope | api-gateway 注入 x-user-roles / x-user-data-scope 头 | ✅ 已就绪 |
### 3.2 push-gatewayai09 负责)— P0
| # | 依赖项 | 用途 | 状态 |
| --- | ------------------------------------ | ----------------------------------------- | --------- |
| 1 | `GET /v1/iam/.well-known/jwks.json` | WebSocket `/ws` 连接时校验客户端 JWT 签名 | ✅ 已就绪 |
| 2 | shared-go/jwks Fetcher 每 5 分钟刷新 | JWKS 缓存刷新 | ✅ 已就绪 |
### 3.3 teacher-bffai03 负责)— P0
| # | 依赖项 | 用途 | 状态 |
| --- | -------------------------------------------------------- | ----------------------------- | --------- |
| 1 | gRPC `GetUserInfo(userId)` :50052 | `currentUser`/`me` 查询 | ✅ 已就绪 |
| 2 | gRPC `BatchGetUsers(userIds)` :50052 | `adminUsers` 查询 | ✅ 已就绪 |
| 3 | gRPC `GetEffectivePermissions(userId)` :50052 | 用户有效权限列表 | ✅ 已就绪 |
| 4 | gRPC `GetEffectiveDataScope(userId)` :50052 | 用户数据范围 | ✅ 已就绪 |
| 5 | gRPC `GetViewports(userId)` :50052 | `adminViewports` 查询 | ✅ 已就绪 |
| 6 | gRPC `GetPublicKey()` :50052 | RS256 公钥 | ✅ 已就绪 |
| 7 | gRPC `GetChildrenByParent(parentId)` :50052 | 家长端学生列表 | ✅ 已就绪 |
| 8 | gRPC `CreateUser` / `UpdateUser` / `DeleteUser` | 用户管理 CRUDadmin-portal | ✅ 已就绪 |
| 9 | REST 全套 RBAC CRUDroles/permissions/viewports/audit | admin-portal 管理 | ✅ 已就绪 |
### 3.4 student-bffai04 负责)— P0
| # | gRPC 方法 | 用途 | 状态 |
| --- | ---------------- | ------------------------- | --------- |
| 1 | `GetUserProfile` | `myProfile` Query | ✅ 已就绪 |
| 2 | `UpdateProfile` | `updateProfile` Mutation | ✅ 已就绪 |
| 3 | `ChangePassword` | `changePassword` Mutation | ✅ 已就绪 |
### 3.5 parent-bffai05 负责)— P0
| # | RPC 方法 | 用途 | 状态 |
| --- | --------------------------------- | ---------------------- | --------- |
| 1 | `getUserInfo(userId)` | 获取家长个人信息 | ✅ 已就绪 |
| 2 | `getChildrenByParent(parentId)` | 获取家长绑定的孩子列表 | ✅ 已就绪 |
| 3 | `getViewports(userId)` | 获取家长可见视口 | ✅ 已就绪 |
| 4 | `getEffectivePermissions(userId)` | 获取家长有效权限 | ✅ 已就绪 |
| 5 | `GET /healthz` 端点 | /readyz 下游健康检查 | ✅ 已就绪 |
### 3.6 admin-portalai16 负责)— P0
| # | 依赖项 | 用途 | 状态 |
| --- | ------------------------------------------------------ | ----------------------------- | --------- |
| 1 | gRPC `CreateUser` / `UpdateUser` / `DeleteUser` | 用户管理 CRUDv2 新增) | ✅ 已就绪 |
| 2 | REST `POST /v1/iam/users` + `DELETE /v1/iam/users/:id` | 用户管理 REST 端点v2 新增) | ✅ 已就绪 |
| 3 | `GET /v1/iam/users` | 用户管理列表(分页 + 搜索) | ✅ 已就绪 |
| 4 | `PATCH /v1/iam/users/:id` | 用户更新 | ✅ 已就绪 |
| 5 | `PATCH /v1/iam/users/:id/status` | 用户状态切换 | ✅ 已就绪 |
| 6 | `POST/PATCH/DELETE /v1/iam/roles` | 角色 CRUD | ✅ 已就绪 |
| 7 | `POST/PATCH/DELETE /v1/iam/permissions` | 权限 CRUD | ✅ 已就绪 |
| 8 | `POST/PATCH/DELETE /v1/iam/viewports` | 视口 CRUD | ✅ 已就绪 |
| 9 | `POST /v1/iam/totp/enable` / `verify` / `disable` | TOTP 2FA 管理 | ✅ 已就绪 |
| 10 | `GET /v1/iam/audit` | 审计日志查询 | ✅ 已就绪 |
### 3.7 teacher-portalai13 负责)— P1
| # | 依赖项 | 用途 | 状态 |
| --- | ----------------------------------- | --------------------------------------------- | --------- |
| 1 | JWT RS256 签发 | 登录后获取 access_token + refresh_token | ✅ 已就绪 |
| 2 | F12 httpOnly Cookie 模式 | refresh_token 通过 Set-Cookie 下发v2 新增) | ✅ 已就绪 |
| 3 | `POST /v1/iam/login` | 教师登录 | ✅ 已就绪 |
| 4 | `POST /v1/iam/refresh` | 刷新 token从 cookie 读取) | ✅ 已就绪 |
| 5 | `POST /v1/iam/logout` | 登出(清除 cookie | ✅ 已就绪 |
| 6 | `GET /v1/iam/me` | 获取当前用户信息 | ✅ 已就绪 |
| 7 | `GET /v1/iam/viewports` | 获取视口配置 | ✅ 已就绪 |
| 8 | `GET /v1/iam/permissions/effective` | 获取有效权限 | ✅ 已就绪 |
### 3.8 student-portalai14 负责)— P1
| # | 依赖项 | 用途 | 状态 |
| --- | ------------------------------ | --------------------------------------------- | --------- |
| 1 | JWT RS256 签发 | 学生登录 | ✅ 已就绪 |
| 2 | F12 httpOnly Cookie 模式 | refresh_token 通过 Set-Cookie 下发v2 新增) | ✅ 已就绪 |
| 3 | `POST /v1/iam/login` | 学生登录 | ✅ 已就绪 |
| 4 | `PATCH /v1/iam/me` | 更新个人资料 | ✅ 已就绪 |
| 5 | `POST /v1/iam/change-password` | 修改密码 | ✅ 已就绪 |
### 3.9 parent-portalai15 负责)— P1
| # | 依赖项 | 用途 | 状态 |
| --- | ------------------------ | --------------------------------------------- | --------- |
| 1 | JWT RS256 签发 | 家长登录 | ✅ 已就绪 |
| 2 | F12 httpOnly Cookie 模式 | refresh_token 通过 Set-Cookie 下发v2 新增) | ✅ 已就绪 |
| 3 | `POST /v1/iam/login` | 家长登录 | ✅ 已就绪 |
| 4 | `GET /v1/iam/children` | 获取绑定的学生列表 | ✅ 已就绪 |
| 5 | `GET /v1/iam/me` | 获取家长个人信息 | ✅ 已就绪 |
### 3.10 同级服务
| # | 模块 | 依赖项 | 用途 | 状态 |
| --- | -------- | ------------------------------------ | ---------------------------------------------- | --------- |
| 1 | ai | gRPC `GetEffectiveDataScope(userId)` | 解析用户可见数据范围DataScope 6 级) | ✅ 已就绪 |
| 2 | core-edu | gRPC `BatchGetUsers` / `GetUserInfo` | DashboardService 学生计数P4+ 未来工作) | ✅ 已就绪 |
| 3 | msg | Kafka 事件 `edu.iam.user.events` | 用户注册/登录/角色变更事件msg 消费方需对齐) | ✅ 已就绪 |
| 4 | data-ana | gRPC `GetEffectiveDataScope(userId)` | 数据分析查询过滤范围 | ✅ 已就绪 |
| 5 | content | 无直接依赖 | — | — |
---
## 4. 需要上下游实现的协调事项
### 4.1 上游Gateway / BFF / Portal需实现
| # | 协调项 | 对端模块 | 说明 |
| --- | ----------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | F12 Cookie 透传:`Cookie` / `Set-Cookie` 头透传 | api-gateway / teacher-bff / student-bff / parent-bff | F12 httpOnly cookie 模式要求 Gateway 和 BFF 透传 `Cookie` 请求头和 `Set-Cookie` 响应头。Gateway 当前已透传 `x-user-*` 头,需确认 `Cookie` 头也在透传白名单中。BFF GraphQL 网关需在 fetch 调用 iam 时带上客户端的 `Cookie` 头。 |
| 2 | Cookie Domain 配置 | api-gateway | 生产环境跨子域共享 cookie 时,需在 iam 环境变量 `REFRESH_TOKEN_COOKIE_DOMAIN` 配置根域(如 `.edu.example.com`)。当前默认不设置 Domain。 |
| 3 | HTTPS 终止点确认 | api-gateway / push-gateway | Cookie 带 `Secure` 属性,浏览器仅在 HTTPS 下发送。开发环境HTTP下浏览器不会自动回传 cookie需用 curl/Postman 手动传递。生产环境 api-gateway 必须做 HTTPS 终止。 |
| 4 | admin-portal 切换到 CreateUser/DeleteUser RPC | admin-portal / teacher-bff | admin-portal 当前可能使用 PATCH /v1/iam/users/:id 更新用户。新增用户需切换到 gRPC `CreateUser`(通过 teacher-bff 聚合)或 REST `POST /v1/iam/users`(通过 gateway 透传)。删除用户使用 `DELETE /v1/iam/users/:id`。 |
| 5 | Kafka 消费者对齐 `edu.iam.user.events` topic | msg / data-ana | iam 发布事件到 `edu.iam.user.events` topic事件类型`UserRegistered` / `UserLoggedIn` / `UserLoggedOut` / `UserRoleChanged` / `UserCreated` / `UserDeleted`。msg 服务的 topic 命名期望为 `edu.identity.user.*`,需协调统一命名。 |
| 6 | Kafka 消费者对齐 `edu.iam.role.events` topic | msg / data-ana | 事件类型:`RoleCreated` / `RoleUpdated` / `RolePermissionsChanged`。下游消费方需订阅此 topic 同步权限缓存。 |
| 7 | Kafka 消费者对齐 `edu.iam.audit.created` topic | data-ana | 审计日志事件data-ana 可消费后入 ClickHouse 供长期分析。 |
| 8 | api-gateway JWKS 缓存刷新策略 | api-gateway | iam JWKS 端点返回 `kid: iam-rs256-v1`,公钥轮换时 kid 会变。api-gateway 需在 JWT 验签失败时触发 JWKS 重新拉取cache miss 回源)。 |
### 4.2 下游(同级服务)需实现
| # | 协调项 | 对端模块 | 说明 |
| --- | ------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | msg 服务消费 `edu.iam.user.events` 创建用户消息视图 | msg | iam 发布 `UserRegistered` / `UserCreated` 事件后msg 服务需消费并为新用户初始化消息收件箱 / 通知偏好。事件 payload 见 `shared/outbox/event-types.ts` |
| 2 | data-ana 消费 `edu.iam.audit.created` 入 ClickHouse | data-ana | iam 审计日志通过 Outbox 投递到 Kafkadata-ana 需消费并写入 ClickHouse `audit_logs` 表供长期留存与查询。 |
| 3 | core-edu DashboardService 调用 iam gRPC `BatchGetUsers` | core-edu | P4+ 工作core-edu 仪表盘需展示学生/教师计数,通过 iam gRPC `BatchGetUsers` 批量查询用户信息。gRPC target: `iam:50052`。 |
| 4 | ai 服务调用 iam gRPC `GetEffectiveDataScope` | ai | ai 服务在生成个性化内容时需解析用户数据范围,调用 `GetEffectiveDataScope(userId)` 返回 `{level, scope_ids, school_id}`。gRPC target: `iam:50052`。 |
### 4.3 事件契约Kafka topic 与 payload
iam 通过 Outbox 模式发布以下事件,下游消费方需按此契约实现 consumer
| Topic | 事件类型 | 触发时机 | 关键字段 |
| ----------------------- | ------------------------ | --------------------------- | ------------------------------------------------------------------------- |
| `edu.iam.user.events` | `UserRegistered` | POST /v1/iam/register | `userId`, `email`, `name`, `timestamp` |
| `edu.iam.user.events` | `UserLoggedIn` | POST /v1/iam/login | `userId`, `email`, `ip`, `userAgent`, `timestamp` |
| `edu.iam.user.events` | `UserLoggedOut` | POST /v1/iam/logout | `userId`, `timestamp` |
| `edu.iam.user.events` | `UserCreated` | POST /v1/iam/users | `userId`, `email`, `name`, `dataScope`, `operatorId`, `timestamp` |
| `edu.iam.user.events` | `UserUpdated` | PATCH /v1/iam/users/:id | `userId`, `changedFields`, `operatorId`, `timestamp` |
| `edu.iam.user.events` | `UserDeleted` | DELETE /v1/iam/users/:id | `userId`, `operatorId`, `timestamp` |
| `edu.iam.user.events` | `UserRoleChanged` | 角色授权/撤销 | `userId`, `roleId`, `permissionId`, `action`, `operatorId`, `timestamp` |
| `edu.iam.role.events` | `RoleCreated` | POST /v1/iam/roles | `roleId`, `name`, `level`, `operatorId`, `timestamp` |
| `edu.iam.role.events` | `RoleUpdated` | PATCH /v1/iam/roles/:id | `roleId`, `changedFields`, `operatorId`, `timestamp` |
| `edu.iam.role.events` | `RolePermissionsChanged` | PATCH roles/:id/permissions | `roleId`, `permissionIds`, `operatorId`, `timestamp` |
| `edu.iam.audit.created` | `AuditLogCreated` | 所有审计操作 | `auditId`, `userId`, `action`, `resource`, `ip`, `userAgent`, `timestamp` |
事件 envelope 格式遵循 `@edu/shared-ts/outbox``{event_id, event_type, aggregate_id, payload, occurred_at, version}`
幂等性:消费方需基于 `event_id` 去重Redis SETNX 或 DB 唯一索引)。
---
## 5. iam 自身待办(非阻塞)
| # | 工作项 | 优先级 | 阻塞条件 |
| --- | ------------------------------------------------------------------------------------------------ | ------ | ------------ |
| 1 | 单元测试 + 集成测试覆盖率 ≥ 80% | P2 | 无 |
| 2 | 生产环境 JWT 密钥轮换流程kid 变更 + JWKS 缓存失效广播) | P3 | 生产部署前 |
| 3 | Kafka topic 创建自动化edu.iam.user.events / role.events / audit.created | P3 | K8s 部署阶段 |
| 4 | 数据库迁移脚本drizzle-kit | P3 | 生产部署前 |
| 5 | UpdateUser REST 端点补齐(当前仅有 PATCH /v1/iam/users/:id无 POST /v1/iam/users/:id 通用更新) | P3 | 无 |
| 6 | Cookie Domain 环境变量配置化(`REFRESH_TOKEN_COOKIE_DOMAIN` | P3 | 生产部署前 |
---
## 6. 联调待办
| # | 联调项 | 对端模块 | 状态 |
| --- | ------------------------------------------------------- | --------------------- | --------- |
| 1 | api-gateway JWKS 真实验签 | api-gateway (ai01) | ✅ 已就绪 |
| 2 | push-gateway JWKS 真实验签 | push-gateway (ai09) | ✅ 已就绪 |
| 3 | teacher-bff gRPC 全量调用(含 CreateUser/DeleteUser | teacher-bff (ai03) | ⏳ 待联调 |
| 4 | student-bff GetUserProfile/UpdateProfile/ChangePassword | student-bff (ai04) | ✅ 已就绪 |
| 5 | parent-bff getUserInfo/getChildrenByParent/getViewports | parent-bff (ai05) | ✅ 已就绪 |
| 6 | admin-portal RBAC CRUD + 用户管理 RPC 全量 | admin-portal (ai16) | ⏳ 待联调 |
| 7 | teacher-portal F12 cookie 模式联调 | teacher-portal (ai13) | ⏳ 待联调 |
| 8 | student-portal F12 cookie 模式联调 | student-portal (ai14) | ⏳ 待联调 |
| 9 | parent-portal F12 cookie 模式联调 | parent-portal (ai15) | ⏳ 待联调 |
| 10 | msg 消费 edu.iam.user.events 事件 | msg | ⏳ 待联调 |
| 11 | data-ana 消费 edu.iam.audit.created 事件 | data-ana | ⏳ 待联调 |
---
**iam 服务 v2 迭代完成。所有 v2 阻塞项admin-portal 用户管理 RPC + F12 cookie 模式 + Docker bcrypt 修复)已就绪,下游模块可开始 v2 联调。**