今天稍微思考了一下插件架构应该怎么设计……

我的场景是这样的:主程序是个 nas 管理工具,需要通过插件来扩展功能。比如下载插件负责下载文件,元数据插件负责处理下载完的媒体文件信息,字幕插件去找字幕。这些插件之间可以互相配合,但也可以独立工作。

所以插件系统需要:
1. 每个插件都要能说明自己能做什么
2. 插件之间要能发布和订阅事件
3. 长时间任务要能反馈进度

经过一番思考,整理出了这样的接口规范:

# 每个插件必须实现的基础接口
@dataclass
class PluginInfo:
    id: str          # 插件唯一标识
    name: str        # 插件名称
    version: str     # 版本
    description: str # 描述
    author: str      # 作者
    homepage: str    # 可选,项目主页

@dataclass
class ActionParameter:
    type: str
    description: str
    required: bool
    default: Any = None

@dataclass
class Action:
    description: str
    method: str      # GET/POST/PUT/DELETE
    path: str
    params: dict[str, ActionParameter]
    returns: dict    # 返回值描述

@dataclass
class Event:
    description: str
    schema: dict     # JSON Schema 描述事件数据结构

# 基础 HTTP 接口
GET /plugin/info -> PluginInfo
GET /plugin/capabilities -> {
    "actions": dict[str, Action],
    "events": dict[str, Event],
    "subscriptions": list[str]
}
GET /health -> {"status": "ok" | "error"}
POST /events -> 接收订阅的事件


然后拿下载插件举个例子:

# 下载插件接口定义
POST /actions/download {
    "request": {
        "url": str,
        "save_path": str,
        "headers": dict[str, str]  # 可选
    },
    "response": {
        "task_id": str,
        "status": "started"
    }
}

GET /tasks/{task_id} {
    "response": {
        "task_id": str,
        "status": "downloading" | "completed" | "error",
        "progress": float,  # 0-100
        "speed": int,      # bytes/s
        "downloaded": int,  # bytes
        "total": int       # bytes
    }
}

# 事件定义
events = {
    "download.completed": {
        "task_id": str,
        "file_path": str,
        "file_size": int,
        "mime_type": str
    }
}


这样的设计既简单又灵活,用 HTTP + JSON 的方式也让插件开发没有语言限制。每个插件都是一个独立的 HTTP 服务,主程序只要调用这些 API 就行了。

插件之间的协作通过事件来完成,比如下载完成时发个事件,元数据插件订阅这个事件就能自动开始处理文件,处理完再发事件给字幕插件……

而用户通过 nastool 的插件管理页面,设置插件的订阅事件,以及如何提取所订阅的事件里的参数映射到插件入参等等。

其实这个配置过程还是不够人性化,有点废手,可以考虑以 mcp 的形式提供,由 ai 来决定怎么填充!

想到这里,感觉这个方案还不错,先这样吧 😋

#开源项目 #nastool
 
 
Back to Top