Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/plugins/core/runtime/service/facade.py
T
insistence 2a055ba648 feat: 新增插件系统 (#112)
* feat: 初始化插件系统

* refactor: 收口插件系统运行时重构

* perf: 优化插件系统类型提示

* fix&perf: 修复和优化插件系统

* fix: 修复gitignore规则误忽略插件文件的问题

* fix: 修复运行时插件根路径算错的问题

* fix: 加强插件发现和路由注册的防护措施

* revert: 回滚定时任务白名单

* fix: 移除未使用的应用路由注册探测

* revert: 恢复部分代码

* perf: 优化插件系统

* docs: 新增插件开发文档

* perf: 优化插件管理模块

* perf: 提升插件系统核心能力

* refactor: 重构生命周期 step runner

* fix: 修复lint错误

* test: 清理测试用例

* test: 调整测试目录名称

* fix: 修复前后端目录硬编码的问题

* fix: 修复插件系统安全性缺口

* refactor: 重新设计插件生命周期 Migration 事务与回滚

* perf: 优化插件系统边界问题

* refactor: 重构当前插件系统的依赖体系设计

* perf: 优化代码

* perf: 优化代码

* fix: 修复代码合并问题

* fix: 修复bug

* perf: 优化代码

* perf&fix: 优化代码和修复bug

* docs: 优化文档格式

* feat: 适配Vue2版本

* docs: 更新README文档

* fix: 修复ruff lint错误

* chore: 更新后端依赖文件
2026-07-28 20:35:18 +08:00

831 lines
30 KiB
Python

import asyncio
from collections.abc import Awaitable, Callable, Mapping
from typing import cast
from plugins.core.environment import PLUGIN_RUNTIME_ENVIRONMENT, PluginRuntimeEnvironmentService
from plugins.core.runtime.support import PluginRuntimePayloadBuilder
from plugins.core.types import PluginConfigValue
from plugins.core.validation.dependencies import NpmDependencyInspector, PluginDependencyChecker
from plugins.core.validation.dependency_policy import DependencyInstallPolicyConfig
from .audit import PluginAuditUseCase
from .batch import PluginBatchUseCase
from .config import PluginConfigUseCase
from .context import PluginRuntimeContextService
from .dependencies import PluginDependencyUseCase
from .dependency_container import PluginRuntimeDependencies, PluginRuntimeGatewayOverrides
from .gateway import (
DefaultPluginCommandRunnerGateway,
PluginCommandOutputCallback,
PluginCommandRunnerGateway,
PluginManagementModelGateway,
UnavailablePluginAuditGateway,
UnavailablePluginConfigGateway,
UnavailablePluginLifecycleStateGateway,
UnavailablePluginLifecycleUnitOfWorkGateway,
UnavailablePluginManagementModelGateway,
UnavailablePluginMigrationExecutionGateway,
UnavailablePluginMigrationHistoryGateway,
UnavailablePluginPurgePlanGateway,
UnavailablePluginStateQueryGateway,
)
from .lifecycle import PluginEnableUseCase, PluginInstallUseCase, PluginPurgeUseCase, PluginUpgradeUseCase
from .lifecycle_lock import NoopPluginLifecycleLock, PluginLifecycleLock
from .migration import MigrationRecoveryStatus, PluginMigrationUseCase
from .precheck import PluginPrecheckUseCase
from .query import PluginQueryUseCase
from .responses import (
PluginBatchItemExecutionResponse,
PluginBatchResponse,
PluginCatalogInfoResponse,
PluginCatalogListResponse,
PluginCheckResponse,
PluginConfigExportResponse,
PluginConfigImportResponse,
PluginConfigStateResponse,
PluginDependencyCheckResponse,
PluginDependencyInstallResponse,
PluginDiagnoseResponse,
PluginDocumentationResponse,
PluginHealthResponse,
PluginLifecycleResponse,
PluginPlanResponse,
PluginPrecheckResponse,
)
from .tools import PluginToolUseCase
class PluginRuntimeService:
"""
插件应用运行时服务。
使用 Facade + 组合式 use case 管理插件查询、检查、生命周期、配置、测试和模板等核心能力。
数据库状态、管理模型和命令执行等外部依赖通过显式端口注入。
"""
def __init__(
self,
*,
runtime_environment: PluginRuntimeEnvironmentService | None = None,
dependency_checker: PluginDependencyChecker | None = None,
gateways: PluginRuntimeGatewayOverrides | None = None,
model_gateway: PluginManagementModelGateway | None = None,
command_gateway: PluginCommandRunnerGateway | None = None,
lifecycle_lock: PluginLifecycleLock | None = None,
) -> None:
"""
初始化插件应用运行时服务。
:param runtime_environment: 插件运行时环境服务
:param dependency_checker: 插件依赖检查器
:param gateways: 插件运行时窄端口覆盖项
:param model_gateway: 插件管理模型工厂网关
:param command_gateway: 插件命令执行网关
:param lifecycle_lock: 插件生命周期操作锁
:return: None
"""
resolved_environment = runtime_environment or PLUGIN_RUNTIME_ENVIRONMENT
resolved_dependency_checker = dependency_checker or PluginDependencyChecker(
npm_inspector=NpmDependencyInspector(frontend_root=resolved_environment.get_frontend_dir()),
frontend_mode=resolved_environment.get_frontend_mode(),
)
gateway_overrides = gateways or PluginRuntimeGatewayOverrides()
self._replace_dependencies(
PluginRuntimeDependencies(
runtime_environment=resolved_environment,
dependency_checker=resolved_dependency_checker,
config_gateway=gateway_overrides.config_gateway or UnavailablePluginConfigGateway(),
audit_gateway=gateway_overrides.audit_gateway or UnavailablePluginAuditGateway(),
state_query_gateway=gateway_overrides.state_query_gateway or UnavailablePluginStateQueryGateway(),
migration_history_gateway=(
gateway_overrides.migration_history_gateway or UnavailablePluginMigrationHistoryGateway()
),
purge_plan_gateway=gateway_overrides.purge_plan_gateway or UnavailablePluginPurgePlanGateway(),
lifecycle_state_gateway=(
gateway_overrides.lifecycle_state_gateway or UnavailablePluginLifecycleStateGateway()
),
lifecycle_uow_gateway=(
gateway_overrides.lifecycle_uow_gateway or UnavailablePluginLifecycleUnitOfWorkGateway()
),
migration_execution_gateway=(
gateway_overrides.migration_execution_gateway or UnavailablePluginMigrationExecutionGateway()
),
model_gateway=model_gateway or UnavailablePluginManagementModelGateway(),
command_gateway=command_gateway or DefaultPluginCommandRunnerGateway(),
)
)
self.lifecycle_lock = lifecycle_lock or NoopPluginLifecycleLock()
self._background_audit_tasks: set[asyncio.Task[None]] = set()
def _replace_dependencies(self, dependencies: PluginRuntimeDependencies) -> None:
"""
替换插件运行时依赖容器并刷新组合 use case。
:param dependencies: 新的插件运行时依赖容器
:return: None
"""
self.dependencies = dependencies
self.context = PluginRuntimeContextService(dependencies)
self.audit = PluginAuditUseCase(dependencies)
self.batch = PluginBatchUseCase(dependencies, runtime_operations=self, context=self.context)
self.config = PluginConfigUseCase(dependencies, context=self.context)
self.dependency = PluginDependencyUseCase(dependencies, context=self.context)
self.enable = PluginEnableUseCase(dependencies, runtime_operations=self, context=self.context)
self.install = PluginInstallUseCase(dependencies, runtime_operations=self, context=self.context)
self.migration = PluginMigrationUseCase(dependencies)
self.precheck = PluginPrecheckUseCase(dependencies, context=self.context)
self.purge = PluginPurgeUseCase(dependencies, runtime_operations=self, context=self.context)
self.query = PluginQueryUseCase(dependencies, runtime_operations=self, context=self.context)
self.tools = PluginToolUseCase(dependencies, context=self.context)
self.upgrade = PluginUpgradeUseCase(dependencies, runtime_operations=self, context=self.context)
def set_dependency_checker(self, dependency_checker: PluginDependencyChecker) -> None:
"""
替换插件依赖检查器。
:param dependency_checker: 新的依赖检查器
:return: None
"""
self.dependencies.dependency_checker = dependency_checker
def refresh_dependency_checker(self) -> None:
"""
刷新插件 Python/npm 依赖检查器。
:return: None
"""
self.set_dependency_checker(
PluginDependencyChecker(
npm_inspector=NpmDependencyInspector(
frontend_root=self.dependencies.runtime_environment.get_frontend_dir(),
),
frontend_mode=self.dependencies.runtime_environment.get_frontend_mode(),
)
)
def list_plugins(self) -> PluginCatalogListResponse:
"""
获取本地插件列表。
:return: 插件列表负载
"""
return cast('PluginCatalogListResponse', self.query.list_plugins())
async def list_plugins_with_state(self) -> PluginCatalogListResponse:
"""
获取合并数据库状态的本地插件列表。
:return: 插件列表负载
"""
return cast('PluginCatalogListResponse', await self.query.list_plugins_with_state())
def get_plugin_info(self, plugin_id: str) -> PluginCatalogInfoResponse:
"""
获取插件详情。
:param plugin_id: 插件ID
:return: 插件详情负载
"""
return cast('PluginCatalogInfoResponse', self.query.get_plugin_info(plugin_id))
async def get_plugin_info_with_state(self, plugin_id: str) -> PluginCatalogInfoResponse:
"""
获取包含数据库状态的插件详情。
:param plugin_id: 插件ID
:return: 插件详情负载
"""
return cast('PluginCatalogInfoResponse', await self.query.get_plugin_info_with_state(plugin_id))
def check_plugin(self, plugin_id: str | None = None) -> PluginCheckResponse:
"""
检查插件依赖状态。
:param plugin_id: 插件ID,未传入时检查全部插件
:return: 插件检查负载
"""
return cast('PluginCheckResponse', self.query.check_plugin(plugin_id))
async def check_plugin_async(self, plugin_id: str | None = None) -> PluginCheckResponse:
"""
异步检查插件依赖状态。
:param plugin_id: 插件ID,未传入时检查全部插件
:return: 插件检查负载
"""
return cast('PluginCheckResponse', await self.query.check_plugin_async(plugin_id))
def check_plugin_dependencies(self, plugin_id: str) -> PluginDependencyCheckResponse:
"""
检查插件依赖状态。
:param plugin_id: 插件ID
:return: 插件依赖检查负载
"""
return cast('PluginDependencyCheckResponse', self.query.check_plugin_dependencies(plugin_id))
async def health_plugin(self, plugin_id: str) -> PluginHealthResponse:
"""
执行插件健康检查。
:param plugin_id: 插件ID
:return: 插件健康检查负载
"""
return cast('PluginHealthResponse', await self.query.health_plugin(plugin_id))
async def diagnose_plugin(self, plugin_id: str) -> PluginDiagnoseResponse:
"""
生成插件诊断包。
:param plugin_id: 插件ID
:return: 插件诊断包负载
"""
return cast('PluginDiagnoseResponse', await self.query.diagnose_plugin(plugin_id))
async def get_plugin_config(self, plugin_id: str, *, reveal_secret: bool = False) -> PluginConfigStateResponse:
"""
获取插件配置。
:param plugin_id: 插件ID
:param reveal_secret: 是否展示敏感配置原值
:return: 插件配置负载
"""
return cast(
'PluginConfigStateResponse', await self.config.get_plugin_config(plugin_id, reveal_secret=reveal_secret)
)
async def export_plugin_config(self, plugin_id: str, *, reveal_secret: bool = False) -> PluginConfigExportResponse:
"""
导出插件配置快照。
:param plugin_id: 插件ID
:param reveal_secret: 是否导出敏感配置明文
:return: 插件配置导出负载
"""
return cast(
'PluginConfigExportResponse', await self.config.export_plugin_config(plugin_id, reveal_secret=reveal_secret)
)
async def set_plugin_config(
self,
plugin_id: str,
values: dict[str, PluginConfigValue],
*,
audit_operation: str = 'config_set',
success_message: str = '插件配置已更新',
) -> PluginConfigStateResponse:
"""
更新插件配置。
:param plugin_id: 插件ID
:param values: 配置键值
:param audit_operation: 审计操作类型
:param success_message: 操作成功提示
:return: 插件配置更新负载
"""
return cast(
'PluginConfigStateResponse',
await self.config.set_plugin_config(
plugin_id,
values,
audit_operation=audit_operation,
success_message=success_message,
),
)
async def import_plugin_config(
self, plugin_id: str, values: dict[str, PluginConfigValue]
) -> PluginConfigImportResponse:
"""
导入插件配置。
:param plugin_id: 插件ID
:param values: 待导入配置键值
:return: 插件配置导入负载
"""
return cast('PluginConfigImportResponse', await self.config.import_plugin_config(plugin_id, values))
async def precheck_plugin_operation(self, plugin_id: str, operation: str) -> PluginPrecheckResponse:
"""
执行插件操作预检。
:param plugin_id: 插件ID
:param operation: 操作类型
:return: 插件操作预检负载
"""
return cast('PluginPrecheckResponse', await self.precheck.precheck_plugin_operation(plugin_id, operation))
def plan_plugins(self, operation: str, plugin_ids: list[str] | None = None) -> PluginPlanResponse:
"""
生成插件批量操作拓扑计划。
:param operation: 批量操作类型
:param plugin_ids: 插件ID列表
:return: 插件批量操作拓扑计划负载
"""
return cast('PluginPlanResponse', self.batch.plan_plugins(operation, plugin_ids))
async def plan_plugins_async(self, operation: str, plugin_ids: list[str] | None = None) -> PluginPlanResponse:
"""
异步生成插件批量操作拓扑计划。
:param operation: 批量操作类型
:param plugin_ids: 插件ID列表
:return: 插件批量操作拓扑计划负载
"""
return cast('PluginPlanResponse', await self.batch.plan_plugins_async(operation, plugin_ids))
async def batch_plugins(
self,
operation: str,
plugin_ids: list[str] | None = None,
*,
dry_run: bool = False,
continue_on_error: bool = False,
) -> PluginBatchResponse:
"""
批量执行插件安装、启用或升级。
:param operation: 批量操作类型
:param plugin_ids: 插件ID列表
:param dry_run: 是否仅预演
:param continue_on_error: 失败后是否继续执行后续插件
:return: 插件批量执行结果负载
"""
return cast(
'PluginBatchResponse',
await self.batch.batch_plugins(
operation,
plugin_ids,
dry_run=dry_run,
continue_on_error=continue_on_error,
),
)
async def execute_batch_plugin_item(self, operation: str, plugin_id: str) -> PluginBatchItemExecutionResponse:
"""
执行单个批量插件操作项。
:param operation: 批量操作类型
:param plugin_id: 插件ID
:return: 单插件操作结果负载
"""
return cast(
'PluginBatchItemExecutionResponse', await self.batch.execute_batch_plugin_item(operation, plugin_id)
)
def install_plugin_dependencies(
self,
plugin_id: str,
*,
dry_run: bool = False,
policy_config: DependencyInstallPolicyConfig | None = None,
confirmed: bool = False,
record_operation_log: bool = True,
output_callback: PluginCommandOutputCallback | None = None,
) -> PluginDependencyInstallResponse:
"""
安装插件依赖。
:param plugin_id: 插件ID
:param dry_run: 是否仅预演
:param policy_config: 依赖安装策略配置
:param confirmed: 是否已显式确认
:param record_operation_log: 是否记录插件操作审计日志
:param output_callback: 依赖安装实时输出回调
:return: 插件依赖安装负载
"""
if output_callback is None:
dependency_payload = self.dependency.install_plugin_dependencies(
plugin_id,
dry_run=dry_run,
policy_config=policy_config,
confirmed=confirmed,
)
else:
dependency_payload = self.dependency.install_plugin_dependencies(
plugin_id,
dry_run=dry_run,
policy_config=policy_config,
confirmed=confirmed,
output_callback=output_callback,
)
payload = cast('PluginDependencyInstallResponse', dependency_payload)
if record_operation_log and not dry_run:
self._record_plugin_operation_log_sync(payload, dry_run=False, continue_on_error=False)
return payload
def _record_plugin_operation_log_sync(
self,
payload: Mapping[str, object],
*,
dry_run: bool,
continue_on_error: bool,
) -> None:
"""
从同步入口记录插件操作审计日志。
:param payload: 操作结果负载
:param dry_run: 是否预演
:param continue_on_error: 失败后是否继续
:return: None
"""
record_coro = self.record_plugin_operation_log(
payload,
dry_run=dry_run,
continue_on_error=continue_on_error,
)
try:
running_loop = asyncio.get_running_loop()
except RuntimeError:
asyncio.run(record_coro)
return
task = running_loop.create_task(record_coro)
self._background_audit_tasks.add(task)
task.add_done_callback(self._background_audit_tasks.discard)
def install_plugin_dependencies_from_result(
self,
plugin_id: str,
dependency_result: object,
*,
dry_run: bool = False,
discovered_plugin: object | None = None,
policy_config: DependencyInstallPolicyConfig | None = None,
confirmed: bool = False,
) -> PluginDependencyInstallResponse:
"""
根据既有依赖检查结果生成计划并执行依赖安装。
:param plugin_id: 插件ID
:param dependency_result: 依赖检查结果
:param dry_run: 是否仅预演
:param discovered_plugin: 已发现插件
:param policy_config: 依赖安装策略配置
:param confirmed: 是否已显式确认
:return: 插件依赖安装负载
"""
return cast(
'PluginDependencyInstallResponse',
self.dependency.install_plugin_dependencies_from_result(
plugin_id,
dependency_result,
dry_run=dry_run,
discovered_plugin=discovered_plugin,
policy_config=policy_config,
confirmed=confirmed,
),
)
async def install_plugin_dependencies_from_result_async(
self,
plugin_id: str,
dependency_result: object,
*,
dry_run: bool = False,
discovered_plugin: object | None = None,
policy_config: DependencyInstallPolicyConfig | None = None,
confirmed: bool = False,
) -> PluginDependencyInstallResponse:
"""
根据既有依赖检查结果异步生成计划并执行依赖安装。
:param plugin_id: 插件ID
:param dependency_result: 依赖检查结果
:param dry_run: 是否仅预演
:param discovered_plugin: 已发现插件
:param policy_config: 依赖安装策略配置
:param confirmed: 是否已显式确认
:return: 插件依赖安装负载
"""
return cast(
'PluginDependencyInstallResponse',
await self.dependency.install_plugin_dependencies_from_result_async(
plugin_id,
dependency_result,
dry_run=dry_run,
discovered_plugin=discovered_plugin,
policy_config=policy_config,
confirmed=confirmed,
),
)
async def record_plugin_operation_log(
self,
payload: Mapping[str, object],
*,
dry_run: bool,
continue_on_error: bool,
) -> None:
"""
记录插件操作审计日志。
:param payload: 插件操作结果负载
:param dry_run: 是否预演
:param continue_on_error: 失败后是否继续
:return: None
"""
await self.audit.record_plugin_operation_log(
cast('dict[str, object]', payload),
dry_run=dry_run,
continue_on_error=continue_on_error,
)
async def record_plugin_failure_state(self, payload: Mapping[str, object], default_message: str) -> None:
"""
记录插件操作失败状态。
:param payload: 插件操作返回负载
:param default_message: 缺省失败信息
:return: None
"""
await self.audit.record_plugin_failure_state(cast('dict[str, object]', payload), default_message)
async def list_plugin_migrations(self, plugin_id: str, status: str | None = None) -> dict[str, object]:
"""
查询插件 migration 历史。
:param plugin_id: 插件ID
:param status: 执行状态
:return: 插件 migration 历史负载
"""
return await self.migration.list_plugin_migrations(plugin_id, status)
async def mark_plugin_migration_success(
self,
plugin_id: str,
migration_path: str,
*,
note: str | None = None,
record_operation_log: bool = True,
) -> dict[str, object]:
"""
人工标记插件 migration 为成功。
:param plugin_id: 插件ID
:param migration_path: migration 相对路径
:param note: 人工恢复备注
:param record_operation_log: 是否记录插件操作审计日志
:return: 插件 migration 状态标记负载
"""
return await self._mark_plugin_migration_status(
plugin_id,
migration_path,
'success',
note=note,
record_operation_log=record_operation_log,
)
async def mark_plugin_migration_failed(
self,
plugin_id: str,
migration_path: str,
*,
note: str | None = None,
record_operation_log: bool = True,
) -> dict[str, object]:
"""
人工标记插件 migration 为失败。
:param plugin_id: 插件ID
:param migration_path: migration 相对路径
:param note: 人工恢复备注
:param record_operation_log: 是否记录插件操作审计日志
:return: 插件 migration 状态标记负载
"""
return await self._mark_plugin_migration_status(
plugin_id,
migration_path,
'failed',
note=note,
record_operation_log=record_operation_log,
)
async def _mark_plugin_migration_status(
self,
plugin_id: str,
migration_path: str,
status: MigrationRecoveryStatus,
*,
note: str | None,
record_operation_log: bool,
) -> dict[str, object]:
"""
在生命周期锁内人工标记插件 migration 状态。
:param plugin_id: 插件ID
:param migration_path: migration 相对路径
:param status: 目标状态
:param note: 人工恢复备注
:param record_operation_log: 是否记录插件操作审计日志
:return: 插件 migration 状态标记负载
"""
operation = f'migration_mark_{status}'
async with self.lifecycle_lock.lock(plugin_id, operation) as lock_result:
if not lock_result.acquired:
return cast(
'dict[str, object]',
PluginRuntimePayloadBuilder.build_invalid_operation_payload(
plugin_id,
operation,
message=lock_result.message,
),
)
payload = await self.migration.mark_plugin_migration_status(
plugin_id,
migration_path,
status,
note=note,
)
if record_operation_log and payload.get('ok') is True:
await self.record_plugin_operation_log(payload, dry_run=False, continue_on_error=False)
return payload
async def install_plugin(
self,
plugin_id: str,
*,
dry_run: bool = False,
record_operation_log: bool = True,
operated_by: str | None = None,
) -> PluginLifecycleResponse:
"""
安装插件并按需记录审计日志。
:param plugin_id: 插件ID
:param dry_run: 是否仅预演
:param record_operation_log: 是否记录插件操作审计日志
:param operated_by: 操作者用户名,非预演时写入审计日志
:return: 插件安装结果负载
"""
return await self._run_with_lifecycle_lock(
plugin_id,
'install',
dry_run=dry_run,
operation=lambda: self.install.install_plugin(
plugin_id,
dry_run=dry_run,
record_operation_log=record_operation_log,
operated_by=operated_by,
),
)
async def set_plugin_enabled(
self,
plugin_id: str,
*,
enabled: bool,
dry_run: bool = False,
record_operation_log: bool = True,
) -> PluginLifecycleResponse:
"""
更新插件启停状态并按需记录审计日志。
:param plugin_id: 插件ID
:param enabled: 是否启用
:param dry_run: 是否仅预演
:param record_operation_log: 是否记录插件操作审计日志
:return: 插件启停结果负载
"""
operation = 'enable' if enabled else 'disable'
return await self._run_with_lifecycle_lock(
plugin_id,
operation,
dry_run=dry_run,
operation=lambda: self.enable.set_plugin_enabled(
plugin_id,
enabled=enabled,
dry_run=dry_run,
record_operation_log=record_operation_log,
),
)
async def uninstall_plugin(
self,
plugin_id: str,
*,
dry_run: bool = False,
record_operation_log: bool = True,
operated_by: str | None = None,
) -> PluginLifecycleResponse:
"""
安全卸载插件。
:param plugin_id: 插件ID
:param dry_run: 是否仅预演
:param record_operation_log: 是否记录插件操作审计日志
:param operated_by: 操作者用户名,非预演时写入审计日志
:return: 插件卸载结果负载
"""
return await self._run_with_lifecycle_lock(
plugin_id,
'uninstall',
dry_run=dry_run,
operation=lambda: self.enable.uninstall_plugin(
plugin_id,
dry_run=dry_run,
record_operation_log=record_operation_log,
operated_by=operated_by,
),
)
async def purge_plugin(
self,
plugin_id: str,
*,
dry_run: bool = False,
record_operation_log: bool = True,
operated_by: str | None = None,
) -> PluginLifecycleResponse:
"""
物理清理插件平台元数据并按需记录审计日志。
:param plugin_id: 插件ID
:param dry_run: 是否仅预演
:param record_operation_log: 是否记录插件操作审计日志
:param operated_by: 操作者用户名,非预演时写入审计日志
:return: 插件物理清理结果负载
"""
return await self._run_with_lifecycle_lock(
plugin_id,
'purge',
dry_run=dry_run,
operation=lambda: self.purge.purge_plugin(
plugin_id,
dry_run=dry_run,
record_operation_log=record_operation_log,
operated_by=operated_by,
),
)
async def upgrade_plugin(
self,
plugin_id: str,
*,
dry_run: bool = False,
record_operation_log: bool = True,
operated_by: str | None = None,
) -> PluginLifecycleResponse:
"""
升级插件并按需记录审计日志。
:param plugin_id: 插件ID
:param dry_run: 是否仅预演
:param record_operation_log: 是否记录插件操作审计日志
:param operated_by: 操作者用户名,非预演时写入审计日志
:return: 插件升级结果负载
"""
return await self._run_with_lifecycle_lock(
plugin_id,
'upgrade',
dry_run=dry_run,
operation=lambda: self.upgrade.upgrade_plugin(
plugin_id,
dry_run=dry_run,
record_operation_log=record_operation_log,
operated_by=operated_by,
),
)
async def _run_with_lifecycle_lock(
self,
plugin_id: str,
lock_operation: str,
*,
dry_run: bool,
operation: Callable[[], Awaitable[PluginLifecycleResponse]],
) -> PluginLifecycleResponse:
"""
在插件生命周期分布式锁内执行写操作。
:param plugin_id: 插件ID
:param lock_operation: 锁定的操作类型
:param dry_run: 是否仅预演
:param operation: 实际操作
:return: 插件生命周期操作结果
"""
if dry_run:
return await operation()
async with self.lifecycle_lock.lock(plugin_id, lock_operation) as lock_result:
if not lock_result.acquired:
return cast(
'PluginLifecycleResponse',
PluginRuntimePayloadBuilder.build_invalid_operation_payload(
plugin_id,
lock_operation,
message=lock_result.message,
),
)
return await operation()
def generate_plugin_docs(self, plugin_id: str) -> PluginDocumentationResponse:
"""
生成插件 Markdown 文档片段。
:param plugin_id: 插件ID
:return: 插件文档生成负载
"""
return self.tools.generate_plugin_docs(plugin_id)