feat: 优化 struct_field

This commit is contained in:
2026-01-05 17:47:12 +08:00
parent e031f56393
commit d74f82d060
2 changed files with 308 additions and 34 deletions

306
openapi/schema.go Normal file
View 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()}
}
}

View File

@@ -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()