From 5db039bbce144ea4dfbf7c1b536fb16206f02553 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 11 Mar 2019 16:14:55 +0800 Subject: [PATCH 1/3] gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model --- g/database/gdb/gdb_func.go | 60 ++++++++++++----- g/database/gdb/gdb_model.go | 70 +++++++++++--------- g/database/gdb/gdb_unit_model_test.go | 94 +++++++++++++++++++++++++-- g/net/ghttp/ghttp_server_cookie.go | 2 +- g/net/ghttp/ghttp_server_session.go | 5 +- g/text/gstr/gstr.go | 14 ++++ geg/other/test.go | 8 ++- 7 files changed, 196 insertions(+), 57 deletions(-) diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index cc46a159a..679ed07d2 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -31,41 +31,61 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne rv = rv.Elem() kind = rv.Kind() } + tmpArgs := []interface{}(nil) switch kind { - // 注意当where为map/struct类型时,args参数必须为空。 + // map/struct类型 case reflect.Map: fallthrough case reflect.Struct: - for k, v := range gconv.Map(where) { + for key, value := range gconv.Map(where) { if buffer.Len() > 0 { buffer.WriteString(" AND ") } - // 支持slice键值/属性,这个时候作为IN查询 - switch reflect.ValueOf(v).Kind() { + // 支持slice键值/属性,如果只有一个?占位符号,那么作为IN查询,否则打散作为多个查询参数 + rv := reflect.ValueOf(value) + switch rv.Kind() { case reflect.Slice: fallthrough case reflect.Array: - buffer.WriteString(k + " IN(?)") - default: - if gstr.Pos(k, "<") == -1 && gstr.Pos(k, ">") == -1 && gstr.Pos(k, "=") == -1 { - buffer.WriteString(k + "=?") + count := gstr.Count(key, "?") + if count == 0 { + buffer.WriteString(key + " IN(?)") + tmpArgs = append(tmpArgs, value) + } else if count != rv.Len() { + buffer.WriteString(key) + tmpArgs = append(tmpArgs, value) } else { - buffer.WriteString(k + "?") + buffer.WriteString(key) + // 如果键名/属性名称中带有多个?占位符号,那么将参数打散 + tmpArgs = append(tmpArgs, gconv.Interfaces(value)...) + } + default: + if value == nil { + buffer.WriteString(key) + } else { + if gstr.Pos(key, "?") == -1 { + if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 { + buffer.WriteString(key + "=?") + } else { + buffer.WriteString(key + "?") + } + } else { + buffer.WriteString(key) + } + tmpArgs = append(tmpArgs, value) } } - // 当给定的Where参数为map/struct时,args参数必定为空, - // 考虑到后续还会对args做处理,特别是判断slice类型,这里直接给args赋值。 - args = append(args, v) } - newWhere = buffer.String() + default: buffer.WriteString(gconv.String(where)) } if buffer.Len() == 0 { buffer.WriteString("1=1") } - // 查询条件参数处理,主要处理slice参数类型 newWhere = buffer.String() - if len(args) > 0 { - for index, arg := range args { + tmpArgs = append(tmpArgs, args...) + // 查询条件参数处理,主要处理slice参数类型 + if len(tmpArgs) > 0 { + for index, arg := range tmpArgs { rv := reflect.ValueOf(arg) kind := rv.Kind() if kind == reflect.Ptr { @@ -90,6 +110,14 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne return s }) default: + // 支持例如 Where/And/Or("uid", 1) 这种格式 + if gstr.Pos(newWhere, "?") == -1 { + if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 { + newWhere += "=?" + } else { + newWhere += "?" + } + } newArgs = append(newArgs, arg) } } diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index 2534b3430..8339dbe7b 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -3,17 +3,18 @@ // 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. +// +// @author john, ymrjqyy package gdb import ( - "fmt" - "errors" - "database/sql" - "github.com/gogf/gf/g/util/gconv" - _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" + "database/sql" + "errors" + "fmt" + "github.com/gogf/gf/g/util/gconv" + _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" "reflect" - "strings" ) // 数据库链式操作模型对象 @@ -35,7 +36,7 @@ type Model struct { cacheEnabled bool // 当前SQL操作是否开启查询缓存功能 cacheTime int // 查询缓存时间 cacheName string // 查询缓存名称 - alterable bool // 当前模型是否运行可修改模式(默认情况下链式操作不会修改当前模型,而是创建新的模型返回) + safe bool // 当前模型是否运行安全模式(可修改当前模型,否则每一次链式操作都是返回新的模型对象) } // 链式操作,数据表字段,可支持多个表,以半角逗号连接 @@ -45,6 +46,7 @@ func (bs *dbBase) Table(tables string) (*Model) { tablesInit : tables, tables : tables, fields : "*", + safe : false, } } @@ -60,6 +62,7 @@ func (tx *TX) Table(tables string) (*Model) { tx : tx, tablesInit : tables, tables : tables, + safe : false, } } @@ -80,21 +83,25 @@ func (md *Model) Clone() *Model { return newModel } -// 标识当前对象可被修改。 +// 标识当前对象运行安全模式(可被修改)。 // 1. 默认情况下,模型对象的对象属性无法被修改, // 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。 // 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。 // 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作, // 即使是链式操作分开执行。 // 3. 大部分ORM框架默认模型对象是可修改的,但是GF框架的ORM提供给开发者更灵活,更安全的链式操作选项。 -func (md *Model) Alterable() *Model { - md.alterable = true +func (md *Model) Safe(safe...bool) *Model { + if len(safe) > 0 { + md.safe = safe[0] + } else { + md.safe = true + } return md } // 返回操作的模型对象,可能是当前对象,也可能是新的克隆对象,根据alterable决定。 func (md *Model) getModel() *Model { - if md.alterable { + if !md.safe { return md } else { return md.Clone() @@ -137,16 +144,15 @@ func (md *Model) Filter() (*Model) { } // 链式操作,condition,支持string & gdb.Map. -// 注意,多个Where调用时,会相互覆盖,只有最后一个Where语句生效。 +// 注意,多个Where调用时,会自动转换为And条件调用。 func (md *Model) Where(where interface{}, args ...interface{}) (*Model) { - model := md.getModel() + model := md.getModel() + if model.where != "" { + return md.And(where, args...) + } newWhere, newArgs := formatCondition(where, args) model.where = newWhere model.whereArgs = newArgs - // 支持 Where("uid", 1)这种格式 - if len(args) == 1 && strings.Index(model.where , "?") < 0 { - model.where += "=?" - } return model } @@ -154,8 +160,12 @@ func (md *Model) Where(where interface{}, args ...interface{}) (*Model) { func (md *Model) And(where interface{}, args ...interface{}) (*Model) { model := md.getModel() newWhere, newArgs := formatCondition(where, args) - model.where += " AND " + newWhere - model.whereArgs = append(model.whereArgs, newArgs...) + if len(model.where) > 0 && model.where[0] == '(' { + model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere) + } else { + model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere) + } + model.whereArgs = append(model.whereArgs, newArgs...) return model } @@ -163,8 +173,12 @@ func (md *Model) And(where interface{}, args ...interface{}) (*Model) { func (md *Model) Or(where interface{}, args ...interface{}) (*Model) { model := md.getModel() newWhere, newArgs := formatCondition(where, args) - model.where += " OR " + newWhere - model.whereArgs = append(model.whereArgs, newArgs...) + if len(model.where) > 0 && model.where[0] == '(' { + model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere) + } else { + model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere) + } + model.whereArgs = append(model.whereArgs, newArgs...) return model } @@ -190,8 +204,7 @@ func (md *Model) Limit(start int, limit int) (*Model) { return model } -// 链式操作,翻页 -// @author ymrjqyy +// 链式操作,翻页,注意分页页码从1开始,而Limit方法从0开始。 func (md *Model) ForPage(page, limit int) (*Model) { model := md.getModel() model.start = (page - 1) * limit @@ -586,14 +599,13 @@ func (md *Model) getFormattedSql() string { return s } -// 组块结果集 -// @author ymrjqyy -// @author 2018-08-15 +// 组块结果集。 func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) { - page := 1 + page := 1 + model := md for { - md.ForPage(page, limit) - data, err := md.getAll(md.getFormattedSql(), md.whereArgs...) + model = model.ForPage(page, limit) + data, err := model.All() if err != nil { callback(nil, err) break diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index cd667ad6f..2ba7ab5ef 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -175,9 +175,9 @@ func TestModel_Clone(t *testing.T) { gtest.Assert(result[1]["id"].Int(), 3) } -func TestModel_Alterable(t *testing.T) { +func TestModel_Safe(t *testing.T) { gtest.Case(t, func() { - md := db.Table("user").Alterable().Where("id IN(?)", g.Slice{1,3}) + md := db.Table("user").Safe(false).Where("id IN(?)", g.Slice{1,3}) count, err := md.Count() if err != nil { gtest.Fatal(err) @@ -190,6 +190,20 @@ func TestModel_Alterable(t *testing.T) { } gtest.Assert(count, 1) }) + gtest.Case(t, func() { + md := db.Table("user").Safe(true).Where("id IN(?)", g.Slice{1,3}) + count, err := md.Count() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(count, 2) + md.And("id = ?", 1) + count, err = md.Count() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(count, 2) + }) } func TestModel_All(t *testing.T) { @@ -420,12 +434,52 @@ func TestModel_GroupBy(t *testing.T) { func TestModel_Where(t *testing.T) { // string gtest.Case(t, func() { - result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One() + result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One() + if err != nil { + gtest.Fatal(err) + } + gtest.AssertGT(len(result), 0) + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 3).One() + if err != nil { + gtest.Fatal(err) + } + gtest.AssertGT(len(result), 0) + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 3).Where("nickname", "T3").One() if err != nil { gtest.Fatal(err) } gtest.Assert(result["id"].Int(), 3) }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 3).And("nickname", "T3").One() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").One() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>?", 1).One() + gtest.Assert(err, nil) + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>", 1).One() + gtest.Assert(err, nil) + gtest.Assert(result["id"].Int(), 3) + }) // map gtest.Case(t, func() { result, err := db.Table("user").Where(g.Map{"id" : 3, "nickname" : "T3"}).One() @@ -437,11 +491,39 @@ func TestModel_Where(t *testing.T) { // map key operator gtest.Case(t, func() { result, err := db.Table("user").Where(g.Map{"id>" : 1, "id<" : 3}).One() - if err != nil { - gtest.Fatal(err) - } + gtest.Assert(err, nil) gtest.Assert(result["id"].Int(), 2) }) + // complicated where 1 + gtest.Case(t, func() { + //db.SetDebug(true) + conditions := g.Map{ + "nickname like ?" : "%T%", + "id between ? and ?" : g.Slice{1,3}, + "id > 0" : nil, + "create_time > 0" : nil, + "id" : g.Slice{1,2,3}, + } + result, err := db.Table("user").Where(conditions).OrderBy("id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + }) + // complicated where 2 + gtest.Case(t, func() { + //db.SetDebug(true) + conditions := g.Map{ + "nickname like ?" : "%T%", + "id between ? and ?" : g.Slice{1,3}, + "id >= ?" : 1, + "create_time > ?" : 0, + "id in(?)" : g.Slice{1,2,3}, + } + result, err := db.Table("user").Where(conditions).OrderBy("id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + }) // struct gtest.Case(t, func() { type User struct { diff --git a/g/net/ghttp/ghttp_server_cookie.go b/g/net/ghttp/ghttp_server_cookie.go index e01ba78f3..1dac80c39 100644 --- a/g/net/ghttp/ghttp_server_cookie.go +++ b/g/net/ghttp/ghttp_server_cookie.go @@ -5,7 +5,7 @@ // You can obtain one at https://github.com/gogf/gf. // // HTTP Cookie管理对象, -// 由于Cookie是和HTTP请求挂钩的,因此被包含到 ghttp 包中进行管理 +// 由于Cookie是和HTTP请求挂钩的,因此被包含到 ghttp 包中进行管理。 package ghttp diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index f2bf95351..f7ec825e7 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -26,9 +26,9 @@ type Session struct { request *Request // 关联的请求 } -// 生成一个唯一的SessionId字符串,长度16位 +// 生成一个唯一的SessionId字符串,长度18位。 func makeSessionId() string { - return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3)) + return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6)) } // 获取或者生成一个session对象(延迟初始化) @@ -42,6 +42,7 @@ func GetSession(r *Request) *Session { } // 执行初始化(用于延迟初始化) +// @TODO 验证提交的SESSIONID合法性 func (s *Session) init() { if len(s.id) == 0 { s.id = s.request.Cookie.SessionId() diff --git a/g/text/gstr/gstr.go b/g/text/gstr/gstr.go index e902696fa..49403b136 100644 --- a/g/text/gstr/gstr.go +++ b/g/text/gstr/gstr.go @@ -59,6 +59,20 @@ func ReplaceI(origin, search, replace string, count...int) string { return origin } +// Count counts the number of appears in . It returns 0 if no found in . +// +// 计算字符串substr在字符串s中出现的次数,如果没有在s中找到substr,那么返回0。 +func Count(s, substr string) int { + return strings.Count(s, substr) +} + +// Count counts the number of appears in , case-insensitive. It returns 0 if no found in . +// +// (非大小写敏感)计算字符串substr在字符串s中出现的次数,如果没有在s中找到substr,那么返回0。 +func CountI(s, substr string) int { + return strings.Count(ToLower(s), ToLower(substr)) +} + // Replace string by array/slice. // // 使用map进行字符串替换(大小写敏感) diff --git a/geg/other/test.go b/geg/other/test.go index 3c0c2433a..cb9e7cbc2 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,10 +2,12 @@ package main import ( "fmt" - "github.com/gogf/gf/g/util/gconv" + "github.com/gogf/gf/g/os/gtime" + "strconv" + "strings" ) func main() { - t := gconv.GTime("2010-10-10 00:00:01") - fmt.Println(t.String()) + //t := gconv.GTime("2010-10-10 00:00:01") + fmt.Println(strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36))) } \ No newline at end of file From 127fb6718569682e1027a61a3bfb57c33ca3b743 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 11 Mar 2019 16:33:51 +0800 Subject: [PATCH 2/3] add session id check in session object initialzing --- g/net/ghttp/ghttp_server_config.go | 2 +- g/net/ghttp/ghttp_server_cookie.go | 8 ++++++++ g/net/ghttp/ghttp_server_session.go | 21 +++++++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go index 50af99bc3..f73cfe606 100644 --- a/g/net/ghttp/ghttp_server_config.go +++ b/g/net/ghttp/ghttp_server_config.go @@ -24,7 +24,7 @@ const ( NAME_TO_URI_TYPE_CAMEL = 3 // 采用驼峰命名方式 gDEFAULT_COOKIE_PATH = "/" // 默认path gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) - gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) + gDEFAULT_SESSION_MAX_AGE = 600000 // 默认session有效期(600秒) gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 gCHANGE_CONFIG_WHILE_RUNNING_ERROR = "cannot be changed while running" ) diff --git a/g/net/ghttp/ghttp_server_cookie.go b/g/net/ghttp/ghttp_server_cookie.go index 1dac80c39..97689b620 100644 --- a/g/net/ghttp/ghttp_server_cookie.go +++ b/g/net/ghttp/ghttp_server_cookie.go @@ -87,6 +87,14 @@ func (c *Cookie) SessionId() string { return id } +// 获取SessionId,不存在时则创建 +func (c *Cookie) MakeSessionId() string { + c.init() + id := makeSessionId() + c.SetSessionId(id) + return id +} + // 判断Cookie中是否存在制定键名(并且没有过期) func (c *Cookie) Contains(key string) bool { c.init() diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index f7ec825e7..429262c6f 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -41,15 +41,24 @@ func GetSession(r *Request) *Session { } } -// 执行初始化(用于延迟初始化) -// @TODO 验证提交的SESSIONID合法性 +// 执行初始化(用于延迟初始化). func (s *Session) init() { if len(s.id) == 0 { - s.id = s.request.Cookie.SessionId() s.server = s.request.Server - s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} { - return gmap.NewStringInterfaceMap() - }, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap) + // 根据提交的SESSION ID获取已存在SESSION + id := s.request.Cookie.GetSessionId() + if id != "" { + data := s.server.sessions.Get(id) + if data != nil { + s.id = id + s.data = data.(*gmap.StringInterfaceMap) + return + } + } + // 否则执行初始化创建 + s.id = s.request.Cookie.MakeSessionId() + s.data = gmap.NewStringInterfaceMap() + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()) } } From 802568856ce3527c0141751ff990bb2040394c81 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 11 Mar 2019 16:35:44 +0800 Subject: [PATCH 3/3] version updates --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 0251838fe..6b6151fdd 100644 --- a/version.go +++ b/version.go @@ -1,5 +1,5 @@ package gf -const VERSION = "v1.5.13" +const VERSION = "v1.5.14" const AUTHORS = "john"