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

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
import hashlib
import re
import secrets
@@ -12,6 +12,7 @@ from app.database import (
User,
)
from app.log import logger
from app.utils import utcnow
import bcrypt
from jose import JWTError, jwt
@@ -150,9 +151,9 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None) -> s
"""创建访问令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(UTC) + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.now(UTC) + timedelta(minutes=settings.access_token_expire_minutes)
expire = utcnow() + timedelta(minutes=settings.access_token_expire_minutes)
to_encode.update({"exp": expire, "random": secrets.token_hex(16)})
encoded_jwt = jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
@@ -206,7 +207,7 @@ async def store_token(
expires_in: int,
) -> OAuthToken:
"""存储令牌到数据库"""
expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
expires_at = utcnow() + timedelta(seconds=expires_in)
# 删除用户的旧令牌
statement = select(OAuthToken).where(OAuthToken.user_id == user_id, OAuthToken.client_id == client_id)
@@ -238,7 +239,7 @@ async def get_token_by_access_token(db: AsyncSession, access_token: str) -> OAut
"""根据访问令牌获取令牌记录"""
statement = select(OAuthToken).where(
OAuthToken.access_token == access_token,
OAuthToken.expires_at > datetime.utcnow(),
OAuthToken.expires_at > utcnow(),
)
return (await db.exec(statement)).first()
@@ -247,7 +248,7 @@ async def get_token_by_refresh_token(db: AsyncSession, refresh_token: str) -> OA
"""根据刷新令牌获取令牌记录"""
statement = select(OAuthToken).where(
OAuthToken.refresh_token == refresh_token,
OAuthToken.expires_at > datetime.utcnow(),
OAuthToken.expires_at > utcnow(),
)
return (await db.exec(statement)).first()

View File

@@ -1,10 +1,11 @@
from datetime import UTC, datetime
from datetime import datetime
from typing import TYPE_CHECKING
from app.config import settings
from app.models.achievement import MEDALS, Achievement
from app.models.model import UTCBaseModel
from app.models.notification import UserAchievementUnlock
from app.utils import utcnow
from .events import Event, EventType
@@ -28,7 +29,7 @@ if TYPE_CHECKING:
class UserAchievementBase(SQLModel, UTCBaseModel):
achievement_id: int
achieved_at: datetime = Field(default=datetime.now(UTC), sa_column=Column(DateTime(timezone=True)))
achieved_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime(timezone=True)))
class UserAchievement(UserAchievementBase, table=True):
@@ -56,7 +57,7 @@ async def process_achievements(session: AsyncSession, redis: Redis, score_id: in
).all()
not_achieved = {k: v for k, v in MEDALS.items() if k.id not in achieved}
result: list[Achievement] = []
now = datetime.now(UTC)
now = utcnow()
for k, v in not_achieved.items():
if await v(session, score, score.beatmap):
result.append(k)

View File

@@ -3,6 +3,7 @@ import secrets
from typing import TYPE_CHECKING
from app.models.model import UTCBaseModel
from app.utils import utcnow
from sqlalchemy import Column, DateTime
from sqlmodel import (
@@ -30,7 +31,7 @@ class OAuthToken(UTCBaseModel, SQLModel, table=True):
token_type: str = Field(default="Bearer", max_length=20)
scope: str = Field(default="*", max_length=100)
expires_at: datetime = Field(sa_column=Column(DateTime))
created_at: datetime = Field(default_factory=datetime.utcnow, sa_column=Column(DateTime))
created_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
user: "User" = Relationship()

View File

@@ -1,7 +1,7 @@
from datetime import UTC, datetime
from typing import TYPE_CHECKING
from app.database.events import Event, EventType
from app.utils import utcnow
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncAttrs
@@ -71,7 +71,7 @@ async def process_beatmap_playcount(session: AsyncSession, user_id: int, beatmap
existing_playcount.playcount += 1
if existing_playcount.playcount % 100 == 0:
playcount_event = Event(
created_at=datetime.now(UTC),
created_at=utcnow(),
type=EventType.BEATMAP_PLAYCOUNT,
user_id=user_id,
)

View File

@@ -1,9 +1,10 @@
from datetime import UTC, datetime
from datetime import datetime
from enum import Enum
from typing import Self
from app.database.lazer_user import RANKING_INCLUDES, User, UserResp
from app.models.model import UTCBaseModel
from app.utils import utcnow
from pydantic import BaseModel
from redis.asyncio import Redis
@@ -170,7 +171,7 @@ class ChatMessageBase(UTCBaseModel, SQLModel):
content: str = Field(sa_column=Column(VARCHAR(1000)))
message_id: int = Field(index=True, primary_key=True, default=None)
sender_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True))
timestamp: datetime = Field(sa_column=Column(DateTime, index=True), default=datetime.now(UTC))
timestamp: datetime = Field(sa_column=Column(DateTime, index=True), default_factory=utcnow)
type: MessageType = Field(default=MessageType.PLAIN, index=True, exclude=True)
uuid: str | None = Field(default=None)
@@ -208,7 +209,7 @@ class SilenceUser(UTCBaseModel, SQLModel, table=True):
channel_id: int = Field(foreign_key="chat_channels.channel_id", index=True)
until: datetime | None = Field(sa_column=Column(DateTime, index=True), default=None)
reason: str | None = Field(default=None, sa_column=Column(VARCHAR(255), index=True))
banned_at: datetime = Field(sa_column=Column(DateTime, index=True), default=datetime.now(UTC))
banned_at: datetime = Field(sa_column=Column(DateTime, index=True), default_factory=utcnow)
class UserSilenceResp(SQLModel):

View File

@@ -2,7 +2,7 @@ from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING
from app.models.model import UTCBaseModel
from app.utils import are_adjacent_weeks
from app.utils import are_adjacent_weeks, utcnow
from sqlmodel import (
BigInteger,
@@ -79,7 +79,7 @@ async def process_daily_challenge_score(session: AsyncSession, user_id: int, roo
session.add(stats)
stats.playcount += 1
now = datetime.now(UTC)
now = utcnow()
if stats.last_update is None:
stats.daily_streak_best = 1
stats.daily_streak_current = 1

View File

@@ -4,7 +4,9 @@
from __future__ import annotations
from datetime import UTC, datetime
from datetime import datetime
from app.utils import utcnow
from sqlalchemy import BigInteger, Column, ForeignKey
from sqlmodel import Field, SQLModel
@@ -19,7 +21,7 @@ class EmailVerification(SQLModel, table=True):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), nullable=False, index=True))
email: str = Field(index=True)
verification_code: str = Field(max_length=8) # 8位验证码
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
created_at: datetime = Field(default_factory=utcnow)
expires_at: datetime = Field() # 验证码过期时间
is_used: bool = Field(default=False) # 是否已使用
used_at: datetime | None = Field(default=None)
@@ -39,7 +41,7 @@ class LoginSession(SQLModel, table=True):
user_agent: str | None = Field(default=None, max_length=250)
country_code: str | None = Field(default=None)
is_verified: bool = Field(default=False) # 是否已验证
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
created_at: datetime = Field(default_factory=lambda: utcnow())
verified_at: datetime | None = Field(default=None)
expires_at: datetime = Field() # 会话过期时间
is_new_location: bool = Field(default=False) # 是否新位置登录

View File

@@ -3,6 +3,7 @@ from enum import Enum
from typing import TYPE_CHECKING
from app.models.model import UTCBaseModel
from app.utils import utcnow
from pydantic import model_serializer
from sqlmodel import (
@@ -40,7 +41,7 @@ class EventType(str, Enum):
class Event(UTCBaseModel, SQLModel, table=True):
__tablename__: str = "user_events"
id: int = Field(default=None, primary_key=True)
created_at: datetime = Field(sa_column=Column(DateTime(timezone=True), default=datetime.now(UTC)))
created_at: datetime = Field(sa_column=Column(DateTime(timezone=True), default_factory=utcnow))
type: EventType
event_payload: dict = Field(exclude=True, default_factory=dict, sa_column=Column(JSON))
user_id: int | None = Field(

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
import json
from typing import TYPE_CHECKING, NotRequired, TypedDict
@@ -6,6 +6,7 @@ from app.models.model import UTCBaseModel
from app.models.score import GameMode
from app.models.user import Country, Page
from app.path import STATIC_DIR
from app.utils import utcnow
from .achievement import UserAchievement, UserAchievementResp
from .beatmap_playcounts import BeatmapPlaycounts
@@ -75,7 +76,7 @@ class UserBase(UTCBaseModel, SQLModel):
is_active: bool = True
is_bot: bool = False
is_supporter: bool = False
last_visit: datetime | None = Field(default=datetime.now(UTC), sa_column=Column(DateTime(timezone=True)))
last_visit: datetime | None = Field(default_factory=utcnow, sa_column=Column(DateTime(timezone=True)))
pm_friends_only: bool = False
profile_colour: str | None = None
username: str = Field(max_length=32, unique=True, index=True)
@@ -99,7 +100,7 @@ class UserBase(UTCBaseModel, SQLModel):
discord: str | None = None
has_supported: bool = False
interests: str | None = None
join_date: datetime = Field(default=datetime.now(UTC))
join_date: datetime = Field(default_factory=utcnow)
location: str | None = None
max_blocks: int = 50
max_friends: int = 500
@@ -408,7 +409,7 @@ class UserResp(UserBase):
Score.user_id == obj.id,
Score.gamemode == ruleset,
col(Score.passed).is_(True),
Score.ended_at > datetime.now(UTC) - timedelta(hours=24),
Score.ended_at > utcnow() - timedelta(hours=24),
)
)
).one()
@@ -437,7 +438,7 @@ class UserResp(UserBase):
select(LoginSession).where(
LoginSession.user_id == obj.id,
col(LoginSession.is_verified).is_(False),
LoginSession.expires_at > datetime.now(UTC),
LoginSession.expires_at > utcnow(),
)
)
).first()

View File

@@ -1,7 +1,8 @@
from datetime import UTC, datetime
from datetime import datetime
from typing import Any
from app.models.model import UTCBaseModel
from app.utils import utcnow
from sqlmodel import (
JSON,
@@ -24,7 +25,7 @@ class MultiplayerEventBase(SQLModel, UTCBaseModel):
sa_column=Column(
DateTime(timezone=True),
),
default=datetime.now(UTC),
default_factory=utcnow,
)
event_type: str = Field(index=True)
@@ -40,7 +41,7 @@ class MultiplayerEvent(MultiplayerEventBase, table=True):
sa_column=Column(
DateTime(timezone=True),
),
default=datetime.now(UTC),
default_factory=utcnow,
)
event_detail: dict[str, Any] | None = Field(
sa_column=Column(JSON),

View File

@@ -1,7 +1,8 @@
from datetime import UTC, datetime
from datetime import datetime
from typing import Any
from app.models.notification import NotificationDetail, NotificationName
from app.utils import utcnow
from sqlmodel import (
JSON,
@@ -54,7 +55,7 @@ async def insert_notification(session: AsyncSession, detail: NotificationDetail)
object_id=detail.object_id,
source_user_id=detail.source_user_id,
details=detail.model_dump(),
created_at=datetime.now(UTC),
created_at=utcnow(),
)
session.add(notification)
await session.commit()

View File

@@ -4,7 +4,9 @@
from __future__ import annotations
from datetime import UTC, datetime
from datetime import datetime
from app.utils import utcnow
from sqlalchemy import BigInteger, Column, ForeignKey
from sqlmodel import Field, SQLModel
@@ -19,7 +21,7 @@ class PasswordReset(SQLModel, table=True):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), nullable=False, index=True))
email: str = Field(index=True)
reset_code: str = Field(max_length=8) # 8位重置验证码
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
created_at: datetime = Field(default_factory=utcnow)
expires_at: datetime = Field() # 验证码过期时间
is_used: bool = Field(default=False) # 是否已使用
used_at: datetime | None = Field(default=None)

View File

@@ -1,11 +1,10 @@
from datetime import (
UTC,
date as dt,
datetime,
)
from typing import TYPE_CHECKING, Optional
from app.models.score import GameMode
from app.utils import utcnow
from pydantic import BaseModel
from sqlmodel import (
@@ -33,7 +32,7 @@ class RankHistory(SQLModel, table=True):
mode: GameMode
rank: int
date: dt = Field(
default_factory=lambda: datetime.now(UTC).date(),
default_factory=lambda: utcnow().date(),
sa_column=Column(Date, index=True),
)
@@ -48,7 +47,7 @@ class RankTop(SQLModel, table=True):
mode: GameMode
rank: int
date: dt = Field(
default_factory=lambda: datetime.now(UTC).date(),
default_factory=lambda: utcnow().date(),
sa_column=Column(Date, index=True),
)

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime
from app.database.playlist_attempts import PlaylistAggregateScore
from app.database.room_participated_user import RoomParticipatedUser
@@ -12,6 +12,7 @@ from app.models.room import (
RoomPlaylistItemStats,
RoomStatus,
)
from app.utils import utcnow
from .lazer_user import User, UserResp
from .playlists import Playlist, PlaylistResp
@@ -39,7 +40,7 @@ class RoomBase(SQLModel, UTCBaseModel):
sa_column=Column(
DateTime(timezone=True),
),
default=datetime.now(UTC),
default_factory=utcnow,
)
ends_at: datetime | None = Field(
sa_column=Column(

View File

@@ -1,6 +1,8 @@
from datetime import UTC, datetime
from datetime import datetime
from typing import TYPE_CHECKING
from app.utils import utcnow
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlmodel import (
BigInteger,
@@ -25,7 +27,7 @@ class RoomParticipatedUser(AsyncAttrs, SQLModel, table=True):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), nullable=False))
joined_at: datetime = Field(
sa_column=Column(DateTime(timezone=True), nullable=False),
default=datetime.now(UTC),
default_factory=utcnow,
)
left_at: datetime | None = Field(sa_column=Column(DateTime(timezone=True), nullable=True), default=None)

View File

@@ -1,5 +1,5 @@
from collections.abc import Sequence
from datetime import UTC, date, datetime
from datetime import date, datetime
import json
import math
import sys
@@ -31,6 +31,7 @@ from app.models.score import (
ScoreStatistics,
SoloScoreSubmissionInfo,
)
from app.utils import utcnow
from .beatmap import Beatmap, BeatmapResp
from .beatmap_playcounts import process_beatmap_playcount
@@ -733,7 +734,7 @@ async def process_user(
if i < score_range and displaced_position > score_range and displaced_position is not None:
# Create rank lost event for the displaced user
rank_lost_event = Event(
created_at=datetime.now(UTC),
created_at=utcnow(),
type=EventType.RANK_LOST,
user_id=displaced_score.user_id,
)
@@ -843,7 +844,7 @@ async def process_score(
total_score=info.total_score,
total_score_without_mods=info.total_score_without_mods,
beatmap_id=beatmap_id,
ended_at=datetime.now(UTC),
ended_at=utcnow(),
gamemode=gamemode,
started_at=score_token.created_at,
user_id=user.id,

View File

@@ -2,6 +2,7 @@ from datetime import datetime
from app.models.model import UTCBaseModel
from app.models.score import GameMode
from app.utils import utcnow
from .beatmap import Beatmap
from .lazer_user import User
@@ -15,8 +16,8 @@ class ScoreTokenBase(SQLModel, UTCBaseModel):
score_id: int | None = Field(sa_column=Column(BigInteger), default=None)
ruleset_id: GameMode
playlist_item_id: int | None = Field(default=None) # playlist
created_at: datetime = Field(default_factory=datetime.utcnow, sa_column=Column(DateTime))
updated_at: datetime = Field(default_factory=datetime.utcnow, sa_column=Column(DateTime))
created_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
updated_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
class ScoreToken(ScoreTokenBase, table=True):

View File

@@ -1,8 +1,9 @@
from datetime import UTC, datetime, timedelta
from datetime import timedelta
import math
from typing import TYPE_CHECKING
from app.models.score import GameMode
from app.utils import utcnow
from .rank_history import RankHistory
@@ -134,7 +135,7 @@ class UserStatisticsResp(UserStatisticsBase):
rank_best = (
await session.exec(
select(func.max(RankHistory.rank)).where(
RankHistory.date > datetime.now(UTC) - timedelta(days=30),
RankHistory.date > utcnow() - timedelta(days=30),
RankHistory.user_id == obj.user_id,
)
)
@@ -171,7 +172,7 @@ async def get_rank(session: AsyncSession, statistics: UserStatistics, country: s
return None
if country is None:
today = datetime.now(UTC).date()
today = utcnow().date()
rank_history = (
await session.exec(
select(RankHistory).where(

View File

@@ -1,7 +1,8 @@
from datetime import UTC, datetime
from datetime import datetime
from typing import TYPE_CHECKING
from app.models.model import UTCBaseModel
from app.utils import utcnow
from sqlalchemy import Column, DateTime
from sqlmodel import BigInteger, Field, ForeignKey, Relationship, SQLModel
@@ -18,7 +19,7 @@ class Team(SQLModel, UTCBaseModel, table=True):
short_name: str = Field(max_length=10)
flag_url: str | None = Field(default=None)
cover_url: str | None = Field(default=None)
created_at: datetime = Field(default=datetime.now(UTC), sa_column=Column(DateTime))
created_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
leader_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id")))
leader: "User" = Relationship()
@@ -30,7 +31,7 @@ class TeamMember(SQLModel, UTCBaseModel, table=True):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), primary_key=True))
team_id: int = Field(foreign_key="teams.id")
joined_at: datetime = Field(default_factory=datetime.utcnow, sa_column=Column(DateTime))
joined_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
user: "User" = Relationship(back_populates="team_membership", sa_relationship_kwargs={"lazy": "joined"})
team: "Team" = Relationship(back_populates="members", sa_relationship_kwargs={"lazy": "joined"})
@@ -41,7 +42,7 @@ class TeamRequest(SQLModel, UTCBaseModel, table=True):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), primary_key=True))
team_id: int = Field(foreign_key="teams.id", primary_key=True)
requested_at: datetime = Field(default=datetime.now(UTC), sa_column=Column(DateTime))
requested_at: datetime = Field(default_factory=utcnow, sa_column=Column(DateTime))
user: "User" = Relationship(sa_relationship_kwargs={"lazy": "joined"})
team: "Team" = Relationship(sa_relationship_kwargs={"lazy": "joined"})

View File

@@ -1,7 +1,8 @@
from datetime import UTC, datetime
from datetime import datetime
from enum import Enum
from app.models.model import UTCBaseModel
from app.utils import utcnow
from sqlmodel import BigInteger, Column, Field, ForeignKey, Integer, SQLModel
@@ -17,7 +18,7 @@ class UserAccountHistoryBase(SQLModel, UTCBaseModel):
description: str | None = None
length: int
permanent: bool = False
timestamp: datetime = Field(default=datetime.now(UTC))
timestamp: datetime = Field(default_factory=utcnow)
type: UserAccountHistoryType

View File

@@ -4,6 +4,8 @@ User Login Log Database Model
from datetime import datetime
from app.utils import utcnow
from sqlmodel import Field, SQLModel
@@ -16,7 +18,7 @@ class UserLoginLog(SQLModel, table=True):
user_id: int = Field(index=True, description="User ID")
ip_address: str = Field(max_length=45, index=True, description="IP address (supports IPv4 and IPv6)")
user_agent: str | None = Field(default=None, max_length=500, description="User agent information")
login_time: datetime = Field(default_factory=datetime.utcnow, description="Login time")
login_time: datetime = Field(default_factory=utcnow, description="Login time")
# GeoIP information
country_code: str | None = Field(default=None, max_length=2, description="Country code")

View File

@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
import asyncio
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
from enum import IntEnum
from typing import (
TYPE_CHECKING,
@@ -21,6 +21,7 @@ from app.database.beatmap import Beatmap
from app.dependencies.database import with_db
from app.dependencies.fetcher import get_fetcher
from app.exception import InvokeException
from app.utils import utcnow
from .mods import API_MODS, APIMod
from .room import (
@@ -558,7 +559,7 @@ class MultiplayerQueue:
from app.database import Playlist
async with with_db() as session:
played_at = datetime.now(UTC)
played_at = utcnow()
await session.execute(
update(Playlist)
.where(

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
import re
from typing import Literal
@@ -36,6 +36,7 @@ from app.service.email_verification_service import (
)
from app.service.login_log_service import LoginLogService
from app.service.password_reset_service import password_reset_service
from app.utils import utcnow
from fastapi import APIRouter, Depends, Form, Request
from fastapi.responses import JSONResponse
@@ -156,8 +157,8 @@ async def register_user(
pw_bcrypt=get_password_hash(user_password),
priv=1, # 普通用户权限
country_code=country_code, # 根据 IP 地理位置设置国家
join_date=datetime.now(UTC),
last_visit=datetime.now(UTC),
join_date=utcnow(),
last_visit=utcnow(),
is_supporter=settings.enable_supporter_for_all_users,
support_level=int(settings.enable_supporter_for_all_users),
)

View File

@@ -1,7 +1,5 @@
from __future__ import annotations
from datetime import UTC, datetime
from app.config import settings
from app.database.lazer_user import User
from app.database.notification import Notification, UserNotification
@@ -9,6 +7,7 @@ from app.dependencies.database import Database
from app.dependencies.user import get_client_user
from app.models.chat import ChatEvent
from app.router.v2 import api_v2_router as router
from app.utils import utcnow
from . import channel, message # noqa: F401
from .server import (
@@ -135,7 +134,7 @@ async def mark_notifications_as_read(
data={
"notifications": [i.model_dump() for i in identities],
"read_count": len(user_notifications),
"timestamp": datetime.now(UTC).isoformat(),
"timestamp": utcnow().isoformat(),
},
),
)

View File

@@ -7,7 +7,7 @@ import json
from app.dependencies.database import get_redis, get_redis_message
from app.log import logger
from app.utils import bg_tasks
from app.utils import bg_tasks, utcnow
from .router import router
@@ -74,7 +74,7 @@ async def get_server_stats() -> ServerStats:
registered_users=registered_count,
online_users=online_count,
playing_users=playing_count,
timestamp=datetime.utcnow(),
timestamp=utcnow(),
)
except Exception as e:
logger.error(f"Error getting server stats: {e}")
@@ -83,7 +83,7 @@ async def get_server_stats() -> ServerStats:
registered_users=0,
online_users=0,
playing_users=0,
timestamp=datetime.utcnow(),
timestamp=utcnow(),
)
@@ -158,7 +158,7 @@ async def get_stats_debug_info():
try:
from app.service.enhanced_interval_stats import EnhancedIntervalStatsManager
current_time = datetime.utcnow()
current_time = utcnow()
current_interval = await EnhancedIntervalStatsManager.get_current_interval_info()
interval_stats = await EnhancedIntervalStatsManager.get_current_interval_stats()
@@ -334,7 +334,7 @@ async def record_hourly_stats() -> None:
online_count = await _get_online_users_count(redis_async)
playing_count = await _get_playing_users_count(redis_async)
current_time = datetime.utcnow()
current_time = utcnow()
history_point = {
"timestamp": current_time.isoformat(),
"online_count": online_count,

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
from datetime import UTC, datetime
import hashlib
from app.database.lazer_user import BASE_INCLUDES, User, UserResp
@@ -15,7 +14,7 @@ from app.models.notification import (
)
from app.router.notification import server
from app.storage.base import StorageService
from app.utils import check_image
from app.utils import check_image, utcnow
from .router import router
@@ -53,7 +52,7 @@ async def create_team(
check_image(flag, 2 * 1024 * 1024, 240, 120)
check_image(cover, 10 * 1024 * 1024, 3000, 2000)
now = datetime.now(UTC)
now = utcnow()
team = Team(name=name, short_name=short_name, leader_id=user_id, created_at=now)
session.add(team)
await session.commit()
@@ -196,7 +195,7 @@ async def request_join_team(
)
).first():
raise HTTPException(status_code=409, detail="Join request already exists")
team_request = TeamRequest(user_id=current_user.id, team_id=team_id, requested_at=datetime.now(UTC))
team_request = TeamRequest(user_id=current_user.id, team_id=team_id, requested_at=utcnow())
session.add(team_request)
await session.commit()
await session.refresh(team_request)
@@ -233,7 +232,7 @@ async def handle_request(
if (await session.exec(select(exists()).where(TeamMember.user_id == user_id))).first():
raise HTTPException(status_code=409, detail="User is already a member of the team")
session.add(TeamMember(user_id=user_id, team_id=team_id, joined_at=datetime.now(UTC)))
session.add(TeamMember(user_id=user_id, team_id=team_id, joined_at=utcnow()))
await server.new_private_notification(TeamApplicationAccept.init(team_request))
else:

View File

@@ -1,13 +1,12 @@
from __future__ import annotations
from datetime import UTC, datetime
from app.auth import validate_username
from app.config import settings
from app.database.events import Event, EventType
from app.database.lazer_user import User
from app.dependencies.database import Database
from app.dependencies.user import get_client_user
from app.utils import utcnow
from .router import router
@@ -47,7 +46,7 @@ async def user_rename(
current_user.username = new_name
current_user.previous_usernames = previous_username
rename_event = Event(
created_at=datetime.now(UTC),
created_at=utcnow(),
type=EventType.USERNAME_CHANGE,
user_id=current_user.id,
user=current_user,

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
from typing import Literal
from app.database.pp_best_score import PPBestScore
@@ -8,6 +8,7 @@ from app.database.score import Score, get_leaderboard
from app.dependencies.database import Database
from app.models.mods import int_to_mods, mod_to_save, mods_to_int
from app.models.score import GameMode, LeaderboardType
from app.utils import utcnow
from .router import AllStrModel, router
@@ -112,7 +113,7 @@ async def get_user_recent(
.where(
Score.user_id == user if type == "id" or user.isdigit() else col(Score.user).has(username=user),
Score.gamemode == GameMode.from_int_extra(ruleset_id),
Score.ended_at > datetime.now(UTC) - timedelta(hours=24),
Score.ended_at > utcnow() - timedelta(hours=24),
)
.order_by(col(Score.pp).desc())
.options(joinedload(Score.beatmap))

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime
from datetime import UTC
from typing import Literal
from app.database.beatmap import Beatmap, BeatmapResp
@@ -17,6 +17,7 @@ from app.dependencies.user import get_client_user, get_current_user
from app.models.room import RoomCategory, RoomStatus
from app.service.room import create_playlist_room_from_api
from app.signalr.hub import MultiplayerHubs
from app.utils import utcnow
from .router import router
@@ -50,7 +51,7 @@ async def get_all_rooms(
):
resp_list: list[RoomResp] = []
where_clauses: list[ColumnElement[bool]] = [col(Room.category) == category]
now = datetime.now(UTC)
now = utcnow()
if status is not None:
where_clauses.append(col(Room.status) == status)
if mode == "open":
@@ -112,12 +113,12 @@ async def _participate_room(room_id: int, user_id: int, db_room: Room, session:
participated_user = RoomParticipatedUser(
room_id=room_id,
user_id=user_id,
joined_at=datetime.now(UTC),
joined_at=utcnow(),
)
session.add(participated_user)
else:
participated_user.left_at = None
participated_user.joined_at = datetime.now(UTC)
participated_user.joined_at = utcnow()
db_room.participant_count += 1
await redis.publish("chat:room:joined", f"{db_room.channel_id}:{user_id}")
@@ -185,7 +186,7 @@ async def delete_room(
if db_room is None:
raise HTTPException(404, "Room not found")
else:
db_room.ends_at = datetime.now(UTC)
db_room.ends_at = utcnow()
await db.commit()
return None
@@ -238,7 +239,7 @@ async def remove_user_from_room(
)
).first()
if participated_user is not None:
participated_user.left_at = datetime.now(UTC)
participated_user.left_at = utcnow()
if db_room.participant_count > 0:
db_room.participant_count -= 1
await redis.publish("chat:room:left", f"{db_room.channel_id}:{user_id}")

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, date, datetime
from datetime import UTC, date
import math
import time
@@ -50,6 +50,7 @@ from app.models.score import (
from app.service.user_cache_service import get_user_cache_service
from app.storage.base import StorageService
from app.storage.local import LocalStorageService
from app.utils import utcnow
from .router import router
@@ -166,7 +167,7 @@ async def submit_score(
total_users = (await db.exec(select(func.count()).select_from(User))).one()
if resp.rank_global is not None and resp.rank_global <= min(math.ceil(float(total_users) * 0.01), 50):
rank_event = Event(
created_at=datetime.now(UTC),
created_at=utcnow(),
type=EventType.RANK,
user_id=score.user_id,
user=score.user,
@@ -451,7 +452,7 @@ async def create_playlist_score(
if not room:
raise HTTPException(status_code=404, detail="Room not found")
db_room_time = room.ends_at.replace(tzinfo=UTC) if room.ends_at else None
if db_room_time and db_room_time < datetime.now(UTC).replace(tzinfo=UTC):
if db_room_time and db_room_time < utcnow().replace(tzinfo=UTC):
raise HTTPException(status_code=400, detail="Room has ended")
item = (await session.exec(select(Playlist).where(Playlist.id == playlist_id, Playlist.room_id == room_id))).first()
if not item:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from typing import Literal
from app.config import settings
@@ -22,6 +22,7 @@ from app.log import logger
from app.models.score import GameMode
from app.models.user import BeatmapsetType
from app.service.user_cache_service import get_user_cache_service
from app.utils import utcnow
from .router import router
@@ -338,7 +339,7 @@ async def get_user_scores(
where_clause &= exists().where(col(PPBestScore.score_id) == Score.id)
order_by = col(Score.pp).desc()
elif type == "recent":
where_clause &= Score.ended_at > datetime.now(UTC) - timedelta(hours=24)
where_clause &= Score.ended_at > utcnow() - timedelta(hours=24)
order_by = col(Score.ended_at).desc()
elif type == "firsts":
# TODO

View File

@@ -5,13 +5,14 @@
from __future__ import annotations
import asyncio
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from app.config import settings
from app.database.score import Score
from app.dependencies.database import get_redis
from app.log import logger
from app.service.user_cache_service import get_user_cache_service
from app.utils import utcnow
from sqlmodel import col, func, select
@@ -34,7 +35,7 @@ async def schedule_user_cache_preload_task():
async with with_db() as session:
# 获取最近24小时内活跃的用户提交过成绩的用户
recent_time = datetime.now(UTC) - timedelta(hours=24)
recent_time = utcnow() - timedelta(hours=24)
score_count = func.count().label("score_count")
active_user_ids = (

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()

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
from collections import defaultdict
from collections.abc import Coroutine
from datetime import UTC, datetime
import math
from typing import override
@@ -27,6 +26,7 @@ from app.models.metadata_hub import (
)
from app.models.room import RoomCategory
from app.service.subscribers.score_processed import ScoreSubscriber
from app.utils import utcnow
from .hub import Client, Hub
@@ -41,11 +41,11 @@ class MetadataHub(Hub[MetadataClientState]):
self.subscriber = ScoreSubscriber()
self.subscriber.metadata_hub = self
self._daily_challenge_stats: MultiplayerRoomStats | None = None
self._today = datetime.now(UTC).date()
self._today = utcnow().date()
self._lock = asyncio.Lock()
def get_daily_challenge_stats(self, daily_challenge_room: int) -> MultiplayerRoomStats:
if self._daily_challenge_stats is None or self._today != datetime.now(UTC).date():
if self._daily_challenge_stats is None or self._today != utcnow().date():
self._daily_challenge_stats = MultiplayerRoomStats(
room_id=daily_challenge_room,
playlist_item_stats={},
@@ -98,7 +98,7 @@ class MetadataHub(Hub[MetadataClientState]):
async with with_db() as session:
async with session.begin():
user = (await session.exec(select(User).where(User.id == int(state.connection_id)))).one()
user.last_visit = datetime.now(UTC)
user.last_visit = utcnow()
await session.commit()
@override
@@ -149,7 +149,7 @@ class MetadataHub(Hub[MetadataClientState]):
daily_challenge_room = (
await session.exec(
select(Room).where(
col(Room.ends_at) > datetime.now(UTC),
col(Room.ends_at) > utcnow(),
Room.category == RoomCategory.DAILY_CHALLENGE,
)
)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import asyncio
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from typing import override
from app.database import Room
@@ -44,6 +44,7 @@ from app.models.room import (
RoomStatus,
)
from app.models.score import GameMode
from app.utils import utcnow
from .hub import Client, Hub
@@ -233,7 +234,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
item = room.playlist[0]
item.owner_id = client.user_id
room.room_id = db_room.id
starts_at = db_room.starts_at or datetime.now(UTC)
starts_at = db_room.starts_at or utcnow()
beatmap_exists = await session.exec(select(exists().where(col(Beatmap.id) == item.beatmap_id)))
if not beatmap_exists.one():
fetcher = await get_fetcher()
@@ -306,7 +307,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
session.add(participated_user)
else:
participated_user.left_at = None
participated_user.joined_at = datetime.now(UTC)
participated_user.joined_at = utcnow()
db_room = await session.get(Room, room_id)
if db_room is None:
@@ -1056,7 +1057,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
)
).first()
if participated_user is not None:
participated_user.left_at = datetime.now(UTC)
participated_user.left_at = utcnow()
db_room = await session.get(Room, room.room.room_id)
if db_room is None:
@@ -1087,7 +1088,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
.where(col(Room.id) == room.room.room_id)
.values(
name=room.room.settings.name,
ends_at=datetime.now(UTC),
ends_at=utcnow(),
type=room.room.settings.match_type,
queue_mode=room.room.settings.queue_mode,
auto_skip=room.room.settings.auto_skip,

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable, Sequence
from datetime import datetime
from datetime import UTC, datetime
import functools
import inspect
from io import BytesIO
@@ -270,3 +270,7 @@ class BackgroundTasks:
bg_tasks = BackgroundTasks()
def utcnow() -> datetime:
return datetime.now(tz=UTC)

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from contextlib import asynccontextmanager
from datetime import datetime
from pathlib import Path
from app.config import settings
@@ -38,7 +37,7 @@ from app.service.online_status_maintenance import schedule_online_status_mainten
from app.service.osu_rx_statistics import create_rx_statistics
from app.service.redis_message_system import redis_message_system
from app.service.stats_scheduler import start_stats_scheduler, stop_stats_scheduler
from app.utils import bg_tasks
from app.utils import bg_tasks, utcnow
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
@@ -157,7 +156,7 @@ async def root():
@app.get("/health", include_in_schema=False)
async def health_check():
"""健康检查端点"""
return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
return {"status": "ok", "timestamp": utcnow().isoformat()}
@app.exception_handler(RequestValidationError)