From 6664fb17168003454468b954421c18a3ac188745 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 25 Aug 2025 16:37:02 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E6=97=A5=E5=8E=86=E7=9A=84=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/chain/subscribe.py | 27 +++++++++++++++++++ app/core/cache.py | 4 +-- app/core/plugin.py | 1 + app/db/subscribe_oper.py | 8 ++++++ app/helper/plugin.py | 4 +-- app/scheduler.py | 56 ++++++++++++++++++++++++++++++++++++---- 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 083564f09..385cad30f 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -1184,6 +1184,33 @@ class SubscribeChain(ChainBase): logger.error(f'follow用户分享订阅 {title} 添加失败:{message}') logger.info(f'follow用户分享订阅刷新完成,共添加 {success_count} 个订阅') + async def cache_calendar(self): + """ + 预缓存订阅日历,实际上就是查询一遍所有订阅的媒体信息 + 前端请示是异常的,所以需要使用异步缓存方法 + """ + logger.info(f'开始预缓存订阅日历 ...') + for subscribe in await SubscribeOper().async_list(): + if global_vars.is_system_stopped: + break + try: + mtype = MediaType(subscribe.type) + except ValueError: + logger.error(f'订阅 {subscribe.name} 类型错误:{subscribe.type}') + continue + # 识别媒体信息 + mediainfo: MediaInfo = await self.async_recognize_media(mtype=mtype, + tmdbid=subscribe.tmdbid, + doubanid=subscribe.doubanid, + bangumiid=subscribe.bangumiid, + episode_group=subscribe.episode_group, + cache=False) + if not mediainfo: + logger.warn( + f'未识别到媒体信息,标题:{subscribe.name},tmdbid:{subscribe.tmdbid},doubanid:{subscribe.doubanid}') + continue + logger.info(f'订阅日历预缓存完成') + @staticmethod def __update_subscribe_note(subscribe: Subscribe, downloads: Optional[List[Context]]): """ diff --git a/app/core/cache.py b/app/core/cache.py index 14ca33a74..10745418a 100644 --- a/app/core/cache.py +++ b/app/core/cache.py @@ -1393,7 +1393,7 @@ class TTLCache(CacheProxy): def __init__(self, region: Optional[str] = DEFAULT_CACHE_REGION, maxsize: Optional[int] = DEFAULT_CACHE_SIZE, - ttl: Optional[int]= DEFAULT_CACHE_TTL): + ttl: Optional[int] = DEFAULT_CACHE_TTL): """ 初始化 TTL 缓存 @@ -1412,7 +1412,7 @@ class LRUCache(CacheProxy): def __init__(self, region: Optional[str] = DEFAULT_CACHE_REGION, - maxsize: Optional[int]= DEFAULT_CACHE_SIZE + maxsize: Optional[int] = DEFAULT_CACHE_SIZE ): """ 初始化 LRU 缓存 diff --git a/app/core/plugin.py b/app/core/plugin.py index 5e40c1003..89f23c7a2 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -1189,6 +1189,7 @@ class PluginManager(metaclass=Singleton): async def async_get_online_plugins(self, force: bool = False) -> List[schemas.Plugin]: """ 异步获取所有在线插件信息 + :param force: 是否强制刷新(忽略缓存) """ if not settings.PLUGIN_MARKET: return [] diff --git a/app/db/subscribe_oper.py b/app/db/subscribe_oper.py index b92b23b2f..9ba978e6d 100644 --- a/app/db/subscribe_oper.py +++ b/app/db/subscribe_oper.py @@ -119,6 +119,14 @@ class SubscribeOper(DbOper): return Subscribe.get_by_state(self._db, state) return Subscribe.list(self._db) + async def async_list(self, state: Optional[str] = None) -> List[Subscribe]: + """ + 异步获取订阅列表 + """ + if state: + return await Subscribe.async_get_by_state(self._db, state) + return await Subscribe.async_list(self._db) + def delete(self, sid: int): """ 删除订阅 diff --git a/app/helper/plugin.py b/app/helper/plugin.py index eb8d9ca53..0fddfee21 100644 --- a/app/helper/plugin.py +++ b/app/helper/plugin.py @@ -911,10 +911,10 @@ class PluginHelper(metaclass=WeakSingleton): """ # 异步版本直接调用不带缓存的版本(缓存在异步环境下可能有并发问题) if force: - return await self._async_get_plugins_uncached(repo_url, package_version) + await self._async_get_plugins_cached.cache_clear() return await self._async_get_plugins_cached(repo_url, package_version) - @cached(maxsize=64, ttl=1800) + @cached(maxsize=128, ttl=1800) async def _async_get_plugins_cached(self, repo_url: str, package_version: Optional[str] = None) -> Optional[Dict[str, dict]]: """ diff --git a/app/scheduler.py b/app/scheduler.py index 248d5e913..b66519bbc 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -1,3 +1,4 @@ +import inspect import threading import traceback from datetime import datetime, timedelta @@ -27,6 +28,7 @@ from app.helper.wallpaper import WallpaperHelper from app.log import logger from app.schemas import Notification, NotificationType, Workflow, ConfigChangeEventData from app.schemas.types import EventType, SystemConfigKey +from app.utils.asyncio import AsyncUtils from app.utils.singleton import Singleton from app.utils.timer import TimerUtils @@ -162,6 +164,19 @@ class Scheduler(metaclass=Singleton): "name": "推荐缓存", "func": RecommendChain().refresh_recommend, "running": False, + }, + "plugin_market_refresh": { + "name": "插件市场缓存", + "func": PluginManager().async_get_online_plugins, + "running": False, + "kwargs": { + "force": True + } + }, + "subscribe_calendar_cache": { + "name": "订阅日历缓存", + "func": SubscribeChain().cache_calendar, + "running": False } } @@ -180,7 +195,7 @@ class Scheduler(metaclass=Singleton): id="cookiecloud", name="同步CookieCloud站点", minutes=int(settings.COOKIECLOUD_INTERVAL), - next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=1), + next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=5), kwargs={ 'job_id': 'cookiecloud' } @@ -195,7 +210,7 @@ class Scheduler(metaclass=Singleton): id="mediaserver_sync", name="同步媒体服务器", hours=int(settings.MEDIASERVER_SYNC_INTERVAL), - next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=5), + next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=10), kwargs={ 'job_id': 'mediaserver_sync' } @@ -301,7 +316,7 @@ class Scheduler(metaclass=Singleton): id="random_wallpager", name="壁纸缓存", minutes=30, - next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(seconds=3), + next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(seconds=1), kwargs={ 'job_id': 'random_wallpager' } @@ -363,12 +378,37 @@ class Scheduler(metaclass=Singleton): id="recommend_refresh", name="推荐缓存", hours=24, - next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(seconds=3), + next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(seconds=5), kwargs={ 'job_id': 'recommend_refresh' } ) + # 插件市场缓存 + self._scheduler.add_job( + self.start, + "interval", + id="plugin_market_refresh", + name="插件市场缓存", + minutes=30, + kwargs={ + 'job_id': 'plugin_market_refresh' + } + ) + + # 订阅日历缓存 + self._scheduler.add_job( + self.start, + "interval", + id="subscribe_calendar_cache", + name="订阅日历缓存", + hours=6, + next_run_time=datetime.now(pytz.timezone(settings.TZ)) + timedelta(minutes=2), + kwargs={ + 'job_id': 'subscribe_calendar_cache' + } + ) + # 初始化工作流服务 self.init_workflow_jobs() @@ -417,7 +457,13 @@ class Scheduler(metaclass=Singleton): try: if not kwargs: kwargs = job.get("kwargs") or {} - job["func"](*args, **kwargs) + func = job.get("func") + if not func: + return + if inspect.iscoroutinefunction(func): + AsyncUtils.run_async_in_executor(func(*args, **kwargs)) + else: + job["func"](*args, **kwargs) except Exception as e: logger.error(f"定时任务 {job.get('name')} 执行失败:{str(e)} - {traceback.format_exc()}") MessageHelper().put(title=f"{job.get('name')} 执行失败",