Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/cli/groups/plugin/payload.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

155 lines
5.4 KiB
Python

from copy import deepcopy
from typing import Any
class PluginCommandPayloadAdapter:
"""
插件 CLI 输出负载适配器。
核心运行时 payload 同时服务管理接口和 CLI。CLI 在输出前通过本适配器消除
`enabled` 的多重语义,不改变管理接口既有契约。
"""
_LIFECYCLE_OPERATIONS = {'enable', 'disable', 'uninstall'}
@classmethod
def adapt(cls, payload: dict[str, Any]) -> dict[str, Any]:
"""
生成语义明确的 CLI 输出负载。
:param payload: 核心运行时负载
:return: CLI 输出负载
"""
adapted_payload = deepcopy(payload)
cls._adapt_node(adapted_payload)
return adapted_payload
@classmethod
def _adapt_node(cls, node: object, *, parent_key: str | None = None) -> None:
"""
递归适配负载节点。
:param node: 当前负载节点
:param parent_key: 当前节点在父对象中的字段名
:return: None
"""
if isinstance(node, list):
for item in node:
cls._adapt_node(item, parent_key=parent_key)
return
if not isinstance(node, dict):
return
cls._adapt_database_state(node, parent_key)
cls._adapt_lifecycle_target(node)
cls._adapt_action_state(node, parent_key)
cls._adapt_plan_plugin_state(node, parent_key)
cls._adapt_plugin_runtime_state(node)
cls._adapt_manifest_job_state(node, parent_key)
for key, value in list(node.items()):
cls._adapt_node(value, parent_key=key)
@staticmethod
def _adapt_database_state(node: dict[str, Any], parent_key: str | None) -> None:
"""
将数据库启停枚举转换为语义明确的布尔字段。
:param node: 当前负载节点
:param parent_key: 当前节点在父对象中的字段名
:return: None
"""
if parent_key != 'database' or 'enabled' not in node:
return
database_enabled = node.pop('enabled')
node['configuredEnabled'] = PluginCommandPayloadAdapter._normalize_enabled_value(database_enabled)
@classmethod
def _adapt_lifecycle_target(cls, node: dict[str, Any]) -> None:
"""
将生命周期结果中的目标启停值改为 targetEnabled。
:param node: 当前负载节点
:return: None
"""
if node.get('operation') == 'purge' and 'enabled' in node:
node.pop('enabled')
return
if (
node.get('operation') in cls._LIFECYCLE_OPERATIONS
and 'pluginId' in node
and 'dryRun' in node
and 'enabled' in node
):
node['targetEnabled'] = node.pop('enabled')
@staticmethod
def _adapt_action_state(node: dict[str, Any], parent_key: str | None) -> None:
"""
将动作和清理计划项中的 enabled 改为 willRun。
:param node: 当前负载节点
:param parent_key: 当前节点在父对象中的字段名
:return: None
"""
is_action = parent_key == 'actions' and 'name' in node
is_purge_plan_item = parent_key == 'items' and 'destructive' in node and 'label' in node
if (is_action or is_purge_plan_item) and isinstance(node.get('enabled'), bool):
node['willRun'] = node.pop('enabled')
@staticmethod
def _adapt_plan_plugin_state(node: dict[str, Any], parent_key: str | None) -> None:
"""
将批量计划项中的数据库启停枚举改为 configuredEnabled。
:param node: 当前负载节点
:param parent_key: 当前节点在父对象中的字段名
:return: None
"""
if parent_key != 'items' or 'pluginId' not in node or 'ready' not in node or 'enabled' not in node:
return
configured_enabled = node.pop('enabled')
node['configuredEnabled'] = PluginCommandPayloadAdapter._normalize_enabled_value(configured_enabled)
@staticmethod
def _adapt_plugin_runtime_state(node: dict[str, Any]) -> None:
"""
将插件有效启用态改为 runtimeEnabled。
:param node: 当前负载节点
:return: None
"""
if 'pluginId' not in node or 'status' not in node or 'enabled' not in node or 'dryRun' in node:
return
enabled = node.pop('enabled')
if isinstance(enabled, bool):
node['runtimeEnabled'] = enabled
return
node['configuredEnabled'] = PluginCommandPayloadAdapter._normalize_enabled_value(enabled)
@staticmethod
def _adapt_manifest_job_state(node: dict[str, Any], parent_key: str | None) -> None:
"""
将 manifest 任务默认启用配置改为 defaultEnabled。
:param node: 当前负载节点
:param parent_key: 当前节点在父对象中的字段名
:return: None
"""
if parent_key == 'jobs' and 'id' in node and isinstance(node.get('enabled'), bool):
node['defaultEnabled'] = node.pop('enabled')
@staticmethod
def _normalize_enabled_value(value: object) -> object:
"""
将数据库 0/1 枚举转换为布尔值,其余值原样保留。
:param value: 原始启停值
:return: 规范化启停值
"""
if isinstance(value, str) and value in {'0', '1'}:
return value == '0'
return value
__all__ = ['PluginCommandPayloadAdapter']