diff --git a/app/chain/recommend.py b/app/chain/recommend.py index 811001ba..5f4a3e5b 100644 --- a/app/chain/recommend.py +++ b/app/chain/recommend.py @@ -1,4 +1,8 @@ -from typing import Any +from functools import wraps +from typing import Any, Callable + +from cachetools import TTLCache +from cachetools.keys import hashkey from app.chain import ChainBase from app.chain.bangumi import BangumiChain @@ -9,6 +13,35 @@ from app.schemas import MediaType from app.utils.common import log_execution_time from app.utils.singleton import Singleton +# 推荐相关的专用缓存 +recommend_ttl = 6 * 3600 +recommend_cache = TTLCache(maxsize=256, ttl=recommend_ttl) + + +# 推荐缓存装饰器,避免偶发网络获取数据为空时,页面由于缓存长时间渲染异常问题 +def cached_with_empty_check(func: Callable): + """ + 缓存装饰器,用于缓存函数的返回结果,仅在结果非空时进行缓存 + + :param func: 被装饰的函数 + :return: 包装后的函数 + """ + + @wraps(func) + def wrapper(*args, **kwargs): + # 使用 cachetools 缓存,构造缓存键 + cache_key = hashkey(*args, **kwargs) + if cache_key in recommend_cache: + return recommend_cache[cache_key] + result = func(*args, **kwargs) + # 如果返回值为空,则不缓存 + if result in [None, [], {}]: + return result + recommend_cache[cache_key] = result + return result + + return wrapper + class RecommendChain(ChainBase, metaclass=Singleton): """ @@ -21,7 +54,25 @@ class RecommendChain(ChainBase, metaclass=Singleton): self.doubanchain = DoubanChain() self.bangumichain = BangumiChain() + def refresh_recommend(self): + """ + 刷新推荐 + """ + self.tmdb_movies() + self.tmdb_tvs() + self.tmdb_trending() + self.bangumi_calendar() + self.douban_movies() + self.douban_tvs() + self.movie_top250() + self.tv_weekly_chinese() + self.tv_weekly_global() + self.tv_animation() + self.movie_hot() + self.tv_hot() + @log_execution_time(logger=logger) + @cached_with_empty_check def tmdb_movies(self, sort_by: str = "popularity.desc", with_genres: str = "", with_original_language: str = "", page: int = 1) -> Any: """ @@ -35,6 +86,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [movie.to_dict() for movie in movies] if movies else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tmdb_tvs(self, sort_by: str = "popularity.desc", with_genres: str = "", with_original_language: str = "", page: int = 1) -> Any: """ @@ -48,6 +100,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [tv.to_dict() for tv in tvs] if tvs else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tmdb_trending(self, page: int = 1) -> Any: """ TMDB流行趋势 @@ -56,6 +109,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [info.to_dict() for info in infos] if infos else [] @log_execution_time(logger=logger) + @cached_with_empty_check def bangumi_calendar(self, page: int = 1, count: int = 30) -> Any: """ Bangumi每日放送 @@ -64,6 +118,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in medias[(page - 1) * count: page * count]] if medias else [] @log_execution_time(logger=logger) + @cached_with_empty_check def movie_showing(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣正在热映 @@ -72,6 +127,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in movies] if movies else [] @log_execution_time(logger=logger) + @cached_with_empty_check def douban_movies(self, sort: str = "R", tags: str = "", page: int = 1, count: int = 30) -> Any: """ 豆瓣最新电影 @@ -81,6 +137,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in movies] if movies else [] @log_execution_time(logger=logger) + @cached_with_empty_check def douban_tvs(self, sort: str = "R", tags: str = "", page: int = 1, count: int = 30) -> Any: """ 豆瓣最新电视剧 @@ -90,6 +147,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in tvs] if tvs else [] @log_execution_time(logger=logger) + @cached_with_empty_check def movie_top250(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣电影TOP250 @@ -98,6 +156,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in movies] if movies else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tv_weekly_chinese(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣国产剧集榜 @@ -106,6 +165,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in tvs] if tvs else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tv_weekly_global(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣全球剧集榜 @@ -114,6 +174,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in tvs] if tvs else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tv_animation(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣热门动漫 @@ -122,6 +183,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in tvs] if tvs else [] @log_execution_time(logger=logger) + @cached_with_empty_check def movie_hot(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣热门电影 @@ -130,6 +192,7 @@ class RecommendChain(ChainBase, metaclass=Singleton): return [media.to_dict() for media in movies] if movies else [] @log_execution_time(logger=logger) + @cached_with_empty_check def tv_hot(self, page: int = 1, count: int = 30) -> Any: """ 豆瓣热门电视剧