fix Failed to refresh user cache after score submit

This commit is contained in:
咕谷酱
2025-08-22 00:25:58 +08:00
parent b166f6fc0a
commit 49ac399180
3 changed files with 111 additions and 14 deletions

View File

@@ -45,7 +45,7 @@ from .relationship import (
) )
from .score_token import ScoreToken from .score_token import ScoreToken
from pydantic import field_validator from pydantic import field_validator, field_serializer
from redis.asyncio import Redis from redis.asyncio import Redis
from sqlalchemy import Boolean, Column, ColumnExpressionArgument, DateTime from sqlalchemy import Boolean, Column, ColumnExpressionArgument, DateTime
from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.ext.asyncio import AsyncAttrs
@@ -120,6 +120,29 @@ class ScoreBase(AsyncAttrs, SQLModel, UTCBaseModel):
return converted return converted
return v return v
@field_serializer("maximum_statistics", when_used="json")
def serialize_maximum_statistics(self, v):
"""序列化 maximum_statistics 字段,确保枚举值正确转换为字符串"""
if isinstance(v, dict):
serialized = {}
for key, value in v.items():
if hasattr(key, 'value'):
# 如果是枚举,使用其值
serialized[key.value] = value
else:
# 否则直接使用键
serialized[str(key)] = value
return serialized
return v
@field_serializer("rank", when_used="json")
def serialize_rank(self, v):
"""序列化等级,确保枚举值正确转换为字符串"""
if hasattr(v, 'value'):
return v.value
return str(v)
return v
# optional # optional
# TODO: current_user_attributes # TODO: current_user_attributes
@@ -163,6 +186,13 @@ class Score(ScoreBase, table=True):
return GameMode.OSU return GameMode.OSU
return v return v
@field_serializer("gamemode", when_used="json")
def serialize_gamemode(self, v):
"""序列化游戏模式,确保枚举值正确转换为字符串"""
if hasattr(v, 'value'):
return v.value
return str(v)
# optional # optional
beatmap: Beatmap = Relationship() beatmap: Beatmap = Relationship()
user: User = Relationship(sa_relationship_kwargs={"lazy": "joined"}) user: User = Relationship(sa_relationship_kwargs={"lazy": "joined"})
@@ -246,6 +276,28 @@ class ScoreResp(ScoreBase):
return converted return converted
return v return v
@field_serializer("statistics", "maximum_statistics", when_used="json")
def serialize_statistics_fields(self, v):
"""序列化统计字段,确保枚举值正确转换为字符串"""
if isinstance(v, dict):
serialized = {}
for key, value in v.items():
if hasattr(key, 'value'):
# 如果是枚举,使用其值
serialized[key.value] = value
else:
# 否则直接使用键
serialized[str(key)] = value
return serialized
return v
@field_serializer("gamemode", when_used="json")
def serialize_gamemode(self, v):
"""序列化游戏模式,确保枚举值正确转换为字符串"""
if hasattr(v, 'value'):
return v.value
return str(v)
@classmethod @classmethod
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp": async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
s = cls.model_validate(score.model_dump()) s = cls.model_validate(score.model_dump())

View File

@@ -7,7 +7,7 @@ from app.config import settings
from .mods import API_MODS, APIMod from .mods import API_MODS, APIMod
from pydantic import BaseModel, Field, ValidationInfo, field_validator from pydantic import BaseModel, Field, ValidationInfo, field_validator, field_serializer
if TYPE_CHECKING: if TYPE_CHECKING:
import rosu_pp_py as rosu import rosu_pp_py as rosu
@@ -206,6 +206,28 @@ class SoloScoreSubmissionInfo(BaseModel):
incompatible_mods.update(setting_mods["IncompatibleMods"]) incompatible_mods.update(setting_mods["IncompatibleMods"])
return mods return mods
@field_serializer("statistics", "maximum_statistics", when_used="json")
def serialize_statistics(self, v):
"""序列化统计字段,确保枚举值正确转换为字符串"""
if isinstance(v, dict):
serialized = {}
for key, value in v.items():
if hasattr(key, 'value'):
# 如果是枚举,使用其值
serialized[key.value] = value
else:
# 否则直接使用键
serialized[str(key)] = value
return serialized
return v
@field_serializer("rank", when_used="json")
def serialize_rank(self, v):
"""序列化等级,确保枚举值正确转换为字符串"""
if hasattr(v, 'value'):
return v.value
return str(v)
class LegacyReplaySoloScoreInfo(TypedDict): class LegacyReplaySoloScoreInfo(TypedDict):
online_id: int online_id: int

View File

@@ -34,7 +34,7 @@ from app.database.score import (
process_score, process_score,
process_user, process_user,
) )
from app.dependencies.database import Database, get_redis, with_db from app.dependencies.database import Database, get_redis
from app.dependencies.fetcher import get_fetcher from app.dependencies.fetcher import get_fetcher
from app.dependencies.storage import get_storage_service from app.dependencies.storage import get_storage_service
from app.dependencies.user import get_client_user, get_current_user from app.dependencies.user import get_client_user, get_current_user
@@ -75,8 +75,14 @@ READ_SCORE_TIMEOUT = 10
async def process_user_achievement(score_id: int): async def process_user_achievement(score_id: int):
async with with_db() as session: from sqlmodel.ext.asyncio.session import AsyncSession
from app.dependencies.database import engine
session = AsyncSession(engine)
try:
await process_achievements(session, get_redis(), score_id) await process_achievements(session, get_redis(), score_id)
finally:
await session.close()
async def submit_score( async def submit_score(
@@ -184,20 +190,37 @@ async def submit_score(
db.add(rank_event) db.add(rank_event)
await db.commit() await db.commit()
# 成绩提交后刷新用户缓存 # 成绩提交后刷新用户缓存 - 移至后台任务避免阻塞主流程
try: if current_user.id is not None:
user_cache_service = get_user_cache_service(redis) background_task.add_task(
if current_user.id is not None: _refresh_user_cache_background,
await user_cache_service.refresh_user_cache_on_score_submit( redis,
db, current_user.id, score.gamemode current_user.id,
) score.gamemode
except Exception as e: )
logger.error(f"Failed to refresh user cache after score submit: {e}")
background_task.add_task(process_user_achievement, resp.id) background_task.add_task(process_user_achievement, resp.id)
return resp return resp
async def _refresh_user_cache_background(redis: Redis, user_id: int, mode: GameMode):
"""后台任务:刷新用户缓存"""
try:
from sqlmodel.ext.asyncio.session import AsyncSession
from app.dependencies.database import engine
user_cache_service = get_user_cache_service(redis)
# 创建独立的数据库会话
session = AsyncSession(engine)
try:
await user_cache_service.refresh_user_cache_on_score_submit(
session, user_id, mode
)
finally:
await session.close()
except Exception as e:
logger.error(f"Failed to refresh user cache after score submit: {e}")
async def _preload_beatmap_for_pp_calculation(beatmap_id: int) -> None: async def _preload_beatmap_for_pp_calculation(beatmap_id: int) -> None:
""" """
预缓存beatmap文件以加速PP计算 预缓存beatmap文件以加速PP计算