ForcePilot/backend/package/yuxi/channels/contract/dtos/plugin.py
Kris 742299cb07 chore: 批量清理报告相关代码并完成多项功能迭代
本次提交包含多维度代码优化与功能增强:
1.  移除报告模块冗余导入与枚举,清理报表相关代码
2.  新增扫码登录支持方法与飞书适配器适配
3.  完善异常日志与健康检查信息
4.  扩展目录、配对管理、能力查询等接口
5.  优化出站管道与事务提交后钩子逻辑
6.  修复飞书消息解析与响应空值问题
7.  重构配置更新与服务账号创建逻辑
8.  统一传输错误分类契约与错误基类扩展
2026-07-06 20:49:35 +08:00

433 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""插件 DTO。
定义插件相关的枚举、不可变值对象与 Protocol包括权限枚举、环境变量、
领域事件、事件处理 Protocol、控制 RPC 处理 Protocol、配置加载 Protocol
与配置解密 Protocol。所有枚举继承 ``str, Enum`` 以支持 JSON 序列化,
DTO 均为 ``dataclass(frozen=True)``Protocol 使用 ``@runtime_checkable``
装饰以支持 ``isinstance`` 检查。仅依赖标准库,用于插件生命周期、事件
分发、控制 RPC 与配置加载解密。
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import TYPE_CHECKING, Any, Literal, Protocol, runtime_checkable
from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator
from yuxi.channels.contract.errors import ValidationError
if TYPE_CHECKING:
from yuxi.channels.contract.dtos.capability import ChannelCapabilities
from yuxi.channels.contract.plugin.manifest import PluginManifest
# 插件生命周期状态字面量类型(对齐 LifecycleState 枚举值)。
# 供 PluginSummary / PluginCatalogItem / PluginDetail 的 state 字段与
# Router 层 state 查询参数共用,避免 11 个取值在多处重复声明。
PluginStateLiteral = Literal[
"discovered",
"resolved",
"loaded",
"initialized",
"started",
"paused",
"stopped",
"unloaded",
"failed",
"not_installed",
"installed",
]
class Permission(StrEnum):
"""权限等级。
标识插件或操作所需的用户权限等级,用于权限校验与审计。继承
``str, Enum`` 以支持 JSON 序列化与字符串比较。
取值:
REQUIRED_USER: 普通需求用户。
ADMIN_USER: 管理员用户。
SUPERADMIN_USER: 超级管理员用户。
"""
REQUIRED_USER = "required_user"
ADMIN_USER = "admin_user"
SUPERADMIN_USER = "superadmin_user"
@dataclass(frozen=True)
class EnvVar:
"""环境变量声明。
描述插件所需的环境变量元信息,包括名称、描述、是否必填、默认值与
是否敏感,用于插件安装向导的环境变量校验与脱敏渲染。
字段:
name: 环境变量名称。
description: 环境变量描述。
required: 是否必填(默认 True
default: 默认值(可选)。
sensitive: 是否敏感(需脱敏,默认 False
"""
name: str
description: str
required: bool = True
default: str | None = None
sensitive: bool = False
@dataclass(frozen=True)
class DomainEvent:
"""领域事件。
描述插件生命周期或业务流程中产生的领域事件,包括事件 ID、事件类型、
负载、时间戳与链路追踪 ID用于事件总线分发与审计。
字段:
event_id: 事件 ID。
event_type: 事件类型PluginLoaded | PairingApproved | ConfigChanged | ...)。
payload: 事件负载。
timestamp: 事件时间戳。
trace_id: 链路追踪 ID可选
"""
event_id: str
event_type: str
payload: dict[str, Any]
timestamp: datetime
trace_id: str | None = None
@runtime_checkable
class EventHandler(Protocol):
"""事件处理 Protocol。
由插件实现,用于订阅并处理领域事件。使用 ``@runtime_checkable``
装饰以支持 ``isinstance`` 检查。事件处理为异步方法,避免阻塞
事件总线分发线程。
"""
async def handle(self, event: DomainEvent) -> None:
"""处理领域事件。
参数:
event: 领域事件。
"""
...
@runtime_checkable
class ConfigLoader(Protocol):
"""配置加载 Protocol。
由插件实现,用于加载插件配置并返回配置字典。使用
``@runtime_checkable`` 装饰以支持 ``isinstance`` 检查。配置加载
为同步方法,适用于启动期一次性加载场景。
"""
def load(self) -> dict[str, Any]:
"""加载插件配置。
返回:
配置字典。
"""
...
@runtime_checkable
class ConfigDecryptor(Protocol):
"""配置解密 Protocol。
由插件实现,用于解密敏感配置值。使用 ``@runtime_checkable`` 装饰
以支持 ``isinstance`` 检查。解密为同步方法,适用于配置加载后
的就地解密场景。
"""
def decrypt(self, value: str) -> str:
"""解密配置值。
参数:
value: 待解密的配置值。
返回:
解密后的明文配置值。
"""
...
@dataclass(frozen=True)
class PluginSummary:
"""插件摘要PLG-QUERY-01 列表项)。
字段:
plugin_id: 插件唯一标识manifest.manifest.id
name: 插件显示名称manifest.manifest.name
version: 插件版本manifest.manifest.version
channel_type: 绑定渠道类型manifest.manifest.channel_type
state: 当前生命周期状态LifecycleState.value
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
@dataclass(frozen=True)
class PluginCatalogItem:
"""插件目录项PLG-CATALOG 卡片视图数据)。
与 ``PluginSummary`` 的区别在于携带 ``capabilities`` 与 ``icon`` 字段,
供前端卡片网格展示能力摘要与渠道图标。``capabilities`` 为
``ChannelCapabilities`` dataclass由 Router 层
``serialize_control_data`` 递归序列化。
字段:
plugin_id: 插件唯一标识。
name: 插件显示名称。
version: 插件版本。
channel_type: 绑定渠道类型。
state: 当前生命周期状态。
capabilities: 渠道能力集合(静态声明)。
icon: 渠道展示图标名lucide 图标名,缺失时前端回退默认图标)。
来源为 ``ChannelManifest.icon``,供前端卡片/筛选/标签等场景
统一渲染渠道图标,避免在前端硬编码渠道清单。
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
capabilities: ChannelCapabilities
icon: str | None = None
@dataclass(frozen=True)
class PluginDetail:
"""插件详情PLG-QUERY-02 响应)。
字段:
plugin_id: 插件唯一标识。
name: 插件显示名称。
version: 插件版本。
channel_type: 绑定渠道类型。
state: 当前生命周期状态。
manifest: 完整 PluginManifest含内嵌 ChannelManifest 与
adapters / stages / event_subscriptions / config_sources
ChannelManifest 含 capabilities / provides / depends /
config_schema / failure_policy / resource_quota 等字段。
last_error: 最近一次生命周期失败错误信息(可选),供详情页排障展示。
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
manifest: PluginManifest
last_error: str | None = None
@dataclass(frozen=True)
class InstallPluginCmd:
"""安装插件命令PLG-INSTALL
由 ``PluginManagementPort.installPlugin`` 引用,从来源安装插件,需记录
操作人以满足审计要求(超级管理员)。
字段:
operator: 操作人(审计用)。
source_type: 来源类型(``path`` / ``url`` / ``registry``)。
source: 来源路径或 URL。
version: 指定版本(可选)。
force: 是否强制覆盖已存在插件(默认 False
"""
operator: Operator
source_type: Literal["path", "url", "registry"]
source: str
version: str | None = None
force: bool = False
def __post_init__(self) -> None:
"""校验必填字段非空与 source_type 取值PLG-INSTALL
``source`` 必须非空,``source_type`` 必须为 ``path`` / ``url`` /
``registry`` 之一,在构造时即抛出 ``ValidationError``adapter 不再做
该校验INV-8
"""
if not self.source:
raise ValidationError("source", "must not be empty")
if self.source_type not in ("path", "url", "registry"):
raise ValidationError(
"source_type",
"must be one of: path, url, registry",
)
@dataclass(frozen=True)
class InstallPluginResult:
"""安装插件结果PLG-INSTALL
字段:
plugin_id: 插件 ID。
version: 安装的插件版本。
installed_at: 安装时间戳。
requires_load: 是否需要手动加载(默认 True
"""
plugin_id: str
version: str
installed_at: datetime
requires_load: bool = True
@dataclass(frozen=True)
class UninstallPluginResult:
"""卸载插件结果PLG-UNINSTALL-FILE
字段:
plugin_id: 插件 ID。
uninstalled_at: 卸载时间戳。
files_removed: 已删除的文件列表。
"""
plugin_id: str
uninstalled_at: datetime
files_removed: tuple[str, ...]
@dataclass(frozen=True)
class PluginConfigResult:
"""插件配置查询结果PLG-CONFIG
字段:
plugin_id: 插件 ID。
config: 当前配置值。
schema: 配置 schema从 PluginManifest 获取)。
requires_restart: 是否需要重启生效。
updated_at: 配置最后更新时间(可选)。
"""
plugin_id: str
config: dict[str, Any]
schema: dict[str, Any]
requires_restart: bool
updated_at: datetime | None = None
@dataclass(frozen=True)
class UpdatePluginConfigCmd:
"""更新插件配置命令PLG-CONFIG-PUT
由 ``PluginManagementPort.updatePluginConfig`` 引用,按 ``apply_mode``
判断是否需要重启,需记录操作人以满足审计要求。
字段:
plugin_id: 插件 ID。
operator: 操作人(审计用)。
config: 配置值。
apply_mode: 应用模式(``hot`` / ``restart_required``)。
"""
plugin_id: str
operator: Operator
config: dict[str, Any]
apply_mode: Literal["hot", "restart_required"]
def __post_init__(self) -> None:
"""校验必填字段非空与 apply_mode 取值PLG-CONFIG-PUT
``plugin_id`` 必须非空,``config`` 必须非空字典,``apply_mode``
必须为 ``hot`` / ``restart_required`` 之一,在构造时即抛出
``ValidationError``adapter 不再做该校验INV-8
"""
if not self.plugin_id:
raise ValidationError("plugin_id", "must not be empty")
if not self.config:
raise ValidationError("config", "must not be empty")
if self.apply_mode not in ("hot", "restart_required"):
raise ValidationError(
"apply_mode",
"must be one of: hot, restart_required",
)
@dataclass(frozen=True)
class BatchPluginLifecycleSuccessItem:
"""批量插件生命周期成功条目PLG-BATCH-LIFE
字段:
plugin_id: 插件 ID。
new_state: 新生命周期状态。
"""
plugin_id: str
new_state: str
@dataclass(frozen=True)
class BatchPluginLifecycleCmd:
"""批量插件生命周期命令PLG-BATCH-LIFE
逐条独立事务执行批量启动/停止(模式 D``plugin_ids`` 与 ``filter``
二选一:不可同时为空、不可同时提供(由 ``__post_init__`` 校验)。
字段:
operator: 操作人(审计用)。
plugin_ids: 显式插件 ID 元组(默认空元组)。
filter: 筛选条件(含 channel_type / state可选
"""
operator: Operator
plugin_ids: tuple[str, ...] = ()
filter: dict[str, Any] | None = None
def __post_init__(self) -> None:
"""校验 plugin_ids 与 filter 互斥性与非空PLG-BATCH-LIFE
``plugin_ids`` 与 ``filter`` 二选一:不可同时为空、不可同时提供,
``plugin_ids`` 各项不得为空字符串。在构造时即抛出 ``ValidationError``
使非法输入在 Router 层即被拒绝INV-8
"""
has_ids = bool(self.plugin_ids)
has_filter = self.filter is not None
if not has_ids and not has_filter:
raise ValidationError(
"plugin_ids",
"either plugin_ids or filter must be provided",
)
if has_ids and has_filter:
raise ValidationError(
"plugin_ids",
"plugin_ids and filter are mutually exclusive",
)
if has_ids and not all(pid for pid in self.plugin_ids):
raise ValidationError(
"plugin_ids",
"plugin_ids must not contain empty strings",
)
@dataclass(frozen=True)
class BatchPluginLifecycleResult:
"""批量插件生命周期结果PLG-BATCH-LIFE
描述逐条独立事务执行批量启动/停止的执行结果,``failed`` 使用通用
``BatchOperationFailure````id`` 字段承载 plugin_id
字段:
total: 待操作插件总数。
succeeded: 成功条目元组。
failed: 失败条目元组。
"""
total: int
succeeded: tuple[BatchPluginLifecycleSuccessItem, ...]
failed: tuple[BatchOperationFailure, ...]