256 lines
6.9 KiB
Go
256 lines
6.9 KiB
Go
// Package openapi ...
|
|
//
|
|
// Description : openapi 扩展版本,添加更多标签支持和类型映射
|
|
//
|
|
// Author : go_developer@163.com<白茶清欢>
|
|
//
|
|
// Date : 2026-01-05 17:27
|
|
package openapi
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"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 *uint64 `json:"min_length" dc:"最小长度"`
|
|
MaxLength *uint64 `json:"max_length" dc:"最大长度"`
|
|
Pattern string `json:"pattern" dc:"模式"`
|
|
Format string `json:"format" dc:"格式"`
|
|
Enum []define.EnumValue `json:"enum" dc:"枚举值列表"`
|
|
OmitEmpty bool `json:"omit_empty" dc:"是否可控"`
|
|
}
|
|
|
|
// ParseStructField 解析结构体字段信息
|
|
func ParseStructField(field reflect.StructField) *StructFieldInfo {
|
|
if !field.IsExported() {
|
|
return nil
|
|
}
|
|
|
|
info := &StructFieldInfo{
|
|
Name: field.Name,
|
|
Type: field.Type,
|
|
}
|
|
|
|
util.ParseStructFieldTag.GetParamName(field)
|
|
// 解析 JSON tag
|
|
info.JSONName, info.OmitEmpty = util.ParseStructFieldTag.GetParamName(field)
|
|
|
|
// 解析参数描述
|
|
info.Description = util.ParseStructFieldTag.GetParamDesc(field)
|
|
// 解析示例值
|
|
info.Example = util.ParseStructFieldTag.GetExampleValue(field)
|
|
// 解析验证规则
|
|
if required := field.Tag.Get("required"); required == "true" {
|
|
info.Required = true
|
|
}
|
|
|
|
if min := field.Tag.Get("min"); min != "" {
|
|
if val, err := strconv.ParseFloat(min, 64); err == nil {
|
|
info.Min = &val
|
|
}
|
|
}
|
|
|
|
if max := field.Tag.Get("max"); max != "" {
|
|
if val, err := strconv.ParseFloat(max, 64); err == nil {
|
|
info.Max = &val
|
|
}
|
|
}
|
|
|
|
if minLen := field.Tag.Get("minLength"); minLen != "" {
|
|
if val, err := strconv.ParseUint(minLen, 10, 64); err == nil {
|
|
info.MinLength = &val
|
|
}
|
|
}
|
|
|
|
if maxLen := field.Tag.Get("maxLength"); maxLen != "" {
|
|
if val, err := strconv.ParseUint(maxLen, 10, 64); err == nil {
|
|
info.MaxLength = &val
|
|
}
|
|
}
|
|
|
|
if pattern := field.Tag.Get("pattern"); pattern != "" {
|
|
info.Pattern = pattern
|
|
}
|
|
|
|
if format := field.Tag.Get("format"); format != "" {
|
|
info.Format = format
|
|
}
|
|
|
|
// 解析枚举值
|
|
info.Enum = util.ParseStructFieldTag.EnumDescription(field)
|
|
|
|
return info
|
|
}
|
|
|
|
// GenerateOpenAPISchema 生成完整的 OpenAPI Schema
|
|
func GenerateOpenAPISchema(s interface{}) *openapi3.SchemaRef {
|
|
t := reflect.TypeOf(s)
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
|
|
return generateSchemaRecursive(t, make(map[string]bool))
|
|
}
|
|
|
|
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 = *info.MinLength
|
|
}
|
|
if info.MaxLength != nil {
|
|
schema.MaxLength = openapi3.Uint64Ptr(*info.MaxLength)
|
|
}
|
|
if info.Pattern != "" {
|
|
schema.Pattern = info.Pattern
|
|
}
|
|
if info.Format != "" {
|
|
schema.Format = info.Format
|
|
}
|
|
if len(info.Enum) > 0 {
|
|
schema.Enum = make([]any, 0)
|
|
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()}
|
|
}
|
|
}
|