merge master

This commit is contained in:
John
2019-07-06 16:02:05 +08:00
42 changed files with 809 additions and 166 deletions

View File

@ -49,7 +49,7 @@
1. gdb的Cache缓存功能增加可自定义缓存接口以便支持外部缓存功能缓存接口可以通过io.ReadWriter接口实现
1. grpool增加支持阻塞添加任务接口
1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用
1. 增加g.Table快捷方法以方便操作数据表但是得考虑后续模型操作设计特别是脚手架的模型管理
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换

View File

@ -77,9 +77,9 @@ type DB interface {
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
// 创建链式操作对象(Table为From的别名)
Table(tables string) *Model
// 创建链式操作对象
From(tables string) *Model
Table(tables string) *Model
// 设置管理
SetDebug(debug bool)

View File

@ -206,15 +206,12 @@ func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interfac
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
case reflect.Array, reflect.Slice:
return bs.db.GetStructs(objPointer, query, args...)
case reflect.Struct:
return bs.db.GetStruct(objPointer, query, args...)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
// 数据库查询,获取查询字段值

View File

@ -8,6 +8,7 @@ package gdb
import (
"bytes"
"database/sql"
"errors"
"fmt"
"reflect"
@ -182,7 +183,7 @@ func printSql(v *Sql) {
// 格式化错误信息
func formatError(err error, query string, args ...interface{}) error {
if err != nil {
if err != nil && err != sql.ErrNoRows {
errStr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errStr += fmt.Sprintf("DB QUERY: %s\n", query)
if len(args) > 0 {

View File

@ -511,21 +511,18 @@ func (md *Model) Structs(objPointerSlice interface{}) error {
// 链式操作将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (md *Model) Scan(objPointer interface{}) error {
t := reflect.TypeOf(objPointer)
func (md *Model) Scan(pointer interface{}) error {
t := reflect.TypeOf(pointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
switch t.Elem().Kind() {
case reflect.Array:
case reflect.Slice:
return md.Structs(objPointer)
case reflect.Struct:
return md.Struct(objPointer)
return md.Structs(pointer)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
return md.Struct(pointer)
}
return nil
}

View File

@ -7,6 +7,8 @@
package gdb
import (
"database/sql"
"github.com/gogf/gf/g/encoding/gparser"
)
@ -33,5 +35,8 @@ func (r Record) ToMap() Map {
// 将Map变量映射到指定的struct对象中注意参数应当是一个对象的指针
func (r Record) ToStruct(pointer interface{}) error {
if r == nil {
return sql.ErrNoRows
}
return mapToStruct(r.ToMap(), pointer)
}

View File

@ -7,9 +7,11 @@
package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/g/encoding/gparser"
"reflect"
"github.com/gogf/gf/g/encoding/gparser"
)
// 将结果集转换为JSON字符串
@ -100,28 +102,32 @@ func (r Result) ToUintRecord(key string) map[uint]Record {
}
// 将结果列表转换为指定对象的slice。
func (r Result) ToStructs(objPointerSlice interface{}) error {
func (r Result) ToStructs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return nil
return sql.ErrNoRows
}
t := reflect.TypeOf(objPointerSlice)
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", t.Kind())
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
}
a := reflect.MakeSlice(t.Elem(), l, l)
itemType := a.Index(0).Type()
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e.Addr())
if err = r[i].ToStruct(e); err != nil {
return err
}
array.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e)
if err = r[i].ToStruct(e); err != nil {
return err
}
array.Index(i).Set(e)
}
}
reflect.ValueOf(objPointerSlice).Elem().Set(a)
reflect.ValueOf(pointer).Elem().Set(array)
return nil
}

View File

@ -7,6 +7,7 @@
package gdb_test
import (
"database/sql"
"testing"
"github.com/gogf/gf/g"
@ -357,6 +358,53 @@ func TestModel_Struct(t *testing.T) {
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
// Auto creating struct object.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := (*User)(nil)
err := db.Table("user").Where("id=1").Struct(&user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
// Just using Scan.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := (*User)(nil)
err := db.Table("user").Where("id=1").Scan(&user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=-1").Struct(user)
gtest.Assert(err, sql.ErrNoRows)
})
}
func TestModel_Structs(t *testing.T) {
@ -382,6 +430,7 @@ func TestModel_Structs(t *testing.T) {
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
// Auto create struct slice.
gtest.Case(t, func() {
type User struct {
Id int
@ -404,6 +453,41 @@ func TestModel_Structs(t *testing.T) {
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
// Just using Scan.
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").Where("id<0").Structs(&users)
gtest.Assert(err, sql.ErrNoRows)
})
}
func TestModel_Scan(t *testing.T) {
@ -483,6 +567,22 @@ func TestModel_Scan(t *testing.T) {
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
users := new([]*User)
err1 := db.Table("user").Where("id < 0").Scan(user)
err2 := db.Table("user").Where("id < 0").Scan(users)
gtest.Assert(err1, sql.ErrNoRows)
gtest.Assert(err2, sql.ErrNoRows)
})
}
func TestModel_OrderBy(t *testing.T) {

View File

@ -0,0 +1,15 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 structs provides functions for struct conversion.
package structs
import (
"github.com/gogf/gf/third/github.com/fatih/structs"
)
// Field is alias of structs.Field.
type Field = structs.Field

View File

@ -0,0 +1,64 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 structs
import (
"reflect"
"github.com/gogf/gf/third/github.com/fatih/structs"
)
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns it.
//
// 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 {
fieldMap := make(map[string]*Field)
fields := ([]*structs.Field)(nil)
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(pointer)
}
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
tag = ""
for _, p := range priority {
tag = field.Tag(p)
if tag != "" {
break
}
}
if tag != "" {
fieldMap[tag] = field
}
if recursive {
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
}

View File

@ -0,0 +1,81 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 structs
import (
"reflect"
"github.com/gogf/gf/third/github.com/fatih/structs"
)
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
//
// 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 TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string {
tagMap := TagMapField(pointer, priority, recursive)
if len(tagMap) > 0 {
m := make(map[string]string, len(tagMap))
for k, v := range tagMap {
m[k] = v.Name()
}
return m
}
return nil
}
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
//
// 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 TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
tagMap := make(map[string]*Field)
fields := ([]*structs.Field)(nil)
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(pointer)
}
tag := ""
name := ""
for _, field := range fields {
name = field.Name()
// Only retrieve exported attributes.
if name[0] < byte('A') || name[0] > byte('Z') {
continue
}
tag = ""
for _, p := range priority {
tag = field.Tag(p)
if tag != "" {
break
}
}
if tag != "" {
tagMap[tag] = field
}
if recursive {
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 TagMapField(rv, priority, true) {
if _, ok := tagMap[k]; !ok {
tagMap[k] = v
}
}
}
}
}
return tagMap
}

View File

@ -0,0 +1,73 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 structs_test
import (
"testing"
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/test/gtest"
)
func Test_Basic(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Name string `params:"name"`
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
}
var user User
gtest.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
gtest.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
gtest.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
gtest.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
gtest.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
})
gtest.Case(t, func() {
type Base struct {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
}
type UserWithBase struct {
Id int
Name string
Base `params:"base"`
}
user := new(UserWithBase)
gtest.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{
"base": "Base",
"password1": "Pass1",
"password2": "Pass2",
})
})
gtest.Case(t, func() {
type Base struct {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
}
type UserWithBase1 struct {
Id int
Name string
Base
}
type UserWithBase2 struct {
Id int
Name string
Pass Base
}
user1 := new(UserWithBase1)
user2 := new(UserWithBase2)
gtest.Assert(structs.TagMapName(user1, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
gtest.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
})
}

View File

@ -6,3 +6,7 @@
// Package ghttp provides powerful http server and simple client implements.
package ghttp
var (
paramTagPriority = []string{"param", "params"}
)

View File

@ -8,14 +8,14 @@ package ghttp
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/third/github.com/fatih/structs"
"io/ioutil"
"net/http"
"strings"
)
// 请求对象
@ -232,17 +232,3 @@ func (r *Request) GetUrl() string {
func (r *Request) GetReferer() string {
return r.Header.Get("Referer")
}
// 获得结构体对象的参数名称标签构成map返回
func (r *Request) getStructParamsTagMap(pointer interface{}) map[string]string {
tagMap := make(map[string]string)
fields := structs.Fields(pointer)
for _, field := range fields {
if tag := field.Tag("params"); tag != "" {
for _, v := range strings.Split(tag, ",") {
tagMap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagMap
}

View File

@ -7,6 +7,7 @@
package ghttp
import (
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g/util/gconv"
)
@ -143,7 +144,7 @@ func (r *Request) GetPostMap(def ...map[string]string) map[string]string {
// 将所有的request参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := r.getStructParamsTagMap(pointer)
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v

View File

@ -7,8 +7,10 @@
package ghttp
import (
"github.com/gogf/gf/g/util/gconv"
"strings"
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g/util/gconv"
)
// 初始化GET请求参数
@ -151,7 +153,7 @@ func (r *Request) GetQueryMap(def ...map[string]string) map[string]string {
// 将所有的get参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]string) error {
tagmap := r.getStructParamsTagMap(pointer)
tagmap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagmap[k] = v
@ -161,5 +163,5 @@ func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]st
for k, v := range r.GetQueryMap() {
params[k] = v
}
return gconv.Struct(params, pointer, tagmap)
return gconv.StructDeep(params, pointer, tagmap)
}

View File

@ -8,6 +8,7 @@ package ghttp
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g/util/gconv"
)
@ -133,7 +134,7 @@ func (r *Request) GetRequestMap(def ...map[string]string) map[string]string {
// 将所有的request参数映射到struct属性上参数object应当为一个struct对象的指针, mapping为非必需参数自定义参数与属性的映射关系
func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := r.getStructParamsTagMap(pointer)
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v
@ -148,5 +149,5 @@ func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]
params = j.ToMap()
}
}
return gconv.Struct(params, pointer, tagMap)
return gconv.StructDeep(params, pointer, tagMap)
}

View File

@ -4,16 +4,16 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 请求参数测试
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Params_Basic(t *testing.T) {
@ -97,6 +97,30 @@ func Test_Params_Basic(t *testing.T) {
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.BindHandler("/struct-with-base", func(r *ghttp.Request) {
type Base struct {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
}
type UserWithBase1 struct {
Id int
Name string
Base
}
type UserWithBase2 struct {
Id int
Name string
Pass Base
}
if m := r.GetPostMap(); len(m) > 0 {
user1 := new(UserWithBase1)
user2 := new(UserWithBase2)
r.GetToStruct(user1)
r.GetToStruct(user2)
r.Response.Write(user1.Id, user1.Name, user1.Pass1, user1.Pass2)
r.Response.Write(user2.Id, user2.Name, user2.Pass.Pass1, user2.Pass.Pass2)
}
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
@ -144,5 +168,6 @@ func Test_Params_Basic(t *testing.T) {
// Struct
gtest.Assert(client.GetContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
gtest.Assert(client.PostContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
gtest.Assert(client.PostContent("/struct-with-base", `id=1&name=john&password1=123&password2=456`), "1john1234561john123456")
})
}

View File

@ -44,9 +44,10 @@ func (w *Watcher) addWithCallbackFunc(path string, callbackFunc func(event *Even
path = t
}
callback = &Callback{
Id: callbackIdGenerator.Add(1),
Func: callbackFunc,
Path: path,
Id: callbackIdGenerator.Add(1),
Func: callbackFunc,
Path: path,
recursive: true,
}
if len(recursive) > 0 {
callback.recursive = recursive[0]

View File

@ -7,6 +7,8 @@
package gfsnotify
import (
"fmt"
"github.com/gogf/gf/g/container/glist"
)
@ -21,7 +23,7 @@ func (w *Watcher) startWatchLoop() {
// 监听事件
case ev := <-w.watcher.Events:
//fmt.Println("ev:", ev.String())
fmt.Println("ev:", ev.String())
w.cache.SetIfNotExist(ev.String(), func() interface{} {
w.events.Push(&Event{
event: ev,

View File

@ -285,7 +285,8 @@ func compareMap(value, expect interface{}) error {
}
for k, v := range mExpect {
if v != mValue[k] {
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == %v`, k, mValue[k], v)
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == map["%v"]:%v`+
"\nGIVEN : %v\nEXPECT: %v", k, mValue[k], k, v, mValue, mExpect)
}
}
} else {

View File

@ -9,10 +9,11 @@ package gconv
import (
"encoding/json"
"github.com/gogf/gf/g/encoding/gbinary"
"reflect"
"strconv"
"strings"
"github.com/gogf/gf/g/encoding/gbinary"
)
// Type assert api for String().
@ -25,6 +26,10 @@ type apiError interface {
Error() string
}
const (
gGCONV_TAG = "gconv"
)
var (
// Empty strings.
emptyStringMap = map[string]struct{}{
@ -33,6 +38,9 @@ var (
"off": struct{}{},
"false": struct{}{},
}
// Priority tags for Map*/Struct* functions.
structTagPriority = []string{gGCONV_TAG, "json"}
)
// Convert converts the variable <i> to the type <t>, the type <t> is specified by string.

View File

@ -7,20 +7,19 @@
package gconv
import (
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
const (
gGCONV_TAG = "gconv"
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/text/gstr"
)
// Map converts any variable <value> to map[string]interface{}.
// If the parameter <value> is not a map type, then the conversion will fail and returns nil.
// If <value> is a struct object, the second parameter <tags> specifies the most priority
// tags that will be detected, otherwise it detects the tags in order of: gconv, json.
//
// If the parameter <value> is not a map/struct/*struct type, then the conversion will fail and returns nil.
//
// If <value> is a struct/*struct object, the second parameter <tags> specifies the most priority
// tags that will be detected, otherwise it detects the tags in order of: gconv, json, and then the field name.
func Map(value interface{}, tags ...string) map[string]interface{} {
if value == nil {
return nil
@ -105,17 +104,14 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
case reflect.Struct:
rt := rv.Type()
name := ""
tagArray := []string{gGCONV_TAG, "json"}
tagArray := structTagPriority
switch len(tags) {
case 0:
// No need handle.
case 1:
tagArray = strings.Split(tags[0], ",")
tagArray = append(strings.Split(tags[0], ","), structTagPriority...)
default:
tagArray = tags
}
if gstr.SearchArray(tagArray, gGCONV_TAG) < 0 {
tagArray = append(tagArray, gGCONV_TAG)
tagArray = append(tags, structTagPriority...)
}
for i := 0; i < rv.NumField(); i++ {
// Only convert the public attributes.

View File

@ -7,8 +7,11 @@
package gconv
import (
"github.com/gogf/gf/g/text/gstr"
"errors"
"fmt"
"reflect"
"github.com/gogf/gf/g/text/gstr"
)
// Ints converts <i> to []int.
@ -306,9 +309,7 @@ func Interfaces(i interface{}) []interface{} {
kind = rv.Kind()
}
switch kind {
case reflect.Slice:
fallthrough
case reflect.Array:
case reflect.Slice, reflect.Array:
for i := 0; i < rv.Len(); i++ {
array = append(array, rv.Index(i).Interface())
}
@ -348,3 +349,79 @@ func Maps(i interface{}) []map[string]interface{} {
return list
}
}
// Structs converts any slice to given struct slice.
func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, false, mapping...)
}
// StructsDeep converts any slice to given struct slice recursively.
func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, true, mapping...)
}
// doStructs converts any slice to given struct slice.
//
// The parameter <params> should be type of slice.
//
// The parameter <pointer> should be type of pointer to slice of struct.
// Note that if <pointer> is a pointer to another pointer of type of slice of struct,
// it will create the struct/pointer internally.
func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
if params == nil {
return errors.New("params cannot be nil")
}
if pointer == nil {
return errors.New("object pointer cannot be nil")
}
pointerRt := reflect.TypeOf(pointer)
if kind := pointerRt.Kind(); kind != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", kind)
}
rv := reflect.ValueOf(params)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice, reflect.Array:
array := reflect.MakeSlice(pointerRt.Elem(), rv.Len(), rv.Len())
itemType := array.Index(0).Type()
for i := 0; i < rv.Len(); i++ {
if itemType.Kind() == reflect.Ptr {
// Slice element is type pointer.
e := reflect.New(itemType.Elem()).Elem()
if deep {
if err = StructDeep(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
} else {
if err = Struct(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
}
array.Index(i).Set(e.Addr())
} else {
// Slice element is not type of pointer.
e := reflect.New(itemType).Elem()
if deep {
if err = StructDeep(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
} else {
if err = Struct(rv.Index(i).Interface(), e, mapping...); err != nil {
return err
}
}
array.Index(i).Set(e)
}
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil
default:
return fmt.Errorf("params should be type of slice, but got: %v", kind)
}
}

View File

@ -9,10 +9,11 @@ package gconv
import (
"errors"
"fmt"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/third/github.com/fatih/structs"
"reflect"
"strings"
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g/text/gstr"
)
// Struct maps the params key-value pairs to the corresponding struct object's properties.
@ -51,6 +52,13 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
return errors.New("object pointer cannot be nil")
}
elem = rv.Elem()
// Auto create struct object.
// For example, if <pointer> is **User, then <elem> is *User, which is a pointer to User.
if elem.Type().Kind() == reflect.Ptr && (!elem.IsValid() || elem.IsNil()) {
e := reflect.New(elem.Type().Elem()).Elem()
elem.Set(e.Addr())
elem = e
}
}
// It only performs one converting to the same attribute.
// doneMap is used to check repeated converting.
@ -67,7 +75,7 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
}
}
// It secondly checks the tags of attributes.
tagMap := getTagMapOfStruct(pointer)
tagMap := structs.TagMapName(pointer, structTagPriority, true)
for tagK, tagV := range tagMap {
if _, ok := doneMap[tagV]; ok {
continue
@ -167,31 +175,6 @@ func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]s
return nil
}
// 解析指针对象的tag
func getTagMapOfStruct(pointer interface{}) map[string]string {
tagMap := make(map[string]string)
// 反射类型判断
fields := ([]*structs.Field)(nil)
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(pointer)
}
// 将struct中定义的属性转换名称构建成tagmap
for _, field := range fields {
tag := field.Tag("gconv")
if tag == "" {
tag = field.Tag("json")
}
if tag != "" {
for _, v := range strings.Split(tag, ",") {
tagMap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagMap
}
// 将参数值绑定到对象指定名称的属性上
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}) (err error) {
structFieldValue := elem.FieldByName(name)

View File

@ -1,12 +1,19 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 gconv_test
import (
"testing"
"time"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
)
type apiString interface {

View File

@ -7,10 +7,11 @@
package gconv_test
import (
"testing"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
)
func Test_Slice(t *testing.T) {
@ -35,3 +36,50 @@ func Test_Slice_PrivateAttribute(t *testing.T) {
gtest.Assert(gconv.Interfaces(user), g.Slice{1})
})
}
func Test_Slice_Structs(t *testing.T) {
type Base struct {
Age int
}
type User struct {
Id int
Name string
Base
}
gtest.Case(t, func() {
users := make([]User, 0)
params := []g.Map{
{"id": 1, "name": "john", "age": 18},
{"id": 2, "name": "smith", "age": 20},
}
err := gconv.Structs(params, &users)
gtest.Assert(err, nil)
gtest.Assert(len(users), 2)
gtest.Assert(users[0].Id, params[0]["id"])
gtest.Assert(users[0].Name, params[0]["name"])
gtest.Assert(users[0].Age, 0)
gtest.Assert(users[1].Id, params[1]["id"])
gtest.Assert(users[1].Name, params[1]["name"])
gtest.Assert(users[1].Age, 0)
})
gtest.Case(t, func() {
users := make([]User, 0)
params := []g.Map{
{"id": 1, "name": "john", "age": 18},
{"id": 2, "name": "smith", "age": 20},
}
err := gconv.StructsDeep(params, &users)
gtest.Assert(err, nil)
gtest.Assert(len(users), 2)
gtest.Assert(users[0].Id, params[0]["id"])
gtest.Assert(users[0].Name, params[0]["name"])
gtest.Assert(users[0].Age, params[0]["age"])
gtest.Assert(users[1].Id, params[1]["id"])
gtest.Assert(users[1].Name, params[1]["name"])
gtest.Assert(users[1].Age, params[1]["age"])
})
}

View File

@ -7,12 +7,13 @@
package gconv_test
import (
"testing"
"time"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
)
func Test_Struct_Basic1(t *testing.T) {
@ -25,9 +26,8 @@ func Test_Struct_Basic1(t *testing.T) {
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user := (*User)(nil)
// 使用默认映射规则绑定属性值到对象
user = new(User)
user := new(User)
params1 := g.Map{
"uid": 1,
"Name": "john",
@ -338,6 +338,29 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
}
func Test_Struct_Deep(t *testing.T) {
gtest.Case(t, func() {
type Base struct {
Age int
}
type User struct {
Id int
Name string
Base
}
user := new(User)
params := g.Map{
"id": 1,
"name": "john",
"age": 18,
}
err := gconv.StructDeep(params, user)
gtest.Assert(err, nil)
gtest.Assert(user.Id, params["id"])
gtest.Assert(user.Name, params["name"])
gtest.Assert(user.Age, params["age"])
})
gtest.Case(t, func() {
type Ids struct {
Id int `json:"id"`
@ -362,7 +385,8 @@ func Test_Struct_Deep(t *testing.T) {
"create_time": "2019",
}
user := new(User)
gconv.StructDeep(data, user)
err := gconv.StructDeep(data, user)
gtest.Assert(err, nil)
gtest.Assert(user.Id, 100)
gtest.Assert(user.Uid, 101)
gtest.Assert(user.Nickname, "T1")
@ -431,3 +455,38 @@ func Test_Struct_Time(t *testing.T) {
gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
})
}
// Auto create struct when given pointer.
func Test_Struct_Create(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"Name": "john",
}
err := gconv.Struct(params, &user)
gtest.Assert(err, nil)
gtest.Assert(user.Uid, 1)
gtest.Assert(user.Name, "john")
})
gtest.Case(t, func() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"Name": "john",
}
err := gconv.Struct(params, user)
gtest.AssertNE(err, nil)
gtest.Assert(user, nil)
})
}

View File

@ -9,15 +9,19 @@ package gvalid
import (
"strings"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/internal/structs"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/third/github.com/fatih/structs"
)
var (
// 同时支持valid和gvalid标签优先使用valid
structTagPriority = []string{"valid", "gvalid"}
)
// 校验struct对象属性object参数也可以是一个指向对象的指针返回值同CheckMap方法。
// struct的数据校验结果信息是顺序的。
func CheckStruct(object interface{}, rules interface{}, msgs ...CustomMsg) *Error {
fields := structs.Fields(object)
params := make(map[string]interface{})
checkRules := make(map[string]string)
customMsgs := make(CustomMsg)
@ -57,26 +61,25 @@ func CheckStruct(object interface{}, rules interface{}, msgs ...CustomMsg) *Erro
errorRules = append(errorRules, name+"@"+rule)
}
// 不支持校验错误顺序: map[键名]校验规则
// 不支持校验错误顺序: map[键名]校验规则
case map[string]string:
checkRules = v
}
// 首先, 按照属性循环一遍将struct的属性、数值、tag解析
for _, field := range fields {
tagValue := ""
for _, field := range structs.MapField(object, structTagPriority, true) {
fieldName := field.Name()
// 只检测公开属性
if !gstr.IsLetterUpper(fieldName[0]) {
continue
}
params[fieldName] = field.Value()
// 同时支持valid和gvalid标签优先使用valid
tag := field.Tag("valid")
if tag == "" {
tag = field.Tag("gvalid")
tagValue = ""
for _, v := range structTagPriority {
tagValue = field.Tag(v)
if tagValue != "" {
break
}
}
if tag != "" {
if tagValue != "" {
// sequence tag == struct tag, 这里的name为别名
name, rule, msg := parseSequenceTag(tag)
name, rule, msg := parseSequenceTag(tagValue)
if len(name) == 0 {
name = fieldName
}

View File

@ -4,18 +4,16 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 返回错误对象。
package gvalid
import "strings"
// 校验错误对象
type Error struct {
rules []string // 校验结果顺序(可能为nil)
errors ErrorMap // 校验结果(map无序)
firstKey string // 第一条错误项键名(常用操作冗余数据)
firstItem map[string]string // 第一条错误项(常用操作冗余数据)
rules []string // 校验结果顺序(可能为nil),可保证返回校验错误的顺序性
errors ErrorMap // 完整的数据校验结果存储(map无序)
firstKey string // 第一条错误项键名(常用操作冗余数据),默认为空
firstItem map[string]string // 第一条错误项(常用操作冗余数据)默认为nil
}
// 校验错误信息: map[键名]map[规则名]错误信息

View File

@ -9,6 +9,8 @@ package gvalid_test
import (
"testing"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gvalid"
)
@ -90,3 +92,29 @@ func Test_CheckStruct(t *testing.T) {
gtest.Assert(err.Maps()["uid"]["min"], "ID不能为空")
})
}
func Test_CheckStruct_With_Inherit(t *testing.T) {
gtest.Case(t, func() {
type Pass struct {
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
}
type User struct {
Id int
Name string `valid:"name@required#请输入您的姓名"`
Pass Pass
}
user := &User{
Name: "",
Pass: Pass{
Pass1: "1",
Pass2: "2",
},
}
err := gvalid.CheckStruct(user, nil)
gtest.AssertNE(err, nil)
gtest.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
gtest.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
gtest.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
})
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/g"
)
func main() {
db := g.DB()
// 开启调试模式以便于记录所有执行的SQL
db.SetDebug(true)
type User struct {
Uid int
Name string
}
user := (*User)(nil)
fmt.Println(user)
err := db.Table("test").Where("id=1").Struct(&user)
fmt.Println(err)
fmt.Println(user)
}

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"github.com/gogf/gf/g"
)

View File

@ -6,11 +6,11 @@ import (
)
func main() {
//path := "D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go"
path := "/Users/john/Temp/test"
//path := `D:\temp`
path := "/Users/john/Temp"
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Println(event)
}, true)
})
if err != nil {
glog.Fatal(err)
} else {

View File

@ -3,7 +3,7 @@ package main
import (
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
"time"
)
@ -21,12 +21,12 @@ func main() {
panic(err)
}
// 5秒后移除c1的回调函数注册仅剩c2
gtime.SetTimeout(5*time.Second, func() {
gtimer.SetTimeout(5*time.Second, func() {
gfsnotify.RemoveCallback(c1.Id)
glog.Println("remove callback c1")
})
// 10秒后移除c2的回调函数注册所有的回调都移除不再有任何打印信息输出
gtime.SetTimeout(10*time.Second, func() {
gtimer.SetTimeout(10*time.Second, func() {
gfsnotify.RemoveCallback(c2.Id)
glog.Println("remove callback c2")
})

View File

@ -3,7 +3,7 @@ package main
import (
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
"time"
)
@ -18,7 +18,7 @@ func main() {
// 在此期间创建文件、目录、修改文件、删除文件
// 20秒后移除回调函数注册所有的回调都移除不再有任何打印信息输出
gtime.SetTimeout(20*time.Second, func() {
gtimer.SetTimeout(20*time.Second, func() {
gfsnotify.RemoveCallback(callback.Id)
glog.Println("remove callback")
})

View File

@ -13,7 +13,7 @@ func main() {
glog.Println(event)
})
if err != nil {
glog.Fatalln(err)
glog.Fatal(err)
}
}
}

View File

@ -27,9 +27,12 @@ func main() {
// 7 反白显示
// 8 不可见
for b := 40; b <= 47; b++ { // 背景色彩 = 40-47
for f := 30; f <= 37; f++ { // 前景色彩 = 30-37
for d := range []int{0, 1, 4, 5, 7, 8} { // 显示方式 = 0,1,4,5,7,8
// 背景色彩 = 40-47
for b := 40; b <= 47; b++ {
// 前景色彩 = 30-37
for f := 30; f <= 37; f++ {
// 显示方式 = 0,1,4,5,7,8
for _, d := range []int{0, 1, 4, 5, 7, 8} {
fmt.Printf(" %c[%d;%d;%dm%s(f=%d,b=%d,d=%d)%c[0m ", 0x1B, d, b, f, "", f, b, d, 0x1B)
}
fmt.Println("")

View File

@ -1,6 +1,7 @@
package main
import (
<<<<<<< HEAD
<<<<<<< HEAD
"fmt"
@ -65,28 +66,33 @@ func main() {
//fmt.Printf("%+v\n", Test2())
=======
"github.com/gogf/gf/g/os/glog"
=======
"fmt"
>>>>>>> d0fe2d2f75a91f44d59969bb25fda3729eabcdde
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g"
)
func localCache() {
result := gcache.GetOrSetFunc("test.key.1", func() interface{} {
return nil
}, 1000*60*2)
if result == nil {
glog.Error("未获取到值")
} else {
glog.Infofln("result is $v", result)
}
}
func TestCache() {
for i := 0; i < 100; i++ {
localCache()
}
type User struct {
Uid int
Name string
}
func main() {
<<<<<<< HEAD
TestCache()
>>>>>>> c90ed0d4242527435a3b4c9d7c27742d29c9aaa1
=======
if r, err := g.DB().Table("user").Where("uid=?", 1).One(); r != nil {
u := new(User)
if err := r.ToStruct(u); err == nil {
fmt.Println(" uid:", u.Uid)
fmt.Println("name:", u.Name)
} else {
fmt.Println(err)
}
} else if err != nil {
fmt.Println(err)
}
>>>>>>> d0fe2d2f75a91f44d59969bb25fda3729eabcdde
}

View File

@ -0,0 +1,18 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Id int `json:"uid"`
Name string `my-tag:"nick-name" json:"name"`
}
user := &User{
Id: 1,
Name: "john",
}
g.Dump(gconv.Map(user, "my-tag"))
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Uid int
Name string
}
user := (*User)(nil)
params := g.Map{
"uid": 1,
"name": "john",
}
err := gconv.Struct(params, &user)
if err != nil {
panic(err)
}
g.Dump(user)
}

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v1.7.1"
const VERSION = "v1.7.2"
const AUTHORS = "john<john@goframe.org>"