commit 017cab889e1be178e415e02ad6ce53f9aab117a3
Author: DarkiT
Date: Mon Mar 17 16:34:27 2025 +0000
基于Go语言的动态插件系统,支持热插拔和动态加载插件。
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..989d417
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.idea
+.vscode
+*.so
+*.log
+
+dist
+logs
+example/storage
+web_admin
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0288d57
--- /dev/null
+++ b/README.md
@@ -0,0 +1,280 @@
+# Go插件系统
+
+这是一个基于Go语言的动态插件系统,支持热插拔和动态加载插件。系统提供了一个统一的插件接口和管理框架,使开发者能够轻松地开发和集成新的插件。
+
+## 功能特点
+
+- 支持动态加载和卸载插件
+- 提供统一的插件接口和基础实现
+- 支持插件配置管理
+- 提供Web管理界面
+- 支持插件操作的动态发现和执行
+- 内置完整的错误处理和日志记录
+- 支持插件状态管理和生命周期控制
+
+## 系统架构
+
+### 核心组件
+
+1. **插件接口** (`interface.go`)
+ - 定义了插件必须实现的基本接口
+ - 包含生命周期管理方法
+ - 定义了插件操作和配置接口
+
+2. **基础插件** (`plugin.go`)
+ - 提供插件接口的默认实现
+ - 包含插件管理器的核心功能
+ - 处理插件的加载、启动、停止和卸载
+
+3. **Web管理界面** (`admin/`)
+ - 提供插件的可视化管理
+ - 支持插件配置修改
+ - 允许执行插件操作
+
+## 快速开始
+
+### 1. 创建新插件
+
+要创建一个新插件,需要实现 `IPlugin` 接口。推荐使用 `BasePlugin` 作为基础结构:
+
+```go
+package main
+
+import (
+ "context"
+ "plugins"
+)
+
+type MyPlugin struct {
+ *plugins.BasePlugin
+ // 添加插件特定的字段
+}
+
+var Plugin = &MyPlugin{
+ BasePlugin: plugins.NewBasePluginWithDefaultType(
+ "MyPlugin", // 插件名称
+ "1.0.0", // 版本
+ "插件描述", // 描述
+ "开发者", // 作者
+ ),
+}
+```
+
+### 2. 实现必要的接口方法
+
+```go
+// 初始化插件
+func (p *MyPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ // 初始化逻辑
+ return nil
+}
+
+// 启动插件
+func (p *MyPlugin) Start(ctx context.Context) error {
+ // 启动逻辑
+ return nil
+}
+
+// 停止插件
+func (p *MyPlugin) Stop(ctx context.Context) error {
+ // 停止逻辑
+ return nil
+}
+```
+
+### 3. 实现插件操作
+
+```go
+// 定义插件支持的操作
+func (p *MyPlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "myOperation":
+ return &plugins.OperationInfo{
+ Name: "myOperation",
+ Description: "操作描述",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "param1",
+ Type: "string",
+ Required: true,
+ Description: "参数描述",
+ },
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("不支持的操作: %s", operation)
+ }
+}
+
+// 实现操作执行逻辑
+func (p *MyPlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ switch action {
+ case "myOperation":
+ // 实现操作逻辑
+ return map[string]interface{}{"result": "success"}, nil
+ default:
+ return nil, fmt.Errorf("不支持的操作: %s", action)
+ }
+}
+```
+
+## 插件开发指南
+
+### 1. 插件结构
+
+每个插件必须:
+- 实现 `IPlugin` 接口
+- 导出名为 `Plugin` 的变量
+- 包含 `main` 函数(虽然不会被调用)
+
+### 2. 配置管理
+
+插件配置通过 `Init` 方法传入,建议在插件中定义配置结构:
+
+```go
+type Config struct {
+ Option1 string `json:"option1"`
+ Option2 int `json:"option2"`
+}
+
+func (p *MyPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ // 解析配置
+ // 处理配置项
+ return nil
+}
+```
+
+### 3. 错误处理
+
+- 所有错误应该返回有意义的错误信息
+- 使用 `fmt.Errorf` 格式化错误信息
+- 在适当的地方记录错误日志
+
+### 4. 生命周期管理
+
+插件的生命周期包括:
+1. 加载(Load)
+2. 初始化(Init)
+3. 启动(Start)
+4. 运行(Running)
+5. 停止(Stop)
+6. 卸载(Unload)
+
+确保在每个阶段都正确处理资源。
+
+## 示例插件
+
+### 默认日志插件
+
+参考 `plugins/defaultlogger/default_logger_plugin.go` 作为完整的插件实现示例:
+
+- 实现了基本的日志记录功能
+- 支持文件和控制台输出
+- 提供了多个操作接口
+- 包含完整的配置管理
+
+## 构建和部署
+
+### 1. 构建插件
+
+使用提供的构建脚本:
+
+```bash
+./build_all.sh
+```
+
+这将编译所有插件并将它们放置在正确的目录中。
+
+### 2. 配置插件
+
+在 `plugins.json` 中配置插件:
+
+```json
+{
+ "MyPlugin": {
+ "enabled": true,
+ "config": {
+ "option1": "value1",
+ "option2": 123
+ }
+ }
+}
+```
+
+### 3. 启动管理界面
+
+```bash
+cd admin
+./web_admin_new
+```
+
+## API接口
+
+### 1. 插件管理
+
+- `GET /plugins` - 获取所有插件列表
+- `POST /plugin/enable` - 启用插件
+- `POST /plugin/disable` - 禁用插件
+- `POST /plugin/config` - 更新插件配置
+
+### 2. 插件操作
+
+- `POST /api/execute` - 执行插件操作
+
+请求示例:
+```json
+{
+ "plugin": "MyPlugin",
+ "operation": "myOperation",
+ "params": {
+ "param1": "value1"
+ }
+}
+```
+
+## 最佳实践
+
+1. **模块化设计**
+ - 将功能划分为独立的模块
+ - 避免插件间的直接依赖
+
+2. **错误处理**
+ - 提供详细的错误信息
+ - 实现优雅的错误恢复机制
+
+3. **资源管理**
+ - 正确处理资源的分配和释放
+ - 在插件停止时清理所有资源
+
+4. **配置验证**
+ - 在初始化时验证所有配置项
+ - 提供合理的默认值
+
+5. **文档**
+ - 详细记录插件的功能和用法
+ - 提供配置项的说明
+ - 包含示例代码
+
+## 故障排除
+
+1. **插件加载失败**
+ - 检查插件文件权限
+ - 验证插件接口实现
+ - 查看系统日志
+
+2. **操作执行失败**
+ - 检查参数格式
+ - 验证插件状态
+ - 查看插件日志
+
+## 贡献指南
+
+1. Fork 项目
+2. 创建特性分支
+3. 提交变更
+4. 推送到分支
+5. 创建 Pull Request
+
+## 许可证
+
+[添加许可证信息]
\ No newline at end of file
diff --git a/build_all.sh b/build_all.sh
new file mode 100755
index 0000000..f5b938e
--- /dev/null
+++ b/build_all.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+
+# 构建脚本,用于编译所有插件和 web_example.go
+
+# # 设置工作目录
+WORK_DIR="/www/plugins"
+
+# # 设置 GOPATH 以确保使用相同版本的 plugins 包
+# export GOPATH=$WORK_DIR
+export GO111MODULE=on
+
+# 确保输出目录存在
+DIST_DIR="/www/plugins/example/dist"
+mkdir -p $DIST_DIR
+
+go mod tidy
+
+echo "===== 开始编译插件 ====="
+
+# 编译带有明确类型的日志插件
+echo "编译日志插件 (明确类型)..."
+cd $WORK_DIR/plugins/logger
+go build -buildmode=plugin -o $DIST_DIR/logger.so
+if [ $? -eq 0 ]; then
+ echo "日志插件编译成功!"
+else
+ echo "日志插件编译失败!"
+ exit 1
+fi
+
+# 编译使用默认类型的日志插件
+echo "编译默认日志插件 (默认类型)..."
+cd $WORK_DIR/plugins/defaultlogger
+go build -buildmode=plugin -o $DIST_DIR/defaultlogger.so
+if [ $? -eq 0 ]; then
+ echo "默认日志插件编译成功!"
+else
+ echo "默认日志插件编译失败!"
+ exit 1
+fi
+
+# 编译统计插件
+echo "编译统计插件..."
+cd $WORK_DIR/plugins/stats
+go build -buildmode=plugin -o $DIST_DIR/stats.so
+if [ $? -eq 0 ]; then
+ echo "统计插件编译成功!"
+else
+ echo "统计插件编译失败!"
+ exit 1
+fi
+
+# 编译存储插件
+echo "编译存储插件..."
+cd $WORK_DIR/plugins/storage
+go build -buildmode=plugin -o $DIST_DIR/storage.so
+if [ $? -eq 0 ]; then
+ echo "存储插件编译成功!"
+else
+ echo "存储插件编译失败!"
+ exit 1
+fi
+
+# 编译示例插件
+echo "编译示例插件..."
+cd $WORK_DIR/plugins/demoutils
+go build -buildmode=plugin -o $DIST_DIR/demoutils.so
+if [ $? -eq 0 ]; then
+ echo "示例插件编译成功!"
+else
+ echo "示例插件编译失败!"
+ exit 1
+fi
+cd ..
+
+echo "===== 所有插件编译完成 ====="
+
+# 复制 web_admin.go 到工作目录
+mkdir -p $WORK_DIR/example
+
+# 编译 web_admin.go
+echo "===== 编译 web_admin ====="
+cd $WORK_DIR/example
+go build -o /www/plugins/example/web_admin web_admin.go
+if [ $? -eq 0 ]; then
+ echo "web_admin.go 编译成功!"
+else
+ echo "web_admin.go 编译失败!"
+ exit 1
+fi
+
+echo "===== 所有编译完成 ====="
+echo "插件文件保存在 $DIST_DIR 目录中:"
+ls -la $DIST_DIR
+echo "web_admin 可执行文件保存在 /www/plugins/example/web_admin"
+
+echo "===== 启动web_admin ====="
+/www/plugins/example/web_admin
\ No newline at end of file
diff --git a/example/demo_utils_client.html b/example/demo_utils_client.html
new file mode 100644
index 0000000..b3494cf
--- /dev/null
+++ b/example/demo_utils_client.html
@@ -0,0 +1,856 @@
+
+
+
+
+
+ DemoUtilsPlugin 测试客户端
+
+
+
+
+
DemoUtilsPlugin 测试客户端
+
+
+
+
+
操作调用
+
+
+
+
+
+
以下是 DemoUtilsPlugin 支持的所有操作:
+
+ 正在加载操作列表...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
快速操作
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/web_admin.go b/example/web_admin.go
new file mode 100644
index 0000000..4fe1b9f
--- /dev/null
+++ b/example/web_admin.go
@@ -0,0 +1,1490 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/darkit/plugins"
+)
+
+// 插件目录
+var pluginsDir = "./dist"
+
+// 全局插件管理器
+var pm *plugins.PluginManager
+
+// AdminHandler 处理管理界面主页
+func AdminHandler(w http.ResponseWriter, r *http.Request) {
+ // HTML模板
+ tmpl := `
+
+
+
+
+ 插件管理系统
+
+
+
+
+
+
+
+ 插件管理系统
+
+
+
系统信息
+
操作系统: {{.OS}}
+
动态加载支持: {{if .DynamicLoadingSupported}}是{{else}}否{{end}}
+
插件目录: {{.PluginsDir}}
+
已加载插件数量: {{len .Plugins}}
+
+
+ 已安装插件
+
+ {{range .Plugins}}
+
+
+
+
{{.Description}}
+
+ {{if .Config}}
+
+
配置:
+ {{range $key, $value := .Config}}
+
+
{{$key}}:
+
{{$value}}
+
+ {{end}}
+
+ {{end}}
+
+
+ {{if .Enabled}}
+
+ {{else}}
+
+ {{end}}
+
+
+
+
+ {{end}}
+
+ {{if not .Plugins}}
+ 没有已安装的插件。
+ {{end}}
+
+ 操作
+
+
+
+
+
+`
+
+ // 解析模板
+ t, err := template.New("admin").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 准备数据
+ data := struct {
+ Plugins []plugins.PluginInfo
+ OS string
+ DynamicLoadingSupported bool
+ PluginsDir string
+ }{
+ Plugins: pm.GetPluginInfos(),
+ OS: strings.Title(strings.ToLower(fmt.Sprintf("%s", strings.Split(runtime.GOOS, "/")[0]))),
+ DynamicLoadingSupported: pm.IsDynamicLoadingSupported(),
+ PluginsDir: pluginsDir,
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// EnablePluginHandler 启用插件
+func EnablePluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/enable/"):]
+ log.Printf("尝试启用插件: %s", pluginName)
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // 使用goroutine和通道处理可能的阻塞操作
+ enableErrChan := make(chan error, 1)
+ go func() {
+ enableErrChan <- pm.EnablePlugin(pluginName)
+ }()
+
+ // 等待操作完成或超时
+ select {
+ case err := <-enableErrChan:
+ if err != nil {
+ log.Printf("启用插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("启用插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(5 * time.Second):
+ log.Printf("启用插件操作超时")
+ http.Error(w, "启用插件操作超时", http.StatusRequestTimeout)
+ return
+ }
+
+ // 尝试初始化和启动插件
+ plugin, exists := pm.GetPlugin(pluginName)
+ if exists && plugin.IsEnabled() {
+ config, _ := pm.GetPluginConfig(pluginName)
+ log.Printf("初始化插件: %s", pluginName)
+
+ initErrChan := make(chan error, 1)
+ go func() {
+ initErrChan <- plugin.Init(ctx, config)
+ }()
+
+ // 等待初始化完成或超时
+ select {
+ case err := <-initErrChan:
+ if err != nil {
+ log.Printf("初始化插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(5 * time.Second):
+ log.Printf("初始化插件超时")
+ http.Error(w, "初始化插件超时", http.StatusRequestTimeout)
+ return
+ }
+
+ log.Printf("启动插件: %s", pluginName)
+ startErrChan := make(chan error, 1)
+ go func() {
+ startErrChan <- plugin.Start(ctx)
+ }()
+
+ // 等待启动完成或超时
+ select {
+ case err := <-startErrChan:
+ if err != nil {
+ log.Printf("启动插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(5 * time.Second):
+ log.Printf("启动插件超时")
+ http.Error(w, "启动插件超时", http.StatusRequestTimeout)
+ return
+ }
+ }
+
+ log.Printf("插件 %s 启用成功", pluginName)
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// DisablePluginHandler 禁用插件
+func DisablePluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/disable/"):]
+ log.Printf("尝试禁用插件: %s", pluginName)
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // 先检查插件是否存在和启用状态
+ plugin, exists := pm.GetPlugin(pluginName)
+ if !exists {
+ log.Printf("插件不存在: %s", pluginName)
+ http.Error(w, fmt.Sprintf("插件 %s 不存在", pluginName), http.StatusNotFound)
+ return
+ }
+
+ // 如果插件已经是禁用状态,直接返回成功
+ if !plugin.IsEnabled() {
+ log.Printf("插件 %s 已经是禁用状态", pluginName)
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ log.Printf("开始停止插件: %s", pluginName)
+ // 使用goroutine和通道处理可能的阻塞操作
+ stopErrChan := make(chan error, 1)
+ go func() {
+ if err := plugin.Stop(ctx); err != nil {
+ log.Printf("停止插件时发生错误: %v", err)
+ stopErrChan <- err
+ } else {
+ log.Printf("插件停止成功")
+ stopErrChan <- nil
+ }
+ }()
+
+ // 等待停止操作完成或超时
+ select {
+ case err := <-stopErrChan:
+ if err != nil {
+ log.Printf("停止插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(5 * time.Second):
+ log.Printf("停止插件超时,继续执行禁用操作")
+ // 即使停止超时,我们也继续执行禁用操作
+ }
+
+ // 禁用插件 - 使用goroutine和通道处理可能的阻塞
+ log.Printf("开始禁用插件: %s", pluginName)
+ disableErrChan := make(chan error, 1)
+ disableDone := make(chan struct{})
+
+ go func() {
+ defer close(disableDone)
+ if err := pm.DisablePlugin(pluginName); err != nil {
+ log.Printf("禁用插件时发生错误: %v", err)
+ disableErrChan <- err
+ } else {
+ log.Printf("插件禁用成功")
+ disableErrChan <- nil
+ }
+ }()
+
+ // 等待禁用操作完成或超时
+ select {
+ case err := <-disableErrChan:
+ if err != nil {
+ log.Printf("禁用插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("禁用插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ log.Printf("插件 %s 已成功禁用", pluginName)
+ case <-time.After(5 * time.Second):
+ log.Printf("禁用插件操作超时,尝试强制设置禁用状态")
+ // 超时后尝试直接设置禁用状态
+ plugin.SetEnabled(false)
+ // 保存配置
+ config, _ := pm.GetPluginConfig(pluginName)
+ if config == nil {
+ config = make(map[string]interface{})
+ }
+ config["enabled"] = false
+ if err := pm.SetPluginConfig(pluginName, config); err != nil {
+ log.Printf("保存禁用状态失败: %v", err)
+ http.Error(w, "禁用插件超时且无法保存状态", http.StatusRequestTimeout)
+ return
+ }
+ log.Printf("插件 %s 已强制设置为禁用状态", pluginName)
+ }
+
+ // 等待禁用操作的goroutine完成
+ go func() {
+ <-disableDone
+ log.Printf("禁用操作的goroutine已完成")
+ }()
+
+ // 返回成功响应
+ log.Printf("禁用操作处理完成,重定向到主页")
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// ConfigPluginHandler 配置插件
+func ConfigPluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/config/"):]
+ log.Printf("访问插件配置页面: %s, 方法: %s", pluginName, r.Method)
+
+ if r.Method == http.MethodPost {
+ // 处理配置表单提交
+ if err := r.ParseForm(); err != nil {
+ log.Printf("解析表单失败: %v", err)
+ http.Error(w, fmt.Sprintf("解析表单失败: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // 收集配置项
+ config := make(map[string]interface{})
+ for key, values := range r.PostForm {
+ if key == "pluginName" {
+ continue // 跳过插件名称字段
+ }
+
+ if len(values) > 0 {
+ // 尝试将值转换为适当的类型
+ value := values[0]
+ log.Printf("配置项: %s = %s", key, value)
+
+ // 布尔值处理
+ if value == "true" {
+ config[key] = true
+ } else if value == "false" {
+ config[key] = false
+ } else if strings.Contains(value, ".") {
+ // 尝试解析为浮点数
+ if f, err := strconv.ParseFloat(value, 64); err == nil {
+ config[key] = f
+ } else {
+ config[key] = value
+ }
+ } else if i, err := strconv.Atoi(value); err == nil {
+ // 尝试解析为整数
+ config[key] = i
+ } else {
+ // 默认为字符串
+ config[key] = value
+ }
+ }
+ }
+
+ // 保存配置
+ log.Printf("保存插件配置: %s", pluginName)
+ if err := pm.SetPluginConfig(pluginName, config); err != nil {
+ log.Printf("保存配置失败: %v", err)
+ http.Error(w, fmt.Sprintf("保存配置失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("配置保存成功,重定向到主页")
+ // 重定向回主页
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ // 获取插件信息和配置
+ log.Printf("获取插件信息: %s", pluginName)
+ info, config := getPluginInfo(pluginName)
+ if info == nil {
+ log.Printf("插件不存在: %s", pluginName)
+ http.Error(w, "插件不存在", http.StatusNotFound)
+ return
+ }
+
+ // 配置表单模板
+ tmpl := `
+
+
+
+
+ 配置 {{.Info.Name}}
+
+
+
+ 配置 {{.Info.Name}}
+
+
+
+ 返回插件列表
+
+
+`
+
+ // 解析模板
+ t, err := template.New("config").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 准备数据
+ data := struct {
+ Info *plugins.PluginInfo
+ Config map[string]interface{}
+ }{
+ Info: info,
+ Config: config,
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// ReloadPluginsHandler 重新加载插件
+func ReloadPluginsHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ log.Printf("开始重新加载所有插件")
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ // 停止所有插件
+ log.Printf("停止所有插件")
+
+ // 使用goroutine和通道处理可能的阻塞操作
+ errChan := make(chan error, 1)
+ go func() {
+ errChan <- pm.StopPlugins(ctx)
+ }()
+
+ // 等待操作完成或超时
+ select {
+ case err := <-errChan:
+ if err != nil {
+ log.Printf("停止插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(10 * time.Second):
+ log.Printf("停止插件超时")
+ http.Error(w, "停止插件超时", http.StatusRequestTimeout)
+ return
+ }
+
+ // 重新加载插件
+ log.Printf("加载插件")
+ if err := pm.LoadPlugins(); err != nil {
+ log.Printf("加载插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("加载插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 初始化插件
+ log.Printf("初始化插件")
+ initErrChan := make(chan error, 1)
+ go func() {
+ initErrChan <- pm.InitPlugins(ctx)
+ }()
+
+ // 等待初始化完成或超时
+ select {
+ case err := <-initErrChan:
+ if err != nil {
+ log.Printf("初始化插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(10 * time.Second):
+ log.Printf("初始化插件超时")
+ http.Error(w, "初始化插件超时", http.StatusRequestTimeout)
+ return
+ }
+
+ // 启动插件
+ log.Printf("启动插件")
+ startErrChan := make(chan error, 1)
+ go func() {
+ startErrChan <- pm.StartPlugins(ctx)
+ }()
+
+ // 等待启动完成或超时
+ select {
+ case err := <-startErrChan:
+ if err != nil {
+ log.Printf("启动插件失败: %v", err)
+ http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ case <-time.After(10 * time.Second):
+ log.Printf("启动插件超时")
+ http.Error(w, "启动插件超时", http.StatusRequestTimeout)
+ return
+ }
+
+ log.Printf("所有插件已重新加载")
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// APIPluginsHandler API接口处理器 - 返回插件列表
+func APIPluginsHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ plugins := pm.GetPluginInfos()
+ if err := json.NewEncoder(w).Encode(plugins); err != nil {
+ http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// OperationsHandler 处理插件操作信息页面
+func OperationsHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/operations/"):]
+ log.Printf("访问插件操作页面: %s", pluginName)
+
+ // 获取插件的所有操作信息
+ pluginOps, err := pm.GetPluginAllOperations(pluginName)
+ if err != nil {
+ log.Printf("获取插件操作失败: %v", err)
+ http.Error(w, fmt.Sprintf("获取插件操作失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("获取到 %d 个操作信息", len(pluginOps.Operations))
+
+ // 操作信息页面模板
+ tmpl := `
+
+
+
+
+ {{.PluginOps.PluginName}} 插件操作
+
+
+
+ {{.PluginOps.PluginName}} 插件操作
+
+ {{if .PluginOps.Operations}}
+ {{range .PluginOps.Operations}}
+
+
{{.Name}}
+ {{if .Description}}
+
{{.Description}}
+ {{end}}
+
+ {{if .Params}}
+
参数列表:
+
+
+ | 参数名 |
+ 类型 |
+ 必填 |
+ 默认值 |
+ 描述 |
+
+ {{range .Params}}
+
+ | {{.Name}} |
+ {{.Type}} |
+ {{if .Required}}是{{else}}否{{end}} |
+ {{if .Default}}{{.Default}}{{else}}-{{end}} |
+ {{.Description}} |
+
+ {{end}}
+
+ {{else}}
+
此操作不需要参数
+ {{end}}
+
+
+
+
+
+ {{end}}
+ {{else}}
+ 该插件没有定义任何操作。
+ {{end}}
+
+ 返回插件列表
+
+
+`
+
+ // 解析模板
+ t, err := template.New("operations").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 准备数据
+ data := struct {
+ PluginOps *plugins.PluginOperations
+ }{
+ PluginOps: pluginOps,
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// ExecuteOperationHandler 处理操作执行页面
+func ExecuteOperationHandler(w http.ResponseWriter, r *http.Request) {
+ parts := strings.Split(r.URL.Path[len("/execute/"):], "/")
+ if len(parts) != 2 {
+ log.Printf("无效的URL格式: %s", r.URL.Path)
+ http.Error(w, "无效的URL格式", http.StatusBadRequest)
+ return
+ }
+
+ pluginName := parts[0]
+ operationName := parts[1]
+ log.Printf("执行操作: %s/%s, 方法: %s", pluginName, operationName, r.Method)
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ // 获取操作信息
+ opInfo, err := pm.GetPluginOperationInfo(pluginName, operationName)
+ if err != nil {
+ log.Printf("获取操作信息失败: %v", err)
+ http.Error(w, fmt.Sprintf("获取操作信息失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 处理表单提交
+ if r.Method == http.MethodPost {
+ log.Printf("处理操作执行表单")
+ if err := r.ParseForm(); err != nil {
+ log.Printf("解析表单失败: %v", err)
+ http.Error(w, fmt.Sprintf("解析表单失败: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // 构建参数
+ params := make(map[string]interface{})
+ for _, param := range opInfo.Params {
+ value := r.FormValue(param.Name)
+ log.Printf("参数: %s = %s (类型: %s)", param.Name, value, param.Type)
+
+ if value == "" && param.Required {
+ errMsg := fmt.Sprintf("缺少必填参数: %s", param.Name)
+ log.Panicln(errMsg)
+ http.Error(w, errMsg, http.StatusBadRequest)
+ return
+ }
+
+ // 根据参数类型转换值
+ switch param.Type {
+ case "integer", "int", "int64":
+ if value == "" {
+ params[param.Name] = 0
+ } else {
+ i, err := strconv.ParseInt(value, 10, 64)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("参数 %s 格式错误: %v", param.Name, err), http.StatusBadRequest)
+ return
+ }
+ params[param.Name] = i
+ }
+ case "float", "float64":
+ if value == "" {
+ params[param.Name] = 0.0
+ } else {
+ f, err := strconv.ParseFloat(value, 64)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("参数 %s 格式错误: %v", param.Name, err), http.StatusBadRequest)
+ return
+ }
+ params[param.Name] = f
+ }
+ case "boolean", "bool":
+ params[param.Name] = (value == "true" || value == "on")
+ default:
+ params[param.Name] = value
+ }
+ }
+
+ // 使用goroutine和channel执行操作,避免阻塞
+ log.Printf("执行操作: %s", operationName)
+ resultChan := make(chan interface{}, 1)
+ errChan := make(chan error, 1)
+
+ go func() {
+ result, execErr := pm.ExecutePlugin(ctx, pluginName, operationName, params)
+ resultChan <- result
+ errChan <- execErr
+ }()
+
+ // 等待结果或超时
+ var result interface{}
+ var execErr error
+
+ select {
+ case result = <-resultChan:
+ execErr = <-errChan
+ log.Printf("操作执行完成: %v, 错误: %v", result, execErr)
+ case <-time.After(10 * time.Second):
+ log.Printf("操作执行超时")
+ http.Error(w, "操作执行超时", http.StatusRequestTimeout)
+ return
+ }
+
+ // 构建包含参数和结果的数据
+ data := struct {
+ PluginName string
+ OperationName string
+ Operation *plugins.OperationInfo
+ Params map[string]interface{}
+ Result interface{}
+ Error error
+ IsPost bool
+ }{
+ PluginName: pluginName,
+ OperationName: operationName,
+ Operation: opInfo,
+ Params: params,
+ Result: result,
+ Error: execErr,
+ IsPost: true,
+ }
+
+ // 使用相同的模板渲染结果
+ executeTemplate(w, data)
+ return
+ }
+
+ // 显示表单
+ log.Printf("显示操作执行表单")
+ data := struct {
+ PluginName string
+ OperationName string
+ Operation *plugins.OperationInfo
+ Params map[string]interface{}
+ Result interface{}
+ Error error
+ IsPost bool
+ }{
+ PluginName: pluginName,
+ OperationName: operationName,
+ Operation: opInfo,
+ Params: make(map[string]interface{}),
+ Result: nil,
+ Error: nil,
+ IsPost: false,
+ }
+
+ executeTemplate(w, data)
+}
+
+// executeTemplate 渲染操作执行页面模板
+func executeTemplate(w http.ResponseWriter, data interface{}) {
+ tmpl := `
+
+
+
+
+ 执行操作 - {{.PluginName}}/{{.OperationName}}
+
+
+
+ 执行操作: {{.OperationName}}
+ 插件: {{.PluginName}}
+ {{if .Operation.Description}}{{.Operation.Description}}
{{end}}
+
+ {{if .IsPost}}
+
+
执行结果
+ {{if .Error}}
+
错误: {{.Error}}
+ {{else}}
+
操作成功!
+
返回结果:
+
{{printf "%+v" .Result}}
+ {{end}}
+
+
使用的参数:
+
{{printf "%+v" .Params}}
+
+
+
+
+
+ {{else}}
+
+ {{end}}
+
+ 返回操作列表
+
+
+`
+
+ // 解析模板
+ t, err := template.New("execute").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// APIOperationsHandler API接口处理器 - 返回插件操作信息
+func APIOperationsHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/api/operations/"):]
+ if pluginName == "" {
+ // 返回所有插件的操作信息
+ allOps := pm.GetAllPluginsOperations()
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(allOps); err != nil {
+ http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
+ }
+ return
+ }
+
+ // 返回特定插件的操作信息
+ ops, err := pm.GetPluginAllOperations(pluginName)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("获取操作信息失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(ops); err != nil {
+ http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// APIExecuteHandler API接口处理器 - 执行插件操作
+func APIExecuteHandler(w http.ResponseWriter, r *http.Request) {
+ // 只接受POST请求
+ if r.Method != http.MethodPost {
+ http.Error(w, "只接受POST请求", http.StatusMethodNotAllowed)
+ return
+ }
+
+ log.Printf("收到API执行请求")
+
+ // 解析JSON请求
+ var request struct {
+ Plugin string `json:"plugin"`
+ Operation string `json:"operation"`
+ Parameters map[string]interface{} `json:"parameters"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+ log.Printf("解析请求失败: %v", err)
+ http.Error(w, fmt.Sprintf("解析请求失败: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ log.Printf("执行API请求: 插件=%s, 操作=%s", request.Plugin, request.Operation)
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ // 使用goroutine和通道执行操作,避免阻塞
+ resultChan := make(chan interface{}, 1)
+ errChan := make(chan error, 1)
+
+ go func() {
+ result, err := pm.ExecutePlugin(ctx, request.Plugin, request.Operation, request.Parameters)
+ resultChan <- result
+ errChan <- err
+ }()
+
+ // 等待结果或超时
+ var result interface{}
+ var err error
+
+ select {
+ case result = <-resultChan:
+ err = <-errChan
+ log.Printf("API操作执行完成: %v, 错误: %v", result, err)
+ case <-time.After(10 * time.Second):
+ log.Printf("API操作执行超时")
+ http.Error(w, "操作执行超时", http.StatusRequestTimeout)
+ return
+ }
+
+ // 构建响应
+ response := struct {
+ Success bool `json:"success"`
+ Result interface{} `json:"result,omitempty"`
+ Error string `json:"error,omitempty"`
+ }{
+ Success: err == nil,
+ Result: result,
+ }
+
+ if err != nil {
+ response.Error = err.Error()
+ }
+
+ // 返回响应
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ log.Printf("编码JSON失败: %v", err)
+ http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// getPluginInfo 获取插件信息和配置
+func getPluginInfo(name string) (*plugins.PluginInfo, map[string]interface{}) {
+ plugins := pm.GetPluginInfos()
+
+ for _, info := range plugins {
+ if info.Name == name {
+ config, _ := pm.GetPluginConfig(name)
+ return &info, config
+ }
+ }
+
+ return nil, nil
+}
+
+// EventStreamHandler 处理SSE事件流
+func EventStreamHandler(w http.ResponseWriter, r *http.Request) {
+ // 设置SSE相关的响应头
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ // 获取请求参数
+ parts := strings.Split(r.URL.Path, "/")
+ if len(parts) < 3 {
+ http.Error(w, "缺少插件名称", http.StatusBadRequest)
+ return
+ }
+ pluginName := parts[len(parts)-1]
+
+ if pluginName == "" {
+ http.Error(w, "缺少插件名称", http.StatusBadRequest)
+ return
+ }
+
+ // 检查插件是否存在
+ plugin, exists := pm.GetPlugin(pluginName)
+ if !exists {
+ http.Error(w, fmt.Sprintf("插件 %s 不存在", pluginName), http.StatusNotFound)
+ return
+ }
+
+ // 创建通知客户端的通道
+ eventChan := make(chan plugins.PluginEvent)
+ clientClosed := make(chan bool)
+
+ // 创建处理事件的函数
+ eventHandler := func(event plugins.PluginEvent) error {
+ // 仅处理指定插件的事件或全局事件
+ if event.PluginID == "" || event.PluginID == pluginName {
+ select {
+ case eventChan <- event:
+ // 成功发送事件
+ case <-clientClosed:
+ // 客户端已关闭
+ return fmt.Errorf("客户端已关闭连接")
+ }
+ }
+ return nil
+ }
+
+ // 订阅所有类型的事件
+ eventTypes := []plugins.PluginEventType{
+ plugins.PluginEventLoaded,
+ plugins.PluginEventInitialized,
+ plugins.PluginEventStarted,
+ plugins.PluginEventStopped,
+ plugins.PluginEventError,
+ plugins.PluginEventCustom,
+ }
+
+ // 注册事件处理器
+ for _, eventType := range eventTypes {
+ pm.SubscribeEvent(eventType, eventHandler)
+ plugin.SubscribeEvent(eventType, eventHandler)
+ }
+
+ // 在函数返回时取消订阅事件
+ defer func() {
+ for _, eventType := range eventTypes {
+ pm.UnsubscribeEvent(eventType, eventHandler)
+ plugin.UnsubscribeEvent(eventType, eventHandler)
+ }
+ close(clientClosed)
+ }()
+
+ // 发送一个初始事件,表示连接已建立
+ fmt.Fprintf(w, "event: connected\ndata: {\"pluginName\":\"%s\",\"time\":\"%s\"}\n\n",
+ pluginName, time.Now().Format(time.RFC3339))
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+
+ // 监听客户端断开连接
+ notify := r.Context().Done()
+
+ // 事件循环
+ for {
+ select {
+ case <-notify:
+ // 客户端断开连接
+ log.Printf("客户端断开了SSE连接")
+ return
+ case event := <-eventChan:
+ // 序列化事件数据
+ eventData, err := json.Marshal(event)
+ if err != nil {
+ log.Printf("序列化事件失败: %v", err)
+ continue
+ }
+
+ // 发送事件
+ fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, eventData)
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ case <-time.After(30 * time.Second):
+ // 发送保持连接的注释
+ fmt.Fprintf(w, ": keepalive\n\n")
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ }
+ }
+}
+
+// TriggerEventHandler 触发自定义事件
+func TriggerEventHandler(w http.ResponseWriter, r *http.Request) {
+ // 解析请求体
+ var request struct {
+ Plugin string `json:"plugin"`
+ Type string `json:"type"`
+ Data map[string]interface{} `json:"data"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+ http.Error(w, fmt.Sprintf("解析请求失败: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // 获取插件
+ plugin, exists := pm.GetPlugin(request.Plugin)
+ if !exists {
+ http.Error(w, fmt.Sprintf("插件 %s 不存在", request.Plugin), http.StatusNotFound)
+ return
+ }
+
+ // 创建事件
+ event := plugins.PluginEvent{
+ Type: plugins.PluginEventType(request.Type),
+ PluginID: request.Plugin,
+ Timestamp: time.Now(),
+ Data: request.Data,
+ }
+
+ // 触发事件
+ err := plugin.EmitEvent(event)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("触发事件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 返回成功响应
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "success": true,
+ "message": "事件已触发",
+ })
+}
+
+func main() {
+ // 创建插件管理器
+ pm = plugins.NewPluginManager(pluginsDir)
+
+ // 加载插件
+ if err := pm.LoadPlugins(); err != nil {
+ log.Fatalf("加载插件失败: %v", err)
+ }
+
+ // 初始化并启动插件
+ ctx := context.Background()
+ if err := pm.InitPlugins(ctx); err != nil {
+ log.Printf("初始化插件失败: %v", err)
+ }
+
+ if err := pm.StartPlugins(ctx); err != nil {
+ log.Printf("启动插件失败: %v", err)
+ }
+
+ // 添加静态文件服务
+ fs := http.FileServer(http.Dir(pluginsDir))
+ http.Handle("/static/", http.StripPrefix("/static/", fs))
+
+ // 添加demo_utils_client.html的特殊路由
+ http.HandleFunc("/demo", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, filepath.Join("demo_utils_client.html"))
+ })
+
+ // 注册处理器
+ http.HandleFunc("/", AdminHandler)
+ http.HandleFunc("/enable/", EnablePluginHandler)
+ http.HandleFunc("/disable/", DisablePluginHandler)
+ http.HandleFunc("/config/", ConfigPluginHandler)
+ http.HandleFunc("/reload", ReloadPluginsHandler)
+ http.HandleFunc("/operations/", OperationsHandler)
+ http.HandleFunc("/execute/", ExecuteOperationHandler)
+ http.HandleFunc("/api/plugins", APIPluginsHandler)
+ http.HandleFunc("/api/operations/", APIOperationsHandler)
+ http.HandleFunc("/api/execute", APIExecuteHandler)
+ http.HandleFunc("/events/", EventStreamHandler)
+ http.HandleFunc("/api/trigger-event", TriggerEventHandler)
+
+ // 启动Web服务器
+ log.Println("管理界面启动在 :8080...")
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..43f73b4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/darkit/plugins
+
+go 1.24.1
diff --git a/interface.go b/interface.go
new file mode 100644
index 0000000..6b954c5
--- /dev/null
+++ b/interface.go
@@ -0,0 +1,361 @@
+package plugins
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+// PluginType 插件类型
+type PluginType string
+
+// 预定义的插件类型
+const (
+ PluginTypeGeneral PluginType = "general" // 通用插件
+ PluginTypeStorage PluginType = "storage" // 存储插件
+ PluginTypeSecurity PluginType = "security" // 安全插件
+ PluginTypeNetwork PluginType = "network" // 网络插件
+ PluginTypeUtils PluginType = "utils" // 工具插件
+ PluginTypeHardware PluginType = "hardware" // 硬件插件
+ PluginTypeUI PluginType = "ui" // 用户界面插件
+ // 可以根据需求添加更多插件类型
+)
+
+// PluginStatus 插件状态
+type PluginStatus string
+
+// 预定义的插件状态
+const (
+ PluginStatusUninitialized PluginStatus = "uninitialized" // 未初始化
+ PluginStatusInitialized PluginStatus = "initialized" // 已初始化
+ PluginStatusRunning PluginStatus = "running" // 运行中
+ PluginStatusStopped PluginStatus = "stopped" // 已停止
+ PluginStatusError PluginStatus = "error" // 错误状态
+)
+
+// PluginDependency 插件依赖
+type PluginDependency struct {
+ Name string `json:"name"` // 依赖的插件名称
+ MinVersion string `json:"minVersion"` // 最低版本要求
+ MaxVersion string `json:"maxVersion"` // 最高版本要求
+ IsOptional bool `json:"isOptional"` // 是否为可选依赖
+ LoadAfter bool `json:"loadAfter"` // 是否需要在依赖插件之后加载
+ InitAfter bool `json:"initAfter"` // 是否需要在依赖插件初始化之后初始化
+ StrictVersions bool `json:"strictVersions"` // 是否严格检查版本
+ AutoDisableWith bool `json:"autoDisableWith"` // 依赖插件禁用时是否自动禁用
+}
+
+// PluginEventType 插件事件类型
+type PluginEventType string
+
+// 预定义的插件事件类型
+const (
+ PluginEventLoaded PluginEventType = "loaded" // 插件加载事件
+ PluginEventInitialized PluginEventType = "initialized" // 插件初始化事件
+ PluginEventStarted PluginEventType = "started" // 插件启动事件
+ PluginEventStopped PluginEventType = "stopped" // 插件停止事件
+ PluginEventEnabled PluginEventType = "enabled" // 插件启用事件
+ PluginEventDisabled PluginEventType = "disabled" // 插件禁用事件
+ PluginEventError PluginEventType = "error" // 插件错误事件
+ PluginEventCustom PluginEventType = "custom" // 自定义事件
+)
+
+// IPlugin 插件接口
+// 这个文件定义了所有插件必须实现的接口
+// 注意:这个文件应该与实际插件代码一起编译
+type IPlugin interface {
+ // Name 插件名称
+ Name() string
+ // Version 插件版本
+ Version() string
+ // Description 插件描述
+ Description() string
+ // Author 插件作者
+ Author() string
+ // Type 插件类型
+ Type() PluginType
+ // Status 获取插件状态
+ Status() PluginStatus
+ // Dependencies 获取插件依赖
+ Dependencies() []PluginDependency
+ // Init 初始化插件
+ Init(ctx context.Context, config map[string]interface{}) error
+ // Start 启动插件
+ Start(ctx context.Context) error
+ // Stop 停止插件
+ Stop(ctx context.Context) error
+ // IsEnabled 插件是否启用
+ IsEnabled() bool
+ // SetEnabled 设置插件启用状态
+ SetEnabled(enabled bool)
+ // GetOperationInfo 获取操作的参数信息
+ GetOperationInfo(operation string) (*OperationInfo, error)
+ // GetAllOperations 获取所有操作及其参数信息
+ GetAllOperations() []*OperationInfo
+ // Execute 执行插件功能 返回操作结果和可能的错误
+ Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error)
+ // SubscribeEvent 订阅插件事件
+ SubscribeEvent(eventType PluginEventType, handler PluginEventHandler)
+ // UnsubscribeEvent 取消订阅插件事件
+ UnsubscribeEvent(eventType PluginEventType, handler PluginEventHandler)
+ // EmitEvent 发送插件事件
+ EmitEvent(event PluginEvent) error
+ // Reload 重新加载插件
+ Reload(ctx context.Context, config map[string]interface{}) error
+ // Cleanup 清理插件资源
+ Cleanup(ctx context.Context) error
+}
+
+// PluginEvent 插件事件
+type PluginEvent struct {
+ Type PluginEventType `json:"type"` // 事件类型
+ PluginID string `json:"pluginId"` // 插件ID
+ Timestamp time.Time `json:"timestamp"` // 事件时间戳
+ Data map[string]interface{} `json:"data"` // 事件数据
+}
+
+// PluginEventHandler 插件事件处理函数
+type PluginEventHandler func(event PluginEvent) error
+
+// OperationParamInfo 操作参数信息
+type OperationParamInfo struct {
+ Name string `json:"name"` // 参数名称
+ Type string `json:"type"` // 参数类型
+ Required bool `json:"required"` // 是否必须
+ Default interface{} `json:"default"` // 默认值
+ Description string `json:"description"` // 参数描述
+}
+
+// OperationInfo 操作信息
+type OperationInfo struct {
+ Name string `json:"name"` // 操作名称
+ Description string `json:"description"` // 操作描述
+ Params []OperationParamInfo `json:"params"` // 操作参数
+ Extra map[string]interface{} `json:"extra"` // 额外信息
+}
+
+// BasePlugin 提供插件接口的基本实现
+// 插件开发者可以嵌入此结构体,以减少需要实现的方法数量
+type BasePlugin struct {
+ name string
+ version string
+ description string
+ author string
+ pluginType PluginType
+ enabled bool
+ status PluginStatus
+ dependencies []PluginDependency
+ eventHandlers map[PluginEventType][]PluginEventHandler
+}
+
+// NewBasePlugin 创建一个基本插件
+func NewBasePlugin(name, version, description, author string, pluginType PluginType) *BasePlugin {
+ return &BasePlugin{
+ name: name,
+ version: version,
+ description: description,
+ author: author,
+ pluginType: pluginType,
+ enabled: true,
+ status: PluginStatusUninitialized,
+ dependencies: []PluginDependency{},
+ eventHandlers: make(map[PluginEventType][]PluginEventHandler),
+ }
+}
+
+// NewBasePluginWithDefaultType 创建一个基本插件,使用默认的通用插件类型
+// 这是一个便捷的构造函数,适用于不需要指定特殊类型的场景
+func NewBasePluginWithDefaultType(name, version, description, author string) *BasePlugin {
+ return NewBasePlugin(name, version, description, author, PluginTypeGeneral)
+}
+
+// Name 获取插件名称
+func (p *BasePlugin) Name() string {
+ return p.name
+}
+
+// Version 获取插件版本
+func (p *BasePlugin) Version() string {
+ return p.version
+}
+
+// Description 获取插件描述
+func (p *BasePlugin) Description() string {
+ return p.description
+}
+
+// Author 获取插件作者
+func (p *BasePlugin) Author() string {
+ return p.author
+}
+
+// Type 获取插件类型
+func (p *BasePlugin) Type() PluginType {
+ return p.pluginType
+}
+
+// Status 获取插件状态
+func (p *BasePlugin) Status() PluginStatus {
+ return p.status
+}
+
+// Dependencies 获取插件依赖
+func (p *BasePlugin) Dependencies() []PluginDependency {
+ return p.dependencies
+}
+
+// SetDependencies 设置插件依赖
+func (p *BasePlugin) SetDependencies(dependencies []PluginDependency) {
+ p.dependencies = dependencies
+}
+
+// AddDependency 添加插件依赖
+func (p *BasePlugin) AddDependency(dependency PluginDependency) {
+ p.dependencies = append(p.dependencies, dependency)
+}
+
+// IsEnabled 插件是否启用
+func (p *BasePlugin) IsEnabled() bool {
+ return p.enabled
+}
+
+// SetEnabled 设置插件启用状态
+func (p *BasePlugin) SetEnabled(enabled bool) {
+ p.enabled = enabled
+
+ // 发送启用/禁用事件
+ eventType := PluginEventEnabled
+ if !enabled {
+ eventType = PluginEventDisabled
+ }
+
+ p.EmitEvent(PluginEvent{
+ Type: eventType,
+ PluginID: p.name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{"enabled": enabled},
+ })
+}
+
+// Init 初始化插件,子类需要重写此方法
+func (p *BasePlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.status = PluginStatusInitialized
+
+ // 发送初始化事件
+ p.EmitEvent(PluginEvent{
+ Type: PluginEventInitialized,
+ PluginID: p.name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{"config": config},
+ })
+
+ return nil
+}
+
+// Start 启动插件,子类需要重写此方法
+func (p *BasePlugin) Start(ctx context.Context) error {
+ p.status = PluginStatusRunning
+
+ // 发送启动事件
+ p.EmitEvent(PluginEvent{
+ Type: PluginEventStarted,
+ PluginID: p.name,
+ Timestamp: time.Now(),
+ Data: nil,
+ })
+
+ return nil
+}
+
+// Stop 停止插件,子类需要重写此方法
+func (p *BasePlugin) Stop(ctx context.Context) error {
+ p.status = PluginStatusStopped
+
+ // 发送停止事件
+ p.EmitEvent(PluginEvent{
+ Type: PluginEventStopped,
+ PluginID: p.name,
+ Timestamp: time.Now(),
+ Data: nil,
+ })
+
+ return nil
+}
+
+// GetOperationInfo 获取操作的参数信息,子类需要重写此方法
+func (p *BasePlugin) GetOperationInfo(operation string) (*OperationInfo, error) {
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.name, operation)
+}
+
+// GetAllOperations 获取所有操作及其参数信息,子类需要重写此方法
+func (p *BasePlugin) GetAllOperations() []*OperationInfo {
+ return []*OperationInfo{}
+}
+
+// Execute 执行插件功能,子类需要重写此方法
+func (p *BasePlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.name, action)
+}
+
+// SubscribeEvent 订阅插件事件
+func (p *BasePlugin) SubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ if p.eventHandlers[eventType] == nil {
+ p.eventHandlers[eventType] = []PluginEventHandler{}
+ }
+ p.eventHandlers[eventType] = append(p.eventHandlers[eventType], handler)
+}
+
+// UnsubscribeEvent 取消订阅插件事件
+func (p *BasePlugin) UnsubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ if handlers, exists := p.eventHandlers[eventType]; exists {
+ for i, h := range handlers {
+ if fmt.Sprintf("%p", h) == fmt.Sprintf("%p", handler) {
+ p.eventHandlers[eventType] = append(handlers[:i], handlers[i+1:]...)
+ break
+ }
+ }
+ }
+}
+
+// EmitEvent 发送插件事件
+func (p *BasePlugin) EmitEvent(event PluginEvent) error {
+ if handlers, exists := p.eventHandlers[event.Type]; exists {
+ for _, handler := range handlers {
+ if err := handler(event); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// Reload 重新加载插件
+func (p *BasePlugin) Reload(ctx context.Context, config map[string]interface{}) error {
+ // 默认实现是先停止,然后重新初始化和启动
+ if p.status == PluginStatusRunning {
+ if err := p.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件失败: %v", err)
+ }
+ }
+
+ if err := p.Init(ctx, config); err != nil {
+ return fmt.Errorf("重新初始化插件失败: %v", err)
+ }
+
+ if p.enabled {
+ if err := p.Start(ctx); err != nil {
+ return fmt.Errorf("重新启动插件失败: %v", err)
+ }
+ }
+
+ return nil
+}
+
+// Cleanup 清理插件资源
+func (p *BasePlugin) Cleanup(ctx context.Context) error {
+ // 默认实现是停止插件
+ if p.status == PluginStatusRunning {
+ return p.Stop(ctx)
+ }
+ return nil
+}
diff --git a/plugin.go b/plugin.go
new file mode 100644
index 0000000..24fcc1c
--- /dev/null
+++ b/plugin.go
@@ -0,0 +1,1867 @@
+package plugins
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "plugin"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+// PluginInfo 插件信息
+type PluginInfo struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Description string `json:"description"`
+ Author string `json:"author"`
+ Type PluginType `json:"type"`
+ Enabled bool `json:"enabled"`
+ Config map[string]interface{} `json:"config,omitempty"`
+}
+
+// PluginManager 插件管理器
+type PluginManager struct {
+ pluginsDir string
+ plugins map[string]IPlugin
+ pluginsByType map[PluginType]map[string]IPlugin
+ configs map[string]map[string]interface{}
+ mu sync.RWMutex
+ dynamicLoadingSupported bool // 是否支持动态加载插件
+ eventHandlers map[PluginEventType][]PluginEventHandler // 全局事件处理器
+}
+
+// NewPluginManager 创建插件管理器
+func NewPluginManager(pluginsDir string) *PluginManager {
+ // 检查当前系统是否支持动态加载插件
+ dynamicLoadingSupported := runtime.GOOS == "linux" || runtime.GOOS == "darwin"
+
+ return &PluginManager{
+ pluginsDir: pluginsDir,
+ plugins: make(map[string]IPlugin),
+ pluginsByType: make(map[PluginType]map[string]IPlugin),
+ configs: make(map[string]map[string]interface{}),
+ dynamicLoadingSupported: dynamicLoadingSupported,
+ eventHandlers: make(map[PluginEventType][]PluginEventHandler),
+ }
+}
+
+// IsDynamicLoadingSupported 检查是否支持动态加载插件
+func (pm *PluginManager) IsDynamicLoadingSupported() bool {
+ return pm.dynamicLoadingSupported
+}
+
+// LoadPlugins 加载插件
+func (pm *PluginManager) LoadPlugins() error {
+ // 确保插件目录存在
+ if err := os.MkdirAll(pm.pluginsDir, 0o755); err != nil {
+ return fmt.Errorf("创建插件目录失败: %v", err)
+ }
+
+ // 加载插件配置
+ if err := pm.loadPluginConfigs(); err != nil {
+ return fmt.Errorf("加载插件配置失败: %v", err)
+ }
+
+ // 如果不支持动态加载,则返回
+ if !pm.dynamicLoadingSupported {
+ fmt.Printf("警告: 当前系统(%s)不支持动态加载插件,跳过加载\n", runtime.GOOS)
+ return nil
+ }
+
+ // 遍历插件目录
+ err := filepath.Walk(pm.pluginsDir, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // 跳过目录和非.so文件
+ if info.IsDir() || filepath.Ext(path) != ".so" {
+ return nil
+ }
+
+ // 加载插件
+ if err := pm.loadPlugin(path); err != nil {
+ fmt.Printf("加载插件 %s 失败: %v\n", path, err)
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+// loadPluginConfigs 加载插件配置
+func (pm *PluginManager) loadPluginConfigs() error {
+ configPath := filepath.Join(pm.pluginsDir, "plugins.json")
+
+ // 先检查配置文件是否存在
+ _, err := os.Stat(configPath)
+ if os.IsNotExist(err) {
+ // 配置文件不存在,创建一个空的
+ // 注意:这里的文件IO操作在锁之外执行
+ file, err := os.Create(configPath)
+ if err != nil {
+ return fmt.Errorf("创建插件配置文件失败: %v", err)
+ }
+ file.Write([]byte("{}"))
+ file.Close()
+
+ // 初始化空配置
+ pm.mu.Lock()
+ pm.configs = make(map[string]map[string]interface{})
+ pm.mu.Unlock()
+ return nil
+ } else if err != nil {
+ return fmt.Errorf("检查插件配置文件状态失败: %v", err)
+ }
+
+ // 读取配置文件 - 在锁之外执行IO操作
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ return fmt.Errorf("读取插件配置文件失败: %v", err)
+ }
+
+ // 解析配置
+ var configs map[string]map[string]interface{}
+ if err := json.Unmarshal(data, &configs); err != nil {
+ return fmt.Errorf("解析插件配置文件失败: %v", err)
+ }
+
+ // 最后才用锁更新配置
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ // 如果配置为nil,初始化为空映射
+ if configs == nil {
+ configs = make(map[string]map[string]interface{})
+ }
+
+ pm.configs = configs
+ return nil
+}
+
+// savePluginConfigs 保存插件配置
+func (pm *PluginManager) savePluginConfigs() error {
+ configPath := filepath.Join(pm.pluginsDir, "plugins.json")
+
+ // 创建配置数据的深度副本,使用读锁,减少锁持有时间
+ configs := make(map[string]map[string]interface{})
+
+ pm.mu.RLock()
+ // 复制所有插件的配置
+ for name, plugin := range pm.plugins {
+ config := make(map[string]interface{})
+ // 获取并复制现有配置
+ if existingConfig, ok := pm.configs[name]; ok {
+ for k, v := range existingConfig {
+ // 对于复杂类型,执行深度复制
+ switch val := v.(type) {
+ case map[string]interface{}:
+ nestedMap := make(map[string]interface{})
+ for nk, nv := range val {
+ nestedMap[nk] = nv
+ }
+ config[k] = nestedMap
+ case []interface{}:
+ nestedArray := make([]interface{}, len(val))
+ copy(nestedArray, val)
+ config[k] = nestedArray
+ default:
+ config[k] = v
+ }
+ }
+ }
+ // 添加基本信息
+ config["enabled"] = plugin.IsEnabled()
+ config["type"] = string(plugin.Type())
+ configs[name] = config
+ }
+ pm.mu.RUnlock()
+
+ // 在释放锁后执行所有IO操作
+ // 序列化配置
+ data, err := json.MarshalIndent(configs, "", " ")
+ if err != nil {
+ return fmt.Errorf("序列化插件配置失败: %v", err)
+ }
+
+ // 创建临时文件
+ tmpDir := filepath.Dir(configPath)
+ // 确保目录存在
+ if err := os.MkdirAll(tmpDir, 0755); err != nil {
+ return fmt.Errorf("创建配置目录失败: %v", err)
+ }
+
+ // 使用一个唯一的临时文件名
+ tmpFile := fmt.Sprintf("%s.%d.tmp", configPath, time.Now().UnixNano())
+ if err := os.WriteFile(tmpFile, data, 0o644); err != nil {
+ return fmt.Errorf("写入临时配置文件失败: %v", err)
+ }
+
+ // 重命名临时文件为正式文件(原子操作)
+ if err := os.Rename(tmpFile, configPath); err != nil {
+ // 如果重命名失败,确保清理临时文件
+ os.Remove(tmpFile)
+ return fmt.Errorf("更新配置文件失败: %v", err)
+ }
+
+ return nil
+}
+
+// CheckDependencies 检查插件依赖关系
+func (pm *PluginManager) CheckDependencies(plugin IPlugin) error {
+ dependencies := plugin.Dependencies()
+ if len(dependencies) == 0 {
+ return nil // 没有依赖,直接返回
+ }
+
+ for _, dep := range dependencies {
+ depPlugin, exists := pm.plugins[dep.Name]
+ if !exists {
+ if dep.IsOptional {
+ continue // 可选依赖,忽略
+ }
+ return fmt.Errorf("依赖的插件 %s 不存在", dep.Name)
+ }
+
+ // 验证版本兼容性
+ if dep.MinVersion != "" || dep.MaxVersion != "" {
+ currentVersion := depPlugin.Version()
+
+ if dep.MinVersion != "" {
+ result, err := compareVersions(currentVersion, dep.MinVersion)
+ if err != nil {
+ return fmt.Errorf("版本比较错误: %v", err)
+ }
+
+ if result < 0 {
+ return fmt.Errorf("依赖插件 %s 版本 %s 低于最低版本要求 %s",
+ dep.Name, currentVersion, dep.MinVersion)
+ }
+ }
+
+ if dep.MaxVersion != "" {
+ result, err := compareVersions(currentVersion, dep.MaxVersion)
+ if err != nil {
+ return fmt.Errorf("版本比较错误: %v", err)
+ }
+
+ if result > 0 {
+ return fmt.Errorf("依赖插件 %s 版本 %s 高于最高版本要求 %s",
+ dep.Name, currentVersion, dep.MaxVersion)
+ }
+ }
+ }
+
+ // 检查依赖插件是否启用
+ if !depPlugin.IsEnabled() && !dep.IsOptional {
+ if dep.AutoDisableWith {
+ // 自动禁用当前插件
+ plugin.SetEnabled(false)
+ return fmt.Errorf("依赖插件 %s 已禁用,当前插件自动禁用", dep.Name)
+ }
+ return fmt.Errorf("依赖插件 %s 已禁用", dep.Name)
+ }
+ }
+
+ return nil
+}
+
+// InitializeWithDependencies 根据依赖关系初始化插件
+func (pm *PluginManager) InitializeWithDependencies(ctx context.Context) error {
+ // 构建依赖图
+ dependencyGraph := make(map[string][]string)
+ pluginDeps := make(map[string][]PluginDependency)
+
+ pm.mu.RLock()
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ deps := plugin.Dependencies()
+ pluginDeps[name] = deps
+
+ dependencyGraph[name] = []string{}
+ for _, dep := range deps {
+ if !dep.IsOptional && dep.InitAfter {
+ dependencyGraph[name] = append(dependencyGraph[name], dep.Name)
+ }
+ }
+ }
+ pm.mu.RUnlock()
+
+ // 拓扑排序
+ initOrder, err := topologicalSort(dependencyGraph)
+ if err != nil {
+ return fmt.Errorf("解析依赖关系失败: %v", err)
+ }
+
+ // 按依赖顺序初始化插件
+ for _, name := range initOrder {
+ pm.mu.RLock()
+ plugin, exists := pm.plugins[name]
+ config := pm.configs[name]
+ pm.mu.RUnlock()
+
+ if !exists || !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Init(ctx, config); err != nil {
+ return fmt.Errorf("初始化插件 %s 失败: %v", name, err)
+ }
+ }
+
+ return nil
+}
+
+// SubscribeEvent 订阅全局插件事件
+func (pm *PluginManager) SubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if pm.eventHandlers[eventType] == nil {
+ pm.eventHandlers[eventType] = []PluginEventHandler{}
+ }
+ pm.eventHandlers[eventType] = append(pm.eventHandlers[eventType], handler)
+}
+
+// UnsubscribeEvent 取消订阅全局插件事件
+func (pm *PluginManager) UnsubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if handlers, exists := pm.eventHandlers[eventType]; exists {
+ for i, h := range handlers {
+ if fmt.Sprintf("%p", h) == fmt.Sprintf("%p", handler) {
+ pm.eventHandlers[eventType] = append(handlers[:i], handlers[i+1:]...)
+ break
+ }
+ }
+ }
+}
+
+// HandleEvent 处理全局插件事件
+func (pm *PluginManager) HandleEvent(event PluginEvent) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if handlers, exists := pm.eventHandlers[event.Type]; exists {
+ for _, handler := range handlers {
+ handler(event) // 忽略错误,不影响其他处理器
+ }
+ }
+}
+
+// BroadcastEvent 广播插件事件到所有处理器
+func (pm *PluginManager) BroadcastEvent(event PluginEvent) {
+ // 先处理全局事件
+ pm.HandleEvent(event)
+
+ // 再传播到特定插件
+ if event.PluginID != "" {
+ pm.mu.RLock()
+ plugin, exists := pm.plugins[event.PluginID]
+ pm.mu.RUnlock()
+
+ if exists {
+ plugin.EmitEvent(event)
+ }
+ }
+}
+
+// 加载插件时的事件通知
+func (pm *PluginManager) notifyPluginLoaded(plugin IPlugin) {
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventLoaded,
+ PluginID: plugin.Name(),
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "name": plugin.Name(),
+ "version": plugin.Version(),
+ "type": plugin.Type(),
+ },
+ })
+}
+
+// loadPlugin 加载单个插件
+func (pm *PluginManager) loadPlugin(path string) error {
+ if !strings.HasSuffix(path, ".so") && !strings.HasSuffix(path, ".dll") {
+ return nil
+ }
+
+ // 打开插件
+ plug, err := plugin.Open(path)
+ if err != nil {
+ return fmt.Errorf("打开插件失败: %v", err)
+ }
+
+ // 查找 Plugin 符号
+ symPlugin, err := plug.Lookup("Plugin")
+ if err != nil {
+ return fmt.Errorf("查找 Plugin 符号失败: %v", err)
+ }
+
+ // 尝试直接类型断言
+ fmt.Printf("插件类型: %T\n", symPlugin)
+ if p, ok := symPlugin.(IPlugin); ok {
+ pm.registerPlugin(p)
+ pm.notifyPluginLoaded(p)
+ return nil
+ }
+
+ // 直接类型断言失败,尝试通过反射获取值
+ fmt.Println("直接类型断言失败,尝试通过反射获取值")
+
+ // 获取原始插件值,用于存储在适配器中
+ origValue := reflect.ValueOf(symPlugin)
+
+ // 尝试在所有指针级别上查找方法
+ // 保存每一级指针值,用于查找方法
+ pointerValues := []reflect.Value{origValue}
+ currentValue := origValue
+
+ // 解引用并保存每一级指针
+ for currentValue.Kind() == reflect.Ptr && currentValue.Elem().IsValid() {
+ currentValue = currentValue.Elem()
+ pointerValues = append(pointerValues, currentValue)
+ }
+
+ // 尝试在不同级别的指针上查找Name和Version方法
+ var nameMethod reflect.Value
+ var versionMethod reflect.Value
+ var methodPointerLevel reflect.Value
+
+ for _, ptrValue := range pointerValues {
+ nm := ptrValue.MethodByName("Name")
+ vm := ptrValue.MethodByName("Version")
+
+ if nm.IsValid() && vm.IsValid() {
+ nameMethod = nm
+ versionMethod = vm
+ methodPointerLevel = ptrValue
+ break
+ }
+ }
+
+ if !nameMethod.IsValid() || !versionMethod.IsValid() {
+ return fmt.Errorf("插件缺少必需的方法: Name 或 Version")
+ }
+
+ // 调用 Name 和 Version 方法获取基本信息
+ nameResult := nameMethod.Call(nil)
+ versionResult := versionMethod.Call(nil)
+
+ if len(nameResult) == 0 || len(versionResult) == 0 {
+ return fmt.Errorf("插件方法返回无效结果")
+ }
+
+ pluginName := nameResult[0].String()
+ pluginVersion := versionResult[0].String()
+
+ fmt.Printf("找到插件: %s (版本 %s)\n", pluginName, pluginVersion)
+
+ // 创建适配器以包装插件
+ adapter := &PluginAdapter{
+ symPlugin: symPlugin,
+ pluginValue: methodPointerLevel, // 使用找到有效方法的指针级别
+ pluginName: pluginName,
+ pluginVersion: pluginVersion,
+ }
+
+ // 使用找到方法的同一级别指针值检查是否具有Execute方法
+ executeMethod := methodPointerLevel.MethodByName("Execute")
+ if !executeMethod.IsValid() {
+ fmt.Printf("插件未实现 Execute 方法,尝试查找特定操作方法\n")
+
+ // 查找常见的操作方法,如果找到,也可以认为是有效插件
+ methodFound := false
+ commonMethods := []string{"Log", "Info", "Error", "GetStat", "SaveFile", "LoadFile"}
+
+ for _, methodName := range commonMethods {
+ if method := methodPointerLevel.MethodByName(methodName); method.IsValid() {
+ methodFound = true
+ break
+ }
+ }
+
+ if !methodFound {
+ return fmt.Errorf("插件类型错误: 未实现 Execute 方法或任何已知的操作方法")
+ }
+ }
+
+ pm.registerPlugin(adapter)
+ pm.notifyPluginLoaded(adapter)
+ return nil
+}
+
+// registerPlugin 注册插件到插件管理器
+func (pm *PluginManager) registerPlugin(plugin IPlugin) {
+ name := plugin.Name()
+
+ // 检查插件名称是否已存在
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, exists := pm.plugins[name]; exists {
+ log.Printf("警告: 插件 %s 已存在,将被覆盖", name)
+ }
+
+ // 设置插件启用状态
+ if config, exists := pm.configs[name]; exists {
+ if enabled, ok := config["enabled"].(bool); ok {
+ plugin.SetEnabled(enabled)
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+
+ // 检查依赖关系
+ if err := pm.CheckDependencies(plugin); err != nil {
+ log.Printf("警告: 插件 %s 依赖检查失败: %v", name, err)
+ }
+
+ // 添加到插件映射
+ pm.plugins[name] = plugin
+
+ // 添加到类型映射
+ pluginType := plugin.Type()
+ if pm.pluginsByType[pluginType] == nil {
+ pm.pluginsByType[pluginType] = make(map[string]IPlugin)
+ }
+ pm.pluginsByType[pluginType][name] = plugin
+
+ log.Printf("插件 %s (类型: %s, 版本: %s) 已注册", name, pluginType, plugin.Version())
+}
+
+// RegisterPlugin 注册内置插件, 用于在不支持动态加载的平台上注册插件
+func (pm *PluginManager) RegisterPlugin(plugin IPlugin) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, exists := pm.plugins[plugin.Name()]; exists {
+ return fmt.Errorf("插件 %s 已存在", plugin.Name())
+ }
+
+ // 设置插件启用状态
+ if config, exists := pm.configs[plugin.Name()]; exists {
+ if enabled, ok := config["enabled"].(bool); ok {
+ plugin.SetEnabled(enabled)
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+
+ // 检查依赖关系
+ if err := pm.CheckDependencies(plugin); err != nil {
+ return fmt.Errorf("依赖检查失败: %v", err)
+ }
+
+ // 注册插件
+ pm.registerPlugin(plugin)
+
+ // 通知插件已加载
+ go pm.notifyPluginLoaded(plugin)
+
+ return nil
+}
+
+// GetPlugin 获取插件
+func (pm *PluginManager) GetPlugin(name string) (IPlugin, bool) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugin, exists := pm.plugins[name]
+ return plugin, exists
+}
+
+// GetPluginsByType 按类型获取插件
+func (pm *PluginManager) GetPluginsByType(pluginType PluginType) []IPlugin {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugins := make([]IPlugin, 0)
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for _, plugin := range typePlugins {
+ if plugin.IsEnabled() {
+ plugins = append(plugins, plugin)
+ }
+ }
+ }
+ return plugins
+}
+
+// GetAllPluginsByType 获取所有指定类型的插件,无论是否启用
+func (pm *PluginManager) GetAllPluginsByType(pluginType PluginType) []IPlugin {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugins := make([]IPlugin, 0)
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for _, plugin := range typePlugins {
+ plugins = append(plugins, plugin)
+ }
+ }
+ return plugins
+}
+
+// GetAllPlugins 获取所有插件
+func (pm *PluginManager) GetAllPlugins() []IPlugin {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugins := make([]IPlugin, 0, len(pm.plugins))
+ for _, plugin := range pm.plugins {
+ plugins = append(plugins, plugin)
+ }
+ return plugins
+}
+
+// GetPluginInfos 获取所有插件信息
+func (pm *PluginManager) GetPluginInfos() []PluginInfo {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ infos := make([]PluginInfo, 0, len(pm.plugins))
+ for name, plugin := range pm.plugins {
+ info := PluginInfo{
+ Name: plugin.Name(),
+ Version: plugin.Version(),
+ Description: plugin.Description(),
+ Author: plugin.Author(),
+ Type: plugin.Type(),
+ Enabled: plugin.IsEnabled(),
+ Config: pm.configs[name],
+ }
+ infos = append(infos, info)
+ }
+ return infos
+}
+
+// GetPluginInfosByType 按类型获取插件信息
+func (pm *PluginManager) GetPluginInfosByType(pluginType PluginType) []PluginInfo {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ infos := make([]PluginInfo, 0)
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for name, plugin := range typePlugins {
+ info := PluginInfo{
+ Name: plugin.Name(),
+ Version: plugin.Version(),
+ Description: plugin.Description(),
+ Author: plugin.Author(),
+ Type: plugin.Type(),
+ Enabled: plugin.IsEnabled(),
+ Config: pm.configs[name],
+ }
+ infos = append(infos, info)
+ }
+ }
+ return infos
+}
+
+// EnablePlugin 启用插件
+func (pm *PluginManager) EnablePlugin(name string) error {
+ // 先获取插件,避免长时间持有锁
+ pm.mu.RLock()
+ plugin, exists := pm.plugins[name]
+ pm.mu.RUnlock()
+
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 设置插件状态
+ plugin.SetEnabled(true)
+
+ // 更新配置
+ config := make(map[string]interface{})
+ pm.mu.RLock()
+ if existingConfig, ok := pm.configs[name]; ok {
+ // 复制现有配置
+ for k, v := range existingConfig {
+ config[k] = v
+ }
+ }
+ pm.mu.RUnlock()
+
+ config["enabled"] = true
+
+ // 使用单独的锁来更新配置
+ pm.mu.Lock()
+ pm.configs[name] = config
+ pm.mu.Unlock()
+
+ // 保存配置到文件
+ return pm.savePluginConfigs()
+}
+
+// DisablePlugin 禁用插件
+func (pm *PluginManager) DisablePlugin(name string) error {
+ // 先获取插件,避免长时间持有锁
+ pm.mu.RLock()
+ plugin, exists := pm.plugins[name]
+ pm.mu.RUnlock()
+
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 设置插件状态
+ plugin.SetEnabled(false)
+
+ // 更新配置
+ config := make(map[string]interface{})
+ pm.mu.RLock()
+ if existingConfig, ok := pm.configs[name]; ok {
+ // 复制现有配置
+ for k, v := range existingConfig {
+ config[k] = v
+ }
+ }
+ pm.mu.RUnlock()
+
+ config["enabled"] = false
+
+ // 使用单独的锁来更新配置
+ pm.mu.Lock()
+ pm.configs[name] = config
+ pm.mu.Unlock()
+
+ // 保存配置到文件
+ return pm.savePluginConfigs()
+}
+
+// InitPlugins 初始化所有插件
+func (pm *PluginManager) InitPlugins(ctx context.Context) error {
+ // 使用基于依赖的初始化
+ return pm.InitializeWithDependencies(ctx)
+}
+
+// InitPluginsByType 初始化指定类型的所有插件
+func (pm *PluginManager) InitPluginsByType(ctx context.Context, pluginType PluginType) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for name, plugin := range typePlugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ config := pm.configs[name]
+ if err := plugin.Init(ctx, config); err != nil {
+ return fmt.Errorf("初始化插件 %s 失败: %v", name, err)
+ }
+ }
+ }
+ return nil
+}
+
+// StartPlugins 启动所有插件
+func (pm *PluginManager) StartPlugins(ctx context.Context) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Start(ctx); err != nil {
+ return fmt.Errorf("启动插件 %s 失败: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// StartPluginsByType 启动指定类型的所有插件
+func (pm *PluginManager) StartPluginsByType(ctx context.Context, pluginType PluginType) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for name, plugin := range typePlugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Start(ctx); err != nil {
+ return fmt.Errorf("启动插件 %s 失败: %v", name, err)
+ }
+ }
+ }
+ return nil
+}
+
+// StopPlugins 停止所有插件
+func (pm *PluginManager) StopPlugins(ctx context.Context) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件 %s 失败: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// StopPluginsByType 停止指定类型的所有插件
+func (pm *PluginManager) StopPluginsByType(ctx context.Context, pluginType PluginType) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for name, plugin := range typePlugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件 %s 失败: %v", name, err)
+ }
+ }
+ }
+ return nil
+}
+
+// SetPluginConfig 设置插件配置
+func (pm *PluginManager) SetPluginConfig(name string, config map[string]interface{}) error {
+ // 先检查插件是否存在,使用读锁
+ pm.mu.RLock()
+ _, exists := pm.plugins[name]
+ pm.mu.RUnlock()
+
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 创建配置的深度副本
+ configCopy := make(map[string]interface{})
+ for k, v := range config {
+ // 对复杂类型进行深度复制
+ switch val := v.(type) {
+ case map[string]interface{}:
+ // 复制嵌套的map
+ nestedMap := make(map[string]interface{})
+ for nk, nv := range val {
+ nestedMap[nk] = nv
+ }
+ configCopy[k] = nestedMap
+ case []interface{}:
+ // 复制数组
+ nestedArray := make([]interface{}, len(val))
+ copy(nestedArray, val)
+ configCopy[k] = nestedArray
+ default:
+ // 简单类型直接复制
+ configCopy[k] = v
+ }
+ }
+
+ // 使用写锁更新配置,但尽量减少锁持有时间
+ pm.mu.Lock()
+ pm.configs[name] = configCopy
+ pm.mu.Unlock()
+
+ // 创建一个带有超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // 使用 channel 来处理保存操作
+ errChan := make(chan error, 1)
+ go func() {
+ errChan <- pm.savePluginConfigs()
+ }()
+
+ // 等待保存完成或超时
+ select {
+ case err := <-errChan:
+ return err
+ case <-ctx.Done():
+ return fmt.Errorf("保存配置超时")
+ }
+}
+
+// GetPluginConfig 获取插件配置
+func (pm *PluginManager) GetPluginConfig(name string) (map[string]interface{}, error) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if _, exists := pm.plugins[name]; !exists {
+ return nil, fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 确保存在配置
+ config := pm.configs[name]
+ if config == nil {
+ return make(map[string]interface{}), nil
+ }
+
+ // 创建配置的深度副本
+ result := make(map[string]interface{})
+ for k, v := range config {
+ // 对复杂类型进行深度复制
+ switch val := v.(type) {
+ case map[string]interface{}:
+ // 复制嵌套的map
+ nestedMap := make(map[string]interface{})
+ for nk, nv := range val {
+ nestedMap[nk] = nv
+ }
+ result[k] = nestedMap
+ case []interface{}:
+ // 复制数组
+ nestedArray := make([]interface{}, len(val))
+ copy(nestedArray, val)
+ result[k] = nestedArray
+ default:
+ // 简单类型直接复制
+ result[k] = v
+ }
+ }
+
+ return result, nil
+}
+
+// ExecutePlugin 执行指定插件的操作
+func (pm *PluginManager) ExecutePlugin(ctx context.Context, name string, action string, params map[string]interface{}) (interface{}, error) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return nil, fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ if !plugin.IsEnabled() {
+ return nil, fmt.Errorf("插件 %s 已禁用", name)
+ }
+
+ return plugin.Execute(ctx, action, params)
+}
+
+// ExecutePluginsByType 对指定类型的所有插件执行操作
+func (pm *PluginManager) ExecutePluginsByType(ctx context.Context, pluginType PluginType, action string, params map[string]interface{}) map[string]interface{} {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ results := make(map[string]interface{})
+
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for name, plugin := range typePlugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ result, err := plugin.Execute(ctx, action, params)
+ results[name] = map[string]interface{}{
+ "result": result,
+ "error": err,
+ }
+ }
+ }
+
+ return results
+}
+
+// ExecuteAllPlugins 对所有插件执行操作
+func (pm *PluginManager) ExecuteAllPlugins(ctx context.Context, action string, params map[string]interface{}) map[string]interface{} {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ results := make(map[string]interface{})
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ result, err := plugin.Execute(ctx, action, params)
+ results[name] = map[string]interface{}{
+ "result": result,
+ "error": err,
+ }
+ }
+
+ return results
+}
+
+// GetPluginOperationInfo 获取插件特定操作的参数信息
+func (pm *PluginManager) GetPluginOperationInfo(name string, operation string) (*OperationInfo, error) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return nil, fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ if !plugin.IsEnabled() {
+ return nil, fmt.Errorf("插件 %s 已禁用", name)
+ }
+
+ return plugin.GetOperationInfo(operation)
+}
+
+// GetPluginAllOperations 获取插件所有操作及其参数信息
+func (pm *PluginManager) GetPluginAllOperations(name string) (*PluginOperations, error) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return nil, fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ if !plugin.IsEnabled() {
+ return nil, fmt.Errorf("插件 %s 已禁用", name)
+ }
+
+ ops := plugin.GetAllOperations()
+
+ return &PluginOperations{
+ PluginName: plugin.Name(),
+ PluginType: plugin.Type(),
+ Operations: ops,
+ }, nil
+}
+
+// GetOperationsByType 获取指定类型插件的所有操作信息
+func (pm *PluginManager) GetOperationsByType(pluginType PluginType) []*PluginOperations {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ result := make([]*PluginOperations, 0)
+
+ if typePlugins, exists := pm.pluginsByType[pluginType]; exists {
+ for _, plugin := range typePlugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ ops := plugin.GetAllOperations()
+
+ result = append(result, &PluginOperations{
+ PluginName: plugin.Name(),
+ PluginType: plugin.Type(),
+ Operations: ops,
+ })
+ }
+ }
+
+ return result
+}
+
+// GetAllPluginsOperations 获取所有插件的所有操作信息
+func (pm *PluginManager) GetAllPluginsOperations() []*PluginOperations {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ result := make([]*PluginOperations, 0, len(pm.plugins))
+
+ for _, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ ops := plugin.GetAllOperations()
+
+ result = append(result, &PluginOperations{
+ PluginName: plugin.Name(),
+ PluginType: plugin.Type(),
+ Operations: ops,
+ })
+ }
+
+ return result
+}
+
+// ReloadPlugin 重新加载单个插件
+func (pm *PluginManager) ReloadPlugin(ctx context.Context, name string) error {
+ pm.mu.Lock()
+ plugin, exists := pm.plugins[name]
+ config := pm.configs[name]
+ pm.mu.Unlock()
+
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 发出重载事件
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventCustom,
+ PluginID: name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "action": "reloading",
+ },
+ })
+
+ // 执行重载
+ if err := plugin.Reload(ctx, config); err != nil {
+ // 发送错误事件
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventError,
+ PluginID: name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "error": err.Error(),
+ "context": "reload",
+ },
+ })
+ return fmt.Errorf("重载插件 %s 失败: %v", name, err)
+ }
+
+ // 发送重载完成事件
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventCustom,
+ PluginID: name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "action": "reloaded",
+ },
+ })
+
+ return nil
+}
+
+// ReloadAllPlugins 重新加载所有插件
+func (pm *PluginManager) ReloadAllPlugins(ctx context.Context) map[string]error {
+ errors := make(map[string]error)
+
+ pm.mu.RLock()
+ pluginNames := make([]string, 0, len(pm.plugins))
+ for name := range pm.plugins {
+ pluginNames = append(pluginNames, name)
+ }
+ pm.mu.RUnlock()
+
+ for _, name := range pluginNames {
+ if err := pm.ReloadPlugin(ctx, name); err != nil {
+ errors[name] = err
+ }
+ }
+
+ return errors
+}
+
+// InstallErrorHandler 为插件安装错误处理器
+func (pm *PluginManager) InstallErrorHandler() {
+ pm.SubscribeEvent(PluginEventError, func(event PluginEvent) error {
+ errData, ok := event.Data["error"].(string)
+ if !ok {
+ errData = "未知错误"
+ }
+
+ context, _ := event.Data["context"].(string)
+ if context == "" {
+ context = "general"
+ }
+
+ fmt.Printf("[错误] 插件 %s 在 %s 上下文中发生错误: %s\n",
+ event.PluginID, context, errData)
+ return nil
+ })
+}
+
+// RecoverPlugin 尝试恢复错误状态的插件
+func (pm *PluginManager) RecoverPlugin(ctx context.Context, name string) error {
+ pm.mu.Lock()
+ plugin, exists := pm.plugins[name]
+ config := pm.configs[name]
+ pm.mu.Unlock()
+
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ if plugin.Status() != PluginStatusError {
+ return nil // 插件不在错误状态,无需恢复
+ }
+
+ // 尝试清理、重新初始化和启动
+ if err := plugin.Cleanup(ctx); err != nil {
+ return fmt.Errorf("清理插件失败: %v", err)
+ }
+
+ if err := plugin.Init(ctx, config); err != nil {
+ return fmt.Errorf("重新初始化插件失败: %v", err)
+ }
+
+ if plugin.IsEnabled() {
+ if err := plugin.Start(ctx); err != nil {
+ return fmt.Errorf("重新启动插件失败: %v", err)
+ }
+ }
+
+ // 发送恢复事件
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventCustom,
+ PluginID: name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "action": "recovered",
+ },
+ })
+
+ return nil
+}
+
+// RecoverAllPlugins 尝试恢复所有错误状态的插件
+func (pm *PluginManager) RecoverAllPlugins(ctx context.Context) map[string]error {
+ errors := make(map[string]error)
+
+ pm.mu.RLock()
+ plugins := make([]IPlugin, 0)
+ for _, plugin := range pm.plugins {
+ if plugin.Status() == PluginStatusError {
+ plugins = append(plugins, plugin)
+ }
+ }
+ pm.mu.RUnlock()
+
+ for _, plugin := range plugins {
+ name := plugin.Name()
+ if err := pm.RecoverPlugin(ctx, name); err != nil {
+ errors[name] = err
+ }
+ }
+
+ return errors
+}
+
+// UnloadPlugin 卸载插件
+func (pm *PluginManager) UnloadPlugin(ctx context.Context, name string) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ // 检查是否有其他插件依赖该插件
+ for otherName, otherPlugin := range pm.plugins {
+ if otherName == name {
+ continue
+ }
+
+ for _, dep := range otherPlugin.Dependencies() {
+ if dep.Name == name && !dep.IsOptional {
+ return fmt.Errorf("无法卸载插件 %s: 插件 %s 依赖它", name, otherName)
+ }
+ }
+ }
+
+ // 停止并清理插件
+ if plugin.Status() == PluginStatusRunning {
+ if err := plugin.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件失败: %v", err)
+ }
+ }
+
+ if err := plugin.Cleanup(ctx); err != nil {
+ return fmt.Errorf("清理插件失败: %v", err)
+ }
+
+ // 从管理器中移除插件
+ delete(pm.plugins, name)
+ if typePlugins, exists := pm.pluginsByType[plugin.Type()]; exists {
+ delete(typePlugins, name)
+ }
+
+ // 发送卸载事件
+ pm.BroadcastEvent(PluginEvent{
+ Type: PluginEventCustom,
+ PluginID: name,
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "action": "unloaded",
+ },
+ })
+
+ return nil
+}
+
+// topologicalSort 拓扑排序,解决依赖顺序问题
+func topologicalSort(graph map[string][]string) ([]string, error) {
+ result := make([]string, 0, len(graph))
+ visited := make(map[string]bool)
+ temp := make(map[string]bool)
+
+ var visit func(string) error
+ visit = func(node string) error {
+ if temp[node] {
+ return fmt.Errorf("检测到循环依赖: %s", node)
+ }
+ if visited[node] {
+ return nil
+ }
+ temp[node] = true
+
+ for _, dep := range graph[node] {
+ if err := visit(dep); err != nil {
+ return err
+ }
+ }
+
+ temp[node] = false
+ visited[node] = true
+ result = append(result, node)
+ return nil
+ }
+
+ for node := range graph {
+ if !visited[node] {
+ if err := visit(node); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// 简单的版本比较函数
+func compareVersions(v1, v2 string) (int, error) {
+ // 移除可能的'v'前缀
+ v1 = strings.TrimPrefix(v1, "v")
+ v2 = strings.TrimPrefix(v2, "v")
+
+ // 分割版本号
+ parts1 := strings.Split(v1, ".")
+ parts2 := strings.Split(v2, ".")
+
+ // 确保有足够的部分进行比较
+ for len(parts1) < 3 {
+ parts1 = append(parts1, "0")
+ }
+ for len(parts2) < 3 {
+ parts2 = append(parts2, "0")
+ }
+
+ // 比较各部分
+ for i := 0; i < 3; i++ {
+ n1, err := strconv.Atoi(parts1[i])
+ if err != nil {
+ return 0, fmt.Errorf("无效的版本号部分 %s: %v", parts1[i], err)
+ }
+
+ n2, err := strconv.Atoi(parts2[i])
+ if err != nil {
+ return 0, fmt.Errorf("无效的版本号部分 %s: %v", parts2[i], err)
+ }
+
+ if n1 < n2 {
+ return -1, nil // v1 < v2
+ } else if n1 > n2 {
+ return 1, nil // v1 > v2
+ }
+ }
+
+ return 0, nil // v1 == v2
+}
+
+// PluginAdapter 插件适配器
+// 用于将通过反射获取的插件包装为 IPlugin 接口
+type PluginAdapter struct {
+ symPlugin interface{}
+ pluginValue reflect.Value
+ pluginName string
+ pluginVersion string
+}
+
+// Name 获取插件名称
+func (p *PluginAdapter) Name() string {
+ return p.pluginName
+}
+
+// Version 获取插件版本
+func (p *PluginAdapter) Version() string {
+ return p.pluginVersion
+}
+
+// Description 获取插件描述
+func (p *PluginAdapter) Description() string {
+ method := p.pluginValue.MethodByName("Description")
+ if !method.IsValid() {
+ return "未提供描述"
+ }
+ return method.Call(nil)[0].String()
+}
+
+// Author 获取插件作者
+func (p *PluginAdapter) Author() string {
+ method := p.pluginValue.MethodByName("Author")
+ if !method.IsValid() {
+ return "未知作者"
+ }
+ return method.Call(nil)[0].String()
+}
+
+// Type 获取插件类型
+func (p *PluginAdapter) Type() PluginType {
+ method := p.pluginValue.MethodByName("Type")
+ if !method.IsValid() {
+ return PluginTypeGeneral
+ }
+ return PluginType(method.Call(nil)[0].String())
+}
+
+// Status 获取插件状态
+func (p *PluginAdapter) Status() PluginStatus {
+ method := p.pluginValue.MethodByName("Status")
+ if !method.IsValid() {
+ return PluginStatusUninitialized
+ }
+ return PluginStatus(method.Call(nil)[0].String())
+}
+
+// Dependencies 获取插件依赖
+func (p *PluginAdapter) Dependencies() []PluginDependency {
+ method := p.pluginValue.MethodByName("Dependencies")
+ if !method.IsValid() {
+ return []PluginDependency{}
+ }
+
+ result := method.Call(nil)[0]
+ if result.IsNil() {
+ return []PluginDependency{}
+ }
+
+ // 尝试直接类型断言
+ if dependencies, ok := result.Interface().([]PluginDependency); ok {
+ return dependencies
+ }
+
+ // 尝试使用反射迭代切片并转换每个元素
+ if result.Kind() == reflect.Slice {
+ length := result.Len()
+ dependencies := make([]PluginDependency, 0, length)
+
+ for i := 0; i < length; i++ {
+ item := result.Index(i).Interface()
+
+ // 尝试将元素转换为PluginDependency
+ if dep, ok := item.(PluginDependency); ok {
+ dependencies = append(dependencies, dep)
+ } else {
+ // 如果无法直接转换,尝试使用反射读取字段
+ depValue := reflect.ValueOf(item)
+ if depValue.Kind() == reflect.Struct || (depValue.Kind() == reflect.Ptr && depValue.Elem().Kind() == reflect.Struct) {
+ // 解引用指针
+ if depValue.Kind() == reflect.Ptr {
+ depValue = depValue.Elem()
+ }
+
+ dependency := PluginDependency{}
+
+ // 获取字段值
+ if nameField := depValue.FieldByName("Name"); nameField.IsValid() {
+ dependency.Name = nameField.String()
+ }
+ if minVersionField := depValue.FieldByName("MinVersion"); minVersionField.IsValid() {
+ dependency.MinVersion = minVersionField.String()
+ }
+ if maxVersionField := depValue.FieldByName("MaxVersion"); maxVersionField.IsValid() {
+ dependency.MaxVersion = maxVersionField.String()
+ }
+ if isOptionalField := depValue.FieldByName("IsOptional"); isOptionalField.IsValid() && isOptionalField.Kind() == reflect.Bool {
+ dependency.IsOptional = isOptionalField.Bool()
+ }
+ if loadAfterField := depValue.FieldByName("LoadAfter"); loadAfterField.IsValid() && loadAfterField.Kind() == reflect.Bool {
+ dependency.LoadAfter = loadAfterField.Bool()
+ }
+ if initAfterField := depValue.FieldByName("InitAfter"); initAfterField.IsValid() && initAfterField.Kind() == reflect.Bool {
+ dependency.InitAfter = initAfterField.Bool()
+ }
+ if strictVersionsField := depValue.FieldByName("StrictVersions"); strictVersionsField.IsValid() && strictVersionsField.Kind() == reflect.Bool {
+ dependency.StrictVersions = strictVersionsField.Bool()
+ }
+ if autoDisableWithField := depValue.FieldByName("AutoDisableWith"); autoDisableWithField.IsValid() && autoDisableWithField.Kind() == reflect.Bool {
+ dependency.AutoDisableWith = autoDisableWithField.Bool()
+ }
+
+ dependencies = append(dependencies, dependency)
+ }
+ }
+ }
+
+ return dependencies
+ }
+
+ return []PluginDependency{}
+}
+
+// Init 初始化插件
+func (p *PluginAdapter) Init(ctx context.Context, config map[string]interface{}) error {
+ method := p.pluginValue.MethodByName("Init")
+ if !method.IsValid() {
+ fmt.Printf("插件 %s 没有 Init 方法,跳过初始化\n", p.Name())
+ return nil
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ reflect.ValueOf(config),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
+
+// Start 启动插件
+func (p *PluginAdapter) Start(ctx context.Context) error {
+ method := p.pluginValue.MethodByName("Start")
+ if !method.IsValid() {
+ fmt.Printf("插件 %s 没有 Start 方法,跳过启动\n", p.Name())
+ return nil
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
+
+// Stop 停止插件
+func (p *PluginAdapter) Stop(ctx context.Context) error {
+ method := p.pluginValue.MethodByName("Stop")
+ if !method.IsValid() {
+ return fmt.Errorf("插件未实现 Stop 方法")
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
+
+// IsEnabled 插件是否启用
+func (p *PluginAdapter) IsEnabled() bool {
+ method := p.pluginValue.MethodByName("IsEnabled")
+ if !method.IsValid() {
+ return true
+ }
+
+ return method.Call(nil)[0].Bool()
+}
+
+// SetEnabled 设置插件启用状态
+func (p *PluginAdapter) SetEnabled(enabled bool) {
+ method := p.pluginValue.MethodByName("SetEnabled")
+ if !method.IsValid() {
+ return
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(enabled),
+ }
+
+ method.Call(args)
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *PluginAdapter) GetOperationInfo(operation string) (*OperationInfo, error) {
+ method := p.pluginValue.MethodByName("GetOperationInfo")
+ if !method.IsValid() {
+ return nil, fmt.Errorf("插件未实现 GetOperationInfo 方法")
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(operation),
+ }
+
+ result := method.Call(args)
+ if len(result) > 1 && !result[1].IsNil() {
+ return nil, result[1].Interface().(error)
+ }
+
+ if result[0].IsNil() {
+ return nil, nil
+ }
+
+ // 尝试直接类型断言
+ if opInfo, ok := result[0].Interface().(*OperationInfo); ok {
+ return opInfo, nil
+ }
+
+ // 如果直接断言失败,尝试使用反射获取字段值
+ opValue := reflect.Indirect(result[0])
+ if opValue.Kind() != reflect.Struct {
+ // 如果不是结构体,返回基本信息
+ return &OperationInfo{
+ Name: operation,
+ Description: "通过适配器调用的操作",
+ Params: []OperationParamInfo{},
+ }, nil
+ }
+
+ // 创建操作信息对象
+ opInfo := &OperationInfo{
+ Name: operation,
+ Description: "",
+ Params: []OperationParamInfo{},
+ Extra: make(map[string]interface{}),
+ }
+
+ // 获取描述字段
+ if descField := opValue.FieldByName("Description"); descField.IsValid() && descField.Kind() == reflect.String {
+ opInfo.Description = descField.String()
+ }
+
+ // 获取参数列表
+ if paramsField := opValue.FieldByName("Params"); paramsField.IsValid() && paramsField.Kind() == reflect.Slice {
+ length := paramsField.Len()
+ opInfo.Params = make([]OperationParamInfo, 0, length)
+
+ for i := 0; i < length; i++ {
+ paramValue := reflect.Indirect(paramsField.Index(i))
+ if paramValue.Kind() != reflect.Struct {
+ continue
+ }
+
+ param := OperationParamInfo{}
+
+ // 获取参数字段
+ if nameField := paramValue.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
+ param.Name = nameField.String()
+ }
+ if typeField := paramValue.FieldByName("Type"); typeField.IsValid() && typeField.Kind() == reflect.String {
+ param.Type = typeField.String()
+ }
+ if requiredField := paramValue.FieldByName("Required"); requiredField.IsValid() && requiredField.Kind() == reflect.Bool {
+ param.Required = requiredField.Bool()
+ }
+ if defaultField := paramValue.FieldByName("Default"); defaultField.IsValid() {
+ param.Default = defaultField.Interface()
+ }
+ if descField := paramValue.FieldByName("Description"); descField.IsValid() && descField.Kind() == reflect.String {
+ param.Description = descField.String()
+ }
+
+ opInfo.Params = append(opInfo.Params, param)
+ }
+ }
+
+ // 获取额外信息
+ if extraField := opValue.FieldByName("Extra"); extraField.IsValid() && extraField.Kind() == reflect.Map {
+ for _, key := range extraField.MapKeys() {
+ if key.Kind() == reflect.String {
+ opInfo.Extra[key.String()] = extraField.MapIndex(key).Interface()
+ }
+ }
+ }
+
+ return opInfo, nil
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *PluginAdapter) GetAllOperations() []*OperationInfo {
+ method := p.pluginValue.MethodByName("GetAllOperations")
+ if !method.IsValid() {
+ return []*OperationInfo{}
+ }
+
+ result := method.Call(nil)[0]
+ if result.IsNil() {
+ return []*OperationInfo{}
+ }
+
+ // 解析返回的操作信息并返回
+ if operations, ok := result.Interface().([]*OperationInfo); ok {
+ return operations
+ }
+
+ // 尝试使用反射获取切片内容
+ if result.Kind() == reflect.Slice {
+ length := result.Len()
+ operations := make([]*OperationInfo, 0, length)
+
+ for i := 0; i < length; i++ {
+ item := result.Index(i).Interface()
+ if op, ok := item.(*OperationInfo); ok {
+ operations = append(operations, op)
+ }
+ }
+
+ return operations
+ }
+
+ // 如果无法解析,返回空数组
+ return []*OperationInfo{}
+}
+
+// Execute 执行插件功能
+func (p *PluginAdapter) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ // 首先尝试调用 Execute 方法
+ method := p.pluginValue.MethodByName("Execute")
+ if method.IsValid() {
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ reflect.ValueOf(action),
+ reflect.ValueOf(params),
+ }
+
+ result := method.Call(args)
+
+ var retValue interface{}
+ var retError error
+
+ if len(result) > 0 && !result[0].IsNil() {
+ retValue = result[0].Interface()
+ }
+
+ if len(result) > 1 && !result[1].IsNil() {
+ retError = result[1].Interface().(error)
+ }
+
+ return retValue, retError
+ }
+
+ return nil, nil
+}
+
+// SubscribeEvent 订阅插件事件
+func (p *PluginAdapter) SubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ method := p.pluginValue.MethodByName("SubscribeEvent")
+ if !method.IsValid() {
+ return
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(eventType),
+ reflect.ValueOf(handler),
+ }
+
+ method.Call(args)
+}
+
+// UnsubscribeEvent 取消订阅插件事件
+func (p *PluginAdapter) UnsubscribeEvent(eventType PluginEventType, handler PluginEventHandler) {
+ method := p.pluginValue.MethodByName("UnsubscribeEvent")
+ if !method.IsValid() {
+ return
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(eventType),
+ reflect.ValueOf(handler),
+ }
+
+ method.Call(args)
+}
+
+// EmitEvent 发送插件事件
+func (p *PluginAdapter) EmitEvent(event PluginEvent) error {
+ method := p.pluginValue.MethodByName("EmitEvent")
+ if !method.IsValid() {
+ return nil
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(event),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
+
+// Reload 重新加载插件
+func (p *PluginAdapter) Reload(ctx context.Context, config map[string]interface{}) error {
+ method := p.pluginValue.MethodByName("Reload")
+ if !method.IsValid() {
+ // 默认实现:先停止,然后重新初始化和启动
+ if err := p.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件失败: %v", err)
+ }
+
+ if err := p.Init(ctx, config); err != nil {
+ return fmt.Errorf("重新初始化插件失败: %v", err)
+ }
+
+ if p.IsEnabled() {
+ if err := p.Start(ctx); err != nil {
+ return fmt.Errorf("重新启动插件失败: %v", err)
+ }
+ }
+
+ return nil
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ reflect.ValueOf(config),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
+
+// Cleanup 清理插件资源
+func (p *PluginAdapter) Cleanup(ctx context.Context) error {
+ method := p.pluginValue.MethodByName("Cleanup")
+ if !method.IsValid() {
+ // 默认实现:停止插件
+ if p.Status() == PluginStatusRunning {
+ return p.Stop(ctx)
+ }
+ return nil
+ }
+
+ args := []reflect.Value{
+ reflect.ValueOf(ctx),
+ }
+
+ result := method.Call(args)
+ if len(result) > 0 && !result[0].IsNil() {
+ return result[0].Interface().(error)
+ }
+
+ return nil
+}
diff --git a/plugins/defaultlogger/default_logger_plugin.go b/plugins/defaultlogger/default_logger_plugin.go
new file mode 100644
index 0000000..992ff59
--- /dev/null
+++ b/plugins/defaultlogger/default_logger_plugin.go
@@ -0,0 +1,330 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/darkit/plugins"
+)
+
+// DefaultLoggerPlugin 默认日志插件
+// 提供文件日志和控制台日志功能,使用默认插件类型
+type DefaultLoggerPlugin struct {
+ *plugins.BasePlugin // 嵌入基本插件结构
+ logFile *os.File // 日志文件
+ logger *log.Logger // 日志记录器
+ config map[string]interface{} // 配置
+}
+
+// Plugin 导出的插件变量
+// 注意:变量名必须是Plugin,大小写敏感
+// 这个插件使用默认的通用插件类型(PluginTypeGeneral)
+var Plugin plugins.IPlugin = &DefaultLoggerPlugin{
+ BasePlugin: plugins.NewBasePluginWithDefaultType(
+ "DefaultLoggerPlugin",
+ "1.0.0",
+ "使用默认通用类型的日志记录插件",
+ "开发者",
+ ), // 将自动使用 PluginTypeGeneral 类型
+}
+
+// Init 初始化插件
+func (p *DefaultLoggerPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.config = config
+
+ // 获取日志文件路径
+ logPath, ok := config["log_path"].(string)
+ if !ok {
+ // 使用默认路径
+ logPath = "logs/default"
+ }
+
+ // 确保日志目录存在
+ if err := os.MkdirAll(logPath, 0o755); err != nil {
+ return fmt.Errorf("创建日志目录失败: %v", err)
+ }
+
+ // 创建日志文件
+ logFilePath := filepath.Join(logPath, fmt.Sprintf("default_app_%s.log", time.Now().Format("2006-01-02")))
+ logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ return fmt.Errorf("打开日志文件失败: %v", err)
+ }
+
+ p.logFile = logFile
+ p.logger = log.New(logFile, "[DefaultLoggerPlugin] ", log.LstdFlags)
+
+ p.logger.Println("默认日志插件初始化完成")
+ fmt.Printf("默认日志插件初始化完成,日志文件: %s,插件类型: %s\n", logFilePath, p.Type())
+
+ return nil
+}
+
+// Start 启动插件
+func (p *DefaultLoggerPlugin) Start(ctx context.Context) error {
+ if p.logger == nil {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ p.logger.Println("默认日志插件已启动")
+ fmt.Println("默认日志插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *DefaultLoggerPlugin) Stop(ctx context.Context) error {
+ if p.logger != nil {
+ p.logger.Println("默认日志插件正在停止")
+ }
+
+ if p.logFile != nil {
+ if err := p.logFile.Close(); err != nil {
+ return fmt.Errorf("关闭日志文件失败: %v", err)
+ }
+ }
+
+ fmt.Println("默认日志插件已停止")
+ return nil
+}
+
+// Log 记录日志
+func (p *DefaultLoggerPlugin) Log(level, message string) {
+ if p.logger == nil {
+ fmt.Printf("[DEFAULT][%s] %s\n", level, message)
+ return
+ }
+
+ logMsg := fmt.Sprintf("[%s] %s", level, message)
+ p.logger.Println(logMsg)
+
+ // 如果配置了同时输出到控制台
+ if consoleOutput, ok := p.config["console_output"].(bool); ok && consoleOutput {
+ fmt.Println(logMsg)
+ }
+}
+
+// Info 记录信息日志
+func (p *DefaultLoggerPlugin) Info(message string) {
+ p.Log("INFO", message)
+}
+
+// Warn 记录警告日志
+func (p *DefaultLoggerPlugin) Warn(message string) {
+ p.Log("WARN", message)
+}
+
+// Error 记录错误日志
+func (p *DefaultLoggerPlugin) Error(message string) {
+ p.Log("ERROR", message)
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *DefaultLoggerPlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "log":
+ return &plugins.OperationInfo{
+ Name: "log",
+ Description: "记录日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "level",
+ Type: "string",
+ Required: true,
+ Default: "INFO",
+ Description: "日志级别,可选值:INFO, WARN, ERROR",
+ },
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "info":
+ return &plugins.OperationInfo{
+ Name: "info",
+ Description: "记录信息日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "warn":
+ return &plugins.OperationInfo{
+ Name: "warn",
+ Description: "记录警告日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "error":
+ return &plugins.OperationInfo{
+ Name: "error",
+ Description: "记录错误日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "console":
+ return &plugins.OperationInfo{
+ Name: "console",
+ Description: "向控制台打印日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "level",
+ Type: "string",
+ Required: false,
+ Default: "INFO",
+ Description: "日志级别,可选值:INFO, WARN, ERROR",
+ },
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "控制台操作",
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.Name(), operation)
+ }
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *DefaultLoggerPlugin) GetAllOperations() []*plugins.OperationInfo {
+ operations := []*plugins.OperationInfo{}
+
+ // 添加所有支持的操作
+ ops := []string{"log", "info", "warn", "error", "console"}
+ for _, op := range ops {
+ info, _ := p.GetOperationInfo(op)
+ operations = append(operations, info)
+ }
+
+ return operations
+}
+
+// Execute 实现执行插件功能的方法
+func (p *DefaultLoggerPlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ switch action {
+ case "log":
+ // 获取参数
+ level, ok := params["level"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志级别参数或类型错误")
+ }
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志消息参数或类型错误")
+ }
+
+ // 记录日志
+ p.Log(level, message)
+ return map[string]interface{}{"success": true, "message": "日志已记录"}, nil
+
+ case "info":
+ // 获取参数
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志消息参数或类型错误")
+ }
+
+ // 记录信息日志
+ p.Info(message)
+ return map[string]interface{}{"success": true, "message": "信息日志已记录"}, nil
+
+ case "warn":
+ // 获取参数
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志消息参数或类型错误")
+ }
+
+ // 记录警告日志
+ p.Warn(message)
+ return map[string]interface{}{"success": true, "message": "警告日志已记录"}, nil
+
+ case "error":
+ // 获取参数
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志消息参数或类型错误")
+ }
+
+ // 记录错误日志
+ p.Error(message)
+ return map[string]interface{}{"success": true, "message": "错误日志已记录"}, nil
+
+ case "console":
+ // 获取参数
+ level := "INFO" // 默认级别
+ if levelParam, ok := params["level"].(string); ok && levelParam != "" {
+ level = levelParam
+ }
+
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少日志消息参数或类型错误")
+ }
+
+ // 强制输出到控制台,无论配置如何
+ formattedMsg := fmt.Sprintf("[控制台][%s] %s", level, message)
+ fmt.Println(formattedMsg)
+
+ // 同时也记录到日志文件
+ p.Log(level, fmt.Sprintf("[Web控制台] %s", message))
+
+ return map[string]interface{}{
+ "success": true,
+ "message": "日志已输出到控制台",
+ "console_output": formattedMsg,
+ }, nil
+
+ default:
+ return nil, fmt.Errorf("不支持的操作: %s", action)
+ }
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/plugins/demoutils/README.md b/plugins/demoutils/README.md
new file mode 100644
index 0000000..c23885f
--- /dev/null
+++ b/plugins/demoutils/README.md
@@ -0,0 +1,163 @@
+# DemoUtilsPlugin - 示例工具插件
+
+这是一个功能丰富的示例插件,演示了插件系统的所有接口方法和常见功能的实现方式。本插件提供了多种实用工具,包括文本处理、数据存储、计数器和数学运算等。
+
+## 功能特点
+
+- 完整实现所有插件接口方法
+- 支持多种文本处理操作
+- 内置键值数据存储功能
+- 示范事件订阅与发布机制
+- 实现计数器和状态管理
+- 演示锁机制和并发安全
+- 包含日志记录功能
+
+## 安装与构建
+
+在插件目录中执行构建脚本:
+
+```bash
+cd /www/plugins/plugins/demoutils
+./build.sh
+```
+
+脚本会自动将编译好的插件移动到正确的目录。
+
+或者,使用整体构建脚本:
+
+```bash
+cd /www/plugins
+./build_all.sh
+```
+
+## 配置选项
+
+插件支持以下配置选项:
+
+| 配置项 | 类型 | 默认值 | 描述 |
+|-------|------|-------|------|
+| log_path | string | logs/demo_plugin | 日志文件存储路径 |
+| data_store_path | string | 无 | 数据存储文件路径(可选) |
+
+配置示例:
+
+```json
+{
+ "DemoUtilsPlugin": {
+ "enabled": true,
+ "config": {
+ "log_path": "logs/custompath",
+ "data_store_path": "data/demo_store.dat"
+ }
+ }
+}
+```
+
+## 操作列表
+
+插件支持以下操作:
+
+### 文本处理
+
+| 操作 | 描述 | 参数 |
+|-----|------|------|
+| format | 格式化文本 | text: 要格式化的文本
type: 格式化类型 (upper, lower, title, trim) |
+| encode | 编码文本 | text: 要编码的文本
encoding: 编码类型 (base64, url) |
+| decode | 解码文本 | text: 要解码的文本
encoding: 解码类型 (base64, url) |
+
+### 数学工具
+
+| 操作 | 描述 | 参数 |
+|-----|------|------|
+| calculate | 执行基本数学计算 | expression: 数学表达式,例如 1+2 |
+| sort | 排序文本行或数字 | items: 要排序的项目,逗号分隔
order: 排序顺序 (asc, desc)
numeric: 是否按数字排序 |
+
+### 数据存储
+
+| 操作 | 描述 | 参数 |
+|-----|------|------|
+| store | 存储键值数据 | key: 数据键名
value: 要存储的值 |
+| retrieve | 根据键获取数据 | key: 数据键名 |
+| list | 列出存储的所有键 | 无 |
+| delete | 删除存储的键 | key: 要删除的数据键名 |
+| clear | 清空所有存储的数据 | 无 |
+
+### 状态管理
+
+| 操作 | 描述 | 参数 |
+|-----|------|------|
+| counter | 获取或设置计数器 | action: 操作 (get, set, increment, decrement, reset)
value: 设置值 (仅在 action=set 时使用) |
+
+## 使用示例
+
+### 通过Web界面
+
+1. 启动Web管理界面
+2. 导航到插件操作页面
+3. 选择 DemoUtilsPlugin 和所需操作
+4. 填写参数并执行
+
+### 通过API
+
+```bash
+# 格式化文本为大写
+curl -X POST http://localhost:8080/api/execute -d '{
+ "plugin": "DemoUtilsPlugin",
+ "operation": "format",
+ "params": {
+ "text": "hello world",
+ "type": "upper"
+ }
+}'
+
+# 存储数据
+curl -X POST http://localhost:8080/api/execute -d '{
+ "plugin": "DemoUtilsPlugin",
+ "operation": "store",
+ "params": {
+ "key": "greeting",
+ "value": "你好,世界!"
+ }
+}'
+
+# 执行计算
+curl -X POST http://localhost:8080/api/execute -d '{
+ "plugin": "DemoUtilsPlugin",
+ "operation": "calculate",
+ "params": {
+ "expression": "10*5"
+ }
+}'
+```
+
+## 事件机制
+
+插件实现了一个完整的事件订阅和发布系统,支持以下事件类型:
+
+- PluginEventInitialized - 插件初始化完成时触发
+- PluginEventStarted - 插件启动时触发
+- PluginEventStopped - 插件停止时触发
+- PluginEventCustom - 自定义事件,例如计数器更新
+
+## 开发注释
+
+本插件展示了以下关键概念:
+
+1. **锁机制** - 使用读写锁保护共享状态
+2. **配置管理** - 从配置映射中加载选项
+3. **资源管理** - 在Start和Stop中正确处理资源
+4. **状态持久化** - 将数据保存到文件
+5. **错误处理** - 提供详细的错误信息
+6. **参数验证** - 检查并验证操作参数
+
+## 开发新插件的最佳实践
+
+本示例插件演示了以下最佳实践:
+
+1. 使用嵌入BasePlugin获取基本功能
+2. 实现适当的锁机制保护共享状态
+3. 提供详细的操作参数信息
+4. 正确处理所有错误情况
+5. 实现完整的生命周期管理
+6. 使用有意义的日志记录
+7. 使用事件系统通知状态变化
\ No newline at end of file
diff --git a/plugins/demoutils/build.sh b/plugins/demoutils/build.sh
new file mode 100755
index 0000000..fa3ee6c
--- /dev/null
+++ b/plugins/demoutils/build.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# 构建示例插件的脚本
+
+# 设置变量
+PLUGIN_NAME="demoutils"
+PLUGIN_FILE="demo_plugin.go"
+OUTPUT_FILE="demoutils.so"
+DIST_DIR="../../admin/dist"
+
+# 确保脚本在正确目录下执行
+if [ ! -f "$PLUGIN_FILE" ]; then
+ echo "错误: 找不到插件源文件 $PLUGIN_FILE"
+ echo "请在包含 $PLUGIN_FILE 的目录中运行此脚本"
+ exit 1
+fi
+
+echo "开始构建 $PLUGIN_NAME 插件..."
+
+# 确保目标目录存在
+mkdir -p "$DIST_DIR"
+
+# 编译插件
+echo "编译插件: $PLUGIN_FILE -> $OUTPUT_FILE"
+go build -buildmode=plugin -o "$OUTPUT_FILE" "$PLUGIN_FILE"
+
+# 检查编译结果
+if [ $? -ne 0 ]; then
+ echo "错误: 编译插件失败"
+ exit 1
+fi
+
+# 移动插件到目标目录
+echo "移动插件到目标目录: $OUTPUT_FILE -> $DIST_DIR/$OUTPUT_FILE"
+cp "$OUTPUT_FILE" "$DIST_DIR/$OUTPUT_FILE"
+
+# 清理临时文件
+rm -f "$OUTPUT_FILE"
+
+echo "$PLUGIN_NAME 插件构建完成!"
\ No newline at end of file
diff --git a/plugins/demoutils/demo_plugin.go b/plugins/demoutils/demo_plugin.go
new file mode 100644
index 0000000..63ba833
--- /dev/null
+++ b/plugins/demoutils/demo_plugin.go
@@ -0,0 +1,1012 @@
+package main
+
+import (
+ "context"
+ "encoding/base64"
+ "fmt"
+ "math"
+ "net/url"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/darkit/plugins"
+)
+
+// DemoUtilsPlugin 是一个示例工具插件
+// 演示如何实现所有的插件接口方法和各种常见功能
+type DemoUtilsPlugin struct {
+ *plugins.BasePlugin // 嵌入基本插件结构
+ config map[string]interface{} // 配置信息
+ counter int // 计数器,用于状态演示
+ mu sync.RWMutex // 保护共享状态的互斥锁
+ dataStore map[string]interface{} // 简单的数据存储
+ subscribers map[string][]plugins.PluginEventHandler // 事件订阅者
+ logger func(format string, args ...interface{}) // 日志记录函数
+}
+
+// Plugin 导出的插件变量,必须命名为 Plugin
+var Plugin = &DemoUtilsPlugin{
+ BasePlugin: plugins.NewBasePlugin(
+ "DemoUtilsPlugin", // 插件名称
+ "1.0.0", // 版本
+ "演示插件的完整功能实现", // 描述
+ "开发团队", // 作者
+ plugins.PluginTypeUtils, // 类型为工具插件
+ ),
+ counter: 0,
+ dataStore: make(map[string]interface{}),
+ subscribers: make(map[string][]plugins.PluginEventHandler),
+}
+
+// Init 初始化插件
+func (p *DemoUtilsPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.mu.Lock()
+
+ // 复制配置,避免外部修改
+ p.config = make(map[string]interface{})
+ for k, v := range config {
+ p.config[k] = v
+ }
+
+ // 初始化subscribers字段
+ if p.subscribers == nil {
+ p.subscribers = make(map[string][]plugins.PluginEventHandler)
+ }
+
+ // 暂时释放锁,以便执行IO操作
+ p.mu.Unlock()
+
+ // 创建日志记录功能
+ logPath, ok := config["log_path"].(string)
+ if !ok {
+ logPath = "logs/demo_plugin"
+ }
+
+ // 确保日志目录存在
+ if err := os.MkdirAll(logPath, 0755); err != nil {
+ return fmt.Errorf("创建日志目录失败: %v", err)
+ }
+
+ // 定义日志函数
+ logFunc := func(format string, args ...interface{}) {
+ message := fmt.Sprintf(format, args...)
+ timestamp := time.Now().Format("2006-01-02 15:04:05")
+ logMessage := fmt.Sprintf("[%s] %s\n", timestamp, message)
+
+ // 打印到控制台
+ fmt.Print(logMessage)
+
+ // 写入日志文件
+ logFile := filepath.Join(logPath, fmt.Sprintf("demo_plugin_%s.log", time.Now().Format("2006-01-02")))
+ f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err == nil {
+ defer f.Close()
+ f.WriteString(logMessage)
+ }
+ }
+
+ // 设置依赖项和更新logger(需要加锁)
+ p.mu.Lock()
+ p.logger = logFunc
+ p.SetDependencies([]plugins.PluginDependency{
+ {
+ Name: "DefaultLoggerPlugin",
+ MinVersion: "1.0.0",
+ IsOptional: true,
+ },
+ })
+ p.mu.Unlock()
+
+ // 初始化数据存储
+ dataStore := make(map[string]interface{})
+
+ if storePath, ok := config["data_store_path"].(string); ok && storePath != "" {
+ // 如果配置了存储路径,尝试从文件加载
+ if fileData, err := os.ReadFile(storePath); err == nil {
+ // 简单实现:每行一个键值对,格式为key=value
+ lines := strings.Split(string(fileData), "\n")
+ for _, line := range lines {
+ if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
+ dataStore[parts[0]] = parts[1]
+ }
+ }
+ logFunc("从 %s 加载了 %d 条数据", storePath, len(dataStore))
+ }
+ }
+
+ // 更新数据存储(需要加锁)
+ p.mu.Lock()
+ p.dataStore = dataStore
+ p.mu.Unlock()
+
+ // 发送初始化事件
+ event := plugins.PluginEvent{
+ Type: plugins.PluginEventInitialized,
+ PluginID: p.Name(),
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{"config": config},
+ }
+
+ logFunc("插件 %s 初始化完成,配置: %v", p.Name(), config)
+
+ // 最后发送事件,确保所有初始化工作都已完成
+ return p.EmitEvent(event)
+}
+
+// Start 启动插件
+func (p *DemoUtilsPlugin) Start(ctx context.Context) error {
+ p.mu.Lock()
+ // 避免持有锁太久,只使用它设置必要的状态
+ logger := p.logger // 复制logger指针,避免在goroutine中使用可能变化的指针
+ p.mu.Unlock()
+
+ logger("插件 %s 正在启动...", p.Name())
+
+ // 可以在此处启动后台任务或进行其他启动操作
+ // 例如,每分钟递增计数器的后台任务
+ go func() {
+ ticker := time.NewTicker(time.Minute)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ logger("后台任务停止")
+ return
+ case <-ticker.C:
+ // 最小化锁的持有时间
+ func() {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.counter++
+ }()
+
+ // 在锁之外获取当前计数值
+ var counterVal int
+ func() {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ counterVal = p.counter
+ }()
+
+ // 发送计数更新事件(不持有锁)
+ event := plugins.PluginEvent{
+ Type: plugins.PluginEventCustom,
+ PluginID: p.Name(),
+ Timestamp: time.Now(),
+ Data: map[string]interface{}{
+ "action": "counter_updated",
+ "counter": counterVal,
+ },
+ }
+
+ if err := p.EmitEvent(event); err != nil {
+ logger("发送计数更新事件失败: %v", err)
+ }
+
+ logger("计数器已更新: %d", counterVal)
+ }
+ }
+ }()
+
+ // 发送启动事件
+ event := plugins.PluginEvent{
+ Type: plugins.PluginEventStarted,
+ PluginID: p.Name(),
+ Timestamp: time.Now(),
+ }
+
+ if err := p.EmitEvent(event); err != nil {
+ return fmt.Errorf("发送启动事件失败: %v", err)
+ }
+
+ logger("插件 %s 已启动", p.Name())
+ return nil
+}
+
+// Stop 停止插件
+func (p *DemoUtilsPlugin) Stop(ctx context.Context) error {
+ // 首先获取需要的信息,但不持久持有锁
+ var (
+ storePath string
+ dataToSave map[string]interface{}
+ logger func(format string, args ...interface{})
+ )
+
+ func() {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ if configPath, ok := p.config["data_store_path"].(string); ok {
+ storePath = configPath
+ }
+
+ // 深度复制数据存储
+ dataToSave = make(map[string]interface{})
+ for k, v := range p.dataStore {
+ dataToSave[k] = v
+ }
+
+ logger = p.logger
+ }()
+
+ logger("插件 %s 正在停止...", p.Name())
+
+ // 保存数据,不持有锁
+ if storePath != "" {
+ var fileData strings.Builder
+ for k, v := range dataToSave {
+ fileData.WriteString(fmt.Sprintf("%s=%v\n", k, v))
+ }
+
+ // 确保目录存在
+ dir := filepath.Dir(storePath)
+ if err := os.MkdirAll(dir, 0755); err == nil {
+ if err := os.WriteFile(storePath, []byte(fileData.String()), 0644); err == nil {
+ logger("已将 %d 条数据保存到 %s", len(dataToSave), storePath)
+ } else {
+ logger("保存数据失败: %v", err)
+ }
+ } else {
+ logger("创建数据目录失败: %v", err)
+ }
+ }
+
+ // 发送停止事件
+ event := plugins.PluginEvent{
+ Type: plugins.PluginEventStopped,
+ PluginID: p.Name(),
+ Timestamp: time.Now(),
+ }
+
+ if err := p.EmitEvent(event); err != nil {
+ return fmt.Errorf("发送停止事件失败: %v", err)
+ }
+
+ logger("插件 %s 已停止", p.Name())
+ return nil
+}
+
+// SubscribeEvent 订阅插件事件
+func (p *DemoUtilsPlugin) SubscribeEvent(eventType plugins.PluginEventType, handler plugins.PluginEventHandler) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ eventTypeStr := string(eventType)
+ if p.subscribers[eventTypeStr] == nil {
+ p.subscribers[eventTypeStr] = []plugins.PluginEventHandler{}
+ }
+
+ p.subscribers[eventTypeStr] = append(p.subscribers[eventTypeStr], handler)
+ p.logger("事件 %s 新增了一个订阅者", eventType)
+}
+
+// UnsubscribeEvent 取消订阅插件事件
+func (p *DemoUtilsPlugin) UnsubscribeEvent(eventType plugins.PluginEventType, handler plugins.PluginEventHandler) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ eventTypeStr := string(eventType)
+ if handlers, exists := p.subscribers[eventTypeStr]; exists {
+ // 由于函数不能直接比较,所以使用地址字符串比较
+ handlerAddr := fmt.Sprintf("%p", handler)
+ for i, h := range handlers {
+ if fmt.Sprintf("%p", h) == handlerAddr {
+ // 从切片中删除
+ p.subscribers[eventTypeStr] = append(handlers[:i], handlers[i+1:]...)
+ p.logger("事件 %s 移除了一个订阅者", eventType)
+ break
+ }
+ }
+ }
+}
+
+// EmitEvent 发送插件事件
+func (p *DemoUtilsPlugin) EmitEvent(event plugins.PluginEvent) error {
+ // 复制订阅者列表,避免在调用处理器时持有锁
+ p.mu.RLock()
+ // 从subscribers复制需要的处理器
+ var handlersCopy []plugins.PluginEventHandler
+ if handlers, exists := p.subscribers[string(event.Type)]; exists && len(handlers) > 0 {
+ handlersCopy = make([]plugins.PluginEventHandler, len(handlers))
+ copy(handlersCopy, handlers)
+ }
+ p.mu.RUnlock()
+
+ // 先在不持有锁的情况下调用BasePlugin的方法
+ if err := p.BasePlugin.EmitEvent(event); err != nil {
+ return err
+ }
+
+ // 再调用自己的处理器,也不持有锁
+ for _, handler := range handlersCopy {
+ if err := handler(event); err != nil {
+ p.logger("处理事件 %s 时出错: %v", event.Type, err)
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Reload 重新加载插件
+func (p *DemoUtilsPlugin) Reload(ctx context.Context, config map[string]interface{}) error {
+ p.logger("正在重新加载插件 %s...", p.Name())
+
+ // 先停止
+ if err := p.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件失败: %v", err)
+ }
+
+ // 然后重新初始化
+ if err := p.Init(ctx, config); err != nil {
+ return fmt.Errorf("重新初始化插件失败: %v", err)
+ }
+
+ // 最后重新启动
+ if p.IsEnabled() {
+ if err := p.Start(ctx); err != nil {
+ return fmt.Errorf("重新启动插件失败: %v", err)
+ }
+ }
+
+ p.logger("插件 %s 已重新加载", p.Name())
+ return nil
+}
+
+// Cleanup 清理插件资源
+func (p *DemoUtilsPlugin) Cleanup(ctx context.Context) error {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ p.logger("正在清理插件 %s 资源...", p.Name())
+
+ // 清理资源,例如关闭连接、释放内存等
+ p.dataStore = nil
+ p.subscribers = nil
+
+ p.logger("插件 %s 资源已清理", p.Name())
+ return nil
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *DemoUtilsPlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "calculate":
+ return &plugins.OperationInfo{
+ Name: "calculate",
+ Description: "执行基本数学计算",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "expression",
+ Type: "string",
+ Required: true,
+ Description: "数学表达式,例如 1+2",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数学工具",
+ "examples": []string{"1+2", "10-5", "3*4", "20/5"},
+ },
+ }, nil
+ case "format":
+ return &plugins.OperationInfo{
+ Name: "format",
+ Description: "格式化文本",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "text",
+ Type: "string",
+ Required: true,
+ Description: "要格式化的文本",
+ },
+ {
+ Name: "type",
+ Type: "string",
+ Required: true,
+ Default: "upper",
+ Description: "格式化类型: upper, lower, title, trim",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "文本工具",
+ },
+ }, nil
+ case "encode":
+ return &plugins.OperationInfo{
+ Name: "encode",
+ Description: "编码文本",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "text",
+ Type: "string",
+ Required: true,
+ Description: "要编码的文本",
+ },
+ {
+ Name: "encoding",
+ Type: "string",
+ Required: true,
+ Default: "base64",
+ Description: "编码类型: base64, url",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "编码工具",
+ },
+ }, nil
+ case "decode":
+ return &plugins.OperationInfo{
+ Name: "decode",
+ Description: "解码文本",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "text",
+ Type: "string",
+ Required: true,
+ Description: "要解码的文本",
+ },
+ {
+ Name: "encoding",
+ Type: "string",
+ Required: true,
+ Default: "base64",
+ Description: "解码类型: base64, url",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "编码工具",
+ },
+ }, nil
+ case "store":
+ return &plugins.OperationInfo{
+ Name: "store",
+ Description: "存储键值数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Description: "数据键名",
+ },
+ {
+ Name: "value",
+ Type: "string",
+ Required: true,
+ Description: "要存储的值",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "retrieve":
+ return &plugins.OperationInfo{
+ Name: "retrieve",
+ Description: "根据键获取数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Description: "数据键名",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "list":
+ return &plugins.OperationInfo{
+ Name: "list",
+ Description: "列出存储的所有键",
+ Params: []plugins.OperationParamInfo{},
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "delete":
+ return &plugins.OperationInfo{
+ Name: "delete",
+ Description: "删除存储的键",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Description: "要删除的数据键名",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "clear":
+ return &plugins.OperationInfo{
+ Name: "clear",
+ Description: "清空所有存储的数据",
+ Params: []plugins.OperationParamInfo{},
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "counter":
+ return &plugins.OperationInfo{
+ Name: "counter",
+ Description: "获取或设置计数器",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "action",
+ Type: "string",
+ Required: true,
+ Default: "get",
+ Description: "操作: get, set, increment, decrement, reset",
+ },
+ {
+ Name: "value",
+ Type: "integer",
+ Required: false,
+ Description: "设置值 (仅在 action=set 时使用)",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "状态管理",
+ },
+ }, nil
+ case "sort":
+ return &plugins.OperationInfo{
+ Name: "sort",
+ Description: "排序文本行或数字",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "items",
+ Type: "string",
+ Required: true,
+ Description: "要排序的项目,逗号分隔",
+ },
+ {
+ Name: "order",
+ Type: "string",
+ Required: false,
+ Default: "asc",
+ Description: "排序顺序: asc, desc",
+ },
+ {
+ Name: "numeric",
+ Type: "boolean",
+ Required: false,
+ Default: false,
+ Description: "是否按数字排序",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "排序工具",
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("操作 %s 不支持", operation)
+ }
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *DemoUtilsPlugin) GetAllOperations() []*plugins.OperationInfo {
+ allOperations := []string{
+ "calculate", "format", "encode", "decode",
+ "store", "retrieve", "list", "delete", "clear",
+ "counter", "sort",
+ }
+
+ results := make([]*plugins.OperationInfo, 0, len(allOperations))
+ for _, op := range allOperations {
+ if info, err := p.GetOperationInfo(op); err == nil {
+ results = append(results, info)
+ }
+ }
+
+ return results
+}
+
+// Execute 执行插件功能
+func (p *DemoUtilsPlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ p.logger("执行操作: %s, 参数: %v", action, params)
+
+ switch action {
+ case "calculate":
+ return p.executeCalculate(params)
+ case "format":
+ return p.executeFormat(params)
+ case "encode":
+ return p.executeEncode(params)
+ case "decode":
+ return p.executeDecode(params)
+ case "store":
+ return p.executeStore(params)
+ case "retrieve":
+ return p.executeRetrieve(params)
+ case "list":
+ return p.executeList(params)
+ case "delete":
+ return p.executeDelete(params)
+ case "clear":
+ return p.executeClear(params)
+ case "counter":
+ return p.executeCounter(params)
+ case "sort":
+ return p.executeSort(params)
+ default:
+ return nil, fmt.Errorf("不支持的操作: %s", action)
+ }
+}
+
+// 以下是各操作的具体实现
+
+// executeCalculate 执行基本数学计算
+func (p *DemoUtilsPlugin) executeCalculate(params map[string]interface{}) (interface{}, error) {
+ expr, ok := params["expression"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少表达式参数")
+ }
+
+ // 简单实现,仅支持两个数的加减乘除
+ expr = strings.TrimSpace(expr)
+ var op rune
+ var pos int
+
+ // 查找操作符
+ for i, c := range expr {
+ if c == '+' || c == '-' || c == '*' || c == '/' {
+ op = c
+ pos = i
+ break
+ }
+ }
+
+ if op == 0 {
+ return nil, fmt.Errorf("无效的表达式: 缺少操作符")
+ }
+
+ // 提取操作数
+ leftStr := strings.TrimSpace(expr[:pos])
+ rightStr := strings.TrimSpace(expr[pos+1:])
+
+ left, err := strconv.ParseFloat(leftStr, 64)
+ if err != nil {
+ return nil, fmt.Errorf("无效的左操作数: %v", err)
+ }
+
+ right, err := strconv.ParseFloat(rightStr, 64)
+ if err != nil {
+ return nil, fmt.Errorf("无效的右操作数: %v", err)
+ }
+
+ // 执行计算
+ var result float64
+ switch op {
+ case '+':
+ result = left + right
+ case '-':
+ result = left - right
+ case '*':
+ result = left * right
+ case '/':
+ if right == 0 {
+ return nil, fmt.Errorf("除数不能为零")
+ }
+ result = left / right
+ }
+
+ // 检查结果是否为整数
+ if math.Floor(result) == result {
+ return int(result), nil
+ }
+
+ return result, nil
+}
+
+// executeFormat 格式化文本
+func (p *DemoUtilsPlugin) executeFormat(params map[string]interface{}) (interface{}, error) {
+ text, ok := params["text"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少文本参数")
+ }
+
+ formatType, ok := params["type"].(string)
+ if !ok {
+ formatType = "upper" // 默认格式化类型
+ }
+
+ switch formatType {
+ case "upper":
+ return strings.ToUpper(text), nil
+ case "lower":
+ return strings.ToLower(text), nil
+ case "title":
+ return strings.Title(text), nil
+ case "trim":
+ return strings.TrimSpace(text), nil
+ default:
+ return nil, fmt.Errorf("不支持的格式化类型: %s", formatType)
+ }
+}
+
+// executeEncode 编码文本
+func (p *DemoUtilsPlugin) executeEncode(params map[string]interface{}) (interface{}, error) {
+ text, ok := params["text"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少文本参数")
+ }
+
+ encoding, ok := params["encoding"].(string)
+ if !ok {
+ encoding = "base64" // 默认编码类型
+ }
+
+ switch encoding {
+ case "base64":
+ return base64.StdEncoding.EncodeToString([]byte(text)), nil
+ case "url":
+ return url.QueryEscape(text), nil
+ default:
+ return nil, fmt.Errorf("不支持的编码类型: %s", encoding)
+ }
+}
+
+// executeDecode 解码文本
+func (p *DemoUtilsPlugin) executeDecode(params map[string]interface{}) (interface{}, error) {
+ text, ok := params["text"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少文本参数")
+ }
+
+ encoding, ok := params["encoding"].(string)
+ if !ok {
+ encoding = "base64" // 默认解码类型
+ }
+
+ switch encoding {
+ case "base64":
+ bytes, err := base64.StdEncoding.DecodeString(text)
+ if err != nil {
+ return nil, fmt.Errorf("Base64解码失败: %v", err)
+ }
+ return string(bytes), nil
+ case "url":
+ decoded, err := url.QueryUnescape(text)
+ if err != nil {
+ return nil, fmt.Errorf("URL解码失败: %v", err)
+ }
+ return decoded, nil
+ default:
+ return nil, fmt.Errorf("不支持的解码类型: %s", encoding)
+ }
+}
+
+// executeStore 存储键值数据
+func (p *DemoUtilsPlugin) executeStore(params map[string]interface{}) (interface{}, error) {
+ key, ok := params["key"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少键名参数")
+ }
+
+ value, ok := params["value"]
+ if !ok {
+ return nil, fmt.Errorf("缺少值参数")
+ }
+
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ p.dataStore[key] = value
+
+ return map[string]interface{}{
+ "success": true,
+ "message": fmt.Sprintf("已存储键 %s", key),
+ }, nil
+}
+
+// executeRetrieve 根据键获取数据
+func (p *DemoUtilsPlugin) executeRetrieve(params map[string]interface{}) (interface{}, error) {
+ key, ok := params["key"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少键名参数")
+ }
+
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ value, exists := p.dataStore[key]
+ if !exists {
+ return nil, fmt.Errorf("键 %s 不存在", key)
+ }
+
+ return value, nil
+}
+
+// executeList 列出存储的所有键
+func (p *DemoUtilsPlugin) executeList(params map[string]interface{}) (interface{}, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ keys := make([]string, 0, len(p.dataStore))
+ for k := range p.dataStore {
+ keys = append(keys, k)
+ }
+
+ // 按字母顺序排序
+ sort.Strings(keys)
+
+ return map[string]interface{}{
+ "keys": keys,
+ "count": len(keys),
+ }, nil
+}
+
+// executeDelete 删除存储的键
+func (p *DemoUtilsPlugin) executeDelete(params map[string]interface{}) (interface{}, error) {
+ key, ok := params["key"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少键名参数")
+ }
+
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ _, exists := p.dataStore[key]
+ if !exists {
+ return nil, fmt.Errorf("键 %s 不存在", key)
+ }
+
+ delete(p.dataStore, key)
+
+ return map[string]interface{}{
+ "success": true,
+ "message": fmt.Sprintf("已删除键 %s", key),
+ }, nil
+}
+
+// executeClear 清空所有存储的数据
+func (p *DemoUtilsPlugin) executeClear(params map[string]interface{}) (interface{}, error) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ count := len(p.dataStore)
+ p.dataStore = make(map[string]interface{})
+
+ return map[string]interface{}{
+ "success": true,
+ "message": fmt.Sprintf("已清空 %d 个存储项", count),
+ "cleared": count,
+ }, nil
+}
+
+// executeCounter 获取或设置计数器
+func (p *DemoUtilsPlugin) executeCounter(params map[string]interface{}) (interface{}, error) {
+ action, ok := params["action"].(string)
+ if !ok {
+ action = "get" // 默认操作
+ }
+
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ switch action {
+ case "get":
+ return map[string]interface{}{
+ "counter": p.counter,
+ }, nil
+ case "set":
+ value, ok := params["value"].(float64)
+ if !ok {
+ return nil, fmt.Errorf("设置计数器需要提供有效的数值")
+ }
+
+ p.counter = int(value)
+ return map[string]interface{}{
+ "success": true,
+ "counter": p.counter,
+ "message": fmt.Sprintf("计数器已设置为 %d", p.counter),
+ }, nil
+ case "increment":
+ p.counter++
+ return map[string]interface{}{
+ "success": true,
+ "counter": p.counter,
+ "message": fmt.Sprintf("计数器已递增为 %d", p.counter),
+ }, nil
+ case "decrement":
+ p.counter--
+ return map[string]interface{}{
+ "success": true,
+ "counter": p.counter,
+ "message": fmt.Sprintf("计数器已递减为 %d", p.counter),
+ }, nil
+ case "reset":
+ p.counter = 0
+ return map[string]interface{}{
+ "success": true,
+ "counter": 0,
+ "message": "计数器已重置为 0",
+ }, nil
+ default:
+ return nil, fmt.Errorf("不支持的计数器操作: %s", action)
+ }
+}
+
+// executeSort 排序文本行或数字
+func (p *DemoUtilsPlugin) executeSort(params map[string]interface{}) (interface{}, error) {
+ itemsStr, ok := params["items"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少待排序项目")
+ }
+
+ order, ok := params["order"].(string)
+ if !ok {
+ order = "asc" // 默认升序
+ }
+
+ numericSort, ok := params["numeric"].(bool)
+ if !ok {
+ numericSort = false // 默认文本排序
+ }
+
+ // 分割项目
+ items := strings.Split(itemsStr, ",")
+ for i := range items {
+ items[i] = strings.TrimSpace(items[i])
+ }
+
+ if numericSort {
+ // 数字排序
+ numbers := make([]float64, 0, len(items))
+ for _, item := range items {
+ n, err := strconv.ParseFloat(item, 64)
+ if err != nil {
+ return nil, fmt.Errorf("项目 '%s' 不是有效数字", item)
+ }
+ numbers = append(numbers, n)
+ }
+
+ if order == "asc" {
+ sort.Float64s(numbers)
+ } else {
+ sort.Sort(sort.Reverse(sort.Float64Slice(numbers)))
+ }
+
+ // 转回字符串
+ result := make([]string, len(numbers))
+ for i, n := range numbers {
+ if math.Floor(n) == n {
+ result[i] = fmt.Sprintf("%d", int(n))
+ } else {
+ result[i] = fmt.Sprintf("%g", n)
+ }
+ }
+
+ return map[string]interface{}{
+ "sorted": result,
+ "count": len(result),
+ }, nil
+ } else {
+ // 文本排序
+ if order == "asc" {
+ sort.Strings(items)
+ } else {
+ sort.Sort(sort.Reverse(sort.StringSlice(items)))
+ }
+
+ return map[string]interface{}{
+ "sorted": items,
+ "count": len(items),
+ }, nil
+ }
+}
+
+// 必须有main函数
+func main() {
+ // 这个函数不会被调用,仅用于编译插件
+}
diff --git a/plugins/logger/logger_plugin.go b/plugins/logger/logger_plugin.go
new file mode 100644
index 0000000..c21233d
--- /dev/null
+++ b/plugins/logger/logger_plugin.go
@@ -0,0 +1,316 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/darkit/plugins"
+)
+
+// LoggerPlugin 日志插件
+// 提供文件日志和控制台日志功能
+type LoggerPlugin struct {
+ *plugins.BasePlugin // 嵌入基本插件结构
+ logFile *os.File // 日志文件
+ logger *log.Logger // 日志记录器
+ config map[string]interface{} // 配置
+}
+
+// Plugin 导出的插件变量
+// 注意:变量名必须是Plugin,大小写敏感
+// 使用方式1: 明确指定插件类型
+var Plugin = &LoggerPlugin{
+ BasePlugin: plugins.NewBasePlugin(
+ "LoggerPlugin",
+ "1.0.0",
+ "简单的日志记录插件",
+ "开发者",
+ plugins.PluginTypeUtils, // 明确指定为工具类插件
+ ),
+}
+
+// 使用方式2: 使用默认插件类型(通用插件)
+// 如果您不关心插件类型或想使用默认的通用插件类型,可以使用以下方式:
+//
+// var Plugin = &LoggerPlugin{
+// BasePlugin: plugins.NewBasePluginWithDefaultType(
+// "LoggerPlugin",
+// "1.0.0",
+// "简单的日志记录插件",
+// "开发者",
+// ), // 将自动使用 PluginTypeGeneral 类型
+// }
+
+// Init 初始化插件
+func (p *LoggerPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.config = config
+
+ // 获取日志文件路径
+ logPath, ok := config["log_path"].(string)
+ if !ok {
+ // 使用默认路径
+ logPath = "logs"
+ }
+
+ // 确保日志目录存在
+ if err := os.MkdirAll(logPath, 0o755); err != nil {
+ return fmt.Errorf("创建日志目录失败: %v", err)
+ }
+
+ // 创建日志文件
+ logFilePath := filepath.Join(logPath, fmt.Sprintf("app_%s.log", time.Now().Format("2006-01-02")))
+ logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ return fmt.Errorf("打开日志文件失败: %v", err)
+ }
+
+ p.logFile = logFile
+ p.logger = log.New(logFile, "[LoggerPlugin] ", log.LstdFlags)
+
+ p.logger.Println("日志插件初始化完成")
+ fmt.Printf("日志插件初始化完成,日志文件: %s,插件类型: %s\n", logFilePath, p.Type())
+
+ return nil
+}
+
+// Start 启动插件
+func (p *LoggerPlugin) Start(ctx context.Context) error {
+ if p.logger == nil {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ p.logger.Println("日志插件已启动")
+ fmt.Println("日志插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *LoggerPlugin) Stop(ctx context.Context) error {
+ if p.logger != nil {
+ p.logger.Println("日志插件正在停止")
+ }
+
+ if p.logFile != nil {
+ if err := p.logFile.Close(); err != nil {
+ return fmt.Errorf("关闭日志文件失败: %v", err)
+ }
+ }
+
+ fmt.Println("日志插件已停止")
+ return nil
+}
+
+// Execute 执行插件功能
+func (p *LoggerPlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ switch action {
+ case "log":
+ // 需要参数: level, message
+ level, ok := params["level"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: level")
+ }
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: message")
+ }
+ p.Log(level, message)
+ return true, nil
+
+ case "info":
+ // 需要参数: message
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: message")
+ }
+ p.Info(message)
+ return true, nil
+
+ case "warn":
+ // 需要参数: message
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: message")
+ }
+ p.Warn(message)
+ return true, nil
+
+ case "error":
+ // 需要参数: message
+ message, ok := params["message"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: message")
+ }
+ p.Error(message)
+ return true, nil
+
+ case "getLoggerStatus":
+ // 不需要参数
+ status := map[string]interface{}{
+ "initialized": p.logger != nil,
+ "config": p.config,
+ }
+ return status, nil
+
+ default:
+ return nil, fmt.Errorf("未知的操作: %s", action)
+ }
+}
+
+// Log 记录日志
+func (p *LoggerPlugin) Log(level, message string) {
+ if p.logger == nil {
+ fmt.Printf("[%s] %s\n", level, message)
+ return
+ }
+
+ logMsg := fmt.Sprintf("[%s] %s", level, message)
+ p.logger.Println(logMsg)
+
+ // 如果配置了同时输出到控制台
+ if consoleOutput, ok := p.config["console_output"].(bool); ok && consoleOutput {
+ fmt.Println(logMsg)
+ }
+}
+
+// Info 记录信息日志
+func (p *LoggerPlugin) Info(message string) {
+ p.Log("INFO", message)
+}
+
+// Warn 记录警告日志
+func (p *LoggerPlugin) Warn(message string) {
+ p.Log("WARN", message)
+}
+
+// Error 记录错误日志
+func (p *LoggerPlugin) Error(message string) {
+ p.Log("ERROR", message)
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *LoggerPlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "log":
+ return &plugins.OperationInfo{
+ Name: "log",
+ Description: "记录日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "level",
+ Type: "string",
+ Required: true,
+ Default: "INFO",
+ Description: "日志级别,可选值:INFO, WARN, ERROR",
+ },
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "info":
+ return &plugins.OperationInfo{
+ Name: "info",
+ Description: "记录信息日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "warn":
+ return &plugins.OperationInfo{
+ Name: "warn",
+ Description: "记录警告日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "error":
+ return &plugins.OperationInfo{
+ Name: "error",
+ Description: "记录错误日志",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "message",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "日志消息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "日志操作",
+ },
+ }, nil
+ case "getLoggerStatus":
+ return &plugins.OperationInfo{
+ Name: "getLoggerStatus",
+ Description: "获取日志记录器状态",
+ Params: []plugins.OperationParamInfo{},
+ Extra: map[string]interface{}{
+ "category": "系统操作",
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.Name(), operation)
+ }
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *LoggerPlugin) GetAllOperations() []*plugins.OperationInfo {
+ operations := []*plugins.OperationInfo{}
+
+ // 添加 log 操作
+ logOp, _ := p.GetOperationInfo("log")
+ operations = append(operations, logOp)
+
+ // 添加 info 操作
+ infoOp, _ := p.GetOperationInfo("info")
+ operations = append(operations, infoOp)
+
+ // 添加 warn 操作
+ warnOp, _ := p.GetOperationInfo("warn")
+ operations = append(operations, warnOp)
+
+ // 添加 error 操作
+ errorOp, _ := p.GetOperationInfo("error")
+ operations = append(operations, errorOp)
+
+ // 添加 getLoggerStatus 操作
+ statusOp, _ := p.GetOperationInfo("getLoggerStatus")
+ operations = append(operations, statusOp)
+
+ return operations
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/plugins/stats/stats_plugin.go b/plugins/stats/stats_plugin.go
new file mode 100644
index 0000000..7d4703d
--- /dev/null
+++ b/plugins/stats/stats_plugin.go
@@ -0,0 +1,363 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/darkit/plugins"
+)
+
+// StatsPlugin 统计插件
+// 用于收集和记录系统运行时统计数据
+type StatsPlugin struct {
+ *plugins.BasePlugin
+ stats map[string]int64
+ startTime time.Time
+ mu sync.RWMutex
+ tickerStop chan bool
+ ticker *time.Ticker
+ config map[string]interface{}
+}
+
+// StatsParams 统计请求参数结构体
+// 允许通过结构体传递参数,简化调用
+type StatsParams struct {
+ Name string `json:"name"` // 统计项名称
+ Value int64 `json:"value"` // 统计值
+ BytesReceived int64 `json:"bytesReceived"` // 接收字节数
+ BytesSent int64 `json:"bytesSent"` // 发送字节数
+ IsError bool `json:"isError"` // 是否为错误请求
+}
+
+// Plugin 导出的插件变量
+var Plugin = &StatsPlugin{
+ // 使用默认构造函数,不指定插件类型,将默认为通用插件
+ BasePlugin: plugins.NewBasePluginWithDefaultType(
+ "StatsPlugin",
+ "1.0.0",
+ "系统运行时统计插件",
+ "开发者",
+ ),
+ stats: make(map[string]int64),
+ tickerStop: make(chan bool),
+}
+
+// 为展示如何指定类型,我们也可以显式设置插件类型
+// var Plugin = &StatsPlugin{
+// BasePlugin: plugins.NewBasePlugin(
+// "StatsPlugin",
+// "1.0.0",
+// "系统运行时统计插件",
+// "开发者",
+// plugins.PluginTypeUtils, // 明确指定为工具类插件
+// ),
+// stats: make(map[string]int64),
+// tickerStop: make(chan bool),
+// }
+
+// Init 初始化插件
+func (p *StatsPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.config = config
+
+ // 初始化统计指标
+ p.mu.Lock()
+ p.stats["requests"] = 0
+ p.stats["errors"] = 0
+ p.stats["bytes_sent"] = 0
+ p.stats["bytes_received"] = 0
+ p.mu.Unlock()
+
+ fmt.Println("统计插件初始化完成")
+ return nil
+}
+
+// Start 启动插件
+func (p *StatsPlugin) Start(ctx context.Context) error {
+ p.startTime = time.Now()
+
+ // 启动定时统计任务
+ interval := 60 * time.Second // 默认60秒
+
+ // 从配置中获取统计间隔
+ if intervalSec, ok := p.config["interval_seconds"].(float64); ok {
+ interval = time.Duration(intervalSec) * time.Second
+ }
+
+ p.ticker = time.NewTicker(interval)
+
+ go func() {
+ for {
+ select {
+ case <-p.ticker.C:
+ p.logStats()
+ case <-p.tickerStop:
+ p.ticker.Stop()
+ return
+ case <-ctx.Done():
+ p.ticker.Stop()
+ return
+ }
+ }
+ }()
+
+ fmt.Println("统计插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *StatsPlugin) Stop(ctx context.Context) error {
+ if p.ticker != nil {
+ p.tickerStop <- true
+ }
+
+ // 输出最终统计信息
+ p.logStats()
+
+ fmt.Println("统计插件已停止")
+ return nil
+}
+
+// 以下方法将被自动注册为可通过Execute调用的操作
+
+// IncrementStat 增加统计值
+// 会被自动注册为"incrementstat"操作
+func (p *StatsPlugin) IncrementStat(name string, value int64) error {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ if _, exists := p.stats[name]; exists {
+ p.stats[name] += value
+ } else {
+ p.stats[name] = value
+ }
+
+ return nil
+}
+
+// GetStat 获取统计值
+// 会被自动注册为"getstat"操作
+func (p *StatsPlugin) GetStat(name string) (int64, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ if value, exists := p.stats[name]; exists {
+ return value, nil
+ }
+ return 0, fmt.Errorf("统计项 %s 不存在", name)
+}
+
+// RecordRequest 记录请求
+// 会被自动注册为"recordrequest"操作
+func (p *StatsPlugin) RecordRequest(ctx context.Context, params StatsParams) error {
+ p.IncrementStat("requests", 1)
+ p.IncrementStat("bytes_received", params.BytesReceived)
+ p.IncrementStat("bytes_sent", params.BytesSent)
+
+ if params.IsError {
+ p.IncrementStat("errors", 1)
+ }
+
+ return nil
+}
+
+// ResetStats 重置统计数据
+// 会被自动注册为"resetstats"操作
+func (p *StatsPlugin) ResetStats() error {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ p.stats = make(map[string]int64)
+ p.stats["requests"] = 0
+ p.stats["errors"] = 0
+ p.stats["bytes_sent"] = 0
+ p.stats["bytes_received"] = 0
+ p.startTime = time.Now()
+
+ return nil
+}
+
+// GenerateStatsReport 生成统计报告
+// 会被自动注册为"generatestatsreport"操作
+func (p *StatsPlugin) GenerateStatsReport() (map[string]interface{}, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ uptime := time.Since(p.startTime).Seconds()
+ report := map[string]interface{}{
+ "uptime_seconds": uptime,
+ "stats": p.stats,
+ }
+
+ if uptime > 0 && p.stats["requests"] > 0 {
+ report["requests_per_second"] = float64(p.stats["requests"]) / uptime
+ report["error_rate"] = float64(p.stats["errors"]) * 100 / float64(p.stats["requests"])
+ }
+
+ return report, nil
+}
+
+// GetAllStats 获取所有统计数据
+// 会被自动注册为"getallstats"操作
+func (p *StatsPlugin) GetAllStats() (map[string]int64, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ // 创建一个副本
+ statsCopy := make(map[string]int64, len(p.stats))
+ for k, v := range p.stats {
+ statsCopy[k] = v
+ }
+
+ // 添加运行时间
+ statsCopy["uptime_seconds"] = int64(time.Since(p.startTime).Seconds())
+
+ return statsCopy, nil
+}
+
+// logStats 记录当前统计信息
+// 不会被注册为操作,因为它是内部方法
+func (p *StatsPlugin) logStats() {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ uptime := time.Since(p.startTime).Seconds()
+
+ fmt.Printf("===== 系统统计信息 =====\n")
+ fmt.Printf("运行时间: %.2f 秒\n", uptime)
+ fmt.Printf("总请求数: %d\n", p.stats["requests"])
+ fmt.Printf("错误数: %d\n", p.stats["errors"])
+ fmt.Printf("发送字节: %d\n", p.stats["bytes_sent"])
+ fmt.Printf("接收字节: %d\n", p.stats["bytes_received"])
+
+ if uptime > 0 && p.stats["requests"] > 0 {
+ fmt.Printf("平均请求/秒: %.2f\n", float64(p.stats["requests"])/uptime)
+ fmt.Printf("错误率: %.2f%%\n", float64(p.stats["errors"])*100/float64(p.stats["requests"]))
+ }
+
+ fmt.Printf("=======================\n")
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *StatsPlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "record":
+ return &plugins.OperationInfo{
+ Name: "record",
+ Description: "记录统计数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "统计项的唯一标识符",
+ },
+ {
+ Name: "value",
+ Type: "float64",
+ Required: true,
+ Default: 0,
+ Description: "要记录的数值",
+ },
+ {
+ Name: "tags",
+ Type: "object",
+ Required: false,
+ Default: map[string]string{},
+ Description: "额外的标签信息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据统计",
+ },
+ }, nil
+ case "increment":
+ return &plugins.OperationInfo{
+ Name: "increment",
+ Description: "增加计数器",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "计数器的唯一标识符",
+ },
+ {
+ Name: "amount",
+ Type: "float64",
+ Required: false,
+ Default: 1,
+ Description: "增加的数量,默认为1",
+ },
+ {
+ Name: "tags",
+ Type: "object",
+ Required: false,
+ Default: map[string]string{},
+ Description: "额外的标签信息",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据统计",
+ },
+ }, nil
+ case "getStats":
+ return &plugins.OperationInfo{
+ Name: "getStats",
+ Description: "获取统计数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: false,
+ Default: "",
+ Description: "统计项的唯一标识符,如果为空则返回所有统计项",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "系统操作",
+ },
+ }, nil
+ case "reset":
+ return &plugins.OperationInfo{
+ Name: "reset",
+ Description: "重置统计数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: false,
+ Default: "",
+ Description: "统计项的唯一标识符,如果为空则重置所有统计项",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "系统操作",
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.Name(), operation)
+ }
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *StatsPlugin) GetAllOperations() []*plugins.OperationInfo {
+ operations := []*plugins.OperationInfo{}
+
+ // 添加所有支持的操作
+ ops := []string{"record", "increment", "getStats", "reset"}
+ for _, op := range ops {
+ info, _ := p.GetOperationInfo(op)
+ operations = append(operations, info)
+ }
+
+ return operations
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/plugins/storage/storage_plugin.go b/plugins/storage/storage_plugin.go
new file mode 100644
index 0000000..a560273
--- /dev/null
+++ b/plugins/storage/storage_plugin.go
@@ -0,0 +1,344 @@
+package main
+
+import (
+ "context"
+ "encoding/base64"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/darkit/plugins"
+)
+
+// StoragePlugin 存储插件
+// 提供简单的文件存储功能
+type StoragePlugin struct {
+ *plugins.BasePlugin // 嵌入基本插件结构
+ storageDir string // 存储目录
+ config map[string]interface{} // 配置
+ mu sync.RWMutex // 读写锁
+}
+
+// Plugin 导出的插件变量
+// 注意:变量名必须是Plugin,大小写敏感
+var Plugin = &StoragePlugin{
+ BasePlugin: plugins.NewBasePlugin(
+ "StoragePlugin",
+ "1.0.0",
+ "简单的文件存储插件",
+ "开发者",
+ plugins.PluginTypeStorage, // 设置插件类型为存储插件
+ ),
+}
+
+// Init 初始化插件
+func (p *StoragePlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ p.config = config
+
+ // 获取存储目录路径
+ storageDir, ok := config["storage_dir"].(string)
+ if !ok {
+ // 使用默认路径
+ storageDir = "storage"
+ }
+
+ // 确保存储目录存在
+ if err := os.MkdirAll(storageDir, 0o755); err != nil {
+ return fmt.Errorf("创建存储目录失败: %v", err)
+ }
+
+ p.storageDir = storageDir
+ fmt.Println("存储插件初始化完成,存储目录:", storageDir)
+
+ return nil
+}
+
+// Start 启动插件
+func (p *StoragePlugin) Start(ctx context.Context) error {
+ if p.storageDir == "" {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ fmt.Println("存储插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *StoragePlugin) Stop(ctx context.Context) error {
+ fmt.Println("存储插件已停止")
+ return nil
+}
+
+// Execute 执行插件功能
+func (p *StoragePlugin) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
+ switch action {
+ case "saveFile":
+ // 需要参数: filename, data
+ filename, ok := params["filename"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: filename")
+ }
+
+ // 处理两种数据格式:字符串或Base64编码的二进制数据
+ var data []byte
+ if dataStr, ok := params["data"].(string); ok {
+ // 检查是否为Base64编码
+ if base64Str, ok := params["isBase64"].(bool); ok && base64Str {
+ var err error
+ data, err = base64.StdEncoding.DecodeString(dataStr)
+ if err != nil {
+ return nil, fmt.Errorf("Base64解码失败: %v", err)
+ }
+ } else {
+ data = []byte(dataStr)
+ }
+ } else {
+ return nil, fmt.Errorf("缺少必需参数: data")
+ }
+
+ err := p.SaveFile(filename, data)
+ return err == nil, err
+
+ case "loadFile":
+ // 需要参数: filename, returnBase64
+ filename, ok := params["filename"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: filename")
+ }
+
+ returnBase64, _ := params["returnBase64"].(bool)
+
+ data, err := p.LoadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ if returnBase64 {
+ return base64.StdEncoding.EncodeToString(data), nil
+ }
+ return string(data), nil
+
+ case "deleteFile":
+ // 需要参数: filename
+ filename, ok := params["filename"].(string)
+ if !ok {
+ return nil, fmt.Errorf("缺少必需参数: filename")
+ }
+
+ err := p.DeleteFile(filename)
+ return err == nil, err
+
+ case "listFiles":
+ // 不需要参数
+ files, err := p.ListFiles()
+ return files, err
+
+ case "getStorageInfo":
+ // 不需要参数
+ info := map[string]interface{}{
+ "storageDir": p.storageDir,
+ "config": p.config,
+ }
+ return info, nil
+
+ default:
+ return nil, fmt.Errorf("未知的操作: %s", action)
+ }
+}
+
+// SaveFile 保存文件
+func (p *StoragePlugin) SaveFile(filename string, data []byte) error {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ if p.storageDir == "" {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ filePath := filepath.Join(p.storageDir, filename)
+ return os.WriteFile(filePath, data, 0o644)
+}
+
+// LoadFile 加载文件
+func (p *StoragePlugin) LoadFile(filename string) ([]byte, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ if p.storageDir == "" {
+ return nil, fmt.Errorf("插件未初始化")
+ }
+
+ filePath := filepath.Join(p.storageDir, filename)
+ return os.ReadFile(filePath)
+}
+
+// DeleteFile 删除文件
+func (p *StoragePlugin) DeleteFile(filename string) error {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ if p.storageDir == "" {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ filePath := filepath.Join(p.storageDir, filename)
+ return os.Remove(filePath)
+}
+
+// ListFiles 列出所有文件
+func (p *StoragePlugin) ListFiles() ([]string, error) {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ if p.storageDir == "" {
+ return nil, fmt.Errorf("插件未初始化")
+ }
+
+ var files []string
+ entries, err := os.ReadDir(p.storageDir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ files = append(files, entry.Name())
+ }
+ }
+
+ return files, nil
+}
+
+// GetOperationInfo 获取操作的参数信息
+func (p *StoragePlugin) GetOperationInfo(operation string) (*plugins.OperationInfo, error) {
+ switch operation {
+ case "set":
+ return &plugins.OperationInfo{
+ Name: "set",
+ Description: "存储数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "数据的键名",
+ },
+ {
+ Name: "value",
+ Type: "any",
+ Required: true,
+ Default: nil,
+ Description: "要存储的数据",
+ },
+ {
+ Name: "expiration",
+ Type: "integer",
+ Required: false,
+ Default: 0,
+ Description: "过期时间(秒),0表示永不过期",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "get":
+ return &plugins.OperationInfo{
+ Name: "get",
+ Description: "获取数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "数据的键名",
+ },
+ {
+ Name: "defaultValue",
+ Type: "any",
+ Required: false,
+ Default: nil,
+ Description: "如果键不存在时返回的默认值",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "delete":
+ return &plugins.OperationInfo{
+ Name: "delete",
+ Description: "删除数据",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "要删除的数据键名",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "exists":
+ return &plugins.OperationInfo{
+ Name: "exists",
+ Description: "检查键是否存在",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "key",
+ Type: "string",
+ Required: true,
+ Default: "",
+ Description: "要检查的数据键名",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ case "keys":
+ return &plugins.OperationInfo{
+ Name: "keys",
+ Description: "获取所有键名",
+ Params: []plugins.OperationParamInfo{
+ {
+ Name: "pattern",
+ Type: "string",
+ Required: false,
+ Default: "*",
+ Description: "键名匹配模式,支持通配符 *",
+ },
+ },
+ Extra: map[string]interface{}{
+ "category": "数据存储",
+ },
+ }, nil
+ default:
+ return nil, fmt.Errorf("插件 %s 不支持 %s 操作", p.Name(), operation)
+ }
+}
+
+// GetAllOperations 获取所有操作及其参数信息
+func (p *StoragePlugin) GetAllOperations() []*plugins.OperationInfo {
+ operations := []*plugins.OperationInfo{}
+
+ // 添加所有支持的操作
+ ops := []string{"set", "get", "delete", "exists", "keys"}
+ for _, op := range ops {
+ info, _ := p.GetOperationInfo(op)
+ operations = append(operations, info)
+ }
+
+ return operations
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/types.go b/types.go
new file mode 100644
index 0000000..a31b800
--- /dev/null
+++ b/types.go
@@ -0,0 +1,8 @@
+package plugins
+
+// PluginOperations 插件操作集合
+type PluginOperations struct {
+ PluginName string `json:"pluginName"` // 插件名称
+ PluginType PluginType `json:"pluginType"` // 插件类型
+ Operations []*OperationInfo `json:"operations"` // 操作列表
+}