feat(cache): add user cache invalidation for avatar, cover, and beatmapset updates
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from app.dependencies.cache import UserCacheService
|
||||||
from app.dependencies.database import Database
|
from app.dependencies.database import Database
|
||||||
from app.dependencies.storage import StorageService
|
from app.dependencies.storage import StorageService
|
||||||
from app.dependencies.user import ClientUser
|
from app.dependencies.user import ClientUser
|
||||||
@@ -17,6 +18,7 @@ async def upload_avatar(
|
|||||||
content: Annotated[bytes, File(...)],
|
content: Annotated[bytes, File(...)],
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
storage: StorageService,
|
storage: StorageService,
|
||||||
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
"""上传用户头像
|
"""上传用户头像
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ async def upload_avatar(
|
|||||||
current_user.avatar_url = url
|
current_user.avatar_url = url
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"url": url,
|
"url": url,
|
||||||
"filehash": filehash,
|
"filehash": filehash,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import hashlib
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from app.database.user import UserProfileCover
|
from app.database.user import UserProfileCover
|
||||||
|
from app.dependencies.cache import UserCacheService
|
||||||
from app.dependencies.database import Database
|
from app.dependencies.database import Database
|
||||||
from app.dependencies.storage import StorageService
|
from app.dependencies.storage import StorageService
|
||||||
from app.dependencies.user import ClientUser
|
from app.dependencies.user import ClientUser
|
||||||
@@ -18,6 +19,7 @@ async def upload_cover(
|
|||||||
content: Annotated[bytes, File(...)],
|
content: Annotated[bytes, File(...)],
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
storage: StorageService,
|
storage: StorageService,
|
||||||
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
"""上传用户头图
|
"""上传用户头图
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ async def upload_cover(
|
|||||||
current_user.cover = UserProfileCover(url=url)
|
current_user.cover = UserProfileCover(url=url)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"url": url,
|
"url": url,
|
||||||
"filehash": filehash,
|
"filehash": filehash,
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ from app.database.user_preference import (
|
|||||||
UserListView,
|
UserListView,
|
||||||
UserPreference,
|
UserPreference,
|
||||||
)
|
)
|
||||||
from app.dependencies.database import Database, Redis
|
from app.dependencies.cache import UserCacheService
|
||||||
|
from app.dependencies.database import Database
|
||||||
from app.dependencies.user import ClientUser
|
from app.dependencies.user import ClientUser
|
||||||
from app.models.score import GameMode
|
from app.models.score import GameMode
|
||||||
from app.models.user import Page
|
from app.models.user import Page
|
||||||
@@ -26,7 +27,6 @@ from app.models.userpage import (
|
|||||||
ValidateBBCodeResponse,
|
ValidateBBCodeResponse,
|
||||||
)
|
)
|
||||||
from app.service.bbcode_service import bbcode_service
|
from app.service.bbcode_service import bbcode_service
|
||||||
from app.service.user_cache_service import get_user_cache_service
|
|
||||||
from app.utils import hex_to_hue, utcnow
|
from app.utils import hex_to_hue, utcnow
|
||||||
|
|
||||||
from .router import router
|
from .router import router
|
||||||
@@ -41,6 +41,7 @@ async def user_rename(
|
|||||||
session: Database,
|
session: Database,
|
||||||
new_name: Annotated[str, Body(..., description="新的用户名")],
|
new_name: Annotated[str, Body(..., description="新的用户名")],
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
"""修改用户名
|
"""修改用户名
|
||||||
|
|
||||||
@@ -81,6 +82,9 @@ async def user_rename(
|
|||||||
}
|
}
|
||||||
session.add(rename_event)
|
session.add(rename_event)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -95,6 +99,7 @@ async def update_userpage(
|
|||||||
request: UpdateUserpageRequest,
|
request: UpdateUserpageRequest,
|
||||||
session: Database,
|
session: Database,
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
"""更新用户页面内容"""
|
"""更新用户页面内容"""
|
||||||
if await current_user.is_restricted(session):
|
if await current_user.is_restricted(session):
|
||||||
@@ -110,6 +115,8 @@ async def update_userpage(
|
|||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(current_user)
|
await session.refresh(current_user)
|
||||||
|
|
||||||
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
|
|
||||||
# 返回官方格式的响应:只包含html
|
# 返回官方格式的响应:只包含html
|
||||||
return UpdateUserpageResponse(html=processed_page["html"])
|
return UpdateUserpageResponse(html=processed_page["html"])
|
||||||
|
|
||||||
@@ -295,13 +302,11 @@ async def change_user_preference(
|
|||||||
request: Preferences,
|
request: Preferences,
|
||||||
session: Database,
|
session: Database,
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
redis: Redis,
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
if await current_user.is_restricted(session):
|
if await current_user.is_restricted(session):
|
||||||
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
||||||
|
|
||||||
cache_service = get_user_cache_service(redis)
|
|
||||||
|
|
||||||
await current_user.awaitable_attrs.user_preference
|
await current_user.awaitable_attrs.user_preference
|
||||||
user_pref: UserPreference | None = current_user.user_preference
|
user_pref: UserPreference | None = current_user.user_preference
|
||||||
if user_pref is None:
|
if user_pref is None:
|
||||||
@@ -355,15 +360,14 @@ async def overwrite_user_preference(
|
|||||||
request: Preferences,
|
request: Preferences,
|
||||||
session: Database,
|
session: Database,
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
redis: Redis,
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
if await current_user.is_restricted(session):
|
if await current_user.is_restricted(session):
|
||||||
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
||||||
|
|
||||||
await Preferences.clear(current_user, [])
|
await Preferences.clear(current_user, [])
|
||||||
await change_user_preference(request, session, current_user, redis)
|
await change_user_preference(request, session, current_user, cache_service)
|
||||||
|
|
||||||
cache_service = get_user_cache_service(redis)
|
|
||||||
await cache_service.invalidate_user_cache(current_user.id)
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
@@ -379,13 +383,12 @@ async def delete_user_preference(
|
|||||||
session: Database,
|
session: Database,
|
||||||
current_user: ClientUser,
|
current_user: ClientUser,
|
||||||
fields: list[str],
|
fields: list[str],
|
||||||
redis: Redis,
|
cache_service: UserCacheService,
|
||||||
):
|
):
|
||||||
if await current_user.is_restricted(session):
|
if await current_user.is_restricted(session):
|
||||||
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
raise HTTPException(403, "Your account is restricted and cannot perform this action.")
|
||||||
|
|
||||||
await Preferences.clear(current_user, fields)
|
await Preferences.clear(current_user, fields)
|
||||||
|
|
||||||
cache_service = get_user_cache_service(redis)
|
|
||||||
await cache_service.invalidate_user_cache(current_user.id)
|
await cache_service.invalidate_user_cache(current_user.id)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from urllib.parse import parse_qs
|
|||||||
from app.database import Beatmap, Beatmapset, BeatmapsetResp, FavouriteBeatmapset, User
|
from app.database import Beatmap, Beatmapset, BeatmapsetResp, FavouriteBeatmapset, User
|
||||||
from app.database.beatmapset import SearchBeatmapsetsResp
|
from app.database.beatmapset import SearchBeatmapsetsResp
|
||||||
from app.dependencies.beatmap_download import DownloadService
|
from app.dependencies.beatmap_download import DownloadService
|
||||||
from app.dependencies.cache import BeatmapsetCacheService
|
from app.dependencies.cache import BeatmapsetCacheService, UserCacheService
|
||||||
from app.dependencies.database import Database, Redis, with_db
|
from app.dependencies.database import Database, Redis, with_db
|
||||||
from app.dependencies.fetcher import Fetcher
|
from app.dependencies.fetcher import Fetcher
|
||||||
from app.dependencies.geoip import IPAddress, get_geoip_helper
|
from app.dependencies.geoip import IPAddress, get_geoip_helper
|
||||||
@@ -222,6 +222,7 @@ async def download_beatmapset(
|
|||||||
)
|
)
|
||||||
async def favourite_beatmapset(
|
async def favourite_beatmapset(
|
||||||
db: Database,
|
db: Database,
|
||||||
|
cache_service: UserCacheService,
|
||||||
beatmapset_id: Annotated[int, Path(..., description="谱面集 ID")],
|
beatmapset_id: Annotated[int, Path(..., description="谱面集 ID")],
|
||||||
action: Annotated[
|
action: Annotated[
|
||||||
Literal["favourite", "unfavourite"],
|
Literal["favourite", "unfavourite"],
|
||||||
@@ -247,3 +248,4 @@ async def favourite_beatmapset(
|
|||||||
else:
|
else:
|
||||||
await db.delete(existing_favourite)
|
await db.delete(existing_favourite)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
await cache_service.invalidate_user_beatmapsets_cache(current_user.id)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from app.database.events import Event
|
|||||||
from app.database.score import LegacyScoreResp, Score, ScoreResp, get_user_first_scores
|
from app.database.score import LegacyScoreResp, Score, ScoreResp, get_user_first_scores
|
||||||
from app.database.user import ALL_INCLUDED, SEARCH_INCLUDED
|
from app.database.user import ALL_INCLUDED, SEARCH_INCLUDED
|
||||||
from app.dependencies.api_version import APIVersion
|
from app.dependencies.api_version import APIVersion
|
||||||
|
from app.dependencies.cache import UserCacheService
|
||||||
from app.dependencies.database import Database, get_redis
|
from app.dependencies.database import Database, get_redis
|
||||||
from app.dependencies.user import get_current_user, get_optional_user
|
from app.dependencies.user import get_current_user, get_optional_user
|
||||||
from app.helpers.asset_proxy_helper import asset_proxy_response
|
from app.helpers.asset_proxy_helper import asset_proxy_response
|
||||||
@@ -406,15 +407,13 @@ async def get_user_info(
|
|||||||
async def get_user_beatmapsets(
|
async def get_user_beatmapsets(
|
||||||
session: Database,
|
session: Database,
|
||||||
background_task: BackgroundTasks,
|
background_task: BackgroundTasks,
|
||||||
|
cache_service: UserCacheService,
|
||||||
user_id: Annotated[int, Path(description="用户 ID")],
|
user_id: Annotated[int, Path(description="用户 ID")],
|
||||||
type: Annotated[BeatmapsetType, Path(description="谱面集类型")],
|
type: Annotated[BeatmapsetType, Path(description="谱面集类型")],
|
||||||
current_user: Annotated[User, Security(get_current_user, scopes=["public"])],
|
current_user: Annotated[User, Security(get_current_user, scopes=["public"])],
|
||||||
limit: Annotated[int, Query(ge=1, le=1000, description="返回条数 (1-1000)")] = 100,
|
limit: Annotated[int, Query(ge=1, le=1000, description="返回条数 (1-1000)")] = 100,
|
||||||
offset: Annotated[int, Query(ge=0, description="偏移量")] = 0,
|
offset: Annotated[int, Query(ge=0, description="偏移量")] = 0,
|
||||||
):
|
):
|
||||||
redis = get_redis()
|
|
||||||
cache_service = get_user_cache_service(redis)
|
|
||||||
|
|
||||||
# 先尝试从缓存获取
|
# 先尝试从缓存获取
|
||||||
cached_result = await cache_service.get_user_beatmapsets_from_cache(user_id, type.value, limit, offset)
|
cached_result = await cache_service.get_user_beatmapsets_from_cache(user_id, type.value, limit, offset)
|
||||||
if cached_result is not None:
|
if cached_result is not None:
|
||||||
|
|||||||
@@ -279,6 +279,18 @@ class UserCacheService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error invalidating user scores cache: {e}")
|
logger.error(f"Error invalidating user scores cache: {e}")
|
||||||
|
|
||||||
|
async def invalidate_user_beatmapsets_cache(self, user_id: int):
|
||||||
|
"""使用户谱面集缓存失效"""
|
||||||
|
try:
|
||||||
|
# 删除用户谱面集相关缓存
|
||||||
|
pattern = f"user:{user_id}:beatmapsets:*"
|
||||||
|
keys = await self.redis.keys(pattern)
|
||||||
|
if keys:
|
||||||
|
await self.redis.delete(*keys)
|
||||||
|
logger.info(f"Invalidated {len(keys)} beatmapset cache entries for user {user_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error invalidating user beatmapsets cache: {e}")
|
||||||
|
|
||||||
async def preload_user_cache(self, session: AsyncSession, user_ids: list[int]):
|
async def preload_user_cache(self, session: AsyncSession, user_ids: list[int]):
|
||||||
"""预加载用户缓存"""
|
"""预加载用户缓存"""
|
||||||
if self._refreshing:
|
if self._refreshing:
|
||||||
|
|||||||
Reference in New Issue
Block a user