diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 9136a94c9..e1c4ae66a 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -16,7 +16,6 @@ import ( "github.com/gogf/gf/os/glog" "github.com/gogf/gf/container/gmap" - "github.com/gogf/gf/container/gring" "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gcache" @@ -84,9 +83,6 @@ type DB interface { // 设置管理 SetDebug(debug bool) SetSchema(schema string) - GetQueriedSqls() []*Sql - GetLastSql() *Sql - PrintQueriedSqls() SetLogger(logger *glog.Logger) GetLogger() *glog.Logger SetMaxIdleConnCount(n int) @@ -99,6 +95,7 @@ type DB interface { getCache() *gcache.Cache getChars() (charLeft string, charRight string) getDebug() bool + getPrefix() string quoteWord(s string) string doSetSchema(sqlDb *sql.DB, schema string) error filterFields(table string, data map[string]interface{}) map[string]interface{} @@ -119,9 +116,9 @@ type dbBase struct { db DB // 数据库对象 group string // 配置分组名称 debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性 - sqls *gring.Ring // (debug=true时有效)已执行的SQL列表 cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存 schema *gtype.String // 手动切换的数据库名称 + prefix string // 表名前缀 tables map[string]map[string]string // 数据库表结构 logger *glog.Logger // 日志管理对象 maxIdleConnCount int // 连接池最大限制的连接数 @@ -179,7 +176,7 @@ var ( instances = gmap.NewStrAnyMap(true) ) -// New creates ORM DB object with global configurations. +// 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) { @@ -201,6 +198,7 @@ func New(name ...string) (db DB, err error) { cache: gcache.New(), schema: gtype.NewString(), logger: glog.New(), + prefix: node.Prefix, maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, } switch node.Type { diff --git a/database/gdb/gdb_base.go b/database/gdb/gdb_base.go index e77d8698a..af7167ab5 100644 --- a/database/gdb/gdb_base.go +++ b/database/gdb/gdb_base.go @@ -25,8 +25,7 @@ import ( ) const ( - gDEFAULT_DEBUG_SQL_LENGTH = 1000 - gPATH_FILTER_KEY = "/database/gdb/gdb" + gPATH_FILTER_KEY = "/database/gdb/gdb" ) var ( @@ -34,49 +33,6 @@ var ( lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) // Regular expression object for a string which has operator at its tail. ) -// 获取最近一条执行的sql -func (bs *dbBase) GetLastSql() *Sql { - if bs.sqls == nil { - return nil - } - if v := bs.sqls.Val(); v != nil { - return v.(*Sql) - } - return nil -} - -// 获取已经执行的SQL列表(仅在debug=true时有效) -func (bs *dbBase) GetQueriedSqls() []*Sql { - if bs.sqls == nil { - return nil - } - array := make([]*Sql, 0) - bs.sqls.Prev() - bs.sqls.RLockIteratorPrev(func(value interface{}) bool { - if value == nil { - return false - } - array = append(array, value.(*Sql)) - return true - }) - return array -} - -// 打印已经执行的SQL列表(仅在debug=true时有效) -func (bs *dbBase) PrintQueriedSqls() { - sqlSlice := bs.GetQueriedSqls() - for k, v := range sqlSlice { - fmt.Println(len(sqlSlice)-k, ":") - fmt.Println(" Sql :", v.Sql) - fmt.Println(" Args :", v.Args) - fmt.Println(" Format :", v.Format) - fmt.Println(" Error :", v.Error) - fmt.Println(" Start :", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u")) - fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u")) - fmt.Println(" Cost :", v.End-v.Start, "ms") - } -} - // 打印SQL对象(仅在debug=true时有效) func (bs *dbBase) printSql(v *Sql) { s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format) @@ -113,7 +69,6 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows Start: mTime1, End: mTime2, } - bs.sqls.Put(s) bs.printSql(s) } else { rows, err = link.Query(query, args...) @@ -151,7 +106,6 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result Start: mTime1, End: mTime2, } - bs.sqls.Put(s) bs.printSql(s) } else { result, err = link.Exec(query, args...) @@ -600,6 +554,11 @@ func (bs *dbBase) getCache() *gcache.Cache { return bs.cache } +// 获得表名前缀 +func (bs *dbBase) getPrefix() string { + return bs.prefix +} + // 将数据查询的列表数据*sql.Rows转换为Result类型 func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { if !rows.Next() { diff --git a/database/gdb/gdb_config.go b/database/gdb/gdb_config.go index 279fd4003..4fb0ce395 100644 --- a/database/gdb/gdb_config.go +++ b/database/gdb/gdb_config.go @@ -12,8 +12,6 @@ import ( "time" "github.com/gogf/gf/os/glog" - - "github.com/gogf/gf/container/gring" ) const ( @@ -36,6 +34,7 @@ type ConfigNode struct { Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave Debug bool // (可选)开启调试模式 + Prefix string // (可选)表名前缀 Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 Charset string // (可选,默认为 utf8)编码,默认为 utf8 LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) @@ -159,9 +158,6 @@ func (bs *dbBase) SetDebug(debug bool) { return } bs.debug.Set(debug) - if debug && bs.sqls == nil { - bs.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH, true) - } } // 获取是否开启调试服务 diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index fca9a7b09..1ff902a3a 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -65,12 +65,17 @@ const ( OPTION_ALLOWEMPTY ) -// 链式操作,数据表字段,可支持多个表,以半角逗号连接 -func (bs *dbBase) Table(tables string) *Model { +// Table creates and returns a new model with given table. +// The parameter can be used with alias like "user as u" or "user u". +// And also, the
can be multiple table names joined with char ',', like +// "user, user_detail" or "user u, user_detail ud". +func (bs *dbBase) Table(table string) *Model { + array := gstr.SplitAndTrim(table, " ") + array[0] = bs.db.quoteWord(bs.db.getPrefix() + array[0]) return &Model{ db: bs.db, - tablesInit: tables, - tables: bs.db.quoteWord(tables), + tablesInit: table, + tables: gstr.Join(array, " "), fields: "*", start: -1, offset: -1, @@ -79,18 +84,22 @@ func (bs *dbBase) Table(tables string) *Model { } } -// 链式操作,数据表字段,可支持多个表,以半角逗号连接 +// From is alias of Table. +// See Table. func (bs *dbBase) From(tables string) *Model { return bs.db.Table(tables) } -// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接 -func (tx *TX) Table(tables string) *Model { +// Table creates and returns a new model with given table for transaction object. +// More detail please see dbBase.Table. +func (tx *TX) Table(table string) *Model { + array := gstr.SplitAndTrim(table, " ") + array[0] = tx.db.quoteWord(tx.db.getPrefix() + array[0]) return &Model{ db: tx.db, tx: tx, - tablesInit: tables, - tables: tx.db.quoteWord(tables), + tablesInit: table, + tables: gstr.Join(array, " "), fields: "*", start: -1, offset: -1, @@ -99,12 +108,13 @@ func (tx *TX) Table(tables string) *Model { } } -// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接 +// From is alias of tx.Table. +// See Table. func (tx *TX) From(tables string) *Model { return tx.Table(tables) } -// 克隆一个当前对象 +// Clone creates and returns a new model, which is a deep copy of current model. func (md *Model) Clone() *Model { newModel := (*Model)(nil) if md.tx != nil { @@ -125,14 +135,15 @@ func (md *Model) Clone() *Model { return newModel } -// 设置本次链式操作在主节点上 +// Master marks this database operation will be implemented on the master node. func (md *Model) Master() *Model { model := md.getModel() model.linkType = gLINK_TYPE_MASTER return model } -// 设置本次链式操作在从节点上 +// Slave marks this database operation will be implemented on the slave node. +// Note that this makes sense only if there's any slave node in the database configuration. func (md *Model) Slave() *Model { model := md.getModel() model.linkType = gLINK_TYPE_SLAVE @@ -165,23 +176,29 @@ func (md *Model) getModel() *Model { } // 链式操作,左联表 -func (md *Model) LeftJoin(joinTable string, on string) *Model { +func (md *Model) LeftJoin(table string, on string) *Model { model := md.getModel() - model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on) + array := gstr.SplitAndTrim(table, " ") + array[0] = md.db.quoteWord(md.db.getPrefix() + array[0]) + model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", gstr.Join(array, " "), on) return model } // 链式操作,右联表 -func (md *Model) RightJoin(joinTable string, on string) *Model { +func (md *Model) RightJoin(table string, on string) *Model { model := md.getModel() - model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on) + array := gstr.SplitAndTrim(table, " ") + array[0] = md.db.quoteWord(md.db.getPrefix() + array[0]) + model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", gstr.Join(array, " "), on) return model } // 链式操作,内联表 -func (md *Model) InnerJoin(joinTable string, on string) *Model { +func (md *Model) InnerJoin(table string, on string) *Model { model := md.getModel() - model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on) + array := gstr.SplitAndTrim(table, " ") + array[0] = md.db.quoteWord(md.db.getPrefix() + array[0]) + model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", gstr.Join(array, " "), on) return model } @@ -393,7 +410,6 @@ func (md *Model) Data(data ...interface{}) *Model { } // filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations. -// Note that, it does not filter list item, which is also type of map, for "omit empty" feature. func (md *Model) filterDataForInsertOrUpdate(data interface{}) interface{} { if list, ok := md.data.(List); ok { for k, m := range list { diff --git a/database/gdb/gdb_unit_init_mysql_test.go b/database/gdb/gdb_unit_init_mysql_test.go index 2fcfcb868..cdb6ed9a6 100644 --- a/database/gdb/gdb_unit_init_mysql_test.go +++ b/database/gdb/gdb_unit_init_mysql_test.go @@ -21,14 +21,16 @@ const ( TABLE = "user" SCHEMA1 = "test1" SCHEMA2 = "test2" + PREFIX1 = "gf_" ) var ( - db gdb.DB + db gdb.DB + dbPrefix gdb.DB ) func InitMysql() { - node := gdb.ConfigNode{ + nodeDefault := gdb.ConfigNode{ Host: "127.0.0.1", Port: "3306", User: "root", @@ -42,35 +44,62 @@ func InitMysql() { MaxOpenConnCount: 10, MaxConnLifetime: 600, } - gdb.AddConfigNode("test", node) - gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, node) + nodePrefix := nodeDefault + nodePrefix.Prefix = PREFIX1 + gdb.AddConfigNode("test", nodeDefault) + gdb.AddConfigNode("prefix", nodePrefix) + gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, nodeDefault) + // Default db. if r, err := gdb.New(); err != nil { gtest.Error(err) } else { db = r } - schemaTemplate := "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET UTF8" if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil { gtest.Error(err) } - if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil { gtest.Error(err) } - db.SetSchema(SCHEMA1) + createTable(TABLE) + // Prefix db. + if r, err := gdb.New("prefix"); err != nil { + gtest.Error(err) + } else { + dbPrefix = r + } + if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil { + gtest.Error(err) + } + if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil { + gtest.Error(err) + } + dbPrefix.SetSchema(SCHEMA1) createTable(TABLE) } -func createTable(table ...string) (name string) { +func createTable(table ...string) string { + return createTableWithDb(db, table...) +} + +func createInitTable(table ...string) string { + return createInitTableWithDb(db, table...) +} + +func dropTable(table string) { + dropTableWithDb(db, table) +} + +func createTableWithDb(db gdb.DB, table ...string) (name string) { if len(table) > 0 { name = table[0] } else { name = fmt.Sprintf(`%s_%d`, TABLE, gtime.Nanosecond()) } - dropTable(name) + dropTableWithDb(db, name) if _, err := db.Exec(fmt.Sprintf(` CREATE TABLE %s ( id int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -86,8 +115,8 @@ func createTable(table ...string) (name string) { return } -func createInitTable(table ...string) (name string) { - name = createTable(table...) +func createInitTableWithDb(db gdb.DB, table ...string) (name string) { + name = createTableWithDb(db, table...) array := garray.New(true) for i := 1; i <= INIT_DATA_SIZE; i++ { array.Append(g.Map{ @@ -98,7 +127,8 @@ func createInitTable(table ...string) (name string) { "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), }) } - result, err := db.Table(name).Data(array.Slice()).Insert() + + result, err := db.BatchInsert(name, array.Slice()) gtest.Assert(err, nil) n, e := result.RowsAffected() @@ -107,7 +137,7 @@ func createInitTable(table ...string) (name string) { return } -func dropTable(table string) { +func dropTableWithDb(db gdb.DB, table string) { if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil { gtest.Error(err) } diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index a4f3af498..bc13f9767 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -1260,3 +1260,34 @@ func Test_Model_FieldsEx(t *testing.T) { gtest.AssertNE(one["password"], "456") }) } + +func Test_Model_Prefix(t *testing.T) { + db := dbPrefix + table := fmt.Sprintf(`%s_%d`, TABLE, gtime.Nanosecond()) + createInitTableWithDb(db, PREFIX1+table) + defer dropTable(PREFIX1 + table) + // Select. + gtest.Case(t, func() { + r, err := db.Table(table).Where("id in (?)", g.Slice{1, 2}).OrderBy("id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(r), 2) + gtest.Assert(r[0]["id"], "1") + gtest.Assert(r[1]["id"], "2") + }) + // Select with alias. + gtest.Case(t, func() { + r, err := db.Table(table+" as u").Where("u.id in (?)", g.Slice{1, 2}).OrderBy("u.id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(r), 2) + gtest.Assert(r[0]["id"], "1") + gtest.Assert(r[1]["id"], "2") + }) + // Select with alias and join statement. + gtest.Case(t, func() { + r, err := db.Table(table+" as u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).OrderBy("u1.id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(r), 2) + gtest.Assert(r[0]["id"], "1") + gtest.Assert(r[1]["id"], "2") + }) +} diff --git a/encoding/gjson/gjson_api_new_load.go b/encoding/gjson/gjson_api_new_load.go index 48b0e4a6d..af644d38a 100644 --- a/encoding/gjson/gjson_api_new_load.go +++ b/encoding/gjson/gjson_api_new_load.go @@ -25,11 +25,10 @@ import ( "github.com/gogf/gf/util/gconv" ) -// New creates a Json object with any variable type of , -// but should be a map or slice for data access reason, -// or it will make no sense. -// The param specifies whether using this Json object -// in un-concurrent-safe context, which is false in default. +// New creates a Json object with any variable type of , but should be a map or slice +// for data access reason, or it will make no sense. +// The param specifies whether using this Json object in concurrent-safe context, which is +// false in default. func New(data interface{}, safe ...bool) *Json { j := (*Json)(nil) switch data.(type) { @@ -61,7 +60,8 @@ func New(data interface{}, safe ...bool) *Json { } case reflect.Map, reflect.Struct: i := interface{}(nil) - i = gconv.Map(data, "json") + // Note that it uses MapDeep function implementing the converting. + i = gconv.MapDeep(data, "json") j = &Json{ p: &i, c: byte(gDEFAULT_SPLIT_CHAR), diff --git a/net/ghttp/ghttp_request.go b/net/ghttp/ghttp_request.go index 5d970b441..7d8534f44 100644 --- a/net/ghttp/ghttp_request.go +++ b/net/ghttp/ghttp_request.go @@ -46,7 +46,7 @@ type Request struct { clientIp string // The parsed client ip for current host used by GetClientIp function. bodyContent []byte // Request body content. isFileRequest bool // A bool marking whether current request is file serving. - view *gview.View // Custom template view engine object for this response. + viewObject *gview.View // Custom template view engine object for this response. viewParams gview.Params // Custom template view variables for this response. } diff --git a/net/ghttp/ghttp_request_view.go b/net/ghttp/ghttp_request_view.go index 372768b18..acccea816 100644 --- a/net/ghttp/ghttp_request_view.go +++ b/net/ghttp/ghttp_request_view.go @@ -8,14 +8,14 @@ package ghttp import "github.com/gogf/gf/os/gview" -// SetView sets template view engine object for this response. +// SetView sets template view engine object for this request. func (r *Request) SetView(view *gview.View) { - r.view = view + r.viewObject = view } -// GetView returns the template view engine object for this response. +// GetView returns the template view engine object for this request. func (r *Request) GetView() *gview.View { - view := r.view + view := r.viewObject if view == nil { view = r.Server.config.View } diff --git a/net/ghttp/ghttp_server_domain.go b/net/ghttp/ghttp_server_domain.go index 1c831d823..1aebd801c 100644 --- a/net/ghttp/ghttp_server_domain.go +++ b/net/ghttp/ghttp_server_domain.go @@ -28,7 +28,6 @@ func (s *Server) Domain(domains string) *Domain { return d } -// 注意该方法是直接绑定方法的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑 func (d *Domain) BindHandler(pattern string, handler HandlerFunc) { for domain, _ := range d.m { d.s.BindHandler(pattern+"@"+domain, handler) @@ -41,7 +40,6 @@ func (d *Domain) doBindHandler(pattern string, handler HandlerFunc, middleware [ } } -// 执行对象方法 func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string) { for domain, _ := range d.m { d.s.BindObject(pattern+"@"+domain, obj, methods...) @@ -54,7 +52,6 @@ func (d *Domain) doBindObject(pattern string, obj interface{}, methods string, m } } -// 执行对象方法注册,methods参数不区分大小写 func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) { for domain, _ := range d.m { d.s.BindObjectMethod(pattern+"@"+domain, obj, method) @@ -67,7 +64,6 @@ func (d *Domain) doBindObjectMethod(pattern string, obj interface{}, method stri } } -// RESTful执行对象注册 func (d *Domain) BindObjectRest(pattern string, obj interface{}) { for domain, _ := range d.m { d.s.BindObjectRest(pattern+"@"+domain, obj) @@ -80,7 +76,6 @@ func (d *Domain) doBindObjectRest(pattern string, obj interface{}, middleware [] } } -// 控制器注册 func (d *Domain) BindController(pattern string, c Controller, methods ...string) { for domain, _ := range d.m { d.s.BindController(pattern+"@"+domain, c, methods...) @@ -93,7 +88,6 @@ func (d *Domain) doBindController(pattern string, c Controller, methods string, } } -// 控制器方法注册,methods参数区分大小写 func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) { for domain, _ := range d.m { d.s.BindControllerMethod(pattern+"@"+domain, c, method) @@ -106,7 +100,6 @@ func (d *Domain) doBindControllerMethod(pattern string, c Controller, method str } } -// RESTful控制器注册 func (d *Domain) BindControllerRest(pattern string, c Controller) { for domain, _ := range d.m { d.s.BindControllerRest(pattern+"@"+domain, c) @@ -119,43 +112,36 @@ func (d *Domain) doBindControllerRest(pattern string, c Controller, middleware [ } } -// 绑定指定的hook回调函数, hook参数的值由ghttp server设定,参数不区分大小写 -// 目前hook支持:Init/Shut func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) { for domain, _ := range d.m { d.s.BindHookHandler(pattern+"@"+domain, hook, handler) } } -// 通过map批量绑定回调函数 func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) { for domain, _ := range d.m { d.s.BindHookHandlerByMap(pattern+"@"+domain, hookmap) } } -// 绑定指定的状态码回调函数 func (d *Domain) BindStatusHandler(status int, handler HandlerFunc) { for domain, _ := range d.m { d.s.setStatusHandler(d.s.statusHandlerKey(status, domain), handler) } } -// 通过map批量绑定状态码回调函数 func (d *Domain) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) { for k, v := range handlerMap { d.BindStatusHandler(k, v) } } -// 注册中间件,绑定到指定的路由规则上,中间件参数支持多个。 func (d *Domain) BindMiddleware(pattern string, handlers ...HandlerFunc) { for domain, _ := range d.m { d.s.BindMiddleware(pattern+"@"+domain, handlers...) } } -// 注册中间件,绑定到全局路由规则("/*")上,中间件参数支持多个。 func (d *Domain) BindMiddlewareDefault(handlers ...HandlerFunc) { for domain, _ := range d.m { d.s.BindMiddleware(gDEFAULT_MIDDLEWARE_PATTERN+"@"+domain, handlers...) diff --git a/net/ghttp/ghttp_server_router_serve.go b/net/ghttp/ghttp_server_router_serve.go index bed47c8b4..a3b728ff9 100644 --- a/net/ghttp/ghttp_server_router_serve.go +++ b/net/ghttp/ghttp_server_router_serve.go @@ -67,6 +67,10 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han // 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理 lists := make([]*glist.List, 0, 16) for k, v := range array { + // In case of double '/' URI, eg: /user//index + if v == "" { + continue + } if _, ok := p.(map[string]interface{})["*list"]; ok { lists = append(lists, p.(map[string]interface{})["*list"].(*glist.List)) } diff --git a/os/gsession/gsession_manager.go b/os/gsession/gsession_manager.go index 12a05e047..156ea9bff 100644 --- a/os/gsession/gsession_manager.go +++ b/os/gsession/gsession_manager.go @@ -35,6 +35,8 @@ func New(ttl time.Duration, storage ...Storage) *Manager { } // New creates or fetches the session for given session id. +// The parameter is optional, it creates a new one if not it's passed +// depending on Storage.New. func (m *Manager) New(sessionId ...string) *Session { var id string if len(sessionId) > 0 && sessionId[0] != "" { diff --git a/os/gview/gview_doparse.go b/os/gview/gview_doparse.go index 021dfbbb1..a067b37fe 100644 --- a/os/gview/gview_doparse.go +++ b/os/gview/gview_doparse.go @@ -30,14 +30,15 @@ import ( const ( // Template name for content parsing. - gCONTENT_TEMPLATE_NAME = "template content" + gCONTENT_TEMPLATE_NAME = "TemplateContent" ) var ( // Templates cache map for template folder. - // TODO Note that there's no expiring logic for this map. - templates = gmap.NewStrAnyMap(true) - resourceTryFiles = []string{"template/", "template", "/template", "/template/"} + // Note that there's no expiring logic for this map. + templates = gmap.NewStrAnyMap(true) + // Try-folders for resource template file searching. + resourceTryFolders = []string{"template/", "template", "/template", "/template/"} ) // fileCacheItem is the cache item for template file. @@ -91,12 +92,12 @@ func (view *View) Parse(file string, params ...Params) (result string, err error return "", nil } // Get the template object instance for . - tpl, err = view.getTemplate(item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path))) + tpl, err = view.getTemplate(item.path, item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path))) if err != nil { return "", err } // Using memory lock to ensure concurrent safety for template parsing. - gmlock.LockFunc("gview.Parse:"+item.folder, func() { + gmlock.LockFunc("gview.Parse:"+item.path, func() { tpl, err = tpl.Parse(item.content) }) @@ -156,7 +157,7 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) err := (error)(nil) key := fmt.Sprintf("%s_%v", gCONTENT_TEMPLATE_NAME, view.delimiters) tpl := templates.GetOrSetFuncLock(key, func() interface{} { - return template.New(key).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap).Option("missingkey=zero") + return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) }).(*template.Template) // Using memory lock to ensure concurrent safety for content parsing. hash := strconv.FormatUint(ghash.DJBHash64([]byte(content)), 10) @@ -207,17 +208,20 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) //return result, nil } -// getTemplate returns the template object associated with given template folder . +// getTemplate returns the template object associated with given template file . // It uses template cache to enhance performance, that is, it will return the same template object // with the same given . It will also automatically refresh the template cache // if the template files under changes (recursively). -func (view *View) getTemplate(path string, pattern string) (tpl *template.Template, err error) { - key := fmt.Sprintf("%s_%v", path, view.delimiters) +func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl *template.Template, err error) { + // Key for template cache. + key := fmt.Sprintf("%s_%v", filePath, view.delimiters) result := templates.GetOrSetFuncLock(key, func() interface{} { - tpl = template.New(key).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) + // Do not use but the as the parameter for template.New, + // because when error occurs the will be printed out. + tpl = template.New(filePath).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) // Firstly checking the resource manager. if !gres.IsEmpty() { - if files := gres.ScanDirFile(path, pattern, true); len(files) > 0 { + if files := gres.ScanDirFile(folderPath, pattern, true); len(files) > 0 { var err error for _, v := range files { _, err = tpl.New(v.FileInfo().Name()).Option("missingkey=zero").Parse(string(v.Content())) @@ -231,7 +235,7 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa // Secondly checking the file system. var files []string - files, err = gfile.ScanDir(path, pattern, true) + files, err = gfile.ScanDir(folderPath, pattern, true) if err != nil { return nil } @@ -247,17 +251,20 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa } // searchFile returns the found absolute path for and its template folder path. +// Note that, the returned is the template folder path, but not the folder of +// the returned template file . func (view *View) searchFile(file string) (path string, folder string, resource *gres.File, err error) { // Firstly checking the resource manager. if !gres.IsEmpty() { - for _, v := range resourceTryFiles { - if resource = gres.Get(v + file); resource != nil { + // Try folders. + for _, folderPath := range resourceTryFolders { + if resource = gres.Get(folderPath + file); resource != nil { path = resource.Name() - folder = v + folder = folderPath return } } - + // Search folders. view.paths.RLockFunc(func(array []string) { for _, v := range array { v = strings.TrimRight(v, "/"+gfile.Separator) @@ -278,14 +285,14 @@ func (view *View) searchFile(file string) (path string, folder string, resource // Secondly checking the file system. if path == "" { view.paths.RLockFunc(func(array []string) { - for _, v := range array { - v = strings.TrimRight(v, gfile.Separator) - if path, _ = gspath.Search(v, file); path != "" { - folder = v + for _, folderPath := range array { + folderPath = strings.TrimRight(folderPath, gfile.Separator) + if path, _ = gspath.Search(folderPath, file); path != "" { + folder = folderPath break } - if path, _ = gspath.Search(v+gfile.Separator+"template", file); path != "" { - folder = v + gfile.Separator + "template" + if path, _ = gspath.Search(folderPath+gfile.Separator+"template", file); path != "" { + folder = folderPath + gfile.Separator + "template" break } } @@ -299,14 +306,14 @@ func (view *View) searchFile(file string) (path string, folder string, resource buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file)) view.paths.RLockFunc(func(array []string) { index := 1 - for _, v := range array { - v = strings.TrimRight(v, "/") - if v == "" { - v = "/" + for _, folderPath := range array { + folderPath = strings.TrimRight(folderPath, "/") + if folderPath == "" { + folderPath = "/" } - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, folderPath)) index++ - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, strings.TrimRight(v, "/")+gfile.Separator+"template")) + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, strings.TrimRight(folderPath, "/")+gfile.Separator+"template")) index++ } }) diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index b34b29318..19a310938 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -20,9 +20,8 @@ type apiMapStrAny interface { MapStrAny() map[string]interface{} } -// Map converts any variable to map[string]interface{}. -// -// If the parameter is not a map/struct/*struct type, then the conversion will fail and returns nil. +// Map converts any variable to map[string]interface{}. If the parameter is not a +// map/struct/*struct type, then the conversion will fail and returns nil. // // If is a struct/*struct object, the second parameter specifies the most priority // tags that will be detected, otherwise it detects the tags in order of: gconv, json, and then the field name. @@ -167,8 +166,10 @@ func Map(value interface{}, tags ...string) map[string]interface{} { } } -// MapDeep do Map function recursively. -// See Map. +// MapDeep does Map function recursively, which means if the attribute of +// is also a struct/*struct, calls Map function on this attribute converting it to +// a map[string]interface{} type variable. +// Also see Map. func MapDeep(value interface{}, tags ...string) map[string]interface{} { data := Map(value, tags...) for key, value := range data {