Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/cli/groups/plugin/payload.py
T
insistence 4bd83bc7cd feat: 支持 v2 插件、独立前端和签名制品发布 (#130)
* feat: 支持 v2 插件、原生扩展与签名发布

* fix: 修复测试

* feat: 增强宿主 SDK、运行观测与制品维护能力

* feat: 增强文件传输、配置观测与本地开发能力

* feat: 支持 SSE 实时事件流并完善开发脚手架

* feat: 完善长连接鉴权复核与关闭回收

* feat: 增强生命周期与任务关闭可靠性

* feat: 收紧健康检查契约并补齐前端与真实网络验收

* feat: 增加SDK溯源与离线更新工具并验证跨版本兼容

* feat: 增加持久化业务示例与受控 Python 交付

* feat: 增加统一运行诊断与多worker状态观测

* feat: 增加固定目标发布等待与自动验收

* fix: 修复多环境插件部署前缀与代理配置不一致

* fix: 修复测试
2026-10-09 23:49:45 +08:00

158 lines
5.6 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()):
if key == 'runtime':
# 新运行观测使用 API 与 CLI 共享契约,不重新解释发布目标的 enabled。
continue
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']