跳到主要内容

实体 Webhook(Entity Webhooks)

网关前缀:${API_BASE}/metadata/entity-webhooks/...

元数据实体 Webhook 用于在实体发生变更(insert/update/upsert/delete)时,将变更事件投递到外部 HTTP 端点。它基于数据服务的 mutation outbox 表实现可靠投递与重试。

后端入口:

  • 配置管理:EntityWebhookConfigController/api/v1/entity-webhooks
  • 投递执行:数据服务中的 WebhookOutboxDispatcher(定时任务)

配置对象结构(WebhookConfigDto)

请求/响应体字段(见 WebhookConfigDtoEntityWebhookConfig):

  • id:配置 ID(返回时)
  • tenantId:租户 ID(由服务端写入)
  • entityName:实体名(如 ordersfin_invoice
  • events:触发事件类型,逗号分隔字符串,例如 "INSERT,UPDATE,DELETE"
  • webhookUrl:目标 Webhook URL(必须为合法 URL)
  • httpMethod:HTTP 方法(默认 POST
  • headersJson:额外请求头配置,JSON 字符串,内容为 Record<string,string>
  • timeoutMs:请求超时时间(毫秒,默认 3000
  • maxRetries:最大重试次数(默认 3
  • backoffInitialSeconds:初始重试间隔秒数(默认 5
  • backoffFactor:指数退避因子(默认 2.0
  • enabled:是否启用(默认 true
  • secret:签名密钥(可选,用于对 payload 做 HMAC 签名)
  • createdAt / updatedAt:创建/更新时间
  • createdBy / updatedBy:审计字段

前端管理页面对应组件:EntityWebhookResource,提供列表、新建、编辑表单,包含校验逻辑(事件类型、URL 格式、headersJson JSON 合法性等)。

配置管理 API

基础路径:/metadata/entity-webhooks(网关映射到 /api/v1/entity-webhooks)。

权限:tenant:admin / admin

请求头:

  • Authorization: Bearer <token>
  • X-Tenant-Id: <tenantId>(由网关或调用方注入)

创建配置

  • POST /metadata/entity-webhooks

示例:

curl -X POST "${API_BASE}/metadata/entity-webhooks" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Id: tenant-abc123" \
-H "Content-Type: application/json" \
-d '{
"entityName": "orders",
"events": "INSERT,UPDATE",
"webhookUrl": "https://example.com/hooks/order-events",
"httpMethod": "POST",
"headersJson": "{\"X-Source\":\"aidaas\"}",
"timeoutMs": 5000,
"maxRetries": 5,
"backoffInitialSeconds": 5,
"backoffFactor": 2.0,
"enabled": true,
"secret": "my-webhook-secret"
}'

注意:

  • events 支持的取值:INSERTUPDATEUPSERTDELETE,可组合使用。
  • headersJson 必须是合法 JSON 且解析为对象,否则会返回错误 WEBHOOK_HEADERS_INVALID
  • 未显式提供的 timeout/重试相关字段会在服务端写入默认值。

更新配置

  • PUT /metadata/entity-webhooks/{id}

示例:

curl -X PUT "${API_BASE}/metadata/entity-webhooks/<id>" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Id: tenant-abc123" \
-H "Content-Type: application/json" \
-d '{
"entityName": "orders",
"events": "INSERT,UPDATE,DELETE",
"enabled": true
}'

说明:

  • 更新会覆盖对应字段的值;未出现的字段保持不变。
  • 若需要关闭配置,可将 enabled 置为 false

删除配置

  • DELETE /metadata/entity-webhooks/{id}

说明:

  • 内部采用软删除(deleted = true),对已有 outbox 记录不做回收,只影响后续读取与投递。

按 ID 查询

  • GET /metadata/entity-webhooks/{id}

返回单个 WebhookConfigDto,若不存在则返回错误码 WEBHOOK_CONFIG_NOT_FOUND

分页查询

  • GET /metadata/entity-webhooks?page=1&pageSize=20&entityName=orders&sortBy=createdAt&sortDirection=DESC

请求参数:

  • page:页号,从 1 开始,默认 1
  • pageSize/size:每页条数,默认 20
  • entityName:按实体名过滤(可选)
  • sortBy:排序字段,默认 createdAt
  • sortDirection:排序方向,默认 DESC

响应为 PageResponse<WebhookConfigDto>

按实体查询(内部接口)

  • GET /metadata/entity-webhooks/internal/by-entity/{entityName}

说明:

  • 供数据服务等内部组件按实体拉取所有 Webhook 配置。
  • 需要权限 tenant:admin / admin / internal

变更事件与 Outbox 模型

实体变更触发时,数据服务会向租户库表 mutation_webhook_outbox 写入一条记录:

  • 核心字段:
    • id:outbox 记录 ID
    • tenant_id:租户 ID
    • entity_name:实体名
    • operation:操作类型(INSERT/UPDATE/UPSERT/DELETE
    • request_id:请求 ID,用于幂等与排查
    • webhook_config_id:引用的 Webhook 配置 ID
    • payload_json:变更 payload 的 JSON
    • status:状态(PENDING / RETRYING / DELIVERED / DEAD
    • retry_count / max_retries:重试计数和上限
    • next_retry_at:下次尝试时间
    • last_error:最近一次错误信息

调度器 WebhookOutboxDispatcher 会定期扫描各租户库中的 outbox 表,根据状态与 next_retry_at 选择待投递记录,并按 Webhook 配置执行 HTTP 请求。

投递与重试机制

投递逻辑(见 WebhookOutboxDispatcher.sendOnce):

  • 解析 Webhook 配置 WebhookConfigDto
    • webhookUrl:目标 URL,缺失或为空时抛出 WEBHOOK_URL_MISSING,记录直接标记为 DEAD
    • httpMethod:解析为 HttpMethod,非法取值回退为 POST
    • headersJson:若非空则反序列化为 Map<String,String> 并写入请求头,解析失败抛出 WEBHOOK_HEADERS_INVALID
    • timeoutMs:在 HTTP 客户端层面配置(由外层 RestTemplate 配置决定)
    • secret:用于签名,见下一节
  • 固定请求头:
    • Content-Type: application/json
    • X-AIDAAS-Request-Id: <request_id>
    • X-AIDAAS-Timestamp: <unix_timestamp_seconds>
    • 若配置了 secret,再附加 X-AIDAAS-Signature
  • 请求体:payload_json 字段的 JSON 文本

重试与状态机:

  • 若 Webhook 配置缺失或 enabled = false
    • 对应 outbox 记录标记为 DEAD,不再重试。
  • 若请求返回非 2xx 状态码:
    • 4084295xx:视为可重试错误,抛出 WEBHOOK_DELIVERY_RETRYABLE
    • 其他状态码:视为不可重试错误,抛出 WEBHOOK_DELIVERY_NON_RETRYABLE
  • 异常处理:
    • 不可重试错误或签名/配置错误:记录状态置为 DEAD,写入 last_error
    • 可重试错误:
      • retry_count + 1 >= max_retries:状态置为 DEAD
      • 否则状态置为 RETRYING,按退避策略更新 next_retry_at

退避策略:

  • 使用 Webhook 配置中的:

    • backoffInitialSeconds:基础秒数,默认 5,小于等于 0 时回退到 5
    • backoffFactor:倍率,默认 2.0,小于 1.0 时回退到 1.5
  • 计算:

    backoffSeconds = base * factor^(currentRetry)
  • 最大退避时长限定为 3600 秒(1 小时)。

签名机制(X-AIDAAS-Signature)

若 Webhook 配置中设置了 secret,则每次投递会在请求头中加入签名:

  • 请求头:

    • X-AIDAAS-Timestamp: <timestamp>(秒级 Unix 时间戳)
    • X-AIDAAS-Signature: v1=<base64(hmac_sha256(secret, "<timestamp>.<payload>"))>
  • 签名算法(见 signPayload):

    data = "<timestamp>.<payloadJson>"
    mac = HMAC-SHA256(secret, data)
    signature = "v1=" + Base64(mac)

验证建议(下游服务):

  • 从请求头读取:
    • X-AIDAAS-Timestamp
    • X-AIDAAS-Signature
  • 检查时间戳是否在合理窗口内(防止重放)
  • 使用共享的 secret 按上述规则计算签名并比对

下游接收端的示例(伪代码)

示例:使用 Node.js / TypeScript 验证签名:

import crypto from "node:crypto";

function verifyWebhook(req: any, secret: string): boolean {
const timestamp = req.headers["x-aidaas-timestamp"];
const signature = req.headers["x-aidaas-signature"]; // 形如 v1=...
if (!timestamp || !signature) return false;

const payload = JSON.stringify(req.body ?? {});
const data = `${timestamp}.${payload}`;
const mac = crypto.createHmac("sha256", secret).update(data, "utf8").digest("base64");
const expected = `v1=${mac}`;

return signature === expected;
}

使用建议

  • 为重要实体(如订单、发票、风险评估结果)配置专用 Webhook,将核心变更推送到风控/CRM/审计系统。
  • 推荐为每类下游系统单独创建 Webhook 配置,便于独立启停与调试。
  • 若对接方需要强安全性,务必配置 secret 并在下游进行签名验证与时间戳检查。
  • 建议接收端实现幂等逻辑,可复用 X-AIDAAS-Request-Id 作为幂等键。