== , the list is not modified.
// The element and
must not be nil.
diff --git a/g/container/gpool/gpool.go b/g/container/gpool/gpool.go
index 69bbfc6cc..43f0647d7 100644
--- a/g/container/gpool/gpool.go
+++ b/g/container/gpool/gpool.go
@@ -43,10 +43,11 @@ type ExpireFunc func(interface{})
// New returns a new object pool.
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
-// Expire:
+//
+// Expiration logistics:
// expire = 0 : not expired;
-// expire < 0 : immediate recovery after use;
-// expire > 0 : timeout recovery;
+// expire < 0 : immediate expired after use;
+// expire > 0 : timeout expired;
// Note that the expiration time unit is ** milliseconds **.
func New(expire int, newFunc NewFunc, expireFunc...ExpireFunc) *Pool {
r := &Pool {
@@ -103,14 +104,26 @@ func (p *Pool) Size() int {
return p.list.Len()
}
-// Close closes the pool.
+// Close closes the pool. If
has ExpireFunc,
+// then it automatically closes all items using this function before it's closed.
func (p *Pool) Close() {
p.closed.Set(true)
}
-// checkExpire secondly removes expired items from pool.
+// checkExpire removes expired items from pool every second.
func (p *Pool) checkExpire() {
if p.closed.Val() {
+ // If p has ExpireFunc,
+ // then it must close all items using this function.
+ if p.ExpireFunc != nil {
+ for {
+ if r := p.list.PopFront(); r != nil {
+ p.ExpireFunc(r.(*poolItem).value)
+ } else {
+ break
+ }
+ }
+ }
gtimer.Exit()
}
for {
diff --git a/g/container/gpool/gpool_test.go b/g/container/gpool/gpool_bench_test.go
similarity index 87%
rename from g/container/gpool/gpool_test.go
rename to g/container/gpool/gpool_bench_test.go
index d86172519..d2f243479 100644
--- a/g/container/gpool/gpool_test.go
+++ b/g/container/gpool/gpool_bench_test.go
@@ -6,14 +6,15 @@
// go test *.go -bench=".*"
-package gpool
+package gpool_test
import (
- "testing"
+ "github.com/gogf/gf/g/container/gpool"
+ "testing"
"sync"
)
-var pool = New(99999999, nil)
+var pool = gpool.New(99999999, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {
diff --git a/g/container/gvar/gvar.go b/g/container/gvar/gvar.go
index 16e573fe0..62e2ea20a 100644
--- a/g/container/gvar/gvar.go
+++ b/g/container/gvar/gvar.go
@@ -80,9 +80,9 @@ func (v *Var) GTime(format...string) *gtime.Time {
// Struct maps value of to .
// The param should be a pointer to a struct instance.
-// The param is used to specify the key-to-attribute mapping rules.
-func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {
- return gconv.Struct(v.Val(), objPointer, attrMapping...)
+// The param is used to specify the key-to-attribute mapping rules.
+func (v *Var) Struct(pointer interface{}, mapping...map[string]string) error {
+ return gconv.Struct(v.Val(), pointer, mapping...)
}
func (v *Var) IsNil() bool { return v.Val() == nil }
diff --git a/g/crypto/gaes/gaes.go b/g/crypto/gaes/gaes.go
index 62b77c257..746deba74 100644
--- a/g/crypto/gaes/gaes.go
+++ b/g/crypto/gaes/gaes.go
@@ -65,7 +65,6 @@ func Decrypt(cipherText []byte, key []byte, iv...[]byte) ([]byte, error) {
if e != nil {
return nil, e
}
-
return plainText, nil
}
diff --git a/g/crypto/gcrc32/gcrc32.go b/g/crypto/gcrc32/gcrc32.go
index 60ea5f7ff..28052d0d4 100644
--- a/g/crypto/gcrc32/gcrc32.go
+++ b/g/crypto/gcrc32/gcrc32.go
@@ -4,25 +4,26 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
-// Package gcrc32 provides API for CRC32 encryption/decryption algorithm.
+// Package gcrc32 provides useful API for CRC32 encryption algorithms.
package gcrc32
import (
- "hash/crc32"
+ "github.com/gogf/gf/g/util/gconv"
+ "hash/crc32"
)
-// Encrypt encrypts bytes using CRC32 algorithm.
-func Encrypt(v []byte) uint32 {
- return crc32.ChecksumIEEE(v)
+// Encrypt encrypts any type of variable using CRC32 algorithms.
+// It uses gconv package to convert to its bytes type.
+func Encrypt(v interface{}) uint32 {
+ return crc32.ChecksumIEEE(gconv.Bytes(v))
}
-// EncryptString encrypts string using CRC32 algorithm.
+// Deprecated.
func EncryptString(v string) uint32 {
return crc32.ChecksumIEEE([]byte(v))
}
-// Alias of Encrypt.
// Deprecated.
func EncryptBytes(v []byte) uint32 {
- return Encrypt(v)
+ return crc32.ChecksumIEEE(v)
}
diff --git a/g/crypto/gdes/gdes.go b/g/crypto/gdes/gdes.go
index 0ca8dcb8a..f115692a4 100644
--- a/g/crypto/gdes/gdes.go
+++ b/g/crypto/gdes/gdes.go
@@ -3,7 +3,8 @@
// 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
+//
+// @author wenzi1
// Package gdes provides useful API for DES encryption/decryption algorithms.
package gdes
@@ -16,11 +17,11 @@ import (
)
const (
- NOPADDING = iota
+ NOPADDING = iota
PKCS5PADDING
)
-//ECB模式DES加密
+// ECB模式DES加密
func DesECBEncrypt(key []byte, clearText []byte, padding int) ([]byte, error) {
text, err := Padding(clearText, padding)
if err != nil {
@@ -42,7 +43,7 @@ func DesECBEncrypt(key []byte, clearText []byte, padding int) ([]byte, error) {
return cipherText, nil
}
-//ECB模式DES解密
+// ECB模式DES解密
func DesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
text := make([]byte, len(cipherText))
block, err := des.NewCipher(key)
@@ -63,7 +64,7 @@ func DesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
return clearText, nil
}
-//ECB模式3DES加密,密钥长度可以是16或24位长
+// ECB模式3DES加密,密钥长度可以是16或24位长
func TripleDesECBEncrypt(key []byte, clearText []byte, padding int) ( []byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length error")
@@ -96,7 +97,7 @@ func TripleDesECBEncrypt(key []byte, clearText []byte, padding int) ( []byte, er
return cipherText, nil
}
-//ECB模式3DES解密,密钥长度可以是16或24位长
+// ECB模式3DES解密,密钥长度可以是16或24位长
func TripleDesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length error")
@@ -129,7 +130,7 @@ func TripleDesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, e
return clearText, nil
}
-//CBC模式DES加密
+// CBC模式DES加密
func DesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
@@ -152,7 +153,7 @@ func DesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte
return cipherText, nil
}
-//CBC模式DES解密
+// CBC模式DES解密
func DesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
@@ -175,7 +176,7 @@ func DesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ([]byt
return clearText, nil
}
-//CBC模式3DES加密
+// CBC模式3DES加密
func TripleDesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length invalid")
@@ -210,7 +211,7 @@ func TripleDesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) (
return cipherText, nil
}
-//CBC模式3DES解密
+// CBC模式3DES解密
func TripleDesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ( []byte, error) {
if len(key) != 16 && len(key) != 24 {
return nil, errors.New("key length invalid")
@@ -245,21 +246,21 @@ func TripleDesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int)
return clearText, nil
}
-//PKCS5补位
+// PKCS5补位
func PKCS5Padding(text []byte, blockSize int) []byte {
padding := blockSize - len(text) % blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(text, padtext...)
}
-//去除PKCS5补位
+// 去除PKCS5补位
func PKCS5Unpadding(text []byte) []byte{
length := len(text)
padtext := int(text[length - 1])
return text[:(length - padtext)]
}
-//补位方法
+// 补位方法
func Padding(text []byte, padding int)([]byte, error) {
switch padding {
case NOPADDING:
@@ -275,7 +276,7 @@ func Padding(text []byte, padding int)([]byte, error) {
return text, nil
}
-//去除补位方法
+// 去除补位方法
func UnPadding(text []byte, padding int)([]byte, error) {
switch padding {
case NOPADDING:
diff --git a/g/crypto/gmd5/gmd5.go b/g/crypto/gmd5/gmd5.go
index aa5fab0a3..63fc08911 100644
--- a/g/crypto/gmd5/gmd5.go
+++ b/g/crypto/gmd5/gmd5.go
@@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
-// Package gmd5 provides useful API for MD5 encryption/decryption algorithms.
+// Package gmd5 provides useful API for MD5 encryption algorithms.
package gmd5
import (
@@ -15,28 +15,31 @@ import (
"github.com/gogf/gf/g/util/gconv"
)
-// 将任意类型的变量进行md5摘要(注意map等非排序变量造成的不同结果)
+// Encrypt encrypts any type of variable using MD5 algorithms.
+// It uses gconv package to convert to its bytes type.
func Encrypt(v interface{}) string {
h := md5.New()
h.Write([]byte(gconv.Bytes(v)))
return fmt.Sprintf("%x", h.Sum(nil))
}
-// 将字符串进行MD5哈希摘要计算
+
+// Deprecated.
func EncryptString(v string) string {
- h := md5.New()
- h.Write([]byte(v))
- return fmt.Sprintf("%x", h.Sum(nil))
+ h := md5.New()
+ h.Write([]byte(v))
+ return fmt.Sprintf("%x", h.Sum(nil))
}
-// 将文件内容进行MD5哈希摘要计算
+
+// EncryptFile encrypts file content of using MD5 algorithms.
func EncryptFile(path string) string {
f, e := os.Open(path)
if e != nil {
return ""
}
defer f.Close()
- h := md5.New()
+ h := md5.New()
_, e = io.Copy(h, f)
if e != nil {
return ""
diff --git a/g/crypto/gsha1/gsha1.go b/g/crypto/gsha1/gsha1.go
index 8caa35aee..a12a02884 100644
--- a/g/crypto/gsha1/gsha1.go
+++ b/g/crypto/gsha1/gsha1.go
@@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
-// Package gsha1 provides useful API for SHA1 encryption/decryption algorithms.
+// Package gsha1 provides useful API for SHA1 encryption algorithms.
package gsha1
import (
@@ -15,20 +15,20 @@ import (
"github.com/gogf/gf/g/util/gconv"
)
-// 将任意类型的变量进行SHA摘要(注意map等非排序变量造成的不同结果)
-// 内部使用了md5计算,因此效率会稍微差一些,更多情况请使用 EncodeString
+// Encrypt encrypts any type of variable using SHA1 algorithms.
+// It uses gconv package to convert to its bytes type.
func Encrypt(v interface{}) string {
r := sha1.Sum(gconv.Bytes(v))
return hex.EncodeToString(r[:])
}
-// 对字符串行SHA1摘要计算
+// Deprecated.
func EncryptString(s string) string {
- r := sha1.Sum([]byte(s))
- return hex.EncodeToString(r[:])
+ r := sha1.Sum([]byte(s))
+ return hex.EncodeToString(r[:])
}
-// 对文件内容进行SHA1摘要计算
+// EncryptFile encrypts file content of using SHA1 algorithms.
func EncryptFile(path string) string {
f, e := os.Open(path)
if e != nil {
diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go
index f1c2b2e2b..68a069344 100644
--- a/g/database/gdb/gdb.go
+++ b/g/database/gdb/gdb.go
@@ -84,6 +84,7 @@ type DB interface {
SetDebug(debug bool)
SetSchema(schema string)
GetQueriedSqls() []*Sql
+ GetLastSql() *Sql
PrintQueriedSqls()
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)
diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go
index d16be23d1..bf50a1b7f 100644
--- a/g/database/gdb/gdb_base.go
+++ b/g/database/gdb/gdb_base.go
@@ -24,6 +24,17 @@ const (
gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数
)
+// 获取最近一条执行的sql
+func (bs *dbBase) GetLastSql() *Sql {
+ if bs.sqls == nil {
+ return nil
+ }
+ if v := bs.sqls.Val(); v != nil {
+ return v.(*Sql)
+ }
+ return nil
+}
+
// 获取已经执行的SQL列表(仅在debug=true时有效)
func (bs *dbBase) GetQueriedSqls() []*Sql {
if bs.sqls == nil {
@@ -312,7 +323,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map: fallthrough
case reflect.Struct:
- dataMap = gconv.Map(data)
+ dataMap = structToMap(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
@@ -390,11 +401,11 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
- listMap[i] = gconv.Map(rv.Index(i).Interface())
+ listMap[i] = structToMap(rv.Index(i).Interface())
}
case reflect.Map: fallthrough
case reflect.Struct:
- listMap = List{Map(gconv.Map(list))}
+ listMap = List{Map(structToMap(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
@@ -504,7 +515,7 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
case reflect.Map: fallthrough
case reflect.Struct:
var fields []string
- for k, v := range gconv.Map(data) {
+ for k, v := range structToMap(data) {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
params = append(params, convertParam(v))
}
diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go
index 63a11f846..f010d23b4 100644
--- a/g/database/gdb/gdb_func.go
+++ b/g/database/gdb/gdb_func.go
@@ -7,19 +7,24 @@
package gdb
import (
- "bytes"
- "errors"
- "fmt"
- "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"
+ "bytes"
+ "errors"
+ "fmt"
+ "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().
+type apiString interface {
+ String() string
+}
+
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) {
// 条件字符串处理
@@ -36,7 +41,7 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
// map/struct类型
case reflect.Map: fallthrough
case reflect.Struct:
- for key, value := range gconv.Map(where) {
+ for key, value := range structToMap(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
@@ -137,10 +142,19 @@ func convertParam(value interface{}) interface{} {
}
switch kind {
case reflect.Struct:
- // 底层数据库引擎支持 time.Time 类型
- if _, ok := value.(time.Time); ok {
+ // 底层数据库引擎支持 time.Time/*time.Time 类型
+ if v, ok := value.(time.Time); ok {
+ if v.IsZero() {
+ return "null"
+ }
return value
}
+ if v, ok := value.(*time.Time); ok {
+ if v.IsZero() {
+ return ""
+ }
+ return value
+ }
return gconv.String(value)
}
return value
@@ -165,12 +179,12 @@ func printSql(v *Sql) {
// 格式化错误信息
func formatError(err error, query string, args ...interface{}) error {
if err != nil {
- errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
- errstr += fmt.Sprintf("DB QUERY: %s\n", query)
+ errStr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
+ errStr += fmt.Sprintf("DB QUERY: %s\n", query)
if len(args) > 0 {
- errstr += fmt.Sprintf("DB PARAM: %v\n", args)
+ errStr += fmt.Sprintf("DB PARAM: %v\n", args)
}
- err = errors.New(errstr)
+ err = errors.New(errStr)
}
return err
}
@@ -187,3 +201,42 @@ func getInsertOperationByOption(option int) string {
}
return operator
}
+
+// 将对象转换为map,如果对象带有继承对象,那么执行递归转换。
+// 该方法用于将变量传递给数据库执行之前。
+func structToMap(obj interface{}) map[string]interface{} {
+ data := gconv.Map(obj)
+ for key, value := range data {
+ rv := reflect.ValueOf(value)
+ kind := rv.Kind()
+ if kind == reflect.Ptr {
+ rv = rv.Elem()
+ kind = rv.Kind()
+ }
+ switch kind {
+ case reflect.Struct:
+ // 底层数据库引擎支持 time.Time/*time.Time 类型
+ if _, ok := value.(time.Time); ok {
+ continue
+ }
+ if _, ok := value.(*time.Time); ok {
+ continue
+ }
+ // 如果执行String方法,那么执行字符串转换
+ if s, ok := value.(apiString); ok {
+ data[key] = s.String()
+ continue
+ }
+ delete(data, key)
+ for k, v := range structToMap(value) {
+ data[k] = v
+ }
+ }
+ }
+ return data
+}
+
+// 使用递归的方式将map键值对映射到struct对象上,注意参数是一个指向struct的指针。
+func mapToStruct(data map[string]interface{}, pointer interface{}) error {
+ return gconv.StructDeep(data, pointer)
+}
\ No newline at end of file
diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go
index f039a64ae..e53044cde 100644
--- a/g/database/gdb/gdb_model.go
+++ b/g/database/gdb/gdb_model.go
@@ -268,12 +268,12 @@ func (md *Model) Data(data ...interface{}) *Model {
case reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
- list[i] = gconv.Map(rv.Index(i).Interface())
+ list[i] = structToMap(rv.Index(i).Interface())
}
model.data = list
case reflect.Map: fallthrough
case reflect.Struct:
- model.data = Map(gconv.Map(data[0]))
+ model.data = Map(structToMap(data[0]))
default:
model.data = data[0]
}
diff --git a/g/database/gdb/gdb_mssql.go b/g/database/gdb/gdb_mssql.go
index 7ad243643..bd3de9e08 100644
--- a/g/database/gdb/gdb_mssql.go
+++ b/g/database/gdb/gdb_mssql.go
@@ -67,13 +67,13 @@ func (db *dbMssql) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, sql) == false {
- fmt.Println("not matched..")
+ //fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
- fmt.Println("MatchString error.", err)
+ //fmt.Println("MatchString error.", err)
return ""
}
@@ -83,69 +83,69 @@ func (db *dbMssql) parseSql(sql string) string {
index++
switch keyword {
- case "SELECT":
- //不含LIMIT关键字则不处理
- if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
- break
- }
-
- //不含LIMIT则不处理
- if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
- break
- }
-
- //判断SQL中是否含有order by
- selectStr := ""
- orderbyStr := ""
- haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
- if haveOrderby {
- //取order by 前面的字符串
- queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
-
- if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
+ case "SELECT":
+ //不含LIMIT关键字则不处理
+ if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
break
}
- selectStr = queryExpr[2]
- //取order by表达式的值
- orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
- if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
+ //不含LIMIT则不处理
+ if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
- orderbyStr = orderbyExpr[2]
- } else {
- queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
- if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
- break
- }
- selectStr = queryExpr[2]
- }
- //取limit后面的取值范围
- first, limit := 0, 0
- for i := 1; i < len(res[index]); i++ {
- if len(strings.TrimSpace(res[index][i])) == 0 {
- continue
- }
+ //判断SQL中是否含有order by
+ selectStr := ""
+ orderbyStr := ""
+ haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
+ if haveOrderby {
+ //取order by 前面的字符串
+ queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
- if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
- first, _ = strconv.Atoi(res[index][i+1])
- limit, _ = strconv.Atoi(res[index][i+2])
- break
- }
- }
+ if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
+ break
+ }
+ selectStr = queryExpr[2]
- if haveOrderby {
- sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
- } else {
- if first == 0 {
- first = limit
+ //取order by表达式的值
+ orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
+ if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
+ break
+ }
+ orderbyStr = orderbyExpr[2]
} else {
- first = limit - first
+ queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
+ if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
+ break
+ }
+ selectStr = queryExpr[2]
}
- sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
- }
- default:
+
+ //取limit后面的取值范围
+ first, limit := 0, 0
+ for i := 1; i < len(res[index]); i++ {
+ if len(strings.TrimSpace(res[index][i])) == 0 {
+ continue
+ }
+
+ if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
+ first, _ = strconv.Atoi(res[index][i+1])
+ limit, _ = strconv.Atoi(res[index][i+2])
+ break
+ }
+ }
+
+ if haveOrderby {
+ sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
+ } else {
+ if first == 0 {
+ first = limit
+ } else {
+ first = limit - first
+ }
+ sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
+ }
+ default:
}
return sql
}
diff --git a/g/database/gdb/gdb_oracle.go b/g/database/gdb/gdb_oracle.go
index 5df9057c1..17c7d48fc 100644
--- a/g/database/gdb/gdb_oracle.go
+++ b/g/database/gdb/gdb_oracle.go
@@ -65,80 +65,80 @@ func (db *dbOracle) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, sql) == false {
- fmt.Println("not matched..")
+ //fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
- fmt.Println("MatchString error.", err)
+ //fmt.Println("MatchString error.", err)
return ""
}
- index := 0
+ index := 0
keyword := strings.TrimSpace(res[index][0])
- keyword = strings.ToUpper(keyword)
+ keyword = strings.ToUpper(keyword)
index++
switch keyword {
- case "SELECT":
- //不含LIMIT关键字则不处理
- if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
- break
- }
-
- //取limit前面的字符串
- if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
- break
- }
-
- queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
- if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
- break
- }
-
- //取limit后面的取值范围
- first, limit := 0, 0
- for i := 1; i < len(res[index]); i++ {
- if len(strings.TrimSpace(res[index][i])) == 0 {
- continue
- }
-
- if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
- first, _ = strconv.Atoi(res[index][i+1])
- limit, _ = strconv.Atoi(res[index][i+2])
+ case "SELECT":
+ //不含LIMIT关键字则不处理
+ if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
break
}
- }
- //也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
- sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
- case "INSERT":
- //获取VALUE的值,匹配所有带括号的值,会将INSERT INTO后的值匹配到,所以下面的判断语句会判断数组长度是否小于3
- valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
- if err != nil {
- return sql
- }
+ //取limit前面的字符串
+ if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
+ break
+ }
- //判断VALUE后的值是否有多个,只有在批量插入的时候才需要做转换,如只有1个VALUE则不需要做转换
- if len(valueExpr) < 3 {
- break
- }
+ queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
+ if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
+ break
+ }
- //获取INTO后面的值
- tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
- if err != nil {
- return sql
- }
- tableExpr[0] = strings.TrimSpace(tableExpr[0])
+ //取limit后面的取值范围
+ first, limit := 0, 0
+ for i := 1; i < len(res[index]); i++ {
+ if len(strings.TrimSpace(res[index][i])) == 0 {
+ continue
+ }
- sql = "INSERT ALL"
- for i := 1; i < len(valueExpr); i++ {
- sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
- }
- sql += " SELECT 1 FROM DUAL"
+ if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
+ first, _ = strconv.Atoi(res[index][i+1])
+ limit, _ = strconv.Atoi(res[index][i+2])
+ break
+ }
+ }
- default:
+ //也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
+ sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
+ case "INSERT":
+ //获取VALUE的值,匹配所有带括号的值,会将INSERT INTO后的值匹配到,所以下面的判断语句会判断数组长度是否小于3
+ valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
+ if err != nil {
+ return sql
+ }
+
+ //判断VALUE后的值是否有多个,只有在批量插入的时候才需要做转换,如只有1个VALUE则不需要做转换
+ if len(valueExpr) < 3 {
+ break
+ }
+
+ //获取INTO后面的值
+ tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
+ if err != nil {
+ return sql
+ }
+ tableExpr[0] = strings.TrimSpace(tableExpr[0])
+
+ sql = "INSERT ALL"
+ for i := 1; i < len(valueExpr); i++ {
+ sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
+ }
+ sql += " SELECT 1 FROM DUAL"
+
+ default:
}
return sql
}
diff --git a/g/database/gdb/gdb_structure.go b/g/database/gdb/gdb_structure.go
index 2530f5255..709b114cb 100644
--- a/g/database/gdb/gdb_structure.go
+++ b/g/database/gdb/gdb_structure.go
@@ -28,42 +28,42 @@ func (bs *dbBase) convertValue(fieldValue interface{}, fieldType string) interfa
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
t = strings.ToLower(t)
switch t {
- case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
- return gconv.Bytes(fieldValue)
+ case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
+ return gconv.Bytes(fieldValue)
- case "bit", "int", "tinyint", "small_int", "medium_int":
- return gconv.Int(fieldValue)
+ case "bit", "int", "tinyint", "small_int", "medium_int":
+ return gconv.Int(fieldValue)
- case "big_int":
- return gconv.Int64(fieldValue)
+ case "big_int":
+ return gconv.Int64(fieldValue)
- case "float", "double", "decimal":
- return gconv.Float64(fieldValue)
+ case "float", "double", "decimal":
+ return gconv.Float64(fieldValue)
- case "bool":
- return gconv.Bool(fieldValue)
+ case "bool":
+ return gconv.Bool(fieldValue)
- default:
- // 自动识别类型, 以便默认支持更多数据库类型
- switch {
- case strings.Contains(t, "int"):
- return gconv.Int(fieldValue)
+ default:
+ // 自动识别类型, 以便默认支持更多数据库类型
+ switch {
+ case strings.Contains(t, "int"):
+ return gconv.Int(fieldValue)
- case strings.Contains(t, "text") || strings.Contains(t, "char"):
- return gconv.String(fieldValue)
+ case strings.Contains(t, "text") || strings.Contains(t, "char"):
+ return gconv.String(fieldValue)
- case strings.Contains(t, "float") || strings.Contains(t, "double"):
- return gconv.Float64(fieldValue)
+ case strings.Contains(t, "float") || strings.Contains(t, "double"):
+ return gconv.Float64(fieldValue)
- case strings.Contains(t, "bool"):
- return gconv.Bool(fieldValue)
+ case strings.Contains(t, "bool"):
+ return gconv.Bool(fieldValue)
- case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
- return gconv.Bytes(fieldValue)
+ case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
+ return gconv.Bytes(fieldValue)
- default:
- return gconv.String(fieldValue)
- }
+ default:
+ return gconv.String(fieldValue)
+ }
}
}
diff --git a/g/database/gdb/gdb_type_record.go b/g/database/gdb/gdb_type_record.go
index 16e3f51c9..8d9597666 100644
--- a/g/database/gdb/gdb_type_record.go
+++ b/g/database/gdb/gdb_type_record.go
@@ -7,8 +7,7 @@
package gdb
import (
- "github.com/gogf/gf/g/encoding/gparser"
- "github.com/gogf/gf/g/util/gconv"
+ "github.com/gogf/gf/g/encoding/gparser"
)
// 将记录结果转换为JSON字符串
@@ -33,6 +32,6 @@ func (r Record) ToMap() Map {
}
// 将Map变量映射到指定的struct对象中,注意参数应当是一个对象的指针
-func (r Record) ToStruct(objPointer interface{}) error {
- return gconv.Struct(r.ToMap(), objPointer)
+func (r Record) ToStruct(pointer interface{}) error {
+ return mapToStruct(r.ToMap(), pointer)
}
diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go
index 8bc04ca87..a620bd8c9 100644
--- a/g/database/gdb/gdb_unit_method_test.go
+++ b/g/database/gdb/gdb_unit_method_test.go
@@ -11,6 +11,7 @@ import (
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
+ "time"
)
func TestDbBase_Ping(t *testing.T) {
@@ -488,3 +489,51 @@ func TestDbBase_Delete(t *testing.T) {
}
}
+func TestDbBase_Time(t *testing.T) {
+ gtest.Case(t, func() {
+ result, err := db.Insert("user", g.Map{
+ "id" : 200,
+ "passport" : "t200",
+ "password" : "123456",
+ "nickname" : "T200",
+ "create_time" : time.Now(),
+ })
+ if err != nil {
+ gtest.Fatal(err)
+ }
+ n, _ := result.RowsAffected()
+ gtest.Assert(n, 1)
+ value, err := db.GetValue("select `passport` from `user` where id=?", 200)
+ gtest.Assert(err, nil)
+ gtest.Assert(value.String(), "t200")
+ })
+
+ gtest.Case(t, func() {
+ t := time.Now()
+ result, err := db.Insert("user", g.Map{
+ "id" : 300,
+ "passport" : "t300",
+ "password" : "123456",
+ "nickname" : "T300",
+ "create_time" : &t,
+ })
+ if err != nil {
+ gtest.Fatal(err)
+ }
+ n, _ := result.RowsAffected()
+ gtest.Assert(n, 1)
+ value, err := db.GetValue("select `passport` from `user` where id=?", 300)
+ gtest.Assert(err, nil)
+ gtest.Assert(value.String(), "t300")
+ })
+
+ if result, err := db.Delete("user", nil); err != nil {
+ gtest.Fatal(err)
+ } else {
+ n, _ := result.RowsAffected()
+ gtest.Assert(n, 2)
+ }
+}
+
+
+
diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go
index 504fd8c89..efd1d35a5 100644
--- a/g/database/gdb/gdb_unit_model_test.go
+++ b/g/database/gdb/gdb_unit_model_test.go
@@ -7,10 +7,10 @@
package gdb_test
import (
- "github.com/gogf/gf/g"
- "github.com/gogf/gf/g/os/gtime"
- "github.com/gogf/gf/g/test/gtest"
- "testing"
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/os/gtime"
+ "github.com/gogf/gf/g/test/gtest"
+ "testing"
)
// 基本测试
diff --git a/g/database/gdb/gdb_unit_struct_inherit_test.go b/g/database/gdb/gdb_unit_struct_inherit_test.go
new file mode 100644
index 000000000..dfdd09eef
--- /dev/null
+++ b/g/database/gdb/gdb_unit_struct_inherit_test.go
@@ -0,0 +1,99 @@
+// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gdb_test
+
+import (
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/os/gtime"
+ "github.com/gogf/gf/g/test/gtest"
+ "testing"
+)
+
+func TestModel_Inherit_Insert(t *testing.T) {
+ gtest.Case(t, func() {
+ type Base struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ result, err := db.Table("user").Filter().Data(User{
+ Passport : "john-test",
+ Password : "123456",
+ Nickname : "John",
+ Base : Base {
+ Id : 100,
+ Uid : 100,
+ CreateTime : gtime.Now().String(),
+ },
+ }).Insert()
+ gtest.Assert(err, nil)
+ n, _ := result.RowsAffected()
+ gtest.Assert(n, 1)
+ value, err := db.Table("user").Fields("passport").Where("id=100").Value()
+ gtest.Assert(err, nil)
+ gtest.Assert(value.String(), "john-test")
+ // Delete this test data.
+ _, err = db.Table("user").Where("id", 100).Delete()
+ gtest.Assert(err, nil)
+ })
+}
+
+func TestModel_Inherit_MapToStruct(t *testing.T) {
+ gtest.Case(t, func() {
+ type Ids struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ }
+ type Base struct {
+ Ids
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ data := g.Map{
+ "id" : 100,
+ "uid" : 101,
+ "passport" : "t1",
+ "password" : "123456",
+ "nickname" : "T1",
+ "create_time" : gtime.Now().String(),
+ }
+ result, err := db.Table("user").Filter().Data(data).Insert()
+ gtest.Assert(err, nil)
+ n, _ := result.RowsAffected()
+ gtest.Assert(n, 1)
+
+ one, err := db.Table("user").Where("id=100").One()
+ gtest.Assert(err, nil)
+
+ user := new(User)
+
+ gtest.Assert(one.ToStruct(user), nil)
+ gtest.Assert(user.Id, data["id"])
+ gtest.Assert(user.Passport, data["passport"])
+ gtest.Assert(user.Password, data["password"])
+ gtest.Assert(user.Nickname, data["nickname"])
+ gtest.Assert(user.CreateTime, data["create_time"])
+
+ // Delete this test data.
+ _, err = db.Table("user").Where("id", 100).Delete()
+ gtest.Assert(err, nil)
+ })
+
+}
+
+
diff --git a/g/encoding/gjson/gjson_api.go b/g/encoding/gjson/gjson_api.go
index 028478b8e..25db8a035 100644
--- a/g/encoding/gjson/gjson_api.go
+++ b/g/encoding/gjson/gjson_api.go
@@ -64,29 +64,44 @@ func (j *Json) GetMap(pattern string, def...interface{}) map[string]interface{}
}
// GetJson gets the value by specified ,
-// and converts it to a Json object.
+// and converts it to a un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def...interface{}) *Json {
result := j.Get(pattern, def...)
if result != nil {
- return New(result)
+ return New(result, true)
}
return nil
}
// GetJsons gets the value by specified ,
-// and converts it to a slice of Json object.
+// and converts it to a slice of un-concurrent-safe Json object.
func (j *Json) GetJsons(pattern string, def...interface{}) []*Json {
array := j.GetArray(pattern, def...)
if len(array) > 0 {
- jsons := make([]*Json, len(array))
+ jsonSlice := make([]*Json, len(array))
for i := 0; i < len(array); i++ {
- jsons[i] = New(array[i], !j.mu.IsSafe())
+ jsonSlice[i] = New(array[i], true)
}
- return jsons
+ return jsonSlice
}
return nil
}
+// GetJsonMap gets the value by specified ,
+// and converts it to a map of un-concurrent-safe Json object.
+func (j *Json) GetJsonMap(pattern string, def...interface{}) map[string]*Json {
+ m := j.GetMap(pattern, def...)
+ if len(m) > 0 {
+ jsonMap := make(map[string]*Json, len(m))
+ for k, v := range m {
+ jsonMap[k] = New(v, true)
+ }
+ return jsonMap
+ }
+ return nil
+}
+
+
// GetArray gets the value by specified ,
// and converts it to a slice of []interface{}.
func (j *Json) GetArray(pattern string, def...interface{}) []interface{} {
diff --git a/g/encoding/gjson/gjson_api_new_load.go b/g/encoding/gjson/gjson_api_new_load.go
index 72a0df0ec..04a4b3166 100644
--- a/g/encoding/gjson/gjson_api_new_load.go
+++ b/g/encoding/gjson/gjson_api_new_load.go
@@ -43,6 +43,10 @@ func New(data interface{}, unsafe...bool) *Json {
default:
rv := reflect.ValueOf(data)
kind := rv.Kind()
+ if kind == reflect.Ptr {
+ rv = rv.Elem()
+ kind = rv.Kind()
+ }
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
@@ -56,7 +60,7 @@ func New(data interface{}, unsafe...bool) *Json {
case reflect.Map: fallthrough
case reflect.Struct:
i := interface{}(nil)
- i = gconv.Map(data)
+ i = gconv.Map(data, "json")
j = &Json {
p : &i,
c : byte(gDEFAULT_SPLIT_CHAR),
@@ -136,7 +140,7 @@ func LoadContent(data interface{}, unsafe...bool) (*Json, error) {
// auto check data type
if json.Valid(b) {
t = "json"
- } else if gregex.IsMatch(`^<.+>.*$`, b) {
+ } else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, b) {
t = "xml"
} else if gregex.IsMatch(`^[\s\t]*\w+\s*:\s*.+`, b) || gregex.IsMatch(`\n[\s\t]*\w+\s*:\s*.+`, b) {
t = "yml"
diff --git a/g/encoding/gjson/gjson_z_unit_basic_test.go b/g/encoding/gjson/gjson_z_unit_basic_test.go
index b1d3430f1..0a43eee58 100644
--- a/g/encoding/gjson/gjson_z_unit_basic_test.go
+++ b/g/encoding/gjson/gjson_z_unit_basic_test.go
@@ -264,3 +264,39 @@ func TestJson_ToJson(t *testing.T) {
})
}
+func TestJson_Default(t *testing.T) {
+ gtest.Case(t, func() {
+ j := gjson.New(nil)
+ gtest.AssertEQ(j.Get("no", 100), 100)
+ gtest.AssertEQ(j.GetString("no", 100), "100")
+ gtest.AssertEQ(j.GetBool("no", "on"), true)
+ gtest.AssertEQ(j.GetInt("no", 100), 100)
+ gtest.AssertEQ(j.GetInt8("no", 100), int8(100))
+ gtest.AssertEQ(j.GetInt16("no", 100), int16(100))
+ gtest.AssertEQ(j.GetInt32("no", 100), int32(100))
+ gtest.AssertEQ(j.GetInt64("no", 100), int64(100))
+ gtest.AssertEQ(j.GetUint("no", 100), uint(100))
+ gtest.AssertEQ(j.GetUint8("no", 100), uint8(100))
+ gtest.AssertEQ(j.GetUint16("no", 100), uint16(100))
+ gtest.AssertEQ(j.GetUint32("no", 100), uint32(100))
+ gtest.AssertEQ(j.GetUint64("no", 100), uint64(100))
+ gtest.AssertEQ(j.GetFloat32("no", 123.456), float32(123.456))
+ gtest.AssertEQ(j.GetFloat64("no", 123.456), float64(123.456))
+ gtest.AssertEQ(j.GetArray("no", g.Slice{1,2,3}), g.Slice{1,2,3})
+ gtest.AssertEQ(j.GetInts("no", g.Slice{1,2,3}), g.SliceInt{1,2,3})
+ gtest.AssertEQ(j.GetFloats("no", g.Slice{1,2,3}), []float64{1,2,3})
+ gtest.AssertEQ(j.GetMap("no", g.Map{"k":"v"}), g.Map{"k":"v"})
+ gtest.AssertEQ(j.GetVar("no", 123.456).Float64(), float64(123.456))
+ gtest.AssertEQ(j.GetJson("no", g.Map{"k":"v"}).Get("k"), "v")
+ gtest.AssertEQ(j.GetJsons("no", g.Slice{
+ g.Map{"k1":"v1"},
+ g.Map{"k2":"v2"},
+ g.Map{"k3":"v3"},
+ })[0].Get("k1"), "v1")
+ gtest.AssertEQ(j.GetJsonMap("no", g.Map{
+ "m1" : g.Map{"k1":"v1"},
+ "m2" : g.Map{"k2":"v2"},
+ })["m2"].Get("k2"), "v2")
+ })
+}
+
diff --git a/g/encoding/gjson/gjson_z_unit_load_test.go b/g/encoding/gjson/gjson_z_unit_load_test.go
index 7c096428e..6717ea124 100644
--- a/g/encoding/gjson/gjson_z_unit_load_test.go
+++ b/g/encoding/gjson/gjson_z_unit_load_test.go
@@ -67,6 +67,24 @@ func Test_Load_XML(t *testing.T) {
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("doc.a.1"), 2)
})
+
+ // XML
+ gtest.Case(t, func() {
+ xml := `
+
+ `
+ j, err := gjson.LoadContent(xml)
+ gtest.Assert(err, nil)
+ gtest.Assert(j.Get("Output.ipageIndex"), "2")
+ gtest.Assert(j.Get("Output.itotalRecords"), "GF框架")
+ })
}
func Test_Load_YAML1(t *testing.T) {
diff --git a/g/encoding/gparser/gparser_api_new_load.go b/g/encoding/gparser/gparser_api_new_load.go
index 434d05bd1..0cf99c054 100644
--- a/g/encoding/gparser/gparser_api_new_load.go
+++ b/g/encoding/gparser/gparser_api_new_load.go
@@ -40,7 +40,7 @@ func Load(path string, unsafe...bool) (*Parser, error) {
// LoadContent creates a Parser object from given content,
// it checks the data type of automatically,
// supporting JSON, XML, YAML and TOML types of data.
-func LoadContent(data []byte, unsafe...bool) (*Parser, error) {
+func LoadContent(data interface{}, unsafe...bool) (*Parser, error) {
if j, e := gjson.LoadContent(data, unsafe...); e == nil {
return &Parser{j}, nil
} else {
diff --git a/g/encoding/gparser/gparser_unit_load_test.go b/g/encoding/gparser/gparser_unit_load_test.go
index 930693a72..2702ca05f 100644
--- a/g/encoding/gparser/gparser_unit_load_test.go
+++ b/g/encoding/gparser/gparser_unit_load_test.go
@@ -7,11 +7,11 @@
package gparser_test
import (
- "github.com/gogf/gf/g"
- "github.com/gogf/gf/g/encoding/gparser"
- "github.com/gogf/gf/g/os/gfile"
- "github.com/gogf/gf/g/test/gtest"
- "testing"
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/encoding/gparser"
+ "github.com/gogf/gf/g/os/gfile"
+ "github.com/gogf/gf/g/test/gtest"
+ "testing"
)
@@ -67,6 +67,24 @@ func Test_Load_XML(t *testing.T) {
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
gtest.Assert(j.Get("doc.a.1"), 2)
})
+
+ // XML
+ gtest.Case(t, func() {
+ xml := `
+
+ `
+ j, err := gparser.LoadContent(xml)
+ gtest.Assert(err, nil)
+ gtest.Assert(j.Get("Output.ipageIndex"), "2")
+ gtest.Assert(j.Get("Output.itotalRecords"), "GF框架")
+ })
}
func Test_Load_YAML1(t *testing.T) {
diff --git a/g/encoding/gxml/gxml.go b/g/encoding/gxml/gxml.go
index 828d7f8ca..6d4b1ea97 100644
--- a/g/encoding/gxml/gxml.go
+++ b/g/encoding/gxml/gxml.go
@@ -5,8 +5,6 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gxml provides accessing and converting for XML content.
-//
-// XML数据格式解析。
package gxml
import (
@@ -52,33 +50,26 @@ func ToJson(content []byte) ([]byte, error) {
}
// XML字符集预处理
-// @author wenzi1
-// @date 20180604 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换
-func convert(xmlbyte []byte) (res []byte, err error) {
+func convert(xml []byte) (res []byte, err error) {
patten := `<\?xml.*encoding\s*=\s*['|"](.*?)['|"].*\?>`
- matchStr, err := gregex.MatchString(patten, string(xmlbyte))
+ matchStr, err := gregex.MatchString(patten, string(xml))
if err != nil {
return nil, err
}
-
xmlEncode := "UTF-8"
if len(matchStr) == 2 {
xmlEncode = matchStr[1]
}
-
s := mahonia.GetCharset(xmlEncode)
if s == nil {
return nil, fmt.Errorf("not support charset:%s\n", xmlEncode)
}
-
- res, err = gregex.Replace(patten, []byte(""), []byte(xmlbyte))
+ res, err = gregex.Replace(patten, []byte(""), xml)
if err != nil {
return nil, err
}
-
if !strings.EqualFold(s.Name, "UTF-8") {
res = []byte(s.NewDecoder().ConvertString(string(res)))
}
-
return res, nil
}
diff --git a/g/net/ghttp/ghttp_client_request.go b/g/net/ghttp/ghttp_client_request.go
index e907e8297..0178492d5 100644
--- a/g/net/ghttp/ghttp_client_request.go
+++ b/g/net/ghttp/ghttp_client_request.go
@@ -180,6 +180,17 @@ func (c *Client) Post(url string, data...interface{}) (*ClientResponse, error) {
cookies : make(map[string]string),
}
r.Response = resp
+ // 浏览器模式
+ if c.browserMode {
+ now := time.Now()
+ for _, v := range r.Cookies() {
+ if v.Expires.UnixNano() < now.UnixNano() {
+ delete(c.cookies, v.Name)
+ } else {
+ c.cookies[v.Name] = v.Value
+ }
+ }
+ }
return r, nil
}
@@ -258,7 +269,7 @@ func (c *Client) DoRequestContent(method string, url string, data...interface{})
return string(response.ReadAll())
}
-// 请求并返回response对象,该方法支持二进制提交数据
+// 请求并返回response对象
func (c *Client) DoRequest(method, url string, data...interface{}) (*ClientResponse, error) {
if strings.EqualFold("POST", method) {
return c.Post(url, data...)
diff --git a/g/net/ghttp/ghttp_request_post.go b/g/net/ghttp/ghttp_request_post.go
index f78c3bfbb..8255572c9 100644
--- a/g/net/ghttp/ghttp_request_post.go
+++ b/g/net/ghttp/ghttp_request_post.go
@@ -142,16 +142,16 @@ func (r *Request) GetPostMap(def...map[string]string) map[string]string {
}
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
-func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) error {
- tagmap := r.getStructParamsTagMap(object)
+func (r *Request) GetPostToStruct(pointer interface{}, mapping...map[string]string) error {
+ tagMap := r.getStructParamsTagMap(pointer)
if len(mapping) > 0 {
for k, v := range mapping[0] {
- tagmap[k] = v
+ tagMap[k] = v
}
}
params := make(map[string]interface{})
for k, v := range r.GetPostMap() {
params[k] = v
}
- return gconv.Struct(params, object, tagmap)
+ return gconv.Struct(params, pointer, tagMap)
}
\ No newline at end of file
diff --git a/g/net/ghttp/ghttp_request_query.go b/g/net/ghttp/ghttp_request_query.go
index bfc437aef..0b399ce64 100644
--- a/g/net/ghttp/ghttp_request_query.go
+++ b/g/net/ghttp/ghttp_request_query.go
@@ -150,8 +150,8 @@ func (r *Request) GetQueryMap(def... map[string]string) map[string]string {
}
// 将所有的get参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
-func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) error {
- tagmap := r.getStructParamsTagMap(object)
+func (r *Request) GetQueryToStruct(pointer interface{}, mapping...map[string]string) error {
+ tagmap := r.getStructParamsTagMap(pointer)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagmap[k] = v
@@ -161,5 +161,5 @@ func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]stri
for k, v := range r.GetQueryMap() {
params[k] = v
}
- return gconv.Struct(params, object, tagmap)
+ return gconv.Struct(params, pointer, tagmap)
}
\ No newline at end of file
diff --git a/g/net/ghttp/ghttp_request_request.go b/g/net/ghttp/ghttp_request_request.go
index f51cd4b79..a836903a3 100644
--- a/g/net/ghttp/ghttp_request_request.go
+++ b/g/net/ghttp/ghttp_request_request.go
@@ -133,10 +133,10 @@ func (r *Request) GetRequestMap(def...map[string]string) map[string]string {
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
func (r *Request) GetRequestToStruct(pointer interface{}, mapping...map[string]string) error {
- tagmap := r.getStructParamsTagMap(pointer)
+ tagMap := r.getStructParamsTagMap(pointer)
if len(mapping) > 0 {
for k, v := range mapping[0] {
- tagmap[k] = v
+ tagMap[k] = v
}
}
params := make(map[string]interface{})
@@ -148,6 +148,6 @@ func (r *Request) GetRequestToStruct(pointer interface{}, mapping...map[string]s
params = j.ToMap()
}
}
- return gconv.Struct(params, pointer, tagmap)
+ return gconv.Struct(params, pointer, tagMap)
}
diff --git a/g/net/ghttp/ghttp_response.go b/g/net/ghttp/ghttp_response.go
index b756df942..2956b5769 100644
--- a/g/net/ghttp/ghttp_response.go
+++ b/g/net/ghttp/ghttp_response.go
@@ -134,7 +134,9 @@ func (r *Response) WriteStatus(status int, content...string) {
// 状态码注册回调函数处理
if status != http.StatusOK {
if f := r.request.Server.getStatusHandler(status, r.request); f != nil {
- f(r.request)
+ r.Server.niceCallFunc(func() {
+ f(r.request)
+ })
// 防止多次设置(http: multiple response.WriteHeader calls)
if r.Status == 0 {
r.WriteHeader(status)
diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go
index 5619d2e7c..e6ae04ed0 100644
--- a/g/net/ghttp/ghttp_server_config.go
+++ b/g/net/ghttp/ghttp_server_config.go
@@ -46,6 +46,7 @@ type ServerConfig struct {
IdleTimeout time.Duration // 等待超时
MaxHeaderBytes int // 最大的header长度
TLSConfig tls.Config
+ KeepAlive bool
// 静态文件配置
IndexFiles []string // 默认访问的文件列表
@@ -76,7 +77,7 @@ type ServerConfig struct {
// 日志配置
LogPath string // 存放日志的目录路径(默认为空,表示不写文件)
LogHandler LogHandler // 自定义日志处理回调方法(默认为空)
- LogStdPrint bool // 是否打印日志到终端(默认开启)
+ LogStdout bool // 是否打印日志到终端(默认开启)
ErrorLogEnabled bool // 是否开启error log(默认开启)
AccessLogEnabled bool // 是否开启access log(默认关闭)
@@ -96,6 +97,7 @@ var defaultServerConfig = ServerConfig {
WriteTimeout : 60 * time.Second,
IdleTimeout : 60 * time.Second,
MaxHeaderBytes : 1024,
+ KeepAlive : true,
IndexFiles : []string{"index.html", "index.htm"},
IndexFolder : false,
@@ -111,7 +113,7 @@ var defaultServerConfig = ServerConfig {
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
SessionIdName : gDEFAULT_SESSION_ID_NAME,
- LogStdPrint : true,
+ LogStdout : true,
ErrorLogEnabled : true,
AccessLogEnabled : false,
GzipContentTypes : defaultGzipContentTypes,
@@ -316,6 +318,15 @@ func (s *Server) SetRouterCacheExpire(expire int) {
s.config.RouterCacheExpire = expire
}
+// 设置KeepAlive
+func (s *Server) SetKeepAlive(enabled bool) {
+ if s.Status() == SERVER_STATUS_RUNNING {
+ glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
+ return
+ }
+ s.config.KeepAlive = enabled
+}
+
// 获取WebServer名称
func (s *Server) GetName() string {
return s.name
diff --git a/g/net/ghttp/ghttp_server_config_logger.go b/g/net/ghttp/ghttp_server_config_logger.go
index 5482de3fe..fce013d2d 100644
--- a/g/net/ghttp/ghttp_server_config_logger.go
+++ b/g/net/ghttp/ghttp_server_config_logger.go
@@ -28,12 +28,12 @@ func (s *Server)SetLogPath(path string) {
// 设置日志内容是否输出到终端,默认情况下只有错误日志才会自动输出到终端。
// 如果需要输出请求日志到终端,默认情况下使用SetAccessLogEnabled方法开启请求日志特性即可。
-func (s *Server)SetLogStdPrint(enabled bool) {
+func (s *Server)SetLogStdout(enabled bool) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
- s.config.LogStdPrint = enabled
+ s.config.LogStdout = enabled
}
// 设置是否开启access log日志功能
diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go
index 5b8fa01ba..7ea854cd3 100644
--- a/g/net/ghttp/ghttp_server_graceful.go
+++ b/g/net/ghttp/ghttp_server_graceful.go
@@ -21,9 +21,9 @@ import (
// 优雅的Web Server对象封装
type gracefulServer struct {
- fd uintptr
- addr string
- httpServer *http.Server
+ fd uintptr // 热重启时传递的socket监听文件句柄
+ addr string // 监听地址信息
+ httpServer *http.Server // 底层http.Server
rawListener net.Listener // 原始listener
listener net.Listener // 接口化封装的listener
isHttps bool // 是否HTTPS
@@ -45,7 +45,7 @@ func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer {
// 生成一个底层的Web Server对象
func (s *Server) newHttpServer(addr string) *http.Server {
- return &http.Server {
+ server := &http.Server {
Addr : addr,
Handler : s.config.Handler,
ReadTimeout : s.config.ReadTimeout,
@@ -53,6 +53,8 @@ func (s *Server) newHttpServer(addr string) *http.Server {
IdleTimeout : s.config.IdleTimeout,
MaxHeaderBytes : s.config.MaxHeaderBytes,
}
+ server.SetKeepAlivesEnabled(s.config.KeepAlive)
+ return server
}
// 执行HTTP监听
diff --git a/g/net/ghttp/ghttp_server_log.go b/g/net/ghttp/ghttp_server_log.go
index 3801abf4f..2f4b3158d 100644
--- a/g/net/ghttp/ghttp_server_log.go
+++ b/g/net/ghttp/ghttp_server_log.go
@@ -32,7 +32,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).StdPrint(s.config.LogStdPrint).Println(content)
+ s.logger.Cat("access").Backtrace(false, 2).Stdout(s.config.LogStdout).Println(content)
}
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
@@ -60,5 +60,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).StdPrint(s.config.LogStdPrint).Error(content)
+ s.logger.Cat("error").Backtrace(true, 2).Stdout(s.config.LogStdout).Error(content)
}
diff --git a/g/net/gscanner/scanner.go b/g/net/gscanner/scanner.go
deleted file mode 100644
index 84a42fd45..000000000
--- a/g/net/gscanner/scanner.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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 gscanner provides a port scanner for local intranet.
-package gscanner
-
-import (
- "net"
- "fmt"
- "sync"
- "time"
- "errors"
- "github.com/gogf/gf/g/net/gipv4"
-)
-
-type scanner struct {
- timeout time.Duration
-}
-
-// 初始化一个扫描器
-func New() *scanner {
- return &scanner{
- 6*time.Second,
- }
-}
-
-// 设置超时时间,注意这个时间是每一次扫描的超时时间,而不是总共的超时时间
-func (s *scanner) SetTimeout(t time.Duration) *scanner {
- s.timeout = t
- return s
-}
-
-// 异步TCP扫描网段及端口,如果扫描的端口是打开的,那么将链接给定给回调函数进行调用
-// 注意startIp和endIp需要是同一个网段,否则会报错,并且回调函数不会执行
-func (s *scanner) ScanIp(startIp string, endIp string, port int, callback func(net.Conn)) error {
- if callback == nil {
- return errors.New("callback function should not be nil")
- }
- var waitGroup sync.WaitGroup
- startIplong := gipv4.Ip2long(startIp)
- endIplong := gipv4.Ip2long(endIp)
- result := endIplong - startIplong
- if startIplong == 0 || endIplong == 0 {
- return errors.New("invalid startip or endip: ipv4 string should be given")
- }
- if result < 0 || result > 255 {
- return errors.New("invalid startip and endip: startip and endip should be in the same ip segment")
- }
-
- for i := startIplong; i <= endIplong; i++ {
- waitGroup.Add(1)
- go func(ip string) {
- //fmt.Println("scanning:", ip)
- // 这里必需设置超时时间
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), s.timeout)
- if err == nil {
- callback(conn)
- conn.Close()
- }
- //fmt.Println("scanning:", ip, "done")
- waitGroup.Done()
- }(gipv4.Long2ip(i))
- }
- waitGroup.Wait()
- return nil
-}
-
-// 扫描目标主机打开的端口列表
-func (s *scanner) ScanPort(ip string, callback func(net.Conn)) error {
- if callback == nil {
- return errors.New("callback function should not be nil")
- }
-
- var waitGroup sync.WaitGroup
- for i := 0; i <= 65536; i++ {
- waitGroup.Add(1)
- //fmt.Println("scanning:", i)
- go func(port int) {
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), s.timeout)
- if err == nil {
- callback(conn)
- conn.Close()
- }
- waitGroup.Done()
- }(i)
- }
- waitGroup.Wait()
- return nil
-}
-
diff --git a/g/net/gsmtp/smtp.go b/g/net/gsmtp/gsmtp.go
similarity index 61%
rename from g/net/gsmtp/smtp.go
rename to g/net/gsmtp/gsmtp.go
index 7bf53a9c4..c0c787480 100644
--- a/g/net/gsmtp/smtp.go
+++ b/g/net/gsmtp/gsmtp.go
@@ -5,6 +5,10 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gsmtp provides a SMTP client to access remote mail server.
+//
+// eg:
+// s := smtp.New("smtp.exmail.qq.com:25", "notify@a.com", "password")
+// glog.Println(s.SendMail("notify@a.com", "ulric@b.com;rain@c.com", "subject", "body, red"))
package gsmtp
import (
@@ -14,30 +18,31 @@ import (
"strings"
)
-// 示例:
-// s := smtp.New("smtp.exmail.qq.com:25", "notify@a.com", "password")
-// glog.Println(s.SendMail("notify@a.com", "ulric@b.com;rain@c.com", "这是subject", "这是body,red"))
-
-type Smtp struct {
+type SMTP struct {
Address string
Username string
Password string
}
-func New(address, username, password string) *Smtp {
- return &Smtp{
+// New creates and returns a new SMTP object.
+func New(address, username, password string) *SMTP {
+ return &SMTP{
Address: address,
Username: username,
Password: password,
}
}
-func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...string) error {
- if this.Address == "" {
+// SendMail connects to the server at addr, switches to TLS if
+// possible, authenticates with the optional mechanism a if possible,
+// and then sends an email from address from, to addresses to, with
+// message msg.
+func (s *SMTP) SendMail(from, tos, subject, body string, contentType ...string) error {
+ if s.Address == "" {
return fmt.Errorf("address is necessary")
}
- hp := strings.Split(this.Address, ":")
+ hp := strings.Split(s.Address, ":")
if len(hp) != 2 {
return fmt.Errorf("address format error")
}
@@ -56,14 +61,13 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
return fmt.Errorf("tos invalid")
}
- tos = strings.Join(safeArr, ";")
-
+ tos = strings.Join(safeArr, ";")
b64 := base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
- header := make(map[string]string)
- header["From"] = from
- header["To"] = tos
- header["Subject"] = fmt.Sprintf("=?UTF-8?B?%s?=", b64.EncodeToString([]byte(subject)))
+ header := make(map[string]string)
+ header["From"] = from
+ header["To"] = tos
+ header["Subject"] = fmt.Sprintf("=?UTF-8?B?%s?=", b64.EncodeToString([]byte(subject)))
header["MIME-Version"] = "1.0"
ct := "text/plain; charset=UTF-8"
@@ -71,7 +75,7 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
ct = "text/html; charset=UTF-8"
}
- header["Content-Type"] = ct
+ header["Content-Type"] = ct
header["Content-Transfer-Encoding"] = "base64"
message := ""
@@ -80,6 +84,6 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
}
message += "\r\n" + b64.EncodeToString([]byte(body))
- auth := smtp.PlainAuth("", this.Username, this.Password, hp[0])
- return smtp.SendMail(this.Address, auth, from, strings.Split(tos, ";"), []byte(message))
+ auth := smtp.PlainAuth("", s.Username, s.Password, hp[0])
+ return smtp.SendMail(s.Address, auth, from, strings.Split(tos, ";"), []byte(message))
}
\ No newline at end of file
diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go
index 47bbdb680..a5111de0c 100644
--- a/g/os/gfile/gfile.go
+++ b/g/os/gfile/gfile.go
@@ -27,23 +27,20 @@ import (
)
const (
- // 文件分隔符
+ // Separator for file system.
Separator = string(filepath.Separator)
- // 默认的文件打开权限
+ // Default perm for file opening.
gDEFAULT_PERM = 0666
)
var (
- // 源码的main包所在目录,仅仅会设置一次
- mainPkgPath = gtype.NewString()
-
- // 编译时的 GOROOT 数值
- goRootOfBuild = gtype.NewString()
+ // The absolute file path for main package.
+ // It can be only checked and set once.
+ mainPkgPath = gtype.NewString()
)
-// Create directories recursively.
-//
-// 给定目录的绝对路径创建目录(递归创建)。
+// Mkdir creates directories recursively with given .
+// The parameter is suggested to be absolute path.
func Mkdir(path string) error {
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
@@ -52,9 +49,8 @@ func Mkdir(path string) error {
return nil
}
-// Create file with given path recursively.
-//
-// 给定文件的绝对路径创建文件。
+// Create creates file with given recursively.
+// The parameter is suggested to be absolute path.
func Create(path string) (*os.File, error) {
dir := Dir(path)
if !Exists(dir) {
@@ -63,23 +59,17 @@ func Create(path string) (*os.File, error) {
return os.Create(path)
}
-// Open file/directory with readonly.
-//
-// 只读打开文件
+// Open opens file/directory readonly.
func Open(path string) (*os.File, error) {
return os.Open(path)
}
-// Open file/directory with given and .
-//
-// 打开文件(带flag&perm)
+// OpenFile opens file/directory with given and .
func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
return os.OpenFile(path, flag, perm)
}
-// Open file/directory with default perm and given .
-//
-// 打开文件(带flag)
+// OpenWithFlag opens file/directory with default perm and given .
func OpenWithFlag(path string, flag int) (*os.File, error) {
f, err := os.OpenFile(path, flag, gDEFAULT_PERM)
if err != nil {
@@ -88,9 +78,7 @@ func OpenWithFlag(path string, flag int) (*os.File, error) {
return f, nil
}
-// Open file/directory with given and .
-//
-// 打开文件(带flag&perm)
+// OpenWithFlagPerm opens file/directory with given and .
func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) {
f, err := os.OpenFile(path, flag, os.FileMode(perm))
if err != nil {
@@ -99,9 +87,7 @@ func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) {
return f, nil
}
-// Check whether given path exist.
-//
-// 判断所给路径文件/文件夹是否存在
+// Exists checks whether given exist.
func Exists(path string) bool {
if _, err := os.Stat(path); !os.IsNotExist(err) {
return true
@@ -109,9 +95,7 @@ func Exists(path string) bool {
return false
}
-// Check whether given path a directory.
-//
-// 判断所给路径是否为文件夹
+// IsDir checks whether given a directory.
func IsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
@@ -120,17 +104,13 @@ func IsDir(path string) bool {
return s.IsDir()
}
-// Get current working directory absolute path.
-//
-// 获取当前工作目录(注意与SelfDir的区别).
+// Pwd returns absolute path of current working directory.
func Pwd() string {
path, _ := os.Getwd()
return path
}
-// Check whether given path a file(not a directory).
-//
-// 判断所给路径是否为文件
+// IsFile checks whether given a file, which means it's not a directory.
func IsFile(path string) bool {
s, err := os.Stat(path)
if err != nil {
@@ -139,39 +119,32 @@ func IsFile(path string) bool {
return !s.IsDir()
}
+// Alias of Stat.
// See Stat.
-//
-// Stat 方法的别名。
func Info(path string) (os.FileInfo, error) {
return Stat(path)
}
// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
-//
-// 获取文件或目录信息.
func Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
-// Move renames (moves) src to dst path.
-//
-// 文件移动/重命名
+// Move renames (moves) to path.
func Move(src string, dst string) error {
return os.Rename(src, dst)
}
-// Rename renames (moves) src to dst path.
-//
-// 文件移动/重命名.
+// Alias of Move.
+// See Move.
func Rename(src string, dst string) error {
return Move(src, dst)
}
-// Copy file from src to dst.
+// Copy file from to .
//
-// 文件复制.
-// @TODO 支持目录复制.
+// @TODO directory copy support.
func Copy(src string, dst string) error {
srcFile, err := Open(src)
if err != nil {
@@ -194,9 +167,7 @@ func Copy(src string, dst string) error {
return nil
}
-// Get sub-file names of path.
-//
-// 返回目录下的文件名列表
+// DirNames returns sub-file names of given directory .
func DirNames(path string) ([]string, error) {
f, err := os.Open(path)
if err != nil {
@@ -218,8 +189,6 @@ func DirNames(path string) ([]string, error) {
// Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
-//
-// 文件名正则匹配查找,第二个可选参数指定返回的列表是否仅为文件名(非绝对路径),默认返回绝对路径
func Glob(pattern string, onlyNames...bool) ([]string, error) {
if list, err := filepath.Glob(pattern); err == nil {
if len(onlyNames) > 0 && onlyNames[0] && len(list) > 0 {
@@ -235,16 +204,13 @@ func Glob(pattern string, onlyNames...bool) ([]string, error) {
}
}
-// Remove file/directory with parameter.
-//
-// 文件/目录删除
+// Remove deletes all file/directory with parameter.
+// If parameter is directory, it deletes it recursively.
func Remove(path string) error {
return os.RemoveAll(path)
}
-// Check whether given is readable.
-//
-// 文件是否可读(支持文件/目录)
+// IsReadable checks whether given is readable.
func IsReadable(path string) bool {
result := true
file, err := os.OpenFile(path, os.O_RDONLY, gDEFAULT_PERM)
@@ -255,14 +221,13 @@ func IsReadable(path string) bool {
return result
}
-// Check whether given is writable.
+// IsWritable checks whether given is writable.
//
-// 文件是否可写(支持文件/目录)
-// @TODO 改进性能,利用 golang.org/x/sys 来实现跨平台的权限判断。
+// @TODO improve performance; use golang.org/x/sys to cross-plat-form
func IsWritable(path string) bool {
result := true
if IsDir(path) {
- // 如果是目录,那么创建一个临时文件进行写入测试
+ // If it's a directory, create a temporary file to test whether it's writable.
tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano())
if f, err := Create(tmpFile); err != nil || !Exists(tmpFile){
result = false
@@ -282,16 +247,12 @@ func IsWritable(path string) bool {
}
// See os.Chmod.
-//
-// 修改文件/目录权限
func Chmod(path string, mode os.FileMode) error {
return os.Chmod(path, mode)
}
-// Get all sub-files(absolute) of given ,
-// can be recursively with given parameter true.
-//
-// 打开目录,并返回其下一级文件列表(绝对路径),按照文件名称大小写进行排序,支持目录递归遍历。
+// ScanDir returns all sub-files with absolute paths of given ,
+// It scans directory recursively if given parameter is true.
func ScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
list, err := doScanDir(path, pattern, recursive...)
if err != nil {
@@ -303,22 +264,24 @@ func ScanDir(path string, pattern string, recursive ... bool) ([]string, error)
return list, nil
}
-// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。
-// pattern参数支持多个文件名称模式匹配,使用','符号分隔多个模式。
+// doScanDir is an internal method which scans directory
+// and returns the absolute path list of files that are not sorted.
+//
+// The pattern parameter supports multiple file name patterns,
+// using the ',' symbol to separate multiple patterns.
+//
+// It scans directory recursively if given parameter is true.
func doScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
list := ([]string)(nil)
- // 打开目录
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
- // 读取目录下的文件列表
names, err := file.Readdirnames(-1)
if err != nil {
return nil, err
}
- // 是否递归遍历
for _, name := range names {
path := fmt.Sprintf("%s%s%s", path, Separator, name)
if IsDir(path) && len(recursive) > 0 && recursive[0] {
@@ -327,7 +290,7 @@ func doScanDir(path string, pattern string, recursive ... bool) ([]string, error
list = append(list, array...)
}
}
- // 满足pattern才加入结果列表
+ // If it meets pattern, then add it to the result list.
for _, p := range strings.Split(pattern, ",") {
if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
list = append(list, path)
@@ -337,10 +300,9 @@ func doScanDir(path string, pattern string, recursive ... bool) ([]string, error
return list, nil
}
-// See filepath.Abs.
-//
-// 将所给定的路径转换为绝对路径
-// 并判断文件路径是否存在,如果文件不存在,那么返回空字符串
+// RealPath converts the given to its absolute path
+// and checks if the file path exists.
+// If the file does not exist, return an empty string.
func RealPath(path string) string {
p, err := filepath.Abs(path)
if err != nil {
@@ -352,45 +314,46 @@ func RealPath(path string) string {
return p
}
-// Get absolute file path of current running process(binary).
-//
-// 获取当前执行文件的绝对路径
+// SelfPath returns absolute file path of current running process(binary).
func SelfPath() string {
p, _ := filepath.Abs(os.Args[0])
return p
}
-// Get absolute directory path of current running process(binary).
-//
-// 获取当前执行文件的目录绝对路径
+// SelfDir returns absolute directory path of current running process(binary).
func SelfDir() string {
return filepath.Dir(SelfPath())
}
-// See filepath.Base.
-//
-// 获取指定文件路径的文件名称
+// Basename returns the last element of path.
+// Trailing path separators are removed before extracting the last element.
+// If the path is empty, Base returns ".".
+// If the path consists entirely of separators, Base returns a single separator.
func Basename(path string) string {
return filepath.Base(path)
}
-// See filepath.Dir.
-//
-// 获取指定文件路径的目录地址绝对路径.
+// Dir returns all but the last element of path, typically the path's directory.
+// After dropping the final element, Dir calls Clean on the path and trailing
+// slashes are removed.
+// If the path is empty, Dir returns ".".
+// If the path consists entirely of separators, Dir returns a single separator.
+// The returned path does not end in a separator unless it is the root directory.
func Dir(path string) string {
return filepath.Dir(path)
}
-// See filepath.Ext.
+// Ext returns the file name extension used by path.
+// The extension is the suffix beginning at the final dot
+// in the final element of path; it is empty if there is
+// no dot.
//
-// 获取指定文件路径的文件扩展名(包含"."号)
+// Note: the result contains symbol '.'.
func Ext(path string) string {
return filepath.Ext(path)
}
-// Get absolute home directory path of current user.
-//
-// 获取用户主目录
+// Home returns absolute path of current user's home directory.
func Home() (string, error) {
u, err := user.Current()
if nil == err {
@@ -435,12 +398,15 @@ func homeWindows() (string, error) {
return home, nil
}
-// Get absolute file path of main file, which contains the entrance function main.
-// Available in develop environment.
+// MainPkgPath returns absolute file path of package main,
+// which contains the entrance function main.
//
-// 获取入口函数文件所在目录(main包文件目录),
-// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)**。
-// 注意:该方法被第一次调用时,如果是在异步的goroutine中,该方法可能无法获取到main包路径。
+// It's only available in develop environment.
+//
+// Note1: Only valid for source development environments,
+// IE only valid for systems that generate this executable.
+// Note2: When the method is called for the first time, if it is in an asynchronous goroutine,
+// the method may not get the main package path.
func MainPkgPath() string {
path := mainPkgPath.Val()
if path != "" {
@@ -451,14 +417,16 @@ func MainPkgPath() string {
}
for i := 1; i < 10000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
+ // is separated by '/'
if gstr.Contains(file, "/gf/g/") {
continue
}
if Ext(file) != ".go" {
continue
}
- path = file
- for path != "/" && gstr.Contains(path, "/") {
+ // separator of '/' will be converted to Separator.
+ path = Dir(file)
+ for path != "/" && gstr.Contains(path, Separator) {
files, _ := ScanDir(path, "*.go")
for _, v := range files {
if gregex.IsMatchString(`package\s+main`, GetContents(v)) {
@@ -473,14 +441,13 @@ func MainPkgPath() string {
break
}
}
- // 找不到,下次不用再检索了
+ // If it fails finding the path, then mark it as "-",
+ // which means it will never do this search again.
mainPkgPath.Set("-")
return ""
}
// See os.TempDir().
-//
-// 系统临时目录
func TempDir() string {
return os.TempDir()
}
diff --git a/g/os/gfile/gfile_contents.go b/g/os/gfile/gfile_contents.go
index 63f611686..e129e9850 100644
--- a/g/os/gfile/gfile_contents.go
+++ b/g/os/gfile/gfile_contents.go
@@ -13,18 +13,18 @@ import (
)
const (
- // 方法中涉及到读取的时候的缓冲大小
- gREAD_BUFFER = 1024
- // 方法中涉及到文件指针池的默认缓存时间(毫秒)
- //gFILE_POOL_EXPIRE = 60000
+ // Buffer size for reading file content.
+ gREAD_BUFFER = 1024
)
-// (文本)读取文件内容
+// GetContents returns the file content of as string.
+// It returns en empty string if it fails reading.
func GetContents(path string) string {
return string(GetBinContents(path))
}
-// (二进制)读取文件内容,如果文件不存在或者读取失败,返回nil。
+// GetBinContents returns the file content of as []byte.
+// It returns nil if it fails reading.
func GetBinContents(path string) []byte {
data, err := ioutil.ReadFile(path)
if err != nil {
@@ -33,16 +33,16 @@ func GetBinContents(path string) []byte {
return data
}
-// 写入文件内容
+// putContents puts binary content to file of .
func putContents(path string, data []byte, flag int, perm int) error {
- // 支持目录递归创建
+ // It supports creating file of recursively.
dir := Dir(path)
if !Exists(dir) {
if err := Mkdir(dir); err != nil {
return err
}
}
- // 创建/打开文件
+ // Opening file with given and .
f, err := OpenWithFlagPerm(path, flag, perm)
if err != nil {
return err
@@ -56,32 +56,36 @@ func putContents(path string, data []byte, flag int, perm int) error {
return nil
}
-// Truncate
+// Truncate truncates file of to given size by .
func Truncate(path string, size int) error {
return os.Truncate(path, int64(size))
}
-// (文本)写入文件内容
+// PutContents puts string to file of .
+// It creates file of recursively if it does not exist.
func PutContents(path string, content string) error {
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, gDEFAULT_PERM)
}
-// (文本)追加内容到文件末尾
+// PutContentsAppend appends string to file of .
+// It creates file of recursively if it does not exist.
func PutContentsAppend(path string, content string) error {
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_APPEND, gDEFAULT_PERM)
}
-// (二进制)写入文件内容
+// PutBinContents puts binary to file of .
+// It creates file of recursively if it does not exist.
func PutBinContents(path string, content []byte) error {
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, gDEFAULT_PERM)
}
-// (二进制)追加内容到文件末尾
+// PutBinContentsAppend appends binary to file of .
+// It creates file of recursively if it does not exist.
func PutBinContentsAppend(path string, content []byte) error {
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_APPEND, gDEFAULT_PERM)
}
-// 获得文件内容下一个指定字节的位置
+// GetNextCharOffset returns the file offset for given starting from .
func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
buffer := make([]byte, gREAD_BUFFER)
offset := start
@@ -100,7 +104,8 @@ func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
return -1
}
-// 获得文件内容下一个指定字节的位置
+// GetNextCharOffsetByPath returns the file offset for given starting from .
+// It opens file of for reading with os.O_RDONLY flag and default perm.
func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
defer f.Close()
@@ -109,7 +114,10 @@ func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
return -1
}
-// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
+// GetBinContentsTilChar returns the contents of the file as []byte
+// until the next specified byte position.
+//
+// Note: Returned value contains the character of the last position.
func GetBinContentsTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64) {
if offset := GetNextCharOffset(reader, char, start); offset != -1 {
return GetBinContentsByTwoOffsets(reader, start, offset + 1), offset
@@ -117,7 +125,11 @@ func GetBinContentsTilChar(reader io.ReaderAt, char byte, start int64) ([]byte,
return nil, -1
}
-// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
+// GetBinContentsTilCharByPath returns the contents of the file given by as []byte
+// until the next specified byte position.
+// It opens file of for reading with os.O_RDONLY flag and default perm.
+//
+// Note: Returned value contains the character of the last position.
func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, int64) {
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
defer f.Close()
@@ -126,7 +138,9 @@ func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, i
return nil, -1
}
-// 获得文件内容中两个offset之间的内容 [start, end)
+// GetBinContentsByTwoOffsets returns the binary content as []byte from to .
+// Note: Returned value does not contain the character of the last position, which means
+// it returns content range as [start, end).
func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte {
buffer := make([]byte, end - start)
if _, err := reader.ReadAt(buffer, start); err != nil {
@@ -135,7 +149,10 @@ func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []by
return buffer
}
-// 获得文件内容中两个offset之间的内容 [start, end)
+// GetBinContentsByTwoOffsetsByPath returns the binary content as []byte from to .
+// Note: Returned value does not contain the character of the last position, which means
+// it returns content range as [start, end).
+// It opens file of for reading with os.O_RDONLY flag and default perm.
func GetBinContentsByTwoOffsetsByPath(path string, start int64, end int64) []byte {
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
defer f.Close()
diff --git a/g/os/gfile/gfile_search.go b/g/os/gfile/gfile_search.go
index fbc6e10f0..acce3d0eb 100644
--- a/g/os/gfile/gfile_search.go
+++ b/g/os/gfile/gfile_search.go
@@ -13,31 +13,25 @@ import (
"github.com/gogf/gf/g/container/garray"
)
-
-// 如果给定绝对路径将会去掉其中的相对路径符号后返回;
-// 如果是给定的相对路径,那么将会按照以下路径优先级搜索文件(重复路径会去重):
-// prioritySearchPaths、当前工作目录、二进制文件目录、源码main包目录(开发环境下)
+// Search searches file by name in following paths with priority:
+// prioritySearchPaths, Pwd()、SelfDir()、MainPkgPath().
+// It returns the absolute file path of if found, or en empty string if not found.
func Search(name string, prioritySearchPaths...string) (realPath string, err error) {
- // 是否绝对路径
+ // Check if it's a absolute path.
realPath = RealPath(name)
if realPath != "" {
return
}
- // 相对路径搜索
+ // Search paths array.
array := garray.NewStringArray(true)
- // 自定义优先路径
array.Append(prioritySearchPaths...)
- // 用户工作目录
- array.Append(Pwd())
- // 二进制所在目录
- array.Append(SelfDir())
- // 源码main包目录
+ array.Append(Pwd(), SelfDir())
if path := MainPkgPath(); path != "" {
array.Append(path)
}
- // 路径去重
+ // Remove repeated items.
array.Unique()
- // 执行相对路径搜索
+ // Do the searching.
array.RLockFunc(func(array []string) {
path := ""
for _, v := range array {
@@ -48,7 +42,7 @@ func Search(name string, prioritySearchPaths...string) (realPath string, err err
}
}
})
- // 目录不存在错误处理
+ // If it fails searching, it returns formatted error.
if realPath == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("cannot find file/folder \"%s\" in following paths:", name))
diff --git a/g/os/gfile/gfile_size.go b/g/os/gfile/gfile_size.go
index 463b443ba..df37205b2 100644
--- a/g/os/gfile/gfile_size.go
+++ b/g/os/gfile/gfile_size.go
@@ -11,7 +11,7 @@ import (
"os"
)
-// 文件大小(bytes)
+// Size returns the size of file specified by in byte.
func Size(path string) int64 {
s, e := os.Stat(path)
if e != nil {
@@ -20,12 +20,12 @@ func Size(path string) int64 {
return s.Size()
}
-// 格式化文件大小
+// ReadableSize formats size of file given by , for more human readable.
func ReadableSize(path string) string {
return FormatSize(float64(Size(path)))
}
-// 格式化文件大小
+// FormatSize formats size for more human readable.
func FormatSize(raw float64) string {
var t float64 = 1024
var d float64 = 1
diff --git a/g/os/gfile/gfile_time.go b/g/os/gfile/gfile_time.go
index 9391c1827..a2e138fc6 100644
--- a/g/os/gfile/gfile_time.go
+++ b/g/os/gfile/gfile_time.go
@@ -10,7 +10,7 @@ import (
"os"
)
-// 文件修改时间(时间戳,秒)
+// MTime returns the modification time of file given by in second.
func MTime(path string) int64 {
s, e := os.Stat(path)
if e != nil {
@@ -19,7 +19,7 @@ func MTime(path string) int64 {
return s.ModTime().Unix()
}
-// 文件修改时间(时间戳,毫秒)
+// MTimeMillisecond returns the modification time of file given by in millisecond.
func MTimeMillisecond(path string) int64 {
s, e := os.Stat(path)
if e != nil {
diff --git a/g/os/gfile/gfile_contents_test.go b/g/os/gfile/gfile_z_contents_test.go
similarity index 100%
rename from g/os/gfile/gfile_contents_test.go
rename to g/os/gfile/gfile_z_contents_test.go
diff --git a/g/os/gfile/gfile_search_test.go b/g/os/gfile/gfile_z_search_test.go
similarity index 100%
rename from g/os/gfile/gfile_search_test.go
rename to g/os/gfile/gfile_z_search_test.go
diff --git a/g/os/gfile/gfile_size_test.go b/g/os/gfile/gfile_z_size_test.go
similarity index 100%
rename from g/os/gfile/gfile_size_test.go
rename to g/os/gfile/gfile_z_size_test.go
diff --git a/g/os/gfile/gfile_test.go b/g/os/gfile/gfile_z_test.go
similarity index 100%
rename from g/os/gfile/gfile_test.go
rename to g/os/gfile/gfile_z_test.go
diff --git a/g/os/gfile/gfile_time_test.go b/g/os/gfile/gfile_z_time_test.go
similarity index 100%
rename from g/os/gfile/gfile_time_test.go
rename to g/os/gfile/gfile_z_time_test.go
diff --git a/g/os/gfpool/gfpool.go b/g/os/gfpool/gfpool.go
index 034c43e42..e904a79e2 100644
--- a/g/os/gfpool/gfpool.go
+++ b/g/os/gfpool/gfpool.go
@@ -1,12 +1,10 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright 2017-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 gfpool provides io-reusable pool for file pointer.
-//
-// 文件指针池.
package gfpool
import (
@@ -19,11 +17,11 @@ import (
"sync"
)
-// 文件指针池
+// File pointer pool.
type Pool struct {
- id *gtype.Int // 指针池ID,用以识别指针池是否重建
+ id *gtype.Int // 指针池ID,用以识别指针池是否需要重建
pool *gpool.Pool // 底层对象池
- inited *gtype.Bool // 是否初始化(在执行第一次File方法后初始化,主要用于监听的添加,但是只能添加一次)
+ inited *gtype.Bool // 是否初始化(在执行第一次执行File方法后初始化,主要用于文件监听的添加,但是只能添加一次)
expire int // 过期时间
}
@@ -38,8 +36,11 @@ type File struct {
path string // 绝对路径
}
-// 全局指针池,expire < 0表示不过期,expire = 0表示使用完立即回收,expire > 0表示超时回收
-var pools = gmap.NewStrAnyMap()
+
+var (
+ // 全局文件指针池Map, 不过期
+ pools = gmap.NewStrAnyMap()
+)
// 获得文件对象,并自动创建指针池(过期时间单位:毫秒)
func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
@@ -54,12 +55,14 @@ func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, er
return pool.File()
}
+// Deprecated.
+// See Open.
func OpenFile(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
return Open(path, flag, perm, expire...)
}
-// 创建一个文件指针池,expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收,默认值为0不过期
-// 过期时间单位:毫秒
+// 创建一个文件指针池,expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收,默认值为0表示不过期。
+// 注意过期时间单位为:毫秒。
func New(path string, flag int, perm os.FileMode, expire...int) *Pool {
fpExpire := 0
if len(expire) > 0 {
@@ -130,7 +133,7 @@ func (p *Pool) File() (*File, error) {
return nil, err
}
}
- // !p.inited.Val() 使用原子读取操作判断,保证该操作判断的效率;
+ // 优先使用 !p.inited.Val() 原子读取操作判断,保证判断操作的效率;
// p.inited.Set(true) == false 使用原子写入操作,保证该操作的原子性;
if !p.inited.Val() && p.inited.Set(true) == false {
gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
diff --git a/g/os/glog/glog.go b/g/os/glog/glog.go
index bdd08f641..ad59cad70 100644
--- a/g/os/glog/glog.go
+++ b/g/os/glog/glog.go
@@ -10,9 +10,9 @@
package glog
import (
- "github.com/gogf/gf/g/container/gtype"
- "github.com/gogf/gf/g/internal/cmdenv"
- "io"
+ "github.com/gogf/gf/g/internal/cmdenv"
+ "github.com/gogf/gf/g/os/grpool"
+ "io"
)
const (
@@ -28,11 +28,10 @@ const (
)
var (
- // Default level for log
- defaultLevel = gtype.NewInt(LEVEL_ALL)
-
// Default logger object, for package method usage
logger = New()
+ // Goroutine pool for async logging output.
+ asyncPool = grpool.New(1)
)
func init() {
@@ -44,6 +43,12 @@ func SetPath(path string) {
logger.SetPath(path)
}
+// GetPath returns the logging directory path for file logging.
+// It returns empty string if no directory path set.
+func GetPath() string {
+ return logger.GetPath()
+}
+
// SetFile sets the file name for file logging.
// Datetime pattern can be used in , eg: access-{Ymd}.log.
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
@@ -54,7 +59,11 @@ func SetFile(pattern string) {
// SetLevel sets the default logging level.
func SetLevel(level int) {
logger.SetLevel(level)
- defaultLevel.Set(level)
+}
+
+// GetLevel returns the default logging level value.
+func GetLevel() int {
+ return logger.GetLevel()
}
// SetWriter sets the customized logging for logging.
@@ -71,26 +80,41 @@ func GetWriter() io.Writer {
return logger.GetWriter()
}
-// GetLevel returns the default logging level value.
-func GetLevel() int {
- return defaultLevel.Val()
-}
-
// SetDebug enables/disables the debug level for default logger.
// The debug level is enbaled in default.
func SetDebug(debug bool) {
logger.SetDebug(debug)
}
-// SetStdPrint sets whether ouptput the logging contents to stdout, which is false in default.
-func SetStdPrint(open bool) {
- logger.SetStdPrint(open)
+// SetAsync enables/disables async logging output feature for default logger.
+func SetAsync(enabled bool) {
+ logger.SetAsync(enabled)
}
-// GetPath returns the logging directory path for file logging.
-// It returns empty string if no directory path set.
-func GetPath() string {
- return logger.GetPath()
+// SetStdoutPrint sets whether ouptput the logging contents to stdout, which is false in default.
+func SetStdoutPrint(enabled bool) {
+ logger.SetStdoutPrint(enabled)
+}
+
+// SetHeaderPrint sets whether output header of the logging contents, which is true in default.
+func SetHeaderPrint(enabled bool) {
+ logger.SetHeaderPrint(enabled)
+}
+
+// SetPrefix sets prefix string for every logging content.
+// Prefix is part of header, which means if header output is shut, no prefix will be output.
+func SetPrefix(prefix string) {
+ logger.SetPrefix(prefix)
+}
+
+// SetFlags sets extra flags for logging output features.
+func SetFlags(flags int) {
+ logger.SetFlags(flags)
+}
+
+// GetFlags returns the flags of logger.
+func GetFlags() int {
+ return logger.GetFlags()
}
// PrintBacktrace prints the caller backtrace,
@@ -109,166 +133,3 @@ func GetBacktrace(skip...int) string {
func SetBacktrace(enabled bool) {
logger.SetBacktrace(enabled)
}
-
-// To is a chaining function,
-// which redirects current logging content output to the sepecified .
-func To(writer io.Writer) *Logger {
- return logger.To(writer)
-}
-
-// Path is a chaining function,
-// which sets the directory path to for current logging content output.
-func Path(path string) *Logger {
- return logger.Path(path)
-}
-
-// Cat is a chaining function,
-// which sets the category to for current logging content output.
-func Cat(category string) *Logger {
- return logger.Cat(category)
-}
-
-// File is a chaining function,
-// which sets file name for the current logging content output.
-func File(pattern string) *Logger {
- return logger.File(pattern)
-}
-
-// Level is a chaining function,
-// which sets logging level for the current logging content output.
-func Level(level int) *Logger {
- return logger.Level(level)
-}
-
-// 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...)
-}
-
-// StdPrint is a chaining function,
-// which enables/disables stdout for the current logging content output.
-func StdPrint(enabled bool) *Logger {
- return logger.StdPrint(enabled)
-}
-
-// Header is a chaining function,
-// which enables/disables log header for the current logging content output.
-func Header(enabled bool) *Logger {
- return logger.Header(enabled)
-}
-
-func Print(v ...interface{}) {
- logger.Print(v ...)
-}
-
-func Printf(format string, v ...interface{}) {
- logger.Printf(format, v ...)
-}
-
-func Println(v ...interface{}) {
- logger.Println(v ...)
-}
-
-func Printfln(format string, v ...interface{}) {
- logger.Printfln(format, v ...)
-}
-
-// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
-func Fatal(v ...interface{}) {
- logger.Fatal(v ...)
-}
-
-// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
-func Fatalf(format string, v ...interface{}) {
- logger.Fatalf(format, v ...)
-}
-
-// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
-func Fatalfln(format string, v ...interface{}) {
- logger.Fatalfln(format, v ...)
-}
-
-func Panic(v ...interface{}) {
- logger.Panic(v ...)
-}
-
-func Panicf(format string, v ...interface{}) {
- logger.Panicf(format, v ...)
-}
-
-func Panicfln(format string, v ...interface{}) {
- logger.Panicfln(format, v ...)
-}
-
-func Info(v ...interface{}) {
- logger.Info(v...)
-}
-
-func Debug(v ...interface{}) {
- logger.Debug(v...)
-}
-
-func Notice(v ...interface{}) {
- logger.Notice(v...)
-}
-
-func Warning(v ...interface{}) {
- logger.Warning(v...)
-}
-
-func Error(v ...interface{}) {
- logger.Error(v...)
-}
-
-func Critical(v ...interface{}) {
- logger.Critical(v...)
-}
-
-func Infof(format string, v ...interface{}) {
- logger.Infof(format, v...)
-}
-
-func Debugf(format string, v ...interface{}) {
- logger.Debugf(format, v...)
-}
-
-func Noticef(format string, v ...interface{}) {
- logger.Noticef(format, v...)
-}
-
-func Warningf(format string, v ...interface{}) {
- logger.Warningf(format, v...)
-}
-
-func Errorf(format string, v ...interface{}) {
- logger.Errorf(format, v...)
-}
-
-func Criticalf(format string, v ...interface{}) {
- logger.Criticalf(format, v...)
-}
-
-func Infofln(format string, v ...interface{}) {
- logger.Infofln(format, v...)
-}
-
-func Debugfln(format string, v ...interface{}) {
- logger.Debugfln(format, v...)
-}
-
-func Noticefln(format string, v ...interface{}) {
- logger.Noticefln(format, v...)
-}
-
-func Warningfln(format string, v ...interface{}) {
- logger.Warningfln(format, v...)
-}
-
-func Errorfln(format string, v ...interface{}) {
- logger.Errorfln(format, v...)
-}
-
-func Criticalfln(format string, v ...interface{}) {
- logger.Criticalfln(format, v...)
-}
diff --git a/g/os/glog/glog_api.go b/g/os/glog/glog_api.go
new file mode 100644
index 000000000..d7700bfe8
--- /dev/null
+++ b/g/os/glog/glog_api.go
@@ -0,0 +1,166 @@
+// 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 glog
+
+// Print prints with newline using fmt.Sprintln.
+// The param can be multiple variables.
+func Print(v ...interface{}) {
+ logger.Print(v ...)
+}
+
+// Printf prints with format using fmt.Sprintf.
+// The param can be multiple variables.
+func Printf(format string, v ...interface{}) {
+ logger.Printf(format, v ...)
+}
+
+// See Print.
+func Println(v ...interface{}) {
+ logger.Println(v ...)
+}
+
+// Deprecated.
+// Use Printf instead.
+func Printfln(format string, v ...interface{}) {
+ logger.Printfln(format, v ...)
+}
+
+// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
+func Fatal(v ...interface{}) {
+ logger.Fatal(v ...)
+}
+
+// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
+func Fatalf(format string, v ...interface{}) {
+ logger.Fatalf(format, v ...)
+}
+
+// Deprecated.
+// Use Fatalf instead.
+func Fatalfln(format string, v ...interface{}) {
+ logger.Fatalfln(format, v ...)
+}
+
+// Panic prints the logging content with [PANI] header and newline, then panics.
+func Panic(v ...interface{}) {
+ logger.Panic(v ...)
+}
+
+// Panicf prints the logging content with [PANI] header, custom format and newline, then panics.
+func Panicf(format string, v ...interface{}) {
+ logger.Panicf(format, v ...)
+}
+
+// Deprecated.
+// Use Panicf instead.
+func Panicfln(format string, v ...interface{}) {
+ logger.Panicfln(format, v ...)
+}
+
+// Info prints the logging content with [INFO] header and newline.
+func Info(v ...interface{}) {
+ logger.Info(v...)
+}
+
+// Infof prints the logging content with [INFO] header, custom format and newline.
+func Infof(format string, v ...interface{}) {
+ logger.Infof(format, v...)
+}
+
+// Deprecated.
+// Use Infof instead.
+func Infofln(format string, v ...interface{}) {
+ logger.Infofln(format, v...)
+}
+
+// Debug prints the logging content with [DEBU] header and newline.
+func Debug(v ...interface{}) {
+ logger.Debug(v...)
+}
+
+// Debugf prints the logging content with [DEBU] header, custom format and newline.
+func Debugf(format string, v ...interface{}) {
+ logger.Debugf(format, v...)
+}
+
+// Deprecated.
+// Use Debugf instead.
+func Debugfln(format string, v ...interface{}) {
+ logger.Debugfln(format, v...)
+}
+
+// Notice prints the logging content with [NOTI] header and newline.
+// It also prints caller backtrace info if backtrace 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.
+func Noticef(format string, v ...interface{}) {
+ logger.Noticef(format, v...)
+}
+
+// Deprecated.
+// Use Noticef instead.
+func Noticefln(format string, v ...interface{}) {
+ logger.Noticefln(format, v...)
+}
+
+// Warning prints the logging content with [WARN] header and newline.
+// It also prints caller backtrace info if backtrace 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.
+func Warningf(format string, v ...interface{}) {
+ logger.Warningf(format, v...)
+}
+
+// Deprecated.
+// Use Warningf instead.
+func Warningfln(format string, v ...interface{}) {
+ logger.Warningfln(format, v...)
+}
+
+// Error prints the logging content with [ERRO] header and newline.
+// It also prints caller backtrace info if backtrace 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.
+func Errorf(format string, v ...interface{}) {
+ logger.Errorf(format, v...)
+}
+
+// Deprecated.
+// Use Errorf instead.
+func Errorfln(format string, v ...interface{}) {
+ logger.Errorfln(format, v...)
+}
+
+// Critical prints the logging content with [CRIT] header and newline.
+// It also prints caller backtrace info if backtrace 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.
+func Criticalf(format string, v ...interface{}) {
+ logger.Criticalf(format, v...)
+}
+
+// Deprecated.
+// Use Criticalf instead.
+func Criticalfln(format string, v ...interface{}) {
+ logger.Criticalfln(format, v...)
+}
diff --git a/g/os/glog/glog_chaining.go b/g/os/glog/glog_chaining.go
new file mode 100644
index 000000000..471c96701
--- /dev/null
+++ b/g/os/glog/glog_chaining.go
@@ -0,0 +1,81 @@
+// 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 glog
+
+import (
+ "io"
+)
+
+// To is a chaining function,
+// which redirects current logging content output to the sepecified .
+func To(writer io.Writer) *Logger {
+ return logger.To(writer)
+}
+
+// Path is a chaining function,
+// which sets the directory path to for current logging content output.
+func Path(path string) *Logger {
+ return logger.Path(path)
+}
+
+// Cat is a chaining function,
+// which sets the category to for current logging content output.
+func Cat(category string) *Logger {
+ return logger.Cat(category)
+}
+
+// File is a chaining function,
+// which sets file name for the current logging content output.
+func File(pattern string) *Logger {
+ return logger.File(pattern)
+}
+
+// Level is a chaining function,
+// which sets logging level for the current logging content output.
+func Level(level int) *Logger {
+ return logger.Level(level)
+}
+
+// Skip is a chaining function,
+// which sets backtrace 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...)
+}
+
+// StdPrint is a chaining function,
+// which enables/disables stdout for the current logging content output.
+// It's enabled in default.
+func Stdout(enabled...bool) *Logger {
+ return logger.Stdout(enabled...)
+}
+
+// Header is a chaining function,
+// which enables/disables log header for the current logging content output.
+// It's enabled in default.
+func Header(enabled...bool) *Logger {
+ return logger.Header(enabled...)
+}
+
+// Line is a chaining function,
+// which enables/disables printing its caller file along with its line number.
+// The param specified whether print the long absolute file path, eg: /a/b/c/d.go:23.
+func Line(long...bool) *Logger {
+ return logger.Line(long...)
+}
+
+// Async is a chaining function,
+// which enables/disables async logging output feature.
+func Async(enabled...bool) *Logger {
+ return logger.Async(enabled...)
+}
diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go
index c75333071..7b9329c5f 100644
--- a/g/os/glog/glog_logger.go
+++ b/g/os/glog/glog_logger.go
@@ -3,38 +3,37 @@
// 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, zseeker
package glog
import (
+ "bytes"
"errors"
"fmt"
- "github.com/gogf/gf/g/container/gtype"
"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"
"runtime"
"strings"
- "sync"
"time"
)
type Logger struct {
- mu sync.RWMutex
- pr *Logger // Parent logger.
- writer io.Writer // Customized io.Writer.
- path *gtype.String // Logging directory path.
- file *gtype.String // Format for logging file.
- level *gtype.Int // Output level.
- btSkip *gtype.Int // Skip count for backtrace.
- btStatus *gtype.Int // Backtrace status(1: enabled - default; 0: disabled)
- printHeader *gtype.Bool // Print header or not(true in default).
- alsoStdPrint *gtype.Bool // Output to stdout or not(true in default).
+ parent *Logger // Parent logger.
+ writer io.Writer // Customized io.Writer.
+ flags int // Extra flags for logging output features.
+ path string // Logging directory path.
+ 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)
+ headerPrint bool // Print header or not(true in default).
+ stdoutPrint bool // Output to stdout or not(true in default).
}
const (
@@ -44,11 +43,19 @@ const (
gDEFAULT_FPOOL_EXPIRE = 60000
)
+const (
+ F_ASYNC = 1 << iota // Print logging content asynchronously。
+ F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
+ F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
+ F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
+ F_TIME_TIME // Print the time in the local time zone: 01:23:23.
+ F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
+ F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
+)
+
var (
// Default line break.
- ln = "\n"
- // Mutex to ensure log output sequence.
- stdMu = sync.RWMutex{}
+ ln = "\n"
)
func init() {
@@ -61,70 +68,75 @@ func init() {
// New creates and returns a custom logger.
func New() *Logger {
logger := &Logger {
- path : gtype.NewString(),
- file : gtype.NewString(gDEFAULT_FILE_FORMAT),
- level : gtype.NewInt(defaultLevel.Val()),
- btSkip : gtype.NewInt(),
- btStatus : gtype.NewInt(1),
- printHeader : gtype.NewBool(true),
- alsoStdPrint : gtype.NewBool(true),
- }
- logger.writer = &Writer {
- logger : logger,
+ file : gDEFAULT_FILE_FORMAT,
+ flags : F_TIME_STD,
+ level : LEVEL_ALL,
+ btStatus : 1,
+ headerPrint : true,
+ stdoutPrint : true,
}
return logger
}
// Clone returns a new logger, which is the clone the current logger.
func (l *Logger) Clone() *Logger {
- logger := &Logger {
- pr : l,
- path : l.path.Clone(),
- file : l.file.Clone(),
- level : l.level.Clone(),
- btSkip : l.btSkip.Clone(),
- btStatus : l.btStatus.Clone(),
- printHeader : l.printHeader.Clone(),
- alsoStdPrint : l.alsoStdPrint.Clone(),
- }
- logger.writer = &Writer {
- logger : logger,
- }
- return logger
+ logger := Logger{}
+ logger = *l
+ logger.parent = l
+ return &logger
}
// SetLevel sets the logging level.
func (l *Logger) SetLevel(level int) {
- l.level.Set(level)
+ l.level = level
}
// GetLevel returns the logging level value.
func (l *Logger) GetLevel() int {
- return l.level.Val()
+ return l.level
}
// SetDebug enables/disables the debug level for logger.
// The debug level is enabled in default.
func (l *Logger) SetDebug(debug bool) {
if debug {
- l.level.Set(l.level.Val() | LEVEL_DEBU)
+ l.level = l.level | LEVEL_DEBU
} else {
- l.level.Set(l.level.Val() & ^LEVEL_DEBU)
+ l.level = l.level & ^LEVEL_DEBU
}
}
+// SetAsync enables/disables async logging output feature.
+func (l *Logger) SetAsync(enabled bool) {
+ if enabled {
+ l.flags = l.flags | F_ASYNC
+ } else {
+ l.flags = l.flags & ^F_ASYNC
+ }
+}
+
+// SetFlags sets extra flags for logging output features.
+func (l *Logger) SetFlags(flags int) {
+ l.flags = flags
+}
+
+// GetFlags returns the flags of logger.
+func (l *Logger) GetFlags() int {
+ return l.flags
+}
+
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
func (l *Logger) SetBacktrace(enabled bool) {
if enabled {
- l.btStatus.Set(1)
+ l.btStatus = 1
} else {
- l.btStatus.Set(0)
+ l.btStatus = 0
}
}
// SetBacktraceSkip sets the backtrace offset from the end point.
func (l *Logger) SetBacktraceSkip(skip int) {
- l.btSkip.Set(skip)
+ l.btSkip = skip
}
// SetWriter sets the customized logging for logging.
@@ -132,26 +144,21 @@ func (l *Logger) SetBacktraceSkip(skip int) {
// Developer can use customized logging to redirect logging output to another service,
// eg: kafka, mysql, mongodb, etc.
func (l *Logger) SetWriter(writer io.Writer) {
- l.mu.Lock()
l.writer = writer
- l.mu.Unlock()
}
// GetWriter returns the customized writer object, which implements the io.Writer interface.
-// It returns a default writer if no customized writer set.
+// It returns nil if no writer previously set.
func (l *Logger) GetWriter() io.Writer {
- l.mu.RLock()
- r := l.writer
- l.mu.RUnlock()
- return r
+ return l.writer
}
// getFilePointer returns the file pinter for file logging.
// It returns nil if file logging is disabled, or file opening fails.
func (l *Logger) getFilePointer() *gfpool.File {
- if path := l.path.Val(); path != "" {
+ if path := l.path; path != "" {
// Content containing "{}" in the file name is formatted using gtime
- file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file.Val(), func(s string) string {
+ file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file, func(s string) string {
return gtime.Now().Format(strings.Trim(s, "{}"))
})
// Create path if it does not exist。
@@ -161,8 +168,11 @@ func (l *Logger) getFilePointer() *gfpool.File {
return nil
}
}
- fpath := path + gfile.Separator + file
- if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil {
+ if fp, err := gfpool.Open(
+ path + gfile.Separator + file,
+ gDEFAULT_FILE_POOL_FLAGS,
+ gDEFAULT_FPOOL_PERM,
+ gDEFAULT_FPOOL_EXPIRE); err == nil {
return fp
} else {
fmt.Fprintln(os.Stderr, err)
@@ -182,97 +192,146 @@ func (l *Logger) SetPath(path string) error {
return err
}
}
- l.path.Set(strings.TrimRight(path, gfile.Separator))
+ l.path = strings.TrimRight(path, gfile.Separator)
return nil
}
// GetPath returns the logging directory path for file logging.
// It returns empty string if no directory path set.
func (l *Logger) GetPath() string {
- return l.path.Val()
+ return l.path
}
// SetFile sets the file name for file logging.
// Datetime pattern can be used in , eg: access-{Ymd}.log.
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
func (l *Logger) SetFile(pattern string) {
- l.file.Set(pattern)
+ l.file = pattern
}
-// SetStdPrint sets whether output the logging contents to stdout, which is false in default.
-func (l *Logger) SetStdPrint(enabled bool) {
- l.alsoStdPrint.Set(enabled)
+// SetStdoutPrint sets whether output the logging contents to stdout, which is true in default.
+func (l *Logger) SetStdoutPrint(enabled bool) {
+ l.stdoutPrint = enabled
+}
+
+// SetHeaderPrint sets whether output header of the logging contents, which is true in default.
+func (l *Logger) SetHeaderPrint(enabled bool) {
+ l.headerPrint = enabled
+}
+
+// SetPrefix sets prefix string for every logging content.
+// Prefix is part of header, which means if header output is shut, no prefix will be output.
+func (l *Logger) SetPrefix(prefix string) {
+ l.prefix = prefix
}
// print prints to defined writer, logging file or passed .
-// It internally uses memory lock for file logging to ensure logging sequence.
-func (l *Logger) print(std io.Writer, s string) {
- // Customized writer has the most high priority.
- if l.printHeader.Val() {
- s = l.format(s)
- }
- writer := l.GetWriter()
- if _, ok := writer.(*Writer); ok {
- if f := l.getFilePointer(); f != nil {
- defer f.Close()
- if _, err := io.WriteString(f, s); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- }
- }
- // Also output to stdout?
- if l.alsoStdPrint.Val() {
- l.doStdLockPrint(std, s)
- }
- } else {
- l.doStdLockPrint(writer, s)
+func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
+ buffer := bytes.NewBuffer(nil)
+ if l.headerPrint {
+ // Time.
+ timeFormat := ""
+ if l.flags & F_TIME_DATE > 0 {
+ timeFormat += "2006-01-02 "
+ }
+ if l.flags & F_TIME_TIME > 0 {
+ timeFormat += "15:04:05 "
+ }
+ if l.flags & F_TIME_MILLI > 0 {
+ timeFormat += "15:04:05.000 "
+ }
+ if len(timeFormat) > 0 {
+ buffer.WriteString(time.Now().Format(timeFormat))
+ }
+ // Caller path.
+ callerPath := ""
+ if l.flags & F_FILE_LONG > 0 {
+ callerPath = l.getLongFile() + ": "
+ }
+ if l.flags & F_FILE_SHORT > 0 {
+ callerPath = gfile.Basename(l.getLongFile()) + ": "
+ }
+ if len(callerPath) > 0 {
+ buffer.WriteString(callerPath)
+ }
+ // Prefix.
+ if len(l.prefix) > 0 {
+ buffer.WriteString(l.prefix + " ")
+ }
}
+ if len(lead) > 0 {
+ buffer.WriteString(lead)
+ if len(value) > 0 {
+ buffer.WriteByte(' ')
+ }
+ }
+ for k, v := range value {
+ if k > 0 {
+ buffer.WriteByte(' ')
+ }
+ buffer.WriteString(gconv.String(v))
+ }
+ buffer.WriteString(ln)
+ if l.flags & F_ASYNC > 0 {
+ asyncPool.Add(func() {
+ l.printToWriter(std, buffer)
+ })
+ } else {
+ l.printToWriter(std, buffer)
+ }
}
-// doStdLockPrint prints to concurrent-safely.
-func (l *Logger) doStdLockPrint(std io.Writer, s string) {
- stdMu.Lock()
- if _, err := std.Write([]byte(s)); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- }
- stdMu.Unlock()
+// printToWriter writes buffer to writer.
+func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) {
+ if l.writer == nil {
+ if f := l.getFilePointer(); f != nil {
+ defer f.Close()
+ if _, err := io.WriteString(f, buffer.String()); err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ }
+ }
+ // Allow output to stdout?
+ if l.stdoutPrint {
+ if _, err := std.Write(buffer.Bytes()); err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ }
+ }
+ } else {
+ if _, err := l.writer.Write(buffer.Bytes()); err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ }
+ }
}
-// stdPrint prints content without backtrace.
-func (l *Logger) stdPrint(s string) {
- l.print(os.Stdout, s)
+// printStd prints content without backtrace.
+func (l *Logger) printStd(lead string, value...interface{}) {
+ l.print(os.Stdout, lead, value...)
}
-// stdPrint prints content with backtrace check.
-func (l *Logger) errPrint(s string) {
- if l.btStatus.Val() == 1 {
- s = l.appendBacktrace(s)
+// printStd prints content with backtrace 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)
+ }
}
// In matter of sequence, do not use stderr here, but use the same stdout.
- l.print(os.Stdout, s)
+ l.print(os.Stdout, lead, value...)
}
-// appendBacktrace appends backtrace to the .
-func (l *Logger) appendBacktrace(s string, skip...int) string {
- trace := l.GetBacktrace(skip...)
- if trace != "" {
- backtrace := "Backtrace:" + ln + trace
- if len(s) > 0 {
- if s[len(s)-1] == byte('\n') {
- s = s + backtrace + ln
- } else {
- s = s + ln + backtrace + ln
- }
- } else {
- s = backtrace
- }
- }
- return s
+// format formats using fmt.Sprintf.
+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) {
- l.Println(l.appendBacktrace("", skip...))
+ if s := l.GetBacktrace(skip...); s != "" {
+ l.Println("Backtrace:" + ln + s)
+ } else {
+ l.Println()
+ }
}
// GetBacktrace returns the caller backtrace content,
@@ -283,10 +342,9 @@ func (l *Logger) GetBacktrace(skip...int) string {
customSkip = skip[0]
}
backtrace := ""
- index := 1
from := 0
- // 首先定位业务文件开始位置
- for i := 0; i < 10; i++ {
+ // 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
@@ -294,11 +352,11 @@ func (l *Logger) GetBacktrace(skip...int) string {
}
}
}
- // 从业务文件开始位置根据自定义的skip开始backtrace
+ // Find the true caller file path using custom skip.
+ index := 1
goRoot := runtime.GOROOT()
- for i := from + customSkip + l.btSkip.Val(); i < 10000; i++ {
- if _, file, cline, ok := runtime.Caller(i); ok && file != "" {
- // 不打印出go源码路径及glog包文件路径,日志打印必须从业务源码文件开始,且从glog包文件开始检索
+ 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++
@@ -310,171 +368,28 @@ func (l *Logger) GetBacktrace(skip...int) string {
return backtrace
}
-func (l *Logger) format(s string) string {
- return time.Now().Format("2006-01-02 15:04:05.000 ") + s
+// getLongFile returns the absolute file path along with its line number of the caller.
+func (l *Logger) getLongFile() string {
+ from := 0
+ // Find the caller position exclusive of the glog file.
+ for i := 0; i < 1000; i++ {
+ if _, file, _, ok := runtime.Caller(i); ok {
+ if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
+ from = i
+ break
+ }
+ }
+ }
+ // Find the true caller file path using custom skip.
+ goRoot := runtime.GOROOT()
+ for i := from + l.btSkip; i < 1000; i++ {
+ if _, file, line, ok := runtime.Caller(i); ok && len(file) > 2 {
+ if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(``, file) {
+ return fmt.Sprintf(`%s:%d`, file, line)
+ }
+ } else {
+ break
+ }
+ }
+ return ""
}
-
-func (l *Logger) Print(v ...interface{}) {
- l.stdPrint(fmt.Sprintln(v...))
-}
-
-func (l *Logger) Printf(format string, v ...interface{}) {
- l.stdPrint(fmt.Sprintf(format, v...))
-}
-
-func (l *Logger) Println(v ...interface{}) {
- l.stdPrint(fmt.Sprintln(v...))
-}
-
-func (l *Logger) Printfln(format string, v ...interface{}) {
- l.stdPrint(fmt.Sprintf(format + ln, v...))
-}
-
-// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
-func (l *Logger) Fatal(v ...interface{}) {
- l.errPrint("[FATA] " + fmt.Sprintln(v...))
- os.Exit(1)
-}
-
-// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
-func (l *Logger) Fatalf(format string, v ...interface{}) {
- l.errPrint("[FATA] " + fmt.Sprintf(format, v...))
- os.Exit(1)
-}
-
-// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
-func (l *Logger) Fatalfln(format string, v ...interface{}) {
- l.errPrint("[FATA] " + fmt.Sprintf(format + ln, v...))
- os.Exit(1)
-}
-
-func (l *Logger) Panic(v ...interface{}) {
- s := fmt.Sprintln(v...)
- l.errPrint("[PANI] " + s)
- panic(s)
-}
-
-func (l *Logger) Panicf(format string, v ...interface{}) {
- s := fmt.Sprintf(format, v...)
- l.errPrint("[PANI] " + s)
- panic(s)
-}
-
-func (l *Logger) Panicfln(format string, v ...interface{}) {
- s := fmt.Sprintf(format + ln, v...)
- l.errPrint("[PANI] " + s)
- panic(s)
-}
-
-func (l *Logger) Info(v ...interface{}) {
- if l.checkLevel(LEVEL_INFO) {
- l.stdPrint("[INFO] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Infof(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_INFO) {
- l.stdPrint("[INFO] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Infofln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_INFO) {
- l.stdPrint("[INFO] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-func (l *Logger) Debug(v ...interface{}) {
- if l.checkLevel(LEVEL_DEBU) {
- l.stdPrint("[DEBU] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Debugf(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_DEBU) {
- l.stdPrint("[DEBU] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Debugfln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_DEBU) {
- l.stdPrint("[DEBU] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-func (l *Logger) Notice(v ...interface{}) {
- if l.checkLevel(LEVEL_NOTI) {
- l.errPrint("[NOTI] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Noticef(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_NOTI) {
- l.errPrint("[NOTI] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Noticefln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_NOTI) {
- l.errPrint("[NOTI] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-func (l *Logger) Warning(v ...interface{}) {
- if l.checkLevel(LEVEL_WARN) {
- l.errPrint("[WARN] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Warningf(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_WARN) {
- l.errPrint("[WARN] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Warningfln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_WARN) {
- l.errPrint("[WARN] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-func (l *Logger) Error(v ...interface{}) {
- if l.checkLevel(LEVEL_ERRO) {
- l.errPrint("[ERRO] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Errorf(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_ERRO) {
- l.errPrint("[ERRO] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Errorfln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_ERRO) {
- l.errPrint("[ERRO] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-func (l *Logger) Critical(v ...interface{}) {
- if l.checkLevel(LEVEL_CRIT) {
- l.errPrint("[CRIT] " + fmt.Sprintln(v...))
- }
-}
-
-func (l *Logger) Criticalf(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_CRIT) {
- l.errPrint("[CRIT] " + fmt.Sprintf(format, v...))
- }
-}
-
-func (l *Logger) Criticalfln(format string, v ...interface{}) {
- if l.checkLevel(LEVEL_CRIT) {
- l.errPrint("[CRIT] " + fmt.Sprintf(format, v...) + ln)
- }
-}
-
-// checkLevel checks whether the given could be output.
-func (l *Logger) checkLevel(level int) bool {
- return l.level.Val() & level > 0
-}
\ No newline at end of file
diff --git a/g/os/glog/glog_logger_api.go b/g/os/glog/glog_logger_api.go
new file mode 100644
index 000000000..0c75f1336
--- /dev/null
+++ b/g/os/glog/glog_logger_api.go
@@ -0,0 +1,217 @@
+// 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 glog
+
+import (
+ "fmt"
+ "os"
+)
+
+// Print prints with newline using fmt.Sprintln.
+// The param can be multiple variables.
+func (l *Logger) Print(v...interface{}) {
+ l.printStd("", v...)
+}
+
+// Printf prints with format using fmt.Sprintf.
+// The param can be multiple variables.
+func (l *Logger) Printf(format string, v...interface{}) {
+ l.printStd(l.format(format, v...))
+}
+
+// See Print.
+func (l *Logger) Println(v...interface{}) {
+ l.Print(v...)
+}
+
+// Deprecated.
+// Use Printf instead.
+func (l *Logger) Printfln(format string, v...interface{}) {
+ l.printStd(l.format(format, v...))
+}
+
+// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
+func (l *Logger) Fatal(v...interface{}) {
+ l.printErr("[FATA]", v...)
+ os.Exit(1)
+}
+
+// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
+func (l *Logger) Fatalf(format string, v...interface{}) {
+ l.printErr("[FATA]", l.format(format, v...))
+ os.Exit(1)
+}
+
+// Deprecated.
+// Use Fatalf instead.
+func (l *Logger) Fatalfln(format string, v...interface{}) {
+ l.Fatalf(format, v...)
+ os.Exit(1)
+}
+
+// Panic prints the logging content with [PANI] header and newline, then panics.
+func (l *Logger) Panic(v...interface{}) {
+ l.printErr("[PANI]", v...)
+ panic(fmt.Sprint(v...))
+}
+
+// Panicf prints the logging content with [PANI] header, custom format and newline, then panics.
+func (l *Logger) Panicf(format string, v...interface{}) {
+ l.printErr("[PANI]", l.format(format, v...))
+ panic(l.format(format, v...))
+}
+
+// Deprecated.
+// Use Panicf instead.
+func (l *Logger) Panicfln(format string, v...interface{}) {
+ l.Panicf(format, v...)
+}
+
+// Info prints the logging content with [INFO] header and newline.
+func (l *Logger) Info(v...interface{}) {
+ if l.checkLevel(LEVEL_INFO) {
+ l.printStd("[INFO]", v...)
+ }
+}
+
+// Infof prints the logging content with [INFO] header, custom format and newline.
+func (l *Logger) Infof(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_INFO) {
+ l.printStd("[INFO]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Infof instead.
+func (l *Logger) Infofln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_INFO) {
+ l.Infof(format, v...)
+ }
+}
+
+// Debug prints the logging content with [DEBU] header and newline.
+func (l *Logger) Debug(v...interface{}) {
+ if l.checkLevel(LEVEL_DEBU) {
+ l.printStd("[DEBU]", v...)
+ }
+}
+
+// Debugf prints the logging content with [DEBU] header, custom format and newline.
+func (l *Logger) Debugf(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_DEBU) {
+ l.printStd("[DEBU]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Debugf instead.
+func (l *Logger) Debugfln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_DEBU) {
+ l.Debugf(format, v...)
+ }
+}
+
+// Notice prints the logging content with [NOTI] header and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Notice(v...interface{}) {
+ if l.checkLevel(LEVEL_NOTI) {
+ l.printErr("[NOTI]", v...)
+ }
+}
+
+// Noticef prints the logging content with [NOTI] header, custom format and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Noticef(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_NOTI) {
+ l.printErr("[NOTI]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Noticef instead.
+func (l *Logger) Noticefln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_NOTI) {
+ l.Noticef(format, v...)
+ }
+}
+
+// Warning prints the logging content with [WARN] header and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Warning(v...interface{}) {
+ if l.checkLevel(LEVEL_WARN) {
+ l.printErr("[WARN]", v...)
+ }
+}
+
+// Warningf prints the logging content with [WARN] header, custom format and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Warningf(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_WARN) {
+ l.printErr("[WARN]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Warningf instead.
+func (l *Logger) Warningfln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_WARN) {
+ l.Warningf(format, v...)
+ }
+}
+
+// Error prints the logging content with [ERRO] header and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Error(v...interface{}) {
+ if l.checkLevel(LEVEL_ERRO) {
+ l.printErr("[ERRO]", v...)
+ }
+}
+
+// Errorf prints the logging content with [ERRO] header, custom format and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Errorf(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_ERRO) {
+ l.printErr("[ERRO]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Errorf instead.
+func (l *Logger) Errorfln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_ERRO) {
+ l.Errorf(format, v...)
+ }
+}
+
+// Critical prints the logging content with [CRIT] header and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Critical(v...interface{}) {
+ if l.checkLevel(LEVEL_CRIT) {
+ l.printErr("[CRIT]", v...)
+ }
+}
+
+// Criticalf prints the logging content with [CRIT] header, custom format and newline.
+// It also prints caller backtrace info if backtrace feature is enabled.
+func (l *Logger) Criticalf(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_CRIT) {
+ l.printErr("[CRIT]", l.format(format, v...))
+ }
+}
+
+// Deprecated.
+// Use Criticalf instead.
+func (l *Logger) Criticalfln(format string, v...interface{}) {
+ if l.checkLevel(LEVEL_CRIT) {
+ l.Criticalf(format, v...)
+ }
+}
+
+// checkLevel checks whether the given could be output.
+func (l *Logger) checkLevel(level int) bool {
+ return l.level & level > 0
+}
\ No newline at end of file
diff --git a/g/os/glog/glog_logger_chaining.go b/g/os/glog/glog_logger_chaining.go
index b478a6c76..1070e8882 100644
--- a/g/os/glog/glog_logger_chaining.go
+++ b/g/os/glog/glog_logger_chaining.go
@@ -15,7 +15,7 @@ import (
// which redirects current logging content output to the specified .
func (l *Logger) To(writer io.Writer) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@@ -28,7 +28,7 @@ func (l *Logger) To(writer io.Writer) *Logger {
// which sets the directory path to for current logging content output.
func (l *Logger) Path(path string) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@@ -44,14 +44,13 @@ func (l *Logger) Path(path string) *Logger {
// Param can be hierarchical, eg: module/user.
func (l *Logger) Cat(category string) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
- path := l.path.Val()
- if path != "" {
- logger.SetPath(path + gfile.Separator + category)
+ if logger.path != "" {
+ logger.SetPath(logger.path + gfile.Separator + category)
}
return logger
}
@@ -60,7 +59,7 @@ func (l *Logger) Cat(category string) *Logger {
// which sets file name for the current logging content output.
func (l *Logger) File(file string) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@@ -73,7 +72,7 @@ func (l *Logger) File(file string) *Logger {
// which sets logging level for the current logging content output.
func (l *Logger) Level(level int) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@@ -82,11 +81,25 @@ func (l *Logger) Level(level int) *Logger {
return logger
}
+// Skip is a chaining function,
+// which sets backtrace 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)
+ if l.parent == nil {
+ logger = l.Clone()
+ } else {
+ logger = l
+ }
+ logger.SetBacktraceSkip(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 {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@@ -98,28 +111,83 @@ func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
return logger
}
-// StdPrint is a chaining function,
+// Stdout is a chaining function,
// which enables/disables stdout for the current logging content output.
-func (l *Logger) StdPrint(enabled bool) *Logger {
- logger := (*Logger)(nil)
- if l.pr == nil {
- logger = l.Clone()
- } else {
- logger = l
- }
- logger.SetStdPrint(enabled)
- return logger
+// It's enabled in default.
+func (l *Logger) Stdout(enabled...bool) *Logger {
+ logger := (*Logger)(nil)
+ if l.parent == nil {
+ logger = l.Clone()
+ } else {
+ logger = l
+ }
+ // stdout printing is enabled if is not passed.
+ if len(enabled) > 0 && !enabled[0] {
+ logger.stdoutPrint = false
+ } else {
+ logger.stdoutPrint = true
+ }
+ return logger
+}
+
+// See Stdout.
+// Deprecated.
+func (l *Logger) StdPrint(enabled...bool) *Logger {
+ return l.Stdout(enabled...)
}
// Header is a chaining function,
// which enables/disables log header for the current logging content output.
-func (l *Logger) Header(enabled bool) *Logger {
+// It's enabled in default.
+func (l *Logger) Header(enabled...bool) *Logger {
logger := (*Logger)(nil)
- if l.pr == nil {
+ if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
- logger.printHeader.Set(enabled)
+ // header is enabled if is not passed.
+ if len(enabled) > 0 && !enabled[0] {
+ logger.SetHeaderPrint(false)
+ } else {
+ logger.SetHeaderPrint(true)
+ }
return logger
+}
+
+// Line is a chaining function,
+// which enables/disables printing its caller file path along with its line number.
+// The param specified whether print the long absolute file path, eg: /a/b/c/d.go:23,
+// or else short one: d.go:23.
+func (l *Logger) Line(long...bool) *Logger {
+ logger := (*Logger)(nil)
+ if l.parent == nil {
+ logger = l.Clone()
+ } else {
+ logger = l
+ }
+ if len(long) > 0 && long[0] {
+ logger.flags |= F_FILE_LONG
+ } else {
+ logger.flags |= F_FILE_SHORT
+ }
+ return logger
+}
+
+// Async is a chaining function,
+// which enables/disables async logging output feature.
+func (l *Logger) Async(enabled...bool) *Logger {
+ logger := (*Logger)(nil)
+ if l.parent == nil {
+ logger = l.Clone()
+ } else {
+ logger = l
+ }
+ // async feature is enabled if is not passed.
+ if len(enabled) > 0 && !enabled[0] {
+ logger.SetAsync(false)
+ } else {
+ logger.SetAsync(true)
+ }
+ return logger
}
\ No newline at end of file
diff --git a/g/os/glog/glog_logger_writer.go b/g/os/glog/glog_logger_writer.go
index 525fe438f..d2eef9dee 100644
--- a/g/os/glog/glog_logger_writer.go
+++ b/g/os/glog/glog_logger_writer.go
@@ -6,13 +6,9 @@
package glog
-type Writer struct {
- logger *Logger
-}
-
// Write implements the io.Writer interface.
-// It just prints the content with header or level.
-func (w *Writer) Write(p []byte) (n int, err error) {
- w.logger.Header(false).Print(string(p))
+// It just prints the content using Print.
+func (l *Logger) Write(p []byte) (n int, err error) {
+ l.Header(false).Print(string(p))
return len(p), nil
}
\ No newline at end of file
diff --git a/g/os/grpool/grpool.go b/g/os/grpool/grpool.go
index 82a0c6318..8d2bf116c 100644
--- a/g/os/grpool/grpool.go
+++ b/g/os/grpool/grpool.go
@@ -1,121 +1,103 @@
-// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright 2017-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 grpool implements a goroutine reusable pool.
-//
-// Goroutine池,
-// 用于goroutine复用,提升异步操作执行效率(避免goroutine限制,并节约内存开销).
-// 需要注意的是,grpool提供给的公共池不提供关闭方法,自创建的池可以手动关闭掉。
package grpool
import (
- "github.com/gogf/gf/g/container/glist"
- "github.com/gogf/gf/g/container/gtype"
- "math"
+ "github.com/gogf/gf/g/container/glist"
+ "github.com/gogf/gf/g/container/gtype"
)
-// goroutine池对象
+// Goroutine Pool
type Pool struct {
- workerChan chan struct{} // 使用channel限制最大的goroutine数量
- workerNum *gtype.Int // 当前正在运行的worker/goroutine数量
- jobQueue *glist.List // 待处理任务操作队列
- jobEvents chan struct{} // 任务添加事件(jobQueue+jobEvents结合使用)
- closed *gtype.Bool
+ limit int // Max goroutine count limit.
+ count *gtype.Int // Current running goroutine count.
+ list *glist.List // Job list.
+ closed *gtype.Bool // Is pool closed or not.
}
-// 默认的goroutine池管理对象
-// 该对象与进程同生命周期,无需Close
-var defaultPool = New()
+// Default goroutine pool.
+var pool = New()
-// 创建goroutine池管理对象, 参数用于限制限制最大的goroutine数量/线程数/worker数量,非必需参数,默认不做限制
-func New(size...int) *Pool {
- s := 0
- if len(size) > 0 {
- s = size[0]
- }
+// New creates and returns a new goroutine pool object.
+// The param is used to limit the max goroutine count,
+// which is not limited in default.
+func New(limit...int) *Pool {
p := &Pool {
- workerNum : gtype.NewInt(),
- jobQueue : glist.New(),
- jobEvents : make(chan struct{}, math.MaxInt32),
- workerChan : make(chan struct{}, s),
- closed : gtype.NewBool(),
+ limit : -1,
+ count : gtype.NewInt(),
+ list : glist.New(),
+ closed : gtype.NewBool(),
+ }
+ if len(limit) > 0 {
+ p.limit = limit[0]
}
return p
}
-// 添加异步任务(使用默认的池对象)
-func Add(f func()) error {
- return defaultPool.Add(f)
+// Add pushes a new job to the pool using default goroutine pool.
+// The job will be executed asynchronously.
+func Add(f func()) {
+ pool.Add(f)
}
-// 查询当前goroutine总数
+// Size returns current goroutine count of default goroutine pool.
func Size() int {
- return defaultPool.workerNum.Val()
+ return pool.count.Val()
}
-// 查询当前等待处理的任务总数
+// Jobs returns current job count of default goroutine pool.
func Jobs() int {
- return len(defaultPool.jobEvents)
+ return pool.list.Len()
}
-// 添加异步任务
-func (p *Pool) Add(f func()) error {
- p.jobQueue.PushBack(f)
- p.jobEvents <- struct{}{}
- // 判断是否创建新的worker
- if p.Jobs() > 1 || p.workerNum.Val() == 0 {
- p.ForkWorker()
+// Add pushes a new job to the pool.
+// The job will be executed asynchronously.
+func (p *Pool) Add(f func()) {
+ p.list.PushFront(f)
+ // check whether to create a new goroutine or not.
+ if p.count.Val() == p.limit {
+ return
}
- return nil
+ // ensure atomicity.
+ if p.limit != -1 && p.count.Add(1) > p.limit {
+ p.count.Add(-1)
+ return
+ }
+ // fork a new goroutine to consume the job list.
+ p.fork()
}
-// 查询当前goroutine worker总数
+// Size returns current goroutine count of the pool.
func (p *Pool) Size() int {
- return p.workerNum.Val()
+ return p.count.Val()
}
-// 查询当前等待处理的任务总数
+// Jobs returns current job count of the pool.
func (p *Pool) Jobs() int {
- return p.jobQueue.Len()
+ return p.list.Size()
}
-// 创建新的worker执行任务
-func (p *Pool) ForkWorker() {
- if cap(p.workerChan) > 0 {
- // 如果worker数量已经达到限制,那么不创建新worker,直接返回
- if p.workerNum.Val() == cap(p.workerChan) {
- return
- }
- p.workerNum.Add(1)
- p.workerChan <- struct{}{}
- } else {
- p.workerNum.Add(1)
- }
+// fork creates a new goroutine pool.
+func (p *Pool) fork() {
go func() {
+ defer p.count.Add(-1)
+ job := (interface{})(nil)
for !p.closed.Val() {
- select {
- case <- p.jobEvents:
- if job := p.jobQueue.PopFront(); job != nil {
- job.(func())()
- } else {
- goto WorkerDone
- }
- default:
- goto WorkerDone
- }
- }
-WorkerDone:
- p.workerNum.Add(-1)
- if cap(p.workerChan) > 0 {
- <- p.workerChan
+ if job = p.list.PopBack(); job != nil {
+ job.(func())()
+ } else {
+ return
+ }
}
}()
}
-// 关闭池,所有的任务将会停止,此后继续添加的任务将不会被执行
+// Close closes the goroutine pool, which makes all goroutines exit.
func (p *Pool) Close() {
- p.closed.Set(true)
+ p.closed.Set(true)
}
\ No newline at end of file
diff --git a/g/os/grpool/grpool_unit_test.go b/g/os/grpool/grpool_unit_test.go
new file mode 100644
index 000000000..2131a4683
--- /dev/null
+++ b/g/os/grpool/grpool_unit_test.go
@@ -0,0 +1,95 @@
+// 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.
+
+// go test *.go -bench=".*" -count=1
+
+package grpool_test
+
+import (
+ "github.com/gogf/gf/g/container/garray"
+ "github.com/gogf/gf/g/os/grpool"
+ "github.com/gogf/gf/g/test/gtest"
+ "sync"
+ "testing"
+ "time"
+)
+
+
+func Test_Basic(t *testing.T) {
+ gtest.Case(t, func() {
+ wg := sync.WaitGroup{}
+ array := garray.NewArray()
+ size := 100
+ wg.Add(size)
+ for i := 0; i < size; i++ {
+ grpool.Add(func() {
+ array.Append(1)
+ wg.Done()
+ })
+ }
+ wg.Wait()
+ gtest.Assert(array.Len(), size)
+ })
+}
+
+func Test_Limit1(t *testing.T) {
+ gtest.Case(t, func() {
+ wg := sync.WaitGroup{}
+ array := garray.NewArray()
+ size := 100
+ pool := grpool.New(10)
+ wg.Add(size)
+ for i := 0; i < size; i++ {
+ pool.Add(func() {
+ array.Append(1)
+ wg.Done()
+ })
+ }
+ wg.Wait()
+ gtest.Assert(array.Len(), size)
+ })
+}
+
+func Test_Limit2(t *testing.T) {
+ gtest.Case(t, func() {
+ wg := sync.WaitGroup{}
+ array := garray.NewArray()
+ size := 100
+ pool := grpool.New(1)
+ wg.Add(size)
+ for i := 0; i < size; i++ {
+ pool.Add(func() {
+ array.Append(1)
+ wg.Done()
+ })
+ }
+ wg.Wait()
+ gtest.Assert(array.Len(), size)
+ })
+}
+
+func Test_Limit3(t *testing.T) {
+ gtest.Case(t, func() {
+ array := garray.NewArray()
+ size := 1000
+ pool := grpool.New(100)
+ for i := 0; i < size; i++ {
+ pool.Add(func() {
+ array.Append(1)
+ time.Sleep(2*time.Second)
+ })
+ }
+ time.Sleep(time.Second)
+ gtest.Assert(pool.Size(), 100)
+ gtest.Assert(pool.Jobs(), 900)
+ gtest.Assert(array.Len(), 100)
+ pool.Close()
+ time.Sleep(2*time.Second)
+ gtest.Assert(pool.Size(), 0)
+ gtest.Assert(pool.Jobs(), 900)
+ gtest.Assert(array.Len(), 100)
+ })
+}
\ No newline at end of file
diff --git a/g/os/gtime/gtime_format.go b/g/os/gtime/gtime_format.go
index f0ef77a16..d3a578a11 100644
--- a/g/os/gtime/gtime_format.go
+++ b/g/os/gtime/gtime_format.go
@@ -21,19 +21,21 @@ var (
'd': "02", // 月份中的第几天,有前导零的 2 位数字(01 到 31)
'D': "Mon", // 星期中的第几天,文本表示,3 个字母(Mon 到 Sun)
'w': "Monday", // 星期中的第几天,数字型式的文本表示 0为星期天 6为星期六
- 'W': "", // ISO-8601 格式年份中的第几周,每周从星期一开始 例如:42(当年的第 42 周)
'N': "Monday", // ISO-8601 格式数字表示的星期中的第几天 1(表示星期一)到 7(表示星期天)
'j': "=j=02", // 月份中的第几天,没有前导零(1 到 31)
'S': "02", // 每月天数后面的英文后缀,2 个字符 st,nd,rd 或者 th。可以和 j 一起用
'l': "Monday", // ("L"的小写字母)星期几,完整的文本格式(Sunday 到 Saturday)
'z': "", // 年份中的第几天 0到365
- 't': "", // 指定的月份有几天 28到31
+
+ // ================== 日 ==================
+ 'W': "", // ISO-8601 格式年份中的第几周,每周从星期一开始 例如:42(当年的第 42 周)
// ================== 月 ==================
'F': "January", // 月份,完整的文本格式,例如 January 或者 March January 到 December
'm': "01", // 数字表示的月份,有前导零(01 到 12)
'M': "Jan", // 三个字母缩写表示的月份(Jan 到 Dec)
'n': "1", // 数字表示的月份,没有前导零(1 到 12)
+ 't': "", // 指定的月份有几天 28到31
// ================== 年 ==================
'Y': "2006", // 4 位数字完整表示的年份, 例如:1999 或 2003
@@ -49,6 +51,7 @@ var (
'i': "04", // 有前导零的分钟数, 00 到 59
's': "05", // 秒数,有前导零, 00 到 59
'u': "=u=.000", // 毫秒(3位)
+ 'U': "", // 将时间格式化为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位秒)
// ================== 时区 ==================
'O': "-0700", // 与UTC相差的小时数, 例如:+0200
@@ -75,56 +78,9 @@ var (
dayOfMonth = []int{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
)
-// 将自定义的格式转换为标准库时间格式
-func formatToStdLayout(format string) string {
- b := bytes.NewBuffer(nil)
- for i := 0; i < len(format); {
- switch format[i] {
- case '\\':
- if i < len(format)-1 {
- b.WriteByte(format[i+1])
- i += 2
- continue
- } else {
- return b.String()
- }
-
- default:
- if f, ok := formats[format[i]]; ok {
- // 有几个转换的符号需要特殊处理
- switch format[i] {
- case 'j': b.WriteString("02")
- case 'G': b.WriteString("15")
- case 'u':
- if i > 0 && format[i-1] == '.' {
- b.WriteString("000")
- } else {
- b.WriteString(".000")
- }
-
- default:
- b.WriteString(f)
- }
- } else {
- b.WriteByte(format[i])
- }
- i++
- }
- }
- return b.String()
-}
-
-// 将format格式转换为正则表达式规则
-func formatToRegexPattern(format string) string {
- s := gregex.Quote(formatToStdLayout(format))
- s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
- s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
- return s
-}
-
-// 格式化,使用自定义日期格式
+// 使用自定义日期格式格式化输出日期。
func (t *Time) Format(format string) string {
- runes := []rune(format)
+ runes := []rune(format)
buffer := bytes.NewBuffer(nil)
for i := 0; i < len(runes); {
switch runes[i] {
@@ -139,6 +95,7 @@ func (t *Time) Format(format string) string {
case 'W': buffer.WriteString(strconv.Itoa(t.WeeksOfYear()))
case 'z': buffer.WriteString(strconv.Itoa(t.DayOfYear()))
case 't': buffer.WriteString(strconv.Itoa(t.DaysInMonth()))
+ case 'U': buffer.WriteString(strconv.FormatInt(t.Unix(),10))
default:
if runes[i] > 255 {
buffer.WriteRune(runes[i])
@@ -165,14 +122,21 @@ func (t *Time) Format(format string) string {
return buffer.String()
}
-// 每月天数后面的英文后缀,2 个字符st nd,rd 或者 th
-func formatMonthDaySuffixMap(day string) string {
- switch day {
- case "01": return "st"
- case "02": return "nd"
- case "03": return "rd"
- default: return "th"
- }
+// 通过自定义格式转换当前日期为新的日期。
+func (t *Time) FormatTo(format string) *Time {
+ t.Time = NewFromStr(t.Format(format)).Time
+ return t
+}
+
+// 使用标准库格式格式化输出日期。
+func (t *Time) Layout(layout string) string {
+ return t.Time.Format(layout)
+}
+
+// 通过标准库格式转换当前日期为新的日期。
+func (t *Time) LayoutTo(layout string) *Time {
+ t.Time = NewFromStr(t.Layout(layout)).Time
+ return t
}
// 返回是否是润年
@@ -221,7 +185,61 @@ func (t *Time) WeeksOfYear() int {
return week
}
-// 格式化使用标准库格式
-func (t *Time) Layout(layout string) string {
- return t.Time.Format(layout)
+// 将自定义的格式转换为标准库时间格式
+func formatToStdLayout(format string) string {
+ b := bytes.NewBuffer(nil)
+ for i := 0; i < len(format); {
+ switch format[i] {
+ case '\\':
+ if i < len(format)-1 {
+ b.WriteByte(format[i+1])
+ i += 2
+ continue
+ } else {
+ return b.String()
+ }
+
+ default:
+ if f, ok := formats[format[i]]; ok {
+ // 有几个转换的符号需要特殊处理
+ switch format[i] {
+ case 'j': b.WriteString("02")
+ case 'G': b.WriteString("15")
+ case 'u':
+ if i > 0 && format[i-1] == '.' {
+ b.WriteString("000")
+ } else {
+ b.WriteString(".000")
+ }
+
+ default:
+ b.WriteString(f)
+ }
+ } else {
+ b.WriteByte(format[i])
+ }
+ i++
+ }
+ }
+ return b.String()
}
+
+// 将format格式转换为正则表达式规则
+func formatToRegexPattern(format string) string {
+ s := gregex.Quote(formatToStdLayout(format))
+ s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
+ s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
+ return s
+}
+
+// 每月天数后面的英文后缀,2 个字符st nd,rd 或者 th
+func formatMonthDaySuffixMap(day string) string {
+ switch day {
+ case "01": return "st"
+ case "02": return "nd"
+ case "03": return "rd"
+ default: return "th"
+ }
+}
+
+
diff --git a/g/os/gtime/gtime_time.go b/g/os/gtime/gtime_time.go
index 3256945e6..f017472cb 100644
--- a/g/os/gtime/gtime_time.go
+++ b/g/os/gtime/gtime_time.go
@@ -13,7 +13,7 @@ type Time struct {
}
// 创建一个空的时间对象,参数可以是标准库时间对象,可选
-func New (t...time.Time) *Time {
+func New(t...time.Time) *Time {
if len(t) > 0 {
return NewFromTime(t[0])
}
@@ -30,14 +30,14 @@ func Now() *Time {
}
// 标准时间对象转换为自定义的时间对象
-func NewFromTime (t time.Time) *Time {
+func NewFromTime(t time.Time) *Time {
return &Time{
t,
}
}
// 从字符串转换为时间对象,复杂的时间字符串需要给定格式
-func NewFromStr (str string) *Time {
+func NewFromStr(str string) *Time {
if t, err := StrToTime(str); err == nil {
return t
}
@@ -45,7 +45,7 @@ func NewFromStr (str string) *Time {
}
// 从字符串转换为时间对象,指定字符串时间格式,format格式形如:Y-m-d H:i:s
-func NewFromStrFormat (str string, format string) *Time {
+func NewFromStrFormat(str string, format string) *Time {
if t, err := StrToTimeFormat(str, format); err == nil {
return t
}
@@ -53,7 +53,7 @@ func NewFromStrFormat (str string, format string) *Time {
}
// 从字符串转换为时间对象,通过标准库layout格式进行解析,layout格式形如:2006-01-02 15:04:05
-func NewFromStrLayout (str string, layout string) *Time {
+func NewFromStrLayout(str string, layout string) *Time {
if t, err := StrToTimeLayout(str, layout); err == nil {
return t
}
@@ -61,7 +61,7 @@ func NewFromStrLayout (str string, layout string) *Time {
}
// 时间戳转换为时间对象,时间戳支持到纳秒的数值
-func NewFromTimeStamp (timestamp int64) *Time {
+func NewFromTimeStamp(timestamp int64) *Time {
if timestamp == 0 {
return &Time {}
}
@@ -98,7 +98,8 @@ func (t *Time) String() string {
return t.Format("Y-m-d H:i:s")
}
-// 转换为标准库日期对象
+// Deprecated.
+// Directly use t.Time instead.
func (t *Time) ToTime() time.Time {
return t.Time
}
@@ -121,13 +122,12 @@ func (t *Time) ToLocation(location *time.Location) *Time {
}
// 时区转换为指定的时区(通过时区名称,如:Asia/Shanghai)
-func (t *Time) ToZone(zone string) *Time {
+func (t *Time) ToZone(zone string) (*Time, error) {
if l, err := time.LoadLocation(zone); err == nil {
t.Time = t.Time.In(l)
- return t
+ return t, nil
} else {
- //panic(err)
- return nil
+ return nil, err
}
}
diff --git a/g/os/gtime/gtime_z_unit_format_test.go b/g/os/gtime/gtime_z_unit_format_test.go
index 6a67df146..e56b11b5d 100644
--- a/g/os/gtime/gtime_z_unit_format_test.go
+++ b/g/os/gtime/gtime_z_unit_format_test.go
@@ -42,6 +42,8 @@ func Test_Format(t *testing.T) {
}
gtest.Assert(timeTemp2.Format("Y-n-j G:i:s"), "2006-1-2 3:04:05")
+ gtest.Assert(timeTemp2.Format("U"), "1136142245")
+
// 测试数字型的星期
times := []map[string]string{
{"k": "2019-04-22", "f": "w", "r": "1"},
@@ -85,9 +87,24 @@ func Test_Format(t *testing.T) {
})
}
+func Test_FormatTo(t *testing.T) {
+ gtest.Case(t, func() {
+ timeTemp := gtime.Now()
+ gtest.Assert(timeTemp.FormatTo("Y-m-01 00:00:01"), timeTemp.Time.Format("2006-01") + "-01 00:00:01")
+ })
+}
+
+
func Test_Layout(t *testing.T) {
gtest.Case(t, func() {
timeTemp := gtime.Now()
gtest.Assert(timeTemp.Layout("2006-01-02 15:04:05"), timeTemp.Time.Format("2006-01-02 15:04:05"))
})
}
+
+func Test_LayoutTo(t *testing.T) {
+ gtest.Case(t, func() {
+ timeTemp := gtime.Now()
+ gtest.Assert(timeTemp.LayoutTo("2006-01-02 00:00:00"), timeTemp.Time.Format("2006-01-02 00:00:00"))
+ })
+}
diff --git a/g/os/gtime/gtime_z_unit_time_test.go b/g/os/gtime/gtime_z_unit_time_test.go
index 22dbe5e46..f00b2d760 100644
--- a/g/os/gtime/gtime_z_unit_time_test.go
+++ b/g/os/gtime/gtime_z_unit_time_test.go
@@ -137,7 +137,7 @@ func Test_ToZone(t *testing.T) {
timeTemp.ToLocation(loc)
gtest.Assert(timeTemp.Time.Location().String(), "Asia/Shanghai")
- timeTemp1 := timeTemp.ToZone("errZone")
+ timeTemp1, _ := timeTemp.ToZone("errZone")
if timeTemp1 != nil {
t.Error("test fail")
}
diff --git a/g/text/gregex/gregex_z_bench_test.go b/g/text/gregex/gregex_z_bench_test.go
index db72f9d28..cd83dc1db 100644
--- a/g/text/gregex/gregex_z_bench_test.go
+++ b/g/text/gregex/gregex_z_bench_test.go
@@ -10,51 +10,29 @@ package gregex_test
import (
"github.com/gogf/gf/g/text/gregex"
- "testing"
+ "regexp"
+ "testing"
)
-var pattern = `(.+):(\d+)`
-var src = "johng.cn:80"
-var replace = "johng.cn"
+var pattern = `(\w+).+\-\-\s*(.+)`
+var src = `GF is best! -- John`
-func BenchmarkValidate(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.Validate(pattern)
- }
+func Benchmark_GF(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ gregex.IsMatchString(pattern, src)
+ }
}
-func BenchmarkIsMatch(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.IsMatch(pattern, []byte(src))
- }
+func Benchmark_Compile(b *testing.B) {
+ var wcdRegexp = regexp.MustCompile(pattern)
+ for i := 0; i < b.N; i++ {
+ wcdRegexp.MatchString(src)
+ }
}
-func BenchmarkIsMatchString(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.IsMatchString(pattern, src)
- }
-}
-
-func BenchmarkMatchString(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.MatchString(pattern, src)
- }
-}
-
-func BenchmarkMatchAllString(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.MatchAllString(pattern, src)
- }
-}
-
-func BenchmarkReplace(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.Replace(pattern, []byte(replace), []byte(src))
- }
-}
-
-func BenchmarkReplaceString(b *testing.B) {
- for i := 0; i < b.N; i++ {
- gregex.ReplaceString(pattern, replace, src)
- }
+func Benchmark_Compile_Actual(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ wcdRegexp := regexp.MustCompile(pattern)
+ wcdRegexp.MatchString(src)
+ }
}
diff --git a/g/util/gconv/gconv.go b/g/util/gconv/gconv.go
index af64db5c3..6b517ca37 100644
--- a/g/util/gconv/gconv.go
+++ b/g/util/gconv/gconv.go
@@ -137,6 +137,7 @@ func String(i interface{}) string {
case bool: return strconv.FormatBool(value)
case string: return value
case []byte: return string(value)
+ case []rune: return string(value)
default:
if f, ok := value.(apiString); ok {
// If the variable implements the String() interface,
diff --git a/g/util/gconv/gconv_map.go b/g/util/gconv/gconv_map.go
index 94f6f72e9..bc9cca1a5 100644
--- a/g/util/gconv/gconv_map.go
+++ b/g/util/gconv/gconv_map.go
@@ -7,15 +7,19 @@
package gconv
import (
- "github.com/gogf/gf/g/internal/empty"
- "github.com/gogf/gf/g/text/gstr"
- "reflect"
- "strings"
+ "github.com/gogf/gf/g/internal/empty"
+ "github.com/gogf/gf/g/text/gstr"
+ "reflect"
+ "strings"
)
-// Map converts any variable to map[string]interface{}.
-// If the parameter is not a map type, then the conversion will fail and returns nil.
-// If is a struct object, the second parameter specifies the most priority
+const (
+ gGCONV_TAG = "gconv"
+)
+
+// Map converts any variable to map[string]interface{}.
+// If the parameter is not a map type, then the conversion will fail and returns nil.
+// If is a struct object, the second parameter specifies the most priority
// tags that will be detected, otherwise it detects the tags in order of: gconv, json.
func Map(value interface{}, tags...string) map[string]interface{} {
if value == nil {
@@ -24,7 +28,7 @@ func Map(value interface{}, tags...string) map[string]interface{} {
if r, ok := value.(map[string]interface{}); ok {
return r
} else {
- // Only assert the common combination type of maps, and finally use reflection.
+ // Only assert the common combination of types, and finally it uses reflection.
m := make(map[string]interface{})
switch value.(type) {
case map[interface{}]interface{}:
@@ -71,7 +75,6 @@ func Map(value interface{}, tags...string) map[string]interface{} {
for k, v := range value.(map[string]float64) {
m[k] = v
}
-
case map[int]interface{}:
for k, v := range value.(map[int]interface{}) {
m[String(k)] = v
@@ -102,8 +105,7 @@ func Map(value interface{}, tags...string) map[string]interface{} {
case reflect.Struct:
rt := rv.Type()
name := ""
- gconvTag := "gconv"
- tagArray := []string{gconvTag, "json"}
+ tagArray := []string{gGCONV_TAG, "json"}
switch len(tags) {
case 0:
// No need handle.
@@ -112,8 +114,8 @@ func Map(value interface{}, tags...string) map[string]interface{} {
default:
tagArray = tags
}
- if gstr.SearchArray(tagArray, gconvTag) < 0 {
- tagArray = append(tagArray, gconvTag)
+ if gstr.SearchArray(tagArray, gGCONV_TAG) < 0 {
+ tagArray = append(tagArray, gGCONV_TAG)
}
for i := 0; i < rv.NumField(); i++ {
// Only convert the public attributes.
@@ -159,3 +161,25 @@ func Map(value interface{}, tags...string) map[string]interface{} {
return m
}
}
+
+// MapDeep do Map function recursively.
+// See Map.
+func MapDeep(value interface{}, tags...string) map[string]interface{} {
+ data := Map(value, tags...)
+ for key, value := range data {
+ rv := reflect.ValueOf(value)
+ kind := rv.Kind()
+ if kind == reflect.Ptr {
+ rv = rv.Elem()
+ kind = rv.Kind()
+ }
+ switch kind {
+ case reflect.Struct:
+ delete(data, key)
+ for k, v := range MapDeep(value, tags...) {
+ data[k] = v
+ }
+ }
+ }
+ return data
+}
diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go
index 879bf7826..e84455496 100644
--- a/g/util/gconv/gconv_struct.go
+++ b/g/util/gconv/gconv_struct.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright 2017-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,
@@ -15,35 +15,34 @@ import (
"strings"
)
-// Struct maps the params key-value pairs to the corresponding struct object properties.
-// The third parameter mapping is unnecessary, indicating the mapping between the custom name
-// and the attribute name.
+// Struct maps the params key-value pairs to the corresponding struct object's properties.
+// The third parameter is unnecessary, indicating the mapping rules between the custom key name
+// and the attribute name(case sensitive).
//
// Note:
-// 1. The can be any type of may/struct, usually a map;
-// 2. The second parameter should be a pointer to the struct object;
-// 3. Only the public attributes of struct object can be mapped;
+// 1. The can be any type of map/struct, usually a map.
+// 2. The second parameter should be a pointer to the struct object.
+// 3. Only the public attributes of struct object can be mapped.
// 4. If is a map, the key of the map can be lowercase.
// It will automatically convert the first letter of the key to uppercase
// in mapping procedure to do the matching.
-// If it does not match, ignore the key;
-func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error {
+// It ignores the map key, if it does not match.
+func Struct(params interface{}, pointer interface{}, mapping...map[string]string) error {
if params == nil {
return errors.New("params cannot be nil")
}
- if objPointer == nil {
+ if pointer == nil {
return errors.New("object pointer cannot be nil")
}
paramsMap := Map(params)
if paramsMap == nil {
return fmt.Errorf("invalid params: %v", params)
}
- // struct的反射对象
- elem := reflect.Value{}
- if v, ok := objPointer.(reflect.Value); ok {
- elem = v
- } else {
- rv := reflect.ValueOf(objPointer)
+ // Using reflect to do the converting,
+ // it also supports type of reflect.Value for (always in internal usage).
+ elem, ok := pointer.(reflect.Value)
+ if !ok {
+ rv := reflect.ValueOf(pointer)
if kind := rv.Kind(); kind != reflect.Ptr {
return fmt.Errorf("object pointer should be type of: %v", kind)
}
@@ -52,12 +51,12 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
}
elem = rv.Elem()
}
- // 已执行过转换的属性,只执行一次转换。
- // 或者是已经执行过转换检查的属性(即使不进行转换), 以便重复判断。
+ // It only performs one converting to the same attribute.
+ // doneMap is used to check repeated converting.
doneMap := make(map[string]bool)
- // 首先按照传递的映射关系进行匹配
- if len(attrMapping) > 0 && len(attrMapping[0]) > 0 {
- for mapK, mapV := range attrMapping[0] {
+ // It first checks the passed mapping rules.
+ if len(mapping) > 0 && len(mapping[0]) > 0 {
+ for mapK, mapV := range mapping[0] {
if v, ok := paramsMap[mapK]; ok {
doneMap[mapV] = true
if err := bindVarToStructAttr(elem, mapV, v); err != nil {
@@ -66,25 +65,24 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
}
}
}
- // 其次匹配对象定义时绑定的属性名称,
- // 标签映射关系map,如果有的话
- tagMap := getTagMapOfStruct(objPointer)
- for tagk, tagv := range tagMap {
- if _, ok := doneMap[tagv]; ok {
+ // It secondly checks the tags of attributes.
+ tagMap := getTagMapOfStruct(pointer)
+ for tagK, tagV := range tagMap {
+ if _, ok := doneMap[tagV]; ok {
continue
}
- if v, ok := paramsMap[tagk]; ok {
- doneMap[tagv] = true
- if err := bindVarToStructAttr(elem, tagv, v); err != nil {
+ if v, ok := paramsMap[tagK]; ok {
+ doneMap[tagV] = true
+ if err := bindVarToStructAttr(elem, tagV, v); err != nil {
return err
}
}
}
- // 最后按照默认规则进行匹配
+ // It finally do the converting with default rules.
attrMap := make(map[string]struct{})
elemType := elem.Type()
for i := 0; i < elem.NumField(); i++ {
- // 只转换公开属性
+ // Only do converting to public attributes.
if !gstr.IsLetterUpper(elemType.Field(i).Name[0]) {
continue
}
@@ -105,7 +103,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
if _, ok := tagMap[checkName]; ok {
continue
}
- // 循环查找属性名称进行匹配
+ // Loop to find the matched attribute name.
for value, _ := range attrMap {
if strings.EqualFold(checkName, value) {
name = value
@@ -121,7 +119,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
break
}
}
- // 如果没有匹配到属性名称,放弃
+ // No matching, give up this attribute converting.
if name == "" {
continue
}
@@ -132,15 +130,49 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
return nil
}
+// StructDeep do Struct function recursively.
+// See Struct.
+func StructDeep(params interface{}, pointer interface{}, mapping...map[string]string) error {
+ if err := Struct(params, pointer, mapping...); err != nil {
+ return err
+ } else {
+ rv, ok := pointer.(reflect.Value)
+ if !ok {
+ rv = reflect.ValueOf(pointer)
+ }
+ kind := rv.Kind()
+ if kind == reflect.Ptr {
+ rv = rv.Elem()
+ kind = rv.Kind()
+ }
+ switch kind {
+ case reflect.Struct:
+ rt := rv.Type()
+ for i := 0; i < rv.NumField(); i++ {
+ // Only do converting to public attributes.
+ if !gstr.IsLetterUpper(rt.Field(i).Name[0]) {
+ continue
+ }
+ trv := rv.Field(i)
+ switch trv.Kind() {
+ case reflect.Struct:
+ StructDeep(params, trv, mapping...)
+ }
+ }
+ }
+ }
+ return nil
+}
+
// 解析指针对象的tag
-func getTagMapOfStruct(objPointer interface{}) map[string]string {
- tagmap := make(map[string]string)
+func getTagMapOfStruct(pointer interface{}) map[string]string {
+ tagMap := make(map[string]string)
// 反射类型判断
fields := ([]*structs.Field)(nil)
- if v, ok := objPointer.(reflect.Value); ok {
+ if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
- fields = structs.Fields(objPointer)
+ fields = structs.Fields(pointer)
}
// 将struct中定义的属性转换名称构建成tagmap
for _, field := range fields {
@@ -150,11 +182,11 @@ func getTagMapOfStruct(objPointer interface{}) map[string]string {
}
if tag != "" {
for _, v := range strings.Split(tag, ",") {
- tagmap[strings.TrimSpace(v)] = field.Name()
+ tagMap[strings.TrimSpace(v)] = field.Name()
}
}
}
- return tagmap
+ return tagMap
}
// 将参数值绑定到对象指定名称的属性上
diff --git a/g/util/gconv/gconv_z_unit_map_test.go b/g/util/gconv/gconv_z_unit_map_test.go
index 15d2ac16a..e9b4c3f40 100644
--- a/g/util/gconv/gconv_z_unit_map_test.go
+++ b/g/util/gconv/gconv_z_unit_map_test.go
@@ -123,23 +123,30 @@ func Test_Map_PrivateAttribute(t *testing.T) {
gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
})
}
-//
-//func Test_Map_StructInherit(t *testing.T) {
-// type Base struct {
-// Id int
-// }
-// type User struct {
-// Base
-// Name string
-// }
-// gtest.Case(t, func() {
-// user := &User{
-// Base : Base {
-// Id : 100,
-// },
-// Name : "john",
-// }
-// fmt.Println(gconv.Map(user))
-// //gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
-// })
-//}
\ No newline at end of file
+
+func Test_Map_StructInherit(t *testing.T) {
+ gtest.Case(t, func() {
+ type Ids struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ }
+ type Base struct {
+ Ids
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ user := new(User)
+ user.Id = 100
+ user.Nickname = "john"
+ user.CreateTime = "2019"
+ m := gconv.MapDeep(user)
+ gtest.Assert(m["id"], user.Id)
+ gtest.Assert(m["nickname"], user.Nickname)
+ gtest.Assert(m["create_time"], user.CreateTime)
+ })
+}
\ No newline at end of file
diff --git a/g/util/gconv/gconv_z_unit_struct_test.go b/g/util/gconv/gconv_z_unit_struct_test.go
index 78af7942c..35d4274f8 100644
--- a/g/util/gconv/gconv_z_unit_struct_test.go
+++ b/g/util/gconv/gconv_z_unit_struct_test.go
@@ -7,10 +7,12 @@
package gconv_test
import (
- "github.com/gogf/gf/g"
- "github.com/gogf/gf/g/test/gtest"
- "github.com/gogf/gf/g/util/gconv"
- "testing"
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/os/gtime"
+ "github.com/gogf/gf/g/test/gtest"
+ "github.com/gogf/gf/g/util/gconv"
+ "testing"
+ "time"
)
func Test_Struct_Basic1(t *testing.T) {
@@ -302,7 +304,6 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
})
}
-// 私有属性不会进行转换
func Test_Struct_PrivateAttribute(t *testing.T) {
type User struct {
Id int
@@ -315,4 +316,100 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
gtest.Assert(user.Id, 1)
gtest.Assert(user.name, "")
})
-}
\ No newline at end of file
+}
+
+func Test_Struct_Deep(t *testing.T) {
+ gtest.Case(t, func() {
+ type Ids struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ }
+ type Base struct {
+ Ids
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ data := g.Map{
+ "id" : 100,
+ "uid" : 101,
+ "passport" : "t1",
+ "password" : "123456",
+ "nickname" : "T1",
+ "create_time" : "2019",
+ }
+ user := new(User)
+ gconv.StructDeep(data, user)
+ gtest.Assert(user.Id, 100)
+ gtest.Assert(user.Uid, 101)
+ gtest.Assert(user.Nickname, "T1")
+ gtest.Assert(user.CreateTime, "2019")
+ })
+}
+
+func Test_Struct_Time(t *testing.T) {
+ gtest.Case(t, func() {
+ type User struct {
+ CreateTime time.Time
+ }
+ now := time.Now()
+ user := new(User)
+ gconv.Struct(g.Map{
+ "create_time" : now,
+ }, user)
+ gtest.Assert(user.CreateTime.UTC().String(), now.UTC().String())
+ })
+
+ gtest.Case(t, func() {
+ type User struct {
+ CreateTime *time.Time
+ }
+ now := time.Now()
+ user := new(User)
+ gconv.Struct(g.Map{
+ "create_time" : &now,
+ }, user)
+ gtest.Assert(user.CreateTime.UTC().String(), now.UTC().String())
+ })
+
+ gtest.Case(t, func() {
+ type User struct {
+ CreateTime *gtime.Time
+ }
+ now := time.Now()
+ user := new(User)
+ gconv.Struct(g.Map{
+ "create_time" : &now,
+ }, user)
+ gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
+ })
+
+ gtest.Case(t, func() {
+ type User struct {
+ CreateTime gtime.Time
+ }
+ now := time.Now()
+ user := new(User)
+ gconv.Struct(g.Map{
+ "create_time" : &now,
+ }, user)
+ gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
+ })
+
+ gtest.Case(t, func() {
+ type User struct {
+ CreateTime gtime.Time
+ }
+ now := time.Now()
+ user := new(User)
+ gconv.Struct(g.Map{
+ "create_time" : now,
+ }, user)
+ gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
+ })
+}
+
diff --git a/g/util/gconv/gconv_z_unit_time_test.go b/g/util/gconv/gconv_z_unit_time_test.go
index 7195f89f6..994b7856e 100644
--- a/g/util/gconv/gconv_z_unit_time_test.go
+++ b/g/util/gconv/gconv_z_unit_time_test.go
@@ -17,9 +17,9 @@ import (
func Test_Time(t *testing.T) {
gtest.Case(t, func() {
- t1 := "2011-10-10 01:02:03.456"
- gtest.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1))
- gtest.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time)
- gtest.AssertEQ(gconv.Duration(100), 100*time.Nanosecond)
- })
+ t1 := "2011-10-10 01:02:03.456"
+ gtest.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1))
+ gtest.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time)
+ gtest.AssertEQ(gconv.Duration(100), 100*time.Nanosecond)
+ })
}
diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go
index b664df319..625214bac 100644
--- a/g/util/gutil/gutil.go
+++ b/g/util/gutil/gutil.go
@@ -69,11 +69,17 @@ func Throw(exception interface{}) {
// TryCatch implements try...catch... logistics.
func TryCatch(try func(), catch ... func(exception interface{})) {
if len(catch) > 0 {
+ // If is given, it's used to handle the exception.
defer func() {
if e := recover(); e != nil {
catch[0](e)
}
}()
+ } else {
+ // If no function passed, it filters the exception.
+ defer func() {
+ recover()
+ }()
}
try()
}
diff --git a/geg/container/gpool/gpool_expirefunc.go b/geg/container/gpool/gpool_expirefunc.go
new file mode 100644
index 000000000..4341ef271
--- /dev/null
+++ b/geg/container/gpool/gpool_expirefunc.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gogf/gf/g/container/gpool"
+ "github.com/gogf/gf/g/net/gtcp"
+ "github.com/gogf/gf/g/os/glog"
+ "time"
+)
+
+func main() {
+ // 创建对象复用池,对象过期时间为3000毫秒,并给定创建及销毁方法
+ p := gpool.New(3000, func() (interface{}, error) {
+ return gtcp.NewConn("www.baidu.com:80")
+ }, func(i interface{}) {
+ glog.Println("expired")
+ i.(*gtcp.Conn).Close()
+ })
+ conn, err := p.Get()
+ if err != nil {
+ panic(err)
+ }
+ result, err := conn.(*gtcp.Conn).SendRecv([]byte("HEAD / HTTP/1.1\n\n"), -1)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(string(result))
+ // 丢回池中以便重复使用
+ p.Put(conn)
+ // 等待一定时间观察过期方法调用
+ time.Sleep(4*time.Second)
+}
diff --git a/geg/container/gqueue/gqueue3.go b/geg/container/gqueue/gqueue3.go
index 87ac9468d..5e25a6339 100644
--- a/geg/container/gqueue/gqueue3.go
+++ b/geg/container/gqueue/gqueue3.go
@@ -18,12 +18,12 @@ func main() {
// 消费者,不停读取队列数据并输出到终端
for {
select {
- case v := <-queue.C:
- if v != nil {
- fmt.Println(v)
- } else {
- return
- }
+ case v := <-queue.C:
+ if v != nil {
+ fmt.Println(v)
+ } else {
+ return
+ }
}
}
}
diff --git a/geg/database/orm/mysql/gdb_debug.go b/geg/database/orm/mysql/gdb_debug.go
index 6aee83cb7..f171f68a4 100644
--- a/geg/database/orm/mysql/gdb_debug.go
+++ b/geg/database/orm/mysql/gdb_debug.go
@@ -1,6 +1,7 @@
package main
import (
+ "fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/os/glog"
@@ -32,5 +33,7 @@ func main() {
db.Table("user").Data(g.Map{"name": "smith"}).Where("uid=?", 1).Save()
- //db.PrintQueriedSqls()
+ db.PrintQueriedSqls()
+
+ fmt.Println(db.GetLastSql())
}
diff --git a/geg/database/orm/mysql/gdb_update_union.go b/geg/database/orm/mysql/gdb_update_union.go
new file mode 100644
index 000000000..907c66b0d
--- /dev/null
+++ b/geg/database/orm/mysql/gdb_update_union.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gogf/gf/g"
+)
+
+func main() {
+ db := g.DB()
+ db.SetDebug(true)
+ result, err := db.Table("pw_passageway m,pw_template t").Data("t.status", 99).Where("m.templateId=t.id AND m.status = 0").Update()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(result.RowsAffected())
+}
diff --git a/geg/encoding/gparser.go b/geg/encoding/gparser/gparser.go
similarity index 100%
rename from geg/encoding/gparser.go
rename to geg/encoding/gparser/gparser.go
diff --git a/geg/encoding/gparser/gparser_xml.go b/geg/encoding/gparser/gparser_xml.go
new file mode 100644
index 000000000..375600951
--- /dev/null
+++ b/geg/encoding/gparser/gparser_xml.go
@@ -0,0 +1,21 @@
+package main
+
+import "github.com/gogf/gf/g/encoding/gparser"
+
+func main() {
+ xml := `
+
+ `
+ p, err := gparser.LoadContent([]byte(xml))
+ if err != nil {
+ panic(err)
+ }
+ p.Dump()
+}
diff --git a/geg/os/glog/glog_async1.go b/geg/os/glog/glog_async1.go
new file mode 100644
index 000000000..0825f2f39
--- /dev/null
+++ b/geg/os/glog/glog_async1.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+ "time"
+)
+
+func main() {
+ for i := 0; i < 10; i++ {
+ glog.Async().Print("async log", i)
+ }
+ time.Sleep(time.Second)
+}
diff --git a/geg/os/glog/glog_async2.go b/geg/os/glog/glog_async2.go
new file mode 100644
index 000000000..3170d0f23
--- /dev/null
+++ b/geg/os/glog/glog_async2.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+ "time"
+)
+
+func main() {
+ glog.SetAsync(true)
+ for i := 0; i < 10; i++ {
+ glog.Async().Print("async log", i)
+ }
+ time.Sleep(time.Second)
+}
diff --git a/geg/os/glog/glog_category.go b/geg/os/glog/glog_category.go
index 8a0d0d9c0..3df48a275 100644
--- a/geg/os/glog/glog_category.go
+++ b/geg/os/glog/glog_category.go
@@ -9,7 +9,7 @@ import (
func main() {
path := "/tmp/glog-cat"
glog.SetPath(path)
- glog.StdPrint(false).Cat("cat1").Cat("cat2").Println("test")
+ glog.Stdout(false).Cat("cat1").Cat("cat2").Println("test")
list, err := gfile.ScanDir(path, "*", true)
g.Dump(err)
g.Dump(list)
diff --git a/geg/os/glog/glog_debug.go b/geg/os/glog/glog_debug.go
index 259f0f8b4..faa4f1c9e 100644
--- a/geg/os/glog/glog_debug.go
+++ b/geg/os/glog/glog_debug.go
@@ -3,11 +3,12 @@ package main
import (
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
+ "github.com/gogf/gf/g/os/gtimer"
"time"
)
func main() {
- gtime.SetTimeout(3*time.Second, func() {
+ gtimer.SetTimeout(3*time.Second, func() {
glog.SetDebug(false)
})
for {
diff --git a/geg/os/glog/glog_error.go b/geg/os/glog/glog_error.go
index a2273b458..18738726c 100644
--- a/geg/os/glog/glog_error.go
+++ b/geg/os/glog/glog_error.go
@@ -7,4 +7,5 @@ import (
func main() {
//glog.SetPath("/tmp/")
glog.Error("This is error!")
+ glog.Errorf("This is error, %d!", 2)
}
diff --git a/geg/os/glog/glog_file.go b/geg/os/glog/glog_file.go
index 8a409863f..29797dff0 100644
--- a/geg/os/glog/glog_file.go
+++ b/geg/os/glog/glog_file.go
@@ -11,7 +11,7 @@ func main() {
l := glog.New()
path := "/tmp/glog"
l.SetPath(path)
- l.SetStdPrint(false)
+ l.SetStdoutPrint(false)
// 使用默认文件名称格式
l.Println("标准文件名称格式,使用当前时间时期")
// 通过SetFile设置文件名称格式
diff --git a/geg/os/glog/glog_flags.go b/geg/os/glog/glog_flags.go
new file mode 100644
index 000000000..b2561ee33
--- /dev/null
+++ b/geg/os/glog/glog_flags.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+)
+
+func main() {
+ l := glog.New()
+ l.SetFlags(glog.F_TIME_TIME|glog.F_FILE_SHORT)
+ l.Println("time and short line number")
+ l.SetFlags(glog.F_TIME_MILLI|glog.F_FILE_LONG)
+ l.Println("time with millisecond and long line number")
+ l.SetFlags(glog.F_TIME_STD|glog.F_FILE_LONG)
+ l.Println("standard time format and long line number")
+}
diff --git a/geg/os/glog/glog_json.go b/geg/os/glog/glog_json.go
new file mode 100644
index 000000000..75ceeb6dc
--- /dev/null
+++ b/geg/os/glog/glog_json.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/os/glog"
+)
+
+func main() {
+ glog.Debug(g.Map{"uid" : 100, "name" : "john"})
+
+ type User struct {
+ Uid int `json:"uid"`
+ Name string `json:"name"`
+ }
+ glog.Debug(User{100, "john"})
+}
diff --git a/geg/os/glog/glog_line.go b/geg/os/glog/glog_line.go
new file mode 100644
index 000000000..41d2e0779
--- /dev/null
+++ b/geg/os/glog/glog_line.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+)
+
+func main() {
+ glog.Line().Println("this is the short file name with its line number")
+ glog.Line(true).Println("lone file name with line number")
+}
diff --git a/geg/os/glog/glog_line2.go b/geg/os/glog/glog_line2.go
new file mode 100644
index 000000000..2e3730114
--- /dev/null
+++ b/geg/os/glog/glog_line2.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+)
+
+func PrintLog(content string) {
+ glog.Skip(1).Line().Println("line number with skip:", content)
+ glog.Line().Println("line number without skip:", content)
+}
+
+func main() {
+ PrintLog("just test")
+}
diff --git a/geg/os/glog/glog_prefix.go b/geg/os/glog/glog_prefix.go
new file mode 100644
index 000000000..7ce5a3d61
--- /dev/null
+++ b/geg/os/glog/glog_prefix.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+)
+
+func main() {
+ l := glog.New()
+ l.SetPrefix("[API]")
+ l.Println("hello world")
+ l.Error("error occurred")
+}
diff --git a/geg/os/glog/glog_stdout.go b/geg/os/glog/glog_stdout.go
new file mode 100644
index 000000000..3935519ff
--- /dev/null
+++ b/geg/os/glog/glog_stdout.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "github.com/gogf/gf/g/os/glog"
+ "sync"
+)
+
+func main() {
+ wg := sync.WaitGroup{}
+ c := make(chan struct{})
+ wg.Add(3000)
+ for i := 0; i < 3000; i++ {
+ go func() {
+ <-c
+ glog.Println("abcdefghijklmnopqrstuvwxyz1234567890")
+ wg.Done()
+ }()
+ }
+ close(c)
+ wg.Wait()
+}
diff --git a/geg/os/glog/glog_writer.go b/geg/os/glog/glog_writer.go
deleted file mode 100644
index 792678261..000000000
--- a/geg/os/glog/glog_writer.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package main
-
-import (
- "github.com/gogf/gf/g/os/glog"
-)
-
-func main() {
- w := glog.GetWriter()
- w.Write([]byte("hello"))
-
- glog.Path("/tmp/glog/test").GetWriter().Write([]byte("hello"))
-}
diff --git a/geg/os/glog/glog_writer_greylog.go b/geg/os/glog/glog_writer_greylog.go
new file mode 100644
index 000000000..7d154fa62
--- /dev/null
+++ b/geg/os/glog/glog_writer_greylog.go
@@ -0,0 +1,30 @@
+package main
+
+//import (
+// "github.com/gogf/gf/g/os/glog"
+// "github.com/robertkowalski/graylog-golang"
+//)
+//
+//type MyGrayLogWriter struct {
+// gelf *gelf.Gelf
+// logger *glog.Logger
+//}
+//
+//func (w *MyGrayLogWriter) Write(p []byte) (n int, err error) {
+// w.gelf.Send(p)
+// return w.logger.Write(p)
+//}
+//
+//func main() {
+// glog.SetWriter(&MyGrayLogWriter{
+// logger : glog.New(),
+// gelf : gelf.New(gelf.Config{
+// GraylogPort : 80,
+// GraylogHostname : "graylog-host.com",
+// Connection : "wan",
+// MaxChunkSizeWan : 42,
+// MaxChunkSizeLan : 1337,
+// }),
+// })
+// glog.Println("test log")
+//}
diff --git a/geg/os/glog/glog_writer_hook.go b/geg/os/glog/glog_writer_hook.go
new file mode 100644
index 000000000..02e999248
--- /dev/null
+++ b/geg/os/glog/glog_writer_hook.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gogf/gf/g/net/ghttp"
+ "github.com/gogf/gf/g/os/glog"
+ "github.com/gogf/gf/g/text/gregex"
+)
+
+type MyWriter struct {
+ logger *glog.Logger
+}
+
+func (w *MyWriter) Write(p []byte) (n int, err error) {
+ s := string(p)
+ if gregex.IsMatchString(`\[(PANI|FATA)\]`, s) {
+ fmt.Println("SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time!")
+ ghttp.PostContent("http://monitor.mydomain.com", s)
+ }
+ return w.logger.Write(p)
+}
+
+func main() {
+ glog.SetWriter(&MyWriter{
+ logger : glog.New(),
+ })
+ glog.Fatal("FATAL ERROR")
+}
diff --git a/geg/os/grpool/grpool1.go b/geg/os/grpool/grpool1.go
index 969098184..4aec3d922 100644
--- a/geg/os/grpool/grpool1.go
+++ b/geg/os/grpool/grpool1.go
@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/gogf/gf/g/os/grpool"
- "github.com/gogf/gf/g/os/gtime"
+ "github.com/gogf/gf/g/os/gtimer"
"time"
)
@@ -18,11 +18,11 @@ func main() {
}
fmt.Println("worker:", pool.Size())
fmt.Println(" jobs:", pool.Jobs())
- gtime.SetInterval(time.Second, func() bool {
+ gtimer.SetInterval(time.Second, func() {
fmt.Println("worker:", pool.Size())
fmt.Println(" jobs:", pool.Jobs())
fmt.Println()
- return true
+ gtimer.Exit()
})
select {}
diff --git a/geg/os/grpool/grpool2.go b/geg/os/grpool/grpool2.go
index b2eb2d881..72bfbb0b5 100644
--- a/geg/os/grpool/grpool2.go
+++ b/geg/os/grpool/grpool2.go
@@ -10,8 +10,9 @@ func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
+ v := i
grpool.Add(func() {
- fmt.Println(i)
+ fmt.Println(v)
wg.Done()
})
}
diff --git a/geg/os/grpool/grpool3.go b/geg/os/grpool/grpool3.go
index 4a2fd6773..343700108 100644
--- a/geg/os/grpool/grpool3.go
+++ b/geg/os/grpool/grpool3.go
@@ -2,17 +2,20 @@ package main
import (
"fmt"
+ "github.com/gogf/gf/g/os/grpool"
"sync"
)
func main() {
+ p := grpool.New(1)
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
- go func(v int) {
+ v := i
+ p.Add(func() {
fmt.Println(v)
wg.Done()
- }(i)
+ })
}
wg.Wait()
}
diff --git a/geg/os/grpool/grpool4.go b/geg/os/grpool/grpool4.go
new file mode 100644
index 000000000..4a2fd6773
--- /dev/null
+++ b/geg/os/grpool/grpool4.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+func main() {
+ wg := sync.WaitGroup{}
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func(v int) {
+ fmt.Println(v)
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+}
diff --git a/geg/os/grpool/grpool5.go b/geg/os/grpool/grpool5.go
new file mode 100644
index 000000000..3d12ee5db
--- /dev/null
+++ b/geg/os/grpool/grpool5.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gogf/gf/g/os/grpool"
+ "time"
+)
+
+func main() {
+ p := grpool.New(1)
+ for i := 0; i < 10; i++ {
+ v := i
+ p.Add(func() {
+ fmt.Println(v)
+ time.Sleep(3*time.Second)
+ })
+ }
+ time.Sleep(time.Minute)
+}
diff --git a/geg/other/test.go b/geg/other/test.go
index 47632c222..41ec4db51 100644
--- a/geg/other/test.go
+++ b/geg/other/test.go
@@ -2,29 +2,27 @@ package main
import (
"fmt"
+
+ "github.com/gogf/gf"
+ "github.com/gogf/gf/g/net/ghttp"
+
"github.com/gogf/gf/g"
- "github.com/gogf/gf/g/os/gfile"
- "github.com/gogf/gf/g/util/gconv"
)
func main() {
- fmt.Println(gfile.Dir("/"))
- return
- a := []int{1,2,3}
- fmt.Println(a[:0])
- return
- type Person struct{
- Name string
- }
- type Staff struct{
- Person
- StaffId int
- }
- staff := &Staff{}
- params := g.Map{
- "Name" : "john",
- "StaffId" : "10000",
- }
- gconv.Struct(params, staff)
- fmt.Println(staff)
+ // fmt.Print(g.)
+ fmt.Println(gf.VERSION)
+ s := g.Server()
+
+ s.BindHandler("/status/:status", func(r *ghttp.Request) {
+ r.Response.Write("woops, status ", r.Get("status"), " found")
+ })
+ s.BindStatusHandler(404, func(r *ghttp.Request) {
+ r.Response.RedirectTo("/status/404")
+ })
+
+ s.SetErrorLogEnabled(true)
+ s.SetAccessLogEnabled(true)
+ s.SetPort(8890)
+ s.Run()
}
\ No newline at end of file
diff --git a/geg/other/try-catch.go b/geg/other/try-catch.go
index 085b77bb0..a0bb032c7 100644
--- a/geg/other/try-catch.go
+++ b/geg/other/try-catch.go
@@ -7,9 +7,15 @@ import (
func main() {
g.TryCatch(func() {
- glog.Printfln("hello")
+ glog.Println("hello")
g.Throw("exception")
- glog.Printfln("world")
+ glog.Println("world")
+ })
+
+ g.TryCatch(func() {
+ glog.Println("hello")
+ g.Throw("exception")
+ glog.Println("world")
}, func(exception interface{}) {
glog.Error(exception)
})
diff --git a/geg/util/gconv/gconv_map_deep.go b/geg/util/gconv/gconv_map_deep.go
new file mode 100644
index 000000000..479efb41d
--- /dev/null
+++ b/geg/util/gconv/gconv_map_deep.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/util/gconv"
+)
+
+func main() {
+ type Ids struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ }
+ type Base struct {
+ Ids
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ user := new(User)
+ user.Id = 1
+ user.Uid = 100
+ user.Nickname = "John"
+ user.Passport = "johng"
+ user.Password = "123456"
+ user.CreateTime = "2019"
+ g.Dump(gconv.MapDeep(user))
+}
diff --git a/geg/util/gconv/gconv_struct_deep.go b/geg/util/gconv/gconv_struct_deep.go
new file mode 100644
index 000000000..ed9b228b2
--- /dev/null
+++ b/geg/util/gconv/gconv_struct_deep.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "github.com/gogf/gf/g"
+ "github.com/gogf/gf/g/util/gconv"
+)
+
+func main() {
+ type Ids struct {
+ Id int `json:"id"`
+ Uid int `json:"uid"`
+ }
+ type Base struct {
+ Ids
+ CreateTime string `json:"create_time"`
+ }
+ type User struct {
+ Base
+ Passport string `json:"passport"`
+ Password string `json:"password"`
+ Nickname string `json:"nickname"`
+ }
+ data := g.Map{
+ "id" : 1,
+ "uid" : 100,
+ "passport" : "johng",
+ "password" : "123456",
+ "nickname" : "John",
+ "create_time" : "2019",
+ }
+ user := new(User)
+ gconv.StructDeep(data, user)
+ g.Dump(user)
+}
diff --git a/version.go b/version.go
index 3a5151943..904859e1e 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
package gf
-const VERSION = "v1.6.13"
+const VERSION = "v1.6.16"
const AUTHORS = "john"