feat: 优化 struct_field
This commit is contained in:
306
openapi/schema.go
Normal file
306
openapi/schema.go
Normal file
@@ -0,0 +1,306 @@
|
||||
// Package openapi ...
|
||||
//
|
||||
// Description : openapi 扩展版本,添加更多标签支持和类型映射
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-05 17:27
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 []any `json:"enum" dc:"枚举值列表"`
|
||||
OmitEmpty bool `json:"omit_empty" dc:"是否可控"`
|
||||
}
|
||||
|
||||
func getDesc(field reflect.StructField) string {
|
||||
|
||||
}
|
||||
|
||||
// ParseStructField 解析结构体字段信息
|
||||
func ParseStructField(field reflect.StructField) *StructFieldInfo {
|
||||
if !field.IsExported() {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &StructFieldInfo{
|
||||
Name: field.Name,
|
||||
Type: field.Type,
|
||||
}
|
||||
|
||||
// 解析 JSON tag
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" {
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
if len(parts) > 0 {
|
||||
if parts[0] == "-" {
|
||||
return nil // 跳过该字段
|
||||
}
|
||||
if parts[0] != "" {
|
||||
info.JSONName = parts[0]
|
||||
}
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
switch part {
|
||||
case "omitempty":
|
||||
info.OmitEmpty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if info.JSONName == "" {
|
||||
// 使用结构体字段名作为默认的 json tag
|
||||
info.JSONName = info.Name
|
||||
}
|
||||
|
||||
// 解析其他标签
|
||||
if desc := field.Tag.Get("description"); desc != "" {
|
||||
info.Description = desc
|
||||
}
|
||||
|
||||
if example := field.Tag.Get("example"); example != "" {
|
||||
info.Example = parseExampleValue(example, field.Type)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if enum := field.Tag.Get("enum"); enum != "" {
|
||||
enums := strings.Split(enum, ",")
|
||||
for _, e := range enums {
|
||||
info.Enum = append(info.Enum, strings.TrimSpace(e))
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func parseExampleValue(example string, t reflect.Type) interface{} {
|
||||
// 根据类型解析示例值
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return example
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if val, err := strconv.ParseInt(example, 10, 64); err == nil {
|
||||
return val
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if val, err := strconv.ParseFloat(example, 64); err == nil {
|
||||
return val
|
||||
}
|
||||
case reflect.Bool:
|
||||
if val, err := strconv.ParseBool(example); err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return example
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if info.Min != nil {
|
||||
schema.Min = &openapi3.Min{Value: *info.Min}
|
||||
}
|
||||
if info.Max != nil {
|
||||
schema.Max = &openapi3.Max{Value: *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 = info.Enum
|
||||
}
|
||||
}
|
||||
|
||||
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()}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// Package api_doc ...
|
||||
// Package util ...
|
||||
//
|
||||
// Description : api_doc ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-02-12 22:15
|
||||
package api_doc
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -23,10 +23,6 @@ type parseStructFieldTag struct {
|
||||
}
|
||||
|
||||
// GetParamName 获取参数名称
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:58 2025/2/11
|
||||
func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) string {
|
||||
paramNameTagList := []string{
|
||||
define.TagJson, define.TagForm,
|
||||
@@ -46,10 +42,6 @@ func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) str
|
||||
}
|
||||
|
||||
// GetParamDesc ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:01 2025/2/11
|
||||
func (psf parseStructFieldTag) GetParamDesc(structField reflect.StructField) string {
|
||||
descTagList := []string{define.TagDc, define.TagDesc, define.TagDescription}
|
||||
for _, tag := range descTagList {
|
||||
@@ -63,10 +55,6 @@ func (psf parseStructFieldTag) GetParamDesc(structField reflect.StructField) str
|
||||
}
|
||||
|
||||
// GetDefaultValue 获取默认值
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:05 2025/2/11
|
||||
func (psf parseStructFieldTag) GetDefaultValue(structField reflect.StructField) any {
|
||||
defaultTagList := []string{define.TagD, define.TagDefault}
|
||||
fieldType := structField.Type.Kind().String()
|
||||
@@ -104,10 +92,6 @@ func (psf parseStructFieldTag) GetDefaultValue(structField reflect.StructField)
|
||||
}
|
||||
|
||||
// GetValidateRule 获取验证规则
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:30 2025/2/13
|
||||
func (psf parseStructFieldTag) GetValidateRule(structField reflect.StructField) string {
|
||||
defaultTagList := []string{define.TagValidate, define.TagBinding}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -119,10 +103,6 @@ func (psf parseStructFieldTag) GetValidateRule(structField reflect.StructField)
|
||||
}
|
||||
|
||||
// Deprecated 是否弃用
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:12 2025/2/13
|
||||
func (psf parseStructFieldTag) Deprecated(structField reflect.StructField) bool {
|
||||
defaultTagList := []string{define.TagDeprecated}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -134,10 +114,6 @@ func (psf parseStructFieldTag) Deprecated(structField reflect.StructField) bool
|
||||
}
|
||||
|
||||
// Summary 摘要信息
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:15 2025/2/14
|
||||
func (psf parseStructFieldTag) Summary(structField reflect.StructField) string {
|
||||
defaultTagList := []string{define.TagSummary}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -153,10 +129,6 @@ func (psf parseStructFieldTag) Summary(structField reflect.StructField) string {
|
||||
}
|
||||
|
||||
// EnumDescription .枚举值详细描述
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:40 2025/2/18
|
||||
func (psf parseStructFieldTag) EnumDescription(structField reflect.StructField) map[string]string {
|
||||
defaultTagList := []string{define.TagNameEnumDescription}
|
||||
res := map[string]string{}
|
||||
@@ -181,10 +153,6 @@ func (psf parseStructFieldTag) EnumDescription(structField reflect.StructField)
|
||||
}
|
||||
|
||||
// GetExampleValue 示例值
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 14:42 2025/2/20
|
||||
func (psf parseStructFieldTag) GetExampleValue(structField reflect.StructField) any {
|
||||
descTagList := []string{define.TagEg, define.TagExample}
|
||||
fieldType := structField.Type.Kind().String()
|
||||
Reference in New Issue
Block a user