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

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

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

View File

@@ -0,0 +1,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"
}
}