|
|
@@ -9,6 +9,7 @@ import sys
|
|
|
from datetime import datetime, timedelta
|
|
|
from pathlib import Path
|
|
|
from typing import Any, Literal, Optional
|
|
|
+from zoneinfo import ZoneInfo
|
|
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
|
from pydantic import BaseModel
|
|
|
@@ -51,10 +52,13 @@ DEMAND_SCHEDULER_ENABLED: bool = os.getenv("DEMAND_SCHEDULER_ENABLED", "1").stri
|
|
|
|
|
|
DEMAND_SCHEDULER_START_HOUR: int = 2
|
|
|
|
|
|
+# 定时任务统一使用北京时间,避免服务器时区(如 UTC)带来的偏差
|
|
|
+BEIJING_TZ = ZoneInfo("Asia/Shanghai")
|
|
|
+
|
|
|
|
|
|
def _get_today_time_window(now: datetime) -> tuple[datetime, datetime]:
|
|
|
"""返回今天的 [start, end) 时间窗口(本地时区)。"""
|
|
|
- start_of_today = datetime(year=now.year, month=now.month, day=now.day)
|
|
|
+ start_of_today = datetime(year=now.year, month=now.month, day=now.day, tzinfo=now.tzinfo)
|
|
|
end_of_today = start_of_today + timedelta(days=1)
|
|
|
return start_of_today, end_of_today
|
|
|
|
|
|
@@ -99,6 +103,9 @@ def _today_has_status_0_or_1(cluster_name: str, platform_type: str, now: datetim
|
|
|
- 若存在 status 为 0 或 1 的记录,则跳过
|
|
|
"""
|
|
|
start_of_today, end_of_today = _get_today_time_window(now)
|
|
|
+ # MySQL DATETIME 一般按无时区存储,这里使用北京时间对应的“本地时间”窗口做过滤
|
|
|
+ start_of_today_naive = start_of_today.replace(tzinfo=None)
|
|
|
+ end_of_today_naive = end_of_today.replace(tzinfo=None)
|
|
|
return mysql_db.exists(
|
|
|
"demand_task",
|
|
|
where=(
|
|
|
@@ -111,8 +118,8 @@ def _today_has_status_0_or_1(cluster_name: str, platform_type: str, now: datetim
|
|
|
where_params=(
|
|
|
str(cluster_name)[:32],
|
|
|
str(platform_type)[:32],
|
|
|
- start_of_today,
|
|
|
- end_of_today,
|
|
|
+ start_of_today_naive,
|
|
|
+ end_of_today_naive,
|
|
|
),
|
|
|
)
|
|
|
|
|
|
@@ -126,7 +133,7 @@ async def demand_scheduled_run_once() -> None:
|
|
|
if not DEMAND_SCHEDULE_CLUSTER_PLATFORM_LIST:
|
|
|
return
|
|
|
|
|
|
- now = datetime.now()
|
|
|
+ now = datetime.now(BEIJING_TZ)
|
|
|
for item in DEMAND_SCHEDULE_CLUSTER_PLATFORM_LIST:
|
|
|
cluster_name = item.get("cluster_name")
|
|
|
platform_type = item.get("platform_type")
|
|
|
@@ -204,11 +211,11 @@ async def _start_demand_scheduler() -> None:
|
|
|
print("[scheduler] apscheduler 未安装,跳过定时任务启动")
|
|
|
return
|
|
|
|
|
|
- scheduler = AsyncIOScheduler()
|
|
|
+ scheduler = AsyncIOScheduler(timezone=BEIJING_TZ)
|
|
|
# 02:00 - 23:30:每 30 分钟一次
|
|
|
scheduler.add_job(
|
|
|
func=_demand_scheduler_job,
|
|
|
- trigger=CronTrigger(hour=f"{DEMAND_SCHEDULER_START_HOUR}-23", minute="0,30"),
|
|
|
+ trigger=CronTrigger(hour=f"{DEMAND_SCHEDULER_START_HOUR}-23", minute="0,30", timezone=BEIJING_TZ),
|
|
|
id="demand_scheduler_job_main",
|
|
|
replace_existing=True,
|
|
|
max_instances=1,
|
|
|
@@ -217,7 +224,7 @@ async def _start_demand_scheduler() -> None:
|
|
|
# 24:00(即下一天 00:00):每天一次
|
|
|
scheduler.add_job(
|
|
|
func=_demand_scheduler_job,
|
|
|
- trigger=CronTrigger(hour="0", minute="0"),
|
|
|
+ trigger=CronTrigger(hour="0", minute="0", timezone=BEIJING_TZ),
|
|
|
id="demand_scheduler_job_midnight",
|
|
|
replace_existing=True,
|
|
|
max_instances=1,
|