mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 11:18:02 +08:00
add prefix feature for gdb
This commit is contained in:
parent
2804834540
commit
34ef0ea792
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取是否开启调试服务
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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...)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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] != "" {
|
||||
|
@ -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++
|
||||
}
|
||||
})
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user