diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index a6e6685..a646f46 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -87,6 +87,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { case "volcengine-dcdn": logger.Debug("部署到火山DCDN...") return DeployVolcEngineDCdn(cfg) + case "plugin": + logger.Debug("使用插件部署...") + return DeployPlugin(cfg) default: return fmt.Errorf("不支持的部署: %s", providerName) } diff --git a/backend/internal/cert/deploy/plugin/deploy.go b/backend/internal/cert/deploy/plugin/deploy.go new file mode 100644 index 0000000..767eedd --- /dev/null +++ b/backend/internal/cert/deploy/plugin/deploy.go @@ -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), ¶msMap) + 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 +} diff --git a/backend/internal/cert/deploy/plugin/plugin.go b/backend/internal/cert/deploy/plugin/plugin.go new file mode 100644 index 0000000..fd96fac --- /dev/null +++ b/backend/internal/cert/deploy/plugin/plugin.go @@ -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 +}