mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
150 lines
4.9 KiB
Go
150 lines
4.9 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package structcache
|
|
|
|
import (
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/internal/utils"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/util/gtag"
|
|
)
|
|
|
|
// CommonConverter holds some converting functions of common types for internal usage.
|
|
type CommonConverter struct {
|
|
Int64 func(any interface{}) int64
|
|
Uint64 func(any interface{}) uint64
|
|
String func(any interface{}) string
|
|
Float32 func(any interface{}) float32
|
|
Float64 func(any interface{}) float64
|
|
Time func(any interface{}, format ...string) time.Time
|
|
GTime func(any interface{}, format ...string) *gtime.Time
|
|
Bytes func(any interface{}) []byte
|
|
Bool func(any interface{}) bool
|
|
}
|
|
|
|
var (
|
|
// map[reflect.Type]*CachedStructInfo
|
|
cachedStructsInfoMap = sync.Map{}
|
|
|
|
// localCommonConverter holds some converting functions of common types for internal usage.
|
|
localCommonConverter CommonConverter
|
|
)
|
|
|
|
// RegisterCommonConverter registers the CommonConverter for local usage.
|
|
func RegisterCommonConverter(commonConverter CommonConverter) {
|
|
localCommonConverter = commonConverter
|
|
}
|
|
|
|
// GetCachedStructInfo retrieves or parses and returns a cached info for certain struct type.
|
|
// The given `structType` should be type of struct.
|
|
func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStructInfo {
|
|
if structType.Kind() != reflect.Struct {
|
|
return nil
|
|
}
|
|
// check if it has been cached.
|
|
cachedStructInfo, ok := getCachedConvertStructInfo(structType)
|
|
if ok {
|
|
// directly returns the cached struct info if already exists.
|
|
return cachedStructInfo
|
|
}
|
|
|
|
// else create one.
|
|
|
|
// it parses and generates a cache info for given struct type.
|
|
cachedStructInfo = &CachedStructInfo{
|
|
tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo),
|
|
}
|
|
var (
|
|
priorityTagArray []string
|
|
parentIndex = make([]int, 0)
|
|
)
|
|
if priorityTag != "" {
|
|
priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), gtag.StructTagPriority...)
|
|
} else {
|
|
priorityTagArray = gtag.StructTagPriority
|
|
}
|
|
parseStructToCachedStructInfo(structType, parentIndex, cachedStructInfo, priorityTagArray)
|
|
storeCachedStructInfo(structType, cachedStructInfo)
|
|
return cachedStructInfo
|
|
}
|
|
|
|
func storeCachedStructInfo(structType reflect.Type, cachedStructInfo *CachedStructInfo) {
|
|
// Temporarily enabled as an experimental feature
|
|
cachedStructsInfoMap.Store(structType, cachedStructInfo)
|
|
}
|
|
|
|
func getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, bool) {
|
|
// Temporarily enabled as an experimental feature
|
|
v, ok := cachedStructsInfoMap.Load(structType)
|
|
if ok {
|
|
return v.(*CachedStructInfo), ok
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// parseStructToCachedStructInfo parses given struct reflection type and stores its fields info into given CachedStructInfo.
|
|
// It stores nothing into CachedStructInfo if given struct reflection type has no fields.
|
|
func parseStructToCachedStructInfo(
|
|
structType reflect.Type,
|
|
fieldIndexes []int,
|
|
cachedStructInfo *CachedStructInfo,
|
|
priorityTagArray []string,
|
|
) {
|
|
var (
|
|
fieldName string
|
|
structField reflect.StructField
|
|
fieldType reflect.Type
|
|
)
|
|
// TODO:
|
|
// Check if the structure has already been cached in the cache.
|
|
// If it has been cached, some information can be reused,
|
|
// but the [FieldIndex] needs to be reset.
|
|
// We will not implement it temporarily because it is somewhat complex
|
|
for i := 0; i < structType.NumField(); i++ {
|
|
structField = structType.Field(i)
|
|
fieldType = structField.Type
|
|
fieldName = structField.Name
|
|
// Only do converting to public attributes.
|
|
if !utils.IsLetterUpper(fieldName[0]) {
|
|
continue
|
|
}
|
|
|
|
copyFieldIndexes := make([]int, len(fieldIndexes))
|
|
copy(copyFieldIndexes, fieldIndexes)
|
|
|
|
// normal basic attributes.
|
|
if structField.Anonymous {
|
|
// handle struct attributes, it might be struct/*struct embedded..
|
|
if fieldType.Kind() == reflect.Ptr {
|
|
fieldType = fieldType.Elem()
|
|
}
|
|
if fieldType.Kind() != reflect.Struct {
|
|
continue
|
|
}
|
|
// Skip the embedded structure of the 0 field,
|
|
if fieldType.NumField() == 0 {
|
|
continue
|
|
}
|
|
if structField.Tag != "" {
|
|
// Do not add anonymous structures without tags
|
|
cachedStructInfo.AddField(structField, append(copyFieldIndexes, i), priorityTagArray)
|
|
}
|
|
parseStructToCachedStructInfo(fieldType, append(copyFieldIndexes, i), cachedStructInfo, priorityTagArray)
|
|
continue
|
|
}
|
|
// Do not directly use append(fieldIndexes, i)
|
|
// When the structure is nested deeply, it may lead to bugs,
|
|
// which are caused by the slice expansion mechanism
|
|
// So it is necessary to allocate a separate index for each field
|
|
// See details https://github.com/gogf/gf/issues/3789
|
|
cachedStructInfo.AddField(structField, append(copyFieldIndexes, i), priorityTagArray)
|
|
}
|
|
}
|