mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-06 09:41:55 +00:00
* 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: 更新后端依赖文件
1250 lines
35 KiB
Python
1250 lines
35 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import threading
|
|
from collections.abc import Callable
|
|
from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeAlias, runtime_checkable
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Mapping
|
|
from contextlib import AbstractAsyncContextManager
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from common.vo import CrudResponseModel
|
|
from plugins.core.discovery.scanner import DiscoveredPlugin
|
|
from plugins.core.lifecycle.migration import PluginMigrationResult
|
|
from plugins.core.lifecycle.purge import PluginPurgePlan
|
|
from plugins.core.management.entity.vo.schemas import (
|
|
PluginConfigModel,
|
|
PluginConfigUpdateModel,
|
|
PluginConfigValueModel,
|
|
PluginMigrationModel,
|
|
PluginModel,
|
|
PluginOperationLogDetailModel,
|
|
PluginOperationLogExportQueryModel,
|
|
)
|
|
from plugins.core.types import PluginConfigValue, PluginStateRecord
|
|
from plugins.core.validation.menus import PluginMenuConflictItem
|
|
|
|
PluginCommandOutputKind = Literal['status', 'stdout', 'stderr']
|
|
PluginCommandOutputCallback: TypeAlias = Callable[[PluginCommandOutputKind, str], None]
|
|
|
|
|
|
def run_plugin_command(
|
|
command: list[str],
|
|
workdir: str,
|
|
*,
|
|
timeout: int | None = None,
|
|
output_callback: PluginCommandOutputCallback | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
"""
|
|
执行插件系统命令,并可选实时转发标准输出和错误输出。
|
|
|
|
:param command: 命令参数列表
|
|
:param workdir: 命令工作目录
|
|
:param timeout: 命令超时时间
|
|
:param output_callback: 实时输出回调
|
|
:return: 命令执行结果
|
|
"""
|
|
if output_callback is None:
|
|
return subprocess.run(
|
|
command,
|
|
cwd=workdir,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
timeout=timeout,
|
|
)
|
|
|
|
process = subprocess.Popen(
|
|
command,
|
|
cwd=workdir,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
stdout_parts: list[str] = []
|
|
stderr_parts: list[str] = []
|
|
callback_lock = threading.Lock()
|
|
|
|
def consume_stream(
|
|
stream: Any,
|
|
kind: Literal['stdout', 'stderr'],
|
|
output_parts: list[str],
|
|
) -> None:
|
|
"""持续读取并转发单个子进程输出流。"""
|
|
try:
|
|
for text in iter(stream.readline, ''):
|
|
output_parts.append(text)
|
|
with callback_lock:
|
|
output_callback(kind, text)
|
|
finally:
|
|
stream.close()
|
|
|
|
stdout_thread = threading.Thread(
|
|
target=consume_stream,
|
|
args=(process.stdout, 'stdout', stdout_parts),
|
|
daemon=True,
|
|
)
|
|
stderr_thread = threading.Thread(
|
|
target=consume_stream,
|
|
args=(process.stderr, 'stderr', stderr_parts),
|
|
daemon=True,
|
|
)
|
|
stdout_thread.start()
|
|
stderr_thread.start()
|
|
|
|
try:
|
|
return_code = process.wait(timeout=timeout)
|
|
except subprocess.TimeoutExpired as exc:
|
|
process.kill()
|
|
process.wait()
|
|
stdout_thread.join()
|
|
stderr_thread.join()
|
|
raise subprocess.TimeoutExpired(
|
|
command,
|
|
timeout,
|
|
output=''.join(stdout_parts),
|
|
stderr=''.join(stderr_parts),
|
|
) from exc
|
|
|
|
stdout_thread.join()
|
|
stderr_thread.join()
|
|
return subprocess.CompletedProcess(
|
|
args=command,
|
|
returncode=return_code,
|
|
stdout=''.join(stdout_parts),
|
|
stderr=''.join(stderr_parts),
|
|
)
|
|
|
|
|
|
@runtime_checkable
|
|
class AsyncSessionFactoryProtocol(Protocol):
|
|
"""
|
|
异步数据库会话工厂协议。
|
|
"""
|
|
|
|
def __call__(self) -> AbstractAsyncContextManager[AsyncSession]:
|
|
"""
|
|
创建异步数据库会话上下文。
|
|
|
|
:return: 异步数据库会话上下文
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginManagementServiceProtocol(Protocol):
|
|
"""
|
|
插件运行时依赖的管理服务协议。
|
|
"""
|
|
|
|
@classmethod
|
|
async def get_plugin_list_services(cls, query_db: AsyncSession) -> list[PluginStateRecord]:
|
|
"""
|
|
获取插件状态列表。
|
|
|
|
:param query_db: orm对象
|
|
:return: 插件状态列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def plugin_detail_services(cls, query_db: AsyncSession, plugin_id: str) -> PluginStateRecord | None:
|
|
"""
|
|
获取插件详情。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:return: 插件状态
|
|
"""
|
|
|
|
@classmethod
|
|
async def upsert_discovered_plugin_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
backend_root: Path,
|
|
frontend_root: Path | None = None,
|
|
) -> PluginModel:
|
|
"""
|
|
写入或更新已发现插件。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:param backend_root: 后端插件根目录
|
|
:param frontend_root: 前端插件根目录
|
|
:return: 插件信息
|
|
"""
|
|
|
|
@classmethod
|
|
async def update_plugin_enabled_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
enabled: bool,
|
|
discovered_plugin: DiscoveredPlugin | None = None,
|
|
) -> CrudResponseModel:
|
|
"""
|
|
更新插件启停状态。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:param enabled: 是否启用
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 操作响应
|
|
"""
|
|
|
|
@classmethod
|
|
async def mark_plugin_installed_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
) -> PluginModel:
|
|
"""
|
|
标记插件安装完成。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 插件信息
|
|
"""
|
|
|
|
@classmethod
|
|
async def mark_plugin_uninstalled_services(cls, query_db: AsyncSession, plugin_id: str) -> CrudResponseModel:
|
|
"""
|
|
标记插件卸载完成。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:return: 操作响应
|
|
"""
|
|
|
|
@classmethod
|
|
async def install_plugin_menu_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
*,
|
|
enabled: bool,
|
|
) -> None:
|
|
"""
|
|
安装插件菜单。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:param enabled: 是否启用
|
|
:return: None
|
|
"""
|
|
|
|
@classmethod
|
|
async def install_plugin_default_config_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
) -> list[PluginConfigModel]:
|
|
"""
|
|
安装插件默认配置。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 插件配置列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def install_plugin_job_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
*,
|
|
enabled: bool,
|
|
) -> None:
|
|
"""
|
|
同步单个插件任务。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件
|
|
:param enabled: 插件任务是否启用
|
|
:return: None
|
|
"""
|
|
|
|
@classmethod
|
|
async def get_plugin_config_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
*,
|
|
reveal_secret: bool = False,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
获取插件配置。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:param reveal_secret: 是否展示敏感配置原值
|
|
:return: 插件配置值列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def is_plugin_installed_services(cls, query_db: AsyncSession, plugin_id: str) -> bool:
|
|
"""
|
|
判断插件是否已经完成安装。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:return: 是否已安装
|
|
"""
|
|
|
|
@classmethod
|
|
async def update_plugin_config_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
update_model: PluginConfigUpdateModel,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
更新插件配置。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:param update_model: 配置更新对象
|
|
:return: 插件配置值列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def add_plugin_operation_log_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
payload: dict[str, Any],
|
|
*,
|
|
dry_run: bool,
|
|
continue_on_error: bool,
|
|
) -> object:
|
|
"""
|
|
记录插件操作审计日志。
|
|
|
|
:param query_db: orm对象
|
|
:param payload: 操作结果负载
|
|
:param dry_run: 是否预演
|
|
:param continue_on_error: 失败后是否继续
|
|
:return: 操作日志模型
|
|
"""
|
|
|
|
@classmethod
|
|
async def get_plugin_operation_log_export_list_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
query_object: PluginOperationLogExportQueryModel,
|
|
) -> list[PluginOperationLogDetailModel]:
|
|
"""
|
|
获取插件操作日志导出列表。
|
|
|
|
:param query_db: orm对象
|
|
:param query_object: 操作日志导出查询对象
|
|
:return: 操作日志详情列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def check_installed_menu_conflict_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
) -> list[PluginMenuConflictItem]:
|
|
"""
|
|
检查已安装菜单冲突。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 菜单冲突列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def mark_plugin_error_services(
|
|
cls, query_db: AsyncSession, plugin_id: str, error_message: str
|
|
) -> CrudResponseModel:
|
|
"""
|
|
标记插件错误。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:param error_message: 错误信息
|
|
:return: 操作响应
|
|
"""
|
|
|
|
@classmethod
|
|
async def build_plugin_purge_plan_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
) -> PluginPurgePlan:
|
|
"""
|
|
构建插件清理计划。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 插件清理计划
|
|
"""
|
|
|
|
@classmethod
|
|
async def purge_plugin_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
) -> PluginPurgePlan:
|
|
"""
|
|
清理插件平台元数据。
|
|
|
|
:param query_db: orm对象
|
|
:param discovered_plugin: 已发现插件对象
|
|
:return: 插件清理计划
|
|
"""
|
|
|
|
@classmethod
|
|
async def build_plugin_purge_plan_by_id_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
) -> PluginPurgePlan:
|
|
"""
|
|
按插件 ID 构建孤儿元数据清理计划。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:return: 插件清理计划
|
|
"""
|
|
|
|
@classmethod
|
|
async def purge_plugin_metadata_by_id_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
) -> PluginPurgePlan:
|
|
"""
|
|
按插件 ID 清理孤儿元数据。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:return: 插件清理计划
|
|
"""
|
|
|
|
@classmethod
|
|
async def get_plugin_migration_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
) -> PluginMigrationModel | None:
|
|
"""
|
|
获取插件 migration 历史。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:return: 插件 migration 历史
|
|
"""
|
|
|
|
@classmethod
|
|
async def get_plugin_migration_list_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
status: str | None = None,
|
|
) -> list[PluginMigrationModel]:
|
|
"""
|
|
获取插件 migration 历史列表。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:param status: 执行状态
|
|
:return: 插件 migration 历史列表
|
|
"""
|
|
|
|
@classmethod
|
|
async def add_plugin_migration_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_migration: PluginMigrationModel,
|
|
) -> PluginMigrationModel:
|
|
"""
|
|
新增插件 migration 历史。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_migration: 插件 migration 历史
|
|
:return: 插件 migration 历史
|
|
"""
|
|
|
|
@classmethod
|
|
async def mark_plugin_migration_status_services(
|
|
cls,
|
|
query_db: AsyncSession,
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
status: str,
|
|
error_message: str | None = None,
|
|
) -> PluginMigrationModel | None:
|
|
"""
|
|
人工标记插件 migration 历史状态。
|
|
|
|
:param query_db: orm对象
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:param status: 执行状态
|
|
:param error_message: 失败错误信息
|
|
:return: 插件 migration 历史
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginConfigGateway(Protocol):
|
|
"""
|
|
插件配置网关协议。
|
|
"""
|
|
|
|
async def get_plugin_config(
|
|
self,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
*,
|
|
reveal_secret: bool = False,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
获取插件配置。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param reveal_secret: 是否展示敏感配置原值
|
|
:return: 插件配置列表
|
|
"""
|
|
|
|
async def update_plugin_config(
|
|
self,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
values: dict[str, PluginConfigValue],
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
更新插件配置。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param values: 配置键值
|
|
:return: 插件配置列表
|
|
"""
|
|
|
|
async def set_plugin_config(
|
|
self,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
values: dict[str, PluginConfigValue],
|
|
*,
|
|
audit_operation: str,
|
|
success_message: str,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
在同一事务中更新插件配置并记录审计日志。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param values: 配置键值
|
|
:param audit_operation: 审计操作类型
|
|
:param success_message: 操作成功提示
|
|
:return: 插件配置列表
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginAuditGateway(Protocol):
|
|
"""
|
|
插件审计网关协议。
|
|
"""
|
|
|
|
async def list_plugin_operation_logs(self, *, export_limit: int) -> list[PluginOperationLogDetailModel]:
|
|
"""
|
|
获取插件操作审计日志列表。
|
|
|
|
:param export_limit: 导出数量上限
|
|
:return: 插件操作日志详情列表
|
|
"""
|
|
|
|
async def add_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
|
|
"""
|
|
|
|
async def mark_plugin_error(self, plugin_id: str, error_message: str) -> bool:
|
|
"""
|
|
标记插件错误状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param error_message: 错误信息
|
|
:return: 是否标记成功
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginStateQueryGateway(Protocol):
|
|
"""
|
|
插件状态查询网关协议。
|
|
"""
|
|
|
|
async def list_plugin_states(self) -> list[PluginStateRecord]:
|
|
"""
|
|
获取插件状态列表。
|
|
|
|
:return: 插件状态列表
|
|
"""
|
|
|
|
async def get_plugin_state(self, plugin_id: str) -> PluginStateRecord | None:
|
|
"""
|
|
获取插件状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 插件状态
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginMigrationHistoryGateway(Protocol):
|
|
"""
|
|
插件 migration 历史网关协议。
|
|
"""
|
|
|
|
async def list_plugin_migrations(
|
|
self,
|
|
plugin_id: str,
|
|
status: str | None = None,
|
|
) -> list[PluginMigrationModel]:
|
|
"""
|
|
查询插件 migration 历史。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param status: 执行状态
|
|
:return: 插件 migration 历史列表
|
|
"""
|
|
|
|
async def get_plugin_migration(self, plugin_id: str, migration_path: str) -> PluginMigrationModel | None:
|
|
"""
|
|
获取插件 migration 历史。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:return: 插件 migration 历史
|
|
"""
|
|
|
|
async def mark_plugin_migration_status(
|
|
self,
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
status: str,
|
|
error_message: str | None = None,
|
|
) -> PluginMigrationModel | None:
|
|
"""
|
|
人工标记插件 migration 历史状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:param status: 执行状态
|
|
:param error_message: 失败错误信息
|
|
:return: 插件 migration 历史
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginPurgePlanGateway(Protocol):
|
|
"""
|
|
插件清理计划网关协议。
|
|
"""
|
|
|
|
async def build_plugin_purge_plan(self, discovered_plugin: DiscoveredPlugin) -> PluginPurgePlan:
|
|
"""
|
|
构建插件物理清理计划。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件物理清理计划
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginLifecycleStateGateway(Protocol):
|
|
"""
|
|
插件生命周期状态写入网关协议。
|
|
"""
|
|
|
|
async def set_plugin_enabled_state(
|
|
self,
|
|
plugin_id: str,
|
|
enabled: bool,
|
|
discovered_plugin: DiscoveredPlugin | None = None,
|
|
) -> CrudResponseModel:
|
|
"""
|
|
更新插件启停状态,并在启用时同步插件菜单。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param enabled: 是否启用
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 操作响应
|
|
"""
|
|
|
|
async def mark_plugin_uninstalled_state(self, plugin_id: str) -> CrudResponseModel:
|
|
"""
|
|
标记插件安全卸载。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 操作响应
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginLifecycleUnitOfWork(Protocol):
|
|
"""
|
|
插件生命周期主事务工作单元协议。
|
|
"""
|
|
|
|
session: AsyncSession
|
|
|
|
async def check_installed_menu_conflicts(self, discovered_plugin: DiscoveredPlugin) -> list[PluginMenuConflictItem]:
|
|
"""
|
|
检查已安装菜单冲突。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 菜单冲突列表
|
|
"""
|
|
|
|
async def upsert_discovered_plugin(
|
|
self,
|
|
discovered_plugin: DiscoveredPlugin,
|
|
backend_root: Path,
|
|
frontend_root: Path | None = None,
|
|
) -> PluginModel:
|
|
"""
|
|
写入或更新已发现插件。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param backend_root: 后端插件根目录
|
|
:param frontend_root: 前端插件根目录
|
|
:return: 插件模型
|
|
"""
|
|
|
|
async def install_plugin_menu(self, discovered_plugin: DiscoveredPlugin, *, enabled: bool) -> None:
|
|
"""
|
|
安装插件菜单。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param enabled: 是否启用菜单
|
|
:return: None
|
|
"""
|
|
|
|
async def install_plugin_default_config(self, discovered_plugin: DiscoveredPlugin) -> list[PluginConfigModel]:
|
|
"""
|
|
安装插件默认配置。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件配置列表
|
|
"""
|
|
|
|
async def install_plugin_jobs(self, discovered_plugin: DiscoveredPlugin, *, enabled: bool) -> None:
|
|
"""
|
|
同步单个插件任务。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param enabled: 插件任务是否启用
|
|
:return: None
|
|
"""
|
|
|
|
async def mark_plugin_installed(self, discovered_plugin: DiscoveredPlugin) -> PluginModel:
|
|
"""
|
|
标记插件已安装。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件模型
|
|
"""
|
|
|
|
async def build_plugin_purge_plan(self, discovered_plugin: DiscoveredPlugin) -> PluginPurgePlan:
|
|
"""
|
|
构建插件物理清理计划。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件物理清理计划
|
|
"""
|
|
|
|
async def purge_plugin_metadata(self, discovered_plugin: DiscoveredPlugin) -> PluginPurgePlan:
|
|
"""
|
|
清理插件平台元数据。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件物理清理计划
|
|
"""
|
|
|
|
async def build_plugin_purge_plan_by_id(self, plugin_id: str) -> PluginPurgePlan:
|
|
"""
|
|
按插件 ID 构建孤儿元数据清理计划。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 插件物理清理计划
|
|
"""
|
|
|
|
async def purge_plugin_metadata_by_id(self, plugin_id: str) -> PluginPurgePlan:
|
|
"""
|
|
按插件 ID 清理孤儿元数据。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 插件物理清理计划
|
|
"""
|
|
|
|
async def commit(self) -> None:
|
|
"""
|
|
提交生命周期主事务。
|
|
|
|
:return: None
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginLifecycleUnitOfWorkGateway(Protocol):
|
|
"""
|
|
插件生命周期主事务工作单元网关协议。
|
|
"""
|
|
|
|
def open_lifecycle_unit_of_work(self) -> AbstractAsyncContextManager[PluginLifecycleUnitOfWork]:
|
|
"""
|
|
打开生命周期主事务工作单元。
|
|
|
|
:return: 生命周期主事务工作单元上下文
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginMigrationExecutionGateway(Protocol):
|
|
"""
|
|
插件 migration 独立执行网关协议。
|
|
"""
|
|
|
|
async def run_plugin_migrations(self, discovered_plugin: DiscoveredPlugin) -> list[PluginMigrationResult]:
|
|
"""
|
|
使用独立执行事务运行插件 migration。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: migration 执行结果列表
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginManagementModelGateway(Protocol):
|
|
"""
|
|
插件管理模型工厂网关协议。
|
|
"""
|
|
|
|
def build_operation_log_export_query(self, export_limit: int) -> PluginOperationLogExportQueryModel:
|
|
"""
|
|
构建插件操作日志导出查询对象。
|
|
|
|
:param export_limit: 导出数量上限
|
|
:return: 插件操作日志导出查询对象
|
|
"""
|
|
|
|
def build_config_update(self, values: dict[str, PluginConfigValue]) -> PluginConfigUpdateModel:
|
|
"""
|
|
构建插件配置更新对象。
|
|
|
|
:param values: 配置键值
|
|
:return: 插件配置更新对象
|
|
"""
|
|
|
|
def build_migration_record(
|
|
self,
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
checksum: str,
|
|
version: str,
|
|
statement_count: int,
|
|
status: str = 'success',
|
|
error_message: str | None = None,
|
|
) -> PluginMigrationModel:
|
|
"""
|
|
构建插件 migration 执行历史对象。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:param checksum: 内容校验值
|
|
:param version: 执行时插件版本
|
|
:param statement_count: SQL 语句数量
|
|
:return: 插件 migration 执行历史对象
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class PluginCommandRunnerGateway(Protocol):
|
|
"""
|
|
插件命令执行网关协议。
|
|
"""
|
|
|
|
def run_command(
|
|
self,
|
|
command: list[str],
|
|
workdir: str,
|
|
*,
|
|
timeout: int | None = None,
|
|
output_callback: PluginCommandOutputCallback | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
"""
|
|
执行系统命令。
|
|
|
|
:param command: 命令参数列表
|
|
:param workdir: 命令工作目录
|
|
:param timeout: 命令超时时间
|
|
:param output_callback: 实时输出回调
|
|
:return: 命令执行结果
|
|
"""
|
|
|
|
|
|
class UnavailablePluginStateQueryGateway:
|
|
"""
|
|
不可用的插件状态查询网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def list_plugin_states() -> list[PluginStateRecord]:
|
|
"""
|
|
获取插件状态列表。
|
|
|
|
:return: 插件状态列表
|
|
:raises RuntimeError: 默认网关不提供插件状态查询能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件状态查询适配器')
|
|
|
|
@staticmethod
|
|
async def get_plugin_state(plugin_id: str) -> PluginStateRecord | None:
|
|
"""
|
|
获取插件状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 插件状态
|
|
:raises RuntimeError: 默认网关不提供插件状态查询能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件状态查询适配器')
|
|
|
|
|
|
class UnavailablePluginMigrationHistoryGateway:
|
|
"""
|
|
不可用的插件 migration 历史网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def list_plugin_migrations(
|
|
plugin_id: str,
|
|
status: str | None = None,
|
|
) -> list[PluginMigrationModel]:
|
|
"""
|
|
查询插件 migration 历史。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param status: 执行状态
|
|
:return: 插件 migration 历史列表
|
|
:raises RuntimeError: 默认网关不提供插件 migration 历史能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少 migration 历史适配器')
|
|
|
|
@staticmethod
|
|
async def get_plugin_migration(plugin_id: str, migration_path: str) -> PluginMigrationModel | None:
|
|
"""
|
|
获取插件 migration 历史。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:return: 插件 migration 历史
|
|
:raises RuntimeError: 默认网关不提供插件 migration 历史能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少 migration 历史适配器')
|
|
|
|
@staticmethod
|
|
async def mark_plugin_migration_status(
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
status: str,
|
|
error_message: str | None = None,
|
|
) -> PluginMigrationModel | None:
|
|
"""
|
|
人工标记插件 migration 历史状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:param status: 执行状态
|
|
:param error_message: 失败错误信息
|
|
:return: 插件 migration 历史
|
|
:raises RuntimeError: 默认网关不提供插件 migration 历史能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少 migration 历史适配器')
|
|
|
|
|
|
class UnavailablePluginPurgePlanGateway:
|
|
"""
|
|
不可用的插件清理计划网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def build_plugin_purge_plan(discovered_plugin: DiscoveredPlugin) -> PluginPurgePlan:
|
|
"""
|
|
构建插件物理清理计划。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 插件物理清理计划
|
|
:raises RuntimeError: 默认网关不提供插件清理计划能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少清理计划适配器')
|
|
|
|
|
|
class UnavailablePluginLifecycleStateGateway:
|
|
"""
|
|
不可用的插件生命周期状态写入网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def set_plugin_enabled_state(
|
|
plugin_id: str,
|
|
enabled: bool,
|
|
discovered_plugin: DiscoveredPlugin | None = None,
|
|
) -> CrudResponseModel:
|
|
"""
|
|
更新插件启停状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param enabled: 是否启用
|
|
:param discovered_plugin: 已发现插件
|
|
:return: 操作响应
|
|
:raises RuntimeError: 默认网关不提供生命周期状态写入能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少生命周期状态适配器')
|
|
|
|
@staticmethod
|
|
async def mark_plugin_uninstalled_state(plugin_id: str) -> CrudResponseModel:
|
|
"""
|
|
标记插件安全卸载。
|
|
|
|
:param plugin_id: 插件ID
|
|
:return: 操作响应
|
|
:raises RuntimeError: 默认网关不提供生命周期状态写入能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少生命周期状态适配器')
|
|
|
|
|
|
class UnavailablePluginLifecycleUnitOfWorkGateway:
|
|
"""
|
|
不可用的插件生命周期主事务工作单元网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
def open_lifecycle_unit_of_work() -> AbstractAsyncContextManager[PluginLifecycleUnitOfWork]:
|
|
"""
|
|
打开生命周期主事务工作单元。
|
|
|
|
:return: 生命周期主事务工作单元上下文
|
|
:raises RuntimeError: 默认网关不提供生命周期主事务能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少生命周期主事务适配器')
|
|
|
|
|
|
class UnavailablePluginMigrationExecutionGateway:
|
|
"""
|
|
不可用的插件 migration 独立执行网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def run_plugin_migrations(discovered_plugin: DiscoveredPlugin) -> list[PluginMigrationResult]:
|
|
"""
|
|
使用独立执行事务运行插件 migration。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:return: migration 执行结果列表
|
|
:raises RuntimeError: 默认网关不提供 migration 执行能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少 migration 执行适配器')
|
|
|
|
|
|
class UnavailablePluginConfigGateway:
|
|
"""
|
|
不可用的插件配置网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def get_plugin_config(
|
|
discovered_plugin: DiscoveredPlugin,
|
|
*,
|
|
reveal_secret: bool = False,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
获取插件配置。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param reveal_secret: 是否展示敏感配置原值
|
|
:return: 插件配置列表
|
|
:raises RuntimeError: 默认网关不提供插件配置能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件配置适配器')
|
|
|
|
@staticmethod
|
|
async def update_plugin_config(
|
|
discovered_plugin: DiscoveredPlugin,
|
|
values: dict[str, PluginConfigValue],
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
更新插件配置。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param values: 配置键值
|
|
:return: 插件配置列表
|
|
:raises RuntimeError: 默认网关不提供插件配置能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件配置适配器')
|
|
|
|
@staticmethod
|
|
async def set_plugin_config(
|
|
discovered_plugin: DiscoveredPlugin,
|
|
values: dict[str, PluginConfigValue],
|
|
*,
|
|
audit_operation: str,
|
|
success_message: str,
|
|
) -> list[PluginConfigValueModel]:
|
|
"""
|
|
在同一事务中更新插件配置并记录审计日志。
|
|
|
|
:param discovered_plugin: 已发现插件
|
|
:param values: 配置键值
|
|
:param audit_operation: 审计操作类型
|
|
:param success_message: 操作成功提示
|
|
:return: 插件配置列表
|
|
:raises RuntimeError: 默认网关不提供插件配置能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件配置适配器')
|
|
|
|
|
|
class UnavailablePluginAuditGateway:
|
|
"""
|
|
不可用的插件审计网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
async def list_plugin_operation_logs(*, export_limit: int) -> list[PluginOperationLogDetailModel]:
|
|
"""
|
|
获取插件操作审计日志列表。
|
|
|
|
:param export_limit: 导出数量上限
|
|
:return: 插件操作日志详情列表
|
|
:raises RuntimeError: 默认网关不提供插件审计能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件审计适配器')
|
|
|
|
@staticmethod
|
|
async def add_plugin_operation_log(
|
|
payload: Mapping[str, object],
|
|
*,
|
|
dry_run: bool,
|
|
continue_on_error: bool,
|
|
) -> None:
|
|
"""
|
|
记录插件操作审计日志。
|
|
|
|
:param payload: 操作日志负载
|
|
:param dry_run: 是否预演
|
|
:param continue_on_error: 失败后是否继续
|
|
:raises RuntimeError: 默认网关不提供插件审计能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件审计适配器')
|
|
|
|
@staticmethod
|
|
async def mark_plugin_error(plugin_id: str, error_message: str) -> bool:
|
|
"""
|
|
标记插件错误状态。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param error_message: 错误信息
|
|
:return: 是否标记成功
|
|
:raises RuntimeError: 默认网关不提供插件审计能力
|
|
"""
|
|
raise RuntimeError('插件运行时缺少插件审计适配器')
|
|
|
|
|
|
class UnavailablePluginManagementModelGateway:
|
|
"""
|
|
不可用的插件管理模型工厂网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
def build_operation_log_export_query(export_limit: int) -> PluginOperationLogExportQueryModel:
|
|
"""
|
|
构建插件操作日志导出查询对象。
|
|
|
|
:param export_limit: 导出数量上限
|
|
:return: 插件操作日志导出查询对象
|
|
:raises RuntimeError: 默认网关不提供管理状态 VO 适配器
|
|
"""
|
|
raise RuntimeError('插件运行时缺少操作日志查询适配器')
|
|
|
|
@staticmethod
|
|
def build_config_update(values: dict[str, PluginConfigValue]) -> PluginConfigUpdateModel:
|
|
"""
|
|
构建插件配置更新对象。
|
|
|
|
:param values: 配置键值
|
|
:return: 插件配置更新对象
|
|
:raises RuntimeError: 默认网关不提供管理状态 VO 适配器
|
|
"""
|
|
raise RuntimeError('插件运行时缺少配置更新适配器')
|
|
|
|
@staticmethod
|
|
def build_migration_record(
|
|
plugin_id: str,
|
|
migration_path: str,
|
|
checksum: str,
|
|
version: str,
|
|
statement_count: int,
|
|
status: str = 'success',
|
|
error_message: str | None = None,
|
|
) -> PluginMigrationModel:
|
|
"""
|
|
构建插件 migration 执行历史对象。
|
|
|
|
:param plugin_id: 插件ID
|
|
:param migration_path: migration 相对路径
|
|
:param checksum: 内容校验值
|
|
:param version: 执行时插件版本
|
|
:param statement_count: SQL 语句数量
|
|
:return: 插件 migration 执行历史对象
|
|
:raises RuntimeError: 默认网关不提供管理状态 VO 适配器
|
|
"""
|
|
raise RuntimeError('插件运行时缺少 migration 历史记录适配器')
|
|
|
|
|
|
class DefaultPluginCommandRunnerGateway:
|
|
"""
|
|
默认插件命令执行网关。
|
|
"""
|
|
|
|
@staticmethod
|
|
def run_command(
|
|
command: list[str],
|
|
workdir: str,
|
|
*,
|
|
timeout: int | None = None,
|
|
output_callback: PluginCommandOutputCallback | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
"""
|
|
执行系统命令。
|
|
|
|
:param command: 命令参数列表
|
|
:param workdir: 命令工作目录
|
|
:param timeout: 命令超时时间
|
|
:param output_callback: 实时输出回调
|
|
:return: 命令执行结果
|
|
"""
|
|
return run_plugin_command(
|
|
command,
|
|
workdir,
|
|
timeout=timeout,
|
|
output_callback=output_callback,
|
|
)
|