diff --git a/README.MD b/README.MD index b2f87e6f0..b8ff12533 100644 --- a/README.MD +++ b/README.MD @@ -14,7 +14,11 @@ [![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf) --> -`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules and many more features. + + +`GF(GoFrame)` is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features. # Installation ``` diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 380a2f37a..bb67f3966 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -47,7 +47,9 @@ type DB interface { GetOne(query string, args ...interface{}) (Record, error) GetValue(query string, args ...interface{}) (Value, error) GetCount(query string, args ...interface{}) (int, error) - GetStruct(obj interface{}, query string, args ...interface{}) error + GetStruct(objPointer interface{}, query string, args ...interface{}) error + GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error + GetScan(objPointer interface{}, query string, args ...interface{}) error // 创建底层数据库master/slave链接对象 Master() (*sql.DB, error) diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index 3219deba7..c7f86ef6c 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -165,13 +165,44 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) { return nil, nil } -// 数据库查询,获取查询结果记录,自动映射数据到给定的struct对象中 -func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error { +// 数据库查询,查询单条记录,自动映射数据到给定的struct对象中 +func (bs *dbBase) GetStruct(objPointer interface{}, query string, args ...interface{}) error { one, err := bs.GetOne(query, args...) if err != nil { return err } - return one.ToStruct(obj) + return one.ToStruct(objPointer) +} + +// 数据库查询,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 +func (bs *dbBase) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error { + all, err := bs.GetAll(query, args...) + if err != nil { + return err + } + return all.ToStructs(objPointerSlice) +} + +// 将结果转换为指定的struct/*struct/[]struct/[]*struct, +// 参数应该为指针类型,否则返回失败。 +// 该方法自动识别参数类型,调用Struct/Structs方法。 +func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interface{}) error { + t := reflect.TypeOf(objPointer) + 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 { + case reflect.Array: + case 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 } // 数据库查询,获取查询字段值 diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index 3986e9ee9..c8a9d69da 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -35,6 +35,7 @@ type Model struct { cacheEnabled bool // 当前SQL操作是否开启查询缓存功能 cacheTime int // 查询缓存时间 cacheName string // 查询缓存名称 + alterable bool // 当前模型是否运行可修改模式(默认情况下链式操作不会修改当前模型,而是创建新的模型返回) } // 链式操作,数据表字段,可支持多个表,以半角逗号连接 @@ -79,44 +80,65 @@ func (md *Model) Clone() *Model { return newModel } +// 标识当前对象可被修改。 +// 1. 默认情况下,模型对象的对象属性无法被修改, +// 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。 +// 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。 +// 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作, +// 即使是链式操作分开执行。 +// 3. 大部分ORM框架默认模型对象是可修改的,但是GF框架的ORM提供给开发者更灵活,更安全的链式操作选项。 +func (md *Model) Alterable() *Model { + md.alterable = true + return md +} + +// 返回操作的模型对象,可能是当前对象,也可能是新的克隆对象,根据alterable决定。 +func (md *Model) getModel() *Model { + if md.alterable { + return md + } else { + return md.Clone() + } +} + // 链式操作,左联表 func (md *Model) LeftJoin(joinTable string, on string) (*Model) { - model := md.Clone() + model := md.getModel() model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on) return model } // 链式操作,右联表 func (md *Model) RightJoin(joinTable string, on string) (*Model) { - model := md.Clone() + model := md.getModel() model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on) return model } // 链式操作,内联表 func (md *Model) InnerJoin(joinTable string, on string) (*Model) { - model := md.Clone() + model := md.getModel() model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on) return model } // 链式操作,查询字段 func (md *Model) Fields(fields string) (*Model) { - model := md.Clone() + model := md.getModel() model.fields = fields return model } // 链式操作,过滤字段 func (md *Model) Filter() (*Model) { - model := md.Clone() + model := md.getModel() model.filter = true return model } // 链式操作,condition,支持string & gdb.Map func (md *Model) Where(where interface{}, args ...interface{}) (*Model) { - model := md.Clone() + model := md.getModel() newWhere, newArgs := formatCondition(where, args) model.where = newWhere model.whereArgs = append(model.whereArgs, newArgs...) @@ -129,7 +151,7 @@ func (md *Model) Where(where interface{}, args ...interface{}) (*Model) { // 链式操作,添加AND条件到Where中 func (md *Model) And(where interface{}, args ...interface{}) (*Model) { - model := md.Clone() + model := md.getModel() newWhere, newArgs := formatCondition(where, args) model.where += " AND " + newWhere model.whereArgs = append(model.whereArgs, newArgs...) @@ -138,7 +160,7 @@ func (md *Model) And(where interface{}, args ...interface{}) (*Model) { // 链式操作,添加OR条件到Where中 func (md *Model) Or(where interface{}, args ...interface{}) (*Model) { - model := md.Clone() + model := md.getModel() newWhere, newArgs := formatCondition(where, args) model.where += " OR " + newWhere model.whereArgs = append(model.whereArgs, newArgs...) @@ -147,21 +169,21 @@ func (md *Model) Or(where interface{}, args ...interface{}) (*Model) { // 链式操作,group by func (md *Model) GroupBy(groupBy string) (*Model) { - model := md.Clone() + model := md.getModel() model.groupBy = groupBy return model } // 链式操作,order by func (md *Model) OrderBy(orderBy string) (*Model) { - model := md.Clone() + model := md.getModel() model.orderBy = orderBy return model } // 链式操作,limit func (md *Model) Limit(start int, limit int) (*Model) { - model := md.Clone() + model := md.getModel() model.start = start model.limit = limit return model @@ -170,7 +192,7 @@ func (md *Model) Limit(start int, limit int) (*Model) { // 链式操作,翻页 // @author ymrjqyy func (md *Model) ForPage(page, limit int) (*Model) { - model := md.Clone() + model := md.getModel() model.start = (page - 1) * limit model.limit = limit return model @@ -178,7 +200,7 @@ func (md *Model) ForPage(page, limit int) (*Model) { // 设置批处理的大小 func (md *Model) Batch(batch int) *Model { - model := md.Clone() + model := md.getModel() model.batch = batch return model } @@ -188,7 +210,7 @@ func (md *Model) Batch(batch int) *Model { // name表示自定义的缓存名称,便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称), // 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。 func (md *Model) Cache(time int, name ... string) *Model { - model := md.Clone() + model := md.getModel() model.cacheTime = time if len(name) > 0 { model.cacheName = name[0] @@ -203,7 +225,7 @@ func (md *Model) Cache(time int, name ... string) *Model { // 链式操作,操作数据项,参数data类型支持 string/map/slice/struct/*struct , // 也可以是:key,value,key,value,...。 func (md *Model) Data(data ...interface{}) *Model { - model := md.Clone() + model := md.getModel() if len(data) > 1 { m := make(map[string]interface{}) for i := 0; i < len(data); i += 2 { @@ -438,13 +460,44 @@ func (md *Model) Value() (Value, error) { return nil, nil } -// 链式操作,查询单条记录,并自动转换为struct对象 -func (md *Model) Struct(obj interface{}) error { +// 链式操作,查询单条记录,并自动转换为struct对象, 参数必须为对象的指针,不能为空指针。 +func (md *Model) Struct(objPointer interface{}) error { one, err := md.One() if err != nil { return err } - return one.ToStruct(obj) + return one.ToStruct(objPointer) +} + +// 链式操作,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 +func (md *Model) Structs(objPointerSlice interface{}) error { + r, err := md.All() + if err != nil { + return err + } + return r.ToStructs(objPointerSlice) +} + +// 链式操作,将结果转换为指定的struct/*struct/[]struct/[]*struct, +// 参数应该为指针类型,否则返回失败。 +// 该方法自动识别参数类型,调用Struct/Structs方法。 +func (md *Model) Scan(objPointer interface{}) error { + t := reflect.TypeOf(objPointer) + 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 { + case reflect.Array: + case reflect.Slice: + return md.Structs(objPointer) + case reflect.Struct: + return md.Struct(objPointer) + default: + return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) + } + return nil } // 链式操作,查询数量,fields可以为空,也可以自定义查询字段, diff --git a/g/database/gdb/gdb_transaction.go b/g/database/gdb/gdb_transaction.go index b658b15fb..9f90e458c 100644 --- a/g/database/gdb/gdb_transaction.go +++ b/g/database/gdb/gdb_transaction.go @@ -8,8 +8,10 @@ package gdb import ( "database/sql" + "fmt" "github.com/gogf/gf/g/text/gregex" _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" + "reflect" ) // 数据库事务对象 @@ -75,6 +77,37 @@ func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) erro return one.ToStruct(obj) } +// 数据库查询,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 +func (tx *TX) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error { + all, err := tx.GetAll(query, args...) + if err != nil { + return err + } + return all.ToStructs(objPointerSlice) +} + +// 将结果转换为指定的struct/*struct/[]struct/[]*struct, +// 参数应该为指针类型,否则返回失败。 +// 该方法自动识别参数类型,调用Struct/Structs方法。 +func (tx *TX) GetScan(objPointer interface{}, query string, args ...interface{}) error { + t := reflect.TypeOf(objPointer) + 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 { + case reflect.Array: + case reflect.Slice: + return tx.db.GetStructs(objPointer, query, args ...) + case reflect.Struct: + return tx.db.GetStruct(objPointer, query, args ...) + default: + return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) + } + return nil +} + // 数据库查询,获取查询字段值 func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) { one, err := tx.GetOne(query, args ...) diff --git a/g/database/gdb/gdb_type_record.go b/g/database/gdb/gdb_type_record.go index 4b71f35d9..16e3f51c9 100644 --- a/g/database/gdb/gdb_type_record.go +++ b/g/database/gdb/gdb_type_record.go @@ -33,10 +33,6 @@ func (r Record) ToMap() Map { } // 将Map变量映射到指定的struct对象中,注意参数应当是一个对象的指针 -func (r Record) ToStruct(obj interface{}) error { - m := make(map[string]interface{}) - for k, v := range r { - m[k] = v.Val() - } - return gconv.Struct(m, obj) +func (r Record) ToStruct(objPointer interface{}) error { + return gconv.Struct(r.ToMap(), objPointer) } diff --git a/g/database/gdb/gdb_type_result.go b/g/database/gdb/gdb_type_result.go index 9636cbd73..b550ebe19 100644 --- a/g/database/gdb/gdb_type_result.go +++ b/g/database/gdb/gdb_type_result.go @@ -7,7 +7,9 @@ package gdb import ( + "fmt" "github.com/gogf/gf/g/encoding/gparser" + "reflect" ) // 将结果集转换为JSON字符串 @@ -96,3 +98,30 @@ func (r Result) ToUintRecord(key string) map[uint]Record { } return m } + +// 将结果列表转换为指定对象的slice。 +func (r Result) ToStructs(objPointerSlice interface{}) error { + l := len(r) + if l == 0 { + return nil + } + t := reflect.TypeOf(objPointerSlice) + if t.Kind() != reflect.Ptr { + return fmt.Errorf("params should be type of pointer, but got: %v", t.Kind()) + } + a := reflect.MakeSlice(t.Elem(), l, l) + itemType := a.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()) + } else { + e := reflect.New(itemType).Elem() + r[i].ToStruct(e) + a.Index(i).Set(e) + } + } + reflect.ValueOf(objPointerSlice).Elem().Set(a) + return nil +} diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go index 4a62574ff..38b03ede5 100644 --- a/g/database/gdb/gdb_unit_method_test.go +++ b/g/database/gdb/gdb_unit_method_test.go @@ -266,19 +266,159 @@ func TestDbBase_GetCount(t *testing.T) { } func TestDbBase_GetStruct(t *testing.T) { - type User struct { - Id int - Passport string - Password string - NickName string - CreateTime gtime.Time - } - user := new(User) - if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil { - gtest.Fatal(err) - } else { - gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") - } + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } else { + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + } + }) + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } else { + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + } + }) +} + +func TestDbBase_GetStructs(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); 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[2].CreateTime.String(), "2010-10-10 00:00:01") + }) + + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); 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[2].CreateTime.String(), "2010-10-10 00:00:01") + }) +} + +func TestDbBase_GetScan(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } else { + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + } + }) + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } else { + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + } + }) + + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); 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[2].CreateTime.String(), "2010-10-10 00:00:01") + }) + + gtest.Case(t, func() { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); 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[2].CreateTime.String(), "2010-10-10 00:00:01") + }) } func TestDbBase_Delete(t *testing.T) { diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index a534aadeb..10e02b5b5 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -121,7 +121,7 @@ func TestModel_Replace(t *testing.T) { "passport" : "t11", "password" : "25d55ad283aa400af464c76d713c07ad", "nickname" : "T11", - "create_time" : gtime.Now().String(), + "create_time" : "2018-10-10 00:01:10", }).Replace() if err != nil { gtest.Fatal(err) @@ -136,7 +136,7 @@ func TestModel_Save(t *testing.T) { "passport" : "t111", "password" : "25d55ad283aa400af464c76d713c07ad", "nickname" : "T111", - "create_time" : gtime.Now().String(), + "create_time" : "2018-10-10 00:01:10", }).Save() if err != nil { gtest.Fatal(err) @@ -168,13 +168,30 @@ func TestModel_Clone(t *testing.T) { if err != nil { gtest.Fatal(err) } - gtest.Assert(count, 2) - gtest.Assert(record["id"].Int(), 3) - gtest.Assert(len(result), 2) + gtest.Assert(count, 2) + gtest.Assert(record["id"].Int(), 3) + gtest.Assert(len(result), 2) gtest.Assert(result[0]["id"].Int(), 1) gtest.Assert(result[1]["id"].Int(), 3) } +func TestModel_Alterable(t *testing.T) { + gtest.Case(t, func() { + md := db.Table("user").Alterable().Where("id IN(?)", g.Slice{1,3}) + count, err := md.Count() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(count, 2) + md.And("id = ?", 1) + count, err = md.Count() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(count, 1) + }) +} + func TestModel_All(t *testing.T) { result, err := db.Table("user").All() if err != nil { @@ -222,19 +239,164 @@ func TestModel_Select(t *testing.T) { } func TestModel_Struct(t *testing.T) { - 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) - if err != nil { - gtest.Fatal(err) - } - gtest.Assert(user.NickName, "T111") + 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) + 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) + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(user.NickName, "T111") + gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10") + }) +} + +func TestModel_Structs(t *testing.T) { + 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").Structs(&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").OrderBy("id asc").Structs(&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") + }) +} + +func TestModel_Scan(t *testing.T) { + 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").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").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 + } + 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").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") + }) } func TestModel_OrderBy(t *testing.T) { diff --git a/g/database/gdb/gdb_unit_transaction_test.go b/g/database/gdb/gdb_unit_transaction_test.go index e73033be0..ccd2a52df 100644 --- a/g/database/gdb/gdb_unit_transaction_test.go +++ b/g/database/gdb/gdb_unit_transaction_test.go @@ -267,6 +267,29 @@ func TestTX_Save(t *testing.T) { } } +func TestTX_Update(t *testing.T) { + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + if result, err := db.Update("user", "create_time='2010-10-10 00:00:01'", "id=3"); err != nil { + gtest.Fatal(err) + } else { + n, _ := result.RowsAffected() + gtest.Assert(n, 1) + } + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + if value, err := db.Table("user").Fields("create_time").Where("id", 3).Value(); err != nil { + gtest.Fatal(err) + } else { + gtest.Assert(value.String(), "2010-10-10 00:00:01") + } + }) +} + func TestTX_GetAll(t *testing.T) { tx, err := db.Begin() if err != nil { @@ -331,26 +354,215 @@ func TestTX_GetCount(t *testing.T) { } func TestTX_GetStruct(t *testing.T) { - tx, err := db.Begin() - if err != nil { - gtest.Fatal(err) - } - type User struct { - Id int - Passport string - Password string - NickName string - CreateTime gtime.Time - } - user := new(User) - if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 1); err != nil { - gtest.Fatal(err) - } else { - gtest.Assert(user.NickName, "T11") - } - if err := tx.Commit(); err != nil { - gtest.Fatal(err) - } + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } + gtest.Assert(user.NickName, "T3") + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } + gtest.Assert(user.NickName, "T3") + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) +} + +func TestTX_GetStructs(t *testing.T) { + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil { + gtest.Fatal(err) + } + gtest.Assert(len(users), 4) + gtest.Assert(users[0].Id, 1) + gtest.Assert(users[1].Id, 2) + gtest.Assert(users[2].Id, 3) + gtest.Assert(users[0].NickName, "T11") + gtest.Assert(users[1].NickName, "T2") + gtest.Assert(users[2].NickName, "T3") + gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) + + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil { + gtest.Fatal(err) + } + gtest.Assert(len(users), 4) + gtest.Assert(users[0].Id, 1) + gtest.Assert(users[1].Id, 2) + gtest.Assert(users[2].Id, 3) + gtest.Assert(users[0].NickName, "T11") + gtest.Assert(users[1].NickName, "T2") + gtest.Assert(users[2].NickName, "T3") + gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) +} + +func TestTX_GetScan(t *testing.T) { + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + if err := tx.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } + gtest.Assert(user.NickName, "T3") + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + if err := tx.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil { + gtest.Fatal(err) + } + gtest.Assert(user.NickName, "T3") + gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) + + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil { + gtest.Fatal(err) + } + gtest.Assert(len(users), 4) + gtest.Assert(users[0].Id, 1) + gtest.Assert(users[1].Id, 2) + gtest.Assert(users[2].Id, 3) + gtest.Assert(users[0].NickName, "T11") + gtest.Assert(users[1].NickName, "T2") + gtest.Assert(users[2].NickName, "T3") + gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) + + gtest.Case(t, func() { + tx, err := db.Begin() + if err != nil { + gtest.Fatal(err) + } + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil { + gtest.Fatal(err) + } + gtest.Assert(len(users), 4) + gtest.Assert(users[0].Id, 1) + gtest.Assert(users[1].Id, 2) + gtest.Assert(users[2].Id, 3) + gtest.Assert(users[0].NickName, "T11") + gtest.Assert(users[1].NickName, "T2") + gtest.Assert(users[2].NickName, "T3") + gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01") + if err := tx.Commit(); err != nil { + gtest.Fatal(err) + } + }) } func TestTX_Delete(t *testing.T) { diff --git a/g/util/gconv/gconv.go b/g/util/gconv/gconv.go index 16d06ca97..2b4ae31fd 100644 --- a/g/util/gconv/gconv.go +++ b/g/util/gconv/gconv.go @@ -47,7 +47,16 @@ func Convert(i interface{}, t string, extraParams...interface{}) interface{} { return Time(i, String(extraParams[0])) } return Time(i) - + case "gtime.Time": + if len(extraParams) > 0 { + return GTime(i, String(extraParams[0])) + } + return *GTime(i) + case "*gtime.Time": + if len(extraParams) > 0 { + return GTime(i, String(extraParams[0])) + } + return GTime(i) case "time.Duration": return TimeDuration(i) default: return i diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index cf87ac1b5..852ada0d6 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -15,68 +15,60 @@ import ( "strings" ) -// 将params键值对参数映射到对应的struct对象属性上,第三个参数mapping为非必需,表示自定义名称与属性名称的映射关系。 +// 将params键值对参数映射到对应的struct对象属性上, +// 第三个参数mapping为非必需,表示自定义名称与属性名称的映射关系。 // 需要注意: // 1、第二个参数应当为struct对象指针; // 2、struct对象的**公开属性(首字母大写)**才能被映射赋值; // 3、map中的键名可以为小写,映射转换时会自动将键名首字母转为大写做匹配映射,如果无法匹配则忽略; func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error { if params == nil { - return nil + return errors.New("params cannot be nil") } - isParamMap := true - paramsMap := (map[string]interface{})(nil) - // 先将参数转为 map[string]interface{} 类型 - if m, ok := params.(map[string]interface{}); ok { - paramsMap = m - } else { - paramsMap = make(map[string]interface{}) - if reflect.ValueOf(params).Kind() == reflect.Map { - ks := reflect.ValueOf(params).MapKeys() - vs := reflect.ValueOf(params) - for _, k := range ks { - paramsMap[String(k.Interface())] = vs.MapIndex(k).Interface() - } - } else { - isParamMap = false - } + if objPointer == nil { + return errors.New("object pointer cannot be nil") + } + paramsMap := Map(params) + if paramsMap == nil { + return fmt.Errorf("invalid params: %v", params) } // struct的反射对象 elem := reflect.Value{} if v, ok := objPointer.(reflect.Value); ok { elem = v } else { - elem = reflect.ValueOf(objPointer).Elem() - } - // 如果给定的参数不是map类型,那么直接将参数值映射到第一个属性上 - if !isParamMap { - if err := bindVarToStructByIndex(elem, 0, params); err != nil { - return err + rv := reflect.ValueOf(objPointer) + if kind := rv.Kind(); kind != reflect.Ptr { + return fmt.Errorf("object pointer should be type of: %v", kind) } - return nil + if !rv.IsValid() || rv.IsNil() { + return errors.New("object pointer cannot be nil") + } + elem = rv.Elem() } - // 已执行过转换的属性,只执行一次转换 - dmap := make(map[string]bool) + // 已执行过转换的属性,只执行一次转换。 + // 或者是已经执行过转换检查的属性(即使不进行转换), 以便重复判断。 + doneMap := make(map[string]bool) // 首先按照传递的映射关系进行匹配 if len(attrMapping) > 0 && len(attrMapping[0]) > 0 { - for mappingk, mappingv := range attrMapping[0] { - if v, ok := paramsMap[mappingk]; ok { - dmap[mappingv] = true - if err := bindVarToStructAttr(elem, mappingv, v); err != nil { + for mapK, mapV := range attrMapping[0] { + if v, ok := paramsMap[mapK]; ok { + doneMap[mapV] = true + if err := bindVarToStructAttr(elem, mapV, v); err != nil { return err } } } } - // 其次匹配对象定义时绑定的属性名称 + // 其次匹配对象定义时绑定的属性名称, // 标签映射关系map,如果有的话 - tagmap := getTagMapOfStruct(objPointer) - for tagk, tagv := range tagmap { - if _, ok := dmap[tagv]; ok { + tagMap := getTagMapOfStruct(objPointer) + for tagk, tagv := range tagMap { + if _, ok := doneMap[tagv]; ok { continue } if v, ok := paramsMap[tagk]; ok { - dmap[tagv] = true + doneMap[tagv] = true if err := bindVarToStructAttr(elem, tagv, v); err != nil { return err } @@ -88,19 +80,19 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string for i := 0; i < elem.NumField(); i++ { attrMap[elemType.Field(i).Name] = struct{}{} } - for mapk, mapv := range paramsMap { + for mapK, mapV := range paramsMap { name := "" for _, checkName := range []string { - gstr.UcFirst(mapk), - gstr.ReplaceByMap(mapk, map[string]string{ + gstr.UcFirst(mapK), + gstr.ReplaceByMap(mapK, map[string]string{ "_" : "", "-" : "", " " : "", - })} { - if _, ok := dmap[checkName]; ok { + })} { + if _, ok := doneMap[checkName]; ok { continue } - if _, ok := tagmap[checkName]; ok { + if _, ok := tagMap[checkName]; ok { continue } // 循环查找属性名称进行匹配 @@ -114,6 +106,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string break } } + doneMap[checkName] = true if name != "" { break } @@ -122,7 +115,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string if name == "" { continue } - if err := bindVarToStructAttr(elem, name, mapv); err != nil { + if err := bindVarToStructAttr(elem, name, mapV); err != nil { return err } } diff --git a/g/util/gconv/gconv_time.go b/g/util/gconv/gconv_time.go index e7d0a79f3..3a464b2ab 100644 --- a/g/util/gconv/gconv_time.go +++ b/g/util/gconv/gconv_time.go @@ -22,7 +22,7 @@ func TimeDuration(i interface{}) time.Duration { return time.Duration(Int64(i)) } -// 将变量i转换为time.Time类型 +// 将变量i转换为time.Time类型, 自动识别i为时间戳或者标准化的时间字符串。 func GTime(i interface{}, format...string) *gtime.Time { s := String(i) if len(s) == 0 { diff --git a/geg/other/test.go b/geg/other/test.go index c0c204684..3c0c2433a 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -5,19 +5,7 @@ import ( "github.com/gogf/gf/g/util/gconv" ) -type User struct { - Id int -} - -type RPCResponse struct { - ID interface{} `json:"id,omitempty"` - JsonRPC string `json:"jsonrpc"` - Error *User `json:"error,omitempty"` - Result interface{} `json:"result,omitempty"` -} - func main() { - var rpc RPCResponse - fmt.Println(rpc.Error) - fmt.Println(gconv.Map(rpc)) + t := gconv.GTime("2010-10-10 00:00:01") + fmt.Println(t.String()) } \ No newline at end of file