diff --git a/.example/net/ghttp/server/session/basic/session.go b/.example/net/ghttp/server/session/basic/session.go index c7a21a684..d7ae0e1ec 100644 --- a/.example/net/ghttp/server/session/basic/session.go +++ b/.example/net/ghttp/server/session/basic/session.go @@ -8,6 +8,7 @@ import ( func main() { s := g.Server() + s.SetSessionCookieMaxAge(0) s.Group("/", func(group *ghttp.RouterGroup) { group.GET("/set", func(r *ghttp.Request) { r.Session.Set("time", gtime.Timestamp()) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 033a9666d..12c0d25c1 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -83,9 +83,13 @@ func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { sql, args = formatSql(sql, args) sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args) + ctx := c.DB.GetCtx() + if c.GetConfig().QueryTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) + } if c.DB.GetDebug() { mTime1 := gtime.TimestampMilli() - rows, err = link.QueryContext(c.DB.GetCtx(), sql, args...) + rows, err = link.QueryContext(ctx, sql, args...) mTime2 := gtime.TimestampMilli() s := &Sql{ Sql: sql, @@ -98,7 +102,7 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro } c.writeSqlToLogger(s) } else { - rows, err = link.QueryContext(c.DB.GetCtx(), sql, args...) + rows, err = link.QueryContext(ctx, sql, args...) } if err == nil { return rows, nil @@ -123,10 +127,14 @@ func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err err func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) { sql, args = formatSql(sql, args) sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args) + ctx := c.DB.GetCtx() + if c.GetConfig().ExecTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) + } if c.DB.GetDebug() { mTime1 := gtime.TimestampMilli() if !c.DB.GetDryRun() { - result, err = link.ExecContext(c.DB.GetCtx(), sql, args...) + result, err = link.ExecContext(ctx, sql, args...) } else { result = new(SqlResult) } @@ -143,7 +151,7 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re c.writeSqlToLogger(s) } else { if !c.DB.GetDryRun() { - result, err = link.ExecContext(c.DB.GetCtx(), sql, args...) + result, err = link.ExecContext(ctx, sql, args...) } else { result = new(SqlResult) } @@ -178,7 +186,11 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error) { // doPrepare calls prepare function on given link object and returns the statement object. func (c *Core) DoPrepare(link Link, sql string) (*sql.Stmt, error) { - return link.PrepareContext(c.DB.GetCtx(), sql) + ctx := c.DB.GetCtx() + if c.GetConfig().QueryTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) + } + return link.PrepareContext(ctx, sql) } // GetAll queries and returns data records from database. @@ -320,7 +332,11 @@ func (c *Core) Begin() (*TX, error) { if master, err := c.DB.Master(); err != nil { return nil, err } else { - if tx, err := master.Begin(); err == nil { + ctx := c.DB.GetCtx() + if c.GetConfig().TranTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().TranTimeout) + } + if tx, err := master.BeginTx(ctx, nil); err == nil { return &TX{ db: c.DB, tx: tx, diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 85a9ee888..cbc4e4f9f 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -16,8 +16,7 @@ import ( ) const ( - DEFAULT_GROUP_NAME = "default" // Deprecated, use DefaultGroupName instead. - DefaultGroupName = "default" // Default group name. + DefaultGroupName = "default" // Default group name. ) // Config is the configuration management object. @@ -28,26 +27,30 @@ type ConfigGroup []ConfigNode // ConfigNode is configuration for one node. type ConfigNode struct { - Host string // Host of server, ip or domain like: 127.0.0.1, localhost - Port string // Port, it's commonly 3306. - User string // Authentication username. - Pass string // Authentication password. - Name string // Default used database name. - Type string // Database type: mysql, sqlite, mssql, pgsql, oracle. - Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. - Debug bool // (Optional) Debug mode enables debug information logging and output. - Prefix string // (Optional) Table prefix. - DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. - Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node. - Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database. - LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. - MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool. - MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool. - MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool. - CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime. - UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime. - DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime. - TimeMaintainDisabled bool // (Optional) Disable the automatic time maintaining feature. + Host string `json:"host"` // Host of server, ip or domain like: 127.0.0.1, localhost + Port string `json:"port"` // Port, it's commonly 3306. + User string `json:"user"` // Authentication username. + Pass string `json:"pass"` // Authentication password. + Name string `json:"name"` // Default used database name. + Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle. + Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. + Debug bool `json:"debug"` // (Optional) Debug mode enables debug information logging and output. + Prefix string `json:"prefix"` // (Optional) Table prefix. + DryRun bool `json:"dryRun"` // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. + Weight int `json:"weight"` // (Optional) Weight for load balance calculating, it's useless if there's just one node. + Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database. + LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. + MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool. + MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool. + MaxConnLifetime time.Duration `json:"maxLifetime"` // (Optional) Max connection TTL configuration for underlying connection pool. + QueryTimeout time.Duration `json:"queryTimeout"` // (Optional) Max query time for per dql. + ExecTimeout time.Duration `json:"execTimeout"` // (Optional) Max exec time for dml. + TranTimeout time.Duration `json:"tranTimeout"` // (Optional) Max exec time time for a transaction. + PrepareTimeout time.Duration `json:"prepareTimeout"` // (Optional) Max exec time time for prepare operation. + CreatedAt string `json:"createdAt"` // (Optional) The filed name of table for automatic-filled created datetime. + UpdatedAt string `json:"updatedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. + DeletedAt string `json:"deletedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. + TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature. } // configs is internal used configuration object. diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 78c9ac47b..5e7c9b7bf 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -48,9 +48,9 @@ type apiMapStrAny interface { } const ( - ORM_TAG_FOR_STRUCT = "orm" - ORM_TAG_FOR_UNIQUE = "unique" - ORM_TAG_FOR_PRIMARY = "primary" + OrmTagForStruct = "orm" + OrmTagForUnique = "unique" + OrmTagForPrimary = "primary" ) var ( @@ -58,7 +58,7 @@ var ( quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // Priority tags for struct converting for orm field mapping. - structTagPriority = append([]string{ORM_TAG_FOR_STRUCT}, gconv.StructTagPriority...) + structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...) ) // ListItemValues retrieves and returns the elements of all item struct/map with key . @@ -315,14 +315,14 @@ func doQuoteString(s, charLeft, charRight string) string { // GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer. // This function automatically retrieves primary or unique field and its attribute value as condition. func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) { - tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + tagField, err := structs.TagFields(pointer, []string{OrmTagForStruct}) if err != nil { return "", nil, err } array := ([]string)(nil) for _, field := range tagField { array = strings.Split(field.TagValue, ",") - if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) { + if len(array) > 1 && gstr.InArray([]string{OrmTagForUnique, OrmTagForPrimary}, array[1]) { return array[0], []interface{}{field.Value()}, nil } if len(where) > 0 { @@ -336,14 +336,14 @@ func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interf // GetPrimaryKey retrieves and returns primary key field name from given struct. func GetPrimaryKey(pointer interface{}) (string, error) { - tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + tagField, err := structs.TagFields(pointer, []string{OrmTagForStruct}) if err != nil { return "", err } array := ([]string)(nil) for _, field := range tagField { array = strings.Split(field.TagValue, ",") - if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY { + if len(array) > 1 && array[1] == OrmTagForPrimary { return array[0], nil } } @@ -741,7 +741,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { // convertMapToStruct maps the to given struct. // Note that the given parameter should be a pointer to s struct. func convertMapToStruct(data map[string]interface{}, pointer interface{}) error { - tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}) + tagNameMap, err := structs.TagMapName(pointer, []string{OrmTagForStruct}) if err != nil { return err } diff --git a/database/gdb/gdb_type_record.go b/database/gdb/gdb_type_record.go index 7801e18fc..13edcf385 100644 --- a/database/gdb/gdb_type_record.go +++ b/database/gdb/gdb_type_record.go @@ -11,6 +11,7 @@ import ( "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/encoding/gparser" "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/util/gconv" "reflect" ) @@ -48,7 +49,10 @@ func (r Record) GMap() *gmap.StrAnyMap { func (r Record) Struct(pointer interface{}) error { // If the record is empty, it returns error. if r.IsEmpty() { - return sql.ErrNoRows + if !empty.IsNil(pointer, true) { + return sql.ErrNoRows + } + return nil } // Special handling for parameter type: reflect.Value if _, ok := pointer.(reflect.Value); ok { diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 697f78382..a462f3d4b 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -981,6 +981,18 @@ func Test_Model_Struct(t *testing.T) { err := db.Table(table).Where("id=-1").Struct(user) t.Assert(err, sql.ErrNoRows) }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var user *User + err := db.Table(table).Where("id=-1").Struct(&user) + t.Assert(err, nil) + }) } func Test_Model_Struct_CustomType(t *testing.T) { @@ -2135,6 +2147,27 @@ func Test_Model_Option_List(t *testing.T) { }) } +func Test_Model_OmitEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := fmt.Sprintf(`table_%s`, gtime.TimestampNanoStr()) + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + _, err := db.Table(table).OmitEmpty().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.AssertNE(err, nil) + }) +} + func Test_Model_Option_Where(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createInitTable() diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index d11648aa3..baf5c8f7a 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -7,6 +7,7 @@ package gdb_test import ( + "database/sql" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" @@ -252,24 +253,25 @@ func Test_Struct_Empty(t *testing.T) { } gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) user := new(User) - t.AssertNE(one.Struct(user), nil) + err := db.Table(table).Where("id=100").Struct(user) + t.Assert(err, sql.ErrNoRows) + t.AssertNE(user, nil) }) gtest.C(t, func(t *gtest.T) { one, err := db.Table(table).Where("id=100").One() t.Assert(err, nil) var user *User - t.AssertNE(one.Struct(&user), nil) + t.Assert(one.Struct(&user), nil) + t.Assert(user, nil) }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) var user *User - t.AssertNE(one.Struct(user), nil) + err := db.Table(table).Where("id=100").Struct(&user) + t.Assert(err, nil) + t.Assert(user, nil) }) } diff --git a/database/gredis/gredis.go b/database/gredis/gredis.go index a489c325f..a19b7e26e 100644 --- a/database/gredis/gredis.go +++ b/database/gredis/gredis.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -36,17 +36,17 @@ type Conn struct { // Redis configuration. type Config struct { - Host string - Port int - Db int - Pass string // Password for AUTH. - MaxIdle int // Maximum number of connections allowed to be idle (default is 10) - MaxActive int // Maximum number of connections limit (default is 0 means no limit). - IdleTimeout time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0) - MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0) - ConnectTimeout time.Duration // Dial connection timeout. - TLS bool // Specifies the config to use when a TLS connection is dialed. - TLSSkipVerify bool // Disables server name verification when connecting over TLS + Host string `json:"host"` + Port int `json:"port"` + Db int `json:"db"` + Pass string `json:"pass"` // Password for AUTH. + MaxIdle int `json:"maxIdle"` // Maximum number of connections allowed to be idle (default is 10) + MaxActive int `json:"maxActive"` // Maximum number of connections limit (default is 0 means no limit). + IdleTimeout time.Duration `json:"idleTimeout"` // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0) + MaxConnLifetime time.Duration `json:"maxConnLifetime"` // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0) + ConnectTimeout time.Duration `json:"connectTimeout"` // Dial connection timeout. + TLS bool `json:"tls"` // Specifies the config to use when a TLS connection is dialed. + TLSSkipVerify bool `json:"tlsSkipVerify"` // Disables server name verification when connecting over TLS } // Pool statistics. diff --git a/database/gredis/gredis_z_example_test.go b/database/gredis/gredis_z_example_test.go index 499d31b34..47cb81487 100644 --- a/database/gredis/gredis_z_example_test.go +++ b/database/gredis/gredis_z_example_test.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gutil" ) func Example_autoMarshalUnmarshalMap() { @@ -101,7 +102,7 @@ func Example_autoMarshalUnmarshalStructSlice() { fmt.Println(users2) } -func Example_hashSet() { +func Example_hSet() { var ( err error result *gvar.Var @@ -124,3 +125,54 @@ func Example_hashSet() { // May Output: // map[id:10000 name:john] } + +func Example_hMSet_Map() { + var ( + key = "user_100" + data = g.Map{ + "name": "gf", + "sex": 0, + "score": 100, + } + ) + _, err := g.Redis().Do("HMSET", append(g.Slice{key}, gutil.MapToSlice(data)...)...) + if err != nil { + g.Log().Fatal(err) + } + v, err := g.Redis().DoVar("HMGET", key, "name") + if err != nil { + g.Log().Fatal(err) + } + fmt.Println(v.Slice()) + + // May Output: + // [gf] +} + +func Example_hMSet_Struct() { + type User struct { + Name string `json:"name"` + Sex int `json:"sex"` + Score int `json:"score"` + } + var ( + key = "user_100" + data = &User{ + Name: "gf", + Sex: 0, + Score: 100, + } + ) + _, err := g.Redis().Do("HMSET", append(g.Slice{key}, gutil.StructToSlice(data)...)...) + if err != nil { + g.Log().Fatal(err) + } + v, err := g.Redis().DoVar("HMGET", key, "name") + if err != nil { + g.Log().Fatal(err) + } + fmt.Println(v.Slice()) + + // May Output: + // ["gf"] +} diff --git a/database/gredis/gredis_z_unit_test.go b/database/gredis/gredis_z_unit_test.go index e13d1934c..dc50185b6 100644 --- a/database/gredis/gredis_z_unit_test.go +++ b/database/gredis/gredis_z_unit_test.go @@ -10,6 +10,7 @@ import ( "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/guid" + "github.com/gogf/gf/util/gutil" "testing" "time" @@ -255,9 +256,11 @@ func Test_HSet(t *testing.T) { func Test_HGetAll1(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var err error - redis := gredis.New(config) - key := guid.S() + var ( + err error + key = guid.S() + redis = gredis.New(config) + ) defer redis.Do("DEL", key) _, err = redis.Do("HSET", key, "id", 100) @@ -296,6 +299,54 @@ func Test_HGetAll2(t *testing.T) { }) } +func Test_HMSet(t *testing.T) { + // map + gtest.C(t, func(t *gtest.T) { + var ( + err error + key = guid.S() + redis = gredis.New(config) + data = g.Map{ + "name": "gf", + "sex": 0, + "score": 100, + } + ) + defer redis.Do("DEL", key) + + _, err = redis.Do("HMSET", append(g.Slice{key}, gutil.MapToSlice(data)...)...) + t.Assert(err, nil) + v, err := redis.DoVar("HMGET", key, "name") + t.Assert(err, nil) + t.Assert(v.Slice(), g.Slice{data["name"]}) + }) + // struct + gtest.C(t, func(t *gtest.T) { + type User struct { + Name string `json:"name"` + Sex int `json:"sex"` + Score int `json:"score"` + } + var ( + err error + key = guid.S() + redis = gredis.New(config) + data = &User{ + Name: "gf", + Sex: 0, + Score: 100, + } + ) + defer redis.Do("DEL", key) + + _, err = redis.Do("HMSET", append(g.Slice{key}, gutil.StructToSlice(data)...)...) + t.Assert(err, nil) + v, err := redis.DoVar("HMGET", key, "name") + t.Assert(err, nil) + t.Assert(v.Slice(), g.Slice{data.Name}) + }) +} + func Test_Auto_Marshal(t *testing.T) { var ( err error diff --git a/errors/gerror/gerror_error.go b/errors/gerror/gerror_error.go index dfbd5183f..cfe301486 100644 --- a/errors/gerror/gerror_error.go +++ b/errors/gerror/gerror_error.go @@ -169,6 +169,12 @@ func (err *Error) Next() error { return err.error } +// MarshalJSON implements the interface MarshalJSON for json.Marshal. +// Note that do not use pointer as its receiver here. +func (err *Error) MarshalJSON() ([]byte, error) { + return []byte(`"` + err.Error() + `"`), nil +} + // formatSubStack formats the stack for error. func formatSubStack(st stack, buffer *bytes.Buffer) { index := 1 diff --git a/errors/gerror/gerror_z_unit_test.go b/errors/gerror/gerror_z_unit_test.go index 254f5c159..e60243aa7 100644 --- a/errors/gerror/gerror_z_unit_test.go +++ b/errors/gerror/gerror_z_unit_test.go @@ -9,6 +9,7 @@ package gerror_test import ( "errors" "fmt" + "github.com/gogf/gf/internal/json" "testing" "github.com/gogf/gf/errors/gerror" @@ -308,3 +309,12 @@ func Test_Code(t *testing.T) { t.Assert(err.Error(), "3: 2: 1") }) } + +func Test_Json(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := gerror.Wrap(gerror.New("1"), "2") + b, e := json.Marshal(err) + t.Assert(e, nil) + t.Assert(string(b), `"2: 1"`) + }) +} diff --git a/frame/g/g_func.go b/frame/g/g_func.go index d99a77ff3..7b1afc730 100644 --- a/frame/g/g_func.go +++ b/frame/g/g_func.go @@ -52,9 +52,12 @@ func TryCatch(try func(), catch ...func(exception error)) { } // IsNil checks whether given is nil. +// Parameter is used for tracing to the source variable if given is type +// of a pinter that also points to a pointer. It returns nil if the source is nil when +// is true. // Note that it might use reflect feature which affects performance a little bit. -func IsNil(value interface{}) bool { - return empty.IsNil(value) +func IsNil(value interface{}, traceSource ...bool) bool { + return empty.IsNil(value, traceSource...) } // IsEmpty checks whether given empty. diff --git a/internal/empty/empty.go b/internal/empty/empty.go index ae0beb57a..2e59ea5a0 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -1,10 +1,10 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame gf Author(https://goframe.org). 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 empty provides functions for checking empty variables. +// Package empty provides functions for checking empty/nil variables. package empty import ( @@ -152,8 +152,11 @@ func IsEmpty(value interface{}) bool { } // IsNil checks whether given is nil. +// Parameter is used for tracing to the source variable if given is type +// of a pinter that also points to a pointer. It returns nil if the source is nil when +// is true. // Note that it might use reflect feature which affects performance a little bit. -func IsNil(value interface{}) bool { +func IsNil(value interface{}, traceSource ...bool) bool { if value == nil { return true } @@ -168,10 +171,24 @@ func IsNil(value interface{}) bool { reflect.Map, reflect.Slice, reflect.Func, - reflect.Ptr, reflect.Interface, reflect.UnsafePointer: - return rv.IsNil() + return !rv.IsValid() || rv.IsNil() + + case reflect.Ptr: + if len(traceSource) > 0 && traceSource[0] { + for rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + if !rv.IsValid() { + return true + } + if rv.Kind() == reflect.Ptr { + return rv.IsNil() + } + } else { + return !rv.IsValid() || rv.IsNil() + } } return false } diff --git a/internal/empty/empty_test.go b/internal/empty/empty_test.go index 520aa64ee..2a4e6ecbd 100644 --- a/internal/empty/empty_test.go +++ b/internal/empty/empty_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -108,3 +108,22 @@ func TestIsEmpty(t *testing.T) { t.Assert(empty.IsEmpty(tmpF6), false) }) } + +func TestIsNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(empty.IsNil(nil), true) + }) + gtest.C(t, func(t *gtest.T) { + var i int + t.Assert(empty.IsNil(i), false) + }) + gtest.C(t, func(t *gtest.T) { + var i *int + t.Assert(empty.IsNil(i), true) + }) + gtest.C(t, func(t *gtest.T) { + var i *int + t.Assert(empty.IsNil(&i), false) + t.Assert(empty.IsNil(&i, true), true) + }) +} diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 770f07d19..0fa1e3445 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -350,7 +350,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.config.HTTPSAddr = s.config.Address s.config.Address = "" } else { - s.config.HTTPSAddr = gDEFAULT_HTTPS_ADDR + s.config.HTTPSAddr = defaultHttpsAddr } } httpsEnabled = len(s.config.HTTPSAddr) > 0 @@ -385,7 +385,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { } // HTTP if !httpsEnabled && len(s.config.Address) == 0 { - s.config.Address = gDEFAULT_HTTP_ADDR + s.config.Address = defaultHttpAddr } var array []string if v, ok := fdMap["http"]; ok && len(v) > 0 { diff --git a/net/ghttp/ghttp_server_admin_process.go b/net/ghttp/ghttp_server_admin_process.go index 18299e8b6..577a0168d 100644 --- a/net/ghttp/ghttp_server_admin_process.go +++ b/net/ghttp/ghttp_server_admin_process.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -233,8 +233,13 @@ func shutdownWebServers(signal ...string) { } } -// gracefulShutdownWebServers gracefully shuts down all servers. -func gracefulShutdownWebServers() { +// shutdownWebServersGracefully gracefully shuts down all servers. +func shutdownWebServersGracefully(signal ...string) { + if len(signal) > 0 { + glog.Printf("%d: server gracefully shutting down by signal: %s", gproc.Pid(), signal[0]) + } else { + glog.Printf("%d: server gracefully shutting down by api", gproc.Pid()) + } serverMapping.RLockFunc(func(m map[string]interface{}) { for _, v := range m { for _, s := range v.(*Server).servers { @@ -262,7 +267,7 @@ func handleProcessMessage() { if msg := gproc.Receive(adminGProcCommGroup); msg != nil { if bytes.EqualFold(msg.Data, []byte("exit")) { intlog.Printf("%d: process message: exit", gproc.Pid()) - gracefulShutdownWebServers() + shutdownWebServersGracefully() allDoneChan <- struct{}{} intlog.Printf("%d: process message: exit done", gproc.Pid()) return diff --git a/net/ghttp/ghttp_server_admin_unix.go b/net/ghttp/ghttp_server_admin_unix.go index 19fff62af..de96d89e5 100644 --- a/net/ghttp/ghttp_server_admin_unix.go +++ b/net/ghttp/ghttp_server_admin_unix.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright 2017 gf Author(https://goframe.org). 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, @@ -35,14 +35,22 @@ func handleProcessSignal() { sig = <-procSignalChan intlog.Printf(`signal received: %s`, sig.String()) switch sig { - // Stop the servers. - case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGABRT: + // Shutdown the servers. + case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGABRT: shutdownWebServers(sig.String()) return + // Shutdown the servers gracefully. + // Especially from K8S when running server in POD. + case syscall.SIGTERM: + shutdownWebServersGracefully(sig.String()) + return + // Restart the servers. case syscall.SIGUSR1: - restartWebServers(sig.String()) + if err := restartWebServers(sig.String()); err != nil { + intlog.Error(err) + } return default: diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 9a8a7aa9b..760b17b43 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -27,12 +27,12 @@ import ( ) const ( - gDEFAULT_HTTP_ADDR = ":80" // Default listening port for HTTP. - gDEFAULT_HTTPS_ADDR = ":443" // Default listening port for HTTPS. - URI_TYPE_DEFAULT = 0 // Method name to URI converting type, which converts name to its lower case and joins the words using char '-'. - URI_TYPE_FULLNAME = 1 // Method name to URI converting type, which does no converting to the method name. - URI_TYPE_ALLLOWER = 2 // Method name to URI converting type, which converts name to its lower case. - URI_TYPE_CAMEL = 3 // Method name to URI converting type, which converts name to its camel case. + defaultHttpAddr = ":80" // Default listening port for HTTP. + defaultHttpsAddr = ":443" // Default listening port for HTTPS. + URI_TYPE_DEFAULT = 0 // Method name to URI converting type, which converts name to its lower case and joins the words using char '-'. + URI_TYPE_FULLNAME = 1 // Method name to URI converting type, which does no converting to the method name. + URI_TYPE_ALLLOWER = 2 // Method name to URI converting type, which converts name to its lower case. + URI_TYPE_CAMEL = 3 // Method name to URI converting type, which converts name to its camel case. ) // ServerConfig is the HTTP Server configuration manager. @@ -43,16 +43,16 @@ type ServerConfig struct { // Address specifies the server listening address like "port" or ":port", // multiple addresses joined using ','. - Address string + Address string `json:"address"` // HTTPSAddr specifies the HTTPS addresses, multiple addresses joined using char ','. - HTTPSAddr string + HTTPSAddr string `json:"httpsAddr"` // HTTPSCertPath specifies certification file path for HTTPS service. - HTTPSCertPath string + HTTPSCertPath string `json:"httpsCertPath"` // HTTPSKeyPath specifies the key file path for HTTPS service. - HTTPSKeyPath string + HTTPSKeyPath string `json:"httpsKeyPath"` // TLSConfig optionally provides a TLS configuration for use // by ServeTLS and ListenAndServeTLS. Note that this value is @@ -61,10 +61,10 @@ type ServerConfig struct { // tls.Config.SetSessionTicketKeys. To use // SetSessionTicketKeys, use Server.Serve with a TLS Listener // instead. - TLSConfig *tls.Config + TLSConfig *tls.Config `json:"tlsConfig"` // Handler the handler for HTTP request. - Handler http.Handler + Handler http.Handler `json:"-"` // ReadTimeout is the maximum duration for reading the entire // request, including the body. @@ -73,19 +73,19 @@ type ServerConfig struct { // decisions on each request body's acceptable deadline or // upload rate, most users will prefer to use // ReadHeaderTimeout. It is valid to use them both. - ReadTimeout time.Duration + ReadTimeout time.Duration `json:"readTimeout"` // WriteTimeout is the maximum duration before timing out // writes of the response. It is reset whenever a new // request's header is read. Like ReadTimeout, it does not // let Handlers make decisions on a per-request basis. - WriteTimeout time.Duration + WriteTimeout time.Duration `json:"writeTimeout"` // IdleTimeout is the maximum amount of time to wait for the // next request when keep-alives are enabled. If IdleTimeout // is zero, the value of ReadTimeout is used. If both are // zero, there is no timeout. - IdleTimeout time.Duration + IdleTimeout time.Duration `json:"idleTimeout"` // MaxHeaderBytes controls the maximum number of bytes the // server will read parsing the request header's keys and @@ -94,98 +94,102 @@ type ServerConfig struct { // // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 10240 bytes in default. - MaxHeaderBytes int + MaxHeaderBytes int `json:"maxHeaderBytes"` // KeepAlive enables HTTP keep-alive. - KeepAlive bool + KeepAlive bool `json:"keepAlive"` // ServerAgent specifies the server agent information, which is wrote to // HTTP response header as "Server". - ServerAgent string + ServerAgent string `json:"serverAgent"` // View specifies the default template view object for the server. - View *gview.View + View *gview.View `json:"view"` // ================================== // Static. // ================================== // Rewrites specifies the URI rewrite rules map. - Rewrites map[string]string + Rewrites map[string]string `json:"rewrites"` // IndexFiles specifies the index files for static folder. - IndexFiles []string + IndexFiles []string `json:"indexFiles"` // IndexFolder specifies if listing sub-files when requesting folder. // The server responses HTTP status code 403 if it is false. - IndexFolder bool + IndexFolder bool `json:"indexFolder"` // ServerRoot specifies the root directory for static service. - ServerRoot string + ServerRoot string `json:"serverRoot"` // SearchPaths specifies additional searching directories for static service. - SearchPaths []string + SearchPaths []string `json:"searchPaths"` // StaticPaths specifies URI to directory mapping array. - StaticPaths []staticPathItem + StaticPaths []staticPathItem `json:"staticPaths"` // FileServerEnabled is the global switch for static service. // It is automatically set enabled if any static path is set. - FileServerEnabled bool + FileServerEnabled bool `json:"fileServerEnabled"` // ================================== // Cookie. // ================================== // CookieMaxAge specifies the max TTL for cookie items. - CookieMaxAge time.Duration + CookieMaxAge time.Duration `json:"cookieMaxAge"` // CookiePath specifies cookie path. // It also affects the default storage for session id. - CookiePath string + CookiePath string `json:"cookiePath"` // CookieDomain specifies cookie domain. // It also affects the default storage for session id. - CookieDomain string + CookieDomain string `json:"cookieDomain"` // ================================== // Session. // ================================== - // SessionMaxAge specifies max TTL for session items. - SessionMaxAge time.Duration - // SessionIdName specifies the session id name. - SessionIdName string + SessionIdName string `json:"sessionIdName"` - // SessionCookieOutput specifies whether automatic outputting session id to cookie. - SessionCookieOutput bool + // SessionMaxAge specifies max TTL for session items. + SessionMaxAge time.Duration `json:"sessionMaxAge"` // SessionPath specifies the session storage directory path for storing session files. // It only makes sense if the session storage is type of file storage. - SessionPath string + SessionPath string `json:"sessionPath"` // SessionStorage specifies the session storage. - SessionStorage gsession.Storage + SessionStorage gsession.Storage `json:"sessionStorage"` + + // SessionCookieMaxAge specifies the cookie ttl for session id. + // It it is set 0, it means it expires along with browser session. + SessionCookieMaxAge time.Duration `json:"sessionCookieMaxAge"` + + // SessionCookieOutput specifies whether automatic outputting session id to cookie. + SessionCookieOutput bool `json:"sessionCookieOutput"` // ================================== // Logging. // ================================== - Logger *glog.Logger // Logger specifies the logger for server. - LogPath string // LogPath specifies the directory for storing logging files. - LogLevel string // LogLevel specifies the logging level for logger. - LogStdout bool // LogStdout specifies whether printing logging content to stdout. - ErrorStack bool // ErrorStack specifies whether logging stack information when error. - ErrorLogEnabled bool // ErrorLogEnabled enables error logging content to files. - ErrorLogPattern string // ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log - AccessLogEnabled bool // AccessLogEnabled enables access logging content to files. - AccessLogPattern string // AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log + Logger *glog.Logger `json:"logger"` // Logger specifies the logger for server. + LogPath string `json:"logPath"` // LogPath specifies the directory for storing logging files. + LogLevel string `json:"logLevel"` // LogLevel specifies the logging level for logger. + LogStdout bool `json:"logStdout"` // LogStdout specifies whether printing logging content to stdout. + ErrorStack bool `json:"errorStack"` // ErrorStack specifies whether logging stack information when error. + ErrorLogEnabled bool `json:"errorLogEnabled"` // ErrorLogEnabled enables error logging content to files. + ErrorLogPattern string `json:"errorLogPattern"` // ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + AccessLogEnabled bool `json:"accessLogEnabled"` // AccessLogEnabled enables access logging content to files. + AccessLogPattern string `json:"accessLogPattern"` // AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log // ================================== // PProf. // ================================== - PProfEnabled bool // PProfEnabled enables PProf feature. - PProfPattern string // PProfPattern specifies the PProf service pattern for router. + PProfEnabled bool `json:"pprofEnabled"` // PProfEnabled enables PProf feature. + PProfPattern string `json:"pprofPattern"` // PProfPattern specifies the PProf service pattern for router. // ================================== // Other. @@ -194,26 +198,26 @@ type ServerConfig struct { // ClientMaxBodySize specifies the max body size limit in bytes for client request. // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 8MB in default. - ClientMaxBodySize int64 + ClientMaxBodySize int64 `json:"clientMaxBodySize"` // FormParsingMemory specifies max memory buffer size in bytes which can be used for // parsing multimedia form. // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 1MB in default. - FormParsingMemory int64 + FormParsingMemory int64 `json:"formParsingMemory"` // NameToUriType specifies the type for converting struct method name to URI when // registering routes. - NameToUriType int + NameToUriType int `json:"nameToUriType"` // RouteOverWrite allows overwrite the route if duplicated. - RouteOverWrite bool + RouteOverWrite bool `json:"routeOverWrite"` // DumpRouterMap specifies whether automatically dumps router map when server starts. - DumpRouterMap bool + DumpRouterMap bool `json:"dumpRouterMap"` // Graceful enables graceful reload feature for all servers of the process. - Graceful bool + Graceful bool `json:"graceful"` } // Deprecated. Use NewConfig instead. @@ -243,10 +247,11 @@ func NewConfig() ServerConfig { CookieMaxAge: time.Hour * 24 * 365, CookiePath: "/", CookieDomain: "", - SessionMaxAge: time.Hour * 24, SessionIdName: "gfsessionid", SessionPath: gsession.DefaultStorageFilePath, + SessionMaxAge: time.Hour * 24, SessionCookieOutput: true, + SessionCookieMaxAge: time.Hour * 24, Logger: glog.New(), LogLevel: "all", LogStdout: true, diff --git a/net/ghttp/ghttp_server_config_session.go b/net/ghttp/ghttp_server_config_session.go index e9f712629..205eae613 100644 --- a/net/ghttp/ghttp_server_config_session.go +++ b/net/ghttp/ghttp_server_config_session.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -32,6 +32,11 @@ func (s *Server) SetSessionCookieOutput(enabled bool) { s.config.SessionCookieOutput = enabled } +// SetSessionCookieMaxAge sets the SessionCookieMaxAge for server. +func (s *Server) SetSessionCookieMaxAge(maxAge time.Duration) { + s.config.SessionCookieMaxAge = maxAge +} + // GetSessionMaxAge returns the SessionMaxAge of server. func (s *Server) GetSessionMaxAge() time.Duration { return s.config.SessionMaxAge @@ -41,3 +46,8 @@ func (s *Server) GetSessionMaxAge() time.Duration { func (s *Server) GetSessionIdName() string { return s.config.SessionIdName } + +// GetSessionCookieMaxAge returns the SessionCookieMaxAge of server. +func (s *Server) GetSessionCookieMaxAge() time.Duration { + return s.config.SessionCookieMaxAge +} diff --git a/net/ghttp/ghttp_server_config_static.go b/net/ghttp/ghttp_server_config_static.go index f4c5164e0..c71d6faf2 100644 --- a/net/ghttp/ghttp_server_config_static.go +++ b/net/ghttp/ghttp_server_config_static.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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. -// 静态文件搜索优先级: Resource > ServerPaths > ServerRoot > SearchPath +// Static Searching Priority: Resource > ServerPaths > ServerRoot > SearchPath package ghttp diff --git a/net/ghttp/ghttp_server_cookie.go b/net/ghttp/ghttp_server_cookie.go index af7f7473c..47fbfb918 100644 --- a/net/ghttp/ghttp_server_cookie.go +++ b/net/ghttp/ghttp_server_cookie.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -14,9 +14,6 @@ import ( // Cookie for HTTP COOKIE management. type Cookie struct { data map[string]*cookieItem // Underlying cookie items. - path string // The default cookie path. - domain string // The default cookie domain - maxAge time.Duration // The default cookie max age. server *Server // Belonged HTTP server request *Request // Belonged HTTP request. response *Response // Belonged HTTP response. @@ -47,13 +44,10 @@ func (c *Cookie) init() { return } c.data = make(map[string]*cookieItem) - c.path = c.request.Server.GetCookiePath() - c.domain = c.request.Server.GetCookieDomain() - c.maxAge = c.request.Server.GetCookieMaxAge() c.response = c.request.Response // DO NOT ADD ANY DEFAULT COOKIE DOMAIN! - //if c.domain == "" { - // c.domain = c.request.GetHost() + //if c.request.Server.GetCookieDomain() == "" { + // c.request.Server.GetCookieDomain() = c.request.GetHost() //} for _, v := range c.request.Cookies() { c.data[v.Name] = &cookieItem{ @@ -86,7 +80,13 @@ func (c *Cookie) Contains(key string) bool { // Set sets cookie item with default domain, path and expiration age. func (c *Cookie) Set(key, value string) { - c.SetCookie(key, value, c.domain, c.path, c.maxAge) + c.SetCookie( + key, + value, + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + c.request.Server.GetCookieMaxAge(), + ) } // SetCookie sets cookie item given given domain, path and expiration age. @@ -128,7 +128,13 @@ func (c *Cookie) GetSessionId() string { // SetSessionId sets session id in the cookie. func (c *Cookie) SetSessionId(id string) { - c.Set(c.server.GetSessionIdName(), id) + c.SetCookie( + c.server.GetSessionIdName(), + id, + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + c.server.GetSessionCookieMaxAge(), + ) } // Get retrieves and returns the value with specified key. @@ -149,7 +155,13 @@ func (c *Cookie) Get(key string, def ...string) string { // Remove deletes specified key and its value from cookie using default domain and path. // It actually tells the http client that the cookie is expired, do not send it to server next time. func (c *Cookie) Remove(key string) { - c.SetCookie(key, "", c.domain, c.path, -86400) + c.SetCookie( + key, + "", + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + -86400, + ) } // RemoveCookie deletes specified key and its value from cookie using given domain and path. diff --git a/net/ghttp/ghttp_server_pprof.go b/net/ghttp/ghttp_server_pprof.go index 27179030b..2cde7ab6e 100644 --- a/net/ghttp/ghttp_server_pprof.go +++ b/net/ghttp/ghttp_server_pprof.go @@ -18,7 +18,7 @@ import ( type utilPProf struct{} const ( - gDEFAULT_PPROF_PATTERN = "/debug/pprof" + defaultPProfPattern = "/debug/pprof" ) // EnablePProf enables PProf feature for server. @@ -28,7 +28,7 @@ func (s *Server) EnablePProf(pattern ...string) { // EnablePProf enables PProf feature for server of specified domain. func (d *Domain) EnablePProf(pattern ...string) { - p := gDEFAULT_PPROF_PATTERN + p := defaultPProfPattern if len(pattern) > 0 && pattern[0] != "" { p = pattern[0] } diff --git a/os/gcfg/gcfg.go b/os/gcfg/gcfg.go index fd89a38cf..34f9c2859 100644 --- a/os/gcfg/gcfg.go +++ b/os/gcfg/gcfg.go @@ -283,6 +283,10 @@ func (c *Config) FilePath(file ...string) (path string) { } }) } + // Already found? + if path != "" { + return + } // Searching the file system. c.paths.RLockFunc(func(array []string) { for _, prefix := range array { diff --git a/os/glog/glog_logger_config.go b/os/glog/glog_logger_config.go index b51b75986..dd619d20e 100644 --- a/os/glog/glog_logger_config.go +++ b/os/glog/glog_logger_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -21,25 +21,25 @@ import ( // Config is the configuration object for logger. type Config struct { - 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. - StSkip int // Skip count for stack. - StStatus int // Stack status(1: enabled - default; 0: disabled) - StFilter string // Stack string filter. - CtxKeys []interface{} // Context keys for logging, which is used for value retrieving from context. - HeaderPrint bool `c:"header"` // Print header or not(true in default). - StdoutPrint bool `c:"stdout"` // Output to stdout or not(true in default). - LevelPrefixes map[int]string // Logging level to its prefix string mapping. - RotateSize int64 // Rotate the logging file if its size > 0 in bytes. - RotateExpire time.Duration // Rotate the logging file if its mtime exceeds this duration. - RotateBackupLimit int // Max backup for rotated files, default is 0, means no backups. - RotateBackupExpire time.Duration // Max expire for rotated files, which is 0 in default, means no expiration. - RotateBackupCompress int // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. - RotateCheckInterval time.Duration // Asynchronizely checks the backups and expiration at intervals. It's 1 hour in default. + Writer io.Writer `json:"-"` // Customized io.Writer. + Flags int `json:"flags"` // Extra flags for logging output features. + Path string `json:"path"` // Logging directory path. + File string `json:"file"` // Format for logging file. + Level int `json:"level"` // Output level. + Prefix string `json:"prefix"` // Prefix string for every logging content. + StSkip int `json:"stSkip"` // Skip count for stack. + StStatus int `json:"stStatus"` // Stack status(1: enabled - default; 0: disabled) + StFilter string `json:"stFilter"` // Stack string filter. + CtxKeys []interface{} `json:"ctxKeys"` // Context keys for logging, which is used for value retrieving from context. + HeaderPrint bool `json:"header"` // Print header or not(true in default). + StdoutPrint bool `json:"stdout"` // Output to stdout or not(true in default). + LevelPrefixes map[int]string `json:"levelPrefixes"` // Logging level to its prefix string mapping. + RotateSize int64 `json:"rotateSize"` // Rotate the logging file if its size > 0 in bytes. + RotateExpire time.Duration `json:"rotateExpire"` // Rotate the logging file if its mtime exceeds this duration. + RotateBackupLimit int `json:"rotateBackupLimit"` // Max backup for rotated files, default is 0, means no backups. + RotateBackupExpire time.Duration `json:"rotateBackupExpire"` // Max expire for rotated files, which is 0 in default, means no expiration. + RotateBackupCompress int `json:"rotateBackupCompress"` // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. + RotateCheckInterval time.Duration `json:"rotateCheckInterval"` // Asynchronizely checks the backups and expiration at intervals. It's 1 hour in default. } // DefaultConfig returns the default configuration for logger. diff --git a/os/gres/gres_func.go b/os/gres/gres_func.go index 52d449ab8..4995fbc29 100644 --- a/os/gres/gres_func.go +++ b/os/gres/gres_func.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -20,7 +20,7 @@ import ( ) const ( - gPACKAGE_TEMPLATE = ` + packedGoSouceTemplate = ` package %s import "github.com/gogf/gf/os/gres" @@ -39,8 +39,10 @@ func init() { // // Note that parameter supports multiple paths join with ','. func Pack(srcPaths string, keyPrefix ...string) ([]byte, error) { - buffer := bytes.NewBuffer(nil) - headerPrefix := "" + var ( + buffer = bytes.NewBuffer(nil) + headerPrefix = "" + ) if len(keyPrefix) > 0 && keyPrefix[0] != "" { headerPrefix = keyPrefix[0] } @@ -79,7 +81,7 @@ func PackToGoFile(srcPath, goFilePath, pkgName string, keyPrefix ...string) erro } return gfile.PutContents( goFilePath, - fmt.Sprintf(gstr.TrimLeft(gPACKAGE_TEMPLATE), pkgName, gbase64.EncodeToString(data)), + fmt.Sprintf(gstr.TrimLeft(packedGoSouceTemplate), pkgName, gbase64.EncodeToString(data)), ) } diff --git a/os/gres/gres_func_zip.go b/os/gres/gres_func_zip.go index 22af99258..bc6e0ac50 100644 --- a/os/gres/gres_func_zip.go +++ b/os/gres/gres_func_zip.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -11,6 +11,7 @@ import ( "github.com/gogf/gf/internal/fileinfo" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/text/gregex" "io" "os" "strings" @@ -72,7 +73,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix intlog.Printf(`exclude file path: %s`, file) continue } - err := zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter) + err = zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter) if err != nil { return err } @@ -83,7 +84,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix path = headerPrefix for { name = gfile.Basename(path) - err := zipFileVirtual( + err = zipFileVirtual( fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), path, zipWriter, ) if err != nil { @@ -136,7 +137,7 @@ func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error { return err } header.Name = path - if _, err := zw.CreateHeader(header); err != nil { + if _, err = zw.CreateHeader(header); err != nil { return err } return nil @@ -148,9 +149,9 @@ func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) return nil, err } if len(prefix) > 0 { - prefix = strings.Replace(prefix, `\`, `/`, -1) - prefix = strings.TrimRight(prefix, `/`) header.Name = prefix + `/` + header.Name + header.Name = strings.Replace(header.Name, `\`, `/`, -1) + header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name) } return header, nil } diff --git a/os/gres/gres_resource.go b/os/gres/gres_resource.go index ce410108f..2ed7b2e46 100644 --- a/os/gres/gres_resource.go +++ b/os/gres/gres_resource.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -74,6 +74,7 @@ func (r *Resource) Get(path string) *File { return nil } path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] @@ -93,6 +94,7 @@ func (r *Resource) Get(path string) *File { func (r *Resource) GetWithIndex(path string, indexFiles []string) *File { // Necessary for double char '/' replacement in prefix. path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] @@ -114,7 +116,6 @@ func (r *Resource) GetWithIndex(path string, indexFiles []string) *File { // GetContent directly returns the content of . func (r *Resource) GetContent(path string) []byte { - path = strings.Replace(path, "\\", "/", -1) file := r.Get(path) if file != nil { return file.Content() @@ -169,6 +170,7 @@ func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) [ // It scans directory recursively if given parameter is true. func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File { path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] diff --git a/os/gres/gres_z_unit_2_test.go b/os/gres/gres_z_unit_2_test.go index f051f952b..8e7bc3496 100644 --- a/os/gres/gres_z_unit_2_test.go +++ b/os/gres/gres_z_unit_2_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/os/gres/testdata/data/data.go b/os/gres/testdata/data/data.go index 4c5967568..099d514e3 100644 --- a/os/gres/testdata/data/data.go +++ b/os/gres/testdata/data/data.go @@ -3,7 +3,7 @@ package data import "github.com/gogf/gf/os/gres" func init() { - if err := gres.Add("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"); err != nil { + if err := gres.Add("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"); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/os/gres/testdata/testdata.go b/os/gres/testdata/testdata.go index 1b3381164..b228bde1c 100644 --- a/os/gres/testdata/testdata.go +++ b/os/gres/testdata/testdata.go @@ -3,7 +3,7 @@ package testdata import "github.com/gogf/gf/os/gres" func init() { - if err := gres.Add("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"); err != nil { + if err := gres.Add("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"); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index b97dd820a..f1df7d8db 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -68,8 +68,10 @@ func NewStorageFile(path ...string) *StorageFile { // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() { //intlog.Print("StorageFile.timer start") - var id string - var err error + var ( + id string + err error + ) for { if id = s.updatingIdSet.Pop(); id == "" { break @@ -221,7 +223,7 @@ func (s *StorageFile) SetSession(id string, data *gmap.StrAnyMap, ttl time.Durat // It just adds the session id to the async handling queue. func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error { intlog.Printf("StorageFile.UpdateTTL: %s, %v", id, ttl) - if ttl >= DefaultStorageRedisLoopInterval { + if ttl >= DefaultStorageFileLoopInterval { s.updatingIdSet.Add(id) } return nil diff --git a/os/gsession/gsession_storage_redis.go b/os/gsession/gsession_storage_redis.go index 567fe2036..bd5714e66 100644 --- a/os/gsession/gsession_storage_redis.go +++ b/os/gsession/gsession_storage_redis.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -26,7 +26,7 @@ type StorageRedis struct { var ( // DefaultStorageRedisLoopInterval is the interval updating TTL for session ids // in last duration. - DefaultStorageRedisLoopInterval = time.Minute + DefaultStorageRedisLoopInterval = 10 * time.Second ) // NewStorageRedis creates and returns a redis storage object for session. @@ -45,9 +45,11 @@ func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageRedisLoopInterval, func() { intlog.Print("StorageRedis.timer start") - var id string - var err error - var ttlSeconds int + var ( + id string + err error + ttlSeconds int + ) for { if id, ttlSeconds = s.updatingIdMap.Pop(); id == "" { break diff --git a/os/gsession/gsession_storage_redis_hashtable.go b/os/gsession/gsession_storage_redis_hashtable.go index b7eec2970..15eb825a3 100644 --- a/os/gsession/gsession_storage_redis_hashtable.go +++ b/os/gsession/gsession_storage_redis_hashtable.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 4584b661e..7da67768a 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -318,6 +318,113 @@ func (t *Time) Sub(u *Time) time.Duration { return t.Time.Sub(u.Time) } +// StartOfMinute clones and returns a new time of which the seconds is set to 0. +func (t *Time) StartOfMinute() *Time { + newTime := t.Clone() + newTime.Time = newTime.Time.Truncate(time.Minute) + return newTime +} + +// StartOfHour clones and returns a new time of which the hour, minutes and seconds are set to 0. +func (t *Time) StartOfHour() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, newTime.Time.Hour(), 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfDay clones and returns a new time which is the start of day, its time is set to 00:00:00. +func (t *Time) StartOfDay() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfWeek clones and returns a new time which is the first day of week and its time is set to +// 00:00:00. +func (t *Time) StartOfWeek() *Time { + weekday := int(t.Weekday()) + return t.StartOfDay().AddDate(0, 0, -weekday) +} + +// StartOfMonth clones and returns a new time which is the first day of the month and its is set to +// 00:00:00 +func (t *Time) StartOfMonth() *Time { + y, m, _ := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, 1, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfQuarter clones and returns a new time which is the first day of the quarter and its time is set +// to 00:00:00. +func (t *Time) StartOfQuarter() *Time { + month := t.StartOfMonth() + offset := (int(month.Month()) - 1) % 3 + return month.AddDate(0, -offset, 0) +} + +// StartOfHalf clones and returns a new time which is the first day of the half year and its time is set +// to 00:00:00. +func (t *Time) StartOfHalf() *Time { + month := t.StartOfMonth() + offset := (int(month.Month()) - 1) % 6 + return month.AddDate(0, -offset, 0) +} + +// StartOfYear clones and returns a new time which is the first day of the year and its time is set to +// 00:00:00. +func (t *Time) StartOfYear() *Time { + y, _, _ := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, time.January, 1, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// EndOfMinute clones and returns a new time of which the seconds is set to 59. +func (t *Time) EndOfMinute() *Time { + return t.StartOfMinute().Add(time.Minute - time.Nanosecond) +} + +// EndOfHour clones and returns a new time of which the minutes and seconds are both set to 59. +func (t *Time) EndOfHour() *Time { + return t.StartOfHour().Add(time.Hour - time.Nanosecond) +} + +// EndOfDay clones and returns a new time which is the end of day the and its time is set to 23:59:59. +func (t *Time) EndOfDay() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), newTime.Time.Location()) + return newTime +} + +// EndOfWeek clones and returns a new time which is the end of week and its time is set to 23:59:59. +func (t *Time) EndOfWeek() *Time { + return t.StartOfWeek().AddDate(0, 0, 7).Add(-time.Nanosecond) +} + +// EndOfMonth clones and returns a new time which is the end of the month and its time is set to 23:59:59. +func (t *Time) EndOfMonth() *Time { + return t.StartOfMonth().AddDate(0, 1, 0).Add(-time.Nanosecond) +} + +// EndOfQuarter clones and returns a new time which is end of the quarter and its time is set to 23:59:59. +func (t *Time) EndOfQuarter() *Time { + return t.StartOfQuarter().AddDate(0, 3, 0).Add(-time.Nanosecond) +} + +// EndOfHalf clones and returns a new time which is the end of the half year and its time is set to 23:59:59. +func (t *Time) EndOfHalf() *Time { + return t.StartOfHalf().AddDate(0, 6, 0).Add(-time.Nanosecond) +} + +// EndOfYear clones and returns a new time which is the end of the year and its time is set to 23:59:59. +func (t *Time) EndOfYear() *Time { + return t.StartOfYear().AddDate(1, 0, 0).Add(-time.Nanosecond) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (t *Time) MarshalJSON() ([]byte, error) { return []byte(`"` + t.String() + `"`), nil diff --git a/os/gtime/gtime_z_unit_time_test.go b/os/gtime/gtime_z_unit_time_test.go index edfa6530e..5eb2809bd 100644 --- a/os/gtime/gtime_z_unit_time_test.go +++ b/os/gtime/gtime_z_unit_time_test.go @@ -256,3 +256,131 @@ func Test_Truncate(t *testing.T) { t.Assert(timeTemp.UnixNano(), timeTemp1.Truncate(time.Hour).UnixNano()) }) } + +func Test_StartOfMinute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfMinute() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:24:00") + }) +} + +func Test_EndOfMinute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfMinute() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:24:59") + }) +} + +func Test_StartOfHour(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfHour() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:00:00") + }) +} + +func Test_EndOfHour(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfHour() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:59:59") + }) +} + +func Test_StartOfDay(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfDay() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 00:00:00") + }) +} + +func Test_EndOfDay(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfDay() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 23:59:59") + }) +} + +func Test_StartOfWeek(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfWeek() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-06 00:00:00") + }) +} + +func Test_EndOfWeek(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfWeek() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 23:59:59") + }) +} + +func Test_StartOfMonth(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfMonth() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-01 00:00:00") + }) +} + +func Test_EndOfMonth(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfMonth() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfQuarter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfQuarter() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-10-01 00:00:00") + }) +} + +func Test_EndOfQuarter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfQuarter() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfHalf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfHalf() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-07-01 00:00:00") + }) +} + +func Test_EndOfHalf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfHalf() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfYear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfYear() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-01-01 00:00:00") + }) +} + +func Test_EndOfYear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfYear() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} diff --git a/os/gview/gview_config.go b/os/gview/gview_config.go index 2404c2f56..508344a0a 100644 --- a/os/gview/gview_config.go +++ b/os/gview/gview_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -21,12 +21,12 @@ import ( // Config is the configuration object for template engine. type Config struct { - Paths []string // Searching array for path, NOT concurrent-safe for performance purpose. - Data map[string]interface{} // Global template variables including configuration. - DefaultFile string // Default template file for parsing. - Delimiters []string // Custom template delimiters. - AutoEncode bool // Automatically encodes and provides safe html output, which is good for avoiding XSS. - I18nManager *gi18n.Manager // I18n manager for the view. + Paths []string `json:"paths"` // Searching array for path, NOT concurrent-safe for performance purpose. + Data map[string]interface{} `json:"data"` // Global template variables including configuration. + DefaultFile string `json:"defaultFile"` // Default template file for parsing. + Delimiters []string `json:"delimiters"` // Custom template delimiters. + AutoEncode bool `json:"autoEncode"` // Automatically encodes and provides safe html output, which is good for avoiding XSS. + I18nManager *gi18n.Manager `json:"-"` // I18n manager for the view. } const ( diff --git a/text/gstr/gstr_case.go b/text/gstr/gstr_case.go index e472f9055..f8f6aa3e0 100644 --- a/text/gstr/gstr_case.go +++ b/text/gstr/gstr_case.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -6,15 +6,15 @@ // // | Function | Result | // |-----------------------------------|--------------------| -// | SnakeCase(s) | any_kind_of_string | -// | SnakeScreamingCase(s) | ANY_KIND_OF_STRING | -// | KebabCase(s) | any-kind-of-string | -// | KebabScreamingCase(s) | ANY-KIND-OF-STRING | -// | DelimitedCase(s, '.') | any.kind.of.string | -// | DelimitedScreamingCase(s, '.') | ANY.KIND.OF.STRING | -// | CamelCase(s) | AnyKindOfString | -// | CamelLowerCase(s) | anyKindOfString | -// | SnakeFirstUpperCase(RGBCodeMd5) | rgb_code_md5 | +// | CaseSnake(s) | any_kind_of_string | +// | CaseSnakeScreaming(s) | ANY_KIND_OF_STRING | +// | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5 | +// | CaseKebab(s) | any-kind-of-string | +// | CaseKebabScreaming(s) | ANY-KIND-OF-STRING | +// | CaseDelimited(s, '.') | any.kind.of.string | +// | CaseDelimitedScreaming(s, '.') | ANY.KIND.OF.STRING | +// | CaseCamel(s) | AnyKindOfString | +// | CaseCamelLower(s) | anyKindOfString | package gstr @@ -30,12 +30,24 @@ var ( ) // CamelCase converts a string to CamelCase. +// Deprecated, use CaseCamel instead. func CamelCase(s string) string { + return CaseCamel(s) +} + +// CaseCamel converts a string to CamelCase. +func CaseCamel(s string) string { return toCamelInitCase(s, true) } // CamelLowerCase converts a string to lowerCamelCase. +// Deprecated, use CaseCamelLower instead. func CamelLowerCase(s string) string { + return CaseCamelLower(s) +} + +// CaseCamelLower converts a string to lowerCamelCase. +func CaseCamelLower(s string) string { if s == "" { return s } @@ -46,19 +58,37 @@ func CamelLowerCase(s string) string { } // SnakeCase converts a string to snake_case. +// Deprecated, use CaseSnake instead. func SnakeCase(s string) string { + return CaseSnake(s) +} + +// CaseSnake converts a string to snake_case. +func CaseSnake(s string) string { return DelimitedCase(s, '_') } // SnakeScreamingCase converts a string to SNAKE_CASE_SCREAMING. +// Deprecated, use CaseSnakeScreaming instead. func SnakeScreamingCase(s string) string { + return CaseSnakeScreaming(s) +} + +// CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING. +func CaseSnakeScreaming(s string) string { return DelimitedScreamingCase(s, '_', true) } // SnakeFirstUpperCase converts a string from RGBCodeMd5 to rgb_code_md5. // The length of word should not be too long -// TODO for efficiency should change regexp to traversing string in future +// Deprecated, use CaseSnakeFirstUpper instead. func SnakeFirstUpperCase(word string, underscore ...string) string { + return CaseSnakeFirstUpper(word, underscore...) +} + +// CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5". +// TODO for efficiency should change regexp to traversing string in future. +func CaseSnakeFirstUpper(word string, underscore ...string) string { replace := "_" if len(underscore) > 0 { replace = underscore[0] @@ -73,7 +103,7 @@ func SnakeFirstUpperCase(word string, underscore ...string) string { m := firstCamelCaseStart.FindAllStringSubmatch(word, 1) if len(m) > 0 && m[0][1] != "" { w := strings.ToLower(m[0][1]) - w = string(w[:len(w)-1]) + replace + string(w[len(w)-1]) + w = w[:len(w)-1] + replace + string(w[len(w)-1]) word = strings.Replace(word, m[0][1], w, 1) } else { @@ -84,23 +114,47 @@ func SnakeFirstUpperCase(word string, underscore ...string) string { return TrimLeft(word, replace) } -// KebabCase converts a string to kebab-case +// KebabCase converts a string to kebab-case. +// Deprecated, use CaseKebab instead. func KebabCase(s string) string { - return DelimitedCase(s, '-') + return CaseKebab(s) +} + +// CaseKebab converts a string to kebab-case +func CaseKebab(s string) string { + return CaseDelimited(s, '-') } // KebabScreamingCase converts a string to KEBAB-CASE-SCREAMING. +// Deprecated, use CaseKebabScreaming instead. func KebabScreamingCase(s string) string { - return DelimitedScreamingCase(s, '-', true) + return CaseKebabScreaming(s) +} + +// CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING. +func CaseKebabScreaming(s string) string { + return CaseDelimitedScreaming(s, '-', true) } // DelimitedCase converts a string to snake.case.delimited. +// Deprecated, use CaseDelimited instead. func DelimitedCase(s string, del uint8) string { - return DelimitedScreamingCase(s, del, false) + return CaseDelimited(s, del) +} + +// CaseDelimited converts a string to snake.case.delimited. +func CaseDelimited(s string, del uint8) string { + return CaseDelimitedScreaming(s, del, false) } // DelimitedScreamingCase converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. +// Deprecated, use CaseDelimitedScreaming instead. func DelimitedScreamingCase(s string, del uint8, screaming bool) string { + return CaseDelimitedScreaming(s, del, screaming) +} + +// CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. +func CaseDelimitedScreaming(s string, del uint8, screaming bool) string { s = addWordBoundariesToNumbers(s) s = strings.Trim(s, " ") n := "" diff --git a/text/gstr/gstr_z_unit_case_test.go b/text/gstr/gstr_z_unit_case_test.go index cde0ed961..a87de35e4 100644 --- a/text/gstr/gstr_z_unit_case_test.go +++ b/text/gstr/gstr_z_unit_case_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -7,12 +7,13 @@ package gstr_test import ( + "github.com/gogf/gf/test/gtest" "testing" "github.com/gogf/gf/text/gstr" ) -func Test_CamelCase(t *testing.T) { +func Test_CaseCamel(t *testing.T) { cases := [][]string{ {"test_case", "TestCase"}, {"test", "Test"}, @@ -28,14 +29,14 @@ func Test_CamelCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.CamelCase(in) + result := gstr.CaseCamel(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_CamelLowerCase(t *testing.T) { +func Test_CaseCamelLower(t *testing.T) { cases := [][]string{ {"foo-bar", "fooBar"}, {"TestCase", "testCase"}, @@ -45,14 +46,14 @@ func Test_CamelLowerCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.CamelLowerCase(in) + result := gstr.CaseCamelLower(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_SnakeCase(t *testing.T) { +func Test_CaseSnake(t *testing.T) { cases := [][]string{ {"testCase", "test_case"}, {"TestCase", "test_case"}, @@ -75,14 +76,14 @@ func Test_SnakeCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.SnakeCase(in) + result := gstr.CaseSnake(in) if result != out { t.Error("'" + in + "'('" + result + "' != '" + out + "')") } } } -func Test_DelimitedCase(t *testing.T) { +func Test_CaseDelimited(t *testing.T) { cases := [][]string{ {"testCase", "test@case"}, {"TestCase", "test@case"}, @@ -106,28 +107,28 @@ func Test_DelimitedCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.DelimitedCase(in, '@') + result := gstr.CaseDelimited(in, '@') if result != out { t.Error("'" + in + "' ('" + result + "' != '" + out + "')") } } } -func Test_SnakeScreamingCase(t *testing.T) { +func Test_CaseSnakeScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST_CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.SnakeScreamingCase(in) + result := gstr.CaseSnakeScreaming(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_KebabCase(t *testing.T) { +func Test_CaseKebab(t *testing.T) { cases := [][]string{ {"testCase", "test-case"}, {"optimization1.0.0", "optimization-1-0-0"}, @@ -135,42 +136,42 @@ func Test_KebabCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.KebabCase(in) + result := gstr.CaseKebab(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_KebabScreamingCase(t *testing.T) { +func Test_CaseKebabScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST-CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.KebabScreamingCase(in) + result := gstr.CaseKebabScreaming(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_DelimitedScreamingCase(t *testing.T) { +func Test_CaseDelimitedScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST.CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.DelimitedScreamingCase(in, '.', true) + result := gstr.CaseDelimitedScreaming(in, '.', true) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func TestSnakeFirstUpperCase(t *testing.T) { +func Test_CaseSnakeFirstUpper(t *testing.T) { cases := [][]string{ {"RGBCodeMd5", "rgb_code_md5"}, {"testCase", "test_case"}, @@ -182,13 +183,12 @@ func TestSnakeFirstUpperCase(t *testing.T) { {"User_ID", "user_id"}, {"user_id", "user_id"}, {"md5", "md5"}, + {"Numbers2And55With000", "numbers2_and55_with000"}, } - for _, i := range cases { - in := i[0] - out := i[1] - result := gstr.SnakeFirstUpperCase(in) - if result != out { - t.Error("'" + result + "' != '" + out + "'") + gtest.C(t, func(t *gtest.T) { + for _, item := range cases { + t.Assert(gstr.CaseSnakeFirstUpper(item[0]), item[1]) } - } + }) + } diff --git a/util/grand/grand_z_bench_test.go b/util/grand/grand_z_bench_test.go index fc3a165ca..dc2361c28 100644 --- a/util/grand/grand_z_bench_test.go +++ b/util/grand/grand_z_bench_test.go @@ -9,11 +9,12 @@ package grand_test import ( - "crypto/rand" - "encoding/binary" - "testing" + cryptoRand "crypto/rand" + mathRand "math/rand" + "encoding/binary" "github.com/gogf/gf/util/grand" + "testing" ) var ( @@ -23,15 +24,21 @@ var ( strForStr = "我爱GoFrame" ) -func Benchmark_Rand_Buffer4(b *testing.B) { +func Benchmark_Math_Rand_Int(b *testing.B) { for i := 0; i < b.N; i++ { - rand.Read(randBuffer4) + mathRand.Int() } } -func Benchmark_Rand_Buffer1024(b *testing.B) { +func Benchmark_CryptoRand_Buffer4(b *testing.B) { for i := 0; i < b.N; i++ { - rand.Read(randBuffer1024) + cryptoRand.Read(randBuffer4) + } +} + +func Benchmark_CryptoRand_Buffer1024(b *testing.B) { + for i := 0; i < b.N; i++ { + cryptoRand.Read(randBuffer1024) } } @@ -101,9 +108,9 @@ func Benchmark_Uint32Converting(b *testing.B) { } } -func Benchmark_Buffer(b *testing.B) { +func Benchmark_CryptoRand_Buffer(b *testing.B) { for i := 0; i < b.N; i++ { - if _, err := rand.Read(buffer); err == nil { + if _, err := cryptoRand.Read(buffer); err == nil { binary.LittleEndian.Uint64(buffer) } } diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 59a45dec4..136b37d87 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -8,6 +8,7 @@ package gvalid import ( + "regexp" "strings" "github.com/gogf/gf/text/gregex" @@ -62,6 +63,136 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} +const ( + // regular expression pattern for single validation rule. + singleRulePattern = `^([\w-]+):{0,1}(.*)` + invalidRulesErrKey = "invalid_rules" + invalidParamsErrKey = "invalid_params" + invalidObjectErrKey = "invalid_object" +) + +var ( + // defaultValidator is the default validator for package functions. + defaultValidator = New() + + // all internal error keys. + internalErrKeyMap = map[string]string{ + invalidRulesErrKey: invalidRulesErrKey, + invalidParamsErrKey: invalidParamsErrKey, + invalidObjectErrKey: invalidObjectErrKey, + } + // regular expression object for single rule + // which is compiled just once and of repeatable usage. + ruleRegex, _ = regexp.Compile(singleRulePattern) + + // mustCheckRulesEvenValueEmpty specifies some rules that must be validated + // even the value is empty (nil or empty). + mustCheckRulesEvenValueEmpty = map[string]struct{}{ + "required": {}, + "required-if": {}, + "required-unless": {}, + "required-with": {}, + "required-with-all": {}, + "required-without": {}, + "required-without-all": {}, + //"same": {}, + //"different": {}, + //"in": {}, + //"not-in": {}, + //"regex": {}, + } + // allSupportedRules defines all supported rules that is used for quick checks. + allSupportedRules = map[string]struct{}{ + "required": {}, + "required-if": {}, + "required-unless": {}, + "required-with": {}, + "required-with-all": {}, + "required-without": {}, + "required-without-all": {}, + "date": {}, + "date-format": {}, + "email": {}, + "phone": {}, + "phone-loose": {}, + "telephone": {}, + "passport": {}, + "password": {}, + "password2": {}, + "password3": {}, + "postcode": {}, + "resident-id": {}, + "bank-card": {}, + "qq": {}, + "ip": {}, + "ipv4": {}, + "ipv6": {}, + "mac": {}, + "url": {}, + "domain": {}, + "length": {}, + "min-length": {}, + "max-length": {}, + "between": {}, + "min": {}, + "max": {}, + "json": {}, + "integer": {}, + "float": {}, + "boolean": {}, + "same": {}, + "different": {}, + "in": {}, + "not-in": {}, + "regex": {}, + } + // boolMap defines the boolean values. + boolMap = map[string]struct{}{ + "1": {}, + "true": {}, + "on": {}, + "yes": {}, + "": {}, + "0": {}, + "false": {}, + "off": {}, + "no": {}, + } +) + +// Check checks single value with specified rules. +// It returns nil if successful validation. +// +// The parameter can be any type of variable, which will be converted to string +// for validation. +// The parameter can be one or more rules, multiple rules joined using char '|'. +// The parameter specifies the custom error messages, which can be type of: +// string/map/struct/*struct. +// The optional parameter specifies the extra validation parameters for some rules +// like: required-*、same、different, etc. +func Check(value interface{}, rules string, messages interface{}, params ...interface{}) *Error { + return defaultValidator.Check(value, rules, messages, params...) +} + +// CheckMap validates map and returns the error result. It returns nil if with successful validation. +// +// The parameter can be type of []string/map[string]string. It supports sequence in error result +// if is type of []string. +// The optional parameter specifies the custom error messages for specified keys and rules. +func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.CheckMap(params, rules, messages...) +} + +// CheckStruct validates strcut and returns the error result. +// +// The parameter should be type of struct/*struct. +// The parameter can be type of []string/map[string]string. It supports sequence in error result +// if is type of []string. +// The optional parameter specifies the custom error messages for specified keys and rules. +func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.CheckStruct(object, rules, messages...) +} + // parseSequenceTag parses one sequence tag to field, rule and error message. // The sequence tag is like: [alias@]rule[...#msg...] func parseSequenceTag(tag string) (field, rule, msg string) { diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go new file mode 100644 index 000000000..49c8505aa --- /dev/null +++ b/util/gvalid/gvalid_validator.go @@ -0,0 +1,31 @@ +// Copyright GoFrame Author(https://goframe.org). 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 gvalid + +// Validator is the validation manager. +type Validator struct { + i18nLang string // I18n language. +} + +// New creates and returns a new Validator. +func New() *Validator { + return &Validator{} +} + +// Clone creates and returns a new Validator which is a shallow copy of current one. +func (v *Validator) Clone() *Validator { + newValidator := New() + *newValidator = *v + return newValidator +} + +// I18n is a chaining operation function which sets the I18n language for next validation. +func (v *Validator) I18n(language string) *Validator { + newValidator := v.Clone() + newValidator.i18nLang = language + return newValidator +} diff --git a/util/gvalid/gvalid_check.go b/util/gvalid/gvalid_validator_check.go similarity index 74% rename from util/gvalid/gvalid_check.go rename to util/gvalid/gvalid_validator_check.go index 99e5c327f..035276256 100644 --- a/util/gvalid/gvalid_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -14,105 +14,10 @@ import ( "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" - "regexp" "strconv" "strings" ) -const ( - // regular expression pattern for single validation rule. - singleRulePattern = `^([\w-]+):{0,1}(.*)` - invalidRulesErrKey = "invalid_rules" - invalidParamsErrKey = "invalid_params" - invalidObjectErrKey = "invalid_object" -) - -var ( - // all internal error keys. - internalErrKeyMap = map[string]string{ - invalidRulesErrKey: invalidRulesErrKey, - invalidParamsErrKey: invalidParamsErrKey, - invalidObjectErrKey: invalidObjectErrKey, - } - // regular expression object for single rule - // which is compiled just once and of repeatable usage. - ruleRegex, _ = regexp.Compile(singleRulePattern) - - // mustCheckRulesEvenValueEmpty specifies some rules that must be validated - // even the value is empty (nil or empty). - mustCheckRulesEvenValueEmpty = map[string]struct{}{ - "required": {}, - "required-if": {}, - "required-unless": {}, - "required-with": {}, - "required-with-all": {}, - "required-without": {}, - "required-without-all": {}, - //"same": {}, - //"different": {}, - //"in": {}, - //"not-in": {}, - //"regex": {}, - } - // allSupportedRules defines all supported rules that is used for quick checks. - allSupportedRules = map[string]struct{}{ - "required": {}, - "required-if": {}, - "required-unless": {}, - "required-with": {}, - "required-with-all": {}, - "required-without": {}, - "required-without-all": {}, - "date": {}, - "date-format": {}, - "email": {}, - "phone": {}, - "phone-loose": {}, - "telephone": {}, - "passport": {}, - "password": {}, - "password2": {}, - "password3": {}, - "postcode": {}, - "resident-id": {}, - "bank-card": {}, - "qq": {}, - "ip": {}, - "ipv4": {}, - "ipv6": {}, - "mac": {}, - "url": {}, - "domain": {}, - "length": {}, - "min-length": {}, - "max-length": {}, - "between": {}, - "min": {}, - "max": {}, - "json": {}, - "integer": {}, - "float": {}, - "boolean": {}, - "same": {}, - "different": {}, - "in": {}, - "not-in": {}, - "regex": {}, - } - // boolMap defines the boolean values. - boolMap = map[string]struct{}{ - "1": {}, - "true": {}, - "on": {}, - "yes": {}, - "": {}, - "0": {}, - "false": {}, - "off": {}, - "no": {}, - } -) - // Check checks single value with specified rules. // It returns nil if successful validation. // @@ -123,12 +28,12 @@ var ( // string/map/struct/*struct. // The optional parameter specifies the extra validation parameters for some rules // like: required-*、same、different, etc. -func Check(value interface{}, rules string, messages interface{}, params ...interface{}) *Error { - return doCheck("", value, rules, messages, params...) +func (v *Validator) Check(value interface{}, rules string, messages interface{}, params ...interface{}) *Error { + return v.doCheck("", value, rules, messages, params...) } // doCheck does the really rules validation for single key-value. -func doCheck(key string, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { +func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { // If there's no validation rules, it does nothing and returns quickly. if rules == "" { return nil @@ -196,7 +101,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, // It checks custom validation rules with most priority. var ( dataMap map[string]interface{} - message = getErrorMessageByRule(ruleKey, customMsgMap) + message = v.getErrorMessageByRule(ruleKey, customMsgMap) ) if len(params) > 0 { dataMap = gconv.Map(params[0]) @@ -209,7 +114,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, } } else { // It checks build-in validation rules if there's no custom rule. - match, err = doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap) + match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap) if !match && err != nil { errorMsgArray[ruleKey] = err.Error() } @@ -220,7 +125,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, // It does nothing if the error message for this rule // is already set in previous validation. if _, ok := errorMsgArray[ruleKey]; !ok { - errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap) + errorMsgArray[ruleKey] = v.getErrorMessageByRule(ruleKey, customMsgMap) } } index++ @@ -233,7 +138,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, return nil } -func doCheckBuildInRules( +func (v *Validator) doCheckBuildInRules( index int, value interface{}, ruleKey string, @@ -253,7 +158,7 @@ func doCheckBuildInRules( "required-with-all", "required-without", "required-without-all": - match = checkRequired(valueStr, ruleKey, rulePattern, dataMap) + match = v.checkRequired(valueStr, ruleKey, rulePattern, dataMap) // Length rules. // It also supports length of unicode string. @@ -261,7 +166,7 @@ func doCheckBuildInRules( "length", "min-length", "max-length": - if msg := checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { + if msg := v.checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { return match, errors.New(msg) } else { match = true @@ -272,7 +177,7 @@ func doCheckBuildInRules( "min", "max", "between": - if msg := checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { + if msg := v.checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { return match, errors.New(msg) } else { match = true @@ -308,7 +213,7 @@ func doCheckBuildInRules( match = true } else { var msg string - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":format", rulePattern, -1) return match, errors.New(msg) } @@ -322,7 +227,7 @@ func doCheckBuildInRules( } if !match { var msg string - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":field", rulePattern, -1) return match, errors.New(msg) } @@ -337,7 +242,7 @@ func doCheckBuildInRules( } if !match { var msg string - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":field", rulePattern, -1) return match, errors.New(msg) } @@ -430,11 +335,11 @@ func doCheckBuildInRules( // 总: // (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$) case "resident-id": - match = checkResidentId(valueStr) + match = v.checkResidentId(valueStr) // Bank card number using LUHN algorithm. case "bank-card": - match = checkLuHn(valueStr) + match = v.checkLuHn(valueStr) // Universal passport format rule: // Starting with letter, containing only numbers or underscores, length between 6 and 18. diff --git a/util/gvalid/gvalid_check_map.go b/util/gvalid/gvalid_validator_check_map.go similarity index 93% rename from util/gvalid/gvalid_check_map.go rename to util/gvalid/gvalid_validator_check_map.go index ca5a754f8..195253743 100644 --- a/util/gvalid/gvalid_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -7,9 +7,8 @@ package gvalid import ( - "strings" - "github.com/gogf/gf/util/gconv" + "strings" ) // CheckMap validates map and returns the error result. It returns nil if with successful validation. @@ -17,7 +16,7 @@ import ( // The parameter can be type of []string/map[string]string. It supports sequence in error result // if is type of []string. // The optional parameter specifies the custom error messages for specified keys and rules. -func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Error { +func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Error { // If there's no validation rules, it does nothing and returns quickly. if params == nil || rules == nil { return nil @@ -96,7 +95,7 @@ func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Err value = v } // It checks each rule and its value in loop. - if e := doCheck(key, value, rule, customMsgs[key], data); e != nil { + if e := v.doCheck(key, value, rule, customMsgs[key], data); e != nil { _, item := e.FirstItem() // =========================================================== // Only in map and struct validations, if value is nil or empty diff --git a/util/gvalid/gvalid_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go similarity index 96% rename from util/gvalid/gvalid_check_struct.go rename to util/gvalid/gvalid_validator_check_struct.go index 69fee0430..9fe1b105d 100644 --- a/util/gvalid/gvalid_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -7,10 +7,9 @@ package gvalid import ( - "strings" - "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" + "strings" ) var ( @@ -24,7 +23,7 @@ var ( // The parameter can be type of []string/map[string]string. It supports sequence in error result // if is type of []string. // The optional parameter specifies the custom error messages for specified keys and rules. -func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { +func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { // It here must use structs.TagFields not structs.MapField to ensure error sequence. tagField, err := structs.TagFields(object, structTagPriority) if err != nil { @@ -166,7 +165,7 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) * value = v } // It checks each rule and its value in loop. - if e := doCheck(key, value, rule, customMessage[key], params); e != nil { + if e := v.doCheck(key, value, rule, customMessage[key], params); e != nil { _, item := e.FirstItem() // =========================================================== // Only in map and struct validations, if value is nil or empty diff --git a/util/gvalid/gvalid_message.go b/util/gvalid/gvalid_validator_message.go similarity index 94% rename from util/gvalid/gvalid_message.go rename to util/gvalid/gvalid_validator_message.go index c3b6e6c2b..56bf17251 100644 --- a/util/gvalid/gvalid_message.go +++ b/util/gvalid/gvalid_validator_message.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -63,18 +63,18 @@ var defaultMessages = map[string]string{ // getErrorMessageByRule retrieves and returns the error message for specified rule. // It firstly retrieves the message from custom message map, and then checks i18n manager, // it returns the default error message if it's not found in custom message map or i18n manager. -func getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string { +func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string { content := customMsgMap[ruleKey] if content != "" { return content } - content = gi18n.GetContent(fmt.Sprintf(`gf.gvalid.rule.%s`, ruleKey)) + content = gi18n.GetContent(fmt.Sprintf(`gf.gvalid.rule.%s`, ruleKey), v.i18nLang) if content == "" { content = defaultMessages[ruleKey] } // If there's no configured rule message, it uses default one. if content == "" { - content = gi18n.GetContent(`gf.gvalid.rule.__default__`) + content = gi18n.GetContent(`gf.gvalid.rule.__default__`, v.i18nLang) if content == "" { content = defaultMessages["__default__"] } diff --git a/util/gvalid/gvalid_rule_length.go b/util/gvalid/gvalid_validator_rule_length.go similarity index 80% rename from util/gvalid/gvalid_rule_length.go rename to util/gvalid/gvalid_validator_rule_length.go index 7d67284f8..df0f36189 100644 --- a/util/gvalid/gvalid_rule_length.go +++ b/util/gvalid/gvalid_validator_rule_length.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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,7 +15,7 @@ import ( // checkLength checks using length rules. // The length is calculated using unicode string, which means one chinese character or letter // both has the length of 1. -func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { +func (v *Validator) checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { var ( msg = "" runeArray = gconv.Runes(value) @@ -39,7 +39,7 @@ func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) } } if valueLen < min || valueLen > max { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) return msg @@ -48,14 +48,14 @@ func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) case "min-length": min, err := strconv.Atoi(ruleVal) if valueLen < min || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) } case "max-length": max, err := strconv.Atoi(ruleVal) if valueLen > max || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) } } diff --git a/util/gvalid/gvalid_rule_luhn.go b/util/gvalid/gvalid_validator_rule_luhn.go similarity index 82% rename from util/gvalid/gvalid_rule_luhn.go rename to util/gvalid/gvalid_validator_rule_luhn.go index ed8983dae..d6eff5fd1 100644 --- a/util/gvalid/gvalid_rule_luhn.go +++ b/util/gvalid/gvalid_validator_rule_luhn.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -8,7 +8,7 @@ package gvalid // checkLuHn checks with LUHN algorithm. // It's usually used for bank card number validation. -func checkLuHn(value string) bool { +func (v *Validator) checkLuHn(value string) bool { var ( sum = 0 nDigits = len(value) diff --git a/util/gvalid/gvalid_rule_range.go b/util/gvalid/gvalid_validator_rule_range.go similarity index 76% rename from util/gvalid/gvalid_rule_range.go rename to util/gvalid/gvalid_validator_rule_range.go index 1993f2ac8..7730cd610 100644 --- a/util/gvalid/gvalid_rule_range.go +++ b/util/gvalid/gvalid_validator_rule_range.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -12,7 +12,7 @@ import ( ) // checkRange checks using range rules. -func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { +func (v *Validator) checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { msg := "" switch ruleKey { // Value range. @@ -30,9 +30,9 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) max = v } } - v, err := strconv.ParseFloat(value, 10) - if v < min || v > max || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + valueF, err := strconv.ParseFloat(value, 10) + if valueF < min || valueF > max || err != nil { + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) } @@ -44,7 +44,7 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) valueN, err2 = strconv.ParseFloat(value, 10) ) if valueN < min || err1 != nil || err2 != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) } @@ -55,7 +55,7 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) valueN, err2 = strconv.ParseFloat(value, 10) ) if valueN > max || err1 != nil || err2 != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) } diff --git a/util/gvalid/gvalid_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go similarity index 93% rename from util/gvalid/gvalid_rule_required.go rename to util/gvalid/gvalid_validator_rule_required.go index c92456788..0578e3212 100644 --- a/util/gvalid/gvalid_rule_required.go +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -11,7 +11,7 @@ import ( ) // checkRequired checks using required rules. -func checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool { +func (v *Validator) checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool { required := false switch ruleKey { // Required. diff --git a/util/gvalid/gvalid_rule_resident_id.go b/util/gvalid/gvalid_validator_rule_resident_id.go similarity index 93% rename from util/gvalid/gvalid_rule_resident_id.go rename to util/gvalid/gvalid_validator_rule_resident_id.go index b43cc952d..8a83e949b 100644 --- a/util/gvalid/gvalid_rule_resident_id.go +++ b/util/gvalid/gvalid_validator_rule_resident_id.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, @@ -32,7 +32,7 @@ import ( // // 总: // (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$) -func checkResidentId(id string) bool { +func (v *Validator) checkResidentId(id string) bool { id = strings.ToUpper(strings.TrimSpace(id)) if len(id) != 18 { return false diff --git a/util/gvalid/gvalid_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go similarity index 99% rename from util/gvalid/gvalid_unit_basic_all_test.go rename to util/gvalid/gvalid_z_unit_basic_all_test.go index b1aea6849..81861aa14 100755 --- a/util/gvalid/gvalid_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/util/gvalid/gvalid_unit_checkmap_test.go b/util/gvalid/gvalid_z_unit_checkmap_test.go similarity index 98% rename from util/gvalid/gvalid_unit_checkmap_test.go rename to util/gvalid/gvalid_z_unit_checkmap_test.go index e29d9104a..4660ebf21 100755 --- a/util/gvalid/gvalid_unit_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_checkmap_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/util/gvalid/gvalid_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go similarity index 99% rename from util/gvalid/gvalid_unit_checkstruct_test.go rename to util/gvalid/gvalid_z_unit_checkstruct_test.go index 3e9f3aae5..4f630acae 100755 --- a/util/gvalid/gvalid_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/util/gvalid/gvalid_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go similarity index 98% rename from util/gvalid/gvalid_unit_custom_rule_test.go rename to util/gvalid/gvalid_z_unit_custom_rule_test.go index b6ec82d69..2fdc36a92 100644 --- a/util/gvalid/gvalid_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/util/gvalid/gvalid_unit_customerror_test.go b/util/gvalid/gvalid_z_unit_customerror_test.go similarity index 96% rename from util/gvalid/gvalid_unit_customerror_test.go rename to util/gvalid/gvalid_z_unit_customerror_test.go index ef76bb6bc..9f73afa4b 100755 --- a/util/gvalid/gvalid_unit_customerror_test.go +++ b/util/gvalid/gvalid_z_unit_customerror_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/util/gvalid/gvalid_unit_internal_test.go b/util/gvalid/gvalid_z_unit_internal_test.go similarity index 94% rename from util/gvalid/gvalid_unit_internal_test.go rename to util/gvalid/gvalid_z_unit_internal_test.go index 6a68cd6f2..89d38dcd6 100644 --- a/util/gvalid/gvalid_unit_internal_test.go +++ b/util/gvalid/gvalid_z_unit_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). 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, diff --git a/version.go b/version.go index 3bf3e1b03..82df3b549 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.15.0" +const VERSION = "v1.15.1" const AUTHORS = "john"