diff --git a/.example/database/gdb/driver/driver/driver.go b/.example/database/gdb/driver/driver/driver.go index f672730d4..97948b80c 100644 --- a/.example/database/gdb/driver/driver/driver.go +++ b/.example/database/gdb/driver/driver/driver.go @@ -9,7 +9,6 @@ package driver import ( "database/sql" "github.com/gogf/gf/database/gdb" - "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" ) @@ -51,14 +50,13 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { tsMilli := gtime.TimestampMilli() rows, err = d.DriverMysql.DoQuery(link, sql, args...) - if _, err := d.DriverMysql.InsertIgnore("monitor", g.Map{ - "sql": gdb.FormatSqlWithArgs(sql, args), - "cost": gtime.TimestampMilli() - tsMilli, - "time": gtime.Now(), - "error": err.Error(), - }); err != nil { - panic(err) - } + link.Exec( + "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", + gdb.FormatSqlWithArgs(sql, args), + gtime.TimestampMilli()-tsMilli, + gtime.Now(), + err, + ) return } @@ -67,13 +65,12 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) { tsMilli := gtime.TimestampMilli() result, err = d.DriverMysql.DoExec(link, sql, args...) - if _, err := d.DriverMysql.InsertIgnore("monitor", g.Map{ - "sql": gdb.FormatSqlWithArgs(sql, args), - "cost": gtime.TimestampMilli() - tsMilli, - "time": gtime.Now(), - "error": err.Error(), - }); err != nil { - panic(err) - } + link.Exec( + "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", + gdb.FormatSqlWithArgs(sql, args), + gtime.TimestampMilli()-tsMilli, + gtime.Now(), + err, + ) return } diff --git a/.example/database/gdb/mssql/gdb_sqlserver.go b/.example/database/gdb/mssql/gdb_sqlserver.go index fb3ffdcf3..0b81c802f 100644 --- a/.example/database/gdb/mssql/gdb_sqlserver.go +++ b/.example/database/gdb/mssql/gdb_sqlserver.go @@ -512,16 +512,6 @@ func mapToStruct() { } } -// getQueriedSqls -func getQueriedSqls() { - for k, v := range db.GetQueriedSqls() { - fmt.Println(k, ":") - fmt.Println("Sql :", v.Sql) - fmt.Println("Args :", v.Args) - fmt.Println("Error:", v.Error) - } -} - func main() { db.PingMaster() diff --git a/.example/database/gdb/mysql/config.toml b/.example/database/gdb/mysql/config.toml index af15452d4..48a479b62 100644 --- a/.example/database/gdb/mysql/config.toml +++ b/.example/database/gdb/mysql/config.toml @@ -1,10 +1,15 @@ -# MySQL数据库配置 +# MySQL. [database] debug = true - link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local" + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true" MaxOpen = 100 +# Redis. +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" + #[database] # [[database.default]] # type = "mysql" diff --git a/.example/database/gdb/mysql/gdb_cache.go b/.example/database/gdb/mysql/gdb_cache.go index ff0848857..cefc858da 100644 --- a/.example/database/gdb/mysql/gdb_cache.go +++ b/.example/database/gdb/mysql/gdb_cache.go @@ -3,6 +3,7 @@ package main import ( "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/util/gutil" + "time" ) func main() { @@ -10,7 +11,7 @@ func main() { Host: "127.0.0.1", Port: "3306", User: "root", - Pass: "123456", + Pass: "12345678", Name: "test", Type: "mysql", Role: "master", @@ -20,19 +21,20 @@ func main() { if err != nil { panic(err) } + //db.GetCache().SetAdapter(adapter.NewRedis(g.Redis())) // 开启调试模式,以便于记录所有执行的SQL db.SetDebug(true) // 执行2次查询并将查询结果缓存3秒,并可执行缓存名称(可选) - for i := 0; i < 2; i++ { - r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One() - gutil.Dump(r.ToMap()) + for i := 0; i < 3; i++ { + r, _ := db.Table("user").Cache(3000*time.Second).Where("id=?", 1).One() + gutil.Dump(r.Map()) } // 执行更新操作,并清理指定名称的查询缓存 - db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("uid=?", 1).Update() + //db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("id=?", 1).Update() // 再次执行查询,启用查询缓存特性 - r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One() - gutil.Dump(r.ToMap()) + //r, _ := db.Table("user").Cache(300000*time.Second, "vip-user").Where("id=?", 1).One() + //gutil.Dump(r.Map()) } diff --git a/.example/database/gdb/mysql/gdb_insert.go b/.example/database/gdb/mysql/gdb_insert.go index 509e30106..ee345b370 100644 --- a/.example/database/gdb/mysql/gdb_insert.go +++ b/.example/database/gdb/mysql/gdb_insert.go @@ -3,7 +3,7 @@ package main import ( "fmt" "github.com/gogf/gf/database/gdb" - "time" + "github.com/gogf/gf/frame/g" ) func main() { @@ -18,10 +18,9 @@ func main() { db.SetDebug(true) - type User struct { - CreateTime time.Time `orm:"create_time"` - } - r, e := db.Table("user").Data(User{CreateTime: time.Now()}).Insert() + r, e := db.Table("user").Data(g.Map{ + "create_at": "now()", + }).Unscoped().Insert() if e != nil { panic(e) } diff --git a/.example/database/gdb/mysql/gdb_pool.go b/.example/database/gdb/mysql/gdb_pool.go index 2ccbe4b97..0a435d618 100644 --- a/.example/database/gdb/mysql/gdb_pool.go +++ b/.example/database/gdb/mysql/gdb_pool.go @@ -8,9 +8,6 @@ import ( func main() { db := g.DB() - db.SetMaxIdleConnCount(10) - db.SetMaxOpenConnCount(10) - db.SetMaxConnLifetime(time.Minute) // 开启调试模式,以便于记录所有执行的SQL db.SetDebug(true) @@ -19,7 +16,7 @@ func main() { for i := 0; i < 10; i++ { go db.Table("user").All() } - time.Sleep(time.Second) + time.Sleep(time.Millisecond * 100) } } diff --git a/.example/database/gdb/mysql/gdb_value.go b/.example/database/gdb/mysql/gdb_value.go index 0fa7af53a..3ba996e67 100644 --- a/.example/database/gdb/mysql/gdb_value.go +++ b/.example/database/gdb/mysql/gdb_value.go @@ -1,12 +1,15 @@ package main import ( + "fmt" "github.com/gogf/gf/frame/g" ) func main() { - db := g.DB() - db.SetDebug(true) - - db.Table("user").Data("num=num+1").Where("id", 8).Update() + one, err := g.DB().Table("carlist c"). + LeftJoin("cardetail d", "c.postid=d.carid"). + Where("c.postid", "142039140032006"). + Fields("c.*,d.*").One() + fmt.Println(err) + g.Dump(one) } diff --git a/.example/database/gdb/oracle/gdb.go b/.example/database/gdb/oracle/gdb.go index bf2020868..902015a13 100644 --- a/.example/database/gdb/oracle/gdb.go +++ b/.example/database/gdb/oracle/gdb.go @@ -513,16 +513,6 @@ func mapToStruct() { } } -// getQueriedSqls -func getQueriedSqls() { - for k, v := range db.GetQueriedSqls() { - fmt.Println(k, ":") - fmt.Println("Sql :", v.Sql) - fmt.Println("Args :", v.Args) - fmt.Println("Error:", v.Error) - } -} - func main() { db.PingMaster() diff --git a/.example/i18n/gi18n/gi18n.go b/.example/i18n/gi18n/gi18n.go index 5787c6481..630d5a954 100644 --- a/.example/i18n/gi18n/gi18n.go +++ b/.example/i18n/gi18n/gi18n.go @@ -2,25 +2,14 @@ package main import ( "fmt" - - "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/frame/g" ) func main() { - t := gi18n.New() - t.SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n") - t.SetLanguage("en") - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) - - t.SetLanguage("ja") - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) - - t.SetLanguage("ru") - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) - - fmt.Println(t.Translate(`hello`, "zh-CN")) - fmt.Println(t.Translate(`{#hello}{#world}!`, "zh-CN")) + var ( + orderId = 865271654 + orderAmount = 99.8 + ) + fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount)) + fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount)) } diff --git a/.example/i18n/gi18n/i18n/en.toml b/.example/i18n/gi18n/i18n/en.toml index 17df41597..67762d579 100644 --- a/.example/i18n/gi18n/i18n/en.toml +++ b/.example/i18n/gi18n/i18n/en.toml @@ -1,3 +1 @@ - -hello = "Hello" -world = "World" \ No newline at end of file +OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f." \ No newline at end of file diff --git a/.example/i18n/gi18n/i18n/zh-CN.toml b/.example/i18n/gi18n/i18n/zh-CN.toml index b3a52c527..80acf06de 100644 --- a/.example/i18n/gi18n/i18n/zh-CN.toml +++ b/.example/i18n/gi18n/i18n/zh-CN.toml @@ -1,2 +1 @@ -hello = "你好" -world = "世界" \ No newline at end of file +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" \ No newline at end of file diff --git a/.example/net/ghttp/server/exit.go b/.example/net/ghttp/server/exit.go index a2904fc8b..864a9ddec 100644 --- a/.example/net/ghttp/server/exit.go +++ b/.example/net/ghttp/server/exit.go @@ -15,10 +15,10 @@ func main() { r.Response.Writeln("end") }) s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { glog.To(r.Response.Writer).Println("BeforeServe") }, - ghttp.HOOK_AFTER_SERVE: func(r *ghttp.Request) { + ghttp.HookAfterServe: func(r *ghttp.Request) { glog.To(r.Response.Writer).Println("AfterServe") }, }) diff --git a/.example/net/ghttp/server/hello.go b/.example/net/ghttp/server/hello.go index 5cdadabc1..4263a0e06 100644 --- a/.example/net/ghttp/server/hello.go +++ b/.example/net/ghttp/server/hello.go @@ -3,15 +3,12 @@ package main import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/os/glog" ) func main() { s := g.Server() - s.SetIndexFolder(true) s.BindHandler("/", func(r *ghttp.Request) { - glog.Println(r.Header) - r.Response.Write("hello world") + r.Response.Write("Hello World") }) s.SetPort(8999) s.Run() diff --git a/.example/net/ghttp/server/hooks/cors2.go b/.example/net/ghttp/server/hooks/cors2.go index 9992b5f15..71e43e28a 100644 --- a/.example/net/ghttp/server/hooks/cors2.go +++ b/.example/net/ghttp/server/hooks/cors2.go @@ -12,7 +12,7 @@ func Order(r *ghttp.Request) { func main() { s := g.Server() s.Group("/api.v1", func(group *ghttp.RouterGroup) { - group.Hook("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + group.Hook("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.CORSDefault() }) g.GET("/order", Order) diff --git a/.example/net/ghttp/server/hooks/hooks1.go b/.example/net/ghttp/server/hooks/hooks1.go index aa47e84e3..c87922cf5 100644 --- a/.example/net/ghttp/server/hooks/hooks1.go +++ b/.example/net/ghttp/server/hooks/hooks1.go @@ -11,10 +11,10 @@ func main() { p := "/:name/info/{uid}" s := g.Server() s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_BEFORE_SERVE) }, - ghttp.HOOK_AFTER_SERVE: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_AFTER_SERVE) }, - ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_BEFORE_OUTPUT) }, - ghttp.HOOK_AFTER_OUTPUT: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_AFTER_OUTPUT) }, + ghttp.HookBeforeServe: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeServe) }, + ghttp.HookAfterServe: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterServe) }, + ghttp.HookBeforeOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeOutput) }, + ghttp.HookAfterOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterOutput) }, }) s.BindHandler(p, func(r *ghttp.Request) { r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid")) diff --git a/.example/net/ghttp/server/hooks/hooks4.go b/.example/net/ghttp/server/hooks/hooks4.go index 783a3bbee..3a44c8ae4 100644 --- a/.example/net/ghttp/server/hooks/hooks4.go +++ b/.example/net/ghttp/server/hooks/hooks4.go @@ -11,7 +11,7 @@ func main() { // 多事件回调示例,事件1 pattern1 := "/:name/info" s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { r.SetParam("uid", 1000) }, }) @@ -22,7 +22,7 @@ func main() { // 多事件回调示例,事件2 pattern2 := "/{object}/list/{page}.java" s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) { + ghttp.HookBeforeOutput: func(r *ghttp.Request) { r.Response.SetBuffer([]byte( fmt.Sprintf("通过事件修改输出内容, object:%s, page:%s", r.Get("object"), r.GetRouterString("page"))), ) diff --git a/.example/net/ghttp/server/hooks/hooks5.go b/.example/net/ghttp/server/hooks/hooks5.go index 19a6caeb7..d7af76007 100644 --- a/.example/net/ghttp/server/hooks/hooks5.go +++ b/.example/net/ghttp/server/hooks/hooks5.go @@ -7,10 +7,10 @@ import ( func main() { s := g.Server() - s.BindHookHandler("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + s.BindHookHandler("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Writeln("/*any") }) - s.BindHookHandler("/v1/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + s.BindHookHandler("/v1/*", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Writeln("/v1/*") r.ExitHook() }) diff --git a/.example/net/ghttp/server/hooks/hooks_param.go b/.example/net/ghttp/server/hooks/hooks_param.go index 5aee3767b..6f6d698d7 100644 --- a/.example/net/ghttp/server/hooks/hooks_param.go +++ b/.example/net/ghttp/server/hooks/hooks_param.go @@ -11,7 +11,7 @@ func main() { r.Response.Writeln(r.Get("name")) }) s.BindHookHandlerByMap("/", map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { r.SetParam("name", "john") }, }) diff --git a/.example/net/ghttp/server/hooks/same_route_multi_hook.go b/.example/net/ghttp/server/hooks/same_route_multi_hook.go index 41ffbdae0..605b31a03 100644 --- a/.example/net/ghttp/server/hooks/same_route_multi_hook.go +++ b/.example/net/ghttp/server/hooks/same_route_multi_hook.go @@ -12,17 +12,17 @@ func main() { }) s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { r.Response.Writeln("/priority/:name") }, }) s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { r.Response.Writeln("/priority/*any") }, }) s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { + ghttp.HookBeforeServe: func(r *ghttp.Request) { r.Response.Writeln("/priority/show") }, }) diff --git a/.example/net/ghttp/server/middleware/middleware.go b/.example/net/ghttp/server/middleware/middleware.go index 9bad81b1c..4dcf2ddb9 100644 --- a/.example/net/ghttp/server/middleware/middleware.go +++ b/.example/net/ghttp/server/middleware/middleware.go @@ -27,10 +27,10 @@ func main() { }) }) group.Group("/hook", func(group *ghttp.RouterGroup) { - group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Write("hook any") }) - group.Hook("/:name", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Write("hook name") }) }) diff --git a/.example/net/ghttp/server/pprof.go b/.example/net/ghttp/server/pprof.go index fd16f2766..79f96ebdc 100644 --- a/.example/net/ghttp/server/pprof.go +++ b/.example/net/ghttp/server/pprof.go @@ -1,12 +1,13 @@ package main import ( + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" ) func main() { - s := ghttp.GetServer() - s.EnablePProf() + s := g.Server() + s.Domain("localhost").EnablePProf() s.BindHandler("/", func(r *ghttp.Request) { r.Response.Writeln("哈喽世界!") }) diff --git a/.example/net/ghttp/server/reload/admin.go b/.example/net/ghttp/server/reload/admin.go index 94caa4040..a4147ccc8 100644 --- a/.example/net/ghttp/server/reload/admin.go +++ b/.example/net/ghttp/server/reload/admin.go @@ -6,6 +6,7 @@ import ( func main() { s := g.Server() + s.SetConfigWithMap(g.Map{"Graceful": true}) s.EnableAdmin() s.SetPort(8199) s.Run() diff --git a/.example/net/ghttp/server/resource/resource.go b/.example/net/ghttp/server/resource/resource.go index 50ea7ea50..c9784aeed 100644 --- a/.example/net/ghttp/server/resource/resource.go +++ b/.example/net/ghttp/server/resource/resource.go @@ -18,7 +18,7 @@ func main() { s := g.Server() s.SetIndexFolder(true) s.SetServerRoot("root") - s.BindHookHandler("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + s.BindHookHandler("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) { fmt.Println(r.URL.Path, r.IsFileRequest()) }) s.BindHandler("/template", func(r *ghttp.Request) { diff --git a/.example/net/ghttp/server/router/group/batch.go b/.example/net/ghttp/server/router/group/batch.go index a335d2cf0..84519907b 100644 --- a/.example/net/ghttp/server/router/group/batch.go +++ b/.example/net/ghttp/server/router/group/batch.go @@ -27,7 +27,7 @@ func main() { s := g.Server() obj := new(Object) s.Group("/api").Bind([]ghttp.GroupItem{ - {"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE}, + {"ALL", "*", HookHandler, ghttp.HookBeforeServe}, {"ALL", "/handler", Handler}, {"ALL", "/obj", obj}, {"GET", "/obj/show", obj, "Show"}, diff --git a/.example/net/ghttp/server/router/group/level.go b/.example/net/ghttp/server/router/group/level.go index c8668a169..1ee710586 100644 --- a/.example/net/ghttp/server/router/group/level.go +++ b/.example/net/ghttp/server/router/group/level.go @@ -54,10 +54,10 @@ func main() { }) }) group.Group("/hook", func(group *ghttp.RouterGroup) { - group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Write("hook any") }) - group.Hook("/:name", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) { r.Response.Write("hook name") }) }) diff --git a/.example/net/ghttp/server/router/router5.go b/.example/net/ghttp/server/router/router5.go index aed4fc3ef..c280fadb0 100644 --- a/.example/net/ghttp/server/router/router5.go +++ b/.example/net/ghttp/server/router/router5.go @@ -9,7 +9,7 @@ import ( func main() { s := g.Server() - s.BindHookHandler("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { + s.BindHookHandler("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) { fmt.Println(r.Router) fmt.Println(r.Get("customer_id")) }) diff --git a/.example/os/gmlock/locker4.go b/.example/os/gmlock/locker4.go index a7d8df5f4..3f27939c0 100644 --- a/.example/os/gmlock/locker4.go +++ b/.example/os/gmlock/locker4.go @@ -13,7 +13,7 @@ func main() { key := "key" // 第一次锁带时间 - gmlock.Lock(key, 1000) + gmlock.Lock(key) glog.Println("lock1") // 这个时候上一次的计时解锁已失效 gmlock.Unlock(key) diff --git a/.example/os/gview/layout/layout1/main.go b/.example/os/gview/layout/layout1/main.go index 660cca012..ed12906bc 100644 --- a/.example/os/gview/layout/layout1/main.go +++ b/.example/os/gview/layout/layout1/main.go @@ -2,19 +2,18 @@ package main import ( "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" + "github.com/gogf/gf/net/ghttp" ) -type Controller struct { - gmvc.Controller -} - -func (c *Controller) Test() { - c.View.Display("layout.html") -} func main() { s := g.Server() - s.BindControllerMethod("/", new(Controller), "Test") + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "header": "This is header", + "container": "This is container", + "footer": "This is footer", + }) + }) s.SetPort(8199) s.Run() } diff --git a/.example/os/gview/layout/layout1/template/container.html b/.example/os/gview/layout/layout1/template/container.html index 5902716a8..4f6af6313 100644 --- a/.example/os/gview/layout/layout1/template/container.html +++ b/.example/os/gview/layout/layout1/template/container.html @@ -1,3 +1,3 @@ {{define "container"}} -

CONTAINER

+

{{.container}}

{{end}} \ No newline at end of file diff --git a/.example/os/gview/layout/layout1/template/footer.html b/.example/os/gview/layout/layout1/template/footer.html index 782a3c05f..8a5f09a53 100644 --- a/.example/os/gview/layout/layout1/template/footer.html +++ b/.example/os/gview/layout/layout1/template/footer.html @@ -1,3 +1,3 @@ {{define "footer"}} -

FOOTER

+

{{.footer}}

{{end}} \ No newline at end of file diff --git a/.example/os/gview/layout/layout1/template/header.html b/.example/os/gview/layout/layout1/template/header.html index 7e2c837c4..bc500316a 100644 --- a/.example/os/gview/layout/layout1/template/header.html +++ b/.example/os/gview/layout/layout1/template/header.html @@ -1,3 +1,3 @@ {{define "header"}} -

HEADER

+

{{.header}}

{{end}} \ No newline at end of file diff --git a/.example/os/gview/layout/layout1/template/layout.html b/.example/os/gview/layout/layout1/template/layout.html index 8c117123a..223fce6b8 100644 --- a/.example/os/gview/layout/layout1/template/layout.html +++ b/.example/os/gview/layout/layout1/template/layout.html @@ -2,14 +2,14 @@ GoFrame Layout - {{template "header"}} + {{template "header" .}}
- {{template "container"}} + {{template "container" .}}
\ No newline at end of file diff --git a/.example/os/gview/layout/layout2/main.go b/.example/os/gview/layout/layout2/main.go index f0abb36ec..f90ae6fc1 100644 --- a/.example/os/gview/layout/layout2/main.go +++ b/.example/os/gview/layout/layout2/main.go @@ -9,11 +9,13 @@ func main() { s := g.Server() s.BindHandler("/main1", func(r *ghttp.Request) { r.Response.WriteTpl("layout.html", g.Map{ + "name": "smith", "mainTpl": "main/main1.html", }) }) s.BindHandler("/main2", func(r *ghttp.Request) { r.Response.WriteTpl("layout.html", g.Map{ + "name": "john", "mainTpl": "main/main2.html", }) }) diff --git a/.example/util/gutil/try_catch.go b/.example/util/gutil/try_catch.go index 6ae1afea4..0aa3ccf4b 100644 --- a/.example/util/gutil/try_catch.go +++ b/.example/util/gutil/try_catch.go @@ -11,7 +11,7 @@ func main() { fmt.Println(1) gutil.Throw("error") fmt.Println(2) - }, func(err interface{}) { + }, func(err error) { fmt.Println(err) }) } diff --git a/.travis.yml b/.travis.yml index 5ef9c9afa..ee2f52ac1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ go: - "1.12.x" - "1.13.x" - "1.14.x" + - "1.15.x" branches: only: diff --git a/DONATOR.MD b/DONATOR.MD deleted file mode 100644 index 0a96fcb47..000000000 --- a/DONATOR.MD +++ /dev/null @@ -1,99 +0,0 @@ -# Donators - -We currently accept donation by [Alipay](https://goframe.org/images/donate.png) / [Gitee](https://gitee.com/johng/gf), -please note your github/gitee account in your payment bill. - -| Name | Channel | Amount | Comment -|---|---|--- | --- -|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 | -|[ireadx](https://github.com/ireadx)|alipay|¥301.00 | -|[mg91](https://gitee.com/mg91)|gitee|¥10.00 | -|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 | -|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 | -|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00 | -|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 | -|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 | -|[arden](https://github.com/arden)|alipay|¥10.00 | -|[macnie](https://www.macnie.com)|wechat|¥110.00 | -|lah|wechat|¥100.00 | -|x*z|wechat|¥20.00 | -|潘兄|wechat|¥100.00 | -|Fly的狐狸|wechat|¥100.00 | -|全|alipay|¥100.00 | -|东东|wechat|¥100.00 | -|严宇轩|alipay|¥99.99 | -|土豆相公|alipay|¥66.60 | -|Hades|alipay|¥66.66 | -|蔡蔡|wechat|¥666.00 | gf越来越强 -|上海金保证网络科技|bank|¥2000.00 | -|[foxhack](https://github.com/foxhack)|wechat|¥20.00 | -|*栈|wechat|¥5.00 | -|*络|wechat|¥10.00| -|M*e|wechat|¥20.00| -|*G|wechat|¥10.00| -|E*_|wechat|¥10.00| -|A*y|wechat|¥1.00| 感谢大佬goframe -|K*e|wechat|¥168.00| 感谢老大 -|*雨|wechat|¥100.00| -|*洁|wechat|¥10.00|赞助你肥宅快乐水 -|R*s|wechat|¥18.88| 谢谢GF!辛苦了! -|粟*e|wechat|¥50.00| -|[李超](https://github.com/effortlee)|wechat|¥124.00| -|soidea666|wechat+qq|¥800.00| -|[王哈哈](https://gitee.com/develop1024)|wechat|¥6.66| 希望gf越来越好 -|夕景|alipay+qq|¥9.96+3.57| -|struggler|alipay|¥18.80| -|*铁|wechat|¥0.01| -|C*e|wechat|¥66.66| GF越来越好,棒👍! -|---P คิดถึง|wechat|¥6.66| -|[王飞](https://gitee.com/wang_2018)|gitee|¥20.00| 感谢您的开源项目! -|[Zeroing-ZY](https://gitee.com/yunjieg)|gitee|¥20.00| 感谢您的开源项目! -|[katydid酱](https://gitee.com/katydid2005)|gitee|¥50.00| 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬! -|[李海峰](https://gitee.com/dlhf)|gitee|¥10.00| 希望GF越来越好,框架很牛逼! -|陆昱天|alipay|¥100.00| -|[Dockercore](https://github.com/dockercore)|wechat|¥200.00| 非常喜欢!简洁好用!文档超级全! -|🚶|wechat|¥6.88| 喝杯冰阔落 -|a*l|wechat|¥10.00| gf -|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00| -|[米司特包](https://github.com/misitebao)|wechat|¥9.99| -|重庆宝尔威科技|wechat|¥6.66| -|琦玉-QPT|wechat|¥6.66| -|sailsea|wechat|¥11.00| -|[seny0929](https://gitee.com/seny0929)|wechat|¥99.90| -|*华|wechat|¥6.66| 感谢郭强的热心 -|[Playhi](https://github.com/Playhi)|alipay|¥10.00| -|北京京纬互动科技|alipay|¥200.00| -|[米司特包](https://github.com/misitebao)|wechat|¥99.99| -|金毛|alipay|¥100.00| -|1*1x|wechat|¥100.00| -|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66| -|[侯哥](http://www.macnie.com)|wechat|¥10.00| -|如果🍋|alipay|¥100.00| 错过的奶茶^_^ -|蔡蔡|wechat|¥666.00| gf真强大,让项目省心 -|jack|wechat|¥100.00| -|sbilly|wechat|¥100.00| 祝好! -|[米司特包](https://github.com/misitebao)|wechat|¥166.66| 大佬加油! -|*秦|wechat|¥20.00| 给群主献上一杯咖啡... -|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00| -|faddei|qq|¥9.99| -|*天|wechat|¥10.00| -|*.|wechat|¥20.00| 学生一个微薄之力,冲 -|李勇|wechat|¥50.00| -|[米司特包](https://github.com/misitebao)|wechat|¥66.66| -|千年|wechat|¥1.08| -|[szzxing](https://github.com/szzxing)|wechat|¥50.00| -|伟客互联|wechat|¥66.66| -|[sbilly](https://github.com/sbilly)|wechat|¥99.00| -|**阳|alipay|¥100.01| -|**亮|alipay|¥10.00| -|[mingzaily](https://github.com/mingzaily)|alipay|¥30.00| -|[seny0929](https://gitee.com/seny0929)|alipay|¥9.90| -|Fly的狐狸|alipay|¥100.00| -|六七 ·|wechat|¥88.88| gf越来越好 -|Tom|wechat|¥500.00| 一点心意 希望GF越来越好 -|Chao|wechat|¥166.00| - - - - - diff --git a/README.MD b/README.MD index 8fe86519a..cdc2e6ac1 100644 --- a/README.MD +++ b/README.MD @@ -9,8 +9,8 @@ English | [简体中文](README_ZH.MD) -`GF(GoFrame)` is a modular, powerful, high-performance and production-ready application development framework -of golang. Providing a series of core components and dozens of practical modules, such as: +`GF(GoFrame)` is a modular, powerful, high-performance and enterprise-class application development framework +of Golang. Providing a series of core components and dozens of practical modules, such as: cache, logging, containers, timer, resource, validator, database orm, etc. Supporting web server integrated with router, cookie, session, middleware, logger, configure, template, https, hooks, rewrites and many more features. @@ -47,56 +47,9 @@ golang version >= 1.11 # Performance -Here's the most popular Golang frameworks and libraries performance testing result in `WEB Server`. Performance testing cases source codes are hosted at: https://github.com/gogf/gf-performance +The `Web` component performance of `GoFrame`, please refer to third-party project: https://github.com/the-benchmarker/web-frameworks -## Environment - OS : Ubuntu 18.04 amd64 - CPU : AMD A8-6600K x 4 - MEM : 32GB - GO : v1.13.4 - -## Testing Tool - -`ab`: Apache HTTP server benchmarking tool. - -Command: -``` -ab -t 10 -c 100 http://127.0.0.1:3000/hello -ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000 -ab -t 10 -c 100 http://127.0.0.1:3000/json -``` -The concurrency starts from `100` to `10000`. - -> Run `5` times for each case of each project and pick up the best testing result. - -## 1. Hello World - - - - - - - - - - - -
ThroughputsMean LatencyP99 Latency
- -## 2. Json Response - - - - - - - - - - - -
ThroughputsMean LatencyP99 Latency
# Documentation @@ -115,9 +68,19 @@ The concurrency starts from `100` to `10000`. `GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever. -# Known Users +# Part Of Users -Logos are not authorized to be shown due to trademark copyrights. +- [Tencent](https://www.tencent.com/) +- [ZTE](https://www.zte.com.cn/china/) +- [Ant Financial Services](https://www.antfin.com/) +- [MedLinker](https://www.medlinker.com/) +- [KuCoin](https://www.kucoin.io/) +- [LeYouJia](https://www.leyoujia.com/) +- [IGG](https://igg.com) +- [XiMaLaYa](https://www.ximalaya.com) +- [ZYBang](https://www.zybang.com/) + +> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168). # Contributors @@ -127,7 +90,7 @@ This project exists thanks to all the people who contribute. [[Contributors](htt # Donators -We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)? +If you love `GF`, why not [buy developer a cup of coffee](https://itician.org/pages/viewpage.action?pageId=1115633)? # Sponsors We appreciate any kind of sponsorship for `GF` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`. @@ -135,8 +98,8 @@ We appreciate any kind of sponsorship for `GF` development. If you've got some i # Thanks -JetBrains - +JetBrains +Atlassian diff --git a/README_ZH.MD b/README_ZH.MD index efcb41889..77f472620 100644 --- a/README_ZH.MD +++ b/README_ZH.MD @@ -8,7 +8,7 @@ [English](README.MD) | 简体中文 -`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。 +`GF(Go Frame)`是一款模块化、高性能、企业级的Go基础开发框架。 实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块, 如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、 配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。 @@ -65,58 +65,7 @@ golang版本 >= 1.11 # 性能 -以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。 -性能测试用例源代码仓库: https://github.com/gogf/gf-performance - -## 环境: - - OS : Ubuntu 18.04 amd64 - CPU : AMD A8-6600K x 4 - MEM : 32GB - GO : v1.13.4 - -## 工具 - -`ab`: Apache HTTP server benchmarking tool. - -测试命令: -``` -ab -t 10 -c 100 http://127.0.0.1:3000/hello -ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000 -ab -t 10 -c 100 http://127.0.0.1:3000/json -``` -并发客户端数量从 `100` 递增到 `10000`。 - -> 每个项目的每个用例均运行`5`次,取最优的结果展示。 - -## 1. Hello World - - - - - - - - - - - -
ThroughputsMean LatencyP99 Latency
- -## 2. Json Response - - - - - - - - - - - -
ThroughputsMean LatencyP99 Latency
- +`Web`组件的性能测试,请参考第三方性能测试评估:https://github.com/the-benchmarker/web-frameworks # 文档 @@ -137,7 +86,17 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json # 用户 -由于商标版权缘故,未经厂商商务部授权允许无法用于宣传展示。 +- [腾讯科技](https://www.tencent.com/) +- [中兴科技](https://www.zte.com.cn/china/) +- [蚂蚁金服](https://www.antfin.com/) +- [医联科技](https://www.medlinker.com/) +- [库币科技](https://www.kucoin.io/) +- [乐有家](https://www.leyoujia.com/) +- [IGG](https://igg.com) +- [喜马拉雅](https://www.ximalaya.com) +- [作业帮](https://www.zybang.com/) + +> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。 # 贡献 @@ -147,7 +106,7 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json # 捐赠 -如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)! +如果您喜欢`GF`,要不给开发者 [来杯咖啡](https://itician.org/pages/viewpage.action?pageId=1115633) 吧! 请在捐赠时备注您的`github`/`gitee`账号名称。 # 赞助 @@ -155,5 +114,5 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json 赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 微信 `389961817` / 邮件 `john@goframe.org`。 # 感谢 -JetBrains - +JetBrains +Atlassian diff --git a/RELEASE.1.MD b/RELEASE.1.MD deleted file mode 100644 index 8ce74ae84..000000000 --- a/RELEASE.1.MD +++ /dev/null @@ -1,566 +0,0 @@ -# `v1.8.0` (2019-07-15) - -## 新功能改进 -1. 框架目前 `69` 个开发模块(不包括内部模块),原生代码 `65302` 行(不包含第三包依赖包),单元测试覆盖率达到`77%`; -1. 新增`gerror`错误处理模块:https://goframe.org/errors/gerror/index -1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/index -1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性:https://goframe.org/os/gmutex/index -1. 改进`glog`日志模块: - - 新增日志异步输出特性:https://goframe.org/os/glog/async - - 新增`Flags`额外功能特性:https://goframe.org/os/glog/flags - - 新增`Json`数据格式输出:https://goframe.org/os/glog/json - - 新增自定义`Writer`接口特性:https://goframe.org/os/glog/writer - - **修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式;** - - 新增`Expose`方法暴露内部默认`Logger`对象; -1. 改进`gdb`数据库ORM模块: - - **改进错误处理,当数据库操作没有查询到数据时,`error`返回`sql.ErrNoRows`**:https://goframe.org/database/gdb/error - - 改进`Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; - - 数据库链式操作及方法操作中,预处理变量参数支持`slice`参数:https://goframe.org/database/gdb/chaining/model - - **修改`Priority`权重配置名称为`Weight`;** - - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config - - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法; - - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; - - 改进简化配置文件结构:https://goframe.org/database/gdb/config -1. 改进`gconv`数据转换模块: - - 对结构体对象转换时支持更多的标签:`gconv/c/json`; - - 支持`*struct/[]struct/[]*struct`自动初始化创建对象/数组:https://goframe.org/util/gconv/struct - - 新增`Strusts/StrctsDeep`方法,用于结构体数组的递归转换; - - 新增`StructDeep`方法,用于对结构体对象的递归转换; - - 新增`MapDeep`方法,用于对结构体属性的递归转换; -1. 改进`ghttp`模块: - - 改进`ghttp`模块的分组路由功能,完善逻辑处理细节,程序更加稳健; - - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换,并且支持`**struct`参数的对象自动初始化; - - 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`; -1. 改进`gvalid`数据校验模块: - - 增加对校验标签`gvalid/valid/v`的支持; - - 改进`CheckStruct`支持对结构体对象的递归校验:https://goframe.org/util/gvalid/checkstruct -1. 改进`gtcp`TCP通信模块: - - 改进通信包协议设计,更加轻量级高效:https://goframe.org/net/gtcp/conn/pkg - - 改进`TCP Server`增加对`TLS`的支持:https://goframe.org/net/gtcp/tls - - 增加`Server.Cloce`服务端关闭方法; -1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑; -1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; -1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的操作方法; -1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: -1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; -1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; -1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;** -1. **改进`gbase64`模块,输入输出类型发生改变,并增加多个相关方法;** -1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; -1. 新增`gfile.CopyFile/CopyDir`方法,用于文件及目录的复制; -1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; -1. 改进`gcache`模块,过期时间参数支持`time.Duration`类型; -1. 新增`internal/structs`包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块; -1. 改进`gbinary`增加封装方法对`BigEndian`的支持; - -## Bug Fix -1. 修复`garray.Search`返回值问题; -1. 修复`garray.Contains`, `garray.New*ArrayFromCopy`方法逻辑问题; -1. 修复`gjson.Remove`删除`slice`参数问题; -1. 修复`gtree.AVLTree.Remove`方法返回值问题; -1. 修复`gqueue.Size`不准确的大小问题; -1. 修复`queue.Close`问题; -1. 修复`gcache.GetOrSetLockFunc`当回调函数返回`nil`结果时的死锁问题; -1. 修复`gfsnotify.Add`方法默认递归监控添加失效问题; -1. 修复`gdb.Model.Scan`在某些参数类型下的失效问题; - -## 注意事项 - -请注意以上粗体文字部分,如有使用,在您升级时可能会出现不兼容性。 - - - -# `v1.7.0` (2019-06-10) -## 新功能/改进 -1. 重构改进`glog`模块: - - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 - - 增加日志内容的异步输出特性:https://goframe.org/os/glog/async - - 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json - - 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags - - 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer - - 修改`SetStdPrint`方法名为`SetStdoutPrint` - - 修改链式方法`StdPrint`方法名为`Stdout` - - 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出 - - 新增更多的链式方法支持:https://goframe.org/os/glog/chain -1. 重构改进`gmap`模块: - - 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap` - - 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap` - - 改进`Map/Keys/Values`方法以提高性能 - - 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes` - - 新增更多功能方法支持:https://goframe.org/container/gmap/index -1. 改进`gtime`时间模块: - - 增加并完善更多的类`PHP`时间格式支持 - - 新增更多功能方法,如`FormatTo`/`LayoutTo`等等 - - 详见开发文档:https://goframe.org/os/gtime/index -1. 改进`gdb`数据库模块: - - 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior - - 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句 - - 其他的细节处理改进 -1. 改进`gtcp`通信模块: - - 完善处理细节,提高通信性能; - - 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls - - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg - - TCP服务端增加`Close`方法 - - 更多细节查看开发文档:https://goframe.org/net/gtcp/index -1. 改进`gconv`类型转换模块 - - 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration` - - 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换 - - 详见开发文档:https://goframe.org/util/gconv/struct -1. 改进`ghttp`模块: - - 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs - - 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性 - - 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址 - - `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client -1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index -1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index -1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index -1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index -1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes` -1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index -1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境; -1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index -1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理 -1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法 -1. 去除`gvar.VarRead`接口类型支持 - -## Bug Fix -1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突; -1. 修复`gcron.AddOnce`方法的细节逻辑问题; -1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误; -1. 修复`gview`模板引擎的并发安全问题; -1. 修复`ghttp.Server`的SESSION初始化过期时间问题; - -# `v1.6.0` (2019-04-09) - -## 新功能/改进 -1. `gcron`定时任务模块增加运行日志记录功能:https://goframe.org/os/gcron/index -1. `gredis`增加全局分组配置功能,并增加更多的配置选项`maxIdle/maxActive/idleTimeout/maxConnLifetime`:https://goframe.org/database/gredis/index -1. `gcfg`模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加`Instance`单例方法:https://goframe.org/os/gcfg/index -1. `gview`模块增加更多的默认配置文件检索路径,并且增加`Instance`单例方法:https://goframe.org/os/gview/index -1. `ghttp`模块新功能及改进: - - 新增`CORS`HTTP(S)跨域请求特性: https://goframe.org/net/ghttp/cors - - 增加`TLSConfig`配置功能; - - 去掉路由注册方法的`error`返回值,当产生注册错误时直接终端打印错误/输出到日志文件; - - 增加在`HTTP Code 302`跳转时的`Set-Cookie`支持; - - 增加对`SESSION ID`的安全性检查; - - 增加对基于`HTTPS`的`WebSocket`支持(`WSS`):https://goframe.org/net/ghttp/websocket/index - - `Request`对象增加`Error`方法,用于输出自定义错误信息到`WebServer`错误日志中; - - 其他一些改进; -1. `gdb`模块新功能及改进: - - 新增`Instance`单例管理方法; - - 新增`Structs/Scan`链式操作方法,`gdb.DB/TX`新增`GetStructs/GetScan`方法,用于结果集`struct`/`slice`映射转换:https://goframe.org/database/gdb/chaining - - 新增`Safe`链式操作方法(默认非并发安全),用于链式安全控制:https://goframe.org/database/gdb/chaining - - `Where`链式操作方法改进: - - 方法支持任意的`string/map/slice/struct/*struct`类型; - - 逻辑调整,当链式操作中存在多个`Where`方法调用时,自动转换为`And`条件; - - 支持`slice`条件参数,常用在`SELECT IN`查询中,例如:`Where("uid IN(?)", g.Slice{1,2,3})`; - - 支持在`map`类型条件参数的`key`中传递条件,例如:`Where(g.Map{"uid>?", uid})`; -1. `gconv`及`gvalid`模块改进并去掉对私有`struct`方法属性的转换/校验; -1. `gconv.Map`转换方法新增对`json tag`: `-`, `omitempty`的支持: https://goframe.org/util/gconv/map -1. `gstr`模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap`大小写非敏感替换方法; -1. `gutil`模块增加`IsEmpty`方法,用于判断给定变量是否为空(整型0, 布尔false, slice/map长度为0, 其他为nil的情况,判断为空),并增加快捷方法`g.IsEmpty`; -1. `gutil`模块增加`Export`方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法`g.Export`; -1. `gspath`增加缓存及非缓存检索检索方法`Search`/`SearchWithCache`; -1. `gjson`模块增加默认的`UseNumber`功能支持; -1. `gmap`增加`SetIfNotExistFunc/SetIfNotExistFuncLock`方法; -1. 迁移`greuseport`模块到新的仓库:https://github.com/gogf/greuseport -1. 大量的单元测试完善; - -## Bug Fix -1. 修复`gqueue`模块的资源竞争问题; -1. 修复`gconv.GTime`转换失败问题; -1. 修复`gconv.String`在转换`int`参数时字节溢出问题; -1. 修复`ghttp.Request`的`HTTP Basic Auth`校验问题; -1. 修复`gxml`针对于非`UTF-8`编码内容转换的并发安全问题; -1. 修复`gtime`部分`Format`(`G`&`j`)格式失效问题; -1. 修复`gudp.Conn`对象的`RemoteAddr`获取客户端连接地址方法问题; -1. 修复`gmap/gcache`模块的`GetOrSetFuncLock`方法,增加对回调方法返回值的`nil`判断,只有非nil返回值才会被保存; - - - -# `v1.5.8` (2019-02-28) - -## 新特性 -1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf ),`gitee`作为镜像站,用于国内的代码贡献及ISSUE提交,迁移说明详见:https://goframe.org/upgradeto150 -1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/garray -1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/gset -1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/gmap -1. 对常用的字符串模块: `gstr`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/text/gstr -1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递,具体请参考:https://goframe.org/database/orm/chaining -1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`; -1. 由于`gkafka`模块比较重,且不是框架核心模块,因此将该模块迁移到新的仓库中独立管理,并去掉相关依赖包:https://github.com/gogf/gkafka -1. 新增`greuseport`模块,用以实现TCP的`REUSEPORT`特性:https://godoc.org/github.com/gogf/gf/net/greuseport - -## 新功能/改进 -1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题; -1. `ghttp.Client`改进,增加若干方法,详见:https://goframe.org/net/ghttp/client -1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由; -1. 更新框架依赖的`golang.org/x/sys`模块; -1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数; -1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下; -1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下; -1. 完善`glog`方法注释; - -## Bug Fix -1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功; -1. 修复`gvalid.Check`在`regex`规则下的检查失败问题; -1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题; -1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题; -1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题; -1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题; -1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题; -1. 其他一些修复; - - - -# `v1.4.6` (2019-01-24) - -## 新特性 -1. 新增并发安全的高性能任务定时器模块`gtimer`, 类似于Java的`Timer`,但是比较于Java的`Timer`更加强大,内部实现采用灵活高效的`分层时间轮`设计,被设计为可管理维护百万级别以上数量的定时任务。`gtimer`为`GF`框架的核心模块之一,单元测试覆盖率达到`93.6%`:[https://goframe.org/os/gtimer/index](https://goframe.org/os/gtimer/index) -1. 采用任务定时器`gtimer`重构`gcron`定时任务模块,去掉第三方`github.com/robfig/cron`包的使用。`gcron`增加单例模式的定时任务:[https://goframe.org/os/gcron/index#](https://goframe.org/os/gcron/index#); -1. `gconv`类型转换模块支持对`struct`结构体中的**指针属性**转换:[https://goframe.org/util/gconv/struct](https://goframe.org/util/gconv/struct); -1. `gform`增加对数据库类型的自动识别特性,这一特性在需要将查询结果`json`编码返回时非常有用: [https://goframe.org/database/orm/index](https://goframe.org/database/orm/index) -1. `Travis CI`增加对`386`架构的自动化测试支持(目前已支持`386`和`amd64`); - -## 新功能 -1. `ghttp`模块新增`Exit`、`ExitAll`、`ExitHook`方法,用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object); -1. `grand`模块增加`Meet/MeetProb`方法,用于给定概率的随机满足判断,增加别名方法`N/Str/Digits/Letters`; -1. `gvalid`数据/表单校验模块增加`16X`及`19X`手机号的校验支持; - -## 功能改进 -1. `gform`设置默认的数据库连接池`CONN_MAX_LIFE`参数值为`30`秒; -1. 改进`glist`模块,提高约`20%`左右性能,并增加若干链表操作方法; -1. 改进`gqueue`模块,提高约`50`左右性能,并增加模块对`select`语法的支持(使用`Queue.C`): [https://goframe.org/container/gqueue/index](https://goframe.org/container/gqueue/index); -1. 改进`gmlock`内存锁模块,并完善单元测试用例:[https://goframe.org/os/gmlock/index](https://goframe.org/os/gmlock/index); -1. 改进并发安全容器所有的模块,调整并发安全控制非必需参数`safe...bool`为`unsafe...bool`; -1. 改进`gpool`对象复用模块,支持并发安全; -1. 更新`gkafka`模块的第三方依赖包; -1. 完善`ghttp`模块的单元测试用例; - - -## Bug Fix -1. 修复`gmd5`模块操作文件时的文件指针未关闭问题; -1. 修复`gcache`缓存项过期删除失效问题; -1. 其他修复; - -# `v1.3.8` (2018-12-26) - -## 新特性 -1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://goframe.org/database/orm/index](https://goframe.org/database/orm/index)); -1. `WebServer`路由注册新增分组路由特性([https://goframe.org/net/ghttp/group](https://goframe.org/net/ghttp/group)); -1. `WebServer`新增`Rewrite`路由重写特性([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static)); -1. 增加框架运行时对开发环境的自动识别; -1. 增加了`Travis CI`自动化构建/测试; - -## 新功能 -1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static)); -1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)); -1. `gcache`新增`Data`方法,用以获取所有的缓存数据项; -1. `gredis`增加`GetConn`方法获取原生redis连接对象; - -## 功能改进 -1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)); -1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`; -1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法; -1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间; -1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为``); -1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题; -1. 改进`gcache`性能,并完善基准测试; - -## Bug Fix -1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题,并修复`doSetWithLockCheck`内部方法的返回值问题; -1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题; -1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题; -1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题; -1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题; -1. 修复`ghttp.Request.GetVar`方法获取参数问题; -1. 修复`gform`的数据库连接池不起作用的问题; - - - - - -# `v1.2.11` (2018-11-26) -## 新特性 -1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://goframe.org/database/orm/database](https://goframe.org/database/orm/database)); -1. 完成`gvalid`模块校验结果的顺序特性([https://goframe.org/util/gvalid/checkmap](https://goframe.org/util/gvalid/checkmap)); -1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object)); -1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://goframe.org/os/gview/funcs)); -1. 模板引擎新增内置变量`Config` ([https://goframe.org/os/gview/vars](https://goframe.org/os/gview/vars)); -1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配; -1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://goframe.org/database/orm/config](https://goframe.org/database/orm/config)); -1. `gfsnotify`模块增加对特定回调的取消注册功能([https://goframe.org/os/gfsnotify/index](https://goframe.org/os/gfsnotify/index)); - - - -## 新功能 -1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://goframe.org/net/ghttp/request](https://goframe.org/net/ghttp/request)); -1. 改进`ghttp.Response`,增加`ServeFileDownload`方法,用于WebServer引导客户端下载文件([https://goframe.org/net/ghttp/response](https://goframe.org/net/ghttp/response)); -1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能; -1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装; -1. 改进`gcron`模块,增加自定义的Cron管理对象,增加`New/Start/Stop`方法; - - -## 功能改进 -1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间; -1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://goframe.org/net/ghttp/service/hook](https://goframe.org/net/ghttp/service/hook)); -1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径; -1. 改进`WebSocket`默认支持跨域请求([https://goframe.org/net/ghttp/websocket](https://goframe.org/net/ghttp/websocket)); -1. 改进`gtime.Format`支持中文; -1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题; -1. 改进`gtype.Set`方法,增加Set原子操作返回旧的变量值; -1. `gfile.ScanDir`增加支持`pattern`多个文件模式匹配,使用'`,`'符号分隔多个匹配模式; -1. `gcfg`模块增加获取配置变量为`*gvar.Var`; -1. `gstr`模块增加对中文截取方法; -1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法; -1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式; -1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://goframe.org/util/grand/index](https://goframe.org/util/grand/index)); - -## 问题修复 -1. 修复`gspath`模块在`windows`下搜索失效问题; -1. 修复`gspath`模块Search时带有indexFiles的检索问题; -1. bug fix INZS1([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1)); -1. 修复`gproc.ShellRun`在windows下的执行问题; - - - - - - -# `v1.0.898 stable` (2018-10-24) - -## 新特性 -1. `gf-orm`增加`sqlite`数据库类型支持([http://gf.johng.cn/database/orm/database](http://gf.johng.cn/database/orm/database)); -1. 增加`gkafka`模块,对kafka的客户端程序封装,支持分组消费及指定起始位置等特性,并提供简便易用的API接口([http://gf.johng.cn/database/gkafka/index](http://gf.johng.cn/database/gkafka/index)); -1. 增加go语言最新版本的`go modules`特性支持; -1. 增加`gcron`定时任务模块([http://gf.johng.cn/os/gcron/index](http://gf.johng.cn/os/gcron/index)); -1. `Web Server`增加路由注册项获取/打印特性,所有的路由注册/回调注册一览无余; -1. 模板引擎增加全局变量管理,并增加多个常用的内置函数及内置变量([http://gf.johng.cn/os/gview/funcs](http://gf.johng.cn/os/gview/funcs)); -1. `gredis`改进为单例操作方式(基于基层连接池特性),每次操作`redis`服务器时开发者无需显示调用`Close`方法执行关闭([http://gf.johng.cn/database/gredis/index](http://gf.johng.cn/database/gredis/index)); -1. `gf-orm`增加数据库操作自动`Close`特性(基于底层链接池特性),开发者无需再`defer db.Close()`,并增加`g.DB`数据库对象单例别名([http://gf.johng.cn/database/orm/linkop](http://gf.johng.cn/database/orm/linkop)); -1. 增加`gvar`通用动态变量模块([http://gf.johng.cn/container/gvar/index](http://gf.johng.cn/container/gvar/index)); -1. 数据结构容器增加`并发安全特性开启/关闭功能`,当关闭后和普通的数据结构无异,且在非并发安全模式下性能会得到提高; -1. 新增`gmlock`内存锁模块([http://gf.johng.cn/os/gmlock/index](http://gf.johng.cn/os/gmlock/index)); -1. 增加`gaes`算法模块([http://gf.johng.cn/crypto/gaes/index](http://gf.johng.cn/crypto/gaes/index)); -1. `gproc`模块增加执行`shell`命令方法([http://gf.johng.cn/os/gproc/index](http://gf.johng.cn/os/gproc/index)); -1. 新增`gfcache`模块,用于带自动缓存更新的文件内容操作(文档待完善); - -## 新功能 -1. `glog`增加链式操作方法,增加日志级别管理控制、分类管理、调试管理功能; -1. `g.View`增加分组名称设置,支持通过`g.*`对象管理器获取多个命名的单例模板引擎对象; -1. `glog`增加对文件名称格式的自定义设置,支持`gtime日期格式`; -1. `gconv`增加`Ints/Uints/Floats/Interfaces`转换方法; -1. `gjson`增加`Append`方法; -1. `gparser`增加`NewUnsafe/Append`方法; -1. `gcache`增加`GetOrSet/GetOrSetFunc/GetOrSetFuncLock`方法; -1. `gset`增加`LockFunc/RLockFunc`方法; -1. `ghttp.Response`方法完善,增加`ParseTpl/ParseTplContent/TplContent`方法,`Template`修改为`Tpl`方法; -1. `ghttp.Request`增加获取用户真实IP判断; -1. `Session`增加`Contains`方法; -1. 完善`ghtml`模块,增加多个方法; -1. `gcache`新增`Contains/SetIfNotExist`方法; -1. `gvalid`增加`Error`对象,用以管理校验错误信息; -1. `gvalid`模块增加`struct tag`的校验规则、自定义错误提示信息绑定的支持特性([http://gf.johng.cn/util/gvalid/index](http://gf.johng.cn/util/gvalid/index)); -1. `ghttp`增加输入参数与`struct`的`绑定机制`,并增加对应`params`标签支持([http://gf.johng.cn/net/ghttp/service/handler](http://gf.johng.cn/net/ghttp/service/handler)); -1. `ghttp.Request`增加服务端`BasicAuth`功能(文档待完善); -1. `gvalid`增加字段校验别名,用于自定义返回结果字段,并更新WebServer中相关使用的模块; -1. `gf-orm`链式操作增加`ForPage`方法,调整`Chunks`方法; -1. `ghttp`对象路由注册增加`Init&Shut`自动回调方法,增加重复路由注册检测功能; -1. `gfsnotify`增加默认递归`Add/Remove`特性; -1. `ghttp.Response`增加`ServiceFile`方法; -1. 其他一些新功能; - -## 功能改进 -1. 改进`ghttp.Server`配置管理; -1. 改进`gcache`底层对象继承关系,改进部分设计细节,提高性能; -1. 改进`gfpool`文件指针池,修复部分错误,提升性能,并增加基准测试代码; -1. 改进`gmap`系列并发安全map数据结构,增加多个易用性的方法; -1. 改进`gconv.Struct`对象转换功能([http://gf.johng.cn/util/gconv/index](http://gf.johng.cn/util/gconv/index)); -1. 改进`grand`随机数生成规则,提供了极高的随机数生成性能,并保证每一次调用随机方法时生成的都是不同的随机数值([http://gf.johng.cn/util/grand/index](http://gf.johng.cn/util/grand/index)); -1. 改进`gfile`文件内容操作方法,增加若干常用的文件内容读取方法; -1. 改进`gtime`模块,并增加时区转换方法; -1. 改进`COOKIE`,去掉锁机制; -1. 改进`SESSION`获取方法,新增多个类型获取方法; -1. 改进`g.DB/g.Config`单例缓存键名; -1. 改进`gtcp/gudp`超时错误判断机制; -1. 改进`gtype`底层统一修改为原子操作; -1. 改进`gvalid`对`struct`的`string`属性的默认值非必需校验; -1. 改进`gvalid`在关联规则下的非必需校验; -1. 改进`gf-orm`在调试模式下日志自动输出功能; -1. `ghttp.Server/gspath`模块静态文件检索改进; -1. 优化`ghttp.ServerConfig`配置,增加`struct/method``名称到uri`的转换规则,通过`SetNameToUri`方法进行灵活配置([http://gf.johng.cn/net/ghttp/service/object](http://gf.johng.cn/net/ghttp/service/object)); -1. 改进`*any/:name`路由匹配规则,支持不带名字的`*/:`路由规则; -1. 修改默认配置文件名称 `config.yml` -> `config.toml`([http://gf.johng.cn/os/gcfg/index](http://gf.johng.cn/os/gcfg/index)); -1. 调整服务注册的`BindControllerMethod`及`BindObjectMethod`逻辑为绑定路由到指定的方法执行; -1. 改进`garray`二分查找方法,增加安全操作处理; -1. 改进`gdb.Result/Recorde` `ToXml`方法,增加可选的`rootTag`参数; -1. 其他一些改进; - -## 问题修复 -1. 修复`ghttp.Server`在`windows`下的重启失效问题; -1. 修复`ghttp.Server`服务注册与回调注册路由重复判断问题; -1. 修复`garray`排序数组`Add`变参时的死锁问题; -1. 修复`gfsnotify`默认递归监控整个`gspath.Add`添加的目录的问题; -1. 修复`ghttp.BindParams`对`@file`文件上传标识符的转义问题; -1. 修复`ghttp.Server`日志路径丢失问题; -1. 修复`多WebServer`下的状态检测问题; -1. 修复`gvalid`模块`min/max`校验问题; -1. 修复控制器和执行对象服务注册时绑定'/'路由的问题; -1. 修复`gvalid.CheckStruct`自定义错误提示失效问题; -1. `ghttp.Server`修复`hook`与`serve`方法的路由影响,并新增跳转方法; -1. 其他一些修复; - -## 其他改动 -1. 去掉`gfile.IsExecutable`方法; -1. 目录调整,将`加密/解密`相关的包从`encoding`目录迁移到`crypto`目录下; -1. 增加`gfsnotify/gfcache`调试信息; -1. `gf-orm`允许写入的键值为`nil`时往数据库中写入`null`; -1. 统一使用`gview.Params`类型作为模板变量类型; -1. `gconv.MapToStruct`方法名称修改为`gconv.Struct`; -1. `ghttp.Server`完善重启及停止的终端提示信息; -1. 完善`gring`模块,增加`约瑟夫问题`代码作为`gring`示例程序; -1. 其他一些改动; - - - -# `v0.99.682 beta` (2018-08-07) -## 新特性 - 1、新增gdes包,用于DES加密/加密算法处理; - 2、新增gkafka包,kafka的golang客户端; - 3、新增gpool对象复用池,比较于标准库的sync.Pool更加灵活强大,可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654); - 4、完成网络通信gtcp/gudp包的重构,并进行了大量的改进工作,新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382); - 5、增加gring并发安全环,标准库container/ring包的并发安全版本,并做了易用性的封装(http://gf.johng.cn/686655); - 6、gtime包新增了自定义日期格式话的支持,格式化语法类似PHP的date语法(http://gf.johng.cn/494387); - 7、gdb增加调试模式特性,使用SetDebug方法实现,在调试模式下可以获取详细的SQL执行记录,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801); - 8、gdb增加查询缓存特性,使用Cache方法实现,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801); - 9、ghttp.Server路由功能增加字段匹配规则特性,支持如:/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766); - 10、gpage分页包增加分页URL规则生成模板特性,内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438); - 11、增加gmap.Map对象,这是gmap.InterfaceInterfaceMap的别名; - -## 新功能 - 1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置,并增加SetMaxConnLifetime方法; - 2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth); - 3、glog新增对系统换行符号的自适配调整(\n|\r\n); - 4、增加glog控制台调试模式打印开关(SetDebug); - 5、gcfg增加SetFileName方法设置默认读取的配置文件名称; - 6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法; - 7、增加gzip方法的封装(Zip/Unzip); - 8、gview增加模板变量分隔符设置方法SetDelimiters; - 9、ghttp.Response增加Writef、Writefln方法; - -## 功能改进 - 1、改进gfilepool文件指针池设计;改进gfile文本内容写入,增加指针池使用 - 2、gdb包增加调试模式特性,并支持在调试模式下获得已执行的SQL列表结果 - 3、改进gproc进程间通信机制,增加进程消息分组特性,并限定队列大小 - 4、gdb结果方法处理增加ToXml/ToJson方法 - 5、gregx包名修改为gregex - 6、改进gtime.StrToTime方法,新增对常见标准时间日期的自动转换,以及对时区的自动识别支持,并调整gconv,gvalid对该包的引用 - 7、增加对字符集转换的封装,gxml包中使用新增的字符集转换包来做处理 - 8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持 - 9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径,特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新) - 10、改进ghttp.Server静态文件检索设计,增加开发环境时的main包源码目录查找机制;改进gcfg/gview的main包源码目录查找机制 - 11、优化gcache设计,LRU特性非默认开启;优化gtype/gcache基准测试脚本;新增gregx基准测试脚本,改进设计,提升性能 - 12、gfile包增加GoRootOfBuild方法,用于获取编译时的GOROOT数值;并改进glog包中backtrace的GOROOT路径过滤处理; - 13、改进grpool代码质量,并改进对池化goroutine数量的限制设计 - 14、改进gdb.Map/List及g.Map/List的类型定义,改用别名特性以便支持原生类型输入(map/slice),并修复gdb.Model.Update方法参数处理问题 - 15、调整ghttp包示例代码目录结构,增加ghttp.Client自定义Header方法,ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值,构造成map返回 - 16、删除gcharset中的getcharset方法 - 17、去掉gmap中常用的基本数据类型转换获取方法 - 18、改进gconv.String方法,当无法使用基本类型进行字符串转换时,使用json.Marshal进行转换 - 19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct - - -## 问题修复 - 1、修正gstr.IsNumeric错误 - 2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题 - 3、修正gconv包float32->float64精度问题 - 4、修复gpage包分页计数问题 - 5、修复gdb批量数据Save错误 - 6、去掉gpool中math.MAXINT64常量的使用,以修复int64到int类型的转换错误,兼容32位系统 - 7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题 - - - - -# `v0.98.503 beta` (2018-05-21) -## 新特性 - 1、平滑重启特性( http://gf.johng.cn/625833 ); - 2、gflock文件锁模块( http://gf.johng.cn/626062 ); - 3、gproc进程管理及通信模块( http://gf.johng.cn/626063 ); - 4、gpage分页管理模块,强大的动态分页及静态分页功能,并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 ); - 5、ghttp.Server增加多端口监听特性,并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 ); - 6、增加gspath目录检索包管理工具,支持对多目录下的文件检索特性; - 7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性,路由规则增加{method}变量支持; - -## 新功能 - 1、gutil包增加MapToStruct方法,支持将map数据类型映射为struct对象; - 2、gconv - 1)、gconv包增加按照类型名称字符串进行类型转换; - 2)、gconv包新增Time/TimeDuration类型转换方法; - 3、ghttp - 1)、增加Web Server目录安全访问控制机制; - 2)、ghttp.Server增加自定义状态码回调函数注册处理; - 4、gdb - 1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法,获取查询结果记录自动转换为指定对象; - 2)、gdb增加Value/Record/Result类型,增加对Value类型的系列类型转换方法; - 3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法; - -## 功能改进 - 1、改进gredis客户端功能封装; - 2、改进grand包随机数生成性能; - 3、grand/gdb/gredis包增加benchmark性能测试脚本; - 4、改进gjson/gparser包的ToStruct方法实现; - 5、gdb :改进gdb.New获取ORM操作对象性能; - 6、gcfg :改进配置文件检索功能; - 7、gview:模板引擎增加多目录检索功能; - 8、gfile:增加源码main包目录获取方法MainPkgPath; - 9、ghttp - 1)、ghttp.Request增加请求进入和完成时间记录,并增加到默认日志内容中; - 2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参; - 10、gdb - 1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换,便于业务层数据编码处理(如json/xml); - 2)、改进gdb.Tx.GetValue返回值类型; - 3)、gdb.Model.Data参数支持更加灵活的map参数; - -## 问题修复 - 1、ghttp - 1)、修复ghttp包路由缓存问题; - 2)、修复服务注册时的控制器及执行对象方法丢失问题; - 2、gconv - 1)、修正gconv.Float64方法位大小设置问题; - 2)、修复gconv.Int64(float64(xxx))问题; - 2、gdb - 1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题; - 2)、修复gdb.Delete方法错误; - 3)、修复gdb.Model.And/Or方法; - 4)、修复gdb.Model.Where方法参数处理问题; - 3、garray:修复garray包Remove方法锁机制问题; - 4、gtype :修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误; - 5、gfsnotify:修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题; - 6、修复gvalid包验证问题:如果值为nil,并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。 - - - - - -# `v0.97.399 beta` (2018-04-23) - 1、 增加gfsnotify文件监控模块; - 2、 配置管理模块增加配置文件自动检测更新机制; - 3、 模板引擎增加对模板文件的自动检测更新机制; - 4、 改进gconv包基本类型转换功能,提高转换性能; - 5、 增加gpage分页管理包,支持动态分页、静态分页以及自定义分页样式特性; - 6、 ghttp.Request增加Exit方法,用以标记服务退出,当在服务执行前调用后,服务将不再执行; - 7、 ghttp.Response去掉WriteString方法,统一使用Write方法返回数据流,是使用灵活的参数形式; - 8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc,以便支持开发者灵活处理模板变量; - 9、 ghttp.Server增加access & error log功能,并支持开发者自定义日志处理回调函数注册; - 10、增加gredis包,支持对redis的客户端操作封装,并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护; - 11、gins单例管理器增加对单例对象配置文件的自动检测更新机制,当配置文件在外部发生变更时,自动刷新单例管理器中的单例对象; - 12、gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性; - 13、对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档; - 14、修复ISSUE: - #IISWI github.com/gogf/gf/issues/IISWI, - #IISMY github.com/gogf/gf/issues/IISMY, - 反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48); - - - diff --git a/RELEASE.2.MD b/RELEASE.2.MD deleted file mode 100644 index 3dc331d0e..000000000 --- a/RELEASE.2.MD +++ /dev/null @@ -1,824 +0,0 @@ -# `v1.13.1` (2020-06-10) - -# GoFrame - -`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 - -## 特点 - -* 模块化、松耦合设计; -* 模块丰富、开箱即用; -* 简便易用、易于维护; -* 高代码质量、高单元测试覆盖率; -* 社区活跃,大牛谦逊低调脾气好; -* 详尽的开发文档及示例; -* 完善的本地中文化支持; -* 设计为团队及企业使用; - -## 发展 - -`GoFrame`开始得比较早,`2011`年始于北京一个智能物联网平台项目,那时还没有这么多物联网的现行标准,`Go`的标准库以及生态也未如此丰富。`2017`年的时候`GF`才开始发布测试版,`2018`年`1024`程序员节日的时候才发布`v1.0`正式版,为`Go`生态发展添砖加瓦。开源以来快速迭代、发展成长,广受开发者和企业的青睐,也有许多的开发者加入了贡献行列。`GF`原本是为开发团队设计的,因此她的开发效率和可维护性做得非常好,有着很高的代码质量以及丰富的单元测试和示例,并且`GF`是目前中文化文档做的最好的`Golang`开发框架。 - -# Change Log - -1. 应多数开发者的要求,框架要求的最低`Golang`运行版本降级为了`v1.11`。 -1. 新增`GoFrame`视频教程地址: - - bilibili:https://www.bilibili.com/video/av94410029 - - 西瓜视频: https://www.ixigua.com/pseries/6809291194665796100/ -1. 将不常用的`guuid`模块迁移到 github.com/gogf/guuid 作为社区模块维护,保持`gf`主仓库的轻量级。 -1. 新增`guid`模块,用于高效轻量级的唯一字符串生成:https://goframe.org/util/guid/index - - -## `tool chain` - -1. 工具链更新:https://goframe.org/toolchain/cli -1. 新增`gf env`命令,更优雅地查看当前`Golang`环境变量信息。 -1. 新增`gf mod path`命令,用于将当前`go modules`包拷贝到`GOPATH`中,以便使用原始的`GOPATH`方式开发项目。 -1. 对现有`cli`命令进行了一些改进,提高使用体验;预编译二进制版本在部分平台下提供了`upx`压缩,使得下载的文件更小。 - -## `container` -1. `garray` - - https://goframe.org/container/garray/index - - 简化数组使用方式,支持类似于`var garray.Array`的变量定义使用方式; - - 增加`Walk`方法,用于自定义的数组元素处理方法; - - 增加`ContainsI`方法,用于大小写忽略匹配的数组元素项存在性查找; - - 完善单元测试,代码覆盖率`94%`; - - 代码改进,提高性能; - - 修复一些问题; -1. `gchan` - - 由于该封装包实际意义不是很大,因此从主框架中删除; -1. `glist` - - https://goframe.org/container/glist/index - - 简化链表使用方式,支持类似于`var glist.List`的变量定义使用方式; - - 完善单元测试,代码覆盖率`99%`; -1. `gmap` - - https://goframe.org/container/gmap/index - - 简化`Map`使用方式,支持类似于`var gmap.Map`的变量定义使用方式; - - 完善单元测试,代码覆盖率`81%`; - - 代码改进,提高性能; -1. `gset` - - https://goframe.org/container/gset/index - - 简化集合使用方式,支持类似于`var gset.Set`的变量定义使用方式; - - 增加`Walk`方法,用于自定义的集合元素处理方法; - - 完善单元测试,代码覆盖率`90%`; - - 代码改进,提高性能; -1. `gtree` - - https://goframe.org/container/gtree/index - - 简化树型使用方式,支持类似于`var gtree.BTree`的变量定义使用方式; - - 完善单元测试,代码覆盖率`90%`; -1. `gvar` - - https://goframe.org/container/gvar/index - - 完善单元测试,代码覆盖率`69%`; - - 代码组织结构调整,提高维护性; - - 代码改进,提高性能; - -## `database` - -1. `gdb` - - 增加`Transaction(f func(tx *TX) error) (err error)`接口方法,用于通过闭包实现事务封装处理:https://goframe.org/database/gdb/transaction - - 去掉不常用的`From`接口方法,改进`Table`及`Model`方法的参数为不定参数,并支持通过不定参数传递表别名:https://goframe.org/database/gdb/chaining/select - - 增加`DryRun`特性,支持空跑时只执行查询不执行写入/更新/删除操作:https://goframe.org/database/gdb/senior - - 增加`create_at`, `update_at`写入时间、更新时间字段自动填充特性:https://goframe.org/database/gdb/chaining/auto-time - - 增加`delete_at`软删除特性:https://goframe.org/database/gdb/chaining/auto-time - - 增加`Having`链式操作方法,用于`having`条件查询:https://goframe.org/database/gdb/chaining/select - - `Result`结果对象增加`Chunk`方法,用于自定义的数据分批处理:https://goframe.org/database/gdb/result - - 改进`Schema`数据库运行时切换特性; - - 改进对`pgsql`, `mssql`, `sqlite`, `oracle`数据库字段类型的支持; - - 进一步完善单元测试; - - 代码组织结构调整,提高维护性; - - 代码改进,提高性能; -1. `gredis` - - 增加`MaxActive`连接池参数默认配置为`100`,限制默认的连接数量; - - 改进`Conn`连接对象的`Do`方法,支持对`map/slice/struct`类型进行自动的`json.Marshal`处理,注意获取数据时使用`DoVar`方法获取:https://goframe.org/database/gredis/usage - - 完善单元测试,代码覆盖率`72%`; - -## `net` -1. `ghttp` - - 增加`Prefix`及`Retry`客户端链式操作方法; - - 增加客户端原始请求打印特性:https://goframe.org/net/ghttp/client/demo/dump - - 增加`ClientMaxBodySize`的服务端配置,用于限制客户端提交的`Body`大小,默认为`8MB`;在涉及到上传的Server中需要增加该配置的大小,在配置文件中指定对应的大小即可,如`ClientMaxBodySize="100MB"`:https://goframe.org/net/ghttp/config - - 改进`SessionId`生成的随机性,提高`Session`安全性:https://goframe.org/os/gsession/index - - 改进`ghttp.Server`实现了标准库的`http.Handler`接口,便于与其他第三方的服务如`Prometheus`进行代码集成; - - 其他大量的代码细节改进工作,提高性能及持久维护性; - - 完善单元测试,代码覆盖率`61%`; - -1. `gipv4` - - 增加`GetIpArray`方法,用于获取当前主机的所有IPv4地址; - - 增加`GetMacArray`及`GetMac`方法,用于获取当前主机的`MAC`地址信息; - - 修改`IntranetIP`方法名称为`GetIntranetIp`,修改`IntranetIPArray`方法名称为`GetIntranetIpArray`; - -## `encoding` -1. `gjson` - - 新增`GetMaps`获取`JSON`内部节点变量方法; - - 改进`NewWithTag`方法对`map/struct`的处理; - - 完善单元测试,代码覆盖率`77%`; -1. `gyaml` - - 升级依赖的第三方`yaml`解析包,解决了`map[interface{}]interface{}`转换问题; - -## `error` -1. `gerror` - - 新增`NewfSkip`方法,用于创建`skip`指定堆栈的错误对象; - - 放开框架所有的堆栈链路打印,展示错误时真实的链路调用详情; - -## `os` -1. `gcache` - - 增加`GetVar`方法,用于获得可以便捷转换为其他数据类型的"泛型"变量; - - 标记`Removes`方法废弃,改进`Remove`方法参数为不定参数,统一使用`Remove`方法删除单个/多个键值对; - - 完善单元测试,代码覆盖率`96%`; - -1. `genv` - - 增加`GetVar`方法,用于获得可以便捷转换为其他数据类型的"泛型"变量; - -1. `gfile` - - 改进`CopyDir/CopyFile`复制目录/文件方法; - - 新增`ScanDirFunc`方法,用于支持自定义处理回调的目录检索; - - 完善单元测试,代码覆盖率`64%`; - -1. `glog` - - 增加支持`Context`上下文变量的日志打印特性:https://goframe.org/os/glog/context - -1. `gres` - - 改进打包特性,增强生成二进制文件及Go文件的压缩比,比旧版本增加`20%`压缩率,使得编译生成的二进制文件体积更小; - - 代码结构改进,提高执行效率及可持久维护性; - -1. `gsession` - - 改进`SessionId`默认生成方法,采用`guid.S`方法生成; - - 增加`SetId`及`SetIdFunc`方法,用于自定义`SessionId`及自定义的`SessionId`生成方法; - -## `frame` -1. `g` - - 新增`g.Table`方法,用于快速创建数据库模型操作对象; - -## `i18n` -1. `gi18n` - - 新增`GetContent`方法,用于获取指定`i18n`关键字为转译内容; - - 改进代码细节,提高性能和持久可维护性; - - 完善单元测试,代码覆盖率`74%`; - -## `test` -1. `gtest` - - 增加`AssertNQ`断言方法,用于强类型的不相等判断; - -## `text` -1. `gstr` - - 增加`SubStrRune`方法,用于支持`unicode`的字符串截取; - - 增加`StrLimitRune`方法,用于支持`unicode`的字符串截断隐藏; - - 增加`LenRune`方法,用于替换`RuneLen`方法,统一方法命名风格; - - 增加`PosRune/PosIRune/PosRRune/PosRIRune`方法,用于支持`unicode`的字符串左右位置查找; - - 增加`CompareVersionGo`方法,用于`Golang`风格的版本号大小比较; - - 完善单元测试,代码覆盖率`75%`; - -## `util` -1. `gconv` - - 改进`Convert`转换方法,支持常见`map`类型的转换; - - 改进类型转换过程中异常错误的捕获,通过`error`返回; - - 其他一些细节改进; - - 完善单元测试,代码覆盖率`63%`; - -1. `grand` - - 增加`B`方法,用于获得随机的二进制数据; - - 改进代码底层实现,部分接口性能提高`50%`; - - 完善单元测试,代码覆盖率`74%`; - -1. `guid` - - 新增`guid`模块,用于高效轻量级的唯一字符串生成:https://goframe.org/util/guid/index - -1. `gutil` - - 增加`MapContains`方法,用于判断map中是否包含指定键名; - - 增加`MapDelete`方法,用于删除map中指定的键名,可以为多个键名; - - 增加`MapMerge`方法,用于合并两个map; - - 增加`MapMergeCopy`方法,用于拷贝多个map; - - 增加`MapContainsPossibleKey`方法,用于查找指定键名,忽略大小写及字符`'-'/'_'/'.'/' '`; - -1. `gvalid` - - 所有默认的错误提示改为了英文; - - 错误提示的配置改为了通过`i18n`来配置实现,以便支持国际化:https://goframe.org/util/gvalid/message - - 身份证号规则名称从`id-number`改为了`resident-id `; - - 银行卡号规则名称从`luhn`改为了`bank-card`; - - 完善单元测试,代码覆盖率`96%`; - -## Bug Fix -1. 修复`gcompress`的多文件`zip`压缩问题; -1. 修复`ghttp.Client`获取返回的过期`Cookie`的问题; -1. 修复`gres.File`对于`http.File`接口的实现细节; -1. 修复`garray.Pop*`方法的边界问题; -1. 修复`gres`中`Readdir`方法参数为`0`时报错的问题; -1. 其他一些修复:https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug - - - - -# `v1.12.1` (2020-03-31) - -大家好啊!久等啦! - -由于自从上次版本的发布以来,越来越多小伙伴加入了`GF`的大家庭,并提供了许多不错的建议和反馈,这次版本对其中大部分反馈进行了处理,包括大部分的改进建议和部分新特性,因此这次的版本发布时隔了两个多月。`GF`非常注重代码质量以及可持续维护性,这次版本也进一步对框架大部分模块的示例、注释和单元测试用例进行了完善,目前单元测试用例数量约为`1991`例,代码覆盖率为`71%`,覆盖了所有模块的绝大部分主要功能。 - -`GF`框架提供了比较常用、高质量的基础开发模块,轻量级、模块化、高性能,推荐大家阅读框架源码了解更多细节。本次发布有个别的不兼容升级,往往批量替换即可,以下`change log`比较完善,建议升级前仔细阅读。 - -本次发布即意味下一版本开发计划的开启,欢迎更多小伙伴参与开源贡献:https://github.com/gogf/gf/projects/8 - -感谢大家支持!Enjoy your `GF`! - - -# GoFrame - -`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 - -## 特点 -* 模块化、松耦合设计; -* 模块丰富,开箱即用; -* 简便易用,易于维护; -* 社区活跃,大牛谦逊低调脾气好; -* 高代码质量、高单元测试覆盖率; -* 详尽的开发文档及示例; -* 完善的本地中文化支持; -* 更适合企业及团队使用; - -## 地址 -- 官网:https://goframe.org -- 主库:https://github.com/gogf/gf -- 码云:https://gitee.com/johng/gf - -# Change Log - -从`GF v1.12`版本开始,框架要求的最低`Golang`运行版本为`v1.13`,由于`Golang`新版本都是向后兼容的,因此推荐大家更新使用`Golang`新版本:https://golang.google.cn/dl/ - -> 本次版本也新增了`Swagger`的工具及插件支持,另行独立发布。 - -## `tool chain` -1. `gen model`命令新增对`pgsql/mssql/sqlite/oracle`的模型生成支持。 -1. `gen model`命令生成模型新增公开包变量`Columns`用于获得表的字段名称。 - -## `net` -1. `ghttp` - - 注意:从该版本开始,`Server`默认关闭了平滑重启特性。开发者可以通过相应的配置选项打开。 - - 改进`Client.Get`方法,增加可选的请求参数。 - - 新增`Client`链式操作方法:`Header`, `HeaderRaw`, `Cookie`, `ContentType`, `ContentJson`, `ContentXml`, `Timeout`, `BasicAuth`, `Ctx`:https://goframe.org/net/ghttp/client/chain - - 新增`Request.GetCtx/GetCtxVar/SetCtxVar/Context`上下文变量管理方法,用于请求内部的上下文变量特性: - - 自定义变量:https://goframe.org/net/ghttp/request/custom - - 上下文变量:https://goframe.org/net/ghttp/request/context - - 新增`Request.GetUploadFile/GetUploadFiles`方法,以及`UploadFile`类型,极大简化文件上传处理逻辑:https://goframe.org/net/ghttp/client/demo/upload - - 新增`Request.GetPage`方法,用于便捷地获得分页对象: - - 基本介绍:https://goframe.org/util/gpage/index - - 动态分页:https://goframe.org/util/gpage/dynamic - - 静态分页:https://goframe.org/util/gpage/static - - Ajax分页:https://goframe.org/util/gpage/ajax - - URL模板:https://goframe.org/util/gpage/template - - 自定义分页:https://goframe.org/util/gpage/custom - - 改进`Response.Redirect*`方法,增加自定义的跳转HTTP状态码参数。 - - 改进`CORS`特性,完善跨域功能处理,并完全遵守`W3C`关于`OPTIONS`请求方法的规范约定:https://goframe.org/net/ghttp/cors - - 模板视图对象增加`Request`内置变量,用于模板获得请求参数。由于`GF`框架的模板引擎采用两级缓存设计,减少`IO`开销的同时提升了执行效率,即使增加了大量的内置变量以及内置方法,经过大规模的性能测试,性能比其他`WebServer`库相同逻辑下高出`50% - 200%`的效率。 - - 新增`Server`实验性的`Plugin`特性。 - - 改进`Server`底层路由存储、检索逻辑,优化代码,提升效率。 - - 改进分组路由注册的源码位置记录功能,当路由注册冲突时能够精准定位及提示重复路由源码位置。 - - 改进`Server`日志处理。 - - 完善单元测试。 - - -## `database` -1. `gdb` - - 代码重构改进,增加`Driver`驱动接口设计,方便开发者自定义驱动实现。新增`Register`包方法,用于开发者注册自定义的数据库类型驱动:https://goframe.org/database/gdb/driver - - 新增`GetArray`接口及实现,用于获取指定字段列的数据,构造成数组返回:https://goframe.org/database/gdb/chaining/select - - 新增`InsertIgnore`接口及实现,用于写入时忽略写入冲突,仅对`mysql`数据库类型有效:https://goframe.org/database/gdb/chaining/insert-save - - 新增`Schema`接口及实现,用于动态切换并获取指定名称的数据库对象:https://goframe.org/database/gdb/chaining/schema - - 新增`FieldsStr/FieldsExStr`模型方法,用于获取表字段,并构造成字符串返回:hhttps://goframe.org/database/gdb/chaining/fields-retrieve - - 新增`LockUpdate/LockShared`模型链式操作方法,用于实现悲观锁操作:https://goframe.org/database/gdb/chaining/lock - - 改进`Where/Data`方法对更新参数输入方式的支持,提高灵活性: - - https://goframe.org/database/gdb/chaining/select - - https://goframe.org/database/gdb/chaining/update-delete - - 查询结果对象`Result`新增`Array`方法,用于获得指定字段的数值,构造成数组返回:https://goframe.org/database/gdb/result - - 改进`OmitEmpty`方法对`Data`输入参数的过滤,当给定的`Data`参数为空时间对象时,将会被过滤。 - - 增加默认的数据库连接池参数:`MaxIdleConns=10`。 - - 其他一些改进。 - - 完善单元测试。 - -1. `gredis` - - 增加/修改默认的数据库连接池参数:`MaxIdle=10`, `IdleTimeout=10s`, `MaxConnLifetime=30s`, `Wait=true`。 - - 完善单元测试。 - -## `container` -1. 所有容器对象新增`UnmarshalValue(interface{}) error`接口方法实现,用于`gconv`转换时根据任意类型变量创建/设置对象内容,`GF`框架的所有容器对象均实现了该接口。 - -1. `garray` - - 新增`RemoveValue`方法,用于根据数值检索并删除元素项。 - - 新增`FilterNil`方法,用于遍历并删除数组中的`nil`元素项。 - - 新增`FilterEmpty`方法,用于遍历并删除数组中的空值元素项,空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。 - - 改进`Set/Fill/InsertBefore/InsertAfter`方法严谨性,将原有的链式操作返回值对象修改为`error`返回值。 - - 改进`Get/Remove/PopRand/PopLeft/PopRight/Rand`方法严谨性,增加`found`的`bool`返回值,标识当前方法是否有数据返回,当空数组或者操作越界时`found`返回值为`false`。 - - 改变`Rands`方法原有逻辑,保证按照给定大小返回随机数组。 - - 完善单元测试。 - -1. `gpool` - - 调整缓存池过期时间参数类型,旧版本为`int`类型表示毫秒,新版本为`time.Duration`类型使得时间控制更灵活。 - - 内部代码优化,性能改进。 - - 完善单元测试。 - - 完善注释。 - -## `os` -1. `glog` - - 增加`Rotation`日志滚动切分特性,新增按照文件大小或过期时间进行日志切分,并支持切分文件数量限制、对日志文件进行自动压缩、可自定义压缩级别(`1-9`)、支持对切分文件过期时间清理:https://goframe.org/os/glog/rotate - - 新增`LevelPrefixes`特性,支持对日志级别的前缀名称进行自定义:https://goframe.org/os/glog/level - - 新增`SetLevelStr`方法,并增加按照字符串进行日志级别配置的特性: - - https://goframe.org/os/glog/level - - https://goframe.org/os/glog/config - - 新增`SetDefaultLogger`包方法,用于设置全局默认的`Logger`对象。 - -1. `gres` - 1. 改进资源内容编码设计,采用新的压缩算法,减少资源文件大小,例如原本`15MB`的网站静态资源文件(`css/js/html`等),资源文件打包后约为`4MB`左右:https://goframe.org/os/gres/index - 1. 注意:该改进与旧版本无法兼容,需要重新打包原有的资源文件。 - 1. 完善单元测试。 - -1. `gcfg` - - 去掉配置对象属性的原子并发安全控制,改为普通变量类型。由于配置管理是最常用模块之一,因此确保高效的设计及方法实现。 - - 单例对象做较大调整:为方便多文件场景下的配置文件调用,简便使用并提高开发效率,因此当给定的单例名称对应的`toml`配置文件在配置目录中存在时,将自动设置该单例对象的默认配置文件为该文件。例如:`g.Cfg("redis")`获取到的单例对象将会默认去检索并设置默认的配置文件为`redis.toml`,当该文件不存在时,则使用默认的配置文件(`config.toml`):https://goframe.org/net/ghttp/config - - 完善单元测试。 - -1. `gview` - - 新增`concat`内置字符串拼接方法:https://goframe.org/os/gview/function/buildin - - 完善单元测试。 - -1. `gfile` - - 改进`SelfPath`获取当前执行文件路径方法,提高执行效率。 - - 改进`Join`文件路径连接方法,防止多余的路径连接符号。 - - 改建`GetContents`文件内容获取方法执行效率,降低内存占用。 - - 新增`StrToSize`方法,用于将大小字符串转换为字节数字,大小字符串例如`10m`, `5KB`, `1.25Gib`等。 - - 新增`ReadLines/ReadByteLines`方法,用于按行读取指定文件内容,并给定读取回调函数。 - - 完善单元测试。 - -1. `gtime` - - 改进`gtime.Time`对象实现,统一字符串打印时间格式为`Y-m-d H:i:s`,如:`2020-01-01 12:00:00`。 - -1. `gcmd` - - 命令行解析方法增加`strict`参数,用于设置当前解析是否严格解析,严格解析下如果给定了非法的选项,将会停止解析并返回错误。默认情况下为非严格解析。 - -1. `genv` - - 改进`Remove`删除环境变量键值对方法,增加对多个键值对环境变量的删除。 - -1. `gfpool` - - 改进代码实现,提高效率。 - - 完善单元测试。 - -1. `gfsnotify` - - 改进包初始化方法,当系统原因引起默认`Watcher`对象创建失败时,直接`panic`。 - -1. `gproc` - - 改进`SearchBinaryPath`方法。 - - 改进`Process.Kill`方法在`windows`及`*niux`平台下不同表现的处理。 - -## `encoding` -1. `gjson` - - 代码改进、完善注释、新增大量代码示例。 - - 文档更新: - - 基本介绍:https://goframe.org/encoding/gjson/index - - 对象创建:https://goframe.org/encoding/gjson/object - - 层级访问:https://goframe.org/encoding/gjson/pattern - - Struct转换:https://goframe.org/encoding/gjson/conversion-struct - - 动态创建修改:https://goframe.org/encoding/gjson/dataset - - 数据格式转换:https://goframe.org/encoding/gjson/conversion-format - -## `frame` -1. `g` - - 新增`IsNil`方法,用于判断当前给定的变量是否为`nil`,该方法有可能会使用到反射来实现判断。 - - 新增`IsEmpty`方法,用于判断当前给定的变量是否为`空`,该方法优先使用断言判断但有可能会使用到反射来实现判断。空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。 - - 标记废弃`SetServerGraceful`方法,转而统一交给`Server`的配置来管理。 -1. `gins` - - 改进代码结构,方便维护。 - - 改进、完善单元测试。 -1. `gmvc` - - 新增`M`类型,为`*gdb.Model`的别名简称,用于工具链自动生成模型中的`M`属性。 - -## `text` -1. `gstr` - - 新增`HasPrefix/HasSuffix`方法。 - - 新增`OctStr`方法,用于将八进制字符串转换为其对应的`unicode`字符串,例如转换为中文。常用于`gRPC`底层通信编码中。 - - 完善单元测试。 - -## `debug` -1. `gdebug` - - 改进代码结构,方便维护。 - - 新增`TestDataPath`方法,用于当前包单元测试中获得当前包中`testdata`目录的绝对路径。 - -## `util` -1. `gconv` - - 改进`String`字符串转换方法,增加对`time.Time/*time.Time/gtime.Time`类型的内置支持。 - - 改进`Map/Struct`转换方法,增加对一些特殊场景的细节处理。经过大规模的使用,大量的反馈改进,不断完善了细节。 - - 改进`Struct`转换方法,增加对`UnmarshalValue(interface{}) error`接口的支持。 - - 完善单元测试。 - -1. `grand` - - 注意:不兼容调整,原有的`Str`方法更名为`S`方法,用以获取指定长度的随机字符串、数字,并增加`symbol`参数,指定是否可以随机返回特殊可见字符。 - - 新增`Str`方法,用于从指定的字符串字符中随机获取指定长度的字符串。该方法同时支持`unicode`字符串,例如:中文:https://goframe.org/util/grand/index - - 新增`Symbols`方法,用于随机返回指定场孤独的特殊可见字符:https://goframe.org/util/grand/index - - 完善单元测试。 - -1. `gvalid` - - 长度校验规则增加对`unicode`字符串的支持,例如:中文。 - -# Bug Fix -1. 修复`Server`的视图对象配置失效问题。 -1. 修复`Server`中间件在中间件`panic`情况下,忽略`Middleware.Next`方法控制,导致鉴权中间件失效的问题。 -1. 修复`gudp.Server`在请求包大小超过`64bytes`时的断包问题,并调整默认缓冲区大小为`1024byte`,开发者可自定义缓冲区大小。 -1. 修复`gfile.MTimeMillisecond`方法返回错误的文件修改毫秒时间戳。 -1. 修复`gconv.Int64`对负数转换的支持。 -1. 其他一些修复。 -1. 详见:https://github.com/gogf/gf/issues?q=label%3Abug - - - -# `v1.11.2` (2020-01-14) - -`GF(Go Frame)` https://goframe.org 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件, 如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、 定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。 并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等, 支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。 - -`GF`有着丰富的基础模块、完善的工具链、详尽的开发文档。开源近两年以来,`GF`得到越来越多小伙伴的肯定和支持,从寂寂无名到现在被广泛应用于微服务、物联网、区块链、电商系统、银行系统等企业级的生产项目中,经历了百万级、千万级项目的考验,2019年度被码云`gitee`评选为`GVP`最有价值开源项目。`GF`正在快速地成长中,目前保持着1-2个月迭代版本的发布规律,社区活跃,欢迎加入`GF`大家庭。 - -最后,祝大家2020新年快乐,鼠年大吉! - - - -## 新特性 - -1. 新年新气象,官网文档大量更新:https://goframe.org/index -1. `GF`工具链更新:https://goframe.org/toolchain/cli - - 新增`gf run`热编译运行命令; - - 新增`gf docker` Docker镜像编译命令; - - 新增`gf gen model` 强大的模型自动生成命令; - - `gf build`命令增加对配置文件配置支持; - - 大量命令行工具改进工作; - - 新增自动代理设置特性; -1. 数据库`ORM`新特性: - - 增加`prefix`数据表前缀支持:https://goframe.org/database/gdb/config - - 新增`Schema`数据库对象并改进数据库切换特性:https://goframe.org/database/gdb/chaining/schema - - 新增`WherePri`方法,用于自动识别主键的条件方法:https://goframe.org/database/gdb/chaining/select - - 文档及示例大量更新,覆盖95%以上的功能特性; - - - -## 功能改进 - -### `container` -1. `garray` - - 新增`New*ArrayRange`方法,用于初始化创建指定数值范围的数组。 - - 新增`Iterator*`方法,用于数组项元素回调遍历。 - - 完善单元测试。 -1. `gvar` - - 改进`MapStrStr`、`MapStrStrDeep`方法实现。 - -### `net` -1. `ghttp` - - 改进HTTP客户端,增加对提交参数的自动`Content-Type`识别功能。 - - `Request`对象增加`Parse`方法,用于快捷的对象转换即参数校验。 - - `Request.GetPost*`方法全部标记为`deprecated`,统一客户端参数提交方式为`QueryString`, `Form`, `Body`。 - - 去掉`Response`模板解析时的`Get`/`Post`内置变量,新增`Query`, `Form`, `Request`内置变量:https://goframe.org/net/ghttp/response/template - - 改进`Response.WriteJson*`及`Response.WriteXml*`方法,增加对`string`, `[]byte`类型参数的支持。 - - `Server`新增`GetRouterArray`方法,用于向应用层暴露并获取`Server`的路由列表。 - - `Server`新增`Use`方法,该方法为`BindMiddlewareDefault`的别名,用以全局中间件的注册。 - - `Server`新增`RouteOverWrite`配置项,用于控制是否在注册路由冲突时自动覆盖,默认关闭并提示。 - - `Server`新增`Graceful`配置项,用于在单服务场景下控制平滑重启特性的开启/关闭,默认开启。 - - 完善单元测试。 -1. `gtcp` - - 改进简单协议下的数据包发送接收功能。 - - 将连接池默认的缓存过期时间`30`秒修改为`10`秒。 - - 完善单元测试。 - -### `database` -1. `gdb` - - 新增`As`数据表别名方法。 - - 改进数据表、字段的安全字符自动识别添加功能。 - - 新增`DB`数据库对象切换方法。 - - 新增`TX`链式操作事务支持方法。 - - 完善单元测试。 -### `os` -1. `gcfg` - - 新增`GetMapStrStr`方法。 -1. `gcmd` - - 增加参数解析的`strict`严格参数,默认严格解析,不存在指定参数/选项名称时则报错返回。 -1. `genv` - - 改进`Remove`方法支持多个环境变量的删除。 -1. `gfile` - - 改进`TempDir`临时目录获取方法,在`*nix`系统下默认为`/tmp`目录。 - - 新增`ReadLines`, `ReadByteLines`方法,用以按行回调读取文件内容。 - - 新增`Copy*`方法,用以文件/目录的拷贝,支持递归。 - - 新增`Replace*`方法,用以目录下的文件内容替换,支持递归。 - - 改进`Scan*`方法,用以检索并返回指定目录下的所有文件/目录,支持文件模式指定,支持递归。 - - 完善单元测试。 -1. `gproc` - - 改进命令行运行方法。 - - 改进`Shell`命令文件检索逻辑。 - - 改进实验性的进程间通信设计。 -1. `gtime` - - 将包方法以及`Time`对象的时间戳方法`Second`, `Millisecond`, `Microsecond`, `Nanosecond`标记为废除, - 并新增`Timestamp`, `TimestampMilli`, `TimestampMicro`, `TimestampNano`替换。 - - 需要注意的是以上修改可能和老版本存在兼容性问题。 - -1. `gview` - - 解析功能、缓存设计改进。 - - 新增`encode`, `decode`HTML编码/解码模板函数。 - - 新增`concat`字符串拼接模板函数。 - - 新增`dump`模板函数,功能类似于`g.Dump`方法。 - - 新增`AutoEncode`配置项,用于自动转码输出的`HTML`内容,常用于防止`XSS`,默认关闭。需要注意的是该特性并不会影响`include`内置函数: https://goframe.org/os/gview/xss - - 单元测试完善。 - -### `crypto` -1. `gmd5` - - 增加`MustEncrypt`, `MustEncryptBytes`, `MustEncryptString`, `MustEncryptFile`方法。 -1. `gsha1` - - 增加`MustEncryptFile`方法 - -### `encoding` -1. `gbase64` - - 新增`MustEncodeFile`, `MustEncodeFileToString`, `MustDecode`, `MustDecodeToString`方法。 -1. `gjson`/`gparser` - - 新增`GetMapStrStr`方法。 - - 新增`Must*`方法,用于指定数据格式的转换失败时产生`panic`错误,而不会返回`error`参数。 - -### `util` -1. `gconv` - - 改进`Convert`方法增加对`[]int32`, `[]int64`, `[]uint`, `[]uint32`, `[]uint64`, `[]float32`, `[]float64`数据类型的转换支持。 - - 改进`String`字符串转换方法对指针参数的支持。 - - 改进`Map*` Map转换方法的代码结构及性能。 - - 新增`Floats`, `Float32s`, `Float64s`对`[]float32`, `[]float64`类型转换方法。 - - 新增`Ints`, `Int32s`, `Int64s`对`[]int`, `[]int32`, `[]int64`类型转换方法。 - - 新增`Uints`, `Uint32s`, `Uint64s`对`[]uint`, `[]uint32`, `[]uint64`类型转换方法。 - - 完善单元测试。 - - -### `frame` -1. `gins` - - 所有的单例对象在获取失败时产生`panic`错误。 - -## Bug Fix -1. 增加对常见错误路由格式例如`/user//index`的兼容支持。 -1. 修复`gtcp`/`gudp`在数据接收时的间隔时间单位问题。 -1. 修复`gfile`/`gspath`/`gfsnotify`包对文件的存在性判断不严谨问题。 -1. 修复`gproc.Kill`方法在`windows`系统下的运行阻塞问题。 -1. 修复`gstr.TrimLeftStr`/`gstr.TrimRightStr`在被替换字符串长度小于替换字符串长度时的数组溢出问题。 - - - -# `v1.10.0` (2019-12-05) - -各位`gfer`久等了,较上一次发布时间过去已有两个多月了,这段时间`GF`也在不断地迭代改进,细节比较多,拟了个大概,以下是`release log`。 - -另外,`GoFrame`也参加了2019最受欢迎中国开源软件评选投票,明天就结束了,欢迎为`GF`投票啊:https://www.oschina.net/project/top_cn_2019 网页可以投一票,微信也可以投一票。 - -## 新特性 - -1. `Web Server`新特性: - - 改进中间件及分组路由实现:https://goframe.org/net/ghttp/router/middleware - - 增加文件配置管理特性:https://goframe.org/net/ghttp/config - - 改进参数获取:https://goframe.org/net/ghttp/request - - 改进文件上传:https://goframe.org/net/ghttp/client/demo/upload -1. `Session`增加内置的多种`Storage`实现: - - 基本介绍:https://goframe.org/os/gsession/index - - 文件存储:https://goframe.org/os/gsession/file - - 内存存储:https://goframe.org/os/gsession/memory - - `Redis`存储:https://goframe.org/os/gsession/redis -1. 增加日志组件单例对象,并优化配置管理: - - https://goframe.org/frame/g/index - - https://goframe.org/os/glog/config -1. 常用的`container`容器增加`JSON`数据格式的`Marshal`/`UnMarshal`接口实现: - - https://goframe.org/container/gmap/index - - https://goframe.org/container/garray/index - - https://goframe.org/container/gset/index - - https://goframe.org/container/gvar/index - - https://goframe.org/container/gtype/index - - https://goframe.org/container/glist/index - - https://goframe.org/container/gvar/index -1. 新增`guuid`模块,用于通用的`UUID`生成:https://goframe.org/util/guuid/index - -## 功能改进 - -### `net` -1. `ghttp` - - 改进请求流程处理性能; - - `Server`增加对`Logger`日志对象的配置; - - `Server`开放了`GetRouterMap`方法,用于获得当前服务的路由列表信息,使得开发者可以更方便地实现自定义权限管理; - - `Server`配置管理优化; - - `Client`客户端对象进行了大量的改进工作; - - `Client`客户端对象增加多文件上传功能; - - `Request`对象增加`GetError`方法,用于获取当前处理错误; - - `Request`对象增加独立的视图对象及视图变量绑定功能,使得每个请求可以独立视图管理,也可以通过中间件切换请求对象的视图对象。默认情况下该功能关闭,视图解析时使用的是`Server`对象的视图对象; - - 改建`Response`对象的`CORS`功能; - - 增加`Response.WriteTplDefault`方法,用于解析并返回默认的模板内容; - - 增加更多的单元测试用例; - - 其他改进; -1. `gipv4`/`gipv6` - - 一些改进工作; -1. `gtcp`/`gudp` - - 一些改进工作; - -### `database` -1. `gdb` - - 大量细节改进工作; - - 去掉查询数据为空时的`sql.ErrNoRows`错误返回,保留`Struct`/`Structs`/`Scan`方法在操作数据为空的该错误返回; - - 调试模式开启时,输出的SQL语句改进为完整的带参数的SQL,仅作参考; - - `Where`方法增加对`gmap`数据类型支持,包括顺序性的`ListMap`/`TreeMap`等等; - - 查询缓存方法`Cache`的缓存时间参数类型修改为`time.Duration`; - - 修改`Record`/`Result`的数据类型转换方法名称,原有的转换方法标记为`deprecated`; - - `Record`/`Result`查询结果类型增加`IsEmpty`方法,用于判断结果集是否为空; - - `Record`类型增加`GMap`方法,用于将查询记录转换为`gmap`类型; - - 增加`Option`/`OptionOmitEmpty`方法,用于输入参数过滤,包括`Data`参数及`Where`参数:https://goframe.org/database/gdb/empty - - 增加字段排除方法`FieldsEx`:https://goframe.org/database/gdb/senior - - 增加日志功能特性:https://goframe.org/database/gdb/senior - - 改进数据库配置管理:https://goframe.org/database/gdb/config - - 增加大量单元测试; -1. `gredis` - - 返回数据类型转换改进:https://github.com/gogf/gf/issues/415 - - 完善单元测试; - - 其他改进; - -### `os` -1. `gcache` - - 需要注意了:缓存的有效时间参数从`interface{}`类型调整为了`time.Duration`类型,因此不再兼容之前的`int`参数类型,以保证更好的性能; -1. `gfcache` - - 由于`gcache`组件的缓存时间参数类型的变更,因此该组件的时间参数也变更为了`time.Duration`类型; -1. `gcfg` - - 增加`Available`方法,用以判断配置是否有效; -1. `gfile` - - 增加`Chdir`方法,用于工作目录切换; -1. `gtime` - - 增加`JSON`数据格式的`Marshal`/`UnMarshal`接口实现; - -### `container` -1. `gmap` - - 增加`MapStrAny`方法,用于常见`map`类型的转换; - - 增加`MapCopy`方法,用于底层`map`数据复制; - - 增加`FilterEmpty`方法,用于`map`空值过滤; - - 增加`Pop`/`Pops`方法,用于随机返回`map`中的数据项(并删除); - - 增加`Replace`方法,用于给定的`map`数据覆盖底层`map`数据项; - - 完善单元测试; - - 其他改进; -1. `garray` - - 增加`Interfaces`转换方法,返回`[]interface{}`类型; - - 对排序数组增加`SetComparator`方法用户自定义修改比较器; - - 完善单元测试; - - 其他改进; -1. `glist` - - 增加`NewFrom`方法,基于给定的`[]interface{}`变量创建链表; - - 增加`Join`方法,用于将链表项使用给定字符串连接为字符串返回; - - 完善单元测试; - - 其他改进; -1. `gset` - - 增加`AddIfNotExistFunc`/`AddIfNotExistFuncLock`方法; - - 完善单元测试; - - 其他改进; -1. `gtree` - - 增加`Replace`方法,用于更新现有树的数据项; - - 其他改进; -1. `gtype` - - 一些细节改进工作,不一一列出; - - 完善基准测试、单元测试; -1. `gvar` - - 增加`Ints`/`Uints`类型转换方法; - - 其他改进; - -### `crypto` -1. `gmd5` - - 小细节改进; -1. `gsha1` - - 小细节改进; - -### `text` -1. `gstr` - - 改进`SplitAndTrim`方法,将`SplitAndTrimSpace`标记为`deprecated`; - - 增加`TrimStr`方法; - - 完善单元测试; - - 其他改进; - -### `debug` - -1. `gdebug` - - 增加`CallerFileLineShort`/`FuncPath`/`FuncName`方法; - - 其他改进; - -### `encoding` - -1. `gbase64` - - 增加`EncodeToString`/`EncodeFile`/`EncodeFileToString`/`DecodeToString`方法; - - 完善单元测试; -1. `gjson` - - 完善单元测试; - -### `frame` - -1. `g`/`gins` - - https://goframe.org/frame/g/index - - 增加`CreateVar`方法; - - 完善单元测试; - - 其他改进; - -### `util` - -1. `gconv` - - 改进优化部分类型转换方法性能; - - 增加`Uints`/`SliceUint`类型转换方法; - - 增加`UnsafeStrToBytes`/`UnsafeBytesToStr`高性能的类型转换方法; - - 增加对`MapStrAny`接口方法的支持,用于常见`map`类型的转换; - - 其他改进; -1. `gvalid` - - 改进对中国身份证号的识别校验功能; - - 增加`luhn`银行卡号的校验功能; -1. `grand` - - 一些性能改进工作; - - -## Bug Fix -1. 解决`WebSocket`关闭时的`hijacked`报错问题:https://github.com/gogf/gf/issues/381 -1. 解决静态文件服务时大文件的内存占用问题; -1. 修复前置`Nginx`后默认情况下的`Cookie`域名设置问题; -1. 修复`gconv.Struct`在属性为`[]struct`并且输入属性参数为空时的转换失败问题:https://github.com/gogf/gf/issues/405 -1. 其他一些修复; - - - -# `v1.9.3` (2019-09-24) - -该版本实际为`v2.0`的大版本发布,为避免`go module`机制严格要求`v2`版本以上需要修改`import`并加上`v2`后缀,因此使用了`v1.9`版本进行发布。 - -## 新特性 - -1. 新增`gf`命令行开发辅助工具:https://goframe.org/toolchain/cli -1. 新增`gres`资源管理器模块:https://goframe.org/os/gres/index -1. 重构`Session`功能,新增`gsession`模块,`WebServer`默认使用文件存储`Session`:https://goframe.org/net/ghttp/session -1. `WebServer`新增中间件特性,并保留原有的HOOK设计,两者都可实现请求拦截、预处理等等特性:https://goframe.org/net/ghttp/router/middleware -1. 新增`gi18n`国际化管理模块:https://goframe.org/i18n/gi18n/index -1. 新增`gini`模块:https://goframe.org/encoding/gini/index -1. `WebServer`新增更便捷的层级路由注册方式:https://goframe.org/net/ghttp/group/level -1. `gcmd`命令行参数解析模块重构,增加`Parser`解析对象:https://goframe.org/os/gcmd/index -1. 新增`gdebug`模块,用于堆栈信息获取/打印:https://goframe.org/debug/gdebug/index - - -## 重大调整 -1. 去掉`1.x`版本中已经被标记为`deprecated`的方法; -1. 调整`container`分类的容器模块,将默认并发安全参数调整为默认非并发安全; -1. 目录调整: - - 去掉`third`目录,统一使用`go module`管理包依赖; - - 将原有`g`目录中的模块移出到框架主目录,原有的`g`模块移动到`frame/g`目录; - - 将原有`geg`示例代码目录名称修改为`.example`; - - - -## 功能改进 - -1. `ghttp` - - 改进`Request`参数解析方式:https://goframe.org/net/ghttp/request - - 改进跨域请求功能,新增`Origin`设置及校验功能:https://goframe.org/net/ghttp/cors - - `Cookie`及`Session`的`TTL`配置数据类型修改为`time.Duration`; - - 新增允许同时通过`Header/Cookie`传递`SessionId`; - - 新增`ConfigFromMap/SetConfigWithMap`方法,支持通过`map`参数设置WebServer; - - 改进默认的`CORS`配置,增加对常见`Header`参数的默认支持; - - 新增`IsExitError`方法,用于开发者自定义处理`recover`错误处理时,过滤框架本身自定义的非异常错误; - - 新增`SetSessionStorage`配置方法,用于开发者自定义`Session`存储; - - `ghttp.Request`新增更多的参数获取方法; -1. `gdb` - - 增加对SQL中部分字段的自动转义(`Quote`)功能; - - 增加对方法操作以及链式操作中的`slice`参数的支持; - - 增加`SetLogger`方法用于开发者自定义数据库的日志打印; - - 增加`Master/Slave`方法,开发者可自主选择数据库操作执行的主从节点; - - 增加对`mssql/pgsql/oracle`的单元测试; - - 在`debug`模式支持完整带参数整合的SQL语句调试打印; - - 增加了更多的功能方法; -1. `glog` - - 新增`Default`方法用于获取默认的`Logger`对象; - - 新增`StackWithFilter`方法用于自定义堆栈打印过滤; - - 增加了更多的功能方法; -1. `gfile` - - 部分方法名称调整:`Get/PutBinContents`修改为`Get/PutBytes`; - - 增加`ScanDirFile`方法,用于仅检索文件目录,支持递归检索; - - 增加了更多的功能方法; -1. `gview` - - 新增`SetI18n`方法用于设置视图对象自定义的`gi18n`国际化对象; - - 新增对`gres`资源管理器的内置支持; -1. `gcompress` - - 增加`zip`算法的文件/目录的压缩/解压方法; - - 文件/目录压缩参数支持多路径; -1. `gconv` - - 改进对`[]byte`数据类型参数的支持; - - 新增`Unsafe`转换方法,开发者可在特定场景下使用,提高转换效率; - - 新增`MapDeep/StructDeep/StructsDeep`方法,支持递归`struct`转换; -1. `gjson/gparser` - - 改进类型自动识别功能; - - 新增`LoadJson/LoadXml/LoadToml/LoadYaml/LoadIni`方法用于自定义的数据类型内容加载; - - 增加了更多的功能方法; -1. `gerror` - - 改进错误堆栈获取逻辑; - - 增加了更多的功能方法; -1. `gmap/garray/gset/glist/gvar` - - 改进并发安全基准测试脚本; - - 修改`garray.StringArray`为`garray.StrArray`; - - 增加了更多的功能方法; -1. `gdes` - - 规范化修改方法名称; -1. `gstr` - - 增加`Camel/Snake`相关命名转换方法; - - 增加了更多的功能方法; -1. `genv` - - 增加了更多的功能方法; - - -## Bug Fix -1. 修复`gvalid`校验`struct`时的`tag`自定义错误失效的问题; -1. 修复`gcfg`配置管理模块在特定情况下的内容类型自动识别失败问题; -1. 修复`gqueue`在用户主动关闭队列时的并发安全问题; -1. 修复`session`在开发者设置的`TTL`过大时的整型变量溢出问题; \ No newline at end of file diff --git a/TODO.Deprecated.MD b/TODO.Deprecated.MD deleted file mode 100644 index 44b4f00f5..000000000 --- a/TODO.Deprecated.MD +++ /dev/null @@ -1,135 +0,0 @@ -> This markdown is deprecated and will be removed in future. All TODO features are staged in the issue: https://github.com/gogf/gf/issues - -# ON THE WAY -1. 增加图形验证码支持,至少支持数字和英文字母; -1. Cookie&Session数据池化处理; -1. ghttp.Client增加proxy特性; -1. gtime增加对时区转换的封装,并简化失去转换时对类似+80500时区的支持; -1. orm增加sqlite对Save方法的支持(去掉触发器语句); -1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps); -1. ghttp增加返回数据压缩机制; -1. ghttp.Server增加proxy功能特性,本地proxy和远程proxy,本地即将路由规则映射;远程即反向代理; -1. gjson对大json数据的解析效率问题; -1. ghttp增加route name特性,并同时支持backend和template(提供内置函数)引用,可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上; -1. gvalid校验支持当第一个规则失败后便不再校验后续的规则,最好做成链式操作; -1. ghttp.Request增加对输入参数的自动HtmlEncode机制; -1. 常量命名风格根据golint进行修改; -1. 开放rwmutex包,并将gjson的互斥锁使用自定义的mutex替换; -1. 文档完善: - - gconv struct tag、 - - 控制器及执行对象注册的Init&Shut方法、 - - ghttp.Response&ServeFile、gfcache、gproc shell执行、 - - ghttp Server&Client basic auth、 - - glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、 -1. 服务注册域名增加对泛域名的支持; -1. 项目参考: - - https://github.com/namreg/godown - - https://github.com/Masterminds/sprig -1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进 -1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题,并完善文档(参考:https://www.cnblogs.com/kex1n/p/6502002.html); -1. 路由增加不区分大小写得匹配方式; -1. 改进WebServer获取POST参数处理逻辑,当提交非form数据时,例如json数据,针对某些方法可以直接解析; -1. WebServer增加可选择的路由覆盖配置,默认情况下不覆盖; -1. 增加jumplist的数据结构容器; -1. DelayQueue/PriorityQueue; -1. 权限管理模块; -1. 从ghttp中剥离SESSION功能构成单独的模块gsession; -1. 改进gproc进程间通信处理逻辑,提高稳定性,以应对进程间大批量的数据发送/接收; -1. 添加Save/Replace/BatchSave/BatchReplace方法对sqlite数据库的支持; -1. 添加sqlite数据库的单元测试用例; -1. gredis增加cluster支持; -1. gset.Add/Remove/Contains方法增加批量操作支持; -1. gmlock增加手动清理机制:当内存锁不再使用时,由调用端决定是否清理内存锁; -1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进; -1. grpool增加支持阻塞添加任务接口; -1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用; -1. 增加g.Table快捷方法以方便操作数据表,但是得考虑后续模型操作设计,特别是脚手架的模型管理; - - - - - - -# DONE -1. Cookie设置中文失效问题; -1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节); -1. grpool性能压测结果变慢的问题; -1. ghttp的热重启的本地进程端口监听,在不使用该特性时默认关闭掉; -1. gtcp增加对TLS加密通信的支持; -1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式; -1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现; -1. 改进ghttp分组路由中对hook的支持方式,以便格式与BindHookHandler统一; -1. 使用gconv将slice映射到struct属性上,例如redis hscan的结果集; -1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; -2. ghttp.Server请求执行中增加服务退出的方法,不再执行后续操作; -3. ghttp.Response对象完善并改进数据返回方法(Write/WriteString); -4. ghttp.Server请求执行中增加服务退出的方法,不再执行后续操作; -5. 增加fsnotify包支持; -6. 改进gcfg和gview的文件自动更新机制; -7. 将模板变量进行暴露,以便应用端可以进行灵活控制; -8. 跟踪第三方mxj包的issue问题:https://github.com/clbanning/mxj/issues/48; -9. gdb Where方法参数的改进,研究是否可以将string参数类型修改为interface{}; -10. gpage分页控制功能; -11. https支持; -12. ghttp.Server日志中增加请求时间和返回时间,以便计算执行时间差; -13. 由于去掉了gdb的单例模式,并且将gins的部分对象封装迁移到了g包中,需要同时梳理文档,完善修改; -14. 在代码中增加https与http同时开启使用的示例代码,这块大家问得比较多; -15. ghttp.Server多个事件之间通过ghttp.Request.Param自定义参数传参; -16. 研究是否增加配置文件目录检索功能,特别是如何友好改进开发环境的配置文件默认目录问题; -17. 增加ghttp.Server不同状态码的自定义处理方法; -18. ghttp.Server平滑重启方案; -19. 完善gconv类型转换功能,增加time.Time/time.Duration类型转换,并增加benchmark测试脚本 -20. 当二进制参数为nil时,gjson.LoadContent并将gjson.Json对象ToMap时会报错; -21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name,现有的控制器及执行对象注册很难友好支持这种动态形式; -22. 当前gpage分页包的输出标签不支持li,大多数CSS框架都是li+a标签模式,需要提供可更加灵活的定制化功能实现; -23. 平滑重启机制改进,以便于开发阶段调试; -24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/gogf/gf/issues/6; -25. gredis增加redis密码支持; -26. 改进ghttp.Server平滑重启机制,当新进程接管服务后,再使用进程间通信方式通知父进程销毁; -27. gproc进程间通信增加分组特性,不同的进程间可以通过进程ID以及分组名称发送/获取进程消息; -28. ORM增加获取被执行的sql语句的方法; -29. gdb增加查询缓存特性; -30. gpage分页增加对自定义后缀的支持,如:2.html, 2.php等等; -31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性; -32. 增加文件缓存包,可根据fsnotify机制进行缓存更新; -33. *any/:name路由匹配路由改进支持不带名字的*/:路由规则; -34. ghttp静态文件服务改进(特别是403返回状态的修改); -35. map转struct增加对tag的支持; -36. gcache检查在i386下的int64->int转换问题; -37. ghttp获取参数支持直接转struct功能; -38. gfsnotify增加对于目录的监控; -39. 检查windows下的平滑重启失效问题; -40. ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制); -41. 解决glog串日志情况; -42. glog增加对日志文件名称的生成规则设定,支持时间格式规则; -43. ghttp日志增加客户端IP信息; -44. 完善gform配置管理说明,g.DB/Database和gdb.New的区别; -1. 完善配置管理章节,说明默认的配置文件更改方式; -1. 服务注册时判断方法定义满足规范时才执行绑定,否则提示WARN信息; -1. `gfsnotify`增加添加监听文件时的监听ID返回,以便调用端删除监听时只删除自己添加的监听,而不影响其他对该同一文件的监听回调; -1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案; -1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`; -1. WebServer事件回调允许对同一个路由规则绑定多个事件回调; -1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题; -1. 改进gfpool在文件指针变化时的更新; -1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理; -1. gform对于MySQL字段类型为datetime类型的时区问题分析; -1. 改进证书打开失败时的WebServer错误提示,前置HOOK校验后关闭后续的HOOK逻辑执行; -1. 目前WebServer的HOOK是按照优先级执行的,需要增加覆盖特性; -1. 更新跨域请求CORS相关功能文档; -1. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现; -1. gcfg包目前允许添加重复的目录路径,需要在SetPath/AddPath时判断重复性,不能添加重复的路径; -1. gdb执行数据写入时,如果参数为struct/[]struct,自动映射与表字段对应关系,不再使用gconv标签标识; -1. gdb的Data方法支持struct参数传入; -1. gfcache依旧使用gcache作为缓存控制对象,不要使用gmap; -1. 增加对ghttp路由注册的{.struct}/{.method}单元测试; -1. gconv针对struct的转换增加json tag支持,gconv.Map默认也支持json tag, 完善开发文档; -1. 增加SO_REUSEPORT的支持; -1. gkafka这个包比较重,未来从框架中剥离出来; -1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php -1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php -1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性; -1. ghttp.Client自动Close机制; -1. ghttp路由功能增加分组路由特性; -1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射; -1. gview中的template标签失效问题; \ No newline at end of file diff --git a/container/garray/garray_normal_any.go b/container/garray/garray_normal_any.go index 2ec882819..4c41cc2d4 100644 --- a/container/garray/garray_normal_any.go +++ b/container/garray/garray_normal_any.go @@ -453,7 +453,7 @@ func (a *Array) Clone() (newArray *Array) { array := make([]interface{}, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewArrayFrom(array, !a.mu.IsSafe()) + return NewArrayFrom(array, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/garray/garray_normal_int.go b/container/garray/garray_normal_int.go index ef13c8851..69d4c157a 100644 --- a/container/garray/garray_normal_int.go +++ b/container/garray/garray_normal_int.go @@ -469,7 +469,7 @@ func (a *IntArray) Clone() (newArray *IntArray) { array := make([]int, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewIntArrayFrom(array, !a.mu.IsSafe()) + return NewIntArrayFrom(array, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/garray/garray_normal_str.go b/container/garray/garray_normal_str.go index 4d3f27b8b..889a19a09 100644 --- a/container/garray/garray_normal_str.go +++ b/container/garray/garray_normal_str.go @@ -457,7 +457,7 @@ func (a *StrArray) Clone() (newArray *StrArray) { array := make([]string, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewStrArrayFrom(array, !a.mu.IsSafe()) + return NewStrArrayFrom(array, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go index d056464d7..3122be023 100644 --- a/container/garray/garray_sorted_any.go +++ b/container/garray/garray_sorted_any.go @@ -446,7 +446,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result mid := 0 cmp := -2 for min <= max { - mid = (min + max) / 2 + mid = min + int((max-min)/2) cmp = a.getComparator()(value, a.array[mid]) switch { case cmp < 0: @@ -499,7 +499,7 @@ func (a *SortedArray) Clone() (newArray *SortedArray) { array := make([]interface{}, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe()) + return NewSortedArrayFrom(array, a.comparator, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go index 28d3e97f9..fa44ed534 100644 --- a/container/garray/garray_sorted_int.go +++ b/container/garray/garray_sorted_int.go @@ -443,7 +443,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) mid := 0 cmp := -2 for min <= max { - mid = (min + max) / 2 + mid = min + int((max-min)/2) cmp = a.getComparator()(value, a.array[mid]) switch { case cmp < 0: @@ -496,7 +496,7 @@ func (a *SortedIntArray) Clone() (newArray *SortedIntArray) { array := make([]int, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewSortedIntArrayFrom(array, !a.mu.IsSafe()) + return NewSortedIntArrayFrom(array, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go index 649732347..762017d72 100644 --- a/container/garray/garray_sorted_str.go +++ b/container/garray/garray_sorted_str.go @@ -445,7 +445,7 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i mid := 0 cmp := -2 for min <= max { - mid = (min + max) / 2 + mid = min + int((max-min)/2) cmp = a.getComparator()(value, a.array[mid]) switch { case cmp < 0: @@ -498,7 +498,7 @@ func (a *SortedStrArray) Clone() (newArray *SortedStrArray) { array := make([]string, len(a.array)) copy(array, a.array) a.mu.RUnlock() - return NewSortedStrArrayFrom(array, !a.mu.IsSafe()) + return NewSortedStrArrayFrom(array, a.mu.IsSafe()) } // Clear deletes all items of current array. diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index c2006af43..28859ed45 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) { } } +// String returns the map as a string. +func (m *AnyAnyMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *AnyAnyMap) MarshalJSON() ([]byte, error) { return json.Marshal(gconv.Map(m.Map())) diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 5cbbafe63..52df0cebc 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -56,7 +56,7 @@ func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) { // Clone returns a new hash map with copy of current map data. func (m *IntAnyMap) Clone() *IntAnyMap { - return NewIntAnyMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) { } } +// String returns the map as a string. +func (m *IntAnyMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *IntAnyMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index d37e8d453..4d546661e 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -54,7 +54,7 @@ func (m *IntIntMap) Iterator(f func(k int, v int) bool) { // Clone returns a new hash map with copy of current map data. func (m *IntIntMap) Clone() *IntIntMap { - return NewIntIntMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) { } } +// String returns the map as a string. +func (m *IntIntMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *IntIntMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index 9d131054e..8d1f1edfd 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -54,7 +54,7 @@ func (m *IntStrMap) Iterator(f func(k int, v string) bool) { // Clone returns a new hash map with copy of current map data. func (m *IntStrMap) Clone() *IntStrMap { - return NewIntStrMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) { } } +// String returns the map as a string. +func (m *IntStrMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *IntStrMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index f5f502eff..423923e2c 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -56,7 +56,7 @@ func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) { // Clone returns a new hash map with copy of current map data. func (m *StrAnyMap) Clone() *StrAnyMap { - return NewStrAnyMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) { } } +// String returns the map as a string. +func (m *StrAnyMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *StrAnyMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index 1716b055f..58f98f017 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -54,7 +54,7 @@ func (m *StrIntMap) Iterator(f func(k string, v int) bool) { // Clone returns a new hash map with copy of current map data. func (m *StrIntMap) Clone() *StrIntMap { - return NewStrIntMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) { } } +// String returns the map as a string. +func (m *StrIntMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *StrIntMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index c6922eae6..5565b28a7 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -55,7 +55,7 @@ func (m *StrStrMap) Iterator(f func(k string, v string) bool) { // Clone returns a new hash map with copy of current map data. func (m *StrStrMap) Clone() *StrStrMap { - return NewStrStrMapFrom(m.MapCopy(), !m.mu.IsSafe()) + return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe()) } // Map returns the underlying data map. @@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) { } } +// String returns the map as a string. +func (m *StrStrMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *StrStrMap) MarshalJSON() ([]byte, error) { m.mu.RLock() diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index 407e5decb..823cd7a54 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) { }) } +// String returns the map as a string. +func (m *ListMap) String() string { + b, _ := m.MarshalJSON() + return gconv.UnsafeBytesToStr(b) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m *ListMap) MarshalJSON() ([]byte, error) { return json.Marshal(gconv.Map(m.Map())) diff --git a/container/gpool/gpool.go b/container/gpool/gpool.go index 3248331e3..e7a59905d 100644 --- a/container/gpool/gpool.go +++ b/container/gpool/gpool.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -19,18 +19,10 @@ import ( // Pool is an Object-Reusable Pool. type Pool struct { - // Available/idle items list. - list *glist.List - - // Whether the pool is closed. - closed *gtype.Bool - - // Time To Live for pool items. - TTL time.Duration - - // Callback function to create pool item. - NewFunc func() (interface{}, error) - + list *glist.List // Available/idle items list. + closed *gtype.Bool // Whether the pool is closed. + TTL time.Duration // Time To Live for pool items. + NewFunc func() (interface{}, error) // Callback function to create pool item. // ExpireFunc is the for expired items destruction. // This function needs to be defined when the pool items // need to perform additional destruction operations. @@ -40,8 +32,8 @@ type Pool struct { // Pool item. type poolItem struct { - expire int64 // Expire timestamp in milliseconds. - value interface{} // Item value. + value interface{} // Item value. + expireAt int64 // Expire timestamp in milliseconds. } // Creation function for object. @@ -80,11 +72,11 @@ func (p *Pool) Put(value interface{}) error { value: value, } if p.TTL == 0 { - item.expire = 0 + item.expireAt = 0 } else { // As for Golang version < 1.13, there's no method Milliseconds for time.Duration. // So we need calculate the milliseconds using its nanoseconds value. - item.expire = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000 + item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000 } p.list.PushBack(item) return nil @@ -112,8 +104,11 @@ func (p *Pool) Get() (interface{}, error) { for !p.closed.Val() { if r := p.list.PopFront(); r != nil { f := r.(*poolItem) - if f.expire == 0 || f.expire > gtime.TimestampMilli() { + if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() { return f.value, nil + } else if p.ExpireFunc != nil { + // TODO: move expire function calling asynchronously from `Get` operation. + p.ExpireFunc(f.value) } } else { break @@ -169,9 +164,9 @@ func (p *Pool) checkExpireItems() { } if r := p.list.PopFront(); r != nil { item := r.(*poolItem) - latestExpire = item.expire + latestExpire = item.expireAt // TODO improve the auto-expiration mechanism of the pool. - if item.expire > timestampMilli { + if item.expireAt > timestampMilli { p.list.PushFront(item) break } diff --git a/container/gtree/gtree_avltree.go b/container/gtree/gtree_avltree.go index 028e19850..eb8e72d05 100644 --- a/container/gtree/gtree_avltree.go +++ b/container/gtree/gtree_avltree.go @@ -56,7 +56,7 @@ func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{ // Clone returns a new tree with a copy of current tree. func (tree *AVLTree) Clone() *AVLTree { - newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe()) + newTree := NewAVLTree(tree.comparator, tree.mu.IsSafe()) newTree.Sets(tree.Map()) return newTree } @@ -441,9 +441,9 @@ func (tree *AVLTree) MapStrAny() map[string]interface{} { func (tree *AVLTree) Flip(comparator ...func(v1, v2 interface{}) int) { t := (*AVLTree)(nil) if len(comparator) > 0 { - t = NewAVLTree(comparator[0], !tree.mu.IsSafe()) + t = NewAVLTree(comparator[0], tree.mu.IsSafe()) } else { - t = NewAVLTree(tree.comparator, !tree.mu.IsSafe()) + t = NewAVLTree(tree.comparator, tree.mu.IsSafe()) } tree.IteratorAsc(func(key, value interface{}) bool { t.put(value, key, nil, &t.root) diff --git a/container/gtree/gtree_btree.go b/container/gtree/gtree_btree.go index 926898add..a8c1bbb3b 100644 --- a/container/gtree/gtree_btree.go +++ b/container/gtree/gtree_btree.go @@ -68,7 +68,7 @@ func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[inter // Clone returns a new tree with a copy of current tree. func (tree *BTree) Clone() *BTree { - newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe()) + newTree := NewBTree(tree.m, tree.comparator, tree.mu.IsSafe()) newTree.Sets(tree.Map()) return newTree } @@ -620,7 +620,7 @@ func (tree *BTree) middle() int { func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) { low, mid, high := 0, 0, len(node.Entries)-1 for low <= high { - mid = (high + low) / 2 + mid = low + int((high-low)/2) compare := tree.getComparator()(key, node.Entries[mid].Key) switch { case compare > 0: diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index c464889b4..f73c9e568 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -83,7 +83,7 @@ func (tree *RedBlackTree) SetComparator(comparator func(a, b interface{}) int) { // Clone returns a new tree with a copy of current tree. func (tree *RedBlackTree) Clone() *RedBlackTree { - newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe()) + newTree := NewRedBlackTree(tree.comparator, tree.mu.IsSafe()) newTree.Sets(tree.Map()) return newTree } @@ -656,9 +656,9 @@ func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 interface{}) int) { t := (*RedBlackTree)(nil) if len(comparator) > 0 { - t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe()) + t = NewRedBlackTree(comparator[0], tree.mu.IsSafe()) } else { - t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe()) + t = NewRedBlackTree(tree.comparator, tree.mu.IsSafe()) } tree.IteratorAsc(func(key, value interface{}) bool { t.doSet(value, key) diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index e9bc40dc0..3ee988702 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -1,4 +1,4 @@ -// Copyright 2018-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -11,8 +11,6 @@ import ( "github.com/gogf/gf/internal/json" "time" - "github.com/gogf/gf/internal/empty" - "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" @@ -88,16 +86,6 @@ func (v *Var) Interface() interface{} { return v.Val() } -// IsNil checks whether is nil. -func (v *Var) IsNil() bool { - return v.Val() == nil -} - -// IsEmpty checks whether is empty. -func (v *Var) IsEmpty() bool { - return empty.IsEmpty(v.Val()) -} - // Bytes converts and returns as []byte. func (v *Var) Bytes() []byte { return gconv.Bytes(v.Val()) diff --git a/container/gvar/gvar_is.go b/container/gvar/gvar_is.go new file mode 100644 index 000000000..396b4e40e --- /dev/null +++ b/container/gvar/gvar_is.go @@ -0,0 +1,98 @@ +// Copyright GoFrame 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 gvar + +import ( + "github.com/gogf/gf/internal/empty" + "reflect" +) + +// IsNil checks whether is nil. +func (v *Var) IsNil() bool { + return v.Val() == nil +} + +// IsEmpty checks whether is empty. +func (v *Var) IsEmpty() bool { + return empty.IsEmpty(v.Val()) +} + +// IsInt checks whether is type of int. +func (v *Var) IsInt() bool { + switch v.Val().(type) { + case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64: + return true + } + return false +} + +// IsUint checks whether is type of uint. +func (v *Var) IsUint() bool { + switch v.Val().(type) { + case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64: + return true + } + return false +} + +// IsFloat checks whether is type of float. +func (v *Var) IsFloat() bool { + switch v.Val().(type) { + case float32, *float32, float64, *float64: + return true + } + return false +} + +// IsSlice checks whether is type of slice. +func (v *Var) IsSlice() bool { + var ( + reflectValue = reflect.ValueOf(v.Val()) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + return true + } + return false +} + +// IsMap checks whether is type of map. +func (v *Var) IsMap() bool { + var ( + reflectValue = reflect.ValueOf(v.Val()) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Map: + return true + } + return false +} + +// IsStruct checks whether is type of struct. +func (v *Var) IsStruct() bool { + var ( + reflectValue = reflect.ValueOf(v.Val()) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Struct: + return true + } + return false +} diff --git a/container/gvar/gvar_struct.go b/container/gvar/gvar_struct.go index 6ae985279..d215e4df1 100644 --- a/container/gvar/gvar_struct.go +++ b/container/gvar/gvar_struct.go @@ -20,6 +20,7 @@ func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error { // Struct maps value of to recursively. // The parameter should be a pointer to a struct instance. // The parameter is used to specify the key-to-attribute mapping rules. +// Deprecated, use Struct instead. func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error { return gconv.StructDeep(v.Val(), pointer, mapping...) } @@ -30,6 +31,7 @@ func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error { } // StructsDeep converts and returns as given struct slice recursively. +// Deprecated, use Struct instead. func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error { return gconv.StructsDeep(v.Val(), pointer, mapping...) } diff --git a/container/gvar/gvar_z_unit_is_test.go b/container/gvar/gvar_z_unit_is_test.go new file mode 100644 index 000000000..c1656e044 --- /dev/null +++ b/container/gvar/gvar_z_unit_is_test.go @@ -0,0 +1,170 @@ +// Copyright GoFrame 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 gvar_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func TestVar_IsNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsNil(), false) + t.Assert(g.NewVar(nil).IsNil(), true) + t.Assert(g.NewVar(g.Map{}).IsNil(), false) + t.Assert(g.NewVar(g.Slice{}).IsNil(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsNil(), false) + t.Assert(g.NewVar(0.1).IsNil(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsNil(), false) + t.Assert(g.NewVar(g.Slice{0}).IsNil(), false) + }) +} + +func TestVar_IsEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsEmpty(), true) + t.Assert(g.NewVar(nil).IsEmpty(), true) + t.Assert(g.NewVar(g.Map{}).IsEmpty(), true) + t.Assert(g.NewVar(g.Slice{}).IsEmpty(), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsEmpty(), false) + t.Assert(g.NewVar(0.1).IsEmpty(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsEmpty(), false) + t.Assert(g.NewVar(g.Slice{0}).IsEmpty(), false) + }) +} + +func TestVar_IsInt(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsInt(), true) + t.Assert(g.NewVar(nil).IsInt(), false) + t.Assert(g.NewVar(g.Map{}).IsInt(), false) + t.Assert(g.NewVar(g.Slice{}).IsInt(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsInt(), true) + t.Assert(g.NewVar(-1).IsInt(), true) + t.Assert(g.NewVar(0.1).IsInt(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsInt(), false) + t.Assert(g.NewVar(g.Slice{0}).IsInt(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(int8(1)).IsInt(), true) + t.Assert(g.NewVar(uint8(1)).IsInt(), false) + }) +} + +func TestVar_IsUint(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsUint(), false) + t.Assert(g.NewVar(nil).IsUint(), false) + t.Assert(g.NewVar(g.Map{}).IsUint(), false) + t.Assert(g.NewVar(g.Slice{}).IsUint(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsUint(), false) + t.Assert(g.NewVar(-1).IsUint(), false) + t.Assert(g.NewVar(0.1).IsUint(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsUint(), false) + t.Assert(g.NewVar(g.Slice{0}).IsUint(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(int8(1)).IsUint(), false) + t.Assert(g.NewVar(uint8(1)).IsUint(), true) + }) +} + +func TestVar_IsFloat(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsFloat(), false) + t.Assert(g.NewVar(nil).IsFloat(), false) + t.Assert(g.NewVar(g.Map{}).IsFloat(), false) + t.Assert(g.NewVar(g.Slice{}).IsFloat(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsFloat(), false) + t.Assert(g.NewVar(-1).IsFloat(), false) + t.Assert(g.NewVar(0.1).IsFloat(), true) + t.Assert(g.NewVar(float64(1)).IsFloat(), true) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsFloat(), false) + t.Assert(g.NewVar(g.Slice{0}).IsFloat(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(int8(1)).IsFloat(), false) + t.Assert(g.NewVar(uint8(1)).IsFloat(), false) + }) +} + +func TestVar_IsSlice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsSlice(), false) + t.Assert(g.NewVar(nil).IsSlice(), false) + t.Assert(g.NewVar(g.Map{}).IsSlice(), false) + t.Assert(g.NewVar(g.Slice{}).IsSlice(), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsSlice(), false) + t.Assert(g.NewVar(-1).IsSlice(), false) + t.Assert(g.NewVar(0.1).IsSlice(), false) + t.Assert(g.NewVar(float64(1)).IsSlice(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsSlice(), false) + t.Assert(g.NewVar(g.Slice{0}).IsSlice(), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(int8(1)).IsSlice(), false) + t.Assert(g.NewVar(uint8(1)).IsSlice(), false) + }) +} + +func TestVar_IsMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsMap(), false) + t.Assert(g.NewVar(nil).IsMap(), false) + t.Assert(g.NewVar(g.Map{}).IsMap(), true) + t.Assert(g.NewVar(g.Slice{}).IsMap(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsMap(), false) + t.Assert(g.NewVar(-1).IsMap(), false) + t.Assert(g.NewVar(0.1).IsMap(), false) + t.Assert(g.NewVar(float64(1)).IsMap(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsMap(), true) + t.Assert(g.NewVar(g.Slice{0}).IsMap(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(int8(1)).IsMap(), false) + t.Assert(g.NewVar(uint8(1)).IsMap(), false) + }) +} + +func TestVar_IsStruct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(0).IsStruct(), false) + t.Assert(g.NewVar(nil).IsStruct(), false) + t.Assert(g.NewVar(g.Map{}).IsStruct(), false) + t.Assert(g.NewVar(g.Slice{}).IsStruct(), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(g.NewVar(1).IsStruct(), false) + t.Assert(g.NewVar(-1).IsStruct(), false) + t.Assert(g.NewVar(0.1).IsStruct(), false) + t.Assert(g.NewVar(float64(1)).IsStruct(), false) + t.Assert(g.NewVar(g.Map{"k": "v"}).IsStruct(), false) + t.Assert(g.NewVar(g.Slice{0}).IsStruct(), false) + }) + gtest.C(t, func(t *gtest.T) { + a := &struct { + }{} + t.Assert(g.NewVar(a).IsStruct(), true) + t.Assert(g.NewVar(*a).IsStruct(), true) + t.Assert(g.NewVar(&a).IsStruct(), true) + }) +} diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 1814c19be..41846facd 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -8,12 +8,15 @@ package gdb import ( + "context" "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gcmd" + "time" + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/internal/intlog" - "time" "github.com/gogf/gf/os/glog" @@ -40,6 +43,12 @@ type DB interface { // Note that it is not recommended using the this function manually. Open(config *ConfigNode) (*sql.DB, error) + // Ctx is a chaining function, which creates and returns a new DB that is a shallow copy + // of current DB object and with given context in it. + // Note that this returned DB object can be used only once, so do not assign it to + // a global or package variable for long using. + Ctx(ctx context.Context) DB + // =========================================================================== // Query APIs. // =========================================================================== @@ -126,6 +135,7 @@ type DB interface { GetDryRun() bool SetLogger(logger *glog.Logger) GetLogger() *glog.Logger + GetConfig() *ConfigNode SetMaxIdleConnCount(n int) SetMaxOpenConnCount(n int) SetMaxConnLifetime(d time.Duration) @@ -134,6 +144,7 @@ type DB interface { // Utility methods. // =========================================================================== + GetCtx() context.Context GetChars() (charLeft string, charRight string) GetMaster(schema ...string) (*sql.DB, error) GetSlave(schema ...string) (*sql.DB, error) @@ -142,6 +153,7 @@ type DB interface { QuotePrefixTableName(table string) string Tables(schema ...string) (tables []string, err error) TableFields(table string, schema ...string) (map[string]*TableField, error) + HasTable(name string) (bool, error) // HandleSqlBeforeCommit is a hook function, which deals with the sql string before // it's committed to underlying driver. The parameter specifies the current @@ -150,27 +162,24 @@ type DB interface { HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) // =========================================================================== - // Internal methods. + // Internal methods, for internal usage purpose, you do not need consider it. // =========================================================================== - filterFields(schema, table string, data map[string]interface{}) map[string]interface{} - convertValue(fieldValue []byte, fieldType string) interface{} - rowsToResult(rows *sql.Rows) (Result, error) + mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) + convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} + convertRowsToResult(rows *sql.Rows) (Result, error) } // Core is the base struct for database management. type Core struct { - DB DB // DB interface object. - group string // Configuration group name. - debug *gtype.Bool // Enable debug mode for the database. - cache *gcache.Cache // Cache manager. - schema *gtype.String // Custom schema for this object. - dryrun *gtype.Bool // Dry run. - prefix string // Table prefix. - logger *glog.Logger // Logger. - maxIdleConnCount int // Max idle connection count. - maxOpenConnCount int // Max open connection count. - maxConnLifetime time.Duration // Max TTL for a connection. + DB DB // DB interface object. + group string // Configuration group name. + debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. + cache *gcache.Cache // Cache manager, SQL result cache only. + schema *gtype.String // Custom schema for this object. + logger *glog.Logger // Logger. + config *ConfigNode // Current config node. + ctx context.Context // Context for chaining operation only. } // Driver is the interface for integrating sql drivers into package gdb. @@ -187,6 +196,7 @@ type Sql struct { Error error // Execution result. Start int64 // Start execution timestamp in milliseconds. End int64 // End execution timestamp in milliseconds. + Group string // Group is the group name of the configuration that the sql is executed from. } // TableField is the struct for table field. @@ -208,32 +218,30 @@ type Link interface { Prepare(sql string) (*sql.Stmt, error) } +// Counter is the type for update count. +type Counter struct { + Field string + Value float64 +} + type ( - // Value is the field value type. - Value = *gvar.Var - - // Record is the row record of the table. - Record map[string]Value - - // Result is the row record array. - Result []Record - - // Map is alias of map[string]interface{}, - // which is the most common usage map type. - Map = map[string]interface{} - - // List is type of map array. - List = []Map + Raw string // Raw is a raw sql that will not be treated as argument but as a direct sql part. + Value = *gvar.Var // Value is the field value type. + Record map[string]Value // Record is the row record of the table. + Result []Record // Result is the row record array. + Map = map[string]interface{} // Map is alias of map[string]interface{}, which is the most common usage map type. + List = []Map // List is type of map array. ) const ( - gINSERT_OPTION_DEFAULT = 0 - gINSERT_OPTION_REPLACE = 1 - gINSERT_OPTION_SAVE = 2 - gINSERT_OPTION_IGNORE = 3 - gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save - gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool. - gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds. + insertOptionDefault = 0 + insertOptionReplace = 1 + insertOptionSave = 2 + insertOptionIgnore = 3 + defaultBatchNumber = 10 // Per count for batch insert/replace/save. + defaultMaxIdleConnCount = 10 // Max idle connection count in pool. + defaultMaxOpenConnCount = 100 // Max open connection count in pool. + defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds. ) var ( @@ -258,9 +266,21 @@ var ( // regularFieldNameRegPattern is the regular expression pattern for a string // which is a regular field name of table. - regularFieldNameRegPattern = `^[\w\.\-]+$` + regularFieldNameRegPattern = `^[\w\.\-\_]+$` + + // internalCache is the memory cache for internal usage. + internalCache = gcache.New() + + // allDryRun sets dry-run feature for all database connections. + // It is commonly used for command options for convenience. + allDryRun = false ) +func init() { + // allDryRun is initialized from environment or command options. + allDryRun = gcmd.GetWithEnv("gf.gdb.dryrun", false).Bool() +} + // Register registers custom database driver to gdb. func Register(name string, driver Driver) error { driverMap[name] = driver @@ -269,30 +289,27 @@ func Register(name string, driver Driver) error { // New creates and returns an ORM object with global configurations. // The parameter specifies the configuration group name, -// which is DEFAULT_GROUP_NAME in default. -func New(name ...string) (db DB, err error) { - group := configs.group - if len(name) > 0 && name[0] != "" { - group = name[0] +// which is DefaultGroupName in default. +func New(group ...string) (db DB, err error) { + groupName := configs.group + if len(group) > 0 && group[0] != "" { + groupName = group[0] } configs.RLock() defer configs.RUnlock() if len(configs.config) < 1 { - return nil, errors.New("empty database configuration") + return nil, gerror.New("empty database configuration") } - if _, ok := configs.config[group]; ok { - if node, err := getConfigNodeByGroup(group, true); err == nil { + if _, ok := configs.config[groupName]; ok { + if node, err := getConfigNodeByGroup(groupName, true); err == nil { c := &Core{ - group: group, - debug: gtype.NewBool(), - cache: gcache.New(), - schema: gtype.NewString(), - dryrun: gtype.NewBool(), - logger: glog.New(), - prefix: node.Prefix, - maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT, - maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure. + group: groupName, + debug: gtype.NewBool(), + cache: gcache.New(), + schema: gtype.NewString(), + logger: glog.New(), + config: node, } if v, ok := driverMap[node.Type]; ok { c.DB, err = v.New(c, node) @@ -301,19 +318,19 @@ func New(name ...string) (db DB, err error) { } return c.DB, nil } else { - return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type)) + return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type)) } } else { return nil, err } } else { - return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group)) + return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName)) } } // Instance returns an instance for DB operations. // The parameter specifies the configuration group name, -// which is DEFAULT_GROUP_NAME in default. +// which is DefaultGroupName in default. func Instance(name ...string) (db DB, err error) { group := configs.group if len(name) > 0 && name[0] != "" { @@ -347,7 +364,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { } } if len(masterList) < 1 { - return nil, errors.New("at least one master node configuration's need to make sense") + return nil, gerror.New("at least one master node configuration's need to make sense") } if len(slaveList) < 1 { slaveList = masterList @@ -358,7 +375,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { return getConfigNodeByWeight(slaveList), nil } } else { - return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group)) + return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group)) } } @@ -425,30 +442,34 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error node = &n } // Cache the underlying connection pool object by node. - v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} { + v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) { sqlDb, err = c.DB.Open(node) if err != nil { intlog.Printf("DB open failed: %v, %+v", err, node) - return nil + return nil, err } - if c.maxIdleConnCount > 0 { - sqlDb.SetMaxIdleConns(c.maxIdleConnCount) - } else if node.MaxIdleConnCount > 0 { - sqlDb.SetMaxIdleConns(node.MaxIdleConnCount) + if c.config.MaxIdleConnCount > 0 { + sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount) + } else { + sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount) } - - if c.maxOpenConnCount > 0 { - sqlDb.SetMaxOpenConns(c.maxOpenConnCount) - } else if node.MaxOpenConnCount > 0 { - sqlDb.SetMaxOpenConns(node.MaxOpenConnCount) + if c.config.MaxOpenConnCount > 0 { + sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount) + } else { + sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount) } - - if c.maxConnLifetime > 0 { - sqlDb.SetConnMaxLifetime(c.maxConnLifetime * time.Second) - } else if node.MaxConnLifetime > 0 { - sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second) + if c.config.MaxConnLifetime > 0 { + // Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc. + // Or else it is configured just using number, which means value in seconds. + if c.config.MaxConnLifetime > time.Second { + sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime) + } else { + sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second) + } + } else { + sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime) } - return sqlDb + return sqlDb, nil }, 0) if v != nil && sqlDb == nil { sqlDb = v.(*sql.DB) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 372a6f073..14cab14f1 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -8,19 +8,51 @@ package gdb import ( + "context" "database/sql" - "errors" "fmt" - "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/text/gstr" "reflect" "strings" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" ) +// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy +// of current DB object and with given context in it. +// Note that this returned DB object can be used only once, so do not assign it to +// a global or package variable for long using. +func (c *Core) Ctx(ctx context.Context) DB { + if ctx == nil { + return c.DB + } + var ( + err error + newCore = &Core{} + configNode = c.DB.GetConfig() + ) + *newCore = *c + newCore.ctx = ctx + newCore.DB, err = driverMap[configNode.Type].New(newCore, configNode) + // Seldom error, just log it. + if err != nil { + c.DB.GetLogger().Ctx(ctx).Error(err) + } + return newCore.DB +} + +// GetCtx returns the context for current DB. +// Note that it might be nil. +func (c *Core) GetCtx() context.Context { + return c.ctx +} + // Master creates and returns a connection from master node if master-slave configured. // It returns the default connection if master-slave not configured. func (c *Core) Master() (*sql.DB, error) { @@ -59,6 +91,7 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro Error: err, Start: mTime1, End: mTime2, + Group: c.DB.GetGroup(), } c.writeSqlToLogger(s) } else { @@ -102,6 +135,7 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re Error: err, Start: mTime1, End: mTime2, + Group: c.DB.GetGroup(), } c.writeSqlToLogger(s) } else { @@ -160,7 +194,7 @@ func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Resu return nil, err } defer rows.Close() - return c.DB.rowsToResult(rows) + return c.DB.convertRowsToResult(rows) } // GetOne queries and returns one record from database. @@ -307,6 +341,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) { return err } defer func() { + if err == nil { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + } if err != nil { if e := tx.Rollback(); e != nil { err = e @@ -331,7 +370,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) { // // The parameter specifies the batch operation count when given data is slice. func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...) + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).Insert() + } + return c.Model(table).Data(data).Insert() } // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. @@ -344,7 +386,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, // // The parameter specifies the batch operation count when given data is slice. func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore() + } + return c.Model(table).Data(data).InsertIgnore() } // Replace does "REPLACE INTO ..." statement for the table. @@ -360,7 +405,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R // If given data is type of slice, it then does batch replacing, and the optional parameter // specifies the batch operation count. func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).Replace() + } + return c.Model(table).Data(data).Replace() } // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. @@ -375,11 +423,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result // If given data is type of slice, it then does batch saving, and the optional parameter // specifies the batch operation count. func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).Save() + } + return c.Model(table).Data(data).Save() } // doInsert inserts or updates data for given table. -// +// This function is usually used for custom interface definition, you do not need call it manually. // The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) @@ -407,13 +458,19 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b switch reflectKind { case reflect.Slice, reflect.Array: return c.DB.DoBatchInsert(link, table, data, option, batch...) - case reflect.Map, reflect.Struct: - dataMap = DataToMapDeep(data) + case reflect.Struct: + if _, ok := data.(apiInterfaces); ok { + return c.DB.DoBatchInsert(link, table, data, option, batch...) + } else { + dataMap = ConvertDataForTableRecord(data) + } + case reflect.Map: + dataMap = ConvertDataForTableRecord(data) default: - return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind)) + return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind)) } if len(dataMap) == 0 { - return nil, errors.New("data cannot be empty") + return nil, gerror.New("data cannot be empty") } var ( charL, charR = c.DB.GetChars() @@ -422,14 +479,18 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b ) for k, v := range dataMap { fields = append(fields, charL+k+charR) - values = append(values, "?") - params = append(params, v) + if s, ok := v.(Raw); ok { + values = append(values, gconv.String(s)) + } else { + values = append(values, "?") + params = append(params, v) + } } - if option == gINSERT_OPTION_SAVE { + if option == insertOptionSave { for k, _ := range dataMap { // If it's SAVE operation, // do not automatically update the creating time. - if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) { + if c.isSoftCreatedFiledName(k) { continue } if len(updateStr) > 0 { @@ -462,45 +523,58 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b // BatchInsert batch inserts data. // The parameter must be type of slice of map or struct. func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...) + if len(batch) > 0 { + return c.Model(table).Data(list).Batch(batch[0]).Insert() + } + return c.Model(table).Data(list).Insert() } -// BatchInsert batch inserts data with ignore option. +// BatchInsertIgnore batch inserts data with ignore option. // The parameter must be type of slice of map or struct. func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore() + } + return c.Model(table).Data(list).InsertIgnore() } // BatchReplace batch replaces data. // The parameter must be type of slice of map or struct. func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(list).Batch(batch[0]).Replace() + } + return c.Model(table).Data(list).Replace() } // BatchSave batch replaces data. // The parameter must be type of slice of map or struct. func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) { - return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...) + if len(batch) > 0 { + return c.Model(table).Data(list).Batch(batch[0]).Save() + } + return c.Model(table).Data(list).Save() } // DoBatchInsert batch inserts/replaces/saves data. +// This function is usually used for custom interface definition, you do not need call it manually. func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { table = c.DB.QuotePrefixTableName(table) var ( - keys []string - values []string - params []interface{} - listMap List + keys []string // Field names. + values []string // Value holder string array, like: (?,?,?) + params []interface{} // Values that will be committed to underlying database driver. + listMap List // The data list that passed from caller. ) - switch v := list.(type) { + switch value := list.(type) { case Result: - listMap = v.List() + listMap = value.List() case Record: - listMap = List{v.Map()} + listMap = List{value.Map()} case List: - listMap = v + listMap = value case Map: - listMap = List{v} + listMap = List{value} default: var ( rv = reflect.ValueOf(list) @@ -515,16 +589,29 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i case reflect.Slice, reflect.Array: listMap = make(List, rv.Len()) for i := 0; i < rv.Len(); i++ { - listMap[i] = DataToMapDeep(rv.Index(i).Interface()) + listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) + } + case reflect.Map: + listMap = List{ConvertDataForTableRecord(value)} + case reflect.Struct: + if v, ok := value.(apiInterfaces); ok { + var ( + array = v.Interfaces() + list = make(List, len(array)) + ) + for i := 0; i < len(array); i++ { + list[i] = ConvertDataForTableRecord(array[i]) + } + listMap = list + } else { + listMap = List{ConvertDataForTableRecord(value)} } - case reflect.Map, reflect.Struct: - listMap = List{DataToMapDeep(v)} default: - return result, errors.New(fmt.Sprint("unsupported list type:", kind)) + return result, gerror.New(fmt.Sprint("unsupported list type:", kind)) } } if len(listMap) < 1 { - return result, errors.New("data list cannot be empty") + return result, gerror.New("data list cannot be empty") } if link == nil { if link, err = c.DB.Master(); err != nil { @@ -532,25 +619,22 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i } } // Handle the field names and place holders. - holders := []string(nil) for k, _ := range listMap[0] { keys = append(keys, k) - holders = append(holders, "?") } // Prepare the batch result pointer. var ( - charL, charR = c.DB.GetChars() - batchResult = new(SqlResult) - keysStr = charL + strings.Join(keys, charR+","+charL) + charR - valueHolderStr = "(" + strings.Join(holders, ",") + ")" - operation = GetInsertOperationByOption(option) - updateStr = "" + charL, charR = c.DB.GetChars() + batchResult = new(SqlResult) + keysStr = charL + strings.Join(keys, charR+","+charL) + charR + operation = GetInsertOperationByOption(option) + updateStr = "" ) - if option == gINSERT_OPTION_SAVE { + if option == insertOptionSave { for _, k := range keys { // If it's SAVE operation, // do not automatically update the creating time. - if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) { + if c.isSoftCreatedFiledName(k) { continue } if len(updateStr) > 0 { @@ -564,27 +648,34 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i } updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr) } - batchNum := gDEFAULT_BATCH_NUM + batchNum := defaultBatchNumber if len(batch) > 0 && batch[0] > 0 { batchNum = batch[0] } - listMapLen := len(listMap) + var ( + listMapLen = len(listMap) + valueHolder = make([]string, 0) + ) for i := 0; i < listMapLen; i++ { + values = values[:0] // Note that the map type is unordered, // so it should use slice+key to retrieve the value. for _, k := range keys { - params = append(params, listMap[i][k]) + if s, ok := listMap[i][k].(Raw); ok { + values = append(values, gconv.String(s)) + } else { + values = append(values, "?") + params = append(params, listMap[i][k]) + } } - values = append(values, valueHolderStr) + valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")") if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) { r, err := c.DB.DoExec( link, fmt.Sprintf( "%s INTO %s(%s) VALUES%s %s", - operation, - table, - keysStr, - strings.Join(values, ","), + operation, table, keysStr, + gstr.Join(valueHolder, ","), updateStr, ), params..., @@ -599,7 +690,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i batchResult.affected += n } params = params[:0] - values = values[:0] + valueHolder = valueHolder[:0] } } return batchResult, nil @@ -620,15 +711,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i // "age IN(?,?)", 18, 50 // User{ Id : 1, UserName : "john"} func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { - newWhere, newArgs := formatWhere(c.DB, condition, args, false) - if newWhere != "" { - newWhere = " WHERE " + newWhere - } - return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...) + return c.Model(table).Data(data).Where(condition, args...).Update() } // doUpdate does "UPDATE ... " statement for the table. -// Also see Update. +// This function is usually used for custom interface definition, you do not need call it manually. func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) { table = c.DB.QuotePrefixTableName(table) var ( @@ -647,18 +734,38 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str case reflect.Map, reflect.Struct: var ( fields []string - dataMap = DataToMapDeep(data) + dataMap = ConvertDataForTableRecord(data) ) for k, v := range dataMap { - fields = append(fields, c.DB.QuoteWord(k)+"=?") - params = append(params, v) + switch value := v.(type) { + case *Counter: + if value.Value != 0 { + column := c.DB.QuoteWord(value.Field) + fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) + params = append(params, value.Value) + } + case Counter: + if value.Value != 0 { + column := c.DB.QuoteWord(value.Field) + fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) + params = append(params, value.Value) + } + default: + if s, ok := v.(Raw); ok { + fields = append(fields, c.DB.QuoteWord(k)+"="+gconv.String(s)) + } else { + fields = append(fields, c.DB.QuoteWord(k)+"=?") + params = append(params, v) + } + + } } updates = strings.Join(fields, ",") default: updates = gconv.String(data) } if len(updates) == 0 { - return nil, errors.New("data cannot be empty") + return nil, gerror.New("data cannot be empty") } if len(params) > 0 { args = append(params, args...) @@ -688,15 +795,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str // "age IN(?,?)", 18, 50 // User{ Id : 1, UserName : "john"} func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) { - newWhere, newArgs := formatWhere(c.DB, condition, args, false) - if newWhere != "" { - newWhere = " WHERE " + newWhere - } - return c.DB.DoDelete(nil, table, newWhere, newArgs...) + return c.Model(table).Where(condition, args...).Delete() } // DoDelete does "DELETE FROM ... " statement for the table. -// Also see Delete. +// This function is usually used for custom interface definition, you do not need call it manually. func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { if link == nil { if link, err = c.DB.Master(); err != nil { @@ -707,8 +810,8 @@ func (c *Core) DoDelete(link Link, table string, condition string, args ...inter return c.DB.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) } -// rowsToResult converts underlying data record type sql.Rows to Result type. -func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) { +// convertRowsToResult converts underlying data record type sql.Rows to Result type. +func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) { if !rows.Next() { return nil, nil } @@ -724,7 +827,7 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) { columnNames[k] = v.Name() } var ( - values = make([]sql.RawBytes, len(columnNames)) + values = make([]interface{}, len(columnNames)) records = make(Result, 0) scanArgs = make([]interface{}, len(values)) ) @@ -735,19 +838,12 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) { if err := rows.Scan(scanArgs...); err != nil { return records, err } - // Creates a new row object. row := make(Record) - // Note that the internal looping variable is type of []byte, - // which points to the same memory address. So it should do a copy. for i, value := range values { if value == nil { row[columnNames[i]] = gvar.New(nil) } else { - // As sql.RawBytes is type of slice, - // it should do a copy of it. - v := make([]byte, len(value)) - copy(v, value) - row[columnNames[i]] = gvar.New(c.DB.convertValue(v, columnTypes[i])) + row[columnNames[i]] = gvar.New(c.DB.convertFieldValueToLocalValue(value, columnTypes[i])) } } records = append(records, row) @@ -768,13 +864,46 @@ func (c *Core) MarshalJSON() ([]byte, error) { } // writeSqlToLogger outputs the sql object to logger. -// It is enabled when configuration "debug" is true. +// It is enabled only if configuration "debug" is true. func (c *Core) writeSqlToLogger(v *Sql) { - s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format) + s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format) if v.Error != nil { s += "\nError: " + v.Error.Error() - c.logger.Error(s) + c.logger.Ctx(c.DB.GetCtx()).Error(s) } else { - c.logger.Debug(s) + c.logger.Ctx(c.DB.GetCtx()).Debug(s) } } + +// HasTable determine whether the table name exists in the database. +func (c *Core) HasTable(name string) (bool, error) { + tableList, err := c.DB.Tables() + if err != nil { + return false, err + } + for _, table := range tableList { + if table == name { + return true, nil + } + } + return false, nil +} + +// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time. +func (c *Core) isSoftCreatedFiledName(fieldName string) bool { + if fieldName == "" { + return false + } + if config := c.DB.GetConfig(); config.CreatedAt != "" { + if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) { + return true + } + return gstr.InArray(append([]string{config.CreatedAt}, createdFiledNames...), fieldName) + } + for _, v := range createdFiledNames { + if utils.EqualFoldWithoutChars(fieldName, v) { + return true + } + } + return false +} diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 9bee4bde3..85a9ee888 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -16,7 +16,8 @@ import ( ) const ( - DEFAULT_GROUP_NAME = "default" // Default group name. + DEFAULT_GROUP_NAME = "default" // Deprecated, use DefaultGroupName instead. + DefaultGroupName = "default" // Default group name. ) // Config is the configuration management object. @@ -27,22 +28,26 @@ type ConfigGroup []ConfigNode // ConfigNode is configuration for one node. type ConfigNode struct { - Host string // Host of server, ip or domain like: 127.0.0.1, localhost - Port string // Port, it's commonly 3306. - User string // Authentication username. - Pass string // Authentication password. - Name string // Default used database name. - Type string // Database type: mysql, sqlite, mssql, pgsql, oracle. - Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. - Debug bool // (Optional) Debug mode enables debug information logging and output. - Prefix string // (Optional) Table prefix. - DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. - Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node. - Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database. - LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. - MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool. - MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool. - MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool. + Host string // Host of server, ip or domain like: 127.0.0.1, localhost + Port string // Port, it's commonly 3306. + User string // Authentication username. + Pass string // Authentication password. + Name string // Default used database name. + Type string // Database type: mysql, sqlite, mssql, pgsql, oracle. + Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. + Debug bool // (Optional) Debug mode enables debug information logging and output. + Prefix string // (Optional) Table prefix. + DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. + Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node. + Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database. + LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. + MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool. + MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool. + MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool. + CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime. + UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime. + DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime. + TimeMaintainDisabled bool // (Optional) Disable the automatic time maintaining feature. } // configs is internal used configuration object. @@ -54,7 +59,7 @@ var configs struct { func init() { configs.config = make(Config) - configs.group = DEFAULT_GROUP_NAME + configs.group = DefaultGroupName } // SetConfig sets the global configuration for package. @@ -84,12 +89,12 @@ func AddConfigNode(group string, node ConfigNode) { // AddDefaultConfigNode adds one node configuration to configuration of default group. func AddDefaultConfigNode(node ConfigNode) { - AddConfigNode(DEFAULT_GROUP_NAME, node) + AddConfigNode(DefaultGroupName, node) } // AddDefaultConfigGroup adds multiple node configurations to configuration of default group. func AddDefaultConfigGroup(nodes ConfigGroup) { - SetConfigGroup(DEFAULT_GROUP_NAME, nodes) + SetConfigGroup(DefaultGroupName, nodes) } // GetConfig retrieves and returns the configuration of given group. @@ -115,6 +120,14 @@ func GetDefaultGroup() string { return configs.group } +// IsConfigured checks and returns whether the database configured. +// It returns true if any configuration exists. +func IsConfigured() bool { + configs.RLock() + defer configs.RUnlock() + return len(configs.config) > 0 +} + // SetLogger sets the logger for orm. func (c *Core) SetLogger(logger *glog.Logger) { c.logger = logger @@ -127,18 +140,18 @@ func (c *Core) GetLogger() *glog.Logger { // SetMaxIdleConnCount sets the max idle connection count for underlying connection pool. func (c *Core) SetMaxIdleConnCount(n int) { - c.maxIdleConnCount = n + c.config.MaxIdleConnCount = n } // SetMaxOpenConnCount sets the max open connection count for underlying connection pool. func (c *Core) SetMaxOpenConnCount(n int) { - c.maxOpenConnCount = n + c.config.MaxOpenConnCount = n } // SetMaxConnLifetime sets the connection TTL for underlying connection pool. // If parameter <= 0, it means the connection never expires. func (c *Core) SetMaxConnLifetime(d time.Duration) { - c.maxConnLifetime = d + c.config.MaxConnLifetime = d } // String returns the node as string. @@ -154,6 +167,11 @@ func (node *ConfigNode) String() string { ) } +// GetConfig returns the current used node configuration. +func (c *Core) GetConfig() *ConfigNode { + return c.config +} + // SetDebug enables/disables the debug mode. func (c *Core) SetDebug(debug bool) { c.debug.Set(debug) @@ -169,24 +187,27 @@ func (c *Core) GetCache() *gcache.Cache { return c.cache } -// GetPrefix returns the table prefix string configured. -func (c *Core) GetPrefix() string { - return c.prefix -} - // GetGroup returns the group string configured. func (c *Core) GetGroup() string { return c.group } // SetDryRun enables/disables the DryRun feature. -func (c *Core) SetDryRun(dryrun bool) { - c.dryrun.Set(dryrun) +// Deprecated, use GetConfig instead. +func (c *Core) SetDryRun(enabled bool) { + c.config.DryRun = enabled } // GetDryRun returns the DryRun value. +// Deprecated, use GetConfig instead. func (c *Core) GetDryRun() bool { - return c.dryrun.Val() + return c.config.DryRun +} + +// GetPrefix returns the table prefix string configured. +// Deprecated, use GetConfig instead. +func (c *Core) GetPrefix() string { + return c.config.Prefix } // SetSchema changes the schema for this database connection object. diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go new file mode 100644 index 000000000..27fedb2a1 --- /dev/null +++ b/database/gdb/gdb_core_structure.go @@ -0,0 +1,197 @@ +// Copyright GoFrame 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 gdb + +import ( + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/util/gutil" + "strings" + "time" + + "github.com/gogf/gf/text/gstr" + + "github.com/gogf/gf/os/gtime" + + "github.com/gogf/gf/encoding/gbinary" + + "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/util/gconv" +) + +// convertFieldValueToLocalValue automatically checks and converts field value from database type +// to golang variable type. +func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} { + // If there's no type retrieved, it returns the directly + // to use its original data type, as is type of interface{}. + if fieldType == "" { + return fieldValue + } + t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType) + t = strings.ToLower(t) + switch t { + case + "binary", + "varbinary", + "blob", + "tinyblob", + "mediumblob", + "longblob": + return gconv.Bytes(fieldValue) + + case + "int", + "tinyint", + "small_int", + "smallint", + "medium_int", + "mediumint", + "serial": + if gstr.ContainsI(fieldType, "unsigned") { + gconv.Uint(gconv.String(fieldValue)) + } + return gconv.Int(gconv.String(fieldValue)) + + case + "big_int", + "bigint", + "bigserial": + if gstr.ContainsI(fieldType, "unsigned") { + gconv.Uint64(gconv.String(fieldValue)) + } + return gconv.Int64(gconv.String(fieldValue)) + + case "real": + return gconv.Float32(gconv.String(fieldValue)) + + case + "float", + "double", + "decimal", + "money", + "numeric", + "smallmoney": + return gconv.Float64(gconv.String(fieldValue)) + + case "bit": + s := gconv.String(fieldValue) + // mssql is true|false string. + if strings.EqualFold(s, "true") { + return 1 + } + if strings.EqualFold(s, "false") { + return 0 + } + return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)) + + case "bool": + return gconv.Bool(fieldValue) + + case "date": + if t, ok := fieldValue.(time.Time); ok { + return gtime.NewFromTime(t).Format("Y-m-d") + } + t, _ := gtime.StrToTime(gconv.String(fieldValue)) + return t.Format("Y-m-d") + + case + "datetime", + "timestamp": + if t, ok := fieldValue.(time.Time); ok { + return gtime.NewFromTime(t) + } + t, _ := gtime.StrToTime(gconv.String(fieldValue)) + return t.String() + + default: + // Auto detect field type, using key match. + switch { + case strings.Contains(t, "text") || strings.Contains(t, "char") || strings.Contains(t, "character"): + return gconv.String(fieldValue) + + case strings.Contains(t, "float") || strings.Contains(t, "double") || strings.Contains(t, "numeric"): + return gconv.Float64(gconv.String(fieldValue)) + + case strings.Contains(t, "bool"): + return gconv.Bool(gconv.String(fieldValue)) + + case strings.Contains(t, "binary") || strings.Contains(t, "blob"): + return fieldValue + + case strings.Contains(t, "int"): + return gconv.Int(gconv.String(fieldValue)) + + case strings.Contains(t, "time"): + s := gconv.String(fieldValue) + t, err := gtime.StrToTime(s) + if err != nil { + return s + } + return t.String() + + case strings.Contains(t, "date"): + s := gconv.String(fieldValue) + t, err := gtime.StrToTime(s) + if err != nil { + return s + } + return t.Format("Y-m-d") + + default: + return gconv.String(fieldValue) + } + } +} + +// mappingAndFilterData automatically mappings the map key to table field and removes +// all key-value pairs that are not the field of given table. +func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) { + if fieldsMap, err := c.DB.TableFields(table, schema); err == nil { + fieldsKeyMap := make(map[string]interface{}, len(fieldsMap)) + for k, _ := range fieldsMap { + fieldsKeyMap[k] = nil + } + // Automatic data key to table field name mapping. + var foundKey string + for dataKey, dataValue := range data { + if _, ok := fieldsKeyMap[dataKey]; !ok { + foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey) + if foundKey != "" { + data[foundKey] = dataValue + delete(data, dataKey) + } else if !filter { + if schema != "" { + return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema) + } + return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table) + } + } + } + // Data filtering. + if filter { + for dataKey, _ := range data { + if _, ok := fieldsMap[dataKey]; !ok { + delete(data, dataKey) + } + } + } + } + return data, nil +} + +//// filterFields removes all key-value pairs which are not the field of given table. +//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} { +// // It must use data copy here to avoid its changing the origin data map. +// newDataMap := make(map[string]interface{}, len(data)) +// if fields, err := c.DB.TableFields(table, schema); err == nil { +// for k, v := range data { +// if _, ok := fields[k]; ok { +// newDataMap[k] = v +// } +// } +// } +// return newDataMap +//} diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go index 5c7e13005..c86086d99 100644 --- a/database/gdb/gdb_core_utility.go +++ b/database/gdb/gdb_core_utility.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index 520582156..b3718e333 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -13,8 +13,8 @@ package gdb import ( "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "strconv" "strings" @@ -64,10 +64,10 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) { // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { var index int - // Convert place holder char '?' to string "@vx". + // Convert place holder char '?' to string "@px". str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string { index++ - return fmt.Sprintf("@v%d", index) + return fmt.Sprintf("@p%d", index) }) str, _ = gregex.ReplaceString("\"", "", str) return d.parseSql(str), args @@ -192,53 +192,64 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, errors.New("function TableFields supports only single table operations") + return nil, gerror.New("function TableFields supports only single table operations") } checkSchema := d.DB.GetSchema() if len(schema) > 0 && schema[0] != "" { checkSchema = schema[0] } - v := d.DB.GetCache().GetOrSetFunc( - fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} { - var result Result - var link *sql.DB + v, _ := internalCache.GetOrSetFunc( + fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), + func() (interface{}, error) { + var ( + result Result + link *sql.DB + ) link, err = d.DB.GetSlave(checkSchema) if err != nil { - return nil + return nil, err } - result, err = d.DB.DoGetAll(link, fmt.Sprintf(` - SELECT a.name Field, + structureSql := fmt.Sprintf(` +SELECT + a.name Field, CASE b.name WHEN 'datetime' THEN 'datetime' - WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')' - WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')' - WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')' - ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE, - case when a.isnullable=1 then 'YES'else 'NO' end as [Null], - case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in ( - SELECT name FROM sysindexes WHERE indid in( - SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid - ))) then 'PRI' else '' end AS [Key], - case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra, - isnull(e.text,'') as [Default], + WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')' + WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')' + WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')' + ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type, + CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null], + CASE WHEN exists ( + SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN ( + SELECT name FROM sysindexes WHERE indid IN ( + SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid + ) + ) + ) THEN 'PRI' ELSE '' END AS [Key], + CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra, + isnull(e.text,'') AS [Default], isnull(g.[value],'') AS [Comment] - FROM syscolumns a - left join systypes b on a.xtype=b.xusertype - inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties' - left join syscomments e on a.cdefault=e.id - left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id - left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0 - where d.name='%s' - order by a.id,a.colorder`, strings.ToUpper(table))) +FROM syscolumns a +LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype +INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties' +LEFT JOIN syscomments e ON a.cdefault=e.id +LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id +LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0 +WHERE d.name='%s' +ORDER BY a.id,a.colorder`, + strings.ToUpper(table), + ) + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DB.DoGetAll(link, structureSql) if err != nil { - return nil + return nil, err } fields = make(map[string]*TableField) for i, m := range result { - fields[strings.ToLower(m["FIELD"].String())] = &TableField{ + fields[strings.ToLower(m["Field"].String())] = &TableField{ Index: i, - Name: strings.ToLower(m["FIELD"].String()), - Type: strings.ToLower(m["TYPE"].String()), + Name: strings.ToLower(m["Field"].String()), + Type: strings.ToLower(m["Type"].String()), Null: m["Null"].Bool(), Key: m["Key"].String(), Default: m["Default"].Val(), @@ -246,7 +257,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st Comment: m["Comment"].String(), } } - return fields + return fields, nil }, 0) if err == nil { fields = v.(map[string]*TableField) diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index 2aecc9b3b..509152a58 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -8,8 +8,8 @@ package gdb import ( "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" @@ -31,6 +31,7 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) { } // Open creates and returns a underlying sql.DB object for mysql. +// Note that it converts time.Time argument to local timezone in default. func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) { var source string if config.LinkInfo != "" { @@ -41,7 +42,7 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) { } } else { source = fmt.Sprintf( - "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local", + "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true", config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset, ) } @@ -96,27 +97,29 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, errors.New("function TableFields supports only single table operations") + return nil, gerror.New("function TableFields supports only single table operations") } checkSchema := d.schema.Val() if len(schema) > 0 && schema[0] != "" { checkSchema = schema[0] } - v := d.cache.GetOrSetFunc( - fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema), - func() interface{} { - var result Result - var link *sql.DB + v, _ := internalCache.GetOrSetFunc( + fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), + func() (interface{}, error) { + var ( + result Result + link *sql.DB + ) link, err = d.DB.GetSlave(checkSchema) if err != nil { - return nil + return nil, err } result, err = d.DB.DoGetAll( link, fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)), ) if err != nil { - return nil + return nil, err } fields = make(map[string]*TableField) for i, m := range result { @@ -131,7 +134,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st Comment: m["Comment"].String(), } } - return fields + return fields, nil }, 0) if err == nil { fields = v.(map[string]*TableField) diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index 59b06bae0..1e0ee3c98 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -13,10 +13,11 @@ package gdb import ( "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" "reflect" "strconv" "strings" @@ -72,50 +73,60 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter return fmt.Sprintf(":v%d", index) }) str, _ = gregex.ReplaceString("\"", "", str) + // Change time string argument wrapping with TO_DATE function. + for i, v := range args { + if reflect.TypeOf(v).Kind() == reflect.String { + valueStr := gconv.String(v) + if gregex.IsMatchString(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`, valueStr) { + args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr) + } + } + } return d.parseSql(str), args } // parseSql does some replacement of the sql before commits it to underlying driver, // for support of oracle server. func (d *DriverOracle) parseSql(sql string) string { - patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))` - if gregex.IsMatchString(patten, sql) == false { + var ( + patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))` + allMatch, _ = gregex.MatchAllString(patten, sql) + ) + if len(allMatch) == 0 { return sql } - - res, err := gregex.MatchAllString(patten, sql) - if err != nil { - return "" - } - var ( index = 0 - keyword = strings.ToUpper(strings.TrimSpace(res[index][0])) + keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0])) ) index++ switch keyword { case "SELECT": - if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && - strings.HasPrefix(res[index][0], "limit") == false) { + if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false { break } if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false { break } queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) - if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || + if len(queryExpr) != 4 || + strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false { break } first, limit := 0, 0 - for i := 1; i < len(res[index]); i++ { - if len(strings.TrimSpace(res[index][i])) == 0 { + for i := 1; i < len(allMatch[index]); i++ { + if len(strings.TrimSpace(allMatch[index][i])) == 0 { continue } - if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") { - first, _ = strconv.Atoi(res[index][i+1]) - limit, _ = strconv.Atoi(res[index][i+2]) + if strings.HasPrefix(allMatch[index][i], "LIMIT") { + if allMatch[index][i+2] != "" { + first, _ = strconv.Atoi(allMatch[index][i+1]) + limit, _ = strconv.Atoi(allMatch[index][i+2]) + } else { + limit, _ = strconv.Atoi(allMatch[index][i+1]) + } break } } @@ -151,24 +162,30 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, errors.New("function TableFields supports only single table operations") + return nil, gerror.New("function TableFields supports only single table operations") } checkSchema := d.DB.GetSchema() if len(schema) > 0 && schema[0] != "" { checkSchema = schema[0] } - v := d.DB.GetCache().GetOrSetFunc( - fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema), - func() interface{} { + v, _ := internalCache.GetOrSetFunc( + fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), + func() (interface{}, error) { result := (Result)(nil) - result, err = d.DB.GetAll(fmt.Sprintf(` - SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE - WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' - WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' - ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE - FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table))) + structureSql := fmt.Sprintf(` +SELECT + COLUMN_NAME AS FIELD, + CASE DATA_TYPE + WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' + WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' + ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE +FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, + strings.ToUpper(table), + ) + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DB.GetAll(structureSql) if err != nil { - return nil + return nil, err } fields = make(map[string]*TableField) for i, m := range result { @@ -178,7 +195,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s Type: strings.ToLower(m["TYPE"].String()), } } - return fields + return fields, nil }, 0) if err == nil { fields = v.(map[string]*TableField) @@ -188,24 +205,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) { table = strings.ToUpper(table) - v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} { - res := (Result)(nil) - res, err = d.DB.GetAll(fmt.Sprintf(` + v, _ := internalCache.GetOrSetFunc( + "table_unique_index_"+table, + func() (interface{}, error) { + res := (Result)(nil) + res, err = d.DB.GetAll(fmt.Sprintf(` SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS WHERE TABLE_NAME = '%s' AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE') ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table)) - if err != nil { - return nil - } - fields := make(map[string]map[string]string) - for _, v := range res { - mm := make(map[string]string) - mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String() - fields[v["INDEX_NAME"].String()] = mm - } - return fields - }, 0) + if err != nil { + return nil, err + } + fields := make(map[string]map[string]string) + for _, v := range res { + mm := make(map[string]string) + mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String() + fields[v["INDEX_NAME"].String()] = mm + } + return fields, nil + }, 0) if err == nil { fields = v.(map[string]map[string]string) } @@ -231,15 +250,15 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio case reflect.Map: fallthrough case reflect.Struct: - dataMap = DataToMapDeep(data) + dataMap = ConvertDataForTableRecord(data) default: - return result, errors.New(fmt.Sprint("unsupported data type:", kind)) + return result, gerror.New(fmt.Sprint("unsupported data type:", kind)) } indexs := make([]string, 0) indexMap := make(map[string]string) indexExists := false - if option != gINSERT_OPTION_DEFAULT { + if option != insertOptionDefault { index, err := d.getTableUniqueIndex(table) if err != nil { return nil, err @@ -267,7 +286,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio k = strings.ToUpper(k) // 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert - if (option == gINSERT_OPTION_REPLACE || option == gINSERT_OPTION_SAVE) && indexExists { + if (option == insertOptionReplace || option == insertOptionSave) && indexExists { fields = append(fields, tableAlias1+"."+charL+k+charR) values = append(values, tableAlias2+"."+charL+k+charR) params = append(params, v) @@ -293,18 +312,18 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio } } - if indexExists && option != gINSERT_OPTION_DEFAULT { + if indexExists && option != insertOptionDefault { switch option { - case gINSERT_OPTION_REPLACE: + case insertOptionReplace: fallthrough - case gINSERT_OPTION_SAVE: + case insertOptionSave: tmp := fmt.Sprintf( "MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)", table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2, strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","), ) return d.DB.DoExec(link, tmp, params...) - case gINSERT_OPTION_IGNORE: + case insertOptionIgnore: return d.DB.DoExec(link, fmt.Sprintf( "INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)", @@ -351,19 +370,19 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, case reflect.Array: listMap = make(List, rv.Len()) for i := 0; i < rv.Len(); i++ { - listMap[i] = DataToMapDeep(rv.Index(i).Interface()) + listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) } case reflect.Map: fallthrough case reflect.Struct: - listMap = List{Map(DataToMapDeep(list))} + listMap = List{Map(ConvertDataForTableRecord(list))} default: - return result, errors.New(fmt.Sprint("unsupported list type:", kind)) + return result, gerror.New(fmt.Sprint("unsupported list type:", kind)) } } // 判断长度 if len(listMap) < 1 { - return result, errors.New("empty data list") + return result, gerror.New("empty data list") } if link == nil { if link, err = d.DB.Master(); err != nil { @@ -382,7 +401,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, valueHolderStr := strings.Join(holders, ",") // 当操作类型非insert时调用单笔的insert功能 - if option != gINSERT_OPTION_DEFAULT { + if option != insertOptionDefault { for _, v := range listMap { r, err := d.DB.DoInsert(link, table, v, option, 1) if err != nil { @@ -400,7 +419,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, } // 构造批量写入数据格式(注意map的遍历是无序的) - batchNum := gDEFAULT_BATCH_NUM + batchNum := defaultBatchNumber if len(batch) > 0 { batchNum = batch[0] } diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index 134203248..828439e4b 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -13,8 +13,8 @@ package gdb import ( "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" "strings" @@ -62,10 +62,10 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) { // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { var index int - // Convert place holder char '?' to string "$vx". + // Convert place holder char '?' to string "$x". sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { index++ - return fmt.Sprintf("$v%d", index) + return fmt.Sprintf("$%d", index) }) sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql) return sql, args @@ -100,28 +100,35 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, errors.New("function TableFields supports only single table operations") + return nil, gerror.New("function TableFields supports only single table operations") } table, _ = gregex.ReplaceString("\"", "", table) checkSchema := d.DB.GetSchema() if len(schema) > 0 && schema[0] != "" { checkSchema = schema[0] } - v := d.DB.GetCache().GetOrSetFunc( - fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} { - var result Result - var link *sql.DB + v, _ := internalCache.GetOrSetFunc( + fmt.Sprintf(`pgsql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), + func() (interface{}, error) { + var ( + result Result + link *sql.DB + ) link, err = d.DB.GetSlave(checkSchema) if err != nil { - return nil + return nil, err } - result, err = d.DB.DoGetAll(link, fmt.Sprintf(` - SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a - LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t - WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid - ORDER BY a.attnum`, strings.ToLower(table))) + structureSql := fmt.Sprintf(` +SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a +LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t +WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid +ORDER BY a.attnum`, + strings.ToLower(table), + ) + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DB.DoGetAll(link, structureSql) if err != nil { - return nil + return nil, err } fields = make(map[string]*TableField) @@ -132,7 +139,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st Type: m["type"].String(), } } - return fields + return fields, nil }, 0) if err == nil { fields = v.(map[string]*TableField) diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index 3f9eea3b8..b70808b32 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -12,8 +12,8 @@ package gdb import ( "database/sql" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" @@ -36,15 +36,14 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) { // Open creates and returns a underlying sql.DB object for sqlite. func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) { var source string - var err error if config.LinkInfo != "" { source = config.LinkInfo } else { source = config.Name } - source, err = gfile.Search(source) - if err != nil { - return nil, err + // It searches the source file to locate its absolute path.. + if absolutePath, _ := gfile.Search(source); absolutePath != "" { + source = absolutePath } intlog.Printf("Open: %s", source) if db, err := sql.Open("sqlite3", source); err == nil { @@ -92,23 +91,26 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, errors.New("function TableFields supports only single table operations") + return nil, gerror.New("function TableFields supports only single table operations") } checkSchema := d.DB.GetSchema() if len(schema) > 0 && schema[0] != "" { checkSchema = schema[0] } - v := d.DB.GetCache().GetOrSetFunc( - fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} { - var result Result - var link *sql.DB + v, _ := internalCache.GetOrSetFunc( + fmt.Sprintf(`sqlite_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), + func() (interface{}, error) { + var ( + result Result + link *sql.DB + ) link, err = d.DB.GetSlave(checkSchema) if err != nil { - return nil + return nil, err } result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) if err != nil { - return nil + return nil, err } fields = make(map[string]*TableField) for i, m := range result { @@ -118,7 +120,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s Type: strings.ToLower(m["type"].String()), } } - return fields + return fields, nil }, 0) if err == nil { fields = v.(map[string]*TableField) diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 1b1000c95..78c9ac47b 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame 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, @@ -8,9 +8,10 @@ package gdb import ( "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/empty" + "github.com/gogf/gf/internal/json" "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gutil" @@ -55,21 +56,41 @@ const ( var ( // quoteWordReg is the regular expression object for a word check. quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) + + // Priority tags for struct converting for orm field mapping. + structTagPriority = append([]string{ORM_TAG_FOR_STRUCT}, gconv.StructTagPriority...) ) -// ListItemValues is alias for gutil.ListItemValues. +// ListItemValues retrieves and returns the elements of all item struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// +// The parameter supports types like: +// []map[string]interface{} +// []map[string]sub-map +// []struct +// []struct:sub-struct +// Note that the sub-map/sub-struct makes sense only if the optional parameter is given. // See gutil.ListItemValues. func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) { return gutil.ListItemValues(list, key, subKey...) } +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// See gutil.ListItemValuesUnique. +func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} { + return gutil.ListItemValuesUnique(list, key, subKey...) +} + // GetInsertOperationByOption returns proper insert option with given parameter