mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: plugin
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ctwj/urldb/plugin"
|
||||
@@ -79,22 +80,6 @@ func (ph *PluginHandler) UninstallPlugin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否可以安全卸载
|
||||
canUninstall, dependents, err := manager.CanUninstall(pluginName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if !canUninstall && !force {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Plugin cannot be safely uninstalled",
|
||||
"dependents": dependents,
|
||||
"can_force": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.UninstallPlugin(pluginName, force); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -113,19 +98,13 @@ func (ph *PluginHandler) InitializePlugin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var config map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&config); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid configuration format: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
manager := plugin.GetManager()
|
||||
if manager == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Plugin manager not initialized"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.InitializePlugin(pluginName, config); err != nil {
|
||||
if err := manager.InitializePlugin(pluginName); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
@@ -197,12 +176,19 @@ func (ph *PluginHandler) GetPluginConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
config, err := manager.GetLatestConfigVersion(pluginName)
|
||||
// 获取插件实例
|
||||
instance, err := manager.GetPluginInstance(pluginName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果插件支持配置接口,获取配置
|
||||
config := make(map[string]interface{})
|
||||
if configurablePlugin, ok := instance.Plugin.(interface{ GetConfig() map[string]interface{} }); ok {
|
||||
config = configurablePlugin.GetConfig()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"plugin_name": pluginName,
|
||||
"config": config,
|
||||
@@ -229,30 +215,37 @@ func (ph *PluginHandler) UpdatePluginConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取插件信息验证插件是否存在
|
||||
_, err := manager.GetPluginInfo(pluginName)
|
||||
// 获取插件实例验证插件是否存在
|
||||
instance, err := manager.GetPluginInstance(pluginName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查插件是否支持配置更新
|
||||
configurablePlugin, ok := instance.Plugin.(interface{ UpdateConfig(map[string]interface{}) error })
|
||||
if !ok {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Plugin does not support configuration updates"})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果插件正在运行,先停止
|
||||
status := manager.GetPluginStatus(pluginName)
|
||||
if status == types.StatusRunning {
|
||||
instanceInfo, _ := manager.GetPluginInstance(pluginName)
|
||||
if instanceInfo.Status == types.StatusRunning {
|
||||
if err := manager.StopPlugin(pluginName); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to stop plugin for configuration update: " + err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 保存新的配置
|
||||
if err := manager.SaveConfigVersion(pluginName, "latest", "Configuration updated via API", "system", config); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration: " + err.Error()})
|
||||
// 更新配置
|
||||
if err := configurablePlugin.UpdateConfig(config); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update configuration: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果插件之前是运行状态,重新启动
|
||||
if status == types.StatusRunning {
|
||||
if instanceInfo.Status == types.StatusRunning {
|
||||
if err := manager.StartPlugin(pluginName); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart plugin after configuration update: " + err.Error()})
|
||||
return
|
||||
@@ -285,17 +278,25 @@ func (ph *PluginHandler) GetPluginDependencies(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取依赖检查结果
|
||||
dependenciesStatus := manager.CheckAllDependencies()
|
||||
// 获取插件实例
|
||||
instance, err := manager.GetPluginInstance(pluginName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取依赖项列表
|
||||
dependents := manager.GetDependents(pluginName)
|
||||
// 获取依赖项列表(如果插件支持依赖接口)
|
||||
dependencies := make([]string, 0)
|
||||
dependents := make([]string, 0)
|
||||
|
||||
if dependencyPlugin, ok := instance.Plugin.(interface{ Dependencies() []string }); ok {
|
||||
dependencies = dependencyPlugin.Dependencies()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"plugin_info": pluginInfo,
|
||||
"dependencies": dependenciesStatus[pluginName],
|
||||
"dependents": dependents,
|
||||
"dependencies_status": dependenciesStatus,
|
||||
"plugin_info": pluginInfo,
|
||||
"dependencies": dependencies,
|
||||
"dependents": dependents,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -307,10 +308,11 @@ func (ph *PluginHandler) GetPluginLoadOrder(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
loadOrder, err := manager.GetLoadOrder()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
// 简化版管理器直接返回所有插件名称
|
||||
plugins := manager.ListPlugins()
|
||||
loadOrder := make([]string, len(plugins))
|
||||
for i, plugin := range plugins {
|
||||
loadOrder[i] = plugin.Name
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -327,18 +329,13 @@ func (ph *PluginHandler) ValidatePluginDependencies(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err := manager.ValidateDependencies()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
"valid": false,
|
||||
"message": "Plugin dependencies validation failed",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 检查是否有插件注册
|
||||
plugins := manager.ListPlugins()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": true,
|
||||
"message": "All plugin dependencies are satisfied",
|
||||
"valid": len(plugins) > 0, // 简单验证:如果有插件则认为有效
|
||||
"count": len(plugins),
|
||||
"plugins": plugins,
|
||||
"message": fmt.Sprintf("Found %d plugins", len(plugins)),
|
||||
})
|
||||
}
|
||||
100
plugin/demo/demo_plugin.go
Normal file
100
plugin/demo/demo_plugin.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ctwj/urldb/plugin/types"
|
||||
)
|
||||
|
||||
// DemoPlugin 演示插件
|
||||
type DemoPlugin struct {
|
||||
name string
|
||||
version string
|
||||
description string
|
||||
author string
|
||||
config map[string]interface{}
|
||||
}
|
||||
|
||||
// NewDemoPlugin 创建演示插件
|
||||
func NewDemoPlugin() *DemoPlugin {
|
||||
return &DemoPlugin{
|
||||
name: "demo-plugin",
|
||||
version: "1.0.0",
|
||||
description: "这是一个演示插件,用于测试插件系统",
|
||||
author: "Plugin System Developer",
|
||||
config: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Name 返回插件名称
|
||||
func (p *DemoPlugin) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
// Version 返回插件版本
|
||||
func (p *DemoPlugin) Version() string {
|
||||
return p.version
|
||||
}
|
||||
|
||||
// Description 返回插件描述
|
||||
func (p *DemoPlugin) Description() string {
|
||||
return p.description
|
||||
}
|
||||
|
||||
// Author 返回插件作者
|
||||
func (p *DemoPlugin) Author() string {
|
||||
return p.author
|
||||
}
|
||||
|
||||
// Start 启动插件
|
||||
func (p *DemoPlugin) Start() error {
|
||||
fmt.Printf("启动插件: %s v%s\n", p.name, p.version)
|
||||
// 模拟一些初始化工作
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println("插件启动完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止插件
|
||||
func (p *DemoPlugin) Stop() error {
|
||||
fmt.Printf("停止插件: %s\n", p.name)
|
||||
// 模拟一些清理工作
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Println("插件停止完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig 获取插件配置
|
||||
func (p *DemoPlugin) GetConfig() map[string]interface{} {
|
||||
return p.config
|
||||
}
|
||||
|
||||
// UpdateConfig 更新插件配置
|
||||
func (p *DemoPlugin) UpdateConfig(config map[string]interface{}) error {
|
||||
p.config = config
|
||||
fmt.Printf("更新插件配置: %v\n", config)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dependencies 返回插件依赖
|
||||
func (p *DemoPlugin) Dependencies() []string {
|
||||
// 这个演示插件没有依赖
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Plugin 导出的插件实例(这是插件加载器查找的符号)
|
||||
var Plugin types.Plugin = NewDemoPlugin()
|
||||
|
||||
func main() {
|
||||
// 这个main函数仅用于独立测试插件
|
||||
fmt.Println("演示插件测试")
|
||||
plugin := NewDemoPlugin()
|
||||
fmt.Printf("插件名称: %s\n", plugin.Name())
|
||||
fmt.Printf("插件版本: %s\n", plugin.Version())
|
||||
fmt.Printf("插件描述: %s\n", plugin.Description())
|
||||
|
||||
// 测试启动和停止
|
||||
plugin.Start()
|
||||
plugin.Stop()
|
||||
}
|
||||
159
plugin/loader/native_loader.go
Normal file
159
plugin/loader/native_loader.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ctwj/urldb/plugin/types"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
)
|
||||
|
||||
// NativePluginLoader 直接加载 .so 文件的插件加载器
|
||||
type NativePluginLoader struct {
|
||||
pluginDir string
|
||||
}
|
||||
|
||||
// NewNativePluginLoader 创建原生插件加载器
|
||||
func NewNativePluginLoader(pluginDir string) *NativePluginLoader {
|
||||
return &NativePluginLoader{
|
||||
pluginDir: pluginDir,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadPlugin 加载单个插件文件
|
||||
func (l *NativePluginLoader) LoadPlugin(filename string) (types.Plugin, error) {
|
||||
if !l.isPluginFile(filename) {
|
||||
return nil, fmt.Errorf("not a plugin file: %s", filename)
|
||||
}
|
||||
|
||||
pluginPath := filepath.Join(l.pluginDir, filename)
|
||||
|
||||
// 打开插件文件
|
||||
p, err := plugin.Open(pluginPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open plugin %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// 查找插件符号
|
||||
sym, err := p.Lookup("Plugin")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin symbol not found in %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// 类型断言
|
||||
pluginInstance, ok := sym.(types.Plugin)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin type in %s", filename)
|
||||
}
|
||||
|
||||
utils.Info("成功加载插件: %s", filename)
|
||||
return pluginInstance, nil
|
||||
}
|
||||
|
||||
// LoadAllPlugins 加载所有插件
|
||||
func (l *NativePluginLoader) LoadAllPlugins() ([]types.Plugin, error) {
|
||||
var plugins []types.Plugin
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(l.pluginDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create plugin directory: %v", err)
|
||||
}
|
||||
|
||||
// 读取插件目录
|
||||
files, err := ioutil.ReadDir(l.pluginDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read plugin directory: %v", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if l.isPluginFile(file.Name()) {
|
||||
plugin, err := l.LoadPlugin(file.Name())
|
||||
if err != nil {
|
||||
utils.Error("加载插件 %s 失败: %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Info("总共加载了 %d 个插件", len(plugins))
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// isPluginFile 检查是否为插件文件
|
||||
func (l *NativePluginLoader) isPluginFile(filename string) bool {
|
||||
// 根据平台检查文件扩展名
|
||||
return strings.HasSuffix(filename, l.getPluginExtension())
|
||||
}
|
||||
|
||||
// getPluginExtension 获取当前平台的插件文件扩展名
|
||||
func (l *NativePluginLoader) getPluginExtension() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return ".so"
|
||||
case "windows":
|
||||
return ".dll"
|
||||
case "darwin":
|
||||
return ".dylib"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// GetPluginInfo 获取插件文件信息
|
||||
func (l *NativePluginLoader) GetPluginInfo(filename string) (map[string]interface{}, error) {
|
||||
pluginPath := filepath.Join(l.pluginDir, filename)
|
||||
|
||||
info := make(map[string]interface{})
|
||||
info["filename"] = filename
|
||||
info["path"] = pluginPath
|
||||
|
||||
// 获取文件信息
|
||||
fileInfo, err := os.Stat(pluginPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info["size"] = fileInfo.Size()
|
||||
info["mod_time"] = fileInfo.ModTime()
|
||||
info["is_plugin"] = l.isPluginFile(filename)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListPluginFiles 列出所有插件文件
|
||||
func (l *NativePluginLoader) ListPluginFiles() ([]map[string]interface{}, error) {
|
||||
var pluginFiles []map[string]interface{}
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(l.pluginDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(l.pluginDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && l.isPluginFile(file.Name()) {
|
||||
info, err := l.GetPluginInfo(file.Name())
|
||||
if err != nil {
|
||||
utils.Error("获取插件信息失败: %v", err)
|
||||
continue
|
||||
}
|
||||
pluginFiles = append(pluginFiles, info)
|
||||
}
|
||||
}
|
||||
|
||||
return pluginFiles, nil
|
||||
}
|
||||
192
plugin/loader/simple_loader.go
Normal file
192
plugin/loader/simple_loader.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ctwj/urldb/plugin/types"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
)
|
||||
|
||||
// SimplePluginLoader 简单插件加载器,直接加载.so文件
|
||||
type SimplePluginLoader struct {
|
||||
pluginDir string
|
||||
}
|
||||
|
||||
// NewSimplePluginLoader 创建简单插件加载器
|
||||
func NewSimplePluginLoader(pluginDir string) *SimplePluginLoader {
|
||||
return &SimplePluginLoader{
|
||||
pluginDir: pluginDir,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadPlugin 加载单个插件文件
|
||||
func (l *SimplePluginLoader) LoadPlugin(filename string) (types.Plugin, error) {
|
||||
if !l.isPluginFile(filename) {
|
||||
return nil, fmt.Errorf("not a plugin file: %s", filename)
|
||||
}
|
||||
|
||||
pluginPath := filepath.Join(l.pluginDir, filename)
|
||||
|
||||
// 打开插件文件
|
||||
p, err := plugin.Open(pluginPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open plugin %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// 查找插件符号
|
||||
sym, err := p.Lookup("Plugin")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin symbol not found in %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// 类型断言
|
||||
pluginInstance, ok := sym.(types.Plugin)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin type in %s", filename)
|
||||
}
|
||||
|
||||
utils.Info("成功加载插件: %s (名称: %s, 版本: %s)",
|
||||
filename, pluginInstance.Name(), pluginInstance.Version())
|
||||
return pluginInstance, nil
|
||||
}
|
||||
|
||||
// LoadAllPlugins 加载所有插件
|
||||
func (l *SimplePluginLoader) LoadAllPlugins() ([]types.Plugin, error) {
|
||||
var plugins []types.Plugin
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(l.pluginDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create plugin directory: %v", err)
|
||||
}
|
||||
|
||||
// 读取插件目录
|
||||
files, err := ioutil.ReadDir(l.pluginDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read plugin directory: %v", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if l.isPluginFile(file.Name()) {
|
||||
pluginInstance, err := l.LoadPlugin(file.Name())
|
||||
if err != nil {
|
||||
utils.Error("加载插件 %s 失败: %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, pluginInstance)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Info("总共加载了 %d 个插件", len(plugins))
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// isPluginFile 检查是否为插件文件
|
||||
func (l *SimplePluginLoader) isPluginFile(filename string) bool {
|
||||
// 根据平台检查文件扩展名
|
||||
return strings.HasSuffix(filename, l.getPluginExtension())
|
||||
}
|
||||
|
||||
// getPluginExtension 获取当前平台的插件文件扩展名
|
||||
func (l *SimplePluginLoader) getPluginExtension() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return ".so"
|
||||
case "windows":
|
||||
return ".dll"
|
||||
case "darwin":
|
||||
return ".dylib"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// GetPluginInfo 获取插件信息(通过调用插件方法)
|
||||
func (l *SimplePluginLoader) GetPluginInfo(filename string) (map[string]interface{}, error) {
|
||||
pluginInstance, err := l.LoadPlugin(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := make(map[string]interface{})
|
||||
info["name"] = pluginInstance.Name()
|
||||
info["version"] = pluginInstance.Version()
|
||||
info["description"] = pluginInstance.Description()
|
||||
info["author"] = pluginInstance.Author()
|
||||
info["filename"] = filename
|
||||
|
||||
// 如果插件实现了ConfigurablePlugin接口,获取配置schema
|
||||
if configurablePlugin, ok := pluginInstance.(interface{ ConfigSchema() map[string]interface{} }); ok {
|
||||
info["config_schema"] = configurablePlugin.ConfigSchema()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListPluginFiles 列出所有插件文件及其信息
|
||||
func (l *SimplePluginLoader) ListPluginFiles() ([]map[string]interface{}, error) {
|
||||
var pluginFiles []map[string]interface{}
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(l.pluginDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(l.pluginDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && l.isPluginFile(file.Name()) {
|
||||
info, err := l.GetPluginInfo(file.Name())
|
||||
if err != nil {
|
||||
utils.Error("获取插件信息失败: %v", err)
|
||||
continue
|
||||
}
|
||||
pluginFiles = append(pluginFiles, info)
|
||||
}
|
||||
}
|
||||
|
||||
return pluginFiles, nil
|
||||
}
|
||||
|
||||
// InstallPluginFromBytes 从字节数据安装插件
|
||||
func (l *SimplePluginLoader) InstallPluginFromBytes(pluginName string, data []byte) error {
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(l.pluginDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建插件文件
|
||||
pluginPath := filepath.Join(l.pluginDir, pluginName+l.getPluginExtension())
|
||||
|
||||
return ioutil.WriteFile(pluginPath, data, 0755)
|
||||
}
|
||||
|
||||
// UninstallPlugin 卸载插件
|
||||
func (l *SimplePluginLoader) UninstallPlugin(pluginName string) error {
|
||||
pluginPath := filepath.Join(l.pluginDir, pluginName+l.getPluginExtension())
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("plugin %s not found", pluginName)
|
||||
}
|
||||
|
||||
// 删除插件文件
|
||||
if err := os.Remove(pluginPath); err != nil {
|
||||
return fmt.Errorf("failed to remove plugin: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("成功卸载插件: %s", pluginName)
|
||||
return nil
|
||||
}
|
||||
335
plugin/loader/zip_loader.go
Normal file
335
plugin/loader/zip_loader.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ctwj/urldb/plugin/types"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
)
|
||||
|
||||
// PluginMetadata 插件元数据
|
||||
type PluginMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
EntryPoint string `json:"entry_point"`
|
||||
ConfigSchema map[string]interface{} `json:"config_schema"`
|
||||
}
|
||||
|
||||
// ZipPluginLoader ZIP格式插件加载器
|
||||
type ZipPluginLoader struct {
|
||||
pluginDir string
|
||||
tempDir string
|
||||
}
|
||||
|
||||
// NewZipPluginLoader 创建ZIP插件加载器
|
||||
func NewZipPluginLoader(pluginDir string) *ZipPluginLoader {
|
||||
tempDir := filepath.Join(os.TempDir(), "urldb-plugins")
|
||||
return &ZipPluginLoader{
|
||||
pluginDir: pluginDir,
|
||||
tempDir: tempDir,
|
||||
}
|
||||
}
|
||||
|
||||
// InstallPlugin 安装ZIP格式的插件
|
||||
func (z *ZipPluginLoader) InstallPlugin(zipData []byte) (*PluginMetadata, error) {
|
||||
// 创建临时目录
|
||||
if err := os.MkdirAll(z.tempDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp directory: %v", err)
|
||||
}
|
||||
|
||||
// 创建临时文件
|
||||
tempFile, err := ioutil.TempFile(z.tempDir, "plugin-*.zip")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// 写入ZIP数据
|
||||
if _, err := tempFile.Write(zipData); err != nil {
|
||||
return nil, fmt.Errorf("failed to write zip data: %v", err)
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// 读取ZIP文件
|
||||
reader, err := zip.OpenReader(tempFile.Name())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open zip file: %v", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// 解析元数据
|
||||
metadata, err := z.extractMetadata(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract metadata: %v", err)
|
||||
}
|
||||
|
||||
// 验证插件
|
||||
if err := z.validatePlugin(reader, metadata); err != nil {
|
||||
return nil, fmt.Errorf("plugin validation failed: %v", err)
|
||||
}
|
||||
|
||||
// 提取插件文件
|
||||
if err := z.extractPlugin(reader, metadata); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract plugin: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("成功安装插件: %s v%s", metadata.Name, metadata.Version)
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// LoadPlugin 加载已安装的插件
|
||||
func (z *ZipPluginLoader) LoadPlugin(name string) (types.Plugin, error) {
|
||||
pluginPath := filepath.Join(z.pluginDir, name, z.getPlatformBinaryName())
|
||||
|
||||
// 检查插件文件是否存在
|
||||
if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
|
||||
// 加载插件
|
||||
p, err := plugin.Open(pluginPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open plugin %s: %v", name, err)
|
||||
}
|
||||
|
||||
// 查找插件符号
|
||||
sym, err := p.Lookup("Plugin")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin symbol not found in %s: %v", name, err)
|
||||
}
|
||||
|
||||
// 类型断言
|
||||
pluginInstance, ok := sym.(types.Plugin)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid plugin type in %s", name)
|
||||
}
|
||||
|
||||
utils.Info("成功加载插件: %s", name)
|
||||
return pluginInstance, nil
|
||||
}
|
||||
|
||||
// LoadAllPlugins 加载所有已安装的插件
|
||||
func (z *ZipPluginLoader) LoadAllPlugins() ([]types.Plugin, error) {
|
||||
var plugins []types.Plugin
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(z.pluginDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create plugin directory: %v", err)
|
||||
}
|
||||
|
||||
// 读取插件目录
|
||||
dirs, err := ioutil.ReadDir(z.pluginDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read plugin directory: %v", err)
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
plugin, err := z.LoadPlugin(dir.Name())
|
||||
if err != nil {
|
||||
utils.Error("加载插件 %s 失败: %v", dir.Name(), err)
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
|
||||
utils.Info("总共加载了 %d 个插件", len(plugins))
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// UninstallPlugin 卸载插件
|
||||
func (z *ZipPluginLoader) UninstallPlugin(name string) error {
|
||||
pluginDir := filepath.Join(z.pluginDir, name)
|
||||
|
||||
// 检查插件是否存在
|
||||
if _, err := os.Stat(pluginDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
|
||||
// 删除插件目录
|
||||
if err := os.RemoveAll(pluginDir); err != nil {
|
||||
return fmt.Errorf("failed to remove plugin directory: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("成功卸载插件: %s", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractMetadata 从ZIP文件中提取元数据
|
||||
func (z *ZipPluginLoader) extractMetadata(zipReader *zip.ReadCloser) (*PluginMetadata, error) {
|
||||
// 查找plugin.json文件
|
||||
for _, file := range zipReader.File {
|
||||
if file.Name == "plugin.json" {
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var metadata PluginMetadata
|
||||
if err := json.Unmarshal(data, &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metadata, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("plugin.json not found in zip file")
|
||||
}
|
||||
|
||||
// validatePlugin 验证插件文件
|
||||
func (z *ZipPluginLoader) validatePlugin(zipReader *zip.ReadCloser, metadata *PluginMetadata) error {
|
||||
// 检查必要的文件
|
||||
requiredFiles := []string{
|
||||
metadata.EntryPoint,
|
||||
"plugin.json",
|
||||
}
|
||||
|
||||
for _, requiredFile := range requiredFiles {
|
||||
found := false
|
||||
for _, file := range zipReader.File {
|
||||
if file.Name == requiredFile {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("required file not found: %s", requiredFile)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractPlugin 提取插件文件
|
||||
func (z *ZipPluginLoader) extractPlugin(zipReader *zip.ReadCloser, metadata *PluginMetadata) error {
|
||||
pluginDir := filepath.Join(z.pluginDir, metadata.Name)
|
||||
|
||||
// 创建插件目录
|
||||
if err := os.MkdirAll(pluginDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create plugin directory: %v", err)
|
||||
}
|
||||
|
||||
// 提取所有文件
|
||||
for _, file := range zipReader.File {
|
||||
// 跳过目录
|
||||
if file.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 打开源文件
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
targetPath := filepath.Join(pluginDir, file.Name)
|
||||
targetDir := filepath.Dir(targetPath)
|
||||
|
||||
// 确保目录存在
|
||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
dst, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// 复制文件内容
|
||||
_, err = io.Copy(dst, rc)
|
||||
rc.Close()
|
||||
dst.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置文件权限
|
||||
if strings.HasSuffix(file.Name, z.getPlatformBinaryName()) {
|
||||
os.Chmod(targetPath, 0755)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPlatformBinaryName 获取当前平台的二进制文件名
|
||||
func (z *ZipPluginLoader) getPlatformBinaryName() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "plugin.so"
|
||||
case "windows":
|
||||
return "plugin.dll"
|
||||
case "darwin":
|
||||
return "plugin.dylib"
|
||||
default:
|
||||
return "plugin"
|
||||
}
|
||||
}
|
||||
|
||||
// ListInstalledPlugins 列出已安装的插件
|
||||
func (z *ZipPluginLoader) ListInstalledPlugins() ([]PluginMetadata, error) {
|
||||
var plugins []PluginMetadata
|
||||
|
||||
// 确保插件目录存在
|
||||
if err := os.MkdirAll(z.pluginDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(z.pluginDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
metadataPath := filepath.Join(z.pluginDir, dir.Name(), "plugin.json")
|
||||
if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(metadataPath)
|
||||
if err != nil {
|
||||
utils.Error("读取插件元数据失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var metadata PluginMetadata
|
||||
if err := json.Unmarshal(data, &metadata); err != nil {
|
||||
utils.Error("解析插件元数据失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
plugins = append(plugins, metadata)
|
||||
}
|
||||
|
||||
return plugins, nil
|
||||
}
|
||||
314
plugin/manager/simple_manager.go
Normal file
314
plugin/manager/simple_manager.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/ctwj/urldb/plugin/loader"
|
||||
"github.com/ctwj/urldb/plugin/monitor"
|
||||
"github.com/ctwj/urldb/plugin/types"
|
||||
"github.com/ctwj/urldb/task"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SimpleManager 简化的插件管理器,使用.so文件直接加载
|
||||
type SimpleManager struct {
|
||||
plugins map[string]types.Plugin
|
||||
instances map[string]*types.PluginInstance
|
||||
mutex sync.RWMutex
|
||||
taskManager *task.TaskManager
|
||||
repoManager *repo.RepositoryManager
|
||||
db *gorm.DB
|
||||
pluginMonitor *monitor.PluginMonitor
|
||||
pluginLoader *loader.SimplePluginLoader
|
||||
}
|
||||
|
||||
// NewSimpleManager 创建简化版插件管理器
|
||||
func NewSimpleManager(taskManager *task.TaskManager, repoManager *repo.RepositoryManager, database *gorm.DB, pluginMonitor *monitor.PluginMonitor) *SimpleManager {
|
||||
manager := &SimpleManager{
|
||||
plugins: make(map[string]types.Plugin),
|
||||
instances: make(map[string]*types.PluginInstance),
|
||||
taskManager: taskManager,
|
||||
repoManager: repoManager,
|
||||
db: database,
|
||||
pluginMonitor: pluginMonitor,
|
||||
pluginLoader: loader.NewSimplePluginLoader("./plugins"),
|
||||
}
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// RegisterPlugin 注册插件
|
||||
func (sm *SimpleManager) RegisterPlugin(plugin types.Plugin) error {
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
|
||||
name := plugin.Name()
|
||||
if _, exists := sm.plugins[name]; exists {
|
||||
utils.Info("插件 %s 已存在,更新插件定义", name)
|
||||
}
|
||||
|
||||
sm.plugins[name] = plugin
|
||||
utils.Info("成功注册插件: %s (版本: %s)", name, plugin.Version())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPluginFromFile 从文件加载插件
|
||||
func (sm *SimpleManager) LoadPluginFromFile(filepath string) error {
|
||||
plugin, err := sm.pluginLoader.LoadPlugin(filepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载插件失败: %v", err)
|
||||
}
|
||||
|
||||
name := plugin.Name()
|
||||
if name == "" {
|
||||
return fmt.Errorf("插件名称不能为空")
|
||||
}
|
||||
|
||||
sm.mutex.Lock()
|
||||
sm.plugins[name] = plugin
|
||||
sm.mutex.Unlock()
|
||||
|
||||
utils.Info("成功从文件加载并注册插件: %s (版本: %s)", name, plugin.Version())
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializePlugin 初始化插件
|
||||
func (sm *SimpleManager) InitializePlugin(name string) error {
|
||||
sm.mutex.RLock()
|
||||
plugin, exists := sm.plugins[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("插件不存在: %s", name)
|
||||
}
|
||||
|
||||
// 创建插件实例
|
||||
instance := &types.PluginInstance{
|
||||
Plugin: plugin,
|
||||
Status: types.StatusInitialized,
|
||||
}
|
||||
|
||||
sm.mutex.Lock()
|
||||
sm.instances[name] = instance
|
||||
sm.mutex.Unlock()
|
||||
|
||||
utils.Info("插件 %s 初始化完成", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartPlugin 启动插件
|
||||
func (sm *SimpleManager) StartPlugin(name string) error {
|
||||
sm.mutex.RLock()
|
||||
instance, exists := sm.instances[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("插件未初始化: %s", name)
|
||||
}
|
||||
|
||||
if instance.Status == types.StatusRunning {
|
||||
return fmt.Errorf("插件已在运行状态: %s", name)
|
||||
}
|
||||
|
||||
// 启动插件
|
||||
err := instance.Plugin.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("启动插件失败: %v", err)
|
||||
}
|
||||
|
||||
instance.Status = types.StatusRunning
|
||||
instance.StartTime = time.Now()
|
||||
utils.Info("插件 %s 启动成功", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopPlugin 停止插件
|
||||
func (sm *SimpleManager) StopPlugin(name string) error {
|
||||
sm.mutex.RLock()
|
||||
instance, exists := sm.instances[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("插件不存在: %s", name)
|
||||
}
|
||||
|
||||
if instance.Status != types.StatusRunning {
|
||||
return fmt.Errorf("插件未在运行状态: %s", name)
|
||||
}
|
||||
|
||||
// 停止插件
|
||||
err := instance.Plugin.Stop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("停止插件失败: %v", err)
|
||||
}
|
||||
|
||||
instance.Status = types.StatusStopped
|
||||
stopTime := time.Now()
|
||||
instance.StopTime = &stopTime
|
||||
utils.Info("插件 %s 停止成功", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UninstallPlugin 卸载插件
|
||||
func (sm *SimpleManager) UninstallPlugin(name string, force bool) error {
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
|
||||
// 先停止插件
|
||||
if instance, exists := sm.instances[name]; exists && instance.Status == types.StatusRunning {
|
||||
if err := instance.Plugin.Stop(); err != nil {
|
||||
if !force {
|
||||
return fmt.Errorf("停止插件失败: %v", err)
|
||||
}
|
||||
utils.Warn("强制停止插件: %s", name)
|
||||
}
|
||||
instance.Status = types.StatusStopped
|
||||
stopTime := time.Now()
|
||||
instance.StopTime = &stopTime
|
||||
}
|
||||
|
||||
// 从管理器中移除插件
|
||||
delete(sm.plugins, name)
|
||||
delete(sm.instances, name)
|
||||
|
||||
// 尝试从文件系统卸载
|
||||
if err := sm.pluginLoader.UninstallPlugin(name); err != nil {
|
||||
if !force {
|
||||
return fmt.Errorf("卸载插件文件失败: %v", err)
|
||||
}
|
||||
utils.Warn("插件文件可能已不存在: %s", name)
|
||||
}
|
||||
|
||||
utils.Info("插件 %s 卸载完成", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPlugin 获取插件
|
||||
func (sm *SimpleManager) GetPlugin(name string) (types.Plugin, error) {
|
||||
sm.mutex.RLock()
|
||||
plugin, exists := sm.plugins[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("插件不存在: %s", name)
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
// GetPluginInstance 获取插件实例
|
||||
func (sm *SimpleManager) GetPluginInstance(name string) (*types.PluginInstance, error) {
|
||||
sm.mutex.RLock()
|
||||
instance, exists := sm.instances[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("插件实例不存在: %s", name)
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// GetPluginInfo 获取插件信息
|
||||
func (sm *SimpleManager) GetPluginInfo(name string) (*types.PluginInfo, error) {
|
||||
sm.mutex.RLock()
|
||||
plugin, exists := sm.plugins[name]
|
||||
instance, instanceExists := sm.instances[name]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("插件不存在: %s", name)
|
||||
}
|
||||
|
||||
info := &types.PluginInfo{
|
||||
Name: plugin.Name(),
|
||||
Version: plugin.Version(),
|
||||
Description: plugin.Description(),
|
||||
Author: plugin.Author(),
|
||||
}
|
||||
|
||||
if instanceExists {
|
||||
info.Status = instance.Status
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListPlugins 列出所有插件
|
||||
func (sm *SimpleManager) ListPlugins() []types.PluginInfo {
|
||||
sm.mutex.RLock()
|
||||
defer sm.mutex.RUnlock()
|
||||
|
||||
var plugins []types.PluginInfo
|
||||
for name, plugin := range sm.plugins {
|
||||
instance, exists := sm.instances[name]
|
||||
status := types.StatusRegistered
|
||||
if exists {
|
||||
status = instance.Status
|
||||
}
|
||||
|
||||
info := types.PluginInfo{
|
||||
Name: plugin.Name(),
|
||||
Version: plugin.Version(),
|
||||
Description: plugin.Description(),
|
||||
Author: plugin.Author(),
|
||||
Status: status,
|
||||
}
|
||||
|
||||
// 尝试获取更多统计信息(如果插件支持监控)
|
||||
if sm.pluginMonitor != nil {
|
||||
// 简化处理,不直接调用不存在的方法
|
||||
// 这里可以留空或添加其他监控逻辑
|
||||
}
|
||||
|
||||
plugins = append(plugins, info)
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
// GetEnabledPlugins 获取所有启用的插件
|
||||
func (sm *SimpleManager) GetEnabledPlugins() []types.Plugin {
|
||||
sm.mutex.RLock()
|
||||
defer sm.mutex.RUnlock()
|
||||
|
||||
var plugins []types.Plugin
|
||||
for _, plugin := range sm.plugins {
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
// InstallPluginFromFile 从文件安装插件
|
||||
func (sm *SimpleManager) InstallPluginFromFile(filepath string) error {
|
||||
return sm.pluginLoader.InstallPluginFromBytes(filepath, nil) // 需要先读取文件内容
|
||||
}
|
||||
|
||||
// LoadAllPluginsFromFilesystem 从文件系统加载所有.so文件
|
||||
func (sm *SimpleManager) LoadAllPluginsFromFilesystem() error {
|
||||
plugins, err := sm.pluginLoader.LoadAllPlugins()
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载插件失败: %v", err)
|
||||
}
|
||||
|
||||
for _, plugin := range plugins {
|
||||
name := plugin.Name()
|
||||
if name == "" {
|
||||
utils.Error("发现插件名称为空,跳过: %v", plugin)
|
||||
continue
|
||||
}
|
||||
|
||||
sm.mutex.Lock()
|
||||
sm.plugins[name] = plugin
|
||||
sm.mutex.Unlock()
|
||||
|
||||
utils.Info("从文件系统加载插件: %s (版本: %s)", name, plugin.Version())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// GlobalManager is the global plugin manager instance
|
||||
var GlobalManager *manager.Manager
|
||||
var GlobalManager *manager.SimpleManager
|
||||
|
||||
// GlobalPluginMonitor is the global plugin monitor instance
|
||||
var GlobalPluginMonitor *monitor.PluginMonitor
|
||||
@@ -26,7 +26,12 @@ func InitPluginSystem(taskManager *task.TaskManager, repoManager *repo.Repositor
|
||||
// Create plugin manager with database and repo manager
|
||||
// 创建插件监控器
|
||||
GlobalPluginMonitor = monitor.NewPluginMonitor()
|
||||
GlobalManager = manager.NewManager(taskManager, repoManager, db.DB, GlobalPluginMonitor)
|
||||
GlobalManager = manager.NewSimpleManager(taskManager, repoManager, db.DB, GlobalPluginMonitor)
|
||||
|
||||
// Load all plugins from filesystem
|
||||
if err := GlobalManager.LoadAllPluginsFromFilesystem(); err != nil {
|
||||
utils.Error("加载文件系统插件失败: %v", err)
|
||||
}
|
||||
|
||||
// Log initialization
|
||||
utils.Info("Plugin system initialized")
|
||||
@@ -47,6 +52,6 @@ func RegisterPlugin(plugin types.Plugin) error {
|
||||
}
|
||||
|
||||
// GetManager returns the global plugin manager
|
||||
func GetManager() *manager.Manager {
|
||||
func GetManager() *manager.SimpleManager {
|
||||
return GlobalManager
|
||||
}
|
||||
25
plugin/types/http_handler.go
Normal file
25
plugin/types/http_handler.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package types
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// HTTPHandler is an optional interface for plugins that want to register HTTP routes
|
||||
type HTTPHandler interface {
|
||||
// RegisterRoutes registers HTTP routes for the plugin
|
||||
RegisterRoutes(group *gin.RouterGroup)
|
||||
}
|
||||
|
||||
// TaskHandler is an optional interface for plugins that want to handle background tasks
|
||||
type TaskHandler interface {
|
||||
// RegisterTaskProcessor registers a task processor for the plugin
|
||||
RegisterTaskProcessor() TaskProcessor
|
||||
}
|
||||
|
||||
// TaskProcessor handles plugin-specific tasks
|
||||
type TaskProcessor interface {
|
||||
// Process processes a task
|
||||
Process(taskData map[string]interface{}) error
|
||||
// Validate validates task data
|
||||
Validate(taskData map[string]interface{}) error
|
||||
// GetName returns the name of the task processor
|
||||
GetName() string
|
||||
}
|
||||
33
scripts/build-demo-plugin.sh
Normal file
33
scripts/build-demo-plugin.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 构建演示插件的脚本
|
||||
|
||||
echo "构建演示插件..."
|
||||
|
||||
# 创建插件输出目录
|
||||
mkdir -p ./plugins
|
||||
|
||||
# 检查是否在Linux环境下
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
echo "在Linux环境下构建插件..."
|
||||
|
||||
# 构建演示插件
|
||||
echo "编译演示插件..."
|
||||
go build -buildmode=plugin -o ./plugins/demo-plugin.so ./plugin/demo/demo_plugin.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "演示插件构建成功!"
|
||||
ls -la ./plugins/demo-plugin.so
|
||||
else
|
||||
echo "演示插件构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "当前环境是Windows,不支持plugin构建模式"
|
||||
echo "请在Linux环境下运行此脚本,或者使用Docker容器进行构建"
|
||||
|
||||
# 提供Docker构建选项的说明
|
||||
echo ""
|
||||
echo "使用Docker构建插件的命令:"
|
||||
echo "docker run --rm -v \${PWD}:/usr/src/myapp -w /usr/src/myapp golang:1.19 go build -buildmode=plugin -o ./plugins/demo-plugin.so ./plugin/demo/demo_plugin.go"
|
||||
fi
|
||||
35
scripts/build-plugin.sh
Normal file
35
scripts/build-plugin.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 插件构建脚本
|
||||
|
||||
PLUGIN_DIR="./plugin/demo"
|
||||
OUTPUT_DIR="./plugins"
|
||||
|
||||
# 创建输出目录
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# 检查是否在Linux环境下
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
echo "在Linux环境下构建插件..."
|
||||
|
||||
# 构建演示插件
|
||||
echo "构建演示插件..."
|
||||
go build -buildmode=plugin -o "$OUTPUT_DIR/demo-plugin.so" "$PLUGIN_DIR/demo_plugin.go"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "插件构建成功!"
|
||||
ls -la "$OUTPUT_DIR/demo-plugin.so"
|
||||
else
|
||||
echo "插件构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "当前环境不支持plugin构建模式"
|
||||
echo "请在Linux环境下运行此脚本"
|
||||
|
||||
# 在Windows环境下,我们创建一个简单的说明文件
|
||||
echo "在Windows环境下,请使用交叉编译构建插件:" > "$OUTPUT_DIR/README.md"
|
||||
echo "GOOS=linux GOARCH=amd64 go build -buildmode=plugin -o $OUTPUT_DIR/demo-plugin.so $PLUGIN_DIR/demo_plugin.go" >> "$OUTPUT_DIR/README.md"
|
||||
echo "" >> "$OUTPUT_DIR/README.md"
|
||||
echo "或者使用Docker容器进行构建。" >> "$OUTPUT_DIR/README.md"
|
||||
fi
|
||||
Reference in New Issue
Block a user