From e4e8702fd4ac37a81ac32265c510b7df0ed83350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sun, 9 Feb 2025 15:00:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96schema=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 2 + generate.go | 122 ++++++++++++++++++++++++++++++++++++++++------ parser_test.go | 26 +++++----- 3 files changed, 121 insertions(+), 29 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index a7e816b..5926b16 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -44,6 +44,7 @@ type PathConfig struct { Head *PathItemOperationConfig `json:"head,omitempty"` // 定义适用于此路径的 HEAD 操作。 Patch *PathItemOperationConfig `json:"patch,omitempty"` // 定义适用于此路径的 PATCH 操作。 Trace *PathItemOperationConfig `json:"trace,omitempty"` // 定义适用于此路径的 TRACE 操作。 + Connect *PathItemOperationConfig `json:"connect,omitempty"` // 定义适用于此路径的 CONNECT 操作。 } // PathItemConfig 接口的具体配置 @@ -130,6 +131,7 @@ type Schema struct { Required []string `json:"required,omitempty"` // 必传属性列表 Enum []any `json:"enum,omitempty"` // 枚举值列表 Type string `json:"type"` // 类型 + Ref string `json:"$ref"` // 类型引用 } // Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。 diff --git a/generate.go b/generate.go index 20ab3a4..2dca5a3 100644 --- a/generate.go +++ b/generate.go @@ -8,9 +8,12 @@ package api_doc import ( + "errors" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/wrapper" + "net/http" "reflect" "strings" ) @@ -164,23 +167,96 @@ func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.Par 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 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 -func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { - 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) - } +func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) string { if len(pkgPath) == 0 { pkgPath = inputType.PkgPath() } @@ -191,19 +267,20 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { if inputType.Kind() == reflect.Ptr { inputType = inputType.Elem() } + schemaName := schemaPrefix + inputType.Name() if inputType.Kind() == reflect.Map { // map, 直接添加公共 - if _, exist := g.docData.Components.Schemas[schemaPrefix+inputType.Name()]; !exist { - g.docData.Components.Schemas[schemaPrefix+inputType.Name()] = &define.Schema{ + if _, exist := g.docData.Components.Schemas[schemaName]; !exist { + g.docData.Components.Schemas[schemaName] = &define.Schema{ Type: consts.SwaggerDataTypeObject, } } - return + return schemaName } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { g.parseSliceItem(inputType) - return + return schemaName } // 结构体 if inputType.Kind() == reflect.Struct { @@ -218,6 +295,7 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { fmt.Println(inputType.Field(i).Name) } } + return schemaName } // parseSliceItem 解析数组每一项 @@ -238,3 +316,15 @@ func (g *Generate) parseSliceItem(inputType reflect.Type) 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 +} diff --git a/parser_test.go b/parser_test.go index b7122be..39eb4f4 100644 --- a/parser_test.go +++ b/parser_test.go @@ -8,12 +8,10 @@ package api_doc import ( - "encoding/json" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/serialize" - "os" - "os/user" + "net/http" "reflect" "testing" ) @@ -31,17 +29,19 @@ func Test_parser_Openapi3(t *testing.T) { List []A `json:"list"` } 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))) - 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) {