refactor(project): use unified utcnow

This commit is contained in:
MingxuanGame
2025-08-22 11:27:45 +00:00
parent da66420eaa
commit 9b00dbda28
49 changed files with 201 additions and 167 deletions

View File

@@ -6,11 +6,12 @@ Beatmap缓存预取服务
from __future__ import annotations
import asyncio
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from typing import TYPE_CHECKING
from app.config import settings
from app.log import logger
from app.utils import utcnow
from redis.asyncio import Redis
from sqlmodel import col, func, select
@@ -40,7 +41,7 @@ class BeatmapCacheService:
logger.info(f"Starting preload of top {limit} popular beatmaps")
# 获取过去24小时内最热门的beatmap
recent_time = datetime.now(UTC) - timedelta(hours=24)
recent_time = utcnow() - timedelta(hours=24)
from app.database.score import Score

View File

@@ -1,19 +1,20 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from app.database import RankHistory, UserStatistics
from app.database.rank_history import RankTop
from app.dependencies.database import with_db
from app.dependencies.scheduler import get_scheduler
from app.models.score import GameMode
from app.utils import utcnow
from sqlmodel import col, exists, select, update
@get_scheduler().scheduled_job("cron", hour=0, minute=0, second=0, id="calculate_user_rank")
async def calculate_user_rank(is_today: bool = False):
today = datetime.now(UTC).date()
today = utcnow().date()
target_date = today if is_today else today - timedelta(days=1)
async with with_db() as session:
for gamemode in GameMode:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import UTC, timedelta
import json
from math import ceil
@@ -17,7 +17,7 @@ from app.log import logger
from app.models.metadata_hub import DailyChallengeInfo
from app.models.mods import APIMod, get_available_mods
from app.models.room import RoomCategory
from app.utils import are_same_weeks
from app.utils import are_same_weeks, utcnow
from .room import create_playlist_room
@@ -32,7 +32,7 @@ async def create_daily_challenge_room(
allowed_mods: list[APIMod] = [],
) -> Room:
async with with_db() as session:
today = datetime.now(UTC).date()
today = utcnow().date()
return await create_playlist_room(
session=session,
name=str(today),
@@ -57,7 +57,7 @@ async def create_daily_challenge_room(
async def daily_challenge_job():
from app.signalr.hub import MetadataHubs
now = datetime.now(UTC)
now = utcnow()
redis = get_redis()
key = f"daily_challenge:{now.date()}"
if not await redis.exists(key):
@@ -67,7 +67,7 @@ async def daily_challenge_job():
await session.exec(
select(Room).where(
Room.category == RoomCategory.DAILY_CHALLENGE,
col(Room.ends_at) > datetime.now(UTC),
col(Room.ends_at) > utcnow(),
)
)
).first()
@@ -87,7 +87,7 @@ async def daily_challenge_job():
get_scheduler().add_job(
daily_challenge_job,
"date",
run_date=datetime.now(UTC) + timedelta(minutes=5),
run_date=utcnow() + timedelta(minutes=5),
)
return
@@ -121,14 +121,14 @@ async def daily_challenge_job():
get_scheduler().add_job(
daily_challenge_job,
"date",
run_date=datetime.now(UTC) + timedelta(minutes=5),
run_date=utcnow() + timedelta(minutes=5),
)
@get_scheduler().scheduled_job("cron", hour=0, minute=1, second=0, id="daily_challenge_last_top")
async def process_daily_challenge_top():
async with with_db() as session:
now = datetime.now(UTC)
now = utcnow()
room = (
await session.exec(
select(Room).where(

View File

@@ -4,10 +4,11 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from app.database.email_verification import EmailVerification, LoginSession
from app.log import logger
from app.utils import utcnow
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -29,7 +30,7 @@ class DatabaseCleanupService:
"""
try:
# 查找过期的验证码记录
current_time = datetime.now(UTC)
current_time = utcnow()
stmt = select(EmailVerification).where(EmailVerification.expires_at < current_time)
result = await db.exec(stmt)
@@ -66,7 +67,7 @@ class DatabaseCleanupService:
"""
try:
# 查找过期的登录会话记录
current_time = datetime.now(UTC)
current_time = utcnow()
stmt = select(LoginSession).where(LoginSession.expires_at < current_time)
result = await db.exec(stmt)
@@ -104,7 +105,7 @@ class DatabaseCleanupService:
"""
try:
# 查找指定天数前的已使用验证码记录
cutoff_time = datetime.now(UTC) - timedelta(days=days_old)
cutoff_time = utcnow() - timedelta(days=days_old)
stmt = select(EmailVerification).where(col(EmailVerification.is_used).is_(True))
result = await db.exec(stmt)
@@ -147,7 +148,7 @@ class DatabaseCleanupService:
"""
try:
# 查找指定天数前的已验证会话记录
cutoff_time = datetime.now(UTC) - timedelta(days=days_old)
cutoff_time = utcnow() - timedelta(days=days_old)
stmt = select(LoginSession).where(col(LoginSession.is_verified).is_(True))
result = await db.exec(stmt)
@@ -225,7 +226,7 @@ class DatabaseCleanupService:
dict: 统计信息
"""
try:
current_time = datetime.now(UTC)
current_time = utcnow()
cutoff_7_days = current_time - timedelta(days=7)
cutoff_30_days = current_time - timedelta(days=30)

View File

@@ -4,7 +4,7 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
import secrets
import string
@@ -12,6 +12,7 @@ from app.config import settings
from app.database.email_verification import EmailVerification, LoginSession
from app.log import logger
from app.service.email_queue import email_queue # 导入邮件队列
from app.utils import utcnow
from redis.asyncio import Redis
from sqlmodel import col, select
@@ -200,7 +201,7 @@ This email was sent automatically, please do not reply.
select(EmailVerification).where(
EmailVerification.user_id == user_id,
col(EmailVerification.is_used).is_(False),
EmailVerification.expires_at > datetime.now(UTC),
EmailVerification.expires_at > utcnow(),
)
)
existing = existing_result.first()
@@ -217,7 +218,7 @@ This email was sent automatically, please do not reply.
user_id=user_id,
email=email,
verification_code=code,
expires_at=datetime.now(UTC) + timedelta(minutes=10), # 10分钟过期
expires_at=utcnow() + timedelta(minutes=10), # 10分钟过期
ip_address=ip_address,
user_agent=user_agent,
)
@@ -306,7 +307,7 @@ This email was sent automatically, please do not reply.
EmailVerification.user_id == user_id,
EmailVerification.verification_code == code,
col(EmailVerification.is_used).is_(False),
EmailVerification.expires_at > datetime.now(UTC),
EmailVerification.expires_at > utcnow(),
)
)
@@ -316,7 +317,7 @@ This email was sent automatically, please do not reply.
# 标记为已使用
verification.is_used = True
verification.used_at = datetime.now(UTC)
verification.used_at = utcnow()
# 同时更新对应的登录会话状态
await LoginSessionService.mark_session_verified(db, user_id)
@@ -397,7 +398,7 @@ class LoginSessionService:
user_agent=None,
country_code=country_code,
is_new_location=is_new_location,
expires_at=datetime.now(UTC) + timedelta(hours=24), # 24小时过期
expires_at=utcnow() + timedelta(hours=24), # 24小时过期
is_verified=not is_new_location, # 新位置需要验证
)
@@ -446,7 +447,7 @@ class LoginSessionService:
session = result.first()
if session:
session.is_verified = True
session.verified_at = datetime.now(UTC)
session.verified_at = utcnow()
await db.commit()
logger.info(f"[Login Session] User {user_id} session verification successful")
@@ -463,7 +464,7 @@ class LoginSessionService:
"""检查是否为新位置登录"""
try:
# 查看过去30天内是否有相同IP或相同国家的登录记录
thirty_days_ago = datetime.now(UTC) - timedelta(days=30)
thirty_days_ago = utcnow() - timedelta(days=30)
result = await db.exec(
select(LoginSession).where(
@@ -492,7 +493,7 @@ class LoginSessionService:
select(LoginSession).where(
LoginSession.user_id == user_id,
col(LoginSession.is_verified).is_(False),
LoginSession.expires_at > datetime.now(UTC),
LoginSession.expires_at > utcnow(),
)
)
@@ -501,7 +502,7 @@ class LoginSessionService:
# 标记所有会话为已验证
for session in sessions:
session.is_verified = True
session.verified_at = datetime.now(UTC)
session.verified_at = utcnow()
if sessions:
logger.info(f"[Login Session] Marked {len(sessions)} session(s) as verified for user {user_id}")

View File

@@ -16,6 +16,7 @@ from app.router.private.stats import (
_get_playing_users_count,
_redis_exec,
)
from app.utils import utcnow
# Redis keys for interval statistics
INTERVAL_STATS_BASE_KEY = "server:interval_stats"
@@ -34,7 +35,7 @@ class IntervalInfo:
def is_current(self) -> bool:
"""检查是否是当前区间"""
now = datetime.utcnow()
now = utcnow()
return self.start_time <= now < self.end_time
def to_dict(self) -> dict:
@@ -101,7 +102,7 @@ class EnhancedIntervalStatsManager:
@staticmethod
def get_current_interval_boundaries() -> tuple[datetime, datetime]:
"""获取当前30分钟区间的边界"""
now = datetime.utcnow()
now = utcnow()
# 计算区间开始时间向下取整到最近的30分钟
minute = (now.minute // 30) * 30
start_time = now.replace(minute=minute, second=0, microsecond=0)
@@ -157,7 +158,7 @@ class EnhancedIntervalStatsManager:
peak_online_count=0,
peak_playing_count=0,
total_samples=0,
created_at=datetime.utcnow(),
created_at=utcnow(),
)
await _redis_exec(
@@ -195,7 +196,7 @@ class EnhancedIntervalStatsManager:
needed_points = 48 - history_length
# 从当前时间往前推创建缺失的时间点都填充为0
current_time = datetime.utcnow() # noqa: F841
current_time = utcnow() # noqa: F841
current_interval_start, _ = EnhancedIntervalStatsManager.get_current_interval_boundaries()
# 从当前区间开始往前推创建历史数据点确保时间对齐到30分钟边界
@@ -323,7 +324,7 @@ class EnhancedIntervalStatsManager:
peak_online_count=current_online,
peak_playing_count=current_playing,
total_samples=1,
created_at=datetime.utcnow(),
created_at=utcnow(),
)
# 更新独特用户数
@@ -431,7 +432,7 @@ class EnhancedIntervalStatsManager:
try:
# 删除过期的区间统计数据超过2小时的
cutoff_time = datetime.utcnow() - timedelta(hours=2)
cutoff_time = utcnow() - timedelta(hours=2)
pattern = f"{INTERVAL_STATS_BASE_KEY}:*"
keys = await redis_async.keys(pattern)

View File

@@ -5,11 +5,11 @@
from __future__ import annotations
import asyncio
from datetime import datetime
from app.database.user_login_log import UserLoginLog
from app.dependencies.geoip import get_client_ip, get_geoip_helper, normalize_ip
from app.log import logger
from app.utils import utcnow
from fastapi import Request
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -56,7 +56,7 @@ class LoginLogService:
user_id=user_id,
ip_address=ip_address,
user_agent=user_agent,
login_time=datetime.utcnow(),
login_time=utcnow(),
login_success=login_success,
login_method=login_method,
notes=notes,

View File

@@ -6,11 +6,10 @@
from __future__ import annotations
from datetime import datetime
from app.dependencies.database import get_redis
from app.log import logger
from app.router.private.stats import add_online_user
from app.utils import utcnow
class OnlineStatusManager:
@@ -37,7 +36,7 @@ class OnlineStatusManager:
# 3. 设置最后活跃时间戳
last_seen_key = f"user:last_seen:{user_id}"
await redis.set(last_seen_key, int(datetime.utcnow().timestamp()), ex=7200)
await redis.set(last_seen_key, int(utcnow().timestamp()), ex=7200)
logger.debug(f"[OnlineStatusManager] User {user_id} set online via {hub_type}")
@@ -62,7 +61,7 @@ class OnlineStatusManager:
# 刷新最后活跃时间
last_seen_key = f"user:last_seen:{user_id}"
await redis.set(last_seen_key, int(datetime.utcnow().timestamp()), ex=7200)
await redis.set(last_seen_key, int(utcnow().timestamp()), ex=7200)
logger.debug(f"[OnlineStatusManager] Refreshed online status for user {user_id}")

View File

@@ -4,7 +4,7 @@
from __future__ import annotations
from datetime import UTC, datetime
from datetime import datetime
import json
import secrets
import string
@@ -15,6 +15,7 @@ from app.dependencies.database import with_db
from app.log import logger
from app.service.email_queue import email_queue # 导入邮件队列
from app.service.email_service import EmailService
from app.utils import utcnow
from redis.asyncio import Redis
from sqlmodel import select
@@ -88,7 +89,7 @@ class PasswordResetService:
"user_id": user.id,
"email": email,
"reset_code": reset_code,
"created_at": datetime.now(UTC).isoformat(),
"created_at": utcnow().isoformat(),
"ip_address": ip_address,
"user_agent": user_agent,
"used": False,
@@ -346,7 +347,7 @@ class PasswordResetService:
try:
# 先标记验证码为已使用(在数据库操作之前)
reset_data["used"] = True
reset_data["used_at"] = datetime.now(UTC).isoformat()
reset_data["used_at"] = utcnow().isoformat()
# 保存用户ID用于日志记录
user_id = user.id
@@ -391,7 +392,7 @@ class PasswordResetService:
# 计算剩余的TTL时间
created_at = datetime.fromisoformat(reset_data.get("created_at", ""))
elapsed = (datetime.now(UTC) - created_at).total_seconds()
elapsed = (utcnow() - created_at).total_seconds()
remaining_ttl = max(0, 600 - int(elapsed)) # 600秒总过期时间
if remaining_ttl > 0:

View File

@@ -6,7 +6,7 @@
from __future__ import annotations
import asyncio
from datetime import UTC, datetime
from datetime import datetime
import json
from typing import TYPE_CHECKING, Literal
@@ -14,6 +14,7 @@ from app.config import settings
from app.database.statistics import UserStatistics, UserStatisticsResp
from app.log import logger
from app.models.score import GameMode
from app.utils import utcnow
from redis.asyncio import Redis
from sqlmodel import col, select
@@ -258,7 +259,7 @@ class RankingCacheService:
# 计算统计信息
stats = {
"total_users": total_users,
"last_updated": datetime.now(UTC).isoformat(),
"last_updated": utcnow().isoformat(),
"type": type,
"ruleset": ruleset,
"country": country,
@@ -370,7 +371,7 @@ class RankingCacheService:
# 计算统计信息
stats = {
"total_countries": len(country_stats_list),
"last_updated": datetime.now(UTC).isoformat(),
"last_updated": utcnow().isoformat(),
"ruleset": ruleset,
}

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from app.database.beatmap import Beatmap
from app.database.chat import ChannelType, ChatChannel
@@ -8,6 +8,7 @@ from app.database.playlists import Playlist
from app.database.room import APIUploadedRoom, Room
from app.dependencies.fetcher import get_fetcher
from app.models.room import MatchType, QueueMode, RoomCategory, RoomStatus
from app.utils import utcnow
from sqlalchemy import exists
from sqlmodel import col, select
@@ -17,7 +18,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
async def create_playlist_room_from_api(session: AsyncSession, room: APIUploadedRoom, host_id: int) -> Room:
db_room = room.to_room()
db_room.host_id = host_id
db_room.starts_at = datetime.now(UTC)
db_room.starts_at = utcnow()
db_room.ends_at = db_room.starts_at + timedelta(minutes=db_room.duration if db_room.duration is not None else 0)
session.add(db_room)
await session.commit()
@@ -52,8 +53,8 @@ async def create_playlist_room(
name=name,
category=category,
duration=duration,
starts_at=datetime.now(UTC),
ends_at=datetime.now(UTC) + timedelta(minutes=duration),
starts_at=utcnow(),
ends_at=utcnow() + timedelta(minutes=duration),
participant_count=0,
max_attempts=max_attempts,
type=MatchType.PLAYLISTS,

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import datetime, timedelta
from datetime import timedelta
from app.dependencies.database import get_redis, get_redis_message
from app.log import logger
@@ -9,6 +9,7 @@ from app.router.private.stats import (
REDIS_PLAYING_USERS_KEY,
_redis_exec,
)
from app.utils import utcnow
async def cleanup_stale_online_users() -> tuple[int, int]:
@@ -25,7 +26,7 @@ async def cleanup_stale_online_users() -> tuple[int, int]:
playing_users = await _redis_exec(redis_sync.smembers, REDIS_PLAYING_USERS_KEY)
# 检查在线用户的最后活动时间
current_time = datetime.utcnow()
current_time = utcnow()
stale_threshold = current_time - timedelta(hours=2) # 2小时无活动视为过期 # noqa: F841
# 对于在线用户我们检查metadata在线标记

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
from datetime import timedelta
from app.log import logger
from app.router.private.stats import record_hourly_stats, update_registered_users_count
@@ -10,6 +10,7 @@ from app.service.stats_cleanup import (
cleanup_stale_online_users,
refresh_redis_key_expiry,
)
from app.utils import utcnow
class StatsScheduler:
@@ -60,7 +61,7 @@ class StatsScheduler:
while self._running:
try:
# 计算下次区间结束时间
now = datetime.utcnow()
now = utcnow()
# 计算当前区间的结束时间
current_minute = (now.minute // 30) * 30
@@ -93,15 +94,11 @@ class StatsScheduler:
# 完成当前区间并记录到历史
finalized_stats = await EnhancedIntervalStatsManager.finalize_interval()
if finalized_stats:
logger.info(
f"Finalized enhanced interval statistics at {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}"
)
logger.info(f"Finalized enhanced interval statistics at {utcnow().strftime('%Y-%m-%d %H:%M:%S')}")
else:
# 如果区间完成失败,使用原有方式记录
await record_hourly_stats()
logger.info(
f"Recorded hourly statistics (fallback) at {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}"
)
logger.info(f"Recorded hourly statistics (fallback) at {utcnow().strftime('%Y-%m-%d %H:%M:%S')}")
# 开始新的区间统计
await EnhancedIntervalStatsManager.initialize_current_interval()