# -*- coding: utf-8 -*- """ETL 状态监控 API 提供 2 个端点: - GET /api/etl-status/cursors — 返回各任务的数据游标(最后抓取时间、记录数) - GET /api/etl-status/recent-runs — 返回最近 50 条任务执行记录 所有端点需要 JWT 认证。 游标端点查询 ETL 数据库(meta.etl_cursor), 执行记录端点查询 zqyy_app 数据库(task_execution_log)。 """ from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, status from psycopg2 import OperationalError from app.auth.dependencies import CurrentUser, get_current_user from app.database import get_connection, get_etl_readonly_connection from app.schemas.etl_status import CursorInfo, RecentRun logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/etl-status", tags=["ETL 状态"]) # 最近执行记录条数上限 _RECENT_RUNS_LIMIT = 50 # ── GET /api/etl-status/cursors ────────────────────────────── @router.get("/cursors", response_model=list[CursorInfo]) async def list_cursors( user: CurrentUser = Depends(get_current_user), ) -> list[CursorInfo]: """返回各 ODS 表的最新数据游标。 查询 ETL 数据库中的 meta.etl_cursor 表。 如果该表不存在,返回空列表而非报错。 """ conn = get_etl_readonly_connection(user.site_id) try: with conn.cursor() as cur: # CHANGE 2026-02-15 | 对齐新库 etl_feiqiu 六层架构:etl_admin → meta cur.execute( """ SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'meta' AND table_name = 'etl_cursor' ) """ ) exists = cur.fetchone()[0] if not exists: return [] cur.execute( """ SELECT task_code, last_fetch_time, record_count FROM meta.etl_cursor ORDER BY task_code """ ) rows = cur.fetchall() return [ CursorInfo( task_code=row[0], last_fetch_time=str(row[1]) if row[1] is not None else None, record_count=row[2], ) for row in rows ] except OperationalError as exc: logger.error("ETL 游标查询连接错误: %s", exc) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="ETL 数据库连接错误", ) finally: conn.close() # ── GET /api/etl-status/recent-runs ────────────────────────── @router.get("/recent-runs", response_model=list[RecentRun]) async def list_recent_runs( user: CurrentUser = Depends(get_current_user), ) -> list[RecentRun]: """返回最近 50 条任务执行记录。 查询 zqyy_app 数据库中的 task_execution_log 表, 按 site_id 过滤,按 started_at DESC 排序。 """ conn = get_connection() try: with conn.cursor() as cur: cur.execute( """ SELECT id, task_codes, status, started_at, finished_at, duration_ms, exit_code FROM task_execution_log WHERE site_id = %s ORDER BY started_at DESC LIMIT %s """, (user.site_id, _RECENT_RUNS_LIMIT), ) rows = cur.fetchall() return [ RecentRun( id=str(row[0]), task_codes=list(row[1]) if row[1] else [], status=row[2], started_at=str(row[3]), finished_at=str(row[4]) if row[4] is not None else None, duration_ms=row[5], exit_code=row[6], ) for row in rows ] except OperationalError as exc: logger.error("执行记录查询连接错误: %s", exc) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="数据库连接错误", ) finally: conn.close()