fix Failed to refresh user cache after score submit
This commit is contained in:
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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计算
|
||||||
|
|||||||
Reference in New Issue
Block a user