client.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. """将冻结的业务结果直接 POST 到结果平台,不经过取数网关。"""
  2. import math
  3. import httpx
  4. from extensions.result_platform.settings import ResultPlatformSettings
  5. from runtime.result_models import PlatformResultPayload
  6. class ResultPlatformError(RuntimeError):
  7. def __init__(self, operation, status=0, *, retryable=False, retry_after=None):
  8. self.status, self.retryable, self.retry_after = status, retryable, retry_after
  9. super().__init__(f"{operation}失败(状态 {status})")
  10. class ResultPlatformClient:
  11. def __init__(self, settings: ResultPlatformSettings, *, transport=None):
  12. self.settings = settings
  13. self.http = httpx.AsyncClient(timeout=settings.timeout, trust_env=False,
  14. follow_redirects=False, transport=transport)
  15. async def close(self):
  16. await self.http.aclose()
  17. async def push(self, payload):
  18. body = PlatformResultPayload.model_validate(payload).model_dump(mode="json")
  19. headers = {"Idempotency-Key": body["taskId"]}
  20. if self.settings.auth_header:
  21. headers[self.settings.auth_header] = " ".join(
  22. s for s in (self.settings.auth_scheme, self.settings.token) if s)
  23. try:
  24. response = await self.http.post(self.settings.url, json=body, headers=headers)
  25. except httpx.RequestError:
  26. raise ResultPlatformError("结果平台连接", retryable=True) from None
  27. if not 200 <= response.status_code < 300:
  28. try:
  29. retry_after = float(response.headers["Retry-After"])
  30. if not math.isfinite(retry_after) or retry_after < 0:
  31. retry_after = None
  32. except (KeyError, ValueError):
  33. retry_after = None
  34. raise ResultPlatformError("结果平台推送", response.status_code,
  35. retryable=response.status_code in {408, 425, 429, 500, 502, 503, 504}, retry_after=retry_after)
  36. if response.status_code == 204:
  37. return {"httpStatus": 204, "taskId": body["taskId"]}
  38. try:
  39. receipt = response.json()
  40. except ValueError:
  41. raise ResultPlatformError("结果平台成功回执不是 JSON", response.status_code) from None
  42. if not isinstance(receipt, dict) or receipt.get("success") is False:
  43. raise ResultPlatformError("结果平台业务回执", response.status_code)
  44. value = receipt
  45. for field in self.settings.success_field.split("."):
  46. value = value.get(field) if isinstance(value, dict) else None
  47. if str(value) not in self.settings.success_values:
  48. raise ResultPlatformError("结果平台业务回执", response.status_code)
  49. # 对方明确回传任务身份时,不能把另一任务的回执算作成功。
  50. returned_id = receipt.get("taskId")
  51. if isinstance(receipt.get("data"), dict):
  52. returned_id = returned_id or receipt["data"].get("taskId")
  53. if returned_id is not None and returned_id != body["taskId"]:
  54. raise ResultPlatformError("结果平台回执任务身份不一致", response.status_code)
  55. return receipt