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

- 在插件助手中新增 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{}"
}
}