Add grade hot cache

This commit is contained in:
咕谷酱
2025-08-21 23:35:25 +08:00
parent 7c193937d1
commit 822d7c6377
13 changed files with 973 additions and 47 deletions

View File

@@ -7,8 +7,8 @@ from app.models.score import GameMode
from .lazer_user import BASE_INCLUDES, User, UserResp
from pydantic import BaseModel, model_validator
from sqlalchemy import JSON, Column, DateTime, Text
from pydantic import BaseModel, field_validator, model_validator
from sqlalchemy import Boolean, JSON, Column, DateTime, Text
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlmodel import Field, Relationship, SQLModel, col, exists, func, select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -73,16 +73,16 @@ class BeatmapsetBase(SQLModel):
artist_unicode: str = Field(index=True)
covers: BeatmapCovers | None = Field(sa_column=Column(JSON))
creator: str = Field(index=True)
nsfw: bool = Field(default=False)
nsfw: bool = Field(default=False, sa_column=Column(Boolean))
play_count: int = Field(index=True)
preview_url: str
source: str = Field(default="")
spotlight: bool = Field(default=False)
spotlight: bool = Field(default=False, sa_column=Column(Boolean))
title: str = Field(index=True)
title_unicode: str = Field(index=True)
user_id: int = Field(index=True)
video: bool = Field(index=True)
video: bool = Field(sa_column=Column(Boolean, index=True))
# optional
# converts: list[Beatmap] = Relationship(back_populates="beatmapset")
@@ -102,13 +102,13 @@ class BeatmapsetBase(SQLModel):
# BeatmapsetExtended
bpm: float = Field(default=0.0)
can_be_hyped: bool = Field(default=False)
discussion_locked: bool = Field(default=False)
can_be_hyped: bool = Field(default=False, sa_column=Column(Boolean))
discussion_locked: bool = Field(default=False, sa_column=Column(Boolean))
last_updated: datetime = Field(sa_column=Column(DateTime, index=True))
ranked_date: datetime | None = Field(
default=None, sa_column=Column(DateTime, index=True)
)
storyboard: bool = Field(default=False, index=True)
storyboard: bool = Field(default=False, sa_column=Column(Boolean, index=True))
submitted_date: datetime = Field(sa_column=Column(DateTime, index=True))
tags: str = Field(default="", sa_column=Column(Text))
@@ -133,7 +133,7 @@ class Beatmapset(AsyncAttrs, BeatmapsetBase, table=True):
hype_current: int = Field(default=0)
hype_required: int = Field(default=0)
availability_info: str | None = Field(default=None)
download_disabled: bool = Field(default=False)
download_disabled: bool = Field(default=False, sa_column=Column(Boolean))
favourites: list["FavouriteBeatmapset"] = Relationship(back_populates="beatmapset")
@classmethod
@@ -205,6 +205,14 @@ class BeatmapsetResp(BeatmapsetBase):
favourite_count: int = 0
recent_favourites: list[UserResp] = Field(default_factory=list)
@field_validator('nsfw', 'spotlight', 'video', 'can_be_hyped', 'discussion_locked', 'storyboard', 'discussion_enabled', 'is_scoreable', 'has_favourited', mode='before')
@classmethod
def validate_bool_fields(cls, v):
"""将整数 0/1 转换为布尔值,处理数据库中的布尔字段"""
if isinstance(v, int):
return bool(v)
return v
@model_validator(mode="after")
def fix_genre_language(self) -> Self:
if self.genre is None:

View File

@@ -0,0 +1,35 @@
"""
数据库字段类型工具
提供处理数据库和 Pydantic 之间类型转换的工具
"""
from typing import Any, Union
from pydantic import field_validator
from sqlalchemy import Boolean
def bool_field_validator(field_name: str):
"""为特定布尔字段创建验证器,处理数据库中的 0/1 整数"""
@field_validator(field_name, mode="before")
@classmethod
def validate_bool_field(cls, v: Any) -> bool:
"""将整数 0/1 转换为布尔值"""
if isinstance(v, int):
return bool(v)
return v
return validate_bool_field
def create_bool_field(**kwargs):
"""创建一个带有正确 SQLAlchemy 列定义的布尔字段"""
from sqlmodel import Field, Column
# 如果没有指定 sa_column则使用 Boolean 类型
if 'sa_column' not in kwargs:
# 处理 index 参数
index = kwargs.pop('index', False)
if index:
kwargs['sa_column'] = Column(Boolean, index=True)
else:
kwargs['sa_column'] = Column(Boolean)
return Field(**kwargs)

View File

@@ -45,8 +45,9 @@ from .relationship import (
)
from .score_token import ScoreToken
from pydantic import field_validator
from redis.asyncio import Redis
from sqlalchemy import Column, ColumnExpressionArgument, DateTime
from sqlalchemy import Boolean, Column, ColumnExpressionArgument, DateTime
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import aliased
from sqlalchemy.sql.elements import ColumnElement
@@ -79,13 +80,13 @@ class ScoreBase(AsyncAttrs, SQLModel, UTCBaseModel):
default=0, sa_column=Column(BigInteger)
) # solo_score
ended_at: datetime = Field(sa_column=Column(DateTime))
has_replay: bool
has_replay: bool = Field(sa_column=Column(Boolean))
max_combo: int
mods: list[APIMod] = Field(sa_column=Column(JSON))
passed: bool
passed: bool = Field(sa_column=Column(Boolean))
playlist_item_id: int | None = Field(default=None) # multiplayer
pp: float = Field(default=0.0)
preserve: bool = Field(default=True)
preserve: bool = Field(default=True, sa_column=Column(Boolean))
rank: Rank
room_id: int | None = Field(default=None) # multiplayer
started_at: datetime = Field(sa_column=Column(DateTime))
@@ -176,6 +177,14 @@ class ScoreResp(ScoreBase):
ranked: bool = False
current_user_attributes: CurrentUserAttributes | None = None
@field_validator('has_replay', 'passed', 'preserve', 'is_perfect_combo', 'legacy_perfect', 'processed', 'ranked', mode='before')
@classmethod
def validate_bool_fields(cls, v):
"""将整数 0/1 转换为布尔值,处理数据库中的布尔字段"""
if isinstance(v, int):
return bool(v)
return v
@classmethod
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
s = cls.model_validate(score.model_dump())