From d5f7ca634d2e316fa3d9f523447f04d4818d47e5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 29 Jun 2019 10:45:50 +0800 Subject: [PATCH 01/27] add package internal/debug; add gutil.Stack/PrintStack; rename Backtrace to Stack for glog --- g/database/gdb/gdb_config.go | 15 +-- g/database/gdb/gdb_func.go | 9 +- g/internal/debug/stack.go | 50 ++++++++++ g/net/ghttp/ghttp_server_log.go | 5 +- g/os/glog/glog.go | 25 ++--- g/os/glog/glog_api.go | 16 +-- g/os/glog/glog_chaining.go | 10 +- g/os/glog/glog_logger.go | 98 +++++++------------ g/os/glog/glog_logger_api.go | 16 +-- g/os/glog/glog_logger_chaining.go | 17 ++-- g/test/gtest/gtest.go | 19 ++-- g/util/gutil/gutil.go | 19 +--- g/util/gutil/gutil_debug.go | 36 +++++++ ...t.go => gutil_z_comparator_z_unit_test.go} | 0 g/util/gutil/gutil_z_unit_test.go | 4 +- geg/os/glog/glog_backtrace.go | 15 --- geg/os/glog/glog_stack.go | 16 +++ geg/other/test.go | 19 +--- geg/util/gutil/stack.go | 14 +++ 19 files changed, 229 insertions(+), 174 deletions(-) create mode 100644 g/internal/debug/stack.go create mode 100644 g/util/gutil/gutil_debug.go rename g/util/gutil/{gutil_comparator_z_unit_test.go => gutil_z_comparator_z_unit_test.go} (100%) delete mode 100644 geg/os/glog/glog_backtrace.go create mode 100644 geg/os/glog/glog_stack.go create mode 100644 geg/util/gutil/stack.go diff --git a/g/database/gdb/gdb_config.go b/g/database/gdb/gdb_config.go index 1ecfe42fb..4ff704812 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,7 +31,7 @@ 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 Charset string // (可选,默认为 utf8)编码,默认为 utf8 Priority int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 @@ -136,24 +137,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) } diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index a225719d5..23ed73964 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -10,14 +10,15 @@ import ( "bytes" "errors" "fmt" + "reflect" + "strings" + "time" + "github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/text/gregex" "github.com/gogf/gf/g/text/gstr" "github.com/gogf/gf/g/util/gconv" - "reflect" - "strings" - "time" ) // Type assert api for String(). @@ -173,7 +174,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/internal/debug/stack.go b/g/internal/debug/stack.go new file mode 100644 index 000000000..2bb9600c5 --- /dev/null +++ b/g/internal/debug/stack.go @@ -0,0 +1,50 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package debug contains facilities for programs to debug themselves while +// they are running. +package debug + +import ( + "bytes" + "fmt" + "runtime" + "strconv" +) + +// PrintStack prints to standard error the stack trace returned by runtime.Stack. +func PrintStack(skip ...int) { + fmt.Print(string(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) []byte { + buffer := make([]byte, 512) + number := 0 + if len(skip) > 0 { + number = skip[0] + } + for { + n := runtime.Stack(buffer, false) + if n < len(buffer) { + lines := bytes.Split(buffer[:n], []byte{'\n'}) + index := 1 + stacks := bytes.NewBuffer(nil) + for i, line := range lines { + if i < 5+number*2 || len(line) == 0 { + continue + } + if i%2 != 0 { + stacks.WriteString(strconv.Itoa(index) + ".\t") + index++ + } + stacks.Write(line) + stacks.WriteByte('\n') + } + return stacks.Bytes() + } + buffer = make([]byte, 2*len(buffer)) + } +} 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/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..788451af7 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -10,17 +10,19 @@ import ( "bytes" "errors" "fmt" + "io" + "os" + "runtime" + "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 +33,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). } @@ -72,7 +74,7 @@ func New() *Logger { file: gDEFAULT_FILE_FORMAT, flags: F_TIME_STD, level: LEVEL_ALL, - btStatus: 1, + stStatus: 1, headerPrint: true, stdoutPrint: true, } @@ -126,18 +128,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. @@ -305,16 +307,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, ln+"Stack:"+ln+s) } } // In matter of sequence, do not use stderr here, but use the same stdout. @@ -326,52 +328,24 @@ 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:" + ln + 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 + return string(debug.Stack(number)) } // getLongFile returns the absolute file path along with its line number of the caller. @@ -380,7 +354,7 @@ func (l *Logger) getLongFile() string { // 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) { + if !gregex.IsMatchString("/os/glog/glog.+$", file) { from = i break } @@ -388,7 +362,7 @@ func (l *Logger) getLongFile() string { } // Find the true caller file path using custom skip. goRoot := runtime.GOROOT() - for i := from + l.btSkip; i < 1000; i++ { + for i := from + l.stSkip; 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) 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/test/gtest/gtest.go b/g/test/gtest/gtest.go index 6fdeb4c11..d2c6eb40e 100644 --- a/g/test/gtest/gtest.go +++ b/g/test/gtest/gtest.go @@ -9,13 +9,14 @@ package gtest import ( "fmt" - "github.com/gogf/gf/g/util/gconv" "os" "reflect" "regexp" "runtime" "strings" "testing" + + "github.com/gogf/gf/g/util/gconv" ) // Case creates an unit test case. @@ -24,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() } }() @@ -255,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) } @@ -297,14 +298,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. @@ -335,13 +336,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..97cf4c864 --- /dev/null +++ b/g/util/gutil/gutil_debug.go @@ -0,0 +1,36 @@ +// 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 is alias for PrintStack. +// Deprecated. +func PrintStack() { + PrintStack() +} + +// 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) []byte { + 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/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_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 1b134b6c5..602c93c2a 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,24 +1,13 @@ package main import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/util/gutil" ) -type Order struct{} - -func (order *Order) Get(r *ghttp.Request) { - r.Response.Write("GET") +func Test(s interface{}) { + gutil.PrintStack() } func main() { - s := g.Server() - s.BindHookHandlerByMap("/api.v1/*any", map[string]ghttp.HandlerFunc{ - "BeforeServe": func(r *ghttp.Request) { - r.Response.CORSDefault() - }, - }) - s.BindObjectRest("/api.v1/{.struct}", new(Order)) - s.SetPort(8199) - s.Run() + Test(nil) } 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) +} From 58a405bfa8f8c8b3cd16068139987a436a562840 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 29 Jun 2019 18:17:33 +0800 Subject: [PATCH 02/27] add package gerror; improve internal/debug,glog package --- .gitignore | 1 - g/database/gdb/gdb.go | 9 ++- g/errors/gerror/gerror.go | 135 +++++++++++++++++++++++++++++++++ g/errors/gerror/gerror_test.go | 39 ++++++++++ g/internal/debug/stack.go | 117 ++++++++++++++++++++++------ g/os/glog/glog_logger.go | 33 +------- g/os/gtime/gtime_time.go | 10 +++ g/util/gutil/gutil_debug.go | 8 +- geg/os/glog/glog_error.go | 11 +-- geg/os/glog/glog_line2.go | 2 +- geg/other/test.go | 60 ++++++++++++++- geg/other/test2.go | 34 +-------- go.mod | 2 + go.sum | 2 + 14 files changed, 356 insertions(+), 107 deletions(-) create mode 100644 g/errors/gerror/gerror.go create mode 100644 g/errors/gerror/gerror_test.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 8470fac8f..8ea5a3d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,4 @@ bin/ cbuild **/.DS_Store .vscode/ -go.sum diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index d4c14ce8c..4ea743d86 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -14,13 +14,14 @@ import ( "database/sql" "errors" "fmt" + "time" + "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gring" "github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gvar" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/util/grand" - "time" ) // 数据库操作接口 @@ -86,9 +87,9 @@ 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 diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go new file mode 100644 index 000000000..048d1e2fa --- /dev/null +++ b/g/errors/gerror/gerror.go @@ -0,0 +1,135 @@ +// 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 ( + "bytes" + "fmt" + "runtime" + "strings" + + "github.com/gogf/gf/g/util/gconv" + "github.com/pkg/errors" +) + +type stacker interface { + StackTrace() errors.StackTrace +} + +type causer interface { + Cause() error +} + +const ( + gFILTER_KEY = "/g/errors/gerror/gerror.go" +) + +var ( + // goRootForFilter is used for stack filtering purpose. + goRootForFilter = runtime.GOROOT() +) + +func init() { + if goRootForFilter != "" { + goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) + } +} + +// 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 errors.New(text) +} + +// Wrap wraps error with text. +func Wrap(err error, text string) error { + if err == nil { + return nil + } + return errors.Wrap(err, text) +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + return errors.Wrapf(err, format, args...) +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + return errors.Cause(err) +} + +// Stack returns the stack callers as string. +func Stack(err error) string { + if err == nil { + return "" + } + if _, ok := err.(causer); !ok { + return "" + } + index := 1 + buffer := bytes.NewBuffer(nil) + for err != nil { + cause, ok := err.(causer) + if !ok { + if err, ok := err.(stacker); ok { + buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + index++ + formatSubStack(err, buffer) + } + break + } + if err, ok := err.(stacker); ok { + buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + index++ + formatSubStack(err, buffer) + } + err = cause.Cause() + } + return buffer.String() +} + +// formatSubStack formats the stack for error. +func formatSubStack(err stacker, buffer *bytes.Buffer) { + index := 1 + for _, f := range err.StackTrace() { + if fn := runtime.FuncForPC(uintptr(f) - 1); fn != nil { + file, line := fn.FileLine(uintptr(f) - 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_test.go b/g/errors/gerror/gerror_test.go new file mode 100644 index 000000000..737744d58 --- /dev/null +++ b/g/errors/gerror/gerror_test.go @@ -0,0 +1,39 @@ +// 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 ( + "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/internal/debug/stack.go b/g/internal/debug/stack.go index 2bb9600c5..77567c82b 100644 --- a/g/internal/debug/stack.go +++ b/g/internal/debug/stack.go @@ -1,6 +1,8 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// 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. @@ -10,41 +12,106 @@ import ( "bytes" "fmt" "runtime" - "strconv" + "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(string(Stack(skip...))) + 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) []byte { - buffer := make([]byte, 512) +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] } - for { - n := runtime.Stack(buffer, false) - if n < len(buffer) { - lines := bytes.Split(buffer[:n], []byte{'\n'}) - index := 1 - stacks := bytes.NewBuffer(nil) - for i, line := range lines { - if i < 5+number*2 || len(line) == 0 { - continue - } - if i%2 != 0 { - stacks.WriteString(strconv.Itoa(index) + ".\t") - index++ - } - stacks.Write(line) - stacks.WriteByte('\n') + 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 } - return stacks.Bytes() + 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 } - buffer = make([]byte, 2*len(buffer)) } + 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/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 788451af7..84cca92a4 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -44,6 +44,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 ( @@ -256,10 +257,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) @@ -345,31 +346,5 @@ func (l *Logger) GetStack(skip ...int) string { if len(skip) > 0 { number = skip[0] + 1 } - return string(debug.Stack(number)) -} - -// 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("/os/glog/glog.+$", file) { - from = i - break - } - } - } - // Find the true caller file path using custom skip. - goRoot := runtime.GOROOT() - for i := from + l.stSkip; 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/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/util/gutil/gutil_debug.go b/g/util/gutil/gutil_debug.go index 97cf4c864..a9ba7c9f5 100644 --- a/g/util/gutil/gutil_debug.go +++ b/g/util/gutil/gutil_debug.go @@ -10,12 +10,6 @@ import ( "github.com/gogf/gf/g/internal/debug" ) -// PrintStack is alias for PrintStack. -// Deprecated. -func PrintStack() { - PrintStack() -} - // PrintStack prints to standard error the stack trace returned by runtime.Stack. func PrintStack(skip ...int) { number := 1 @@ -27,7 +21,7 @@ func PrintStack(skip ...int) { // 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) []byte { +func Stack(skip ...int) string { number := 1 if len(skip) > 0 { number = skip[0] + 1 diff --git a/geg/os/glog/glog_error.go b/geg/os/glog/glog_error.go index 18738726c..b4eb1cfb5 100644 --- a/geg/os/glog/glog_error.go +++ b/geg/os/glog/glog_error.go @@ -1,11 +1,12 @@ package main -import ( - "github.com/gogf/gf/g/os/glog" -) +import "github.com/gogf/gf/g/os/glog" -func main() { - //glog.SetPath("/tmp/") +func Test() { glog.Error("This is error!") glog.Errorf("This is error, %d!", 2) } + +func main() { + Test() +} 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/other/test.go b/geg/other/test.go index 602c93c2a..535dbae55 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,13 +1,65 @@ package main import ( - "github.com/gogf/gf/g/util/gutil" + "fmt" + + "github.com/pkg/errors" + + "github.com/gogf/gf/g/errors/gerror" ) -func Test(s interface{}) { - gutil.PrintStack() +func Test1() error { + return gerror.New("test") +} + +func Test2() error { + return gerror.Wrap(Test1(), "error test1") +} + +type stackTracer interface { + StackTrace() errors.StackTrace } func main() { - Test(nil) + err := Test2() + + //fmt.Printf("%+v", err) + //fmt.Println("==============") + fmt.Println(gerror.Stack(err)) + return + + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + fmt.Println("ERROR:", err) + if err, ok := err.(stackTracer); ok { + for _, f := range err.StackTrace() { + fmt.Printf("%+s:%d\n", f, f) + } + } + break + } + fmt.Println("ERROR:", err) + if err, ok := err.(stackTracer); ok { + for _, f := range err.StackTrace() { + fmt.Printf("%+s:%d\n", f, f) + } + } + fmt.Println() + err = cause.Cause() + } + + //err, ok := Test2().(stackTracer) + //if !ok { + // panic("oops, err does not implement stackTracer") + //} + // + //st := err.StackTrace() + //fmt.Printf("%+v\n", st) + //fmt.Println() + //fmt.Printf("%+v\n", Test2()) } 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/go.mod b/go.mod index ef37cb8d6..a283f6e2b 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/gogf/gf + +require github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f29ab350a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 009ce9063e487b4c4b45e7eb67ae83b2ab169d82 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 29 Jun 2019 23:35:32 +0800 Subject: [PATCH 03/27] improve gerror/glog --- g/crypto/gmd5/gmd5.go | 4 +-- g/crypto/gsha1/gsha1.go | 4 +-- g/errors/gerror/gerror.go | 37 +++++++++++++++++++++--- g/errors/gerror/gerror_test.go | 12 ++++---- g/internal/errors/errors.go | 48 -------------------------------- g/internal/errors/errors_test.go | 39 -------------------------- g/net/gtcp/gtcp_conn.go | 6 ++-- g/net/gtcp/gtcp_conn_pkg.go | 6 ++-- g/net/gtcp/gtcp_pool.go | 7 ++--- g/net/gtcp/gtcp_pool_pkg.go | 6 ++-- g/net/gudp/gudp_conn.go | 6 ++-- g/os/glog/glog_logger.go | 39 +++++++++++++------------- geg/errors/gerror/gerror1.go | 31 +++++++++++++++++++++ geg/errors/gerror/gerror2.go | 22 +++++++++++++++ 14 files changed, 130 insertions(+), 137 deletions(-) delete mode 100644 g/internal/errors/errors.go delete mode 100644 g/internal/errors/errors_test.go create mode 100644 geg/errors/gerror/gerror1.go create mode 100644 geg/errors/gerror/gerror2.go 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/errors/gerror/gerror.go b/g/errors/gerror/gerror.go index 048d1e2fa..00978b2d0 100644 --- a/g/errors/gerror/gerror.go +++ b/g/errors/gerror/gerror.go @@ -10,6 +10,7 @@ package gerror import ( "bytes" "fmt" + "io" "runtime" "strings" @@ -17,14 +18,21 @@ import ( "github.com/pkg/errors" ) +// stacker is an interface for errors.StackTrace. type stacker interface { StackTrace() errors.StackTrace } +// stacker is an interface for errors.Cause. type causer interface { Cause() error } +// stackError is custom error for additional features. +type stackError struct { + error +} + const ( gFILTER_KEY = "/g/errors/gerror/gerror.go" ) @@ -53,7 +61,7 @@ func NewText(text string) error { if text == "" { return nil } - return errors.New(text) + return &stackError{errors.New(text)} } // Wrap wraps error with text. @@ -61,14 +69,14 @@ func Wrap(err error, text string) error { if err == nil { return nil } - return errors.Wrap(err, text) + return &stackError{errors.Wrap(err, text)} } // Wrapf returns an error annotating err with a stack trace // at the point Wrapf is called, and the format specifier. // If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { - return errors.Wrapf(err, format, args...) + return &stackError{errors.Wrapf(err, format, args...)} } // Cause returns the underlying cause of the error, if possible. @@ -83,10 +91,31 @@ func Wrapf(err error, format string, args ...interface{}) error { // be returned. If the error is nil, nil will be returned without further // investigation. func Cause(err error) error { - return errors.Cause(err) + return &stackError{errors.Cause(err)} +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %v, %s : Print the error string; +// %+v, %+s : Print the error stack list; +func (err *stackError) Format(s fmt.State, verb rune) { + switch verb { + case 's', 'v': + switch { + case s.Flag('+'): + io.WriteString(s, Stack(err.error)) + default: + io.WriteString(s, err.Error()) + } + } +} + +func (err *stackError) Cause() error { + return err.error } // Stack returns the stack callers as string. +// It returns am empty string id the does not support stacks. func Stack(err error) string { if err == nil { return "" diff --git a/g/errors/gerror/gerror_test.go b/g/errors/gerror/gerror_test.go index 737744d58..71aaa7d6c 100644 --- a/g/errors/gerror/gerror_test.go +++ b/g/errors/gerror/gerror_test.go @@ -9,7 +9,7 @@ package gerror_test import ( "testing" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" "github.com/gogf/gf/g/test/gtest" ) @@ -23,16 +23,16 @@ func nilError() error { func Test_Nil(t *testing.T) { gtest.Case(t, func() { - gtest.Assert(errors.New(interfaceNil()), nil) - gtest.Assert(errors.Wrap(nilError(), "test"), nil) + 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 = errors.Wrap(err, "func2 error") - err = errors.Wrap(err, "func3 error") + err := gerror.New("1") + err = gerror.Wrap(err, "func2 error") + err = gerror.Wrap(err, "func3 error") gtest.AssertNE(err, nil) gtest.Assert(err.Error(), "func3 error: func2 error: 1") }) 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/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..f02a9affd 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -11,7 +11,7 @@ import ( "fmt" "time" - "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/errors/gerror" ) const ( @@ -74,7 +74,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 @@ -147,7 +147,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/glog/glog_logger.go b/g/os/glog/glog_logger.go index 84cca92a4..be9a6ca1f 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "os" - "runtime" "strings" "time" @@ -57,18 +56,6 @@ 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{ @@ -270,13 +257,25 @@ 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(' ') + tempStr := "" + valueStr := "" + for _, v := range value { + 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 += " " + } + } 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) @@ -317,7 +316,7 @@ func (l *Logger) printStd(lead string, value ...interface{}) { func (l *Logger) printErr(lead string, value ...interface{}) { if l.stStatus == 1 { if s := l.GetStack(); s != "" { - value = append(value, ln+"Stack:"+ln+s) + value = append(value, "\nStack:\n"+s) } } // In matter of sequence, do not use stderr here, but use the same stdout. @@ -333,7 +332,7 @@ func (l *Logger) format(format string, value ...interface{}) string { // 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:" + ln + s) + l.Println("Stack:\n" + s) } else { l.Println() } diff --git a/geg/errors/gerror/gerror1.go b/geg/errors/gerror/gerror1.go new file mode 100644 index 000000000..4c12dc29e --- /dev/null +++ b/geg/errors/gerror/gerror1.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/gogf/gf/g/errors/gerror" +) + +func Test1() error { + return gerror.New("test") +} + +func Test2() error { + return gerror.Wrap(Test1(), "error test1") +} + +type stackTracer interface { + StackTrace() errors.StackTrace +} + +func main() { + err := Test2() + fmt.Printf("%s\n", err) + fmt.Printf("%v\n", err) + fmt.Printf("%+v\n", err) + fmt.Println(gerror.Stack(err)) + return + +} diff --git a/geg/errors/gerror/gerror2.go b/geg/errors/gerror/gerror2.go new file mode 100644 index 000000000..0147ee368 --- /dev/null +++ b/geg/errors/gerror/gerror2.go @@ -0,0 +1,22 @@ +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 main() { + glog.Println(OpenConfig()) + glog.Printf("unexpected error:\n%+s", OpenConfig()) + glog.Errorf("unexpected error:\n%+s", OpenConfig()) + +} From 10102f2db97c2c6f34fcc1da1e33e53deabb9201 Mon Sep 17 00:00:00 2001 From: john Date: Sun, 30 Jun 2019 12:54:06 +0800 Subject: [PATCH 04/27] improving gerror --- g/errors/gerror/gerror.go | 10 +++++++--- g/errors/gerror/gerror_stack.go | 14 ++++++++++++++ g/os/glog/glog_logger.go | 2 +- geg/errors/gerror/gerror2.go | 10 +++++++--- geg/os/glog/glog_gerror.go | 26 ++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 g/errors/gerror/gerror_stack.go create mode 100644 geg/os/glog/glog_gerror.go diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go index 00978b2d0..15db337bb 100644 --- a/g/errors/gerror/gerror.go +++ b/g/errors/gerror/gerror.go @@ -31,6 +31,7 @@ type causer interface { // stackError is custom error for additional features. type stackError struct { error + *stack } const ( @@ -61,7 +62,10 @@ func NewText(text string) error { if text == "" { return nil } - return &stackError{errors.New(text)} + return &stackError{ + err, + callers(), + } } // Wrap wraps error with text. @@ -129,14 +133,14 @@ func Stack(err error) string { cause, ok := err.(causer) if !ok { if err, ok := err.(stacker); ok { - buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err)) index++ formatSubStack(err, buffer) } break } if err, ok := err.(stacker); ok { - buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err)) index++ formatSubStack(err, buffer) } diff --git a/g/errors/gerror/gerror_stack.go b/g/errors/gerror/gerror_stack.go new file mode 100644 index 000000000..53a7b22cc --- /dev/null +++ b/g/errors/gerror/gerror_stack.go @@ -0,0 +1,14 @@ +package gerror + +import "runtime" + +// stack represents a stack of program counters. +type stack []uintptr + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index be9a6ca1f..a9108120f 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -269,7 +269,7 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { valueStr += tempStr } } else { - valueStr += " " + valueStr += " " + tempStr } } else { valueStr = tempStr diff --git a/geg/errors/gerror/gerror2.go b/geg/errors/gerror/gerror2.go index 0147ee368..8d0d44f8a 100644 --- a/geg/errors/gerror/gerror2.go +++ b/geg/errors/gerror/gerror2.go @@ -14,9 +14,13 @@ func OpenConfig() error { return gerror.Wrap(OpenFile(), "configuration file opening failed") } +func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") +} + func main() { - glog.Println(OpenConfig()) - glog.Printf("unexpected error:\n%+s", OpenConfig()) - glog.Errorf("unexpected error:\n%+s", OpenConfig()) + //glog.Println(OpenConfig()) + glog.Printf("unexpected error: %+s", ReadConfig()) + //glog.Errorf("unexpected error: %+s", OpenConfig()) } diff --git a/geg/os/glog/glog_gerror.go b/geg/os/glog/glog_gerror.go new file mode 100644 index 000000000..3d24f5d16 --- /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.Errorf("connection error: %+v", err1) + glog.Errorf("connection error: %+v", err2) +} + +func main() { + TestGError() +} From 6302789c41e02a234f9e5a85254a9ceaeb4337af Mon Sep 17 00:00:00 2001 From: john Date: Sun, 30 Jun 2019 22:21:08 +0800 Subject: [PATCH 05/27] improve package gerror; add support for gerror formating output in glog --- g/errors/gerror/gerror.go | 139 +++++--------------------------- g/errors/gerror/gerror_error.go | 122 ++++++++++++++++++++++++++++ g/errors/gerror/gerror_stack.go | 18 +++-- g/errors/gerror/gerror_test.go | 76 ++++++++++++++++- g/os/glog/glog_logger.go | 7 +- geg/errors/gerror/gerror1.go | 30 +++---- geg/errors/gerror/gerror2.go | 8 +- go.mod | 4 +- go.sum | 2 - 9 files changed, 251 insertions(+), 155 deletions(-) create mode 100644 g/errors/gerror/gerror_error.go delete mode 100644 go.sum diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go index 15db337bb..29605bb1e 100644 --- a/g/errors/gerror/gerror.go +++ b/g/errors/gerror/gerror.go @@ -8,47 +8,10 @@ package gerror import ( - "bytes" "fmt" - "io" - "runtime" - "strings" - "github.com/gogf/gf/g/util/gconv" - "github.com/pkg/errors" ) -// stacker is an interface for errors.StackTrace. -type stacker interface { - StackTrace() errors.StackTrace -} - -// stacker is an interface for errors.Cause. -type causer interface { - Cause() error -} - -// stackError is custom error for additional features. -type stackError struct { - error - *stack -} - -const ( - gFILTER_KEY = "/g/errors/gerror/gerror.go" -) - -var ( - // goRootForFilter is used for stack filtering purpose. - goRootForFilter = runtime.GOROOT() -) - -func init() { - if goRootForFilter != "" { - goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) - } -} - // New returns an error that formats as the given value. func New(value interface{}) error { if value == nil { @@ -62,107 +25,47 @@ func NewText(text string) error { if text == "" { return nil } - return &stackError{ - err, - callers(), + 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 &stackError{errors.Wrap(err, text)} + 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. -// If err is nil, Wrapf returns nil. +// It returns nil if given err is nil. func Wrapf(err error, format string, args ...interface{}) error { - return &stackError{errors.Wrapf(err, format, args...)} -} - -// Cause returns the underlying cause of the error, if possible. -// An error value has a cause if it implements the following -// interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - return &stackError{errors.Cause(err)} -} - -// Format formats the frame according to the fmt.Formatter interface. -// -// %v, %s : Print the error string; -// %+v, %+s : Print the error stack list; -func (err *stackError) Format(s fmt.State, verb rune) { - switch verb { - case 's', 'v': - switch { - case s.Flag('+'): - io.WriteString(s, Stack(err.error)) - default: - io.WriteString(s, err.Error()) - } + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(), + text: fmt.Sprintf(format, args...), } } -func (err *stackError) Cause() error { - return err.error -} - // Stack returns the stack callers as string. -// It returns am empty string id the does not support stacks. +// It returns an empty string id the does not support stacks. func Stack(err error) string { if err == nil { return "" } - if _, ok := err.(causer); !ok { - return "" - } - index := 1 - buffer := bytes.NewBuffer(nil) - for err != nil { - cause, ok := err.(causer) - if !ok { - if err, ok := err.(stacker); ok { - buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err)) - index++ - formatSubStack(err, buffer) - } - break - } - if err, ok := err.(stacker); ok { - buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err)) - index++ - formatSubStack(err, buffer) - } - err = cause.Cause() - } - return buffer.String() -} - -// formatSubStack formats the stack for error. -func formatSubStack(err stacker, buffer *bytes.Buffer) { - index := 1 - for _, f := range err.StackTrace() { - if fn := runtime.FuncForPC(uintptr(f) - 1); fn != nil { - file, line := fn.FileLine(uintptr(f) - 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++ - } + 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..c92158159 --- /dev/null +++ b/g/errors/gerror/gerror_error.go @@ -0,0 +1,122 @@ +// 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() +} + +// 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 index 53a7b22cc..5a0d5239b 100644 --- a/g/errors/gerror/gerror_stack.go +++ b/g/errors/gerror/gerror_stack.go @@ -1,3 +1,9 @@ +// 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" @@ -5,10 +11,12 @@ import "runtime" // stack represents a stack of program counters. type stack []uintptr -func callers() *stack { - const depth = 32 - var pcs [depth]uintptr +const ( + gMAX_STACK_DEPTH = 32 +) + +func callers() stack { + var pcs [gMAX_STACK_DEPTH]uintptr n := runtime.Callers(3, pcs[:]) - var st stack = pcs[0:n] - return &st + return pcs[0:n] } diff --git a/g/errors/gerror/gerror_test.go b/g/errors/gerror/gerror_test.go index 71aaa7d6c..b14053ea9 100644 --- a/g/errors/gerror/gerror_test.go +++ b/g/errors/gerror/gerror_test.go @@ -7,6 +7,8 @@ package gerror_test import ( + "errors" + "fmt" "testing" "github.com/gogf/gf/g/errors/gerror" @@ -30,10 +32,76 @@ func Test_Nil(t *testing.T) { func Test_Wrap(t *testing.T) { gtest.Case(t, func() { - err := gerror.New("1") - err = gerror.Wrap(err, "func2 error") - err = gerror.Wrap(err, "func3 error") + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.Wrap(err, "3") gtest.AssertNE(err, nil) - gtest.Assert(err.Error(), "func3 error: func2 error: 1") + 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_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/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index a9108120f..a8ea66ee4 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -257,10 +257,15 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { buffer.WriteString(l.prefix + " ") } } + // Convert value to string. tempStr := "" valueStr := "" for _, v := range value { - tempStr = gconv.String(v) + 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' { diff --git a/geg/errors/gerror/gerror1.go b/geg/errors/gerror/gerror1.go index 4c12dc29e..809fc81f6 100644 --- a/geg/errors/gerror/gerror1.go +++ b/geg/errors/gerror/gerror1.go @@ -1,31 +1,25 @@ package main import ( + "errors" "fmt" - "github.com/pkg/errors" - "github.com/gogf/gf/g/errors/gerror" ) -func Test1() error { +func Error1() error { + return errors.New("test") +} + +func Error2() error { return gerror.New("test") } -func Test2() error { - return gerror.Wrap(Test1(), "error test1") -} - -type stackTracer interface { - StackTrace() errors.StackTrace -} - func main() { - err := Test2() - fmt.Printf("%s\n", err) - fmt.Printf("%v\n", err) - fmt.Printf("%+v\n", err) - fmt.Println(gerror.Stack(err)) - return - + 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 index 8d0d44f8a..a210d5fef 100644 --- a/geg/errors/gerror/gerror2.go +++ b/geg/errors/gerror/gerror2.go @@ -19,8 +19,8 @@ func ReadConfig() error { } func main() { - //glog.Println(OpenConfig()) - glog.Printf("unexpected error: %+s", ReadConfig()) - //glog.Errorf("unexpected error: %+s", OpenConfig()) - + err := ReadConfig() + glog.Printf("%s\n%+s", err, err) + glog.Printf("%+v", err) + glog.Error(err) } diff --git a/go.mod b/go.mod index a283f6e2b..fd3232e72 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1 @@ -module github.com/gogf/gf - -require github.com/pkg/errors v0.8.1 +module github.com/gogf/gf \ No newline at end of file diff --git a/go.sum b/go.sum deleted file mode 100644 index f29ab350a..000000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 39bd2616c43961a963e3659ab69f725079517bca Mon Sep 17 00:00:00 2001 From: john Date: Sun, 30 Jun 2019 22:22:24 +0800 Subject: [PATCH 06/27] add go.sum to gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ea5a3d4c..c3165c5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ bin/ cbuild **/.DS_Store .vscode/ - +go.sum From 9cf4ea5c910aea0630b9d6180598a470f79607bc Mon Sep 17 00:00:00 2001 From: John Date: Sat, 6 Jul 2019 16:14:45 +0800 Subject: [PATCH 07/27] rename Priority to Weight for gdb --- g/database/gdb/gdb.go | 14 +++++------ g/database/gdb/gdb_config.go | 33 +------------------------ g/database/gdb/gdb_unit_init_test.go | 18 +++++++------- g/frame/gins/gins.go | 7 +++--- g/frame/gins/gins_database_test.go | 9 ++++--- geg/database/gdb/mssql/gdb_sqlserver.go | 9 ++++--- geg/database/gdb/mysql/config.toml | 2 +- geg/database/gdb/mysql/gdb.go | 11 +++++---- geg/database/gdb/oracle/gdb.go | 9 ++++--- 9 files changed, 43 insertions(+), 69 deletions(-) diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 2a37eba86..001564faf 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -248,9 +248,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 +263,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 +286,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] diff --git a/g/database/gdb/gdb_config.go b/g/database/gdb/gdb_config.go index 4ff704812..df9a02c89 100644 --- a/g/database/gdb/gdb_config.go +++ b/g/database/gdb/gdb_config.go @@ -33,8 +33,8 @@ type ConfigNode struct { Name string // 数据库名称 Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave + Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 Charset string // (可选,默认为 utf8)编码,默认为 utf8 - Priority int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) MaxIdleConnCount int // (可选)连接池最大限制的连接数 MaxOpenConnCount int // (可选)连接池最大打开的连接数 @@ -48,37 +48,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) 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/frame/gins/gins.go b/g/frame/gins/gins.go index f2030224d..aa7380001 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -9,6 +9,8 @@ package gins import ( "fmt" + "time" + "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/database/gdb" "github.com/gogf/gf/g/database/gredis" @@ -19,7 +21,6 @@ import ( "github.com/gogf/gf/g/text/gregex" "github.com/gogf/gf/g/text/gstr" "github.com/gogf/gf/g/util/gconv" - "time" ) const ( @@ -117,8 +118,8 @@ func Database(name ...string) gdb.DB { 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) } // Deprecated if value, ok := nodeMap["linkinfo"]; ok { 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/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..300ff3557 100644 --- a/geg/database/gdb/mysql/config.toml +++ b/geg/database/gdb/mysql/config.toml @@ -9,5 +9,5 @@ name = "test" type = "mysql" role = "master" + 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/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, // }, // }, //}) From 6426409bf9357360f596a790e1fe32126a24cad8 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 6 Jul 2019 16:51:50 +0800 Subject: [PATCH 08/27] add debug configuration support for gdb --- g/database/gdb/gdb.go | 6 +++++- g/database/gdb/gdb_config.go | 5 +++-- g/frame/gins/gins.go | 7 +++++-- geg/database/gdb/mysql/config.toml | 1 + .../gdb/mysql/{gdb_debug.go => gdb_debug1.go} | 3 +-- geg/database/gdb/mysql/gdb_debug2.go | 17 +++++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) rename geg/database/gdb/mysql/{gdb_debug.go => gdb_debug1.go} (94%) create mode 100644 geg/database/gdb/mysql/gdb_debug2.go diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 001564faf..adb1a6735 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -308,12 +308,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,6 +338,8 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { if v != nil && sqlDb == nil { sqlDb = v.(*sql.DB) } + // 是否开启调试模式 + bs.SetDebug(node.Debug) // 是否手动选择数据库 if v := bs.schema.Val(); v != "" { sqlDb.Exec("USE " + v) diff --git a/g/database/gdb/gdb_config.go b/g/database/gdb/gdb_config.go index df9a02c89..c18992b94 100644 --- a/g/database/gdb/gdb_config.go +++ b/g/database/gdb/gdb_config.go @@ -33,6 +33,7 @@ type ConfigNode struct { Name string // 数据库名称 Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave + Debug bool // (可选)开启调试模式 Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 Charset string // (可选,默认为 utf8)编码,默认为 utf8 LinkInfo string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) @@ -132,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/frame/gins/gins.go b/g/frame/gins/gins.go index aa7380001..983b13c82 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -115,12 +115,15 @@ func Database(name ...string) gdb.DB { if value, ok := nodeMap["role"]; ok { node.Role = gconv.String(value) } - if value, ok := nodeMap["charset"]; ok { - node.Charset = gconv.String(value) + if value, ok := nodeMap["debug"]; ok { + node.Debug = gconv.Bool(value) } if value, ok := nodeMap["weight"]; ok { node.Weight = gconv.Int(value) } + if value, ok := nodeMap["charset"]; ok { + node.Charset = gconv.String(value) + } // Deprecated if value, ok := nodeMap["linkinfo"]; ok { node.LinkInfo = gconv.String(value) diff --git a/geg/database/gdb/mysql/config.toml b/geg/database/gdb/mysql/config.toml index 300ff3557..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" 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() +} From 2504e405a78a7168d02a93fc3e38aa44adef007a Mon Sep 17 00:00:00 2001 From: John Date: Sat, 6 Jul 2019 17:13:17 +0800 Subject: [PATCH 09/27] add sql.ErrNoRows feature for querying of gdb --- g/database/gdb/gdb_base.go | 8 +++- g/database/gdb/gdb_unit_model_test.go | 54 ++++++++++++++++----------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index 00ab5afb6..311f826ff 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -567,6 +567,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 +588,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 +606,9 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { } } records = append(records, row) + if !rows.Next() { + break + } } return records, nil } diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index 1daf31db4..9f97fe586 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) { From b439aedce552474c70d264fa78ed550cce070513 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 6 Jul 2019 17:35:03 +0800 Subject: [PATCH 10/27] RELEASE updates --- RELEASE.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.MD b/RELEASE.MD index 31a031b1a..806326ef6 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,4 +1,4 @@ -# `v1.7.0` +# `v1.7.0` (2019-06-10) ## 新功能/改进 1. 重构改进`glog`模块: - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 From 691baf5c16d3809c0a68a647f8064efbde3b41c8 Mon Sep 17 00:00:00 2001 From: john Date: Sun, 7 Jul 2019 09:47:33 +0800 Subject: [PATCH 11/27] comment updates --- g/database/gdb/gdb_base.go | 3 +- g/internal/empty/empty.go | 1 - g/util/gvalid/gvalid_check_struct.go | 4 +- g/util/gvalid/gvalid_unit_checkstruct_test.go | 2 +- geg/other/test.go | 107 +++--------------- 5 files changed, 23 insertions(+), 94 deletions(-) diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index 311f826ff..dfc17d577 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 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/util/gvalid/gvalid_check_struct.go b/g/util/gvalid/gvalid_check_struct.go index fba014f95..82d90ba2d 100644 --- a/g/util/gvalid/gvalid_check_struct.go +++ b/g/util/gvalid/gvalid_check_struct.go @@ -15,8 +15,8 @@ import ( ) var ( - // 同时支持valid和gvalid标签,优先使用valid - structTagPriority = []string{"valid", "gvalid"} + // 同时支持gvalid和valid标签,优先使用gvalid + structTagPriority = []string{"gvalid", "valid"} ) // 校验struct对象属性,object参数也可以是一个指向对象的指针,返回值同CheckMap方法。 diff --git a/g/util/gvalid/gvalid_unit_checkstruct_test.go b/g/util/gvalid/gvalid_unit_checkstruct_test.go index d5461a62f..b13d508dc 100644 --- a/g/util/gvalid/gvalid_unit_checkstruct_test.go +++ b/g/util/gvalid/gvalid_unit_checkstruct_test.go @@ -105,7 +105,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/other/test.go b/geg/other/test.go index b3630b512..94f49578c 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,98 +1,27 @@ package main import ( -<<<<<<< HEAD -<<<<<<< HEAD - "fmt" - - "github.com/pkg/errors" - - "github.com/gogf/gf/g/errors/gerror" -) - -func Test1() error { - return gerror.New("test") -} - -func Test2() error { - return gerror.Wrap(Test1(), "error test1") -} - -type stackTracer interface { - StackTrace() errors.StackTrace -} - -func main() { - err := Test2() - - //fmt.Printf("%+v", err) - //fmt.Println("==============") - fmt.Println(gerror.Stack(err)) - return - - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - fmt.Println("ERROR:", err) - if err, ok := err.(stackTracer); ok { - for _, f := range err.StackTrace() { - fmt.Printf("%+s:%d\n", f, f) - } - } - break - } - fmt.Println("ERROR:", err) - if err, ok := err.(stackTracer); ok { - for _, f := range err.StackTrace() { - fmt.Printf("%+s:%d\n", f, f) - } - } - fmt.Println() - err = cause.Cause() - } - - //err, ok := Test2().(stackTracer) - //if !ok { - // panic("oops, err does not implement stackTracer") - //} - // - //st := err.StackTrace() - //fmt.Printf("%+v\n", st) - //fmt.Println() - //fmt.Printf("%+v\n", Test2()) -======= - "github.com/gogf/gf/g/os/glog" -======= - "fmt" ->>>>>>> d0fe2d2f75a91f44d59969bb25fda3729eabcdde - "github.com/gogf/gf/g" + "github.com/gogf/gf/g/util/gvalid" ) -type User struct { - Uid int - Name string -} - func main() { -<<<<<<< HEAD - TestCache() ->>>>>>> c90ed0d4242527435a3b4c9d7c27742d29c9aaa1 -======= - if r, err := g.DB().Table("user").Where("uid=?", 1).One(); r != nil { - u := new(User) - if err := r.ToStruct(u); err == nil { - fmt.Println(" uid:", u.Uid) - fmt.Println("name:", u.Name) - } else { - fmt.Println(err) - } - } else if err != nil { - fmt.Println(err) + type Pass struct { + Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"` + Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` } ->>>>>>> d0fe2d2f75a91f44d59969bb25fda3729eabcdde + type User struct { + Id int + Name string `valid:"name@required#请输入您的姓名"` + Pass Pass + } + user := &User{ + Name: "john", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + err := gvalid.CheckStruct(user, nil) + g.Dump(err.Maps()) } From 8ff21d6936c893c05e56168d4bf6295cc35c5d9b Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 08:40:28 +0800 Subject: [PATCH 12/27] adding support of slice parameters for gdb.Model.Where --- g/database/gdb/gdb_unit_model_test.go | 9 +++++++++ g/database/gredis/gredis_unit_test.go | 5 +++-- geg/database/gdb/mysql/gdb_args_slice.go | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 geg/database/gdb/mysql/gdb_args_slice.go diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index 9f97fe586..8b5431fd5 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -625,6 +625,15 @@ 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", 3).One() if err != nil { 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/geg/database/gdb/mysql/gdb_args_slice.go b/geg/database/gdb/mysql/gdb_args_slice.go new file mode 100644 index 000000000..28e3193c8 --- /dev/null +++ b/geg/database/gdb/mysql/gdb_args_slice.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/gogf/gf/g" +) + +func main() { + db := g.DB() + 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() +} From 9ca9d6c4bdf205445aca8625aebfd145902dd0a2 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 9 Jul 2019 10:40:26 +0800 Subject: [PATCH 13/27] add Cause function for gerror --- g/errors/gerror/gerror.go | 10 ++++++++++ g/errors/gerror/gerror_error.go | 17 +++++++++++++++++ g/errors/gerror/gerror_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go index 29605bb1e..99f52fad2 100644 --- a/g/errors/gerror/gerror.go +++ b/g/errors/gerror/gerror.go @@ -58,6 +58,16 @@ func Wrapf(err error, format string, args ...interface{}) error { } } +// 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 { diff --git a/g/errors/gerror/gerror_error.go b/g/errors/gerror/gerror_error.go index c92158159..95f6c1bb7 100644 --- a/g/errors/gerror/gerror_error.go +++ b/g/errors/gerror/gerror_error.go @@ -47,6 +47,23 @@ func (err *Error) Error() string { 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; diff --git a/g/errors/gerror/gerror_test.go b/g/errors/gerror/gerror_test.go index b14053ea9..e1a040f33 100644 --- a/g/errors/gerror/gerror_test.go +++ b/g/errors/gerror/gerror_test.go @@ -48,6 +48,32 @@ func Test_Wrap(t *testing.T) { }) } +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") From 0bba2092af17489f859d6b315bf6c9579ece4c68 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 11:34:45 +0800 Subject: [PATCH 14/27] add one slice passing all parameters support for gdb.Model.Where --- g/database/gdb/gdb_func.go | 14 ++++++++------ g/database/gdb/gdb_unit_model_test.go | 9 ++++++++- geg/database/gdb/mysql/gdb_args_slice.go | 3 +++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index 090694155..9fb964292 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -91,8 +91,8 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg if buffer.Len() == 0 { return "", args } - newWhere = buffer.String() tmpArgs = append(tmpArgs, args...) + newWhere = buffer.String() // 查询条件参数处理,主要处理slice参数类型 if len(tmpArgs) > 0 { for index, arg := range tmpArgs { @@ -103,14 +103,16 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg kind = rv.Kind() } switch kind { - // '?'占位符支持slice类型, - // 这里会将slice参数拆散,并更新原有占位符'?'为多个'?',使用','符号连接。 - case reflect.Slice: - fallthrough - case reflect.Array: + // '?'占位符支持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(newWhere, "?") == rv.Len() { + break + } // counter用于匹配该参数的位置(与index对应) counter := 0 newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string { diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index 2971ad3bd..e815de0bd 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -682,7 +682,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/geg/database/gdb/mysql/gdb_args_slice.go b/geg/database/gdb/mysql/gdb_args_slice.go index 28e3193c8..9c7c568ad 100644 --- a/geg/database/gdb/mysql/gdb_args_slice.go +++ b/geg/database/gdb/mysql/gdb_args_slice.go @@ -6,6 +6,9 @@ import ( 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}, From 835a971f895a6680b996bb02af57051fef654c8a Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 12:04:24 +0800 Subject: [PATCH 15/27] add slice parameter support for method operations for gdb --- g/database/gdb/gdb_base.go | 18 ++-- g/database/gdb/gdb_func.go | 101 +++++++++++--------- g/database/gdb/gdb_unit_method_test.go | 63 +++++++++--- g/database/gdb/gdb_unit_transaction_test.go | 19 +++- 4 files changed, 135 insertions(+), 66 deletions(-) diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index dfc17d577..8d1e82db8 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -79,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() @@ -115,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() @@ -179,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) @@ -208,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) } diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index 9fb964292..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,47 +133,16 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg if buffer.Len() == 0 { return "", args } - tmpArgs = append(tmpArgs, args...) + newArgs = append(newArgs, args...) newWhere = buffer.String() // 查询条件参数处理,主要处理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, 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(newWhere, "?") == rv.Len() { - break - } - // 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 += "?" } } } diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go index 886277a24..21f72c1b7 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,44 @@ 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 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_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") } From b9440587d080e43133126ecc882bee762deb15f3 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 12:50:38 +0800 Subject: [PATCH 16/27] add Offset function for gdb.Model; improve schema changing feature for gdb --- g/database/gdb/gdb.go | 7 +++++-- g/database/gdb/gdb_base.go | 6 ++++++ g/database/gdb/gdb_model.go | 16 ++++++++++++++-- g/database/gdb/gdb_pgsql.go | 19 +++++++++++++++---- g/database/gdb/gdb_sqlite.go | 2 +- geg/other/test.go | 16 +++++----------- 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index adb1a6735..f3afee6c9 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -95,6 +95,7 @@ type DB interface { 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) @@ -339,10 +340,12 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { sqlDb = v.(*sql.DB) } // 是否开启调试模式 - bs.SetDebug(node.Debug) + 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 8d1e82db8..0d33a3449 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -615,3 +615,9 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { } 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_model.go b/g/database/gdb/gdb_model.go index ef61c8041..f2ab4d437 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -30,6 +30,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 +48,7 @@ func (bs *dbBase) Table(tables string) *Model { tables: tables, fields: "*", start: -1, + offset: -1, safe: false, } } @@ -214,6 +216,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 +615,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_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..708243231 100644 --- a/g/database/gdb/gdb_sqlite.go +++ b/g/database/gdb/gdb_sqlite.go @@ -41,7 +41,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/geg/other/test.go b/geg/other/test.go index 381a581b9..5bd1eae9c 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,19 +1,13 @@ package main import ( - "encoding/base64" "fmt" - "github.com/gogf/gf/g/encoding/gbase64" + + "github.com/gogf/gf/g/text/gregex" ) func main() { - data := "HwHsGhXMaGc===" - datab, err := gbase64.Decode([]byte(data)) - fmt.Println(err) - fmt.Println(datab) - fmt.Println(string(datab)) - - s, e := base64.StdEncoding.DecodeString(data) - fmt.Println(e) - fmt.Println(string(s)) + query := "SELECT * FROM user where status=1 LIMIT 10, 100" + query, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $1 OFFSET $2`, query) + fmt.Println(query) } From 1f315c5b8d767c37a00ff287eee4d8dab99dafe1 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 13:15:53 +0800 Subject: [PATCH 17/27] add time.Duration parameter support for gcache --- g/os/gcache/gcache.go | 24 +++++++++---------- g/os/gcache/gcache_mem_cache.go | 36 +++++++++++++++++++++-------- g/os/gcache/gcache_z_unit_1_test.go | 17 ++++++++++---- 3 files changed, 51 insertions(+), 26 deletions(-) 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") } }) } From 9ac3841342885f3cb468cc74fe889ffc9d543481 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2019 13:19:28 +0800 Subject: [PATCH 18/27] add time.Duration parameter support for gfcache --- g/os/gfcache/gfcache.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) 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 } From f9c478f2507ab6c4081927e5e34d7a2b2771084e Mon Sep 17 00:00:00 2001 From: john Date: Tue, 9 Jul 2019 14:03:43 +0800 Subject: [PATCH 19/27] improve package feature for gtcp --- g/net/gtcp/gtcp_conn_pkg.go | 67 ++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index f02a9affd..c3172e1e4 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -16,15 +16,18 @@ import ( 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:]) } // 简单协议: 带超时时间的数据发送 @@ -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 From 9495b858fd02d6116f922d3ac4e4b49ecf04c656 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 11 Jul 2019 21:28:06 +0800 Subject: [PATCH 20/27] RELEASE updates --- RELEASE.MD | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/RELEASE.MD b/RELEASE.MD index 806326ef6..a07e5c688 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,3 +1,65 @@ +# `v1.8.0` + +## 新功能改进 +1. 框架目前 69 个开发模块(不包括内部模块),原生代码 65302 行(不包含第三包依赖包),单元测试覆盖率达到77%; +1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/index +1. 新增`glog.Expose`方法暴露内部默认Logger对象; +1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑; +1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; +1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性; +1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的方法; +1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: +1. 改进`ghttp`模块的分组路由功能; +1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; +1. 新增`gerror`错误处理模块:https://goframe.org/errors/gerror/index +1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; +1. 改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格; +1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; +1. 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`; +1. 改进`glog`新增xxxx特性,修改名称; +1. 改进堆栈打印特性,统一堆栈打印风格; +1. 新增`internal/structs`包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块; +1. 改进`gdb`数据库ORM模块: + - 改进错误处理,当数据库操作没有查询到数据时,返回`sql.ErrNoRows`:https://goframe.org/database/gdb/error + - `Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; + - 数据库链式操作及方法操作可直接给定`slice`参数; + - 修改`Priority`权重配置名称为`Weight`; + - 新增`Debug`配置,可配置开启/关闭调试特性; + - 新增`Offset`方法,该方法为可选链式操作方法,可直接通过`Limit`方法实现部分数据库的`Offset`特性; + - 改进数据库动态切换特性; + - 改进简化配置文件结构:https://goframe.org/database/gdb/config +1. 改进`ghttp`模块: + - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换; +1. 改进`gvalid`数据校验模块: + - 增加对校验标签`gvalid/valid/v`的支持; + - 改进`CheckStruct`支持对结构体对象的递归校验:https://goframe.org/util/gvalid/checkstruct +1. 改进`gconv`数据转换模块: + - 对结构体对象转换时支持更多的标签:`gconv/c/json`; + - 支持`*struct/[]struct/[]*struct`自动初始化创建对象/数组:https://goframe.org/util/gconv/struct + - 新增`Strusts/StrctsDeep`方法,用于结构体数组的转换即递归转换; + - 新增`StructDeep`方法,用于对结构体对象的递归转换; + - 新增`MapDeep`方法,用于对结构体属性的递归转换; +1. `gfile`增加`CopyFile`及`CopyDir`方法,用于文件及目录的复制; +1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; +1. 改进`gcache`模块,过期时间参数支持`time.Duration`类型; +1. 改进`gtcp`TCP通信模块: + - 改进通信包协议设计,更加轻量级高效:https://goframe.org/net/gtcp/conn/pkg +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`模块: From 1dedf3d83b263ad4d5c5a6436f44fbbc26d84a2b Mon Sep 17 00:00:00 2001 From: John Date: Fri, 12 Jul 2019 22:33:34 +0800 Subject: [PATCH 21/27] RELEASE updates --- RELEASE.MD | 56 ++++++++++++++++++++------------------ geg/os/glog/glog_error.go | 1 - geg/os/glog/glog_gerror.go | 4 +-- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/RELEASE.MD b/RELEASE.MD index a07e5c688..7758983a0 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,49 +1,53 @@ # `v1.8.0` ## 新功能改进 -1. 框架目前 69 个开发模块(不包括内部模块),原生代码 65302 行(不包含第三包依赖包),单元测试覆盖率达到77%; -1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/index -1. 新增`glog.Expose`方法暴露内部默认Logger对象; -1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑; -1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; -1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性; -1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的方法; -1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: -1. 改进`ghttp`模块的分组路由功能; -1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; +1. 框架目前 `69` 个开发模块(不包括内部模块),原生代码 `65302` 行(不包含第三包依赖包),单元测试覆盖率达到`77%`; 1. 新增`gerror`错误处理模块:https://goframe.org/errors/gerror/index -1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; -1. 改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格; -1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; -1. 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`; -1. 改进`glog`新增xxxx特性,修改名称; -1. 改进堆栈打印特性,统一堆栈打印风格; -1. 新增`internal/structs`包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块; +1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/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模块: - 改进错误处理,当数据库操作没有查询到数据时,返回`sql.ErrNoRows`:https://goframe.org/database/gdb/error - - `Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; + - 改进`Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; - 数据库链式操作及方法操作可直接给定`slice`参数; - 修改`Priority`权重配置名称为`Weight`; - 新增`Debug`配置,可配置开启/关闭调试特性; - 新增`Offset`方法,该方法为可选链式操作方法,可直接通过`Limit`方法实现部分数据库的`Offset`特性; - 改进数据库动态切换特性; - 改进简化配置文件结构:https://goframe.org/database/gdb/config -1. 改进`ghttp`模块: - - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换; -1. 改进`gvalid`数据校验模块: - - 增加对校验标签`gvalid/valid/v`的支持; - - 改进`CheckStruct`支持对结构体对象的递归校验:https://goframe.org/util/gvalid/checkstruct 1. 改进`gconv`数据转换模块: - 对结构体对象转换时支持更多的标签:`gconv/c/json`; - 支持`*struct/[]struct/[]*struct`自动初始化创建对象/数组:https://goframe.org/util/gconv/struct - 新增`Strusts/StrctsDeep`方法,用于结构体数组的转换即递归转换; - 新增`StructDeep`方法,用于对结构体对象的递归转换; - 新增`MapDeep`方法,用于对结构体属性的递归转换; -1. `gfile`增加`CopyFile`及`CopyDir`方法,用于文件及目录的复制; -1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; -1. 改进`gcache`模块,过期时间参数支持`time.Duration`类型; +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 +1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑; +1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; +1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性; +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 diff --git a/geg/os/glog/glog_error.go b/geg/os/glog/glog_error.go index b4eb1cfb5..1a145d768 100644 --- a/geg/os/glog/glog_error.go +++ b/geg/os/glog/glog_error.go @@ -4,7 +4,6 @@ import "github.com/gogf/gf/g/os/glog" func Test() { glog.Error("This is error!") - glog.Errorf("This is error, %d!", 2) } func main() { diff --git a/geg/os/glog/glog_gerror.go b/geg/os/glog/glog_gerror.go index 3d24f5d16..f5837c871 100644 --- a/geg/os/glog/glog_gerror.go +++ b/geg/os/glog/glog_gerror.go @@ -17,8 +17,8 @@ func MakeGError() error { func TestGError() { err1 := MakeError() err2 := MakeGError() - glog.Errorf("connection error: %+v", err1) - glog.Errorf("connection error: %+v", err2) + glog.Error(err1) + glog.Error(err2) } func main() { From 3521f1b6412f2ed86486eec84d6455e2f28969e3 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 12 Jul 2019 22:37:49 +0800 Subject: [PATCH 22/27] add more unit test cases for gdb for slice parameter feature --- g/database/gdb/gdb_unit_method_test.go | 6 ++++++ g/database/gdb/gdb_unit_model_test.go | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go index 21f72c1b7..db948cd60 100644 --- a/g/database/gdb/gdb_unit_method_test.go +++ b/g/database/gdb/gdb_unit_method_test.go @@ -302,6 +302,12 @@ func TestDbBase_GetAll(t *testing.T) { 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) diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index e815de0bd..34cc9590e 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -634,6 +634,14 @@ func TestModel_Where(t *testing.T) { 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 { From a2283563994a8f124338e2ce74e86c3d756739e2 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 12 Jul 2019 22:57:40 +0800 Subject: [PATCH 23/27] RELEASE updates --- RELEASE.MD | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/RELEASE.MD b/RELEASE.MD index 7758983a0..9ed3b7756 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -4,6 +4,7 @@ 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 @@ -12,13 +13,13 @@ - 修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式; - 新增`Expose`方法暴露内部默认`Logger`对象; 1. 改进`gdb`数据库ORM模块: - - 改进错误处理,当数据库操作没有查询到数据时,返回`sql.ErrNoRows`:https://goframe.org/database/gdb/error + - 改进错误处理,当数据库操作没有查询到数据时,`error`返回`sql.ErrNoRows`:https://goframe.org/database/gdb/error - 改进`Update`/`Delete`方法支持`Order BY`及`LIMIT`特性; - - 数据库链式操作及方法操作可直接给定`slice`参数; + - 数据库链式操作及方法操作中,预处理变量参数支持`slice`参数:https://goframe.org/database/gdb/chaining/model - 修改`Priority`权重配置名称为`Weight`; - - 新增`Debug`配置,可配置开启/关闭调试特性; - - 新增`Offset`方法,该方法为可选链式操作方法,可直接通过`Limit`方法实现部分数据库的`Offset`特性; - - 改进数据库动态切换特性; + - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config + - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法; + - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; - 改进简化配置文件结构:https://goframe.org/database/gdb/config 1. 改进`gconv`数据转换模块: - 对结构体对象转换时支持更多的标签:`gconv/c/json`; @@ -27,7 +28,7 @@ - 新增`StructDeep`方法,用于对结构体对象的递归转换; - 新增`MapDeep`方法,用于对结构体属性的递归转换; 1. 改进`ghttp`模块: - - 改进`ghttp`模块的分组路由功能; + - 改进`ghttp`模块的分组路由功能,完善逻辑处理细节,程序更加稳健; - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换,并且支持`**struct`参数的对象自动初始化; - 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`; 1. 改进`gvalid`数据校验模块: @@ -35,10 +36,11 @@ - 改进`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. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性; -1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的方法; +1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的操作方法; 1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: 1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; 1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; From 769f31e69af96088020ea9a4822fa42bcd05b113 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2019 09:53:53 +0800 Subject: [PATCH 24/27] RELEASE updates --- RELEASE.MD | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/RELEASE.MD b/RELEASE.MD index 9ed3b7756..298a96a95 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -10,13 +10,13 @@ - 新增`Flags`额外功能特性:https://goframe.org/os/glog/flags - 新增`Json`数据格式输出:https://goframe.org/os/glog/json - 新增自定义`Writer`接口特性:https://goframe.org/os/glog/writer - - 修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式; + - **修改`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`; + - **修改`Priority`权重配置名称为`Weight`;** - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法; - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; @@ -44,7 +44,7 @@ 1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: 1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; 1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; -1. 改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格; +1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;** 1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; 1. 新增`gfile.CopyFile/CopyDir`方法,用于文件及目录的复制; 1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; @@ -63,6 +63,9 @@ 1. 修复`gfsnotify.Add`方法默认递归监控添加失效问题; 1. 修复`gdb.Model.Scan`在某些参数类型下的失效问题; +## 注意事项 + +以上粗体、下划线部分为不兼容调整,升级时请注意。 From 08eeff7f1c60a26c255ba6ca456087a1ddde790f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2019 09:55:10 +0800 Subject: [PATCH 25/27] RELEASE updates --- RELEASE.MD | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASE.MD b/RELEASE.MD index 298a96a95..2631d93a7 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -10,13 +10,13 @@ - 新增`Flags`额外功能特性:https://goframe.org/os/glog/flags - 新增`Json`数据格式输出:https://goframe.org/os/glog/json - 新增自定义`Writer`接口特性:https://goframe.org/os/glog/writer - - **修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式;** + - **修改`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`;** + - **修改`Priority`权重配置名称为`Weight`;** - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法; - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; @@ -44,7 +44,7 @@ 1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法: 1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能; 1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换; -1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;** +1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;** 1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法; 1. 新增`gfile.CopyFile/CopyDir`方法,用于文件及目录的复制; 1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法; @@ -65,7 +65,7 @@ ## 注意事项 -以上粗体、下划线部分为不兼容调整,升级时请注意。 +以上粗体文字部分为不兼容调整,升级时请注意。 From e6bb5e82a3c7b71a70afa746272274f1de312ec8 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2019 09:57:40 +0800 Subject: [PATCH 26/27] version updates --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 28e5c33e8137dac039c8d5c0948f4b08532abb8b Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2019 11:35:22 +0800 Subject: [PATCH 27/27] comment updates for gdb --- g/database/gdb/gdb_model.go | 2 -- g/database/gdb/gdb_mssql.go | 16 +++++++--------- g/database/gdb/gdb_oracle.go | 15 ++++++--------- g/database/gdb/gdb_sqlite.go | 1 - 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index f2ab4d437..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 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_sqlite.go b/g/database/gdb/gdb_sqlite.go index 708243231..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