diff --git a/.gitignore b/.gitignore index 8470fac8f..c3165c5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ cbuild **/.DS_Store .vscode/ go.sum - diff --git a/RELEASE.MD b/RELEASE.MD index 31a031b1a..2631d93a7 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,4 +1,75 @@ -# `v1.7.0` +# `v1.8.0` + +## 新功能改进 +1. 框架目前 `69` 个开发模块(不包括内部模块),原生代码 `65302` 行(不包含第三包依赖包),单元测试覆盖率达到`77%`; +1. 新增`gerror`错误处理模块:https://goframe.org/errors/gerror/index +1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/index +1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性:https://goframe.org/os/gmutex/index +1. 改进`glog`日志模块: + - 新增日志异步输出特性:https://goframe.org/os/glog/async + - 新增`Flags`额外功能特性:https://goframe.org/os/glog/flags + - 新增`Json`数据格式输出:https://goframe.org/os/glog/json + - 新增自定义`Writer`接口特性:https://goframe.org/os/glog/writer + - **修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式;** + - 新增`Expose`方法暴露内部默认`Logger`对象; +1. 改进`gdb`数据库ORM模块: + - 改进错误处理,当数据库操作没有查询到数据时,`error`返回`sql.ErrNoRows`:https://goframe.org/database/gdb/error + - 改进`Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; + - 数据库链式操作及方法操作中,预处理变量参数支持`slice`参数:https://goframe.org/database/gdb/chaining/model + - **修改`Priority`权重配置名称为`Weight`;** + - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config + - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法; + - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; + - 改进简化配置文件结构:https://goframe.org/database/gdb/config +1. 改进`gconv`数据转换模块: + - 对结构体对象转换时支持更多的标签:`gconv/c/json`; + - 支持`*struct/[]struct/[]*struct`自动初始化创建对象/数组:https://goframe.org/util/gconv/struct + - 新增`Strusts/StrctsDeep`方法,用于结构体数组的转换即递归转换; + - 新增`StructDeep`方法,用于对结构体对象的递归转换; + - 新增`MapDeep`方法,用于对结构体属性的递归转换; +1. 改进`ghttp`模块: + - 改进`ghttp`模块的分组路由功能,完善逻辑处理细节,程序更加稳健; + - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换,并且支持`**struct`参数的对象自动初始化; + - 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`; +1. 改进`gvalid`数据校验模块: + - 增加对校验标签`gvalid/valid/v`的支持; + - 改进`CheckStruct`支持对结构体对象的递归校验:https://goframe.org/util/gvalid/checkstruct +1. 改进`gtcp`TCP通信模块: + - 改进通信包协议设计,更加轻量级高效:https://goframe.org/net/gtcp/conn/pkg + - 改进`TCP Server`增加对`TLS`的支持:https://goframe.org/net/gtcp/tls + - 增加`Server.Cloce`服务端关闭方法; +1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑; +1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; +1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的操作方法; +1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: +1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; +1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; +1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;** +1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; +1. 新增`gfile.CopyFile/CopyDir`方法,用于文件及目录的复制; +1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; +1. 改进`gcache`模块,过期时间参数支持`time.Duration`类型; +1. 新增`internal/structs`包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块; +1. 改进`gbinary`增加封装方法对`BigEndian`的支持; + +## Bug Fix +1. 修复`garray.Search`返回值问题; +1. 修复`garray.Contains`, `garray.New*ArrayFromCopy`方法逻辑问题; +1. 修复`gjson.Remove`删除`slice`参数问题; +1. 修复`gtree.AVLTree.Remove`方法返回值问题; +1. 修复`gqueue.Size`不准确的大小问题; +1. 修复`queue.Close`问题; +1. 修复`gcache.GetOrSetLockFunc`当回调函数返回`nil`结果时的死锁问题; +1. 修复`gfsnotify.Add`方法默认递归监控添加失效问题; +1. 修复`gdb.Model.Scan`在某些参数类型下的失效问题; + +## 注意事项 + +以上粗体文字部分为不兼容调整,升级时请注意。 + + + +# `v1.7.0` (2019-06-10) ## 新功能/改进 1. 重构改进`glog`模块: - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 diff --git a/g/crypto/gmd5/gmd5.go b/g/crypto/gmd5/gmd5.go index 1a9a56ea3..2e46b2c11 100644 --- a/g/crypto/gmd5/gmd5.go +++ b/g/crypto/gmd5/gmd5.go @@ -13,7 +13,7 @@ import ( "io" "os" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" "github.com/gogf/gf/g/util/gconv" ) @@ -40,7 +40,7 @@ func EncryptFile(path string) (encrypt string, err error) { return "", err } defer func() { - err = errors.Wrap(f.Close(), "file closing error") + err = gerror.Wrap(f.Close(), "file closing error") }() h := md5.New() _, err = io.Copy(h, f) diff --git a/g/crypto/gsha1/gsha1.go b/g/crypto/gsha1/gsha1.go index e38a8299c..904625078 100644 --- a/g/crypto/gsha1/gsha1.go +++ b/g/crypto/gsha1/gsha1.go @@ -13,7 +13,7 @@ import ( "io" "os" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" "github.com/gogf/gf/g/util/gconv" ) @@ -37,7 +37,7 @@ func EncryptFile(path string) (encrypt string, err error) { return "", err } defer func() { - err = errors.Wrap(f.Close(), "file closing error") + err = gerror.Wrap(f.Close(), "file closing error") }() h := sha1.New() _, err = io.Copy(h, f) diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index b187fcd6c..f3afee6c9 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -87,14 +87,15 @@ type DB interface { GetQueriedSqls() []*Sql GetLastSql() *Sql PrintQueriedSqls() - SetMaxIdleConns(n int) - SetMaxOpenConns(n int) - SetConnMaxLifetime(n int) + SetMaxIdleConnCount(n int) + SetMaxOpenConnCount(n int) + SetMaxConnLifetime(n int) // 内部方法接口 getCache() *gcache.Cache getChars() (charLeft string, charRight string) getDebug() bool + setSchema(sqlDb *sql.DB, schema string) error filterFields(table string, data map[string]interface{}) map[string]interface{} convertValue(fieldValue interface{}, fieldType string) interface{} getTableFields(table string) (map[string]string, error) @@ -248,9 +249,9 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { slaveList = masterList } if master { - return getConfigNodeByPriority(masterList), nil + return getConfigNodeByWeight(masterList), nil } else { - return getConfigNodeByPriority(slaveList), nil + return getConfigNodeByWeight(slaveList), nil } } else { return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group)) @@ -263,19 +264,19 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { // 2、那么节点1的权重范围为[0, 99],节点2的权重范围为[100, 199],比例为1:1; // 3、假如计算出的随机数为99; // 4、那么选择的配置为节点1; -func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode { +func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode { if len(cg) < 2 { return &cg[0] } var total int for i := 0; i < len(cg); i++ { - total += cg[i].Priority * 100 + total += cg[i].Weight * 100 } // 如果total为0表示所有连接都没有配置priority属性,那么默认都是1 if total == 0 { for i := 0; i < len(cg); i++ { - cg[i].Priority = 1 - total += cg[i].Priority * 100 + cg[i].Weight = 1 + total += cg[i].Weight * 100 } } // 不能取到末尾的边界点 @@ -286,7 +287,7 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode { min := 0 max := 0 for i := 0; i < len(cg); i++ { - max = min + cg[i].Priority*100 + max = min + cg[i].Weight*100 //fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max) if r >= min && r < max { return &cg[i] @@ -308,12 +309,14 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { if node.Charset == "" { node.Charset = "utf8" } + // 缓存连接对象(该对象其实是一个连接池对象) v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} { sqlDb, err = bs.db.Open(node) if err != nil { return nil } - + // 接口对象可能会覆盖这些连接参数,所以这里优先判断有误设置连接池属性。 + // 若无设置则使用配置节点的连接池参数 if n := bs.maxIdleConnCount.Val(); n > 0 { sqlDb.SetMaxIdleConns(n) } else if node.MaxIdleConnCount > 0 { @@ -336,9 +339,13 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { if v != nil && sqlDb == nil { sqlDb = v.(*sql.DB) } + // 是否开启调试模式 + bs.db.SetDebug(node.Debug) // 是否手动选择数据库 if v := bs.schema.Val(); v != "" { - sqlDb.Exec("USE " + v) + if e := bs.db.setSchema(sqlDb, v); e != nil { + err = e + } } return } diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index 00ab5afb6..0d33a3449 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -22,7 +22,8 @@ import ( ) const ( - gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数 + // 默认调试模式下记录的SQL条数 + gDEFAULT_DEBUG_SQL_LENGTH = 1000 ) // 获取最近一条执行的sql @@ -78,6 +79,7 @@ func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err // 数据库sql查询操作,主要执行查询 func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) { + query, args = formatQuery(query, args) query = bs.db.handleSqlBeforeExec(query) if bs.db.getDebug() { mTime1 := gtime.Millisecond() @@ -114,6 +116,7 @@ func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, er // 执行一条sql,并返回执行情况,主要用于非查询操作 func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) { + query, args = formatQuery(query, args) query = bs.db.handleSqlBeforeExec(query) if bs.db.getDebug() { mTime1 := gtime.Millisecond() @@ -178,28 +181,28 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) { } // 数据库查询,查询单条记录,自动映射数据到给定的struct对象中 -func (bs *dbBase) GetStruct(objPointer interface{}, query string, args ...interface{}) error { +func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface{}) error { one, err := bs.GetOne(query, args...) if err != nil { return err } - return one.ToStruct(objPointer) + return one.ToStruct(pointer) } // 数据库查询,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 -func (bs *dbBase) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error { +func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interface{}) error { all, err := bs.GetAll(query, args...) if err != nil { return err } - return all.ToStructs(objPointerSlice) + return all.ToStructs(pointer) } // 将结果转换为指定的struct/*struct/[]struct/[]*struct, // 参数应该为指针类型,否则返回失败。 // 该方法自动识别参数类型,调用Struct/Structs方法。 -func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interface{}) error { - t := reflect.TypeOf(objPointer) +func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}) error { + t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { return fmt.Errorf("params should be type of pointer, but got: %v", k) @@ -207,9 +210,9 @@ func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interfac k = t.Elem().Kind() switch k { case reflect.Array, reflect.Slice: - return bs.db.GetStructs(objPointer, query, args...) + return bs.db.GetStructs(pointer, query, args...) case reflect.Struct: - return bs.db.GetStruct(objPointer, query, args...) + return bs.db.GetStruct(pointer, query, args...) } return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) } @@ -567,6 +570,9 @@ func (bs *dbBase) getCache() *gcache.Cache { // 将数据查询的列表数据*sql.Rows转换为Result类型 func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { + if !rows.Next() { + return nil, sql.ErrNoRows + } // 列信息列表, 名称与类型 columnTypes, err := rows.ColumnTypes() if err != nil { @@ -585,7 +591,7 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { for i := range values { scanArgs[i] = &values[i] } - for rows.Next() { + for { if err := rows.Scan(scanArgs...); err != nil { return records, err } @@ -603,6 +609,15 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { } } records = append(records, row) + if !rows.Next() { + break + } } return records, nil } + +// 动态切换数据库 +func (bs *dbBase) setSchema(sqlDb *sql.DB, schema string) error { + _, err := sqlDb.Exec("USE " + schema) + return err +} diff --git a/g/database/gdb/gdb_config.go b/g/database/gdb/gdb_config.go index 1ecfe42fb..c18992b94 100644 --- a/g/database/gdb/gdb_config.go +++ b/g/database/gdb/gdb_config.go @@ -9,8 +9,9 @@ package gdb import ( "fmt" - "github.com/gogf/gf/g/container/gring" "sync" + + "github.com/gogf/gf/g/container/gring" ) const ( @@ -30,10 +31,11 @@ type ConfigNode struct { User string // 账号 Pass string // 密码 Name string // 数据库名称 - Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle(目前仅支持mysql) + Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave + Debug bool // (可选)开启调试模式 + Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 Charset string // (可选,默认为 utf8)编码,默认为 utf8 - Priority int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) MaxIdleConnCount int // (可选)连接池最大限制的连接数 MaxOpenConnCount int // (可选)连接池最大打开的连接数 @@ -47,37 +49,6 @@ var configs struct { defaultGroup string // 默认数据库分组名称 } -// 数据库集群配置示例,支持主从处理,多数据库集群支持 -/* -var DatabaseConfiguration = Config { - // 数据库集群配置名称 - "default" : ConfigGroup { - { - Host : "192.168.1.100", - Port : "3306", - User : "root", - Pass : "123456", - Name : "test", - Type : "mysql", - Role : "master", - Charset : "utf8", - Priority : 100, - }, - { - Host : "192.168.1.101", - Port : "3306", - User : "root", - Pass : "123456", - Name : "test", - Type : "mysql", - Role : "slave", - Charset : "utf8", - Priority : 100, - }, - }, -} -*/ - // 包初始化 func init() { configs.config = make(Config) @@ -136,24 +107,24 @@ func SetDefaultGroup(name string) { // 获取默认链接的数据库链接配置项(默认是 default) func GetDefaultGroup() string { defer instances.Clear() - configs.Lock() - defer configs.Unlock() + configs.RLock() + defer configs.RUnlock() return configs.defaultGroup } // 设置数据库连接池中空闲链接的大小 -func (bs *dbBase) SetMaxIdleConns(n int) { +func (bs *dbBase) SetMaxIdleConnCount(n int) { bs.maxIdleConnCount.Set(n) } // 设置数据库连接池最大打开的链接数量 -func (bs *dbBase) SetMaxOpenConns(n int) { +func (bs *dbBase) SetMaxOpenConnCount(n int) { bs.maxOpenConnCount.Set(n) } // 设置数据库连接可重复利用的时间,超过该时间则被关闭废弃 // 如果 d <= 0 表示该链接会一直重复利用 -func (bs *dbBase) SetConnMaxLifetime(n int) { +func (bs *dbBase) SetMaxConnLifetime(n int) { bs.maxConnLifetime.Set(n) } @@ -162,8 +133,8 @@ func (node *ConfigNode) String() string { 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, + return fmt.Sprintf(`%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d`, node.User, node.Host, node.Port, + node.Name, node.Type, node.Role, node.Charset, node.Debug, node.MaxIdleConnCount, node.MaxOpenConnCount, node.MaxConnLifetime, ) } diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index abe20611c..1dd68c58a 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -27,7 +27,49 @@ type apiString interface { String() string } -// 格式化Where查询条件 +// 格式化SQL语句。 +// 1. 支持参数只传一个slice; +// 2. 支持占位符号数量自动扩展; +func formatQuery(query string, args []interface{}) (newQuery string, newArgs []interface{}) { + newQuery = query + // 查询条件参数处理,主要处理slice参数类型 + if len(args) > 0 { + for index, arg := range args { + rv := reflect.ValueOf(arg) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + // '?'占位符支持slice类型, 这里会将slice参数拆散,并更新原有占位符'?'为多个'?',使用','符号连接。 + case reflect.Slice, reflect.Array: + for i := 0; i < rv.Len(); i++ { + newArgs = append(newArgs, rv.Index(i).Interface()) + } + // 如果参数直接传递slice,并且占位符数量与slice长度相等, + // 那么不用替换扩展占位符数量,直接使用该slice作为查询参数 + if len(args) == 1 && gstr.Count(newQuery, "?") == rv.Len() { + break + } + // counter用于匹配该参数的位置(与index对应) + counter := 0 + newQuery, _ = gregex.ReplaceStringFunc(`\?`, newQuery, func(s string) string { + counter++ + if counter == index+1 { + return "?" + strings.Repeat(",?", rv.Len()-1) + } + return s + }) + default: + newArgs = append(newArgs, arg) + } + } + } + return +} + +// 格式化Where查询条件。 func formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) { // 条件字符串处理 buffer := bytes.NewBuffer(nil) @@ -38,7 +80,6 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg rv = rv.Elem() kind = rv.Kind() } - tmpArgs := []interface{}(nil) switch kind { // map/struct类型 case reflect.Map: @@ -57,19 +98,20 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg count := gstr.Count(key, "?") if count == 0 { buffer.WriteString(key + " IN(?)") - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } else if count != rv.Len() { buffer.WriteString(key) - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } else { buffer.WriteString(key) // 如果键名/属性名称中带有多个?占位符号,那么将参数打散 - tmpArgs = append(tmpArgs, gconv.Interfaces(value)...) + newArgs = append(newArgs, gconv.Interfaces(value)...) } default: if value == nil { buffer.WriteString(key) } else { + // 支持key带操作符号 if gstr.Pos(key, "?") == -1 { if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 { buffer.WriteString(key + "=?") @@ -79,7 +121,7 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg } else { buffer.WriteString(key) } - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } } } @@ -91,45 +133,16 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg if buffer.Len() == 0 { return "", args } + newArgs = append(newArgs, args...) newWhere = buffer.String() - tmpArgs = append(tmpArgs, args...) // 查询条件参数处理,主要处理slice参数类型 - if len(tmpArgs) > 0 { - for index, arg := range tmpArgs { - rv := reflect.ValueOf(arg) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - // '?'占位符支持slice类型, - // 这里会将slice参数拆散,并更新原有占位符'?'为多个'?',使用','符号连接。 - case reflect.Slice: - fallthrough - case reflect.Array: - for i := 0; i < rv.Len(); i++ { - newArgs = append(newArgs, rv.Index(i).Interface()) - } - // counter用于匹配该参数的位置(与index对应) - counter := 0 - newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string { - counter++ - if counter == index+1 { - return "?" + strings.Repeat(",?", rv.Len()-1) - } - return s - }) - default: - // 支持例如 Where/And/Or("uid", 1) 这种格式 - if gstr.Pos(newWhere, "?") == -1 { - if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 { - newWhere += "=?" - } else { - newWhere += "?" - } - } - newArgs = append(newArgs, arg) + if len(newArgs) > 0 { + // 支持例如 Where/And/Or("uid", 1) 这种格式 + if gstr.Pos(newWhere, "?") == -1 { + if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 { + newWhere += "=?" + } else { + newWhere += "?" } } } @@ -175,7 +188,7 @@ func printSql(v *Sql) { ) if v.Error != nil { s += "\nError: " + v.Error.Error() - glog.Backtrace(true, 2).Error(s) + glog.Stack(true, 2).Error(s) } else { glog.Debug(s) } diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index ef61c8041..9e026df62 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -3,8 +3,6 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// -// @author john, ymrjqyy package gdb @@ -30,6 +28,7 @@ type Model struct { orderBy string // 排序语句 start int // 分页开始 limit int // 分页条数 + offset int // 查询偏移量(OFFSET语法) data interface{} // 操作数据(注意仅支持Map/List/string类型) batch int // 批量操作条数 filter bool // 是否按照表字段过滤data参数 @@ -47,6 +46,7 @@ func (bs *dbBase) Table(tables string) *Model { tables: tables, fields: "*", start: -1, + offset: -1, safe: false, } } @@ -214,6 +214,14 @@ func (md *Model) Limit(limit ...int) *Model { return model } +// 链式操作,OFFSET语法(部分数据库支持)。 +// 注意:可以使用Limit方法调用替换该方法特性,底层不同数据库将会自动替换LIMIT语法为OFFSET语法。 +func (md *Model) Offset(offset int) *Model { + model := md.getModel() + model.offset = offset + return model +} + // 链式操作,翻页,注意分页页码从1开始,而Limit方法从0开始。 func (md *Model) ForPage(page, limit int) *Model { model := md.getModel() @@ -605,11 +613,13 @@ func (md *Model) getConditionSql() string { } if md.limit != 0 { if md.start >= 0 { - s += fmt.Sprintf(" LIMIT %d, %d", md.start, md.limit) + s += fmt.Sprintf(" LIMIT %d,%d", md.start, md.limit) } else { s += fmt.Sprintf(" LIMIT %d", md.limit) } - + } + if md.offset >= 0 { + s += fmt.Sprintf(" OFFSET %d", md.offset) } return s } diff --git a/g/database/gdb/gdb_mssql.go b/g/database/gdb/gdb_mssql.go index c0a1fff85..9d8f1ad0f 100644 --- a/g/database/gdb/gdb_mssql.go +++ b/g/database/gdb/gdb_mssql.go @@ -3,23 +3,21 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -/* -@author wenzi1 -@date 20181109 -说明: - 1.需要导入sqlserver驱动: github.com/denisenkom/go-mssqldb - 2.不支持save/replace方法 - 3.不支持LastInsertId方法 -*/ +// 说明: +// 1.需要导入sqlserver驱动: github.com/denisenkom/go-mssqldb +// 2.不支持save/replace方法 +// 3.不支持LastInsertId方法 +// package gdb import ( "database/sql" "fmt" - "github.com/gogf/gf/g/text/gregex" "strconv" "strings" + + "github.com/gogf/gf/g/text/gregex" ) // 数据库链接对象 diff --git a/g/database/gdb/gdb_oracle.go b/g/database/gdb/gdb_oracle.go index ad196e835..b17324555 100644 --- a/g/database/gdb/gdb_oracle.go +++ b/g/database/gdb/gdb_oracle.go @@ -3,23 +3,20 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -/* -@author wenzi1 -@date 20181026 -说明: - 1.需要导入oracle驱动: github.com/mattn/go-oci8 - 2.不支持save/replace方法,可以调用这2个方法估计会报错,还没测试过,(应该是可以通过oracle的merge来实现这2个功能的,还没仔细研究) - 3.不支持LastInsertId方法 -*/ +// 说明: +// 1.需要导入oracle驱动: github.com/mattn/go-oci8 +// 2.不支持save/replace方法,可以调用这2个方法估计会报错,还没测试过,(应该是可以通过oracle的merge来实现这2个功能的,还没仔细研究) +// 3.不支持LastInsertId方法 package gdb import ( "database/sql" "fmt" - "github.com/gogf/gf/g/text/gregex" "strconv" "strings" + + "github.com/gogf/gf/g/text/gregex" ) // 数据库链接对象 diff --git a/g/database/gdb/gdb_pgsql.go b/g/database/gdb/gdb_pgsql.go index 60d921b1a..44a1c72e2 100644 --- a/g/database/gdb/gdb_pgsql.go +++ b/g/database/gdb/gdb_pgsql.go @@ -9,12 +9,16 @@ package gdb import ( "database/sql" "fmt" - "regexp" + + "github.com/gogf/gf/g/text/gregex" ) // PostgreSQL的适配. +// // 使用时需要import: +// // _ "github.com/gogf/gf/third/github.com/lib/pq" +// // @todo 需要完善replace和save的操作覆盖 // 数据库链接对象 @@ -37,6 +41,12 @@ func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) { } } +// 动态切换数据库 +func (db *dbPgsql) setSchema(sqlDb *sql.DB, schema string) error { + _, err := sqlDb.Exec("SET search_path TO " + schema) + return err +} + // 获得关键字操作符 func (db *dbPgsql) getChars() (charLeft string, charRight string) { return "\"", "\"" @@ -44,11 +54,12 @@ func (db *dbPgsql) getChars() (charLeft string, charRight string) { // 在执行sql之前对sql进行进一步处理 func (db *dbPgsql) handleSqlBeforeExec(query string) string { - reg := regexp.MustCompile("\\?") index := 0 - str := reg.ReplaceAllStringFunc(query, func(s string) string { + query, _ = gregex.ReplaceStringFunc("\\?", query, func(s string) string { index++ return fmt.Sprintf("$%d", index) }) - return str + // 分页语法替换 + query, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $1 OFFSET $2`, query) + return query } diff --git a/g/database/gdb/gdb_sqlite.go b/g/database/gdb/gdb_sqlite.go index f368cd548..e890c4ddf 100644 --- a/g/database/gdb/gdb_sqlite.go +++ b/g/database/gdb/gdb_sqlite.go @@ -3,7 +3,6 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// @author wxkj package gdb @@ -41,7 +40,7 @@ func (db *dbSqlite) getChars() (charLeft string, charRight string) { return "`", "`" } -// 在执行sql之前对sql进行进一步处理 +// 在执行sql之前对sql进行进一步处理。 // @todo 需要增加对Save方法的支持,可使用正则来实现替换, // @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE) func (db *dbSqlite) handleSqlBeforeExec(query string) string { diff --git a/g/database/gdb/gdb_unit_init_test.go b/g/database/gdb/gdb_unit_init_test.go index 223c2ed20..aad09ec86 100644 --- a/g/database/gdb/gdb_unit_init_test.go +++ b/g/database/gdb/gdb_unit_init_test.go @@ -32,15 +32,15 @@ var ( // 测试前需要修改连接参数。 func init() { node := gdb.ConfigNode{ - Host: "127.0.0.1", - Port: "3306", - User: "root", - Pass: "", - Name: "", - Type: "mysql", - Role: "master", - Charset: "utf8", - Priority: 1, + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pass: "", + Name: "", + Type: "mysql", + Role: "master", + Charset: "utf8", + Weight: 1, } hostname, _ := os.Hostname() // 本地测试hack diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go index 886277a24..db948cd60 100644 --- a/g/database/gdb/gdb_unit_method_test.go +++ b/g/database/gdb/gdb_unit_method_test.go @@ -7,11 +7,12 @@ package gdb_test import ( + "testing" + "time" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" - "testing" - "time" ) func TestDbBase_Ping(t *testing.T) { @@ -24,12 +25,17 @@ func TestDbBase_Ping(t *testing.T) { } func TestDbBase_Query(t *testing.T) { - if _, err := db.Query("SELECT ?", 1); err != nil { - gtest.Fatal(err) - } - if _, err := db.Query("ERROR"); err == nil { - gtest.Fatal("FAIL") - } + gtest.Case(t, func() { + _, err := db.Query("SELECT ?", 1) + gtest.Assert(err, nil) + _, err = db.Query("SELECT ?+?", 1, 2) + gtest.Assert(err, nil) + _, err = db.Query("SELECT ?+?", g.Slice{1, 2}) + gtest.Assert(err, nil) + _, err = db.Query("ERROR") + gtest.AssertNE(err, nil) + }) + } func TestDbBase_Exec(t *testing.T) { @@ -290,11 +296,50 @@ func TestDbBase_Update(t *testing.T) { } func TestDbBase_GetAll(t *testing.T) { - if result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil { - gtest.Fatal(err) - } else { + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1) + gtest.Assert(err, nil) gtest.Assert(len(result), 1) - } + gtest.Assert(result[0]["id"].Int(), 1) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id=?", g.Slice{1}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 1) + gtest.Assert(result[0]["id"].Int(), 1) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?)", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?,?,?)", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?,?,?)", g.Slice{1, 2, 3}...) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id>=? AND id <=?", g.Slice{1, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) } func TestDbBase_GetOne(t *testing.T) { diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index 7b47901da..34cc9590e 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -280,33 +280,45 @@ func TestModel_Safe(t *testing.T) { } func TestModel_All(t *testing.T) { - result, err := db.Table("user").All() - if err != nil { - gtest.Fatal(err) - } - gtest.Assert(len(result), 3) + gtest.Case(t, func() { + result, err := db.Table("user").All() + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + }) + // sql.ErrNoRows + gtest.Case(t, func() { + result, err := db.Table("user").Where("id<0").All() + gtest.Assert(result, nil) + gtest.Assert(err, sql.ErrNoRows) + }) } func TestModel_One(t *testing.T) { - record, err := db.Table("user").Where("id", 1).One() - if err != nil { - gtest.Fatal(err) - } - if record == nil { - gtest.Fatal("FAIL") - } - gtest.Assert(record["nickname"].String(), "T111") + gtest.Case(t, func() { + record, err := db.Table("user").Where("id", 1).One() + gtest.Assert(err, nil) + gtest.Assert(record["nickname"].String(), "T111") + }) + // sql.ErrNoRows + gtest.Case(t, func() { + record, err := db.Table("user").Where("id", 0).One() + gtest.Assert(err, sql.ErrNoRows) + gtest.Assert(record, nil) + }) } func TestModel_Value(t *testing.T) { - value, err := db.Table("user").Fields("nickname").Where("id", 1).Value() - if err != nil { - gtest.Fatal(err) - } - if value == nil { - gtest.Fatal("FAIL") - } - gtest.Assert(value.String(), "T111") + gtest.Case(t, func() { + value, err := db.Table("user").Fields("nickname").Where("id", 1).Value() + gtest.Assert(err, nil) + gtest.Assert(value.String(), "T111") + }) + // sql.ErrNoRows + gtest.Case(t, func() { + value, err := db.Table("user").Fields("nickname").Where("id", 0).Value() + gtest.Assert(err, sql.ErrNoRows) + gtest.Assert(value, nil) + }) } func TestModel_Count(t *testing.T) { @@ -613,6 +625,23 @@ func TestModel_Where(t *testing.T) { gtest.AssertGT(len(result), 0) gtest.Assert(result["id"].Int(), 3) }) + // slice parameter + gtest.Case(t, func() { + result, err := db.Table("user").Where("id=? and nickname=?", g.Slice{3, "T3"}).One() + if err != nil { + gtest.Fatal(err) + } + gtest.AssertGT(len(result), 0) + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("id=?", g.Slice{3}).One() + if err != nil { + gtest.Fatal(err) + } + gtest.AssertGT(len(result), 0) + gtest.Assert(result["id"].Int(), 3) + }) gtest.Case(t, func() { result, err := db.Table("user").Where("id", 3).One() if err != nil { @@ -661,7 +690,14 @@ func TestModel_Where(t *testing.T) { gtest.Assert(result["id"].Int(), 3) }) gtest.Case(t, func() { - result, err := db.Table("user").Where("passport like ? and nickname like ?", g.Slice{"t3", "T3"}...).One() + result, err := db.Table("user").Where("id=? AND nickname=?", g.Slice{3, "T3"}).One() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("passport like ? and nickname like ?", g.Slice{"t3", "T3"}).One() if err != nil { gtest.Fatal(err) } diff --git a/g/database/gdb/gdb_unit_transaction_test.go b/g/database/gdb/gdb_unit_transaction_test.go index 835004241..5ddedabd6 100644 --- a/g/database/gdb/gdb_unit_transaction_test.go +++ b/g/database/gdb/gdb_unit_transaction_test.go @@ -7,10 +7,11 @@ package gdb_test import ( + "testing" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" - "testing" ) func TestTX_Query(t *testing.T) { @@ -23,6 +24,16 @@ func TestTX_Query(t *testing.T) { } else { rows.Close() } + if rows, err := tx.Query("SELECT ?+?", 1, 2); err != nil { + gtest.Fatal(err) + } else { + rows.Close() + } + if rows, err := tx.Query("SELECT ?+?", g.Slice{1, 2}); err != nil { + gtest.Fatal(err) + } else { + rows.Close() + } if _, err := tx.Query("ERROR"); err == nil { gtest.Fatal("FAIL") } @@ -39,6 +50,12 @@ func TestTX_Exec(t *testing.T) { if _, err := tx.Exec("SELECT ?", 1); err != nil { gtest.Fatal(err) } + if _, err := tx.Exec("SELECT ?+?", 1, 2); err != nil { + gtest.Fatal(err) + } + if _, err := tx.Exec("SELECT ?+?", g.Slice{1, 2}); err != nil { + gtest.Fatal(err) + } if _, err := tx.Exec("ERROR"); err == nil { gtest.Fatal("FAIL") } diff --git a/g/database/gredis/gredis_unit_test.go b/g/database/gredis/gredis_unit_test.go index 99e9427cf..c58bed430 100644 --- a/g/database/gredis/gredis_unit_test.go +++ b/g/database/gredis/gredis_unit_test.go @@ -7,11 +7,12 @@ package gredis_test import ( + "testing" + "time" + "github.com/gogf/gf/g/database/gredis" "github.com/gogf/gf/g/test/gtest" redis2 "github.com/gogf/gf/third/github.com/gomodule/redigo/redis" - "testing" - "time" ) var ( diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go new file mode 100644 index 000000000..99f52fad2 --- /dev/null +++ b/g/errors/gerror/gerror.go @@ -0,0 +1,81 @@ +// 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 errors provides simple functions to manipulate errors. +package gerror + +import ( + "fmt" + "github.com/gogf/gf/g/util/gconv" +) + +// New returns an error that formats as the given value. +func New(value interface{}) error { + if value == nil { + return nil + } + return NewText(gconv.String(value)) +} + +// NewText returns an error that formats as the given text. +func NewText(text string) error { + if text == "" { + return nil + } + return &Error{ + stack: callers(), + text: text, + } +} + +// Wrap wraps error with text. +// It returns nil if given err is nil. +func Wrap(err error, text string) error { + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(), + text: text, + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// It returns nil if given err is nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(), + text: fmt.Sprintf(format, args...), + } +} + +// Cause returns the root cause error. +func Cause(err error) error { + if err != nil { + if e, ok := err.(*Error); ok { + return e.Cause() + } + } + return err +} + +// Stack returns the stack callers as string. +// It returns an empty string id the does not support stacks. +func Stack(err error) string { + if err == nil { + return "" + } + if e, ok := err.(*Error); !ok { + return e.Stack() + } + return "" +} diff --git a/g/errors/gerror/gerror_error.go b/g/errors/gerror/gerror_error.go new file mode 100644 index 000000000..95f6c1bb7 --- /dev/null +++ b/g/errors/gerror/gerror_error.go @@ -0,0 +1,139 @@ +// 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 gerror + +import ( + "bytes" + "fmt" + "io" + "runtime" + "strings" +) + +// Error is custom error for additional features. +type Error struct { + error error // Wrapped error. + stack stack // Stack array, which records the stack information when this error is created or wrapped. + text string // Error text, which is created by New* functions. +} + +const ( + gFILTER_KEY = "/g/errors/gerror/gerror" +) + +var ( + // goRootForFilter is used for stack filtering purpose. + goRootForFilter = runtime.GOROOT() +) + +func init() { + if goRootForFilter != "" { + goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) + } +} + +// Error implements the interface of Error, it returns the error as string. +func (err *Error) Error() string { + if err.text != "" { + if err.error != nil { + return err.text + ": " + err.error.Error() + } + return err.text + } + return err.error.Error() +} + +// Cause returns the root cause error. +func (err *Error) Cause() error { + loop := err + for loop != nil { + if loop.error != nil { + if e, ok := loop.error.(*Error); ok { + loop = e + } else { + return loop.error + } + } else { + return loop + } + } + return nil +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %v, %s : Print the error string; +// %-v, %-s : Print current error string; +// %+s : Print full stack error list; +// %+v : Print the error string and full stack error list; +func (err *Error) Format(s fmt.State, verb rune) { + switch verb { + case 's', 'v': + switch { + case s.Flag('-'): + if err.text != "" { + io.WriteString(s, err.text) + } else { + io.WriteString(s, err.Error()) + } + case s.Flag('+'): + if verb == 's' { + io.WriteString(s, err.Stack()) + } else { + io.WriteString(s, err.Error()+"\n"+err.Stack()) + } + default: + io.WriteString(s, err.Error()) + } + } +} + +// Stack returns the stack callers as string. +// It returns an empty string id the does not support stacks. +func (err *Error) Stack() string { + if err == nil { + return "" + } + loop := err + index := 1 + buffer := bytes.NewBuffer(nil) + for loop != nil { + buffer.WriteString(fmt.Sprintf("%d.\t%-v\n", index, loop)) + index++ + formatSubStack(loop.stack, buffer) + if loop.error != nil { + if e, ok := loop.error.(*Error); ok { + loop = e + } else { + buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, loop.error.Error())) + index++ + break + } + } else { + break + } + } + return buffer.String() +} + +// formatSubStack formats the stack for error. +func formatSubStack(st stack, buffer *bytes.Buffer) { + index := 1 + for _, p := range st { + if fn := runtime.FuncForPC(p - 1); fn != nil { + file, line := fn.FileLine(p - 1) + if strings.Contains(file, gFILTER_KEY) { + continue + } + if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + buffer.WriteString(fmt.Sprintf("\t%d).\t%s\n\t\t%s:%d\n", index, fn.Name(), file, line)) + index++ + } + } +} diff --git a/g/errors/gerror/gerror_stack.go b/g/errors/gerror/gerror_stack.go new file mode 100644 index 000000000..5a0d5239b --- /dev/null +++ b/g/errors/gerror/gerror_stack.go @@ -0,0 +1,22 @@ +// 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 gerror + +import "runtime" + +// stack represents a stack of program counters. +type stack []uintptr + +const ( + gMAX_STACK_DEPTH = 32 +) + +func callers() stack { + var pcs [gMAX_STACK_DEPTH]uintptr + n := runtime.Callers(3, pcs[:]) + return pcs[0:n] +} diff --git a/g/errors/gerror/gerror_test.go b/g/errors/gerror/gerror_test.go new file mode 100644 index 000000000..e1a040f33 --- /dev/null +++ b/g/errors/gerror/gerror_test.go @@ -0,0 +1,133 @@ +// 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 gerror_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/gogf/gf/g/errors/gerror" + "github.com/gogf/gf/g/test/gtest" +) + +func interfaceNil() interface{} { + return nil +} + +func nilError() error { + return nil +} + +func Test_Nil(t *testing.T) { + gtest.Case(t, func() { + gtest.Assert(gerror.New(interfaceNil()), nil) + gtest.Assert(gerror.Wrap(nilError(), "test"), nil) + }) +} + +func Test_Wrap(t *testing.T) { + gtest.Case(t, func() { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + gtest.Assert(err.Error(), "3: 2: 1") + }) + + gtest.Case(t, func() { + err := gerror.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + gtest.Assert(err.Error(), "3: 2: 1") + }) +} + +func Test_Cause(t *testing.T) { + gtest.Case(t, func() { + err := errors.New("1") + gtest.Assert(gerror.Cause(err), err) + }) + + gtest.Case(t, func() { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.Assert(gerror.Cause(err), "1") + }) + + gtest.Case(t, func() { + err := gerror.New("1") + gtest.Assert(gerror.Cause(err), "1") + }) + + gtest.Case(t, func() { + err := gerror.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.Assert(gerror.Cause(err), "1") + }) +} + +func Test_Format(t *testing.T) { + gtest.Case(t, func() { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + gtest.Assert(fmt.Sprintf("%s", err), "3: 2: 1") + gtest.Assert(fmt.Sprintf("%v", err), "3: 2: 1") + }) + + gtest.Case(t, func() { + err := gerror.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + gtest.Assert(fmt.Sprintf("%s", err), "3: 2: 1") + gtest.Assert(fmt.Sprintf("%v", err), "3: 2: 1") + }) + + gtest.Case(t, func() { + err := gerror.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + gtest.Assert(fmt.Sprintf("%-s", err), "3") + gtest.Assert(fmt.Sprintf("%-v", err), "3") + }) +} + +func Test_Stack(t *testing.T) { + gtest.Case(t, func() { + err := errors.New("1") + gtest.Assert(fmt.Sprintf("%+v", err), "1") + }) + + gtest.Case(t, func() { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + //fmt.Printf("%+v", err) + }) + + gtest.Case(t, func() { + err := gerror.New("1") + gtest.AssertNE(fmt.Sprintf("%+v", err), "1") + //fmt.Printf("%+v", err) + }) + + gtest.Case(t, func() { + err := gerror.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") + gtest.AssertNE(err, nil) + //fmt.Printf("%+v", err) + }) +} diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 3414599cd..4421016be 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -162,8 +162,8 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode { if value, ok := nodeMap["charset"]; ok { node.Charset = gconv.String(value) } - if value, ok := nodeMap["priority"]; ok { - node.Priority = gconv.Int(value) + if value, ok := nodeMap["weight"]; ok { + node.Weight = gconv.Int(value) } if value, ok := nodeMap["linkinfo"]; ok { node.LinkInfo = gconv.String(value) diff --git a/g/frame/gins/gins_database_test.go b/g/frame/gins/gins_database_test.go index 699087e2a..9f850cf39 100644 --- a/g/frame/gins/gins_database_test.go +++ b/g/frame/gins/gins_database_test.go @@ -8,11 +8,12 @@ package gins_test import ( "fmt" + "testing" + "time" + "github.com/gogf/gf/g/frame/gins" "github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/test/gtest" - "testing" - "time" ) func Test_Database(t *testing.T) { @@ -31,8 +32,8 @@ test = "v=2" name = "test" type = "mysql" role = "master" + weight = "1" charset = "utf8" - priority = "1" [[database.test]] host = "127.0.0.1" port = "3306" @@ -42,8 +43,8 @@ test = "v=2" name = "test" type = "mysql" role = "master" + weight = "1" charset = "utf8" - priority = "1" # Redis数据库配置 [redis] default = "127.0.0.1:6379,0" diff --git a/g/internal/debug/stack.go b/g/internal/debug/stack.go new file mode 100644 index 000000000..77567c82b --- /dev/null +++ b/g/internal/debug/stack.go @@ -0,0 +1,117 @@ +// 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 debug contains facilities for programs to debug themselves while +// they are running. +package debug + +import ( + "bytes" + "fmt" + "runtime" + "strings" +) + +const ( + gMAX_DEPTH = 1000 + gFILTER_KEY = "/g/internal/debug/stack.go" +) + +var ( + // goRootForFilter is used for stack filtering purpose. + goRootForFilter = runtime.GOROOT() +) + +func init() { + if goRootForFilter != "" { + goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) + } +} + +// PrintStack prints to standard error the stack trace returned by runtime.Stack. +func PrintStack(skip ...int) { + fmt.Print(Stack(skip...)) +} + +// Stack returns a formatted stack trace of the goroutine that calls it. +// It calls runtime.Stack with a large enough buffer to capture the entire trace. +func Stack(skip ...int) string { + return StackWithFilter("", skip...) +} + +// StackWithFilter returns a formatted stack trace of the goroutine that calls it. +// It calls runtime.Stack with a large enough buffer to capture the entire trace. +// +// The parameter is used to filter the path of the caller. +func StackWithFilter(filter string, skip ...int) string { + number := 0 + if len(skip) > 0 { + number = skip[0] + } + name := "" + index := 1 + buffer := bytes.NewBuffer(nil) + for i := callerFromIndex() + number; i < gMAX_DEPTH; i++ { + if pc, file, line, ok := runtime.Caller(i); ok { + if filter != "" && strings.Contains(file, filter) { + continue + } + if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + if fn := runtime.FuncForPC(pc); fn == nil { + name = "unknown" + } else { + name = fn.Name() + } + buffer.WriteString(fmt.Sprintf("%d.\t%s\n\t%s:%d\n", index, name, file, line)) + index++ + } else { + break + } + } + return buffer.String() +} + +// CallerPath returns the absolute file path along with its line number of the caller. +func Caller(skip ...int) string { + return CallerWithFilter("", skip...) +} + +// CallerPathWithFilter returns the absolute file path along with its line number of the caller. +// +// The parameter is used to filter the path of the caller. +func CallerWithFilter(filter string, skip ...int) string { + number := 0 + if len(skip) > 0 { + number = skip[0] + } + for i := callerFromIndex() + number; i < gMAX_DEPTH; i++ { + if _, file, line, ok := runtime.Caller(i); ok { + if filter != "" && strings.Contains(file, filter) { + continue + } + return fmt.Sprintf(`%s:%d`, file, line) + } else { + break + } + } + return "" +} + +// callerFromIndex returns the caller position exclusive of the debug package. +func callerFromIndex() int { + for i := 0; i < gMAX_DEPTH; i++ { + if _, file, _, ok := runtime.Caller(i); ok { + if strings.Contains(file, gFILTER_KEY) { + continue + } + // exclude the depth from the function of current package. + return i - 1 + } + } + return 0 +} diff --git a/g/internal/empty/empty.go b/g/internal/empty/empty.go index 3e07bb32e..6c9a632d8 100644 --- a/g/internal/empty/empty.go +++ b/g/internal/empty/empty.go @@ -18,7 +18,6 @@ func IsEmpty(value interface{}) bool { if value == nil { return true } - // 优先通过断言来进行常用类型判断 switch value := value.(type) { case int: return value == 0 diff --git a/g/internal/errors/errors.go b/g/internal/errors/errors.go deleted file mode 100644 index 7d6270869..000000000 --- a/g/internal/errors/errors.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 errors provides simple functions to manipulate errors. -// -// This package can be scalable due to https://go.googlesource.com/proposal/+/master/design/go2draft.md. -package errors - -import "github.com/gogf/gf/g/util/gconv" - -// errorWrapper is a simple wrapper for errors. -type errorWrapper struct { - s string -} - -// New returns an error that formats as the given value. -func New(value interface{}) error { - if value == nil { - return nil - } - return NewText(gconv.String(value)) -} - -// NewText returns an error that formats as the given text. -func NewText(text string) error { - if text == "" { - return nil - } - return &errorWrapper{ - s: text, - } -} - -// Wrap wraps error with text. -func Wrap(err error, text string) error { - if err == nil { - return nil - } - return NewText(text + ": " + err.Error()) -} - -// Error implements interface Error. -func (e *errorWrapper) Error() string { - return e.s -} diff --git a/g/internal/errors/errors_test.go b/g/internal/errors/errors_test.go deleted file mode 100644 index 7d0328baa..000000000 --- a/g/internal/errors/errors_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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 errors_test - -import ( - "testing" - - "github.com/gogf/gf/g/internal/errors" - "github.com/gogf/gf/g/test/gtest" -) - -func interfaceNil() interface{} { - return nil -} - -func nilError() error { - return nil -} - -func Test_Nil(t *testing.T) { - gtest.Case(t, func() { - gtest.Assert(errors.New(interfaceNil()), nil) - gtest.Assert(errors.Wrap(nilError(), "test"), nil) - }) -} - -func Test_Wrap(t *testing.T) { - gtest.Case(t, func() { - err := errors.New("1") - err = errors.Wrap(err, "func2 error") - err = errors.Wrap(err, "func3 error") - gtest.AssertNE(err, nil) - gtest.Assert(err.Error(), "func3 error: func2 error: 1") - }) -} diff --git a/g/net/ghttp/ghttp_server_log.go b/g/net/ghttp/ghttp_server_log.go index 745155e22..cc08b828c 100644 --- a/g/net/ghttp/ghttp_server_log.go +++ b/g/net/ghttp/ghttp_server_log.go @@ -9,6 +9,7 @@ package ghttp import ( "fmt" + "github.com/gogf/gf/g/os/gtime" ) @@ -32,7 +33,7 @@ func (s *Server) handleAccessLog(r *Request) { ) content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime-r.EnterTime)/1000) content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent()) - s.logger.Cat("access").Backtrace(false, 2).Stdout(s.config.LogStdout).Println(content) + s.logger.Cat("access").Stack(false, 2).Stdout(s.config.LogStdout).Println(content) } // 处理服务错误信息,主要是panic,http请求的status由access log进行管理 @@ -60,5 +61,5 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) { content += fmt.Sprintf(` %.3f`, float64(gtime.Microsecond()-r.EnterTime)/1000) } content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent()) - s.logger.Cat("error").Backtrace(true, 2).Stdout(s.config.LogStdout).Error(content) + s.logger.Cat("error").Stack(true, 2).Stdout(s.config.LogStdout).Error(content) } diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index 43ee80ec3..af57bf0fa 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -14,7 +14,7 @@ import ( "net" "time" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" ) // 封装的链接对象 @@ -209,7 +209,7 @@ func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry return nil, err } defer func() { - err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") + err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") }() data, err = c.Recv(length, retry...) return @@ -221,7 +221,7 @@ func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retr return err } defer func() { - err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") + err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") }() err = c.Send(data, retry...) return diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index 90b02ca97..c3172e1e4 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -11,20 +11,23 @@ import ( "fmt" "time" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" ) const ( // 默认允许最大的简单协议包大小(byte), 65535 byte - gPKG_MAX_DATA_SIZE = 65535 - // 简单协议包头大小 - gPKG_HEADER_SIZE = 3 + gPKG_DEFAULT_MAX_DATA_SIZE = 65535 + // 默认简单协议包头大小 + gPKG_DEFAULT_HEADER_SIZE = 2 + // 协议头最大大小 + gPKG_MAX_HEADER_SIZE = 4 ) // 数据读取选项 type PkgOption struct { - MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte - Retry Retry // 失败重试 + HeaderSize int // 自定义头大小(默认为2字节,最大不能超过4字节) + MaxDataSize int // (byte)数据读取的最大包大小,默认最大不能超过2字节(65535 byte) + Retry Retry // 失败重试 } // getPkgOption wraps and returns the PkgOption. @@ -34,10 +37,13 @@ func getPkgOption(option ...PkgOption) (*PkgOption, error) { if len(option) > 0 { pkgOption = option[0] } - if pkgOption.MaxSize == 0 { - pkgOption.MaxSize = gPKG_MAX_DATA_SIZE - } else if pkgOption.MaxSize > 0xFFFFFF { - return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF) + if pkgOption.HeaderSize == 0 { + pkgOption.HeaderSize = gPKG_DEFAULT_HEADER_SIZE + } + if pkgOption.MaxDataSize == 0 { + pkgOption.MaxDataSize = gPKG_DEFAULT_MAX_DATA_SIZE + } else if pkgOption.MaxDataSize > 0xFFFFFF { + return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxDataSize, 0xFFFFFF) } return &pkgOption, nil } @@ -55,17 +61,18 @@ func (c *Conn) SendPkg(data []byte, option ...PkgOption) error { return err } length := len(data) - if length > pkgOption.MaxSize { - return fmt.Errorf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_DATA_SIZE) + if length > pkgOption.MaxDataSize { + return fmt.Errorf(`data size %d exceeds max pkg size %d`, length, pkgOption.MaxDataSize) } - buffer := make([]byte, gPKG_HEADER_SIZE+1+len(data)) + offset := gPKG_MAX_HEADER_SIZE - pkgOption.HeaderSize + buffer := make([]byte, gPKG_MAX_HEADER_SIZE+len(data)) binary.BigEndian.PutUint32(buffer[0:], uint32(length)) - copy(buffer[gPKG_HEADER_SIZE+1:], data) + copy(buffer[gPKG_MAX_HEADER_SIZE:], data) if pkgOption.Retry.Count > 0 { - return c.Send(buffer[1:], pkgOption.Retry) + return c.Send(buffer[offset:], pkgOption.Retry) } - //fmt.Println("SendPkg:", buffer[1:]) - return c.Send(buffer[1:]) + //fmt.Println("SendPkg:", buffer[offset:]) + return c.Send(buffer[offset:]) } // 简单协议: 带超时时间的数据发送 @@ -74,7 +81,7 @@ func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ... return err } defer func() { - err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") + err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") }() err = c.SendPkg(data, option...) return @@ -109,26 +116,38 @@ func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) { for { // 先根据对象的缓冲区数据进行计算 for { - if len(c.buffer) >= gPKG_HEADER_SIZE { - // 注意"数据长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 - length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})) + if len(c.buffer) >= pkgOption.HeaderSize { + // 不满足4个字节的uint32类型,因此这里"低位"补0 + if length <= 0 { + switch pkgOption.HeaderSize { + case 1: + length = int(binary.BigEndian.Uint32([]byte{0, 0, 0, c.buffer[0]})) + case 2: + length = int(binary.BigEndian.Uint32([]byte{0, 0, c.buffer[0], c.buffer[1]})) + case 3: + length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})) + default: + length = int(binary.BigEndian.Uint32([]byte{c.buffer[0], c.buffer[1], c.buffer[2], c.buffer[3]})) + } + } // 解析的大小是否符合规范,清空从该连接接收到的所有数据包 - if length < 0 || length > pkgOption.MaxSize { + if length < 0 || length > pkgOption.MaxDataSize { c.buffer = c.buffer[:0] return nil, fmt.Errorf(`invalid package size %d`, length) } // 不满足包大小,需要继续读取 - if len(c.buffer) < length+gPKG_HEADER_SIZE { + if len(c.buffer) < length+pkgOption.HeaderSize { break } - result = c.buffer[gPKG_HEADER_SIZE : gPKG_HEADER_SIZE+length] - c.buffer = c.buffer[gPKG_HEADER_SIZE+length:] + result = c.buffer[pkgOption.HeaderSize : pkgOption.HeaderSize+length] + c.buffer = c.buffer[pkgOption.HeaderSize+length:] + length = 0 return } else { break } } - // 读取系统socket缓冲区的完整数据 + // 读取系统socket当前缓冲区的数据 temp, err = c.Recv(0, pkgOption.Retry) if err != nil { break @@ -147,7 +166,7 @@ func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (d return nil, err } defer func() { - err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") + err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") }() data, err = c.RecvPkg(option...) return diff --git a/g/net/gtcp/gtcp_pool.go b/g/net/gtcp/gtcp_pool.go index ec706e344..a1a0e2050 100644 --- a/g/net/gtcp/gtcp_pool.go +++ b/g/net/gtcp/gtcp_pool.go @@ -9,10 +9,9 @@ package gtcp import ( "time" - "github.com/gogf/gf/g/internal/errors" - "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gpool" + "github.com/gogf/gf/g/errors/gerror" ) // 链接池链接对象 @@ -122,7 +121,7 @@ func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...R return nil, err } defer func() { - err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") + err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") }() data, err = c.Recv(length, retry...) return @@ -134,7 +133,7 @@ func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry ... return err } defer func() { - err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") + err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") }() err = c.Send(data, retry...) return diff --git a/g/net/gtcp/gtcp_pool_pkg.go b/g/net/gtcp/gtcp_pool_pkg.go index 03a182de6..d62cdc122 100644 --- a/g/net/gtcp/gtcp_pool_pkg.go +++ b/g/net/gtcp/gtcp_pool_pkg.go @@ -9,7 +9,7 @@ package gtcp import ( "time" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" ) // 简单协议: (方法覆盖)发送数据 @@ -47,7 +47,7 @@ func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption return nil, err } defer func() { - err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") + err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") }() data, err = c.RecvPkg(option...) return @@ -59,7 +59,7 @@ func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option return err } defer func() { - err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") + err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") }() err = c.SendPkg(data, option...) return diff --git a/g/net/gudp/gudp_conn.go b/g/net/gudp/gudp_conn.go index 2e6c9fdf0..663c16b7e 100644 --- a/g/net/gudp/gudp_conn.go +++ b/g/net/gudp/gudp_conn.go @@ -11,7 +11,7 @@ import ( "net" "time" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" ) // 封装的UDP链接对象 @@ -182,7 +182,7 @@ func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry return nil, err } defer func() { - err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") + err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error") }() data, err = c.Recv(length, retry...) return @@ -194,7 +194,7 @@ func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retr return err } defer func() { - err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") + err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error") }() err = c.Send(data, retry...) return diff --git a/g/os/gcache/gcache.go b/g/os/gcache/gcache.go index 6e3fa7e6e..8815b4da7 100644 --- a/g/os/gcache/gcache.go +++ b/g/os/gcache/gcache.go @@ -12,21 +12,21 @@ var cache = New() // Set sets cache with - pair, which is expired after milliseconds. // If <=0 means it does not expire. -func Set(key interface{}, value interface{}, expire int) { - cache.Set(key, value, expire) +func Set(key interface{}, value interface{}, duration interface{}) { + cache.Set(key, value, duration) } // SetIfNotExist sets cache with - pair if does not exist in the cache, // which is expired after milliseconds. // If <=0 means it does not expire. -func SetIfNotExist(key interface{}, value interface{}, expire int) bool { - return cache.SetIfNotExist(key, value, expire) +func SetIfNotExist(key interface{}, value interface{}, duration interface{}) bool { + return cache.SetIfNotExist(key, value, duration) } // Sets batch sets cache with key-value pairs by , which is expired after milliseconds. // If <=0 means it does not expire. -func Sets(data map[interface{}]interface{}, expire int) { - cache.Sets(data, expire) +func Sets(data map[interface{}]interface{}, duration interface{}) { + cache.Sets(data, duration) } // Get returns the value of . @@ -39,8 +39,8 @@ func Get(key interface{}) interface{} { // or sets - pair and returns if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func GetOrSet(key interface{}, value interface{}, expire int) interface{} { - return cache.GetOrSet(key, value, expire) +func GetOrSet(key interface{}, value interface{}, duration interface{}) interface{} { + return cache.GetOrSet(key, value, duration) } // GetOrSetFunc returns the value of , @@ -48,8 +48,8 @@ func GetOrSet(key interface{}, value interface{}, expire int) interface{} { // if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} { - return cache.GetOrSetFunc(key, f, expire) +func GetOrSetFunc(key interface{}, f func() interface{}, duration interface{}) interface{} { + return cache.GetOrSetFunc(key, f, duration) } // GetOrSetFuncLock returns the value of , @@ -59,8 +59,8 @@ func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} // If <=0 means it does not expire. // // Note that the function is executed within writing mutex lock. -func GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} { - return cache.GetOrSetFuncLock(key, f, expire) +func GetOrSetFuncLock(key interface{}, f func() interface{}, duration interface{}) interface{} { + return cache.GetOrSetFuncLock(key, f, duration) } // Contains returns true if exists in the cache, or else returns false. diff --git a/g/os/gcache/gcache_mem_cache.go b/g/os/gcache/gcache_mem_cache.go index edb648c10..976d2c5a1 100644 --- a/g/os/gcache/gcache_mem_cache.go +++ b/g/os/gcache/gcache_mem_cache.go @@ -9,6 +9,7 @@ package gcache import ( "math" "sync" + "time" "github.com/gogf/gf/g/container/glist" "github.com/gogf/gf/g/container/gset" @@ -103,9 +104,21 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) { return } +// getMilliExpire converts parameter to int type in milliseconds. +// +// Note that there's some performance cost in type assertion here, but it's valuable. +func (c *memCache) getMilliExpire(duration interface{}) int { + if d, ok := duration.(time.Duration); ok { + return int(d.Nanoseconds() / 1000000) + } else { + return duration.(int) + } +} + // Set sets cache with - pair, which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) Set(key interface{}, value interface{}, expire int) { +func (c *memCache) Set(key interface{}, value interface{}, duration interface{}) { + expire := c.getMilliExpire(duration) expireTime := c.getInternalExpire(expire) c.dataMu.Lock() c.data[key] = memCacheItem{v: value, e: expireTime} @@ -119,7 +132,8 @@ func (c *memCache) Set(key interface{}, value interface{}, expire int) { // // It doubly checks the whether exists in the cache using mutex writing lock // before setting it to the cache. -func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, expire int) interface{} { +func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration interface{}) interface{} { + expire := c.getMilliExpire(duration) expireTimestamp := c.getInternalExpire(expire) c.dataMu.Lock() defer c.dataMu.Unlock() @@ -149,7 +163,8 @@ func (c *memCache) getInternalExpire(expire int) int64 { // SetIfNotExist sets cache with - pair if does not exist in the cache, // which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) SetIfNotExist(key interface{}, value interface{}, expire int) bool { +func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration interface{}) bool { + expire := c.getMilliExpire(duration) if !c.Contains(key) { c.doSetWithLockCheck(key, value, expire) return true @@ -159,7 +174,8 @@ func (c *memCache) SetIfNotExist(key interface{}, value interface{}, expire int) // Sets batch sets cache with key-value pairs by , which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) Sets(data map[interface{}]interface{}, expire int) { +func (c *memCache) Sets(data map[interface{}]interface{}, duration interface{}) { + expire := c.getMilliExpire(duration) expireTime := c.getInternalExpire(expire) for k, v := range data { c.dataMu.Lock() @@ -189,9 +205,9 @@ func (c *memCache) Get(key interface{}) interface{} { // or sets - pair and returns if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func (c *memCache) GetOrSet(key interface{}, value interface{}, expire int) interface{} { +func (c *memCache) GetOrSet(key interface{}, value interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, value, expire) + return c.doSetWithLockCheck(key, value, duration) } else { return v } @@ -202,9 +218,9 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, expire int) inte // if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} { +func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, f(), expire) + return c.doSetWithLockCheck(key, f(), duration) } else { return v } @@ -217,9 +233,9 @@ func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire in // If <=0 means it does not expire. // // Note that the function is executed within writing mutex lock. -func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} { +func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, f, expire) + return c.doSetWithLockCheck(key, f, duration) } else { return v } diff --git a/g/os/gcache/gcache_z_unit_1_test.go b/g/os/gcache/gcache_z_unit_1_test.go index 0cd6ea07c..3c89dad8f 100644 --- a/g/os/gcache/gcache_z_unit_1_test.go +++ b/g/os/gcache/gcache_z_unit_1_test.go @@ -9,13 +9,14 @@ package gcache_test import ( + "testing" + "time" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/container/gset" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/os/grpool" "github.com/gogf/gf/g/test/gtest" - "testing" - "time" ) //clear 用于清除全局缓存,因gcache api 暂未暴露 Clear 方法 @@ -49,6 +50,14 @@ func TestCache_Set_Expire(t *testing.T) { gtest.Assert(cache.Size(), 0) cache.Close() }) + + gtest.Case(t, func() { + cache := gcache.New() + cache.Set(1, 11, 100*time.Millisecond) + gtest.Assert(cache.Get(1), 11) + time.Sleep(200 * time.Millisecond) + gtest.Assert(cache.Get(1), nil) + }) } func TestCache_Keys_Values(t *testing.T) { @@ -205,7 +214,7 @@ func TestCache_SetConcurrency(t *testing.T) { }() select { case <-time.After(2 * time.Second): - t.Log("first part end") + //t.Log("first part end") } go func() { @@ -217,7 +226,7 @@ func TestCache_SetConcurrency(t *testing.T) { }() select { case <-time.After(2 * time.Second): - t.Log("second part end") + //t.Log("second part end") } }) } diff --git a/g/os/gfcache/gfcache.go b/g/os/gfcache/gfcache.go index 9cea2062b..d32bb1f18 100644 --- a/g/os/gfcache/gfcache.go +++ b/g/os/gfcache/gfcache.go @@ -8,6 +8,8 @@ package gfcache import ( + "time" + "github.com/gogf/gf/g/internal/cmdenv" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/os/gfile" @@ -27,18 +29,18 @@ var ( // GetContents returns string content of given file by from cache. // If there's no content in the cache, it will read it from disk file specified by . // The parameter specifies the caching time for this file content in seconds. -func GetContents(path string, expire ...int) string { - return string(GetBinContents(path, expire...)) +func GetContents(path string, duration ...interface{}) string { + return string(GetBinContents(path, duration...)) } // GetBinContents returns []byte content of given file by from cache. // If there's no content in the cache, it will read it from disk file specified by . // The parameter specifies the caching time for this file content in seconds. -func GetBinContents(path string, expire ...int) []byte { +func GetBinContents(path string, duration ...interface{}) []byte { k := cacheKey(path) e := cacheExpire - if len(expire) > 0 { - e = expire[0] + if len(duration) > 0 { + e = getSecondExpire(duration[0]) } r := gcache.GetOrSetFuncLock(k, func() interface{} { b := gfile.GetBinContents(path) @@ -58,7 +60,18 @@ func GetBinContents(path string, expire ...int) []byte { return nil } -// 生成缓存键名 +// getSecondExpire converts parameter to int type in seconds. +// +// Note that there's some performance cost in type assertion here, but it's valuable. +func getSecondExpire(duration interface{}) int { + if d, ok := duration.(time.Duration); ok { + return int(d.Nanoseconds() / 1000000000) + } else { + return duration.(int) + } +} + +// cacheKey produces the cache key for gcache. func cacheKey(path string) string { return "gf.gfcache:" + path } diff --git a/g/os/glog/glog.go b/g/os/glog/glog.go index 474d7af2d..b6bee7509 100644 --- a/g/os/glog/glog.go +++ b/g/os/glog/glog.go @@ -8,9 +8,10 @@ package glog import ( + "io" + "github.com/gogf/gf/g/internal/cmdenv" "github.com/gogf/gf/g/os/grpool" - "io" ) const ( @@ -115,19 +116,19 @@ func GetFlags() int { return logger.GetFlags() } -// PrintBacktrace prints the caller backtrace, -// the optional parameter specify the skipped backtrace offset from the end point. -func PrintBacktrace(skip ...int) { - logger.PrintBacktrace(skip...) +// PrintStack prints the caller stack, +// the optional parameter specify the skipped stack offset from the end point. +func PrintStack(skip ...int) { + logger.PrintStack(skip...) } -// GetBacktrace returns the caller backtrace content, -// the optional parameter specify the skipped backtrace offset from the end point. -func GetBacktrace(skip ...int) string { - return logger.GetBacktrace(skip...) +// GetStack returns the caller stack content, +// the optional parameter specify the skipped stack offset from the end point. +func GetStack(skip ...int) string { + return logger.GetStack(skip...) } -// SetBacktrace enables/disables the backtrace feature in failure logging outputs. -func SetBacktrace(enabled bool) { - logger.SetBacktrace(enabled) +// SetStack enables/disables the stack feature in failure logging outputs. +func SetStack(enabled bool) { + logger.SetStack(enabled) } diff --git a/g/os/glog/glog_api.go b/g/os/glog/glog_api.go index 1a127a012..52c9bfb1d 100644 --- a/g/os/glog/glog_api.go +++ b/g/os/glog/glog_api.go @@ -94,13 +94,13 @@ func Debugfln(format string, v ...interface{}) { } // Notice prints the logging content with [NOTI] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Notice(v ...interface{}) { logger.Notice(v...) } // Noticef prints the logging content with [NOTI] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Noticef(format string, v ...interface{}) { logger.Noticef(format, v...) } @@ -112,13 +112,13 @@ func Noticefln(format string, v ...interface{}) { } // Warning prints the logging content with [WARN] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Warning(v ...interface{}) { logger.Warning(v...) } // Warningf prints the logging content with [WARN] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Warningf(format string, v ...interface{}) { logger.Warningf(format, v...) } @@ -130,13 +130,13 @@ func Warningfln(format string, v ...interface{}) { } // Error prints the logging content with [ERRO] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Error(v ...interface{}) { logger.Error(v...) } // Errorf prints the logging content with [ERRO] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Errorf(format string, v ...interface{}) { logger.Errorf(format, v...) } @@ -148,13 +148,13 @@ func Errorfln(format string, v ...interface{}) { } // Critical prints the logging content with [CRIT] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Critical(v ...interface{}) { logger.Critical(v...) } // Criticalf prints the logging content with [CRIT] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func Criticalf(format string, v ...interface{}) { logger.Criticalf(format, v...) } diff --git a/g/os/glog/glog_chaining.go b/g/os/glog/glog_chaining.go index f380c0e02..7195f22bf 100644 --- a/g/os/glog/glog_chaining.go +++ b/g/os/glog/glog_chaining.go @@ -46,16 +46,16 @@ func Level(level int) *Logger { } // Skip is a chaining function, -// which sets backtrace skip for the current logging content output. +// which sets stack skip for the current logging content output. // It also affects the caller file path checks when line number printing enabled. func Skip(skip int) *Logger { return logger.Skip(skip) } -// Backtrace is a chaining function, -// which sets backtrace options for the current logging content output . -func Backtrace(enabled bool, skip ...int) *Logger { - return logger.Backtrace(enabled, skip...) +// Stack is a chaining function, +// which sets stack options for the current logging content output . +func Stack(enabled bool, skip ...int) *Logger { + return logger.Stack(enabled, skip...) } // StdPrint is a chaining function, diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 49e448352..a8ea66ee4 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -10,17 +10,18 @@ import ( "bytes" "errors" "fmt" + "io" + "os" + "strings" + "time" + + "github.com/gogf/gf/g/internal/debug" + "github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/os/gfpool" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/text/gregex" "github.com/gogf/gf/g/util/gconv" - "io" - "os" - "regexp" - "runtime" - "strings" - "time" ) type Logger struct { @@ -31,8 +32,8 @@ type Logger struct { file string // Format for logging file. level int // Output level. prefix string // Prefix string for every logging content. - btSkip int // Skip count for backtrace. - btStatus int // Backtrace status(1: enabled - default; 0: disabled) + stSkip int // Skip count for stack. + stStatus int // Stack status(1: enabled - default; 0: disabled) headerPrint bool // Print header or not(true in default). stdoutPrint bool // Output to stdout or not(true in default). } @@ -42,6 +43,7 @@ const ( gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE | os.O_WRONLY | os.O_APPEND gDEFAULT_FPOOL_PERM = os.FileMode(0666) gDEFAULT_FPOOL_EXPIRE = 60000 + gPATH_FILTER_KEY = "/g/os/glog/glog" ) const ( @@ -54,25 +56,13 @@ const ( F_TIME_STD = F_TIME_DATE | F_TIME_MILLI ) -var ( - // Default line break. - ln = "\n" -) - -func init() { - // Initialize log line breaks depending on underlying os. - if runtime.GOOS == "windows" { - ln = "\r\n" - } -} - // New creates and returns a custom logger. func New() *Logger { logger := &Logger{ file: gDEFAULT_FILE_FORMAT, flags: F_TIME_STD, level: LEVEL_ALL, - btStatus: 1, + stStatus: 1, headerPrint: true, stdoutPrint: true, } @@ -126,18 +116,18 @@ func (l *Logger) GetFlags() int { return l.flags } -// SetBacktrace enables/disables the backtrace feature in failure logging outputs. -func (l *Logger) SetBacktrace(enabled bool) { +// SetStack enables/disables the stack feature in failure logging outputs. +func (l *Logger) SetStack(enabled bool) { if enabled { - l.btStatus = 1 + l.stStatus = 1 } else { - l.btStatus = 0 + l.stStatus = 0 } } -// SetBacktraceSkip sets the backtrace offset from the end point. -func (l *Logger) SetBacktraceSkip(skip int) { - l.btSkip = skip +// SetStackSkip sets the stack offset from the end point. +func (l *Logger) SetStackSkip(skip int) { + l.stSkip = skip } // SetWriter sets the customized logging for logging. @@ -254,10 +244,10 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { // Caller path. callerPath := "" if l.flags&F_FILE_LONG > 0 { - callerPath = l.getLongFile() + ": " + callerPath = debug.CallerWithFilter(gPATH_FILTER_KEY) + ": " } if l.flags&F_FILE_SHORT > 0 { - callerPath = gfile.Basename(l.getLongFile()) + ": " + callerPath = gfile.Basename(debug.CallerWithFilter(gPATH_FILTER_KEY)) + ": " } if len(callerPath) > 0 { buffer.WriteString(callerPath) @@ -267,13 +257,30 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { buffer.WriteString(l.prefix + " ") } } - for k, v := range value { - if k > 0 { - buffer.WriteByte(' ') + // Convert value to string. + tempStr := "" + valueStr := "" + for _, v := range value { + if err, ok := v.(error); ok { + tempStr = fmt.Sprintf("%+v", err) + } else { + tempStr = gconv.String(v) + } + if len(valueStr) > 0 { + if valueStr[len(valueStr)-1] == '\n' { + if tempStr[0] == '\n' { + valueStr += tempStr[1:] + } else { + valueStr += tempStr + } + } else { + valueStr += " " + tempStr + } + } else { + valueStr = tempStr } - buffer.WriteString(gconv.String(v)) } - buffer.WriteString(ln) + buffer.WriteString(valueStr + "\n") if l.flags&F_ASYNC > 0 { asyncPool.Add(func() { l.printToWriter(std, buffer) @@ -305,16 +312,16 @@ func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) { } } -// printStd prints content without backtrace. +// printStd prints content without stack. func (l *Logger) printStd(lead string, value ...interface{}) { l.print(os.Stdout, lead, value...) } -// printStd prints content with backtrace check. +// printStd prints content with stack check. func (l *Logger) printErr(lead string, value ...interface{}) { - if l.btStatus == 1 { - if s := l.GetBacktrace(); s != "" { - value = append(value, ln+"Backtrace:"+ln+s) + if l.stStatus == 1 { + if s := l.GetStack(); s != "" { + value = append(value, "\nStack:\n"+s) } } // In matter of sequence, do not use stderr here, but use the same stdout. @@ -326,76 +333,22 @@ func (l *Logger) format(format string, value ...interface{}) string { return fmt.Sprintf(format, value...) } -// PrintBacktrace prints the caller backtrace, -// the optional parameter specify the skipped backtrace offset from the end point. -func (l *Logger) PrintBacktrace(skip ...int) { - if s := l.GetBacktrace(skip...); s != "" { - l.Println("Backtrace:" + ln + s) +// PrintStack prints the caller stack, +// the optional parameter specify the skipped stack offset from the end point. +func (l *Logger) PrintStack(skip ...int) { + if s := l.GetStack(skip...); s != "" { + l.Println("Stack:\n" + s) } else { l.Println() } } -// GetBacktrace returns the caller backtrace content, -// the optional parameter specify the skipped backtrace offset from the end point. -func (l *Logger) GetBacktrace(skip ...int) string { - customSkip := 0 +// GetStack returns the caller stack content, +// the optional parameter specify the skipped stack offset from the end point. +func (l *Logger) GetStack(skip ...int) string { + number := 1 if len(skip) > 0 { - customSkip = skip[0] + number = skip[0] + 1 } - backtrace := "" - from := 0 - // Find the caller position exclusive of the glog file. - for i := 0; i < 1000; i++ { - if _, file, _, ok := runtime.Caller(i); ok { - if !gregex.IsMatchString("/g/os/glog/glog.+$", file) { - from = i - break - } - } - } - // Find the true caller file path using custom skip. - index := 1 - goRoot := runtime.GOROOT() - if goRoot != "" { - goRoot = strings.Replace(goRoot, "\\", "/", -1) - goRoot = regexp.QuoteMeta(goRoot) - } - for i := from + customSkip + l.btSkip; i < 1000; i++ { - if _, file, cline, ok := runtime.Caller(i); ok && len(file) > 2 { - if (goRoot == "" || !gregex.IsMatchString("^"+goRoot, file)) && !gregex.IsMatchString(``, file) { - backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, ln) - index++ - } - } else { - break - } - } - return backtrace -} - -// getLongFile returns the absolute file path along with its line number of the caller. -func (l *Logger) getLongFile() string { - from := 0 - // Find the caller position exclusive of the glog file. - for i := 0; i < 1000; i++ { - if _, file, _, ok := runtime.Caller(i); ok { - if !gregex.IsMatchString("/g/os/glog/glog.+$", file) { - from = i - break - } - } - } - // Find the true caller file path using custom skip. - goRoot := runtime.GOROOT() - for i := from + l.btSkip; i < 1000; i++ { - if _, file, line, ok := runtime.Caller(i); ok && len(file) > 2 { - if (goRoot == "" || !gregex.IsMatchString("^"+goRoot, file)) && !gregex.IsMatchString(``, file) { - return fmt.Sprintf(`%s:%d`, file, line) - } - } else { - break - } - } - return "" + return debug.StackWithFilter(gPATH_FILTER_KEY, number) } diff --git a/g/os/glog/glog_logger_api.go b/g/os/glog/glog_logger_api.go index d75fefb51..adbcdf6e2 100644 --- a/g/os/glog/glog_logger_api.go +++ b/g/os/glog/glog_logger_api.go @@ -116,7 +116,7 @@ func (l *Logger) Debugfln(format string, v ...interface{}) { } // Notice prints the logging content with [NOTI] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Notice(v ...interface{}) { if l.checkLevel(LEVEL_NOTI) { l.printErr("[NOTI]", v...) @@ -124,7 +124,7 @@ func (l *Logger) Notice(v ...interface{}) { } // Noticef prints the logging content with [NOTI] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Noticef(format string, v ...interface{}) { if l.checkLevel(LEVEL_NOTI) { l.printErr("[NOTI]", l.format(format, v...)) @@ -140,7 +140,7 @@ func (l *Logger) Noticefln(format string, v ...interface{}) { } // Warning prints the logging content with [WARN] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Warning(v ...interface{}) { if l.checkLevel(LEVEL_WARN) { l.printErr("[WARN]", v...) @@ -148,7 +148,7 @@ func (l *Logger) Warning(v ...interface{}) { } // Warningf prints the logging content with [WARN] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Warningf(format string, v ...interface{}) { if l.checkLevel(LEVEL_WARN) { l.printErr("[WARN]", l.format(format, v...)) @@ -164,7 +164,7 @@ func (l *Logger) Warningfln(format string, v ...interface{}) { } // Error prints the logging content with [ERRO] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Error(v ...interface{}) { if l.checkLevel(LEVEL_ERRO) { l.printErr("[ERRO]", v...) @@ -172,7 +172,7 @@ func (l *Logger) Error(v ...interface{}) { } // Errorf prints the logging content with [ERRO] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Errorf(format string, v ...interface{}) { if l.checkLevel(LEVEL_ERRO) { l.printErr("[ERRO]", l.format(format, v...)) @@ -188,7 +188,7 @@ func (l *Logger) Errorfln(format string, v ...interface{}) { } // Critical prints the logging content with [CRIT] header and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Critical(v ...interface{}) { if l.checkLevel(LEVEL_CRIT) { l.printErr("[CRIT]", v...) @@ -196,7 +196,7 @@ func (l *Logger) Critical(v ...interface{}) { } // Criticalf prints the logging content with [CRIT] header, custom format and newline. -// It also prints caller backtrace info if backtrace feature is enabled. +// It also prints caller stack info if stack feature is enabled. func (l *Logger) Criticalf(format string, v ...interface{}) { if l.checkLevel(LEVEL_CRIT) { l.printErr("[CRIT]", l.format(format, v...)) diff --git a/g/os/glog/glog_logger_chaining.go b/g/os/glog/glog_logger_chaining.go index d60e3366a..02e43630e 100644 --- a/g/os/glog/glog_logger_chaining.go +++ b/g/os/glog/glog_logger_chaining.go @@ -7,8 +7,9 @@ package glog import ( - "github.com/gogf/gf/g/os/gfile" "io" + + "github.com/gogf/gf/g/os/gfile" ) // To is a chaining function, @@ -82,7 +83,7 @@ func (l *Logger) Level(level int) *Logger { } // Skip is a chaining function, -// which sets backtrace skip for the current logging content output. +// which sets stack skip for the current logging content output. // It also affects the caller file path checks when line number printing enabled. func (l *Logger) Skip(skip int) *Logger { logger := (*Logger)(nil) @@ -91,22 +92,22 @@ func (l *Logger) Skip(skip int) *Logger { } else { logger = l } - logger.SetBacktraceSkip(skip) + logger.SetStackSkip(skip) return logger } -// Backtrace is a chaining function, -// which sets backtrace options for the current logging content output . -func (l *Logger) Backtrace(enabled bool, skip ...int) *Logger { +// Stack is a chaining function, +// which sets stack options for the current logging content output . +func (l *Logger) Stack(enabled bool, skip ...int) *Logger { logger := (*Logger)(nil) if l.parent == nil { logger = l.Clone() } else { logger = l } - logger.SetBacktrace(enabled) + logger.SetStack(enabled) if len(skip) > 0 { - logger.SetBacktraceSkip(skip[0]) + logger.SetStackSkip(skip[0]) } return logger } diff --git a/g/os/gtime/gtime_time.go b/g/os/gtime/gtime_time.go index 368d515a6..675d3d88a 100644 --- a/g/os/gtime/gtime_time.go +++ b/g/os/gtime/gtime_time.go @@ -115,6 +115,16 @@ func (t *Time) Add(d time.Duration) *Time { return t } +// 当前时间加上指定时间段(使用字符串格式) +func (t *Time) AddStr(duration string) error { + if d, err := time.ParseDuration(duration); err != nil { + return err + } else { + t.Time = t.Time.Add(d) + } + return nil +} + // 时区转换为指定的时区(通过time.Location) func (t *Time) ToLocation(location *time.Location) *Time { t.Time = t.Time.In(location) diff --git a/g/test/gtest/gtest.go b/g/test/gtest/gtest.go index 71f03b764..a27f91c34 100644 --- a/g/test/gtest/gtest.go +++ b/g/test/gtest/gtest.go @@ -25,7 +25,7 @@ import ( func Case(t *testing.T, f func()) { defer func() { if err := recover(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n%s", err, getBacktrace()) + fmt.Fprintf(os.Stderr, "%v\n%s", err, getStack()) t.Fail() } }() @@ -256,7 +256,7 @@ func Error(message ...interface{}) { // Fatal prints to stderr and exit the process. func Fatal(message ...interface{}) { - fmt.Fprintf(os.Stderr, "[FATAL] %s\n%s", fmt.Sprint(message...), getBacktrace()) + fmt.Fprintf(os.Stderr, "[FATAL] %s\n%s", fmt.Sprint(message...), getStack()) os.Exit(1) } @@ -299,14 +299,14 @@ func compareMap(value, expect interface{}) error { return nil } -// getBacktrace returns the caller backtrace content from getBacktrace. -// The parameter indicates the skip count of the caller backtrace from getBacktrace. -func getBacktrace(skip ...int) string { +// getStack returns the caller stack content from getStack. +// The parameter indicates the skip count of the caller stack from getStack. +func getStack(skip ...int) string { customSkip := 0 if len(skip) > 0 { customSkip = skip[0] } - backtrace := "" + stack := "" index := 1 from := 0 // Ignore current gtest lines and find the beginning index of caller file. @@ -337,13 +337,13 @@ func getBacktrace(skip ...int) string { continue } } - backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, "\n") + stack += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, "\n") index++ } else { break } } - return backtrace + return stack } // isNil checks whether is nil. diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go index 2ef4c851e..de320ba5a 100644 --- a/g/util/gutil/gutil.go +++ b/g/util/gutil/gutil.go @@ -11,10 +11,10 @@ import ( "bytes" "encoding/json" "fmt" + "os" + "github.com/gogf/gf/g/internal/empty" "github.com/gogf/gf/g/util/gconv" - "os" - "runtime" ) // Dump prints variables to stdout with more manually readable. @@ -46,21 +46,6 @@ func Export(i ...interface{}) string { return buffer.String() } -// PrintBacktrace prints the caller backtrace to stdout. -func PrintBacktrace() { - index := 1 - buffer := bytes.NewBuffer(nil) - for i := 1; i < 10000; i++ { - if _, path, line, ok := runtime.Caller(i); ok { - buffer.WriteString(fmt.Sprintf(`%d. %s:%d%s`, index, path, line, "\n")) - index++ - } else { - break - } - } - fmt.Print(buffer.String()) -} - // Throw throws out an exception, which can be caught be TryCatch or recover. func Throw(exception interface{}) { panic(exception) diff --git a/g/util/gutil/gutil_debug.go b/g/util/gutil/gutil_debug.go new file mode 100644 index 000000000..a9ba7c9f5 --- /dev/null +++ b/g/util/gutil/gutil_debug.go @@ -0,0 +1,30 @@ +// Copyright 2017 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 gutil + +import ( + "github.com/gogf/gf/g/internal/debug" +) + +// PrintStack prints to standard error the stack trace returned by runtime.Stack. +func PrintStack(skip ...int) { + number := 1 + if len(skip) > 0 { + number = skip[0] + 1 + } + debug.PrintStack(number) +} + +// Stack returns a formatted stack trace of the goroutine that calls it. +// It calls runtime.Stack with a large enough buffer to capture the entire trace. +func Stack(skip ...int) string { + number := 1 + if len(skip) > 0 { + number = skip[0] + 1 + } + return debug.Stack(number) +} diff --git a/g/util/gutil/gutil_comparator_z_unit_test.go b/g/util/gutil/gutil_z_comparator_z_unit_test.go similarity index 100% rename from g/util/gutil/gutil_comparator_z_unit_test.go rename to g/util/gutil/gutil_z_comparator_z_unit_test.go diff --git a/g/util/gutil/gutil_z_unit_test.go b/g/util/gutil/gutil_z_unit_test.go index cf52edc2a..05fd8a9b9 100755 --- a/g/util/gutil/gutil_z_unit_test.go +++ b/g/util/gutil/gutil_z_unit_test.go @@ -23,9 +23,9 @@ func Test_Dump(t *testing.T) { }) } -func Test_PrintBacktrace(t *testing.T) { +func Test_PrintStack(t *testing.T) { gtest.Case(t, func() { - gutil.PrintBacktrace() + gutil.PrintStack() }) } diff --git a/g/util/gvalid/gvalid_unit_checkstruct_test.go b/g/util/gvalid/gvalid_unit_checkstruct_test.go index 6c4b68754..a02fc555b 100755 --- a/g/util/gvalid/gvalid_unit_checkstruct_test.go +++ b/g/util/gvalid/gvalid_unit_checkstruct_test.go @@ -237,7 +237,7 @@ func Test_CheckStruct_With_Inherit(t *testing.T) { Pass Pass } user := &User{ - Name: "", + Name: "john", Pass: Pass{ Pass1: "1", Pass2: "2", diff --git a/geg/database/gdb/mssql/gdb_sqlserver.go b/geg/database/gdb/mssql/gdb_sqlserver.go index 518d03515..36e8a49f2 100644 --- a/geg/database/gdb/mssql/gdb_sqlserver.go +++ b/geg/database/gdb/mssql/gdb_sqlserver.go @@ -3,6 +3,7 @@ package main import ( "fmt" "time" + //_ "github.com/denisenkom/go-mssqldb" "github.com/gogf/gf/g" "github.com/gogf/gf/g/database/gdb" @@ -49,7 +50,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.2", @@ -59,7 +60,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.3", @@ -69,7 +70,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.4", @@ -79,7 +80,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // }, //}) diff --git a/geg/database/gdb/mysql/config.toml b/geg/database/gdb/mysql/config.toml index be988e9f5..db81820fe 100644 --- a/geg/database/gdb/mysql/config.toml +++ b/geg/database/gdb/mysql/config.toml @@ -9,5 +9,6 @@ name = "test" type = "mysql" role = "master" + debug = "true" + weight = "1" charset = "utf8" - priority = "1" diff --git a/geg/database/gdb/mysql/gdb.go b/geg/database/gdb/mysql/gdb.go index 1a3b29a4a..e3aaf7e9f 100644 --- a/geg/database/gdb/mysql/gdb.go +++ b/geg/database/gdb/mysql/gdb.go @@ -2,9 +2,10 @@ package main import ( "fmt" + "time" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/database/gdb" - "time" ) // 本文件用于gf框架的mysql数据库操作示例,不作为单元测试使用 @@ -48,7 +49,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.2", @@ -58,7 +59,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.3", @@ -68,7 +69,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.4", @@ -78,7 +79,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // }, //}) diff --git a/geg/database/gdb/mysql/gdb_args_slice.go b/geg/database/gdb/mysql/gdb_args_slice.go new file mode 100644 index 000000000..9c7c568ad --- /dev/null +++ b/geg/database/gdb/mysql/gdb_args_slice.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/gogf/gf/g" +) + +func main() { + db := g.DB() + + db.Table("user").Where("nickname like ? and passport like ?", g.Slice{"T3", "t3"}).OrderBy("id asc").All() + + conditions := g.Map{ + "nickname like ?": "%T%", + "id between ? and ?": g.Slice{1, 3}, + "id >= ?": 1, + "create_time > ?": 0, + "id in(?)": g.Slice{1, 2, 3}, + } + db.Table("user").Where(conditions).OrderBy("id asc").All() +} diff --git a/geg/database/gdb/mysql/gdb_debug.go b/geg/database/gdb/mysql/gdb_debug1.go similarity index 94% rename from geg/database/gdb/mysql/gdb_debug.go rename to geg/database/gdb/mysql/gdb_debug1.go index 8354514e2..6452d5eed 100644 --- a/geg/database/gdb/mysql/gdb_debug.go +++ b/geg/database/gdb/mysql/gdb_debug1.go @@ -24,8 +24,7 @@ func main() { panic(err) } db.SetDebug(true) - db.Table("user").Limit(2).Delete() - return + glog.SetPath("/tmp") // 执行3条SQL查询 diff --git a/geg/database/gdb/mysql/gdb_debug2.go b/geg/database/gdb/mysql/gdb_debug2.go new file mode 100644 index 000000000..504e25e82 --- /dev/null +++ b/geg/database/gdb/mysql/gdb_debug2.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/gogf/gf/g" +) + +func main() { + db := g.DB() + // 执行3条SQL查询 + for i := 1; i <= 3; i++ { + db.Table("user").Where("id=?", i).One() + } + // 构造一条错误查询 + db.Table("user").Where("no_such_field=?", "just_test").One() + + db.Table("user").Data(g.Map{"name": "smith"}).Where("uid=?", 1).Save() +} diff --git a/geg/database/gdb/oracle/gdb.go b/geg/database/gdb/oracle/gdb.go index 7c572b908..9fa8bb9f0 100644 --- a/geg/database/gdb/oracle/gdb.go +++ b/geg/database/gdb/oracle/gdb.go @@ -3,6 +3,7 @@ package main import ( "fmt" "time" + //_ "github.com/mattn/go-oci8" "github.com/gogf/gf/g" "github.com/gogf/gf/g/database/gdb" @@ -48,7 +49,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.2", @@ -58,7 +59,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.3", @@ -68,7 +69,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // gdb.ConfigNode { // Host : "127.0.0.4", @@ -78,7 +79,7 @@ func init() { // Name : "test", // Type : "mysql", // Role : "master", - // Priority : 100, + // Weight : 100, // }, // }, //}) diff --git a/geg/errors/gerror/gerror1.go b/geg/errors/gerror/gerror1.go new file mode 100644 index 000000000..809fc81f6 --- /dev/null +++ b/geg/errors/gerror/gerror1.go @@ -0,0 +1,25 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/gogf/gf/g/errors/gerror" +) + +func Error1() error { + return errors.New("test") +} + +func Error2() error { + return gerror.New("test") +} + +func main() { + err1 := Error1() + err2 := Error2() + fmt.Printf("%s, %-s, %+s\n", err1, err1, err1) + fmt.Printf("%v, %-v, %+v\n", err1, err1, err1) + fmt.Printf("%s, %-s, %+s\n", err2, err2, err2) + fmt.Printf("%v, %-v, %+v\n", err2, err2, err2) +} diff --git a/geg/errors/gerror/gerror2.go b/geg/errors/gerror/gerror2.go new file mode 100644 index 000000000..a210d5fef --- /dev/null +++ b/geg/errors/gerror/gerror2.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/gogf/gf/g/os/glog" + + "github.com/gogf/gf/g/errors/gerror" +) + +func OpenFile() error { + return gerror.New("permission denied") +} + +func OpenConfig() error { + return gerror.Wrap(OpenFile(), "configuration file opening failed") +} + +func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") +} + +func main() { + err := ReadConfig() + glog.Printf("%s\n%+s", err, err) + glog.Printf("%+v", err) + glog.Error(err) +} diff --git a/geg/os/glog/glog_backtrace.go b/geg/os/glog/glog_backtrace.go deleted file mode 100644 index 1cd001b7f..000000000 --- a/geg/os/glog/glog_backtrace.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "fmt" - "github.com/gogf/gf/g/os/glog" -) - -func main() { - - glog.PrintBacktrace() - glog.New().PrintBacktrace() - - fmt.Println(glog.GetBacktrace()) - fmt.Println(glog.New().GetBacktrace()) -} diff --git a/geg/os/glog/glog_error.go b/geg/os/glog/glog_error.go index 18738726c..1a145d768 100644 --- a/geg/os/glog/glog_error.go +++ b/geg/os/glog/glog_error.go @@ -1,11 +1,11 @@ package main -import ( - "github.com/gogf/gf/g/os/glog" -) +import "github.com/gogf/gf/g/os/glog" + +func Test() { + glog.Error("This is error!") +} func main() { - //glog.SetPath("/tmp/") - glog.Error("This is error!") - glog.Errorf("This is error, %d!", 2) + Test() } diff --git a/geg/os/glog/glog_gerror.go b/geg/os/glog/glog_gerror.go new file mode 100644 index 000000000..f5837c871 --- /dev/null +++ b/geg/os/glog/glog_gerror.go @@ -0,0 +1,26 @@ +package main + +import ( + "errors" + "github.com/gogf/gf/g/errors/gerror" + "github.com/gogf/gf/g/os/glog" +) + +func MakeError() error { + return errors.New("connection closed with normal error") +} + +func MakeGError() error { + return gerror.New("connection closed with gerror") +} + +func TestGError() { + err1 := MakeError() + err2 := MakeGError() + glog.Error(err1) + glog.Error(err2) +} + +func main() { + TestGError() +} diff --git a/geg/os/glog/glog_line2.go b/geg/os/glog/glog_line2.go index 2e3730114..ade091755 100644 --- a/geg/os/glog/glog_line2.go +++ b/geg/os/glog/glog_line2.go @@ -6,7 +6,7 @@ import ( func PrintLog(content string) { glog.Skip(1).Line().Println("line number with skip:", content) - glog.Line().Println("line number without skip:", content) + glog.Line(true).Println("line number without skip:", content) } func main() { diff --git a/geg/os/glog/glog_stack.go b/geg/os/glog/glog_stack.go new file mode 100644 index 000000000..26e69ad26 --- /dev/null +++ b/geg/os/glog/glog_stack.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + + "github.com/gogf/gf/g/os/glog" +) + +func main() { + + glog.PrintStack() + glog.New().PrintStack() + + fmt.Println(glog.GetStack()) + fmt.Println(glog.New().GetStack()) +} diff --git a/geg/other/test.go b/geg/other/test.go index f67617e91..06ab7d0f9 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,20 +1 @@ package main - -import ( - "fmt" - "math" - - "github.com/gogf/gf/g/encoding/gbinary" -) - -func main() { - v := math.MaxUint16 - //v := []byte{255, 127} - //ve := gbinary.Encode(v) - //ve1 := gbinary.BeEncodeByLength(len(ve), v) - //fmt.Println(ve) - //fmt.Println(ve1) - - //fmt.Println(gbinary.LeDecodeToInt(gbinary.LeEncode(v))) - fmt.Println(gbinary.BeDecodeToInt(gbinary.BeEncode(v))) -} diff --git a/geg/other/test2.go b/geg/other/test2.go index 8623d32ca..76168a175 100644 --- a/geg/other/test2.go +++ b/geg/other/test2.go @@ -1,39 +1,11 @@ package main -import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" -) +import "github.com/gogf/gf/g/os/glog" -type Schedule struct{} +func Test() { -type Task struct{} - -func (c *Schedule) ListDir(r *ghttp.Request) { - r.Response.Writeln("ListDir") -} - -func (c *Task) Add(r *ghttp.Request) { - r.Response.Writeln("Add") -} - -func (c *Task) Task(r *ghttp.Request) { - r.Response.Writeln("Task") -} - -// 实现权限校验 -// 通过事件回调,类似于中间件机制,但是可控制的粒度更细,可以精准注册到路由规则 -func AuthHookHandler(r *ghttp.Request) { - // 如果权限校验失败,调用 r.ExitAll() 退出执行流程 } func main() { - s := g.Server() - s.Group("/schedule").Bind([]ghttp.GroupItem{ - {"ALL", "*", AuthHookHandler, ghttp.HOOK_BEFORE_SERVE}, - {"POST", "/schedule", new(Schedule)}, - {"POST", "/task", new(Task)}, - }) - s.SetPort(8199) - s.Run() + glog.Line().Println("123") } diff --git a/geg/util/gutil/stack.go b/geg/util/gutil/stack.go new file mode 100644 index 000000000..d790d8d07 --- /dev/null +++ b/geg/util/gutil/stack.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/gogf/gf/g/util/gutil" +) + +func Test(s *interface{}) { + //debug.PrintStack() + gutil.PrintStack() +} + +func main() { + Test(nil) +} diff --git a/version.go b/version.go index 4c74aa0db..6afe722ac 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.7.3" +const VERSION = "v1.8.0" const AUTHORS = "john"