Update plugin config and dependency cache (#1130)

* Update plugin config and dependency cache

* Fix deps check
This commit is contained in:
Wu Clan
2026-03-27 23:50:58 +08:00
committed by GitHub
parent 7a934fee32
commit c96181bd08
3 changed files with 50 additions and 16 deletions
+5 -5
View File
@@ -25,8 +25,7 @@ class PluginService:
async def get_all() -> list[dict[str, Any]]:
"""获取所有插件"""
changed_key = f'{settings.PLUGIN_REDIS_PREFIX}:changed'
keys = [key async for key in redis_client.scan_iter(f'{settings.PLUGIN_REDIS_PREFIX}:*') if key != changed_key]
keys = [key async for key in redis_client.scan_iter(f'{settings.PLUGIN_REDIS_PREFIX}:config:*')]
if not keys:
return []
@@ -83,7 +82,7 @@ class PluginService:
backup_file = PLUGIN_DIR / f'{plugin}.{timezone.now().strftime("%Y%m%d%H%M%S")}.backup.zip'
await run_in_threadpool(zip_plugin, plugin_dir, backup_file)
await run_in_threadpool(remove_plugin, plugin_dir)
await redis_client.delete(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}')
await redis_client.delete(f'{settings.PLUGIN_REDIS_PREFIX}:config:{plugin}')
await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'true')
@staticmethod
@@ -94,7 +93,8 @@ class PluginService:
:param plugin: 插件名称
:return:
"""
plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}')
plugin_key = f'{settings.PLUGIN_REDIS_PREFIX}:config:{plugin}'
plugin_info = await redis_client.get(plugin_key)
if not plugin_info:
raise errors.NotFoundError(msg='插件不存在')
plugin_info = json.loads(plugin_info)
@@ -106,7 +106,7 @@ class PluginService:
else str(StatusType.disable.value)
)
plugin_info['plugin']['enable'] = new_status
await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}', json.dumps(plugin_info, ensure_ascii=False))
await redis_client.set(plugin_key, json.dumps(plugin_info, ensure_ascii=False))
await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'true')
@staticmethod
+7 -7
View File
@@ -144,9 +144,11 @@ def parse_plugin_config() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
run_await(current_redis_client.init)()
# 清理未知插件信息
exclude_keys = [f'{settings.PLUGIN_REDIS_PREFIX}:config:{key}' for key in plugins]
exclude_keys.extend(f'{settings.PLUGIN_REDIS_PREFIX}:requirements_hash:{key}' for key in plugins)
run_await(current_redis_client.delete_prefix)(
settings.PLUGIN_REDIS_PREFIX,
exclude=[f'{settings.PLUGIN_REDIS_PREFIX}:{key}' for key in plugins],
exclude=exclude_keys,
)
for plugin in plugins:
@@ -160,7 +162,8 @@ def parse_plugin_config() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
# 补充插件信息
data['plugin']['name'] = plugin
plugin_cache_info = run_await(current_redis_client.get)(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}')
plugin_cache_key = f'{settings.PLUGIN_REDIS_PREFIX}:config:{plugin}'
plugin_cache_info = run_await(current_redis_client.get)(plugin_cache_key)
if plugin_cache_info:
try:
data['plugin']['enable'] = json.loads(plugin_cache_info)['plugin']['enable']
@@ -170,10 +173,7 @@ def parse_plugin_config() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
data['plugin']['enable'] = str(StatusType.enable.value)
# 缓存最新插件信息
run_await(current_redis_client.set)(
f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}',
json.dumps(data, ensure_ascii=False),
)
run_await(current_redis_client.set)(plugin_cache_key, json.dumps(data, ensure_ascii=False))
# 重置插件变更状态
run_await(current_redis_client.delete)(f'{settings.PLUGIN_REDIS_PREFIX}:changed')
@@ -308,7 +308,7 @@ class PluginStatusChecker:
:param request: FastAPI 请求对象
:return:
"""
plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:{self.plugin}')
plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:config:{self.plugin}')
if not plugin_info:
log.error('插件状态未初始化或丢失,需重启服务自动修复')
raise PluginInjectError('插件状态未初始化或丢失,请联系系统管理员')
+38 -4
View File
@@ -1,10 +1,14 @@
import hashlib
import os
import site
import subprocess
import sys
from importlib import invalidate_caches
from importlib.metadata import PackageNotFoundError, distribution
from pathlib import Path
from packaging.requirements import Requirement
from starlette.concurrency import run_in_threadpool
from backend.core.conf import settings
@@ -20,6 +24,14 @@ def _is_in_virtualenv() -> bool:
return hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
def _refresh_site_packages() -> None:
"""刷新当前进程的 site-packages 路径,确保新装依赖立即可导入。"""
invalidate_caches()
for site_dir in site.getsitepackages():
if site_dir.endswith('site-packages'):
site.addsitedir(site_dir)
def install_requirements(plugin: str | None) -> None: # noqa: C901
"""
安装插件依赖
@@ -31,15 +43,36 @@ def install_requirements(plugin: str | None) -> None: # noqa: C901
for plugin in plugins:
requirements_file = PLUGIN_DIR / plugin / 'requirements.txt'
hash_key = f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}:requirements_hash'
hash_key = f'{settings.PLUGIN_REDIS_PREFIX}:requirements_hash:{plugin}'
cached_hash = run_await(redis_client.get)(hash_key)
if not os.path.exists(requirements_file):
run_await(redis_client.delete)(hash_key)
continue
missing_dependencies = False
for line in Path(requirements_file).read_text().splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
try:
req = Requirement(line)
dependency = req.name.lower()
except Exception as e:
raise PluginInstallError(f'插件 {plugin} 依赖 {line} 格式错误: {e!s}') from e
try:
dist = distribution(dependency)
except PackageNotFoundError:
missing_dependencies = True
break
if req.specifier and not req.specifier.contains(dist.version, prereleases=True):
missing_dependencies = True
break
current_hash = hashlib.sha256(Path(requirements_file).read_bytes()).hexdigest()
cached_hash = run_await(redis_client.get)(hash_key)
if cached_hash == current_hash:
if cached_hash == current_hash and not missing_dependencies:
continue
pip_install = ['uv', 'pip', 'install', '-r', requirements_file]
@@ -52,6 +85,7 @@ def install_requirements(plugin: str | None) -> None: # noqa: C901
for attempt in range(max_retries):
try:
subprocess.check_call(pip_install)
_refresh_site_packages()
run_await(redis_client.set)(hash_key, current_hash)
break
except subprocess.TimeoutExpired:
@@ -71,7 +105,7 @@ def uninstall_requirements(plugin: str) -> None:
:param plugin: 插件名称
:return:
"""
run_await(redis_client.delete)(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}:requirements_hash')
run_await(redis_client.delete)(f'{settings.PLUGIN_REDIS_PREFIX}:requirements_hash:{plugin}')
requirements_file = PLUGIN_DIR / plugin / 'requirements.txt'
if os.path.exists(requirements_file):
try: