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
This commit is contained in:
SpecialX
2026-07-14 22:59:28 +08:00
parent d11441c9a8
commit ad39a3bb0f
9 changed files with 650 additions and 40 deletions

View File

@@ -0,0 +1,283 @@
# 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 联调。**