// Package api_doc ... // // Description : api_doc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-07-22 15:55 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" ) // NewOpenapiDoc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:56 2024/7/22 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: "0.0.1", } } if len(info.Version) == 0 { info.Version = "0.0.1" } 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{ readMethodList: []string{ http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, }, docData: &define.OpenapiDoc{ 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), }, } } // Generate 文档生成实例 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:57 2024/7/22 type Generate struct { docData *define.OpenapiDoc readMethodList []string } func (g *Generate) Doc() *define.OpenapiDoc { return g.docData } // SetLicense 设置文档协议 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:56 2024/8/14 func (g *Generate) SetLicense(name string, url string) { g.docData.Info.License.Name = name g.docData.Info.License.Url = url } // AddTag 新增tag // // Author : go_developer@163.com<白茶清欢> // // Date : 14:23 2024/8/14 func (g *Generate) AddTag(tagName string, tagDesc string) { isHasTag := false for _, item := range g.docData.Tags { if item.Name == tagName { if len(tagDesc) > 0 { item.Description = tagDesc } isHasTag = true break } } if !isHasTag { g.docData.Tags = append(g.docData.Tags, &define.TagItem{ Name: tagName, Description: tagDesc, }) } } // AddServer 添加server // // Author : go_developer@163.com<白茶清欢> // // Date : 14:45 2024/8/14 func (g *Generate) AddServer(serverDomain string, serverDesc string, serverVariable map[string]*define.ServerItemVariable) { if nil == serverVariable { serverVariable = make(map[string]*define.ServerItemVariable) } serverDomain = strings.TrimRight(serverDomain, "/") isHasServer := false for _, item := range g.docData.Servers { if item.Url != serverDomain { continue } isHasServer = true if len(serverDesc) > 0 { item.Description = serverDesc } for varName, varValue := range serverVariable { item.Variables[varName] = varValue } break } if !isHasServer { g.docData.Servers = append(g.docData.Servers, &define.ServerItem{ Url: serverDomain, Description: serverDesc, Variables: serverVariable, }) } } // AddApi 新增Api // // Author : go_developer@163.com<白茶清欢> // // Date : 15:04 2024/8/14 // // baseCfg : 接口基础配置, 示例数据 // // &define.UriBaseConfig{ // Uri: "/foo/bar", // Method: http.MethodPost, // ContentType: ["application/json"], // TagList: []string{"测试标签"}, // Summary: "这是一份示例基础配置", // Description: "这是一份示例基础配置", // } // // paramList : 参数列表 // // resultList : 返回值列表 func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.ParamConfig, resultList []*define.ResultConfig) error { return nil } // AddApiFromInAndOut 通过请求参数的 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:22 2025/2/9 func (g *Generate) AddApiFromInAndOut(uriPrefix string, paramType reflect.Type, resultType reflect.Type) error { if paramType.Kind() == reflect.Ptr { paramType = paramType.Elem() } if resultType.Kind() == reflect.Ptr { resultType = resultType.Elem() } baseCfg, err := g.parseBaseUriConfig(uriPrefix, paramType) if nil != err { return err } if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } // 接口文档初始化 cfg := g.getApiDocBaseCfg(baseCfg, paramType) if wrapper.ArrayType(g.readMethodList).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 { cfg := &define.PathItemOperationConfig{ Tags: baseCfg.TagList, Summary: baseCfg.Summary, Description: baseCfg.Description, ExternalDocs: nil, OperationID: baseCfg.Summary + "(" + baseCfg.Method + "-" + strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "-") + ")", Parameters: make([]*define.PathConfigParameter, 0), RequestBody: &define.RequestBody{ Required: true, Description: "", Content: map[string]*define.Media{}, Ref: "", }, Responses: map[string]*define.Response{ fmt.Sprintf("%v", http.StatusOK): &define.Response{ Content: map[string]*define.Media{}, }, }, Callbacks: nil, Deprecated: baseCfg.Deprecated, 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, }) } } return cfg } // 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") } if nil == baseReqCfg.Parameters { baseReqCfg.Parameters = make([]*define.PathConfigParameter, 0) } for i := 0; i < inputType.NumField(); i++ { if inputType.Field(i).Anonymous { // 匿名字段, 直接对齐到当前的父级 g.ParseReadConfigParam(requestCfg, baseReqCfg, inputType.Field(i).Type) continue } propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i)) if propertyName == "-" { continue } 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 { // 空Meta字段认为是用来描述元信息的, 忽略 continue } convertBaseType, isBaseType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) realInputTypeFormat := inputType.Field(i).Type.String() fieldType := inputType.Field(i).Type if isBaseType { // 当做默认基础类型, 默认不会出现 *map *[] itemParam := &define.PathConfigParameter{ Name: propertyName, In: consts.SwaggerParameterInQuery, Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), Schema: &define.Schema{ Type: convertBaseType, Format: realInputTypeFormat, }, AllowEmptyValue: false, Style: "", Explode: false, AllowReserved: false, } g.setStructFieldProperty(itemParam.Schema, inputType.Field(i)) baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam) continue } if inputType.Field(i).Type.Kind() == reflect.Interface { itemParam := &define.PathConfigParameter{ Name: ParseStructFieldTag.GetParamName(inputType.Field(i)), In: consts.SwaggerParameterInQuery, Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), Schema: &define.Schema{ OneOf: g.anyTypeConfig(inputType.Field(i)).OneOf, Format: realInputTypeFormat, }, } g.setStructFieldProperty(itemParam.Schema, inputType.Field(i)) baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam) continue } 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{ Name: propertyName, In: consts.SwaggerParameterInQuery, Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), Schema: &define.Schema{ // Format: realInputTypeFormat, Ref: g.getSchemaRef(schemaNameNext), }, AllowEmptyValue: false, Style: "", Explode: false, AllowReserved: false, }) } else { } continue } if fieldType.Kind() == reflect.Struct || fieldType.Kind() == reflect.Map || fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice { baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ Name: ParseStructFieldTag.GetParamName(inputType.Field(i)), In: consts.SwaggerParameterInQuery, Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), Required: ValidateRule.IsRequired(inputType.Field(i)), Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), Schema: &define.Schema{ Type: convertBaseType, Items: nil, Ref: "", Format: realInputTypeFormat, Enum: ValidateRule.Enum(inputType.Field(i)), XEnumDescription: ParseStructFieldTag.EnumDescription(inputType.Field(i)), Example: ParseStructFieldTag.GetExampleValue(inputType.Field(i)), }, AllowEmptyValue: false, Style: "", Explode: false, AllowReserved: false, Ref: "", }) } } 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 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 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(), ".") inputName := inputNameArr[len(inputNameArr)-1] schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", ".") if schemaName == "-" { // 忽略的属性 return schemaName } if _, exist := g.docData.Components.Schemas[schemaName]; exist { // 已存在, 无需重复生成 return schemaName } if _, exist := g.docData.Components.Schemas[schemaName]; !exist { s := &define.Schema{ 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 = "" } g.docData.Components.Schemas[schemaName] = s } if inputType.Kind() == reflect.Map { // map, 直接添加公共 g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeObject return schemaName } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType) propertyXOf := &define.PropertyXOf{} if itemIsBaseType { propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType) propertyXOf.Format = sliceItemType propertyXOf.Ref = "" } else { propertyXOf.Type = "" propertyXOf.Format = "" propertyXOf.Ref = g.getSchemaRef(sliceItemType) } if len(rootSchemaName) == 0 { g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray g.docData.Components.Schemas[schemaName].Items = propertyXOf } else { g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.String(), Items: propertyXOf, } } return schemaName } // 结构体 if inputType.Kind() == reflect.Struct { for i := 0; i < inputType.NumField(); i++ { propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i)) if propertyName == "-" { continue } if inputType.Field(i).Anonymous { // 处理匿名字段 g.handleAnonymousField(schemaName, inputType.Field(i)) continue } if inputType.Kind() == reflect.Interface { // 处理interface{}类型参数 g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i)) continue } if inputType.Field(i).Type.Kind() == reflect.Ptr { // 处理指针 if inputType.Field(i).Type.Elem().Kind() == reflect.Struct { // 结构体指针 schemaNameNext := g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type.Elem()) g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ // Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), Ref: g.getSchemaRef(schemaNameNext), } } else { // 当做默认基础类型, 默认不会出现 *map *[] convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: convertBaseType, Format: inputType.Field(i).Type.String(), } } // 设置参数各种属性 g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i)) 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 || inputType.Field(i).Type.Kind() == reflect.Map { g.docData.Components.Schemas[schemaName].Properties[propertyName] = &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 { sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType.Field(i).Type) propertyXOf := &define.PropertyXOf{} if itemIsBaseType { propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType) propertyXOf.Format = sliceItemType propertyXOf.Ref = "" } else { propertyXOf.Type = "" propertyXOf.Format = "" propertyXOf.Ref = g.getSchemaRef(sliceItemType) } g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.Field(i).Type.String(), Items: propertyXOf, } } else { g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type) } } else { if inputType.Field(i).Type.Kind() == reflect.Interface { g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i)) g.docData.Components.Schemas[schemaName].Properties[propertyName].Example = ParseStructFieldTag.GetExampleValue(inputType.Field(i)) } else { convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ Type: convertBaseType, Format: inputType.Field(i).Type.String(), } } } // 设置参数各种属性 g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i)) } return schemaName } // 指针 if inputType.Kind() == reflect.Ptr { if inputType.Elem().Kind() == reflect.Struct { // 非基础数据类型 return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem()) } else { convertType, _ := g.realBaseType2SwaggerType(inputType.String()) g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ Type: convertType, Format: inputType.String(), Properties: map[string]*define.Property{}, } } } return schemaName } // handleAnonymousField 处理匿名字段 // // Author : go_developer@163.com<白茶清欢> // // Date : 18:43 2025/2/17 func (g *Generate) handleAnonymousField(schemaName string, field reflect.StructField) { if !field.Anonymous { // 不是匿名字段 return } handleType := field.Type if handleType.Kind() == reflect.Ptr { handleType = handleType.Elem() } for i := 0; i < handleType.NumField(); i++ { itemField := handleType.Field(i) if itemField.Anonymous { // 递归处理多层嵌套匿名字段 g.handleAnonymousField(schemaName, itemField) continue } else { baseConvertType, isBaseType := g.realBaseType2SwaggerType(itemField.Type.String()) if !isBaseType { paramName := ParseStructFieldTag.GetParamName(itemField) if itemField.Type.Kind() == reflect.Interface { g.docData.Components.Schemas[schemaName].Properties[paramName] = g.anyTypeConfig(itemField) } g.AddComponentsSchema(schemaName, handleType.Field(i).Type.PkgPath(), handleType.Field(i).Type) continue } else { paramName := ParseStructFieldTag.GetParamName(itemField) g.docData.Components.Schemas[schemaName].Properties[paramName] = &define.Property{ Type: baseConvertType, Format: itemField.Type.String(), Example: ParseStructFieldTag.GetExampleValue(itemField), Enum: ValidateRule.Enum(itemField), XEnumDescription: ParseStructFieldTag.EnumDescription(itemField), Default: ParseStructFieldTag.GetDefaultValue(handleType.Field(i)), Description: ParseStructFieldTag.GetParamDesc(handleType.Field(i)), } if ValidateRule.IsRequired(itemField) { g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName) } } } } } // parseSliceItem 解析数组每一项 // // Author : go_developer@163.com<白茶清欢> // // Date : 21:33 2025/2/8 func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) (string, bool) { if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { // 不是数组 return "", false } sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceItemType := sliceValue.Index(0).Type() realSliceItemType := sliceItemType.String() if sliceItemType.Kind() == reflect.Ptr { sliceItemType = sliceItemType.Elem() } _, isBaseType := g.realBaseType2SwaggerType(sliceItemType.String()) if isBaseType { return realSliceItemType, true } g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) if len(sliceItemType.PkgPath()) == 0 { return realSliceItemType, false } return sliceItemType.PkgPath() + "." + sliceItemType.Name(), false } // getSchemaRef 获取引用的类型 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:25 2025/2/9 func (g *Generate) getSchemaRef(schemaName string) string { if "" == schemaName { return "" } schemaName = strings.ReplaceAll(schemaName, "*", "") // 去除指针类型 * convertType, isBaseType := g.realBaseType2SwaggerType(schemaName) if isBaseType { return convertType } return "#/components/schemas/" + strings.ReplaceAll(convertType, "/", ".") } // realType2SwaggerType golang 真实数据类型转换为golang数据类型 // // Author : go_developer@163.com<白茶清欢> // // Date : 20:25 2025/2/11 func (g *Generate) realBaseType2SwaggerType(realType string) (string, bool) { switch realType { case "bool", "*bool": return consts.SwaggerDataTypeBoolean, true case "string", "*string": return consts.SwaggerDataTypeString, true case "byte", "*byte": return consts.SwaggerDataTypeByte, true case "float32", "*float32", "float64", "*float64": return consts.SwaggerDataTypeDouble, true case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": return consts.SwaggerDataTypeInteger, true case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": return consts.SwaggerDataTypeInteger, true default: if strings.HasPrefix(realType, "[]") { return consts.SwaggerDataTypeArray, true } return realType, false } } // setStructFieldProperty 添加struct_field各种属性 // // Author : go_developer@163.com<白茶清欢> // // Date : 16:13 2025/2/13 func (g *Generate) setStructFieldProperty(schema *define.Schema, structField reflect.StructField) { paramName := ParseStructFieldTag.GetParamName(structField) if paramName == "" || paramName == "-" { return } isRequired := ValidateRule.IsRequired(structField) enum := ValidateRule.Enum(structField) xEnumDescription := ParseStructFieldTag.EnumDescription(structField) example := ParseStructFieldTag.GetExampleValue(structField) description := ParseStructFieldTag.GetParamDesc(structField) maxVal := ValidateRule.Maximum(structField) minVal := ValidateRule.Minimum(structField) if nil != schema { if isRequired { schema.Required = append(schema.Required, paramName) } if nil == schema.Properties[paramName] { if schema.Type == consts.SwaggerDataTypeString { schema.MinLength = minVal schema.MaxLength = maxVal } else { schema.Minimum = minVal schema.Maximum = maxVal } schema.Enum = enum schema.XEnumDescription = xEnumDescription schema.Example = example schema.Description = description return } if schema.Properties[paramName].Type == consts.SwaggerDataTypeString { schema.Properties[paramName].MinLength = minVal schema.Properties[paramName].MaxLength = maxVal } else { schema.Properties[paramName].Minimum = minVal schema.Properties[paramName].Maximum = maxVal } schema.Properties[paramName].Enum = enum schema.Properties[paramName].XEnumDescription = xEnumDescription schema.Properties[paramName].Example = example schema.Properties[paramName].Description = description } } // parseBaseUriConfig 通过Meta字段解析Uri基础配置信息 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:13 2025/2/14 func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type) (*define.UriBaseConfig, error) { // 解析meta信息 metaField, metaFieldExist := paramType.FieldByName("Meta") if !metaFieldExist { return nil, errors.New("Meta field not found") } 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.TagPath) if len(uriPrefix) > 0 { 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.Description = metaField.Tag.Get(define.TagDesc) res.TagList = strings.Split(metaField.Tag.Get(define.TagUriTag), ",") // 解析第一个返回值, 要求必须是结构体或者是map outputStrictModel := metaField.Tag.Get(define.TagOutputStrict) res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" deprecated := metaField.Tag.Get(define.TagDeprecated) res.Deprecated = deprecated == "1" || deprecated == "true" requestContentType := strings.TrimSpace(metaField.Tag.Get(define.TagContentType)) if len(requestContentType) == 0 { if wrapper.ArrayType(g.readMethodList).Has(res.Method) >= 0 { // get类请求 requestContentType = consts.MimeTypeXWWWFormUrlencoded } else { requestContentType = consts.MimeTypeJson } } res.ContentType = strings.Split(requestContentType, ",") responseContentType := strings.TrimSpace(metaField.Tag.Get(define.TagOutputContentType)) if len(responseContentType) == 0 { // 未设置响应类型默认JSON数据 responseContentType = consts.MimeTypeJson } res.OutputContentType = strings.Split(responseContentType, ",") res.Summary = ParseStructFieldTag.Summary(metaField) if len(res.Summary) == 0 { res.Summary = wrapper.String(strings.ReplaceAll(strings.TrimLeft(res.Uri, "/"), "/", "_")).SnakeCaseToCamel() } if res.Method == "" { return nil, errors.New("baseCfg.Method is empty") } if res.Uri == "" { return nil, errors.New("baseCfg.Uri is empty") } if nil == g.docData.Tags { g.docData.Tags = make([]*define.TagItem, 0) } // 增加tag for _, itemTag := range res.TagList { exist := false for _, t := range g.docData.Tags { if itemTag == t.Name { exist = true break } } if !exist { g.docData.Tags = append(g.docData.Tags, &define.TagItem{ Name: itemTag, Description: itemTag, }) } } return res, nil } // anyTypeConfig 任意类型数据配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:33 2025/2/19 func (g *Generate) anyTypeConfig(structField reflect.StructField) *define.Property { return &define.Property{ Description: ParseStructFieldTag.GetParamDesc(structField), OneOf: []*define.PropertyXOf{ { Type: consts.SwaggerDataTypeObject, Format: "map[string]any", }, /* { Type: consts.SwaggerDataTypeArray, Format: "[]any", Items: &define.PropertyXOf{ Items: nil, }, },*/ { Type: consts.SwaggerDataTypeInteger, Format: "int/uint", }, { Type: consts.SwaggerDataTypeNumber, Format: "int/uint/float", }, { Type: consts.SwaggerDataTypeString, Format: "string", }, { Type: consts.SwaggerDataTypeBoolean, Format: "bool", }, }, } }