feat(plugin_system): 引入插件HTTP端点系统
引入了全新的 `BaseRouterComponent` 组件类型,允许插件开发者通过继承并实现 `register_endpoints` 方法来创建 FastAPI 路由。 - 插件系统现在可以自动发现并注册这些路由组件,并将它们挂载到主 FastAPI 应用的 `/plugins/<plugin_name>` 前缀下。 - 新增了全局配置 `[plugin_http_system]`,提供了总开关、API 速率限制和 API 密钥认证 (`X-API-Key`) 等功能,以确保端点的安全性和稳定性。 - 更新了 `hello_world_plugin` 插件,增加了一个简单的 `/greet` 端点作为实现示例。
This commit is contained in:
committed by
Windpicker-owo
parent
fea007b429
commit
717d4ba555
@@ -44,6 +44,7 @@ from .base import (
|
||||
PluginInfo,
|
||||
# 新增的增强命令系统
|
||||
PlusCommand,
|
||||
BaseRouterComponent,
|
||||
PythonDependency,
|
||||
ToolInfo,
|
||||
ToolParamType,
|
||||
@@ -56,7 +57,7 @@ from .utils.dependency_manager import configure_dependency_manager, get_dependen
|
||||
|
||||
__version__ = "2.0.0"
|
||||
|
||||
__all__ = [
|
||||
__all__ = [ # noqa: RUF022
|
||||
"ActionActivationType",
|
||||
"ActionInfo",
|
||||
"BaseAction",
|
||||
@@ -82,6 +83,7 @@ __all__ = [
|
||||
"PluginInfo",
|
||||
# 增强命令系统
|
||||
"PlusCommand",
|
||||
"BaseRouterComponent"
|
||||
"PythonDependency",
|
||||
"ToolInfo",
|
||||
"ToolParamType",
|
||||
@@ -114,4 +116,4 @@ __all__ = [
|
||||
# "ManifestGenerator",
|
||||
# "validate_plugin_manifest",
|
||||
# "generate_plugin_manifest",
|
||||
]
|
||||
] # type: ignore
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
from .base_action import BaseAction
|
||||
from .base_command import BaseCommand
|
||||
from .base_events_handler import BaseEventHandler
|
||||
from .base_http_component import BaseRouterComponent
|
||||
from .base_plugin import BasePlugin
|
||||
from .base_prompt import BasePrompt
|
||||
from .base_tool import BaseTool
|
||||
@@ -55,7 +56,7 @@ __all__ = [
|
||||
"PluginMetadata",
|
||||
# 增强命令系统
|
||||
"PlusCommand",
|
||||
"PlusCommandAdapter",
|
||||
"BaseRouterComponent"
|
||||
"PlusCommandInfo",
|
||||
"PythonDependency",
|
||||
"ToolInfo",
|
||||
|
||||
37
src/plugin_system/base/base_http_component.py
Normal file
37
src/plugin_system/base/base_http_component.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from fastapi import APIRouter
|
||||
from .component_types import ComponentType, RouterInfo
|
||||
|
||||
class BaseRouterComponent(ABC):
|
||||
"""
|
||||
用于暴露HTTP端点的组件基类。
|
||||
插件开发者应继承此类,并实现 register_endpoints 方法来定义API路由。
|
||||
"""
|
||||
# 组件元数据,由插件管理器读取
|
||||
component_name: str
|
||||
component_description: str
|
||||
component_version: str = "1.0.0"
|
||||
|
||||
# 每个组件实例都会管理自己的APIRouter
|
||||
router: APIRouter
|
||||
|
||||
def __init__(self):
|
||||
self.router = APIRouter()
|
||||
self.register_endpoints()
|
||||
|
||||
@abstractmethod
|
||||
def register_endpoints(self) -> None:
|
||||
"""
|
||||
【开发者必须实现】
|
||||
在此方法中定义所有HTTP端点。
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_router_info(cls) -> "RouterInfo":
|
||||
"""从类属性生成RouterInfo"""
|
||||
return RouterInfo(
|
||||
name=cls.component_name,
|
||||
description=getattr(cls, "component_description", "路由组件"),
|
||||
component_type=ComponentType.ROUTER,
|
||||
)
|
||||
@@ -53,6 +53,7 @@ class ComponentType(Enum):
|
||||
CHATTER = "chatter" # 聊天处理器组件
|
||||
INTEREST_CALCULATOR = "interest_calculator" # 兴趣度计算组件
|
||||
PROMPT = "prompt" # Prompt组件
|
||||
ROUTER = "router" # 路由组件
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
@@ -146,6 +147,7 @@ class PermissionNodeField:
|
||||
node_name: str # 节点名称 (例如 "manage" 或 "view")
|
||||
description: str # 权限描述
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComponentInfo:
|
||||
"""组件信息"""
|
||||
@@ -442,3 +444,13 @@ class MaiMessages:
|
||||
def __post_init__(self):
|
||||
if self.message_segments is None:
|
||||
self.message_segments = []
|
||||
|
||||
@dataclass
|
||||
class RouterInfo(ComponentInfo):
|
||||
"""路由组件信息"""
|
||||
|
||||
auth_required: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
self.component_type = ComponentType.ROUTER
|
||||
|
||||
@@ -5,11 +5,15 @@ from pathlib import Path
|
||||
from re import Pattern
|
||||
from typing import Any, cast
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config as bot_config
|
||||
from src.plugin_system.base.base_action import BaseAction
|
||||
from src.plugin_system.base.base_chatter import BaseChatter
|
||||
from src.plugin_system.base.base_command import BaseCommand
|
||||
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
||||
from src.plugin_system.base.base_http_component import BaseRouterComponent
|
||||
from src.plugin_system.base.base_interest_calculator import BaseInterestCalculator
|
||||
from src.plugin_system.base.base_prompt import BasePrompt
|
||||
from src.plugin_system.base.base_tool import BaseTool
|
||||
@@ -24,6 +28,7 @@ from src.plugin_system.base.component_types import (
|
||||
PluginInfo,
|
||||
PlusCommandInfo,
|
||||
PromptInfo,
|
||||
RouterInfo,
|
||||
ToolInfo,
|
||||
)
|
||||
from src.plugin_system.base.plus_command import PlusCommand, create_legacy_command_adapter
|
||||
@@ -40,6 +45,7 @@ ComponentClassType = (
|
||||
| type[BaseChatter]
|
||||
| type[BaseInterestCalculator]
|
||||
| type[BasePrompt]
|
||||
| type[BaseRouterComponent]
|
||||
)
|
||||
|
||||
|
||||
@@ -194,6 +200,10 @@ class ComponentRegistry:
|
||||
assert isinstance(component_info, PromptInfo)
|
||||
assert issubclass(component_class, BasePrompt)
|
||||
ret = self._register_prompt_component(component_info, component_class)
|
||||
case ComponentType.ROUTER:
|
||||
assert isinstance(component_info, RouterInfo)
|
||||
assert issubclass(component_class, BaseRouterComponent)
|
||||
ret = self._register_router_component(component_info, component_class)
|
||||
case _:
|
||||
logger.warning(f"未知组件类型: {component_type}")
|
||||
ret = False
|
||||
@@ -373,6 +383,48 @@ class ComponentRegistry:
|
||||
logger.debug(f"已注册Prompt组件: {prompt_name}")
|
||||
return True
|
||||
|
||||
def _register_router_component(self, router_info: RouterInfo, router_class: type[BaseRouterComponent]) -> bool:
|
||||
"""注册Router组件并将其端点挂载到主服务器"""
|
||||
# 1. 检查总开关是否开启
|
||||
if not bot_config.plugin_http_system.enable_plugin_http_endpoints:
|
||||
logger.info("插件HTTP端点功能已禁用,跳过路由注册")
|
||||
return True
|
||||
try:
|
||||
from src.common.security import get_api_key
|
||||
from src.common.server import get_global_server
|
||||
|
||||
router_name = router_info.name
|
||||
plugin_name = router_info.plugin_name
|
||||
|
||||
# 2. 实例化组件以触发其 __init__ 和 register_endpoints
|
||||
component_instance = router_class()
|
||||
|
||||
# 3. 获取配置好的 APIRouter
|
||||
plugin_router = component_instance.router
|
||||
|
||||
# 4. 获取全局服务器实例
|
||||
server = get_global_server()
|
||||
|
||||
# 5. 生成唯一的URL前缀
|
||||
prefix = f"/plugins/{plugin_name}"
|
||||
|
||||
# 6. 根据需要应用安全依赖项
|
||||
dependencies = []
|
||||
if router_info.auth_required:
|
||||
dependencies.append(Depends(get_api_key))
|
||||
|
||||
# 7. 注册路由,并使用插件名作为API文档的分组标签
|
||||
server.app.include_router(
|
||||
plugin_router, prefix=prefix, tags=[plugin_name], dependencies=dependencies
|
||||
)
|
||||
|
||||
logger.debug(f"成功将插件 '{plugin_name}' 的路由组件 '{router_name}' 挂载到: {prefix}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"注册路由组件 '{router_info.name}' 时出错: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
# === 组件移除相关 ===
|
||||
|
||||
async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool:
|
||||
@@ -616,6 +668,7 @@ class ComponentRegistry:
|
||||
| BaseChatter
|
||||
| BaseInterestCalculator
|
||||
| BasePrompt
|
||||
| BaseRouterComponent
|
||||
]
|
||||
| None
|
||||
):
|
||||
@@ -643,6 +696,8 @@ class ComponentRegistry:
|
||||
| type[PlusCommand]
|
||||
| type[BaseChatter]
|
||||
| type[BaseInterestCalculator]
|
||||
| type[BasePrompt]
|
||||
| type[BaseRouterComponent]
|
||||
| None,
|
||||
self._components_classes.get(namespaced_name),
|
||||
)
|
||||
@@ -867,6 +922,7 @@ class ComponentRegistry:
|
||||
plus_command_components: int = 0
|
||||
chatter_components: int = 0
|
||||
prompt_components: int = 0
|
||||
router_components: int = 0
|
||||
for component in self._components.values():
|
||||
if component.component_type == ComponentType.ACTION:
|
||||
action_components += 1
|
||||
@@ -882,6 +938,8 @@ class ComponentRegistry:
|
||||
chatter_components += 1
|
||||
elif component.component_type == ComponentType.PROMPT:
|
||||
prompt_components += 1
|
||||
elif component.component_type == ComponentType.ROUTER:
|
||||
router_components += 1
|
||||
return {
|
||||
"action_components": action_components,
|
||||
"command_components": command_components,
|
||||
@@ -891,6 +949,7 @@ class ComponentRegistry:
|
||||
"plus_command_components": plus_command_components,
|
||||
"chatter_components": chatter_components,
|
||||
"prompt_components": prompt_components,
|
||||
"router_components": router_components,
|
||||
"total_components": len(self._components),
|
||||
"total_plugins": len(self._plugins),
|
||||
"components_by_type": {
|
||||
|
||||
@@ -405,13 +405,14 @@ class PluginManager:
|
||||
plus_command_count = stats.get("plus_command_components", 0)
|
||||
chatter_count = stats.get("chatter_components", 0)
|
||||
prompt_count = stats.get("prompt_components", 0)
|
||||
router_count = stats.get("router_components", 0)
|
||||
total_components = stats.get("total_components", 0)
|
||||
|
||||
# 📋 显示插件加载总览
|
||||
if total_registered > 0:
|
||||
logger.info("🎉 插件系统加载完成!")
|
||||
logger.info(
|
||||
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count})"
|
||||
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count}, Router: {router_count})"
|
||||
)
|
||||
|
||||
# 显示详细的插件列表
|
||||
@@ -452,6 +453,9 @@ class PluginManager:
|
||||
prompt_components = [
|
||||
c for c in plugin_info.components if c.component_type == ComponentType.PROMPT
|
||||
]
|
||||
router_components = [
|
||||
c for c in plugin_info.components if c.component_type == ComponentType.ROUTER
|
||||
]
|
||||
|
||||
if action_components:
|
||||
action_details = [format_component(c) for c in action_components]
|
||||
@@ -478,6 +482,9 @@ class PluginManager:
|
||||
if prompt_components:
|
||||
prompt_details = [format_component(c) for c in prompt_components]
|
||||
logger.info(f" 📝 Prompt组件: {', '.join(prompt_details)}")
|
||||
if router_components:
|
||||
router_details = [format_component(c) for c in router_components]
|
||||
logger.info(f" 🌐 Router组件: {', '.join(router_details)}")
|
||||
|
||||
# 权限节点信息
|
||||
if plugin_instance := self.loaded_plugins.get(plugin_name):
|
||||
|
||||
Reference in New Issue
Block a user