remove third party package 'structs'; improve performance for package internal/structs

This commit is contained in:
John 2020-11-08 14:25:17 +08:00
parent e1dd5cce7d
commit d178102f82
10 changed files with 220 additions and 197 deletions

View File

@ -312,32 +312,40 @@ func doQuoteString(s, charLeft, charRight string) string {
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
// This function automatically retrieves primary or unique field and its attribute value as condition.
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) {
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return "", nil, err
}
array := ([]string)(nil)
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) {
array = strings.Split(field.Tag, ",")
for _, field := range tagField {
array = strings.Split(field.CurrentTag, ",")
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
return array[0], []interface{}{field.Value()}
return array[0], []interface{}{field.Value()}, nil
}
if len(where) > 0 {
where += " "
}
where += field.Tag + "=?"
where += field.CurrentTag + "=?"
args = append(args, field.Value())
}
return
}
// GetPrimaryKey retrieves and returns primary key field name from given struct.
func GetPrimaryKey(pointer interface{}) string {
func GetPrimaryKey(pointer interface{}) (string, error) {
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return "", err
}
array := ([]string)(nil)
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) {
array = strings.Split(field.Tag, ",")
for _, field := range tagField {
array = strings.Split(field.CurrentTag, ",")
if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY {
return array[0]
return array[0], nil
}
}
return ""
return "", nil
}
// GetPrimaryKeyCondition returns a new where condition by primary field name.
@ -721,9 +729,13 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
// mapToStruct maps the <data> to given struct.
// Note that the given parameter <pointer> should be a pointer to s struct.
func mapToStruct(data map[string]interface{}, pointer interface{}) error {
tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return err
}
// It retrieves and returns the mapping between orm tag and the struct attribute name.
mapping := make(map[string]string)
for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}) {
for tag, attr := range tagNameMap {
mapping[strings.Split(tag, ",")[0]] = attr
}
return gconv.Struct(data, pointer, mapping)

View File

@ -14,27 +14,27 @@ import (
strip "github.com/grokify/html-strip-tags-go"
)
// 过滤掉HTML标签只返回text内容
// 参考:http://php.net/manual/zh/function.strip-tags.php
// StripTags strips HTML tags from content, and returns only text.
// Referer: http://php.net/manual/zh/function.strip-tags.php
func StripTags(s string) string {
return strip.StripTags(s)
}
// 本函数各方面都和SpecialChars一样
// 除了Entities会转换所有具有 HTML 实体的字符。
// 参考http://php.net/manual/zh/function.htmlentities.php
// Entities encodes all HTML chars for content.
// Referer: http://php.net/manual/zh/function.htmlentities.php
func Entities(s string) string {
return html.EscapeString(s)
}
// Entities 的相反操作
// 参考:http://php.net/manual/zh/function.html-entity-decode.php
// EntitiesDecode decodes all HTML chars for content.
// Referer: http://php.net/manual/zh/function.html-entity-decode.php
func EntitiesDecode(s string) string {
return html.UnescapeString(s)
}
// 将html中的部分特殊标签转换为html转义标签
// 参考http://php.net/manual/zh/function.htmlspecialchars.php
// SpecialChars encodes some special chars for content, these special chars are:
// "&", "<", ">", `"`, "'".
// Referer: http://php.net/manual/zh/function.htmlspecialchars.php
func SpecialChars(s string) string {
return strings.NewReplacer(
"&", "&amp;",
@ -45,8 +45,9 @@ func SpecialChars(s string) string {
).Replace(s)
}
// 将html部分转义标签还原为html特殊标签
// 参考http://php.net/manual/zh/function.htmlspecialchars-decode.php
// SpecialCharsDecode decodes some special chars for content, these special chars are:
// "&", "<", ">", `"`, "'".
// Referer: http://php.net/manual/zh/function.htmlspecialchars-decode.php
func SpecialCharsDecode(s string) string {
return strings.NewReplacer(
"&amp;", "&",

View File

@ -3,6 +3,7 @@
// 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 ghtml_test
import (

1
go.mod
View File

@ -9,7 +9,6 @@ require (
github.com/go-sql-driver/mysql v1.5.0
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/websocket v1.4.1
github.com/gqcn/structs v1.1.1
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
github.com/json-iterator/go v1.1.10
github.com/mattn/go-runewidth v0.0.9 // indirect

View File

@ -5,14 +5,46 @@
// You can obtain one at https://github.com/gogf/gf.
// Package structs provides functions for struct conversion.
//
// Inspired and improved from: https://github.com/fatih/structs
package structs
import "github.com/gqcn/structs"
import (
"reflect"
)
// Field is alias of structs.Field.
// Field contains information of a struct field .
type Field struct {
*structs.Field
value reflect.Value
field reflect.StructField
// Retrieved tag name. There might be more than one tags in the field,
// but only one can be retrieved according to calling function rules.
Tag string
CurrentTag string
}
// Tag returns the value associated with key in the tag string. If there is no
// such key in the tag, Tag returns the empty string.
func (f *Field) Tag(key string) string {
return f.field.Tag.Get(key)
}
// Value returns the underlying value of the field. It panics if the field
// is not exported.
func (f *Field) Value() interface{} {
return f.value.Interface()
}
// IsEmbedded returns true if the given field is an anonymous field (embedded)
func (f *Field) IsEmbedded() bool {
return f.field.Anonymous
}
// IsExported returns true if the given field is exported.
func (f *Field) IsExported() bool {
return f.field.PkgPath == ""
}
// Name returns the name of the given field
func (f *Field) Name() string {
return f.field.Name
}

View File

@ -6,12 +6,6 @@
package structs
import (
"reflect"
"github.com/gqcn/structs"
)
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map.
//
// The parameter <pointer> should be type of struct/*struct.
@ -21,75 +15,18 @@ import (
// The parameter <recursive> specifies whether retrieving the struct field recursively.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
// If <pointer> points to an invalid address, for example a nil variable,
// it here creates an empty struct using reflect feature.
var (
tempValue reflect.Value
pointerValue = reflect.ValueOf(pointer)
)
for pointerValue.Kind() == reflect.Ptr {
tempValue = pointerValue.Elem()
if !tempValue.IsValid() {
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
break
} else {
pointerValue = tempValue
func MapField(pointer interface{}, priority []string) (map[string]*Field, error) {
tagFields, err := getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
if err != nil {
return nil, err
}
tagFieldMap := make(map[string]*Field, len(tagFields))
for _, field := range tagFields {
tagField := field
tagFieldMap[field.Name()] = tagField
if tagField.CurrentTag != "" {
tagFieldMap[tagField.CurrentTag] = tagField
}
}
var (
fields []*structs.Field
fieldMap = make(map[string]*Field)
)
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(pointer)
}
var (
tag = ""
name = ""
)
for _, field := range fields {
name = field.Name()
// Only retrieve exported attributes.
if name[0] < byte('A') || name[0] > byte('Z') {
continue
}
fieldMap[name] = &Field{
Field: field,
Tag: tag,
}
tag = ""
for _, p := range priority {
tag = field.Tag(p)
if tag != "" {
break
}
}
if tag != "" {
fieldMap[tag] = &Field{
Field: field,
Tag: tag,
}
}
if recursive {
var (
rv = reflect.ValueOf(field.Value())
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
if kind == reflect.Struct {
for k, v := range MapField(rv, priority, true) {
if _, ok := fieldMap[k]; !ok {
fieldMap[k] = v
}
}
}
}
}
return fieldMap
return tagFieldMap, nil
}

View File

@ -7,9 +7,8 @@
package structs
import (
"errors"
"reflect"
"github.com/gqcn/structs"
)
// TagFields retrieves struct tags as []*Field from <pointer>, and returns it.
@ -17,94 +16,105 @@ import (
// The parameter <pointer> should be type of struct/*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagFields(pointer interface{}, priority []string) []*Field {
return doTagFields(pointer, priority, map[string]struct{}{})
func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
}
// doTagFields retrieves the tag and corresponding attribute name from <pointer>. It also filters repeated
// tag internally.
// The parameter <pointer> should be type of struct/*struct.
// TODO remove third-party package "structs" by reducing the reflect usage to improve the performance.
func doTagFields(pointer interface{}, priority []string, tagMap map[string]struct{}) []*Field {
// If <pointer> points to an invalid address, for example a nil variable,
// it here creates an empty struct using reflect feature.
func getFieldValues(value interface{}) ([]*Field, error) {
var (
tempValue reflect.Value
pointerValue = reflect.ValueOf(pointer)
reflectValue reflect.Value
reflectKind reflect.Kind
)
for pointerValue.Kind() == reflect.Ptr {
tempValue = pointerValue.Elem()
if !tempValue.IsValid() {
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
break
} else {
pointerValue = tempValue
}
}
var fields []*structs.Field
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
if v, ok := value.(reflect.Value); ok {
reflectValue = v
reflectKind = reflectValue.Kind()
} else {
var (
rv = reflect.ValueOf(pointer)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
// If pointer is type of **struct and nil, then automatically create a temporary struct,
// which is used for structs.Fields.
if kind == reflect.Ptr && (!rv.IsValid() || rv.IsNil()) {
fields = structs.Fields(reflect.New(rv.Type().Elem()).Elem().Interface())
reflectValue = reflect.ValueOf(value)
reflectKind = reflectValue.Kind()
}
if reflectKind == reflect.Ptr {
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
fields = structs.Fields(pointer)
// If pointer is type of **struct and nil, then automatically create a temporary struct.
var (
pointedValue = reflectValue.Elem()
pointedValueKind = pointedValue.Kind()
)
if pointedValueKind == reflect.Ptr && (!pointedValue.IsValid() || pointedValue.IsNil()) {
reflectValue = reflect.New(pointedValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = pointedValue
reflectKind = pointedValueKind
}
}
}
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Struct {
return nil, errors.New("given value should be type of struct/*struct")
}
var (
tag = ""
name = ""
structType = reflectValue.Type()
length = reflectValue.NumField()
fields = make([]*Field, length)
)
for i := 0; i < length; i++ {
fields[i] = &Field{
value: reflectValue.Field(i),
field: structType.Field(i),
}
}
return fields, nil
}
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]*Field, error) {
fields, err := getFieldValues(pointer)
if err != nil {
return nil, err
}
var (
tagName = ""
tagFields = make([]*Field, 0)
)
tagFields := make([]*Field, 0)
for _, field := range fields {
name = field.Name()
// Only retrieve exported attributes.
if name[0] < byte('A') || name[0] > byte('Z') {
if !field.IsExported() {
continue
}
tag = ""
tagName = ""
for _, p := range priority {
tag = field.Tag(p)
if tag != "" {
tagName = field.Tag(p)
if tagName != "" && tagName != "-" {
break
}
}
if tag != "" {
if tagName != "" {
// Filter repeated tag.
if _, ok := tagMap[tag]; ok {
if _, ok := tagMap[tagName]; ok {
continue
}
tagFields = append(tagFields, &Field{
Field: field,
Tag: tag,
})
tagField := field
tagField.CurrentTag = tagName
tagFields = append(tagFields, tagField)
}
// If this is an embedded attribute, it retrieves the tags recursively.
if field.IsEmbedded() {
var (
rv = reflect.ValueOf(field.Value())
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
if kind == reflect.Struct {
tagFields = append(tagFields, doTagFields(rv, priority, tagMap)...)
if subTagFields, err := getFieldValuesByTagPriority(field.value, priority, tagMap); err != nil {
return nil, err
} else {
tagFields = append(tagFields, subTagFields...)
}
}
}
return tagFields
return tagFields, nil
}
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
@ -112,13 +122,16 @@ func doTagFields(pointer interface{}, priority []string, tagMap map[string]struc
// The parameter <pointer> should be type of struct/*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapName(pointer interface{}, priority []string) map[string]string {
fields := TagFields(pointer, priority)
tagMap := make(map[string]string, len(fields))
for _, v := range fields {
tagMap[v.Tag] = v.Name()
func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
fields, err := TagFields(pointer, priority)
if err != nil {
return nil, err
}
return tagMap
tagMap := make(map[string]string, len(fields))
for _, field := range fields {
tagMap[field.CurrentTag] = field.Name()
}
return tagMap, nil
}
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
@ -126,11 +139,15 @@ func TagMapName(pointer interface{}, priority []string) map[string]string {
// The parameter <pointer> should be type of struct/*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapField(pointer interface{}, priority []string) map[string]*Field {
fields := TagFields(pointer, priority)
tagMap := make(map[string]*Field, len(fields))
for _, v := range fields {
tagMap[v.Tag] = v
func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) {
fields, err := TagFields(pointer, priority)
if err != nil {
return nil, err
}
return tagMap
tagMap := make(map[string]*Field, len(fields))
for _, field := range fields {
tagField := field
tagMap[field.CurrentTag] = tagField
}
return tagMap, nil
}

View File

@ -24,12 +24,17 @@ func Test_Basic(t *testing.T) {
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
}
var user User
t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
m, _ := structs.TagMapName(user, []string{"params"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
m, _ = structs.TagMapName(&user, []string{"params"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}), g.Map{"name": "Name", "pass1": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}), g.Map{"name": "Name", "pass2": "Pass"})
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
})
gtest.C(t, func(t *gtest.T) {
@ -43,7 +48,8 @@ func Test_Basic(t *testing.T) {
Base `params:"base"`
}
user := new(UserWithBase)
t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{
m, _ := structs.TagMapName(user, []string{"params"})
t.Assert(m, g.Map{
"base": "Base",
"password1": "Pass1",
"password2": "Pass2",
@ -67,8 +73,10 @@ func Test_Basic(t *testing.T) {
}
user1 := new(UserWithEmbeddedAttribute)
user2 := new(UserWithoutEmbeddedAttribute)
t.Assert(structs.TagMapName(user1, []string{"params"}), g.Map{"password1": "Pass1", "password2": "Pass2"})
t.Assert(structs.TagMapName(user2, []string{"params"}), g.Map{})
m, _ := structs.TagMapName(user1, []string{"params"})
t.Assert(m, g.Map{"password1": "Pass1", "password2": "Pass2"})
m, _ = structs.TagMapName(user2, []string{"params"})
t.Assert(m, g.Map{})
})
}
@ -80,11 +88,16 @@ func Test_StructOfNilPointer(t *testing.T) {
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
}
var user *User
t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
m, _ := structs.TagMapName(user, []string{"params"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
m, _ = structs.TagMapName(&user, []string{"params"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}), g.Map{"name": "Name", "pass1": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}), g.Map{"name": "Name", "pass2": "Pass"})
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
})
}

View File

@ -11,10 +11,10 @@ import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/internal/structs"
"reflect"
"strings"
"github.com/gogf/gf/internal/structs"
"github.com/gogf/gf/internal/utils"
)
@ -196,7 +196,11 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
// The key of the tagMap is the attribute name of the struct,
// and the value is its replaced tag name for later comparison to improve performance.
tagMap := make(map[string]string)
for k, v := range structs.TagMapName(pointer, StructTagPriority) {
tagToNameMap, err := structs.TagMapName(elem, StructTagPriority)
if err != nil {
return err
}
for k, v := range tagToNameMap {
tagMap[v] = utils.RemoveSymbols(k)
}
@ -263,8 +267,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
}
defer func() {
if e := recover(); e != nil {
err = bindVarToReflectValue(structFieldValue, value, mapping...)
if err != nil {
if err = bindVarToReflectValue(structFieldValue, value, mapping...); err != nil {
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
}
}

View File

@ -73,16 +73,24 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *
checkRules = v
}
// Checks and extends the parameters map with struct alias tag.
for nameOrTag, field := range structs.MapField(object, aliasNameTagPriority, true) {
mapField, err := structs.MapField(object, aliasNameTagPriority)
if err != nil {
return newErrorStr("invalid_object", err.Error())
}
for nameOrTag, field := range mapField {
params[nameOrTag] = field.Value()
params[field.Name()] = field.Value()
}
// It here must use structs.TagFields not structs.MapField to ensure error sequence.
for _, field := range structs.TagFields(object, structTagPriority) {
tagField, err := structs.TagFields(object, structTagPriority)
if err != nil {
return newErrorStr("invalid_object", err.Error())
}
for _, field := range tagField {
fieldName := field.Name()
// sequence tag == struct tag
// The name here is alias of field name.
name, rule, msg := parseSequenceTag(field.Tag)
name, rule, msg := parseSequenceTag(field.CurrentTag)
if len(name) == 0 {
name = fieldName
} else {