update: plugin

This commit is contained in:
Kerwin
2025-11-05 18:57:33 +08:00
parent 0806ef7a69
commit 7fd33cdcd1
10 changed files with 1254 additions and 59 deletions

View File

@@ -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
View 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()
}

View 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
}

View 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
View 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
}

View 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
}

View File

@@ -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
}

View 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
}

View 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
View 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