// Package openapi ... // // Description : openapi 扩展版本,添加更多标签支持和类型映射 // // Author : go_developer@163.com<白茶清欢> // // Date : 2026-01-05 17:27 package openapi import ( "reflect" "time" "git.zhangdeman.cn/zhangdeman/api-doc/define" "git.zhangdeman.cn/zhangdeman/api-doc/util" "github.com/getkin/kin-openapi/openapi3" ) // StructFieldInfo 结构体字段信息 type StructFieldInfo struct { Name string `json:"name" dc:"结构体字段名"` JSONName string `json:"json_name" dc:"json tag"` Type reflect.Type `json:"type" dc:"字段类型"` Description string `json:"description" dc:"参数描述"` Example any `json:"example" dc:"示例值"` Default any `json:"default" dc:"默认值"` Required bool `json:"required" dc:"是否必传"` Min *float64 `json:"min" dc:"最小值"` Max *float64 `json:"max" dc:"最大值"` MinLength *float64 `json:"min_length" dc:"最小长度"` MaxLength *float64 `json:"max_length" dc:"最大长度"` Pattern string `json:"pattern" dc:"模式"` Format string `json:"format" dc:"格式"` Enum []any `json:"enum" dc:"枚举值列表"` EnumDesc []define.EnumValue `json:"enum_desc" dc:"枚举值详细描述"` OmitEmpty bool `json:"omit_empty" dc:"是否可控"` } // ParseStructField 解析结构体字段信息 func ParseStructField(field reflect.StructField) *StructFieldInfo { if !field.IsExported() { return nil } // 解析验证规则 validateRule := util.ParseValidateRule(field.Type, util.ParseStructFieldTag.GetValidateRule(field)) info := &StructFieldInfo{ Name: field.Name, JSONName: "", Type: field.Type, Description: util.ParseStructFieldTag.GetParamDesc(field), // 解析参数描述 Example: util.ParseStructFieldTag.GetExampleValue(field), // 解析示例值 Default: util.ParseStructFieldTag.GetDefaultValue(field), // 解析默认值 Required: validateRule.Required, Min: validateRule.Min, Max: validateRule.Max, MinLength: validateRule.Min, MaxLength: validateRule.Max, Pattern: validateRule.Regexp, Format: field.Type.String(), Enum: validateRule.Oneof, // 解析枚举值 EnumDesc: util.ParseStructFieldTag.EnumDescription(field), // 解析枚举值描述 OmitEmpty: false, } // 解析 JSON tag info.JSONName, info.OmitEmpty = util.ParseStructFieldTag.GetParamName(field) return info } // GenerateOpenAPISchema 生成完整的 OpenAPI Schema func GenerateOpenAPISchema(s any) *openapi3.SchemaRef { var ( ok bool tType reflect.Type ) if tType, ok = s.(reflect.Type); !ok { tType = reflect.TypeOf(s) if tType.Kind() == reflect.Ptr { tType = tType.Elem() } } schema := generateSchemaRecursive(tType, make(map[string]bool)) return schema } // 生成 schema func generateSchemaRecursive(t reflect.Type, seen map[string]bool) *openapi3.SchemaRef { // 检查循环引用 typeName := t.PkgPath() + "." + t.Name() if seen[typeName] && t.Kind() == reflect.Struct { return &openapi3.SchemaRef{ Value: openapi3.NewObjectSchema(), } } seen[typeName] = true defer delete(seen, typeName) // 处理不同类型 switch t.Kind() { case reflect.Struct: // 特殊处理 time.Time if t == reflect.TypeOf(time.Time{}) { schema := openapi3.NewDateTimeSchema() return &openapi3.SchemaRef{Value: schema} } schema := openapi3.NewObjectSchema() for i := 0; i < t.NumField(); i++ { field := t.Field(i) info := ParseStructField(field) if info == nil { continue } fieldSchema := generateSchemaFromType(info.Type, seen) if fieldSchema == nil { continue } // 应用字段信息到 Schema applyFieldInfoToSchema(fieldSchema.Value, info) if info.Required && !info.OmitEmpty { schema.Required = append(schema.Required, info.JSONName) } schema.Properties[info.JSONName] = fieldSchema } return &openapi3.SchemaRef{Value: schema} case reflect.Slice, reflect.Array: elemType := t.Elem() elemSchema := generateSchemaRecursive(elemType, seen) if elemSchema == nil { return nil } return &openapi3.SchemaRef{ Value: openapi3.NewArraySchema().WithItems(elemSchema.Value), } case reflect.Map: if t.Key().Kind() != reflect.String { // OpenAPI 只支持字符串键 return &openapi3.SchemaRef{ Value: openapi3.NewObjectSchema(), } } valueType := t.Elem() valueSchema := generateSchemaRecursive(valueType, seen) if valueSchema == nil { return nil } return &openapi3.SchemaRef{ Value: openapi3.NewObjectSchema(). WithAdditionalProperties(valueSchema.Value), } case reflect.Ptr: return generateSchemaRecursive(t.Elem(), seen) case reflect.Interface: return &openapi3.SchemaRef{Value: openapi3.NewSchema()} default: return generatePrimitiveSchema(t) } } func generateSchemaFromType(t reflect.Type, seen map[string]bool) *openapi3.SchemaRef { if t.Kind() == reflect.Ptr { t = t.Elem() } return generateSchemaRecursive(t, seen) } func applyFieldInfoToSchema(schema *openapi3.Schema, info *StructFieldInfo) { if info.Description != "" { schema.Description = info.Description } if info.Example != nil { schema.Example = info.Example } schema.Min = info.Min if info.Max != nil { schema.Max = info.Max } if info.MinLength != nil { schema.MinLength = uint64(*info.MinLength) } if info.MaxLength != nil { schema.MaxLength = openapi3.Ptr(uint64(*info.MaxLength)) } if info.Pattern != "" { schema.Pattern = info.Pattern } if info.Format != "" { schema.Format = info.Format } if len(info.Enum) > 0 { schema.Enum = info.Enum /*for _, item := range info.Enum { schema.Enum = append(schema.Enum, item.Value) }*/ } } func generatePrimitiveSchema(t reflect.Type) *openapi3.SchemaRef { switch t.Kind() { case reflect.String: return &openapi3.SchemaRef{Value: openapi3.NewStringSchema()} case reflect.Bool: return &openapi3.SchemaRef{Value: openapi3.NewBoolSchema()} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: return &openapi3.SchemaRef{Value: openapi3.NewInt32Schema()} case reflect.Int64: return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema()} case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema().WithMin(0)} case reflect.Uint64: return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema().WithMin(0)} case reflect.Float32: return &openapi3.SchemaRef{Value: openapi3.NewFloat64Schema()} case reflect.Float64: return &openapi3.SchemaRef{Value: openapi3.NewFloat64Schema()} default: return &openapi3.SchemaRef{Value: openapi3.NewSchema()} } }