refactor(repo): 优化仓库层代码,新增多条件查询能力

1. 为count_deleted方法新增按deleted_at时间范围过滤的参数
2. 为幂等查询get方法新增operation参数以避免响应体错误重放
3. 优化定时任务更新方法的注释说明与排除字段逻辑
4. 新增活跃任务统计并更新handler聚合摘要查询
5. 为任务状态统计接口新增handler名称过滤能力
This commit is contained in:
Kris 2026-07-11 15:53:38 +08:00
parent 6ddc2a8c25
commit 202defb5f2
3 changed files with 49 additions and 33 deletions

View File

@ -155,9 +155,18 @@ class BaseRepository:
result = await self.db.execute(stmt)
return list(result.scalars().all())
async def count_deleted(self) -> int:
"""统计软删除记录数。"""
async def count_deleted(
self,
*,
start_time: datetime | None = None,
end_time: datetime | None = None,
) -> int:
"""统计软删除记录数,支持按 deleted_at 时间范围过滤。"""
stmt = select(func.count()).select_from(self.model).where(self.model.is_deleted == 1)
if start_time is not None:
stmt = stmt.where(self.model.deleted_at >= start_time)
if end_time is not None:
stmt = stmt.where(self.model.deleted_at <= end_time)
result = await self.db.execute(stmt)
return int(result.scalar() or 0)

View File

@ -129,19 +129,24 @@ class ScheduledTaskIdempotencyRepository(BaseRepository):
# 幂等查询
# ------------------------------------------------------------------
async def get(self, key: str) -> dict | None:
async def get(self, key: str, *, operation: str | None = None) -> dict | None:
"""查询幂等记录的响应体。
Args:
key: 幂等键
operation: 操作类型可选传入时额外校验记录中的 ``operation``
字段与当前操作一致避免不同 API 操作间因复用同一 key 导致
响应体被错误重放 create 的响被 update 重放
Returns:
``response_body`` 字段值dict记录不存在或响应体未就绪
``response_body IS NULL``时返回 ``None``
``response_body`` 字段值dict记录不存在响应体未就绪
``response_body IS NULL````operation`` 不匹配时返回 ``None``
"""
stmt = (
select(self.model.response_body).where(self.model.idempotency_key == key).where(self.model.is_deleted == 0)
)
if operation is not None:
stmt = stmt.where(self.model.operation == operation)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()

View File

@ -35,6 +35,7 @@ class HandlerSummary:
name: str
task_count: int
active_task_count: int
last_active_at: datetime | None
@ -226,25 +227,22 @@ class ScheduledTaskRepository(BaseRepository):
return tasks, total
async def update(self, task_id: str, data: dict[str, Any], *, commit: bool = True) -> ScheduledTask | None:
"""按 ``task_id`` 更新任务配置字段(排除已软删除)。
"""按 ``task_id`` 更新任务字段(排除已软删除)。
自动设置 ``updated_at=now`` ``data`` 中提取允许更新的字段
排除主键审计字段与运行时状态字段运行时状态字段
``consecutive_errors`` / ``last_run_at`` / ``next_run_at`` /
``last_error`` / ``status``由专用状态机方法管理禁止通过本方法
直接更新避免绕过状态机约束
自动设置 ``updated_at=now`` ``data`` 中提取允许更新的字段
排除主键审计字段与运行轨迹字段运行轨迹字段
``consecutive_errors`` / ``last_run_at`` / ``last_error``仍由
``record_run_result`` 专用方法管理禁止通过本方法直接更新
``status`` / ``next_run_at`` ``update_task`` 用例在已校验状态机
与调度语义后显式写入避免 lower-level 调用方绕过约束
- ``pause`` / ``resume`` active paused
- ``mark_dead_letter`` dead_letter
- ``record_run_result`` 原子回写执行结果与 next_run_at
Args:
task_id: 业务任务标识
data: 待更新字段字典
commit: ``True`` 时提交事务``False`` 时仅 flush
Args:
task_id: 业务任务标识
data: 待更新字段字典
commit: ``True`` 时提交事务``False`` 时仅 flush
Returns:
更新后的 ``ScheduledTask`` 实例 None任务不存在时
Returns:
更新后的 ``ScheduledTask`` 实例 None任务不存在时
"""
excluded = {
"task_id",
@ -254,9 +252,7 @@ class ScheduledTaskRepository(BaseRepository):
"is_deleted",
"consecutive_errors",
"last_run_at",
"next_run_at",
"last_error",
"status",
}
update_fields = {k: v for k, v in data.items() if k not in excluded}
update_fields["updated_at"] = utc_now_naive()
@ -372,11 +368,13 @@ class ScheduledTaskRepository(BaseRepository):
async def list_handler_summary(self) -> list[HandlerSummary]:
"""返回 handler 维度的聚合摘要。
SQL 语义``SELECT handler_name, COUNT(*) as task_count, MAX(last_run_at)
as last_active_at FROM scheduled_tasks WHERE is_deleted=0 GROUP BY
handler_name ORDER BY handler_name``聚合查询用于 Admin API 展示
handler 的任务数与最近执行时间扫描全表但行数有限handler ×
状态组合性能可接受
SQL 语义``SELECT handler_name,
COUNT(*) as task_count,
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active_task_count,
MAX(last_run_at) as last_active_at
FROM scheduled_tasks WHERE is_deleted=0 GROUP BY handler_name``
聚合查询用于 Admin API 展示各 handler 的任务数活跃任务数与最近执行时间
扫描全表但行数有限handler × 状态组合性能可接受
``last_active_at`` ``last_run_at``最近一次执行开始时间而非
``updated_at``避免配置变更等非执行操作刷新活跃时间导致语义失真
@ -388,6 +386,7 @@ class ScheduledTaskRepository(BaseRepository):
select(
ScheduledTask.handler_name,
func.count().label("task_count"),
func.sum(func.case((ScheduledTask.status == "active", 1), else_=0)).label("active_task_count"),
func.max(ScheduledTask.last_run_at).label("last_active_at"),
)
.where(self._not_deleted())
@ -399,6 +398,7 @@ class ScheduledTaskRepository(BaseRepository):
HandlerSummary(
name=row.handler_name,
task_count=int(row.task_count),
active_task_count=int(row.active_task_count or 0),
last_active_at=row.last_active_at,
)
for row in result.all()
@ -409,18 +409,20 @@ class ScheduledTaskRepository(BaseRepository):
*,
owner_scope: str | None = None,
owner_id: str | None = None,
handler_name: str | None = None,
) -> dict[str, int]:
"""按状态统计任务数,返回 ``{active: N, paused: N, dead_letter: N}``。
用于 Admin API 仪表盘展示任务状态分布SQL 语义
``SELECT status, COUNT(*) FROM scheduled_tasks WHERE is_deleted=0
[AND owner_scope=? AND owner_id=?] GROUP BY status``一次查询
获取所有状态分布避免上层多次调用 ``list(status=..., page_size=1)``
[AND owner_scope=? AND owner_id=? AND handler_name=?] GROUP BY status``
一次查询获取所有状态分布避免上层多次调用 ``list(status=..., page_size=1)``
获取 total 的低效方案
Args:
owner_scope: 可选归属范围过滤system / business
owner_id: 可选业务方 ID 过滤
handler_name: 可选 handler 名称过滤
Returns:
状态 任务数的映射未出现的状态不在字典中调用方按需 ``.get`` 0
@ -434,6 +436,8 @@ class ScheduledTaskRepository(BaseRepository):
stmt = stmt.where(ScheduledTask.owner_scope == owner_scope)
if owner_id is not None:
stmt = stmt.where(ScheduledTask.owner_id == owner_id)
if handler_name is not None:
stmt = stmt.where(ScheduledTask.handler_name == handler_name)
result = await self.db.execute(stmt)
return {row.status: int(row.count) for row in result.all()}
@ -502,9 +506,7 @@ class ScheduledTaskRepository(BaseRepository):
# 状态机转换
# ------------------------------------------------------------------
async def pause(
self, task_id: str, *, updated_by: str | None = None, commit: bool = True
) -> ScheduledTask | None:
async def pause(self, task_id: str, *, updated_by: str | None = None, commit: bool = True) -> ScheduledTask | None:
"""暂停任务(``active`` → ``paused``)。
``active`` 状态的任务可暂停``paused`` / ``dead_letter`` 状态