增强插件系统:添加参数验证功能

- 在插件助手中新增 ValidateParams 函数,用于验证传入参数是否符合定义,确保参数的完整性和正确性。
- 更新 ExecuteAction 方法,集成参数验证逻辑,提升插件执行的安全性和可靠性。
- 示例程序中添加新的选项,展示如何生成插件API文档和OpenAPI/Swagger文档,增强用户体验。

此更新提升了插件系统的健壮性,便于开发者更好地管理和使用插件功能。
This commit is contained in:
2025-03-14 13:32:36 +08:00
parent 2e29253909
commit cc4c677553
8 changed files with 2330 additions and 1 deletions

View File

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

View File

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

View File

@@ -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示例结束")
}

View File

@@ -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示例结束")
}

View File

@@ -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("无效的选项")
}

View File

@@ -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示例结束")
}

View File

@@ -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 `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>插件系统API文档</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.5.0/swagger-ui.css" >
<style>
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
*, *:before, *:after { box-sizing: inherit; }
body { margin: 0; background: #fafafa; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.5.0/swagger-ui-bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.5.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
window.ui = ui;
};
</script>
</body>
</html>`
}
// mapTypeToOpenAPI 将内部类型映射到OpenAPI类型
func (g *OpenAPIGenerator) mapTypeToOpenAPI(internalType string) string {
switch internalType {
case "integer":
return "integer"
case "float":
return "number"
case "boolean":
return "boolean"
case "array":
return "array"
case "object", "struct":
return "object"
default:
return "string"
}
}

View File

@@ -133,6 +133,103 @@ func (h *PluginHelper) GetAvailableActions() []string {
return actions
}
// ValidateParams 验证传入的参数是否符合操作的参数定义
func ValidateParams(paramsDef map[string]interface{}, params map[string]interface{}) error {
// 检查参数定义是否存在
if paramsDef == nil {
return nil // 没有参数定义,不需要验证
}
// 获取参数定义中的每个参数
paramsInfo, ok := paramsDef["parameters"].(map[string]interface{})
if !ok {
return nil // 参数定义格式不正确,跳过验证
}
// 遍历每个参数定义进行验证
errors := make([]string, 0)
for paramName, paramInfo := range paramsInfo {
info, ok := paramInfo.(map[string]interface{})
if !ok {
continue // 参数信息格式不正确,跳过
}
// 检查是否为必需参数
required, ok := info["required"].(bool)
if ok && required {
// 对于结构体参数,要检查至少有一个字段被提供
if info["type"] == "struct" {
// 结构体参数的验证
if fields, ok := info["fields"].(map[string]interface{}); ok {
// 检查必填的结构体字段
for fieldName, fieldInfo := range fields {
if fieldInfoMap, ok := fieldInfo.(map[string]interface{}); ok {
fieldRequired, ok := fieldInfoMap["required"].(bool)
if ok && fieldRequired {
if _, exists := params[fieldName]; !exists {
errors = append(errors, fmt.Sprintf("缺少必需的结构体字段: %s", fieldName))
}
}
}
}
}
} else {
// 普通参数验证是否存在
if _, exists := params[paramName]; !exists {
errors = append(errors, fmt.Sprintf("缺少必需参数: %s", paramName))
}
}
}
// 如果参数存在,验证其类型
if value, exists := params[paramName]; exists && info["type"] != nil {
expectedType := info["type"].(string)
// 根据期望的类型验证参数
switch expectedType {
case "string":
if _, ok := value.(string); !ok {
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为字符串", paramName))
}
case "integer":
// 支持多种整数类型
switch value.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
// 这些类型都是可接受的
default:
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为整数", paramName))
}
case "float":
switch value.(type) {
case float32, float64:
// 这些类型是可接受的
default:
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为浮点数", paramName))
}
case "boolean":
if _, ok := value.(bool); !ok {
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为布尔值", paramName))
}
case "array":
if _, ok := value.([]interface{}); !ok {
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为数组", paramName))
}
case "object":
if _, ok := value.(map[string]interface{}); !ok {
errors = append(errors, fmt.Sprintf("参数 %s 类型错误,应为对象", paramName))
}
}
}
}
// 如果有错误,返回组合的错误信息
if len(errors) > 0 {
return fmt.Errorf("参数验证失败: %s", strings.Join(errors, "; "))
}
return nil
}
// ExecuteAction 执行指定的动作
func (h *PluginHelper) ExecuteAction(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
// 转换为小写以实现不区分大小写的匹配
@@ -143,6 +240,15 @@ func (h *PluginHelper) ExecuteAction(ctx context.Context, action string, params
return nil, fmt.Errorf("未知的操作: %s", action)
}
// 获取操作的参数信息并验证参数
opInfo, err := h.GetParameterInfo(action)
if err == nil {
// 验证参数
if err := ValidateParams(opInfo, params); err != nil {
return nil, err
}
}
paramTypes := h.methodParams[action]
// 准备参数