add prefix feature for gdb

This commit is contained in:
John 2019-12-10 21:14:15 +08:00
parent 2804834540
commit 34ef0ea792
14 changed files with 180 additions and 150 deletions

View File

@ -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 <name> 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 {

View File

@ -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() {

View File

@ -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)
}
}
// 获取是否开启调试服务

View File

@ -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 <table> can be used with alias like "user as u" or "user u".
// And also, the <table> 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 {

View File

@ -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)
}

View File

@ -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")
})
}

View File

@ -25,11 +25,10 @@ import (
"github.com/gogf/gf/util/gconv"
)
// New creates a Json object with any variable type of <data>,
// but <data> should be a map or slice for data access reason,
// or it will make no sense.
// The <unsafe> 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 <data>, but <data> should be a map or slice
// for data access reason, or it will make no sense.
// The <safe> 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),

View File

@ -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.
}

View File

@ -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
}

View File

@ -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...)

View File

@ -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))
}

View File

@ -35,6 +35,8 @@ func New(ttl time.Duration, storage ...Storage) *Manager {
}
// New creates or fetches the session for given session id.
// The parameter <sessionId> 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] != "" {

View File

@ -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 <folder>.
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 <path>.
// getTemplate returns the template object associated with given template file <path>.
// It uses template cache to enhance performance, that is, it will return the same template object
// with the same given <path>. It will also automatically refresh the template cache
// if the template files under <path> 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 <key> but the <path> as the parameter <name> for template.New,
// because when error occurs the <name> 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 <file> and its template folder path.
// Note that, the returned <folder> is the template folder path, but not the folder of
// the returned template file <path>.
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++
}
})

View File

@ -20,9 +20,8 @@ type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
// Map converts any variable <value> to map[string]interface{}.
//
// If the parameter <value> is not a map/struct/*struct type, then the conversion will fail and returns nil.
// Map converts any variable <value> to map[string]interface{}. If the parameter <value> is not a
// map/struct/*struct type, then the conversion will fail and returns nil.
//
// If <value> is a struct/*struct object, the second parameter <tags> 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 <value>
// 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 {