#!/usr/bin/env python3 # -*- coding: utf-8 -*- from __future__ import annotations import json import os import time from services.runtime import get_logger, get_socketio from services.state import safe_get_account, safe_iter_task_status_items def _to_int(value, default: int = 0) -> int: try: return int(value) except Exception: return int(default) def _payload_signature(payload: dict) -> str: try: return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str) except Exception: return repr(payload) def _should_emit( *, last_sig: str | None, last_ts: float, new_sig: str, now_ts: float, min_interval: float, force_interval: float, ) -> bool: if last_sig is None: return True if (now_ts - last_ts) >= force_interval: return True if new_sig != last_sig and (now_ts - last_ts) >= min_interval: return True return False def status_push_worker() -> None: """后台线程:按间隔推送排队/运行中任务状态(变更驱动+心跳兜底)。""" logger = get_logger() try: push_interval = float(os.environ.get("STATUS_PUSH_INTERVAL_SECONDS", "1")) except Exception: push_interval = 1.0 push_interval = max(0.5, push_interval) try: queue_min_interval = float(os.environ.get("STATUS_PUSH_MIN_QUEUE_INTERVAL_SECONDS", str(push_interval))) except Exception: queue_min_interval = push_interval queue_min_interval = max(push_interval, queue_min_interval) try: progress_min_interval = float( os.environ.get("STATUS_PUSH_MIN_PROGRESS_INTERVAL_SECONDS", str(push_interval)) ) except Exception: progress_min_interval = push_interval progress_min_interval = max(push_interval, progress_min_interval) try: force_interval = float(os.environ.get("STATUS_PUSH_FORCE_INTERVAL_SECONDS", "10")) except Exception: force_interval = 10.0 force_interval = max(push_interval, force_interval) socketio = get_socketio() from services.tasks import get_task_scheduler scheduler = get_task_scheduler() emitted_state: dict[str, dict] = {} while True: try: now_ts = time.time() queue_snapshot = scheduler.get_queue_state_snapshot() pending_total = int(queue_snapshot.get("pending_total", 0) or 0) running_total = int(queue_snapshot.get("running_total", 0) or 0) running_by_user = queue_snapshot.get("running_by_user") or {} positions = queue_snapshot.get("positions") or {} active_account_ids = set() status_items = safe_iter_task_status_items() for account_id, status_info in status_items: status = status_info.get("status") if status not in ("运行中", "排队中"): continue user_id = status_info.get("user_id") if not user_id: continue active_account_ids.add(str(account_id)) account = safe_get_account(user_id, account_id) if not account: continue user_id_int = _to_int(user_id) account_data = account.to_dict() pos = positions.get(account_id) or positions.get(str(account_id)) or {} account_data.update( { "queue_pending_total": pending_total, "queue_running_total": running_total, "queue_ahead": pos.get("queue_ahead"), "queue_position": pos.get("queue_position"), "queue_is_vip": pos.get("is_vip"), "queue_running_user": _to_int(running_by_user.get(user_id_int, running_by_user.get(str(user_id_int), 0))), } ) cache_entry = emitted_state.setdefault(str(account_id), {}) account_sig = _payload_signature(account_data) if _should_emit( last_sig=cache_entry.get("account_sig"), last_ts=float(cache_entry.get("account_ts", 0) or 0), new_sig=account_sig, now_ts=now_ts, min_interval=queue_min_interval, force_interval=force_interval, ): socketio.emit("account_update", account_data, room=f"user_{user_id}") cache_entry["account_sig"] = account_sig cache_entry["account_ts"] = now_ts if status != "运行中": continue progress = status_info.get("progress", {}) or {} progress_data = { "account_id": account_id, "stage": status_info.get("detail_status", ""), "total_items": account.total_items, "browsed_items": progress.get("items", 0), "total_attachments": account.total_attachments, "viewed_attachments": progress.get("attachments", 0), "start_time": status_info.get("start_time", 0), "elapsed_seconds": account_data.get("elapsed_seconds", 0), "elapsed_display": account_data.get("elapsed_display", ""), "queue_pending_total": pending_total, "queue_running_total": running_total, "queue_ahead": pos.get("queue_ahead"), "queue_position": pos.get("queue_position"), "queue_running_user": _to_int(running_by_user.get(user_id_int, running_by_user.get(str(user_id_int), 0))), } progress_sig = _payload_signature(progress_data) if _should_emit( last_sig=cache_entry.get("progress_sig"), last_ts=float(cache_entry.get("progress_ts", 0) or 0), new_sig=progress_sig, now_ts=now_ts, min_interval=progress_min_interval, force_interval=force_interval, ): socketio.emit("task_progress", progress_data, room=f"user_{user_id}") cache_entry["progress_sig"] = progress_sig cache_entry["progress_ts"] = now_ts if emitted_state: stale_ids = [account_id for account_id in emitted_state.keys() if account_id not in active_account_ids] for account_id in stale_ids: emitted_state.pop(account_id, None) time.sleep(push_interval) except Exception as e: logger.debug(f"状态推送出错: {e}") time.sleep(push_interval)