优化schema生成

This commit is contained in:
白茶清欢 2025-02-09 15:00:25 +08:00
parent 27f2ccf2aa
commit e4e8702fd4
3 changed files with 121 additions and 29 deletions

View File

@ -44,6 +44,7 @@ type PathConfig struct {
Head *PathItemOperationConfig `json:"head,omitempty"` // 定义适用于此路径的 HEAD 操作。 Head *PathItemOperationConfig `json:"head,omitempty"` // 定义适用于此路径的 HEAD 操作。
Patch *PathItemOperationConfig `json:"patch,omitempty"` // 定义适用于此路径的 PATCH 操作。 Patch *PathItemOperationConfig `json:"patch,omitempty"` // 定义适用于此路径的 PATCH 操作。
Trace *PathItemOperationConfig `json:"trace,omitempty"` // 定义适用于此路径的 TRACE 操作。 Trace *PathItemOperationConfig `json:"trace,omitempty"` // 定义适用于此路径的 TRACE 操作。
Connect *PathItemOperationConfig `json:"connect,omitempty"` // 定义适用于此路径的 CONNECT 操作。
} }
// PathItemConfig 接口的具体配置 // PathItemConfig 接口的具体配置
@ -130,6 +131,7 @@ type Schema struct {
Required []string `json:"required,omitempty"` // 必传属性列表 Required []string `json:"required,omitempty"` // 必传属性列表
Enum []any `json:"enum,omitempty"` // 枚举值列表 Enum []any `json:"enum,omitempty"` // 枚举值列表
Type string `json:"type"` // 类型 Type string `json:"type"` // 类型
Ref string `json:"$ref"` // 类型引用
} }
// Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。 // Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。

View File

@ -8,9 +8,12 @@
package api_doc package api_doc
import ( import (
"errors"
"fmt" "fmt"
"git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/wrapper"
"net/http"
"reflect" "reflect"
"strings" "strings"
) )
@ -164,23 +167,96 @@ func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.Par
return nil return nil
} }
// AddApiFromInAndOut 通过请求参数的
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:22 2025/2/9
func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType reflect.Type, resultType reflect.Type) error {
if nil == baseCfg {
return errors.New("baseCfg is nil")
}
if baseCfg.Method == "" {
return errors.New("baseCfg.Method is empty")
}
if baseCfg.Uri == "" {
return errors.New("baseCfg.Uri is empty")
}
baseCfg.Method = strings.ToUpper(baseCfg.Method)
paramMethod := []string{
http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace,
}
if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 {
// Get类请求, TODO : get类解析
return nil
}
// post类解析
defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel()
paramPkgPath := defaultPkgPath + "Param"
resultPkgPath := defaultPkgPath + "Result"
g.AddComponentsSchema(paramPkgPath, paramType)
g.AddComponentsSchema(resultPkgPath, resultType)
if _, exist := g.docData.Paths[baseCfg.Uri]; !exist {
g.docData.Paths[baseCfg.Uri] = &define.PathConfig{}
}
cfg := &define.PathItemOperationConfig{
Tags: baseCfg.TagList,
Summary: baseCfg.Summary,
Description: baseCfg.Description,
ExternalDocs: nil,
OperationID: defaultPkgPath + baseCfg.Method,
Parameters: nil,
RequestBody: &define.RequestBody{
Required: true,
Description: "",
Content: map[string]*define.Media{},
Ref: "",
},
Responses: nil,
Callbacks: nil,
Deprecated: false,
Security: nil,
Servers: nil,
}
for _, itemOutputType := range baseCfg.OutputContentType {
cfg.RequestBody.Content[itemOutputType] = &define.Media{
Schema: &define.Schema{
Ref: g.getSchemaRes(paramPkgPath + "." + paramType.Name()),
},
Example: "",
Examples: nil,
Encoding: nil,
}
}
switch baseCfg.Method {
case http.MethodGet:
g.docData.Paths[baseCfg.Uri].Get = cfg
case http.MethodHead:
g.docData.Paths[baseCfg.Uri].Head = cfg
case http.MethodPost:
g.docData.Paths[baseCfg.Uri].Post = cfg
case http.MethodPut:
g.docData.Paths[baseCfg.Uri].Put = cfg
case http.MethodPatch:
g.docData.Paths[baseCfg.Uri].Patch = cfg
case http.MethodDelete:
g.docData.Paths[baseCfg.Uri].Delete = cfg
case http.MethodConnect:
g.docData.Paths[baseCfg.Uri].Connect = cfg
case http.MethodOptions:
g.docData.Paths[baseCfg.Uri].Options = cfg
case http.MethodTrace:
g.docData.Paths[baseCfg.Uri].Trace = cfg
}
return nil
}
// AddComponentsSchema 添加schema // AddComponentsSchema 添加schema
// //
// Author : go_developer@163.com<白茶清欢> // Author : go_developer@163.com<白茶清欢>
// //
// Date : 15:25 2025/2/8 // Date : 15:25 2025/2/8
func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) string {
if nil == g.docData {
g.docData = &define.OpenapiDoc{}
}
if nil == g.docData.Components {
g.docData.Components = &define.Components{
Schemas: map[string]*define.Schema{},
}
}
if nil == g.docData.Components.Schemas {
g.docData.Components.Schemas = make(map[string]*define.Schema)
}
if len(pkgPath) == 0 { if len(pkgPath) == 0 {
pkgPath = inputType.PkgPath() pkgPath = inputType.PkgPath()
} }
@ -191,19 +267,20 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) {
if inputType.Kind() == reflect.Ptr { if inputType.Kind() == reflect.Ptr {
inputType = inputType.Elem() inputType = inputType.Elem()
} }
schemaName := schemaPrefix + inputType.Name()
if inputType.Kind() == reflect.Map { if inputType.Kind() == reflect.Map {
// map, 直接添加公共 // map, 直接添加公共
if _, exist := g.docData.Components.Schemas[schemaPrefix+inputType.Name()]; !exist { if _, exist := g.docData.Components.Schemas[schemaName]; !exist {
g.docData.Components.Schemas[schemaPrefix+inputType.Name()] = &define.Schema{ g.docData.Components.Schemas[schemaName] = &define.Schema{
Type: consts.SwaggerDataTypeObject, Type: consts.SwaggerDataTypeObject,
} }
} }
return return schemaName
} }
// 数组 // 数组
if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array {
g.parseSliceItem(inputType) g.parseSliceItem(inputType)
return return schemaName
} }
// 结构体 // 结构体
if inputType.Kind() == reflect.Struct { if inputType.Kind() == reflect.Struct {
@ -218,6 +295,7 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) {
fmt.Println(inputType.Field(i).Name) fmt.Println(inputType.Field(i).Name)
} }
} }
return schemaName
} }
// parseSliceItem 解析数组每一项 // parseSliceItem 解析数组每一项
@ -238,3 +316,15 @@ func (g *Generate) parseSliceItem(inputType reflect.Type) string {
} }
return sliceItemType.PkgPath() + "." + sliceItemType.String() return sliceItemType.PkgPath() + "." + sliceItemType.String()
} }
// getSchemaRes 获取引用的类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:25 2025/2/9
func (g *Generate) getSchemaRes(schemaName string) string {
if "" == schemaName {
return ""
}
return "#/components/schemas/" + schemaName
}

View File

@ -8,12 +8,10 @@
package api_doc package api_doc
import ( import (
"encoding/json"
"fmt" "fmt"
"git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/serialize" "git.zhangdeman.cn/zhangdeman/serialize"
"os" "net/http"
"os/user"
"reflect" "reflect"
"testing" "testing"
) )
@ -31,17 +29,19 @@ func Test_parser_Openapi3(t *testing.T) {
List []A `json:"list"` List []A `json:"list"`
} }
var bArr []*B var bArr []*B
g := Generate{} g := NewOpenapiDoc()
g.AddApiFromInAndOut(&define.UriBaseConfig{
Uri: "/a/b/c/d",
Method: http.MethodPost,
ContentType: []string{"application/x-www-form-urlencoded"},
OutputContentType: []string{"application/yml", "application/json", "application/xml"},
TagList: []string{"测试文档生成"},
Summary: "测试接口",
Description: "",
ParamList: nil,
ResultList: nil,
}, reflect.TypeOf(bArr), reflect.TypeOf(bArr))
fmt.Println(g.parseSliceItem(reflect.TypeOf(bArr))) fmt.Println(g.parseSliceItem(reflect.TypeOf(bArr)))
current, _ := user.Current()
byteData, _ := os.ReadFile(current.HomeDir + "/Downloads/test-openapi-doc.json")
var data define.OpenapiDoc
err := json.Unmarshal(byteData, &data)
if nil != err {
fmt.Println("解析失败 : " + err.Error())
} else {
fmt.Println("解析成功")
}
} }
func TestParseForSwagger(t *testing.T) { func TestParseForSwagger(t *testing.T) {