实体 Webhook(Entity Webhooks)
网关前缀:${API_BASE}/metadata/entity-webhooks/...
元数据实体 Webhook 用于在实体发生变更(insert/update/upsert/delete)时,将变更事件投递到外部 HTTP 端点。它基于数据服务的 mutation outbox 表实现可靠投递与重试。
后端入口:
- 配置管理:
EntityWebhookConfigController(/api/v1/entity-webhooks) - 投递执行:数据服务中的
WebhookOutboxDispatcher(定时任务)
配置对象结构(WebhookConfigDto)
请求/响应体字段(见 WebhookConfigDto 与 EntityWebhookConfig):
id:配置 ID(返回时)tenantId:租户 ID(由服务端写入)entityName:实体名(如orders、fin_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支持的取值:INSERT、UPDATE、UPSERT、DELETE,可组合使用。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开始,默认1pageSize/size:每页条数,默认20entityName:按实体名过滤(可选)sortBy:排序字段,默认createdAtsortDirection:排序方向,默认DESC
响应为 PageResponse<WebhookConfigDto>。
按实体查询(内部接口)
- GET
/metadata/entity-webhooks/internal/by-entity/{entityName}
说明:
- 供数据服务等内部组件按实体拉取所有 Webhook 配置。
- 需要权限
tenant:admin/admin/internal。
变更事件与 Outbox 模型
实体变更触发时,数据服务会向租户库表 mutation_webhook_outbox 写入一条记录:
- 核心字段:
id:outbox 记录 IDtenant_id:租户 IDentity_name:实体名operation:操作类型(INSERT/UPDATE/UPSERT/DELETE)request_id:请求 ID,用于幂等与排查webhook_config_id:引用的 Webhook 配置 IDpayload_json:变更 payload 的 JSONstatus:状态(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,记录直接标记为DEADhttpMethod:解析为HttpMethod,非法取值回退为POSTheadersJson:若非空则反序列化为Map<String,String>并写入请求头,解析失败抛出WEBHOOK_HEADERS_INVALIDtimeoutMs:在 HTTP 客户端层面配置(由外层 RestTemplate 配置决定)secret:用于签名,见下一节
- 固定请求头:
Content-Type: application/jsonX-AIDAAS-Request-Id: <request_id>X-AIDAAS-Timestamp: <unix_timestamp_seconds>- 若配置了
secret,再附加X-AIDAAS-Signature
- 请求体:
payload_json字段的 JSON 文本
重试与状态机:
- 若 Webhook 配置缺失或
enabled = false:- 对应 outbox 记录标记为
DEAD,不再重试。
- 对应 outbox 记录标记为
- 若请求返回非 2xx 状态码:
408、429、5xx:视为可重试错误,抛出WEBHOOK_DELIVERY_RETRYABLE- 其他状态码:视为不可重试错误,抛出
WEBHOOK_DELIVERY_NON_RETRYABLE
- 异常处理:
- 不可重试错误或签名/配置错误:记录状态置为
DEAD,写入last_error - 可重试错误:
retry_count + 1 >= max_retries:状态置为DEAD- 否则状态置为
RETRYING,按退避策略更新next_retry_at
- 不可重试错误或签名/配置错误:记录状态置为
退避策略:
-
使用 Webhook 配置中的:
backoffInitialSeconds:基础秒数,默认5,小于等于0时回退到5backoffFactor:倍率,默认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-TimestampX-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作为幂等键。