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:
+23
-1
@@ -7,8 +7,9 @@ intent 字段刻意保留成 `dict[str, Any]`——网关不解释下单意图
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.shared.task_state import AccountQueryKind, OrderState, QueryStatus, TaskStatus
|
||||
|
||||
@@ -49,6 +50,24 @@ class SubmitOrderRequest(BaseModel):
|
||||
"max_total_yen": 30000,
|
||||
}],
|
||||
)
|
||||
callback_url: str | None = Field(
|
||||
default=None,
|
||||
description="终结类事件的异步通知地址(http/https)。任务到达终态(succeeded / "
|
||||
"failed / needs_human)或被置 stale 时,网关向该地址 POST 一条 JSON 通知,"
|
||||
"上游可免去轮询。注意:幂等重发(created=false)不会更新既有任务的回调地址",
|
||||
examples=["https://upstream.example.com/hooks/rakuten-order"],
|
||||
)
|
||||
|
||||
@field_validator("callback_url")
|
||||
@classmethod
|
||||
def _validate_callback_url(cls, value: str | None) -> str | None:
|
||||
"""只接受 http/https 且带 host 的绝对 URL,其余当场 422"""
|
||||
if value is None:
|
||||
return None
|
||||
parts = urlsplit(value)
|
||||
if parts.scheme not in ("http", "https") or not parts.netloc:
|
||||
raise ValueError("callback_url 必须是 http/https 绝对 URL")
|
||||
return value
|
||||
|
||||
|
||||
class SubmitOrderData(BaseModel):
|
||||
@@ -194,6 +213,9 @@ class TaskDetail(BaseModel):
|
||||
task_id: str = Field(description="任务 ID(幂等键)")
|
||||
site: str = Field(description="站点标识(rakuten)")
|
||||
intent: dict[str, Any] = Field(description="下单意图原文")
|
||||
callback_url: str | None = Field(
|
||||
default=None, description="提交时登记的终结类事件通知地址;未登记为 null"
|
||||
)
|
||||
status: TaskStatus = Field(
|
||||
description="任务状态:queued / leased / running / succeeded / failed / needs_human / stale"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user