233 lines
6.8 KiB
Go
233 lines
6.8 KiB
Go
// 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()
|
|
}
|
|
}
|
|
|
|
return generateSchemaRecursive(tType, make(map[string]bool))
|
|
}
|
|
|
|
// 生成 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()}
|
|
}
|
|
}
|