"""持久化任务执行者;获取连接级锁后才恢复或领取任务。""" from datetime import datetime from pathlib import Path from app.core.tplus_db import get_tplus_session_factory, resolve_tplus_settings from app.core.tplus_lock import TPlusBusyError, sync_lock from app.repositories import tplus_repository as repository from app.services.tplus import task_service from app.services.tplus.automation import WorkerHeartbeat from app.services.tplus.errors import classify_source_error from app.services.tplus.relationship_audit import TPlusRelationshipAudit from app.services.tplus.sync_service import TPlusSyncService def run_once(session, source_factory=None, *, worker_owner=None): """返回本轮处理的批次号;忙锁或队列为空时不假称已执行。""" try: with sync_lock(session), WorkerHeartbeat(session, owner=worker_owner): from sqlalchemy import inspect from app.models import TPlusWorkRun from app.services.tplus.workbench import execute_next if inspect(session.connection()).has_table(TPlusWorkRun.__tablename__): number = execute_next(session) if number: return number task_service.recover_running(session) batch = repository.next_queued(session) if batch is None: session.rollback() return None batch.status, batch.started_at = "RUNNING", datetime.now() session.commit() _execute(session, batch, source_factory) return batch.batch_no except TPlusBusyError: session.rollback() return None def _execute(session, batch, source_factory): configured = False try: if batch.requested_by is not None: from app.models import User from app.services.authorization_service import require_permission require_permission(session, session.get(User, batch.requested_by), "tplus:retry" if batch.parent_batch_no else "tplus:collect") settings = resolve_tplus_settings(task_service.get_settings()) if batch.source_database and batch.source_database != settings.t_plus_database: raise RuntimeError("任务排队后的源账套配置已改变") configured = True factory = source_factory or get_tplus_session_factory() with factory() as source: if batch.sync_mode == "AUDIT": root = Path(settings.t_plus_query_dir) if settings.t_plus_query_dir else None report = TPlusRelationshipAudit(source, root).run() batch.audit_result_json = report batch.source_record_count = sum(row["row_count"] for row in report["receipt_kinds"]) batch.finished_at, batch.status = datetime.now(), "SUCCEEDED" if report["relationship_status"] != "OBSERVED": task_service.fail_batch(session, batch, "TYPE_UNRESOLVED") else: session.commit() else: TPlusSyncService(source, session).execute_claimed(batch) except BaseException as exc: session.rollback() task_service.fail_batch( session, batch, "INTERRUPTED" if isinstance(exc, (KeyboardInterrupt, SystemExit)) else classify_source_error(exc, "EXECUTION_ERROR" if configured else "SOURCE_CONFIG_ERROR"), ) if isinstance(exc, (KeyboardInterrupt, SystemExit)): raise