兼容执行插件

pull/236/head
zhangchenhao 2025-06-09 20:31:15 +08:00
parent 9a9a91bf37
commit 08172287ef
3 changed files with 283 additions and 0 deletions

View File

@ -87,6 +87,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error {
case "volcengine-dcdn": case "volcengine-dcdn":
logger.Debug("部署到火山DCDN...") logger.Debug("部署到火山DCDN...")
return DeployVolcEngineDCdn(cfg) return DeployVolcEngineDCdn(cfg)
case "plugin":
logger.Debug("使用插件部署...")
return DeployPlugin(cfg)
default: default:
return fmt.Errorf("不支持的部署: %s", providerName) return fmt.Errorf("不支持的部署: %s", providerName)
} }

View File

@ -0,0 +1,90 @@
package plugin
import (
"ALLinSSL/backend/internal/access"
"encoding/json"
"fmt"
"strconv"
)
type CertDeployPlugin struct {
Config map[string]any
Key string
Cert string
}
func Deploy(cfg map[string]any) error {
cert, ok := cfg["certificate"].(map[string]any)
if !ok {
return fmt.Errorf("证书不存在")
}
action, ok := cfg["action"].(string)
if !ok {
return fmt.Errorf("操作类型错误action")
}
var providerID string
switch v := cfg["provider_id"].(type) {
case float64:
providerID = strconv.Itoa(int(v))
case string:
providerID = v
default:
return fmt.Errorf("参数错误provider_id")
}
providerData, err := access.GetAccess(providerID)
if err != nil {
return err
}
providerConfigStr, ok := providerData["config"].(string)
if !ok {
return fmt.Errorf("api配置错误")
}
var providerConfig map[string]any
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
if err != nil {
return fmt.Errorf("api配置解析错误%v", err)
}
pluginName, ok := providerConfig["name"].(string)
if !ok {
return fmt.Errorf("插件名称错误")
}
pluginConfig, ok := providerConfig["config"].(map[string]any)
if !ok {
return fmt.Errorf("插件配置错误")
}
pluginParams, ok := pluginConfig["params"].(string)
if !ok {
return fmt.Errorf("插件参数错误:")
}
var paramsMap map[string]any
err = json.Unmarshal([]byte(pluginParams), &paramsMap)
if err != nil {
return fmt.Errorf("插件参数解析错误:%v", err)
}
// 合并插件配置和参数
for k, v := range paramsMap {
pluginConfig[k] = v
}
// 设置证书
keyPem, ok := cert["key"].(string)
if !ok {
return fmt.Errorf("证书错误key")
}
certPem, ok := cert["cert"].(string)
if !ok {
return fmt.Errorf("证书错误cert")
}
params := map[string]any{
"config": pluginConfig,
"key": keyPem,
"cert": certPem,
}
rep, err := CallPlugin(pluginName, action, params)
if err != nil {
return fmt.Errorf("调用插件失败:%v", err)
}
fmt.Println(rep)
return err
}

View File

@ -0,0 +1,190 @@
package plugin
import (
"ALLinSSL/backend/public"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"os/exec"
"path/filepath"
)
var (
ErrPluginNotFound = errors.New("插件未找到")
ErrActionNotFound = errors.New("插件不支持该 action")
pluginRegistry = map[string]PluginMetadata{}
)
type ActionInfo struct {
Name string `json:"name"`
Description string `json:"description"`
}
type PluginMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
Version string `json:"version"`
Author string `json:"author"`
Actions []ActionInfo `json:"actions"`
Path string // 插件路径
}
type Request struct {
Action string `json:"action"`
Params map[string]interface{} `json:"params,omitempty"`
}
type Response struct {
Status string `json:"status"`
Message string `json:"message"`
Result map[string]interface{} `json:"result"`
}
func scanPlugins(dir string) ([]PluginMetadata, error) {
pluginRegistry = map[string]PluginMetadata{} // 清空旧的
var plugins []PluginMetadata
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || filepath.Ext(path) != ".exe" {
return nil
}
meta, err := getMetadata(path)
if err != nil {
fmt.Println("❌ 插件无效:", path, "错误:", err)
return nil
}
meta.Path = path
plugins = append(plugins, meta)
pluginRegistry[meta.Name] = meta
return nil
})
return plugins, nil
}
func getMetadata(path string) (PluginMetadata, error) {
req := Request{Action: "get_metadata"}
data, _ := json.Marshal(req)
cmd := exec.Command(path)
cmd.Stdin = bytes.NewReader(data)
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return PluginMetadata{}, fmt.Errorf("运行失败: %w", err)
}
var resp Response
if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
return PluginMetadata{}, fmt.Errorf("输出无效: %w", err)
}
if resp.Status != "success" {
return PluginMetadata{}, fmt.Errorf("插件响应错误: %s", resp.Message)
}
var meta PluginMetadata
raw, _ := json.Marshal(resp.Result)
if err := json.Unmarshal(raw, &meta); err != nil {
return PluginMetadata{}, fmt.Errorf("元数据解析失败: %w", err)
}
if meta.Name == "" || len(meta.Actions) == 0 {
return PluginMetadata{}, fmt.Errorf("元数据缺失")
}
return meta, nil
}
func CallPlugin(name, action string, params map[string]interface{}) (*Response, error) {
// 第一次尝试
resp, err := tryCallPlugin(name, action, params)
if err == nil {
return resp, nil
}
// 如果是插件或 action 不存在,则刷新插件列表并再试一次
if errors.Is(err, ErrPluginNotFound) || errors.Is(err, ErrActionNotFound) {
fmt.Println("🔄 尝试刷新插件列表...")
_, scanErr := scanPlugins("plugins")
if scanErr != nil {
return nil, fmt.Errorf("插件刷新失败: %v", scanErr)
}
return tryCallPlugin(name, action, params)
}
// 其他错误直接返回
return nil, err
}
func tryCallPlugin(name, action string, params map[string]interface{}) (*Response, error) {
plugin, ok := pluginRegistry[name]
if !ok {
return nil, ErrPluginNotFound
}
// 检查 action 是否存在
found := false
for _, a := range plugin.Actions {
if a.Name == action {
found = true
break
}
}
if !found {
return nil, ErrActionNotFound
}
// 构造请求
req := Request{
Action: action,
Params: params,
}
// 启动插件进程
cmd := exec.Command(plugin.Path)
cmd.Stderr = io.Discard // ❌ 忽略所有插件错误/日志输出,避免污染
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if err := json.NewEncoder(stdin).Encode(req); err != nil {
return nil, err
}
stdin.Close()
respBytes, err := io.ReadAll(stdout)
if err != nil {
return nil, err
}
var resp Response
if err := json.Unmarshal(respBytes, &resp); err != nil {
return nil, fmt.Errorf("解析插件响应失败: %v\n内容: %s", err, respBytes)
}
cmd.Wait()
return &resp, nil
}
func GetPlugins() ([]PluginMetadata, error) {
pluginDir := public.GetSettingIgnoreError("plugin_dir")
return scanPlugins(pluginDir)
}
func GetActions(pluginName string) ([]ActionInfo, error) {
_, err := GetPlugins()
if err != nil {
return nil, fmt.Errorf("获取插件列表失败: %v", err)
}
return pluginRegistry[pluginName].Actions, err
}