采集任务支持手动触发 + 定时调度(cron)
后端: - 新增 Scheduler + ScheduledTask 核心模块(croniter 解析 cron 表达式) - 新增 Schedule 数据模型(alembic 0016) - 新增 /api/v1/admin/schedules CRUD 接口 - lifespan 启动时注册默认定时任务并加载数据库配置 - 默认任务:每日比赛/积分榜/30分钟统计回填(默认禁用) 前端: - 设置页新增「定时任务」管理区块 - 支持:新建/启用禁用/立即执行/删除定时任务 - 显示:任务ID、类型、cron表达式、上次执行时间/状态 依赖:新增 croniter>=2.0 Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
1fc799a5b0
commit
0a5a14cbbb
@@ -22,11 +22,43 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
migrate_plaintext_sensitive_settings,
|
||||
)
|
||||
from src.core.security_check import assert_security_on_startup
|
||||
from src.core.scheduler import scheduler
|
||||
from src.api.routes.schedules import _run_scheduled_task
|
||||
from src.data.config import BZZOIRO_LEAGUE_IDS
|
||||
await init_db() # 验证连接,不建表
|
||||
await migrate_plaintext_sensitive_settings() # 明文敏感配置 → 加密(幂等)
|
||||
await ensure_admin_password_hashed() # .env 明文密码 → scrypt 哈希(幂等)
|
||||
await assert_security_on_startup() # 启动安全校验(生产拒绝/开发警告)
|
||||
|
||||
# 注册默认定时任务(如果数据库中没有)
|
||||
from src.db.base import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from src.db.models import Schedule
|
||||
async with AsyncSessionLocal() as session:
|
||||
existing = (await session.execute(select(Schedule.id))).scalars().all()
|
||||
if "daily-events" not in existing:
|
||||
session.add(Schedule(id="daily-events", task="events", cron="0 8 * * *", enabled=False))
|
||||
if "daily-standings" not in existing:
|
||||
session.add(Schedule(id="daily-standings", task="standings", cron="0 9 * * *", enabled=False))
|
||||
if "daily-stats" not in existing:
|
||||
session.add(Schedule(id="daily-stats", task="stats", cron="*/30 * * * *", enabled=False))
|
||||
await session.commit()
|
||||
|
||||
# 从数据库加载所有启用的定时任务
|
||||
async with AsyncSessionLocal() as session:
|
||||
schedules = (await session.execute(select(Schedule).where(Schedule.enabled))).scalars().all()
|
||||
for s in schedules:
|
||||
leagues = s.leagues.split(",") if s.leagues else list(BZZOIRO_LEAGUE_IDS.keys())
|
||||
scheduler.register(
|
||||
s.id, s.task,
|
||||
lambda sid=s.id: _run_scheduled_task(sid),
|
||||
enabled=s.enabled,
|
||||
)
|
||||
|
||||
await scheduler.start()
|
||||
logger.info("应用启动完成")
|
||||
yield
|
||||
await scheduler.stop()
|
||||
await close_client()
|
||||
|
||||
|
||||
@@ -62,6 +94,7 @@ def create_app() -> FastAPI:
|
||||
from src.api.routes.backtest import router as backtest_router
|
||||
from src.api.routes.auth import router as auth_router
|
||||
from src.api.routes.admin_settings import router as admin_settings_router
|
||||
from src.api.routes.schedules import router as schedules_router
|
||||
|
||||
app.include_router(matches_router)
|
||||
app.include_router(predict_router)
|
||||
@@ -70,6 +103,7 @@ def create_app() -> FastAPI:
|
||||
app.include_router(backtest_router)
|
||||
app.include_router(auth_router)
|
||||
app.include_router(admin_settings_router)
|
||||
app.include_router(schedules_router)
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
|
||||
Reference in New Issue
Block a user