增强插件系统:添加参数验证功能
- 在插件助手中新增 ValidateParams 函数,用于验证传入参数是否符合定义,确保参数的完整性和正确性。 - 更新 ExecuteAction 方法,集成参数验证逻辑,提升插件执行的安全性和可靠性。 - 示例程序中添加新的选项,展示如何生成插件API文档和OpenAPI/Swagger文档,增强用户体验。 此更新提升了插件系统的健壮性,便于开发者更好地管理和使用插件功能。
This commit is contained in:
853
examples/plugin/client_generator.go
Normal file
853
examples/plugin/client_generator.go
Normal 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{}"
|
||||
}
|
||||
}
|
346
examples/plugin/doc_generator.go
Normal file
346
examples/plugin/doc_generator.go
Normal 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
|
||||
}
|
83
examples/plugin/example/client_example.go
Normal file
83
examples/plugin/example/client_example.go
Normal 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示例结束")
|
||||
}
|
77
examples/plugin/example/doc_example.go
Normal file
77
examples/plugin/example/doc_example.go
Normal 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示例结束")
|
||||
}
|
@@ -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("无效的选项")
|
||||
}
|
||||
|
91
examples/plugin/example/openapi_example.go
Normal file
91
examples/plugin/example/openapi_example.go
Normal 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示例结束")
|
||||
}
|
767
examples/plugin/openapi_generator.go
Normal file
767
examples/plugin/openapi_generator.go
Normal 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"
|
||||
}
|
||||
}
|
@@ -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]
|
||||
|
||||
// 准备参数
|
||||
|
Reference in New Issue
Block a user