微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -16,6 +16,7 @@ from __future__ import annotations
import asyncio
import json
import logging
import platform
import uuid
from dataclasses import dataclass, field
from typing import Any
@@ -25,6 +26,11 @@ from ..schemas.tasks import TaskConfigSchema
logger = logging.getLogger(__name__)
# CHANGE 2026-03-07 | 实例标识:用于多后端实例共享同一 DB 时的任务隔离
# 背景:发现有另一台机器(宿主机 D 盘)的后端也在消费同一个 task_queue
# 导致任务被错误实例执行。通过 enqueued_by 列实现"谁入队谁消费"。
_INSTANCE_ID = platform.node()
# 后台循环轮询间隔(秒)
POLL_INTERVAL_SECONDS = 2
@@ -43,6 +49,7 @@ class QueuedTask:
finished_at: Any = None
exit_code: int | None = None
error_message: str | None = None
schedule_id: str | None = None
class TaskQueue:
@@ -56,12 +63,13 @@ class TaskQueue:
# 入队
# ------------------------------------------------------------------
def enqueue(self, config: TaskConfigSchema, site_id: int) -> str:
def enqueue(self, config: TaskConfigSchema, site_id: int, schedule_id: str | None = None) -> str:
"""将任务配置入队,自动分配 position。
Args:
config: 任务配置
site_id: 门店 ID门店隔离
schedule_id: 关联的调度任务 ID可选
Returns:
新创建的队列任务 IDUUID 字符串)
@@ -84,18 +92,19 @@ class TaskQueue:
max_pos = cur.fetchone()[0]
new_pos = max_pos + 1
# CHANGE 2026-03-07 | 写入 enqueued_by 实现多实例任务隔离
cur.execute(
"""
INSERT INTO task_queue (id, site_id, config, status, position)
VALUES (%s, %s, %s, 'pending', %s)
INSERT INTO task_queue (id, site_id, config, status, position, schedule_id, enqueued_by)
VALUES (%s, %s, %s, 'pending', %s, %s, %s)
""",
(task_id, site_id, json.dumps(config_json), new_pos),
(task_id, site_id, json.dumps(config_json), new_pos, schedule_id, _INSTANCE_ID),
)
conn.commit()
finally:
conn.close()
logger.info("任务入队 [%s] site_id=%s position=%s", task_id, site_id, new_pos)
logger.info("任务入队 [%s] site_id=%s position=%s schedule_id=%s", task_id, site_id, new_pos, schedule_id)
return task_id
# ------------------------------------------------------------------
@@ -114,19 +123,21 @@ class TaskQueue:
conn = get_connection()
try:
with conn.cursor() as cur:
# 选取 position 最小的 pending 任务并锁定
# CHANGE 2026-03-07 | 只消费本实例入队的任务enqueued_by 匹配)
# 背景:多后端实例共享同一 DB 时,防止 A 实例消费 B 实例入队的任务
cur.execute(
"""
SELECT id, site_id, config, status, position,
created_at, started_at, finished_at,
exit_code, error_message
exit_code, error_message, schedule_id
FROM task_queue
WHERE site_id = %s AND status = 'pending'
AND (enqueued_by = %s OR enqueued_by IS NULL)
ORDER BY position ASC
LIMIT 1
FOR UPDATE SKIP LOCKED
""",
(site_id,),
(site_id, _INSTANCE_ID),
)
row = cur.fetchone()
if row is None:
@@ -144,6 +155,7 @@ class TaskQueue:
finished_at=row[7],
exit_code=row[8],
error_message=row[9],
schedule_id=str(row[10]) if row[10] else None,
)
# 更新状态为 running
@@ -261,10 +273,11 @@ class TaskQueue:
# ------------------------------------------------------------------
def list_pending(self, site_id: int) -> list[QueuedTask]:
"""列出指定门店的所有 pending 任务,按 position 升序。"""
"""列出指定门店的所有 pending 任务(仅限本实例入队的),按 position 升序。"""
conn = get_connection()
try:
with conn.cursor() as cur:
# CHANGE 2026-03-07 | 只列出本实例入队的 pending 任务
cur.execute(
"""
SELECT id, site_id, config, status, position,
@@ -272,9 +285,10 @@ class TaskQueue:
exit_code, error_message
FROM task_queue
WHERE site_id = %s AND status = 'pending'
AND (enqueued_by = %s OR enqueued_by IS NULL)
ORDER BY position ASC
""",
(site_id,),
(site_id, _INSTANCE_ID),
)
rows = cur.fetchall()
conn.commit()
@@ -298,18 +312,20 @@ class TaskQueue:
]
def has_running(self, site_id: int) -> bool:
"""检查指定门店是否有 running 状态任务。"""
"""检查指定门店是否有本实例的 running 状态任务。"""
conn = get_connection()
try:
with conn.cursor() as cur:
# CHANGE 2026-03-07 | 只检查本实例的 running 任务
cur.execute(
"""
SELECT EXISTS(
SELECT 1 FROM task_queue
WHERE site_id = %s AND status = 'running'
AND (enqueued_by = %s OR enqueued_by IS NULL)
)
""",
(site_id,),
(site_id, _INSTANCE_ID),
)
result = cur.fetchone()[0]
conn.commit()
@@ -333,7 +349,10 @@ class TaskQueue:
from .task_executor import task_executor
self._running = True
logger.info("TaskQueue process_loop 启动")
logger.info(
"TaskQueue process_loop 启动 (instance_id=%s,仅消费本实例入队的任务)",
_INSTANCE_ID,
)
while self._running:
try:
@@ -369,6 +388,7 @@ class TaskQueue:
asyncio.create_task(
self._execute_and_update(
executor, config, execution_id, task.id, site_id,
schedule_id=task.schedule_id,
)
)
@@ -379,6 +399,7 @@ class TaskQueue:
execution_id: str,
queue_id: str,
site_id: int,
schedule_id: str | None = None,
) -> None:
"""执行任务并更新队列状态。"""
try:
@@ -387,6 +408,7 @@ class TaskQueue:
execution_id=execution_id,
queue_id=queue_id,
site_id=site_id,
schedule_id=schedule_id,
)
# 执行完成后根据 executor 的结果更新 task_queue 状态
self._update_queue_status_from_log(queue_id)
@@ -395,15 +417,18 @@ class TaskQueue:
self._mark_failed(queue_id, "执行过程中发生未捕获异常")
def _get_pending_site_ids(self) -> list[int]:
"""获取所有有 pending 任务的 site_id 列表。"""
"""获取所有有 pending 任务的 site_id 列表(仅限本实例入队的)"""
conn = get_connection()
try:
with conn.cursor() as cur:
# CHANGE 2026-03-07 | 只查本实例入队的 pending 任务
cur.execute(
"""
SELECT DISTINCT site_id FROM task_queue
WHERE status = 'pending'
"""
AND (enqueued_by = %s OR enqueued_by IS NULL)
""",
(_INSTANCE_ID,),
)
rows = cur.fetchall()
conn.commit()