增强插件系统:添加参数验证功能
- 在插件助手中新增 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("1. 基本插件加载和管理")
|
||||||
fmt.Println("2. 动态参数传递示例")
|
fmt.Println("2. 动态参数传递示例")
|
||||||
fmt.Println("3. 插件操作参数信息示例")
|
fmt.Println("3. 插件操作参数信息示例")
|
||||||
fmt.Print("请输入选项 (1-3): ")
|
fmt.Println("4. 插件API文档生成示例")
|
||||||
|
fmt.Println("5. OpenAPI/Swagger文档生成示例")
|
||||||
|
fmt.Print("请输入选项 (1-5): ")
|
||||||
|
|
||||||
var choice int
|
var choice int
|
||||||
fmt.Scan(&choice)
|
fmt.Scan(&choice)
|
||||||
@@ -27,6 +29,10 @@ func main() {
|
|||||||
DynamicParamsExample()
|
DynamicParamsExample()
|
||||||
case 3:
|
case 3:
|
||||||
OperationInfoExample()
|
OperationInfoExample()
|
||||||
|
case 4:
|
||||||
|
DocGeneratorExample()
|
||||||
|
case 5:
|
||||||
|
OpenAPIGeneratorExample()
|
||||||
default:
|
default:
|
||||||
fmt.Println("无效的选项")
|
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
|
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 执行指定的动作
|
// ExecuteAction 执行指定的动作
|
||||||
func (h *PluginHelper) ExecuteAction(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) {
|
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)
|
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]
|
paramTypes := h.methodParams[action]
|
||||||
|
|
||||||
// 准备参数
|
// 准备参数
|
||||||
|
Reference in New Issue
Block a user