diff --git a/examples/plugin/client_generator.go b/examples/plugin/client_generator.go new file mode 100644 index 0000000..6c3153b --- /dev/null +++ b/examples/plugin/client_generator.go @@ -0,0 +1,853 @@ +package plugin + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + "time" +) + +// ClientGenerator 客户端代码生成器 +// 用于根据插件API信息生成类型安全的客户端代码 +type ClientGenerator struct { + pm *PluginManager // 插件管理器 + outputPath string // 输出路径 + moduleName string // 生成的代码模块名称 + language string // 生成的代码语言 +} + +// NewClientGenerator 创建一个新的客户端代码生成器 +func NewClientGenerator(pm *PluginManager, outputPath, moduleName, language string) *ClientGenerator { + if language == "" { + language = "go" // 默认生成Go语言代码 + } + + return &ClientGenerator{ + pm: pm, + outputPath: outputPath, + moduleName: moduleName, + language: language, + } +} + +// GenerateAllClients 为所有插件生成客户端代码 +func (g *ClientGenerator) GenerateAllClients() error { + // 确保输出目录存在 + if err := os.MkdirAll(g.outputPath, 0755); err != nil { + return fmt.Errorf("创建输出目录失败: %v", err) + } + + // 根据语言选择生成器 + switch g.language { + case "go": + return g.generateGoClients() + default: + return fmt.Errorf("不支持的语言: %s,目前仅支持 Go 语言", g.language) + } +} + +// generateGoClients 生成Go语言客户端代码 +func (g *ClientGenerator) generateGoClients() error { + // 创建client目录 + clientDir := filepath.Join(g.outputPath, "client") + if err := os.MkdirAll(clientDir, 0755); err != nil { + return fmt.Errorf("创建client目录失败: %v", err) + } + + // 创建pkg目录 + pkgDir := filepath.Join(clientDir, "pkg") + if err := os.MkdirAll(pkgDir, 0755); err != nil { + return fmt.Errorf("创建pkg目录失败: %v", err) + } + + // 创建models目录 + modelsDir := filepath.Join(pkgDir, "models") + if err := os.MkdirAll(modelsDir, 0755); err != nil { + return fmt.Errorf("创建models目录失败: %v", err) + } + + // 为每种类型创建单独的目录 + plugins := g.pm.GetAllPlugins() + pluginsByType := make(map[PluginType][]Plugin) + + for _, p := range plugins { + pluginType := p.Type() + pluginsByType[pluginType] = append(pluginsByType[pluginType], p) + } + + // 生成模型 + if err := g.generateModelCode(); err != nil { + return fmt.Errorf("生成模型代码失败: %v", err) + } + + // 生成基本客户端代码 + if err := g.generateBaseClientCode(); err != nil { + return fmt.Errorf("生成基本客户端代码失败: %v", err) + } + + // 为每种插件类型生成客户端代码 + for pluginType, plugins := range pluginsByType { + typeDirName := strings.ToLower(string(pluginType)) + typeDir := filepath.Join(pkgDir, typeDirName) + + if err := os.MkdirAll(typeDir, 0755); err != nil { + return fmt.Errorf("创建 %s 目录失败: %v", typeDirName, err) + } + + // 为每个插件生成客户端代码 + for _, p := range plugins { + if err := g.generatePluginClientCode(p, typeDir); err != nil { + return fmt.Errorf("生成 %s 插件客户端代码失败: %v", p.Name(), err) + } + } + + // 生成类型包装代码 + if err := g.generateTypeWrapperCode(pluginType, plugins, typeDir); err != nil { + return fmt.Errorf("生成 %s 类型包装代码失败: %v", pluginType, err) + } + } + + // 生成示例代码 + if err := g.generateExampleCode(pluginsByType); err != nil { + return fmt.Errorf("生成示例代码失败: %v", err) + } + + return nil +} + +// generateModelCode 生成模型代码 +func (g *ClientGenerator) generateModelCode() error { + // 生成插件信息模型 + pluginInfoTemplate := `// 代码由客户端生成器自动生成,请勿手动修改 +package models + +// PluginInfo 插件信息 +type PluginInfo struct { + Name string ` + "`json:\"name\"`" + ` + Version string ` + "`json:\"version\"`" + ` + Description string ` + "`json:\"description\"`" + ` + Author string ` + "`json:\"author\"`" + ` + Type string ` + "`json:\"type\"`" + ` + Enabled bool ` + "`json:\"enabled\"`" + ` + Config map[string]interface{} ` + "`json:\"config,omitempty\"`" + ` +} + +// PluginType 插件类型 +type PluginType string + +const ( + PluginTypeGeneral PluginType = "General" + PluginTypeStorage PluginType = "Storage" + PluginTypeUtils PluginType = "Utils" + PluginTypeNetwork PluginType = "Network" +) + +// OperationInfo 操作信息 +type OperationInfo struct { + Name string ` + "`json:\"name\"`" + ` + Description string ` + "`json:\"description\"`" + ` + Parameters map[string]interface{} ` + "`json:\"parameters\"`" + ` + Returns map[string]interface{} ` + "`json:\"returns\"`" + ` +} + +// GenericResponse 通用响应 +type GenericResponse struct { + Success bool ` + "`json:\"success\"`" + ` + Error string ` + "`json:\"error,omitempty\"`" + ` + Result interface{} ` + "`json:\"result,omitempty\"`" + ` +} +` + + // 保存模型代码 + modelFilePath := filepath.Join(g.outputPath, "client/pkg/models/models.go") + if err := os.WriteFile(modelFilePath, []byte(pluginInfoTemplate), 0644); err != nil { + return fmt.Errorf("写入模型代码失败: %v", err) + } + + return nil +} + +// generateBaseClientCode 生成基本客户端代码 +func (g *ClientGenerator) generateBaseClientCode() error { + baseClientTemplate := `// 代码由客户端生成器自动生成,请勿手动修改 +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "time" + + "{{.ModuleName}}/client/pkg/models" +) + +// Client 插件系统客户端 +type Client struct { + baseURL string + httpClient *http.Client +} + +// ClientOptions 客户端选项 +type ClientOptions struct { + Timeout time.Duration +} + +// NewClient 创建一个新的客户端 +func NewClient(baseURL string, options *ClientOptions) *Client { + client := &Client{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 30 * time.Second, // 默认超时时间 + }, + } + + if options != nil { + if options.Timeout > 0 { + client.httpClient.Timeout = options.Timeout + } + } + + return client +} + +// GetAllPlugins 获取所有插件 +func (c *Client) GetAllPlugins(ctx context.Context) ([]models.PluginInfo, error) { + var plugins []models.PluginInfo + err := c.get(ctx, "/plugins", nil, &plugins) + return plugins, err +} + +// GetPluginsByType 按类型获取插件 +func (c *Client) GetPluginsByType(ctx context.Context, pluginType models.PluginType) ([]models.PluginInfo, error) { + var plugins []models.PluginInfo + err := c.get(ctx, fmt.Sprintf("/plugins/type/%s", pluginType), nil, &plugins) + return plugins, err +} + +// GetPluginInfo 获取插件信息 +func (c *Client) GetPluginInfo(ctx context.Context, pluginName string) (*models.PluginInfo, error) { + var pluginInfo models.PluginInfo + err := c.get(ctx, fmt.Sprintf("/plugins/%s/info", pluginName), nil, &pluginInfo) + return &pluginInfo, err +} + +// GetPluginActions 获取插件所有操作 +func (c *Client) GetPluginActions(ctx context.Context, pluginName string) (map[string]models.OperationInfo, error) { + var operations map[string]models.OperationInfo + err := c.get(ctx, fmt.Sprintf("/plugins/%s/actions", pluginName), nil, &operations) + return operations, err +} + +// ExecutePluginAction 执行插件操作 +func (c *Client) ExecutePluginAction(ctx context.Context, pluginName, action string, params interface{}) (*models.GenericResponse, error) { + var response models.GenericResponse + err := c.post(ctx, fmt.Sprintf("/plugins/%s/execute/%s", pluginName, action), params, &response) + return &response, err +} + +// get 发送GET请求 +func (c *Client) get(ctx context.Context, apiPath string, query url.Values, result interface{}) error { + return c.request(ctx, http.MethodGet, apiPath, query, nil, result) +} + +// post 发送POST请求 +func (c *Client) post(ctx context.Context, apiPath string, body interface{}, result interface{}) error { + return c.request(ctx, http.MethodPost, apiPath, nil, body, result) +} + +// request 发送请求 +func (c *Client) request(ctx context.Context, method, apiPath string, query url.Values, body interface{}, result interface{}) error { + // 构建URL + u, err := url.Parse(c.baseURL) + if err != nil { + return fmt.Errorf("解析URL失败: %v", err) + } + + u.Path = path.Join(u.Path, apiPath) + + if query != nil { + u.RawQuery = query.Encode() + } + + // 准备请求体 + var bodyReader io.Reader + if body != nil { + bodyData, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("序列化请求体失败: %v", err) + } + bodyReader = bytes.NewReader(bodyData) + } + + // 创建请求 + req, err := http.NewRequestWithContext(ctx, method, u.String(), bodyReader) + if err != nil { + return fmt.Errorf("创建请求失败: %v", err) + } + + // 设置Content-Type + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + // 发送请求 + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应体 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("读取响应体失败: %v", err) + } + + // 检查响应状态码 + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + var errResp struct { + Error string ` + "`json:\"error\"`" + ` + } + + if err := json.Unmarshal(respBody, &errResp); err == nil && errResp.Error != "" { + return fmt.Errorf("API错误: %s", errResp.Error) + } + + return fmt.Errorf("HTTP错误: %s", resp.Status) + } + + // 解析响应体 + if result != nil && len(respBody) > 0 { + if err := json.Unmarshal(respBody, result); err != nil { + return fmt.Errorf("解析响应体失败: %v", err) + } + } + + return nil +} +` + + // 准备模板数据 + data := struct { + ModuleName string + }{ + ModuleName: g.moduleName, + } + + // 编译模板 + tmpl, err := template.New("baseClient").Parse(baseClientTemplate) + if err != nil { + return fmt.Errorf("解析基本客户端模板失败: %v", err) + } + + // 创建文件 + baseClientPath := filepath.Join(g.outputPath, "client/client.go") + file, err := os.Create(baseClientPath) + if err != nil { + return fmt.Errorf("创建基本客户端文件失败: %v", err) + } + defer file.Close() + + // 执行模板 + if err := tmpl.Execute(file, data); err != nil { + return fmt.Errorf("执行基本客户端模板失败: %v", err) + } + + return nil +} + +// generatePluginClientCode 为特定插件生成客户端代码 +func (g *ClientGenerator) generatePluginClientCode(p Plugin, outputDir string) error { + // 获取插件信息 + pluginName := p.Name() + operations := p.GetAllOperations() + + // 准备模板数据 + type OperationData struct { + Name string + MethodName string + Description string + HasParameters bool + Parameters []map[string]interface{} + ParamsVarName string + ReturnType string + ReturnVarName string + HasContext bool + ReturnExists bool + HasComplexReturn bool + } + + type PluginData struct { + PluginName string + ClassName string + Version string + ModuleName string + Description string + Author string + Type string + Operations []OperationData + ImportTime bool + ImportContext bool + GeneratedDate string + } + + // 准备操作数据 + operations_data := make([]OperationData, 0) + importTime := false + importContext := false + + for opName, opInfo := range operations { + // 跳过内部操作 + if strings.HasPrefix(opName, "_") { + continue + } + + // 生成方法名 + methodName := strings.Title(opName) + + // 确定方法是否接受context参数 + hasContext := false + if params, ok := opInfo["parameters"].(map[string]interface{}); ok { + for _, paramInfo := range params { + if info, ok := paramInfo.(map[string]interface{}); ok { + if hasCtx, ok := info["hasContext"].(bool); ok && hasCtx { + hasContext = true + importContext = true + break + } + } + } + } + + // 分析参数 + parameters := make([]map[string]interface{}, 0) + hasParameters := false + paramsVarName := "params" + + if params, ok := opInfo["parameters"].(map[string]interface{}); ok && len(params) > 0 { + for paramName, paramInfo := range params { + if info, ok := paramInfo.(map[string]interface{}); ok { + paramType := getMapString(info, "type", "string") + desc := getMapString(info, "description", "") + required := getMapBool(info, "required", false) + + // 检查是否为结构体参数 + if paramType == "struct" || paramType == "object" { + // 将参数名作为变量名 + paramsVarName = paramName + } + + parameters = append(parameters, map[string]interface{}{ + "name": paramName, + "type": convertToGoType(paramType), + "description": desc, + "required": required, + }) + + hasParameters = true + } + } + } + + // 分析返回值 + returnType := "interface{}" + returnVarName := "result" + hasComplexReturn := false + returnExists := false + + if returns, ok := opInfo["returns"].(map[string]interface{}); ok && len(returns) > 0 { + for retName, retInfo := range returns { + if retName == "error" { + continue + } + + returnExists = true + + if info, ok := retInfo.(map[string]interface{}); ok { + retType := getMapString(info, "type", "string") + + // 如果返回值类型为对象,认为是复杂返回 + if retType == "object" || retType == "struct" { + hasComplexReturn = true + } + + returnType = convertToGoType(retType) + returnVarName = retName + break + } + } + } + + operations_data = append(operations_data, OperationData{ + Name: opName, + MethodName: methodName, + Description: getMapString(opInfo, "description", ""), + HasParameters: hasParameters, + Parameters: parameters, + ParamsVarName: paramsVarName, + ReturnType: returnType, + ReturnVarName: returnVarName, + HasContext: hasContext, + ReturnExists: returnExists, + HasComplexReturn: hasComplexReturn, + }) + } + + // 创建插件数据 + data := PluginData{ + PluginName: pluginName, + ClassName: strings.Title(pluginName), + Version: p.Version(), + ModuleName: g.moduleName, + Description: p.Description(), + Author: p.Author(), + Type: string(p.Type()), + Operations: operations_data, + ImportTime: importTime, + ImportContext: importContext, + GeneratedDate: time.Now().Format("2006-01-02 15:04:05"), + } + + // 定义模板 + pluginClientTemplate := `// 代码由客户端生成器自动生成,请勿手动修改 +// 生成时间: {{.GeneratedDate}} +package {{.Type | lower}} + +import ( + "context" + "encoding/json" + "fmt"{{if .ImportTime}} + "time"{{end}} + + "{{.ModuleName}}/client/pkg/models" +) + +// {{.ClassName}} {{.Description}} +type {{.ClassName}} struct { + client *Client + pluginName string +} + +// New{{.ClassName}} 创建{{.ClassName}}客户端 +func New{{.ClassName}}(client *Client) *{{.ClassName}} { + return &{{.ClassName}}{ + client: client, + pluginName: "{{.PluginName}}", + } +} + +// GetInfo 获取插件信息 +func (c *{{.ClassName}}) GetInfo(ctx context.Context) (*models.PluginInfo, error) { + return c.client.GetPluginInfo(ctx, c.pluginName) +} + +// GetAllOperations 获取所有操作信息 +func (c *{{.ClassName}}) GetAllOperations(ctx context.Context) (map[string]models.OperationInfo, error) { + return c.client.GetPluginActions(ctx, c.pluginName) +} + +{{range .Operations}} +// {{.MethodName}} {{.Description}} +func (c *{{$.ClassName}}) {{.MethodName}}({{if .HasContext}}ctx context.Context, {{end}}{{if .HasParameters}}{{.ParamsVarName}} {{if .HasComplexReturn}}map[string]interface{}{{else}}interface{}{{end}}{{end}}) ({{if .ReturnExists}}{{.ReturnType}}, {{end}}error) { + resp, err := c.client.ExecutePluginAction({{if .HasContext}}ctx{{else}}context.Background(){{end}}, c.pluginName, "{{.Name}}", {{if .HasParameters}}{{.ParamsVarName}}{{else}}nil{{end}}) + if err != nil { + return {{if .ReturnExists}}{{if eq .ReturnType "string"}}""{{else if eq .ReturnType "int"}}0{{else if eq .ReturnType "bool"}}false{{else if eq .ReturnType "float64"}}0.0{{else}}nil{{end}}, {{end}}err + } + + if !resp.Success { + return {{if .ReturnExists}}{{if eq .ReturnType "string"}}""{{else if eq .ReturnType "int"}}0{{else if eq .ReturnType "bool"}}false{{else if eq .ReturnType "float64"}}0.0{{else}}nil{{end}}, {{end}}fmt.Errorf("执行操作失败: %s", resp.Error) + } + + {{if .ReturnExists}} + // 提取结果 + {{if eq .ReturnType "string"}} + if str, ok := resp.Result.(string); ok { + return str, nil + } + return "", fmt.Errorf("结果类型错误") + {{else if eq .ReturnType "int"}} + if num, ok := resp.Result.(float64); ok { + return int(num), nil + } + return 0, fmt.Errorf("结果类型错误") + {{else if eq .ReturnType "bool"}} + if b, ok := resp.Result.(bool); ok { + return b, nil + } + return false, fmt.Errorf("结果类型错误") + {{else if eq .ReturnType "float64"}} + if num, ok := resp.Result.(float64); ok { + return num, nil + } + return 0.0, fmt.Errorf("结果类型错误") + {{else}} + // 处理复杂返回类型 + var result {{.ReturnType}} + resultData, err := json.Marshal(resp.Result) + if err != nil { + return nil, fmt.Errorf("序列化结果失败: %v", err) + } + + if err := json.Unmarshal(resultData, &result); err != nil { + return nil, fmt.Errorf("反序列化结果失败: %v", err) + } + + return result, nil + {{end}} + {{else}} + return nil + {{end}} +} +{{end}} +` + + // 编译模板 + funcMap := template.FuncMap{ + "lower": strings.ToLower, + } + tmpl, err := template.New("pluginClient").Funcs(funcMap).Parse(pluginClientTemplate) + if err != nil { + return fmt.Errorf("解析插件客户端模板失败: %v", err) + } + + // 创建文件 + fileName := strings.ToLower(pluginName) + ".go" + filePath := filepath.Join(outputDir, fileName) + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("创建插件客户端文件失败: %v", err) + } + defer file.Close() + + // 执行模板 + if err := tmpl.Execute(file, data); err != nil { + return fmt.Errorf("执行插件客户端模板失败: %v", err) + } + + return nil +} + +// generateTypeWrapperCode 生成类型包装代码 +func (g *ClientGenerator) generateTypeWrapperCode(pluginType PluginType, plugins []Plugin, outputDir string) error { + // 准备模板数据 + type PluginData struct { + ClassName string + PluginName string + } + + type TypeData struct { + TypeName string + ModuleName string + Plugins []PluginData + GeneratedDate string + } + + // 构造插件数据 + pluginsData := make([]PluginData, 0, len(plugins)) + for _, p := range plugins { + pluginsData = append(pluginsData, PluginData{ + ClassName: strings.Title(p.Name()), + PluginName: p.Name(), + }) + } + + data := TypeData{ + TypeName: string(pluginType), + ModuleName: g.moduleName, + Plugins: pluginsData, + GeneratedDate: time.Now().Format("2006-01-02 15:04:05"), + } + + // 定义模板 + typeWrapperTemplate := `// 代码由客户端生成器自动生成,请勿手动修改 +// 生成时间: {{.GeneratedDate}} +package {{.TypeName | lower}} + +import ( + "{{.ModuleName}}/client" +) + +// Client {{.TypeName}}类型插件的客户端包装器 +type Client struct { + client *client.Client + {{range .Plugins}} + {{.ClassName}} *{{.ClassName}}{{end}} +} + +// NewClient 创建{{.TypeName}}类型插件的客户端包装器 +func NewClient(baseClient *client.Client) *Client { + c := &Client{ + client: baseClient, + } + + {{range .Plugins}} + c.{{.ClassName}} = New{{.ClassName}}(baseClient){{end}} + + return c +} +` + + // 编译模板 + funcMap := template.FuncMap{ + "lower": strings.ToLower, + } + tmpl, err := template.New("typeWrapper").Funcs(funcMap).Parse(typeWrapperTemplate) + if err != nil { + return fmt.Errorf("解析类型包装器模板失败: %v", err) + } + + // 创建文件 + fileName := "client.go" + filePath := filepath.Join(outputDir, fileName) + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("创建类型包装器文件失败: %v", err) + } + defer file.Close() + + // 执行模板 + if err := tmpl.Execute(file, data); err != nil { + return fmt.Errorf("执行类型包装器模板失败: %v", err) + } + + return nil +} + +// generateExampleCode 生成示例代码 +func (g *ClientGenerator) generateExampleCode(pluginsByType map[PluginType][]Plugin) error { + // 准备模板数据 + type TypeData struct { + TypeName string + HasPlugin bool + } + + type ExampleData struct { + ModuleName string + TypesData []TypeData + GeneratedDate string + } + + // 构造类型数据 + typesData := make([]TypeData, 0) + for pType, plugins := range pluginsByType { + typesData = append(typesData, TypeData{ + TypeName: string(pType), + HasPlugin: len(plugins) > 0, + }) + } + + data := ExampleData{ + ModuleName: g.moduleName, + TypesData: typesData, + GeneratedDate: time.Now().Format("2006-01-02 15:04:05"), + } + + // 定义模板 + exampleTemplate := `// 代码由客户端生成器自动生成,请勿手动修改 +// 生成时间: {{.GeneratedDate}} +package main + +import ( + "context" + "fmt" + "log" + "time" + + "{{.ModuleName}}/client" + {{range .TypesData}}{{if .HasPlugin}} + "{{$.ModuleName}}/client/pkg/{{.TypeName | lower}}"{{end}}{{end}} + "{{.ModuleName}}/client/pkg/models" +) + +func main() { + // 创建客户端 + baseClient := client.NewClient("http://localhost:8080/api", &client.ClientOptions{ + Timeout: 30 * time.Second, + }) + + // 创建上下文 + ctx := context.Background() + + // 获取所有插件 + plugins, err := baseClient.GetAllPlugins(ctx) + if err != nil { + log.Fatalf("获取插件失败: %v", err) + } + + fmt.Printf("发现 %d 个插件\n", len(plugins)) + for _, plugin := range plugins { + fmt.Printf("- %s (版本: %s, 类型: %s)\n", plugin.Name, plugin.Version, plugin.Type) + } + + // 按类型使用插件 + {{range .TypesData}}{{if .HasPlugin}} + // {{.TypeName}}类型插件 + {{.TypeName | lower}}Client := {{.TypeName | lower}}.NewClient(baseClient) + // 使用{{.TypeName}}客户端...{{end}} + {{end}} + + // 其他示例... + fmt.Println("示例结束") +} +` + + // 编译模板 + funcMap := template.FuncMap{ + "lower": strings.ToLower, + } + tmpl, err := template.New("example").Funcs(funcMap).Parse(exampleTemplate) + if err != nil { + return fmt.Errorf("解析示例模板失败: %v", err) + } + + // 创建文件 + filePath := filepath.Join(g.outputPath, "client/example/main.go") + + // 确保目录存在 + exampleDir := filepath.Join(g.outputPath, "client/example") + if err := os.MkdirAll(exampleDir, 0755); err != nil { + return fmt.Errorf("创建示例目录失败: %v", err) + } + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("创建示例文件失败: %v", err) + } + defer file.Close() + + // 执行模板 + if err := tmpl.Execute(file, data); err != nil { + return fmt.Errorf("执行示例模板失败: %v", err) + } + + return nil +} + +// 辅助函数 + +// convertToGoType 将插件系统类型转换为Go类型 +func convertToGoType(pluginType string) string { + switch pluginType { + case "string": + return "string" + case "integer": + return "int" + case "float": + return "float64" + case "boolean": + return "bool" + case "array": + return "[]interface{}" + case "object", "struct": + return "map[string]interface{}" + default: + return "interface{}" + } +} diff --git a/examples/plugin/doc_generator.go b/examples/plugin/doc_generator.go new file mode 100644 index 0000000..f98c677 --- /dev/null +++ b/examples/plugin/doc_generator.go @@ -0,0 +1,346 @@ +package plugin + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" +) + +// DocGenerator 文档生成器 +// 用于生成插件系统的API文档 +type DocGenerator struct { + pm *PluginManager // 插件管理器 + outputPath string // 输出路径 +} + +// NewDocGenerator 创建一个新的文档生成器 +func NewDocGenerator(pm *PluginManager, outputPath string) *DocGenerator { + return &DocGenerator{ + pm: pm, + outputPath: outputPath, + } +} + +// GenerateAllDocs 为所有插件生成文档 +func (g *DocGenerator) GenerateAllDocs() error { + // 确保输出目录存在 + if err := os.MkdirAll(g.outputPath, 0755); err != nil { + return fmt.Errorf("创建输出目录失败: %v", err) + } + + // 生成索引文档 + if err := g.generateIndexDoc(); err != nil { + return err + } + + // 为每个插件生成文档 + plugins := g.pm.GetAllPlugins() + for _, p := range plugins { + if err := g.generatePluginDoc(p); err != nil { + return err + } + } + + // 生成按类型分组的文档 + if err := g.generatePluginsByTypeDoc(); err != nil { + return err + } + + return nil +} + +// generateIndexDoc 生成索引文档 +func (g *DocGenerator) generateIndexDoc() error { + indexPath := filepath.Join(g.outputPath, "index.md") + file, err := os.Create(indexPath) + if err != nil { + return fmt.Errorf("创建索引文档失败: %v", err) + } + defer file.Close() + + // 获取所有插件信息 + plugins := g.pm.GetPluginInfos() + + // 写入标题 + file.WriteString("# 插件系统API文档\n\n") + file.WriteString(fmt.Sprintf("生成时间: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))) + + // 插件计数 + file.WriteString(fmt.Sprintf("## 插件总览\n\n总共发现 %d 个插件\n\n", len(plugins))) + + // 写入插件列表 + file.WriteString("## 插件列表\n\n") + file.WriteString("| 插件名称 | 版本 | 类型 | 描述 | 作者 | 状态 |\n") + file.WriteString("|---------|------|------|------|------|------|\n") + + for _, info := range plugins { + status := "启用" + if !info.Enabled { + status = "禁用" + } + + // 使用相对链接 + pluginLink := fmt.Sprintf("[%s](%s.md)", info.Name, info.Name) + + file.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s |\n", + pluginLink, info.Version, info.Type, info.Description, info.Author, status)) + } + + // 按类型分组的链接 + file.WriteString("\n## 按类型查看插件\n\n") + file.WriteString("- [按类型分组查看所有插件](plugins_by_type.md)\n\n") + + return nil +} + +// generatePluginDoc 为单个插件生成文档 +func (g *DocGenerator) generatePluginDoc(p Plugin) error { + pluginName := p.Name() + docPath := filepath.Join(g.outputPath, pluginName+".md") + + file, err := os.Create(docPath) + if err != nil { + return fmt.Errorf("创建插件文档失败: %v", err) + } + defer file.Close() + + // 写入插件标题和基本信息 + file.WriteString(fmt.Sprintf("# %s\n\n", pluginName)) + file.WriteString(fmt.Sprintf("- **版本**: %s\n", p.Version())) + file.WriteString(fmt.Sprintf("- **描述**: %s\n", p.Description())) + file.WriteString(fmt.Sprintf("- **作者**: %s\n", p.Author())) + file.WriteString(fmt.Sprintf("- **类型**: %s\n", p.Type())) + file.WriteString(fmt.Sprintf("- **状态**: %s\n\n", boolToStatus(p.IsEnabled()))) + + // 获取插件的所有操作 + operations := p.GetAllOperations() + + file.WriteString(fmt.Sprintf("## 支持的操作\n\n此插件支持 %d 个操作:\n\n", len(operations))) + + // 操作列表 + for opName := range operations { + file.WriteString(fmt.Sprintf("- [%s](#%s)\n", opName, strings.ToLower(opName))) + } + + // 详细操作文档 + file.WriteString("\n## 操作详情\n\n") + + for opName, opInfo := range operations { + file.WriteString(fmt.Sprintf("### %s\n\n", opName)) + + // 操作描述 + if desc, ok := opInfo["description"].(string); ok { + file.WriteString(fmt.Sprintf("%s\n\n", desc)) + } + + // 参数表格 + file.WriteString("#### 参数\n\n") + + if params, ok := opInfo["parameters"].(map[string]interface{}); ok && len(params) > 0 { + file.WriteString("| 参数名 | 类型 | 必填 | 描述 |\n") + file.WriteString("|--------|------|------|------|\n") + + for paramName, paramInfo := range params { + if info, ok := paramInfo.(map[string]interface{}); ok { + paramType := getMapString(info, "type", "未知") + required := getMapBool(info, "required", false) + requiredStr := "否" + if required { + requiredStr = "是" + } + description := getMapString(info, "description", "") + + file.WriteString(fmt.Sprintf("| %s | %s | %s | %s |\n", + paramName, paramType, requiredStr, description)) + + // 如果是结构体,则显示其字段 + if paramType == "struct" || paramType == "object" { + if fields, ok := info["fields"].(map[string]interface{}); ok { + file.WriteString("\n**字段详情:**\n\n") + file.WriteString("| 字段名 | 类型 | 必填 | 描述 |\n") + file.WriteString("|--------|------|------|------|\n") + + for fieldName, fieldInfo := range fields { + if fi, ok := fieldInfo.(map[string]interface{}); ok { + fieldType := getMapString(fi, "type", "未知") + fieldRequired := getMapBool(fi, "required", false) + fieldRequiredStr := "否" + if fieldRequired { + fieldRequiredStr = "是" + } + fieldDesc := getMapString(fi, "description", "") + + file.WriteString(fmt.Sprintf("| %s | %s | %s | %s |\n", + fieldName, fieldType, fieldRequiredStr, fieldDesc)) + } + } + file.WriteString("\n") + } + } + } + } + } else { + file.WriteString("此操作不需要参数\n\n") + } + + // 返回值 + file.WriteString("#### 返回值\n\n") + + if returns, ok := opInfo["returns"].(map[string]interface{}); ok && len(returns) > 0 { + file.WriteString("| 名称 | 类型 | 描述 |\n") + file.WriteString("|------|------|------|\n") + + for retName, retInfo := range returns { + if info, ok := retInfo.(map[string]interface{}); ok { + retType := getMapString(info, "type", "未知") + description := getMapString(info, "description", "") + + file.WriteString(fmt.Sprintf("| %s | %s | %s |\n", + retName, retType, description)) + + // 如果返回值是对象,显示其属性 + if retType == "object" && info["properties"] != nil { + if props, ok := info["properties"].(map[string]interface{}); ok { + file.WriteString("\n**属性详情:**\n\n") + file.WriteString("| 属性名 | 类型 | 描述 |\n") + file.WriteString("|--------|------|------|\n") + + for propName, propInfo := range props { + if pi, ok := propInfo.(map[string]interface{}); ok { + propType := getMapString(pi, "type", "未知") + propDesc := getMapString(pi, "description", "") + + file.WriteString(fmt.Sprintf("| %s | %s | %s |\n", + propName, propType, propDesc)) + } + } + file.WriteString("\n") + } + } + } + } + } else { + file.WriteString("此操作没有返回值\n\n") + } + + // 示例(如果有) + if examples, ok := opInfo["examples"].([]interface{}); ok && len(examples) > 0 { + file.WriteString("#### 示例\n\n") + + for i, example := range examples { + if ex, ok := example.(map[string]interface{}); ok { + file.WriteString(fmt.Sprintf("**示例 %d:**\n\n", i+1)) + + if desc, ok := ex["description"].(string); ok { + file.WriteString(fmt.Sprintf("%s\n\n", desc)) + } + + if req, ok := ex["request"].(string); ok { + file.WriteString("请求:\n```json\n") + file.WriteString(req) + file.WriteString("\n```\n\n") + } + + if resp, ok := ex["response"].(string); ok { + file.WriteString("响应:\n```json\n") + file.WriteString(resp) + file.WriteString("\n```\n\n") + } + } + } + } + + file.WriteString("\n---\n\n") + } + + // 链接回索引 + file.WriteString("\n[返回索引](index.md)\n") + + return nil +} + +// generatePluginsByTypeDoc 生成按类型分组的插件文档 +func (g *DocGenerator) generatePluginsByTypeDoc() error { + filePath := filepath.Join(g.outputPath, "plugins_by_type.md") + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("创建类型分组文档失败: %v", err) + } + defer file.Close() + + // 写入标题 + file.WriteString("# 按类型分组的插件\n\n") + + // 获取所有插件类型 + allPlugins := g.pm.GetAllPlugins() + typeMap := make(map[PluginType][]Plugin) + + for _, p := range allPlugins { + pluginType := p.Type() + typeMap[pluginType] = append(typeMap[pluginType], p) + } + + // 目录 + file.WriteString("## 目录\n\n") + for pType := range typeMap { + file.WriteString(fmt.Sprintf("- [%s](#%s)\n", pType, strings.ToLower(string(pType)))) + } + file.WriteString("\n") + + // 按类型分组列出插件 + for pType, plugins := range typeMap { + file.WriteString(fmt.Sprintf("## %s\n\n", pType)) + file.WriteString(fmt.Sprintf("此类型共有 %d 个插件\n\n", len(plugins))) + + file.WriteString("| 插件名称 | 版本 | 描述 | 作者 | 状态 |\n") + file.WriteString("|---------|------|------|------|------|\n") + + for _, p := range plugins { + status := "启用" + if !p.IsEnabled() { + status = "禁用" + } + + // 使用相对链接 + pluginLink := fmt.Sprintf("[%s](%s.md)", p.Name(), p.Name()) + + file.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s |\n", + pluginLink, p.Version(), p.Description(), p.Author(), status)) + } + + file.WriteString("\n") + } + + // 链接回索引 + file.WriteString("\n[返回索引](index.md)\n") + + return nil +} + +// Utility functions + +// boolToStatus 将布尔值转换为状态字符串 +func boolToStatus(enabled bool) string { + if enabled { + return "启用" + } + return "禁用" +} + +// getMapString 安全地从map中获取字符串值 +func getMapString(m map[string]interface{}, key, defaultVal string) string { + if val, ok := m[key].(string); ok { + return val + } + return defaultVal +} + +// getMapBool 安全地从map中获取布尔值 +func getMapBool(m map[string]interface{}, key string, defaultVal bool) bool { + if val, ok := m[key].(bool); ok { + return val + } + return defaultVal +} diff --git a/examples/plugin/example/client_example.go b/examples/plugin/example/client_example.go new file mode 100644 index 0000000..fb259f7 --- /dev/null +++ b/examples/plugin/example/client_example.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/darkit/goproxy/examples/plugin" +) + +// 展示客户端代码生成功能 +func ClientGeneratorExample() { + // 创建上下文 + ctx := context.Background() + + // 获取插件目录 + pluginsDir := filepath.Join("..", "plugins", "dist") + if _, err := os.Stat(pluginsDir); os.IsNotExist(err) { + fmt.Printf("插件目录不存在: %s\n", pluginsDir) + return + } + + // 创建插件管理器 + pm := plugin.NewPluginManager(pluginsDir) + + // 加载插件 + fmt.Println("正在加载插件...") + if err := pm.LoadPlugins(); err != nil { + fmt.Printf("加载插件失败: %v\n", err) + return + } + + // 初始化所有插件 + if err := pm.InitPlugins(ctx); err != nil { + fmt.Printf("初始化插件失败: %v\n", err) + return + } + + // 启动所有插件 + if err := pm.StartPlugins(ctx); err != nil { + fmt.Printf("启动插件失败: %v\n", err) + return + } + + // 创建客户端代码输出目录 + clientDir := "./client_code" + if err := os.MkdirAll(clientDir, 0755); err != nil { + fmt.Printf("创建客户端代码目录失败: %v\n", err) + return + } + + fmt.Printf("将在 %s 目录生成客户端代码\n", clientDir) + + // 创建客户端代码生成器 + // 参数:插件管理器、输出路径、模块名称、语言(默认为go) + clientGen := plugin.NewClientGenerator(pm, clientDir, "github.com/darkit/plugin-client", "go") + + // 生成客户端代码 + fmt.Println("正在生成客户端代码...") + if err := clientGen.GenerateAllClients(); err != nil { + fmt.Printf("生成客户端代码失败: %v\n", err) + } else { + fmt.Printf("成功生成客户端代码!代码位于: %s\n", clientDir) + fmt.Println("生成的代码包括:") + fmt.Println("- 基本客户端代码 (client/client.go)") + fmt.Println("- 模型定义 (client/pkg/models/models.go)") + fmt.Println("- 按插件类型生成的客户端 (client/pkg/<类型>/*.go)") + fmt.Println("- 示例代码 (client/example/main.go)") + fmt.Println("\n使用生成的客户端代码:") + fmt.Println("1. 将生成的代码复制到你的项目中") + fmt.Println("2. 调整模块名称以匹配你的项目") + fmt.Println("3. 根据示例代码创建客户端实例并调用方法") + } + + // 停止所有插件 + if err := pm.StopPlugins(ctx); err != nil { + fmt.Printf("停止插件失败: %v\n", err) + return + } + + fmt.Println("\n示例结束") +} diff --git a/examples/plugin/example/doc_example.go b/examples/plugin/example/doc_example.go new file mode 100644 index 0000000..32cf910 --- /dev/null +++ b/examples/plugin/example/doc_example.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/darkit/goproxy/examples/plugin" +) + +// 展示文档生成功能 +func DocGeneratorExample() { + // 创建上下文 + ctx := context.Background() + + // 获取插件目录 + pluginsDir := filepath.Join("..", "plugins", "dist") + if _, err := os.Stat(pluginsDir); os.IsNotExist(err) { + fmt.Printf("插件目录不存在: %s\n", pluginsDir) + return + } + + // 创建插件管理器 + pm := plugin.NewPluginManager(pluginsDir) + + // 加载插件 + fmt.Println("正在加载插件...") + if err := pm.LoadPlugins(); err != nil { + fmt.Printf("加载插件失败: %v\n", err) + return + } + + // 初始化所有插件 + if err := pm.InitPlugins(ctx); err != nil { + fmt.Printf("初始化插件失败: %v\n", err) + return + } + + // 启动所有插件 + if err := pm.StartPlugins(ctx); err != nil { + fmt.Printf("启动插件失败: %v\n", err) + return + } + + // 创建文档输出目录 + docsDir := "./plugin_docs" + if err := os.MkdirAll(docsDir, 0755); err != nil { + fmt.Printf("创建文档目录失败: %v\n", err) + return + } + + fmt.Printf("将在 %s 目录生成API文档\n", docsDir) + + // 创建文档生成器 + docGen := plugin.NewDocGenerator(pm, docsDir) + + // 生成所有文档 + fmt.Println("正在生成API文档...") + if err := docGen.GenerateAllDocs(); err != nil { + fmt.Printf("生成文档失败: %v\n", err) + } else { + fmt.Printf("成功生成API文档!文档位于: %s\n", docsDir) + fmt.Println("文档包括:") + fmt.Println("- 索引页 (index.md)") + fmt.Println("- 按类型分组的插件列表 (plugins_by_type.md)") + fmt.Println("- 每个插件的详细文档 (<插件名>.md)") + } + + // 停止所有插件 + if err := pm.StopPlugins(ctx); err != nil { + fmt.Printf("停止插件失败: %v\n", err) + return + } + + fmt.Println("\n示例结束") +} diff --git a/examples/plugin/example/main.go b/examples/plugin/example/main.go index 4df8ddc..c0b64b7 100644 --- a/examples/plugin/example/main.go +++ b/examples/plugin/example/main.go @@ -15,7 +15,9 @@ func main() { fmt.Println("1. 基本插件加载和管理") fmt.Println("2. 动态参数传递示例") fmt.Println("3. 插件操作参数信息示例") - fmt.Print("请输入选项 (1-3): ") + fmt.Println("4. 插件API文档生成示例") + fmt.Println("5. OpenAPI/Swagger文档生成示例") + fmt.Print("请输入选项 (1-5): ") var choice int fmt.Scan(&choice) @@ -27,6 +29,10 @@ func main() { DynamicParamsExample() case 3: OperationInfoExample() + case 4: + DocGeneratorExample() + case 5: + OpenAPIGeneratorExample() default: fmt.Println("无效的选项") } diff --git a/examples/plugin/example/openapi_example.go b/examples/plugin/example/openapi_example.go new file mode 100644 index 0000000..113ee8a --- /dev/null +++ b/examples/plugin/example/openapi_example.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/darkit/goproxy/examples/plugin" +) + +// 展示OpenAPI文档生成功能 +func OpenAPIGeneratorExample() { + // 创建上下文 + ctx := context.Background() + + // 获取插件目录 + pluginsDir := filepath.Join("..", "plugins", "dist") + if _, err := os.Stat(pluginsDir); os.IsNotExist(err) { + fmt.Printf("插件目录不存在: %s\n", pluginsDir) + return + } + + // 创建插件管理器 + pm := plugin.NewPluginManager(pluginsDir) + + // 加载插件 + fmt.Println("正在加载插件...") + if err := pm.LoadPlugins(); err != nil { + fmt.Printf("加载插件失败: %v\n", err) + return + } + + // 初始化所有插件 + if err := pm.InitPlugins(ctx); err != nil { + fmt.Printf("初始化插件失败: %v\n", err) + return + } + + // 启动所有插件 + if err := pm.StartPlugins(ctx); err != nil { + fmt.Printf("启动插件失败: %v\n", err) + return + } + + // 创建文档输出目录 + apiDocsDir := "./openapi_docs" + if err := os.MkdirAll(apiDocsDir, 0755); err != nil { + fmt.Printf("创建API文档目录失败: %v\n", err) + return + } + + fmt.Printf("将在 %s 目录生成OpenAPI文档\n", apiDocsDir) + + // 创建OpenAPI生成器 + openAPIGen := plugin.NewOpenAPIGenerator(pm, apiDocsDir) + + // 设置文档信息 + openAPIGen.SetInfo( + "插件系统API", + "插件系统提供的所有API接口,包括插件管理和操作执行", + "1.0.0", + ) + + // 设置联系人信息 + openAPIGen.SetContact( + "插件系统开发团队", + "plugins@example.com", + "https://example.com/plugins", + ) + + // 生成OpenAPI文档 + fmt.Println("正在生成OpenAPI文档...") + if err := openAPIGen.GenerateOpenAPI(); err != nil { + fmt.Printf("生成OpenAPI文档失败: %v\n", err) + } else { + fmt.Printf("成功生成OpenAPI文档!文档位于: %s\n", apiDocsDir) + fmt.Println("文档包括:") + fmt.Println("- OpenAPI规范文件 (openapi.json)") + fmt.Println("- Swagger UI 界面 (index.html)") + fmt.Printf("打开 %s/index.html 可以在浏览器中查看API文档\n", apiDocsDir) + } + + // 停止所有插件 + if err := pm.StopPlugins(ctx); err != nil { + fmt.Printf("停止插件失败: %v\n", err) + return + } + + fmt.Println("\n示例结束") +} diff --git a/examples/plugin/openapi_generator.go b/examples/plugin/openapi_generator.go new file mode 100644 index 0000000..de28a9e --- /dev/null +++ b/examples/plugin/openapi_generator.go @@ -0,0 +1,767 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// OpenAPIGenerator 用于生成符合OpenAPI 3.0规范的API文档 +type OpenAPIGenerator struct { + pm *PluginManager // 插件管理器 + outputPath string // 输出路径 + info map[string]interface{} +} + +// NewOpenAPIGenerator 创建一个新的OpenAPI生成器 +func NewOpenAPIGenerator(pm *PluginManager, outputPath string) *OpenAPIGenerator { + return &OpenAPIGenerator{ + pm: pm, + outputPath: outputPath, + info: map[string]interface{}{ + "title": "插件系统API", + "description": "插件系统提供的API接口文档", + "version": "1.0.0", + "contact": map[string]interface{}{ + "name": "开发团队", + "email": "dev@example.com", + "url": "https://example.com", + }, + }, + } +} + +// SetInfo 设置API文档的基本信息 +func (g *OpenAPIGenerator) SetInfo(title, description, version string) { + g.info["title"] = title + g.info["description"] = description + g.info["version"] = version +} + +// SetContact 设置联系人信息 +func (g *OpenAPIGenerator) SetContact(name, email, url string) { + g.info["contact"] = map[string]interface{}{ + "name": name, + "email": email, + "url": url, + } +} + +// GenerateOpenAPI 生成OpenAPI文档 +func (g *OpenAPIGenerator) GenerateOpenAPI() error { + // 确保输出目录存在 + if err := os.MkdirAll(g.outputPath, 0755); err != nil { + return fmt.Errorf("创建输出目录失败: %v", err) + } + + // 创建OpenAPI规范文档 + openapiSpec := g.createOpenAPISpec() + + // 将文档保存为JSON文件 + jsonPath := filepath.Join(g.outputPath, "openapi.json") + jsonData, err := json.MarshalIndent(openapiSpec, "", " ") + if err != nil { + return fmt.Errorf("JSON序列化失败: %v", err) + } + + if err := os.WriteFile(jsonPath, jsonData, 0644); err != nil { + return fmt.Errorf("写入JSON文件失败: %v", err) + } + + // 创建HTML查看器 + htmlPath := filepath.Join(g.outputPath, "index.html") + htmlContent := g.createSwaggerUIHTML() + if err := os.WriteFile(htmlPath, []byte(htmlContent), 0644); err != nil { + return fmt.Errorf("写入HTML文件失败: %v", err) + } + + return nil +} + +// createOpenAPISpec 创建完整的OpenAPI规范文档 +func (g *OpenAPIGenerator) createOpenAPISpec() map[string]interface{} { + // 基本结构 + spec := map[string]interface{}{ + "openapi": "3.0.0", + "info": g.info, + "servers": []map[string]interface{}{ + { + "url": "/api", + "description": "API服务器", + }, + }, + "tags": []map[string]interface{}{}, + "paths": map[string]interface{}{}, + "components": map[string]interface{}{ + "schemas": map[string]interface{}{}, + "parameters": map[string]interface{}{}, + }, + } + + // 获取所有插件 + plugins := g.pm.GetAllPlugins() + + // 生成标签(按插件类型分组) + pluginTypeMap := make(map[PluginType]bool) + for _, p := range plugins { + pluginTypeMap[p.Type()] = true + } + + tags := make([]map[string]interface{}, 0) + for pType := range pluginTypeMap { + tags = append(tags, map[string]interface{}{ + "name": string(pType), + "description": fmt.Sprintf("%s 类型的插件", pType), + }) + } + spec["tags"] = tags + + // 为每个插件生成路径 + paths := make(map[string]interface{}) + schemas := make(map[string]interface{}) + + for _, p := range plugins { + pluginName := p.Name() + pluginType := string(p.Type()) + + // 插件基本信息路径 + pluginInfoPath := fmt.Sprintf("/plugins/{name}/info") + if _, exists := paths[pluginInfoPath]; !exists { + paths[pluginInfoPath] = map[string]interface{}{ + "get": map[string]interface{}{ + "summary": "获取插件信息", + "description": "获取指定插件的详细信息", + "operationId": "getPluginInfo", + "tags": []string{"Plugin"}, + "parameters": []map[string]interface{}{ + { + "name": "name", + "in": "path", + "description": "插件名称", + "required": true, + "schema": map[string]interface{}{ + "type": "string", + }, + }, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "$ref": "#/components/schemas/PluginInfo", + }, + }, + }, + }, + "404": map[string]interface{}{ + "description": "插件不存在", + }, + }, + }, + } + } + + // 定义插件操作的路径 + operations := p.GetAllOperations() + for opName, opInfo := range operations { + pathTemplate := fmt.Sprintf("/plugins/{name}/execute/{action}") + + // 如果路径不存在,创建路径 + if _, exists := paths[pathTemplate]; !exists { + paths[pathTemplate] = map[string]interface{}{ + "post": map[string]interface{}{ + "summary": "执行插件操作", + "description": "对指定插件执行特定操作", + "operationId": "executePluginAction", + "tags": []string{"Plugin"}, + "parameters": []map[string]interface{}{ + { + "name": "name", + "in": "path", + "description": "插件名称", + "required": true, + "schema": map[string]interface{}{ + "type": "string", + }, + }, + { + "name": "action", + "in": "path", + "description": "操作名称", + "required": true, + "schema": map[string]interface{}{ + "type": "string", + }, + }, + }, + "requestBody": map[string]interface{}{ + "description": "操作参数", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "type": "object", + }, + }, + }, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "操作成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "type": "object", + }, + }, + }, + }, + "400": map[string]interface{}{ + "description": "参数错误", + }, + "404": map[string]interface{}{ + "description": "插件或操作不存在", + }, + "500": map[string]interface{}{ + "description": "服务器错误", + }, + }, + }, + } + } + + // 为每个操作创建特定的路径 + opPath := fmt.Sprintf("/plugins/%s/actions/%s", pluginName, opName) + opRequestSchema := g.createRequestSchema(opName, opInfo, schemas) + opResponseSchema := g.createResponseSchema(opName, opInfo, schemas) + + paths[opPath] = map[string]interface{}{ + "post": map[string]interface{}{ + "summary": getMapString(opInfo, "description", fmt.Sprintf("执行 %s", opName)), + "description": getMapString(opInfo, "description", fmt.Sprintf("在 %s 插件上执行 %s 操作", pluginName, opName)), + "operationId": fmt.Sprintf("%s_%s", pluginName, opName), + "tags": []string{pluginType}, + "requestBody": map[string]interface{}{ + "description": "操作参数", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": opRequestSchema, + }, + }, + "required": true, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "操作成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": opResponseSchema, + }, + }, + }, + "400": map[string]interface{}{ + "description": "参数错误", + }, + "500": map[string]interface{}{ + "description": "服务器错误", + }, + }, + }, + } + } + } + + // 添加通用路径 + // 获取所有插件 + paths["/plugins"] = map[string]interface{}{ + "get": map[string]interface{}{ + "summary": "获取所有插件", + "description": "获取系统中所有可用插件的列表", + "operationId": "getAllPlugins", + "tags": []string{"PluginSystem"}, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "type": "array", + "items": map[string]interface{}{ + "$ref": "#/components/schemas/PluginInfo", + }, + }, + }, + }, + }, + }, + }, + } + + // 按类型获取插件 + paths["/plugins/type/{type}"] = map[string]interface{}{ + "get": map[string]interface{}{ + "summary": "按类型获取插件", + "description": "获取指定类型的所有插件", + "operationId": "getPluginsByType", + "tags": []string{"PluginSystem"}, + "parameters": []map[string]interface{}{ + { + "name": "type", + "in": "path", + "description": "插件类型", + "required": true, + "schema": map[string]interface{}{ + "type": "string", + "enum": []string{ + string(PluginTypeGeneral), + string(PluginTypeStorage), + string(PluginTypeUtils), + string(PluginTypeNetwork), + }, + }, + }, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "type": "array", + "items": map[string]interface{}{ + "$ref": "#/components/schemas/PluginInfo", + }, + }, + }, + }, + }, + }, + }, + } + + // 获取插件所有操作 + paths["/plugins/{name}/actions"] = map[string]interface{}{ + "get": map[string]interface{}{ + "summary": "获取插件所有操作", + "description": "获取指定插件支持的所有操作", + "operationId": "getPluginActions", + "tags": []string{"PluginSystem"}, + "parameters": []map[string]interface{}{ + { + "name": "name", + "in": "path", + "description": "插件名称", + "required": true, + "schema": map[string]interface{}{ + "type": "string", + }, + }, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "成功", + "content": map[string]interface{}{ + "application/json": map[string]interface{}{ + "schema": map[string]interface{}{ + "type": "object", + "additionalProperties": map[string]interface{}{ + "type": "object", + }, + }, + }, + }, + }, + "404": map[string]interface{}{ + "description": "插件不存在", + }, + }, + }, + } + + // 设置路径和组件 + spec["paths"] = paths + + // 添加基本模式 + schemas["PluginInfo"] = map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "name": map[string]interface{}{ + "type": "string", + "description": "插件名称", + }, + "version": map[string]interface{}{ + "type": "string", + "description": "插件版本", + }, + "description": map[string]interface{}{ + "type": "string", + "description": "插件描述", + }, + "author": map[string]interface{}{ + "type": "string", + "description": "插件作者", + }, + "type": map[string]interface{}{ + "type": "string", + "description": "插件类型", + "enum": []string{ + string(PluginTypeGeneral), + string(PluginTypeStorage), + string(PluginTypeUtils), + string(PluginTypeNetwork), + }, + }, + "enabled": map[string]interface{}{ + "type": "boolean", + "description": "插件是否启用", + }, + }, + "required": []string{"name", "version", "type", "enabled"}, + } + + // 更新组件模式 + spec["components"].(map[string]interface{})["schemas"] = schemas + + return spec +} + +// createRequestSchema 创建操作请求的JSON Schema +func (g *OpenAPIGenerator) createRequestSchema(opName string, opInfo map[string]interface{}, schemas map[string]interface{}) map[string]interface{} { + schemaName := fmt.Sprintf("%sRequest", opName) + + // 如果没有参数,返回空对象模式 + params, ok := opInfo["parameters"].(map[string]interface{}) + if !ok || len(params) == 0 { + return map[string]interface{}{ + "type": "object", + } + } + + // 创建请求模式 + schema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{}, + "required": []string{}, + } + properties := schema["properties"].(map[string]interface{}) + required := schema["required"].([]string) + + // 添加参数 + for paramName, paramInfo := range params { + if info, ok := paramInfo.(map[string]interface{}); ok { + paramType := getMapString(info, "type", "string") + paramDesc := getMapString(info, "description", "") + paramRequired := getMapBool(info, "required", false) + + // 为不同类型创建不同的模式 + var propSchema map[string]interface{} + + switch paramType { + case "string": + propSchema = map[string]interface{}{ + "type": "string", + "description": paramDesc, + } + case "integer": + propSchema = map[string]interface{}{ + "type": "integer", + "description": paramDesc, + } + case "float": + propSchema = map[string]interface{}{ + "type": "number", + "description": paramDesc, + } + case "boolean": + propSchema = map[string]interface{}{ + "type": "boolean", + "description": paramDesc, + } + case "array": + propSchema = map[string]interface{}{ + "type": "array", + "description": paramDesc, + "items": map[string]interface{}{ + "type": "object", + }, + } + + // 如果有元素类型信息,使用它 + if elemType, ok := info["elemType"].(map[string]interface{}); ok { + elemTypeStr := getMapString(elemType, "type", "string") + propSchema["items"] = map[string]interface{}{ + "type": g.mapTypeToOpenAPI(elemTypeStr), + } + } + case "object", "struct": + // 对于结构体和对象,创建一个引用 + structName := fmt.Sprintf("%s_%s", schemaName, paramName) + propSchema = map[string]interface{}{ + "$ref": fmt.Sprintf("#/components/schemas/%s", structName), + "description": paramDesc, + } + + // 创建结构体模式 + structSchema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{}, + "required": []string{}, + } + + // 添加结构体字段 + if fields, ok := info["fields"].(map[string]interface{}); ok { + structProps := structSchema["properties"].(map[string]interface{}) + structReq := structSchema["required"].([]string) + + for fieldName, fieldInfo := range fields { + if fi, ok := fieldInfo.(map[string]interface{}); ok { + fieldType := getMapString(fi, "type", "string") + fieldDesc := getMapString(fi, "description", "") + fieldRequired := getMapBool(fi, "required", false) + + structProps[fieldName] = map[string]interface{}{ + "type": g.mapTypeToOpenAPI(fieldType), + "description": fieldDesc, + } + + if fieldRequired { + structReq = append(structReq, fieldName) + } + } + } + + structSchema["required"] = structReq + } + + // 添加到模式 + schemas[structName] = structSchema + default: + propSchema = map[string]interface{}{ + "type": "string", + "description": paramDesc, + } + } + + properties[paramName] = propSchema + + if paramRequired { + required = append(required, paramName) + } + } + } + + // 如果没有必填参数,删除required数组 + if len(required) == 0 { + delete(schema, "required") + } else { + schema["required"] = required + } + + // 添加到模式 + schemas[schemaName] = schema + + return map[string]interface{}{ + "$ref": fmt.Sprintf("#/components/schemas/%s", schemaName), + } +} + +// createResponseSchema 创建操作响应的JSON Schema +func (g *OpenAPIGenerator) createResponseSchema(opName string, opInfo map[string]interface{}, schemas map[string]interface{}) map[string]interface{} { + schemaName := fmt.Sprintf("%sResponse", opName) + + // 如果没有返回值,返回空对象模式 + returns, ok := opInfo["returns"].(map[string]interface{}) + if !ok || len(returns) == 0 { + return map[string]interface{}{ + "type": "object", + } + } + + // 如果只有一个错误返回,使用简单响应 + if len(returns) == 1 && returns["error"] != nil { + return map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "success": map[string]interface{}{ + "type": "boolean", + "description": "操作是否成功", + }, + "error": map[string]interface{}{ + "type": "string", + "description": "错误信息", + "nullable": true, + }, + }, + } + } + + // 创建响应模式 + schema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "success": map[string]interface{}{ + "type": "boolean", + "description": "操作是否成功", + }, + }, + } + properties := schema["properties"].(map[string]interface{}) + + // 遍历所有返回值 + for retName, retInfo := range returns { + if retName == "error" { + properties["error"] = map[string]interface{}{ + "type": "string", + "description": "错误信息", + "nullable": true, + } + continue + } + + if info, ok := retInfo.(map[string]interface{}); ok { + retType := getMapString(info, "type", "string") + retDesc := getMapString(info, "description", "") + + // 为不同类型创建不同的模式 + var propSchema map[string]interface{} + + switch retType { + case "string": + propSchema = map[string]interface{}{ + "type": "string", + "description": retDesc, + } + case "integer": + propSchema = map[string]interface{}{ + "type": "integer", + "description": retDesc, + } + case "float": + propSchema = map[string]interface{}{ + "type": "number", + "description": retDesc, + } + case "boolean": + propSchema = map[string]interface{}{ + "type": "boolean", + "description": retDesc, + } + case "array": + propSchema = map[string]interface{}{ + "type": "array", + "description": retDesc, + "items": map[string]interface{}{ + "type": "object", + }, + } + + // 如果有元素类型信息,使用它 + if elemType, ok := info["elemType"].(map[string]interface{}); ok { + elemTypeStr := getMapString(elemType, "type", "string") + propSchema["items"] = map[string]interface{}{ + "type": g.mapTypeToOpenAPI(elemTypeStr), + } + } + case "object": + // 对于对象,创建嵌套模式 + propSchema = map[string]interface{}{ + "type": "object", + "description": retDesc, + "properties": map[string]interface{}{}, + } + + // 如果有属性信息,添加它们 + if props, ok := info["properties"].(map[string]interface{}); ok { + propsMap := propSchema["properties"].(map[string]interface{}) + + for propName, propInfo := range props { + if pi, ok := propInfo.(map[string]interface{}); ok { + propType := getMapString(pi, "type", "string") + propDesc := getMapString(pi, "description", "") + + propsMap[propName] = map[string]interface{}{ + "type": g.mapTypeToOpenAPI(propType), + "description": propDesc, + } + } + } + } + default: + propSchema = map[string]interface{}{ + "type": "string", + "description": retDesc, + } + } + + properties[retName] = propSchema + } + } + + // 添加到模式 + schemas[schemaName] = schema + + return map[string]interface{}{ + "$ref": fmt.Sprintf("#/components/schemas/%s", schemaName), + } +} + +// createSwaggerUIHTML 创建包含Swagger UI的HTML文件 +func (g *OpenAPIGenerator) createSwaggerUIHTML() string { + return ` + +
+ +