feat(gateway): 下单任务支持 callback_url 终结类事件异步通知

- POST /api/orders 新增可选 callback_url(仅 http/https,其余 422)
- 仅终结类事件各通知一次:terminal report 推入终态(succeeded/failed/
  needs_human)、租约过期被 sweep 置 stale;中间态与终结后的监控上报不通知
- 幂等重发不更新既有任务的回调地址;投递 best-effort 单次尝试,失败只记日志
- CallbackNotifier 发后不管(create_task + 在途任务强引用),关停等在途发完;
  生产路径按回调地址逐次构造客户端,满足统一出站代理策略(test_proxy.py)
- tasks 表加 callback_url 列,GatewayDB.start() 内置迁移兼容既有库
- 新增 RAKUTEN_CALLBACK_TIMEOUT_SECONDS(默认 10);文档补 §4.8;
  openapi.json 重新导出(gitignore 未跟踪);新增 17 条测试,全量 492 通过
This commit is contained in:
2026-08-17 01:05:37 +08:00
parent d54af141eb
commit 48c6f2a28f
10 changed files with 649 additions and 28 deletions
+11 -2
View File
@@ -18,6 +18,7 @@ from app.gateway.api.routes.account import router as account_router
from app.gateway.api.routes.health import router as health_router
from app.gateway.api.routes.orders import router as orders_router
from app.gateway.api.routes.queries import router as queries_router
from app.gateway.callback import CallbackNotifier
from app.gateway.collector import OrderDiscoveryCollector
from app.gateway.container import GatewayContainer
from app.gateway.db import GatewayDB
@@ -33,13 +34,18 @@ SWEEP_INTERVAL_SECONDS = 60
def build_container() -> GatewayContainer:
"""构建网关容器:DB + 下单任务队列 + 账号只读查询队列"""
"""构建网关容器:DB + 下单任务队列 + 账号只读查询队列 + 回调通知器"""
settings = get_settings()
db = GatewayDB(settings.gateway_db_path_resolved)
# 终结类事件回调(§4.8):best-effort 单次投递,失败只记日志
notifier = CallbackNotifier(
settings=settings, timeout_seconds=settings.callback_timeout_seconds
)
task_queue = TaskQueue(
db,
lease_ttl_seconds=settings.lease_ttl_seconds,
worker_offline_alert_seconds=settings.worker_offline_alert_seconds,
notifier=notifier,
)
query_queue = QueryQueue(
db,
@@ -53,7 +59,7 @@ def build_container() -> GatewayContainer:
)
return GatewayContainer(
settings=settings, db=db, task_queue=task_queue,
query_queue=query_queue, collector=collector,
query_queue=query_queue, collector=collector, notifier=notifier,
)
@@ -131,6 +137,9 @@ async def lifespan(app: FastAPI):
except asyncio.CancelledError:
pass
container.sweep_task = None
if container.notifier is not None:
# 等在途回调发完(各次发送有超时兜底),避免关停时静默丢通知
await container.notifier.aclose()
await container.db.close()