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_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 e01ba78f3..97689b620 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 @@ -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 f2bf95351..429262c6f 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对象(延迟初始化) @@ -41,14 +41,24 @@ func GetSession(r *Request) *Session { } } -// 执行初始化(用于延迟初始化) +// 执行初始化(用于延迟初始化). 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()) } } 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 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"