feat(scheduler router): 为定时任务接口添加幂等性支持和审计字段
1. 新增Header参数Idempotency-Key实现幂等校验 2. 为create/update/delete/pause/resume/trigger接口补充幂等键参数 3. 为trigger任务接口添加操作人审计字段和透传逻辑
This commit is contained in:
parent
6d3cf3fedd
commit
1e0f4b3b14
@ -13,7 +13,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Header, Query
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from yuxi.scheduler.infrastructure.container import create_scheduler_service
|
from yuxi.scheduler.infrastructure.container import create_scheduler_service
|
||||||
@ -149,12 +149,22 @@ async def list_tasks(
|
|||||||
@scheduler_router.post("/tasks", response_model=dict)
|
@scheduler_router.post("/tasks", response_model=dict)
|
||||||
async def create_task(
|
async def create_task(
|
||||||
body: CreateTaskRequest,
|
body: CreateTaskRequest,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""创建定时任务(FR-ST-01 / FR-ST-05)。"""
|
"""创建定时任务(FR-ST-01 / FR-ST-05)。"""
|
||||||
service = create_scheduler_service(db)
|
service = create_scheduler_service(db)
|
||||||
input_dto = CreateTaskInput(**body.model_dump(), created_by=current_user.uid)
|
input_dto = CreateTaskInput(
|
||||||
|
**body.model_dump(),
|
||||||
|
created_by=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
|
)
|
||||||
output = await service.create_task(input_dto)
|
output = await service.create_task(input_dto)
|
||||||
return {"success": True, "data": output.model_dump()}
|
return {"success": True, "data": output.model_dump()}
|
||||||
|
|
||||||
@ -231,6 +241,12 @@ async def get_task(
|
|||||||
async def update_task(
|
async def update_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
body: UpdateTaskRequest,
|
body: UpdateTaskRequest,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@ -239,6 +255,7 @@ async def update_task(
|
|||||||
input_dto = UpdateTaskInput(
|
input_dto = UpdateTaskInput(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
updated_by=current_user.uid,
|
updated_by=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
**body.model_dump(exclude_unset=True),
|
**body.model_dump(exclude_unset=True),
|
||||||
)
|
)
|
||||||
output = await service.update_task(input_dto)
|
output = await service.update_task(input_dto)
|
||||||
@ -248,12 +265,22 @@ async def update_task(
|
|||||||
@scheduler_router.delete("/tasks/{task_id}", response_model=dict)
|
@scheduler_router.delete("/tasks/{task_id}", response_model=dict)
|
||||||
async def delete_task(
|
async def delete_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""删除定时任务(软删除,FR-ST-05)。"""
|
"""删除定时任务(软删除,FR-ST-05)。"""
|
||||||
service = create_scheduler_service(db)
|
service = create_scheduler_service(db)
|
||||||
input_dto = DeleteTaskInput(task_id=task_id, updated_by=current_user.uid)
|
input_dto = DeleteTaskInput(
|
||||||
|
task_id=task_id,
|
||||||
|
updated_by=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
|
)
|
||||||
await service.delete_task(input_dto)
|
await service.delete_task(input_dto)
|
||||||
return {"success": True, "data": {"task_id": task_id, "status": "deleted"}}
|
return {"success": True, "data": {"task_id": task_id, "status": "deleted"}}
|
||||||
|
|
||||||
@ -261,12 +288,22 @@ async def delete_task(
|
|||||||
@scheduler_router.post("/tasks/{task_id}/pause", response_model=dict)
|
@scheduler_router.post("/tasks/{task_id}/pause", response_model=dict)
|
||||||
async def pause_task(
|
async def pause_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""暂停任务(FR-ST-05)。"""
|
"""暂停任务(FR-ST-05)。"""
|
||||||
service = create_scheduler_service(db)
|
service = create_scheduler_service(db)
|
||||||
input_dto = PauseTaskInput(task_id=task_id, updated_by=current_user.uid)
|
input_dto = PauseTaskInput(
|
||||||
|
task_id=task_id,
|
||||||
|
updated_by=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
|
)
|
||||||
output = await service.pause_task(input_dto)
|
output = await service.pause_task(input_dto)
|
||||||
return {"success": True, "data": output.model_dump()}
|
return {"success": True, "data": output.model_dump()}
|
||||||
|
|
||||||
@ -274,6 +311,12 @@ async def pause_task(
|
|||||||
@scheduler_router.post("/tasks/{task_id}/resume", response_model=dict)
|
@scheduler_router.post("/tasks/{task_id}/resume", response_model=dict)
|
||||||
async def resume_task(
|
async def resume_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@ -282,7 +325,11 @@ async def resume_task(
|
|||||||
``paused`` -> ``active`` / ``dead_letter`` -> ``active``。
|
``paused`` -> ``active`` / ``dead_letter`` -> ``active``。
|
||||||
"""
|
"""
|
||||||
service = create_scheduler_service(db)
|
service = create_scheduler_service(db)
|
||||||
input_dto = ResumeTaskInput(task_id=task_id, updated_by=current_user.uid)
|
input_dto = ResumeTaskInput(
|
||||||
|
task_id=task_id,
|
||||||
|
updated_by=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
|
)
|
||||||
output = await service.resume_task(input_dto)
|
output = await service.resume_task(input_dto)
|
||||||
return {"success": True, "data": output.model_dump()}
|
return {"success": True, "data": output.model_dump()}
|
||||||
|
|
||||||
@ -291,6 +338,12 @@ async def resume_task(
|
|||||||
async def trigger_task(
|
async def trigger_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
body: TriggerTaskRequest | None = None,
|
body: TriggerTaskRequest | None = None,
|
||||||
|
idempotency_key: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Idempotency-Key",
|
||||||
|
max_length=128,
|
||||||
|
description="幂等键,启用后重复请求回放缓存响应",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_admin_user),
|
current_user: User = Depends(get_admin_user),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@ -298,6 +351,7 @@ async def trigger_task(
|
|||||||
|
|
||||||
生成 ``run_id`` 后入队 ARQ 执行,不影响下一次自动执行。
|
生成 ``run_id`` 后入队 ARQ 执行,不影响下一次自动执行。
|
||||||
可通过 body 传入 payload 覆盖任务定义中的 payload,None 时使用任务原 payload。
|
可通过 body 传入 payload 覆盖任务定义中的 payload,None 时使用任务原 payload。
|
||||||
|
触发人 ``current_user.uid`` 透传到 worker 写入 ``run_log.created_by`` 审计字段。
|
||||||
"""
|
"""
|
||||||
arq_pool = await get_arq_pool()
|
arq_pool = await get_arq_pool()
|
||||||
service = create_scheduler_service(db, arq_pool=arq_pool)
|
service = create_scheduler_service(db, arq_pool=arq_pool)
|
||||||
@ -306,6 +360,8 @@ async def trigger_task(
|
|||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
triggered_by="manual",
|
triggered_by="manual",
|
||||||
payload=payload,
|
payload=payload,
|
||||||
|
operator=current_user.uid,
|
||||||
|
idempotency_key=idempotency_key,
|
||||||
)
|
)
|
||||||
output = await service.trigger_task(input_dto)
|
output = await service.trigger_task(input_dto)
|
||||||
return {"success": True, "data": output.model_dump()}
|
return {"success": True, "data": output.model_dump()}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user