One request exercises every layer: JWT auth, quota reservation, a versioned prompt through the LLM gateway, a cost row, quota commit or refund, and the unified envelope. The trivial ping task proves the queue routing and the tk_task transitions before any real long-running task is written. Routes are mounted at the /api/v1 base path of 架构文档 §9.1; /v1/* is rewritten to it at the ASGI layer so both spellings work from one route table.
69 lines
2.4 KiB
Python
Executable File
69 lines
2.4 KiB
Python
Executable File
"""Celery task definitions.
|
|
|
|
Only the scaffold's ``ping`` task lives here. It proves the whole async path:
|
|
API creates ``tk_task`` → queue → worker transitions running → succeeded →
|
|
task centre reflects it. Real long-running tasks (report / tryon / matting /
|
|
render) follow exactly this shape and additionally call
|
|
``billing.commit()``/``refund()`` and ``compliance.publish_asset()``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
|
|
from apps.worker.celery_app import celery_app
|
|
from packages.domain.task.state import TaskStatus
|
|
from packages.shared.context import bind_context
|
|
from packages.shared.db import get_session_factory
|
|
from packages.shared.logging import get_logger
|
|
from packages.task.service import TaskService
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
async def _run_ping(task_id: int, echo: str | None = None) -> dict[str, Any]:
|
|
factory = get_session_factory()
|
|
async with factory() as session:
|
|
service = TaskService(session)
|
|
task = await service.transition(
|
|
task_id=task_id, target=TaskStatus.RUNNING, stage_text="执行中"
|
|
)
|
|
payload = task.payload or {}
|
|
message = echo or str(payload.get("echo", "pong"))
|
|
await session.commit()
|
|
|
|
finished = await service.transition(
|
|
task_id=task_id,
|
|
target=TaskStatus.SUCCEEDED,
|
|
stage_text="已完成",
|
|
result={"echo": message},
|
|
)
|
|
await session.commit()
|
|
return {"task_id": str(finished.task_id), "echo": message}
|
|
|
|
|
|
async def _fail_ping(task_id: int, error: str) -> None:
|
|
factory = get_session_factory()
|
|
async with factory() as session:
|
|
service = TaskService(session)
|
|
await service.transition(
|
|
task_id=task_id,
|
|
target=TaskStatus.FAILED,
|
|
stage_text="执行失败",
|
|
error_message=error,
|
|
)
|
|
await session.commit()
|
|
|
|
|
|
@celery_app.task(name="meida.task.ping", bind=True, max_retries=2)
|
|
def ping_task(self: Any, task_id: int, echo: str | None = None) -> dict[str, Any]:
|
|
bind_context(task_id=task_id)
|
|
try:
|
|
return asyncio.run(_run_ping(task_id, echo))
|
|
except Exception as exc:
|
|
logger.exception("ping_task_failed", task_id=task_id)
|
|
# Real tasks additionally refund the quota reservation here (FR-BILL-05).
|
|
asyncio.run(_fail_ping(task_id, f"{type(exc).__name__}: {exc}"))
|
|
raise
|