design.md 3.2 KB

架构要点

请求流程

External System → POST /api/com/{vendor}/{action}
                  Header: X-Caller-Id: {callerId}
                          X-Signature: {HMAC-SHA256(callerSecret, body+timestamp)}
                          X-Timestamp: {unix ms}
                  Body: { ...params aligned with base Client method args }

     ↓
CallerAuthInterceptor
  ├── 校验 X-Timestamp 5 分钟内
  ├── CallerRegistryService.get(callerId) → callerSecret
  ├── 计算 HMAC 对比
  └── 通过 → 继续;失败 → 401
     ↓
GatewayController
  ├── 查 ActionWhitelist:{vendor}.{action} 是否开放
  ├── 用反射或注册的 handler 调对应基座 Client 方法
  └── 包 McR 返回

调用方注册(宜搭权限表单)

字段约定:

宜搭字段 含义
textField_callerId 调用方唯一 ID(签发时生成,如 caller-YDCBC-001
textField_callerSecret HMAC 密钥(生成时返回给调用方,服务端也存,需加密字段)
textField_callerName 可读名称(便于审计)
textareaField_allowedActions JSON 数组:["dingtalk.user.get", "aliwork.form.save"],精确白名单
numberField_rateLimit 每秒限流上限
radioField_enabled on / off
dateField_expireAt 密钥过期时间(可选)

Vendor Action 路由

为避免反射开销和控制边界,每个开放的 action 在代码里显式注册:

@Component
public class DingtalkActionRegistry {
    @PostConstruct
    public void register(ActionRegistry reg) {
        reg.register("dingtalk.user.get", (ctx, body) -> {
            DDConf conf = resolveConf(ctx);  // 可选:按 caller 配置不同 dingtalk 应用
            return ddClient.getUserDetail(conf, body.getString("userid"));
        });
    }
}

application.yml 只维护"哪些 action 默认开放"的白名单;细粒度调用方权限通过宜搭权限表单的 allowedActions 限制。

限流

  • 本地 Guava RateLimiter 按 callerId 维度
  • 单实例场景足够;若未来多实例部署再评估 Redis / 分布式限流
  • 超限返回 429 { code: "RATE_LIMITED" }

审计

继承 mjava-baseline §3.5 审计规范,追加字段:

字段 说明
callerId 调用方 ID
vendorAction {vendor}.{action}
signatureValid 签名校验结果

日志输出 ./log/{日期}/com-{callerId}.log(按调用方分片)。

安全要点

  • callerSecret 存宜搭"加密字段"类型;内存访问后不得回传前端或记录到日志
  • HMAC 防重放:X-Timestamp 5 分钟外拒绝 + 可选 Nonce(Phase 1 不做,5 分钟窗口足够内部场景)
  • 每次调用尝试失败都打 WARN,连续 5 次同 callerId 失败触发 ERROR 告警(ExceptionNotice 已有通道)

Non-Goals(设计边界)

  • 不做 REST 规范化(不强制 HATEOAS / JSON:API)
  • 不做 WebSocket / 长连接
  • 不做异步任务回调(请求同步完成,调用方需要异步能力自行轮询或用 mjava-pro)
  • 不做请求转发到非 mjava vendor(本质是 mjava Client 方法的 HTTP 门面)