This commit is contained in:
Your Name
2026-04-22 14:13:21 +08:00
parent e0406b5d0e
commit 7cbde2aa78
145 changed files with 23086 additions and 2243 deletions

View File

@@ -2,12 +2,13 @@ from __future__ import annotations
import json
import socket
import threading
import urllib.error
import urllib.request
from datetime import datetime, timedelta
from app.core.config import settings
from app.core.db import get_db
from app.core.db import db_read_retry, get_db
_DEBUG_SCHEMA_SQL = """
@@ -27,6 +28,9 @@ CREATE INDEX IF NOT EXISTS idx_detect_debug_events_created
ON detect_debug_events(created_at DESC);
"""
_DEBUG_SCHEMA_READY = False
_DEBUG_SCHEMA_LOCK = threading.Lock()
def _format_time(value: datetime | None) -> str:
return value.isoformat(sep=" ", timespec="seconds") if value else ""
@@ -82,11 +86,18 @@ def _debug_ingest_url(base_url: str) -> str:
def ensure_debug_event_schema() -> None:
with get_db() as conn:
conn.autocommit = False
with conn.cursor() as cur:
cur.execute(_DEBUG_SCHEMA_SQL)
conn.commit()
global _DEBUG_SCHEMA_READY
if _DEBUG_SCHEMA_READY:
return
with _DEBUG_SCHEMA_LOCK:
if _DEBUG_SCHEMA_READY:
return
with get_db() as conn:
conn.autocommit = False
with conn.cursor() as cur:
cur.execute(_DEBUG_SCHEMA_SQL)
conn.commit()
_DEBUG_SCHEMA_READY = True
def append_debug_event(
@@ -159,12 +170,185 @@ def append_debug_event(
return record_id
def _load_debug_event_record(record_id: int) -> dict | None:
with get_db() as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, source_region, node_code, service, event_type, level, message, payload_json, created_at
FROM detect_debug_events
WHERE id = %s
LIMIT 1
""",
(int(record_id),),
)
row = cur.fetchone()
if not row:
return None
return {
"id": int(row[0]),
"source_region": str(row[1] or ""),
"node_code": str(row[2] or ""),
"service": str(row[3] or ""),
"event_type": str(row[4] or ""),
"level": str(row[5] or "info"),
"message": str(row[6] or ""),
"payload": row[7] if isinstance(row[7], dict) else {},
"created_at": _format_time(row[8]),
}
def _normalize_worker_log_event(debug_event: dict) -> dict | None:
payload = dict(debug_event.get("payload") or {})
message = _normalize_message(debug_event.get("message"), fallback="worker_log")
domain = str(payload.get("domain") or "").strip().lower()
status = str(payload.get("status") or "").strip().lower()
if not domain and ":" in message:
domain = message.rsplit(":", 1)[-1].strip().lower()
event_type = ""
if status == "completed":
event_type = "domain_completed"
elif status == "failed":
event_type = "domain_failed"
elif status == "blacklisted":
event_type = "domain_blacklisted"
elif "开始检测域名" in message:
event_type = "domain_started"
elif "域名检测完成" in message:
event_type = "domain_completed"
payload.setdefault("status", "completed")
elif "域名已命中黑名单" in message or "命中黑名单" in message:
event_type = "domain_blacklisted"
payload.setdefault("status", "blacklisted")
elif "域名检测失败" in message:
event_type = "domain_failed"
payload.setdefault("status", "failed")
if not event_type or not domain:
return None
payload.setdefault("domain", domain)
payload["imported_from_debug_event"] = True
payload["debug_event_record_id"] = int(debug_event.get("id") or 0)
payload["debug_event_source_region"] = str(debug_event.get("source_region") or "")
return {
"node_code": str(debug_event.get("node_code") or "").strip(),
"event_type": event_type,
"level": str(debug_event.get("level") or "info"),
"message": message,
"payload": payload,
"created_at": str(debug_event.get("created_at") or "").strip(),
}
def _ingest_worker_log_into_active_job(debug_event: dict) -> dict:
if str(debug_event.get("event_type") or "").strip() != "worker_log":
return {"imported": False, "reason": "not_worker_log"}
normalized_event = _normalize_worker_log_event(debug_event)
if not normalized_event:
return {"imported": False, "reason": "not_domain_progress_event"}
from app.services.detect_job_service import get_active_detect_job_summary
from app.services.sync_push_service import (
_apply_detect_result_event_to_domain,
_apply_detect_result_event_to_job_item,
)
active_job = get_active_detect_job_summary(event_limit=1) or {}
target_job_id = int(active_job.get("job_id") or 0)
if target_job_id <= 0:
return {"imported": False, "reason": "no_active_job"}
debug_event_record_id = int(debug_event.get("id") or 0)
updated_job_items = 0
with get_db() as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id
FROM detect_run_events
WHERE job_id = %s
AND (payload_json->>'debug_event_record_id') = %s
ORDER BY id DESC
LIMIT 1
""",
(target_job_id, str(debug_event_record_id)),
)
existing = cur.fetchone()
if existing:
return {
"imported": False,
"reason": "deduplicated",
"target_job_id": target_job_id,
"detect_run_event_id": int(existing[0]),
}
created_at = _parse_time(normalized_event.get("created_at"))
payload_json = _safe_json_dumps(normalized_event.get("payload") or {})
if created_at:
cur.execute(
"""
INSERT INTO detect_run_events (
job_id, node_code, event_type, level, message, payload_json, created_at
) VALUES (%s, %s, %s, %s, %s, %s::jsonb, %s)
RETURNING id
""",
(
target_job_id,
normalized_event["node_code"],
normalized_event["event_type"],
normalized_event["level"],
normalized_event["message"],
payload_json,
created_at,
),
)
else:
cur.execute(
"""
INSERT INTO detect_run_events (
job_id, node_code, event_type, level, message, payload_json
) VALUES (%s, %s, %s, %s, %s, %s::jsonb)
RETURNING id
""",
(
target_job_id,
normalized_event["node_code"],
normalized_event["event_type"],
normalized_event["level"],
normalized_event["message"],
payload_json,
),
)
detect_run_event_id = int((cur.fetchone() or [0])[0] or 0)
_apply_detect_result_event_to_domain(cur, normalized_event)
updated_job_items = _apply_detect_result_event_to_job_item(
cur,
target_job_id=target_job_id,
event=normalized_event,
)
conn.commit()
return {
"imported": True,
"reason": "imported",
"target_job_id": target_job_id,
"detect_run_event_id": detect_run_event_id,
"updated_job_items": updated_job_items,
"event_type": normalized_event["event_type"],
"domain": str((normalized_event.get("payload") or {}).get("domain") or ""),
}
@db_read_retry()
def list_debug_events(
limit: int = 50,
*,
service: str | None = None,
event_type: str | None = None,
source_region: str | None = None,
node_code: str | None = None,
level: str | None = None,
before_id: int | None = None,
after_id: int | None = None,
@@ -183,6 +367,9 @@ def list_debug_events(
if str(source_region or "").strip():
conditions.append("source_region = %s")
params.append(str(source_region).strip())
if str(node_code or "").strip():
conditions.append("node_code = %s")
params.append(str(node_code).strip())
if str(level or "").strip():
conditions.append("level = %s")
params.append(str(level).strip())
@@ -237,6 +424,7 @@ def list_debug_events(
}
@db_read_retry()
def get_debug_event_overview(*, window_minutes: int = 10, source_region: str | None = None) -> dict:
ensure_debug_event_schema()
safe_window = max(1, min(int(window_minutes or 10), 180))
@@ -671,7 +859,24 @@ def ingest_debug_event(payload: dict, *, shared_token: str | None = None) -> tup
message=_normalize_message(payload.get("message"), fallback="remote debug event"),
payload=payload.get("payload") or {},
)
return True, "调试事件接收成功", {"record_id": record_id}
debug_event = _load_debug_event_record(record_id) or {
"id": int(record_id),
"source_region": str(payload.get("source_region") or settings.node_region),
"node_code": str(payload.get("node_code") or ""),
"service": str(payload.get("service") or "unknown"),
"event_type": str(payload.get("event_type") or "event"),
"level": str(payload.get("level") or "info"),
"message": _normalize_message(payload.get("message"), fallback="remote debug event"),
"payload": payload.get("payload") or {},
"created_at": "",
}
job_import = {}
if str(debug_event.get("service") or "").strip() == "worker-event":
try:
job_import = _ingest_worker_log_into_active_job(debug_event)
except Exception as exc:
job_import = {"imported": False, "reason": f"job_import_failed: {exc}"}
return True, "调试事件接收成功", {"record_id": record_id, "job_import": job_import}
def push_debug_event(