From 07476a434900aa1c2612fe021b739ecdf53060b4 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Apr 2019 14:37:46 +0800 Subject: [PATCH] add instance management feature for gdb/gredis; add customized configuration content management feature for gcfg; update gjson for data content type check --- g/database/gdb/gdb.go | 45 +++++++++++---- g/database/gdb/gdb_config.go | 72 ++++++++++++++---------- g/database/gdb/gdb_mssql.go | 4 +- g/database/gdb/gdb_mysql.go | 4 +- g/database/gdb/gdb_oracle.go | 4 +- g/database/gdb/gdb_pgsql.go | 4 +- g/database/gdb/gdb_sqlite.go | 4 +- g/database/gdb/gdb_unit_basic_test.go | 28 ++++++++++ g/database/gredis/gredis.go | 50 ++++++++++++++--- g/database/gredis/gredis_config.go | 69 +++++++++++++++++++++++ g/database/gredis/gredis_unit_test.go | 27 +++++++++ g/encoding/gjson/gjson.go | 8 ++- g/frame/gins/gins.go | 77 +++++++++++++++----------- g/net/gtcp/gtcp_server.go | 2 +- g/os/gcfg/gcfg.go | 49 ++++++++-------- g/os/gcfg/gcfg_config.go | 34 ++++++++++++ g/os/gcfg/gcfg_z_unit_test.go | 55 ++++++++++++++++++ g/os/gcron/gcron_cron.go | 12 ++-- g/os/gcron/gcron_entry.go | 3 +- g/os/gcron/gcron_unit_1_test.go | 18 ++++++ geg/net/ghttp/server/nethttp_server.go | 19 +++++++ geg/net/gudp/gudp_server.go | 5 +- geg/other/test.go | 17 ++---- third/README.MD | 2 +- 24 files changed, 472 insertions(+), 140 deletions(-) create mode 100644 g/database/gdb/gdb_unit_basic_test.go create mode 100644 g/database/gredis/gredis_config.go create mode 100644 g/os/gcfg/gcfg_config.go create mode 100644 geg/net/ghttp/server/nethttp_server.go diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 12ed83f0b..bbc2175aa 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -14,6 +14,7 @@ import ( "database/sql" "errors" "fmt" + "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gring" "github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gvar" @@ -155,22 +156,26 @@ const ( gDEFAULT_BATCH_NUM = 10 // 默认的连接池连接存活时间(秒) gDEFAULT_CONN_MAX_LIFE_TIME = 30 +) +var ( + // 单例对象Map + instances = gmap.NewStringInterfaceMap() ) // 使用默认/指定分组配置进行连接,数据库集群配置项:default -func New(groupName ...string) (db DB, err error) { - group := config.d - if len(groupName) > 0 { - group = groupName[0] +func New(name...string) (db DB, err error) { + group := configs.defaultGroup + if len(name) > 0 { + group = name[0] } - config.RLock() - defer config.RUnlock() + configs.RLock() + defer configs.RUnlock() - if len(config.c) < 1 { + if len(configs.config) < 1 { return nil, errors.New("empty database configuration") } - if _, ok := config.c[group]; ok { + if _, ok := configs.config[group]; ok { if node, err := getConfigNodeByGroup(group, true); err == nil { base := &dbBase { group : group, @@ -204,9 +209,25 @@ func New(groupName ...string) (db DB, err error) { } } +// 获得数据库操作对象单例 +func Instance(name...string) (db DB, err error) { + group := configs.defaultGroup + if len(name) > 0 { + group = name[0] + } + v := instances.GetOrSetFuncLock(group, func() interface{} { + db, err = New(group) + return db + }) + if v != nil { + return v.(DB), nil + } + return +} + // 获取指定数据库角色的一个配置项,内部根据权重计算负载均衡 func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { - if list, ok := config.c[group]; ok { + if list, ok := configs.config[group]; ok { // 将master, slave集群列表拆分出来 masterList := make(ConfigGroup, 0) slaveList := make(ConfigGroup, 0) @@ -319,17 +340,17 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { return } -// 切换操作的数据库(注意该切换是全局的) +// 切换当前数据库对象操作的数据库。 func (bs *dbBase) SetSchema(schema string) { bs.schema.Set(schema) } -// 创建底层数据库master链接对象 +// 创建底层数据库master链接对象。 func (bs *dbBase) Master() (*sql.DB, error) { return bs.getSqlDb(true) } -// 创建底层数据库slave链接对象 +// 创建底层数据库slave链接对象。 func (bs *dbBase) Slave() (*sql.DB, error) { return bs.getSqlDb(false) } diff --git a/g/database/gdb/gdb_config.go b/g/database/gdb/gdb_config.go index 179fde46f..726a3c82f 100644 --- a/g/database/gdb/gdb_config.go +++ b/g/database/gdb/gdb_config.go @@ -17,14 +17,7 @@ const ( DEFAULT_GROUP_NAME = "default" // 默认配置名称 ) -// 数据库配置包内对象 -var config struct { - sync.RWMutex - c Config // 数据库配置 - d string // 默认数据库分组名称 -} - -// 数据库配置 +// 数据库分组配置 type Config map[string]ConfigGroup // 数据库集群配置 @@ -41,12 +34,19 @@ type ConfigNode struct { Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave Charset string // (可选,默认为 utf8)编码,默认为 utf8 Priority int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 - Linkinfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) + LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) MaxIdleConnCount int // (可选)连接池最大限制的连接数 MaxOpenConnCount int // (可选)连接池最大打开的连接数 MaxConnLifetime int // (可选,单位秒)连接对象可重复使用的时间长度 } +// 数据库配置包内对象 +var configs struct { + sync.RWMutex // 并发安全互斥锁 + config Config // 数据库分组配置 + defaultGroup string // 默认数据库分组名称 +} + // 数据库集群配置示例,支持主从处理,多数据库集群支持 /* var DatabaseConfiguration = Config { @@ -80,29 +80,32 @@ var DatabaseConfiguration = Config { // 包初始化 func init() { - config.c = make(Config) - config.d = DEFAULT_GROUP_NAME + configs.config = make(Config) + configs.defaultGroup = DEFAULT_GROUP_NAME } // 设置当前应用的数据库配置信息,进行全局数据库配置覆盖操作 -func SetConfig (c Config) { - config.Lock() - defer config.Unlock() - config.c = c +func SetConfig (config Config) { + defer instances.Clear() + configs.Lock() + defer configs.Unlock() + configs.config = config } // 添加数据库服务器集群配置 func AddConfigGroup (group string, nodes ConfigGroup) { - config.Lock() - config.c[group] = nodes - config.Unlock() + defer instances.Clear() + configs.Lock() + defer configs.Unlock() + configs.config[group] = nodes } // 添加一台数据库服务器配置 func AddConfigNode (group string, node ConfigNode) { - config.Lock() - config.c[group] = append(config.c[group], node) - config.Unlock() + defer instances.Clear() + configs.Lock() + defer configs.Unlock() + configs.config[group] = append(configs.config[group], node) } // 添加默认链接的一台数据库服务器配置 @@ -117,16 +120,25 @@ func AddDefaultConfigGroup (nodes ConfigGroup) { // 添加一台数据库服务器配置 func GetConfig (group string) ConfigGroup { - config.RLock() - defer config.RUnlock() - return config.c[group] + configs.RLock() + defer configs.RUnlock() + return configs.config[group] } // 设置默认链接的数据库链接配置项(默认是 default) -func SetDefaultGroup (groupName string) { - config.Lock() - config.d = groupName - config.Unlock() +func SetDefaultGroup (name string) { + defer instances.Clear() + configs.Lock() + defer configs.Unlock() + configs.defaultGroup = name +} + +// 获取默认链接的数据库链接配置项(默认是 default) +func GetDefaultGroup() string { + defer instances.Clear() + configs.Lock() + defer configs.Unlock() + return configs.defaultGroup } // 设置数据库连接池中空闲链接的大小 @@ -147,8 +159,8 @@ func (bs *dbBase) SetConnMaxLifetime(n int) { // 节点配置转换为字符串 func (node *ConfigNode) String() string { - if node.Linkinfo != "" { - return node.Linkinfo + if node.LinkInfo != "" { + return node.LinkInfo } return fmt.Sprintf(`%s@%s:%s,%s,%s,%s,%s,%d-%d-%d`, node.User, node.Host, node.Port, node.Name, node.Type, node.Role, node.Charset, diff --git a/g/database/gdb/gdb_mssql.go b/g/database/gdb/gdb_mssql.go index 916441d5c..7ad243643 100644 --- a/g/database/gdb/gdb_mssql.go +++ b/g/database/gdb/gdb_mssql.go @@ -30,8 +30,8 @@ type dbMssql struct { // 创建SQL操作对象 func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) { source := "" - if config.Linkinfo != "" { - source = config.Linkinfo + if config.LinkInfo != "" { + source = config.LinkInfo } else { source = fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable", config.User, config.Pass, config.Host, config.Port, config.Name) diff --git a/g/database/gdb/gdb_mysql.go b/g/database/gdb/gdb_mysql.go index fc3f519e4..66b24b64c 100644 --- a/g/database/gdb/gdb_mysql.go +++ b/g/database/gdb/gdb_mysql.go @@ -20,8 +20,8 @@ type dbMysql struct { // 创建SQL操作对象,内部采用了lazy link处理 func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) { var source string - if config.Linkinfo != "" { - source = config.Linkinfo + if config.LinkInfo != "" { + source = config.LinkInfo } else { source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true", config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset) diff --git a/g/database/gdb/gdb_oracle.go b/g/database/gdb/gdb_oracle.go index 1a7a3768d..5df9057c1 100644 --- a/g/database/gdb/gdb_oracle.go +++ b/g/database/gdb/gdb_oracle.go @@ -30,8 +30,8 @@ type dbOracle struct { // 创建SQL操作对象 func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.Linkinfo != "" { - source = config.Linkinfo + if config.LinkInfo != "" { + source = config.LinkInfo } else { source = fmt.Sprintf("%s/%s@%s", config.User, config.Pass, config.Name) } diff --git a/g/database/gdb/gdb_pgsql.go b/g/database/gdb/gdb_pgsql.go index b9f319075..7f4ac9af5 100644 --- a/g/database/gdb/gdb_pgsql.go +++ b/g/database/gdb/gdb_pgsql.go @@ -26,8 +26,8 @@ type dbPgsql struct { // 创建SQL操作对象,内部采用了lazy link处理 func (db *dbPgsql) Open (config *ConfigNode) (*sql.DB, error) { var source string - if config.Linkinfo != "" { - source = config.Linkinfo + if config.LinkInfo != "" { + source = config.LinkInfo } else { source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", config.User, config.Pass, config.Host, config.Port, config.Name) } diff --git a/g/database/gdb/gdb_sqlite.go b/g/database/gdb/gdb_sqlite.go index 93716d07c..c83a07086 100644 --- a/g/database/gdb/gdb_sqlite.go +++ b/g/database/gdb/gdb_sqlite.go @@ -24,8 +24,8 @@ type dbSqlite struct { func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.Linkinfo != "" { - source = config.Linkinfo + if config.LinkInfo != "" { + source = config.LinkInfo } else { source = config.Name } diff --git a/g/database/gdb/gdb_unit_basic_test.go b/g/database/gdb/gdb_unit_basic_test.go new file mode 100644 index 000000000..b816a853c --- /dev/null +++ b/g/database/gdb/gdb_unit_basic_test.go @@ -0,0 +1,28 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// 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. + +package gdb_test + +import ( + "github.com/gogf/gf/g/database/gdb" + "github.com/gogf/gf/g/test/gtest" + "testing" +) + +func Test_Instance(t *testing.T) { + gtest.Case(t, func() { + _, err := gdb.Instance("none") + gtest.AssertNE(err, nil) + + db, err := gdb.Instance() + gtest.Assert(err, nil) + + err1 := db.PingMaster() + err2 := db.PingSlave() + gtest.Assert(err1, nil) + gtest.Assert(err2, nil) + }) +} diff --git a/g/database/gredis/gredis.go b/g/database/gredis/gredis.go index 1b503f699..45d25abb3 100644 --- a/g/database/gredis/gredis.go +++ b/g/database/gredis/gredis.go @@ -4,10 +4,11 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gredis provides client for redis server. +// Package gredis provides convenient client for redis server. // // Redis客户端. -// Redis中文手册文档请参考:http://redisdoc.com/ , Redis官方命令请参考:https://redis.io/commands +// Redis中文手册请参考:http://redisdoc.com/ +// Redis官方命令请参考:https://redis.io/commands package gredis import ( @@ -24,8 +25,9 @@ const ( // Redis客户端(管理连接池) type Redis struct { - pool *redis.Pool - config Config + pool *redis.Pool // 底层连接池 + group string // 配置分组 + config Config // 配置对象 } // Redis连接对象(连接池中的单个连接) @@ -48,13 +50,17 @@ type PoolStats struct { redis.PoolStats } -// 连接池map -var pools = gmap.NewStringInterfaceMap() +var ( + // 单例对象Map + instances = gmap.NewStringInterfaceMap() + // 连接池Map + pools = gmap.NewStringInterfaceMap() +) // New creates a redis client object with given configuration. // Redis client maintains a connection pool automatically. // -// 创建redis操作对象. +// 创建redis操作对象,底层根据配置信息公用的连接池(连接池单例)。 func New(config Config) *Redis { if config.IdleTimeout == 0 { config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT @@ -96,11 +102,41 @@ func New(config Config) *Redis { } } +// Instance returns an instance of redis client with specified group. +// The param is unnecessary, if is not passed, +// return redis instance with default group. +// +// 获取指定分组名称的Redis单例对象,底层根据配置信息公用的连接池(连接池单例)。 +func Instance(name...string) *Redis { + group := DEFAULT_GROUP_NAME + if len(name) > 0 { + group = name[0] + } + v := instances.GetOrSetFuncLock(group, func() interface{} { + if config, ok := GetConfig(group); ok { + r := New(config) + r.group = group + return r + } + return nil + }) + if v != nil { + return v.(*Redis) + } + return nil +} + // Close closes the redis connection pool, // it will release all connections reserved by this pool. +// It always not necessary to call Close manually. // // 关闭redis管理对象,将会关闭底层的连接池。 +// 往往没必要手动调用,跟随进程销毁即可。 func (r *Redis) Close() error { + if r.group != "" { + // 如果是单例对象,那么需要从单例对象Map中删除 + instances.Remove(r.group) + } pools.Remove(fmt.Sprintf("%v", r.config)) return r.pool.Close() } diff --git a/g/database/gredis/gredis_config.go b/g/database/gredis/gredis_config.go new file mode 100644 index 000000000..82f43c68f --- /dev/null +++ b/g/database/gredis/gredis_config.go @@ -0,0 +1,69 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// 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. + +package gredis + +import "github.com/gogf/gf/g/container/gmap" + +const ( + // 默认分组名称 + DEFAULT_GROUP_NAME = "default" +) +var ( + // 分组配置 + configs = gmap.NewStringInterfaceMap() +) + +// SetConfig sets the global configuration for specified group. +// If is not passed, it sets configuration for the default group name. +// +// 设置全局分组配置,name为非必需参数,默认为默认分组名称。 +func SetConfig(config Config, name...string) { + group := DEFAULT_GROUP_NAME + if len(name) > 0 { + group = name[0] + } + configs.Set(group, config) + instances.Remove(group) +} + +// GetConfig returns the global configuration with specified group. +// If is not passed, it returns configuration of the default group name. +// +// 获取指定全局分组配置,group为非必需参数,默认为默认分组名称。 +func GetConfig(name...string) (config Config, ok bool) { + group := DEFAULT_GROUP_NAME + if len(name) > 0 { + group = name[0] + } + if v := configs.Get(group); v != nil { + return v.(Config), true + } + return Config{}, false +} + +// RemoveConfig removes the global configuration with specified group. +// If is not passed, it removes configuration of the default group name. +// +// 删除指定全局分组配置,name为非必需参数,默认为默认分组名称。 +func RemoveConfig(name...string) { + group := DEFAULT_GROUP_NAME + if len(name) > 0 { + group = name[0] + } + configs.Remove(group) + instances.Remove(group) +} + +// ClearConfig removes all configurations and instances of redis. +// +// 清除所有的配置内容。 +func ClearConfig() { + configs.Clear() + instances.Clear() +} + + diff --git a/g/database/gredis/gredis_unit_test.go b/g/database/gredis/gredis_unit_test.go index d3a16c825..7b3a37384 100644 --- a/g/database/gredis/gredis_unit_test.go +++ b/g/database/gredis/gredis_unit_test.go @@ -99,6 +99,33 @@ func Test_Conn(t *testing.T) { conn := redis.Conn() defer conn.Close() + + r, err := conn.Do("GET", "k") + gtest.Assert(err, nil) + gtest.Assert(r, []byte("v")) + + _, err = conn.Do("DEL", "k") + gtest.Assert(err, nil) + r, err = conn.Do("GET", "k") + gtest.Assert(err, nil) + gtest.Assert(r, nil) + }) +} + +func Test_Instance(t *testing.T) { + gtest.Case(t, func() { + group := "my-test" + gredis.SetConfig(config, group) + defer gredis.RemoveConfig(group) + redis := gredis.Instance(group) + defer redis.Close() + + conn := redis.Conn() + defer conn.Close() + + _, err := conn.Do("SET", "k", "v") + gtest.Assert(err, nil) + r, err := conn.Do("GET", "k") gtest.Assert(err, nil) gtest.Assert(r, []byte("v")) diff --git a/g/encoding/gjson/gjson.go b/g/encoding/gjson/gjson.go index 8efa70421..bdacc4578 100644 --- a/g/encoding/gjson/gjson.go +++ b/g/encoding/gjson/gjson.go @@ -138,9 +138,9 @@ func LoadContent(data interface{}, dataType...string) (*Json, error) { t = "json" } else if gregex.IsMatch(`<.+>.*`, b) { t = "xml" - } else if gregex.IsMatch(`\w+\s*:\s*.+`, b) { + } else if gregex.IsMatch(`\n[\s\t]*\w+\s*:\s*.+`, b) { t = "yml" - } else if gregex.IsMatch(`\w+\s*=\s*.+`, b) { + } else if gregex.IsMatch(`\n[\s\t]*\w+\s*=\s*.+`, b) { t = "toml" } } @@ -169,6 +169,10 @@ func LoadContent(data interface{}, dataType...string) (*Json, error) { if err := decoder.Decode(&result); err != nil { return nil, err } + switch result.(type) { + case string, []byte: + return nil, fmt.Errorf(`json decoding failed for content: %s`, string(b)) + } } return New(result), nil } diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 1c4586b24..d9966b973 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -4,10 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gins provides instances management and some core components. -// -// 单例对象管理. -// 框架内置了一些核心对象获取方法,并且可以通过Set和Get方法实现IoC以及对内置核心对象的自定义替换 +// Package gins provides instances management and core components management. package gins import ( @@ -21,9 +18,9 @@ import ( "github.com/gogf/gf/g/os/gfsnotify" "github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/os/gview" + "github.com/gogf/gf/g/text/gregex" "github.com/gogf/gf/g/text/gstr" "github.com/gogf/gf/g/util/gconv" - "github.com/gogf/gf/g/text/gregex" "time" ) @@ -101,7 +98,8 @@ func Config(file...string) *gcfg.Config { if len(file) > 0 { configFile = file[0] } - return instances.GetOrSetFuncLock(fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile), + key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile) + return instances.GetOrSetFuncLock(key, func() interface{} { // 默认当前工作目录 config := gcfg.New(gfile.Pwd(), configFile) @@ -139,62 +137,66 @@ func Database(name...string) gdb.DB { for group, v := range m { cg := gdb.ConfigGroup{} if list, ok := v.([]interface{}); ok { - for _, nodev := range list { - node := gdb.ConfigNode{} - nodem := nodev.(map[string]interface{}) - if value, ok := nodem["host"]; ok { + for _, nodeValue := range list { + node := gdb.ConfigNode{} + nodeMap := nodeValue.(map[string]interface{}) + if value, ok := nodeMap["host"]; ok { node.Host = gconv.String(value) } - if value, ok := nodem["port"]; ok { + if value, ok := nodeMap["port"]; ok { node.Port = gconv.String(value) } - if value, ok := nodem["user"]; ok { + if value, ok := nodeMap["user"]; ok { node.User = gconv.String(value) } - if value, ok := nodem["pass"]; ok { + if value, ok := nodeMap["pass"]; ok { node.Pass = gconv.String(value) } - if value, ok := nodem["name"]; ok { + if value, ok := nodeMap["name"]; ok { node.Name = gconv.String(value) } - if value, ok := nodem["type"]; ok { + if value, ok := nodeMap["type"]; ok { node.Type = gconv.String(value) } - if value, ok := nodem["role"]; ok { + if value, ok := nodeMap["role"]; ok { node.Role = gconv.String(value) } - if value, ok := nodem["charset"]; ok { + if value, ok := nodeMap["charset"]; ok { node.Charset = gconv.String(value) } - if value, ok := nodem["priority"]; ok { + if value, ok := nodeMap["priority"]; ok { node.Priority = gconv.Int(value) } // Deprecated - if value, ok := nodem["linkinfo"]; ok { - node.Linkinfo = gconv.String(value) - } - if value, ok := nodem["linkInfo"]; ok { - node.Linkinfo = gconv.String(value) + if value, ok := nodeMap["linkinfo"]; ok { + node.LinkInfo = gconv.String(value) } // Deprecated - if value, ok := nodem["max-idle"]; ok { + if value, ok := nodeMap["link-info"]; ok { + node.LinkInfo = gconv.String(value) + } + if value, ok := nodeMap["linkInfo"]; ok { + node.LinkInfo = gconv.String(value) + } + // Deprecated + if value, ok := nodeMap["max-idle"]; ok { node.MaxIdleConnCount = gconv.Int(value) } - if value, ok := nodem["maxIdle"]; ok { + if value, ok := nodeMap["maxIdle"]; ok { node.MaxIdleConnCount = gconv.Int(value) } // Deprecated - if value, ok := nodem["max-open"]; ok { + if value, ok := nodeMap["max-open"]; ok { node.MaxOpenConnCount = gconv.Int(value) } - if value, ok := nodem["maxOpen"]; ok { + if value, ok := nodeMap["maxOpen"]; ok { node.MaxOpenConnCount = gconv.Int(value) } // Deprecated - if value, ok := nodem["max-lifetime"]; ok { + if value, ok := nodeMap["max-lifetime"]; ok { node.MaxConnLifetime = gconv.Int(value) } - if value, ok := nodem["maxLifetime"]; ok { + if value, ok := nodeMap["maxLifetime"]; ok { node.MaxConnLifetime = gconv.Int(value) } cg = append(cg, node) @@ -202,10 +204,7 @@ func Database(name...string) gdb.DB { } gdb.AddConfigGroup(group, cg) } - // 使用gfsnotify进行文件监控,当配置文件有任何变化时,清空数据库配置缓存 - gfsnotify.Add(config.GetFilePath(), func(event *gfsnotify.Event) { - instances.Remove(key) - }) + addConfigMonitor(key) } if db, err := gdb.New(name...); err == nil { return db @@ -254,10 +253,12 @@ func Redis(name...string) *gredis.Redis { if v, ok := parse["maxConnLifetime"]; ok { config.MaxConnLifetime = gconv.TimeDuration(v)*time.Second } + addConfigMonitor(key) return gredis.New(config) } array, _ = gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line) if len(array) == 5 { + addConfigMonitor(key) return gredis.New(gredis.Config{ Host : array[1], Port : gconv.Int(array[2]), @@ -281,6 +282,16 @@ func Redis(name...string) *gredis.Redis { return nil } +// 添加对单例对象的配置文件inotify监控 +func addConfigMonitor(key string) { + // 使用gfsnotify进行文件监控,当配置文件有任何变化时,清空对象单例缓存 + if path := Config().GetFilePath(); path != "" { + gfsnotify.Add(path, func(event *gfsnotify.Event) { + instances.Remove(key) + }) + } +} + // 模板内置方法:config func funcConfig(pattern string, file...string) string { return Config().GetString(pattern, file...) diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index f547481bc..eb68cedda 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -30,7 +30,7 @@ var serverMapping = gmap.NewStringInterfaceMap() // 获取/创建一个空配置的TCP Server // 单例模式,请保证name的唯一性 -func GetServer(name...interface{}) (*Server) { +func GetServer(name...interface{}) *Server { serverName := gDEFAULT_SERVER if len(name) > 0 { serverName = gconv.String(name[0]) diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index a046ee02e..0762c9781 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -4,10 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gcfg provides reading, caching and managing for configuration files. -// -// 配置管理, -// 配置文件格式支持:json, xml, toml, yaml/yml +// Package gcfg provides reading, caching and managing for configuration files/contents. package gcfg import ( @@ -26,21 +23,20 @@ import ( ) const ( - // 默认的配置管理文件名称 + // Default configuration file name. DEFAULT_CONFIG_FILE = "config.toml" ) -// 配置管理对象 +// Configuration struct. type Config struct { - name *gtype.String // 默认配置文件名称 - paths *garray.StringArray // 搜索目录路径 - jsons *gmap.StringInterfaceMap // 配置文件对象 - vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率) + name *gtype.String // Default configuration file name. + paths *garray.StringArray // Searching path array. + jsons *gmap.StringInterfaceMap // The pared JSON objects for configuration files. + vc *gtype.Bool // Whether do violence check in value index searching. + // It affects the performance when set true(false in default). } // New returns a new configuration management object. -// -// 生成一个配置管理对象 func New(path string, file...string) *Config { name := DEFAULT_CONFIG_FILE if len(file) > 0 { @@ -59,8 +55,6 @@ func New(path string, file...string) *Config { } // filePath returns the absolute configuration file path for the given filename by . -// -// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径 func (c *Config) filePath(file...string) (path string) { name := c.name.Val() if len(file) > 0 { @@ -228,19 +222,30 @@ func (c *Config) getJson(file...string) *gjson.Json { name = file[0] } r := c.jsons.GetOrSetFuncLock(name, func() interface{} { - filePath := c.filePath(file...) - if filePath == "" { - return nil + content := "" + filePath := "" + if content = GetContent(name); content == "" { + filePath = c.filePath(name) + if filePath == "" { + return nil + } + content = gfile.GetContents(filePath) } - if j, err := gjson.Load(filePath); err == nil { + if j, err := gjson.LoadContent(content); err == nil { j.SetViolenceCheck(c.vc.Val()) // 添加配置文件监听,如果有任何变化,删除文件内容缓存,下一次查询会自动更新 - gfsnotify.Add(filePath, func(event *gfsnotify.Event) { - c.jsons.Remove(name) - }) + if filePath != "" { + gfsnotify.Add(filePath, func(event *gfsnotify.Event) { + c.jsons.Remove(name) + }) + } return j } else { - glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) + if filePath != "" { + glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) + } else { + glog.Criticalfln(`[gcfg] Load configuration failed: %s`, err.Error()) + } } return nil }) diff --git a/g/os/gcfg/gcfg_config.go b/g/os/gcfg/gcfg_config.go new file mode 100644 index 000000000..16fe3b9c6 --- /dev/null +++ b/g/os/gcfg/gcfg_config.go @@ -0,0 +1,34 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// 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. + +package gcfg + +import "github.com/gogf/gf/g/container/gmap" + +var ( + // Customized configuration content. + configs = gmap.NewStringStringMap() +) + +// SetContent sets customized configuration content for specified . +// The is unnecessary param, default is DEFAULT_CONFIG_FILE. +func SetContent(content string, file...string) { + name := DEFAULT_CONFIG_FILE + if len(file) > 0 { + name = file[0] + } + configs.Set(name, content) +} + +// GetContent returns customized configuration content for specified . +// The is unnecessary param, default is DEFAULT_CONFIG_FILE. +func GetContent(file...string) string { + name := DEFAULT_CONFIG_FILE + if len(file) > 0 { + name = file[0] + } + return configs.Get(name) +} \ No newline at end of file diff --git a/g/os/gcfg/gcfg_z_unit_test.go b/g/os/gcfg/gcfg_z_unit_test.go index 7e54bd513..0f1d7c2c2 100644 --- a/g/os/gcfg/gcfg_z_unit_test.go +++ b/g/os/gcfg/gcfg_z_unit_test.go @@ -75,3 +75,58 @@ array = [1,2,3] }) } + +func Test_Content(t *testing.T) { + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + gcfg.SetContent(content) + gtest.Case(t, func() { + c := gcfg.New(".") + gtest.Assert(c.Get("v1"), 1) + gtest.AssertEQ(c.GetInt("v1"), 1) + gtest.AssertEQ(c.GetInt8("v1"), int8(1)) + gtest.AssertEQ(c.GetInt16("v1"), int16(1)) + gtest.AssertEQ(c.GetInt32("v1"), int32(1)) + gtest.AssertEQ(c.GetInt64("v1"), int64(1)) + gtest.AssertEQ(c.GetUint("v1"), uint(1)) + gtest.AssertEQ(c.GetUint8("v1"), uint8(1)) + gtest.AssertEQ(c.GetUint16("v1"), uint16(1)) + gtest.AssertEQ(c.GetUint32("v1"), uint32(1)) + gtest.AssertEQ(c.GetUint64("v1"), uint64(1)) + + gtest.AssertEQ(c.GetVar("v1").String(), "1") + gtest.AssertEQ(c.GetVar("v1").Bool(), true) + gtest.AssertEQ(c.GetVar("v2").String(), "true") + gtest.AssertEQ(c.GetVar("v2").Bool(), true) + + gtest.AssertEQ(c.GetString("v1"), "1") + gtest.AssertEQ(c.GetFloat32("v4"), float32(1.23)) + gtest.AssertEQ(c.GetFloat64("v4"), float64(1.23)) + gtest.AssertEQ(c.GetString("v2"), "true") + gtest.AssertEQ(c.GetBool("v2"), true) + gtest.AssertEQ(c.GetBool("v3"), false) + + gtest.AssertEQ(c.Contains("v1"), true) + gtest.AssertEQ(c.Contains("v2"), true) + gtest.AssertEQ(c.Contains("v3"), true) + gtest.AssertEQ(c.Contains("v4"), true) + gtest.AssertEQ(c.Contains("v5"), false) + + gtest.AssertEQ(c.GetInts("array"), []int{1,2,3}) + gtest.AssertEQ(c.GetStrings("array"), []string{"1","2","3"}) + gtest.AssertEQ(c.GetArray("array"), []interface{}{"1","2","3"}) + gtest.AssertEQ(c.GetInterfaces("array"), []interface{}{"1","2","3"}) + gtest.AssertEQ(c.GetMap("redis"), map[string]interface{}{ + "disk" : "127.0.0.1:6379,0", + "cache" : "127.0.0.1:6379,1", + }) + }) +} \ No newline at end of file diff --git a/g/os/gcron/gcron_cron.go b/g/os/gcron/gcron_cron.go index f5e8f125f..bb7eb2a06 100644 --- a/g/os/gcron/gcron_cron.go +++ b/g/os/gcron/gcron_cron.go @@ -141,11 +141,6 @@ func (c *Cron) Search(name string) *Entry { return nil } -// 根据指定名称删除定时任务 -func (c *Cron) Remove(name string) { - c.entries.Remove(name) -} - // 开启定时任务执行(可以指定特定名称的一个或若干个定时任务) func (c *Cron) Start(name...string) { if len(name) > 0 { @@ -172,6 +167,13 @@ func (c *Cron) Stop(name...string) { } } +// 根据指定名称删除定时任务。 +func (c *Cron) Remove(name string) { + if v := c.entries.Get(name); v != nil { + v.(*Entry).Close() + } +} + // 关闭定时任务 func (c *Cron) Close() { c.status.Set(STATUS_CLOSED) diff --git a/g/os/gcron/gcron_entry.go b/g/os/gcron/gcron_entry.go index eb2eb9989..35762c91d 100644 --- a/g/os/gcron/gcron_entry.go +++ b/g/os/gcron/gcron_entry.go @@ -87,7 +87,7 @@ func (entry *Entry) Stop() { // 关闭定时任务 func (entry *Entry) Close() { - entry.cron.Remove(entry.Name) + entry.cron.entries.Remove(entry.Name) entry.entry.Close() } @@ -96,6 +96,7 @@ func (entry *Entry) check() { if entry.schedule.meet(time.Now()) { path := entry.cron.GetLogPath() level := entry.cron.GetLogLevel() + // 检查定时任务对象状态(非任务状态) switch entry.cron.status.Val() { case STATUS_STOPPED: return diff --git a/g/os/gcron/gcron_unit_1_test.go b/g/os/gcron/gcron_unit_1_test.go index ff86385b5..4fb93494d 100644 --- a/g/os/gcron/gcron_unit_1_test.go +++ b/g/os/gcron/gcron_unit_1_test.go @@ -71,6 +71,24 @@ func TestCron_Basic(t *testing.T) { }) } +func TestCron_Remove(t *testing.T) { + gtest.Case(t, func() { + cron := gcron.New() + array := garray.New() + cron.Add("* * * * * *", func() { + array.Append(1) + }, "add") + gtest.Assert(array.Len(), 0) + time.Sleep(1200*time.Millisecond) + gtest.Assert(array.Len(), 1) + + cron.Remove("add") + gtest.Assert(array.Len(), 1) + time.Sleep(1200*time.Millisecond) + gtest.Assert(array.Len(), 1) + }) +} + func TestCron_AddSingleton(t *testing.T) { // un used, can be removed gtest.Case(t, func() { diff --git a/geg/net/ghttp/server/nethttp_server.go b/geg/net/ghttp/server/nethttp_server.go new file mode 100644 index 000000000..a082b125a --- /dev/null +++ b/geg/net/ghttp/server/nethttp_server.go @@ -0,0 +1,19 @@ +package main + +import ( + "net/http" + "time" +) + +func main() { + s := &http.Server{ + Addr: ":8199", + ReadTimeout: 2 * time.Second, + WriteTimeout: 2 * time.Second, + } + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hi")) + time.Sleep(3*time.Second) + }) + s.ListenAndServe() +} \ No newline at end of file diff --git a/geg/net/gudp/gudp_server.go b/geg/net/gudp/gudp_server.go index 9b7bf1f3b..a688a9a59 100644 --- a/geg/net/gudp/gudp_server.go +++ b/geg/net/gudp/gudp_server.go @@ -9,9 +9,8 @@ func main() { gudp.NewServer("127.0.0.1:8999", func(conn *gudp.Conn) { defer conn.Close() for { - if data, _ := conn.Recv(-1); len(data) > 0 { - fmt.Println(string(data)) - } + data, err := conn.Recv(-1) + fmt.Println(err, string(data)) } }).Run() } \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index e0afdc044..df01f4e64 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,18 +1,9 @@ package main -import ( - "bytes" - "encoding/json" - "fmt" -) +import "fmt" func main() { - value := interface{}(nil) - data := []byte(`{"n": 123456789}`) - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.UseNumber() - err := decoder.Decode(&value) - //err := json.Unmarshal(data, &value) - fmt.Println(err) - fmt.Println(value) + x := uintptr(1) + fmt.Println(x^0) + fmt.Println(2^0) } \ No newline at end of file diff --git a/third/README.MD b/third/README.MD index bb95cfc4f..d01b35096 100644 --- a/third/README.MD +++ b/third/README.MD @@ -1 +1 @@ -`GF` self-maintains its thirdparty-packages, developers need no worry about the dependences. \ No newline at end of file +`GF` self-maintains its third-party-packages, developers need no worry about the dependencies. \ No newline at end of file