- 在插件助手中新增 ValidateParams 函数,用于验证传入参数是否符合定义,确保参数的完整性和正确性。 - 更新 ExecuteAction 方法,集成参数验证逻辑,提升插件执行的安全性和可靠性。 - 示例程序中添加新的选项,展示如何生成插件API文档和OpenAPI/Swagger文档,增强用户体验。 此更新提升了插件系统的健壮性,便于开发者更好地管理和使用插件功能。
347 lines
9.8 KiB
Go
347 lines
9.8 KiB
Go
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
|
|
}
|