5cb64f181a
Dashboard (M3):
- 路由 /api/dashboard/{daily,history,today,datasets,stats}/
- daily_summary: 按日期聚合 ok/warning/error/blocked
- history: 同步历史详情(按 task / 日期过滤)
- today: 今日 KPI + 任务列表
- datasets: dataset_registry + 最近 7d run 合并
- stats: 各表行数 + 日期范围(17 张表)
HTML SPA: app/api/static/dashboard.html
- 5 张状态卡片 + 7 天趋势表 + 任务状态表 + 数据规模表 + 历史详情
- 自动 30s 刷新, 纯 vanilla JS (无框架依赖)
Config (M5):
- 路由 /api/config/{GET list, GET key, PUT upsert, DELETE}/
- 凭证类自动脱敏 (MAIRUI_LICENCE / XUEQIU_TOKEN / MAIRUI_RPS_LIMIT)
- POST /api/config/reload: 把 .env / 环境变量已知项回填到 config 表
- 分类过滤 + key 搜索 + 凭证提示
HTML SPA: app/api/static/config.html
- 列表 + 编辑弹窗 + 删除确认
- 凭证类编辑时强制重新输入完整值(避免显示脱敏误以为是新值)
- 一键从环境变量回填
FastAPI 主入口挂载:
- include_router(dashboard, config)
- mount('/static', StaticFiles) 提供 HTML
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
"""FastAPI 入口 + 启动钩子。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# 确保项目根在 sys.path
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
if str(_PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from app.core.config import settings
|
|
from app.core.utils.logging import get_logger, setup_logging
|
|
from app.api.routes import health, sync, datasources, schedule, dashboard, config
|
|
|
|
setup_logging()
|
|
logger = get_logger("api")
|
|
|
|
app = FastAPI(
|
|
title="Market Data Sync API",
|
|
version="0.1.0",
|
|
description="股票市场数据定时同步与管理",
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(health.router)
|
|
app.include_router(sync.router)
|
|
app.include_router(datasources.router)
|
|
app.include_router(schedule.router)
|
|
app.include_router(dashboard.router)
|
|
app.include_router(config.router)
|
|
|
|
# 看板 HTML 静态文件
|
|
import pathlib
|
|
_static_dir = pathlib.Path(__file__).resolve().parent / "static"
|
|
if _static_dir.exists():
|
|
app.mount("/static", StaticFiles(directory=str(_static_dir)), name="static")
|
|
|
|
|
|
# ── 启动钩子 ──────────────────────────────────────────────────────────
|
|
|
|
|
|
@app.on_event("startup")
|
|
def _startup():
|
|
logger.info("[startup] market_data_sync 启动")
|
|
|
|
# 1. 注册数据源
|
|
from app.core.datasource.registry import build_default_registry
|
|
from app.core.datasource.base import registry
|
|
build_default_registry()
|
|
logger.info(f"[startup] 已注册 {len(registry.all())} 个数据源")
|
|
|
|
# 2. seed 数据源定义到 config 表
|
|
from app.core.datasource.registry import seed_datasource_configs
|
|
seed_datasource_configs()
|
|
|
|
# 3. seed 默认数据集注册
|
|
from app.core.sync.registry import seed_sync_registry
|
|
seed_sync_registry()
|
|
|
|
# 4. 启动时恢复卡死的 sync 任务
|
|
from app.core.sync.registry import recover_interrupted_syncs
|
|
n = recover_interrupted_syncs()
|
|
if n > 0:
|
|
logger.warning(f"[startup] 恢复了 {n} 个中断的同步任务")
|
|
|
|
# 5. seed 节假日
|
|
from app.core.db import ops as db_ops
|
|
if settings.holidays_list:
|
|
db_ops.upsert_config(
|
|
"trading_calendar_holidays",
|
|
json.dumps(settings.holidays_list, ensure_ascii=False),
|
|
category="general",
|
|
description="A 股休市日",
|
|
)
|
|
|
|
# 6. seed 默认计划任务
|
|
if settings.scheduler_auto_seed:
|
|
from app.core.scheduler.scheduler import seed_schedule_configs
|
|
seed_schedule_configs()
|
|
|
|
# 7. 注册 sync job + 启动调度器
|
|
from app.core.scheduler.scheduler import register_sync_jobs, start_scheduler
|
|
register_sync_jobs()
|
|
start_scheduler()
|
|
|
|
# 8. 启动数据源健康监控
|
|
from app.core.datasource.registry import start_health_monitor
|
|
start_health_monitor()
|
|
|
|
logger.info("[startup] 完成")
|