## ADDED Requirements ### Requirement: 统一入口 mjava-com SHALL 提供单一网关路由 `POST /api/com/{vendor}/{action}`,每个 action MUST 对应基座某个 Client 方法。 #### Scenario: vendor + action 路由 - **WHEN** 收到 `POST /api/com/dingtalk/user.get` - **THEN** 根据 `ActionRegistry` 查找 `dingtalk.user.get` 注册的 handler - **AND** 调用基座 `DDClient_Contacts.getUser_v2(...)` - **AND** 请求体字段名必须与 Client 方法参数一致 ### Requirement: 签名校验 网关 SHALL 在 Auth 拦截阶段校验调用方签名。校验逻辑 MUST 复用基座 `add-request-auth-replay-guard` 提供的 `UtilSignature` 与 `NonceCache`,不重新实现 HMAC。 #### Scenario: 四层校验顺序 - **WHEN** 请求到达 - **THEN** 依次执行:时间戳窗口 → callerId 注册表查 secret → HMAC-SHA256 比对 → 调用方 enabled - **AND** 任一失败返回 `401 { code: "AUTH_FAILED" }` - **AND** 响应 message 不暴露具体失败环节 ### Requirement: 动作白名单 每个开放的 action MUST 通过三层校验:ActionRegistry 代码注册 + yml `com.actions.enabled` 启用 + 调用方 `allowedActions` 包含。 #### Scenario: 代码未注册 - **WHEN** 请求 action 未在 `ActionRegistry` 注册 - **THEN** 返回 `404 { code: "ACTION_NOT_FOUND" }` #### Scenario: yml 未启用 - **WHEN** `com.actions.enabled` 不含该 action - **THEN** 返回 `403 { code: "ACTION_FORBIDDEN" }` #### Scenario: 调用方无权限 - **WHEN** callerId 的 `allowedActions` 不含该 action - **THEN** 返回 `403 { code: "ACTION_FORBIDDEN" }` ### Requirement: 响应格式 网关 SHALL 统一返回 `McR`。第三方原始响应 MUST 放在 `data` 字段保留原结构。 #### Scenario: 成功透传 - **WHEN** 基座 Client 返回成功 - **THEN** `McR.success(result)`,`data = 原始 Map 或 DTO` #### Scenario: 第三方失败映射 - **WHEN** 基座 Client 抛 McException(vendor 返回错误) - **THEN** `McR.fail(code, message)`,`data` 含 vendor 原始错误信息 ### Requirement: 限流 网关 SHALL 按 callerId 做本地限流,阈值取自宜搭权限表单 `rateLimit` 字段。 #### Scenario: 超限拒绝 - **WHEN** 同一 callerId 请求速率超过配置 QPS - **THEN** 返回 `429 { code: "RATE_LIMITED", retryAfter: 1 }` ### Requirement: 审计扩展 网关审计日志 MUST 在 `mjava-baseline §3.5` 字段基础上追加 callerId / vendorAction / signatureValid。 #### Scenario: 每请求按 caller 分片 - **WHEN** 请求进入 - **THEN** 日志落入 `./log/{日期}/com-{callerId}.log` - **AND** MDC 含 `callerId` 与 `vendorAction`