From 7d5e9a9d9c51e149e5069bf70a784f7533192d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sat, 8 Feb 2025 10:18:44 +0800 Subject: [PATCH 01/32] fix data type --- common.go | 28 ++++++++++++++-------------- go.mod | 2 +- go.sum | 6 ++---- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/common.go b/common.go index 469b561..fb1fac6 100644 --- a/common.go +++ b/common.go @@ -45,36 +45,36 @@ func GetUriPathParamList(uriPath string) []*define.ParamConfig { // Author : go_developer@163.com<白茶清欢> // // Date : 11:52 2024/12/24 -func GetDataType(docParamType string, formatType string) consts.DataType { +func GetDataType(docParamType string, formatType string) string { docParamType = strings.ToLower(docParamType) formatType = strings.ToLower(formatType) if len(formatType) == 0 { formatType = docParamType } switch docParamType { - case "integer": + case consts.SwaggerDataTypeInteger: if formatType == "int64" { - return consts.DataTypeInt + return consts.DataTypeInt.String() } - return consts.DataTypeUint + return consts.DataTypeUint.String() case "string", "apikey": - return consts.DataTypeString + return consts.DataTypeString.String() case "object": - return consts.DataTypeMapStrAny + return consts.DataTypeMapStrAny.String() case "boolean": - return consts.DataTypeBool + return consts.DataTypeBool.String() case "number", "float", "double", "float32", "float64": - return consts.DataTypeFloat + return consts.DataTypeFloat.String() case "array": if formatType == "integer" { - return consts.DataTypeSliceInt + return consts.DataTypeSliceInt.String() } else if formatType == "string" { - return consts.DataTypeSliceString + return consts.DataTypeSliceString.String() } else { - return consts.DataTypeSliceAny + return consts.DataTypeSliceAny.String() } default: - return consts.DataTypeAny + return consts.DataTypeAny.String() } } @@ -137,7 +137,7 @@ func ExpandArrayParam(swaggerDoc *define.Swagger, ref string, rootPath string) [ res = append(res, &define.ParamConfig{ Location: consts.RequestDataLocationBody.String(), Path: pathPrefix + "{{#idx#}}." + itemKey, - Type: GetDataType(itemConfig.Type, "").String(), + Type: GetDataType(itemConfig.Type, ""), Title: pathPrefix + itemKey, Description: pathPrefix + itemKey, Required: false, @@ -158,7 +158,7 @@ func ExpandArrayResult(swaggerDoc *define.Swagger, ref string, rootPath string) res = append(res, &define.ResultConfig{ Location: consts.ResponseDataLocationBody.String(), Path: pathPrefix + "{{#idx#}}." + itemKey, - Type: GetDataType(itemConfig.Type, "").String(), + Type: GetDataType(itemConfig.Type, ""), Title: pathPrefix + itemKey, Description: pathPrefix + itemKey, }) diff --git a/go.mod b/go.mod index 67c3dea..c828600 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.zhangdeman.cn/gateway/api-doc go 1.22.2 require ( - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207132005-330777d80591 + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1 git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250124091620-c757e551a8c9 github.com/tidwall/gjson v1.18.0 diff --git a/go.sum b/go.sum index ef348cb..e1ce507 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207125648-b75d2ec7f3c1 h1:qWqq6dWW2eIpaXvLBUjTrbOzX1xrSw/nswz2ZL7a8Zw= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207125648-b75d2ec7f3c1/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207130030-67b5d5cc6121 h1:48wtD5FTfQ6AiHWrFHg/IaCxzNWYRbuGlENWDhn0Fbw= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207130030-67b5d5cc6121/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207132005-330777d80591 h1:P58+JwVhycrAFqE2Eq25N9y5lDokYBUz+oLxCKk44BE= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207132005-330777d80591/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1 h1:vv4X72I6s6XcTi0ykj2v/cgMZyseFyE2LkS4WloICCs= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI= From 665b6fffbcd06b2a38b312aef90b2ad7fcd1640a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sat, 8 Feb 2025 15:07:04 +0800 Subject: [PATCH 02/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/generate.go | 17 +++++++++-------- generate.go | 1 - 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/define/generate.go b/define/generate.go index 7ecfe1b..66804d7 100644 --- a/define/generate.go +++ b/define/generate.go @@ -30,8 +30,8 @@ type DocParseResult struct { type UriBaseConfig struct { Uri string `json:"uri"` // 接口路由 Method string `json:"method"` // 接口请求方法 - ContentType string `json:"content_type"` // 接口请求类型 - OutputContentType string `json:"output_content_type"` // 输出数据类型 + ContentType []string `json:"content_type"` // 接口请求类型 + OutputContentType []string `json:"output_content_type"` // 输出数据类型 TagList []string `json:"tag_list"` // 接口标签列表 Summary string `json:"summary"` // 接口摘要描述 Description string `json:"description"` // 接口详细描述 @@ -45,12 +45,13 @@ type UriBaseConfig struct { // // Date : 16:23 2024/8/19 type ParamConfig struct { - Location string `json:"location"` // 参数位置 - Path string `json:"path"` // 参数路径 - Type string `json:"type"` // 参数类型 - Title string `json:"title"` // 参数标题 - Description string `json:"description"` // 参数描述 - Required bool `json:"required"` // 是否必传 + Location string `json:"location"` // 参数位置 + Path string `json:"path"` // 参数路径 + Type string `json:"type"` // 参数类型 + Title string `json:"title"` // 参数标题 + Description string `json:"description"` // 参数描述 + Required bool `json:"required"` // 是否必传 + Enum []string `json:"enum"` // 枚举值列表 } // ResultConfig 返回值配置 diff --git a/generate.go b/generate.go index 5b60bea..1beafcc 100644 --- a/generate.go +++ b/generate.go @@ -159,6 +159,5 @@ type PathItemOperationConfig struct { // // resultList : 返回值列表 func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.ParamConfig, resultList []*define.ResultConfig) error { - return nil } From fce39068cae571edd8bcd916ffe4d2f7620df61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sat, 8 Feb 2025 18:52:12 +0800 Subject: [PATCH 03/32] save code --- define/openapi.go | 2 +- generate.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index e8da7ae..a7e816b 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -122,7 +122,7 @@ type Schema struct { Discriminator *SchemaDiscriminator `json:"discriminator,omitempty"` // 说白了, 就是一个字段可能是不同的数据结构。。。 ReadOnly bool `json:"readOnly"` // 仅与 Schema "properties" 定义有关。 声明此属性是 "readonly" 的。这意味着它可以作为 response 的一部分但不应该作为 request 的一部分被发送。如果一个 property 的 readOnly 被标记为 true 且在 required 列表中,required 将只作用于 response。一个 property 的 readOnly 和 writeOnly 不允许同时被标记为 true。默认值是 false。 WriteOnly bool `json:"writeOnly"` // 仅与 Schema "properties" 定义有关。声明此 property 为 "write only"。所以它可以作为 request 的一部分而不应该作为 response 的一部分被发送。如果一个 property 的 writeOnly 被标记为 true 且在 required 列表中,required 将只作用于 request。一个 property 的 readOnly 和 writeOnly 不能同时被标记为 true。默认值是 false。 - Xml *XML `json:"xml,omitempty"` // 这只能用于 properties schemas,在 root schemas 中没有效果。 + Xml *XML `json:"xml,omitempty"` // 这只能用于 properties schemas,在root schemas 中没有效果。 ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 此 schema 附加的外部文档。 Example string `json:"example,omitempty"` // 一个用于示范此 schema实例的示例,可以是任意格式。为了表达无法用 JSON 或 YAML 格式呈现的示例,可以使用 string 类型的值,且在必要的地方需要使用字符转义。 Deprecated bool `json:"deprecated"` // 表示一个 schema 是废弃的,应该逐渐被放弃使用。默认值是 false. diff --git a/generate.go b/generate.go index 1beafcc..aa69328 100644 --- a/generate.go +++ b/generate.go @@ -10,6 +10,7 @@ package api_doc import ( "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" + "reflect" "strings" ) @@ -149,7 +150,7 @@ type PathItemOperationConfig struct { // &define.UriBaseConfig{ // Uri: "/foo/bar", // Method: http.MethodPost, -// ContentType: "application/json", +// ContentType: ["application/json"], // TagList: []string{"测试标签"}, // Summary: "这是一份示例基础配置", // Description: "这是一份示例基础配置", @@ -161,3 +162,45 @@ type PathItemOperationConfig struct { func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.ParamConfig, resultList []*define.ResultConfig) error { 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.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 { + pkgPath = inputType.PkgPath() + } + schemaPrefix := "" + if "" != pkgPath { + schemaPrefix = pkgPath + "." + } + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + 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{ + Type: consts.SwaggerDataTypeObject, + } + } + return + } + // 数组 + if inputType.Kind() == reflect.Slice { + } + // 结构体 + if inputType.Kind() == reflect.Struct { + + } +} From 81975350c1ab91929ee3b0f35682dd53e11a3581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sat, 8 Feb 2025 21:27:40 +0800 Subject: [PATCH 04/32] =?UTF-8?q?=E6=89=BE=E5=88=B0=E5=8F=8D=E5=B0=84?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=95=B0=E7=BB=84=E6=AF=8F=E4=B8=80=E9=A1=B9?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 4 ++-- go.sum | 2 -- parser_test.go | 15 +++++++++++++++ swagger.go | 14 +++++++------- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/generate.go b/generate.go index aa69328..432fbca 100644 --- a/generate.go +++ b/generate.go @@ -197,10 +197,10 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { return } // 数组 - if inputType.Kind() == reflect.Slice { + if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { + inputType.Comparable() } // 结构体 if inputType.Kind() == reflect.Struct { - } } diff --git a/go.sum b/go.sum index e1ce507..78d410d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207132005-330777d80591 h1:P58+JwVhycrAFqE2Eq25N9y5lDokYBUz+oLxCKk44BE= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250207132005-330777d80591/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1 h1:vv4X72I6s6XcTi0ykj2v/cgMZyseFyE2LkS4WloICCs= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250208020330-a50062af46a1/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y= diff --git a/parser_test.go b/parser_test.go index 1bcfa94..71fc30c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -14,6 +14,7 @@ import ( "git.zhangdeman.cn/zhangdeman/serialize" "os" "os/user" + "reflect" "testing" ) @@ -23,6 +24,20 @@ import ( // // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { + type A struct { + Name string `json:"name"` + } + type B struct { + List []A `json:"list"` + } + var b *B + tt := reflect.TypeOf(b) + fmt.Println(tt) + var bArr []*B + v := reflect.MakeSlice(reflect.TypeOf(bArr), 1, 1) + fmt.Println(v) + index9 := v.Index(0) + fmt.Println(index9, index9.Type()) current, _ := user.Current() byteData, _ := os.ReadFile(current.HomeDir + "/Downloads/test-openapi-doc.json") var data define.OpenapiDoc diff --git a/swagger.go b/swagger.go index 5df17fe..a05fdb4 100644 --- a/swagger.go +++ b/swagger.go @@ -54,7 +54,7 @@ func buildSwagger2GlobalSecurityParamList(swaggerDoc *define.Swagger) []*define. result = append(result, &define.ParamConfig{ Location: GetParamLocation(paramConfig.In).String(), Path: paramName, - Type: GetDataType(paramConfig.Type, "").String(), + Type: GetDataType(paramConfig.Type, ""), Title: paramName, Description: paramConfig.Description, Required: false, @@ -72,8 +72,8 @@ func buildUriList(swaggerDoc *define.Swagger) ([]*define.UriBaseConfig, error) { uriResult := &define.UriBaseConfig{ Uri: swaggerDoc.BasePath + "/" + strings.TrimLeft(itemUri, "/"), Method: strings.ToUpper(requestMethod), - ContentType: methodConfig.Consumes[0], - OutputContentType: methodConfig.Produces[0], + ContentType: methodConfig.Consumes, + OutputContentType: methodConfig.Produces, TagList: methodConfig.Tags, Summary: methodConfig.Summary, Description: methodConfig.Description, @@ -120,7 +120,7 @@ func buildSwagger2ParamConfig(swaggerDoc *define.Swagger, paramConfigList []*def paramConfigBuildConfig := &define.ParamConfig{ Location: GetParamLocation(paramConfig.In).String(), Path: paramConfig.Name, - Type: GetDataType(paramConfig.Type, paramConfig.Format).String(), + Type: GetDataType(paramConfig.Type, paramConfig.Format), Title: paramConfig.Name, Description: paramConfig.Description, Required: paramConfig.Required, @@ -143,7 +143,7 @@ func buildSwagger2ParamConfig(swaggerDoc *define.Swagger, paramConfigList []*def paramConfigBuildConfig := &define.ParamConfig{ Location: GetParamLocation(paramConfig.In).String(), Path: paramName, - Type: GetDataType(paramMoreConfig.Type, "").String(), + Type: GetDataType(paramMoreConfig.Type, ""), Title: paramName, Description: paramMoreConfig.Description, Required: requiredTable[paramName], @@ -204,7 +204,7 @@ func buildSwagger2ResultConfig(swaggerDoc *define.Swagger, resultConfig map[stri responseType = consts.DataTypeAny.String() } else { schemaType = responseTypeDefine.Schema.Type - responseType = GetDataType(responseTypeDefine.Schema.Type, "").String() + responseType = GetDataType(responseTypeDefine.Schema.Type, "") } resCfg := &define.ResultConfig{ Location: consts.ResponseDataLocationBody.String(), @@ -223,7 +223,7 @@ func buildSwagger2ResultConfig(swaggerDoc *define.Swagger, resultConfig map[stri res = append(res, &define.ResultConfig{ Location: consts.ResponseDataLocationBody.String(), Path: responseKey, - Type: GetDataType(responseKeyConfig.Type, "").String(), + Type: GetDataType(responseKeyConfig.Type, ""), Title: responseKey, Description: responseKey, }) From 27f2ccf2aafea1d033fb238b61db24bcd2136588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sat, 8 Feb 2025 22:04:25 +0800 Subject: [PATCH 05/32] =?UTF-8?q?=E5=9C=B0=E6=9F=9C=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 36 +++++++++++++++++++++++++++++++++++- parser_test.go | 9 ++------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/generate.go b/generate.go index 432fbca..20ab3a4 100644 --- a/generate.go +++ b/generate.go @@ -8,6 +8,7 @@ package api_doc import ( + "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "reflect" @@ -169,6 +170,9 @@ func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.Par // // 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{}, @@ -198,9 +202,39 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { - inputType.Comparable() + g.parseSliceItem(inputType) + return } // 结构体 if inputType.Kind() == reflect.Struct { + for i := 0; i < inputType.NumField(); i++ { + if inputType.Field(i).Type.Kind() == reflect.Ptr || + inputType.Field(i).Type.Kind() == reflect.Struct || + inputType.Field(i).Type.Kind() == reflect.Map || + inputType.Field(i).Type.Kind() == reflect.Array || + inputType.Field(i).Type.Kind() == reflect.Slice { + g.AddComponentsSchema(inputType.PkgPath(), inputType.Field(i).Type) + } + fmt.Println(inputType.Field(i).Name) + } } } + +// parseSliceItem 解析数组每一项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:33 2025/2/8 +func (g *Generate) parseSliceItem(inputType reflect.Type) string { + if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { + // 不是数组 + return "" + } + sliceValue := reflect.MakeSlice(inputType, 1, 1) + sliceItemType := sliceValue.Index(0).Type() + g.AddComponentsSchema(sliceItemType.PkgPath(), sliceItemType) + if len(sliceItemType.PkgPath()) == 0 { + return sliceItemType.String() + } + return sliceItemType.PkgPath() + "." + sliceItemType.String() +} diff --git a/parser_test.go b/parser_test.go index 71fc30c..b7122be 100644 --- a/parser_test.go +++ b/parser_test.go @@ -30,14 +30,9 @@ func Test_parser_Openapi3(t *testing.T) { type B struct { List []A `json:"list"` } - var b *B - tt := reflect.TypeOf(b) - fmt.Println(tt) var bArr []*B - v := reflect.MakeSlice(reflect.TypeOf(bArr), 1, 1) - fmt.Println(v) - index9 := v.Index(0) - fmt.Println(index9, index9.Type()) + g := Generate{} + fmt.Println(g.parseSliceItem(reflect.TypeOf(bArr))) current, _ := user.Current() byteData, _ := os.ReadFile(current.HomeDir + "/Downloads/test-openapi-doc.json") var data define.OpenapiDoc 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 06/32] =?UTF-8?q?=E4=BC=98=E5=8C=96schema=E7=94=9F?= =?UTF-8?q?=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) { From 88715a7d560d72cafe2fdafde327bff474bf02d3 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 21:23:58 +0800 Subject: [PATCH 07/32] save doc generate --- generate.go | 117 ++++++++++++++++++++++++++----------------------- parser_test.go | 2 +- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/generate.go b/generate.go index 2dca5a3..4570ea2 100644 --- a/generate.go +++ b/generate.go @@ -9,7 +9,6 @@ package api_doc import ( "errors" - "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/wrapper" @@ -23,26 +22,38 @@ import ( // Author : go_developer@163.com<白茶清欢> // // Date : 15:56 2024/7/22 -func NewOpenapiDoc() *Generate { +func NewOpenapiDoc(info *define.Info, servers []*define.ServerItem) *Generate { + if nil == info { + info = &define.Info{ + Description: "openapi接口文档", + Title: "openapi接口文档", + TermsOfService: "", + Contact: nil, + License: nil, + Version: "", + } + } + if nil == info.License { + info.License = &define.License{ + Name: consts.LicenseApache20, + Url: consts.LicenseUrlTable[consts.LicenseApache20], + } + } + if nil == info.Contact { + info.Contact = &define.Contact{ + Name: "研发人员(developer)", + Url: "", + Email: "", + } + } + if nil == servers { + servers = []*define.ServerItem{} + } return &Generate{ docData: &define.OpenapiDoc{ - Openapi: consts.SwaggerDocVersion3, - Info: &define.Info{ - Description: "openapi接口文档", - Title: "openapi接口文档", - TermsOfService: "", - Contact: &define.Contact{ - Name: "", - Url: "", - Email: "", - }, - License: &define.License{ - Name: "", - Url: "", - }, - Version: "", - }, - Servers: make([]*define.ServerItem, 0), + Openapi: consts.SwaggerDocVersion3, + Info: info, + Servers: servers, Components: &define.Components{Schemas: map[string]*define.Schema{}}, Tags: make([]*define.TagItem, 0), Paths: make(map[string]*define.PathConfig), @@ -126,23 +137,6 @@ func (g *Generate) AddServer(serverDomain string, serverDesc string, serverVaria } } -/** -type PathItemOperationConfig struct { - Tags []string `json:"tags"` // 用于控制API文档的标签列表,标签可以用于在逻辑上分组对资源的操作或作为其它用途的先决条件。 - Summary string `json:"summary"` // 对此操作行为的简短描述。 - Description string `json:"description"` // 对此操作行为的详细解释。CommonMark syntax可以被用来呈现富文本格式. - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 附加的外部文档。 - OperationID string `json:"operationId"` // 用于标识此操作的唯一字符串,这个id在此API内包含的所有操作中必须是唯一的。相关的工具和库可能会使用此operationId来唯一的标识一个操作,因此推荐在命名时符合一般的编程命名习惯 - Parameters []*PathConfigParameter `json:"parameters,omitempty"` // 定义可用于此操作的参数列表,如果一个同名的参数已经存在于 Path Item,那么这里的定义会覆盖它但是不能移除上面的定义。这个列表不允许包含重复的参数,参数的唯一性由 name 和 location 的组合来确定。这个列表可以使用 Reference 对象 来连接定义于 OpenAPI 对象 components/parameters 的参数。 - RequestBody *RequestBody `json:"requestBody,omitempty"` // 可用于此操作的请求体。requestBody 只能被用于HTTP 1.1 规范 RFC7231 中明确定义了包含请求体的请求方法,在其他没有明确定义的请求方法中,requestBody的消费者应该应该忽略requestBody。 - Responses map[string]*Response `json:"responses"` // 描述一个操作可能发生的响应的响应码与响应包含的响应体的对象。default字段可以用来标记一个响应适用于其他未被规范明确定义的HTTP响应码的默认响应。一个Responses 对象必须至少包含一个响应码,而且是成功的响应。 - Callbacks map[string]any `json:"callbacks,omitempty"` // 一组相对于父操作的可能出现的回调映射,映射中的每一个键都唯一的映射一个 Callback 对象 - Deprecated bool `json:"deprecated"` // 声明此操作已经被废弃,使用者应该尽量避免使用此操作,默认的值是 false。 - Security any `json:"security,omitempty"` // 声明那种安全机制可用于此操作。这个列表可以包含多种可用于此操作的安全需求对象,但是在认证一个请求时应该仅使用其中一种。这里的定义会覆盖任何在顶层 security 中的安全声明,因此可以声明一个空数组来变相的移除顶层的安全声明。 - Servers []*ServerItem `json:"servers,omitempty"` // 一个可用于此操作的额外的 server 数组,这里的定义会覆盖 Path Item 对象 或 顶层的定义。 -} -*/ - // AddApi 新增Api // // Author : go_developer@163.com<白茶清欢> @@ -209,8 +203,8 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r RequestBody: &define.RequestBody{ Required: true, Description: "", - Content: map[string]*define.Media{}, - Ref: "", + Content: map[string]*define.Media{}, + Ref: "", }, Responses: nil, Callbacks: nil, @@ -256,30 +250,37 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 -func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) string { - if len(pkgPath) == 0 { - pkgPath = inputType.PkgPath() +func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type) string { + if _, exist := g.docData.Components.Schemas[schemaName]; !exist { + g.docData.Components.Schemas[schemaName] = &define.Schema{ + Nullable: false, + Discriminator: nil, + ReadOnly: false, + WriteOnly: false, + Xml: nil, + ExternalDocs: nil, + Example: "", + Deprecated: false, + Properties: make(map[string]*define.Property), + Required: make([]string, 0), + Enum: make([]any, 0), + Type: "", + Ref: "", + } } - schemaPrefix := "" - if "" != pkgPath { - schemaPrefix = pkgPath + "." - } - if inputType.Kind() == reflect.Ptr { - inputType = inputType.Elem() - } - schemaName := schemaPrefix + inputType.Name() if inputType.Kind() == reflect.Map { // map, 直接添加公共 - if _, exist := g.docData.Components.Schemas[schemaName]; !exist { - g.docData.Components.Schemas[schemaName] = &define.Schema{ - Type: consts.SwaggerDataTypeObject, - } - } + g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeObject return schemaName } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { - g.parseSliceItem(inputType) + sliceItemType := g.parseSliceItem(inputType) + g.docData.Components.Schemas[schemaName].Properties[inputType.Name()] = &define.Property{ + Type: consts.SwaggerDataTypeArray, + Format: inputType.String(), + Items: &define.PropertyXOf{Ref: g.getSchemaRes(sliceItemType)}, + } return schemaName } // 结构体 @@ -291,8 +292,12 @@ func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) s inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { g.AddComponentsSchema(inputType.PkgPath(), inputType.Field(i).Type) + } else { + g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Type.String()] = &define.Property{ + Type: "string", + Format: inputType.Field(i).Type.String(), + } } - fmt.Println(inputType.Field(i).Name) } } return schemaName diff --git a/parser_test.go b/parser_test.go index 39eb4f4..40c83cf 100644 --- a/parser_test.go +++ b/parser_test.go @@ -29,7 +29,7 @@ func Test_parser_Openapi3(t *testing.T) { List []A `json:"list"` } var bArr []*B - g := NewOpenapiDoc() + g := NewOpenapiDoc(nil, nil) g.AddApiFromInAndOut(&define.UriBaseConfig{ Uri: "/a/b/c/d", Method: http.MethodPost, From 99a84d80834db15f5f22f587411cfc58571ef8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 10 Feb 2025 11:15:29 +0800 Subject: [PATCH 08/32] =?UTF-8?q?=E5=9F=BA=E7=A1=80schema=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 6 +++--- generate.go | 27 +++++++++++++++++++++------ parser_test.go | 5 ++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index 5926b16..3aa863b 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -279,9 +279,9 @@ type Info struct { // // Date : 16:08 2024/4/19 type Contact struct { - Name string `json:"name"` // 人或组织的名称。 - Url string `json:"url"` // 指向联系人信息的 URL 地址,必须是 URL 地址格式。 - Email string `json:"email"` // 人或组织的 email 地址,必须是 email 地址格式 + Name string `json:"name,omitempty"` // 人或组织的名称。 + Url string `json:"url,omitempty"` // 指向联系人信息的 URL 地址,必须是 URL 地址格式。 + Email string `json:"email,omitempty"` // 人或组织的 email 地址,必须是 email 地址格式 } // License 开源协议 diff --git a/generate.go b/generate.go index 4570ea2..efc0a6c 100644 --- a/generate.go +++ b/generate.go @@ -9,6 +9,7 @@ package api_doc import ( "errors" + "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/wrapper" @@ -186,8 +187,8 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r } // post类解析 defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() - paramPkgPath := defaultPkgPath + "Param" - resultPkgPath := defaultPkgPath + "Result" + paramPkgPath := defaultPkgPath + baseCfg.Method + "Param" + resultPkgPath := defaultPkgPath + baseCfg.Method + "Result" g.AddComponentsSchema(paramPkgPath, paramType) g.AddComponentsSchema(resultPkgPath, resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { @@ -206,16 +207,30 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r Content: map[string]*define.Media{}, Ref: "", }, - Responses: nil, + Responses: map[string]*define.Response{ + fmt.Sprintf("%v", http.StatusOK): &define.Response{ + Content: map[string]*define.Media{}, + }, + }, Callbacks: nil, Deprecated: false, Security: nil, Servers: nil, } - for _, itemOutputType := range baseCfg.OutputContentType { - cfg.RequestBody.Content[itemOutputType] = &define.Media{ + for _, itemType := range baseCfg.ContentType { + cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ - Ref: g.getSchemaRes(paramPkgPath + "." + paramType.Name()), + Ref: g.getSchemaRes(paramPkgPath), + }, + Example: "", + Examples: nil, + Encoding: nil, + } + } + for _, itemOutputType := range baseCfg.OutputContentType { + cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ + Schema: &define.Schema{ + Ref: g.getSchemaRes(resultPkgPath), }, Example: "", Examples: nil, diff --git a/parser_test.go b/parser_test.go index 40c83cf..7e3f360 100644 --- a/parser_test.go +++ b/parser_test.go @@ -8,6 +8,7 @@ package api_doc import ( + "encoding/json" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/serialize" @@ -28,7 +29,7 @@ func Test_parser_Openapi3(t *testing.T) { type B struct { List []A `json:"list"` } - var bArr []*B + var bArr *B g := NewOpenapiDoc(nil, nil) g.AddApiFromInAndOut(&define.UriBaseConfig{ Uri: "/a/b/c/d", @@ -41,6 +42,8 @@ func Test_parser_Openapi3(t *testing.T) { ParamList: nil, ResultList: nil, }, reflect.TypeOf(bArr), reflect.TypeOf(bArr)) + byteData, _ := json.Marshal(g.docData) + fmt.Println(string(byteData)) fmt.Println(g.parseSliceItem(reflect.TypeOf(bArr))) } From f885f61f620bcbe198874ddf68fe709b8d2962ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 10 Feb 2025 11:22:40 +0800 Subject: [PATCH 09/32] =?UTF-8?q?=E4=BC=98=E5=8C=96openapi=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 74 +++++++++++++++++++++++------------------------ parser_test.go | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index 3aa863b..b90ba1f 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -119,14 +119,14 @@ type PathConfigParameter struct { // // Date : 12:32 2024/7/19 type Schema struct { - Nullable bool `json:"nullable"` // 对于定义的schema,允许发送 null 值。默认值是 false. + Nullable bool `json:"nullable,omitempty"` // 对于定义的schema,允许发送 null 值。默认值是 false. Discriminator *SchemaDiscriminator `json:"discriminator,omitempty"` // 说白了, 就是一个字段可能是不同的数据结构。。。 - ReadOnly bool `json:"readOnly"` // 仅与 Schema "properties" 定义有关。 声明此属性是 "readonly" 的。这意味着它可以作为 response 的一部分但不应该作为 request 的一部分被发送。如果一个 property 的 readOnly 被标记为 true 且在 required 列表中,required 将只作用于 response。一个 property 的 readOnly 和 writeOnly 不允许同时被标记为 true。默认值是 false。 - WriteOnly bool `json:"writeOnly"` // 仅与 Schema "properties" 定义有关。声明此 property 为 "write only"。所以它可以作为 request 的一部分而不应该作为 response 的一部分被发送。如果一个 property 的 writeOnly 被标记为 true 且在 required 列表中,required 将只作用于 request。一个 property 的 readOnly 和 writeOnly 不能同时被标记为 true。默认值是 false。 + ReadOnly bool `json:"readOnly,omitempty"` // 仅与 Schema "properties" 定义有关。 声明此属性是 "readonly" 的。这意味着它可以作为 response 的一部分但不应该作为 request 的一部分被发送。如果一个 property 的 readOnly 被标记为 true 且在 required 列表中,required 将只作用于 response。一个 property 的 readOnly 和 writeOnly 不允许同时被标记为 true。默认值是 false。 + WriteOnly bool `json:"writeOnly,omitempty"` // 仅与 Schema "properties" 定义有关。声明此 property 为 "write only"。所以它可以作为 request 的一部分而不应该作为 response 的一部分被发送。如果一个 property 的 writeOnly 被标记为 true 且在 required 列表中,required 将只作用于 request。一个 property 的 readOnly 和 writeOnly 不能同时被标记为 true。默认值是 false。 Xml *XML `json:"xml,omitempty"` // 这只能用于 properties schemas,在root schemas 中没有效果。 ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 此 schema 附加的外部文档。 Example string `json:"example,omitempty"` // 一个用于示范此 schema实例的示例,可以是任意格式。为了表达无法用 JSON 或 YAML 格式呈现的示例,可以使用 string 类型的值,且在必要的地方需要使用字符转义。 - Deprecated bool `json:"deprecated"` // 表示一个 schema 是废弃的,应该逐渐被放弃使用。默认值是 false. + Deprecated bool `json:"deprecated,omitempty"` // 表示一个 schema 是废弃的,应该逐渐被放弃使用。默认值是 false. Properties map[string]*Property `json:"properties,omitempty"` // 数据字段 => 数据规则 Required []string `json:"required,omitempty"` // 必传属性列表 Enum []any `json:"enum,omitempty"` // 枚举值列表 @@ -205,10 +205,10 @@ type RequestBody struct { // // Date : 17:21 2024/7/19 type Media struct { - Schema *Schema `json:"schema"` // 定义此媒体类型的结构。 - Example string `json:"example"` // 媒体类型的示例。示例对象应该符合此媒体类型的格式, 这里指定的example对象 object is mutually exclusive of the examples object. 而且如果引用的schema也包含示例,在这里指定的example值将会覆盖schema提供的示例。 - Examples map[string]*Example `json:"examples"` // 媒体类型的示例,每个媒体对象的值都应该匹配它对应的媒体类型的格式。 The examples object is mutually exclusive of the example object. 而且如果引用的schema也包含示例,在这里指定的example值将会覆盖schema提供的示例。 - Encoding map[string]*Encoding `json:"encoding"` // 属性名与编码信息的映射。每个属性名必须存在于schema属性的key中,当媒体类型等于multipart或application/x-www-form-urlencoded时,编码对象信息仅适用于requestBody。 + Schema *Schema `json:"schema,omitempty"` // 定义此媒体类型的结构。 + Example string `json:"example,omitempty"` // 媒体类型的示例。示例对象应该符合此媒体类型的格式, 这里指定的example对象 object is mutually exclusive of the examples object. 而且如果引用的schema也包含示例,在这里指定的example值将会覆盖schema提供的示例。 + Examples map[string]*Example `json:"examples,omitempty"` // 媒体类型的示例,每个媒体对象的值都应该匹配它对应的媒体类型的格式。 The examples object is mutually exclusive of the example object. 而且如果引用的schema也包含示例,在这里指定的example值将会覆盖schema提供的示例。 + Encoding map[string]*Encoding `json:"encoding,omitempty"` // 属性名与编码信息的映射。每个属性名必须存在于schema属性的key中,当媒体类型等于multipart或application/x-www-form-urlencoded时,编码对象信息仅适用于requestBody。 } // Encoding 一个编码定义仅适用于一个结构属性 @@ -217,11 +217,11 @@ type Media struct { // // Date : 17:28 2024/7/19 type Encoding struct { - ContentType string `json:"content_type"` // 对具体属性的 Content-Type的编码。默认值取决于属性的类型:application/octet-stream编码适用于binary格式的string;text/plain适用于其他原始值;application/json适用于object;对于array值类型的默认值取决于数组内元素的类型,默认值可以是明确的媒体类型(比如application/json), 或者通配符类型的媒体类型(比如image/*), 又或者是用分号分隔的两种媒体类型。 - Headers map[string]*Header `json:"headers"` // 提供附加信息的请求头键值对映射。比如Content-Disposition、Content-Type各自描述了不同的信息而且在这里将会被忽略,如果请求体的媒体类型不是multipart,这个属性将会被忽略。 - Style string `json:"style"` // 描述一个属性根据它的类型将会被如何序列化。查看Parameter 对象的style属性可以得到更多详细信息。这个属性的行为与query参数相同,包括默认值的定义。如果请求体的媒体类型不是application/x-www-form-urlencoded,这个属性将会被忽略。 - Explode bool `json:"explode"` // 当这个值为true时,类型为array或object的属性值会为数组的每个元素或对象的每个键值对分开生成参数。这个属性对其他数据类型没有影响。当style为form时,这个属性的默认值是true,对于其他的style类型,这个属性的默认值是false。这个属性会被忽略如果请求体的媒体类型不是application/x-www-form-urlencoded。 - AllowReserved bool `json:"allowReserved"` // 决定此参数的值是否允许不使用%号编码使用定义于 RFC3986内的保留字符 :/?#[]@!$&'()*+,;=。 这个属性仅用于in的值是query时,此字段的默认值是false。 这个属性会被忽略如果请求体的媒体类型不是application/x-www-form-urlencoded。 + ContentType string `json:"content_type,omitempty"` // 对具体属性的 Content-Type的编码。默认值取决于属性的类型:application/octet-stream编码适用于binary格式的string;text/plain适用于其他原始值;application/json适用于object;对于array值类型的默认值取决于数组内元素的类型,默认值可以是明确的媒体类型(比如application/json), 或者通配符类型的媒体类型(比如image/*), 又或者是用分号分隔的两种媒体类型。 + Headers map[string]*Header `json:"headers,omitempty"` // 提供附加信息的请求头键值对映射。比如Content-Disposition、Content-Type各自描述了不同的信息而且在这里将会被忽略,如果请求体的媒体类型不是multipart,这个属性将会被忽略。 + Style string `json:"style,omitempty"` // 描述一个属性根据它的类型将会被如何序列化。查看Parameter 对象的style属性可以得到更多详细信息。这个属性的行为与query参数相同,包括默认值的定义。如果请求体的媒体类型不是application/x-www-form-urlencoded,这个属性将会被忽略。 + Explode bool `json:"explode,omitempty"` // 当这个值为true时,类型为array或object的属性值会为数组的每个元素或对象的每个键值对分开生成参数。这个属性对其他数据类型没有影响。当style为form时,这个属性的默认值是true,对于其他的style类型,这个属性的默认值是false。这个属性会被忽略如果请求体的媒体类型不是application/x-www-form-urlencoded。 + AllowReserved bool `json:"allowReserved,omitempty"` // 决定此参数的值是否允许不使用%号编码使用定义于 RFC3986内的保留字符 :/?#[]@!$&'()*+,;=。 这个属性仅用于in的值是query时,此字段的默认值是false。 这个属性会被忽略如果请求体的媒体类型不是application/x-www-form-urlencoded。 } // Header 对象除了以下改动之外与 Parameter 对象 一致: @@ -241,10 +241,10 @@ type Header PathConfigParameter // // Date : 17:24 2024/7/19 type Example struct { - Summary string `json:"summary"` // example 的简要描述。 - Description string `json:"description"` // example 的详细描述。CommonMark syntax可以被用来呈现富文本格式. - Value any `json:"value"` // 嵌入的字面量 example。 value 字段和 externalValue 字段是互斥的。无法使用 JSON 或 YAML 表示的媒体类型可以使用字符串值来表示。 - ExternalValue string `json:"externalValue"` // 指向字面 example 的一个 URL。这提供了引用无法被包含在 JSON 或 YAML 文档中的 example。value 字段和 externalValue 字段是互斥的。 + Summary string `json:"summary,omitempty"` // example 的简要描述。 + Description string `json:"description,omitempty"` // example 的详细描述。CommonMark syntax可以被用来呈现富文本格式. + Value any `json:"value,omitempty"` // 嵌入的字面量 example。 value 字段和 externalValue 字段是互斥的。无法使用 JSON 或 YAML 表示的媒体类型可以使用字符串值来表示。 + ExternalValue string `json:"externalValue,omitempty"` // 指向字面 example 的一个 URL。这提供了引用无法被包含在 JSON 或 YAML 文档中的 example。value 字段和 externalValue 字段是互斥的。 } // Response 响应的数据结构 @@ -253,10 +253,10 @@ type Example struct { // // Date : 17:38 2024/7/19 type Response struct { - Description string `json:"description"` // 必选. 对响应的简短描述。CommonMark syntax可以被用来呈现富文本格式. - Headers map[string]*Header `json:"headers"` // 映射HTTP头名称到其定义。RFC7230 规定了HTTP头名称不区分大小写。如果一个响应头使用"Content-Type"作为HTTP头名称,它会被忽略。 - Content map[string]*Media `json:"content"` // 一个包含描述预期响应负载的映射。使用 media type 或 media type range 作为键,以响应的描述作为值。当一个响应匹配多个键时,只有最明确的键才适用。比如:text/plain 会覆盖 text/* - Ref string `json:"$ref"` // 引用描述 + Description string `json:"description,omitempty"` // 必选. 对响应的简短描述。CommonMark syntax可以被用来呈现富文本格式. + Headers map[string]*Header `json:"headers,omitempty"` // 映射HTTP头名称到其定义。RFC7230 规定了HTTP头名称不区分大小写。如果一个响应头使用"Content-Type"作为HTTP头名称,它会被忽略。 + Content map[string]*Media `json:"content,omitempty"` // 一个包含描述预期响应负载的映射。使用 media type 或 media type range 作为键,以响应的描述作为值。当一个响应匹配多个键时,只有最明确的键才适用。比如:text/plain 会覆盖 text/* + Ref string `json:"$ref,omitempty"` // 引用描述 } // Info 信息 @@ -265,12 +265,12 @@ type Response struct { // // Date : 16:10 2024/4/19 type Info struct { - Description string `json:"description"` // 对应用的简短描述。 CommonMark syntax 可以被用来表示富文本呈现。 - Title string `json:"title" required:"true"` // 必选. 应用的名称。 - TermsOfService string `json:"termsOfService"` // 指向服务条款的 URL 地址,必须是 URL 地址格式。 - Contact *Contact `json:"contact,omitempty"` // 联系方式 - License *License `json:"license,omitempty"` // 开源协议 - Version string `json:"version" required:"true"` // 必选. API 文档的版本信息(注意:这个版本和开放 API 规范版本没有任何关系)。 + Description string `json:"description,omitempty"` // 对应用的简短描述。 CommonMark syntax 可以被用来表示富文本呈现。 + Title string `json:"title,omitempty" required:"true"` // 必选. 应用的名称。 + TermsOfService string `json:"termsOfService,omitempty"` // 指向服务条款的 URL 地址,必须是 URL 地址格式。 + Contact *Contact `json:"contact,omitempty,omitempty"` // 联系方式 + License *License `json:"license,omitempty"` // 开源协议 + Version string `json:"version,omitempty" required:"true"` // 必选. API 文档的版本信息(注意:这个版本和开放 API 规范版本没有任何关系)。 } // Contact 联系人信息 @@ -290,8 +290,8 @@ type Contact struct { // // Date : 16:09 2024/4/19 type License struct { - Name string `json:"name"` // 开源协议名 - Url string `json:"url"` // 开源协议地址 + Name string `json:"name,omitempty"` // 开源协议名 + Url string `json:"url,omitempty"` // 开源协议地址 } // ServerItem server 对象结构 @@ -300,9 +300,9 @@ type License struct { // // Date : 14:18 2024/7/19 type ServerItem struct { - Url string `json:"url" required:"true"` // 必选. 指向目标主机的 URL 地址。这个 URL 地址支持服务器变量而且可能是相对路径,表示主机路径是相对于本文档所在的路径。当一个变量被命名为类似{brackets}时需要替换此变量。 - Description string `json:"description"` // 一个可选的字符串,用来描述此 URL 地址。CommonMark syntax可以被用来呈现富文本格式. - Variables map[string]*ServerItemVariable `json:"variables"` // 当 url 中出现变量名的时候, 会从次变量中读取数据替换到url中. 变量名 -> 变量配置 + Url string `json:"url,omitempty" required:"true"` // 必选. 指向目标主机的 URL 地址。这个 URL 地址支持服务器变量而且可能是相对路径,表示主机路径是相对于本文档所在的路径。当一个变量被命名为类似{brackets}时需要替换此变量。 + Description string `json:"description,omitempty"` // 一个可选的字符串,用来描述此 URL 地址。CommonMark syntax可以被用来呈现富文本格式. + Variables map[string]*ServerItemVariable `json:"variables,omitempty"` // 当 url 中出现变量名的时候, 会从次变量中读取数据替换到url中. 变量名 -> 变量配置 } // ServerItemVariable ... @@ -311,9 +311,9 @@ type ServerItem struct { // // Date : 14:22 2024/7/19 type ServerItemVariable struct { - Default string `json:"default"` // 变量默认值 - Description string `json:"description"` // 变量描述 - Enum []string `json:"enum"` // 变量枚举值 + Default string `json:"default,omitempty"` // 变量默认值 + Description string `json:"description,omitempty"` // 变量描述 + Enum []string `json:"enum,omitempty"` // 变量枚举值 } // TagItem 每一个标签数据结构 @@ -322,6 +322,6 @@ type ServerItemVariable struct { // // Date : 12:18 2024/7/19 type TagItem struct { - Name string `json:"name"` // 标签名称 - Description string `json:"description"` // 标签描述 + Name string `json:"name,omitempty"` // 标签名称 + Description string `json:"description,omitempty"` // 标签描述 } diff --git a/parser_test.go b/parser_test.go index 7e3f360..805d9fc 100644 --- a/parser_test.go +++ b/parser_test.go @@ -32,7 +32,7 @@ func Test_parser_Openapi3(t *testing.T) { var bArr *B g := NewOpenapiDoc(nil, nil) g.AddApiFromInAndOut(&define.UriBaseConfig{ - Uri: "/a/b/c/d", + Uri: "/ent/user/detail", Method: http.MethodPost, ContentType: []string{"application/x-www-form-urlencoded"}, OutputContentType: []string{"application/yml", "application/json", "application/xml"}, From 859547fe1df42e0979190b4b5d2c6cd207e6d8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 10 Feb 2025 11:33:10 +0800 Subject: [PATCH 10/32] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=BF=85=E9=A1=BB?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E5=B1=9E=E6=80=A7=E4=BC=98=E5=8C=96=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 20 ++++++++++---------- generate.go | 9 ++++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index b90ba1f..1707625 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -253,10 +253,10 @@ type Example struct { // // Date : 17:38 2024/7/19 type Response struct { - Description string `json:"description,omitempty"` // 必选. 对响应的简短描述。CommonMark syntax可以被用来呈现富文本格式. - Headers map[string]*Header `json:"headers,omitempty"` // 映射HTTP头名称到其定义。RFC7230 规定了HTTP头名称不区分大小写。如果一个响应头使用"Content-Type"作为HTTP头名称,它会被忽略。 - Content map[string]*Media `json:"content,omitempty"` // 一个包含描述预期响应负载的映射。使用 media type 或 media type range 作为键,以响应的描述作为值。当一个响应匹配多个键时,只有最明确的键才适用。比如:text/plain 会覆盖 text/* - Ref string `json:"$ref,omitempty"` // 引用描述 + Description string `json:"description" required:"true"` // 必选. 对响应的简短描述。CommonMark syntax可以被用来呈现富文本格式. + Headers map[string]*Header `json:"headers,omitempty"` // 映射HTTP头名称到其定义。RFC7230 规定了HTTP头名称不区分大小写。如果一个响应头使用"Content-Type"作为HTTP头名称,它会被忽略。 + Content map[string]*Media `json:"content,omitempty"` // 一个包含描述预期响应负载的映射。使用 media type 或 media type range 作为键,以响应的描述作为值。当一个响应匹配多个键时,只有最明确的键才适用。比如:text/plain 会覆盖 text/* + Ref string `json:"$ref,omitempty"` // 引用描述 } // Info 信息 @@ -265,12 +265,12 @@ type Response struct { // // Date : 16:10 2024/4/19 type Info struct { - Description string `json:"description,omitempty"` // 对应用的简短描述。 CommonMark syntax 可以被用来表示富文本呈现。 - Title string `json:"title,omitempty" required:"true"` // 必选. 应用的名称。 - TermsOfService string `json:"termsOfService,omitempty"` // 指向服务条款的 URL 地址,必须是 URL 地址格式。 - Contact *Contact `json:"contact,omitempty,omitempty"` // 联系方式 - License *License `json:"license,omitempty"` // 开源协议 - Version string `json:"version,omitempty" required:"true"` // 必选. API 文档的版本信息(注意:这个版本和开放 API 规范版本没有任何关系)。 + Description string `json:"description,omitempty"` // 对应用的简短描述。 CommonMark syntax 可以被用来表示富文本呈现。 + Title string `json:"title,omitempty" required:"true"` // 必选. 应用的名称。 + TermsOfService string `json:"termsOfService,omitempty"` // 指向服务条款的 URL 地址,必须是 URL 地址格式。 + Contact *Contact `json:"contact,omitempty,omitempty"` // 联系方式 + License *License `json:"license,omitempty"` // 开源协议 + Version string `json:"version" required:"true"` // 必选. API 文档的版本信息(注意:这个版本和开放 API 规范版本没有任何关系)。 } // Contact 联系人信息 diff --git a/generate.go b/generate.go index efc0a6c..9b9f7aa 100644 --- a/generate.go +++ b/generate.go @@ -31,9 +31,12 @@ func NewOpenapiDoc(info *define.Info, servers []*define.ServerItem) *Generate { TermsOfService: "", Contact: nil, License: nil, - Version: "", + Version: "0.0.1", } } + if len(info.Version) == 0 { + info.Version = "0.0.1" + } if nil == info.License { info.License = &define.License{ Name: consts.LicenseApache20, @@ -306,9 +309,9 @@ func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type inputType.Field(i).Type.Kind() == reflect.Map || inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.AddComponentsSchema(inputType.PkgPath(), inputType.Field(i).Type) + g.AddComponentsSchema(schemaName+inputType.Field(i).Name, inputType.Field(i).Type) } else { - g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Type.String()] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ Type: "string", Format: inputType.Field(i).Type.String(), } From 1b48707f8273df3030ac865dd9c382eb70eb56b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 10 Feb 2025 14:38:37 +0800 Subject: [PATCH 11/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=B8=8D=E8=A7=84=E8=8C=83=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 12 ++++++------ generate.go | 37 ++++++++++++++++++++++++++++++------- parser_test.go | 2 +- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index 1707625..7767261 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -130,8 +130,8 @@ type Schema struct { Properties map[string]*Property `json:"properties,omitempty"` // 数据字段 => 数据规则 Required []string `json:"required,omitempty"` // 必传属性列表 Enum []any `json:"enum,omitempty"` // 枚举值列表 - Type string `json:"type"` // 类型 - Ref string `json:"$ref"` // 类型引用 + Type string `json:"type,omitempty"` // 类型 + Ref string `json:"$ref,omitempty"` // 类型引用 } // Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。 @@ -193,10 +193,10 @@ type XML struct { // // Date : 17:17 2024/7/19 type RequestBody struct { - Required bool `json:"required"` // 指定请求体是不是应该被包含在请求中,默认值是false。 - Description string `json:"description"` // 对请求体的简要描述,可以包含使用示例,CommonMark syntax可以被用来呈现富文本格式 - Content map[string]*Media `json:"content"` // content_type => 相应数据描述的映射 必选. 请求体的内容。请求体的属性key是一个媒体类型或者媒体类型范围,值是对应媒体类型的示例数据。对于能匹配多个key的请求,定义更明确的请求会更优先被匹配。比如text/plain会覆盖text/*的定义。 - Ref string `json:"$ref"` // 一个允许引用规范内部的其他部分或外部规范的对象。 Reference 对象 定义于 JSON Reference 且遵循相同的结构、行为和规则。 + Required bool `json:"required"` // 指定请求体是不是应该被包含在请求中,默认值是false。 + Description string `json:"description,omitempty"` // 对请求体的简要描述,可以包含使用示例,CommonMark syntax可以被用来呈现富文本格式 + Content map[string]*Media `json:"content,omitempty"` // content_type => 相应数据描述的映射 必选. 请求体的内容。请求体的属性key是一个媒体类型或者媒体类型范围,值是对应媒体类型的示例数据。对于能匹配多个key的请求,定义更明确的请求会更优先被匹配。比如text/plain会覆盖text/*的定义。 + Ref string `json:"$ref,omitempty"` // 一个允许引用规范内部的其他部分或外部规范的对象。 Reference 对象 定义于 JSON Reference 且遵循相同的结构、行为和规则。 } // Media 本质即为不一样 content_type 对应的数据结构定义 diff --git a/generate.go b/generate.go index 9b9f7aa..37456d5 100644 --- a/generate.go +++ b/generate.go @@ -192,8 +192,8 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() paramPkgPath := defaultPkgPath + baseCfg.Method + "Param" resultPkgPath := defaultPkgPath + baseCfg.Method + "Result" - g.AddComponentsSchema(paramPkgPath, paramType) - g.AddComponentsSchema(resultPkgPath, resultType) + g.AddComponentsSchema("", paramPkgPath, paramType) + g.AddComponentsSchema("", resultPkgPath, resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } @@ -268,7 +268,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 -func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type) string { +func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType reflect.Type) string { if _, exist := g.docData.Components.Schemas[schemaName]; !exist { g.docData.Components.Schemas[schemaName] = &define.Schema{ Nullable: false, @@ -293,7 +293,8 @@ func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { - sliceItemType := g.parseSliceItem(inputType) + g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray + sliceItemType := g.parseSliceItem(schemaName, inputType) g.docData.Components.Schemas[schemaName].Properties[inputType.Name()] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.String(), @@ -303,13 +304,19 @@ func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type } // 结构体 if inputType.Kind() == reflect.Struct { + g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map || inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.AddComponentsSchema(schemaName+inputType.Field(i).Name, inputType.Field(i).Type) + g.AddComponentsSchema(schemaName, schemaName+inputType.Field(i).Name, inputType.Field(i).Type) + g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ + Type: consts.SwaggerDataTypeObject, + Format: inputType.Field(i).Type.String(), + Properties: map[string]*define.Property{}, + } } else { g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ Type: "string", @@ -326,14 +333,30 @@ func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type // Author : go_developer@163.com<白茶清欢> // // Date : 21:33 2025/2/8 -func (g *Generate) parseSliceItem(inputType reflect.Type) string { +func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) string { if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { // 不是数组 return "" } sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceItemType := sliceValue.Index(0).Type() - g.AddComponentsSchema(sliceItemType.PkgPath(), sliceItemType) + g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) + if rootSchemaName != "" { + g.docData.Components.Schemas[rootSchemaName].Properties[sliceItemType.PkgPath()] = &define.Property{ + Type: "", + Format: inputType.String(), + Enum: nil, + Default: nil, + Description: "", + AllOf: nil, + OneOf: nil, + AnyOf: nil, + Items: nil, + AdditionalProperties: nil, + Properties: nil, + Ref: g.getSchemaRes(sliceItemType.PkgPath()), + } + } if len(sliceItemType.PkgPath()) == 0 { return sliceItemType.String() } diff --git a/parser_test.go b/parser_test.go index 805d9fc..6c947a8 100644 --- a/parser_test.go +++ b/parser_test.go @@ -44,7 +44,7 @@ func Test_parser_Openapi3(t *testing.T) { }, reflect.TypeOf(bArr), reflect.TypeOf(bArr)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) - fmt.Println(g.parseSliceItem(reflect.TypeOf(bArr))) + fmt.Println(g.parseSliceItem("", reflect.TypeOf(bArr))) } func TestParseForSwagger(t *testing.T) { From a4a0c197ad0b75ccf9ae12770c073dd3ce321e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Tue, 11 Feb 2025 21:09:42 +0800 Subject: [PATCH 12/32] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=A1=AB=E5=85=85,=20=E6=9C=89BUG=E5=BE=85=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/generate.go b/generate.go index 37456d5..75818e7 100644 --- a/generate.go +++ b/generate.go @@ -269,6 +269,23 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType reflect.Type) string { + fmt.Println(inputType.String()) + if rootSchemaName != "" { + g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ + Type: g.getSchemaRes(schemaName), + Format: inputType.String(), + Enum: nil, + Default: nil, + Description: "", + AllOf: nil, + OneOf: nil, + AnyOf: nil, + Items: nil, + AdditionalProperties: nil, + Properties: nil, + Ref: g.getSchemaRes(schemaName), + } + } if _, exist := g.docData.Components.Schemas[schemaName]; !exist { g.docData.Components.Schemas[schemaName] = &define.Schema{ Nullable: false, @@ -311,6 +328,14 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType.Field(i).Type.Kind() == reflect.Map || inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { + if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); convertType != inputType.Field(i).Type.Kind().String() { + // 针对基础类型指针 + g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ + Type: g.realBaseType2SwaggerType(convertType), + Format: inputType.Field(i).Type.String(), + } + continue + } g.AddComponentsSchema(schemaName, schemaName+inputType.Field(i).Name, inputType.Field(i).Type) g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ Type: consts.SwaggerDataTypeObject, @@ -319,11 +344,26 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } } else { g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ - Type: "string", + Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), Format: inputType.Field(i).Type.String(), } } } + return schemaName + } + // 指针 + if inputType.Kind() == reflect.Ptr { + convertType := g.realBaseType2SwaggerType(inputType.String()) + if convertType == inputType.String() { + // 非基础数据类型 + return g.AddComponentsSchema(schemaName, schemaName+inputType.Elem().String(), inputType.Elem()) + } else { + g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ + Type: convertType, + Format: inputType.String(), + Properties: map[string]*define.Property{}, + } + } } return schemaName } @@ -374,3 +414,27 @@ func (g *Generate) getSchemaRes(schemaName string) string { } return "#/components/schemas/" + schemaName } + +// realType2SwaggerType golang 真实数据类型转换为golang数据类型 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:25 2025/2/11 +func (g *Generate) realBaseType2SwaggerType(realType string) string { + switch realType { + case "bool", "*bool": + return consts.SwaggerDataTypeBoolean + case "string", "*string": + return consts.SwaggerDataTypeString + case "byte", "*byte": + return consts.SwaggerDataTypeByte + case "float32", "*float32", "float64", "*float64": + return consts.SwaggerDataTypeNumber + case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": + return consts.SwaggerDataTypeLong + case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": + return consts.SwaggerDataTypeInteger + default: + return realType + } +} From bdd0845f1dd0ee4575c82e827eca6b39aab6e97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Tue, 11 Feb 2025 21:28:28 +0800 Subject: [PATCH 13/32] save code --- generate.go | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/generate.go b/generate.go index 75818e7..13a56c4 100644 --- a/generate.go +++ b/generate.go @@ -269,10 +269,9 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType reflect.Type) string { - fmt.Println(inputType.String()) if rootSchemaName != "" { g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ - Type: g.getSchemaRes(schemaName), + Type: g.realType2SwaggerType(inputType.String()), Format: inputType.String(), Enum: nil, Default: nil, @@ -328,7 +327,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType.Field(i).Type.Kind() == reflect.Map || inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); convertType != inputType.Field(i).Type.Kind().String() { + if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { // 针对基础类型指针 g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ Type: g.realBaseType2SwaggerType(convertType), @@ -337,11 +336,27 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, continue } g.AddComponentsSchema(schemaName, schemaName+inputType.Field(i).Name, inputType.Field(i).Type) - g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ - Type: consts.SwaggerDataTypeObject, - Format: inputType.Field(i).Type.String(), - Properties: map[string]*define.Property{}, + if inputType.Field(i).Type.Kind() == reflect.Struct || + inputType.Field(i).Type.Kind() == reflect.Map { + g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ + Type: consts.SwaggerDataTypeObject, + Format: inputType.Field(i).Type.String(), + Properties: map[string]*define.Property{}, + } + } else if inputType.Field(i).Type.Kind() == reflect.Array || + inputType.Field(i).Type.Kind() == reflect.Slice { + g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ + Type: consts.SwaggerDataTypeArray, + Format: inputType.Field(i).Type.String(), + Items: &define.PropertyXOf{ + Ref: g.parseSliceItem(schemaName, inputType.Field(i).Type), + }, + Properties: map[string]*define.Property{}, + } + } else if inputType.Field(i).Type.Kind() == reflect.Ptr { + } + } else { g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), @@ -438,3 +453,15 @@ func (g *Generate) realBaseType2SwaggerType(realType string) string { return realType } } + +// realType2SwaggerType ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:20 2025/2/11 +func (g *Generate) realType2SwaggerType(realType string) string { + if strings.HasPrefix(realType, "[]") { + return consts.SwaggerDataTypeArray + } + return g.realBaseType2SwaggerType(realType) +} From f9a1222712594d76df5ebb7e314f55b8135cdc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Tue, 11 Feb 2025 22:10:49 +0800 Subject: [PATCH 14/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 10 ++++----- define/tag.go | 24 ++++++++++++++++++++ generate.go | 57 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 define/tag.go diff --git a/define/openapi.go b/define/openapi.go index 7767261..404a772 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -140,11 +140,11 @@ type Schema struct { // // Date : 17:05 2024/7/19 type Property struct { - Type string `json:"type"` // 数据类型, swagger本身的定义 - Format string `json:"format"` // 对应编程语言中的数据类型描述 - Enum []any `json:"enum,omitempty"` // 枚举值列表 - Default any `json:"default"` // 默认值 : 不同于 JSON Schema,这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string,那么 default 可以是 "foo" 但不能是 1。 - Description string `json:"description"` // 数据描述, CommonMark syntax可以被用来呈现富文本格式. + Type string `json:"type,omitempty"` // 数据类型, swagger本身的定义 + Format string `json:"format,omitempty"` // 对应编程语言中的数据类型描述 + Enum []string `json:"enum,omitempty"` // 枚举值列表 + Default string `json:"default,omitempty"` // 默认值 : 不同于 JSON Schema,这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string,那么 default 可以是 "foo" 但不能是 1。 + Description string `json:"description,omitempty"` // 数据描述, CommonMark syntax可以被用来呈现富文本格式. AllOf []*PropertyXOf `json:"allOf,omitempty"` // type 是一个对象, allOf 指向对象描述 OneOf []*PropertyXOf `json:"oneOf,omitempty"` // type 是一个对象, allOf 指向对象描述 AnyOf []*PropertyXOf `json:"anyOf,omitempty"` // type 是一个对象, allOf 指向对象描述 diff --git a/define/tag.go b/define/tag.go new file mode 100644 index 0000000..5528dcb --- /dev/null +++ b/define/tag.go @@ -0,0 +1,24 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-02-11 21:51 +package define + +const ( + TagJson = "json" + TagXml = "xml" + TagYaml = "yaml" + TagYml = "yml" + TagForm = "form" + TagBinding = "binding" + TagValidate = "validate" + TagErr = "err" + TagMsg = "msg" + TagDesc = "desc" + TagDescription = "description" + TagD = "d" + TagDefault = "default" +) diff --git a/generate.go b/generate.go index 13a56c4..83c31c1 100644 --- a/generate.go +++ b/generate.go @@ -274,7 +274,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, Type: g.realType2SwaggerType(inputType.String()), Format: inputType.String(), Enum: nil, - Default: nil, + Default: "", Description: "", AllOf: nil, OneOf: nil, @@ -401,7 +401,7 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) Type: "", Format: inputType.String(), Enum: nil, - Default: nil, + Default: "", Description: "", AllOf: nil, OneOf: nil, @@ -465,3 +465,56 @@ func (g *Generate) realType2SwaggerType(realType string) string { } return g.realBaseType2SwaggerType(realType) } + +// getParamName 获取参数名称 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:58 2025/2/11 +func (g *Generate) getParamName(structField reflect.StructField) string { + paramNameTagList := []string{ + define.TagJson, define.TagForm, + define.TagXml, define.TagYaml, + define.TagYml, + } + for _, tag := range paramNameTagList { + tagVal := structField.Tag.Get(tag) + if tagVal != "" { + return tagVal + } + } + // 未设置相关字段, 则字段名即为参数名 + return structField.Name +} + +// getParamDesc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:01 2025/2/11 +func (g *Generate) getParamDesc(structField reflect.StructField) string { + descTagList := []string{define.TagDesc, define.TagDescription} + for _, tag := range descTagList { + tagVal := structField.Tag.Get(tag) + if tagVal != "" { + return tagVal + } + } + // 没有显示的设置参数描述, 则使用参数名作为参数描述 + return g.getParamName(structField) +} + +// getDefaultValue 获取默认值 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:05 2025/2/11 +func (g *Generate) getDefaultValue(structField reflect.StructField) string { + defaultTagList := []string{define.TagD, define.TagDefault} + for _, tag := range defaultTagList { + if tagVal, exist := structField.Tag.Lookup(tag); exist { + return tagVal + } + } + return "" +} From 613e004bf225485617e0fba89244e922fe31ead7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Tue, 11 Feb 2025 22:28:45 +0800 Subject: [PATCH 15/32] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=EF=BC=8C=E9=BB=98=E8=AE=A4=E5=80=BC=EF=BC=8C?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 34 ++++++++++++++++++++-------------- parser_test.go | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/generate.go b/generate.go index 83c31c1..dd30e0e 100644 --- a/generate.go +++ b/generate.go @@ -329,25 +329,29 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType.Field(i).Type.Kind() == reflect.Slice { if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { // 针对基础类型指针 - g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ - Type: g.realBaseType2SwaggerType(convertType), - Format: inputType.Field(i).Type.String(), + g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + Type: g.realBaseType2SwaggerType(convertType), + Format: inputType.Field(i).Type.String(), + Default: g.getDefaultValue(inputType.Field(i)), + Description: g.getParamDesc(inputType.Field(i)), } continue } - g.AddComponentsSchema(schemaName, schemaName+inputType.Field(i).Name, inputType.Field(i).Type) + g.AddComponentsSchema(schemaName, schemaName+g.getParamName(inputType.Field(i)), inputType.Field(i).Type) if inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map { - g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ - Type: consts.SwaggerDataTypeObject, - Format: inputType.Field(i).Type.String(), - Properties: map[string]*define.Property{}, + g.docData.Components.Schemas[schemaName].Properties[schemaName+g.getParamName(inputType.Field(i))] = &define.Property{ + Type: consts.SwaggerDataTypeObject, + Format: inputType.Field(i).Type.String(), + Description: g.getParamDesc(inputType.Field(i)), + Properties: map[string]*define.Property{}, } } else if inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.docData.Components.Schemas[schemaName].Properties[schemaName+inputType.Field(i).Name] = &define.Property{ - Type: consts.SwaggerDataTypeArray, - Format: inputType.Field(i).Type.String(), + g.docData.Components.Schemas[schemaName].Properties[schemaName+g.getParamName(inputType.Field(i))] = &define.Property{ + Type: consts.SwaggerDataTypeArray, + Format: inputType.Field(i).Type.String(), + Description: g.getParamDesc(inputType.Field(i)), Items: &define.PropertyXOf{ Ref: g.parseSliceItem(schemaName, inputType.Field(i).Type), }, @@ -358,9 +362,11 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } } else { - g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Name] = &define.Property{ - Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), - Format: inputType.Field(i).Type.String(), + g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), + Format: inputType.Field(i).Type.String(), + Default: g.getDefaultValue(inputType.Field(i)), + Description: g.getParamDesc(inputType.Field(i)), } } } diff --git a/parser_test.go b/parser_test.go index 6c947a8..15b5c3f 100644 --- a/parser_test.go +++ b/parser_test.go @@ -24,7 +24,7 @@ import ( // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { type A struct { - Name string `json:"name"` + Name string `json:"name" d:"zhang" desc:"用户姓名"` } type B struct { List []A `json:"list"` From aff4c86cb0e2e977ca0fa46e986cb63add3e110c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 12 Feb 2025 21:55:35 +0800 Subject: [PATCH 16/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 47 ++++++++++++++++++----------------------------- parser_test.go | 14 ++++++++------ 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/generate.go b/generate.go index dd30e0e..65eabf5 100644 --- a/generate.go +++ b/generate.go @@ -189,14 +189,18 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r return nil } // post类解析 - defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() - paramPkgPath := defaultPkgPath + baseCfg.Method + "Param" - resultPkgPath := defaultPkgPath + baseCfg.Method + "Result" - g.AddComponentsSchema("", paramPkgPath, paramType) - g.AddComponentsSchema("", resultPkgPath, resultType) + if paramType.Kind() == reflect.Ptr { + paramType = paramType.Elem() + } + if resultType.Kind() == reflect.Ptr { + resultType = resultType.Elem() + } + g.AddComponentsSchema("", paramType.PkgPath(), paramType) + g.AddComponentsSchema("", resultType.PkgPath(), resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } + defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() cfg := &define.PathItemOperationConfig{ Tags: baseCfg.TagList, Summary: baseCfg.Summary, @@ -223,7 +227,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r for _, itemType := range baseCfg.ContentType { cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ - Ref: g.getSchemaRes(paramPkgPath), + Ref: g.getSchemaRes(paramType.Name()), }, Example: "", Examples: nil, @@ -233,7 +237,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r for _, itemOutputType := range baseCfg.OutputContentType { cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ Schema: &define.Schema{ - Ref: g.getSchemaRes(resultPkgPath), + Ref: g.getSchemaRes(resultType.Name()), }, Example: "", Examples: nil, @@ -269,22 +273,6 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType reflect.Type) string { - if rootSchemaName != "" { - g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ - Type: g.realType2SwaggerType(inputType.String()), - Format: inputType.String(), - Enum: nil, - Default: "", - Description: "", - AllOf: nil, - OneOf: nil, - AnyOf: nil, - Items: nil, - AdditionalProperties: nil, - Properties: nil, - Ref: g.getSchemaRes(schemaName), - } - } if _, exist := g.docData.Components.Schemas[schemaName]; !exist { g.docData.Components.Schemas[schemaName] = &define.Schema{ Nullable: false, @@ -299,7 +287,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, Required: make([]string, 0), Enum: make([]any, 0), Type: "", - Ref: "", + Ref: g.getSchemaRes(schemaName), } } if inputType.Kind() == reflect.Map { @@ -311,7 +299,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray sliceItemType := g.parseSliceItem(schemaName, inputType) - g.docData.Components.Schemas[schemaName].Properties[inputType.Name()] = &define.Property{ + g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.String(), Items: &define.PropertyXOf{Ref: g.getSchemaRes(sliceItemType)}, @@ -337,10 +325,9 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } continue } - g.AddComponentsSchema(schemaName, schemaName+g.getParamName(inputType.Field(i)), inputType.Field(i).Type) if inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map { - g.docData.Components.Schemas[schemaName].Properties[schemaName+g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ Type: consts.SwaggerDataTypeObject, Format: inputType.Field(i).Type.String(), Description: g.getParamDesc(inputType.Field(i)), @@ -348,7 +335,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } } else if inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.docData.Components.Schemas[schemaName].Properties[schemaName+g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.Field(i).Type.String(), Description: g.getParamDesc(inputType.Field(i)), @@ -359,6 +346,8 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } } else if inputType.Field(i).Type.Kind() == reflect.Ptr { + } else { + g.AddComponentsSchema(schemaName, g.getParamName(inputType.Field(i)), inputType.Field(i).Type) } } else { @@ -377,7 +366,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, convertType := g.realBaseType2SwaggerType(inputType.String()) if convertType == inputType.String() { // 非基础数据类型 - return g.AddComponentsSchema(schemaName, schemaName+inputType.Elem().String(), inputType.Elem()) + return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem()) } else { g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ Type: convertType, diff --git a/parser_test.go b/parser_test.go index 15b5c3f..1caf209 100644 --- a/parser_test.go +++ b/parser_test.go @@ -23,13 +23,15 @@ import ( // // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { - type A struct { + type User struct { Name string `json:"name" d:"zhang" desc:"用户姓名"` + Age int `json:"age" d:"18" desc:"年龄"` } - type B struct { - List []A `json:"list"` + type List struct { + Total int64 `json:"total"` + UserList []User `json:"user_list"` } - var bArr *B + var l List g := NewOpenapiDoc(nil, nil) g.AddApiFromInAndOut(&define.UriBaseConfig{ Uri: "/ent/user/detail", @@ -41,10 +43,10 @@ func Test_parser_Openapi3(t *testing.T) { Description: "", ParamList: nil, ResultList: nil, - }, reflect.TypeOf(bArr), reflect.TypeOf(bArr)) + }, reflect.TypeOf(l), reflect.TypeOf(l)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) - fmt.Println(g.parseSliceItem("", reflect.TypeOf(bArr))) + fmt.Println(g.parseSliceItem("", reflect.TypeOf(l))) } func TestParseForSwagger(t *testing.T) { From 8c8428534721ac03b2f8b2f689b3573e3208017b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 12 Feb 2025 22:15:14 +0800 Subject: [PATCH 17/32] fix --- define/openapi.go | 1 + generate.go | 41 ++++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index 404a772..7ed7962 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -131,6 +131,7 @@ type Schema struct { Required []string `json:"required,omitempty"` // 必传属性列表 Enum []any `json:"enum,omitempty"` // 枚举值列表 Type string `json:"type,omitempty"` // 类型 + Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。 Ref string `json:"$ref,omitempty"` // 类型引用 } diff --git a/generate.go b/generate.go index 65eabf5..e23cecd 100644 --- a/generate.go +++ b/generate.go @@ -195,8 +195,8 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r if resultType.Kind() == reflect.Ptr { resultType = resultType.Elem() } - g.AddComponentsSchema("", paramType.PkgPath(), paramType) - g.AddComponentsSchema("", resultType.PkgPath(), resultType) + paramSchemaName := g.AddComponentsSchema("", strings.ReplaceAll(paramType.PkgPath(), "/", "-"), paramType) + resultSchemaName := g.AddComponentsSchema("", strings.ReplaceAll(resultType.PkgPath(), "/", "-"), resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } @@ -227,7 +227,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r for _, itemType := range baseCfg.ContentType { cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ - Ref: g.getSchemaRes(paramType.Name()), + Ref: g.getSchemaRef(paramSchemaName), }, Example: "", Examples: nil, @@ -237,7 +237,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r for _, itemOutputType := range baseCfg.OutputContentType { cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ Schema: &define.Schema{ - Ref: g.getSchemaRes(resultType.Name()), + Ref: g.getSchemaRef(resultSchemaName), }, Example: "", Examples: nil, @@ -272,7 +272,8 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 -func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, inputType reflect.Type) string { +func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string { + schemaName := pkgPath + "." + inputType.Name() if _, exist := g.docData.Components.Schemas[schemaName]; !exist { g.docData.Components.Schemas[schemaName] = &define.Schema{ Nullable: false, @@ -287,7 +288,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, Required: make([]string, 0), Enum: make([]any, 0), Type: "", - Ref: g.getSchemaRes(schemaName), + Ref: g.getSchemaRef(schemaName), } } if inputType.Kind() == reflect.Map { @@ -297,13 +298,19 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, schemaName string, } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { - g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray - sliceItemType := g.parseSliceItem(schemaName, inputType) - g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ - Type: consts.SwaggerDataTypeArray, - Format: inputType.String(), - Items: &define.PropertyXOf{Ref: g.getSchemaRes(sliceItemType)}, + if len(rootSchemaName) == 0 { + g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray + sliceItemType := g.parseSliceItem(schemaName, inputType) + g.docData.Components.Schemas[schemaName].Items = &define.PropertyXOf{Ref: g.getSchemaRef(g.getSchemaRef(sliceItemType))} + } else { + sliceItemType := g.parseSliceItem(schemaName, inputType) + g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ + Type: consts.SwaggerDataTypeArray, + Format: inputType.String(), + Items: &define.PropertyXOf{Ref: g.getSchemaRef(sliceItemType)}, + } } + return schemaName } // 结构体 @@ -391,7 +398,7 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceItemType := sliceValue.Index(0).Type() g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) - if rootSchemaName != "" { + /* if rootSchemaName != "" { g.docData.Components.Schemas[rootSchemaName].Properties[sliceItemType.PkgPath()] = &define.Property{ Type: "", Format: inputType.String(), @@ -404,21 +411,21 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) Items: nil, AdditionalProperties: nil, Properties: nil, - Ref: g.getSchemaRes(sliceItemType.PkgPath()), + Ref: g.getSchemaRef(sliceItemType.PkgPath()), } - } + }*/ if len(sliceItemType.PkgPath()) == 0 { return sliceItemType.String() } return sliceItemType.PkgPath() + "." + sliceItemType.String() } -// getSchemaRes 获取引用的类型 +// getSchemaRef 获取引用的类型 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:25 2025/2/9 -func (g *Generate) getSchemaRes(schemaName string) string { +func (g *Generate) getSchemaRef(schemaName string) string { if "" == schemaName { return "" } From 77c9e06b39cc1a406aec6076fca1c6905b2ba90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 12 Feb 2025 22:20:50 +0800 Subject: [PATCH 18/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 75 ++++++++----------------------------------------- struct_field.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 struct_field.go diff --git a/generate.go b/generate.go index e23cecd..00be0b9 100644 --- a/generate.go +++ b/generate.go @@ -324,28 +324,28 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in inputType.Field(i).Type.Kind() == reflect.Slice { if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { // 针对基础类型指针 - g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ Type: g.realBaseType2SwaggerType(convertType), Format: inputType.Field(i).Type.String(), - Default: g.getDefaultValue(inputType.Field(i)), - Description: g.getParamDesc(inputType.Field(i)), + Default: ParseStructField.GetDefaultValue(inputType.Field(i)), + Description: ParseStructField.GetParamDesc(inputType.Field(i)), } continue } if inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map { - g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ Type: consts.SwaggerDataTypeObject, Format: inputType.Field(i).Type.String(), - Description: g.getParamDesc(inputType.Field(i)), + Description: ParseStructField.GetParamDesc(inputType.Field(i)), Properties: map[string]*define.Property{}, } } else if inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.Field(i).Type.String(), - Description: g.getParamDesc(inputType.Field(i)), + Description: ParseStructField.GetParamDesc(inputType.Field(i)), Items: &define.PropertyXOf{ Ref: g.parseSliceItem(schemaName, inputType.Field(i).Type), }, @@ -354,15 +354,15 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } else if inputType.Field(i).Type.Kind() == reflect.Ptr { } else { - g.AddComponentsSchema(schemaName, g.getParamName(inputType.Field(i)), inputType.Field(i).Type) + g.AddComponentsSchema(schemaName, ParseStructField.GetParamName(inputType.Field(i)), inputType.Field(i).Type) } } else { - g.docData.Components.Schemas[schemaName].Properties[g.getParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), Format: inputType.Field(i).Type.String(), - Default: g.getDefaultValue(inputType.Field(i)), - Description: g.getParamDesc(inputType.Field(i)), + Default: ParseStructField.GetDefaultValue(inputType.Field(i)), + Description: ParseStructField.GetParamDesc(inputType.Field(i)), } } } @@ -467,56 +467,3 @@ func (g *Generate) realType2SwaggerType(realType string) string { } return g.realBaseType2SwaggerType(realType) } - -// getParamName 获取参数名称 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 21:58 2025/2/11 -func (g *Generate) getParamName(structField reflect.StructField) string { - paramNameTagList := []string{ - define.TagJson, define.TagForm, - define.TagXml, define.TagYaml, - define.TagYml, - } - for _, tag := range paramNameTagList { - tagVal := structField.Tag.Get(tag) - if tagVal != "" { - return tagVal - } - } - // 未设置相关字段, 则字段名即为参数名 - return structField.Name -} - -// getParamDesc ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 22:01 2025/2/11 -func (g *Generate) getParamDesc(structField reflect.StructField) string { - descTagList := []string{define.TagDesc, define.TagDescription} - for _, tag := range descTagList { - tagVal := structField.Tag.Get(tag) - if tagVal != "" { - return tagVal - } - } - // 没有显示的设置参数描述, 则使用参数名作为参数描述 - return g.getParamName(structField) -} - -// getDefaultValue 获取默认值 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 22:05 2025/2/11 -func (g *Generate) getDefaultValue(structField reflect.StructField) string { - defaultTagList := []string{define.TagD, define.TagDefault} - for _, tag := range defaultTagList { - if tagVal, exist := structField.Tag.Lookup(tag); exist { - return tagVal - } - } - return "" -} diff --git a/struct_field.go b/struct_field.go new file mode 100644 index 0000000..e672641 --- /dev/null +++ b/struct_field.go @@ -0,0 +1,73 @@ +// Package api_doc ... +// +// Description : api_doc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-02-12 22:15 +package api_doc + +import ( + "git.zhangdeman.cn/gateway/api-doc/define" + "reflect" +) + +var ( + ParseStructField = parseStructField{} +) + +type parseStructField struct { +} + +// GetParamName 获取参数名称 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:58 2025/2/11 +func (psf parseStructField) GetParamName(structField reflect.StructField) string { + paramNameTagList := []string{ + define.TagJson, define.TagForm, + define.TagXml, define.TagYaml, + define.TagYml, + } + for _, tag := range paramNameTagList { + tagVal := structField.Tag.Get(tag) + if tagVal != "" { + return tagVal + } + } + // 未设置相关字段, 则字段名即为参数名 + return structField.Name +} + +// GetParamDesc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:01 2025/2/11 +func (psf parseStructField) GetParamDesc(structField reflect.StructField) string { + descTagList := []string{define.TagDesc, define.TagDescription} + for _, tag := range descTagList { + tagVal := structField.Tag.Get(tag) + if tagVal != "" { + return tagVal + } + } + // 没有显示的设置参数描述, 则使用参数名作为参数描述 + return psf.GetParamName(structField) +} + +// GetDefaultValue 获取默认值 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:05 2025/2/11 +func (psf parseStructField) GetDefaultValue(structField reflect.StructField) string { + defaultTagList := []string{define.TagD, define.TagDefault} + for _, tag := range defaultTagList { + if tagVal, exist := structField.Tag.Lookup(tag); exist { + return tagVal + } + } + return "" +} From 8a7999a4803a8a2626f8c61b9fe71e74931c9687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 12 Feb 2025 22:46:48 +0800 Subject: [PATCH 19/32] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=80=E8=88=AC?= =?UTF-8?q?=E6=9C=AC=E6=8E=A5=E5=8F=A3=E6=96=87=E6=A1=A3=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/generate.go b/generate.go index 00be0b9..cf9916f 100644 --- a/generate.go +++ b/generate.go @@ -195,12 +195,12 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r if resultType.Kind() == reflect.Ptr { resultType = resultType.Elem() } - paramSchemaName := g.AddComponentsSchema("", strings.ReplaceAll(paramType.PkgPath(), "/", "-"), paramType) - resultSchemaName := g.AddComponentsSchema("", strings.ReplaceAll(resultType.PkgPath(), "/", "-"), resultType) + paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) + resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } - defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() + defaultPkgPath := wrapper.String(strings.TrimLeft(baseCfg.Uri, "/")).SnakeCaseToCamel() cfg := &define.PathItemOperationConfig{ Tags: baseCfg.TagList, Summary: baseCfg.Summary, @@ -273,9 +273,9 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string { - schemaName := pkgPath + "." + inputType.Name() + schemaName := strings.ReplaceAll(pkgPath+"."+inputType.Name(), "/", "-") if _, exist := g.docData.Components.Schemas[schemaName]; !exist { - g.docData.Components.Schemas[schemaName] = &define.Schema{ + s := &define.Schema{ Nullable: false, Discriminator: nil, ReadOnly: false, @@ -290,6 +290,10 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in Type: "", Ref: g.getSchemaRef(schemaName), } + if len(rootSchemaName) == 0 || inputType.Kind() == reflect.Struct { + s.Ref = "" + } + g.docData.Components.Schemas[schemaName] = s } if inputType.Kind() == reflect.Map { // map, 直接添加公共 @@ -315,7 +319,12 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } // 结构体 if inputType.Kind() == reflect.Struct { - g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeObject + if len(rootSchemaName) == 0 { + + } else { + // g.docData.Components.Schemas[schemaName].Properties[""] = schemaName + } + // g.docData.Components.Schemas[schemaName].Ref = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || @@ -429,7 +438,7 @@ func (g *Generate) getSchemaRef(schemaName string) string { if "" == schemaName { return "" } - return "#/components/schemas/" + schemaName + return "#/components/schemas/" + strings.ReplaceAll(schemaName, "/", "-") } // realType2SwaggerType golang 真实数据类型转换为golang数据类型 @@ -448,7 +457,7 @@ func (g *Generate) realBaseType2SwaggerType(realType string) string { case "float32", "*float32", "float64", "*float64": return consts.SwaggerDataTypeNumber case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": - return consts.SwaggerDataTypeLong + return consts.SwaggerDataTypeNumber case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": return consts.SwaggerDataTypeInteger default: From f82c69101197dd6031d6b3f7e0ca672de84305fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 11:37:48 +0800 Subject: [PATCH 20/32] =?UTF-8?q?=E4=BC=98=E5=8C=96ref=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/generate.go b/generate.go index cf9916f..06f8dfa 100644 --- a/generate.go +++ b/generate.go @@ -206,7 +206,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r Summary: baseCfg.Summary, Description: baseCfg.Description, ExternalDocs: nil, - OperationID: defaultPkgPath + baseCfg.Method, + OperationID: baseCfg.Method + "-" + defaultPkgPath, Parameters: nil, RequestBody: &define.RequestBody{ Required: true, @@ -287,7 +287,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in Properties: make(map[string]*define.Property), Required: make([]string, 0), Enum: make([]any, 0), - Type: "", + Type: consts.SwaggerDataTypeObject, Ref: g.getSchemaRef(schemaName), } if len(rootSchemaName) == 0 || inputType.Kind() == reflect.Struct { @@ -305,7 +305,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in if len(rootSchemaName) == 0 { g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray sliceItemType := g.parseSliceItem(schemaName, inputType) - g.docData.Components.Schemas[schemaName].Items = &define.PropertyXOf{Ref: g.getSchemaRef(g.getSchemaRef(sliceItemType))} + g.docData.Components.Schemas[schemaName].Items = &define.PropertyXOf{Ref: g.getSchemaRef(sliceItemType)} } else { sliceItemType := g.parseSliceItem(schemaName, inputType) g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ @@ -356,7 +356,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in Format: inputType.Field(i).Type.String(), Description: ParseStructField.GetParamDesc(inputType.Field(i)), Items: &define.PropertyXOf{ - Ref: g.parseSliceItem(schemaName, inputType.Field(i).Type), + Ref: g.getSchemaRef(g.parseSliceItem(schemaName, inputType.Field(i).Type)), }, Properties: map[string]*define.Property{}, } @@ -407,22 +407,6 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceItemType := sliceValue.Index(0).Type() g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) - /* if rootSchemaName != "" { - g.docData.Components.Schemas[rootSchemaName].Properties[sliceItemType.PkgPath()] = &define.Property{ - Type: "", - Format: inputType.String(), - Enum: nil, - Default: "", - Description: "", - AllOf: nil, - OneOf: nil, - AnyOf: nil, - Items: nil, - AdditionalProperties: nil, - Properties: nil, - Ref: g.getSchemaRef(sliceItemType.PkgPath()), - } - }*/ if len(sliceItemType.PkgPath()) == 0 { return sliceItemType.String() } @@ -455,9 +439,9 @@ func (g *Generate) realBaseType2SwaggerType(realType string) string { case "byte", "*byte": return consts.SwaggerDataTypeByte case "float32", "*float32", "float64", "*float64": - return consts.SwaggerDataTypeNumber + return consts.SwaggerDataTypeDouble case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": - return consts.SwaggerDataTypeNumber + return consts.SwaggerDataTypeInteger case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": return consts.SwaggerDataTypeInteger default: From 0e4f5ae1e30c0ce86ac61f00fc4b2c4b7a917910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 11:44:00 +0800 Subject: [PATCH 21/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E7=BB=84ref?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E7=94=9F=E6=88=90=E9=94=99=E8=AF=AF=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/generate.go b/generate.go index 06f8dfa..d29c6e7 100644 --- a/generate.go +++ b/generate.go @@ -273,7 +273,9 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string { - schemaName := strings.ReplaceAll(pkgPath+"."+inputType.Name(), "/", "-") + inputNameArr := strings.Split(inputType.Name(), ".") + inputName := inputNameArr[len(inputNameArr)-1] + schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-") if _, exist := g.docData.Components.Schemas[schemaName]; !exist { s := &define.Schema{ Nullable: false, @@ -410,7 +412,7 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) if len(sliceItemType.PkgPath()) == 0 { return sliceItemType.String() } - return sliceItemType.PkgPath() + "." + sliceItemType.String() + return sliceItemType.PkgPath() + "." + sliceItemType.Name() } // getSchemaRef 获取引用的类型 From bc5a8afd6c28b152115b09b7eb8b592b850e0d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 16:03:31 +0800 Subject: [PATCH 22/32] =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BF=85=E4=BC=A0=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 25 +++++++++++----------- parser_test.go | 6 +++--- struct_field.go | 15 ++++++++++++++ validateRule.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 validateRule.go diff --git a/generate.go b/generate.go index d29c6e7..10c0ff7 100644 --- a/generate.go +++ b/generate.go @@ -278,19 +278,13 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-") if _, exist := g.docData.Components.Schemas[schemaName]; !exist { s := &define.Schema{ - Nullable: false, - Discriminator: nil, - ReadOnly: false, - WriteOnly: false, - Xml: nil, - ExternalDocs: nil, - Example: "", - Deprecated: false, - Properties: make(map[string]*define.Property), - Required: make([]string, 0), - Enum: make([]any, 0), - Type: consts.SwaggerDataTypeObject, - Ref: g.getSchemaRef(schemaName), + Nullable: false, + Deprecated: false, + Properties: make(map[string]*define.Property), + Required: make([]string, 0), + Enum: make([]any, 0), + Type: consts.SwaggerDataTypeObject, // TODO : 区分数组 + Ref: g.getSchemaRef(schemaName), } if len(rootSchemaName) == 0 || inputType.Kind() == reflect.Struct { s.Ref = "" @@ -328,6 +322,11 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } // g.docData.Components.Schemas[schemaName].Ref = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { + if ValidateRule.IsRequired(inputType.Field(i)) { + // 必传字段 + g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, ParseStructField.GetParamName(inputType.Field(i))) + } + if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map || diff --git a/parser_test.go b/parser_test.go index 1caf209..25c293d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -24,12 +24,12 @@ import ( // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { type User struct { - Name string `json:"name" d:"zhang" desc:"用户姓名"` - Age int `json:"age" d:"18" desc:"年龄"` + Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` + Age int `json:"age" d:"18" desc:"年龄" binding:"required"` } type List struct { Total int64 `json:"total"` - UserList []User `json:"user_list"` + UserList []User `json:"user_list" binding:"required"` } var l List g := NewOpenapiDoc(nil, nil) diff --git a/struct_field.go b/struct_field.go index e672641..1d4b977 100644 --- a/struct_field.go +++ b/struct_field.go @@ -71,3 +71,18 @@ func (psf parseStructField) GetDefaultValue(structField reflect.StructField) str } return "" } + +// GetValidateRule 获取验证规则 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:30 2025/2/13 +func (psf parseStructField) GetValidateRule(structField reflect.StructField) string { + defaultTagList := []string{define.TagValidate, define.TagBinding} + for _, tag := range defaultTagList { + if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 { + return tagVal + } + } + return "" +} diff --git a/validateRule.go b/validateRule.go new file mode 100644 index 0000000..92bef26 --- /dev/null +++ b/validateRule.go @@ -0,0 +1,55 @@ +// Package api_doc ... +// +// Description : api_doc ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-02-13 15:26 +package api_doc + +import ( + "git.zhangdeman.cn/zhangdeman/consts" + "reflect" + "strings" +) + +var ( + ValidateRule = validateRule{} +) + +type validateRule struct{} + +// IsRequired 判断是否必传 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:32 2025/2/13 +func (r validateRule) IsRequired(structField reflect.StructField) bool { + ruleTable := r.getValidateRuleTable(structField) + _, exist := ruleTable[consts.ValidatorRuleCommonRequired.String()] + // 存在即为必传 + return exist +} + +// getValidateRuleTable 解析验证规则表 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:29 2025/2/13 +func (r validateRule) getValidateRuleTable(structField reflect.StructField) map[string]string { + res := map[string]string{} + ruleStr := ParseStructField.GetValidateRule(structField) + if len(ruleStr) == 0 { + return res + } + expressList := strings.Split(ruleStr, ",") + for _, item := range expressList { + if strings.Contains(item, "=") { + arr := strings.Split(item, "=") + res[strings.TrimSpace(arr[0])] = strings.Join(arr[1:], "=") + } else { + res[strings.TrimSpace(item)] = "" + } + } + return res +} From 7307c85cfd94a3064e04e90be2bbc835570756cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 16:18:42 +0800 Subject: [PATCH 23/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BF=85=E4=BC=A0?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E9=87=8D=E5=A4=8D=E8=A7=A3=E6=9E=90=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20+=20=E4=BC=98=E5=8C=96=E7=9B=B8=E5=90=8C=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=BD=93=E6=9C=AA=E5=8E=BB=E9=87=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 22 ++++++++++++++++++---- parser_test.go | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/generate.go b/generate.go index 10c0ff7..8f969ae 100644 --- a/generate.go +++ b/generate.go @@ -276,6 +276,10 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in inputNameArr := strings.Split(inputType.Name(), ".") inputName := inputNameArr[len(inputNameArr)-1] schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-") + if _, exist := g.docData.Components.Schemas[schemaName]; exist { + // 已存在, 无需重复生成 + return schemaName + } if _, exist := g.docData.Components.Schemas[schemaName]; !exist { s := &define.Schema{ Nullable: false, @@ -322,10 +326,8 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } // g.docData.Components.Schemas[schemaName].Ref = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { - if ValidateRule.IsRequired(inputType.Field(i)) { - // 必传字段 - g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, ParseStructField.GetParamName(inputType.Field(i))) - } + // 设置参数各种属性 + g.setStructFieldProperty(schemaName, inputType.Field(i)) if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || @@ -461,3 +463,15 @@ func (g *Generate) realType2SwaggerType(realType string) string { } return g.realBaseType2SwaggerType(realType) } + +// setStructFieldProperty 添加struct_field各种属性 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:13 2025/2/13 +func (g *Generate) setStructFieldProperty(schemaName string, structField reflect.StructField) { + paramName := ParseStructField.GetParamName(structField) + if ValidateRule.IsRequired(structField) { + g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName) + } +} diff --git a/parser_test.go b/parser_test.go index 25c293d..edda094 100644 --- a/parser_test.go +++ b/parser_test.go @@ -28,8 +28,8 @@ func Test_parser_Openapi3(t *testing.T) { Age int `json:"age" d:"18" desc:"年龄" binding:"required"` } type List struct { - Total int64 `json:"total"` - UserList []User `json:"user_list" binding:"required"` + Total int64 `json:"total" binding:"required"` + UserList []User `json:"user_list"` } var l List g := NewOpenapiDoc(nil, nil) From f1c0e473f4d2a0547b0b2d0b132d64b9cb38aae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 18:27:31 +0800 Subject: [PATCH 24/32] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=9E=9A=E4=B8=BE?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 6 +++--- parser_test.go | 2 +- validateRule.go | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/generate.go b/generate.go index 8f969ae..fa44ee6 100644 --- a/generate.go +++ b/generate.go @@ -326,9 +326,6 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } // g.docData.Components.Schemas[schemaName].Ref = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { - // 设置参数各种属性 - g.setStructFieldProperty(schemaName, inputType.Field(i)) - if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map || @@ -377,6 +374,8 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in Description: ParseStructField.GetParamDesc(inputType.Field(i)), } } + // 设置参数各种属性 + g.setStructFieldProperty(schemaName, inputType.Field(i)) } return schemaName } @@ -474,4 +473,5 @@ func (g *Generate) setStructFieldProperty(schemaName string, structField reflect if ValidateRule.IsRequired(structField) { g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName) } + g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(structField)].Enum = ValidateRule.Enum(structField) } diff --git a/parser_test.go b/parser_test.go index edda094..4bac3f7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -25,7 +25,7 @@ import ( func Test_parser_Openapi3(t *testing.T) { type User struct { Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` - Age int `json:"age" d:"18" desc:"年龄" binding:"required"` + Age int `json:"age" d:"18" desc:"年龄" binding:"required,oneof:12 13 18 90"` } type List struct { Total int64 `json:"total" binding:"required"` diff --git a/validateRule.go b/validateRule.go index 92bef26..6a6f868 100644 --- a/validateRule.go +++ b/validateRule.go @@ -31,6 +31,20 @@ func (r validateRule) IsRequired(structField reflect.StructField) bool { return exist } +// Enum 获取枚举值 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 17:23 2025/2/13 +func (r validateRule) Enum(structField reflect.StructField) []string { + ruleTable := r.getValidateRuleTable(structField) + oneOfValue, _ := ruleTable[consts.ValidatorRuleCommonOneOf.String()] + if len(oneOfValue) == 0 { + return []string{} + } + return strings.Split(oneOfValue, " ") +} + // getValidateRuleTable 解析验证规则表 // // Author : go_developer@163.com<白茶清欢> From 45a25e0018979400168e9a3042027c857c96b498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 21:11:27 +0800 Subject: [PATCH 25/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=E8=A7=A3=E6=9E=90=E4=BB=A5=E5=8F=8A=E6=9E=9A=E4=B8=BE?= =?UTF-8?q?=E5=80=BC=E8=A7=A3=E6=9E=90=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 4 +-- parser_test.go | 2 +- struct_field.go | 33 +++++++++++++++++++++--- validateRule.go => validate_rule.go | 39 ++++++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 10 deletions(-) rename validateRule.go => validate_rule.go (61%) diff --git a/define/openapi.go b/define/openapi.go index 7ed7962..09f1adb 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -143,8 +143,8 @@ type Schema struct { type Property struct { Type string `json:"type,omitempty"` // 数据类型, swagger本身的定义 Format string `json:"format,omitempty"` // 对应编程语言中的数据类型描述 - Enum []string `json:"enum,omitempty"` // 枚举值列表 - Default string `json:"default,omitempty"` // 默认值 : 不同于 JSON Schema,这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string,那么 default 可以是 "foo" 但不能是 1。 + Enum []any `json:"enum,omitempty"` // 枚举值列表 + Default any `json:"default,omitempty"` // 默认值 : 不同于 JSON Schema,这个值必须符合定义与相同级别的 Schema 对象 中定义的类型,比如 type 是 string,那么 default 可以是 "foo" 但不能是 1。 Description string `json:"description,omitempty"` // 数据描述, CommonMark syntax可以被用来呈现富文本格式. AllOf []*PropertyXOf `json:"allOf,omitempty"` // type 是一个对象, allOf 指向对象描述 OneOf []*PropertyXOf `json:"oneOf,omitempty"` // type 是一个对象, allOf 指向对象描述 diff --git a/parser_test.go b/parser_test.go index 4bac3f7..1d5c7f6 100644 --- a/parser_test.go +++ b/parser_test.go @@ -25,7 +25,7 @@ import ( func Test_parser_Openapi3(t *testing.T) { type User struct { Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` - Age int `json:"age" d:"18" desc:"年龄" binding:"required,oneof:12 13 18 90"` + Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } type List struct { Total int64 `json:"total" binding:"required"` diff --git a/struct_field.go b/struct_field.go index 1d4b977..20bdeb0 100644 --- a/struct_field.go +++ b/struct_field.go @@ -10,6 +10,8 @@ package api_doc import ( "git.zhangdeman.cn/gateway/api-doc/define" "reflect" + "strconv" + "strings" ) var ( @@ -62,14 +64,37 @@ func (psf parseStructField) GetParamDesc(structField reflect.StructField) string // Author : go_developer@163.com<白茶清欢> // // Date : 22:05 2025/2/11 -func (psf parseStructField) GetDefaultValue(structField reflect.StructField) string { +func (psf parseStructField) GetDefaultValue(structField reflect.StructField) any { defaultTagList := []string{define.TagD, define.TagDefault} + fieldType := structField.Type.Kind().String() for _, tag := range defaultTagList { - if tagVal, exist := structField.Tag.Lookup(tag); exist { - return tagVal + if val, exist := structField.Tag.Lookup(tag); exist && val != "" { + if strings.HasPrefix(fieldType, "int") { + i, _ := strconv.Atoi(val) + return i + } + if strings.HasPrefix(fieldType, "uint") { + uintVal, _ := strconv.ParseUint(val, 10, 64) + return uintVal + } + if strings.HasPrefix(fieldType, "float") { + floatVal, _ := strconv.ParseFloat(val, 64) + return floatVal + } + if strings.HasPrefix(fieldType, "string") { + return val + } + if strings.HasPrefix(fieldType, "bool") { + if val == "true" { + return true + } else { + return false + } + } + return val } } - return "" + return nil } // GetValidateRule 获取验证规则 diff --git a/validateRule.go b/validate_rule.go similarity index 61% rename from validateRule.go rename to validate_rule.go index 6a6f868..a4fa52c 100644 --- a/validateRule.go +++ b/validate_rule.go @@ -10,6 +10,7 @@ package api_doc import ( "git.zhangdeman.cn/zhangdeman/consts" "reflect" + "strconv" "strings" ) @@ -36,13 +37,45 @@ func (r validateRule) IsRequired(structField reflect.StructField) bool { // Author : go_developer@163.com<白茶清欢> // // Date : 17:23 2025/2/13 -func (r validateRule) Enum(structField reflect.StructField) []string { +func (r validateRule) Enum(structField reflect.StructField) []any { ruleTable := r.getValidateRuleTable(structField) oneOfValue, _ := ruleTable[consts.ValidatorRuleCommonOneOf.String()] if len(oneOfValue) == 0 { - return []string{} + return []any{} } - return strings.Split(oneOfValue, " ") + fieldType := structField.Type.Kind().String() + valStrArr := strings.Split(oneOfValue, " ") + anySlice := make([]any, 0) + for _, val := range valStrArr { + if strings.HasPrefix(fieldType, "int") { + i, _ := strconv.Atoi(val) + anySlice = append(anySlice, i) + continue + } + if strings.HasPrefix(fieldType, "uint") { + uintVal, _ := strconv.ParseUint(val, 10, 64) + anySlice = append(anySlice, uintVal) + continue + } + if strings.HasPrefix(fieldType, "float") { + floatVal, _ := strconv.ParseFloat(val, 64) + anySlice = append(anySlice, floatVal) + continue + } + if strings.HasPrefix(fieldType, "string") { + anySlice = append(anySlice, val) + continue + } + if strings.HasPrefix(fieldType, "bool") { + if val == "true" { + anySlice = append(anySlice, true) + } else { + anySlice = append(anySlice, false) + } + continue + } + } + return anySlice } // getValidateRuleTable 解析验证规则表 From e2da9231a12ee6f5917e90e84b96cebacd7c780e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 13 Feb 2025 21:57:18 +0800 Subject: [PATCH 26/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E4=BF=A1=E6=81=AF=E8=A7=A3=E6=9E=90=20+=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E6=8E=A5=E5=8F=A3=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=BC=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/generate.go | 2 ++ define/tag.go | 14 ++++++++++++ define/uri.go | 20 +++++++++++++++++ generate.go | 55 ++++++++++++++++++++++++++++++++++++++++------ parser_test.go | 23 +++++++------------ struct_field.go | 15 +++++++++++++ 6 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 define/uri.go diff --git a/define/generate.go b/define/generate.go index 66804d7..4aad3d6 100644 --- a/define/generate.go +++ b/define/generate.go @@ -37,6 +37,8 @@ type UriBaseConfig struct { Description string `json:"description"` // 接口详细描述 ParamList []*ParamConfig `json:"param_list"` // 参数列表 ResultList []*ResultConfig `json:"result_list"` // 返回值列表 + Deprecated bool `json:"deprecated"` // 是否弃用 + OutputStrict bool `json:"output_strict"` // 严格模式 } // ParamConfig 参数配置 diff --git a/define/tag.go b/define/tag.go index 5528dcb..ac42381 100644 --- a/define/tag.go +++ b/define/tag.go @@ -21,4 +21,18 @@ const ( TagDescription = "description" TagD = "d" TagDefault = "default" + TagDeprecated = "deprecated" +) + +const ( + TagNamePath = "path" // 接口的请求路径 + TagNameMethod = "method" // 接口的请求方法 + TagNameUriTag = "tag" // 接口的tag + TagNameDesc = "desc" // 接口的描述 + TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 + TagNameBinding = "binding" // gin 内置的验证规则tag + TagNameValidate = "validate" // validator v10 默认的验证规则tag + TagNameErrMsg = "err" // 验证失败错误信息tag + TagNameContentType = "content_type" + TagNameOutputContentType = "output_content_type" ) diff --git a/define/uri.go b/define/uri.go new file mode 100644 index 0000000..72b15e5 --- /dev/null +++ b/define/uri.go @@ -0,0 +1,20 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-02-13 21:29 +package define + +// UriConfig 接口基础配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:29 2025/2/13 +type UriConfig struct { + Path string `json:"path"` // 接口路由, 必须配置 + RequestMethod string `json:"request_method"` // 接口请求方法, 必须配置 + TagList []string `json:"tag_list"` // 接口分组 + Desc string `json:"desc"` // 接口描述 +} diff --git a/generate.go b/generate.go index fa44ee6..c97f6ed 100644 --- a/generate.go +++ b/generate.go @@ -170,7 +170,8 @@ func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.Par // 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 { +func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect.Type) error { + baseCfg := g.parseBaseUriConfig(paramType) if nil == baseCfg { return errors.New("baseCfg is nil") } @@ -220,7 +221,7 @@ func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType r }, }, Callbacks: nil, - Deprecated: false, + Deprecated: baseCfg.Deprecated, Security: nil, Servers: nil, } @@ -276,6 +277,10 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in inputNameArr := strings.Split(inputType.Name(), ".") inputName := inputNameArr[len(inputNameArr)-1] schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-") + if schemaName == "-" { + // 忽略的属性 + return schemaName + } if _, exist := g.docData.Components.Schemas[schemaName]; exist { // 已存在, 无需重复生成 return schemaName @@ -326,6 +331,10 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } // g.docData.Components.Schemas[schemaName].Ref = consts.SwaggerDataTypeObject for i := 0; i < inputType.NumField(); i++ { + propertyName := ParseStructField.GetParamName(inputType.Field(i)) + if propertyName == "-" { + continue + } if inputType.Field(i).Type.Kind() == reflect.Ptr || inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map || @@ -333,7 +342,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in inputType.Field(i).Type.Kind() == reflect.Slice { if convertType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { // 针对基础类型指针 - g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: g.realBaseType2SwaggerType(convertType), Format: inputType.Field(i).Type.String(), Default: ParseStructField.GetDefaultValue(inputType.Field(i)), @@ -343,7 +352,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } if inputType.Field(i).Type.Kind() == reflect.Struct || inputType.Field(i).Type.Kind() == reflect.Map { - g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: consts.SwaggerDataTypeObject, Format: inputType.Field(i).Type.String(), Description: ParseStructField.GetParamDesc(inputType.Field(i)), @@ -351,7 +360,7 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } } else if inputType.Field(i).Type.Kind() == reflect.Array || inputType.Field(i).Type.Kind() == reflect.Slice { - g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.Field(i).Type.String(), Description: ParseStructField.GetParamDesc(inputType.Field(i)), @@ -363,11 +372,11 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in } else if inputType.Field(i).Type.Kind() == reflect.Ptr { } else { - g.AddComponentsSchema(schemaName, ParseStructField.GetParamName(inputType.Field(i)), inputType.Field(i).Type) + g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type) } } else { - g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(inputType.Field(i))] = &define.Property{ + g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), Format: inputType.Field(i).Type.String(), Default: ParseStructField.GetDefaultValue(inputType.Field(i)), @@ -475,3 +484,35 @@ func (g *Generate) setStructFieldProperty(schemaName string, structField reflect } g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(structField)].Enum = ValidateRule.Enum(structField) } + +func (g *Generate) parseBaseUriConfig(paramType reflect.Type) *define.UriBaseConfig { + // 解析meta信息 + metaField, metaFieldExist := paramType.FieldByName("Meta") + if !metaFieldExist { + return nil + } + res := &define.UriBaseConfig{ + Uri: "", + Method: "", + ContentType: nil, + OutputContentType: nil, + TagList: nil, + Summary: "", + Description: "", + ParamList: nil, + ResultList: nil, + Deprecated: false, + } + res.Uri = metaField.Tag.Get(define.TagNamePath) + res.Method = metaField.Tag.Get(define.TagNameMethod) + res.Description = metaField.Tag.Get(define.TagNameDesc) + res.TagList = strings.Split(metaField.Tag.Get(define.TagNameUriTag), ",") + // 解析第一个返回值, 要求必须是结构体或者是map + outputStrictModel := metaField.Tag.Get(define.TagNameOutputStrict) + res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" + deprecated := metaField.Tag.Get(define.TagDeprecated) + res.Deprecated = deprecated == "1" || deprecated == "true" + res.ContentType = strings.Split(metaField.Tag.Get(define.TagNameContentType), ",") + res.OutputContentType = strings.Split(metaField.Tag.Get(define.TagNameOutputContentType), ",") + return res +} diff --git a/parser_test.go b/parser_test.go index 1d5c7f6..0d6ab83 100644 --- a/parser_test.go +++ b/parser_test.go @@ -10,13 +10,14 @@ package api_doc import ( "encoding/json" "fmt" - "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/serialize" - "net/http" "reflect" "testing" ) +type Meta struct { +} + // Test_parser_Openapi3 测试数据结构定义正确性 // // Author : go_developer@163.com<白茶清欢> @@ -24,6 +25,7 @@ import ( // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { type User struct { + Meta `json:"-" deprecated:"true" path:"/user/detail" method:"POST" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } @@ -31,22 +33,13 @@ func Test_parser_Openapi3(t *testing.T) { Total int64 `json:"total" binding:"required"` UserList []User `json:"user_list"` } - var l List + var o List + var f User g := NewOpenapiDoc(nil, nil) - g.AddApiFromInAndOut(&define.UriBaseConfig{ - Uri: "/ent/user/detail", - 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(l), reflect.TypeOf(l)) + g.AddApiFromInAndOut(reflect.TypeOf(f), reflect.TypeOf(o)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) - fmt.Println(g.parseSliceItem("", reflect.TypeOf(l))) + fmt.Println(g.parseSliceItem("", reflect.TypeOf(o))) } func TestParseForSwagger(t *testing.T) { diff --git a/struct_field.go b/struct_field.go index 20bdeb0..ea67dae 100644 --- a/struct_field.go +++ b/struct_field.go @@ -111,3 +111,18 @@ func (psf parseStructField) GetValidateRule(structField reflect.StructField) str } return "" } + +// Deprecated 是否弃用 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:12 2025/2/13 +func (psf parseStructField) Deprecated(structField reflect.StructField) bool { + defaultTagList := []string{define.TagDeprecated} + for _, tag := range defaultTagList { + if tagVal, exist := structField.Tag.Lookup(tag); exist && (tagVal == "1" || strings.ToLower(tagVal) == "true") { + return true + } + } + return false +} From d7cfa5fdd3ef2c53cbd662939e9f141493ae8866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 11:30:12 +0800 Subject: [PATCH 27/32] update generate --- generate.go | 2 ++ parser_test.go | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/generate.go b/generate.go index c97f6ed..e644edd 100644 --- a/generate.go +++ b/generate.go @@ -264,6 +264,8 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect g.docData.Paths[baseCfg.Uri].Options = cfg case http.MethodTrace: g.docData.Paths[baseCfg.Uri].Trace = cfg + default: + panic("unknown method: " + baseCfg.Method) } return nil } diff --git a/parser_test.go b/parser_test.go index 0d6ab83..806a7b8 100644 --- a/parser_test.go +++ b/parser_test.go @@ -10,6 +10,7 @@ package api_doc import ( "encoding/json" "fmt" + "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/serialize" "reflect" "testing" @@ -25,7 +26,12 @@ type Meta struct { // Date : 17:55 2024/7/19 func Test_parser_Openapi3(t *testing.T) { type User struct { - Meta `json:"-" deprecated:"true" path:"/user/detail" method:"POST" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` + Meta `json:"-" deprecated:"false" path:"/user/detail" method:"POST" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` + Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` + Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` + } + type UserDelete struct { + Meta `json:"-" deprecated:"false" path:"/user/detail" method:"DELETE" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } @@ -35,11 +41,23 @@ func Test_parser_Openapi3(t *testing.T) { } var o List var f User - g := NewOpenapiDoc(nil, nil) + var fd UserDelete + g := NewOpenapiDoc(nil, []*define.ServerItem{ + &define.ServerItem{ + Url: "http://127.0.0.1/v1", + Description: "v1接口", + Variables: nil, + }, + &define.ServerItem{ + Url: "http://127.0.0.1/v2", + Description: "v2接口", + Variables: nil, + }, + }) g.AddApiFromInAndOut(reflect.TypeOf(f), reflect.TypeOf(o)) + g.AddApiFromInAndOut(reflect.TypeOf(fd), reflect.TypeOf(o)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) - fmt.Println(g.parseSliceItem("", reflect.TypeOf(o))) } func TestParseForSwagger(t *testing.T) { From 39f9c3d67f51a4285897830a011beba8c931eae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 11:47:32 +0800 Subject: [PATCH 28/32] =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=B3=E7=B3=BBurl=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/openapi.go | 23 ++++++++++++----------- generate.go | 21 ++++++++++++++++++++- parser_test.go | 7 +++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/define/openapi.go b/define/openapi.go index 09f1adb..f536588 100644 --- a/define/openapi.go +++ b/define/openapi.go @@ -100,17 +100,17 @@ type ExternalDocs struct { // // Date : 12:29 2024/7/19 type PathConfigParameter struct { - Name string `json:"name"` // 参数名称 - In string `json:"in"` // 必选. 参数的位置,可能的值有 "query", "header", "path" 或 "cookie"。 - Description string `json:"description"` // 对此参数的简要描述,这里可以包含使用示例。CommonMark syntax可以被用来呈现富文本格式. - Required bool `json:"required"` // 标明此参数是否是必选参数。如果 参数位置 的值是 path,那么这个参数一定是 必选 的因此这里的值必须是true。其他的则视情况而定。此字段的默认值是false。 - Deprecated bool `json:"deprecated"` // 标明一个参数是被弃用的而且应该尽快移除对它的使用。 - Schema *Schema `json:"schema,omitempty"` // 定义适用于此参数的类型结构。 Schema 对象 | Reference 对象 - AllowEmptyValue bool `json:"allowEmptyValue"` // 设置是否允许传递空参数,这只在参数值为query时有效,默认值是false。如果同时指定了style属性且值为n/a(无法被序列化),那么此字段 allowEmptyValue应该被忽略。 - Style string `json:"style"` // 描述根据参数值类型的不同如何序列化参数。默认值为(基于in字段的值):query 对应 form;path 对应 simple; header 对应 simple; cookie 对应 form。 - Explode bool `json:"explode"` // 当这个值为true时,参数值类型为array或object的参数使用数组内的值或对象的键值对生成带分隔符的参数值。对于其他类型的参数,这个字段没有任何影响。当 style 是 form时,这里的默认值是 true,对于其他 style 值类型,默认值是false。 - AllowReserved bool `json:"allowReserved"` // 决定此参数的值是否允许不使用%号编码使用定义于 RFC3986内的保留字符 :/?#[]@!$&'()*+,;=。 这个属性仅用于in的值是query时,此字段的默认值是false。 - Ref string `json:"$ref"` // 一个允许引用规范内部的其他部分或外部规范的对象。 Reference 对象 定义于 JSON Reference 且遵循相同的结构、行为和规则。 + Name string `json:"name"` // 参数名称 + In string `json:"in"` // 必选. 参数的位置,可能的值有 "query", "header", "path" 或 "cookie"。 + Description string `json:"description"` // 对此参数的简要描述,这里可以包含使用示例。CommonMark syntax可以被用来呈现富文本格式. + Required bool `json:"required"` // 标明此参数是否是必选参数。如果 参数位置 的值是 path,那么这个参数一定是 必选 的因此这里的值必须是true。其他的则视情况而定。此字段的默认值是false。 + Deprecated bool `json:"deprecated"` // 标明一个参数是被弃用的而且应该尽快移除对它的使用。 + Schema *Schema `json:"schema,omitempty,omitempty"` // 定义适用于此参数的类型结构。 Schema 对象 | Reference 对象 + AllowEmptyValue bool `json:"allowEmptyValue"` // 设置是否允许传递空参数,这只在参数值为query时有效,默认值是false。如果同时指定了style属性且值为n/a(无法被序列化),那么此字段 allowEmptyValue应该被忽略。 + Style string `json:"style,omitempty"` // 描述根据参数值类型的不同如何序列化参数。默认值为(基于in字段的值):query 对应 form;path 对应 simple; header 对应 simple; cookie 对应 form。 + Explode bool `json:"explode,omitempty"` // 当这个值为true时,参数值类型为array或object的参数使用数组内的值或对象的键值对生成带分隔符的参数值。对于其他类型的参数,这个字段没有任何影响。当 style 是 form时,这里的默认值是 true,对于其他 style 值类型,默认值是false。 + AllowReserved bool `json:"allowReserved,omitempty"` // 决定此参数的值是否允许不使用%号编码使用定义于 RFC3986内的保留字符 :/?#[]@!$&'()*+,;=。 这个属性仅用于in的值是query时,此字段的默认值是false。 + Ref string `json:"$ref,omitempty"` // 一个允许引用规范内部的其他部分或外部规范的对象。 Reference 对象 定义于 JSON Reference 且遵循相同的结构、行为和规则。 } // Schema ... @@ -133,6 +133,7 @@ type Schema struct { Type string `json:"type,omitempty"` // 类型 Items *PropertyXOf `json:"items,omitempty"` // items 必须存在如果 type 的值是 array。 Ref string `json:"$ref,omitempty"` // 类型引用 + Format string `json:"format,omitempty"` // 格式化类型 } // Property 是从 JSON Schema 提取出来的,但是做了一些调整以适应 OpenAPI Specification。 diff --git a/generate.go b/generate.go index e644edd..09601af 100644 --- a/generate.go +++ b/generate.go @@ -208,7 +208,7 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect Description: baseCfg.Description, ExternalDocs: nil, OperationID: baseCfg.Method + "-" + defaultPkgPath, - Parameters: nil, + Parameters: make([]*define.PathConfigParameter, 0), RequestBody: &define.RequestBody{ Required: true, Description: "", @@ -225,6 +225,25 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect Security: nil, Servers: nil, } + // 解析绑定在url中的参数 + if paramList := define.UriParamRegexp.FindAllString(baseCfg.Uri, -1); len(paramList) > 0 { + for _, param := range paramList { + param = strings.TrimPrefix(param, "{") + param = strings.TrimSuffix(param, "}") + cfg.Parameters = append(cfg.Parameters, &define.PathConfigParameter{ + Name: param, + In: consts.SwaggerParameterInPath, + Description: param, + Required: true, + Deprecated: false, + Schema: &define.Schema{ + Type: consts.SwaggerDataTypeString, + Format: consts.DataTypeString.String(), + }, + AllowEmptyValue: false, + }) + } + } for _, itemType := range baseCfg.ContentType { cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ diff --git a/parser_test.go b/parser_test.go index 806a7b8..a00cbda 100644 --- a/parser_test.go +++ b/parser_test.go @@ -35,6 +35,11 @@ func Test_parser_Openapi3(t *testing.T) { Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } + type UserPut struct { + Meta `json:"-" deprecated:"false" path:"/user/put/{put_user_id}" method:"PUT" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` + Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` + Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` + } type List struct { Total int64 `json:"total" binding:"required"` UserList []User `json:"user_list"` @@ -42,6 +47,7 @@ func Test_parser_Openapi3(t *testing.T) { var o List var f User var fd UserDelete + var up UserPut g := NewOpenapiDoc(nil, []*define.ServerItem{ &define.ServerItem{ Url: "http://127.0.0.1/v1", @@ -56,6 +62,7 @@ func Test_parser_Openapi3(t *testing.T) { }) g.AddApiFromInAndOut(reflect.TypeOf(f), reflect.TypeOf(o)) g.AddApiFromInAndOut(reflect.TypeOf(fd), reflect.TypeOf(o)) + g.AddApiFromInAndOut(reflect.TypeOf(up), reflect.TypeOf(o)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) } From 8d31a7f2ce1ee53bd98971daeec9efe7e285e9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 12:29:28 +0800 Subject: [PATCH 29/32] =?UTF-8?q?=E5=A4=84=E7=90=86GET=E7=B1=BB=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=96=87=E6=A1=A3=E7=94=9F=E6=88=90=EF=BC=8C=E5=BE=85?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 94 +++++++++++++++++++++++++++++++++++++++++--------- parser_test.go | 7 ++++ 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/generate.go b/generate.go index 09601af..6963c13 100644 --- a/generate.go +++ b/generate.go @@ -171,6 +171,12 @@ func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.Par // // Date : 14:22 2025/2/9 func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect.Type) error { + if paramType.Kind() == reflect.Ptr { + paramType = paramType.Elem() + } + if resultType.Kind() == reflect.Ptr { + resultType = resultType.Elem() + } baseCfg := g.parseBaseUriConfig(paramType) if nil == baseCfg { return errors.New("baseCfg is nil") @@ -182,22 +188,6 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect 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类解析 - if paramType.Kind() == reflect.Ptr { - paramType = paramType.Elem() - } - if resultType.Kind() == reflect.Ptr { - resultType = resultType.Elem() - } - paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) - resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } @@ -244,6 +234,20 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect }) } } + paramMethod := []string{ + http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, + } + if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { + // Get类请求, TODO : get类解析 + // 参数解析 + g.ParseReadConfigParam(baseCfg, cfg, paramType) + // 返回值解析 + g.AddComponentsSchema("", resultType.PkgPath(), resultType) + return nil + } + // post类解析 + paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) + resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) for _, itemType := range baseCfg.ContentType { cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ @@ -289,6 +293,64 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect return nil } +// ParseReadConfigParam 解析get类请求参数 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:55 2025/2/14 +func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseReqCfg *define.PathItemOperationConfig, inputType reflect.Type) { + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + if inputType.Kind() != reflect.Struct { + panic(requestCfg.Uri + " : request param not struct") + } + for i := 0; i < inputType.NumField(); i++ { + propertyName := ParseStructField.GetParamName(inputType.Field(i)) + if propertyName == "-" { + continue + } + realInputTypeFormat := inputType.Field(i).Type.Kind().String() + fieldType := inputType.Field(i).Type + if inputType.Field(i).Type.Kind() == reflect.Ptr { + fieldType = inputType.Field(i).Type.Elem() + } + if fieldType.Kind() == reflect.Ptr || + fieldType.Kind() == reflect.Struct || + fieldType.Kind() == reflect.Map || + fieldType.Kind() == reflect.Array || + fieldType.Kind() == reflect.Slice { + if convertType := g.realBaseType2SwaggerType(fieldType.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { + // 针对基础类型指针 + continue + } + + } else { + if nil == g.docData.Paths[requestCfg.Uri].Get { + g.docData.Paths[requestCfg.Uri].Get = &define.PathItemOperationConfig{} + } + g.docData.Paths[requestCfg.Uri].Get.Parameters = append(g.docData.Paths[requestCfg.Uri].Get.Parameters, &define.PathConfigParameter{ + Name: ParseStructField.GetParamName(inputType.Field(i)), + In: consts.SwaggerParameterInQuery, + Description: ParseStructField.GetParamDesc(inputType.Field(i)), + Required: ValidateRule.IsRequired(inputType.Field(i)), + Deprecated: ParseStructField.Deprecated(inputType.Field(i)), + Schema: &define.Schema{ + Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), + Items: nil, + Ref: "", + Format: realInputTypeFormat, + }, + AllowEmptyValue: false, + Style: "", + Explode: false, + AllowReserved: false, + Ref: "", + }) + } + } +} + // AddComponentsSchema 添加schema // // Author : go_developer@163.com<白茶清欢> diff --git a/parser_test.go b/parser_test.go index a00cbda..1cb2be7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -40,6 +40,11 @@ func Test_parser_Openapi3(t *testing.T) { Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } + type UserGet struct { + Meta `json:"-" deprecated:"false" path:"/user/detail/get/{put_user_id}" method:"GET" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` + Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` + Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` + } type List struct { Total int64 `json:"total" binding:"required"` UserList []User `json:"user_list"` @@ -48,6 +53,7 @@ func Test_parser_Openapi3(t *testing.T) { var f User var fd UserDelete var up UserPut + var ug UserGet g := NewOpenapiDoc(nil, []*define.ServerItem{ &define.ServerItem{ Url: "http://127.0.0.1/v1", @@ -63,6 +69,7 @@ func Test_parser_Openapi3(t *testing.T) { g.AddApiFromInAndOut(reflect.TypeOf(f), reflect.TypeOf(o)) g.AddApiFromInAndOut(reflect.TypeOf(fd), reflect.TypeOf(o)) g.AddApiFromInAndOut(reflect.TypeOf(up), reflect.TypeOf(o)) + g.AddApiFromInAndOut(reflect.TypeOf(ug), reflect.TypeOf(o)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) } From f51388c1d64de52e76c65b1d1ad78e18aa1eb2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 15:31:53 +0800 Subject: [PATCH 30/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0get=E7=B1=BB=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=20body=20=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/tag.go | 49 +++++++++++++++----------------- generate.go | 75 ++++++++++++++++++++++++++----------------------- struct_field.go | 15 ++++++++++ 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/define/tag.go b/define/tag.go index ac42381..4f89323 100644 --- a/define/tag.go +++ b/define/tag.go @@ -8,31 +8,26 @@ package define const ( - TagJson = "json" - TagXml = "xml" - TagYaml = "yaml" - TagYml = "yml" - TagForm = "form" - TagBinding = "binding" - TagValidate = "validate" - TagErr = "err" - TagMsg = "msg" - TagDesc = "desc" - TagDescription = "description" - TagD = "d" - TagDefault = "default" - TagDeprecated = "deprecated" -) - -const ( - TagNamePath = "path" // 接口的请求路径 - TagNameMethod = "method" // 接口的请求方法 - TagNameUriTag = "tag" // 接口的tag - TagNameDesc = "desc" // 接口的描述 - TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 - TagNameBinding = "binding" // gin 内置的验证规则tag - TagNameValidate = "validate" // validator v10 默认的验证规则tag - TagNameErrMsg = "err" // 验证失败错误信息tag - TagNameContentType = "content_type" - TagNameOutputContentType = "output_content_type" + TagJson = "json" + TagXml = "xml" + TagYaml = "yaml" + TagYml = "yml" + TagForm = "form" + TagBinding = "binding" + TagValidate = "validate" + TagErr = "err" + TagMsg = "msg" + TagDesc = "desc" + TagDescription = "description" + TagD = "d" + TagDefault = "default" + TagDeprecated = "deprecated" + TagSummary = "summary" + TagPath = "path" // 接口的请求路径 + TagMethod = "method" // 接口的请求方法 + TagUriTag = "tag" // 接口的tag + TagOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 + TagErrMsg = "err" // 验证失败错误信息tag + TagContentType = "content_type" + TagOutputContentType = "output_content_type" ) diff --git a/generate.go b/generate.go index 6963c13..b1f6dae 100644 --- a/generate.go +++ b/generate.go @@ -177,17 +177,10 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect if resultType.Kind() == reflect.Ptr { resultType = resultType.Elem() } - baseCfg := g.parseBaseUriConfig(paramType) - if nil == baseCfg { - return errors.New("baseCfg is nil") + baseCfg, err := g.parseBaseUriConfig(paramType) + if nil != err { + return err } - 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) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } @@ -238,26 +231,26 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, } if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { + cfg.RequestBody = nil // get类请求没有request body // Get类请求, TODO : get类解析 // 参数解析 g.ParseReadConfigParam(baseCfg, cfg, paramType) - // 返回值解析 - g.AddComponentsSchema("", resultType.PkgPath(), resultType) - return nil - } - // post类解析 - paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) - resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) - for _, itemType := range baseCfg.ContentType { - cfg.RequestBody.Content[itemType] = &define.Media{ - Schema: &define.Schema{ - Ref: g.getSchemaRef(paramSchemaName), - }, - Example: "", - Examples: nil, - Encoding: nil, + } else { + // post类解析 + paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) + for _, itemType := range baseCfg.ContentType { + cfg.RequestBody.Content[itemType] = &define.Media{ + Schema: &define.Schema{ + Ref: g.getSchemaRef(paramSchemaName), + }, + Example: "", + Examples: nil, + Encoding: nil, + } } } + // 无论什么请求, 对于result解析逻辑一致 + resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) for _, itemOutputType := range baseCfg.OutputContentType { cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ Schema: &define.Schema{ @@ -568,11 +561,16 @@ func (g *Generate) setStructFieldProperty(schemaName string, structField reflect g.docData.Components.Schemas[schemaName].Properties[ParseStructField.GetParamName(structField)].Enum = ValidateRule.Enum(structField) } -func (g *Generate) parseBaseUriConfig(paramType reflect.Type) *define.UriBaseConfig { +// parseBaseUriConfig 通过Meta字段解析Uri基础配置信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:13 2025/2/14 +func (g *Generate) parseBaseUriConfig(paramType reflect.Type) (*define.UriBaseConfig, error) { // 解析meta信息 metaField, metaFieldExist := paramType.FieldByName("Meta") if !metaFieldExist { - return nil + return nil, errors.New("Meta field not found") } res := &define.UriBaseConfig{ Uri: "", @@ -586,16 +584,23 @@ func (g *Generate) parseBaseUriConfig(paramType reflect.Type) *define.UriBaseCon ResultList: nil, Deprecated: false, } - res.Uri = metaField.Tag.Get(define.TagNamePath) - res.Method = metaField.Tag.Get(define.TagNameMethod) - res.Description = metaField.Tag.Get(define.TagNameDesc) - res.TagList = strings.Split(metaField.Tag.Get(define.TagNameUriTag), ",") + res.Uri = metaField.Tag.Get(define.TagPath) + res.Method = strings.ToUpper(metaField.Tag.Get(define.TagMethod)) + res.Description = metaField.Tag.Get(define.TagDesc) + res.TagList = strings.Split(metaField.Tag.Get(define.TagUriTag), ",") // 解析第一个返回值, 要求必须是结构体或者是map - outputStrictModel := metaField.Tag.Get(define.TagNameOutputStrict) + outputStrictModel := metaField.Tag.Get(define.TagOutputStrict) res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" deprecated := metaField.Tag.Get(define.TagDeprecated) res.Deprecated = deprecated == "1" || deprecated == "true" - res.ContentType = strings.Split(metaField.Tag.Get(define.TagNameContentType), ",") - res.OutputContentType = strings.Split(metaField.Tag.Get(define.TagNameOutputContentType), ",") - return res + res.ContentType = strings.Split(metaField.Tag.Get(define.TagContentType), ",") + res.OutputContentType = strings.Split(metaField.Tag.Get(define.TagOutputContentType), ",") + res.Summary = ParseStructField.Summary(metaField) + if res.Method == "" { + return nil, errors.New("baseCfg.Method is empty") + } + if res.Uri == "" { + return nil, errors.New("baseCfg.Uri is empty") + } + return res, nil } diff --git a/struct_field.go b/struct_field.go index ea67dae..42f40d3 100644 --- a/struct_field.go +++ b/struct_field.go @@ -126,3 +126,18 @@ func (psf parseStructField) Deprecated(structField reflect.StructField) bool { } return false } + +// Summary 摘要信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:15 2025/2/14 +func (psf parseStructField) Summary(structField reflect.StructField) string { + defaultTagList := []string{define.TagSummary} + for _, tag := range defaultTagList { + if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 { + return tagVal + } + } + return psf.GetParamName(structField) +} From c9604091f2a07f800c21ab0a93425f62a2cd943a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 18:22:53 +0800 Subject: [PATCH 31/32] =?UTF-8?q?=E4=BC=98=E5=8C=96GET=E7=B1=BB=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=96=87=E6=A1=A3=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 172 +++++++++++++++++++++++++++++++----------------- parser_test.go | 15 ++++- struct_field.go | 6 +- 3 files changed, 129 insertions(+), 64 deletions(-) diff --git a/generate.go b/generate.go index b1f6dae..a185ee1 100644 --- a/generate.go +++ b/generate.go @@ -184,6 +184,81 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } + // 接口文档初始化 + cfg := g.getApiDocBaseCfg(baseCfg, paramType) + paramMethod := []string{ + http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, + } + if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { + cfg.RequestBody = nil // get类请求没有request body + // 参数解析 + g.ParseReadConfigParam(baseCfg, cfg, paramType) + } else { + // post类解析 + paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) + for _, itemType := range baseCfg.ContentType { + cfg.RequestBody.Content[itemType] = &define.Media{ + Schema: &define.Schema{ + Ref: g.getSchemaRef(paramSchemaName), + }, + Example: "", + Examples: nil, + Encoding: nil, + } + } + } + // 无论什么请求, 对于result解析逻辑一致 + resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) + for _, itemOutputType := range baseCfg.OutputContentType { + cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ + Schema: &define.Schema{ + Ref: g.getSchemaRef(resultSchemaName), + }, + Example: "", + Examples: nil, + Encoding: nil, + } + } + g.setApiDoc(baseCfg, cfg) + return nil +} + +// setApiDoc 设置文档配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:13 2025/2/14 +func (g *Generate) setApiDoc(baseCfg *define.UriBaseConfig, apiDocCfg *define.PathItemOperationConfig) { + switch baseCfg.Method { + case http.MethodGet: + g.docData.Paths[baseCfg.Uri].Get = apiDocCfg + case http.MethodHead: + g.docData.Paths[baseCfg.Uri].Head = apiDocCfg + case http.MethodPost: + g.docData.Paths[baseCfg.Uri].Post = apiDocCfg + case http.MethodPut: + g.docData.Paths[baseCfg.Uri].Put = apiDocCfg + case http.MethodPatch: + g.docData.Paths[baseCfg.Uri].Patch = apiDocCfg + case http.MethodDelete: + g.docData.Paths[baseCfg.Uri].Delete = apiDocCfg + case http.MethodConnect: + g.docData.Paths[baseCfg.Uri].Connect = apiDocCfg + case http.MethodOptions: + g.docData.Paths[baseCfg.Uri].Options = apiDocCfg + case http.MethodTrace: + g.docData.Paths[baseCfg.Uri].Trace = apiDocCfg + default: + panic("unknown method: " + baseCfg.Method) + } +} + +// getApiDocBaseCfg 获取接口文档的基础配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:10 2025/2/14 +func (g *Generate) getApiDocBaseCfg(baseCfg *define.UriBaseConfig, paramType reflect.Type) *define.PathItemOperationConfig { defaultPkgPath := wrapper.String(strings.TrimLeft(baseCfg.Uri, "/")).SnakeCaseToCamel() cfg := &define.PathItemOperationConfig{ Tags: baseCfg.TagList, @@ -227,63 +302,7 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect }) } } - paramMethod := []string{ - http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, - } - if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { - cfg.RequestBody = nil // get类请求没有request body - // Get类请求, TODO : get类解析 - // 参数解析 - g.ParseReadConfigParam(baseCfg, cfg, paramType) - } else { - // post类解析 - paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) - for _, itemType := range baseCfg.ContentType { - cfg.RequestBody.Content[itemType] = &define.Media{ - Schema: &define.Schema{ - Ref: g.getSchemaRef(paramSchemaName), - }, - Example: "", - Examples: nil, - Encoding: nil, - } - } - } - // 无论什么请求, 对于result解析逻辑一致 - resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) - for _, itemOutputType := range baseCfg.OutputContentType { - cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ - Schema: &define.Schema{ - Ref: g.getSchemaRef(resultSchemaName), - }, - 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 - default: - panic("unknown method: " + baseCfg.Method) - } - return nil + return cfg } // ParseReadConfigParam 解析get类请求参数 @@ -298,6 +317,9 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe if inputType.Kind() != reflect.Struct { panic(requestCfg.Uri + " : request param not struct") } + if nil == baseReqCfg.Parameters { + baseReqCfg.Parameters = make([]*define.PathConfigParameter, 0) + } for i := 0; i < inputType.NumField(); i++ { propertyName := ParseStructField.GetParamName(inputType.Field(i)) if propertyName == "-" { @@ -315,14 +337,28 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe fieldType.Kind() == reflect.Slice { if convertType := g.realBaseType2SwaggerType(fieldType.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { // 针对基础类型指针 + baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ + Name: ParseStructField.GetParamName(inputType.Field(i)), + In: consts.SwaggerParameterInQuery, + Description: ParseStructField.GetParamDesc(inputType.Field(i)), + Required: ValidateRule.IsRequired(inputType.Field(i)), + Deprecated: ParseStructField.Deprecated(inputType.Field(i)), + Schema: &define.Schema{ + Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), + Items: nil, + Ref: "", + Format: realInputTypeFormat, + }, + AllowEmptyValue: false, + Style: "", + Explode: false, + AllowReserved: false, + Ref: "", + }) continue } - } else { - if nil == g.docData.Paths[requestCfg.Uri].Get { - g.docData.Paths[requestCfg.Uri].Get = &define.PathItemOperationConfig{} - } - g.docData.Paths[requestCfg.Uri].Get.Parameters = append(g.docData.Paths[requestCfg.Uri].Get.Parameters, &define.PathConfigParameter{ + baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ Name: ParseStructField.GetParamName(inputType.Field(i)), In: consts.SwaggerParameterInQuery, Description: ParseStructField.GetParamDesc(inputType.Field(i)), @@ -342,6 +378,18 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe }) } } + switch requestCfg.Method { + case http.MethodGet: + g.docData.Paths[requestCfg.Uri].Get = baseReqCfg + case http.MethodHead: + g.docData.Paths[requestCfg.Uri].Head = baseReqCfg + case http.MethodConnect: + g.docData.Paths[requestCfg.Uri].Connect = baseReqCfg + case http.MethodOptions: + g.docData.Paths[requestCfg.Uri].Options = baseReqCfg + case http.MethodTrace: + g.docData.Paths[requestCfg.Uri].Trace = baseReqCfg + } } // AddComponentsSchema 添加schema diff --git a/parser_test.go b/parser_test.go index 1cb2be7..93348dd 100644 --- a/parser_test.go +++ b/parser_test.go @@ -45,6 +45,11 @@ func Test_parser_Openapi3(t *testing.T) { Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` } + type UserHead struct { + Meta `json:"-" deprecated:"false" path:"/user/detail/head/{put_user_id}" method:"HEAD" desc:"测试接口" tag:"用户,搜索" content_type:"application/json" output_content_type:"application/json"` + Name string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"` + Age string `json:"age" d:"18" desc:"年龄" binding:"required,oneof=12 13 18 90"` + } type List struct { Total int64 `json:"total" binding:"required"` UserList []User `json:"user_list"` @@ -54,11 +59,18 @@ func Test_parser_Openapi3(t *testing.T) { var fd UserDelete var up UserPut var ug UserGet + var uh UserHead g := NewOpenapiDoc(nil, []*define.ServerItem{ &define.ServerItem{ Url: "http://127.0.0.1/v1", Description: "v1接口", - Variables: nil, + Variables: map[string]*define.ServerItemVariable{ + "test": &define.ServerItemVariable{ + Default: "123456", + Description: "1111", + Enum: nil, + }, + }, }, &define.ServerItem{ Url: "http://127.0.0.1/v2", @@ -70,6 +82,7 @@ func Test_parser_Openapi3(t *testing.T) { g.AddApiFromInAndOut(reflect.TypeOf(fd), reflect.TypeOf(o)) g.AddApiFromInAndOut(reflect.TypeOf(up), reflect.TypeOf(o)) g.AddApiFromInAndOut(reflect.TypeOf(ug), reflect.TypeOf(o)) + g.AddApiFromInAndOut(reflect.TypeOf(uh), reflect.TypeOf(o)) byteData, _ := json.Marshal(g.docData) fmt.Println(string(byteData)) } diff --git a/struct_field.go b/struct_field.go index 42f40d3..f6bb6e3 100644 --- a/struct_field.go +++ b/struct_field.go @@ -139,5 +139,9 @@ func (psf parseStructField) Summary(structField reflect.StructField) string { return tagVal } } - return psf.GetParamName(structField) + paramName := psf.GetParamName(structField) + if paramName == "-" { + return "" + } + return paramName } From f2cae8542abe206ea1bd6f798b8eed8e993df803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 14 Feb 2025 21:08:19 +0800 Subject: [PATCH 32/32] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E3=80=81=E5=93=8D?= =?UTF-8?q?=E5=BA=94type=E5=A2=9E=E5=8A=A0=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generate.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/generate.go b/generate.go index a185ee1..85dc618 100644 --- a/generate.go +++ b/generate.go @@ -54,6 +54,9 @@ func NewOpenapiDoc(info *define.Info, servers []*define.ServerItem) *Generate { servers = []*define.ServerItem{} } return &Generate{ + readMethodList: []string{ + http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, + }, docData: &define.OpenapiDoc{ Openapi: consts.SwaggerDocVersion3, Info: info, @@ -71,7 +74,8 @@ func NewOpenapiDoc(info *define.Info, servers []*define.ServerItem) *Generate { // // Date : 15:57 2024/7/22 type Generate struct { - docData *define.OpenapiDoc + docData *define.OpenapiDoc + readMethodList []string } // SetLicense 设置文档协议 @@ -186,10 +190,8 @@ func (g *Generate) AddApiFromInAndOut(paramType reflect.Type, resultType reflect } // 接口文档初始化 cfg := g.getApiDocBaseCfg(baseCfg, paramType) - paramMethod := []string{ - http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, - } - if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { + + if wrapper.ArrayType(g.readMethodList).Has(baseCfg.Method) >= 0 { cfg.RequestBody = nil // get类请求没有request body // 参数解析 g.ParseReadConfigParam(baseCfg, cfg, paramType) @@ -642,7 +644,19 @@ func (g *Generate) parseBaseUriConfig(paramType reflect.Type) (*define.UriBaseCo deprecated := metaField.Tag.Get(define.TagDeprecated) res.Deprecated = deprecated == "1" || deprecated == "true" res.ContentType = strings.Split(metaField.Tag.Get(define.TagContentType), ",") + if len(res.ContentType) == 0 { + if wrapper.ArrayType(g.readMethodList).Has(res.Method) >= 0 { + // get类请求 + res.ContentType = []string{consts.MimeTypeXWWWFormUrlencoded} + } else { + res.ContentType = []string{consts.MimeTypeJson} + } + } res.OutputContentType = strings.Split(metaField.Tag.Get(define.TagOutputContentType), ",") + if len(res.OutputContentType) == 0 { + // 未设置响应类型默认JSON数据 + res.OutputContentType = []string{consts.MimeTypeJson} + } res.Summary = ParseStructField.Summary(metaField) if res.Method == "" { return nil, errors.New("baseCfg.Method is empty")