Compare commits

..

10 Commits

3 changed files with 105 additions and 71 deletions

View File

@ -105,7 +105,7 @@ type PathConfigParameter struct {
Description string `json:"description"` // 对此参数的简要描述这里可以包含使用示例。CommonMark syntax可以被用来呈现富文本格式. Description string `json:"description"` // 对此参数的简要描述这里可以包含使用示例。CommonMark syntax可以被用来呈现富文本格式.
Required bool `json:"required"` // 标明此参数是否是必选参数。如果 参数位置 的值是 path那么这个参数一定是 必选 的因此这里的值必须是true。其他的则视情况而定。此字段的默认值是false。 Required bool `json:"required"` // 标明此参数是否是必选参数。如果 参数位置 的值是 path那么这个参数一定是 必选 的因此这里的值必须是true。其他的则视情况而定。此字段的默认值是false。
Deprecated bool `json:"deprecated"` // 标明一个参数是被弃用的而且应该尽快移除对它的使用。 Deprecated bool `json:"deprecated"` // 标明一个参数是被弃用的而且应该尽快移除对它的使用。
Schema *Schema `json:"schema,omitempty,omitempty"` // 定义适用于此参数的类型结构。 Schema 对象 | Reference 对象 Schema *Schema `json:"schema,omitempty"` // 定义适用于此参数的类型结构。 Schema 对象 | Reference 对象
AllowEmptyValue bool `json:"allowEmptyValue"` // 设置是否允许传递空参数这只在参数值为query时有效默认值是false。如果同时指定了style属性且值为n/a无法被序列化,那么此字段 allowEmptyValue应该被忽略。 AllowEmptyValue bool `json:"allowEmptyValue"` // 设置是否允许传递空参数这只在参数值为query时有效默认值是false。如果同时指定了style属性且值为n/a无法被序列化,那么此字段 allowEmptyValue应该被忽略。
Style string `json:"style,omitempty"` // 描述根据参数值类型的不同如何序列化参数。默认值为基于in字段的值query 对应 formpath 对应 simple; header 对应 simple; cookie 对应 form。 Style string `json:"style,omitempty"` // 描述根据参数值类型的不同如何序列化参数。默认值为基于in字段的值query 对应 formpath 对应 simple; header 对应 simple; cookie 对应 form。
Explode bool `json:"explode,omitempty"` // 当这个值为true时参数值类型为array或object的参数使用数组内的值或对象的键值对生成带分隔符的参数值。对于其他类型的参数这个字段没有任何影响。当 style 是 form时这里的默认值是 true对于其他 style 值类型默认值是false。 Explode bool `json:"explode,omitempty"` // 当这个值为true时参数值类型为array或object的参数使用数组内的值或对象的键值对生成带分隔符的参数值。对于其他类型的参数这个字段没有任何影响。当 style 是 form时这里的默认值是 true对于其他 style 值类型默认值是false。

View File

@ -265,13 +265,12 @@ func (g *Generate) setApiDoc(baseCfg *define.UriBaseConfig, apiDocCfg *define.Pa
// //
// Date : 16:10 2025/2/14 // Date : 16:10 2025/2/14
func (g *Generate) getApiDocBaseCfg(baseCfg *define.UriBaseConfig, paramType reflect.Type) *define.PathItemOperationConfig { func (g *Generate) getApiDocBaseCfg(baseCfg *define.UriBaseConfig, paramType reflect.Type) *define.PathItemOperationConfig {
defaultPkgPath := wrapper.String(strings.TrimLeft(baseCfg.Uri, "/")).SnakeCaseToCamel()
cfg := &define.PathItemOperationConfig{ cfg := &define.PathItemOperationConfig{
Tags: baseCfg.TagList, Tags: baseCfg.TagList,
Summary: baseCfg.Summary, Summary: baseCfg.Summary,
Description: baseCfg.Description, Description: baseCfg.Description,
ExternalDocs: nil, ExternalDocs: nil,
OperationID: baseCfg.Method + "-" + defaultPkgPath, OperationID: baseCfg.Method + "-" + baseCfg.Uri,
Parameters: make([]*define.PathConfigParameter, 0), Parameters: make([]*define.PathConfigParameter, 0),
RequestBody: &define.RequestBody{ RequestBody: &define.RequestBody{
Required: true, Required: true,
@ -331,38 +330,59 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
if propertyName == "-" { if propertyName == "-" {
continue continue
} }
realInputTypeFormat := inputType.Field(i).Type.Kind().String() if inputType.Field(i).Type.Kind() == reflect.Struct && (inputType.Field(i).Type.String() == "Meta" || strings.HasSuffix(inputType.Field(i).Type.String(), ".Meta")) && inputType.Field(i).Type.NumField() == 0 {
fieldType := inputType.Field(i).Type // 空Meta字段认为是用来描述元信息的, 忽略
if inputType.Field(i).Type.Kind() == reflect.Ptr { continue
fieldType = inputType.Field(i).Type.Elem()
} }
if fieldType.Kind() == reflect.Ptr || realInputTypeFormat := inputType.Field(i).Type.String()
fieldType.Kind() == reflect.Struct || fieldType := inputType.Field(i).Type
fieldType.Kind() == reflect.Map || /*if inputType.Field(i).Type.Kind() == reflect.Ptr {
fieldType.Kind() == reflect.Array || fieldType = inputType.Field(i).Type.Elem()
fieldType.Kind() == reflect.Slice { }*/
if convertType := g.realBaseType2SwaggerType(fieldType.String()); !strings.HasPrefix(convertType, "[]") && convertType != inputType.Field(i).Type.Kind().String() { if inputType.Field(i).Type.Kind() == reflect.Ptr {
// 针对基础类型指针 // 处理指针
if inputType.Field(i).Type.Elem().Kind() == reflect.Struct {
// 结构体指针
schemaNameNext := g.AddComponentsSchema("", propertyName, inputType.Field(i).Type.Elem())
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: ParseStructField.GetParamName(inputType.Field(i)), Name: propertyName,
In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)),
Schema: &define.Schema{
// Format: realInputTypeFormat,
Ref: g.getSchemaRef(schemaNameNext),
}, AllowEmptyValue: false,
Style: "",
Explode: false,
AllowReserved: false,
})
} else {
// 当做默认基础类型, 默认不会出现 *map *[]
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: propertyName,
In: consts.SwaggerParameterInQuery, In: consts.SwaggerParameterInQuery,
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Description: ParseStructField.GetParamDesc(inputType.Field(i)),
Required: ValidateRule.IsRequired(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)),
Deprecated: ParseStructField.Deprecated(inputType.Field(i)), Deprecated: ParseStructField.Deprecated(inputType.Field(i)),
Schema: &define.Schema{ Schema: &define.Schema{
Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()), Type: g.realBaseType2SwaggerType(inputType.Field(i).Type.String()),
Items: nil,
Ref: "",
Format: realInputTypeFormat, Format: realInputTypeFormat,
}, },
AllowEmptyValue: false, AllowEmptyValue: false,
Style: "", Style: "",
Explode: false, Explode: false,
AllowReserved: false, AllowReserved: false,
Ref: "",
}) })
}
continue continue
} }
if fieldType.Kind() == reflect.Struct ||
fieldType.Kind() == reflect.Map ||
fieldType.Kind() == reflect.Array ||
fieldType.Kind() == reflect.Slice {
// TODO: 完善逻辑解析
} else { } else {
baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{
Name: ParseStructField.GetParamName(inputType.Field(i)), Name: ParseStructField.GetParamName(inputType.Field(i)),
@ -404,6 +424,10 @@ func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseRe
// //
// Date : 15:25 2025/2/8 // Date : 15:25 2025/2/8
func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string { func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string {
if inputType.Kind() == reflect.Struct && (inputType.String() == "Meta" || strings.HasSuffix(inputType.String(), ".Meta")) && inputType.NumField() == 0 {
// 空Meta字段认为是用来描述元信息的, 忽略
return "-"
}
inputNameArr := strings.Split(inputType.Name(), ".") inputNameArr := strings.Split(inputType.Name(), ".")
inputName := inputNameArr[len(inputNameArr)-1] inputName := inputNameArr[len(inputNameArr)-1]
schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-") schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", "-")
@ -454,32 +478,34 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
} }
// 结构体 // 结构体
if inputType.Kind() == reflect.Struct { if inputType.Kind() == reflect.Struct {
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++ { for i := 0; i < inputType.NumField(); i++ {
propertyName := ParseStructField.GetParamName(inputType.Field(i)) propertyName := ParseStructField.GetParamName(inputType.Field(i))
if propertyName == "-" { if propertyName == "-" {
continue continue
} }
if inputType.Field(i).Type.Kind() == reflect.Ptr || if inputType.Field(i).Type.Kind() == reflect.Ptr {
inputType.Field(i).Type.Kind() == reflect.Struct || // 处理指针
inputType.Field(i).Type.Kind() == reflect.Map || if inputType.Field(i).Type.Elem().Kind() == reflect.Struct {
inputType.Field(i).Type.Kind() == reflect.Array || // 结构体指针
inputType.Field(i).Type.Kind() == reflect.Slice { schemaNameNext := g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type.Elem())
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[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: g.realBaseType2SwaggerType(convertType), Ref: g.getSchemaRef(schemaNameNext),
}
} else {
// 当做默认基础类型, 默认不会出现 *map *[]
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
Type: g.realBaseType2SwaggerType(g.realBaseType2SwaggerType(inputType.Field(i).Type.String())),
Format: inputType.Field(i).Type.String(), Format: inputType.Field(i).Type.String(),
Default: ParseStructField.GetDefaultValue(inputType.Field(i)), Default: ParseStructField.GetDefaultValue(inputType.Field(i)),
Description: ParseStructField.GetParamDesc(inputType.Field(i)), Description: ParseStructField.GetParamDesc(inputType.Field(i)),
} }
}
continue continue
} }
if 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 {
if inputType.Field(i).Type.Kind() == reflect.Struct || if inputType.Field(i).Type.Kind() == reflect.Struct ||
inputType.Field(i).Type.Kind() == reflect.Map { inputType.Field(i).Type.Kind() == reflect.Map {
g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{
@ -499,8 +525,6 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
}, },
Properties: map[string]*define.Property{}, Properties: map[string]*define.Property{},
} }
} else if inputType.Field(i).Type.Kind() == reflect.Ptr {
} else { } else {
g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type) g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type)
} }
@ -520,11 +544,11 @@ func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, in
} }
// 指针 // 指针
if inputType.Kind() == reflect.Ptr { if inputType.Kind() == reflect.Ptr {
convertType := g.realBaseType2SwaggerType(inputType.String()) if inputType.Elem().Kind() == reflect.Struct {
if convertType == inputType.String() {
// 非基础数据类型 // 非基础数据类型
return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem()) return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem())
} else { } else {
convertType := g.realBaseType2SwaggerType(inputType.String())
g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{
Type: convertType, Type: convertType,
Format: inputType.String(), Format: inputType.String(),
@ -547,6 +571,9 @@ func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type)
} }
sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceValue := reflect.MakeSlice(inputType, 1, 1)
sliceItemType := sliceValue.Index(0).Type() sliceItemType := sliceValue.Index(0).Type()
if sliceItemType.Kind() == reflect.Ptr {
sliceItemType = sliceItemType.Elem()
}
g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType)
if len(sliceItemType.PkgPath()) == 0 { if len(sliceItemType.PkgPath()) == 0 {
return sliceItemType.String() return sliceItemType.String()
@ -563,6 +590,7 @@ func (g *Generate) getSchemaRef(schemaName string) string {
if "" == schemaName { if "" == schemaName {
return "" return ""
} }
schemaName = strings.ReplaceAll(schemaName, "*", "") // 去除指针类型 *
return "#/components/schemas/" + strings.ReplaceAll(schemaName, "/", "-") return "#/components/schemas/" + strings.ReplaceAll(schemaName, "/", "-")
} }
@ -640,8 +668,11 @@ func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type)
} }
res.Uri = metaField.Tag.Get(define.TagPath) res.Uri = metaField.Tag.Get(define.TagPath)
if len(uriPrefix) > 0 { if len(uriPrefix) > 0 {
res.Uri = strings.TrimRight(uriPrefix, "/") + strings.TrimLeft(res.Uri, "/") res.Uri = strings.TrimRight(uriPrefix, "/") + "/" + strings.TrimLeft(res.Uri, "/")
} }
// 保证接口路由以 /开头
res.Uri = "/" + strings.TrimLeft(res.Uri, "/")
res.Method = strings.ToUpper(metaField.Tag.Get(define.TagMethod)) res.Method = strings.ToUpper(metaField.Tag.Get(define.TagMethod))
res.Description = metaField.Tag.Get(define.TagDesc) res.Description = metaField.Tag.Get(define.TagDesc)
res.TagList = strings.Split(metaField.Tag.Get(define.TagUriTag), ",") res.TagList = strings.Split(metaField.Tag.Get(define.TagUriTag), ",")
@ -650,20 +681,22 @@ func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type)
res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true"
deprecated := metaField.Tag.Get(define.TagDeprecated) deprecated := metaField.Tag.Get(define.TagDeprecated)
res.Deprecated = deprecated == "1" || deprecated == "true" res.Deprecated = deprecated == "1" || deprecated == "true"
res.ContentType = strings.Split(metaField.Tag.Get(define.TagContentType), ",") requestContentType := strings.TrimSpace(metaField.Tag.Get(define.TagContentType))
if len(res.ContentType) == 0 { if len(requestContentType) == 0 {
if wrapper.ArrayType(g.readMethodList).Has(res.Method) >= 0 { if wrapper.ArrayType(g.readMethodList).Has(res.Method) >= 0 {
// get类请求 // get类请求
res.ContentType = []string{consts.MimeTypeXWWWFormUrlencoded} requestContentType = consts.MimeTypeXWWWFormUrlencoded
} else { } else {
res.ContentType = []string{consts.MimeTypeJson} requestContentType = consts.MimeTypeJson
} }
} }
res.OutputContentType = strings.Split(metaField.Tag.Get(define.TagOutputContentType), ",") res.ContentType = strings.Split(requestContentType, ",")
if len(res.OutputContentType) == 0 { responseContentType := strings.TrimSpace(metaField.Tag.Get(define.TagOutputContentType))
if len(responseContentType) == 0 {
// 未设置响应类型默认JSON数据 // 未设置响应类型默认JSON数据
res.OutputContentType = []string{consts.MimeTypeJson} responseContentType = consts.MimeTypeJson
} }
res.OutputContentType = strings.Split(responseContentType, ",")
res.Summary = ParseStructField.Summary(metaField) res.Summary = ParseStructField.Summary(metaField)
if res.Method == "" { if res.Method == "" {
return nil, errors.New("baseCfg.Method is empty") return nil, errors.New("baseCfg.Method is empty")

View File

@ -27,7 +27,7 @@ type Meta struct {
func Test_parser_Openapi3(t *testing.T) { func Test_parser_Openapi3(t *testing.T) {
type User struct { type User struct {
Meta `json:"-" deprecated:"false" 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"` Name *string `json:"name" d:"zhang" desc:"用户姓名" binding:"required"`
Age string `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 UserDelete struct { type UserDelete struct {
@ -52,14 +52,15 @@ func Test_parser_Openapi3(t *testing.T) {
} }
type List struct { type List struct {
Total int64 `json:"total" binding:"required"` Total int64 `json:"total" binding:"required"`
UserList []User `json:"user_list"` UserList []*User `json:"user_list"`
UserDetail *User `json:"user_detail" desc:"用户详情"`
} }
var o List var o *List
var f User var f *User
var fd UserDelete var fd *UserDelete
var up UserPut var up *UserPut
var ug UserGet var ug *UserGet
var uh UserHead var uh *UserHead
g := NewOpenapiDoc(nil, []*define.ServerItem{ g := NewOpenapiDoc(nil, []*define.ServerItem{
&define.ServerItem{ &define.ServerItem{
Url: "http://127.0.0.1/v1", Url: "http://127.0.0.1/v1",
@ -78,11 +79,11 @@ func Test_parser_Openapi3(t *testing.T) {
Variables: nil, Variables: nil,
}, },
}) })
g.AddApiFromInAndOut(reflect.TypeOf(f), reflect.TypeOf(o)) g.AddApiFromInAndOut("", reflect.TypeOf(f), reflect.TypeOf(o))
g.AddApiFromInAndOut(reflect.TypeOf(fd), reflect.TypeOf(o)) g.AddApiFromInAndOut("", reflect.TypeOf(fd), reflect.TypeOf(o))
g.AddApiFromInAndOut(reflect.TypeOf(up), reflect.TypeOf(o)) g.AddApiFromInAndOut("", reflect.TypeOf(up), reflect.TypeOf(o))
g.AddApiFromInAndOut(reflect.TypeOf(ug), reflect.TypeOf(o)) g.AddApiFromInAndOut("", reflect.TypeOf(ug), reflect.TypeOf(o))
g.AddApiFromInAndOut(reflect.TypeOf(uh), reflect.TypeOf(o)) g.AddApiFromInAndOut("", reflect.TypeOf(uh), reflect.TypeOf(o))
byteData, _ := json.Marshal(g.docData) byteData, _ := json.Marshal(g.docData)
fmt.Println(string(byteData)) fmt.Println(string(byteData))
} }