From 524e3bedc73db801a2bc2fa81234cddf091b45bf Mon Sep 17 00:00:00 2001 From: jflyfox Date: Mon, 26 Apr 2021 20:37:36 +0800 Subject: [PATCH 01/70] improve empty value validation for required-with* patterns for package gvalid --- internal/empty/empty.go | 12 ++++ os/gtime/gtime_time.go | 9 +++ util/gvalid/gvalid_validator_check.go | 12 ++-- util/gvalid/gvalid_validator_rule_required.go | 19 +++--- util/gvalid/gvalid_z_unit_basic_all_test.go | 66 +++++++++++++++++++ 5 files changed, 102 insertions(+), 16 deletions(-) diff --git a/internal/empty/empty.go b/internal/empty/empty.go index 234378aca..1d42441ff 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -9,6 +9,7 @@ package empty import ( "reflect" + "time" ) // apiString is used for type assert api for String(). @@ -26,6 +27,11 @@ type apiMapStrAny interface { MapStrAny() map[string]interface{} } +type apiTime interface { + Date() (year int, month time.Month, day int) + IsZero() bool +} + // IsEmpty checks whether given `value` empty. // It returns true if `value` is in: 0, nil, false, "", len(slice/map/chan) == 0, // or else it returns false. @@ -80,6 +86,12 @@ func IsEmpty(value interface{}) bool { return len(value) == 0 default: // Common interfaces checks. + if f, ok := value.(apiTime); ok { + if f == nil { + return true + } + return f.IsZero() + } if f, ok := value.(apiString); ok { if f == nil { return true diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 997cf43ad..871013ed4 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -220,6 +220,15 @@ func (t *Time) String() string { return t.Format("Y-m-d H:i:s") } +// IsZero reports whether t represents the zero time instant, +// January 1, year 1, 00:00:00 UTC. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + // Clone returns a new Time object which is a clone of current time object. func (t *Time) Clone() *Time { return New(t.Time) diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index a349f3486..831930df1 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -47,13 +47,11 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message // It converts value to string and then does the validation. var ( // Do not trim it as the space is also part of the value. - data = make(map[string]string) + data = make(map[string]interface{}) errorMsgArray = make(map[string]string) ) if len(params) > 0 { - for k, v := range gconv.Map(params[0]) { - data[k] = gconv.String(v) - } + data = gconv.Map(params[0]) } // Custom error messages handling. var ( @@ -150,7 +148,7 @@ func (v *Validator) doCheckBuildInRules( ruleKey string, rulePattern string, ruleItems []string, - dataMap map[string]string, + dataMap map[string]interface{}, customMsgMap map[string]string, ) (match bool, err error) { valueStr := gconv.String(value) @@ -235,7 +233,7 @@ func (v *Validator) doCheckBuildInRules( // Values of two fields should be equal as string. case "same": if v, ok := dataMap[rulePattern]; ok { - if strings.Compare(valueStr, v) == 0 { + if strings.Compare(valueStr, gconv.String(v)) == 0 { match = true } } @@ -250,7 +248,7 @@ func (v *Validator) doCheckBuildInRules( case "different": match = true if v, ok := dataMap[rulePattern]; ok { - if strings.Compare(valueStr, v) == 0 { + if strings.Compare(valueStr, gconv.String(v)) == 0 { match = false } } diff --git a/util/gvalid/gvalid_validator_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go index c86e1c8fd..969d7bb56 100644 --- a/util/gvalid/gvalid_validator_rule_required.go +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -7,6 +7,7 @@ package gvalid import ( + "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/util/gconv" "reflect" "strings" @@ -14,7 +15,7 @@ import ( // checkRequired checks `value` using required rules. // It also supports require checks for `value` of type: slice, map. -func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, params map[string]string) bool { +func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, dataMap map[string]interface{}) bool { required := false switch ruleKey { // Required. @@ -31,8 +32,8 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string for i := 0; i < len(array); { tk := array[i] tv := array[i+1] - if v, ok := params[tk]; ok { - if strings.Compare(tv, v) == 0 { + if v, ok := dataMap[tk]; ok { + if strings.Compare(tv, gconv.String(v)) == 0 { required = true break } @@ -51,8 +52,8 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string for i := 0; i < len(array); { tk := array[i] tv := array[i+1] - if v, ok := params[tk]; ok { - if strings.Compare(tv, v) == 0 { + if v, ok := dataMap[tk]; ok { + if strings.Compare(tv, gconv.String(v)) == 0 { required = false break } @@ -67,7 +68,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string required = false array := strings.Split(rulePattern, ",") for i := 0; i < len(array); i++ { - if params[array[i]] != "" { + if !empty.IsEmpty(dataMap[array[i]]) { required = true break } @@ -79,7 +80,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string required = true array := strings.Split(rulePattern, ",") for i := 0; i < len(array); i++ { - if params[array[i]] == "" { + if empty.IsEmpty(dataMap[array[i]]) { required = false break } @@ -91,7 +92,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string required = false array := strings.Split(rulePattern, ",") for i := 0; i < len(array); i++ { - if params[array[i]] == "" { + if empty.IsEmpty(dataMap[array[i]]) { required = true break } @@ -103,7 +104,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string required = true array := strings.Split(rulePattern, ",") for i := 0; i < len(array); i++ { - if params[array[i]] != "" { + if !empty.IsEmpty(dataMap[array[i]]) { required = false break } diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 25ffc0b65..bfde55995 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -88,6 +88,72 @@ func Test_RequiredWith(t *testing.T) { t.AssertNE(err2, nil) t.AssertNE(err3, nil) }) + // time.Time + gtest.C(t, func(t *gtest.T) { + rule := "required-with:id,time" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "time": time.Time{}, + } + err1 := gvalid.Check(val1, rule, nil, params1) + err2 := gvalid.Check(val1, rule, nil, params2) + err3 := gvalid.Check(val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + }) + gtest.C(t, func(t *gtest.T) { + rule := "required-with:id,time" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "time": time.Now(), + } + err1 := gvalid.Check(val1, rule, nil, params1) + err2 := gvalid.Check(val1, rule, nil, params2) + err3 := gvalid.Check(val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + }) + // gtime.Time + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: nil, + } + t.Assert(gvalid.CheckStruct(data, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: gtime.Now(), + } + t.AssertNE(gvalid.CheckStruct(data, nil), nil) + }) } func Test_RequiredWithAll(t *testing.T) { From 563509c4a6cc08f010450cc9a932b0eff494387f Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 1 May 2021 09:04:16 +0800 Subject: [PATCH 02/70] route map dumping updates --- net/ghttp/ghttp_server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 68092e4e4..509ae5f65 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -263,12 +263,12 @@ func (s *Server) GetRouterArray() []RouterItem { if len(item.handler.middleware) > 0 { for _, v := range item.handler.middleware { if item.Middleware != "" { - item.Middleware += "," + item.Middleware += "\n" } item.Middleware += gdebug.FuncName(v) } } - // If the domain does not exist in the dump map, it create the map. + // If the domain does not exist in the dump map, it creates the map. // The value of the map is a custom sorted array. if _, ok := m[item.Domain]; !ok { // Sort in ASC order. From 5856f74d83efe0292ba5ffa6cd7aaf371a538e04 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 08:10:35 +0800 Subject: [PATCH 03/70] up --- net/ghttp/ghttp_server.go | 2 +- net/ghttp/ghttp_server_handler.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 509ae5f65..7b347ce6d 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -263,7 +263,7 @@ func (s *Server) GetRouterArray() []RouterItem { if len(item.handler.middleware) > 0 { for _, v := range item.handler.middleware { if item.Middleware != "" { - item.Middleware += "\n" + item.Middleware += "," } item.Middleware += gdebug.FuncName(v) } diff --git a/net/ghttp/ghttp_server_handler.go b/net/ghttp/ghttp_server_handler.go index 5b0331ad4..bc2bb05d9 100644 --- a/net/ghttp/ghttp_server_handler.go +++ b/net/ghttp/ghttp_server_handler.go @@ -80,9 +80,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Close the request and response body // to release the file descriptor in time. - request.Request.Body.Close() + _ = request.Request.Body.Close() if request.Request.Response != nil { - request.Request.Response.Body.Close() + _ = request.Request.Response.Body.Close() } }() @@ -168,7 +168,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Automatically set the session id to cookie - // if it creates a new session id in this request. + // if it creates a new session id in this request + // and SessionCookieOutput is enabled. if s.config.SessionCookieOutput && request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() { From d7eb1cca070e5c21d3299addc949fb3109315c93 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 09:35:54 +0800 Subject: [PATCH 04/70] add nested transaction feature for package gdb --- database/gdb/gdb_transaction.go | 93 ++++++++++++- database/gdb/gdb_z_mysql_transaction_test.go | 134 +++++++++++++++++++ 2 files changed, 221 insertions(+), 6 deletions(-) diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index 5f4a35f79..7f71a3662 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -9,6 +9,7 @@ package gdb import ( "database/sql" "fmt" + "github.com/gogf/gf/util/gconv" "reflect" "github.com/gogf/gf/text/gregex" @@ -16,21 +17,101 @@ import ( // TX is the struct for transaction management. type TX struct { - db DB - tx *sql.Tx - master *sql.DB + db DB // db is the current gdb database manager. + tx *sql.Tx // tx is the raw and underlying transaction manager. + master *sql.DB // master is the raw and underlying database manager. + transactionCount int // transactionCount marks the times that Begins. } -// Commit commits the transaction. +const ( + transactionPointerPrefix = "transaction" +) + +// Commit commits current transaction. +// Note that it releases previous saved transaction point if it's in a nested transaction procedure, +// or else it commits the hole transaction. func (tx *TX) Commit() error { + if tx.transactionCount > 0 { + tx.transactionCount-- + _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey()) + return err + } return tx.tx.Commit() } -// Rollback aborts the transaction. +// Rollback aborts current transaction. +// Note that it aborts current transaction if it's in a nested transaction procedure, +// or else it aborts the hole transaction. func (tx *TX) Rollback() error { + if tx.transactionCount > 0 { + tx.transactionCount-- + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey()) + return err + } return tx.tx.Rollback() } +// Begin starts a nested transaction procedure. +func (tx *TX) Begin() error { + _, err := tx.Exec("SAVEPOINT " + tx.transactionKey()) + if err != nil { + return err + } + tx.transactionCount++ + return nil +} + +// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point. +// The parameter `point` specifies the point name that will be saved to server. +func (tx *TX) SavePoint(point string) error { + _, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point)) + return err +} + +// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction. +// The parameter `point` specifies the point name that was saved previously. +func (tx *TX) RollbackTo(point string) error { + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point)) + return err +} + +// transactionKey forms and returns the transaction key at current save point. +func (tx *TX) transactionKey() string { + return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount)) +} + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (tx *TX) Transaction(f func(tx *TX) error) (err error) { + err = tx.Begin() + if err != nil { + return err + } + defer func() { + if err == nil { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + } + if err != nil { + if e := tx.Rollback(); e != nil { + err = e + } + } else { + if e := tx.Commit(); e != nil { + err = e + } + } + }() + err = f(tx) + return +} + // Query does query operation on transaction. // See Core.Query. func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { @@ -221,7 +302,7 @@ func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Res return tx.Model(table).Data(list).Insert() } -// BatchInsert batch inserts data with ignore option. +// BatchInsertIgnore batch inserts data with ignore option. // The parameter `list` must be type of slice of map or struct. func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index 96b3dd978..264d59b47 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -789,3 +789,137 @@ func Test_Transaction_Panic(t *testing.T) { } }) } + +func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin() + t.AssertNil(err) + // tx begin. + err = tx.Begin() + t.AssertNil(err) + // tx rollback. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).Insert() + err = tx.Rollback() + t.AssertNil(err) + // tx commit. + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).Insert() + err = tx.Commit() + t.AssertNil(err) + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + }) +} + +func Test_Transaction_Nested_TX_Transaction(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var err error + err = db.Transaction(func(tx *gdb.TX) error { + // commit + err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + // rollback + err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + }) +} + +func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin() + t.AssertNil(err) + // tx save point. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).Insert() + err = tx.SavePoint("MyPoint") + t.AssertNil(err) + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).Insert() + // tx rollback to. + err = tx.RollbackTo("MyPoint") + t.AssertNil(err) + // tx commit. + err = tx.Commit() + t.AssertNil(err) + + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + }) +} From bd84b97614ef413a5f7d9df01ba7ade30cd948fb Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 12:17:06 +0800 Subject: [PATCH 05/70] add more Where*/Min/Max/Avg/Sum/CountColumn/Increment/Decrement/OrderAsc/OrderDesc/OrderRandom functions and associated unit testing cases for package gdb --- database/gdb/gdb_core.go | 11 +- database/gdb/gdb_model.go | 1 + database/gdb/gdb_model_condition.go | 175 +++++++++++++-- database/gdb/gdb_model_fields.go | 34 +-- database/gdb/gdb_model_insert.go | 12 + database/gdb/gdb_model_select.go | 65 +++++- database/gdb/gdb_model_update.go | 16 ++ database/gdb/gdb_model_utility.go | 20 +- database/gdb/gdb_z_mysql_model_test.go | 294 +++++++++++++++++++++++++ 9 files changed, 579 insertions(+), 49 deletions(-) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 8265c540a..099ada0c3 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -812,13 +812,19 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str switch value := v.(type) { case *Counter: if value.Value != 0 { - column := c.db.QuoteWord(value.Field) + column := k + if value.Field != "" { + column = c.db.QuoteWord(value.Field) + } fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) params = append(params, value.Value) } case Counter: if value.Value != 0 { - column := c.db.QuoteWord(value.Field) + column := k + if value.Field != "" { + column = c.db.QuoteWord(value.Field) + } fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) params = append(params, value.Value) } @@ -829,7 +835,6 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str fields = append(fields, c.db.QuoteWord(k)+"=?") params = append(params, v) } - } } updates = strings.Join(fields, ",") diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 4b109534f..ae734d69e 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -39,6 +39,7 @@ type Model struct { data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. batch int // Batch number for batch Insert/Replace/Save operations. filter bool // Filter data and where key-value pairs according to the fields of the table. + distinct string // Force the query to only return distinct results. lockInfo string // Lock for update or in shared lock. cacheEnabled bool // Enable sql result cache feature. cacheDuration time.Duration // Cache TTL duration. diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index a78c43047..3ece170d8 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -7,6 +7,7 @@ package gdb import ( + "fmt" "strings" ) @@ -58,7 +59,125 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { return m.Where(newWhere[0], newWhere[1:]...) } +// WhereBetween builds `xxx BETWEEN x AND y` statement. +func (m *Model) WhereBetween(column string, min, max interface{}) *Model { + return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) +} + +// WhereLike builds `xxx LIKE x` statement. +func (m *Model) WhereLike(column string, like interface{}) *Model { + return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like) +} + +// WhereIn builds `xxx IN (x)` statement. +func (m *Model) WhereIn(column string, in interface{}) *Model { + return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in) +} + +// WhereNull builds `xxx IS NULL` statement. +func (m *Model) WhereNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column))) + } + return model +} + +// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement. +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model { + return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) +} + +// WhereNotLike builds `xxx NOT LIKE x` statement. +func (m *Model) WhereNotLike(column string, like interface{}) *Model { + return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like) +} + +// WhereNotIn builds `xxx NOT IN (x)` statement. +func (m *Model) WhereNotIn(column string, in interface{}) *Model { + return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in) +} + +// WhereNotNull builds `xxx IS NOT NULL` statement. +func (m *Model) WhereNotNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column))) + } + return model +} + +// WhereOr adds "OR" condition to the where statement. +func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model { + model := m.getModel() + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) + } + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: whereHolderOr, + where: where, + args: args, + }) + return model +} + +// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions. +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) +} + +// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions. +func (m *Model) WhereOrLike(column string, like interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like) +} + +// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions. +func (m *Model) WhereOrIn(column string, in interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in) +} + +// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions. +func (m *Model) WhereOrNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column))) + } + return model +} + +// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions. +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) +} + +// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions. +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like) +} + +// WhereOrNotIn builds `xxx NOT IN (x)` statement. +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model { + return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in) +} + +// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions. +func (m *Model) WhereOrNotNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column))) + } + return model +} + +// Group sets the "GROUP BY" statement for the model. +func (m *Model) Group(groupBy string) *Model { + model := m.getModel() + model.groupBy = m.db.QuoteString(groupBy) + return model +} + // And adds "AND" condition to the where statement. +// Deprecated, use Where instead. func (m *Model) And(where interface{}, args ...interface{}) *Model { model := m.getModel() if model.whereHolder == nil { @@ -73,24 +192,9 @@ func (m *Model) And(where interface{}, args ...interface{}) *Model { } // Or adds "OR" condition to the where statement. +// Deprecated, use WhereOr instead. func (m *Model) Or(where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) - } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: whereHolderOr, - where: where, - args: args, - }) - return model -} - -// Group sets the "GROUP BY" statement for the model. -func (m *Model) Group(groupBy string) *Model { - model := m.getModel() - model.groupBy = m.db.QuoteString(groupBy) - return model + return m.WhereOr(where, args...) } // GroupBy is alias of Model.Group. @@ -102,11 +206,41 @@ func (m *Model) GroupBy(groupBy string) *Model { // Order sets the "ORDER BY" statement for the model. func (m *Model) Order(orderBy ...string) *Model { + if len(orderBy) == 0 { + return m + } model := m.getModel() model.orderBy = m.db.QuoteString(strings.Join(orderBy, " ")) return model } +// OrderAsc sets the "ORDER BY xxx ASC" statement for the model. +func (m *Model) OrderAsc(column string) *Model { + if len(column) == 0 { + return m + } + model := m.getModel() + model.orderBy = m.db.QuoteWord(column) + " ASC" + return model +} + +// OrderDesc sets the "ORDER BY xxx DESC" statement for the model. +func (m *Model) OrderDesc(column string) *Model { + if len(column) == 0 { + return m + } + model := m.getModel() + model.orderBy = m.db.QuoteWord(column) + " DESC" + return model +} + +// OrderRandom sets the "ORDER BY RANDOM()" statement for the model. +func (m *Model) OrderRandom(orderBy ...string) *Model { + model := m.getModel() + model.orderBy = "RAND()" + return model +} + // OrderBy is alias of Model.Order. // See Model.Order. // Deprecated, use Order instead. @@ -138,6 +272,13 @@ func (m *Model) Offset(offset int) *Model { return model } +// Distinct forces the query to only return distinct results. +func (m *Model) Distinct() *Model { + model := m.getModel() + model.distinct = "DISTINCT " + return model +} + // Page sets the paging number for the model. // The parameter `page` is started from 1 for paging. // Note that, it differs that the Limit function starts from 0 for "LIMIT" statement. diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index c10e00461..4d278358d 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -14,17 +14,6 @@ import ( "github.com/gogf/gf/util/gutil" ) -// Filter marks filtering the fields which does not exist in the fields of the operated table. -// Note that this function supports only single table operations. -func (m *Model) Filter() *Model { - if gstr.Contains(m.tables, " ") { - panic("function Filter supports only single table operations") - } - model := m.getModel() - model.filter = true - return model -} - // Fields sets the operation fields of the model, multiple fields joined using char ','. // The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct. func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { @@ -81,13 +70,25 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { return m } +// Filter marks filtering the fields which does not exist in the fields of the operated table. +// Note that this function supports only single table operations. +func (m *Model) Filter() *Model { + if gstr.Contains(m.tables, " ") { + panic("function Filter supports only single table operations") + } + model := m.getModel() + model.filter = true + return model +} + +// FieldsStr retrieves and returns all fields from the table, joined with char ','. +// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u."). // Deprecated, use GetFieldsStr instead. -// This function name confuses the user that it was a chaining function. func (m *Model) FieldsStr(prefix ...string) string { return m.GetFieldsStr(prefix...) } -// FieldsStr retrieves and returns all fields from the table, joined with char ','. +// GetFieldsStr retrieves and returns all fields from the table, joined with char ','. // The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u."). func (m *Model) GetFieldsStr(prefix ...string) string { prefixStr := "" @@ -116,13 +117,16 @@ func (m *Model) GetFieldsStr(prefix ...string) string { return newFields } +// FieldsExStr retrieves and returns fields which are not in parameter `fields` from the table, +// joined with char ','. +// The parameter `fields` specifies the fields that are excluded. +// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u."). // Deprecated, use GetFieldsExStr instead. -// This function name confuses the user that it was a chaining function. func (m *Model) FieldsExStr(fields string, prefix ...string) string { return m.GetFieldsExStr(fields, prefix...) } -// FieldsExStr retrieves and returns fields which are not in parameter `fields` from the table, +// GetFieldsExStr retrieves and returns fields which are not in parameter `fields` from the table, // joined with char ','. // The parameter `fields` specifies the fields that are excluded. // The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u."). diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index b0959d304..d67978b72 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -109,6 +109,18 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) { return m.doInsertWithOption(insertOptionDefault) } +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) { + if len(data) > 0 { + return m.Data(data...).InsertAndGetId() + } + result, err := m.doInsertWithOption(insertOptionDefault) + if err != nil { + return 0, err + } + return result.LastInsertId() +} + // InsertIgnore does "INSERT IGNORE INTO ..." statement for the model. // The optional parameter `data` is the same as the parameter of Model.Data function, // see Model.Data. diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 962592b33..31fd0007d 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -19,7 +19,7 @@ import ( // Select is alias of Model.All. // See Model.All. -// Deprecated. +// Deprecated, use All instead. func (m *Model) Select(where ...interface{}) (Result, error) { return m.All(where...) } @@ -50,7 +50,8 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) { // DISTINCT t.user_id uid return m.doGetAllBySql( fmt.Sprintf( - "SELECT %s FROM %s%s", + "SELECT %s%s FROM %s%s", + m.distinct, m.getFieldsFiltered(), m.tables, conditionWhere+conditionExtra, @@ -182,7 +183,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { } // Array queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. +// Note that if there are multiple columns in the result, it returns just one column values randomly. // // If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields // and fieldsAndWhere[1:] is treated as where condition fields. @@ -334,7 +335,7 @@ func (m *Model) Count(where ...interface{}) (int, error) { if m.fields != "" && m.fields != "*" { // DO NOT quote the m.fields here, in case of fields like: // DISTINCT t.user_id uid - countFields = fmt.Sprintf(`COUNT(%s)`, m.fields) + countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields) } conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true) s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra) @@ -353,6 +354,62 @@ func (m *Model) Count(where ...interface{}) (int, error) { return 0, nil } +// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model. +func (m *Model) CountColumn(column string) (int, error) { + if len(column) == 0 { + return 0, nil + } + return m.Fields(column).Count() +} + +// Min does "SELECT MIN(x) FROM ..." statement for the model. +func (m *Model) Min(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Max does "SELECT MAX(x) FROM ..." statement for the model. +func (m *Model) Max(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Avg does "SELECT AVG(x) FROM ..." statement for the model. +func (m *Model) Avg(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Sum does "SELECT SUM(x) FROM ..." statement for the model. +func (m *Model) Sum(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + // FindOne retrieves and returns a single Record by Model.WherePri and Model.One. // Also see Model.WherePri and Model.One. func (m *Model) FindOne(where ...interface{}) (Record, error) { diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index b1f15ff62..ad65e723f 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -89,3 +89,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro m.mergeArguments(conditionArgs)..., ) } + +// Increment increments a column's value by a given amount. +func (m *Model) Increment(column string, amount float64) (sql.Result, error) { + return m.getModel().Data(column, &Counter{ + Field: column, + Value: amount, + }).Update() +} + +// Decrement decrements a column's value by a given amount. +func (m *Model) Decrement(column string, amount float64) (sql.Result, error) { + return m.getModel().Data(column, &Counter{ + Field: column, + Value: -amount, + }).Update() +} diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 93890326b..944946bb6 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -18,16 +18,6 @@ import ( "time" ) -// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns -// the current model. -func (m *Model) getModel() *Model { - if !m.safe { - return m - } else { - return m.Clone() - } -} - // TableFields retrieves and returns the fields information of specified table of current // schema. // @@ -47,6 +37,16 @@ func (m *Model) TableFields(table string, schema ...string) (fields map[string]* return m.db.TableFields(link, table, schema...) } +// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns +// the current model. +func (m *Model) getModel() *Model { + if !m.safe { + return m + } else { + return m.Clone() + } +} + // mappingAndFilterToTableFields mappings and changes given field name to really table field name. // Eg: // ID -> id diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 0eb1b454a..e74123571 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -2778,6 +2778,11 @@ func Test_Model_Distinct(t *testing.T) { t.AssertNil(err) t.Assert(len(all), 2) }) + gtest.C(t, func(t *gtest.T) { + count, err := db.Model(table).Where("id > 1").Distinct().Count() + t.AssertNil(err) + t.Assert(count, 9) + }) } func Test_Model_Min_Max(t *testing.T) { @@ -3321,3 +3326,292 @@ func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) { t.Assert(one["number"].String(), "n") }) } + +func Test_Model_WhereIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3, 4}).WhereIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) +} + +func Test_Model_WhereNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotIn("id", g.Slice{1, 2, 3, 4}).WhereNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 6) + t.Assert(result[1]["id"], 7) + }) +} + +func Test_Model_WhereOrIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrIn("id", g.Slice{1, 2, 3, 4}).WhereOrIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 5) + }) +} + +func Test_Model_WhereOrNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotIn("id", g.Slice{1, 2, 3, 4}).WhereOrNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 7) + }) +} + +func Test_Model_WhereBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereBetween("id", 1, 4).WhereBetween("id", 3, 5).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) +} + +func Test_Model_WhereNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotBetween("id", 2, 8).WhereNotBetween("id", 3, 100).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereOrBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrBetween("id", 1, 4).WhereOrBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 5) + t.Assert(result[4]["id"], 1) + }) +} + +func Test_Model_WhereOrNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + //db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotBetween("id", 1, 4).WhereOrNotBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 10) + t.Assert(result[4]["id"], 6) + }) +} + +func Test_Model_WhereLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrLike("nickname", "namexxx%").WhereOrLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotLike("nickname", "namexxx%").WhereOrNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNull("nickname").WhereNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotNull("nickname").WhereNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").OrderRandom().All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotNull("nickname").WhereOrNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_Min_Max_Avg_Sum(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Min("id") + t.AssertNil(err) + t.Assert(result, 1) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Max("id") + t.AssertNil(err) + t.Assert(result, TableSize) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Avg("id") + t.AssertNil(err) + t.Assert(result, 5.5) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Sum("id") + t.AssertNil(err) + t.Assert(result, 55) + }) +} + +func Test_Model_CountColumn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).CountColumn("id") + t.AssertNil(err) + t.Assert(result, TableSize) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).CountColumn("id") + t.AssertNil(err) + t.Assert(result, 3) + }) +} + +func Test_Model_InsertAndGetId(t *testing.T) { + table := createTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + id, err := db.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).InsertAndGetId() + t.AssertNil(err) + t.Assert(id, 1) + }) + gtest.C(t, func(t *gtest.T) { + id, err := db.Model(table).Data(g.Map{ + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).InsertAndGetId() + t.AssertNil(err) + t.Assert(id, 2) + }) +} + +func Test_Model_Increment_Decrement(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 1).Increment("id", 100) + t.AssertNil(err) + rows, _ := result.RowsAffected() + t.Assert(rows, 1) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 101).Decrement("id", 10) + t.AssertNil(err) + rows, _ := result.RowsAffected() + t.Assert(rows, 1) + }) + gtest.C(t, func(t *gtest.T) { + count, err := db.Model(table).Where("id", 91).Count() + t.AssertNil(err) + t.Assert(count, 1) + }) +} From cdc97e9b2b7916c5b3f2efe1f5489b012f182474 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 15:58:28 +0800 Subject: [PATCH 06/70] improve logging for transaction feature for package gdb --- .../database/gdb/mysql/gdb_transaction.go | 27 +++++++++++ .../gdb/mysql/gdb_transaction_closure.go | 34 ++++++++++++++ .../gdb/mysql/gdb_transaction_savepoint.go | 40 +++++++++++++++++ database/gdb/gdb.go | 6 ++- database/gdb/gdb_transaction.go | 45 ++++++++++++++++++- 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 .example/database/gdb/mysql/gdb_transaction.go create mode 100644 .example/database/gdb/mysql/gdb_transaction_closure.go create mode 100644 .example/database/gdb/mysql/gdb_transaction_savepoint.go diff --git a/.example/database/gdb/mysql/gdb_transaction.go b/.example/database/gdb/mysql/gdb_transaction.go new file mode 100644 index 000000000..fe5642ca2 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + db = g.DB() + table = "user" + ) + tx, err := db.Begin() + if err != nil { + panic(err) + } + if err = tx.Begin(); err != nil { + panic(err) + } + _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() + if err = tx.Rollback(); err != nil { + panic(err) + } + _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + if err = tx.Commit(); err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_transaction_closure.go b/.example/database/gdb/mysql/gdb_transaction_closure.go new file mode 100644 index 000000000..aa05d48d4 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction_closure.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + err error + db = g.DB() + table = "user" + ) + if err = db.Transaction(func(tx *gdb.TX) error { + // Nested transaction 1. + if err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + }); err != nil { + return err + } + return nil + }); err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_transaction_savepoint.go b/.example/database/gdb/mysql/gdb_transaction_savepoint.go new file mode 100644 index 000000000..e677f15ab --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction_savepoint.go @@ -0,0 +1,40 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + err error + db = g.DB() + table = "user" + ) + tx, err := db.Begin() + if err != nil { + panic(err) + } + defer func() { + if err := recover(); err != nil { + _ = tx.Rollback() + } + }() + if _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert(); err != nil { + panic(err) + } + if err = tx.SavePoint("MyPoint"); err != nil { + panic(err) + } + if _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert(); err != nil { + panic(err) + } + if _, err = tx.Model(table).Data(g.Map{"id": 3, "name": "green"}).Insert(); err != nil { + panic(err) + } + if err = tx.RollbackTo("MyPoint"); err != nil { + panic(err) + } + if err = tx.Commit(); err != nil { + panic(err) + } +} diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index b9a448b7d..be69262a6 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -32,11 +32,11 @@ type DB interface { // Model creation. // =========================================================================== + // Table function is deprecated, use Model instead. // The DB interface is designed not only for // relational databases but also for NoSQL databases in the future. The name // "Table" is not proper for that purpose any more. - // Also see Core.Table. - // Deprecated, use Model instead. + // Also see Core.Table. Table(table ...string) *Model // Model creates and returns a new ORM model from given schema. @@ -191,6 +191,8 @@ type DB interface { mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) // See Core.mappingAndFilterData. convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} // See Core.convertFieldValueToLocalValue. convertRowsToResult(rows *sql.Rows) (Result, error) // See Core.convertRowsToResult. + addSqlToTracing(ctx context.Context, sql *Sql) // See Core.addSqlToTracing. + writeSqlToLogger(v *Sql) // See Core.writeSqlToLogger. } // Core is the base struct for database management. diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index 7f71a3662..63031b815 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -9,6 +9,7 @@ package gdb import ( "database/sql" "fmt" + "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" "reflect" @@ -36,7 +37,27 @@ func (tx *TX) Commit() error { _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey()) return err } - return tx.tx.Commit() + var ( + sqlStr = "COMMIT" + mTime1 = gtime.TimestampMilli() + err = tx.tx.Commit() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "TX.Commit", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + } + ) + tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj) + if tx.db.GetDebug() { + tx.db.writeSqlToLogger(sqlObj) + } + return err } // Rollback aborts current transaction. @@ -48,7 +69,27 @@ func (tx *TX) Rollback() error { _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey()) return err } - return tx.tx.Rollback() + var ( + sqlStr = "ROLLBACK" + mTime1 = gtime.TimestampMilli() + err = tx.tx.Rollback() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "TX.Rollback", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + } + ) + tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj) + if tx.db.GetDebug() { + tx.db.writeSqlToLogger(sqlObj) + } + return err } // Begin starts a nested transaction procedure. From 4b15ab5e99d0de8a62b9f4f73071f89cef6e0c40 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 16:42:34 +0800 Subject: [PATCH 07/70] improve OrderRandom function for package gdb --- database/gdb/gdb_model_condition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 3ece170d8..b71288647 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -235,7 +235,7 @@ func (m *Model) OrderDesc(column string) *Model { } // OrderRandom sets the "ORDER BY RANDOM()" statement for the model. -func (m *Model) OrderRandom(orderBy ...string) *Model { +func (m *Model) OrderRandom() *Model { model := m.getModel() model.orderBy = "RAND()" return model From df1ef5db78c0d8ab10a47817397b12f51560cf84 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 17:57:00 +0800 Subject: [PATCH 08/70] add example for package gdb --- .example/database/gdb/mysql/gdb_distinct.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .example/database/gdb/mysql/gdb_distinct.go diff --git a/.example/database/gdb/mysql/gdb_distinct.go b/.example/database/gdb/mysql/gdb_distinct.go new file mode 100644 index 000000000..189422f6b --- /dev/null +++ b/.example/database/gdb/mysql/gdb_distinct.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + g.DB().Model("user").Distinct().CountColumn("uid,name") +} From a8c3d07d9ff44495d63ebaa7268f63ceeb93db37 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 22:35:47 +0800 Subject: [PATCH 09/70] improve with feature for package gdb --- .../database/gdb/mysql/gdb_with_insert.go | 66 ++++++++++++++++ .example/database/gdb/mysql/gdb_with_slect.go | 37 +++++++++ database/gdb/gdb.go | 6 +- database/gdb/gdb_func.go | 2 +- database/gdb/gdb_model.go | 54 ++++++++----- database/gdb/gdb_model_fields.go | 1 + database/gdb/gdb_model_with.go | 14 ++-- .../gdb/gdb_z_mysql_association_with_test.go | 75 +++++++++++++------ database/gdb/gdb_z_mysql_method_test.go | 49 ++++++------ database/gdb/gdb_z_mysql_model_test.go | 49 ++++++------ frame/g/g_object.go | 13 +--- 11 files changed, 258 insertions(+), 108 deletions(-) create mode 100644 .example/database/gdb/mysql/gdb_with_insert.go create mode 100644 .example/database/gdb/mysql/gdb_with_slect.go diff --git a/.example/database/gdb/mysql/gdb_with_insert.go b/.example/database/gdb/mysql/gdb_with_insert.go new file mode 100644 index 000000000..669ab3ce6 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_with_insert.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +func main() { + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` + } + + db := g.DB() + db.Transaction(func(tx *gdb.TX) error { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + if err != nil { + return err + } + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + if err != nil { + return err + } + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert() + if err != nil { + return err + } + } + } + return nil + }) +} diff --git a/.example/database/gdb/mysql/gdb_with_slect.go b/.example/database/gdb/mysql/gdb_with_slect.go new file mode 100644 index 000000000..e15cf40f9 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_with_slect.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +func main() { + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` + } + + db := g.DB() + var user *User + err := db.Model(user).WithAll().Where("id", 3).Scan(&user) + if err != nil { + panic(err) + } + g.Dump(user) +} diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index be69262a6..24e6228db 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -37,7 +37,7 @@ type DB interface { // relational databases but also for NoSQL databases in the future. The name // "Table" is not proper for that purpose any more. // Also see Core.Table. - Table(table ...string) *Model + Table(tableNameOrStruct ...interface{}) *Model // Model creates and returns a new ORM model from given schema. // The parameter `table` can be more than one table names, and also alias name, like: @@ -48,7 +48,7 @@ type DB interface { // Model("user u, user_detail ud") // 2. Model name with alias: Model("user", "u") // Also see Core.Model. - Model(table ...string) *Model + Model(tableNameOrStruct ...interface{}) *Model // Schema creates and returns a schema. // Also see Core.Schema. @@ -56,7 +56,7 @@ type DB interface { // With creates and returns an ORM model based on meta data of given object. // Also see Core.With. - With(object interface{}) *Model + With(objects ...interface{}) *Model // Open creates a raw connection object for database with given node configuration. // Note that it is not recommended using the this function manually. diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 162f08e8c..ec2b7d7dc 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -235,7 +235,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} { name = "" fieldTag = rtField.Tag for _, tag := range structTagPriority { - if s := fieldTag.Get(tag); s != "" { + if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) { name = s break } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index ae734d69e..f345ad857 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -70,53 +70,71 @@ const ( // Table is alias of Core.Model. // See Core.Model. // Deprecated, use Model instead. -func (c *Core) Table(table ...string) *Model { - return c.db.Model(table...) +func (c *Core) Table(tableNameOrStruct ...interface{}) *Model { + return c.db.Model(tableNameOrStruct...) } // Model creates and returns a new ORM model from given schema. -// The parameter `table` can be more than one table names, and also alias name, like: +// The parameter `tableNameOrStruct` can be more than one table names, and also alias name, like: // 1. Model names: // Model("user") // Model("user u") // Model("user, user_detail") // Model("user u, user_detail ud") // 2. Model name with alias: Model("user", "u") -func (c *Core) Model(table ...string) *Model { - tables := "" - if len(table) > 1 { - tables = fmt.Sprintf( - `%s AS %s`, c.db.QuotePrefixTableName(table[0]), c.db.QuoteWord(table[1]), +func (c *Core) Model(tableNameOrStruct ...interface{}) *Model { + // With feature checks. + if len(tableNameOrStruct) > 0 { + if _, ok := tableNameOrStruct[0].(string); !ok { + return c.With(tableNameOrStruct...) + } + } + // Normal model creation. + var ( + tableStr = "" + tableNames = make([]string, len(tableNameOrStruct)) + ) + for k, v := range tableNameOrStruct { + if s, ok := v.(string); ok { + tableNames[k] = s + continue + } + } + + if len(tableNames) > 1 { + tableStr = fmt.Sprintf( + `%s AS %s`, c.db.QuotePrefixTableName(tableNames[0]), c.db.QuoteWord(tableNames[1]), ) - } else if len(table) == 1 { - tables = c.db.QuotePrefixTableName(table[0]) + } else if len(tableNames) == 1 { + tableStr = c.db.QuotePrefixTableName(tableNames[0]) } return &Model{ db: c.db, - tablesInit: tables, - tables: tables, + tablesInit: tableStr, + tables: tableStr, fields: "*", start: -1, offset: -1, option: OptionAllowEmpty, + filter: true, } } // With creates and returns an ORM model based on meta data of given object. -func (c *Core) With(object interface{}) *Model { - return c.db.Model().With(object) +func (c *Core) With(objects ...interface{}) *Model { + return c.db.Model().With(objects...) } // Table is alias of tx.Model. // Deprecated, use Model instead. -func (tx *TX) Table(table ...string) *Model { - return tx.Model(table...) +func (tx *TX) Table(tableNameOrStruct ...interface{}) *Model { + return tx.Model(tableNameOrStruct...) } // Model acts like Core.Model except it operates on transaction. // See Core.Model. -func (tx *TX) Model(table ...string) *Model { - model := tx.db.Model(table...) +func (tx *TX) Model(tableNameOrStruct ...interface{}) *Model { + model := tx.db.Model(tableNameOrStruct...) model.db = tx.db model.tx = tx return model diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index 4d278358d..be082b070 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -72,6 +72,7 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { // Filter marks filtering the fields which does not exist in the fields of the operated table. // Note that this function supports only single table operations. +// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used. func (m *Model) Filter() *Model { if gstr.Contains(m.tables, " ") { panic("function Filter supports only single table operations") diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 8ead1401c..2810d9a25 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -32,13 +32,17 @@ import ( // db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx) // Or: // db.With(UserDetail{}).With(UserDetail{}).Scan(xxx) -func (m *Model) With(object interface{}) *Model { +// Or: +// db.With(UserDetail{}, UserDetail{}).Scan(xxx) +func (m *Model) With(objects ...interface{}) *Model { model := m.getModel() - if m.tables == "" { - m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object)) - return model + for _, object := range objects { + if m.tables == "" { + m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object)) + return model + } + model.withArray = append(model.withArray, object) } - model.withArray = append(model.withArray, object) return model } diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index f4633f50b..d8a34cae9 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -18,7 +18,7 @@ func Test_Table_Relation_With_Scan(t *testing.T) { var ( tableUser = "user" tableUserDetail = "user_detail" - tableUserScores = "user_scores" + tableUserScores = "user_score" ) if _, err := db.Exec(fmt.Sprintf(` CREATE TABLE IF NOT EXISTS %s ( @@ -60,8 +60,8 @@ PRIMARY KEY (id) Address string `json:"address"` } - type UserScores struct { - gmeta.Meta `orm:"table:user_scores"` + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` Id int `json:"id"` Uid int `json:"uid"` Score int `json:"score"` @@ -69,34 +69,61 @@ PRIMARY KEY (id) type User struct { gmeta.Meta `orm:"table:user"` - Id int `json:"id"` - Name string `json:"name"` - UserDetail *UserDetail `orm:"with:uid=id"` - UserScores []*UserScores `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` } // Initialize the data. - var err error + gtest.C(t, func(t *gtest.T) { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + t.AssertNil(err) + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert() + t.AssertNil(err) + } + } + }) for i := 1; i <= 5; i++ { // User. - _, err = db.Insert(tableUser, g.Map{ - "id": i, - "name": fmt.Sprintf(`name_%d`, i), - }) - gtest.Assert(err, nil) + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + gtest.AssertNil(err) // Detail. - _, err = db.Insert(tableUserDetail, g.Map{ - "uid": i, - "address": fmt.Sprintf(`address_%d`, i), - }) - gtest.Assert(err, nil) + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).Insert() + gtest.AssertNil(err) // Scores. for j := 1; j <= 5; j++ { - _, err = db.Insert(tableUserScores, g.Map{ - "uid": i, - "score": j, - }) - gtest.Assert(err, nil) + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).Insert() + gtest.AssertNil(err) } } gtest.C(t, func(t *gtest.T) { @@ -139,7 +166,7 @@ PRIMARY KEY (id) var user *User err := db.With(User{}). With(UserDetail{}). - With(UserScores{}). + With(UserScore{}). Where("id", 4). Scan(&user) t.AssertNil(err) diff --git a/database/gdb/gdb_z_mysql_method_test.go b/database/gdb/gdb_z_mysql_method_test.go index 9db3b9f6c..d83f2f364 100644 --- a/database/gdb/gdb_z_mysql_method_test.go +++ b/database/gdb/gdb_z_mysql_method_test.go @@ -274,30 +274,31 @@ func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) { }) } -func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) { - table := createTable() - defer dropTable(table) - - gtest.C(t, func(t *gtest.T) { - type User struct { - Id int - Passport string - Password string - Nickname string - CreateTime string - NoneExistField string - } - data := User{ - Id: 1, - Passport: "user_1", - Password: "pass_1", - Nickname: "name_1", - CreateTime: "2020-10-10 12:00:01", - } - _, err := db.Insert(table, data) - t.AssertNE(err, nil) - }) -} +// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. +//func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) { +// table := createTable() +// defer dropTable(table) +// +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Id int +// Passport string +// Password string +// Nickname string +// CreateTime string +// NoneExistField string +// } +// data := User{ +// Id: 1, +// Passport: "user_1", +// Password: "pass_1", +// Nickname: "name_1", +// CreateTime: "2020-10-10 12:00:01", +// } +// _, err := db.Insert(table, data) +// t.AssertNE(err, nil) +// }) +//} func Test_DB_InsertIgnore(t *testing.T) { table := createInitTable() diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index e74123571..f7a5fc857 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -240,30 +240,31 @@ func Test_Model_Update_KeyFieldNameMapping(t *testing.T) { }) } -func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) { - table := createTable() - defer dropTable(table) - - gtest.C(t, func(t *gtest.T) { - type User struct { - Id int - Passport string - Password string - Nickname string - CreateTime string - NoneExistFiled string - } - data := User{ - Id: 1, - Passport: "user_1", - Password: "pass_1", - Nickname: "name_1", - CreateTime: "2020-10-10 12:00:01", - } - _, err := db.Model(table).Data(data).Insert() - t.AssertNE(err, nil) - }) -} +// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. +//func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) { +// table := createTable() +// defer dropTable(table) +// +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Id int +// Passport string +// Password string +// Nickname string +// CreateTime string +// NoneExistFiled string +// } +// data := User{ +// Id: 1, +// Passport: "user_1", +// Password: "pass_1", +// Nickname: "name_1", +// CreateTime: "2020-10-10 12:00:01", +// } +// _, err := db.Model(table).Data(data).Insert() +// t.AssertNE(err, nil) +// }) +//} func Test_Model_Insert_Time(t *testing.T) { table := createTable() diff --git a/frame/g/g_object.go b/frame/g/g_object.go index 6e0dceb64..d4bdfdef0 100644 --- a/frame/g/g_object.go +++ b/frame/g/g_object.go @@ -97,18 +97,13 @@ func DB(name ...string) gdb.DB { // relational databases but also for NoSQL databases in the future. The name // "Table" is not proper for that purpose any more. // Deprecated, use Model instead. -func Table(tables ...string) *gdb.Model { - return DB().Model(tables...) +func Table(tableNameOrStruct ...interface{}) *gdb.Model { + return DB().Model(tableNameOrStruct...) } // Model creates and returns a model based on configuration of default database group. -func Model(tables ...string) *gdb.Model { - return DB().Model(tables...) -} - -// With creates and returns an ORM model based on meta data of given object. -func With(object interface{}) *gdb.Model { - return DB().With(object) +func Model(tableNameOrStruct ...interface{}) *gdb.Model { + return DB().Model(tableNameOrStruct...) } // Redis returns an instance of redis client with specified configuration group name. From 742653ce75121b02060b6b92e9f550c57ff1396a Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 May 2021 23:28:24 +0800 Subject: [PATCH 10/70] improve Model function for struct parameter that can retrieve table name tag from --- database/gdb/gdb_model.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index f345ad857..ba834a457 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -83,21 +83,16 @@ func (c *Core) Table(tableNameOrStruct ...interface{}) *Model { // Model("user u, user_detail ud") // 2. Model name with alias: Model("user", "u") func (c *Core) Model(tableNameOrStruct ...interface{}) *Model { - // With feature checks. - if len(tableNameOrStruct) > 0 { - if _, ok := tableNameOrStruct[0].(string); !ok { - return c.With(tableNameOrStruct...) - } - } - // Normal model creation. var ( tableStr = "" + tableName = "" tableNames = make([]string, len(tableNameOrStruct)) ) for k, v := range tableNameOrStruct { if s, ok := v.(string); ok { tableNames[k] = s - continue + } else if tableName = getTableNameFromOrmTag(v); tableName != "" { + tableNames[k] = tableName } } From 6a80091fefde3fdce8bb19fe8689a502a1bb5729 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 3 May 2021 00:00:29 +0800 Subject: [PATCH 11/70] version updates --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 189fb2e19..78fbc6cf1 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.15.6" +const VERSION = "v1.15.7" const AUTHORS = "john" From 034a3f180888759c9f584df230183ab27b3b9064 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 11 May 2021 19:20:39 +0800 Subject: [PATCH 12/70] improve struct validation --- internal/empty/empty.go | 122 ++++++++++++++++++ util/gvalid/gvalid.go | 10 ++ util/gvalid/gvalid_validator.go | 12 +- util/gvalid/gvalid_validator_check.go | 14 +- util/gvalid/gvalid_validator_check_struct.go | 49 +++++-- ...lid_z_unit_checkstructwithparammap_test.go | 39 ++++++ 6 files changed, 227 insertions(+), 19 deletions(-) create mode 100755 util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go diff --git a/internal/empty/empty.go b/internal/empty/empty.go index 1d42441ff..023f759a9 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -85,7 +85,9 @@ func IsEmpty(value interface{}) bool { case map[string]interface{}: return len(value) == 0 default: + // ========================= // Common interfaces checks. + // ========================= if f, ok := value.(apiTime); ok { if f == nil { return true @@ -163,6 +165,126 @@ func IsEmpty(value interface{}) bool { return false } +// IsEmptyLength checks whether given `value` is empty length. +// It returns true if `value` is in: nil, "", len(slice/map/chan) == 0, +// or else it returns false. +//func IsEmptyLength(value interface{}) bool { +// if value == nil { +// return true +// } +// // It firstly checks the variable as common types using assertion to enhance the performance, +// // and then using reflection. +// switch value := value.(type) { +// case +// int, +// int8, +// int16, +// int32, +// int64, +// uint, +// uint8, +// uint16, +// uint32, +// uint64, +// float32, +// float64, +// bool: +// return false +// case string: +// return value == "" +// case []byte: +// return len(value) == 0 +// case []rune: +// return len(value) == 0 +// case []int: +// return len(value) == 0 +// case []string: +// return len(value) == 0 +// case []float32: +// return len(value) == 0 +// case []float64: +// return len(value) == 0 +// case map[string]interface{}: +// return len(value) == 0 +// default: +// // ========================= +// // Common interfaces checks. +// // ========================= +// if f, ok := value.(apiTime); ok { +// if f == nil { +// return true +// } +// return f.IsZero() +// } +// if f, ok := value.(apiString); ok { +// if f == nil { +// return true +// } +// return f.String() == "" +// } +// if f, ok := value.(apiInterfaces); ok { +// if f == nil { +// return true +// } +// return len(f.Interfaces()) == 0 +// } +// if f, ok := value.(apiMapStrAny); ok { +// if f == nil { +// return true +// } +// return len(f.MapStrAny()) == 0 +// } +// // Finally using reflect. +// var rv reflect.Value +// if v, ok := value.(reflect.Value); ok { +// rv = v +// } else { +// rv = reflect.ValueOf(value) +// } +// +// switch rv.Kind() { +// case +// reflect.Int, +// reflect.Int8, +// reflect.Int16, +// reflect.Int32, +// reflect.Int64, +// reflect.Uint, +// reflect.Uint8, +// reflect.Uint16, +// reflect.Uint32, +// reflect.Uint64, +// reflect.Uintptr, +// reflect.Float32, +// reflect.Float64, +// reflect.Bool: +// return false +// case reflect.String: +// return rv.Len() == 0 +// case reflect.Struct: +// for i := 0; i < rv.NumField(); i++ { +// if !IsEmpty(rv) { +// return false +// } +// } +// return true +// case reflect.Chan, +// reflect.Map, +// reflect.Slice, +// reflect.Array: +// return rv.Len() == 0 +// case reflect.Func, +// reflect.Ptr, +// reflect.Interface, +// reflect.UnsafePointer: +// if rv.IsNil() { +// return true +// } +// } +// } +// return false +//} + // IsNil checks whether given `value` is nil. // Parameter `traceSource` is used for tracing to the source variable if given `value` is type // of a pinter that also points to a pointer. It returns nil if the source is nil when `traceSource` diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 42351d218..0b9e18d7e 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -193,6 +193,16 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) * return defaultValidator.CheckStruct(object, rules, messages...) } +// CheckStructWithParamMap validates struct with given parameter map and returns the error result. +// +// The parameter `object` should be type of struct/*struct. +// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +// The optional parameter `messages` specifies the custom error messages for specified keys and rules. +func CheckStructWithParamMap(object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.CheckStructWithParamMap(object, paramMap, 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 index 49c8505aa..5709092f1 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -6,9 +6,12 @@ package gvalid +import "context" + // Validator is the validation manager. type Validator struct { - i18nLang string // I18n language. + i18nLang string // I18n language. + ctx context.Context // Context containing custom context variables. } // New creates and returns a new Validator. @@ -29,3 +32,10 @@ func (v *Validator) I18n(language string) *Validator { newValidator.i18nLang = language return newValidator } + +// Ctx is a chaining operation function which sets the context for next validation. +func (v *Validator) Ctx(ctx context.Context) *Validator { + newValidator := v.Clone() + newValidator.ctx = ctx + return newValidator +} diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index 831930df1..d3400fd48 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -34,12 +34,12 @@ type apiTime interface { // string/map/struct/*struct. // The optional parameter `params` specifies the extra validation parameters for some rules // like: required-*、same、different, etc. -func (v *Validator) Check(value interface{}, rules string, messages interface{}, params ...interface{}) *Error { - return v.doCheck("", value, rules, messages, params...) +func (v *Validator) Check(value interface{}, rules string, messages interface{}, paramMap ...interface{}) *Error { + return v.doCheck("", value, rules, messages, paramMap...) } // doCheck does the really rules validation for single key-value. -func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { +func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) *Error { // If there's no validation rules, it does nothing and returns quickly. if rules == "" { return nil @@ -50,8 +50,8 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message data = make(map[string]interface{}) errorMsgArray = make(map[string]string) ) - if len(params) > 0 { - data = gconv.Map(params[0]) + if len(paramMap) > 0 { + data = gconv.Map(paramMap[0]) } // Custom error messages handling. var ( @@ -107,8 +107,8 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message dataMap map[string]interface{} message = v.getErrorMessageByRule(ruleKey, customMsgMap) ) - if len(params) > 0 { - dataMap = gconv.Map(params[0]) + if len(paramMap) > 0 { + dataMap = gconv.Map(paramMap[0]) } if err := f(ruleItems[index], value, message, dataMap); err != nil { match = false diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 3180806f4..6709f08ea 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -9,6 +9,7 @@ package gvalid import ( "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" "reflect" "strings" ) @@ -25,15 +26,25 @@ var ( // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { + return v.CheckStructWithParamMap(object, nil, rules, messages...) +} + +// CheckStructWithParamMap validates struct with given parameter map and returns the error result. +// +// The parameter `object` should be type of struct/*struct. +// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +// The optional parameter `messages` specifies the custom error messages for specified keys and rules. +func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { var ( - errorMaps = make(ErrorMap) // Returned error. + errorMaps = make(ErrorMap) // Returning error. ) - mapField, err := structs.FieldMap(object, aliasNameTagPriority) + fieldMap, err := structs.FieldMap(object, aliasNameTagPriority) if err != nil { return newErrorStr("invalid_object", err.Error()) } // It checks the struct recursively the its attribute is also a struct. - for _, field := range mapField { + for _, field := range fieldMap { if field.OriginalKind() == reflect.Struct { if err := v.CheckStruct(field.Value, rules, messages...); err != nil { // It merges the errors into single error map. @@ -52,13 +63,19 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages if len(tagField) == 0 && rules == nil { return nil } + var ( - params = make(map[string]interface{}) + inputParamMap map[string]interface{} checkRules = make(map[string]string) customMessage = make(CustomMsg) fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names. errorRules = make([]string, 0) // Sequence rules. ) + if paramMap == nil { + inputParamMap = make(map[string]interface{}) + } else { + inputParamMap = gconv.Map(paramMap) + } switch v := rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. @@ -103,9 +120,19 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages return nil } // Checks and extends the parameters map with struct alias tag. - for nameOrTag, field := range mapField { - params[nameOrTag] = field.Value.Interface() - params[field.Name()] = field.Value.Interface() + for nameOrTag, field := range fieldMap { + if _, ok := inputParamMap[nameOrTag]; !ok { + if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, nameOrTag); foundKey != "" { + inputParamMap[nameOrTag] = foundValue + inputParamMap[field.Name()] = foundValue + } else { + // If the custom tag alias name is quite different from the attribute name, + // it just uses the value of the attribute. + // Note that the attribute may have default value according to its type. + inputParamMap[nameOrTag] = field.Value.Interface() + inputParamMap[field.Name()] = field.Value.Interface() + } + } } for _, field := range tagField { fieldName := field.Name() @@ -118,8 +145,8 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages fieldAliases[fieldName] = name } // It here extends the params map using alias names. - if _, ok := params[name]; !ok { - params[name] = field.Value.Interface() + if _, ok := inputParamMap[name]; !ok { + inputParamMap[name] = field.Value.Interface() } if _, ok := checkRules[name]; !ok { if _, ok := checkRules[fieldName]; ok { @@ -175,11 +202,11 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages var value interface{} for key, rule := range checkRules { value = nil - if v, ok := params[key]; ok { + if v, ok := inputParamMap[key]; ok { value = v } // It checks each rule and its value in loop. - if e := v.doCheck(key, value, rule, customMessage[key], params); e != nil { + if e := v.doCheck(key, value, rule, customMessage[key], inputParamMap); e != nil { _, item := e.FirstItem() // =================================================================== // Only in map and struct validations, if value is nil or empty string diff --git a/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go b/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go new file mode 100755 index 000000000..a660fd65a --- /dev/null +++ b/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go @@ -0,0 +1,39 @@ +// 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_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/util/gvalid" + "testing" +) + +func TestValidator_CheckStructWithParamMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid" v:"required"` + Nickname string `json:"nickname" v:"required-with:Uid"` + } + data := UserApiSearch{} + t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: nil, + } + t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) +} From 2e38416e12a29a527e1d7318bd4b88f64ba6344e Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 11 May 2021 20:00:50 +0800 Subject: [PATCH 13/70] improve struct embedded association case of with feature for package gdb --- database/gdb/gdb_model_with.go | 10 +- .../gdb/gdb_z_mysql_association_with_test.go | 121 +++++++++++++++++- internal/structs/structs_field.go | 9 +- internal/structs/structs_z_unit_test.go | 4 +- util/gvalid/gvalid_validator_check_struct.go | 2 +- 5 files changed, 134 insertions(+), 12 deletions(-) diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 2810d9a25..892474228 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -55,7 +55,7 @@ func (m *Model) WithAll() *Model { // getWithTagObjectArrayFrom retrieves and returns object array that have "with" tag in the struct. func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, error) { - fieldMap, err := structs.FieldMap(pointer, nil) + fieldMap, err := structs.FieldMap(pointer, nil, false) if err != nil { return nil, err } @@ -86,6 +86,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { err error withArray = m.withArray ) + // If with all feature is enabled, it then retrieves all the attributes which have with tag defined. if m.withAll { withArray, err = m.getWithTagObjectArrayFrom(pointer) if err != nil { @@ -95,10 +96,11 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { if len(withArray) == 0 { return nil } - fieldMap, err := structs.FieldMap(pointer, nil) + fieldMap, err := structs.FieldMap(pointer, nil, false) if err != nil { return err } + // Check the with array and automatically call the ScanList to complete association querying. for withIndex, withItem := range withArray { withItemReflectValueType, err := structs.StructType(withItem) if err != nil { @@ -110,6 +112,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { fieldType = fieldValue.Type() fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]") ) + // It does select operation if the field type is in the specified with type array. if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { var ( withTag string @@ -174,6 +177,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { } // doWithScanStructs handles model association operations feature for struct slice. +// Also see doWithScanStruct. func (m *Model) doWithScanStructs(pointer interface{}) error { var ( err error @@ -188,7 +192,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error { if len(withArray) == 0 { return nil } - fieldMap, err := structs.FieldMap(pointer, nil) + fieldMap, err := structs.FieldMap(pointer, nil, false) if err != nil { return err } diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index d8a34cae9..7cf1bfd92 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -212,7 +212,7 @@ PRIMARY KEY (id) }) } -func Test_Table_Relation_With_ScanList(t *testing.T) { +func Test_Table_Relation_With(t *testing.T) { var ( tableUser = "user" tableUserDetail = "user_detail" @@ -411,7 +411,7 @@ PRIMARY KEY (id) }) } -func Test_Table_Relation_WithAll_Scan(t *testing.T) { +func Test_Table_Relation_WithAll(t *testing.T) { var ( tableUser = "user" tableUserDetail = "user_detail" @@ -526,7 +526,7 @@ PRIMARY KEY (id) }) } -func Test_Table_Relation_WithAll_ScanList(t *testing.T) { +func Test_Table_Relation_WithAll_List(t *testing.T) { var ( tableUser = "user" tableUserDetail = "user_detail" @@ -666,3 +666,118 @@ PRIMARY KEY (id) t.Assert(users[1].UserScores[4].Score, 5) }) } + +func Test_Table_Relation_WithAll_Embedded(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + 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; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} diff --git a/internal/structs/structs_field.go b/internal/structs/structs_field.go index a0ad1a1cb..dda5a996e 100644 --- a/internal/structs/structs_field.go +++ b/internal/structs/structs_field.go @@ -61,8 +61,11 @@ func (f *Field) OriginalKind() reflect.Kind { // The parameter `priority` specifies the priority tag array for retrieving from high to low. // If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name. // +// The parameter `recursive` specifies the whether retrieving the fields recursively if the attribute +// is an embedded struct. +// // Note that it only retrieves the exported attributes with first letter up-case from struct. -func FieldMap(pointer interface{}, priority []string) (map[string]*Field, error) { +func FieldMap(pointer interface{}, priority []string, recursive bool) (map[string]*Field, error) { fields, err := getFieldValues(pointer) if err != nil { return nil, err @@ -88,8 +91,8 @@ func FieldMap(pointer interface{}, priority []string) (map[string]*Field, error) if tagValue != "" { mapField[tagValue] = tempField } else { - if field.IsEmbedded() { - m, err := FieldMap(field.Value, priority) + if recursive && field.IsEmbedded() { + m, err := FieldMap(field.Value, priority, recursive) if err != nil { return nil, err } diff --git a/internal/structs/structs_z_unit_test.go b/internal/structs/structs_z_unit_test.go index c4f31d1a2..4268f473a 100644 --- a/internal/structs/structs_z_unit_test.go +++ b/internal/structs/structs_z_unit_test.go @@ -110,7 +110,7 @@ func Test_FieldMap(t *testing.T) { Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` } var user *User - m, _ := structs.FieldMap(user, []string{"params"}) + m, _ := structs.FieldMap(user, []string{"params"}, true) t.Assert(len(m), 3) _, ok := m["Id"] t.Assert(ok, true) @@ -130,7 +130,7 @@ func Test_FieldMap(t *testing.T) { Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` } var user *User - m, _ := structs.FieldMap(user, nil) + m, _ := structs.FieldMap(user, nil, true) t.Assert(len(m), 3) _, ok := m["Id"] t.Assert(ok, true) diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 3180806f4..a3005b74c 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -28,7 +28,7 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages var ( errorMaps = make(ErrorMap) // Returned error. ) - mapField, err := structs.FieldMap(object, aliasNameTagPriority) + mapField, err := structs.FieldMap(object, aliasNameTagPriority, true) if err != nil { return newErrorStr("invalid_object", err.Error()) } From 1eab1cb3677b739f98075beded7016c674c48202 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 11 May 2021 20:14:06 +0800 Subject: [PATCH 14/70] add more unit testing cases,remove function Filter usage in unit testing cases for package gdb --- database/gdb/gdb_z_mysql_filter_test.go | 218 ++++++++++++++++++++++++ database/gdb/gdb_z_mysql_model_test.go | 185 ++------------------ database/gdb/gdb_z_mysql_raw_test.go | 4 +- database/gdb/gdb_z_mysql_struct_test.go | 8 +- 4 files changed, 234 insertions(+), 181 deletions(-) create mode 100644 database/gdb/gdb_z_mysql_filter_test.go diff --git a/database/gdb/gdb_z_mysql_filter_test.go b/database/gdb/gdb_z_mysql_filter_test.go new file mode 100644 index 000000000..cd9c30107 --- /dev/null +++ b/database/gdb/gdb_z_mysql_filter_test.go @@ -0,0 +1,218 @@ +// 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 gdb_test + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/test/gtest" + "testing" +) + +// Using filter dose not affect the outside value inside function. +func Test_Model_Insert_Filter(t *testing.T) { + // map + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + data := g.Map{ + "id": 1, + "uid": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + } + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 1) + + t.Assert(data["uid"], 1) + }) + // slice + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + data := g.List{ + g.Map{ + "id": 1, + "uid": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }, + g.Map{ + "id": 2, + "uid": 2, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }, + } + + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 2) + + t.Assert(data[0]["uid"], 1) + t.Assert(data[1]["uid"], 2) + }) +} + +func Test_Model_Embedded_Filter(t *testing.T) { + table := createTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + type Base struct { + Id int + Uid int + CreateTime string + NoneExist string + } + type User struct { + Base + Passport string + Password string + Nickname string + } + result, err := db.Model(table).Data(User{ + Passport: "john-test", + Password: "123456", + Nickname: "John", + Base: Base{ + Id: 100, + Uid: 100, + CreateTime: gtime.Now().String(), + }, + }).Insert() + t.AssertNil(err) + n, _ := result.RowsAffected() + t.Assert(n, 1) + + var user *User + err = db.Model(table).Fields(user).Where("id=100").Scan(&user) + t.AssertNil(err) + t.Assert(user.Passport, "john-test") + t.Assert(user.Id, 100) + }) +} + +// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. +//func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) { +// table := createTable() +// defer dropTable(table) +// +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Id int +// Passport string +// Password string +// Nickname string +// CreateTime string +// NoneExistFiled string +// } +// data := User{ +// Id: 1, +// Passport: "user_1", +// Password: "pass_1", +// Nickname: "name_1", +// CreateTime: "2020-10-10 12:00:01", +// } +// _, err := db.Model(table).Data(data).Insert() +// t.AssertNE(err, nil) +// }) +//} + +func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var err error + table1 := "user" + table2 := "score" + table3 := "info" + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + name varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table1, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table1) + _, err = db.Model(table1).Insert(g.Map{ + "id": 1, + "name": "john", + }) + t.AssertNil(err) + + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + user_id int(11) NOT NULL DEFAULT 0, + number varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table2, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table2) + _, err = db.Model(table2).Insert(g.Map{ + "id": 1, + "user_id": 1, + "number": "n", + }) + t.AssertNil(err) + + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + user_id int(11) NOT NULL DEFAULT 0, + description varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table3, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table3) + _, err = db.Model(table3).Insert(g.Map{ + "id": 1, + "user_id": 1, + "description": "brief", + }) + t.AssertNil(err) + + one, err := db.Model("user"). + Where("user.id", 1). + Fields("score.number,user.name"). + LeftJoin("score", "user.id=score.user_id"). + LeftJoin("info", "info.id=info.user_id"). + Order("user.id asc"). + One() + t.AssertNil(err) + t.Assert(len(one), 2) + t.Assert(one["name"].String(), "john") + t.Assert(one["number"].String(), "n") + + one, err = db.Model("user"). + LeftJoin("score", "user.id=score.user_id"). + LeftJoin("info", "info.id=info.user_id"). + Fields("score.number,user.name"). + One() + t.AssertNil(err) + t.Assert(len(one), 2) + t.Assert(one["name"].String(), "john") + t.Assert(one["number"].String(), "n") + }) +} diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index f7a5fc857..55ca3aa30 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -30,7 +30,7 @@ func Test_Model_Insert(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { user := db.Model(table) - result, err := user.Filter().Data(g.Map{ + result, err := user.Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -42,7 +42,7 @@ func Test_Model_Insert(t *testing.T) { n, _ := result.LastInsertId() t.Assert(n, 1) - result, err = db.Model(table).Filter().Data(g.Map{ + result, err = db.Model(table).Data(g.Map{ "id": "2", "uid": "2", "passport": "t2", @@ -63,7 +63,7 @@ func Test_Model_Insert(t *testing.T) { CreateTime *gtime.Time `json:"create_time"` } // Model inserting. - result, err = db.Model(table).Filter().Data(User{ + result, err = db.Model(table).Data(User{ Id: 3, Uid: 3, Passport: "t3", @@ -77,7 +77,7 @@ func Test_Model_Insert(t *testing.T) { t.AssertNil(err) t.Assert(value.String(), "t3") - result, err = db.Model(table).Filter().Data(&User{ + result, err = db.Model(table).Data(&User{ Id: 4, Uid: 4, Passport: "t4", @@ -99,60 +99,6 @@ func Test_Model_Insert(t *testing.T) { }) } -// Using filter dose not affect the outside value inside function. -func Test_Model_Insert_Filter(t *testing.T) { - // map - gtest.C(t, func(t *gtest.T) { - table := createTable() - defer dropTable(table) - data := g.Map{ - "id": 1, - "uid": 1, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - } - result, err := db.Model(table).Filter().Data(data).Insert() - t.AssertNil(err) - n, _ := result.LastInsertId() - t.Assert(n, 1) - - t.Assert(data["uid"], 1) - }) - // slice - gtest.C(t, func(t *gtest.T) { - table := createTable() - defer dropTable(table) - data := g.List{ - g.Map{ - "id": 1, - "uid": 1, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - }, - g.Map{ - "id": 2, - "uid": 2, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - }, - } - - result, err := db.Model(table).Filter().Data(data).Insert() - t.AssertNil(err) - n, _ := result.LastInsertId() - t.Assert(n, 2) - - t.Assert(data[0]["uid"], 1) - t.Assert(data[1]["uid"], 2) - }) -} - // Fix issue: https://github.com/gogf/gf/issues/819 func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) { table := createTable() @@ -240,32 +186,6 @@ func Test_Model_Update_KeyFieldNameMapping(t *testing.T) { }) } -// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. -//func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) { -// table := createTable() -// defer dropTable(table) -// -// gtest.C(t, func(t *gtest.T) { -// type User struct { -// Id int -// Passport string -// Password string -// Nickname string -// CreateTime string -// NoneExistFiled string -// } -// data := User{ -// Id: 1, -// Passport: "user_1", -// Password: "pass_1", -// Nickname: "name_1", -// CreateTime: "2020-10-10 12:00:01", -// } -// _, err := db.Model(table).Data(data).Insert() -// t.AssertNE(err, nil) -// }) -//} - func Test_Model_Insert_Time(t *testing.T) { table := createTable() defer dropTable(table) @@ -305,7 +225,7 @@ func Test_Model_BatchInsertWithArrayStruct(t *testing.T) { }) } - result, err := user.Filter().Data(array).Insert() + result, err := user.Data(array).Insert() t.AssertNil(err) n, _ := result.LastInsertId() t.Assert(n, TableSize) @@ -316,7 +236,7 @@ func Test_Model_InsertIgnore(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - _, err := db.Model(table).Filter().Data(g.Map{ + _, err := db.Model(table).Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -327,7 +247,7 @@ func Test_Model_InsertIgnore(t *testing.T) { t.AssertNE(err, nil) }) gtest.C(t, func(t *gtest.T) { - _, err := db.Model(table).Filter().Data(g.Map{ + _, err := db.Model(table).Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -344,7 +264,7 @@ func Test_Model_Batch(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - result, err := db.Model(table).Filter().Data(g.List{ + result, err := db.Model(table).Data(g.List{ { "id": 2, "uid": 2, @@ -2441,7 +2361,7 @@ func Test_Model_Schema1(t *testing.T) { // Model. gtest.C(t, func(t *gtest.T) { i := 1000 - _, err := db.Model(table).Schema(TestSchema1).Filter().Insert(g.Map{ + _, err := db.Model(table).Schema(TestSchema1).Insert(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), "password": fmt.Sprintf(`pass_%d`, i), @@ -2503,7 +2423,7 @@ func Test_Model_Schema2(t *testing.T) { // Schema. gtest.C(t, func(t *gtest.T) { i := 1000 - _, err := db.Schema(TestSchema1).Table(table).Filter().Insert(g.Map{ + _, err := db.Schema(TestSchema1).Table(table).Insert(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), "password": fmt.Sprintf(`pass_%d`, i), @@ -3243,91 +3163,6 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { }) } -func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var err error - table1 := "user" - table2 := "score" - table3 := "info" - if _, err := db.Exec(fmt.Sprintf(` - CREATE TABLE IF NOT EXISTS %s ( - id int(11) NOT NULL AUTO_INCREMENT, - name varchar(500) NOT NULL DEFAULT '', - PRIMARY KEY (id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; - `, table1, - )); err != nil { - t.AssertNil(err) - } - defer dropTable(table1) - _, err = db.Model(table1).Insert(g.Map{ - "id": 1, - "name": "john", - }) - t.AssertNil(err) - - if _, err := db.Exec(fmt.Sprintf(` - CREATE TABLE IF NOT EXISTS %s ( - id int(11) NOT NULL AUTO_INCREMENT, - user_id int(11) NOT NULL DEFAULT 0, - number varchar(500) NOT NULL DEFAULT '', - PRIMARY KEY (id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; - `, table2, - )); err != nil { - t.AssertNil(err) - } - defer dropTable(table2) - _, err = db.Model(table2).Insert(g.Map{ - "id": 1, - "user_id": 1, - "number": "n", - }) - t.AssertNil(err) - - if _, err := db.Exec(fmt.Sprintf(` - CREATE TABLE IF NOT EXISTS %s ( - id int(11) NOT NULL AUTO_INCREMENT, - user_id int(11) NOT NULL DEFAULT 0, - description varchar(500) NOT NULL DEFAULT '', - PRIMARY KEY (id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; - `, table3, - )); err != nil { - t.AssertNil(err) - } - defer dropTable(table3) - _, err = db.Model(table3).Insert(g.Map{ - "id": 1, - "user_id": 1, - "description": "brief", - }) - t.AssertNil(err) - - one, err := db.Model("user"). - Where("user.id", 1). - Fields("score.number,user.name"). - LeftJoin("score", "user.id=score.user_id"). - LeftJoin("info", "info.id=info.user_id"). - Order("user.id asc"). - One() - t.AssertNil(err) - t.Assert(len(one), 2) - t.Assert(one["name"].String(), "john") - t.Assert(one["number"].String(), "n") - - one, err = db.Model("user"). - LeftJoin("score", "user.id=score.user_id"). - LeftJoin("info", "info.id=info.user_id"). - Fields("score.number,user.name"). - One() - t.AssertNil(err) - t.Assert(len(one), 2) - t.Assert(one["name"].String(), "john") - t.Assert(one["number"].String(), "n") - }) -} - func Test_Model_WhereIn(t *testing.T) { table := createInitTable() defer dropTable(table) diff --git a/database/gdb/gdb_z_mysql_raw_test.go b/database/gdb/gdb_z_mysql_raw_test.go index e4677e12f..3b3f3dc05 100644 --- a/database/gdb/gdb_z_mysql_raw_test.go +++ b/database/gdb/gdb_z_mysql_raw_test.go @@ -20,7 +20,7 @@ func Test_Insert_Raw(t *testing.T) { gtest.C(t, func(t *gtest.T) { user := db.Model(table) - result, err := user.Filter().Data(g.Map{ + result, err := user.Data(g.Map{ "id": gdb.Raw("id+2"), "passport": "port_1", "password": "pass_1", @@ -39,7 +39,7 @@ func Test_BatchInsert_Raw(t *testing.T) { gtest.C(t, func(t *gtest.T) { user := db.Model(table) - result, err := user.Filter().Data( + result, err := user.Data( g.List{ g.Map{ "id": gdb.Raw("id+2"), diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index 67a1858cb..090a991a7 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -15,7 +15,7 @@ import ( "testing" ) -func Test_Model_Inherit_Insert(t *testing.T) { +func Test_Model_Embedded_Insert(t *testing.T) { table := createTable() defer dropTable(table) @@ -31,7 +31,7 @@ func Test_Model_Inherit_Insert(t *testing.T) { Password string `json:"password"` Nickname string `json:"nickname"` } - result, err := db.Model(table).Filter().Data(User{ + result, err := db.Model(table).Data(User{ Passport: "john-test", Password: "123456", Nickname: "John", @@ -50,7 +50,7 @@ func Test_Model_Inherit_Insert(t *testing.T) { }) } -func Test_Model_Inherit_MapToStruct(t *testing.T) { +func Test_Model_Embedded_MapToStruct(t *testing.T) { table := createTable() defer dropTable(table) @@ -77,7 +77,7 @@ func Test_Model_Inherit_MapToStruct(t *testing.T) { "nickname": "T1", "create_time": gtime.Now().String(), } - result, err := db.Model(table).Filter().Data(data).Insert() + result, err := db.Model(table).Data(data).Insert() t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) From a4240bdfb78ecbcb7095e0e171fa3b8c65aef75f Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 11 May 2021 20:57:30 +0800 Subject: [PATCH 15/70] improve struct validation for package gvalid --- util/gvalid/gvalid_validator_check_struct.go | 80 +++++++++++++------ util/gvalid/gvalid_z_unit_checkstruct_test.go | 49 ++++++++++++ ...lid_z_unit_checkstructwithparammap_test.go | 39 --------- 3 files changed, 103 insertions(+), 65 deletions(-) delete mode 100755 util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 6709f08ea..3c690ef0a 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -9,11 +9,19 @@ package gvalid import ( "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" - "github.com/gogf/gf/util/gutil" "reflect" "strings" ) +// doCheckStructWithParamMapInput is used for struct validation for internal function. +type doCheckStructWithParamMapInput struct { + Object interface{} // Can be type of struct/*struct. + ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `ParamMapStrict`. + ParamMapStrict bool // Strictly using `ParamMap` as its validation source. If false, it ignores `ParamMap` and retrieves values from `Object`. + CustomRules interface{} // Custom validation rules. + CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. +} + var ( structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. @@ -25,8 +33,18 @@ var ( // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { - return v.CheckStructWithParamMap(object, nil, rules, messages...) +func (v *Validator) CheckStruct(object interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error { + var message CustomMsg + if len(customErrorMessageMap) > 0 { + message = customErrorMessageMap[0] + } + return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ + Object: object, + ParamMap: nil, + ParamMapStrict: false, + CustomRules: customRules, + CustomErrorMessageMap: message, + }) } // CheckStructWithParamMap validates struct with given parameter map and returns the error result. @@ -35,18 +53,32 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { +func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error { + var message CustomMsg + if len(customErrorMessageMap) > 0 { + message = customErrorMessageMap[0] + } + return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ + Object: object, + ParamMap: paramMap, + ParamMapStrict: true, + CustomRules: customRules, + CustomErrorMessageMap: message, + }) +} + +func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) *Error { var ( errorMaps = make(ErrorMap) // Returning error. ) - fieldMap, err := structs.FieldMap(object, aliasNameTagPriority) + fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority) if err != nil { return newErrorStr("invalid_object", err.Error()) } // It checks the struct recursively the its attribute is also a struct. for _, field := range fieldMap { if field.OriginalKind() == reflect.Struct { - if err := v.CheckStruct(field.Value, rules, messages...); err != nil { + if err := v.CheckStruct(field.Value, input.CustomRules, input.CustomErrorMessageMap); err != nil { // It merges the errors into single error map. for k, m := range err.errors { errorMaps[k] = m @@ -55,12 +87,12 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa } } // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. - tagField, err := structs.TagFields(object, structTagPriority) + tagField, err := structs.TagFields(input.Object, structTagPriority) if err != nil { return newErrorStr("invalid_object", err.Error()) } // If there's no struct tag and validation rules, it does nothing and returns quickly. - if len(tagField) == 0 && rules == nil { + if len(tagField) == 0 && input.CustomRules == nil { return nil } @@ -71,12 +103,7 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names. errorRules = make([]string, 0) // Sequence rules. ) - if paramMap == nil { - inputParamMap = make(map[string]interface{}) - } else { - inputParamMap = gconv.Map(paramMap) - } - switch v := rules.(type) { + switch v := input.CustomRules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. case []string: @@ -119,21 +146,22 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa if len(tagField) == 0 && len(checkRules) == 0 { return nil } + // Input parameter map handling. + if input.ParamMap == nil || !input.ParamMapStrict { + inputParamMap = make(map[string]interface{}) + } else { + inputParamMap = gconv.Map(input.ParamMap) + } // Checks and extends the parameters map with struct alias tag. - for nameOrTag, field := range fieldMap { - if _, ok := inputParamMap[nameOrTag]; !ok { - if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, nameOrTag); foundKey != "" { - inputParamMap[nameOrTag] = foundValue - inputParamMap[field.Name()] = foundValue - } else { - // If the custom tag alias name is quite different from the attribute name, - // it just uses the value of the attribute. - // Note that the attribute may have default value according to its type. - inputParamMap[nameOrTag] = field.Value.Interface() + if !input.ParamMapStrict { + for nameOrTag, field := range fieldMap { + inputParamMap[nameOrTag] = field.Value.Interface() + if nameOrTag != field.Name() { inputParamMap[field.Name()] = field.Value.Interface() } } } + for _, field := range tagField { fieldName := field.Name() // sequence tag == struct tag @@ -187,8 +215,8 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa // Custom error messages, // which have the most priority than `rules` and struct tag. - if len(messages) > 0 && len(messages[0]) > 0 { - for k, v := range messages[0] { + if len(input.CustomErrorMessageMap) > 0 { + for k, v := range input.CustomErrorMessageMap { if a, ok := fieldAliases[k]; ok { // Overwrite the key of field name. customMessage[a] = v diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index 188ea4b5c..0f2446dcd 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -8,6 +8,7 @@ package gvalid_test import ( "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/os/gtime" "testing" "github.com/gogf/gf/frame/g" @@ -352,3 +353,51 @@ func Test_CheckStruct_InvalidRule(t *testing.T) { t.AssertNE(err, nil) }) } + +func TestValidator_CheckStructWithParamMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid" v:"required"` + Nickname string `json:"nickname" v:"required-with:Uid"` + } + data := UserApiSearch{} + t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid" v:"required"` + Nickname string `json:"nickname" v:"required-with:Uid"` + } + data := UserApiSearch{ + Uid: 1, + } + t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) + + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: nil, + } + t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: gtime.Now(), + EndTime: nil, + } + t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + }) +} diff --git a/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go b/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go deleted file mode 100755 index a660fd65a..000000000 --- a/util/gvalid/gvalid_z_unit_checkstructwithparammap_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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_test - -import ( - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/os/gtime" - "github.com/gogf/gf/test/gtest" - "github.com/gogf/gf/util/gvalid" - "testing" -) - -func TestValidator_CheckStructWithParamMap(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - type UserApiSearch struct { - Uid int64 `json:"uid" v:"required"` - Nickname string `json:"nickname" v:"required-with:Uid"` - } - data := UserApiSearch{} - t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) - }) - gtest.C(t, func(t *gtest.T) { - type UserApiSearch struct { - Uid int64 `json:"uid"` - Nickname string `json:"nickname" v:"required-with:Uid"` - StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` - EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` - } - data := UserApiSearch{ - StartTime: nil, - EndTime: nil, - } - t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) - }) -} From b06580d343662f883e648e0c827e7d65991e0562 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 12 May 2021 00:01:52 +0800 Subject: [PATCH 16/70] improve struct validation for package gvalid --- net/ghttp/ghttp_request_param.go | 21 ++++--- net/ghttp/ghttp_request_param_form.go | 11 +++- net/ghttp/ghttp_request_param_query.go | 11 +++- net/ghttp/ghttp_request_param_request.go | 11 +++- net/ghttp/ghttp_unit_request_json_test.go | 2 +- net/ghttp/ghttp_unit_request_struct_test.go | 44 +++++++++++-- net/ghttp/ghttp_unit_request_xml_test.go | 2 +- util/gvalid/gvalid_validator_check.go | 12 ++-- util/gvalid/gvalid_validator_check_struct.go | 46 +++++++------- util/gvalid/gvalid_validator_rule_required.go | 62 +++++++++++++------ util/gvalid/gvalid_z_unit_checkstruct_test.go | 19 ++++-- 11 files changed, 168 insertions(+), 73 deletions(-) diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index f77eeeec3..991967df1 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -82,24 +82,27 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { // 1. {"id":1, "name":"john"} // 2. ?id=1&name=john case reflect.Ptr, reflect.Struct: + var ( + err error + data map[string]interface{} + ) // Converting. switch requestType { case parseTypeQuery: - if err := r.GetQueryStruct(pointer); err != nil { + if data, err = r.doGetQueryStruct(pointer); err != nil { return err } case parseTypeForm: - if err := r.GetFormStruct(pointer); err != nil { + if data, err = r.doGetFormStruct(pointer); err != nil { return err } default: - if err := r.GetStruct(pointer); err != nil { + if data, err = r.doGetRequestStruct(pointer); err != nil { return err } } - // Validation. - if err := gvalid.CheckStruct(pointer, nil); err != nil { + if err := gvalid.CheckStructWithParamMap(pointer, data, nil); err != nil { return err } @@ -107,7 +110,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { // [{"id":1, "name":"john"}, {"id":, "name":"smith"}] case reflect.Array, reflect.Slice: // If struct slice conversion, it might post JSON/XML content, - // so it uses gjson for the conversion. + // so it uses `gjson` for the conversion. j, err := gjson.LoadContent(r.GetBody()) if err != nil { return err @@ -116,7 +119,11 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { return err } for i := 0; i < reflectVal2.Len(); i++ { - if err := gvalid.CheckStruct(reflectVal2.Index(i), nil); err != nil { + if err := gvalid.CheckStructWithParamMap( + reflectVal2.Index(i), + j.GetMap(gconv.String(i)), + nil, + ); err != nil { return err } } diff --git a/net/ghttp/ghttp_request_param_form.go b/net/ghttp/ghttp_request_param_form.go index 630e9ea74..2e09b8b00 100644 --- a/net/ghttp/ghttp_request_param_form.go +++ b/net/ghttp/ghttp_request_param_form.go @@ -188,13 +188,18 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]* // given struct object. Note that the parameter is a pointer to the struct object. // The optional parameter is used to specify the key to attribute mapping. func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error { + _, err := r.doGetFormStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetFormStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { r.parseForm() - data := r.formMap + data = r.formMap if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } diff --git a/net/ghttp/ghttp_request_param_query.go b/net/ghttp/ghttp_request_param_query.go index a7257ecbf..b108b4b3c 100644 --- a/net/ghttp/ghttp_request_param_query.go +++ b/net/ghttp/ghttp_request_param_query.go @@ -196,13 +196,18 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string] // to the struct object. The optional parameter is used to specify the key to // attribute mapping. func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error { + _, err := r.doGetQueryStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetQueryStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { r.parseQuery() - data := r.GetQueryMap() + data = r.GetQueryMap() if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index 95a2be8e0..2e62a1992 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -270,14 +270,19 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin // the parameter is a pointer to the struct object. // The optional parameter is used to specify the key to attribute mapping. func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error { - data := r.GetRequestMap() + _, err := r.doGetRequestStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { + data = r.GetRequestMap() if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } // mergeDefaultStructValue merges the request parameters with default values from struct tag definition. diff --git a/net/ghttp/ghttp_unit_request_json_test.go b/net/ghttp/ghttp_unit_request_json_test.go index 656ad6073..09e68c72b 100644 --- a/net/ghttp/ghttp_unit_request_json_test.go +++ b/net/ghttp/ghttp_unit_request_json_test.go @@ -23,7 +23,7 @@ func Test_Params_Json_Request(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` + Pass2 string `p:"password2" v:"password2@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` } p, _ := ports.PopRand() s := g.Server(p) diff --git a/net/ghttp/ghttp_unit_request_struct_test.go b/net/ghttp/ghttp_unit_request_struct_test.go index 4bdd503a8..687c936c4 100644 --- a/net/ghttp/ghttp_unit_request_struct_test.go +++ b/net/ghttp/ghttp_unit_request_struct_test.go @@ -395,7 +395,7 @@ func Test_Params_Struct(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"` + Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"` } p, _ := ports.PopRand() s := g.Server(p) @@ -452,8 +452,8 @@ func Test_Params_Struct(t *testing.T) { t.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`) t.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`) t.Assert(client.PostContent("/struct2", ``), ``) - t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`) - t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`) + t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The password2 value length must be between 2 and 20; 密码强度不足`) + t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The password2 value length must be between 2 and 20; 密码强度不足`) t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`) }) } @@ -464,7 +464,7 @@ func Test_Params_Structs(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"` + Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"` } p, _ := ports.PopRand() s := g.Server(p) @@ -491,3 +491,39 @@ func Test_Params_Structs(t *testing.T) { ) }) } + +func Test_Params_Struct_Validation(t *testing.T) { + type User struct { + Id int `v:"required"` + Name string `v:"name@required-with:id"` + } + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + var ( + err error + user *User + ) + err = r.Parse(&user) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(user.Id, user.Name) + }) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + t.Assert(c.GetContent("/", ``), `The Id field is required`) + t.Assert(c.GetContent("/", `id=1&name=john`), `1john`) + t.Assert(c.PostContent("/", `id=1&name=john&password1=123&password2=456`), `1john`) + t.Assert(c.PostContent("/", `id=1`), `The name field is required`) + }) +} diff --git a/net/ghttp/ghttp_unit_request_xml_test.go b/net/ghttp/ghttp_unit_request_xml_test.go index 37252b9cc..6dd253d11 100644 --- a/net/ghttp/ghttp_unit_request_xml_test.go +++ b/net/ghttp/ghttp_unit_request_xml_test.go @@ -22,7 +22,7 @@ func Test_Params_Xml_Request(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` + Pass2 string `p:"password2" v:"password2@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` } p, _ := ports.PopRand() s := g.Server(p) diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index d3400fd48..c37e4c66f 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" "strconv" "strings" "time" @@ -232,8 +233,9 @@ func (v *Validator) doCheckBuildInRules( // Values of two fields should be equal as string. case "same": - if v, ok := dataMap[rulePattern]; ok { - if strings.Compare(valueStr, gconv.String(v)) == 0 { + _, foundValue := gutil.MapPossibleItemByKey(dataMap, rulePattern) + if foundValue != nil { + if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = true } } @@ -247,8 +249,9 @@ func (v *Validator) doCheckBuildInRules( // Values of two fields should not be equal as string. case "different": match = true - if v, ok := dataMap[rulePattern]; ok { - if strings.Compare(valueStr, gconv.String(v)) == 0 { + _, foundValue := gutil.MapPossibleItemByKey(dataMap, rulePattern) + if foundValue != nil { + if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = false } } @@ -302,6 +305,7 @@ func (v *Validator) doCheckBuildInRules( // 16x, 19x case "phone": match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,2,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr) + // Loose mobile phone number verification(宽松的手机号验证) // As long as the 11 digit numbers beginning with // 13, 14, 15, 16, 17, 18, 19 can pass the verification (只要满足 13、14、15、16、17、18、19开头的11位数字都可以通过验证) diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 3c690ef0a..fb7df4302 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -9,17 +9,18 @@ package gvalid import ( "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" "reflect" "strings" ) // doCheckStructWithParamMapInput is used for struct validation for internal function. type doCheckStructWithParamMapInput struct { - Object interface{} // Can be type of struct/*struct. - ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `ParamMapStrict`. - ParamMapStrict bool // Strictly using `ParamMap` as its validation source. If false, it ignores `ParamMap` and retrieves values from `Object`. - CustomRules interface{} // Custom validation rules. - CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. + Object interface{} // Can be type of struct/*struct. + ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. + UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. + CustomRules interface{} // Custom validation rules. + CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. } var ( @@ -39,11 +40,11 @@ func (v *Validator) CheckStruct(object interface{}, customRules interface{}, cus message = customErrorMessageMap[0] } return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ - Object: object, - ParamMap: nil, - ParamMapStrict: false, - CustomRules: customRules, - CustomErrorMessageMap: message, + Object: object, + ParamMap: nil, + UseParamMapInsteadOfObjectValue: false, + CustomRules: customRules, + CustomErrorMessageMap: message, }) } @@ -59,11 +60,11 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa message = customErrorMessageMap[0] } return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ - Object: object, - ParamMap: paramMap, - ParamMapStrict: true, - CustomRules: customRules, - CustomErrorMessageMap: message, + Object: object, + ParamMap: paramMap, + UseParamMapInsteadOfObjectValue: true, + CustomRules: customRules, + CustomErrorMessageMap: message, }) } @@ -147,13 +148,13 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn return nil } // Input parameter map handling. - if input.ParamMap == nil || !input.ParamMapStrict { + if input.ParamMap == nil || !input.UseParamMapInsteadOfObjectValue { inputParamMap = make(map[string]interface{}) } else { inputParamMap = gconv.Map(input.ParamMap) } // Checks and extends the parameters map with struct alias tag. - if !input.ParamMapStrict { + if !input.UseParamMapInsteadOfObjectValue { for nameOrTag, field := range fieldMap { inputParamMap[nameOrTag] = field.Value.Interface() if nameOrTag != field.Name() { @@ -174,7 +175,9 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn } // It here extends the params map using alias names. if _, ok := inputParamMap[name]; !ok { - inputParamMap[name] = field.Value.Interface() + if !input.UseParamMapInsteadOfObjectValue { + inputParamMap[name] = field.Value.Interface() + } } if _, ok := checkRules[name]; !ok { if _, ok := checkRules[fieldName]; ok { @@ -187,7 +190,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn } errorRules = append(errorRules, name+"@"+rule) } else { - // The passed rules can overwrite the rules in struct tag. + // The input rules can overwrite the rules in struct tag. continue } if len(msg) > 0 { @@ -229,10 +232,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn // The following logic is the same as some of CheckMap. var value interface{} for key, rule := range checkRules { - value = nil - if v, ok := inputParamMap[key]; ok { - value = v - } + _, value = gutil.MapPossibleItemByKey(inputParamMap, key) // It checks each rule and its value in loop. if e := v.doCheck(key, value, rule, customMessage[key], inputParamMap); e != nil { _, item := e.FirstItem() diff --git a/util/gvalid/gvalid_validator_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go index 969d7bb56..9e30b704d 100644 --- a/util/gvalid/gvalid_validator_rule_required.go +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -9,6 +9,7 @@ package gvalid import ( "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" "reflect" "strings" ) @@ -26,17 +27,19 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-if: id,1,age,18 case "required-if": required = false - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) // It supports multiple field and value pairs. if len(array)%2 == 0 { for i := 0; i < len(array); { tk := array[i] tv := array[i+1] - if v, ok := dataMap[tk]; ok { - if strings.Compare(tv, gconv.String(v)) == 0 { - required = true - break - } + _, foundValue = gutil.MapPossibleItemByKey(dataMap, tk) + if strings.Compare(tv, gconv.String(foundValue)) == 0 { + required = true + break } i += 2 } @@ -46,18 +49,21 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-unless: id,1,age,18 case "required-unless": required = true - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) // It supports multiple field and value pairs. if len(array)%2 == 0 { for i := 0; i < len(array); { tk := array[i] tv := array[i+1] - if v, ok := dataMap[tk]; ok { - if strings.Compare(tv, gconv.String(v)) == 0 { - required = false - break - } + _, foundValue = gutil.MapPossibleItemByKey(dataMap, tk) + if strings.Compare(tv, gconv.String(foundValue)) == 0 { + required = false + break } + i += 2 } } @@ -66,9 +72,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-with:id,name case "required-with": required = false - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) for i := 0; i < len(array); i++ { - if !empty.IsEmpty(dataMap[array[i]]) { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if !empty.IsEmpty(foundValue) { required = true break } @@ -78,9 +88,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-with:id,name case "required-with-all": required = true - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) for i := 0; i < len(array); i++ { - if empty.IsEmpty(dataMap[array[i]]) { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if empty.IsEmpty(foundValue) { required = false break } @@ -90,9 +104,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-with:id,name case "required-without": required = false - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) for i := 0; i < len(array); i++ { - if empty.IsEmpty(dataMap[array[i]]) { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if empty.IsEmpty(foundValue) { required = true break } @@ -102,9 +120,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string // Example: required-with:id,name case "required-without-all": required = true - array := strings.Split(rulePattern, ",") + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) for i := 0; i < len(array); i++ { - if !empty.IsEmpty(dataMap[array[i]]) { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if !empty.IsEmpty(foundValue) { required = false break } diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index 0f2446dcd..786145ffb 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -357,11 +357,22 @@ func Test_CheckStruct_InvalidRule(t *testing.T) { func TestValidator_CheckStructWithParamMap(t *testing.T) { gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { - Uid int64 `json:"uid" v:"required"` - Nickname string `json:"nickname" v:"required-with:Uid"` + Uid int64 `v:"required"` + Nickname string `v:"required-with:uid"` + } + data := UserApiSearch{ + Uid: 1, + Nickname: "john", + } + t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `v:"required"` + Nickname string `v:"required-with:uid"` } data := UserApiSearch{} - t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -398,6 +409,6 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { StartTime: gtime.Now(), EndTime: nil, } - t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{"start_time": gtime.Now()}, nil), nil) }) } From d21b9d58e1a2a626a0437dcf7708e93bf0f27a2e Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 12 May 2021 21:34:15 +0800 Subject: [PATCH 17/70] improve with feature by supporting attributes struct which also have 'with' tag --- database/gdb/gdb_model_with.go | 336 +++++++-------- ... gdb_z_mysql_association_scanlist_test.go} | 0 .../gdb/gdb_z_mysql_association_with_test.go | 408 ++++++++++++++++++ 3 files changed, 574 insertions(+), 170 deletions(-) rename database/gdb/{gdb_z_mysql_association_test.go => gdb_z_mysql_association_scanlist_test.go} (100%) diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 892474228..bd6d37883 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -53,18 +53,41 @@ func (m *Model) WithAll() *Model { return model } -// getWithTagObjectArrayFrom retrieves and returns object array that have "with" tag in the struct. -func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, error) { +// doWithScanStruct handles model association operations feature for single struct. +func (m *Model) doWithScanStruct(pointer interface{}) error { + var ( + err error + allowedTypeStrArray = make([]string, 0) + ) fieldMap, err := structs.FieldMap(pointer, nil, false) if err != nil { - return nil, err + return err } - withTagObjectArray := make([]interface{}, 0) - for _, fieldValue := range fieldMap { + // It checks the with array and automatically calls the ScanList to complete association querying. + if !m.withAll { + for _, field := range fieldMap { + for _, withItem := range m.withArray { + withItemReflectValueType, err := structs.StructType(withItem) + if err != nil { + return err + } + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]") + ) + // It does select operation if the field type is in the specified with type array. + if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { + allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr) + } + } + } + } + for _, field := range fieldMap { var ( - withTag string - ormTag = fieldValue.Tag(OrmTagForStruct) - match, _ = gregex.MatchString( + withTag string + ormTag = field.Tag(OrmTagForStruct) + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + match, _ = gregex.MatchString( fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), ormTag, ) @@ -75,103 +98,63 @@ func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, e if withTag == "" { continue } - withTagObjectArray = append(withTagObjectArray, fieldValue.Value.Interface()) - } - return withTagObjectArray, nil -} - -// doWithScanStruct handles model association operations feature for single struct. -func (m *Model) doWithScanStruct(pointer interface{}) error { - var ( - err error - withArray = m.withArray - ) - // If with all feature is enabled, it then retrieves all the attributes which have with tag defined. - if m.withAll { - withArray, err = m.getWithTagObjectArrayFrom(pointer) - if err != nil { - return err + if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { + continue } - } - if len(withArray) == 0 { - return nil - } - fieldMap, err := structs.FieldMap(pointer, nil, false) - if err != nil { - return err - } - // Check the with array and automatically call the ScanList to complete association querying. - for withIndex, withItem := range withArray { - withItemReflectValueType, err := structs.StructType(withItem) - if err != nil { - return err + array := gstr.SplitAndTrim(withTag, "=") + if len(array) == 1 { + // It supports using only one column name + // if both tables associates using the same column name. + array = append(array, withTag) } - withItemReflectValueTypeStr := gstr.TrimAll(withItemReflectValueType.String(), "*[]") - for _, fieldValue := range fieldMap { - var ( - fieldType = fieldValue.Type() - fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]") - ) - // It does select operation if the field type is in the specified with type array. - if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { - var ( - withTag string - ormTag = fieldValue.Tag(OrmTagForStruct) - match, _ = gregex.MatchString( - fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), - ormTag, - ) - ) - if len(match) > 1 { - withTag = match[1] - } - if withTag == "" { - continue - } - array := gstr.SplitAndTrim(withTag, "=") - if len(array) != 2 { - return gerror.Newf(`invalid with tag "%s"`, withTag) - } - var ( - relatedFieldName = array[0] - relatedAttrName = array[1] - relatedFieldValue interface{} - ) - // Find the value of related attribute from `pointer`. - for attributeName, attributeValue := range fieldMap { - if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) { - relatedFieldValue = attributeValue.Value.Interface() - break - } - } - if relatedFieldValue == nil { - return gerror.Newf( - `cannot find the related value for attribute name "%s" of with tag "%s"`, - relatedAttrName, withTag, - ) - } - bindToReflectValue := fieldValue.Value - switch bindToReflectValue.Kind() { - case reflect.Array, reflect.Slice: - if bindToReflectValue.CanAddr() { - bindToReflectValue = bindToReflectValue.Addr() - } - } - model := m.db.With(fieldValue.Value) - for i, v := range withArray { - if i == withIndex { - continue - } - model = model.With(v) - } - err = model.Fields(withItemReflectValueType.FieldKeys()). - Where(relatedFieldName, relatedFieldValue). - Scan(bindToReflectValue) - if err != nil { - return err - } + var ( + model *Model + fieldKeys []string + relatedFieldName = array[0] + relatedAttrName = array[1] + relatedFieldValue interface{} + ) + // Find the value of related attribute from `pointer`. + for attributeName, attributeValue := range fieldMap { + if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) { + relatedFieldValue = attributeValue.Value.Interface() + break } } + if relatedFieldValue == nil { + return gerror.Newf( + `cannot find the related value for attribute name "%s" of with tag "%s"`, + relatedAttrName, withTag, + ) + } + bindToReflectValue := field.Value + switch bindToReflectValue.Kind() { + case reflect.Array, reflect.Slice: + if bindToReflectValue.CanAddr() { + bindToReflectValue = bindToReflectValue.Addr() + } + } + + // It automatically retrieves struct field names from current attribute struct/slice. + if structType, err := structs.StructType(field.Value); err != nil { + return err + } else { + fieldKeys = structType.FieldKeys() + } + + // Recursively with feature checks. + model = m.db.With(field.Value) + if m.withAll { + model = model.WithAll() + } else { + model = model.With(m.withArray...) + } + + err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue) + if err != nil { + return err + } + } return nil } @@ -180,85 +163,98 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { // Also see doWithScanStruct. func (m *Model) doWithScanStructs(pointer interface{}) error { var ( - err error - withArray = m.withArray + err error + allowedTypeStrArray = make([]string, 0) ) - if m.withAll { - withArray, err = m.getWithTagObjectArrayFrom(pointer) - if err != nil { - return err - } - } - if len(withArray) == 0 { - return nil - } fieldMap, err := structs.FieldMap(pointer, nil, false) if err != nil { return err } - for withIndex, withItem := range withArray { - withItemReflectValueType, err := structs.StructType(withItem) - if err != nil { - return err - } - withItemReflectValueTypeStr := gstr.TrimAll(withItemReflectValueType.String(), "*[]") - for fieldName, fieldValue := range fieldMap { - var ( - fieldType = fieldValue.Type() - fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]") - ) - if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { - var ( - withTag string - ormTag = fieldValue.Tag(OrmTagForStruct) - match, _ = gregex.MatchString( - fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), - ormTag, - ) - ) - if len(match) > 1 { - withTag = match[1] - } - if withTag == "" { - continue - } - array := gstr.SplitAndTrim(withTag, "=") - if len(array) != 2 { - return gerror.Newf(`invalid with tag "%s"`, withTag) - } - var ( - relatedFieldName = array[0] - relatedAttrName = array[1] - relatedFieldValue interface{} - ) - // Find the value slice of related attribute from `pointer`. - for attributeName, _ := range fieldMap { - if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) { - relatedFieldValue = ListItemValuesUnique(pointer, attributeName) - break - } - } - if relatedFieldValue == nil { - return gerror.Newf( - `cannot find the related value for attribute name "%s" of with tag "%s"`, - relatedAttrName, withTag, - ) - } - model := m.db.With(fieldValue.Value) - for i, v := range withArray { - if i == withIndex { - continue - } - model = model.With(v) - } - err = model.Fields(withItemReflectValueType.FieldKeys()). - Where(relatedFieldName, relatedFieldValue). - ScanList(pointer, fieldName, withTag) + // It checks the with array and automatically calls the ScanList to complete association querying. + if !m.withAll { + for _, field := range fieldMap { + for _, withItem := range m.withArray { + withItemReflectValueType, err := structs.StructType(withItem) if err != nil { return err } + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]") + ) + // It does select operation if the field type is in the specified with type array. + if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { + allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr) + } } } } + + for fieldName, field := range fieldMap { + var ( + withTag string + ormTag = field.Tag(OrmTagForStruct) + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), + ormTag, + ) + ) + if len(match) > 1 { + withTag = match[1] + } + if withTag == "" { + continue + } + if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { + continue + } + array := gstr.SplitAndTrim(withTag, "=") + if len(array) == 1 { + // It supports using only one column name + // if both tables associates using the same column name. + array = append(array, withTag) + } + var ( + model *Model + fieldKeys []string + relatedFieldName = array[0] + relatedAttrName = array[1] + relatedFieldValue interface{} + ) + // Find the value slice of related attribute from `pointer`. + for attributeName, _ := range fieldMap { + if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) { + relatedFieldValue = ListItemValuesUnique(pointer, attributeName) + break + } + } + if relatedFieldValue == nil { + return gerror.Newf( + `cannot find the related value for attribute name "%s" of with tag "%s"`, + relatedAttrName, withTag, + ) + } + + // It automatically retrieves struct field names from current attribute struct/slice. + if structType, err := structs.StructType(field.Value); err != nil { + return err + } else { + fieldKeys = structType.FieldKeys() + } + + // Recursively with feature checks. + model = m.db.With(field.Value) + if m.withAll { + model = model.WithAll() + } else { + model = model.With(m.withArray...) + } + + err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, withTag) + if err != nil { + return err + } + } return nil } diff --git a/database/gdb/gdb_z_mysql_association_test.go b/database/gdb/gdb_z_mysql_association_scanlist_test.go similarity index 100% rename from database/gdb/gdb_z_mysql_association_test.go rename to database/gdb/gdb_z_mysql_association_scanlist_test.go diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index 7cf1bfd92..43a0f3ab7 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -781,3 +781,411 @@ PRIMARY KEY (id) t.Assert(user.UserScores[4].Score, 5) }) } + +func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + 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; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + 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; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail1 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail2 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail1 *UserDetail1 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail3 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail2 *UserDetail2 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail3 *UserDetail3 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_With_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + 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; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail1 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail2 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail1 *UserDetail1 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail3 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail2 *UserDetail2 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail3 *UserDetail3 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} From a326f4a9890951b0cbd5dbecd983329f58c348f5 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 13 May 2021 00:16:45 +0800 Subject: [PATCH 18/70] improve package gi18n/gview/gvalid for more flexable i18n feature controll,add custom error configuration and i18n translation for custom error message --- i18n/gi18n/gi18n.go | 37 +++----- i18n/gi18n/gi18n_ctx.go | 29 +++++++ i18n/gi18n/gi18n_instance.go | 2 +- i18n/gi18n/gi18n_manager.go | 55 +++++------- i18n/gi18n/gi18n_unit_test.go | 61 ++++++-------- net/ghttp/ghttp_response_view.go | 6 +- net/ghttp/ghttp_server_admin.go | 2 +- net/ghttp/ghttp_server_pprof.go | 2 +- os/gview/gview.go | 5 +- os/gview/gview_buildin.go | 3 +- os/gview/gview_i18n.go | 31 +++++-- os/gview/gview_parse.go | 17 ++-- os/gview/gview_unit_basic_test.go | 89 ++++++++++---------- os/gview/gview_unit_config_test.go | 9 +- os/gview/gview_unit_encode_test.go | 5 +- os/gview/gview_unit_i18n_test.go | 13 +-- util/gvalid/gvalid_error.go | 5 +- util/gvalid/gvalid_validator.go | 36 ++++---- util/gvalid/gvalid_validator_message.go | 15 ++-- util/gvalid/gvalid_z_unit_i18n_test.go | 50 +++++++++++ util/gvalid/testdata/i18n/cn/validation.toml | 48 +++++++++++ util/gvalid/testdata/i18n/en/validation.toml | 45 ++++++++++ 22 files changed, 369 insertions(+), 196 deletions(-) create mode 100644 i18n/gi18n/gi18n_ctx.go create mode 100644 util/gvalid/gvalid_z_unit_i18n_test.go create mode 100644 util/gvalid/testdata/i18n/cn/validation.toml create mode 100644 util/gvalid/testdata/i18n/en/validation.toml diff --git a/i18n/gi18n/gi18n.go b/i18n/gi18n/gi18n.go index b0a85400b..bb2cc07e4 100644 --- a/i18n/gi18n/gi18n.go +++ b/i18n/gi18n/gi18n.go @@ -7,6 +7,8 @@ // Package gi18n implements internationalization and localization. package gi18n +import "context" + // SetPath sets the directory path storing i18n files. func SetPath(path string) error { return Instance().SetPath(path) @@ -23,42 +25,29 @@ func SetDelimiters(left, right string) { } // T is alias of Translate for convenience. -func T(content string, language ...string) string { - return Instance().T(content, language...) +func T(ctx context.Context, content string) string { + return Instance().T(ctx, content) } // Tf is alias of TranslateFormat for convenience. -func Tf(format string, values ...interface{}) string { - return Instance().TranslateFormat(format, values...) -} - -// Tfl is alias of TranslateFormatLang for convenience. -func Tfl(language string, format string, values ...interface{}) string { - return Instance().TranslateFormatLang(language, format, values...) +func Tf(ctx context.Context, format string, values ...interface{}) string { + return Instance().TranslateFormat(ctx, format, values...) } // TranslateFormat translates, formats and returns the with configured language // and given . -func TranslateFormat(format string, values ...interface{}) string { - return Instance().TranslateFormat(format, values...) -} - -// TranslateFormatLang translates, formats and returns the with configured language -// and given . The parameter specifies custom translation language ignoring -// configured language. If is given empty string, it uses the default configured -// language for the translation. -func TranslateFormatLang(language string, format string, values ...interface{}) string { - return Instance().TranslateFormatLang(language, format, values...) +func TranslateFormat(ctx context.Context, format string, values ...interface{}) string { + return Instance().TranslateFormat(ctx, format, values...) } // Translate translates with configured language and returns the translated content. // The parameter specifies custom translation language ignoring configured language. -func Translate(content string, language ...string) string { - return Instance().Translate(content, language...) +func Translate(ctx context.Context, content string) string { + return Instance().Translate(ctx, content) } -// GetValue retrieves and returns the configured content for given key and specified language. +// GetContent retrieves and returns the configured content for given key and specified language. // It returns an empty string if not found. -func GetContent(key string, language ...string) string { - return Instance().GetContent(key, language...) +func GetContent(ctx context.Context, key string) string { + return Instance().GetContent(ctx, key) } diff --git a/i18n/gi18n/gi18n_ctx.go b/i18n/gi18n/gi18n_ctx.go new file mode 100644 index 000000000..4a9bc06ce --- /dev/null +++ b/i18n/gi18n/gi18n_ctx.go @@ -0,0 +1,29 @@ +// 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 gi18n implements internationalization and localization. +package gi18n + +import "context" + +const ( + ctxLanguage = "I18nLanguage" +) + +// WithLanguage append language setting to the context and returns a new context. +func WithLanguage(ctx context.Context, language string) context.Context { + return context.WithValue(ctx, ctxLanguage, language) +} + +// LanguageFromCtx retrieves and returns language name from context. +// It returns an empty string if it is not set previously. +func LanguageFromCtx(ctx context.Context) string { + v := ctx.Value(ctxLanguage) + if v != nil { + return v.(string) + } + return "" +} diff --git a/i18n/gi18n/gi18n_instance.go b/i18n/gi18n/gi18n_instance.go index d9abdbde2..43af97cc1 100644 --- a/i18n/gi18n/gi18n_instance.go +++ b/i18n/gi18n/gi18n_instance.go @@ -9,7 +9,7 @@ package gi18n import "github.com/gogf/gf/container/gmap" const ( - // Default group name for instance usage. + // DefaultName is the default group name for instance usage. DefaultName = "default" ) diff --git a/i18n/gi18n/gi18n_manager.go b/i18n/gi18n/gi18n_manager.go index 6b737df94..9dc6a8639 100644 --- a/i18n/gi18n/gi18n_manager.go +++ b/i18n/gi18n/gi18n_manager.go @@ -7,6 +7,7 @@ package gi18n import ( + "context" "errors" "fmt" "github.com/gogf/gf/internal/intlog" @@ -25,7 +26,7 @@ import ( "github.com/gogf/gf/os/gres" ) -// Manager, it is concurrent safe, supporting hot reload. +// Manager for i18n contents, it is concurrent safe, supporting hot reload. type Manager struct { mu sync.RWMutex data map[string]map[string]string // Translating map. @@ -36,13 +37,13 @@ type Manager struct { // Options is used for i18n object configuration. type Options struct { Path string // I18n files storage path. - Language string // Local language. + Language string // Default local language. Delimiters []string // Delimiters for variable parsing. } var ( - // defaultDelimiters defines the default key variable delimiters. - defaultDelimiters = []string{"{#", "}"} + defaultLanguage = "en" // defaultDelimiters defines the default language if user does not specified in options. + defaultDelimiters = []string{"{#", "}"} // defaultDelimiters defines the default key variable delimiters. ) // New creates and returns a new i18n manager. @@ -55,6 +56,9 @@ func New(options ...Options) *Manager { } else { opts = DefaultOptions() } + if len(opts.Language) == 0 { + opts.Language = defaultLanguage + } if len(opts.Delimiters) == 0 { opts.Delimiters = defaultDelimiters } @@ -118,45 +122,30 @@ func (m *Manager) SetDelimiters(left, right string) { } // T is alias of Translate for convenience. -func (m *Manager) T(content string, language ...string) string { - return m.Translate(content, language...) +func (m *Manager) T(ctx context.Context, content string) string { + return m.Translate(ctx, content) } // Tf is alias of TranslateFormat for convenience. -func (m *Manager) Tf(format string, values ...interface{}) string { - return m.TranslateFormat(format, values...) -} - -// Tfl is alias of TranslateFormatLang for convenience. -func (m *Manager) Tfl(language string, format string, values ...interface{}) string { - return m.TranslateFormatLang(language, format, values...) +func (m *Manager) Tf(ctx context.Context, format string, values ...interface{}) string { + return m.TranslateFormat(ctx, format, values...) } // TranslateFormat translates, formats and returns the with configured language // and given . -func (m *Manager) TranslateFormat(format string, values ...interface{}) string { - return fmt.Sprintf(m.Translate(format), values...) -} - -// TranslateFormatLang translates, formats and returns the with configured language -// and given . The parameter specifies custom translation language ignoring -// configured language. If is given empty string, it uses the default configured -// language for the translation. -func (m *Manager) TranslateFormatLang(language string, format string, values ...interface{}) string { - return fmt.Sprintf(m.Translate(format, language), values...) +func (m *Manager) TranslateFormat(ctx context.Context, format string, values ...interface{}) string { + return fmt.Sprintf(m.Translate(ctx, format), values...) } // Translate translates with configured language. // The parameter specifies custom translation language ignoring configured language. -func (m *Manager) Translate(content string, language ...string) string { +func (m *Manager) Translate(ctx context.Context, content string) string { m.init() m.mu.RLock() defer m.mu.RUnlock() transLang := m.options.Language - if len(language) > 0 && language[0] != "" { - transLang = language[0] - } else { - transLang = m.options.Language + if lang := LanguageFromCtx(ctx); lang != "" { + transLang = lang } data := m.data[transLang] if data == nil { @@ -179,17 +168,15 @@ func (m *Manager) Translate(content string, language ...string) string { return result } -// GetValue retrieves and returns the configured content for given key and specified language. +// GetContent retrieves and returns the configured content for given key and specified language. // It returns an empty string if not found. -func (m *Manager) GetContent(key string, language ...string) string { +func (m *Manager) GetContent(ctx context.Context, key string) string { m.init() m.mu.RLock() defer m.mu.RUnlock() transLang := m.options.Language - if len(language) > 0 && language[0] != "" { - transLang = language[0] - } else { - transLang = m.options.Language + if lang := LanguageFromCtx(ctx); lang != "" { + transLang = lang } if data, ok := m.data[transLang]; ok { return data[key] diff --git a/i18n/gi18n/gi18n_unit_test.go b/i18n/gi18n/gi18n_unit_test.go index b0012c1c7..0579bce5c 100644 --- a/i18n/gi18n/gi18n_unit_test.go +++ b/i18n/gi18n/gi18n_unit_test.go @@ -7,6 +7,7 @@ package gi18n_test import ( + "context" "testing" "github.com/gogf/gf/os/gres" @@ -32,16 +33,16 @@ func Test_Basic(t *testing.T) { Path: gdebug.TestDataPath("i18n"), }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") i18n.SetDelimiters("{$", "}") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") - t.Assert(i18n.T("{$hello}{$world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{$hello}{$world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -49,13 +50,13 @@ func Test_Basic(t *testing.T) { Path: gdebug.TestDataPath("i18n-file"), }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -63,13 +64,13 @@ func Test_Basic(t *testing.T) { Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-dir", }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) } @@ -80,18 +81,10 @@ func Test_TranslateFormat(t *testing.T) { Path: gdebug.TestDataPath("i18n"), }) i18n.SetLanguage("none") - t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020") + t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "{#hello}{#world} 2020") i18n.SetLanguage("ja") - t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "こんにちは世界 2020") - }) - // Tfl - gtest.C(t, func(t *gtest.T) { - i18n := gi18n.New(gi18n.Options{ - Path: gdebug.TestDataPath("i18n"), - }) - t.Assert(i18n.Tfl("ja", "{#hello}{#world} %d", 2020), "こんにちは世界 2020") - t.Assert(i18n.Tfl("zh-CN", "{#hello}{#world} %d", 2020), "你好世界 2020") + t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "こんにちは世界 2020") }) } @@ -101,13 +94,13 @@ func Test_DefaultManager(t *testing.T) { t.Assert(err, nil) gi18n.SetLanguage("none") - t.Assert(gi18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") gi18n.SetLanguage("ja") - t.Assert(gi18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") gi18n.SetLanguage("zh-CN") - t.Assert(gi18n.T("{#hello}{#world}"), "你好世界") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -115,13 +108,13 @@ func Test_DefaultManager(t *testing.T) { t.Assert(err, nil) gi18n.SetLanguage("none") - t.Assert(gi18n.Translate("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") gi18n.SetLanguage("ja") - t.Assert(gi18n.Translate("{#hello}{#world}"), "こんにちは世界") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "こんにちは世界") gi18n.SetLanguage("zh-CN") - t.Assert(gi18n.Translate("{#hello}{#world}"), "你好世界") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "你好世界") }) } @@ -132,21 +125,21 @@ func Test_Instance(t *testing.T) { err := m.SetPath("i18n-dir") t.Assert(err, nil) m.SetLanguage("zh-CN") - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { m := gi18n.Instance() - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { - t.Assert(g.I18n().T("{#hello}{#world}"), "你好世界") + t.Assert(g.I18n().T(context.Background(), "{#hello}{#world}"), "你好世界") }) // Default language is: en gtest.C(t, func(t *gtest.T) { m := gi18n.Instance(gconv.String(gtime.TimestampNano())) - t.Assert(m.T("{#hello}{#world}"), "HelloWorld") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "HelloWorld") }) } @@ -157,12 +150,12 @@ func Test_Resource(t *testing.T) { t.Assert(err, nil) m.SetLanguage("none") - t.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") m.SetLanguage("ja") - t.Assert(m.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") m.SetLanguage("zh-CN") - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) } diff --git a/net/ghttp/ghttp_response_view.go b/net/ghttp/ghttp_response_view.go index e3361197c..194d04c0d 100644 --- a/net/ghttp/ghttp_response_view.go +++ b/net/ghttp/ghttp_response_view.go @@ -59,18 +59,18 @@ func (r *Response) WriteTplContent(content string, params ...gview.Params) error // ParseTpl parses given template file with given template variables // and returns the parsed template content. func (r *Response) ParseTpl(tpl string, params ...gview.Params) (string, error) { - return r.Request.GetView().Parse(tpl, r.buildInVars(params...)) + return r.Request.GetView().Parse(r.Request.Context(), tpl, r.buildInVars(params...)) } // ParseDefault parses the default template file with params. func (r *Response) ParseTplDefault(params ...gview.Params) (string, error) { - return r.Request.GetView().ParseDefault(r.buildInVars(params...)) + return r.Request.GetView().ParseDefault(r.Request.Context(), r.buildInVars(params...)) } // ParseTplContent parses given template file with given template parameters // and returns the parsed template content. func (r *Response) ParseTplContent(content string, params ...gview.Params) (string, error) { - return r.Request.GetView().ParseContent(content, r.buildInVars(params...)) + return r.Request.GetView().ParseContent(r.Request.Context(), content, r.buildInVars(params...)) } // buildInVars merges build-in variables into and returns the new template variables. diff --git a/net/ghttp/ghttp_server_admin.go b/net/ghttp/ghttp_server_admin.go index ee47fc2b5..dc4b107ea 100644 --- a/net/ghttp/ghttp_server_admin.go +++ b/net/ghttp/ghttp_server_admin.go @@ -26,7 +26,7 @@ func (p *utilAdmin) Index(r *Request) { "path": gfile.SelfPath(), "uri": strings.TrimRight(r.URL.Path, "/"), } - buffer, _ := gview.ParseContent(` + buffer, _ := gview.ParseContent(r.Context(), ` GoFrame Web Server Admin diff --git a/net/ghttp/ghttp_server_pprof.go b/net/ghttp/ghttp_server_pprof.go index dc63a6a74..cef4f2d35 100644 --- a/net/ghttp/ghttp_server_pprof.go +++ b/net/ghttp/ghttp_server_pprof.go @@ -62,7 +62,7 @@ func (p *utilPProf) Index(r *Request) { "profiles": profiles, } if len(action) == 0 { - buffer, _ := gview.ParseContent(` + buffer, _ := gview.ParseContent(r.Context(), ` GoFrame PProf diff --git a/os/gview/gview.go b/os/gview/gview.go index 54813b9ab..dc0013add 100644 --- a/os/gview/gview.go +++ b/os/gview/gview.go @@ -11,6 +11,7 @@ package gview import ( + "context" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/internal/intlog" @@ -50,9 +51,9 @@ func checkAndInitDefaultView() { // ParseContent parses the template content directly using the default view object // and returns the parsed content. -func ParseContent(content string, params ...Params) (string, error) { +func ParseContent(ctx context.Context, content string, params ...Params) (string, error) { checkAndInitDefaultView() - return defaultViewObj.ParseContent(content, params...) + return defaultViewObj.ParseContent(ctx, content, params...) } // New returns a new view object. diff --git a/os/gview/gview_buildin.go b/os/gview/gview_buildin.go index 064903897..c838750ee 100644 --- a/os/gview/gview_buildin.go +++ b/os/gview/gview_buildin.go @@ -7,6 +7,7 @@ package gview import ( + "context" "fmt" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/util/gutil" @@ -115,7 +116,7 @@ func (view *View) buildInFuncInclude(file interface{}, data ...map[string]interf return "" } // It will search the file internally. - content, err := view.Parse(path, m) + content, err := view.Parse(context.TODO(), path, m) if err != nil { return htmltpl.HTML(err.Error()) } diff --git a/os/gview/gview_i18n.go b/os/gview/gview_i18n.go index df8ca6584..554f2f567 100644 --- a/os/gview/gview_i18n.go +++ b/os/gview/gview_i18n.go @@ -6,18 +6,33 @@ package gview -import "github.com/gogf/gf/util/gconv" +import ( + "context" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gconv" +) + +const ( + i18nLanguageVariableName = "I18nLanguage" +) // i18nTranslate translate the content with i18n feature. -func (view *View) i18nTranslate(content string, params Params) string { +func (view *View) i18nTranslate(ctx context.Context, content string, variables Params) string { if view.config.I18nManager != nil { - if v, ok := params["I18nLanguage"]; ok { - language := gconv.String(v) - if language != "" { - return view.config.I18nManager.T(content, language) - } + // Compatible with old version. + if language, ok := variables[i18nLanguageVariableName]; ok { + ctx = gi18n.WithLanguage(ctx, gconv.String(language)) } - return view.config.I18nManager.T(content) + return view.config.I18nManager.T(ctx, content) } return content } + +// setI18nLanguageFromCtx retrieves language name from context and sets it to template variables map. +func (view *View) setI18nLanguageFromCtx(ctx context.Context, variables map[string]interface{}) { + if language, ok := variables[i18nLanguageVariableName]; !ok { + if language = gi18n.LanguageFromCtx(ctx); language != "" { + variables[i18nLanguageVariableName] = language + } + } +} diff --git a/os/gview/gview_parse.go b/os/gview/gview_parse.go index e02d9dd75..f69d7e08b 100644 --- a/os/gview/gview_parse.go +++ b/os/gview/gview_parse.go @@ -8,6 +8,7 @@ package gview import ( "bytes" + "context" "errors" "fmt" "github.com/gogf/gf/encoding/ghash" @@ -54,7 +55,7 @@ var ( // Parse parses given template file with given template variables // and returns the parsed template content. -func (view *View) Parse(file string, params ...Params) (result string, err error) { +func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) { var tpl interface{} // It caches the file, folder and its content to enhance performance. r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} { @@ -125,6 +126,8 @@ func (view *View) Parse(file string, params ...Params) (result string, err error if len(view.data) > 0 { gutil.MapMerge(variables, view.data) } + view.setI18nLanguageFromCtx(ctx, variables) + buffer := bytes.NewBuffer(nil) if view.config.AutoEncode { newTpl, err := tpl.(*htmltpl.Template).Clone() @@ -142,18 +145,18 @@ func (view *View) Parse(file string, params ...Params) (result string, err error // TODO any graceful plan to replace ""? result = gstr.Replace(buffer.String(), "", "") - result = view.i18nTranslate(result, variables) + result = view.i18nTranslate(ctx, result, variables) return result, nil } // ParseDefault parses the default template file with params. -func (view *View) ParseDefault(params ...Params) (result string, err error) { - return view.Parse(view.config.DefaultFile, params...) +func (view *View) ParseDefault(ctx context.Context, params ...Params) (result string, err error) { + return view.Parse(ctx, view.config.DefaultFile, params...) } // ParseContent parses given template content with template variables // and returns the parsed content in []byte. -func (view *View) ParseContent(content string, params ...Params) (string, error) { +func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) { // It's not necessary continuing parsing if template content is empty. if content == "" { return "", nil @@ -191,6 +194,8 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) if len(view.data) > 0 { gutil.MapMerge(variables, view.data) } + view.setI18nLanguageFromCtx(ctx, variables) + buffer := bytes.NewBuffer(nil) if view.config.AutoEncode { newTpl, err := tpl.(*htmltpl.Template).Clone() @@ -207,7 +212,7 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) } // TODO any graceful plan to replace ""? result := gstr.Replace(buffer.String(), "", "") - result = view.i18nTranslate(result, variables) + result = view.i18nTranslate(ctx, result, variables) return result, nil } diff --git a/os/gview/gview_unit_basic_test.go b/os/gview/gview_unit_basic_test.go index 53caf1b8c..4cad9247c 100644 --- a/os/gview/gview_unit_basic_test.go +++ b/os/gview/gview_unit_basic_test.go @@ -7,6 +7,7 @@ package gview_test import ( + "context" "github.com/gogf/gf/encoding/ghtml" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" @@ -39,18 +40,18 @@ func Test_Basic(t *testing.T) { view.Assigns(g.Map{"version": "1.7.0"}) view.BindFunc("GetName", func() string { return "gf" }) view.BindFuncMap(gview.FuncMap{"GetVersion": func() string { return "1.7.0" }}) - result, err := view.ParseContent(str, g.Map{"other": "that's all"}) + result, err := view.ParseContent(context.TODO(), str, g.Map{"other": "that's all"}) t.Assert(err != nil, false) t.Assert(result, "hello gf,version:1.7.0;hello gf,version:1.7.0;that's all") //测试api方法 str = `hello {{.name}}` - result, err = gview.ParseContent(str, g.Map{"name": "gf"}) + result, err = gview.ParseContent(context.TODO(), str, g.Map{"name": "gf"}) t.Assert(err != nil, false) t.Assert(result, "hello gf") //测试instance方法 - result, err = gview.Instance().ParseContent(str, g.Map{"name": "gf"}) + result, err = gview.Instance().ParseContent(context.TODO(), str, g.Map{"name": "gf"}) t.Assert(err != nil, false) t.Assert(result, "hello gf") }) @@ -59,132 +60,132 @@ func Test_Basic(t *testing.T) { func Test_Func(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{eq 1 1}};{{eq 1 2}};{{eq "A" "B"}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;false`) str = `{{ne 1 2}};{{ne 1 1}};{{ne "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;true`) str = `{{lt 1 2}};{{lt 1 1}};{{lt 1 0}};{{lt "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;false;true`) str = `{{le 1 2}};{{le 1 1}};{{le 1 0}};{{le "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;true;false;true`) str = `{{gt 1 2}};{{gt 1 1}};{{gt 1 0}};{{gt "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `false;false;true;false`) str = `{{ge 1 2}};{{ge 1 1}};{{ge 1 0}};{{ge "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `false;true;true;false`) str = `{{"
测试
"|text}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `测试`) str = `{{"
测试
"|html}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `<div>测试</div>`) str = `{{"
测试
"|htmlencode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `<div>测试</div>`) str = `{{"<div>测试</div>"|htmldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `
测试
`) str = `{{"https://goframe.org"|url}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https%3A%2F%2Fgoframe.org`) str = `{{"https://goframe.org"|urlencode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https%3A%2F%2Fgoframe.org`) str = `{{"https%3A%2F%2Fgoframe.org"|urldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https://goframe.org`) str = `{{"https%3NA%2F%2Fgoframe.org"|urldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(gstr.Contains(result, "invalid URL escape"), true) str = `{{1540822968 | date "Y-m-d"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `2018-10-29`) str = `{{date "Y-m-d"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) str = `{{"我是中国人" | substr 2 -1}};{{"我是中国人" | substr 2 2}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `中国人;中国`) str = `{{"我是中国人" | strlimit 2 "..."}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `我是...`) str = `{{"I'm中国人" | replace "I'm" "我是"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `我是中国人`) str = `{{compare "A" "B"}};{{compare "1" "2"}};{{compare 2 1}};{{compare 1 1}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `-1;-1;1;0`) str = `{{"热爱GF热爱生活" | hidestr 20 "*"}};{{"热爱GF热爱生活" | hidestr 50 "*"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `热爱GF*爱生活;热爱****生活`) str = `{{"热爱GF热爱生活" | highlight "GF" "red"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `热爱GF热爱生活`) str = `{{"gf" | toupper}};{{"GF" | tolower}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `GF;gf`) str = `{{concat "I" "Love" "GoFrame"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, `ILoveGoFrame`) }) // eq: multiple values. gtest.C(t, func(t *gtest.T) { str := `{{eq 1 2 1 3 4 5}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true`) }) gtest.C(t, func(t *gtest.T) { str := `{{eq 6 2 1 3 4 5}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `false`) }) @@ -193,7 +194,7 @@ func Test_Func(t *testing.T) { func Test_FuncNl2Br(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{"Go\nFrame" | nl2br}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, `Go
Frame`) }) @@ -203,7 +204,7 @@ func Test_FuncNl2Br(t *testing.T) { s += "Go\nFrame\n中文" } str := `{{.content | nl2br}}` - result, err := gview.ParseContent(str, g.Map{ + result, err := gview.ParseContent(context.TODO(), str, g.Map{ "content": s, }) t.Assert(err, nil) @@ -231,10 +232,10 @@ func Test_FuncInclude(t *testing.T) { ioutil.WriteFile(templatePath+gfile.Separator+"footer.html", []byte(footer), 0644) ioutil.WriteFile(templatePath+gfile.Separator+"layout.html", []byte(layout), 0644) view := gview.New(templatePath) - result, err := view.Parse("notfound.html") + result, err := view.Parse(context.TODO(), "notfound.html") t.Assert(err != nil, true) t.Assert(result, ``) - result, err = view.Parse("layout.html") + result, err = view.Parse(context.TODO(), "layout.html") t.Assert(err != nil, false) t.Assert(result, `

HEADER

hello gf

@@ -243,7 +244,7 @@ func Test_FuncInclude(t *testing.T) { gfile.Mkdir(templatePath + gfile.Separator + "template") gfile.Create(notfoundPath) ioutil.WriteFile(notfoundPath, []byte("notfound"), 0644) - result, err = view.Parse("notfound.html") + result, err = view.Parse(context.TODO(), "notfound.html") t.Assert(err != nil, true) t.Assert(result, ``) }) @@ -283,7 +284,7 @@ func Test_ParseContent(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{.name}}` view := gview.New() - result, err := view.ParseContent(str, g.Map{"name": func() {}}) + result, err := view.ParseContent(context.TODO(), str, g.Map{"name": func() {}}) t.Assert(err != nil, true) t.Assert(result, ``) }) @@ -306,7 +307,7 @@ func Test_HotReload(t *testing.T) { view := gview.New(dirPath) time.Sleep(100 * time.Millisecond) - result, err := view.Parse("test.html", g.Map{ + result, err := view.Parse(context.TODO(), "test.html", g.Map{ "var": "1", }) t.Assert(err, nil) @@ -317,7 +318,7 @@ func Test_HotReload(t *testing.T) { t.Assert(err, nil) time.Sleep(100 * time.Millisecond) - result, err = view.Parse("test.html", g.Map{ + result, err = view.Parse(context.TODO(), "test.html", g.Map{ "var": "2", }) t.Assert(err, nil) @@ -329,7 +330,7 @@ func Test_XSS(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() s := "
" - r, err := v.ParseContent("{{.v}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -339,7 +340,7 @@ func Test_XSS(t *testing.T) { v := gview.New() v.SetAutoEncode(true) s := "
" - r, err := v.ParseContent("{{.v}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -350,7 +351,7 @@ func Test_XSS(t *testing.T) { v := gview.New() v.SetAutoEncode(true) s := "
" - r, err := v.ParseContent("{{if eq 1 1}}{{.v}}{{end}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{if eq 1 1}}{{.v}}{{end}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -371,7 +372,7 @@ func Test_BuildInFuncMap(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() v.Assign("v", new(TypeForBuildInFuncMap)) - r, err := v.ParseContent("{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}") + r, err := v.ParseContent(context.TODO(), "{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}") t.Assert(err, nil) t.Assert(gstr.Contains(r, "Name:john"), true) t.Assert(gstr.Contains(r, "Score:99.9"), true) @@ -394,7 +395,7 @@ func Test_BuildInFuncMaps(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() v.Assign("v", new(TypeForBuildInFuncMaps)) - r, err := v.ParseContent("{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}") + r, err := v.ParseContent(context.TODO(), "{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}") t.Assert(err, nil) t.Assert(r, ` 0:john 99.9 1:smith 100 `) }) @@ -407,7 +408,7 @@ func Test_BuildInFuncDump(t *testing.T) { "name": "john", "score": 100, }) - r, err := v.ParseContent("{{dump .}}") + r, err := v.ParseContent(context.TODO(), "{{dump .}}") t.Assert(err, nil) t.Assert(gstr.Contains(r, `"name": "john"`), true) t.Assert(gstr.Contains(r, `"score": 100`), true) @@ -420,7 +421,7 @@ func Test_BuildInFuncJson(t *testing.T) { v.Assign("v", g.Map{ "name": "john", }) - r, err := v.ParseContent("{{json .v}}") + r, err := v.ParseContent(context.TODO(), "{{json .v}}") t.Assert(err, nil) t.Assert(r, `{"name":"john"}`) }) diff --git a/os/gview/gview_unit_config_test.go b/os/gview/gview_unit_config_test.go index 314b4be48..899245157 100644 --- a/os/gview/gview_unit_config_test.go +++ b/os/gview/gview_unit_config_test.go @@ -7,6 +7,7 @@ package gview_test import ( + "context" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gview" @@ -30,11 +31,11 @@ func Test_Config(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(g.Map{"version": "1.7.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello gf,version:1.7.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "name:gf") }) @@ -55,11 +56,11 @@ func Test_ConfigWithMap(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(g.Map{"version": "1.7.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello gf,version:1.7.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "name:gf") }) diff --git a/os/gview/gview_unit_encode_test.go b/os/gview/gview_unit_encode_test.go index 1aad3616d..892be705b 100644 --- a/os/gview/gview_unit_encode_test.go +++ b/os/gview/gview_unit_encode_test.go @@ -7,6 +7,7 @@ package gview_test import ( + "context" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gfile" @@ -20,7 +21,7 @@ func Test_Encode_Parse(t *testing.T) { v := gview.New() v.SetPath(gdebug.TestDataPath("tpl")) v.SetAutoEncode(true) - result, err := v.Parse("encode.tpl", g.Map{ + result, err := v.Parse(context.TODO(), "encode.tpl", g.Map{ "title": "my title", }) t.Assert(err, nil) @@ -33,7 +34,7 @@ func Test_Encode_ParseContent(t *testing.T) { v := gview.New() tplContent := gfile.GetContents(gdebug.TestDataPath("tpl", "encode.tpl")) v.SetAutoEncode(true) - result, err := v.ParseContent(tplContent, g.Map{ + result, err := v.ParseContent(context.TODO(), tplContent, g.Map{ "title": "my title", }) t.Assert(err, nil) diff --git a/os/gview/gview_unit_i18n_test.go b/os/gview/gview_unit_i18n_test.go index b04ba2bbd..23550e66c 100644 --- a/os/gview/gview_unit_i18n_test.go +++ b/os/gview/gview_unit_i18n_test.go @@ -7,6 +7,7 @@ package gview_test import ( + "context" "testing" "github.com/gogf/gf/debug/gdebug" @@ -26,21 +27,21 @@ func Test_I18n(t *testing.T) { g.I18n().SetPath(gdebug.TestDataPath("i18n")) g.I18n().SetLanguage("zh-CN") - result1, err := g.View().ParseContent(content, g.Map{ + result1, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) t.Assert(result1, expect1) g.I18n().SetLanguage("ja") - result2, err := g.View().ParseContent(content, g.Map{ + result2, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) t.Assert(result2, expect2) g.I18n().SetLanguage("none") - result3, err := g.View().ParseContent(content, g.Map{ + result3, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) @@ -54,21 +55,21 @@ func Test_I18n(t *testing.T) { g.I18n().SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n") - result1, err := g.View().ParseContent(content, g.Map{ + result1, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "zh-CN", }) t.Assert(err, nil) t.Assert(result1, expect1) - result2, err := g.View().ParseContent(content, g.Map{ + result2, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "ja", }) t.Assert(err, nil) t.Assert(result2, expect2) - result3, err := g.View().ParseContent(content, g.Map{ + result3, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "none", }) diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 49ebeb510..7a2a30141 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -9,6 +9,7 @@ package gvalid import ( "errors" "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" "strings" ) @@ -29,7 +30,9 @@ func newError(rules []string, errors map[string]map[string]string) *Error { for field, m := range errors { for k, v := range m { v = strings.Replace(v, ":attribute", field, -1) - m[k], _ = gregex.ReplaceString(`\s{2,}`, ` `, v) + v, _ = gregex.ReplaceString(`\s{2,}`, ` `, v) + v = gstr.Trim(v) + m[k] = v } errors[field] = m } diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index 5709092f1..5f516a2c6 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -6,36 +6,34 @@ package gvalid -import "context" +import ( + "context" + "github.com/gogf/gf/i18n/gi18n" +) // Validator is the validation manager. type Validator struct { - i18nLang string // I18n language. - ctx context.Context // Context containing custom context variables. + ctx context.Context // Context containing custom context variables. + i18nManager *gi18n.Manager // I18n manager for error message translation. + } // New creates and returns a new Validator. func New() *Validator { - return &Validator{} + return &Validator{ + ctx: context.TODO(), + i18nManager: gi18n.Instance(), + } } -// 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 +// I18n sets the i18n manager for the validator. +func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator { + v.i18nManager = i18nManager + return v } // Ctx is a chaining operation function which sets the context for next validation. func (v *Validator) Ctx(ctx context.Context) *Validator { - newValidator := v.Clone() - newValidator.ctx = ctx - return newValidator + v.ctx = ctx + return v } diff --git a/util/gvalid/gvalid_validator_message.go b/util/gvalid/gvalid_validator_message.go index 56bf17251..e1737dce9 100644 --- a/util/gvalid/gvalid_validator_message.go +++ b/util/gvalid/gvalid_validator_message.go @@ -6,9 +6,8 @@ package gvalid -import ( - "fmt" - "github.com/gogf/gf/i18n/gi18n" +const ( + ruleMessagePrefixForI18n = "gf.gvalid.rule." ) // defaultMessages is the default error messages. @@ -66,15 +65,21 @@ var defaultMessages = map[string]string{ func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string { content := customMsgMap[ruleKey] if content != "" { + // I18n translation. + i18nContent := v.i18nManager.GetContent(v.ctx, content) + if i18nContent != "" { + return i18nContent + } return content } - content = gi18n.GetContent(fmt.Sprintf(`gf.gvalid.rule.%s`, ruleKey), v.i18nLang) + // Retrieve default message according to certain rule. + content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+ruleKey) 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__`, v.i18nLang) + content = v.i18nManager.GetContent(v.ctx, `gf.gvalid.rule.__default__`) if content == "" { content = defaultMessages["__default__"] } diff --git a/util/gvalid/gvalid_z_unit_i18n_test.go b/util/gvalid/gvalid_z_unit_i18n_test.go new file mode 100644 index 000000000..bb27a3f4e --- /dev/null +++ b/util/gvalid/gvalid_z_unit_i18n_test.go @@ -0,0 +1,50 @@ +// 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_test + +import ( + "context" + "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gvalid" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func TestValidator_I18n(t *testing.T) { + var ( + err *gvalid.Error + i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")}) + ctxCn = gi18n.WithLanguage(context.TODO(), "cn") + validator = gvalid.New().I18n(i18nManager) + ) + gtest.C(t, func(t *gtest.T) { + err = validator.Check("", "required", nil) + t.Assert(err.String(), "The field is required") + + err = validator.Ctx(ctxCn).Check("", "required", nil) + t.Assert(err.String(), "字段不能为空") + }) + gtest.C(t, func(t *gtest.T) { + err = validator.Ctx(ctxCn).Check("", "required", "CustomMessage") + t.Assert(err.String(), "自定义错误") + }) + gtest.C(t, func(t *gtest.T) { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId int `v:"between:1,10000 # project id must between :min, :max"` + } + obj := &Params{ + Page: 1, + Size: 10, + } + err := validator.Ctx(ctxCn).CheckStruct(obj, nil) + t.Assert(err.String(), "项目ID必须大于等于1并且要小于等于10000") + }) +} diff --git a/util/gvalid/testdata/i18n/cn/validation.toml b/util/gvalid/testdata/i18n/cn/validation.toml new file mode 100644 index 000000000..0cc409eb5 --- /dev/null +++ b/util/gvalid/testdata/i18n/cn/validation.toml @@ -0,0 +1,48 @@ +"gf.gvalid.rule.required" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-if" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-unless" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-with" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-with-all" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-without" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-without-all" = ":attribute 字段不能为空" +"gf.gvalid.rule.date" = ":attribute 日期格式不正确" +"gf.gvalid.rule.date-format" = ":attribute 日期格式不满足:format" +"gf.gvalid.rule.email" = ":attribute 邮箱地址格式不正确" +"gf.gvalid.rule.phone" = ":attribute 手机号码格式不正确" +"gf.gvalid.rule.phone-loose" = ":attribute 手机号码格式不正确" +"gf.gvalid.rule.telephone" = ":attribute 电话号码格式不正确" +"gf.gvalid.rule.passport" = ":attribute 账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间" +"gf.gvalid.rule.password" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符" +"gf.gvalid.rule.password2" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字" +"gf.gvalid.rule.password3" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符" +"gf.gvalid.rule.postcode" = ":attribute 邮政编码不正确" +"gf.gvalid.rule.resident-id" = ":attribute 身份证号码格式不正确" +"gf.gvalid.rule.bank-card" = ":attribute 银行卡号格式不正确" +"gf.gvalid.rule.qq" = ":attribute QQ号码格式不正确" +"gf.gvalid.rule.ip" = ":attribute IP地址格式不正确" +"gf.gvalid.rule.ipv4" = ":attribute IPv4地址格式不正确" +"gf.gvalid.rule.ipv6" = ":attribute IPv6地址格式不正确" +"gf.gvalid.rule.mac" = ":attribute MAC地址格式不正确" +"gf.gvalid.rule.url" = ":attribute URL地址格式不正确" +"gf.gvalid.rule.domain" = ":attribute 域名格式不正确" +"gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符" +"gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min" +"gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max" +"gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max" +"gf.gvalid.rule.min" = ":attribute 字段最小值为:min" +"gf.gvalid.rule.max" = ":attribute 字段最大值为:max" +"gf.gvalid.rule.json" = ":attribute 字段应当为JSON格式" +"gf.gvalid.rule.xml" = ":attribute 字段应当为XML格式" +"gf.gvalid.rule.array" = ":attribute 字段应当为数组" +"gf.gvalid.rule.integer" = ":attribute 字段应当为整数" +"gf.gvalid.rule.float" = ":attribute 字段应当为浮点数" +"gf.gvalid.rule.boolean" = ":attribute 字段应当为布尔值" +"gf.gvalid.rule.same" = ":attribute 字段值必须和:field相同" +"gf.gvalid.rule.different" = ":attribute 字段值不能与:field相同" +"gf.gvalid.rule.in" = ":attribute 字段值不合法" +"gf.gvalid.rule.not-in" = ":attribute 字段值不合法" +"gf.gvalid.rule.regex" = ":attribute 字段值不合法" +"gf.gvalid.rule.__default__" = ":attribute 字段值不合法" + +"CustomMessage" = "自定义错误" +"project id must between :min, :max" = "项目ID必须大于等于:min并且要小于等于:max" \ No newline at end of file diff --git a/util/gvalid/testdata/i18n/en/validation.toml b/util/gvalid/testdata/i18n/en/validation.toml new file mode 100644 index 000000000..7dc56cab0 --- /dev/null +++ b/util/gvalid/testdata/i18n/en/validation.toml @@ -0,0 +1,45 @@ +"gf.gvalid.rule.required" = "The :attribute field is required" +"gf.gvalid.rule.required-if" = "The :attribute field is required" +"gf.gvalid.rule.required-unless" = "The :attribute field is required" +"gf.gvalid.rule.required-with" = "The :attribute field is required" +"gf.gvalid.rule.required-with-all" = "The :attribute field is required" +"gf.gvalid.rule.required-without" = "The :attribute field is required" +"gf.gvalid.rule.required-without-all" = "The :attribute field is required" +"gf.gvalid.rule.date" = "The :attribute value is not a valid date" +"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format" +"gf.gvalid.rule.email" = "The :attribute value must be a valid email address" +"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number" +"gf.gvalid.rule.phone-loose" = "The :attribute value must be a valid phone number" +"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number" +"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number" +"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number" +"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number" +"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address" +"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address" +"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address" +"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address" +"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address" +"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format" +"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max" +"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min" +"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max" +"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max" +"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min" +"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max" +"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string" +"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string" +"gf.gvalid.rule.array" = "The :attribute value must be an array" +"gf.gvalid.rule.integer" = "The :attribute value must be an integer" +"gf.gvalid.rule.float" = "The :attribute value must be a float" +"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false" +"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field" +"gf.gvalid.rule.different" = "The :attribute value must be different from field :field" +"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range" +"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range" +"gf.gvalid.rule.regex" = "The :attribute value is invalid" +"gf.gvalid.rule.__default__" = "The :attribute value is invalid" \ No newline at end of file From 23110b5d195fb7e83ce1697ef3a05d60e7117b26 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 13 May 2021 08:53:54 +0800 Subject: [PATCH 19/70] fix issue in unit testing case of package gins --- frame/gins/gins_z_unit_view_test.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/frame/gins/gins_z_unit_view_test.go b/frame/gins/gins_z_unit_view_test.go index bc8f2d409..084c09705 100644 --- a/frame/gins/gins_z_unit_view_test.go +++ b/frame/gins/gins_z_unit_view_test.go @@ -7,6 +7,7 @@ package gins import ( + "context" "fmt" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/os/gcfg" @@ -20,7 +21,7 @@ import ( func Test_View(t *testing.T) { gtest.C(t, func(t *gtest.T) { t.AssertNE(View(), nil) - b, e := View().ParseContent(`{{"我是中国人" | substr 2 -1}}`, nil) + b, e := View().ParseContent(context.TODO(), `{{"我是中国人" | substr 2 -1}}`, nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -30,7 +31,7 @@ func Test_View(t *testing.T) { t.Assert(err, nil) defer gfile.Remove(tpl) - b, e := View().Parse("t.tpl", nil) + b, e := View().Parse(context.TODO(), "t.tpl", nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -43,7 +44,7 @@ func Test_View(t *testing.T) { err = View().AddPath(path) t.Assert(err, nil) - b, e := View().Parse("t.tpl", nil) + b, e := View().Parse(context.TODO(), "t.tpl", nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -64,11 +65,11 @@ func Test_View_Config(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test1,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test1:test1") }) @@ -86,11 +87,11 @@ func Test_View_Config(t *testing.T) { str := `hello #{.name},version:#{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test2,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test2:test2") }) @@ -108,11 +109,11 @@ func Test_View_Config(t *testing.T) { str := `hello {.name},version:{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test:test") }) @@ -130,11 +131,11 @@ func Test_View_Config(t *testing.T) { str := `hello {.name},version:{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test:test") }) From 09de115670d17e4007a91d236aa58651ac9b1245 Mon Sep 17 00:00:00 2001 From: tom <1326675091@qq.com> Date: Thu, 13 May 2021 17:51:06 +0800 Subject: [PATCH 20/70] add websocket client --- net/ghttp/ghttp_client_websocket.go | 29 +++++++++ net/ghttp/ghttp_unit_websocket_client_test.go | 64 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 net/ghttp/ghttp_client_websocket.go create mode 100644 net/ghttp/ghttp_unit_websocket_client_test.go diff --git a/net/ghttp/ghttp_client_websocket.go b/net/ghttp/ghttp_client_websocket.go new file mode 100644 index 000000000..1f1f228f7 --- /dev/null +++ b/net/ghttp/ghttp_client_websocket.go @@ -0,0 +1,29 @@ +// 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 ghttp + +import ( + "github.com/gorilla/websocket" + "net/http" + "time" +) + +// WebSocketClient wraps the underlying websocket client connection +// and provides convenient functions. +type WebSocketClient struct { + *websocket.Dialer +} + +// NewWebSocketClient New creates and returns a new WebSocketClient object. +func NewWebSocketClient() *WebSocketClient { + return &WebSocketClient{ + &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 45 * time.Second, + }, + } +} diff --git a/net/ghttp/ghttp_unit_websocket_client_test.go b/net/ghttp/ghttp_unit_websocket_client_test.go new file mode 100644 index 000000000..823130ee1 --- /dev/null +++ b/net/ghttp/ghttp_unit_websocket_client_test.go @@ -0,0 +1,64 @@ +// 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 ghttp_test + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gorilla/websocket" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/test/gtest" +) + +func Test_WebSocketClient(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/ws", func(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := ghttp.NewWebSocketClient() + client.Proxy = http.ProxyFromEnvironment + client.HandshakeTimeout = time.Minute + + conn, _, err := client.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", p), nil) + t.Assert(err, nil) + defer conn.Close() + + msg := []byte("hello") + err = conn.WriteMessage(websocket.TextMessage, msg) + t.Assert(err, nil) + + mt, data, err := conn.ReadMessage() + t.Assert(err, nil) + t.Assert(mt, websocket.TextMessage) + t.Assert(data, msg) + }) +} From cc1224e0329ac512ccdf23aa0225496575e53de0 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 13 May 2021 20:56:52 +0800 Subject: [PATCH 21/70] add context for package gvalid.Check* functions --- .../server/request/request_validation.go | 4 +- .example/util/gvalid/gvalid.go | 15 +- .example/util/gvalid/gvalid_custom_message.go | 3 +- .example/util/gvalid/gvalid_error.go | 3 +- .example/util/gvalid/gvalid_result.go | 3 +- .example/util/gvalid/gvalid_sequence.go | 3 +- .example/util/gvalid/gvalid_struct1.go | 5 +- .example/util/gvalid/gvalid_struct2.go | 3 +- .example/util/gvalid/gvalid_struct3.go | 3 +- i18n/gi18n/gi18n_ctx.go | 6 + net/ghttp/ghttp_request_param.go | 3 +- net/ghttp/ghttp_unit_request_struct_test.go | 2 +- util/gvalid/gvalid.go | 17 +- util/gvalid/gvalid_validator.go | 4 +- util/gvalid/gvalid_z_example_test.go | 25 +- util/gvalid/gvalid_z_unit_basic_all_test.go | 415 +++++++++--------- util/gvalid/gvalid_z_unit_checkmap_test.go | 21 +- util/gvalid/gvalid_z_unit_checkstruct_test.go | 47 +- util/gvalid/gvalid_z_unit_custom_rule_test.go | 27 +- util/gvalid/gvalid_z_unit_customerror_test.go | 9 +- 20 files changed, 320 insertions(+), 298 deletions(-) diff --git a/.example/net/ghttp/server/request/request_validation.go b/.example/net/ghttp/server/request/request_validation.go index fa23190b2..2e180ad77 100644 --- a/.example/net/ghttp/server/request/request_validation.go +++ b/.example/net/ghttp/server/request/request_validation.go @@ -18,13 +18,13 @@ func main() { s.Group("/", func(rgroup *ghttp.RouterGroup) { rgroup.ALL("/user", func(r *ghttp.Request) { user := new(User) - if err := r.GetToStruct(user); err != nil { + if err := r.GetStruct(user); err != nil { r.Response.WriteJsonExit(g.Map{ "message": err, "errcode": 1, }) } - if err := gvalid.CheckStruct(user, nil); err != nil { + if err := gvalid.CheckStruct(r.Context(), user, nil); err != nil { r.Response.WriteJsonExit(g.Map{ "message": err.Maps(), "errcode": 1, diff --git a/.example/util/gvalid/gvalid.go b/.example/util/gvalid/gvalid.go index 9975bd443..a21b63f77 100644 --- a/.example/util/gvalid/gvalid.go +++ b/.example/util/gvalid/gvalid.go @@ -1,31 +1,32 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) func main() { //rule := "length:6,16" - //if m := gvalid.Check("123456", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { // fmt.Println(m) //} - //if m := gvalid.Check("12345", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "12345", rule, nil); m != nil { // fmt.Println(m) // // map[length:字段长度为6到16个字符] //} //rule := "integer|between:6,16" //msgs := "请输入一个整数|参数大小不对啊老铁" - //fmt.Println(gvalid.Check("5.66", rule, msgs)) + //fmt.Println(gvalid.Check(context.TODO(), "5.66", rule, msgs)) //// map[integer:请输入一个整数 between:参数大小不对啊老铁] //// 参数长度至少为6个数字或者6个字母,但是总长度不能超过16个字符 //rule := `regex:\d{6,}|\D{6,}|max-length:16` - //if m := gvalid.Check("123456", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { // fmt.Println(m) //} - //if m := gvalid.Check("abcde6", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "abcde6", rule, nil); m != nil { // fmt.Println(m) // // map[regex:字段值不合法] //} @@ -40,7 +41,7 @@ func main() { // "password" : "required|length:6,16|same:password2", // "password2" : "required|length:6,16", //} - //fmt.Println(gvalid.CheckMap(params, rules)) + //fmt.Println(gvalid.CheckMap(context.TODO(), params, rules)) //// map[passport:map[length:字段长度为6到16个字符] password:map[same:字段值不合法]] params := map[string]interface{}{ @@ -60,7 +61,7 @@ func main() { "same": "两次密码输入不相等", }, } - if e := gvalid.CheckMap(params, rules, msgs); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules, msgs); e != nil { g.Dump(e.Maps()) } // map[passport:map[length:账号长度应当在6到16之间] password:map[same:两次密码输入不相等]] diff --git a/.example/util/gvalid/gvalid_custom_message.go b/.example/util/gvalid/gvalid_custom_message.go index 816749f10..3c8798693 100644 --- a/.example/util/gvalid/gvalid_custom_message.go +++ b/.example/util/gvalid/gvalid_custom_message.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" @@ -8,6 +9,6 @@ import ( func main() { g.I18n().SetLanguage("cn") - err := gvalid.Check("", "required", nil) + err := gvalid.Check(context.TODO(), "", "required", nil) fmt.Println(err.String()) } diff --git a/.example/util/gvalid/gvalid_error.go b/.example/util/gvalid/gvalid_error.go index fe5c0a2f2..1f5d350d4 100644 --- a/.example/util/gvalid/gvalid_error.go +++ b/.example/util/gvalid/gvalid_error.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -17,7 +18,7 @@ func main() { ConfiemPassword: "", } - e := gvalid.CheckStruct(user, nil) + e := gvalid.CheckStruct(context.TODO(), user, nil) g.Dump(e.Map()) g.Dump(e.Maps()) g.Dump(e.String()) diff --git a/.example/util/gvalid/gvalid_result.go b/.example/util/gvalid/gvalid_result.go index 0190f0df4..ff9e01e6d 100644 --- a/.example/util/gvalid/gvalid_result.go +++ b/.example/util/gvalid/gvalid_result.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -18,7 +19,7 @@ func main() { Pass2: "123", } - e := gvalid.CheckStruct(user, nil) + e := gvalid.CheckStruct(context.TODO(), user, nil) g.Dump(e.String()) g.Dump(e.FirstString()) } diff --git a/.example/util/gvalid/gvalid_sequence.go b/.example/util/gvalid/gvalid_sequence.go index 8274cd1c9..0296a4ab5 100644 --- a/.example/util/gvalid/gvalid_sequence.go +++ b/.example/util/gvalid/gvalid_sequence.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/util/gvalid" @@ -17,7 +18,7 @@ func main() { "password@required|length:6,16|same:password2#密码不能为空}|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) diff --git a/.example/util/gvalid/gvalid_struct1.go b/.example/util/gvalid/gvalid_struct1.go index d992860ee..6995ec2be 100644 --- a/.example/util/gvalid/gvalid_struct1.go +++ b/.example/util/gvalid/gvalid_struct1.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -20,7 +21,7 @@ func main() { } // 使用结构体定义的校验规则和错误提示进行校验 - g.Dump(gvalid.CheckStruct(user, nil).Map()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil).Map()) // 自定义校验规则和错误提示,对定义的特定校验规则和错误提示进行覆盖 rules := map[string]string{ @@ -31,5 +32,5 @@ func main() { "password3": "名称不能为空", }, } - g.Dump(gvalid.CheckStruct(user, rules, msgs).Map()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, rules, msgs).Map()) } diff --git a/.example/util/gvalid/gvalid_struct2.go b/.example/util/gvalid/gvalid_struct2.go index a4c9442fb..8e6c11601 100644 --- a/.example/util/gvalid/gvalid_struct2.go +++ b/.example/util/gvalid/gvalid_struct2.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -13,5 +14,5 @@ func main() { user := &User{} - g.Dump(gvalid.CheckStruct(user, nil)) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil)) } diff --git a/.example/util/gvalid/gvalid_struct3.go b/.example/util/gvalid/gvalid_struct3.go index de9980ca3..fdd0ea965 100644 --- a/.example/util/gvalid/gvalid_struct3.go +++ b/.example/util/gvalid/gvalid_struct3.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -15,5 +16,5 @@ func main() { Pass: "1", } - g.Dump(gvalid.CheckStruct(user, nil).Maps()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil).Maps()) } diff --git a/i18n/gi18n/gi18n_ctx.go b/i18n/gi18n/gi18n_ctx.go index 4a9bc06ce..a2c7293a7 100644 --- a/i18n/gi18n/gi18n_ctx.go +++ b/i18n/gi18n/gi18n_ctx.go @@ -15,12 +15,18 @@ const ( // WithLanguage append language setting to the context and returns a new context. func WithLanguage(ctx context.Context, language string) context.Context { + if ctx == nil { + ctx = context.TODO() + } return context.WithValue(ctx, ctxLanguage, language) } // LanguageFromCtx retrieves and returns language name from context. // It returns an empty string if it is not set previously. func LanguageFromCtx(ctx context.Context) string { + if ctx == nil { + return "" + } v := ctx.Value(ctxLanguage) if v != nil { return v.(string) diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 991967df1..c9bfdcbec 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -102,7 +102,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { } } // Validation. - if err := gvalid.CheckStructWithParamMap(pointer, data, nil); err != nil { + if err := gvalid.CheckStructWithParamMap(r.Context(), pointer, data, nil); err != nil { return err } @@ -120,6 +120,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { } for i := 0; i < reflectVal2.Len(); i++ { if err := gvalid.CheckStructWithParamMap( + r.Context(), reflectVal2.Index(i), j.GetMap(gconv.String(i)), nil, diff --git a/net/ghttp/ghttp_unit_request_struct_test.go b/net/ghttp/ghttp_unit_request_struct_test.go index 687c936c4..5f4601744 100644 --- a/net/ghttp/ghttp_unit_request_struct_test.go +++ b/net/ghttp/ghttp_unit_request_struct_test.go @@ -425,7 +425,7 @@ func Test_Params_Struct(t *testing.T) { if err := r.GetStruct(user); err != nil { r.Response.WriteExit(err) } - if err := gvalid.CheckStruct(user, nil); err != nil { + if err := gvalid.CheckStruct(r.Context(), user, nil); err != nil { r.Response.WriteExit(err) } } diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 0b9e18d7e..4a660a954 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -8,6 +8,7 @@ package gvalid import ( + "context" "regexp" "strings" @@ -170,8 +171,8 @@ var ( // string/map/struct/*struct. // The optional parameter `params` 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...) +func Check(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { + return defaultValidator.Ctx(ctx).Check(value, rules, messages, params...) } // CheckMap validates map and returns the error result. It returns nil if with successful validation. @@ -179,8 +180,8 @@ func Check(value interface{}, rules string, messages interface{}, params ...inte // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` 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...) +func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.Ctx(ctx).CheckMap(params, rules, messages...) } // CheckStruct validates strcut and returns the error result. @@ -189,8 +190,8 @@ func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Err // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` 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...) +func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.Ctx(ctx).CheckStruct(object, rules, messages...) } // CheckStructWithParamMap validates struct with given parameter map and returns the error result. @@ -199,8 +200,8 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) * // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func CheckStructWithParamMap(object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { - return defaultValidator.CheckStructWithParamMap(object, paramMap, rules, messages...) +func CheckStructWithParamMap(ctx context.Context, object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { + return defaultValidator.Ctx(ctx).CheckStructWithParamMap(object, paramMap, rules, messages...) } // parseSequenceTag parses one sequence tag to field, rule and error message. diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index 5f516a2c6..de6ace5f1 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -21,8 +21,8 @@ type Validator struct { // New creates and returns a new Validator. func New() *Validator { return &Validator{ - ctx: context.TODO(), - i18nManager: gi18n.Instance(), + ctx: context.TODO(), // Initialize an empty context. + i18nManager: gi18n.Instance(), // Use default i18n manager. } } diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index 2fa6cedfa..fac360194 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "errors" "fmt" "github.com/gogf/gf/container/gvar" @@ -28,7 +29,7 @@ func ExampleCheckMap() { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) @@ -50,7 +51,7 @@ func ExampleCheckMap2() { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) @@ -72,7 +73,7 @@ func ExampleCheckStruct() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err == nil) // Output: // true @@ -89,7 +90,7 @@ func ExampleCheckStruct2() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err == nil) // Output: // true @@ -106,7 +107,7 @@ func ExampleCheckStruct3() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err) // Output: // project id must between 1, 10000 @@ -138,7 +139,7 @@ func ExampleRegisterRule() { Name: "john", Pass: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) fmt.Println(err.Error()) // May Output: // 用户名称已被占用 @@ -172,14 +173,14 @@ func ExampleRegisterRule_OverwriteRequired() { } return nil }) - fmt.Println(gvalid.Check("", "required", "It's required")) - fmt.Println(gvalid.Check(0, "required", "It's required")) - fmt.Println(gvalid.Check(false, "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), false, "required", "It's required")) gvalid.DeleteRule(rule) fmt.Println("rule deleted") - fmt.Println(gvalid.Check("", "required", "It's required")) - fmt.Println(gvalid.Check(0, "required", "It's required")) - fmt.Println(gvalid.Check(false, "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.Check(context.TODO(), false, "required", "It's required")) // Output: // It's required // It's required diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index bfde55995..55e7687ab 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "testing" @@ -23,9 +24,9 @@ func Test_Check(t *testing.T) { val1 := 0 val2 := 7 val3 := 20 - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) t.Assert(err1, "invalid_rules: abc:6,16") t.Assert(err2, "invalid_rules: abc:6,16") t.Assert(err3, "invalid_rules: abc:6,16") @@ -33,16 +34,16 @@ func Test_Check(t *testing.T) { } func Test_Required(t *testing.T) { - if m := gvalid.Check("1", "required", nil); m != nil { + if m := gvalid.Check(context.TODO(), "1", "required", nil); m != nil { t.Error(m) } - if m := gvalid.Check("", "required", nil); m == nil { + if m := gvalid.Check(context.TODO(), "", "required", nil); m == nil { t.Error(m) } - if m := gvalid.Check("", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { + if m := gvalid.Check(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { t.Error("Required校验失败") } - if m := gvalid.Check("", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { + if m := gvalid.Check(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { t.Error("Required校验失败") } } @@ -50,20 +51,20 @@ func Test_Required(t *testing.T) { func Test_RequiredIf(t *testing.T) { gtest.C(t, func(t *gtest.T) { rule := "required-if:id,1,age,18" - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"id": 1}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"id": 0}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"age": 18}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"age": 20}), nil) + t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) }) } func Test_RequiredUnless(t *testing.T) { gtest.C(t, func(t *gtest.T) { rule := "required-unless:id,1,age,18" - t.Assert(gvalid.Check("", rule, nil, g.Map{"id": 1}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"id": 0}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"age": 18}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"age": 20}), nil) + t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) }) } @@ -81,9 +82,9 @@ func Test_RequiredWith(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -101,9 +102,9 @@ func Test_RequiredWith(t *testing.T) { params3 := g.Map{ "time": time.Time{}, } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -120,9 +121,9 @@ func Test_RequiredWith(t *testing.T) { params3 := g.Map{ "time": time.Now(), } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -139,7 +140,7 @@ func Test_RequiredWith(t *testing.T) { StartTime: nil, EndTime: nil, } - t.Assert(gvalid.CheckStruct(data, nil), nil) + t.Assert(gvalid.CheckStruct(context.TODO(), data, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -152,7 +153,7 @@ func Test_RequiredWith(t *testing.T) { StartTime: nil, EndTime: gtime.Now(), } - t.AssertNE(gvalid.CheckStruct(data, nil), nil) + t.AssertNE(gvalid.CheckStruct(context.TODO(), data, nil), nil) }) } @@ -170,9 +171,9 @@ func Test_RequiredWithAll(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -193,9 +194,9 @@ func Test_RequiredWithOut(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -216,9 +217,9 @@ func Test_RequiredWithOutAll(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -235,13 +236,13 @@ func Test_Date(t *testing.T) { val5 := "2010.11.01" val6 := "2010/11/01" val7 := "2010=11=01" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err7 := gvalid.Check(context.TODO(), val7, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -260,12 +261,12 @@ func Test_DateFormat(t *testing.T) { val4 := "201011-01" val5 := "2010~11~01" val6 := "2010-11~01" - err1 := gvalid.Check(val1, "date-format:Y", nil) - err2 := gvalid.Check(val2, "date-format:Ym", nil) - err3 := gvalid.Check(val3, "date-format:Y.m", nil) - err4 := gvalid.Check(val4, "date-format:Ym-d", nil) - err5 := gvalid.Check(val5, "date-format:Y~m~d", nil) - err6 := gvalid.Check(val6, "date-format:Y~m~d", nil) + err1 := gvalid.Check(context.TODO(), val1, "date-format:Y", nil) + err2 := gvalid.Check(context.TODO(), val2, "date-format:Ym", nil) + err3 := gvalid.Check(context.TODO(), val3, "date-format:Y.m", nil) + err4 := gvalid.Check(context.TODO(), val4, "date-format:Ym-d", nil) + err5 := gvalid.Check(context.TODO(), val5, "date-format:Y~m~d", nil) + err6 := gvalid.Check(context.TODO(), val6, "date-format:Y~m~d", nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -276,8 +277,8 @@ func Test_DateFormat(t *testing.T) { gtest.C(t, func(t *gtest.T) { t1 := gtime.Now() t2 := time.Time{} - err1 := gvalid.Check(t1, "date-format:Y", nil) - err2 := gvalid.Check(t2, "date-format:Y", nil) + err1 := gvalid.Check(context.TODO(), t1, "date-format:Y", nil) + err2 := gvalid.Check(context.TODO(), t2, "date-format:Y", nil) t.Assert(err1, nil) t.AssertNE(err2, nil) }) @@ -290,10 +291,10 @@ func Test_Email(t *testing.T) { value2 := "m@www@johngcn" value3 := "m-m_m@mail.johng.cn" value4 := "m.m-m@johng.cn" - err1 := gvalid.Check(value1, rule, nil) - err2 := gvalid.Check(value2, rule, nil) - err3 := gvalid.Check(value3, rule, nil) - err4 := gvalid.Check(value4, rule, nil) + err1 := gvalid.Check(context.TODO(), value1, rule, nil) + err2 := gvalid.Check(context.TODO(), value2, rule, nil) + err3 := gvalid.Check(context.TODO(), value3, rule, nil) + err4 := gvalid.Check(context.TODO(), value4, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -303,10 +304,10 @@ func Test_Email(t *testing.T) { func Test_Phone(t *testing.T) { gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check("1361990897", "phone", nil) - err2 := gvalid.Check("13619908979", "phone", nil) - err3 := gvalid.Check("16719908979", "phone", nil) - err4 := gvalid.Check("19719908989", "phone", nil) + err1 := gvalid.Check(context.TODO(), "1361990897", "phone", nil) + err2 := gvalid.Check(context.TODO(), "13619908979", "phone", nil) + err3 := gvalid.Check(context.TODO(), "16719908979", "phone", nil) + err4 := gvalid.Check(context.TODO(), "19719908989", "phone", nil) t.AssertNE(err1.String(), nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -316,12 +317,12 @@ func Test_Phone(t *testing.T) { func Test_PhoneLoose(t *testing.T) { gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check("13333333333", "phone-loose", nil) - err2 := gvalid.Check("15555555555", "phone-loose", nil) - err3 := gvalid.Check("16666666666", "phone-loose", nil) - err4 := gvalid.Check("23333333333", "phone-loose", nil) - err5 := gvalid.Check("1333333333", "phone-loose", nil) - err6 := gvalid.Check("10333333333", "phone-loose", nil) + err1 := gvalid.Check(context.TODO(), "13333333333", "phone-loose", nil) + err2 := gvalid.Check(context.TODO(), "15555555555", "phone-loose", nil) + err3 := gvalid.Check(context.TODO(), "16666666666", "phone-loose", nil) + err4 := gvalid.Check(context.TODO(), "23333333333", "phone-loose", nil) + err5 := gvalid.Check(context.TODO(), "1333333333", "phone-loose", nil) + err6 := gvalid.Check(context.TODO(), "10333333333", "phone-loose", nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -338,11 +339,11 @@ func Test_Telephone(t *testing.T) { val3 := "86292651" val4 := "028-8692651" val5 := "0830-8692651" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -359,11 +360,11 @@ func Test_Passport(t *testing.T) { val3 := "aaaaa" val4 := "aaaaaa" val5 := "a123_456" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -380,11 +381,11 @@ func Test_Password(t *testing.T) { val3 := "a12345-6" val4 := ">,/;'[09-" val5 := "a123_456" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -403,13 +404,13 @@ func Test_Password2(t *testing.T) { val5 := "a123_456" val6 := "Nant1986" val7 := "Nant1986!" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err7 := gvalid.Check(context.TODO(), val7, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -430,13 +431,13 @@ func Test_Password3(t *testing.T) { val5 := "a123_456" val6 := "Nant1986" val7 := "Nant1986!" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err7 := gvalid.Check(context.TODO(), val7, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -452,8 +453,8 @@ func Test_Postcode(t *testing.T) { rule := "postcode" val1 := "12345" val2 := "610036" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) }) @@ -467,11 +468,11 @@ func Test_ResidentId(t *testing.T) { val3 := "311128500121201" val4 := "510521198607185367" val5 := "51052119860718536x" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -485,8 +486,8 @@ func Test_BankCard(t *testing.T) { rule := "bank-card" val1 := "6230514630000424470" val2 := "6230514630000424473" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) }) @@ -500,11 +501,11 @@ func Test_QQ(t *testing.T) { val3 := "10000" val4 := "38996181" val5 := "389961817" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -514,31 +515,31 @@ func Test_QQ(t *testing.T) { } func Test_Ip(t *testing.T) { - if m := gvalid.Check("10.0.0.1", "ip", nil); m != nil { + if m := gvalid.Check(context.TODO(), "10.0.0.1", "ip", nil); m != nil { t.Error(m) } - if m := gvalid.Check("10.0.0.1", "ipv4", nil); m != nil { + if m := gvalid.Check(context.TODO(), "10.0.0.1", "ipv4", nil); m != nil { t.Error(m) } - if m := gvalid.Check("0.0.0.0", "ipv4", nil); m != nil { + if m := gvalid.Check(context.TODO(), "0.0.0.0", "ipv4", nil); m != nil { t.Error(m) } - if m := gvalid.Check("1920.0.0.0", "ipv4", nil); m == nil { + if m := gvalid.Check(context.TODO(), "1920.0.0.0", "ipv4", nil); m == nil { t.Error("ipv4校验失败") } - if m := gvalid.Check("1920.0.0.0", "ip", nil); m == nil { + if m := gvalid.Check(context.TODO(), "1920.0.0.0", "ip", nil); m == nil { t.Error("ipv4校验失败") } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { + if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { t.Error(m) } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { + if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { t.Error(m) } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { + if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { t.Error(m) } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { + if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { t.Error(m) } } @@ -551,11 +552,11 @@ func Test_IPv4(t *testing.T) { val3 := "1.1.1.1" val4 := "255.255.255.0" val5 := "127.0.0.1" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -572,11 +573,11 @@ func Test_IPv6(t *testing.T) { val3 := "1030::C9B4:FF12:48AA:1A2B" val4 := "2000:0:0:0:0:0:0:1" val5 := "0000:0000:0000:0000:0000:ffff:c0a8:5909" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -591,9 +592,9 @@ func Test_MAC(t *testing.T) { val1 := "192.168.1.1" val2 := "44-45-53-54-00-00" val3 := "01:00:5e:00:00:00" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -607,10 +608,10 @@ func Test_URL(t *testing.T) { val2 := "https://www.baidu.com" val3 := "http://127.0.0.1" val4 := "file:///tmp/test.txt" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -637,7 +638,7 @@ func Test_Domain(t *testing.T) { } var err error for k, v := range m { - err = gvalid.Check(k, "domain", nil) + err = gvalid.Check(context.TODO(), k, "domain", nil) if v { //fmt.Println(k) t.Assert(err, nil) @@ -651,10 +652,10 @@ func Test_Domain(t *testing.T) { func Test_Length(t *testing.T) { rule := "length:6,16" - if m := gvalid.Check("123456", rule, nil); m != nil { + if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check("12345", rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } } @@ -664,18 +665,18 @@ func Test_MinLength(t *testing.T) { msgs := map[string]string{ "min-length": "地址长度至少为:min位", } - if m := gvalid.Check("123456", rule, nil); m != nil { + if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check("12345", rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } - if m := gvalid.Check("12345", rule, msgs); m == nil { + if m := gvalid.Check(context.TODO(), "12345", rule, msgs); m == nil { t.Error("长度校验失败") } rule2 := "min-length:abc" - if m := gvalid.Check("123456", rule2, nil); m == nil { + if m := gvalid.Check(context.TODO(), "123456", rule2, nil); m == nil { t.Error("长度校验失败") } } @@ -685,31 +686,31 @@ func Test_MaxLength(t *testing.T) { msgs := map[string]string{ "max-length": "地址长度至大为:max位", } - if m := gvalid.Check("12345", rule, nil); m != nil { + if m := gvalid.Check(context.TODO(), "12345", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check("1234567", rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), "1234567", rule, nil); m == nil { t.Error("长度校验失败") } - if m := gvalid.Check("1234567", rule, msgs); m == nil { + if m := gvalid.Check(context.TODO(), "1234567", rule, msgs); m == nil { t.Error("长度校验失败") } rule2 := "max-length:abc" - if m := gvalid.Check("123456", rule2, nil); m == nil { + if m := gvalid.Check(context.TODO(), "123456", rule2, nil); m == nil { t.Error("长度校验失败") } } func Test_Between(t *testing.T) { rule := "between:6.01, 10.01" - if m := gvalid.Check(10, rule, nil); m != nil { + if m := gvalid.Check(context.TODO(), 10, rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(10.02, rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), 10.02, rule, nil); m == nil { t.Error("大小范围校验失败") } - if m := gvalid.Check("a", rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), "a", rule, nil); m == nil { t.Error("大小范围校验失败") } } @@ -722,11 +723,11 @@ func Test_Min(t *testing.T) { val3 := "100" val4 := "1000" val5 := "a" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -734,7 +735,7 @@ func Test_Min(t *testing.T) { t.AssertNE(err5, nil) rule2 := "min:a" - err6 := gvalid.Check(val1, rule2, nil) + err6 := gvalid.Check(context.TODO(), val1, rule2, nil) t.AssertNE(err6, nil) }) } @@ -747,11 +748,11 @@ func Test_Max(t *testing.T) { val3 := "100" val4 := "1000" val5 := "a" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -759,7 +760,7 @@ func Test_Max(t *testing.T) { t.AssertNE(err5, nil) rule2 := "max:a" - err6 := gvalid.Check(val1, rule2, nil) + err6 := gvalid.Check(context.TODO(), val1, rule2, nil) t.AssertNE(err6, nil) }) } @@ -773,12 +774,12 @@ func Test_Json(t *testing.T) { val4 := "[]" val5 := "[1,2,3,4]" val6 := `{"list":[1,2,3,4]}` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -797,12 +798,12 @@ func Test_Integer(t *testing.T) { val4 := "1" val5 := "100" val6 := `999999999` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -821,12 +822,12 @@ func Test_Float(t *testing.T) { val4 := "1.0" val5 := "1.1" val6 := `0.1` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -845,12 +846,12 @@ func Test_Boolean(t *testing.T) { val4 := "1" val5 := "true" val6 := `off` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err6 := gvalid.Check(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -874,9 +875,9 @@ func Test_Same(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -897,9 +898,9 @@ func Test_Different(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) + err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -913,10 +914,10 @@ func Test_In(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -931,10 +932,10 @@ func Test_NotIn(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -946,10 +947,10 @@ func Test_NotIn(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) + err1 := gvalid.Check(context.TODO(), val1, rule, nil) + err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err4 := gvalid.Check(context.TODO(), val4, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -959,10 +960,10 @@ func Test_NotIn(t *testing.T) { func Test_Regex1(t *testing.T) { rule := `regex:\d{6}|\D{6}|length:6,16` - if m := gvalid.Check("123456", rule, nil); m != nil { + if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check("abcde6", rule, nil); m == nil { + if m := gvalid.Check(context.TODO(), "abcde6", rule, nil); m == nil { t.Error("校验失败") } } @@ -973,9 +974,9 @@ func Test_Regex2(t *testing.T) { str1 := "" str2 := "data" str3 := "data:image/jpeg;base64,/9jrbattq22r" - err1 := gvalid.Check(str1, rule, nil) - err2 := gvalid.Check(str2, rule, nil) - err3 := gvalid.Check(str3, rule, nil) + err1 := gvalid.Check(context.TODO(), str1, rule, nil) + err2 := gvalid.Check(context.TODO(), str2, rule, nil) + err3 := gvalid.Check(context.TODO(), str3, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -992,7 +993,7 @@ func Test_InternalError_String(t *testing.T) { Name string `v:"hh"` } aa := a{Name: "2"} - err := gvalid.CheckStruct(&aa, nil) + err := gvalid.CheckStruct(context.TODO(), &aa, nil) t.Assert(err.String(), "invalid_rules: hh") t.Assert(err.Strings(), g.Slice{"invalid_rules: hh"}) diff --git a/util/gvalid/gvalid_z_unit_checkmap_test.go b/util/gvalid/gvalid_z_unit_checkmap_test.go index 4660ebf21..c74ab2925 100755 --- a/util/gvalid/gvalid_z_unit_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_checkmap_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "github.com/gogf/gf/errors/gerror" "testing" @@ -24,7 +25,7 @@ func Test_CheckMap1(t *testing.T) { "id": "required|between:1,100", "name": "required|length:6,16", } - if m := gvalid.CheckMap(data, rules); m == nil { + if m := gvalid.CheckMap(context.TODO(), data, rules); m == nil { t.Error("CheckMap校验失败") } else { t.Assert(len(m.Maps()), 2) @@ -37,7 +38,7 @@ func Test_CheckMap1(t *testing.T) { func Test_CheckMap2(t *testing.T) { var params interface{} gtest.C(t, func(t *gtest.T) { - if err := gvalid.CheckMap(params, nil, nil); err == nil { + if err := gvalid.CheckMap(context.TODO(), params, nil, nil); err == nil { t.Assert(err, nil) } }) @@ -57,7 +58,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m == nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m == nil { t.Error("CheckMap校验失败") } @@ -76,7 +77,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m != nil { t.Error(m) } @@ -95,7 +96,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m != nil { t.Error(m) } @@ -114,7 +115,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } @@ -133,7 +134,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } @@ -152,7 +153,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } } @@ -166,7 +167,7 @@ func Test_CheckMapWithNilAndNotRequiredField(t *testing.T) { "id": "required", "name": "length:4,16", } - if m := gvalid.CheckMap(data, rules); m != nil { + if m := gvalid.CheckMap(context.TODO(), data, rules); m != nil { t.Error(m) } } @@ -183,7 +184,7 @@ func Test_Sequence(t *testing.T) { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - err := gvalid.CheckMap(params, rules) + err := gvalid.CheckMap(context.TODO(), params, rules) t.AssertNE(err, nil) t.Assert(len(err.Map()), 2) t.Assert(err.Map()["required"], "账号不能为空") diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index 786145ffb..da76790fb 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" "testing" @@ -35,7 +36,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.Assert(err, nil) }) @@ -56,7 +57,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -81,7 +82,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -106,7 +107,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -120,7 +121,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"password@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["username"]["required"], "用户名不能为空") @@ -133,7 +134,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.Assert(err, nil) }) @@ -143,7 +144,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"password@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.AssertNE(err, nil) t.Assert(err.Maps()["password"]["required"], "登录密码不能为空") }) @@ -161,7 +162,7 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") @@ -184,7 +185,7 @@ func Test_CheckStruct(t *testing.T) { "username@required#用户名不能为空", } - err := gvalid.CheckStruct(user, rules) + err := gvalid.CheckStruct(context.TODO(), user, rules) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") @@ -202,7 +203,7 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) }) @@ -220,7 +221,7 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") @@ -245,7 +246,7 @@ func Test_CheckStruct_With_EmbeddedObject(t *testing.T) { Pass2: "2", }, } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"}) t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"}) @@ -271,7 +272,7 @@ func Test_CheckStruct_With_StructAttribute(t *testing.T) { Pass2: "2", }, } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"}) t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"}) @@ -290,7 +291,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) gtest.C(t, func(t *gtest.T) { @@ -303,7 +304,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) gtest.C(t, func(t *gtest.T) { @@ -316,7 +317,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err.String(), "project id must between 1, 10000") }) } @@ -332,7 +333,7 @@ func Test_CheckStruct_NoTag(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) } @@ -349,7 +350,7 @@ func Test_CheckStruct_InvalidRule(t *testing.T) { Age: 18, Phone: "123", } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.AssertNE(err, nil) }) } @@ -364,7 +365,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { Uid: 1, Nickname: "john", } - t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) + t.Assert(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -372,7 +373,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { Nickname string `v:"required-with:uid"` } data := UserApiSearch{} - t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -382,7 +383,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { data := UserApiSearch{ Uid: 1, } - t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { @@ -396,7 +397,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { StartTime: nil, EndTime: nil, } - t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil) + t.Assert(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -409,6 +410,6 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { StartTime: gtime.Now(), EndTime: nil, } - t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{"start_time": gtime.Now()}, nil), nil) + t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{"start_time": gtime.Now()}, nil), nil) }) } diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go index 2fdc36a92..326c42ebd 100644 --- a/util/gvalid/gvalid_z_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "errors" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" @@ -30,9 +31,9 @@ func Test_CustomRule1(t *testing.T) { }) gtest.Assert(err, nil) gtest.C(t, func(t *gtest.T) { - err := gvalid.Check("123456", rule, "custom message") + err := gvalid.Check(context.TODO(), "123456", rule, "custom message") t.Assert(err.String(), "custom message") - err = gvalid.Check("123456", rule, "custom message", g.Map{"data": "123456"}) + err = gvalid.Check(context.TODO(), "123456", rule, "custom message", g.Map{"data": "123456"}) t.Assert(err, nil) }) // Error with struct validation. @@ -45,7 +46,7 @@ func Test_CustomRule1(t *testing.T) { Value: "123", Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err.String(), "自定义错误") }) // No error with struct validation. @@ -58,7 +59,7 @@ func Test_CustomRule1(t *testing.T) { Value: "123456", Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err, nil) }) } @@ -76,8 +77,8 @@ func Test_CustomRule2(t *testing.T) { // Check. gtest.C(t, func(t *gtest.T) { errStr := "data map should not be empty" - t.Assert(gvalid.Check(g.Map{}, rule, errStr).String(), errStr) - t.Assert(gvalid.Check(g.Map{"k": "v"}, rule, errStr).String(), nil) + t.Assert(gvalid.Check(context.TODO(), g.Map{}, rule, errStr).String(), errStr) + t.Assert(gvalid.Check(context.TODO(), g.Map{"k": "v"}, rule, errStr).String(), nil) }) // Error with struct validation. gtest.C(t, func(t *gtest.T) { @@ -89,7 +90,7 @@ func Test_CustomRule2(t *testing.T) { Value: map[string]string{}, Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err.String(), "自定义错误") }) // No error with struct validation. @@ -102,7 +103,7 @@ func Test_CustomRule2(t *testing.T) { Value: map[string]string{"k": "v"}, Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err, nil) }) } @@ -120,9 +121,9 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { // Check. gtest.C(t, func(t *gtest.T) { errStr := "error" - t.Assert(gvalid.Check("", rule, errStr).String(), "") - t.Assert(gvalid.Check("gf", rule, errStr).String(), "") - t.Assert(gvalid.Check("gf2", rule, errStr).String(), errStr) + t.Assert(gvalid.Check(context.TODO(), "", rule, errStr).String(), "") + t.Assert(gvalid.Check(context.TODO(), "gf", rule, errStr).String(), "") + t.Assert(gvalid.Check(context.TODO(), "gf2", rule, errStr).String(), errStr) }) // Error with struct validation. gtest.C(t, func(t *gtest.T) { @@ -134,7 +135,7 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { Value: "", Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err.String(), "") }) // No error with struct validation. @@ -147,7 +148,7 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { Value: "john", Data: "123456", } - err := gvalid.CheckStruct(st, nil) + err := gvalid.CheckStruct(context.TODO(), st, nil) t.Assert(err.String(), "自定义错误") }) } diff --git a/util/gvalid/gvalid_z_unit_customerror_test.go b/util/gvalid/gvalid_z_unit_customerror_test.go index 9f73afa4b..ecccf7e4d 100755 --- a/util/gvalid/gvalid_z_unit_customerror_test.go +++ b/util/gvalid/gvalid_z_unit_customerror_test.go @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "strings" "testing" @@ -19,7 +20,7 @@ func Test_Map(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(val, rule, nil) + err = gvalid.Check(context.TODO(), val, rule, nil) msg = map[string]string{ "ipv4": "The value must be a valid IPv4 address", } @@ -33,7 +34,7 @@ func Test_FirstString(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(val, rule, nil) + err = gvalid.Check(context.TODO(), val, rule, nil) n = err.FirstString() ) t.Assert(n, "The value must be a valid IPv4 address") @@ -46,7 +47,7 @@ func Test_CustomError1(t *testing.T) { "integer": "请输入一个整数", "length": "参数长度不对啊老铁", } - e := gvalid.Check("6.66", rule, msgs) + e := gvalid.Check(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { @@ -66,7 +67,7 @@ func Test_CustomError1(t *testing.T) { func Test_CustomError2(t *testing.T) { rule := "integer|length:6,16" msgs := "请输入一个整数|参数长度不对啊老铁" - e := gvalid.Check("6.66", rule, msgs) + e := gvalid.Check(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { From d76e4c8aed7be69bd4e08fd2afd8e1778edc1fdf Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 18:13:51 +0800 Subject: [PATCH 22/70] refract package gtimer for more stable --- container/gqueue/gqueue.go | 2 +- ...e_bench_test.go => gqueue_z_bench_test.go} | 0 ...eue_unit_test.go => gqueue_z_unit_test.go} | 0 net/ghttp/ghttp_server.go | 4 +- os/gcron/gcron.go | 12 +- os/gcron/gcron_cron.go | 28 +-- os/gcron/gcron_entry.go | 56 ++--- os/gcron/gcron_unit_2_test.go | 2 +- os/gtimer/gtimer.go | 73 +++--- os/gtimer/gtimer_entry.go | 211 ----------------- os/gtimer/gtimer_job.go | 134 +++++++++++ os/gtimer/gtimer_loop.go | 93 -------- os/gtimer/gtimer_queue.go | 91 ++++++++ os/gtimer/gtimer_queue_heap.go | 41 ++++ os/gtimer/gtimer_timer.go | 220 ++++-------------- os/gtimer/gtimer_timer_loop.go | 68 ++++++ os/gtimer/gtimer_z_bench_test.go | 2 +- os/gtimer/gtimer_z_unit_api_test.go | 8 +- os/gtimer/gtimer_z_unit_entry_test.go | 37 +-- .../gtimer_z_unit_timer_internal_test.go | 50 ++-- os/gtimer/gtimer_z_unit_timer_test.go | 41 ++-- 21 files changed, 549 insertions(+), 624 deletions(-) rename container/gqueue/{gqueue_bench_test.go => gqueue_z_bench_test.go} (100%) rename container/gqueue/{gqueue_unit_test.go => gqueue_z_unit_test.go} (100%) delete mode 100644 os/gtimer/gtimer_entry.go create mode 100644 os/gtimer/gtimer_job.go delete mode 100644 os/gtimer/gtimer_loop.go create mode 100644 os/gtimer/gtimer_queue.go create mode 100644 os/gtimer/gtimer_queue_heap.go create mode 100644 os/gtimer/gtimer_timer_loop.go diff --git a/container/gqueue/gqueue.go b/container/gqueue/gqueue.go index dc2f9d39d..b8a2dfbaa 100644 --- a/container/gqueue/gqueue.go +++ b/container/gqueue/gqueue.go @@ -129,7 +129,7 @@ func (q *Queue) Close() { // Len returns the length of the queue. // Note that the result might not be accurate as there's a -// asynchronize channel reading the list constantly. +// asynchronous channel reading the list constantly. func (q *Queue) Len() (length int) { if q.list != nil { length += q.list.Len() diff --git a/container/gqueue/gqueue_bench_test.go b/container/gqueue/gqueue_z_bench_test.go similarity index 100% rename from container/gqueue/gqueue_bench_test.go rename to container/gqueue/gqueue_z_bench_test.go diff --git a/container/gqueue/gqueue_unit_test.go b/container/gqueue/gqueue_z_unit_test.go similarity index 100% rename from container/gqueue/gqueue_unit_test.go rename to container/gqueue/gqueue_z_unit_test.go diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 7b347ce6d..5896ace8c 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -78,7 +78,7 @@ func serverProcessInit() { // It's an ugly calling for better initializing the main package path // in source development environment. It is useful only be used in main goroutine. - // It fails retrieving the main package path in asynchronized goroutines. + // It fails retrieving the main package path in asynchronous goroutines. gfile.MainPkgPath() } @@ -416,7 +416,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.servers = append(s.servers, s.newGracefulServer(itemFunc)) } } - // Start listening asynchronizedly. + // Start listening asynchronously. serverRunning.Add(1) for _, v := range s.servers { go func(server *gracefulServer) { diff --git a/os/gcron/gcron.go b/os/gcron/gcron.go index 7f45c4b2c..b3a17f426 100644 --- a/os/gcron/gcron.go +++ b/os/gcron/gcron.go @@ -50,7 +50,7 @@ func GetLogLevel() int { // Add adds a timed task to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func Add(pattern string, job func(), name ...string) (*Entry, error) { +func Add(pattern string, job func(), name ...string) (*Job, error) { return defaultCron.Add(pattern, job, name...) } @@ -58,21 +58,21 @@ func Add(pattern string, job func(), name ...string) (*Entry, error) { // A singleton timed task is that can only be running one single instance at the same time. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { +func AddSingleton(pattern string, job func(), name ...string) (*Job, error) { return defaultCron.AddSingleton(pattern, job, name...) } // AddOnce adds a timed task which can be run only once, to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddOnce(pattern string, job func(), name ...string) (*Entry, error) { +func AddOnce(pattern string, job func(), name ...string) (*Job, error) { return defaultCron.AddOnce(pattern, job, name...) } // AddTimes adds a timed task which can be run specified times, to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { +func AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) { return defaultCron.AddTimes(pattern, times, job, name...) } @@ -100,7 +100,7 @@ func DelayAddTimes(delay time.Duration, pattern string, times int, job func(), n // Search returns a scheduled task with the specified . // It returns nil if no found. -func Search(name string) *Entry { +func Search(name string) *Job { return defaultCron.Search(name) } @@ -115,7 +115,7 @@ func Size() int { } // Entries return all timed tasks as slice. -func Entries() []*Entry { +func Entries() []*Job { return defaultCron.Entries() } diff --git a/os/gcron/gcron_cron.go b/os/gcron/gcron_cron.go index 28e2b8522..de9493fcc 100644 --- a/os/gcron/gcron_cron.go +++ b/os/gcron/gcron_cron.go @@ -60,20 +60,20 @@ func (c *Cron) GetLogLevel() int { // Add adds a timed task. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) Add(pattern string, job func(), name ...string) (*Entry, error) { +func (c *Cron) Add(pattern string, job func(), name ...string) (*Job, error) { if len(name) > 0 { if c.Search(name[0]) != nil { return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0])) } } - return c.addEntry(pattern, job, false, name...) + return c.addJob(pattern, job, false, name...) } // AddSingleton adds a singleton timed task. // A singleton timed task is that can only be running one single instance at the same time. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { +func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Job, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -85,7 +85,7 @@ func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry, // AddOnce adds a timed task which can be run only once. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, error) { +func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Job, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -97,7 +97,7 @@ func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, erro // AddTimes adds a timed task which can be run specified times. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { +func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -146,9 +146,9 @@ func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job // Search returns a scheduled task with the specified . // It returns nil if no found. -func (c *Cron) Search(name string) *Entry { +func (c *Cron) Search(name string) *Job { if v := c.entries.Get(name); v != nil { - return v.(*Entry) + return v.(*Job) } return nil } @@ -182,7 +182,7 @@ func (c *Cron) Stop(name ...string) { // Remove deletes scheduled task which named . func (c *Cron) Remove(name string) { if v := c.entries.Get(name); v != nil { - v.(*Entry).Close() + v.(*Job).Close() } } @@ -197,10 +197,10 @@ func (c *Cron) Size() int { } // Entries return all timed tasks as slice(order by registered time asc). -func (c *Cron) Entries() []*Entry { +func (c *Cron) Entries() []*Job { array := garray.NewSortedArraySize(c.entries.Size(), func(v1, v2 interface{}) int { - entry1 := v1.(*Entry) - entry2 := v2.(*Entry) + entry1 := v1.(*Job) + entry2 := v2.(*Job) if entry1.Time.Nanosecond() > entry2.Time.Nanosecond() { return 1 } @@ -208,13 +208,13 @@ func (c *Cron) Entries() []*Entry { }, true) c.entries.RLockFunc(func(m map[string]interface{}) { for _, v := range m { - array.Add(v.(*Entry)) + array.Add(v.(*Job)) } }) - entries := make([]*Entry, array.Len()) + entries := make([]*Job, array.Len()) array.RLockFunc(func(array []interface{}) { for k, v := range array { - entries[k] = v.(*Entry) + entries[k] = v.(*Job) } }) return entries diff --git a/os/gcron/gcron_entry.go b/os/gcron/gcron_entry.go index 94ff02f43..e0c33e474 100644 --- a/os/gcron/gcron_entry.go +++ b/os/gcron/gcron_entry.go @@ -18,28 +18,28 @@ import ( ) // Timed task entry. -type Entry struct { +type Job struct { cron *Cron // Cron object belonged to. - entry *gtimer.Entry // Associated gtimer.Entry. + job *gtimer.Job // Associated gtimer.Job. schedule *cronSchedule // Timed schedule object. jobName string // Callback function name(address info). times *gtype.Int // Running times limit. - Name string // Entry name. + Name string // Job name. Job func() `json:"-"` // Callback function. Time time.Time // Registered time. } -// addEntry creates and returns a new Entry object. +// addJob creates and returns a new Job object. // Param is the callback function for timed task execution. // Param specifies whether timed task executing in singleton mode. // Param names this entry for manual control. -func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...string) (*Entry, error) { +func (c *Cron) addJob(pattern string, job func(), singleton bool, name ...string) (*Job, error) { schedule, err := newSchedule(pattern) if err != nil { return nil, err } // No limit for , for gtimer checking scheduling every second. - entry := &Entry{ + entry := &Job{ cron: c, schedule: schedule, jobName: runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(), @@ -57,57 +57,57 @@ func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...stri // It should start running after the entry is added to the entries map, // to avoid the task from running during adding where the entries // does not have the entry information, which might cause panic. - entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, -1, gtimer.StatusStopped) + entry.job = gtimer.AddJob(time.Second, entry.check, singleton, -1, gtimer.StatusStopped) c.entries.Set(entry.Name, entry) - entry.entry.Start() + entry.job.Start() return entry, nil } // IsSingleton return whether this entry is a singleton timed task. -func (entry *Entry) IsSingleton() bool { - return entry.entry.IsSingleton() +func (entry *Job) IsSingleton() bool { + return entry.job.IsSingleton() } // SetSingleton sets the entry running in singleton mode. -func (entry *Entry) SetSingleton(enabled bool) { - entry.entry.SetSingleton(true) +func (entry *Job) SetSingleton(enabled bool) { + entry.job.SetSingleton(true) } // SetTimes sets the times which the entry can run. -func (entry *Entry) SetTimes(times int) { +func (entry *Job) SetTimes(times int) { entry.times.Set(times) } // Status returns the status of entry. -func (entry *Entry) Status() int { - return entry.entry.Status() +func (entry *Job) Status() int { + return entry.job.Status() } // SetStatus sets the status of the entry. -func (entry *Entry) SetStatus(status int) int { - return entry.entry.SetStatus(status) +func (entry *Job) SetStatus(status int) int { + return entry.job.SetStatus(status) } // Start starts running the entry. -func (entry *Entry) Start() { - entry.entry.Start() +func (entry *Job) Start() { + entry.job.Start() } // Stop stops running the entry. -func (entry *Entry) Stop() { - entry.entry.Stop() +func (entry *Job) Stop() { + entry.job.Stop() } // Close stops and removes the entry from cron. -func (entry *Entry) Close() { +func (entry *Job) Close() { entry.cron.entries.Remove(entry.Name) - entry.entry.Close() + entry.job.Close() } // Timed task check execution. -// The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry. -// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second. -func (entry *Entry) check() { +// The running times limits feature is implemented by gcron.Job and cannot be implemented by gtimer.Job. +// gcron.Job relies on gtimer to implement a scheduled task check for gcron.Job per second. +func (entry *Job) check() { if entry.schedule.meet(time.Now()) { path := entry.cron.GetLogPath() level := entry.cron.GetLogLevel() @@ -125,7 +125,7 @@ func (entry *Entry) check() { // Running times check. times := entry.times.Add(-1) if times <= 0 { - if entry.entry.SetStatus(StatusClosed) == StatusClosed || times < 0 { + if entry.job.SetStatus(StatusClosed) == StatusClosed || times < 0 { return } } @@ -139,7 +139,7 @@ func (entry *Entry) check() { } else { glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) } - if entry.entry.Status() == StatusClosed { + if entry.job.Status() == StatusClosed { entry.Close() } }() diff --git a/os/gcron/gcron_unit_2_test.go b/os/gcron/gcron_unit_2_test.go index 5956dc8bd..6fca7c849 100644 --- a/os/gcron/gcron_unit_2_test.go +++ b/os/gcron/gcron_unit_2_test.go @@ -16,7 +16,7 @@ import ( "github.com/gogf/gf/test/gtest" ) -func TestCron_Entry_Operations(t *testing.T) { +func TestCron_Job_Operations(t *testing.T) { gtest.C(t, func(t *gtest.T) { var ( cron = gcron.New() diff --git a/os/gtimer/gtimer.go b/os/gtimer/gtimer.go index 80840682c..21474a498 100644 --- a/os/gtimer/gtimer.go +++ b/os/gtimer/gtimer.go @@ -4,8 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gtimer implements Hierarchical Timing Wheel for interval/delayed jobs -// running and management. +// Package gtimer implements timer for interval/delayed jobs running and management. // // This package is designed for management for millions of timing jobs. The differences // between gtimer and gcron are as follows: @@ -21,33 +20,53 @@ package gtimer import ( "fmt" + "github.com/gogf/gf/container/gtype" "math" + "sync" "time" "github.com/gogf/gf/os/gcmd" ) +// Timer is the timer manager, which uses ticks to calculate the timing interval. +type Timer struct { + mu sync.RWMutex + queue *priorityQueue // queue is a priority queue based on heap structure. + status *gtype.Int // status is the current timer status. + ticks *gtype.Int64 // ticks is the proceeded interval number by the timer. + options TimerOptions // timer options is used for timer configuration. +} + +// TimerOptions is the configuration object for Timer. +type TimerOptions struct { + Interval time.Duration // Interval is the interval escaped of the timer. +} + const ( - StatusReady = 0 // Job is ready for running. - StatusRunning = 1 // Job is already running. - StatusStopped = 2 // Job is stopped. - StatusReset = 3 // Job is reset. - StatusClosed = -1 // Job is closed and waiting to be deleted. - panicExit = "exit" // Internal usage for custom job exit function with panic. - defaultTimes = math.MaxInt32 // Default limit running times, a big number. - defaultSlotNumber = 10 // Default slot number. - defaultWheelInterval = 60 // Default wheel interval, for better manually reading. - defaultWheelLevel = 5 // Default wheel level. + StatusReady = 0 // Job or Timer is ready for running. + StatusRunning = 1 // Job or Timer is already running. + StatusStopped = 2 // Job or Timer is stopped. + StatusClosed = -1 // Job or Timer is closed and waiting to be deleted. + panicExit = "exit" // panicExit is used for custom job exit with panic. + defaultTimes = math.MaxInt32 // defaultTimes is the default limit running times, a big number. + defaultTimerInterval = 100 // defaultTimerInterval is the default timer interval in milliseconds. cmdEnvKey = "gf.gtimer" // Configuration key for command argument or environment. ) var ( - defaultSlots = gcmd.GetOptWithEnv(fmt.Sprintf("%s.slots", cmdEnvKey), defaultSlotNumber).Int() - defaultLevel = gcmd.GetOptWithEnv(fmt.Sprintf("%s.level", cmdEnvKey), defaultWheelLevel).Int() - defaultInterval = gcmd.GetOptWithEnv(fmt.Sprintf("%s.interval", cmdEnvKey), defaultWheelInterval).Duration() * time.Millisecond - defaultTimer = New(defaultSlots, defaultInterval, defaultLevel) + defaultTimer = New() + defaultInterval = gcmd.GetOptWithEnv( + fmt.Sprintf("%s.interval", cmdEnvKey), defaultTimerInterval, + ).Duration() * time.Millisecond ) +// DefaultOptions creates and returns a default options object for Timer creation. +func DefaultOptions() TimerOptions { + return TimerOptions{ + Interval: defaultInterval, + } +} + // SetTimeout runs the job once after duration of . // It is like the one in javascript. func SetTimeout(delay time.Duration, job JobFunc) { @@ -61,11 +80,11 @@ func SetInterval(interval time.Duration, job JobFunc) { } // Add adds a timing job to the default timer, which runs in interval of . -func Add(interval time.Duration, job JobFunc) *Entry { +func Add(interval time.Duration, job JobFunc) *Job { return defaultTimer.Add(interval, job) } -// AddEntry adds a timing job to the default timer with detailed parameters. +// AddJob adds a timing job to the default timer with detailed parameters. // // The parameter specifies the running interval of the job. // @@ -76,22 +95,22 @@ func Add(interval time.Duration, job JobFunc) *Entry { // exits if its run times exceeds the . // // The parameter specifies the job status when it's firstly added to the timer. -func AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - return defaultTimer.AddEntry(interval, job, singleton, times, status) +func AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { + return defaultTimer.AddJob(interval, job, singleton, times, status) } // AddSingleton is a convenience function for add singleton mode job. -func AddSingleton(interval time.Duration, job JobFunc) *Entry { +func AddSingleton(interval time.Duration, job JobFunc) *Job { return defaultTimer.AddSingleton(interval, job) } // AddOnce is a convenience function for adding a job which only runs once and then exits. -func AddOnce(interval time.Duration, job JobFunc) *Entry { +func AddOnce(interval time.Duration, job JobFunc) *Job { return defaultTimer.AddOnce(interval, job) } // AddTimes is a convenience function for adding a job which is limited running times. -func AddTimes(interval time.Duration, times int, job JobFunc) *Entry { +func AddTimes(interval time.Duration, times int, job JobFunc) *Job { return defaultTimer.AddTimes(interval, times, job) } @@ -101,10 +120,10 @@ func DelayAdd(delay time.Duration, interval time.Duration, job JobFunc) { defaultTimer.DelayAdd(delay, interval, job) } -// DelayAddEntry adds a timing job after delay of duration. -// Also see AddEntry. -func DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { - defaultTimer.DelayAddEntry(delay, interval, job, singleton, times, status) +// DelayAddJob adds a timing job after delay of duration. +// Also see AddJob. +func DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { + defaultTimer.DelayAddJob(delay, interval, job, singleton, times, status) } // DelayAddSingleton adds a timing job after delay of duration. diff --git a/os/gtimer/gtimer_entry.go b/os/gtimer/gtimer_entry.go deleted file mode 100644 index f413646c4..000000000 --- a/os/gtimer/gtimer_entry.go +++ /dev/null @@ -1,211 +0,0 @@ -// 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 gtimer - -import ( - "time" - - "github.com/gogf/gf/container/gtype" -) - -// Entry is the timing job entry to wheel. -type Entry struct { - name string - wheel *wheel // Belonged wheel. - job JobFunc // The job function. - singleton *gtype.Bool // Singleton mode. - status *gtype.Int // Job status. - times *gtype.Int // Limit running times. - intervalTicks int64 // The interval ticks of the job. - createTicks int64 // Timer ticks when the job installed. - createMs int64 // The timestamp in milliseconds when job installed. - intervalMs int64 // The interval milliseconds of the job. - installIntervalMs int64 // Interval when first installation in milliseconds. -} - -// JobFunc is the job function. -type JobFunc = func() - -// addEntry adds a timing job to the wheel. -func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - if times <= 0 { - times = defaultTimes - } - var ( - intervalMs = interval.Nanoseconds() / 1e6 - intervalTicks = intervalMs / w.intervalMs - ) - if intervalTicks == 0 { - // If the given interval is lesser than the one of the wheel, - // then sets it to one tick, which means it will be run in one interval. - intervalTicks = 1 - } - var ( - nowMs = time.Now().UnixNano() / 1e6 - nowTicks = w.ticks.Val() - entry = &Entry{ - wheel: w, - job: job, - times: gtype.NewInt(times), - status: gtype.NewInt(status), - createTicks: nowTicks, - intervalTicks: intervalTicks, - singleton: gtype.NewBool(singleton), - createMs: nowMs, - intervalMs: intervalMs, - installIntervalMs: intervalMs, - } - ) - // Install the job to the list of the slot. - w.slots[(nowTicks+intervalTicks)%w.number].PushBack(entry) - return entry -} - -// addEntryByParent adds a timing job with parent entry. -// The parameter `rollOn` specifies if just rolling on the entry, which was not met the runnable requirement -// and not executed previously. This is true often when the job internal is too long. -func (w *wheel) addEntryByParent(rollOn bool, nowMs, interval int64, parent *Entry) *Entry { - intervalTicks := interval / w.intervalMs - if intervalTicks == 0 { - intervalTicks = 1 - } - nowTicks := w.ticks.Val() - entry := &Entry{ - name: parent.name, - wheel: w, - job: parent.job, - times: parent.times, - status: parent.status, - intervalTicks: intervalTicks, - singleton: parent.singleton, - createTicks: nowTicks, - createMs: nowMs, - intervalMs: interval, - installIntervalMs: parent.installIntervalMs, - } - if rollOn { - entry.createMs = parent.createMs - if parent.wheel.level == w.level { - entry.createTicks = parent.createTicks - } - } - w.slots[(nowTicks+intervalTicks)%w.number].PushBack(entry) - return entry -} - -// Status returns the status of the job. -func (entry *Entry) Status() int { - return entry.status.Val() -} - -// SetStatus custom sets the status for the job. -func (entry *Entry) SetStatus(status int) int { - return entry.status.Set(status) -} - -// Start starts the job. -func (entry *Entry) Start() { - entry.status.Set(StatusReady) -} - -// Stop stops the job. -func (entry *Entry) Stop() { - entry.status.Set(StatusStopped) -} - -//Reset reset the job. -func (entry *Entry) Reset() { - entry.status.Set(StatusReset) -} - -// Close closes the job, and then it will be removed from the timer. -func (entry *Entry) Close() { - entry.status.Set(StatusClosed) -} - -// IsSingleton checks and returns whether the job in singleton mode. -func (entry *Entry) IsSingleton() bool { - return entry.singleton.Val() -} - -// SetSingleton sets the job singleton mode. -func (entry *Entry) SetSingleton(enabled bool) { - entry.singleton.Set(enabled) -} - -// SetTimes sets the limit running times for the job. -func (entry *Entry) SetTimes(times int) { - entry.times.Set(times) -} - -// Run runs the job. -func (entry *Entry) Run() { - entry.job() -} - -// check checks if the job should be run in given ticks and timestamp milliseconds. -func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool) { - switch entry.status.Val() { - case StatusStopped: - return false, true - case StatusClosed: - return false, false - case StatusReset: - return false, true - } - // Firstly checks using the ticks, this may be low precision as one tick is a little bit long. - //if entry.name == "1" { - // intlog.Print("check:", nowTicks-entry.createTicks, nowTicks, entry.createTicks, entry.intervalTicks) - //} - if diff := nowTicks - entry.createTicks; diff > 0 && diff%entry.intervalTicks == 0 { - // If not the lowest level wheel. - if entry.wheel.level > 0 { - diffMs := nowMs - entry.createMs - switch { - // Add it to the next slot, which means it will run on next interval. - case diffMs < entry.wheel.timer.intervalMs: - entry.wheel.slots[(nowTicks+entry.intervalTicks)%entry.wheel.number].PushBack(entry) - return false, false - - // Normal rolls on the job. - case diffMs >= entry.wheel.timer.intervalMs: - // Calculate the leftover milliseconds, - // if it is greater than the minimum interval, then re-install it. - if leftMs := entry.intervalMs - diffMs; leftMs > entry.wheel.timer.intervalMs { - // Re-calculate and re-installs the job proper slot. - entry.wheel.timer.doAddEntryByParent(false, nowMs, leftMs, entry) - return false, false - } - } - } - // Singleton mode check. - if entry.IsSingleton() { - // Note that it is atomic operation to ensure concurrent safety. - if entry.status.Set(StatusRunning) == StatusRunning { - return false, true - } - } - // Limit running times. - times := entry.times.Add(-1) - if times <= 0 { - // Note that it is atomic operation to ensure concurrent safety. - if entry.status.Set(StatusClosed) == StatusClosed || times < 0 { - return false, false - } - } - // This means it does not limit the running times. - // I know it's ugly, but it is surely high performance for running times limit. - if times < 2000000000 && times > 1000000000 { - entry.times.Set(defaultTimes) - } - //if entry.name == "1" { - // intlog.Print("runnable:", nowTicks-entry.createTicks, nowTicks, entry.createTicks, entry.createTicks, entry.interval) - //} - return true, true - } - return false, true -} diff --git a/os/gtimer/gtimer_job.go b/os/gtimer/gtimer_job.go new file mode 100644 index 000000000..39fc938ae --- /dev/null +++ b/os/gtimer/gtimer_job.go @@ -0,0 +1,134 @@ +// 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 gtimer + +import ( + "github.com/gogf/gf/container/gtype" + "math" +) + +// Job is the timing job. +type Job struct { + job JobFunc // The job function. + timer *Timer // Belonged timer. + ticks int64 // The job runs every ticks. + times *gtype.Int // Limit running times. + status *gtype.Int // Job status. + singleton *gtype.Bool // Singleton mode. + nextTicks *gtype.Int64 // Next run ticks of the job. +} + +// JobFunc is the job function. +type JobFunc = func() + +// Status returns the status of the job. +func (j *Job) Status() int { + return j.status.Val() +} + +// Run runs the timer job asynchronously. +func (j *Job) Run() { + leftRunningTimes := j.times.Add(-1) + if leftRunningTimes < 0 { + j.status.Set(StatusClosed) + return + } + // This means it does not limit the running times. + // I know it's ugly, but it is surely high performance for running times limit. + if leftRunningTimes < 2000000000 && leftRunningTimes > 1000000000 { + j.times.Set(math.MaxInt32) + } + go func() { + defer func() { + if err := recover(); err != nil { + if err != panicExit { + panic(err) + } else { + j.Close() + return + } + } + if j.Status() == StatusRunning { + j.SetStatus(StatusReady) + } + }() + j.job() + }() +} + +// doCheckAndRunByTicks checks the if job can run in given timer ticks, +// it runs asynchronously if the given `currentTimerTicks` meets or else +// it increments its ticks and waits for next running check. +func (j *Job) doCheckAndRunByTicks(currentTimerTicks int64) { + // Ticks check. + if currentTimerTicks < j.nextTicks.Val() { + return + } + j.nextTicks.Set(currentTimerTicks + j.ticks) + // Perform job checking. + switch j.status.Val() { + case StatusRunning: + if j.IsSingleton() { + return + } + case StatusReady: + if !j.status.Cas(StatusReady, StatusRunning) { + return + } + case StatusStopped: + return + case StatusClosed: + return + } + // Perform job running. + j.Run() +} + +// SetStatus custom sets the status for the job. +func (j *Job) SetStatus(status int) int { + return j.status.Set(status) +} + +// Start starts the job. +func (j *Job) Start() { + j.status.Set(StatusReady) +} + +// Stop stops the job. +func (j *Job) Stop() { + j.status.Set(StatusStopped) +} + +// Close closes the job, and then it will be removed from the timer. +func (j *Job) Close() { + j.status.Set(StatusClosed) +} + +// Reset reset the job, which resets its ticks for next running. +func (j *Job) Reset() { + j.nextTicks.Set(j.timer.ticks.Val() + j.ticks) +} + +// IsSingleton checks and returns whether the job in singleton mode. +func (j *Job) IsSingleton() bool { + return j.singleton.Val() +} + +// SetSingleton sets the job singleton mode. +func (j *Job) SetSingleton(enabled bool) { + j.singleton.Set(enabled) +} + +// Job returns the job function of this job. +func (j *Job) Job() JobFunc { + return j.job +} + +// SetTimes sets the limit running times for the job. +func (j *Job) SetTimes(times int) { + j.times.Set(times) +} diff --git a/os/gtimer/gtimer_loop.go b/os/gtimer/gtimer_loop.go deleted file mode 100644 index 725f6de46..000000000 --- a/os/gtimer/gtimer_loop.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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 gtimer - -import ( - "time" - - "github.com/gogf/gf/container/glist" -) - -// start starts the ticker using a standalone goroutine. -func (w *wheel) start() { - go func() { - var ( - tickDuration = time.Duration(w.intervalMs) * time.Millisecond - ticker = time.NewTicker(tickDuration) - ) - for { - select { - case <-ticker.C: - switch w.timer.status.Val() { - case StatusRunning: - w.proceed() - - case StatusStopped: - // Do nothing. - - case StatusClosed: - ticker.Stop() - return - } - - } - } - }() -} - -// proceed checks and rolls on the job. -// If a timing job is time for running, it runs in an asynchronous goroutine, -// or else it removes from current slot and re-installs the job to another wheel and slot -// according to its leftover interval in milliseconds. -func (w *wheel) proceed() { - var ( - nowTicks = w.ticks.Add(1) - list = w.slots[int(nowTicks%w.number)] - length = list.Len() - nowMs = w.timer.nowFunc().UnixNano() / 1e6 - ) - if length > 0 { - go func(l *glist.List, nowTicks int64) { - var entry *Entry - for i := length; i > 0; i-- { - if v := l.PopFront(); v == nil { - break - } else { - entry = v.(*Entry) - } - // Checks whether the time for running. - runnable, addable := entry.check(nowTicks, nowMs) - if runnable { - // Just run it in another goroutine. - go func(entry *Entry) { - defer func() { - if err := recover(); err != nil { - if err != panicExit { - panic(err) - } else { - entry.Close() - } - } - if entry.Status() == StatusRunning { - entry.SetStatus(StatusReady) - } - }() - entry.job() - }(entry) - } - // Add job again, which make the job continuous running. - if addable { - // If StatusReset, reset to runnable state. - if entry.Status() == StatusReset { - entry.SetStatus(StatusReady) - } - entry.wheel.timer.doAddEntryByParent(!runnable, nowMs, entry.installIntervalMs, entry) - } - } - }(list, nowTicks) - } -} diff --git a/os/gtimer/gtimer_queue.go b/os/gtimer/gtimer_queue.go new file mode 100644 index 000000000..b805d7d22 --- /dev/null +++ b/os/gtimer/gtimer_queue.go @@ -0,0 +1,91 @@ +// 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 gtimer + +import ( + "container/heap" + "github.com/gogf/gf/container/gtype" + "math" + "sync" +) + +// priorityQueue is an abstract data type similar to a regular queue or stack data structure in which +// each element additionally has a "priority" associated with it. In a priority queue, an element with +// high priority is served before an element with low priority. +// priorityQueue is based on heap structure. +type priorityQueue struct { + mu sync.RWMutex + heap *priorityQueueHeap // the underlying queue items manager using heap. + latestPriority *gtype.Int64 // latestPriority stores the most priority value of the heap, which is used to check if necessary to call the Pop of heap by Timer. +} + +// priorityQueueHeap is a heap manager, of which the underlying `array` is a array implementing a heap structure. +type priorityQueueHeap struct { + array []priorityQueueItem +} + +// priorityQueueItem stores the queue item which has a `priority` attribute to sort itself in heap. +type priorityQueueItem struct { + value interface{} + priority int64 +} + +// newPriorityQueue creates and returns a priority queue. +func newPriorityQueue() *priorityQueue { + queue := &priorityQueue{ + heap: &priorityQueueHeap{ + array: make([]priorityQueueItem, 0), + }, + latestPriority: gtype.NewInt64(math.MaxInt64), + } + heap.Init(queue.heap) + return queue +} + +// Len retrieves and returns the length of the queue. +func (q *priorityQueue) Len() int { + q.mu.RLock() + defer q.mu.RUnlock() + return q.heap.Len() +} + +// LatestPriority retrieves and returns the minimum and the most priority value of the queue. +func (q *priorityQueue) LatestPriority() int64 { + return q.latestPriority.Val() +} + +// Push pushes a value to the queue. +// The `priority` specifies the priority of the value. +// The lesser the `priority` value the higher priority of the `value`. +func (q *priorityQueue) Push(value interface{}, priority int64) { + q.mu.Lock() + defer q.mu.Unlock() + // Update the minimum priority using atomic operation. + for { + latestPriority := q.latestPriority.Val() + if priority >= latestPriority { + break + } + if q.latestPriority.Cas(latestPriority, priority) { + break + } + } + heap.Push(q.heap, priorityQueueItem{ + value: value, + priority: priority, + }) +} + +// Pop retrieves, removes and returns the most high priority value from the queue. +func (q *priorityQueue) Pop() interface{} { + q.mu.Lock() + defer q.mu.Unlock() + if item := heap.Pop(q.heap); item != nil { + return item.(priorityQueueItem).value + } + return nil +} diff --git a/os/gtimer/gtimer_queue_heap.go b/os/gtimer/gtimer_queue_heap.go new file mode 100644 index 000000000..30879f770 --- /dev/null +++ b/os/gtimer/gtimer_queue_heap.go @@ -0,0 +1,41 @@ +// 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 gtimer + +// Len is used to implement the interface of sort.Interface. +func (h *priorityQueueHeap) Len() int { + return len(h.array) +} + +// Less is used to implement the interface of sort.Interface. +func (h *priorityQueueHeap) Less(i, j int) bool { + return h.array[i].priority < h.array[j].priority +} + +// Swap is used to implement the interface of sort.Interface. +func (h *priorityQueueHeap) Swap(i, j int) { + if len(h.array) == 0 { + return + } + h.array[i], h.array[j] = h.array[j], h.array[i] +} + +// Push pushes an item to the heap. +func (h *priorityQueueHeap) Push(x interface{}) { + h.array = append(h.array, x.(priorityQueueItem)) +} + +// Pop retrieves, removes and returns the most high priority item from the heap. +func (h *priorityQueueHeap) Pop() interface{} { + length := len(h.array) + if length == 0 { + return nil + } + item := h.array[length-1] + h.array = h.array[0 : length-1] + return item +} diff --git a/os/gtimer/gtimer_timer.go b/os/gtimer/gtimer_timer.go index 0280f0680..00e700d4d 100644 --- a/os/gtimer/gtimer_timer.go +++ b/os/gtimer/gtimer_timer.go @@ -7,107 +7,31 @@ package gtimer import ( - "fmt" - "time" - - "github.com/gogf/gf/container/glist" "github.com/gogf/gf/container/gtype" + "time" ) -// Timer is a Hierarchical Timing Wheel manager for timing jobs. -type Timer struct { - status *gtype.Int // Timer status. - wheels []*wheel // The underlying wheels. - length int // Max level of the wheels. - number int // Slot Number of each wheel. - intervalMs int64 // Interval of the slot in milliseconds. - nowFunc func() time.Time // nowFunc returns the current time, which can be custom. -} - -// Wheel is a slot wrapper for timing job install and uninstall. -type wheel struct { - timer *Timer // Belonged timer. - level int // The level in the timer. - slots []*glist.List // Slot array. - number int64 // Slot Number=len(slots). - ticks *gtype.Int64 // Ticked count of the wheel, one tick is one of its interval passed. - totalMs int64 // Total duration in milliseconds=number*interval. - createMs int64 // Created timestamp in milliseconds. - intervalMs int64 // Interval in milliseconds, which is the duration of one slot. -} - -// New creates and returns a Hierarchical Timing Wheel designed timer. -// The parameter specifies the interval of the timer. -// The optional parameter specifies the wheels count of the timer, -// which is defaultWheelLevel in default. -func New(slot int, interval time.Duration, level ...int) *Timer { - t := doNewWithoutAutoStart(slot, interval, level...) - t.wheels[0].start() - return t -} - -func doNewWithoutAutoStart(slot int, interval time.Duration, level ...int) *Timer { - if slot <= 0 { - panic(fmt.Sprintf("invalid slot number: %d", slot)) - } - length := defaultWheelLevel - if len(level) > 0 { - length = level[0] - } +func New(options ...TimerOptions) *Timer { t := &Timer{ - status: gtype.NewInt(StatusRunning), - wheels: make([]*wheel, length), - length: length, - number: slot, - intervalMs: interval.Nanoseconds() / 1e6, - nowFunc: func() time.Time { - return time.Now() - }, + queue: newPriorityQueue(), + status: gtype.NewInt(StatusRunning), + ticks: gtype.NewInt64(), } - for i := 0; i < length; i++ { - if i > 0 { - n := time.Duration(t.wheels[i-1].totalMs) * time.Millisecond - if n <= 0 { - panic(fmt.Sprintf(`inteval is too large with level: %dms x %d`, interval, length)) - } - w := t.newWheel(i, slot, n) - t.wheels[i] = w - t.wheels[i-1].addEntry(n, w.proceed, false, defaultTimes, StatusReady) - if i == length-1 { - t.wheels[i].addEntry(n, w.proceed, false, defaultTimes, StatusReady) - } - } else { - w := t.newWheel(i, slot, interval) - t.wheels[i] = w - } + if len(options) > 0 { + t.options = options[0] + } else { + t.options = DefaultOptions() } + go t.loop() return t } -// newWheel creates and returns a single wheel. -func (t *Timer) newWheel(level int, slot int, interval time.Duration) *wheel { - w := &wheel{ - timer: t, - level: level, - slots: make([]*glist.List, slot), - number: int64(slot), - ticks: gtype.NewInt64(), - totalMs: int64(slot) * interval.Nanoseconds() / 1e6, - createMs: time.Now().UnixNano() / 1e6, - intervalMs: interval.Nanoseconds() / 1e6, - } - for i := int64(0); i < w.number; i++ { - w.slots[i] = glist.New(true) - } - return w -} - // Add adds a timing job to the timer, which runs in interval of . -func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, false, defaultTimes, StatusReady) +func (t *Timer) Add(interval time.Duration, job JobFunc) *Job { + return t.createJob(interval, job, false, defaultTimes, StatusReady) } -// AddEntry adds a timing job to the timer with detailed parameters. +// AddJob adds a timing job to the timer with detailed parameters. // // The parameter specifies the running interval of the job. // @@ -118,23 +42,23 @@ func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry { // exits if its run times exceeds the . // // The parameter specifies the job status when it's firstly added to the timer. -func (t *Timer) AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - return t.doAddEntry(interval, job, singleton, times, status) +func (t *Timer) AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { + return t.createJob(interval, job, singleton, times, status) } // AddSingleton is a convenience function for add singleton mode job. -func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, defaultTimes, StatusReady) +func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Job { + return t.createJob(interval, job, true, defaultTimes, StatusReady) } // AddOnce is a convenience function for adding a job which only runs once and then exits. -func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, 1, StatusReady) +func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Job { + return t.createJob(interval, job, true, 1, StatusReady) } // AddTimes is a convenience function for adding a job which is limited running times. -func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, times, StatusReady) +func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Job { + return t.createJob(interval, job, true, times, StatusReady) } // DelayAdd adds a timing job after delay of duration. @@ -145,11 +69,11 @@ func (t *Timer) DelayAdd(delay time.Duration, interval time.Duration, job JobFun }) } -// DelayAddEntry adds a timing job after delay of duration. -// Also see AddEntry. -func (t *Timer) DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { +// DelayAddJob adds a timing job after delay of duration. +// Also see AddJob. +func (t *Timer) DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { t.AddOnce(delay, func() { - t.AddEntry(interval, job, singleton, times, status) + t.AddJob(interval, job, singleton, times, status) }) } @@ -192,77 +116,29 @@ func (t *Timer) Close() { t.status.Set(StatusClosed) } -// doAddEntry adds a timing job to timer for internal usage. -func (t *Timer) doAddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - return t.wheels[t.getLevelByIntervalMs(interval.Nanoseconds()/1e6)].addEntry(interval, job, singleton, times, status) -} - -// doAddEntryByParent adds a timing job to timer with parent entry for internal usage. -func (t *Timer) doAddEntryByParent(rollOn bool, nowMs, interval int64, parent *Entry) *Entry { - return t.wheels[t.getLevelByIntervalMs(interval)].addEntryByParent(rollOn, nowMs, interval, parent) -} - -// getLevelByIntervalMs calculates and returns the level of timer wheel with given milliseconds. -func (t *Timer) getLevelByIntervalMs(intervalMs int64) int { - pos, cmp := t.binSearchIndex(intervalMs) - switch cmp { - // If equals to the last comparison value, do not add it directly to this wheel, - // but loop and continue comparison from the index to the first level, - // and add it to the proper level wheel. - case 0: - fallthrough - // If lesser than the last comparison value, - // loop and continue comparison from the index to the first level, - // and add it to the proper level wheel. - case -1: - i := pos - for ; i > 0; i-- { - if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs { - return i - } - } - return i - - // If greater than the last comparison value, - // loop and continue comparison from the index to the last level, - // and add it to the proper level wheel. - case 1: - i := pos - for ; i < t.length-1; i++ { - if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs { - return i - } - } - return i +// createJob creates and adds a timing job to the timer. +func (t *Timer) createJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { + if times <= 0 { + times = defaultTimes } - return 0 -} - -// binSearchIndex uses binary search algorithm for finding the possible level of the wheel -// for the interval value. -func (t *Timer) binSearchIndex(n int64) (index int, result int) { - min := 0 - max := t.length - 1 - mid := 0 - cmp := -2 - for min <= max { - mid = min + int((max-min)/2) - switch { - case t.wheels[mid].intervalMs == n: - cmp = 0 - case t.wheels[mid].intervalMs > n: - cmp = -1 - case t.wheels[mid].intervalMs < n: - cmp = 1 - } - switch cmp { - case -1: - max = mid - 1 - case 1: - min = mid + 1 - case 0: - return mid, cmp - } + var ( + intervalTicksOfJob = int64(interval / t.options.Interval) + ) + if intervalTicksOfJob == 0 { + // If the given interval is lesser than the one of the wheel, + // then sets it to one tick, which means it will be run in one interval. + intervalTicksOfJob = 1 } - return mid, cmp + nextTicks := t.ticks.Val() + intervalTicksOfJob + j := &Job{ + job: job, + timer: t, + ticks: intervalTicksOfJob, + times: gtype.NewInt(times), + status: gtype.NewInt(status), + singleton: gtype.NewBool(singleton), + nextTicks: gtype.NewInt64(nextTicks), + } + t.queue.Push(j, nextTicks) + return j } diff --git a/os/gtimer/gtimer_timer_loop.go b/os/gtimer/gtimer_timer_loop.go new file mode 100644 index 000000000..d2e9909d7 --- /dev/null +++ b/os/gtimer/gtimer_timer_loop.go @@ -0,0 +1,68 @@ +// 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 gtimer + +import "time" + +// loop starts the ticker using a standalone goroutine. +func (t *Timer) loop() { + go func() { + var ( + currentTimerTicks int64 + timerIntervalTicker = time.NewTicker(t.options.Interval) + ) + defer timerIntervalTicker.Stop() + for { + select { + case <-timerIntervalTicker.C: + // Check the timer status. + switch t.status.Val() { + case StatusRunning: + // Timer proceeding. + currentTimerTicks = t.ticks.Add(1) + if currentTimerTicks >= t.queue.LatestPriority() { + t.proceed(currentTimerTicks) + } + + case StatusStopped: + // Do nothing. + + case StatusClosed: + // Timer exits. + return + } + } + } + }() +} + +// proceed proceeds the timer job checking and running logic. +func (t *Timer) proceed(currentTimerTicks int64) { + var ( + value interface{} + ) + for { + value = t.queue.Pop() + if value == nil { + break + } + job := value.(*Job) + // It checks if it meets the ticks requirement. + if jobNextTicks := job.nextTicks.Val(); currentTimerTicks < jobNextTicks { + // It push the job back if current ticks does not meet its running ticks requirement. + t.queue.Push(job, job.nextTicks.Val()) + break + } + // It checks the job running requirements and then does asynchronous running. + job.doCheckAndRunByTicks(currentTimerTicks) + // Status check: push back or ignore it. + if job.Status() != StatusClosed { + // It pushes the job back to queue for next running. + t.queue.Push(job, job.nextTicks.Val()) + } + } +} diff --git a/os/gtimer/gtimer_z_bench_test.go b/os/gtimer/gtimer_z_bench_test.go index 1f5eea33c..66ab4e0d6 100644 --- a/os/gtimer/gtimer_z_bench_test.go +++ b/os/gtimer/gtimer_z_bench_test.go @@ -14,7 +14,7 @@ import ( ) var ( - timer = gtimer.New(5, 30*time.Millisecond) + timer = gtimer.New() ) func Benchmark_Add(b *testing.B) { diff --git a/os/gtimer/gtimer_z_unit_api_test.go b/os/gtimer/gtimer_z_unit_api_test.go index debc25d9c..60fdb0837 100644 --- a/os/gtimer/gtimer_z_unit_api_test.go +++ b/os/gtimer/gtimer_z_unit_api_test.go @@ -39,10 +39,10 @@ func TestSetInterval(t *testing.T) { }) } -func TestAddEntry(t *testing.T) { +func TestAddJob(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.AddEntry(200*time.Millisecond, func() { + gtimer.AddJob(200*time.Millisecond, func() { array.Append(1) }, false, 2, gtimer.StatusReady) time.Sleep(1100 * time.Millisecond) @@ -86,10 +86,10 @@ func TestDelayAdd(t *testing.T) { }) } -func TestDelayAddEntry(t *testing.T) { +func TestDelayAddJob(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() { + gtimer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() { array.Append(1) }, false, 2, gtimer.StatusReady) time.Sleep(300 * time.Millisecond) diff --git a/os/gtimer/gtimer_z_unit_entry_test.go b/os/gtimer/gtimer_z_unit_entry_test.go index ba0d2d421..a542111b7 100644 --- a/os/gtimer/gtimer_z_unit_entry_test.go +++ b/os/gtimer/gtimer_z_unit_entry_test.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Entry Operations +// Job Operations package gtimer_test @@ -17,40 +17,40 @@ import ( "github.com/gogf/gf/test/gtest" ) -func TestEntry_Start_Stop_Close(t *testing.T) { +func TestJob_Start_Stop_Close(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) }) time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) - entry.Stop() + job.Stop() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) - entry.Start() + job.Start() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 2) - entry.Close() + job.Close() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 2) - t.Assert(entry.Status(), gtimer.StatusClosed) + t.Assert(job.Status(), gtimer.StatusClosed) }) } -func TestEntry_Singleton(t *testing.T) { +func TestJob_Singleton(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) time.Sleep(10 * time.Second) }) - t.Assert(entry.IsSingleton(), false) - entry.SetSingleton(true) - t.Assert(entry.IsSingleton(), true) + t.Assert(job.IsSingleton(), false) + job.SetSingleton(true) + t.Assert(job.IsSingleton(), true) time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) @@ -59,27 +59,28 @@ func TestEntry_Singleton(t *testing.T) { }) } -func TestEntry_SetTimes(t *testing.T) { +func TestJob_SetTimes(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) }) - entry.SetTimes(2) + job.SetTimes(2) + //job.IsSingleton() time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) }) } -func TestEntry_Run(t *testing.T) { +func TestJob_Run(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(1000*time.Millisecond, func() { + job := timer.Add(1000*time.Millisecond, func() { array.Append(1) }) - entry.Run() + job.Job()() t.Assert(array.Len(), 1) }) } diff --git a/os/gtimer/gtimer_z_unit_timer_internal_test.go b/os/gtimer/gtimer_z_unit_timer_internal_test.go index 5d144fb9a..9882d21c3 100644 --- a/os/gtimer/gtimer_z_unit_timer_internal_test.go +++ b/os/gtimer/gtimer_z_unit_timer_internal_test.go @@ -8,7 +8,6 @@ package gtimer import ( "github.com/gogf/gf/container/garray" - "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/test/gtest" "testing" "time" @@ -16,35 +15,34 @@ import ( func TestTimer_Proceed(t *testing.T) { gtest.C(t, func(t *gtest.T) { - index := gtype.NewInt() array := garray.New(true) - timer := doNewWithoutAutoStart(10, 60*time.Millisecond, 6) - timer.nowFunc = func() time.Time { - return time.Now().Add(time.Duration(index.Add(1)) * time.Millisecond * 60) - } - timer.AddOnce(2*time.Second, func() { + timer := New(TimerOptions{ + Interval: time.Hour, + }) + timer.Add(10000*time.Hour, func() { array.Append(1) }) - timer.AddOnce(1*time.Minute, func() { - array.Append(2) + timer.proceed(10001) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 1) + timer.proceed(20001) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 2) + }) + gtest.C(t, func(t *gtest.T) { + array := garray.New(true) + timer := New(TimerOptions{ + Interval: time.Millisecond * 100, }) - timer.AddOnce(5*time.Minute, func() { - array.Append(3) + timer.Add(10000*time.Hour, func() { + array.Append(1) }) - timer.AddOnce(1*time.Hour, func() { - array.Append(4) - }) - timer.AddOnce(100*time.Minute, func() { - array.Append(5) - }) - timer.AddOnce(2*time.Hour, func() { - array.Append(6) - }) - for i := 0; i < 500000; i++ { - timer.wheels[0].proceed() - time.Sleep(10 * time.Microsecond) - } - time.Sleep(time.Second) - t.Assert(array.Slice(), []int{1, 2, 3, 4, 5, 6}) + ticks := int64((10000 * time.Hour) / (time.Millisecond * 100)) + timer.proceed(ticks + 1) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 1) + timer.proceed(2*ticks + 1) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 2) }) } diff --git a/os/gtimer/gtimer_z_unit_timer_test.go b/os/gtimer/gtimer_z_unit_timer_test.go index d6da5a8b4..f126d79cb 100644 --- a/os/gtimer/gtimer_z_unit_timer_test.go +++ b/os/gtimer/gtimer_z_unit_timer_test.go @@ -9,7 +9,6 @@ package gtimer_test import ( - "github.com/gogf/gf/os/glog" "testing" "time" @@ -19,7 +18,7 @@ import ( ) func New() *gtimer.Timer { - return gtimer.New(10, 10*time.Millisecond) + return gtimer.New() } func TestTimer_Add_Close(t *testing.T) { @@ -28,15 +27,15 @@ func TestTimer_Add_Close(t *testing.T) { array := garray.New(true) //fmt.Println("start", time.Now()) timer.Add(200*time.Millisecond, func() { - //fmt.Println("entry1", time.Now()) + //fmt.Println("job1", time.Now()) array.Append(1) }) timer.Add(200*time.Millisecond, func() { - //fmt.Println("entry2", time.Now()) + //fmt.Println("job2", time.Now()) array.Append(1) }) timer.Add(400*time.Millisecond, func() { - //fmt.Println("entry3", time.Now()) + //fmt.Println("job3", time.Now()) array.Append(1) }) time.Sleep(250 * time.Millisecond) @@ -74,21 +73,21 @@ func TestTimer_Start_Stop_Close(t *testing.T) { }) } -func TestTimer_Reset(t *testing.T) { +func TestJob_Reset(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - glog.Printf("start time:%d", time.Now().Unix()) - singleton := timer.AddSingleton(2*time.Second, func() { - timestamp := time.Now().Unix() - glog.Println(timestamp) - array.Append(timestamp) + job := timer.AddSingleton(500*time.Millisecond, func() { + array.Append(1) }) - time.Sleep(5 * time.Second) - glog.Printf("reset time:%d", time.Now().Unix()) - singleton.Reset() - time.Sleep(10 * time.Second) - t.Assert(array.Len(), 6) + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(600 * time.Millisecond) + t.Assert(array.Len(), 1) }) } @@ -156,11 +155,11 @@ func TestTimer_DelayAdd(t *testing.T) { }) } -func TestTimer_DelayAddEntry(t *testing.T) { +func TestTimer_DelayAddJob(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - timer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() { + timer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() { array.Append(1) }, false, 100, gtimer.StatusReady) time.Sleep(250 * time.Millisecond) @@ -227,7 +226,9 @@ func TestTimer_DelayAddTimes(t *testing.T) { func TestTimer_AddLessThanInterval(t *testing.T) { gtest.C(t, func(t *gtest.T) { - timer := gtimer.New(10, 100*time.Millisecond) + timer := gtimer.New(gtimer.TimerOptions{ + Interval: 100 * time.Millisecond, + }) array := garray.New(true) timer.Add(20*time.Millisecond, func() { array.Append(1) @@ -243,7 +244,7 @@ func TestTimer_AddLessThanInterval(t *testing.T) { }) } -func TestTimer_AddLeveledEntry1(t *testing.T) { +func TestTimer_AddLeveledJob1(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) From 1b1355a595b84c8b9f3dac6a71604957d0b1dc33 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 18:27:46 +0800 Subject: [PATCH 23/70] improve package gtimer --- os/gtimer/gtimer_queue.go | 29 +++++++++++++++++++++-------- os/gtimer/gtimer_z_bench_test.go | 12 ++++++++---- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/os/gtimer/gtimer_queue.go b/os/gtimer/gtimer_queue.go index b805d7d22..9fb6af73d 100644 --- a/os/gtimer/gtimer_queue.go +++ b/os/gtimer/gtimer_queue.go @@ -63,7 +63,11 @@ func (q *priorityQueue) LatestPriority() int64 { // The lesser the `priority` value the higher priority of the `value`. func (q *priorityQueue) Push(value interface{}, priority int64) { q.mu.Lock() - defer q.mu.Unlock() + heap.Push(q.heap, priorityQueueItem{ + value: value, + priority: priority, + }) + q.mu.Unlock() // Update the minimum priority using atomic operation. for { latestPriority := q.latestPriority.Val() @@ -74,18 +78,27 @@ func (q *priorityQueue) Push(value interface{}, priority int64) { break } } - heap.Push(q.heap, priorityQueueItem{ - value: value, - priority: priority, - }) } // Pop retrieves, removes and returns the most high priority value from the queue. func (q *priorityQueue) Pop() interface{} { q.mu.Lock() - defer q.mu.Unlock() - if item := heap.Pop(q.heap); item != nil { - return item.(priorityQueueItem).value + if v := heap.Pop(q.heap); v != nil { + item := v.(priorityQueueItem) + q.mu.Unlock() + // Update the minimum priority using atomic operation. + for { + latestPriority := q.latestPriority.Val() + if item.priority >= latestPriority { + break + } + if q.latestPriority.Cas(latestPriority, item.priority) { + break + } + } + return item.value + } else { + q.mu.Unlock() } return nil } diff --git a/os/gtimer/gtimer_z_bench_test.go b/os/gtimer/gtimer_z_bench_test.go index 66ab4e0d6..139bf1c4a 100644 --- a/os/gtimer/gtimer_z_bench_test.go +++ b/os/gtimer/gtimer_z_bench_test.go @@ -4,17 +4,15 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package gtimer_test +package gtimer import ( "testing" "time" - - "github.com/gogf/gf/os/gtimer" ) var ( - timer = gtimer.New() + timer = New() ) func Benchmark_Add(b *testing.B) { @@ -25,6 +23,12 @@ func Benchmark_Add(b *testing.B) { } } +func Benchmark_PriorityQueue_Pop(b *testing.B) { + for i := 0; i < b.N; i++ { + timer.queue.Pop() + } +} + func Benchmark_StartStop(b *testing.B) { for i := 0; i < b.N; i++ { timer.Start() From facb2949c3a4f3117a94f943dc563060f8fa7871 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 18:31:46 +0800 Subject: [PATCH 24/70] improve unit testing cases for package gtimer --- ...z_unit_entry_test.go => gtimer_z_unit_job_test.go} | 0 os/gtimer/gtimer_z_unit_timer_test.go | 11 +++++------ 2 files changed, 5 insertions(+), 6 deletions(-) rename os/gtimer/{gtimer_z_unit_entry_test.go => gtimer_z_unit_job_test.go} (100%) diff --git a/os/gtimer/gtimer_z_unit_entry_test.go b/os/gtimer/gtimer_z_unit_job_test.go similarity index 100% rename from os/gtimer/gtimer_z_unit_entry_test.go rename to os/gtimer/gtimer_z_unit_job_test.go diff --git a/os/gtimer/gtimer_z_unit_timer_test.go b/os/gtimer/gtimer_z_unit_timer_test.go index f126d79cb..7be8eeef5 100644 --- a/os/gtimer/gtimer_z_unit_timer_test.go +++ b/os/gtimer/gtimer_z_unit_timer_test.go @@ -54,21 +54,20 @@ func TestTimer_Start_Stop_Close(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - timer.Add(200*time.Millisecond, func() { - //glog.Println("add...") + timer.Add(1000*time.Millisecond, func() { array.Append(1) }) t.Assert(array.Len(), 0) - time.Sleep(300 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 1) timer.Stop() - time.Sleep(1000 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 1) timer.Start() - time.Sleep(200 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) timer.Close() - time.Sleep(1000 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) }) } From 7003c284d03433d295c7a39c926bcb3faa80b53f Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 22:38:07 +0800 Subject: [PATCH 25/70] replace json.Unmarshal with json.UnmarshalUseNumber for packages --- .example/container/garray/json_unmarshal.go | 2 +- .example/container/glist/json_unmarshal.go | 2 +- .../container/gmap/gmap_json_unmarshal.go | 2 +- .example/container/gset/json_unmarshal.go | 2 +- .example/container/gtype/json_unmarshal.go | 2 +- .example/container/gvar/json_unmarshal.go | 2 +- .example/encoding/gjson/issue#IZXU2.go | 2 +- .../server/session/redis/redis_bigint.go | 36 ++++++++++ .../gtcp/pkg_operations/common/funcs/funcs.go | 2 +- .../monitor/gtcp_monitor_server.go | 2 +- container/garray/garray_normal_any.go | 4 +- container/garray/garray_normal_int.go | 4 +- container/garray/garray_normal_str.go | 4 +- container/garray/garray_sorted_any.go | 4 +- container/garray/garray_sorted_int.go | 4 +- container/garray/garray_sorted_str.go | 4 +- .../garray_z_unit_normal_any_array_test.go | 12 ++-- .../garray_z_unit_normal_int_array_test.go | 12 ++-- .../garray_z_unit_normal_str_array_test.go | 12 ++-- .../garray_z_unit_sorted_any_array_test.go | 12 ++-- .../garray_z_unit_sorted_int_array_test.go | 12 ++-- .../garray_z_unit_sorted_str_array_test.go | 12 ++-- container/glist/glist.go | 4 +- container/glist/glist_z_unit_test.go | 4 +- container/gmap/gmap_hash_any_any_map.go | 2 +- container/gmap/gmap_hash_int_any_map.go | 4 +- container/gmap/gmap_hash_int_int_map.go | 4 +- container/gmap/gmap_hash_int_str_map.go | 4 +- container/gmap/gmap_hash_str_any_map.go | 2 +- container/gmap/gmap_hash_str_int_map.go | 4 +- container/gmap/gmap_hash_str_str_map.go | 2 +- container/gmap/gmap_list_map.go | 2 +- container/gmap/gmap_z_unit_any_any_test.go | 4 +- container/gmap/gmap_z_unit_int_any_test.go | 2 +- container/gmap/gmap_z_unit_int_int_test.go | 2 +- container/gmap/gmap_z_unit_int_str_test.go | 2 +- container/gmap/gmap_z_unit_list_map_test.go | 4 +- container/gmap/gmap_z_unit_str_any_test.go | 4 +- container/gmap/gmap_z_unit_str_int_test.go | 4 +- container/gmap/gmap_z_unit_str_str_test.go | 4 +- container/gmap/gmap_z_unit_tree_map_test.go | 4 +- container/gset/gset_any_set.go | 4 +- container/gset/gset_int_set.go | 4 +- container/gset/gset_str_set.go | 4 +- container/gset/gset_z_unit_any_test.go | 4 +- container/gset/gset_z_unit_int_test.go | 4 +- container/gset/gset_z_unit_str_test.go | 4 +- container/gtree/gtree_redblacktree.go | 2 +- container/gtype/interface.go | 2 +- container/gtype/z_unit_bool_test.go | 12 ++-- container/gtype/z_unit_byte_test.go | 2 +- container/gtype/z_unit_bytes_test.go | 2 +- container/gtype/z_unit_float32_test.go | 2 +- container/gtype/z_unit_float64_test.go | 2 +- container/gtype/z_unit_int32_test.go | 2 +- container/gtype/z_unit_int64_test.go | 2 +- container/gtype/z_unit_int_test.go | 2 +- container/gtype/z_unit_interface_test.go | 2 +- container/gtype/z_unit_string_test.go | 2 +- container/gtype/z_unit_uint32_test.go | 2 +- container/gtype/z_unit_uint64_test.go | 2 +- container/gtype/z_unit_uint_test.go | 2 +- container/gvar/gvar.go | 2 +- container/gvar/gvar_z_unit_json_test.go | 4 +- database/gdb/gdb_model_select.go | 2 +- encoding/gjson/gjson.go | 6 +- encoding/gjson/gjson_api_new_load.go | 65 ++++++++++--------- encoding/gjson/gjson_z_unit_basic_test.go | 2 +- .../gjson/gjson_z_unit_implements_test.go | 2 +- encoding/gjson/gjson_z_unit_new_test.go | 4 +- internal/json/json.go | 8 +++ net/ghttp/ghttp_request_param.go | 2 +- net/ghttp/ghttp_unit_request_json_test.go | 8 +-- net/gtrace/gtrace_carrier.go | 2 +- os/gbuild/gbuild.go | 2 +- os/gproc/gproc_comm_receive.go | 2 +- os/gproc/gproc_comm_send.go | 2 +- os/gsession/gsession_storage_file.go | 2 +- os/gsession/gsession_storage_redis.go | 2 +- os/gtime/gtime_z_unit_json_test.go | 2 +- util/gconv/gconv_map.go | 4 +- util/gconv/gconv_maps.go | 8 +-- util/gconv/gconv_maptomap.go | 8 +-- util/gconv/gconv_maptomaps.go | 8 +-- util/gconv/gconv_struct.go | 8 +-- util/gconv/gconv_structs.go | 8 +-- util/gconv/gconv_z_unit_struct_test.go | 2 +- 87 files changed, 243 insertions(+), 198 deletions(-) create mode 100644 .example/net/ghttp/server/session/redis/redis_bigint.go diff --git a/.example/container/garray/json_unmarshal.go b/.example/container/garray/json_unmarshal.go index 07e81adb0..99fb7adec 100644 --- a/.example/container/garray/json_unmarshal.go +++ b/.example/container/garray/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *garray.IntArray } s := Student{} - json.Unmarshal(b, &s) + json.UnmarshalUseNumber(b, &s) fmt.Println(s) } diff --git a/.example/container/glist/json_unmarshal.go b/.example/container/glist/json_unmarshal.go index 313dfe570..32e654bcb 100644 --- a/.example/container/glist/json_unmarshal.go +++ b/.example/container/glist/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *glist.List } s := Student{} - json.Unmarshal(b, &s) + json.UnmarshalUseNumber(b, &s) fmt.Println(s) } diff --git a/.example/container/gmap/gmap_json_unmarshal.go b/.example/container/gmap/gmap_json_unmarshal.go index af3c134a8..01bdc4f36 100644 --- a/.example/container/gmap/gmap_json_unmarshal.go +++ b/.example/container/gmap/gmap_json_unmarshal.go @@ -9,6 +9,6 @@ import ( func main() { m := gmap.Map{} s := []byte(`{"name":"john","score":100}`) - json.Unmarshal(s, &m) + json.UnmarshalUseNumber(s, &m) fmt.Println(m.Map()) } diff --git a/.example/container/gset/json_unmarshal.go b/.example/container/gset/json_unmarshal.go index 4e90ce823..f85afac05 100644 --- a/.example/container/gset/json_unmarshal.go +++ b/.example/container/gset/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *gset.IntSet } s := Student{} - json.Unmarshal(b, &s) + json.UnmarshalUseNumber(b, &s) fmt.Println(s) } diff --git a/.example/container/gtype/json_unmarshal.go b/.example/container/gtype/json_unmarshal.go index 6b72cab08..7a17a1971 100644 --- a/.example/container/gtype/json_unmarshal.go +++ b/.example/container/gtype/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *gtype.Interface } s := Student{} - json.Unmarshal(b, &s) + json.UnmarshalUseNumber(b, &s) fmt.Println(s) } diff --git a/.example/container/gvar/json_unmarshal.go b/.example/container/gvar/json_unmarshal.go index 31aa9843a..a4a9deba3 100644 --- a/.example/container/gvar/json_unmarshal.go +++ b/.example/container/gvar/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *g.Var } s := Student{} - json.Unmarshal(b, &s) + json.UnmarshalUseNumber(b, &s) fmt.Println(s) } diff --git a/.example/encoding/gjson/issue#IZXU2.go b/.example/encoding/gjson/issue#IZXU2.go index e96f428d3..f56152fa6 100644 --- a/.example/encoding/gjson/issue#IZXU2.go +++ b/.example/encoding/gjson/issue#IZXU2.go @@ -140,7 +140,7 @@ var data = `{ func main() { struct1 := new(XinYanModel) - err := json.Unmarshal([]byte(data), struct1) + err := json.UnmarshalUseNumber([]byte(data), struct1) fmt.Println(err) fmt.Println(struct1) diff --git a/.example/net/ghttp/server/session/redis/redis_bigint.go b/.example/net/ghttp/server/session/redis/redis_bigint.go new file mode 100644 index 000000000..b8a5b3460 --- /dev/null +++ b/.example/net/ghttp/server/session/redis/redis_bigint.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" +) + +func main() { + type User struct { + Id int64 + Name string + } + s := g.Server() + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/set", func(r *ghttp.Request) { + user := &User{ + Id: 1265476890672672808, + Name: "john", + } + if err := r.Session.Set("user", user); err != nil { + panic(err) + } + r.Response.Write("ok") + }) + group.GET("/get", func(r *ghttp.Request) { + r.Response.WriteJson(r.Session.Get("user")) + }) + group.GET("/clear", func(r *ghttp.Request) { + r.Session.Clear() + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/gtcp/pkg_operations/common/funcs/funcs.go b/.example/net/gtcp/pkg_operations/common/funcs/funcs.go index 368874c1e..4d8c33d10 100644 --- a/.example/net/gtcp/pkg_operations/common/funcs/funcs.go +++ b/.example/net/gtcp/pkg_operations/common/funcs/funcs.go @@ -30,7 +30,7 @@ func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) { return nil, err } else { msg = &types.Msg{} - err = json.Unmarshal(data, msg) + err = json.UnmarshalUseNumber(data, msg) if err != nil { return nil, fmt.Errorf("invalid package structure: %s", err.Error()) } diff --git a/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go b/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go index b9758f893..b0820320c 100644 --- a/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go +++ b/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go @@ -21,7 +21,7 @@ func main() { break } info := &types.NodeInfo{} - if err := json.Unmarshal(data, info); err != nil { + if err := json.UnmarshalUseNumber(data, info); err != nil { glog.Errorf("invalid package structure: %s", err.Error()) } else { glog.Println(info) diff --git a/container/garray/garray_normal_any.go b/container/garray/garray_normal_any.go index 3e0324a10..616a59062 100644 --- a/container/garray/garray_normal_any.go +++ b/container/garray/garray_normal_any.go @@ -736,7 +736,7 @@ func (a *Array) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -748,7 +748,7 @@ func (a *Array) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceAny(value) } diff --git a/container/garray/garray_normal_int.go b/container/garray/garray_normal_int.go index 10b64529d..10dbe16fe 100644 --- a/container/garray/garray_normal_int.go +++ b/container/garray/garray_normal_int.go @@ -735,7 +735,7 @@ func (a *IntArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -747,7 +747,7 @@ func (a *IntArray) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceInt(value) } diff --git a/container/garray/garray_normal_str.go b/container/garray/garray_normal_str.go index 477156a8a..523a75123 100644 --- a/container/garray/garray_normal_str.go +++ b/container/garray/garray_normal_str.go @@ -750,7 +750,7 @@ func (a *StrArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -762,7 +762,7 @@ func (a *StrArray) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceStr(value) } diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go index 497ea9e67..91d7fa073 100644 --- a/container/garray/garray_sorted_any.go +++ b/container/garray/garray_sorted_any.go @@ -685,7 +685,7 @@ func (a *SortedArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.comparator != nil && a.array != nil { @@ -706,7 +706,7 @@ func (a *SortedArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceAny(value) } diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go index ab926a63a..6b96b2c3a 100644 --- a/container/garray/garray_sorted_int.go +++ b/container/garray/garray_sorted_int.go @@ -658,7 +658,7 @@ func (a *SortedIntArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.array != nil { @@ -676,7 +676,7 @@ func (a *SortedIntArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceInt(value) } diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go index 5450f5252..7b8e23b1b 100644 --- a/container/garray/garray_sorted_str.go +++ b/container/garray/garray_sorted_str.go @@ -671,7 +671,7 @@ func (a *SortedStrArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.array != nil { @@ -689,7 +689,7 @@ func (a *SortedStrArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceStr(value) } diff --git a/container/garray/garray_z_unit_normal_any_array_test.go b/container/garray/garray_z_unit_normal_any_array_test.go index b74fc7656..004e9bfd3 100644 --- a/container/garray/garray_z_unit_normal_any_array_test.go +++ b/container/garray/garray_z_unit_normal_any_array_test.go @@ -570,12 +570,12 @@ func TestArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Slice(), s1) var a3 garray.Array - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -589,12 +589,12 @@ func TestArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Slice(), s1) var a3 garray.Array - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -612,7 +612,7 @@ func TestArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -631,7 +631,7 @@ func TestArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) diff --git a/container/garray/garray_z_unit_normal_int_array_test.go b/container/garray/garray_z_unit_normal_int_array_test.go index 3d6b0b27b..ef1e763ef 100644 --- a/container/garray/garray_z_unit_normal_int_array_test.go +++ b/container/garray/garray_z_unit_normal_int_array_test.go @@ -615,11 +615,11 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.IntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -633,11 +633,11 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.IntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -655,7 +655,7 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -674,7 +674,7 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) diff --git a/container/garray/garray_z_unit_normal_str_array_test.go b/container/garray/garray_z_unit_normal_str_array_test.go index f4a02744c..be165863b 100644 --- a/container/garray/garray_z_unit_normal_str_array_test.go +++ b/container/garray/garray_z_unit_normal_str_array_test.go @@ -614,11 +614,11 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.StrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -632,11 +632,11 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.StrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -654,7 +654,7 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -673,7 +673,7 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) diff --git a/container/garray/garray_z_unit_sorted_any_array_test.go b/container/garray/garray_z_unit_sorted_any_array_test.go index f3ce498ea..baecec1b5 100644 --- a/container/garray/garray_z_unit_sorted_any_array_test.go +++ b/container/garray/garray_z_unit_sorted_any_array_test.go @@ -656,11 +656,11 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedArray(gutil.ComparatorString) - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -676,11 +676,11 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedArray(gutil.ComparatorString) - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -699,7 +699,7 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.AssertNE(user.Scores, nil) @@ -735,7 +735,7 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.AssertNE(user.Scores, nil) diff --git a/container/garray/garray_z_unit_sorted_int_array_test.go b/container/garray/garray_z_unit_sorted_int_array_test.go index c4e2e04b7..56b873f47 100644 --- a/container/garray/garray_z_unit_sorted_int_array_test.go +++ b/container/garray/garray_z_unit_sorted_int_array_test.go @@ -557,11 +557,11 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedIntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -576,11 +576,11 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedIntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -598,7 +598,7 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []int{98, 99, 100}) @@ -617,7 +617,7 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []int{98, 99, 100}) diff --git a/container/garray/garray_z_unit_sorted_str_array_test.go b/container/garray/garray_z_unit_sorted_str_array_test.go index 5be967e54..bab5c539e 100644 --- a/container/garray/garray_z_unit_sorted_str_array_test.go +++ b/container/garray/garray_z_unit_sorted_str_array_test.go @@ -577,12 +577,12 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) t.Assert(a2.Interfaces(), s2) var a3 garray.SortedStrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -598,12 +598,12 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) t.Assert(a2.Interfaces(), s2) var a3 garray.SortedStrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -622,7 +622,7 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []string{"A", "A", "A+"}) @@ -641,7 +641,7 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []string{"A", "A", "A+"}) diff --git a/container/glist/glist.go b/container/glist/glist.go index 1a3fd22bb..94319e50e 100644 --- a/container/glist/glist.go +++ b/container/glist/glist.go @@ -519,7 +519,7 @@ func (l *List) UnmarshalJSON(b []byte) error { l.list = list.New() } var array []interface{} - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } l.PushBacks(array) @@ -536,7 +536,7 @@ func (l *List) UnmarshalValue(value interface{}) (err error) { var array []interface{} switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceAny(value) } diff --git a/container/glist/glist_z_unit_test.go b/container/glist/glist_z_unit_test.go index d1dd57c3e..5e1da47c8 100644 --- a/container/glist/glist_z_unit_test.go +++ b/container/glist/glist_z_unit_test.go @@ -711,7 +711,7 @@ func TestList_Json(t *testing.T) { b, err := json.Marshal(a) t.Assert(err, nil) - err = json.Unmarshal(b, l) + err = json.UnmarshalUseNumber(b, l) t.Assert(err, nil) t.Assert(l.FrontAll(), a) }) @@ -721,7 +721,7 @@ func TestList_Json(t *testing.T) { b, err := json.Marshal(a) t.Assert(err, nil) - err = json.Unmarshal(b, &l) + err = json.UnmarshalUseNumber(b, &l) t.Assert(err, nil) t.Assert(l.FrontAll(), a) }) diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index acc10da60..a42cf26ea 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -476,7 +476,7 @@ func (m *AnyAnyMap) UnmarshalJSON(b []byte) error { m.data = make(map[interface{}]interface{}) } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for k, v := range data { diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index d04c49a09..f99fd77d4 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -475,7 +475,7 @@ func (m *IntAnyMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]interface{}) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -490,7 +490,7 @@ func (m *IntAnyMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = v diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index 4372246e3..3f8b8d411 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -446,7 +446,7 @@ func (m *IntIntMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]int) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -461,7 +461,7 @@ func (m *IntIntMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = gconv.Int(v) diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index e92f8023a..c5f5ac323 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -446,7 +446,7 @@ func (m *IntStrMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]string) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -461,7 +461,7 @@ func (m *IntStrMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = gconv.String(v) diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 1ee97612e..04d70f664 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -471,7 +471,7 @@ func (m *StrAnyMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]interface{}) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index 7a51f6ad9..7bc03cd34 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -449,7 +449,7 @@ func (m *StrIntMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]int) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -464,7 +464,7 @@ func (m *StrIntMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[k] = gconv.Int(v) diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 04f6c0698..ae68640c8 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -449,7 +449,7 @@ func (m *StrStrMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]string) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index 224cb9f63..790998da7 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -530,7 +530,7 @@ func (m *ListMap) UnmarshalJSON(b []byte) error { m.list = glist.New() } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for key, value := range data { diff --git a/container/gmap/gmap_z_unit_any_any_test.go b/container/gmap/gmap_z_unit_any_any_test.go index 4a06b4e04..3a5c78592 100644 --- a/container/gmap/gmap_z_unit_any_any_test.go +++ b/container/gmap/gmap_z_unit_any_any_test.go @@ -255,7 +255,7 @@ func Test_AnyAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.New() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -269,7 +269,7 @@ func Test_AnyAnyMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.Map - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_int_any_test.go b/container/gmap/gmap_z_unit_int_any_test.go index c6ac46486..2c6bc25ea 100644 --- a/container/gmap/gmap_z_unit_int_any_test.go +++ b/container/gmap/gmap_z_unit_int_any_test.go @@ -245,7 +245,7 @@ func Test_IntAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntAnyMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_int_int_test.go b/container/gmap/gmap_z_unit_int_int_test.go index 37b940152..762ba223f 100644 --- a/container/gmap/gmap_z_unit_int_int_test.go +++ b/container/gmap/gmap_z_unit_int_int_test.go @@ -251,7 +251,7 @@ func Test_IntIntMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntIntMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_int_str_test.go b/container/gmap/gmap_z_unit_int_str_test.go index 2b4292ef1..6a91b00f6 100644 --- a/container/gmap/gmap_z_unit_int_str_test.go +++ b/container/gmap/gmap_z_unit_int_str_test.go @@ -249,7 +249,7 @@ func Test_IntStrMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntStrMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_list_map_test.go b/container/gmap/gmap_z_unit_list_map_test.go index 045d673a7..4a226cfca 100644 --- a/container/gmap/gmap_z_unit_list_map_test.go +++ b/container/gmap/gmap_z_unit_list_map_test.go @@ -204,7 +204,7 @@ func Test_ListMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewListMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -219,7 +219,7 @@ func Test_ListMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.ListMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_any_test.go b/container/gmap/gmap_z_unit_str_any_test.go index 22180c901..1cee92de2 100644 --- a/container/gmap/gmap_z_unit_str_any_test.go +++ b/container/gmap/gmap_z_unit_str_any_test.go @@ -243,7 +243,7 @@ func Test_StrAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrAnyMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -257,7 +257,7 @@ func Test_StrAnyMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrAnyMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_int_test.go b/container/gmap/gmap_z_unit_str_int_test.go index 56ba1141a..eb80c7760 100644 --- a/container/gmap/gmap_z_unit_str_int_test.go +++ b/container/gmap/gmap_z_unit_str_int_test.go @@ -247,7 +247,7 @@ func Test_StrIntMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrIntMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -261,7 +261,7 @@ func Test_StrIntMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrIntMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_str_test.go b/container/gmap/gmap_z_unit_str_str_test.go index 0dfffd3a5..e64a36af1 100644 --- a/container/gmap/gmap_z_unit_str_str_test.go +++ b/container/gmap/gmap_z_unit_str_str_test.go @@ -244,7 +244,7 @@ func Test_StrStrMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrStrMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -258,7 +258,7 @@ func Test_StrStrMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrStrMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_tree_map_test.go b/container/gmap/gmap_z_unit_tree_map_test.go index e008d9986..470cf40f0 100644 --- a/container/gmap/gmap_z_unit_tree_map_test.go +++ b/container/gmap/gmap_z_unit_tree_map_test.go @@ -188,7 +188,7 @@ func Test_TreeMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewTreeMap(gutil.ComparatorString) - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -202,7 +202,7 @@ func Test_TreeMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.TreeMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gset/gset_any_set.go b/container/gset/gset_any_set.go index f6ceccfa6..9db79a47a 100644 --- a/container/gset/gset_any_set.go +++ b/container/gset/gset_any_set.go @@ -477,7 +477,7 @@ func (set *Set) UnmarshalJSON(b []byte) error { set.data = make(map[interface{}]struct{}) } var array []interface{} - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -496,7 +496,7 @@ func (set *Set) UnmarshalValue(value interface{}) (err error) { var array []interface{} switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceAny(value) } diff --git a/container/gset/gset_int_set.go b/container/gset/gset_int_set.go index 18d6ca9e2..39a98d9f3 100644 --- a/container/gset/gset_int_set.go +++ b/container/gset/gset_int_set.go @@ -437,7 +437,7 @@ func (set *IntSet) UnmarshalJSON(b []byte) error { set.data = make(map[int]struct{}) } var array []int - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -456,7 +456,7 @@ func (set *IntSet) UnmarshalValue(value interface{}) (err error) { var array []int switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceInt(value) } diff --git a/container/gset/gset_str_set.go b/container/gset/gset_str_set.go index d800af22e..68b4ec6bc 100644 --- a/container/gset/gset_str_set.go +++ b/container/gset/gset_str_set.go @@ -465,7 +465,7 @@ func (set *StrSet) UnmarshalJSON(b []byte) error { set.data = make(map[string]struct{}) } var array []string - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -484,7 +484,7 @@ func (set *StrSet) UnmarshalValue(value interface{}) (err error) { var array []string switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceStr(value) } diff --git a/container/gset/gset_z_unit_any_test.go b/container/gset/gset_z_unit_any_test.go index d9d6e6479..bef113955 100644 --- a/container/gset/gset_z_unit_any_test.go +++ b/container/gset/gset_z_unit_any_test.go @@ -327,7 +327,7 @@ func TestSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains("a"), true) t.Assert(a2.Contains("b"), true) @@ -336,7 +336,7 @@ func TestSet_Json(t *testing.T) { t.Assert(a2.Contains("e"), false) var a3 gset.Set - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Contains("a"), true) t.Assert(a3.Contains("b"), true) diff --git a/container/gset/gset_z_unit_int_test.go b/container/gset/gset_z_unit_int_test.go index e45fa21d4..618200691 100644 --- a/container/gset/gset_z_unit_int_test.go +++ b/container/gset/gset_z_unit_int_test.go @@ -358,7 +358,7 @@ func TestIntSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.NewIntSet() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains(1), true) t.Assert(a2.Contains(2), true) @@ -367,7 +367,7 @@ func TestIntSet_Json(t *testing.T) { t.Assert(a2.Contains(5), false) var a3 gset.IntSet - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a2.Contains(1), true) t.Assert(a2.Contains(2), true) diff --git a/container/gset/gset_z_unit_str_test.go b/container/gset/gset_z_unit_str_test.go index 33586cd8e..ab57cf6ca 100644 --- a/container/gset/gset_z_unit_str_test.go +++ b/container/gset/gset_z_unit_str_test.go @@ -404,7 +404,7 @@ func TestStrSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.NewStrSet() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains("a"), true) t.Assert(a2.Contains("b"), true) @@ -413,7 +413,7 @@ func TestStrSet_Json(t *testing.T) { t.Assert(a2.Contains("e"), false) var a3 gset.StrSet - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Contains("a"), true) t.Assert(a3.Contains("b"), true) diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index 33a8d45dd..7ccf0fd1c 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -937,7 +937,7 @@ func (tree *RedBlackTree) UnmarshalJSON(b []byte) error { tree.comparator = gutil.ComparatorString } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for k, v := range data { diff --git a/container/gtype/interface.go b/container/gtype/interface.go index acb26f46c..8cd554dde 100644 --- a/container/gtype/interface.go +++ b/container/gtype/interface.go @@ -58,7 +58,7 @@ func (v *Interface) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (v *Interface) UnmarshalJSON(b []byte) error { var i interface{} - err := json.Unmarshal(b, &i) + err := json.UnmarshalUseNumber(b, &i) if err != nil { return err } diff --git a/container/gtype/z_unit_bool_test.go b/container/gtype/z_unit_bool_test.go index 297f5de85..b30738a84 100644 --- a/container/gtype/z_unit_bool_test.go +++ b/container/gtype/z_unit_bool_test.go @@ -55,16 +55,16 @@ func Test_Bool_JSON(t *testing.T) { gtest.C(t, func(t *gtest.T) { var err error i := gtype.NewBool() - err = json.Unmarshal([]byte("true"), &i) + err = json.UnmarshalUseNumber([]byte("true"), &i) t.Assert(err, nil) t.Assert(i.Val(), true) - err = json.Unmarshal([]byte("false"), &i) + err = json.UnmarshalUseNumber([]byte("false"), &i) t.Assert(err, nil) t.Assert(i.Val(), false) - err = json.Unmarshal([]byte("1"), &i) + err = json.UnmarshalUseNumber([]byte("1"), &i) t.Assert(err, nil) t.Assert(i.Val(), true) - err = json.Unmarshal([]byte("0"), &i) + err = json.UnmarshalUseNumber([]byte("0"), &i) t.Assert(err, nil) t.Assert(i.Val(), false) }) @@ -78,7 +78,7 @@ func Test_Bool_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBool() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i.Val()) }) @@ -91,7 +91,7 @@ func Test_Bool_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBool() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i.Val()) }) diff --git a/container/gtype/z_unit_byte_test.go b/container/gtype/z_unit_byte_test.go index 0a6c77df4..86ba6cfb9 100644 --- a/container/gtype/z_unit_byte_test.go +++ b/container/gtype/z_unit_byte_test.go @@ -53,7 +53,7 @@ func Test_Byte_JSON(t *testing.T) { gtest.C(t, func(t *gtest.T) { var err error i := gtype.NewByte() - err = json.Unmarshal([]byte("49"), &i) + err = json.UnmarshalUseNumber([]byte("49"), &i) t.Assert(err, nil) t.Assert(i.Val(), "49") }) diff --git a/container/gtype/z_unit_bytes_test.go b/container/gtype/z_unit_bytes_test.go index 234307ae8..49145d290 100644 --- a/container/gtype/z_unit_bytes_test.go +++ b/container/gtype/z_unit_bytes_test.go @@ -39,7 +39,7 @@ func Test_Bytes_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBytes() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), b) }) diff --git a/container/gtype/z_unit_float32_test.go b/container/gtype/z_unit_float32_test.go index 14914ed27..7aff7f79c 100644 --- a/container/gtype/z_unit_float32_test.go +++ b/container/gtype/z_unit_float32_test.go @@ -40,7 +40,7 @@ func Test_Float32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewFloat32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_float64_test.go b/container/gtype/z_unit_float64_test.go index 717e0500a..1ba6cfa1e 100644 --- a/container/gtype/z_unit_float64_test.go +++ b/container/gtype/z_unit_float64_test.go @@ -38,7 +38,7 @@ func Test_Float64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewFloat64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_int32_test.go b/container/gtype/z_unit_int32_test.go index c2b7d5690..cf2250ae5 100644 --- a/container/gtype/z_unit_int32_test.go +++ b/container/gtype/z_unit_int32_test.go @@ -51,7 +51,7 @@ func Test_Int32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_int64_test.go b/container/gtype/z_unit_int64_test.go index 85a0bbea8..8b5c6a299 100644 --- a/container/gtype/z_unit_int64_test.go +++ b/container/gtype/z_unit_int64_test.go @@ -50,7 +50,7 @@ func Test_Int64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_int_test.go b/container/gtype/z_unit_int_test.go index 49e44b2f2..939ef81e9 100644 --- a/container/gtype/z_unit_int_test.go +++ b/container/gtype/z_unit_int_test.go @@ -50,7 +50,7 @@ func Test_Int_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_interface_test.go b/container/gtype/z_unit_interface_test.go index 990951b53..130102f63 100644 --- a/container/gtype/z_unit_interface_test.go +++ b/container/gtype/z_unit_interface_test.go @@ -40,7 +40,7 @@ func Test_Interface_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.New() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), s) }) diff --git a/container/gtype/z_unit_string_test.go b/container/gtype/z_unit_string_test.go index 16132474d..3b42cca41 100644 --- a/container/gtype/z_unit_string_test.go +++ b/container/gtype/z_unit_string_test.go @@ -38,7 +38,7 @@ func Test_String_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewString() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), s) }) diff --git a/container/gtype/z_unit_uint32_test.go b/container/gtype/z_unit_uint32_test.go index 1e9d0a508..abe188dad 100644 --- a/container/gtype/z_unit_uint32_test.go +++ b/container/gtype/z_unit_uint32_test.go @@ -50,7 +50,7 @@ func Test_Uint32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_uint64_test.go b/container/gtype/z_unit_uint64_test.go index 1c97b53c5..ab05217ff 100644 --- a/container/gtype/z_unit_uint64_test.go +++ b/container/gtype/z_unit_uint64_test.go @@ -55,7 +55,7 @@ func Test_Uint64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_uint_test.go b/container/gtype/z_unit_uint_test.go index d9237940a..4f314856e 100644 --- a/container/gtype/z_unit_uint_test.go +++ b/container/gtype/z_unit_uint_test.go @@ -49,7 +49,7 @@ func Test_Uint_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index 1babba781..36480d71a 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -189,7 +189,7 @@ func (v *Var) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (v *Var) UnmarshalJSON(b []byte) error { var i interface{} - err := json.Unmarshal(b, &i) + err := json.UnmarshalUseNumber(b, &i) if err != nil { return err } diff --git a/container/gvar/gvar_z_unit_json_test.go b/container/gvar/gvar_z_unit_json_test.go index 53dc856f3..dde33a732 100644 --- a/container/gvar/gvar_z_unit_json_test.go +++ b/container/gvar/gvar_z_unit_json_test.go @@ -41,7 +41,7 @@ func TestVar_Json(t *testing.T) { b, err := json.Marshal(s) t.Assert(err, nil) - err = json.Unmarshal(b, v) + err = json.UnmarshalUseNumber(b, v) t.Assert(err, nil) t.Assert(v.String(), s) }) @@ -52,7 +52,7 @@ func TestVar_Json(t *testing.T) { b, err := json.Marshal(s) t.Assert(err, nil) - err = json.Unmarshal(b, &v) + err = json.UnmarshalUseNumber(b, &v) t.Assert(err, nil) t.Assert(v.String(), s) }) diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 31fd0007d..bafd477bc 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -488,7 +488,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } else { // Other cache, it needs conversion. var result Result - if err = json.Unmarshal(v.Bytes(), &result); err != nil { + if err = json.UnmarshalUseNumber(v.Bytes(), &result); err != nil { return nil, err } else { return result, nil diff --git a/encoding/gjson/gjson.go b/encoding/gjson/gjson.go index df30d9e75..573acce39 100644 --- a/encoding/gjson/gjson.go +++ b/encoding/gjson/gjson.go @@ -22,7 +22,7 @@ const ( defaultSplitChar = '.' ) -// The customized JSON struct. +// Json is the customized JSON struct. type Json struct { mu *rwmutex.RWMutex p *interface{} // Pointer for hierarchical data access, it's the root of data in default. @@ -30,8 +30,8 @@ type Json struct { vc bool // Violence Check(false in default), which is used to access data when the hierarchical data key contains separator char. } -// Option for Json object creating. -type Option struct { +// Options for Json object creating. +type Options struct { Safe bool // Mark this object is for in concurrent-safe usage. Tags string // Custom priority tags for decoding. StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64. diff --git a/encoding/gjson/gjson_api_new_load.go b/encoding/gjson/gjson_api_new_load.go index 8759f2e71..bd6dcb504 100644 --- a/encoding/gjson/gjson_api_new_load.go +++ b/encoding/gjson/gjson_api_new_load.go @@ -42,22 +42,22 @@ func New(data interface{}, safe ...bool) *Json { // The parameter specifies whether using this Json object in concurrent-safe context, which // is false in default. func NewWithTag(data interface{}, tags string, safe ...bool) *Json { - option := Option{ + option := Options{ Tags: tags, } if len(safe) > 0 && safe[0] { option.Safe = true } - return NewWithOption(data, option) + return NewWithOptions(data, option) } -// NewWithOption creates a Json object with any variable type of , but should be a map +// NewWithOptions creates a Json object with any variable type of , but should be a map // or slice for data access reason, or it will make no sense. -func NewWithOption(data interface{}, option Option) *Json { +func NewWithOptions(data interface{}, options Options) *Json { var j *Json switch data.(type) { case string, []byte: - if r, err := loadContentWithOption(data, option); err == nil { + if r, err := loadContentWithOptions(data, options); err == nil { j = r } else { j = &Json{ @@ -86,7 +86,7 @@ func NewWithOption(data interface{}, option Option) *Json { } case reflect.Map, reflect.Struct: i := interface{}(nil) - i = gconv.MapDeep(data, option.Tags) + i = gconv.MapDeep(data, options.Tags) j = &Json{ p: &i, c: byte(defaultSplitChar), @@ -100,7 +100,7 @@ func NewWithOption(data interface{}, option Option) *Json { } } } - j.mu = rwmutex.New(option.Safe) + j.mu = rwmutex.New(options.Safe) return j } @@ -111,56 +111,56 @@ func Load(path string, safe ...bool) (*Json, error) { } else { path = p } - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption(gfile.Ext(path), gfile.GetBytesWithCache(path), option) + return doLoadContentWithOptions(gfile.Ext(path), gfile.GetBytesWithCache(path), option) } // LoadJson creates a Json object from given JSON format content. func LoadJson(data interface{}, safe ...bool) (*Json, error) { - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption("json", gconv.Bytes(data), option) + return doLoadContentWithOptions("json", gconv.Bytes(data), option) } // LoadXml creates a Json object from given XML format content. func LoadXml(data interface{}, safe ...bool) (*Json, error) { - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption("xml", gconv.Bytes(data), option) + return doLoadContentWithOptions("xml", gconv.Bytes(data), option) } // LoadIni creates a Json object from given INI format content. func LoadIni(data interface{}, safe ...bool) (*Json, error) { - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption("ini", gconv.Bytes(data), option) + return doLoadContentWithOptions("ini", gconv.Bytes(data), option) } // LoadYaml creates a Json object from given YAML format content. func LoadYaml(data interface{}, safe ...bool) (*Json, error) { - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption("yaml", gconv.Bytes(data), option) + return doLoadContentWithOptions("yaml", gconv.Bytes(data), option) } // LoadToml creates a Json object from given TOML format content. func LoadToml(data interface{}, safe ...bool) (*Json, error) { - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption("toml", gconv.Bytes(data), option) + return doLoadContentWithOptions("toml", gconv.Bytes(data), option) } // LoadContent creates a Json object from given content, it checks the data type of @@ -186,11 +186,11 @@ func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, er if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF { content = content[3:] } - option := Option{} + option := Options{} if len(safe) > 0 && safe[0] { option.Safe = true } - return doLoadContentWithOption(dataType, content, option) + return doLoadContentWithOptions(dataType, content, option) } // IsValidDataType checks and returns whether given a valid data type for loading. @@ -208,36 +208,36 @@ func IsValidDataType(dataType string) bool { return false } -func loadContentWithOption(data interface{}, option Option) (*Json, error) { +func loadContentWithOptions(data interface{}, options Options) (*Json, error) { content := gconv.Bytes(data) if len(content) == 0 { - return NewWithOption(nil, option), nil + return NewWithOptions(nil, options), nil } - return loadContentTypeWithOption(checkDataType(content), content, option) + return loadContentTypeWithOptions(checkDataType(content), content, options) } -func loadContentTypeWithOption(dataType string, data interface{}, option Option) (*Json, error) { +func loadContentTypeWithOptions(dataType string, data interface{}, options Options) (*Json, error) { content := gconv.Bytes(data) if len(content) == 0 { - return NewWithOption(nil, option), nil + return NewWithOptions(nil, options), nil } //ignore UTF8-BOM if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF { content = content[3:] } - return doLoadContentWithOption(dataType, content, option) + return doLoadContentWithOptions(dataType, content, options) } // doLoadContent creates a Json object from given content. // It supports data content type as follows: // JSON, XML, INI, YAML and TOML. -func doLoadContentWithOption(dataType string, data []byte, option Option) (*Json, error) { +func doLoadContentWithOptions(dataType string, data []byte, options Options) (*Json, error) { var ( err error result interface{} ) if len(data) == 0 { - return NewWithOption(nil, option), nil + return NewWithOptions(nil, options), nil } if dataType == "" { dataType = checkDataType(data) @@ -270,7 +270,7 @@ func doLoadContentWithOption(dataType string, data []byte, option Option) (*Json return nil, err } decoder := json.NewDecoder(bytes.NewReader(data)) - if option.StrNumber { + if options.StrNumber { decoder.UseNumber() } if err := decoder.Decode(&result); err != nil { @@ -280,7 +280,7 @@ func doLoadContentWithOption(dataType string, data []byte, option Option) (*Json case string, []byte: return nil, fmt.Errorf(`json decoding failed for content: %s`, string(data)) } - return NewWithOption(result, option), nil + return NewWithOptions(result, options), nil } // checkDataType automatically checks and returns the data type for . @@ -291,7 +291,8 @@ func checkDataType(content []byte) string { return "json" } else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) { return "xml" - } else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) && + } else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && + !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) && ((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) || (gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) { return "yml" diff --git a/encoding/gjson/gjson_z_unit_basic_test.go b/encoding/gjson/gjson_z_unit_basic_test.go index 733adea8e..c89b615ad 100644 --- a/encoding/gjson/gjson_z_unit_basic_test.go +++ b/encoding/gjson/gjson_z_unit_basic_test.go @@ -474,7 +474,7 @@ func TestJson_Var(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { data := []byte("[9223372036854775807, 9223372036854775806]") - array := gjson.NewWithOption(data, gjson.Option{StrNumber: true}).Var().Array() + array := gjson.NewWithOptions(data, gjson.Options{StrNumber: true}).Var().Array() t.Assert(array, []uint64{9223372036854775807, 9223372036854775806}) }) } diff --git a/encoding/gjson/gjson_z_unit_implements_test.go b/encoding/gjson/gjson_z_unit_implements_test.go index 8264e1625..8350f3a29 100644 --- a/encoding/gjson/gjson_z_unit_implements_test.go +++ b/encoding/gjson/gjson_z_unit_implements_test.go @@ -20,7 +20,7 @@ func TestJson_UnmarshalJSON(t *testing.T) { data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) gtest.C(t, func(t *gtest.T) { j := gjson.New(nil) - err := json.Unmarshal(data, j) + err := json.UnmarshalUseNumber(data, j) t.Assert(err, nil) t.Assert(j.Get("n"), "123456789") t.Assert(j.Get("m"), g.Map{"k": "v"}) diff --git a/encoding/gjson/gjson_z_unit_new_test.go b/encoding/gjson/gjson_z_unit_new_test.go index b4f911379..359cafd64 100644 --- a/encoding/gjson/gjson_z_unit_new_test.go +++ b/encoding/gjson/gjson_z_unit_new_test.go @@ -98,7 +98,7 @@ func Test_New_HierarchicalStruct(t *testing.T) { }) } -func Test_NewWithOption(t *testing.T) { +func Test_NewWithOptions(t *testing.T) { gtest.C(t, func(t *gtest.T) { data := []byte("[9223372036854775807, 9223372036854775806]") array := gjson.New(data).Array() @@ -106,7 +106,7 @@ func Test_NewWithOption(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { data := []byte("[9223372036854775807, 9223372036854775806]") - array := gjson.NewWithOption(data, gjson.Option{StrNumber: true}).Array() + array := gjson.NewWithOptions(data, gjson.Options{StrNumber: true}).Array() t.Assert(array, []uint64{9223372036854775807, 9223372036854775806}) }) } diff --git a/internal/json/json.go b/internal/json/json.go index 1ed12ba79..ae6c38f13 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -8,6 +8,7 @@ package json import ( + "bytes" "encoding/json" "io" ) @@ -33,6 +34,13 @@ func Unmarshal(data []byte, v interface{}) error { return json.Unmarshal(data, v) } +// UnmarshalUseNumber decodes the json data bytes to target interface using number option. +func UnmarshalUseNumber(data []byte, v interface{}) error { + decoder := NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + return decoder.Decode(v) +} + // NewEncoder same as json.NewEncoder func NewEncoder(writer io.Writer) *json.Encoder { return json.NewEncoder(writer) diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index c9bfdcbec..d79788d65 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -320,7 +320,7 @@ func (r *Request) parseBody() { body = bytes.TrimSpace(body) // JSON format checks. if body[0] == '{' && body[len(body)-1] == '}' { - _ = json.Unmarshal(body, &r.bodyMap) + _ = json.UnmarshalUseNumber(body, &r.bodyMap) } // XML format checks. if len(body) > 5 && bytes.EqualFold(body[:5], xmlHeaderBytes) { diff --git a/net/ghttp/ghttp_unit_request_json_test.go b/net/ghttp/ghttp_unit_request_json_test.go index 09e68c72b..7feb32e52 100644 --- a/net/ghttp/ghttp_unit_request_json_test.go +++ b/net/ghttp/ghttp_unit_request_json_test.go @@ -144,7 +144,7 @@ func Test_Params_Json_Response(t *testing.T) { client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) map1 := make(map[string]interface{}) - err1 := json.Unmarshal([]byte(client.GetContent("/json1")), &map1) + err1 := json.UnmarshalUseNumber([]byte(client.GetContent("/json1")), &map1) t.Assert(err1, nil) t.Assert(len(map1), 4) t.Assert(map1["Name"], "john") @@ -153,7 +153,7 @@ func Test_Params_Json_Response(t *testing.T) { t.Assert(map1["password2"], "456") map2 := make(map[string]interface{}) - err2 := json.Unmarshal([]byte(client.GetContent("/json2")), &map2) + err2 := json.UnmarshalUseNumber([]byte(client.GetContent("/json2")), &map2) t.Assert(err2, nil) t.Assert(len(map2), 4) t.Assert(map2["Name"], "john") @@ -162,14 +162,14 @@ func Test_Params_Json_Response(t *testing.T) { t.Assert(map2["password2"], "456") map3 := make(map[string]interface{}) - err3 := json.Unmarshal([]byte(client.GetContent("/json3")), &map3) + err3 := json.UnmarshalUseNumber([]byte(client.GetContent("/json3")), &map3) t.Assert(err3, nil) t.Assert(len(map3), 2) t.Assert(map3["success"], "true") t.Assert(map3["message"], g.Map{"body": "测试", "code": 3, "error": "error"}) map4 := make(map[string]interface{}) - err4 := json.Unmarshal([]byte(client.GetContent("/json4")), &map4) + err4 := json.UnmarshalUseNumber([]byte(client.GetContent("/json4")), &map4) t.Assert(err4, nil) t.Assert(len(map4), 2) t.Assert(map4["success"], "true") diff --git a/net/gtrace/gtrace_carrier.go b/net/gtrace/gtrace_carrier.go index c0cd41979..997f2ea91 100644 --- a/net/gtrace/gtrace_carrier.go +++ b/net/gtrace/gtrace_carrier.go @@ -55,5 +55,5 @@ func (c Carrier) String() string { func (c Carrier) UnmarshalJSON(b []byte) error { carrier := NewCarrier(nil) - return json.Unmarshal(b, carrier) + return json.UnmarshalUseNumber(b, carrier) } diff --git a/os/gbuild/gbuild.go b/os/gbuild/gbuild.go index 7f5cd6e99..5447c0cb7 100644 --- a/os/gbuild/gbuild.go +++ b/os/gbuild/gbuild.go @@ -24,7 +24,7 @@ var ( func init() { if builtInVarStr != "" { - err := json.Unmarshal(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap) + err := json.UnmarshalUseNumber(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap) if err != nil { intlog.Error(err) } diff --git a/os/gproc/gproc_comm_receive.go b/os/gproc/gproc_comm_receive.go index b0bd9f39a..18a815553 100644 --- a/os/gproc/gproc_comm_receive.go +++ b/os/gproc/gproc_comm_receive.go @@ -89,7 +89,7 @@ func receiveTcpHandler(conn *gtcp.Conn) { if len(buffer) > 0 { // Package decoding. msg := new(MsgRequest) - if err := json.Unmarshal(buffer, msg); err != nil { + if err := json.UnmarshalUseNumber(buffer, msg); err != nil { //glog.Error(err) continue } diff --git a/os/gproc/gproc_comm_send.go b/os/gproc/gproc_comm_send.go index 25af7c453..b53c5132e 100644 --- a/os/gproc/gproc_comm_send.go +++ b/os/gproc/gproc_comm_send.go @@ -43,7 +43,7 @@ func Send(pid int, data []byte, group ...string) error { }) if len(result) > 0 { response := new(MsgResponse) - err = json.Unmarshal(result, response) + err = json.UnmarshalUseNumber(result, response) if err == nil { if response.Code != 1 { err = errors.New(response.Message) diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index 51936a499..fb0bc7ca6 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -175,7 +175,7 @@ func (s *StorageFile) GetSession(id string, ttl time.Duration, data *gmap.StrAny } } var m map[string]interface{} - if err = json.Unmarshal(content, &m); err != nil { + if err = json.UnmarshalUseNumber(content, &m); err != nil { return nil, err } if m == nil { diff --git a/os/gsession/gsession_storage_redis.go b/os/gsession/gsession_storage_redis.go index bd5714e66..8d26e2db6 100644 --- a/os/gsession/gsession_storage_redis.go +++ b/os/gsession/gsession_storage_redis.go @@ -126,7 +126,7 @@ func (s *StorageRedis) GetSession(id string, ttl time.Duration, data *gmap.StrAn return nil, nil } var m map[string]interface{} - if err = json.Unmarshal(content, &m); err != nil { + if err = json.UnmarshalUseNumber(content, &m); err != nil { return nil, err } if m == nil { diff --git a/os/gtime/gtime_z_unit_json_test.go b/os/gtime/gtime_z_unit_json_test.go index b79fd3230..781da86d6 100644 --- a/os/gtime/gtime_z_unit_json_test.go +++ b/os/gtime/gtime_z_unit_json_test.go @@ -50,7 +50,7 @@ func Test_Json_Pointer(t *testing.T) { gtest.C(t, func(t *gtest.T) { var t1 gtime.Time s := []byte(`"2006-01-02 15:04:05"`) - err := json.Unmarshal(s, &t1) + err := json.UnmarshalUseNumber(s, &t1) t.Assert(err, nil) t.Assert(t1.String(), "2006-01-02 15:04:05") }) diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 5563ddda8..abab585cb 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -56,7 +56,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] case string: // If it is a JSON string, automatically unmarshal it! if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.Unmarshal([]byte(r), &dataMap); err != nil { + if err := json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { return nil } } else { @@ -65,7 +65,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] case []byte: // If it is a JSON string, automatically unmarshal it! if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.Unmarshal(r, &dataMap); err != nil { + if err := json.UnmarshalUseNumber(r, &dataMap); err != nil { return nil } } else { diff --git a/util/gconv/gconv_maps.go b/util/gconv/gconv_maps.go index 8a05e9600..931b181cf 100644 --- a/util/gconv/gconv_maps.go +++ b/util/gconv/gconv_maps.go @@ -33,7 +33,7 @@ func Maps(value interface{}, tags ...string) []map[string]interface{} { case string: list := make([]map[string]interface{}, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal([]byte(r), &list); err != nil { + if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { return nil } return list @@ -44,7 +44,7 @@ func Maps(value interface{}, tags ...string) []map[string]interface{} { case []byte: list := make([]map[string]interface{}, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal(r, &list); err != nil { + if err := json.UnmarshalUseNumber(r, &list); err != nil { return nil } return list @@ -79,7 +79,7 @@ func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { case string: list := make([]map[string]interface{}, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal([]byte(r), &list); err != nil { + if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { return nil } return list @@ -90,7 +90,7 @@ func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { case []byte: list := make([]map[string]interface{}, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal(r, &list); err != nil { + if err := json.UnmarshalUseNumber(r, &list); err != nil { return nil } return list diff --git a/util/gconv/gconv_maptomap.go b/util/gconv/gconv_maptomap.go index 143d6d013..ded5ea744 100644 --- a/util/gconv/gconv_maptomap.go +++ b/util/gconv/gconv_maptomap.go @@ -36,20 +36,20 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } diff --git a/util/gconv/gconv_maptomaps.go b/util/gconv/gconv_maptomaps.go index 9e0d58306..f4bebc7dc 100644 --- a/util/gconv/gconv_maptomaps.go +++ b/util/gconv/gconv_maptomaps.go @@ -40,20 +40,20 @@ func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string] if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index 0d4924bd7..06416f863 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -83,20 +83,20 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 89e220ac4..8d3626d36 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -74,20 +74,20 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index 062c17d50..faa79d921 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -817,7 +817,7 @@ func Test_Struct_Complex(t *testing.T) { "errorMsg": null }` m := make(g.Map) - err := json.Unmarshal([]byte(data), &m) + err := json.UnmarshalUseNumber([]byte(data), &m) t.Assert(err, nil) model := new(XinYanModel) From f389688caaec8f27c4aae32f712de4e662498f01 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 23:02:21 +0800 Subject: [PATCH 26/70] add unit testing cases for package internal/json --- internal/json/json_test.go | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 internal/json/json_test.go diff --git a/internal/json/json_test.go b/internal/json/json_test.go new file mode 100644 index 000000000..7ff8b7341 --- /dev/null +++ b/internal/json/json_test.go @@ -0,0 +1,93 @@ +// 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 json_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/test/gtest" + "testing" +) + +type User struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +var ( + user = &User{ + Id: 1265476890672672808, + Name: "john", + } + userBytes, _ = json.Marshal(user) +) + +func TestMarshal(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + b, err := json.Marshal(user) + t.AssertNil(err) + t.Assert(b, `{"id":1265476890672672808,"name":"john"}`) + }) +} + +func TestMarshalIndent(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + b, err := json.MarshalIndent(user, "#", "@") + t.AssertNil(err) + t.Assert(b, `{ +#@"id": 1265476890672672808, +#@"name": "john" +#}`) + }) +} + +func TestUnmarshal(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var m map[string]interface{} + b, _ := json.Marshal(g.Map{ + "user": user, + }) + err := json.Unmarshal(b, &m) + t.AssertNil(err) + // precision lost for big int. + t.Assert(m["user"], g.Map{ + "id": 1265476890672672800, + "name": user.Name, + }) + }) +} + +func TestUnmarshalUseNumber(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var m map[string]interface{} + b, _ := json.Marshal(g.Map{ + "user": user, + }) + err := json.UnmarshalUseNumber(b, &m) + t.AssertNil(err) + t.Assert(m["user"], g.Map{ + "id": user.Id, + "name": user.Name, + }) + }) +} + +func TestValid(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := g.Map{ + `{}`: true, + `{"id":1,"name":"john"}`: true, + `1`: true, + `"john"`: true, + `"`: false, + ``: false, + } + for k, v := range m { + t.Assert(json.Valid([]byte(k)), v) + } + }) +} From 8aed1eca1332395454cf1bddced7c313ab5dc58f Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 23:02:44 +0800 Subject: [PATCH 27/70] version updates --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 78fbc6cf1..d2895b5ad 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.15.7" +const VERSION = "v1.16.0" const AUTHORS = "john" From d12409b11830cd038827b20b00f7c5dcf4c57519 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 23:13:31 +0800 Subject: [PATCH 28/70] revert json.UnmarshalUseNumber to json.Unmarshal for code in the example folder --- .example/container/garray/json_unmarshal.go | 2 +- .example/container/glist/json_unmarshal.go | 2 +- .example/container/gmap/gmap_json_unmarshal.go | 2 +- .example/container/gset/json_unmarshal.go | 2 +- .example/container/gtype/json_unmarshal.go | 2 +- .example/container/gvar/json_unmarshal.go | 2 +- .example/encoding/gjson/issue#IZXU2.go | 2 +- .example/net/gtcp/pkg_operations/common/funcs/funcs.go | 2 +- .example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.example/container/garray/json_unmarshal.go b/.example/container/garray/json_unmarshal.go index 99fb7adec..07e81adb0 100644 --- a/.example/container/garray/json_unmarshal.go +++ b/.example/container/garray/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *garray.IntArray } s := Student{} - json.UnmarshalUseNumber(b, &s) + json.Unmarshal(b, &s) fmt.Println(s) } diff --git a/.example/container/glist/json_unmarshal.go b/.example/container/glist/json_unmarshal.go index 32e654bcb..313dfe570 100644 --- a/.example/container/glist/json_unmarshal.go +++ b/.example/container/glist/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *glist.List } s := Student{} - json.UnmarshalUseNumber(b, &s) + json.Unmarshal(b, &s) fmt.Println(s) } diff --git a/.example/container/gmap/gmap_json_unmarshal.go b/.example/container/gmap/gmap_json_unmarshal.go index 01bdc4f36..af3c134a8 100644 --- a/.example/container/gmap/gmap_json_unmarshal.go +++ b/.example/container/gmap/gmap_json_unmarshal.go @@ -9,6 +9,6 @@ import ( func main() { m := gmap.Map{} s := []byte(`{"name":"john","score":100}`) - json.UnmarshalUseNumber(s, &m) + json.Unmarshal(s, &m) fmt.Println(m.Map()) } diff --git a/.example/container/gset/json_unmarshal.go b/.example/container/gset/json_unmarshal.go index f85afac05..4e90ce823 100644 --- a/.example/container/gset/json_unmarshal.go +++ b/.example/container/gset/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *gset.IntSet } s := Student{} - json.UnmarshalUseNumber(b, &s) + json.Unmarshal(b, &s) fmt.Println(s) } diff --git a/.example/container/gtype/json_unmarshal.go b/.example/container/gtype/json_unmarshal.go index 7a17a1971..6b72cab08 100644 --- a/.example/container/gtype/json_unmarshal.go +++ b/.example/container/gtype/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *gtype.Interface } s := Student{} - json.UnmarshalUseNumber(b, &s) + json.Unmarshal(b, &s) fmt.Println(s) } diff --git a/.example/container/gvar/json_unmarshal.go b/.example/container/gvar/json_unmarshal.go index a4a9deba3..31aa9843a 100644 --- a/.example/container/gvar/json_unmarshal.go +++ b/.example/container/gvar/json_unmarshal.go @@ -14,6 +14,6 @@ func main() { Scores *g.Var } s := Student{} - json.UnmarshalUseNumber(b, &s) + json.Unmarshal(b, &s) fmt.Println(s) } diff --git a/.example/encoding/gjson/issue#IZXU2.go b/.example/encoding/gjson/issue#IZXU2.go index f56152fa6..e96f428d3 100644 --- a/.example/encoding/gjson/issue#IZXU2.go +++ b/.example/encoding/gjson/issue#IZXU2.go @@ -140,7 +140,7 @@ var data = `{ func main() { struct1 := new(XinYanModel) - err := json.UnmarshalUseNumber([]byte(data), struct1) + err := json.Unmarshal([]byte(data), struct1) fmt.Println(err) fmt.Println(struct1) diff --git a/.example/net/gtcp/pkg_operations/common/funcs/funcs.go b/.example/net/gtcp/pkg_operations/common/funcs/funcs.go index 4d8c33d10..368874c1e 100644 --- a/.example/net/gtcp/pkg_operations/common/funcs/funcs.go +++ b/.example/net/gtcp/pkg_operations/common/funcs/funcs.go @@ -30,7 +30,7 @@ func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) { return nil, err } else { msg = &types.Msg{} - err = json.UnmarshalUseNumber(data, msg) + err = json.Unmarshal(data, msg) if err != nil { return nil, fmt.Errorf("invalid package structure: %s", err.Error()) } diff --git a/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go b/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go index b0820320c..b9758f893 100644 --- a/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go +++ b/.example/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go @@ -21,7 +21,7 @@ func main() { break } info := &types.NodeInfo{} - if err := json.UnmarshalUseNumber(data, info); err != nil { + if err := json.Unmarshal(data, info); err != nil { glog.Errorf("invalid package structure: %s", err.Error()) } else { glog.Println(info) From b2a15c259e24797c2965ae860747ca7dc90cd2a4 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 23:21:38 +0800 Subject: [PATCH 29/70] improve unit testing case for package internal/json --- internal/json/json_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/json/json_test.go b/internal/json/json_test.go index 7ff8b7341..4df795b27 100644 --- a/internal/json/json_test.go +++ b/internal/json/json_test.go @@ -23,7 +23,6 @@ var ( Id: 1265476890672672808, Name: "john", } - userBytes, _ = json.Marshal(user) ) func TestMarshal(t *testing.T) { From 33567ef33890eb97e52b32d8a86c66b5f1a881df Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 15 May 2021 23:42:39 +0800 Subject: [PATCH 30/70] remove unit testing file for package internal/json --- internal/json/json_test.go | 92 -------------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 internal/json/json_test.go diff --git a/internal/json/json_test.go b/internal/json/json_test.go deleted file mode 100644 index 4df795b27..000000000 --- a/internal/json/json_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// 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 json_test - -import ( - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/internal/json" - "github.com/gogf/gf/test/gtest" - "testing" -) - -type User struct { - Id int64 `json:"id"` - Name string `json:"name"` -} - -var ( - user = &User{ - Id: 1265476890672672808, - Name: "john", - } -) - -func TestMarshal(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - b, err := json.Marshal(user) - t.AssertNil(err) - t.Assert(b, `{"id":1265476890672672808,"name":"john"}`) - }) -} - -func TestMarshalIndent(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - b, err := json.MarshalIndent(user, "#", "@") - t.AssertNil(err) - t.Assert(b, `{ -#@"id": 1265476890672672808, -#@"name": "john" -#}`) - }) -} - -func TestUnmarshal(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var m map[string]interface{} - b, _ := json.Marshal(g.Map{ - "user": user, - }) - err := json.Unmarshal(b, &m) - t.AssertNil(err) - // precision lost for big int. - t.Assert(m["user"], g.Map{ - "id": 1265476890672672800, - "name": user.Name, - }) - }) -} - -func TestUnmarshalUseNumber(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var m map[string]interface{} - b, _ := json.Marshal(g.Map{ - "user": user, - }) - err := json.UnmarshalUseNumber(b, &m) - t.AssertNil(err) - t.Assert(m["user"], g.Map{ - "id": user.Id, - "name": user.Name, - }) - }) -} - -func TestValid(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - m := g.Map{ - `{}`: true, - `{"id":1,"name":"john"}`: true, - `1`: true, - `"john"`: true, - `"`: false, - ``: false, - } - for k, v := range m { - t.Assert(json.Valid([]byte(k)), v) - } - }) -} From a757fbd37d062f3f9f91e8bc625323aa10c638ea Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 00:07:06 +0800 Subject: [PATCH 31/70] improve package gcfg --- os/gcfg/gcfg.go | 433 +++---------------- os/gcfg/gcfg_config.go | 441 +++++++++++++++++--- os/gcfg/{gcfg_api.go => gcfg_config_api.go} | 0 os/gcfg/gcfg_error.go | 22 - os/gcfg/gcfg_instance.go | 48 --- 5 files changed, 452 insertions(+), 492 deletions(-) rename os/gcfg/{gcfg_api.go => gcfg_config_api.go} (100%) delete mode 100644 os/gcfg/gcfg_error.go delete mode 100644 os/gcfg/gcfg_instance.go diff --git a/os/gcfg/gcfg.go b/os/gcfg/gcfg.go index 76f5f7985..d9eac9f2b 100644 --- a/os/gcfg/gcfg.go +++ b/os/gcfg/gcfg.go @@ -8,32 +8,13 @@ package gcfg import ( - "bytes" - "errors" - "fmt" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/internal/intlog" - "github.com/gogf/gf/os/gcmd" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gmode" - - "github.com/gogf/gf/os/gres" - "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" - "github.com/gogf/gf/encoding/gjson" - "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/gfsnotify" - "github.com/gogf/gf/os/glog" - "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" ) -const ( - DefaultConfigFile = "config.toml" // The default configuration file name. - cmdEnvKey = "gf.gcfg" // Configuration key for command argument or environment. -) - -// Configuration struct. +// Config is the configuration manager. type Config struct { defaultName string // Default configuration file name. searchPaths *garray.StrArray // Searching path array. @@ -41,374 +22,82 @@ type Config struct { violenceCheck bool // Whether do violence check in value index searching. It affects the performance when set true(false in default). } -var ( - supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"} - resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} +const ( + DefaultName = "config" // DefaultName is the default group name for instance usage. + DefaultConfigFile = "config.toml" // DefaultConfigFile is the default configuration file name. + cmdEnvKey = "gf.gcfg" // cmdEnvKey is the configuration key for command argument or environment. + errorPrintKey = "gf.gcfg.errorprint" // errorPrintKey is used to specify the key controlling error printing to stdout. ) -// New returns a new configuration management object. -// The parameter `file` specifies the default configuration file name for reading. -func New(file ...string) *Config { +var ( + supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"} // All supported file types suffixes. + resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} // Prefix array for trying searching in resource manager. + instances = gmap.NewStrAnyMap(true) // Instances map containing configuration instances. + customConfigContentMap = gmap.NewStrStrMap(true) // Customized configuration content. +) + +// SetContent sets customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func SetContent(content string, file ...string) { name := DefaultConfigFile if len(file) > 0 { name = file[0] - } else { - // Custom default configuration file name from command line or environment. - if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" { - name = customFile - } } - c := &Config{ - defaultName: name, - searchPaths: garray.NewStrArray(true), - jsonMap: gmap.NewStrAnyMap(true), - } - // Customized dir path from env/cmd. - if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" { - if gfile.Exists(customPath) { - _ = c.SetPath(customPath) - } else { - if errorPrint() { - glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } } - } else { - // Dir path of working dir. - if err := c.AddPath(gfile.Pwd()); err != nil { - intlog.Error(err) - } - - // Dir path of main package. - if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { - if err := c.AddPath(mainPath); err != nil { - intlog.Error(err) - } - } - - // Dir path of binary. - if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { - if err := c.AddPath(selfPath); err != nil { - intlog.Error(err) - } - } - } - return c + customConfigContentMap.Set(name, content) + }) } -// SetPath sets the configuration directory path for file search. -// The parameter `path` can be absolute or relative path, -// but absolute path is strongly recommended. -func (c *Config) SetPath(path string) error { - var ( - isDir = false - realPath = "" - ) - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.searchPaths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - // Path not exist. - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) - c.searchPaths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) - } - err := errors.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - // Should be a directory. - if !isDir { - err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.searchPaths.Search(realPath) != -1 { - return nil - } - c.jsonMap.Clear() - c.searchPaths.Clear() - c.searchPaths.Append(realPath) - intlog.Print("SetPath:", realPath) - return nil -} - -// SetViolenceCheck sets whether to perform hierarchical conflict checking. -// This feature needs to be enabled when there is a level symbol in the key name. -// It is off in default. -// -// Note that, turning on this feature is quite expensive, and it is not recommended -// to allow separators in the key names. It is best to avoid this on the application side. -func (c *Config) SetViolenceCheck(check bool) { - c.violenceCheck = check - c.Clear() -} - -// AddPath adds a absolute or relative path to the search paths. -func (c *Config) AddPath(path string) error { - var ( - isDir = false - realPath = "" - ) - // It firstly checks the resource manager, - // and then checks the filesystem for the path. - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.searchPaths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) - c.searchPaths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) - } - err := gerror.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - if !isDir { - err := gerror.Newf(`[gcfg] AddPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.searchPaths.Search(realPath) != -1 { - return nil - } - c.searchPaths.Append(realPath) - intlog.Print("AddPath:", realPath) - return nil -} - -// SetFileName sets the default configuration file name. -func (c *Config) SetFileName(name string) *Config { - c.defaultName = name - return c -} - -// GetFileName returns the default configuration file name. -func (c *Config) GetFileName() string { - return c.defaultName -} - -// Available checks and returns whether configuration of given `file` is available. -func (c *Config) Available(file ...string) bool { - var name string - if len(file) > 0 && file[0] != "" { - name = file[0] - } else { - name = c.defaultName - } - if path, _ := c.GetFilePath(name); path != "" { - return true - } - if GetContent(name) != "" { - return true - } - return false -} - -// GetFilePath returns the absolute configuration file path for the given filename by `file`. -// If `file` is not passed, it returns the configuration file path of the default name. -// It returns an empty `path` string and an error if the given `file` does not exist. -func (c *Config) GetFilePath(file ...string) (path string, err error) { - name := c.defaultName +// GetContent returns customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func GetContent(file ...string) string { + name := DefaultConfigFile if len(file) > 0 { name = file[0] } - // Searching resource manager. - if !gres.IsEmpty() { - for _, v := range resourceTryFiles { - if file := gres.Get(v + name); file != nil { - path = file.Name() - return - } - } - c.searchPaths.RLockFunc(func(array []string) { - for _, prefix := range array { - for _, v := range resourceTryFiles { - if file := gres.Get(prefix + v + name); file != nil { - path = file.Name() - return - } - } - } - }) - } - c.autoCheckAndAddMainPkgPathToSearchPaths() - // Searching the file system. - c.searchPaths.RLockFunc(func(array []string) { - for _, prefix := range array { - prefix = gstr.TrimRight(prefix, `\/`) - if path, _ = gspath.Search(prefix, name); path != "" { - return - } - if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { - return - } - } - }) - // If it cannot find the path of `file`, it formats and returns a detailed error. - if path == "" { - var ( - buffer = bytes.NewBuffer(nil) - ) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) - c.searchPaths.RLockFunc(func(array []string) { - index := 1 - for _, v := range array { - v = gstr.TrimRight(v, `\/`) - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) - index++ - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) - index++ - } - }) - } else { - buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) - } - err = gerror.New(buffer.String()) - } - return + return customConfigContentMap.Get(name) } -// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main -// to the searching path list if it's currently in development environment. -func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { - if gmode.IsDevelop() { - mainPkgPath := gfile.MainPkgPath() - if mainPkgPath != "" { - if !c.searchPaths.Contains(mainPkgPath) { - c.searchPaths.Append(mainPkgPath) - } - } - } -} - -// getJson returns a *gjson.Json object for the specified `file` content. -// It would print error if file reading fails. It return nil if any error occurs. -func (c *Config) getJson(file ...string) *gjson.Json { - var name string - if len(file) > 0 && file[0] != "" { +// RemoveContent removes the global configuration with specified `file`. +// If `name` is not passed, it removes configuration of the default group name. +func RemoveContent(file ...string) { + name := DefaultConfigFile + if len(file) > 0 { name = file[0] - } else { - name = c.defaultName } - r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { - var ( - err error - content string - filePath string - ) - // The configured content can be any kind of data type different from its file type. - isFromConfigContent := true - if content = GetContent(name); content == "" { - isFromConfigContent = false - filePath, err = c.GetFilePath(name) - if err != nil && errorPrint() { - glog.Error(err) - } - if filePath == "" { - return nil - } - if file := gres.Get(filePath); file != nil { - content = string(file.Content()) - } else { - content = gfile.GetContents(filePath) + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } + customConfigContentMap.Remove(name) } - // Note that the underlying configuration json object operations are concurrent safe. - var ( - j *gjson.Json - ) - dataType := gfile.ExtName(name) - if gjson.IsValidDataType(dataType) && !isFromConfigContent { - j, err = gjson.LoadContentType(dataType, content, true) - } else { - j, err = gjson.LoadContent(content, true) - } - if err == nil { - j.SetViolenceCheck(c.violenceCheck) - // Add monitor for this configuration file, - // any changes of this file will refresh its cache in Config object. - if filePath != "" && !gres.Contains(filePath) { - _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { - c.jsonMap.Remove(name) - }) - if err != nil && errorPrint() { - glog.Error(err) - } - } - return j - } - if errorPrint() { - if filePath != "" { - glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) - } else { - glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) - } - } - return nil }) - if r != nil { - return r.(*gjson.Json) - } - return nil + + intlog.Printf(`RemoveContent: %s`, name) +} + +// ClearContent removes all global configuration contents. +func ClearContent() { + customConfigContentMap.Clear() + // Clear cache for all instances. + instances.LockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Config).jsonMap.Clear() + } + }) + + intlog.Print(`RemoveConfig`) +} + +// errorPrint checks whether printing error to stdout. +func errorPrint() bool { + return gcmd.GetOptWithEnv(errorPrintKey, true).Bool() } diff --git a/os/gcfg/gcfg_config.go b/os/gcfg/gcfg_config.go index 68adf275a..6034f1f88 100644 --- a/os/gcfg/gcfg_config.go +++ b/os/gcfg/gcfg_config.go @@ -7,72 +7,413 @@ package gcfg import ( + "bytes" + "errors" + "fmt" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/os/gfsnotify" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/os/gres" + "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gmode" ) -var ( - // Customized configuration content. - configs = gmap.NewStrStrMap(true) -) - -// SetContent sets customized configuration content for specified `file`. -// The `file` is unnecessary param, default is DefaultConfigFile. -func SetContent(content string, file ...string) { +// New returns a new configuration management object. +// The parameter `file` specifies the default configuration file name for reading. +func New(file ...string) *Config { name := DefaultConfigFile if len(file) > 0 { name = file[0] + } else { + // Custom default configuration file name from command line or environment. + if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" { + name = customFile + } } - // Clear file cache for instances which cached `name`. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsonMap.Remove(name) + c := &Config{ + defaultName: name, + searchPaths: garray.NewStrArray(true), + jsonMap: gmap.NewStrAnyMap(true), + } + // Customized dir path from env/cmd. + if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" { + if gfile.Exists(customPath) { + _ = c.SetPath(customPath) + } else { + if errorPrint() { + glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) } } - configs.Set(name, content) - }) -} + } else { + // Dir path of working dir. + if err := c.AddPath(gfile.Pwd()); err != nil { + intlog.Error(err) + } -// GetContent returns customized configuration content for specified `file`. -// The `file` is unnecessary param, default is DefaultConfigFile. -func GetContent(file ...string) string { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - return configs.Get(name) -} - -// RemoveContent removes the global configuration with specified `file`. -// If `name` is not passed, it removes configuration of the default group name. -func RemoveContent(file ...string) { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - // Clear file cache for instances which cached `name`. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsonMap.Remove(name) + // Dir path of main package. + if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { + if err := c.AddPath(mainPath); err != nil { + intlog.Error(err) } - configs.Remove(name) } - }) - intlog.Printf(`RemoveContent: %s`, name) + // Dir path of binary. + if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { + if err := c.AddPath(selfPath); err != nil { + intlog.Error(err) + } + } + } + return c } -// ClearContent removes all global configuration contents. -func ClearContent() { - configs.Clear() - // Clear cache for all instances. - instances.LockFunc(func(m map[string]interface{}) { - for _, v := range m { - v.(*Config).jsonMap.Clear() +// Instance returns an instance of Config with default settings. +// The parameter `name` is the name for the instance. But very note that, if the file "name.toml" +// exists in the configuration directory, it then sets it as the default configuration file. The +// toml file type is the default configuration file type. +func Instance(name ...string) *Config { + key := DefaultName + if len(name) > 0 && name[0] != "" { + key = name[0] + } + return instances.GetOrSetFuncLock(key, func() interface{} { + c := New() + // If it's not using default configuration or its configuration file is not available, + // it searches the possible configuration file according to the name and all supported + // file types. + if key != DefaultName || !c.Available() { + for _, fileType := range supportedFileTypes { + if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { + c.SetFileName(file) + break + } + } + } + return c + }).(*Config) +} + +// SetPath sets the configuration directory path for file search. +// The parameter `path` can be absolute or relative path, +// but absolute path is strongly recommended. +func (c *Config) SetPath(path string) error { + var ( + isDir = false + realPath = "" + ) + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + // Path not exist. + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) + } + err := errors.New(buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + // Should be a directory. + if !isDir { + err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.jsonMap.Clear() + c.searchPaths.Clear() + c.searchPaths.Append(realPath) + intlog.Print("SetPath:", realPath) + return nil +} + +// SetViolenceCheck sets whether to perform hierarchical conflict checking. +// This feature needs to be enabled when there is a level symbol in the key name. +// It is off in default. +// +// Note that, turning on this feature is quite expensive, and it is not recommended +// to allow separators in the key names. It is best to avoid this on the application side. +func (c *Config) SetViolenceCheck(check bool) { + c.violenceCheck = check + c.Clear() +} + +// AddPath adds a absolute or relative path to the search paths. +func (c *Config) AddPath(path string) error { + var ( + isDir = false + realPath = "" + ) + // It firstly checks the resource manager, + // and then checks the filesystem for the path. + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) + } + err := gerror.New(buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + if !isDir { + err := gerror.Newf(`[gcfg] AddPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.searchPaths.Append(realPath) + intlog.Print("AddPath:", realPath) + return nil +} + +// SetFileName sets the default configuration file name. +func (c *Config) SetFileName(name string) *Config { + c.defaultName = name + return c +} + +// GetFileName returns the default configuration file name. +func (c *Config) GetFileName() string { + return c.defaultName +} + +// Available checks and returns whether configuration of given `file` is available. +func (c *Config) Available(file ...string) bool { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + if path, _ := c.GetFilePath(name); path != "" { + return true + } + if GetContent(name) != "" { + return true + } + return false +} + +// GetFilePath returns the absolute configuration file path for the given filename by `file`. +// If `file` is not passed, it returns the configuration file path of the default name. +// It returns an empty `path` string and an error if the given `file` does not exist. +func (c *Config) GetFilePath(file ...string) (path string, err error) { + name := c.defaultName + if len(file) > 0 { + name = file[0] + } + // Searching resource manager. + if !gres.IsEmpty() { + for _, v := range resourceTryFiles { + if file := gres.Get(v + name); file != nil { + path = file.Name() + return + } + } + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + for _, v := range resourceTryFiles { + if file := gres.Get(prefix + v + name); file != nil { + path = file.Name() + return + } + } + } + }) + } + c.autoCheckAndAddMainPkgPathToSearchPaths() + // Searching the file system. + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + prefix = gstr.TrimRight(prefix, `\/`) + if path, _ = gspath.Search(prefix, name); path != "" { + return + } + if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { + return + } } }) - - intlog.Print(`RemoveConfig`) + // If it cannot find the path of `file`, it formats and returns a detailed error. + if path == "" { + var ( + buffer = bytes.NewBuffer(nil) + ) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) + c.searchPaths.RLockFunc(func(array []string) { + index := 1 + for _, v := range array { + v = gstr.TrimRight(v, `\/`) + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) + index++ + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) + index++ + } + }) + } else { + buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) + } + err = gerror.New(buffer.String()) + } + return +} + +// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main +// to the searching path list if it's currently in development environment. +func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { + if gmode.IsDevelop() { + mainPkgPath := gfile.MainPkgPath() + if mainPkgPath != "" { + if !c.searchPaths.Contains(mainPkgPath) { + c.searchPaths.Append(mainPkgPath) + } + } + } +} + +// getJson returns a *gjson.Json object for the specified `file` content. +// It would print error if file reading fails. It return nil if any error occurs. +func (c *Config) getJson(file ...string) *gjson.Json { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { + var ( + err error + content string + filePath string + ) + // The configured content can be any kind of data type different from its file type. + isFromConfigContent := true + if content = GetContent(name); content == "" { + isFromConfigContent = false + filePath, err = c.GetFilePath(name) + if err != nil && errorPrint() { + glog.Error(err) + } + if filePath == "" { + return nil + } + if file := gres.Get(filePath); file != nil { + content = string(file.Content()) + } else { + content = gfile.GetContents(filePath) + } + } + // Note that the underlying configuration json object operations are concurrent safe. + var ( + j *gjson.Json + ) + dataType := gfile.ExtName(name) + if gjson.IsValidDataType(dataType) && !isFromConfigContent { + j, err = gjson.LoadContentType(dataType, content, true) + } else { + j, err = gjson.LoadContent(content, true) + } + if err == nil { + j.SetViolenceCheck(c.violenceCheck) + // Add monitor for this configuration file, + // any changes of this file will refresh its cache in Config object. + if filePath != "" && !gres.Contains(filePath) { + _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { + c.jsonMap.Remove(name) + }) + if err != nil && errorPrint() { + glog.Error(err) + } + } + return j + } + if errorPrint() { + if filePath != "" { + glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) + } else { + glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) + } + } + return nil + }) + if r != nil { + return r.(*gjson.Json) + } + return nil } diff --git a/os/gcfg/gcfg_api.go b/os/gcfg/gcfg_config_api.go similarity index 100% rename from os/gcfg/gcfg_api.go rename to os/gcfg/gcfg_config_api.go diff --git a/os/gcfg/gcfg_error.go b/os/gcfg/gcfg_error.go deleted file mode 100644 index fd7df93b3..000000000 --- a/os/gcfg/gcfg_error.go +++ /dev/null @@ -1,22 +0,0 @@ -// 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 gcfg - -import ( - "github.com/gogf/gf/os/gcmd" -) - -const ( - // errorPrintKey is used to specify the key controlling error printing to stdout. - // This error is designed not to be returned by functions. - errorPrintKey = "gf.gcfg.errorprint" -) - -// errorPrint checks whether printing error to stdout. -func errorPrint() bool { - return gcmd.GetOptWithEnv(errorPrintKey, true).Bool() -} diff --git a/os/gcfg/gcfg_instance.go b/os/gcfg/gcfg_instance.go deleted file mode 100644 index 4ef396801..000000000 --- a/os/gcfg/gcfg_instance.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 gcfg - -import ( - "fmt" - "github.com/gogf/gf/container/gmap" -) - -const ( - // Default group name for instance usage. - DefaultName = "config" -) - -var ( - // Instances map containing configuration instances. - instances = gmap.NewStrAnyMap(true) -) - -// Instance returns an instance of Config with default settings. -// The parameter `name` is the name for the instance. But very note that, if the file "name.toml" -// exists in the configuration directory, it then sets it as the default configuration file. The -// toml file type is the default configuration file type. -func Instance(name ...string) *Config { - key := DefaultName - if len(name) > 0 && name[0] != "" { - key = name[0] - } - return instances.GetOrSetFuncLock(key, func() interface{} { - c := New() - // If it's not using default configuration or its configuration file is not available, - // it searches the possible configuration file according to the name and all supported - // file types. - if key != DefaultName || !c.Available() { - for _, fileType := range supportedFileTypes { - if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { - c.SetFileName(file) - break - } - } - } - return c - }).(*Config) -} From 522f6cb455d1241b63610ef4a5b142b996cfeae6 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 13:43:36 +0800 Subject: [PATCH 32/70] fix issue of failing in folder watching with no recursive option for package gfsnotify --- os/gfsnotify/gfsnotify.go | 4 +- os/gfsnotify/gfsnotify_watcher_loop.go | 86 +++++++++++++------------- os/gfsnotify/gfsnotify_z_unit_test.go | 25 ++++++++ 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/os/gfsnotify/gfsnotify.go b/os/gfsnotify/gfsnotify.go index 07817a8e5..619454189 100644 --- a/os/gfsnotify/gfsnotify.go +++ b/os/gfsnotify/gfsnotify.go @@ -91,8 +91,8 @@ func New() (*Watcher, error) { intlog.Printf("New watcher failed: %v", err) return nil, err } - w.startWatchLoop() - w.startEventLoop() + w.watchLoop() + w.eventLoop() return w, nil } diff --git a/os/gfsnotify/gfsnotify_watcher_loop.go b/os/gfsnotify/gfsnotify_watcher_loop.go index a05c663de..531225051 100644 --- a/os/gfsnotify/gfsnotify_watcher_loop.go +++ b/os/gfsnotify/gfsnotify_watcher_loop.go @@ -11,8 +11,8 @@ import ( "github.com/gogf/gf/internal/intlog" ) -// startWatchLoop starts the loop for event listening fro underlying inotify monitor. -func (w *Watcher) startWatchLoop() { +// watchLoop starts the loop for event listening fro underlying inotify monitor. +func (w *Watcher) watchLoop() { go func() { for { select { @@ -40,47 +40,8 @@ func (w *Watcher) startWatchLoop() { }() } -// getCallbacks searches and returns all callbacks with given . -// It also searches its parent for callbacks if they're recursive. -func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { - // Firstly add the callbacks of itself. - if v := w.callbacks.Get(path); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - callbacks = append(callbacks, callback) - } - } - // Secondly searches its parent for callbacks. - dirPath := fileDir(path) - if v := w.callbacks.Get(dirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - if callback.recursive { - callbacks = append(callbacks, callback) - } - } - } - // Lastly searches the parent recursively for callbacks. - for { - parentDirPath := fileDir(dirPath) - if parentDirPath == dirPath { - break - } - if v := w.callbacks.Get(parentDirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - if callback.recursive { - callbacks = append(callbacks, callback) - } - } - } - dirPath = parentDirPath - } - return -} - -// startEventLoop is the core event handler. -func (w *Watcher) startEventLoop() { +// eventLoop is the core event handler. +func (w *Watcher) eventLoop() { go func() { for { if v := w.events.Pop(); v != nil { @@ -171,3 +132,42 @@ func (w *Watcher) startEventLoop() { } }() } + +// getCallbacks searches and returns all callbacks with given . +// It also searches its parents for callbacks if they're recursive. +func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { + // Firstly add the callbacks of itself. + if v := w.callbacks.Get(path); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) + } + } + // Secondly searches its direct parent for callbacks. + // It is special handling here, which is the different between `recursive` and `not recursive` logic + // for direct parent folder of `path` that events are from. + dirPath := fileDir(path) + if v := w.callbacks.Get(dirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) + } + } + // Lastly searches all the parents of directory of `path` recursively for callbacks. + for { + parentDirPath := fileDir(dirPath) + if parentDirPath == dirPath { + break + } + if v := w.callbacks.Get(parentDirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + if callback.recursive { + callbacks = append(callbacks, callback) + } + } + } + dirPath = parentDirPath + } + return +} diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index a1f195dc1..2fecbb7f2 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -7,6 +7,7 @@ package gfsnotify_test import ( + "github.com/gogf/gf/container/garray" "testing" "time" @@ -191,3 +192,27 @@ func TestWatcher_Callback2(t *testing.T) { t.Assert(v2.Val(), 2) }) } + +func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + err error + array = garray.New(true) + dirPath = gfile.TempDir(gtime.TimestampNanoStr()) + ) + err = gfile.Mkdir(dirPath) + t.AssertNil(err) + + _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { + array.Append(1) + }, false) + t.AssertNil(err) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 0) + + err = gfile.PutContents(gfile.Join(dirPath, "1"), "1") + t.AssertNil(err) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 1) + }) +} From 302e234bfe8f3aff2894a548212c0a684a126897 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 19:17:04 +0800 Subject: [PATCH 33/70] comment update for package gfsnotify --- os/gfsnotify/gfsnotify_watcher.go | 1 + 1 file changed, 1 insertion(+) diff --git a/os/gfsnotify/gfsnotify_watcher.go b/os/gfsnotify/gfsnotify_watcher.go index 421b40a59..37067a988 100644 --- a/os/gfsnotify/gfsnotify_watcher.go +++ b/os/gfsnotify/gfsnotify_watcher.go @@ -61,6 +61,7 @@ func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), re } // addWithCallbackFunc adds the path to underlying monitor, creates and returns a callback object. +// Very note that if it calls multiple times with the same `path`, the latest one will overwrite the previous one. func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { // Check and convert the given path to absolute path. if t := fileRealPath(path); t == "" { From 2274a10cfd1109e85695f01aa75b78d382075ee4 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 19:59:34 +0800 Subject: [PATCH 34/70] fx issue #1250 --- internal/structs/structs_field.go | 10 ++++ os/gtime/gtime_time.go | 5 ++ util/gvalid/gvalid.go | 22 ++++++- util/gvalid/gvalid_validator_check_struct.go | 34 +++++------ util/gvalid/gvalid_z_unit_checkstruct_test.go | 59 +++++++++++++++++-- 5 files changed, 105 insertions(+), 25 deletions(-) diff --git a/internal/structs/structs_field.go b/internal/structs/structs_field.go index dda5a996e..81ae1d960 100644 --- a/internal/structs/structs_field.go +++ b/internal/structs/structs_field.go @@ -14,6 +14,16 @@ func (f *Field) Tag(key string) string { return f.Field.Tag.Get(key) } +// TagLookup returns the value associated with key in the tag string. +// If the key is present in the tag the value (which may be empty) +// is returned. Otherwise the returned value will be the empty string. +// The ok return value reports whether the value was explicitly set in +// the tag string. If the tag does not have the conventional format, +// the value returned by Lookup is unspecified. +func (f *Field) TagLookup(key string) (value string, ok bool) { + return f.Field.Tag.Lookup(key) +} + // IsEmbedded returns true if the given field is an anonymous field (embedded) func (f *Field) IsEmbedded() bool { return f.Field.Anonymous diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 871013ed4..d3a02594a 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -464,3 +464,8 @@ func (t *Time) UnmarshalText(data []byte) error { } return gerror.Newf(`invalid time value: %s`, data) } + +// NoValidation marks this struct object will not be validated by package gvalid. +func (t *Time) NoValidation() { + +} diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 4a660a954..2a2e1316a 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -64,17 +64,35 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} +// doCheckStructWithParamMapInput is used for struct validation for internal function. +type doCheckStructWithParamMapInput struct { + Object interface{} // Can be type of struct/*struct. + ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. + UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. + CustomRules interface{} // Custom validation rules. + CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. +} + +// apiNoValidation is an interface that marks current struct not validated by package `gvalid`. +type apiNoValidation interface { + NoValidation() +} + const ( // regular expression pattern for single validation rule. singleRulePattern = `^([\w-]+):{0,1}(.*)` invalidRulesErrKey = "invalid_rules" invalidParamsErrKey = "invalid_params" invalidObjectErrKey = "invalid_object" + + // no validation tag name for struct attribute. + noValidationTagName = "nv" ) var ( - // defaultValidator is the default validator for package functions. - defaultValidator = New() + defaultValidator = New() // defaultValidator is the default validator for package functions. + structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. + aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. // all internal error keys. internalErrKeyMap = map[string]string{ diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 6aab61a49..5ed23c45e 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -10,24 +10,9 @@ import ( "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "reflect" "strings" ) -// doCheckStructWithParamMapInput is used for struct validation for internal function. -type doCheckStructWithParamMapInput struct { - Object interface{} // Can be type of struct/*struct. - ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. - UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. - CustomRules interface{} // Custom validation rules. - CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. -} - -var ( - structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. - aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. -) - // CheckStruct validates struct and returns the error result. // // The parameter `object` should be type of struct/*struct. @@ -70,16 +55,27 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) *Error { var ( - errorMaps = make(ErrorMap) // Returning error. + // Returning error. + errorMaps = make(ErrorMap) ) fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority, true) if err != nil { return newErrorStr("invalid_object", err.Error()) } - // It checks the struct recursively the its attribute is also a struct. + // It checks the struct recursively the its attribute is an embedded struct. for _, field := range fieldMap { - if field.OriginalKind() == reflect.Struct { - if err := v.CheckStruct(field.Value, input.CustomRules, input.CustomErrorMessageMap); err != nil { + if field.IsEmbedded() { + // No validation interface implements check. + if _, ok := field.Value.Interface().(apiNoValidation); ok { + continue + } + if _, ok := field.TagLookup(noValidationTagName); ok { + continue + } + recursiveInput := doCheckStructWithParamMapInput{} + recursiveInput = *input + recursiveInput.Object = field.Value + if err := v.doCheckStructWithParamMap(&recursiveInput); err != nil { // It merges the errors into single error map. for k, m := range err.errors { errorMaps[k] = m diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index da76790fb..ac65bd8b4 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -228,6 +228,57 @@ func Test_CheckStruct(t *testing.T) { }) } +func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Base struct { + Time *gtime.Time + } + type Object struct { + Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + obj.Time = gtime.Now() + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + type Base struct { + Name string + Type int + } + type Object struct { + Base Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) +} + func Test_CheckStruct_With_EmbeddedObject(t *testing.T) { gtest.C(t, func(t *gtest.T) { type Pass struct { @@ -261,13 +312,13 @@ func Test_CheckStruct_With_StructAttribute(t *testing.T) { Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` } type User struct { - Id int - Name string `valid:"name@required#请输入您的姓名"` - Passwords Pass + Pass + Id int + Name string `valid:"name@required#请输入您的姓名"` } user := &User{ Name: "", - Passwords: Pass{ + Pass: Pass{ Pass1: "1", Pass2: "2", }, From e5734425ba1c74fee5f14d37a6d8ac06999a0eab Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 20:18:21 +0800 Subject: [PATCH 35/70] debug --- os/gfsnotify/gfsnotify_z_unit_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index 2fecbb7f2..42df8cb32 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -7,6 +7,7 @@ package gfsnotify_test import ( + "fmt" "github.com/gogf/gf/container/garray" "testing" "time" @@ -204,6 +205,7 @@ func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { t.AssertNil(err) _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { + fmt.Println(event.String()) array.Append(1) }, false) t.AssertNil(err) From b84ca9cc13cf601ff5580ccf6f19e421d896d4f1 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 20:25:00 +0800 Subject: [PATCH 36/70] debug --- os/gfsnotify/gfsnotify_z_unit_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index 42df8cb32..ca9730b05 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -212,8 +212,9 @@ func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { time.Sleep(time.Millisecond * 100) t.Assert(array.Len(), 0) - err = gfile.PutContents(gfile.Join(dirPath, "1"), "1") + f, err := gfile.Create(gfile.Join(dirPath, "1")) t.AssertNil(err) + t.AssertNil(f.Close()) time.Sleep(time.Millisecond * 100) t.Assert(array.Len(), 1) }) From 0dfd9688249c9f2853e2447a1fb982b869225b16 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 17 May 2021 21:26:39 +0800 Subject: [PATCH 37/70] comment update for package gconv/gproc --- os/gfsnotify/gfsnotify_z_unit_test.go | 3 +- os/gproc/gproc_manager.go | 34 ++++++++++++----------- os/gproc/gproc_signal.go | 2 +- util/gconv/gconv.go | 40 +++++++++++++-------------- util/gconv/gconv_maps.go | 4 +-- util/gconv/gconv_slice_any.go | 2 +- util/gconv/gconv_slice_float.go | 6 ++-- util/gconv/gconv_slice_int.go | 6 ++-- util/gconv/gconv_slice_str.go | 2 +- util/gconv/gconv_slice_uint.go | 6 ++-- util/gconv/gconv_time.go | 16 +++++------ 11 files changed, 61 insertions(+), 60 deletions(-) diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index ca9730b05..e1e5b1f95 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -7,7 +7,6 @@ package gfsnotify_test import ( - "fmt" "github.com/gogf/gf/container/garray" "testing" "time" @@ -205,7 +204,7 @@ func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { t.AssertNil(err) _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { - fmt.Println(event.String()) + //fmt.Println(event.String()) array.Append(1) }, false) t.AssertNil(err) diff --git a/os/gproc/gproc_manager.go b/os/gproc/gproc_manager.go index 8f3cea114..cc448eb65 100644 --- a/os/gproc/gproc_manager.go +++ b/os/gproc/gproc_manager.go @@ -12,26 +12,27 @@ import ( "github.com/gogf/gf/container/gmap" ) -// 进程管理器 +// Manager is a process manager maintaining multiple processes. type Manager struct { - processes *gmap.IntAnyMap // 所管理的子进程map + processes *gmap.IntAnyMap // Process id to Process object mapping. } -// 创建一个进程管理器 +// NewManager creates and returns a new process manager. func NewManager() *Manager { return &Manager{ processes: gmap.NewIntAnyMap(true), } } -// 创建一个进程(不执行) +// NewProcess creates and returns a Process object. func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { p := NewProcess(path, args, environment) p.Manager = m return p } -// 获取当前进程管理器中的一个进程 +// GetProcess retrieves and returns a Process object. +// It returns nil if it does not find the process with given `pid`. func (m *Manager) GetProcess(pid int) *Process { if v := m.processes.Get(pid); v != nil { return v.(*Process) @@ -39,7 +40,8 @@ func (m *Manager) GetProcess(pid int) *Process { return nil } -// 添加一个已存在进程到进程管理器中 +// AddProcess adds a process to current manager. +// It does nothing if the process with given `pid` does not exist. func (m *Manager) AddProcess(pid int) { if m.processes.Get(pid) == nil { if process, err := os.FindProcess(pid); err == nil { @@ -50,12 +52,12 @@ func (m *Manager) AddProcess(pid int) { } } -// 移除进程管理器中的指定进程 +// RemoveProcess removes a process from current manager. func (m *Manager) RemoveProcess(pid int) { m.processes.Remove(pid) } -// 获取所有的进程对象,构成列表返回 +// Processes retrieves and returns all processes in current manager. func (m *Manager) Processes() []*Process { processes := make([]*Process, 0) m.processes.RLockFunc(func(m map[int]interface{}) { @@ -66,12 +68,12 @@ func (m *Manager) Processes() []*Process { return processes } -// 获取所有的进程pid,构成列表返回 +// Pids retrieves and returns all process id array in current manager. func (m *Manager) Pids() []int { return m.processes.Keys() } -// 等待所有子进程结束 +// WaitAll waits until all process exit. func (m *Manager) WaitAll() { processes := m.Processes() if len(processes) > 0 { @@ -81,7 +83,7 @@ func (m *Manager) WaitAll() { } } -// 关闭所有的进程 +// KillAll kills all processes in current manager. func (m *Manager) KillAll() error { for _, p := range m.Processes() { if err := p.Kill(); err != nil { @@ -91,7 +93,7 @@ func (m *Manager) KillAll() error { return nil } -// 向所有进程发送信号量 +// SignalAll sends a signal `sig` to all processes in current manager. func (m *Manager) SignalAll(sig os.Signal) error { for _, p := range m.Processes() { if err := p.Signal(sig); err != nil { @@ -101,24 +103,24 @@ func (m *Manager) SignalAll(sig os.Signal) error { return nil } -// 向所有进程发送消息 +// Send sends data bytes to all processes in current manager. func (m *Manager) Send(data []byte) { for _, p := range m.Processes() { p.Send(data) } } -// 向指定进程发送消息 +// SendTo sneds data bytes to specified processe in current manager. func (m *Manager) SendTo(pid int, data []byte) error { return Send(pid, data) } -// 清空管理器 +// Clear removes all processes in current manager. func (m *Manager) Clear() { m.processes.Clear() } -// 当前进程总数 +// Size returns the size of processes in current manager. func (m *Manager) Size() int { return m.processes.Size() } diff --git a/os/gproc/gproc_signal.go b/os/gproc/gproc_signal.go index 40ad5464b..6a2b9b700 100644 --- a/os/gproc/gproc_signal.go +++ b/os/gproc/gproc_signal.go @@ -53,7 +53,7 @@ func AddSigHandlerShutdown(handler SigHandler) { } } -// ListenSignal blocks and does signal listening and handling. +// Listen blocks and does signal listening and handling. func Listen() { signals := make([]os.Signal, 0) for sig, _ := range signalHandlerMap { diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 271c23cb7..af74e7369 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -45,7 +45,7 @@ var ( StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"} ) -// Convert converts the variable `i` to the type `t`, the type `t` is specified by string. +// Convert converts the variable `any` to the type `t`, the type `t` is specified by string. // The optional parameter `params` is used for additional necessary parameter for this conversion. // It supports common types conversion as its conversion based on type name string. func Convert(any interface{}, t string, params ...interface{}) interface{} { @@ -277,7 +277,7 @@ func Convert(any interface{}, t string, params ...interface{}) interface{} { } } -// Byte converts `i` to byte. +// Byte converts `any` to byte. func Byte(any interface{}) byte { if v, ok := any.(byte); ok { return v @@ -285,7 +285,7 @@ func Byte(any interface{}) byte { return Uint8(any) } -// Bytes converts `i` to []byte. +// Bytes converts `any` to []byte. func Bytes(any interface{}) []byte { if any == nil { return nil @@ -303,7 +303,7 @@ func Bytes(any interface{}) []byte { } } -// Rune converts `i` to rune. +// Rune converts `any` to rune. func Rune(any interface{}) rune { if v, ok := any.(rune); ok { return v @@ -311,7 +311,7 @@ func Rune(any interface{}) rune { return rune(Int32(any)) } -// Runes converts `i` to []rune. +// Runes converts `any` to []rune. func Runes(any interface{}) []rune { if v, ok := any.([]rune); ok { return v @@ -319,7 +319,7 @@ func Runes(any interface{}) []rune { return []rune(String(any)) } -// String converts `i` to string. +// String converts `any` to string. // It's most common used converting function. func String(any interface{}) string { if any == nil { @@ -422,8 +422,8 @@ func String(any interface{}) string { } } -// Bool converts `i` to bool. -// It returns false if `i` is: false, "", 0, "false", "off", "no", empty slice/map. +// Bool converts `any` to bool. +// It returns false if `any` is: false, "", 0, "false", "off", "no", empty slice/map. func Bool(any interface{}) bool { if any == nil { return false @@ -467,7 +467,7 @@ func Bool(any interface{}) bool { } } -// Int converts `i` to int. +// Int converts `any` to int. func Int(any interface{}) int { if any == nil { return 0 @@ -478,7 +478,7 @@ func Int(any interface{}) int { return int(Int64(any)) } -// Int8 converts `i` to int8. +// Int8 converts `any` to int8. func Int8(any interface{}) int8 { if any == nil { return 0 @@ -489,7 +489,7 @@ func Int8(any interface{}) int8 { return int8(Int64(any)) } -// Int16 converts `i` to int16. +// Int16 converts `any` to int16. func Int16(any interface{}) int16 { if any == nil { return 0 @@ -500,7 +500,7 @@ func Int16(any interface{}) int16 { return int16(Int64(any)) } -// Int32 converts `i` to int32. +// Int32 converts `any` to int32. func Int32(any interface{}) int32 { if any == nil { return 0 @@ -511,7 +511,7 @@ func Int32(any interface{}) int32 { return int32(Int64(any)) } -// Int64 converts `i` to int64. +// Int64 converts `any` to int64. func Int64(any interface{}) int64 { if any == nil { return 0 @@ -592,7 +592,7 @@ func Int64(any interface{}) int64 { } } -// Uint converts `i` to uint. +// Uint converts `any` to uint. func Uint(any interface{}) uint { if any == nil { return 0 @@ -603,7 +603,7 @@ func Uint(any interface{}) uint { return uint(Uint64(any)) } -// Uint8 converts `i` to uint8. +// Uint8 converts `any` to uint8. func Uint8(any interface{}) uint8 { if any == nil { return 0 @@ -614,7 +614,7 @@ func Uint8(any interface{}) uint8 { return uint8(Uint64(any)) } -// Uint16 converts `i` to uint16. +// Uint16 converts `any` to uint16. func Uint16(any interface{}) uint16 { if any == nil { return 0 @@ -625,7 +625,7 @@ func Uint16(any interface{}) uint16 { return uint16(Uint64(any)) } -// Uint32 converts `i` to uint32. +// Uint32 converts `any` to uint32. func Uint32(any interface{}) uint32 { if any == nil { return 0 @@ -636,7 +636,7 @@ func Uint32(any interface{}) uint32 { return uint32(Uint64(any)) } -// Uint64 converts `i` to uint64. +// Uint64 converts `any` to uint64. func Uint64(any interface{}) uint64 { if any == nil { return 0 @@ -699,7 +699,7 @@ func Uint64(any interface{}) uint64 { } } -// Float32 converts `i` to float32. +// Float32 converts `any` to float32. func Float32(any interface{}) float32 { if any == nil { return 0 @@ -720,7 +720,7 @@ func Float32(any interface{}) float32 { } } -// Float64 converts `i` to float64. +// Float64 converts `any` to float64. func Float64(any interface{}) float64 { if any == nil { return 0 diff --git a/util/gconv/gconv_maps.go b/util/gconv/gconv_maps.go index 931b181cf..cc0dd33fb 100644 --- a/util/gconv/gconv_maps.go +++ b/util/gconv/gconv_maps.go @@ -23,7 +23,7 @@ func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string] return Structs(params, pointer, mapping...) } -// Maps converts `i` to []map[string]interface{}. +// Maps converts `value` to []map[string]interface{}. // Note that it automatically checks and converts json string to []map if `value` is string/[]byte. func Maps(value interface{}, tags ...string) []map[string]interface{} { if value == nil { @@ -68,7 +68,7 @@ func Maps(value interface{}, tags ...string) []map[string]interface{} { } } -// MapsDeep converts `i` to []map[string]interface{} recursively. +// MapsDeep converts `value` to []map[string]interface{} recursively. // // TODO completely implement the recursive converting for all types. func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go index e249c56ab..90b5b372b 100644 --- a/util/gconv/gconv_slice_any.go +++ b/util/gconv/gconv_slice_any.go @@ -15,7 +15,7 @@ func SliceAny(any interface{}) []interface{} { return Interfaces(any) } -// Interfaces converts `i` to []interface{}. +// Interfaces converts `any` to []interface{}. func Interfaces(any interface{}) []interface{} { if any == nil { return nil diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go index d3d442c03..86dd7ebe9 100644 --- a/util/gconv/gconv_slice_float.go +++ b/util/gconv/gconv_slice_float.go @@ -23,12 +23,12 @@ func SliceFloat64(any interface{}) []float64 { return Floats(any) } -// Floats converts `i` to []float64. +// Floats converts `any` to []float64. func Floats(any interface{}) []float64 { return Float64s(any) } -// Float32s converts `i` to []float32. +// Float32s converts `any` to []float32. func Float32s(any interface{}) []float32 { if any == nil { return nil @@ -148,7 +148,7 @@ func Float32s(any interface{}) []float32 { return array } -// Float64s converts `i` to []float64. +// Float64s converts `any` to []float64. func Float64s(any interface{}) []float64 { if any == nil { return nil diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go index f19afadd4..1195d2053 100644 --- a/util/gconv/gconv_slice_int.go +++ b/util/gconv/gconv_slice_int.go @@ -23,7 +23,7 @@ func SliceInt64(any interface{}) []int64 { return Int64s(any) } -// Ints converts `i` to []int. +// Ints converts `any` to []int. func Ints(any interface{}) []int { if any == nil { return nil @@ -153,7 +153,7 @@ func Ints(any interface{}) []int { return array } -// Int32s converts `i` to []int32. +// Int32s converts `any` to []int32. func Int32s(any interface{}) []int32 { if any == nil { return nil @@ -283,7 +283,7 @@ func Int32s(any interface{}) []int32 { return array } -// Int64s converts `i` to []int64. +// Int64s converts `any` to []int64. func Int64s(any interface{}) []int64 { if any == nil { return nil diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go index ab003cec2..0d235d529 100644 --- a/util/gconv/gconv_slice_str.go +++ b/util/gconv/gconv_slice_str.go @@ -13,7 +13,7 @@ func SliceStr(any interface{}) []string { return Strings(any) } -// Strings converts `i` to []string. +// Strings converts `any` to []string. func Strings(any interface{}) []string { if any == nil { return nil diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go index f2277573b..2cb3321d9 100644 --- a/util/gconv/gconv_slice_uint.go +++ b/util/gconv/gconv_slice_uint.go @@ -23,7 +23,7 @@ func SliceUint64(any interface{}) []uint64 { return Uint64s(any) } -// Uints converts `i` to []uint. +// Uints converts `any` to []uint. func Uints(any interface{}) []uint { if any == nil { return nil @@ -149,7 +149,7 @@ func Uints(any interface{}) []uint { return array } -// Uint32s converts `i` to []uint32. +// Uint32s converts `any` to []uint32. func Uint32s(any interface{}) []uint32 { if any == nil { return nil @@ -274,7 +274,7 @@ func Uint32s(any interface{}) []uint32 { return array } -// Uint64s converts `i` to []uint64. +// Uint64s converts `any` to []uint64. func Uint64s(any interface{}) []uint64 { if any == nil { return nil diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index 006188985..98aac418d 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -13,7 +13,7 @@ import ( "github.com/gogf/gf/os/gtime" ) -// Time converts `i` to time.Time. +// Time converts `any` to time.Time. func Time(any interface{}, format ...string) time.Time { // It's already this type. if len(format) == 0 { @@ -27,9 +27,9 @@ func Time(any interface{}, format ...string) time.Time { return time.Time{} } -// Duration converts `i` to time.Duration. -// If `i` is string, then it uses time.ParseDuration to convert it. -// If `i` is numeric, then it converts `i` as nanoseconds. +// Duration converts `any` to time.Duration. +// If `any` is string, then it uses time.ParseDuration to convert it. +// If `any` is numeric, then it converts `any` as nanoseconds. func Duration(any interface{}) time.Duration { // It's already this type. if v, ok := any.(time.Duration); ok { @@ -43,10 +43,10 @@ func Duration(any interface{}) time.Duration { return time.Duration(Int64(any)) } -// GTime converts `i` to *gtime.Time. -// The parameter `format` can be used to specify the format of `i`. -// If no `format` given, it converts `i` using gtime.NewFromTimeStamp if `i` is numeric, -// or using gtime.StrToTime if `i` is string. +// GTime converts `any` to *gtime.Time. +// The parameter `format` can be used to specify the format of `any`. +// If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, +// or using gtime.StrToTime if `any` is string. func GTime(any interface{}, format ...string) *gtime.Time { if any == nil { return nil From c8c28770fb2ce6f805e1ff1beb9826bbdc5cee41 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 18 May 2021 20:51:31 +0800 Subject: [PATCH 38/70] change Error from struct to interface for package gvalid;error string update for package gdb --- .example/i18n/gi18n/gi18n-dir.go | 5 +- .example/i18n/gi18n/gi18n-file.go | 5 +- .example/i18n/gi18n/gi18n.go | 12 +++- .example/i18n/gi18n/http_view_i18n.go | 12 ++-- .../net/ghttp/client/middleware/client.go | 64 +++++++++++++++++++ .../net/ghttp/client/middleware/server.go | 17 +++++ .../ghttp/server/request/json-xml/test2.go | 2 +- .../server/request/validation/validation2.go | 2 +- database/gdb/gdb.go | 12 +++- frame/gins/gins_database.go | 4 +- i18n/gi18n/gi18n.go | 1 - i18n/gi18n/gi18n_manager.go | 1 - util/gvalid/gvalid.go | 8 +-- util/gvalid/gvalid_error.go | 49 ++++++++------ util/gvalid/gvalid_validator_check.go | 6 +- util/gvalid/gvalid_validator_check_map.go | 4 +- util/gvalid/gvalid_validator_check_struct.go | 22 ++++--- util/gvalid/gvalid_z_example_test.go | 2 + util/gvalid/gvalid_z_unit_custom_rule_test.go | 8 +-- util/gvalid/gvalid_z_unit_i18n_test.go | 2 +- 20 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 .example/net/ghttp/client/middleware/client.go create mode 100644 .example/net/ghttp/client/middleware/server.go diff --git a/.example/i18n/gi18n/gi18n-dir.go b/.example/i18n/gi18n/gi18n-dir.go index e9359c5ea..552298176 100644 --- a/.example/i18n/gi18n/gi18n-dir.go +++ b/.example/i18n/gi18n/gi18n-dir.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/i18n/gi18n" @@ -13,6 +14,6 @@ func main() { if err != nil { panic(err) } - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) + fmt.Println(t.Translate(context.TODO(), `hello`)) + fmt.Println(t.Translate(context.TODO(), `{#hello}{#world}!`)) } diff --git a/.example/i18n/gi18n/gi18n-file.go b/.example/i18n/gi18n/gi18n-file.go index 1a6d337c3..f1a4423ee 100644 --- a/.example/i18n/gi18n/gi18n-file.go +++ b/.example/i18n/gi18n/gi18n-file.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/i18n/gi18n" @@ -13,6 +14,6 @@ func main() { if err != nil { panic(err) } - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) + fmt.Println(t.Translate(context.TODO(), `hello`)) + fmt.Println(t.Translate(context.TODO(), `{#hello}{#world}!`)) } diff --git a/.example/i18n/gi18n/gi18n.go b/.example/i18n/gi18n/gi18n.go index 630d5a954..17d830c0d 100644 --- a/.example/i18n/gi18n/gi18n.go +++ b/.example/i18n/gi18n/gi18n.go @@ -1,8 +1,10 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" ) func main() { @@ -10,6 +12,12 @@ func main() { orderId = 865271654 orderAmount = 99.8 ) - fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount)) - fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount)) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `en`), + `{#OrderPaid}`, orderId, orderAmount, + )) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `zh-CN`), + `{#OrderPaid}`, orderId, orderAmount, + )) } diff --git a/.example/i18n/gi18n/http_view_i18n.go b/.example/i18n/gi18n/http_view_i18n.go index ca5956c7b..1f1cc2302 100644 --- a/.example/i18n/gi18n/http_view_i18n.go +++ b/.example/i18n/gi18n/http_view_i18n.go @@ -2,15 +2,19 @@ package main import ( "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" "github.com/gogf/gf/net/ghttp" ) func main() { - g.I18n().SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n") s := g.Server() - s.BindHandler("/", func(r *ghttp.Request) { - r.Response.WriteTplContent(`{#hello}{#world}!`, g.Map{ - "I18nLanguage": r.Get("lang", "zh-CN"), + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + r.SetCtx(gi18n.WithLanguage(r.Context(), "zh-CN")) + r.Middleware.Next() + }) + group.ALL("/", func(r *ghttp.Request) { + r.Response.WriteTplContent(`{#hello}{#world}!`) }) }) s.SetPort(8199) diff --git a/.example/net/ghttp/client/middleware/client.go b/.example/net/ghttp/client/middleware/client.go new file mode 100644 index 000000000..7ba07b4bf --- /dev/null +++ b/.example/net/ghttp/client/middleware/client.go @@ -0,0 +1,64 @@ +package main + +import ( + "bytes" + "fmt" + "github.com/gogf/gf/container/garray" + "github.com/gogf/gf/crypto/gmd5" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/guid" + "github.com/gogf/gf/util/gutil" + "io/ioutil" + "net/http" +) + +const ( + appId = "123" + appSecret = "456" +) + +// 注入统一的接口签名参数 +func injectSignature(jsonContent []byte) []byte { + var m map[string]interface{} + _ = json.Unmarshal(jsonContent, &m) + if len(m) > 0 { + m["appid"] = appId + m["nonce"] = guid.S() + m["timestamp"] = gtime.Timestamp() + var ( + keyArray = garray.NewSortedStrArrayFrom(gutil.Keys(m)) + sigContent string + ) + keyArray.Iterator(func(k int, v string) bool { + sigContent += v + sigContent += gconv.String(m[v]) + return true + }) + m["signature"] = gmd5.MustEncryptString(gmd5.MustEncryptString(sigContent) + appSecret) + jsonContent, _ = json.Marshal(m) + } + return jsonContent +} + +func main() { + c := g.Client() + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + bodyBytes, _ := ioutil.ReadAll(r.Body) + if len(bodyBytes) > 0 { + // 注入签名相关参数,修改Request原有的提交参数 + bodyBytes = injectSignature(bodyBytes) + r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + r.ContentLength = int64(len(bodyBytes)) + } + return c.Next(r) + }) + content := c.ContentJson().PostContent("http://127.0.0.1:8199/", g.Map{ + "name": "goframe", + "site": "https://goframe.org", + }) + fmt.Println(content) +} diff --git a/.example/net/ghttp/client/middleware/server.go b/.example/net/ghttp/client/middleware/server.go new file mode 100644 index 000000000..8770dd634 --- /dev/null +++ b/.example/net/ghttp/client/middleware/server.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + r.Response.Write(r.GetMap()) + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/request/json-xml/test2.go b/.example/net/ghttp/server/request/json-xml/test2.go index 5e3aed050..ced00e20d 100644 --- a/.example/net/ghttp/server/request/json-xml/test2.go +++ b/.example/net/ghttp/server/request/json-xml/test2.go @@ -25,7 +25,7 @@ func main() { //fmt.Println(r.GetBody()) if err := r.Parse(&req); err != nil { // Validation error. - if v, ok := err.(*gvalid.Error); ok { + if v, ok := err.(gvalid.Error); ok { r.Response.WriteJsonExit(RegisterRes{ Code: 1, Error: v.FirstString(), diff --git a/.example/net/ghttp/server/request/validation/validation2.go b/.example/net/ghttp/server/request/validation/validation2.go index ccee1e3f9..2800feab1 100644 --- a/.example/net/ghttp/server/request/validation/validation2.go +++ b/.example/net/ghttp/server/request/validation/validation2.go @@ -24,7 +24,7 @@ func main() { var req *RegisterReq if err := r.Parse(&req); err != nil { // Validation error. - if v, ok := err.(*gvalid.Error); ok { + if v, ok := err.(gvalid.Error); ok { r.Response.WriteJsonExit(RegisterRes{ Code: 1, Error: v.FirstString(), diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 24e6228db..8421900d5 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -339,7 +339,7 @@ func New(group ...string) (db DB, err error) { defer configs.RUnlock() if len(configs.config) < 1 { - return nil, gerror.New("empty database configuration") + return nil, gerror.New("database configuration is empty, please set the database configuration before using") } if _, ok := configs.config[groupName]; ok { if node, err := getConfigNodeByGroup(groupName, true); err == nil { @@ -358,13 +358,19 @@ func New(group ...string) (db DB, err error) { } return c.db, nil } else { - return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type)) + return nil, gerror.Newf( + `cannot find database driver for specified database type "%s", did you misspell type name "%s" or forget importing the database driver?`, + node.Type, node.Type, + ) } } else { return nil, err } } else { - return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName)) + return nil, gerror.Newf( + `database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`, + groupName, groupName, + ) } } diff --git a/frame/gins/gins_database.go b/frame/gins/gins_database.go index 2e92392ed..26e97b5fe 100644 --- a/frame/gins/gins_database.go +++ b/frame/gins/gins_database.go @@ -54,14 +54,14 @@ func Database(name ...string) gdb.DB { if exampleConfigFilePath, _ := Config().GetFilePath(exampleFileName); exampleConfigFilePath != "" { panic(gerror.Wrapf( err, - `configuration file "%s" not found, but found "%s", did you miss renaming the configuration example file?`, + `configuration file "%s" not found, but found "%s", did you miss renaming the example configuration file?`, Config().GetFileName(), exampleFileName, )) } else { panic(gerror.Wrapf( err, - `configuration file "%s" not found, did you miss the configuration file or the file name setting?`, + `configuration file "%s" not found, did you miss the configuration file or the misspell the configuration file name?`, Config().GetFileName(), )) } diff --git a/i18n/gi18n/gi18n.go b/i18n/gi18n/gi18n.go index bb2cc07e4..460239807 100644 --- a/i18n/gi18n/gi18n.go +++ b/i18n/gi18n/gi18n.go @@ -41,7 +41,6 @@ func TranslateFormat(ctx context.Context, format string, values ...interface{}) } // Translate translates with configured language and returns the translated content. -// The parameter specifies custom translation language ignoring configured language. func Translate(ctx context.Context, content string) string { return Instance().Translate(ctx, content) } diff --git a/i18n/gi18n/gi18n_manager.go b/i18n/gi18n/gi18n_manager.go index 9dc6a8639..0949c1f44 100644 --- a/i18n/gi18n/gi18n_manager.go +++ b/i18n/gi18n/gi18n_manager.go @@ -138,7 +138,6 @@ func (m *Manager) TranslateFormat(ctx context.Context, format string, values ... } // Translate translates with configured language. -// The parameter specifies custom translation language ignoring configured language. func (m *Manager) Translate(ctx context.Context, content string) string { m.init() m.mu.RLock() diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 2a2e1316a..b9d234dae 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -189,7 +189,7 @@ var ( // string/map/struct/*struct. // The optional parameter `params` specifies the extra validation parameters for some rules // like: required-*、same、different, etc. -func Check(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { +func Check(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) Error { return defaultValidator.Ctx(ctx).Check(value, rules, messages, params...) } @@ -198,7 +198,7 @@ func Check(ctx context.Context, value interface{}, rules string, messages interf // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) *Error { +func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) Error { return defaultValidator.Ctx(ctx).CheckMap(params, rules, messages...) } @@ -208,7 +208,7 @@ func CheckMap(ctx context.Context, params interface{}, rules interface{}, messag // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) *Error { +func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) Error { return defaultValidator.Ctx(ctx).CheckStruct(object, rules, messages...) } @@ -218,7 +218,7 @@ func CheckStruct(ctx context.Context, object interface{}, rules interface{}, mes // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func CheckStructWithParamMap(ctx context.Context, object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error { +func CheckStructWithParamMap(ctx context.Context, object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) Error { return defaultValidator.Ctx(ctx).CheckStructWithParamMap(object, paramMap, rules, messages...) } diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 7a2a30141..c55da3f56 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -14,19 +14,28 @@ import ( ) // Error is the validation error for validation result. -type Error struct { - rules []string // Rules by sequence, which is used for keeping error sequence. - errors ErrorMap // Error map. - firstKey string // The first error rule key(nil in default). - firstItem map[string]string // The first error rule value(nil in default). +type Error interface { + Current() error + Error() string + FirstItem() (key string, messages map[string]string) + FirstRule() (rule string, err string) + FirstString() (err string) + Map() map[string]string + Maps() map[string]map[string]string + String() string + Strings() (errs []string) } -// ErrorMap is the validation error map: -// map[field]map[rule]message -type ErrorMap map[string]map[string]string +// validationError is the validation error for validation result. +type validationError struct { + rules []string // Rules by sequence, which is used for keeping error sequence. + errors map[string]map[string]string // Error map:map[field]map[rule]message + firstKey string // The first error rule key(nil in default). + firstItem map[string]string // The first error rule value(nil in default). +} // newError creates and returns a validation error. -func newError(rules []string, errors map[string]map[string]string) *Error { +func newError(rules []string, errors map[string]map[string]string) *validationError { for field, m := range errors { for k, v := range m { v = strings.Replace(v, ":attribute", field, -1) @@ -36,14 +45,14 @@ func newError(rules []string, errors map[string]map[string]string) *Error { } errors[field] = m } - return &Error{ + return &validationError{ rules: rules, errors: errors, } } // newErrorStr creates and returns a validation error by string. -func newErrorStr(key, err string) *Error { +func newErrorStr(key, err string) *validationError { return newError(nil, map[string]map[string]string{ "__gvalid__": { key: err, @@ -52,7 +61,7 @@ func newErrorStr(key, err string) *Error { } // Map returns the first error message as map. -func (e *Error) Map() map[string]string { +func (e *validationError) Map() map[string]string { if e == nil { return map[string]string{} } @@ -61,7 +70,7 @@ func (e *Error) Map() map[string]string { } // Maps returns all error messages as map. -func (e *Error) Maps() ErrorMap { +func (e *validationError) Maps() map[string]map[string]string { if e == nil { return nil } @@ -69,7 +78,7 @@ func (e *Error) Maps() ErrorMap { } // FirstItem returns the field name and error messages for the first validation rule error. -func (e *Error) FirstItem() (key string, messages map[string]string) { +func (e *validationError) FirstItem() (key string, messages map[string]string) { if e == nil { return "", map[string]string{} } @@ -97,7 +106,7 @@ func (e *Error) FirstItem() (key string, messages map[string]string) { } // FirstRule returns the first error rule and message string. -func (e *Error) FirstRule() (rule string, err string) { +func (e *validationError) FirstRule() (rule string, err string) { if e == nil { return "", "" } @@ -127,7 +136,7 @@ func (e *Error) FirstRule() (rule string, err string) { // FirstString returns the first error message as string. // Note that the returned message might be different if it has no sequence. -func (e *Error) FirstString() (err string) { +func (e *validationError) FirstString() (err string) { if e == nil { return "" } @@ -136,7 +145,7 @@ func (e *Error) FirstString() (err string) { } // Current is alis of FirstString, which implements interface gerror.ApiCurrent. -func (e *Error) Current() error { +func (e *validationError) Current() error { if e == nil { return nil } @@ -145,7 +154,7 @@ func (e *Error) Current() error { } // String returns all error messages as string, multiple error messages joined using char ';'. -func (e *Error) String() string { +func (e *validationError) String() string { if e == nil { return "" } @@ -153,7 +162,7 @@ func (e *Error) String() string { } // Error implements interface of error.Error. -func (e *Error) Error() string { +func (e *validationError) Error() string { if e == nil { return "" } @@ -161,7 +170,7 @@ func (e *Error) Error() string { } // Strings returns all error messages as string array. -func (e *Error) Strings() (errs []string) { +func (e *validationError) Strings() (errs []string) { if e == nil { return []string{} } diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index c37e4c66f..86e367cce 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -35,12 +35,12 @@ type apiTime interface { // string/map/struct/*struct. // The optional parameter `params` specifies the extra validation parameters for some rules // like: required-*、same、different, etc. -func (v *Validator) Check(value interface{}, rules string, messages interface{}, paramMap ...interface{}) *Error { +func (v *Validator) Check(value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { return v.doCheck("", value, rules, messages, paramMap...) } // doCheck does the really rules validation for single key-value. -func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) *Error { +func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { // If there's no validation rules, it does nothing and returns quickly. if rules == "" { return nil @@ -136,7 +136,7 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message index++ } if len(errorMsgArray) > 0 { - return newError([]string{rules}, ErrorMap{ + return newError([]string{rules}, map[string]map[string]string{ key: errorMsgArray, }) } diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index 3229e2752..2f24759a3 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -16,7 +16,7 @@ import ( // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) 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 @@ -25,7 +25,7 @@ func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ... checkRules = make(map[string]string) customMsgs = make(CustomMsg) errorRules = make([]string, 0) - errorMaps = make(ErrorMap) + errorMaps = make(map[string]map[string]string) ) switch v := rules.(type) { // Sequence tag: []sequence tag diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 5ed23c45e..abb4c7829 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -16,10 +16,10 @@ import ( // CheckStruct validates struct and returns the error result. // // The parameter `object` should be type of struct/*struct. -// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// The parameter `customRules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. -// The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStruct(object interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error { +// The optional parameter `customErrorMessageMap` specifies the custom error messages for specified keys and rules. +func (v *Validator) CheckStruct(object interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) Error { var message CustomMsg if len(customErrorMessageMap) > 0 { message = customErrorMessageMap[0] @@ -36,10 +36,11 @@ func (v *Validator) CheckStruct(object interface{}, customRules interface{}, cus // CheckStructWithParamMap validates struct with given parameter map and returns the error result. // // The parameter `object` should be type of struct/*struct. -// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// The parameter `paramMap` should be type of map, which specifies the parameter map used in validation. +// The parameter `customRules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. -// The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error { +// The optional parameter `customErrorMessageMap` specifies the custom error messages for specified keys and rules. +func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) Error { var message CustomMsg if len(customErrorMessageMap) > 0 { message = customErrorMessageMap[0] @@ -53,10 +54,10 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa }) } -func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) *Error { +func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) Error { var ( // Returning error. - errorMaps = make(ErrorMap) + errorMaps = make(map[string]map[string]string) ) fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority, true) if err != nil { @@ -77,7 +78,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn recursiveInput.Object = field.Value if err := v.doCheckStructWithParamMap(&recursiveInput); err != nil { // It merges the errors into single error map. - for k, m := range err.errors { + for k, m := range err.(*validationError).errors { errorMaps[k] = m } } @@ -158,7 +159,8 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn } } } - + // Merge the custom validation rules with rules in struct tag. + // The custom rules has the most high priority that can overwrite the struct tag rules. for _, field := range tagField { fieldName := field.Name() // sequence tag == struct tag diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index fac360194..3f7648bfe 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -187,4 +187,6 @@ func ExampleRegisterRule_OverwriteRequired() { // It's required // rule deleted // It's required + // + // } diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go index 326c42ebd..e4a7f45a6 100644 --- a/util/gvalid/gvalid_z_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -78,7 +78,7 @@ func Test_CustomRule2(t *testing.T) { gtest.C(t, func(t *gtest.T) { errStr := "data map should not be empty" t.Assert(gvalid.Check(context.TODO(), g.Map{}, rule, errStr).String(), errStr) - t.Assert(gvalid.Check(context.TODO(), g.Map{"k": "v"}, rule, errStr).String(), nil) + t.Assert(gvalid.Check(context.TODO(), g.Map{"k": "v"}, rule, errStr), nil) }) // Error with struct validation. gtest.C(t, func(t *gtest.T) { @@ -121,8 +121,8 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { // Check. gtest.C(t, func(t *gtest.T) { errStr := "error" - t.Assert(gvalid.Check(context.TODO(), "", rule, errStr).String(), "") - t.Assert(gvalid.Check(context.TODO(), "gf", rule, errStr).String(), "") + t.Assert(gvalid.Check(context.TODO(), "", rule, errStr), nil) + t.Assert(gvalid.Check(context.TODO(), "gf", rule, errStr), nil) t.Assert(gvalid.Check(context.TODO(), "gf2", rule, errStr).String(), errStr) }) // Error with struct validation. @@ -136,7 +136,7 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { Data: "123456", } err := gvalid.CheckStruct(context.TODO(), st, nil) - t.Assert(err.String(), "") + t.Assert(err, nil) }) // No error with struct validation. gtest.C(t, func(t *gtest.T) { diff --git a/util/gvalid/gvalid_z_unit_i18n_test.go b/util/gvalid/gvalid_z_unit_i18n_test.go index bb27a3f4e..16bc2d809 100644 --- a/util/gvalid/gvalid_z_unit_i18n_test.go +++ b/util/gvalid/gvalid_z_unit_i18n_test.go @@ -18,7 +18,7 @@ import ( func TestValidator_I18n(t *testing.T) { var ( - err *gvalid.Error + err gvalid.Error i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")}) ctxCn = gi18n.WithLanguage(context.TODO(), "cn") validator = gvalid.New().I18n(i18nManager) From 0bd1ea07a7750b486a314d5f68bb0f6500e05a0a Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 18 May 2021 20:52:39 +0800 Subject: [PATCH 39/70] example update --- .../server/request/validation/{ => validation1}/validation1.go | 0 .../server/request/validation/{ => validation2}/validation2.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .example/net/ghttp/server/request/validation/{ => validation1}/validation1.go (100%) rename .example/net/ghttp/server/request/validation/{ => validation2}/validation2.go (100%) diff --git a/.example/net/ghttp/server/request/validation/validation1.go b/.example/net/ghttp/server/request/validation/validation1/validation1.go similarity index 100% rename from .example/net/ghttp/server/request/validation/validation1.go rename to .example/net/ghttp/server/request/validation/validation1/validation1.go diff --git a/.example/net/ghttp/server/request/validation/validation2.go b/.example/net/ghttp/server/request/validation/validation2/validation2.go similarity index 100% rename from .example/net/ghttp/server/request/validation/validation2.go rename to .example/net/ghttp/server/request/validation/validation2/validation2.go From ea0340db8e440a33c9502cd395253ae7fd9be74e Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 09:25:49 +0800 Subject: [PATCH 40/70] improve validation manager feature for package gvalid --- frame/g/g_object.go | 8 +- frame/g/g_setting.go | 2 +- net/ghttp/ghttp_request_param.go | 4 +- util/gvalid/gvalid.go | 43 +- util/gvalid/gvalid_validator.go | 64 ++- util/gvalid/gvalid_validator_check.go | 22 +- util/gvalid/gvalid_validator_check_map.go | 23 +- util/gvalid/gvalid_validator_check_struct.go | 68 +-- util/gvalid/gvalid_z_example_test.go | 12 +- util/gvalid/gvalid_z_unit_basic_all_test.go | 408 +++++++++--------- util/gvalid/gvalid_z_unit_checkstruct_test.go | 12 +- util/gvalid/gvalid_z_unit_custom_rule_test.go | 14 +- util/gvalid/gvalid_z_unit_customerror_test.go | 8 +- util/gvalid/gvalid_z_unit_i18n_test.go | 8 +- 14 files changed, 352 insertions(+), 344 deletions(-) diff --git a/frame/g/g_object.go b/frame/g/g_object.go index d4bdfdef0..d3dfb888c 100644 --- a/frame/g/g_object.go +++ b/frame/g/g_object.go @@ -18,9 +18,10 @@ import ( "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gres" "github.com/gogf/gf/os/gview" + "github.com/gogf/gf/util/gvalid" ) -// Client is a convenience function, that creates and returns a new HTTP client. +// Client is a convenience function, which creates and returns a new HTTP client. func Client() *ghttp.Client { return ghttp.NewClient() } @@ -110,3 +111,8 @@ func Model(tableNameOrStruct ...interface{}) *gdb.Model { func Redis(name ...string) *gredis.Redis { return gins.Redis(name...) } + +// Validator is a convenience function, which creates and returns a new validation manager object. +func Validator() *gvalid.Validator { + return gvalid.New() +} diff --git a/frame/g/g_setting.go b/frame/g/g_setting.go index a8adff947..ddb9e966a 100644 --- a/frame/g/g_setting.go +++ b/frame/g/g_setting.go @@ -11,7 +11,7 @@ import ( "github.com/gogf/gf/net/ghttp" ) -// SetEnabled enables/disables the GoFrame internal logging manually. +// SetDebug enables/disables the GoFrame internal logging manually. // Note that this function is not concurrent safe, be aware of the DATA RACE, // which means you should call this function in your boot but not the runtime. func SetDebug(enabled bool) { diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index d79788d65..1da2af60a 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -102,7 +102,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { } } // Validation. - if err := gvalid.CheckStructWithParamMap(r.Context(), pointer, data, nil); err != nil { + if err := gvalid.CheckStructWithData(r.Context(), pointer, data, nil); err != nil { return err } @@ -119,7 +119,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { return err } for i := 0; i < reflectVal2.Len(); i++ { - if err := gvalid.CheckStructWithParamMap( + if err := gvalid.CheckStructWithData( r.Context(), reflectVal2.Index(i), j.GetMap(gconv.String(i)), diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index b9d234dae..532b6fcfd 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -64,15 +64,6 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} -// doCheckStructWithParamMapInput is used for struct validation for internal function. -type doCheckStructWithParamMapInput struct { - Object interface{} // Can be type of struct/*struct. - ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. - UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. - CustomRules interface{} // Custom validation rules. - CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. -} - // apiNoValidation is an interface that marks current struct not validated by package `gvalid`. type apiNoValidation interface { NoValidation() @@ -179,7 +170,7 @@ var ( } ) -// Check checks single value with specified rules. +// CheckValue checks single value with specified rules. // It returns nil if successful validation. // // The parameter `value` can be any type of variable, which will be converted to string @@ -189,8 +180,12 @@ var ( // string/map/struct/*struct. // The optional parameter `params` specifies the extra validation parameters for some rules // like: required-*、same、different, etc. -func Check(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) Error { - return defaultValidator.Ctx(ctx).Check(value, rules, messages, params...) +func CheckValue(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) Error { + var data interface{} + if len(params) > 0 { + data = params[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Data(data).Messages(messages).CheckValue(value) } // CheckMap validates map and returns the error result. It returns nil if with successful validation. @@ -199,27 +194,39 @@ func Check(ctx context.Context, value interface{}, rules string, messages interf // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) Error { - return defaultValidator.Ctx(ctx).CheckMap(params, rules, messages...) + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckMap(params) } -// CheckStruct validates strcut and returns the error result. +// CheckStruct validates struct and returns the error result. // // The parameter `object` should be type of struct/*struct. // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) Error { - return defaultValidator.Ctx(ctx).CheckStruct(object, rules, messages...) + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckStruct(object) } -// CheckStructWithParamMap validates struct with given parameter map and returns the error result. +// CheckStructWithData validates struct with given parameter map and returns the error result. // // The parameter `object` should be type of struct/*struct. // The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result // if `rules` is type of []string. // The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func CheckStructWithParamMap(ctx context.Context, object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) Error { - return defaultValidator.Ctx(ctx).CheckStructWithParamMap(object, paramMap, rules, messages...) +func CheckStructWithData(ctx context.Context, object interface{}, data interface{}, rules interface{}, messages ...CustomMsg) Error { + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Data(data).Rules(rules).Messages(customErrorMessages).CheckStruct(object) } // parseSequenceTag parses one sequence tag to field, rule and error message. diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index de6ace5f1..a1592e362 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -11,11 +11,16 @@ import ( "github.com/gogf/gf/i18n/gi18n" ) -// Validator is the validation manager. +// Validator is the validation manager for chaining operations. type Validator struct { - ctx context.Context // Context containing custom context variables. - i18nManager *gi18n.Manager // I18n manager for error message translation. - + ctx context.Context // Context containing custom context variables. + i18nManager *gi18n.Manager // I18n manager for error message translation. + key string // Single validation key. + value interface{} // Single validation value. + data interface{} // Validation data, which is usually a map. + rules interface{} // Custom validation data. + messages interface{} // Custom validation error messages, which can be string or type of CustomMsg. + useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`. } // New creates and returns a new Validator. @@ -26,14 +31,49 @@ func New() *Validator { } } -// I18n sets the i18n manager for the validator. -func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator { - v.i18nManager = i18nManager - return v +// 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 } -// Ctx is a chaining operation function which sets the context for next validation. -func (v *Validator) Ctx(ctx context.Context) *Validator { - v.ctx = ctx - return v +// I18n sets the i18n manager for the validator. +func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator { + newValidator := v.Clone() + newValidator.i18nManager = i18nManager + return newValidator +} + +// Ctx is a chaining operation function, which sets the context for next validation. +func (v *Validator) Ctx(ctx context.Context) *Validator { + newValidator := v.Clone() + newValidator.ctx = ctx + return newValidator +} + +// Data is a chaining operation function, which sets validation data for current operation. +// The parameter `data` usually be type of map, which specifies the parameter map used in validation. +// Calling this function also sets `useDataInsteadOfObjectAttributes` true no mather the `data` is nil or not. +func (v *Validator) Data(data interface{}) *Validator { + newValidator := v.Clone() + newValidator.data = data + newValidator.useDataInsteadOfObjectAttributes = true + return newValidator +} + +// Rules is a chaining operation function, which sets custom validation rules for current operation. +func (v *Validator) Rules(rules interface{}) *Validator { + newValidator := v.Clone() + newValidator.rules = rules + return newValidator +} + +// Messages is a chaining operation function, which sets custom error messages for current operation. +// The parameter `messages` can be type of string/[]string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +func (v *Validator) Messages(messages interface{}) *Validator { + newValidator := v.Clone() + newValidator.messages = messages + return newValidator } diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index 86e367cce..ec2e66a83 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -25,22 +25,14 @@ type apiTime interface { IsZero() bool } -// Check checks single value with specified rules. +// CheckValue checks single value with specified rules. // It returns nil if successful validation. -// -// The parameter `value` can be any type of variable, which will be converted to string -// for validation. -// The parameter `rules` can be one or more rules, multiple rules joined using char '|'. -// The parameter `messages` specifies the custom error messages, which can be type of: -// string/map/struct/*struct. -// The optional parameter `params` specifies the extra validation parameters for some rules -// like: required-*、same、different, etc. -func (v *Validator) Check(value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { - return v.doCheck("", value, rules, messages, paramMap...) +func (v *Validator) CheckValue(value interface{}) Error { + return v.doCheckValue("", value, gconv.String(v.rules), v.messages, v.data) } -// doCheck does the really rules validation for single key-value. -func (v *Validator) doCheck(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { +// doCheckSingleValue does the really rules validation for single key-value. +func (v *Validator) doCheckValue(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { // If there's no validation rules, it does nothing and returns quickly. if rules == "" { return nil @@ -51,7 +43,7 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message data = make(map[string]interface{}) errorMsgArray = make(map[string]string) ) - if len(paramMap) > 0 { + if len(paramMap) > 0 && paramMap[0] != nil { data = gconv.Map(paramMap[0]) } // Custom error messages handling. @@ -108,7 +100,7 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message dataMap map[string]interface{} message = v.getErrorMessageByRule(ruleKey, customMsgMap) ) - if len(paramMap) > 0 { + if len(paramMap) > 0 && paramMap[0] != nil { dataMap = gconv.Map(paramMap[0]) } if err := f(ruleItems[index], value, message, dataMap); err != nil { diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index 2f24759a3..e829ddcec 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -12,13 +12,14 @@ import ( ) // CheckMap validates map and returns the error result. It returns nil if with successful validation. -// -// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result -// if `rules` is type of []string. -// The optional parameter `messages` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) Error { +// The parameter `params` should be type of map. +func (v *Validator) CheckMap(params interface{}) Error { + return v.doCheckMap(params) +} + +func (v *Validator) doCheckMap(params interface{}) Error { // If there's no validation rules, it does nothing and returns quickly. - if params == nil || rules == nil { + if params == nil || v.rules == nil { return nil } var ( @@ -27,7 +28,7 @@ func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ... errorRules = make([]string, 0) errorMaps = make(map[string]map[string]string) ) - switch v := rules.(type) { + switch v := v.rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. case []string: @@ -76,13 +77,13 @@ func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ... "invalid params type: convert to map failed", ) } - if len(messages) > 0 && len(messages[0]) > 0 { + if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { if len(customMsgs) > 0 { - for k, v := range messages[0] { + for k, v := range msg { customMsgs[k] = v } } else { - customMsgs = messages[0] + customMsgs = msg } } var value interface{} @@ -95,7 +96,7 @@ func (v *Validator) CheckMap(params interface{}, rules interface{}, messages ... value = v } // It checks each rule and its value in loop. - if e := v.doCheck(key, value, rule, customMsgs[key], data); e != nil { + if e := v.doCheckValue(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_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index abb4c7829..62c578caf 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -14,52 +14,17 @@ import ( ) // CheckStruct validates struct and returns the error result. -// // The parameter `object` should be type of struct/*struct. -// The parameter `customRules` can be type of []string/map[string]string. It supports sequence in error result -// if `rules` is type of []string. -// The optional parameter `customErrorMessageMap` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStruct(object interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) Error { - var message CustomMsg - if len(customErrorMessageMap) > 0 { - message = customErrorMessageMap[0] - } - return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ - Object: object, - ParamMap: nil, - UseParamMapInsteadOfObjectValue: false, - CustomRules: customRules, - CustomErrorMessageMap: message, - }) +func (v *Validator) CheckStruct(object interface{}) Error { + return v.doCheckStruct(object) } -// CheckStructWithParamMap validates struct with given parameter map and returns the error result. -// -// The parameter `object` should be type of struct/*struct. -// The parameter `paramMap` should be type of map, which specifies the parameter map used in validation. -// The parameter `customRules` can be type of []string/map[string]string. It supports sequence in error result -// if `rules` is type of []string. -// The optional parameter `customErrorMessageMap` specifies the custom error messages for specified keys and rules. -func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) Error { - var message CustomMsg - if len(customErrorMessageMap) > 0 { - message = customErrorMessageMap[0] - } - return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{ - Object: object, - ParamMap: paramMap, - UseParamMapInsteadOfObjectValue: true, - CustomRules: customRules, - CustomErrorMessageMap: message, - }) -} - -func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) Error { +func (v *Validator) doCheckStruct(object interface{}) Error { var ( // Returning error. errorMaps = make(map[string]map[string]string) ) - fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority, true) + fieldMap, err := structs.FieldMap(object, aliasNameTagPriority, true) if err != nil { return newErrorStr("invalid_object", err.Error()) } @@ -73,10 +38,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn if _, ok := field.TagLookup(noValidationTagName); ok { continue } - recursiveInput := doCheckStructWithParamMapInput{} - recursiveInput = *input - recursiveInput.Object = field.Value - if err := v.doCheckStructWithParamMap(&recursiveInput); err != nil { + if err := v.doCheckStruct(field.Value); err != nil { // It merges the errors into single error map. for k, m := range err.(*validationError).errors { errorMaps[k] = m @@ -85,12 +47,12 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn } } // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. - tagField, err := structs.TagFields(input.Object, structTagPriority) + tagField, err := structs.TagFields(object, structTagPriority) if err != nil { return newErrorStr("invalid_object", err.Error()) } // If there's no struct tag and validation rules, it does nothing and returns quickly. - if len(tagField) == 0 && input.CustomRules == nil { + if len(tagField) == 0 && v.messages == nil { return nil } @@ -101,7 +63,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names. errorRules = make([]string, 0) // Sequence rules. ) - switch v := input.CustomRules.(type) { + switch v := v.rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. case []string: @@ -145,13 +107,13 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn return nil } // Input parameter map handling. - if input.ParamMap == nil || !input.UseParamMapInsteadOfObjectValue { + if v.data == nil || !v.useDataInsteadOfObjectAttributes { inputParamMap = make(map[string]interface{}) } else { - inputParamMap = gconv.Map(input.ParamMap) + inputParamMap = gconv.Map(v.data) } // Checks and extends the parameters map with struct alias tag. - if !input.UseParamMapInsteadOfObjectValue { + if !v.useDataInsteadOfObjectAttributes { for nameOrTag, field := range fieldMap { inputParamMap[nameOrTag] = field.Value.Interface() if nameOrTag != field.Name() { @@ -173,7 +135,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn } // It here extends the params map using alias names. if _, ok := inputParamMap[name]; !ok { - if !input.UseParamMapInsteadOfObjectValue { + if !v.useDataInsteadOfObjectAttributes { inputParamMap[name] = field.Value.Interface() } } @@ -216,8 +178,8 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn // Custom error messages, // which have the most priority than `rules` and struct tag. - if len(input.CustomErrorMessageMap) > 0 { - for k, v := range input.CustomErrorMessageMap { + if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { + for k, v := range msg { if a, ok := fieldAliases[k]; ok { // Overwrite the key of field name. customMessage[a] = v @@ -232,7 +194,7 @@ func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapIn for key, rule := range checkRules { _, value = gutil.MapPossibleItemByKey(inputParamMap, key) // It checks each rule and its value in loop. - if e := v.doCheck(key, value, rule, customMessage[key], inputParamMap); e != nil { + if e := v.doCheckValue(key, value, rule, customMessage[key], inputParamMap); e != nil { _, item := e.FirstItem() // =================================================================== // Only in map and struct validations, if value is nil or empty string diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index 3f7648bfe..d52175aca 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -173,14 +173,14 @@ func ExampleRegisterRule_OverwriteRequired() { } return nil }) - fmt.Println(gvalid.Check(context.TODO(), "", "required", "It's required")) - fmt.Println(gvalid.Check(context.TODO(), 0, "required", "It's required")) - fmt.Println(gvalid.Check(context.TODO(), false, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), false, "required", "It's required")) gvalid.DeleteRule(rule) fmt.Println("rule deleted") - fmt.Println(gvalid.Check(context.TODO(), "", "required", "It's required")) - fmt.Println(gvalid.Check(context.TODO(), 0, "required", "It's required")) - fmt.Println(gvalid.Check(context.TODO(), false, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), false, "required", "It's required")) // Output: // It's required // It's required diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 55e7687ab..6bdf61a6f 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -24,9 +24,9 @@ func Test_Check(t *testing.T) { val1 := 0 val2 := 7 val3 := 20 - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) t.Assert(err1, "invalid_rules: abc:6,16") t.Assert(err2, "invalid_rules: abc:6,16") t.Assert(err3, "invalid_rules: abc:6,16") @@ -34,16 +34,16 @@ func Test_Check(t *testing.T) { } func Test_Required(t *testing.T) { - if m := gvalid.Check(context.TODO(), "1", "required", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "1", "required", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "", "required", nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "", "required", nil); m == nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { + if m := gvalid.CheckValue(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { t.Error("Required校验失败") } - if m := gvalid.Check(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { + if m := gvalid.CheckValue(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { t.Error("Required校验失败") } } @@ -51,20 +51,20 @@ func Test_Required(t *testing.T) { func Test_RequiredIf(t *testing.T) { gtest.C(t, func(t *gtest.T) { rule := "required-if:id,1,age,18" - t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) - t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) - t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) - t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) }) } func Test_RequiredUnless(t *testing.T) { gtest.C(t, func(t *gtest.T) { rule := "required-unless:id,1,age,18" - t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) - t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) - t.Assert(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) - t.AssertNE(gvalid.Check(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) }) } @@ -82,9 +82,9 @@ func Test_RequiredWith(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -102,9 +102,9 @@ func Test_RequiredWith(t *testing.T) { params3 := g.Map{ "time": time.Time{}, } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -121,9 +121,9 @@ func Test_RequiredWith(t *testing.T) { params3 := g.Map{ "time": time.Now(), } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -171,9 +171,9 @@ func Test_RequiredWithAll(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -194,9 +194,9 @@ func Test_RequiredWithOut(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -217,9 +217,9 @@ func Test_RequiredWithOutAll(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -236,13 +236,13 @@ func Test_Date(t *testing.T) { val5 := "2010.11.01" val6 := "2010/11/01" val7 := "2010=11=01" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) - err7 := gvalid.Check(context.TODO(), val7, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -261,12 +261,12 @@ func Test_DateFormat(t *testing.T) { val4 := "201011-01" val5 := "2010~11~01" val6 := "2010-11~01" - err1 := gvalid.Check(context.TODO(), val1, "date-format:Y", nil) - err2 := gvalid.Check(context.TODO(), val2, "date-format:Ym", nil) - err3 := gvalid.Check(context.TODO(), val3, "date-format:Y.m", nil) - err4 := gvalid.Check(context.TODO(), val4, "date-format:Ym-d", nil) - err5 := gvalid.Check(context.TODO(), val5, "date-format:Y~m~d", nil) - err6 := gvalid.Check(context.TODO(), val6, "date-format:Y~m~d", nil) + err1 := gvalid.CheckValue(context.TODO(), val1, "date-format:Y", nil) + err2 := gvalid.CheckValue(context.TODO(), val2, "date-format:Ym", nil) + err3 := gvalid.CheckValue(context.TODO(), val3, "date-format:Y.m", nil) + err4 := gvalid.CheckValue(context.TODO(), val4, "date-format:Ym-d", nil) + err5 := gvalid.CheckValue(context.TODO(), val5, "date-format:Y~m~d", nil) + err6 := gvalid.CheckValue(context.TODO(), val6, "date-format:Y~m~d", nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -277,8 +277,8 @@ func Test_DateFormat(t *testing.T) { gtest.C(t, func(t *gtest.T) { t1 := gtime.Now() t2 := time.Time{} - err1 := gvalid.Check(context.TODO(), t1, "date-format:Y", nil) - err2 := gvalid.Check(context.TODO(), t2, "date-format:Y", nil) + err1 := gvalid.CheckValue(context.TODO(), t1, "date-format:Y", nil) + err2 := gvalid.CheckValue(context.TODO(), t2, "date-format:Y", nil) t.Assert(err1, nil) t.AssertNE(err2, nil) }) @@ -291,10 +291,10 @@ func Test_Email(t *testing.T) { value2 := "m@www@johngcn" value3 := "m-m_m@mail.johng.cn" value4 := "m.m-m@johng.cn" - err1 := gvalid.Check(context.TODO(), value1, rule, nil) - err2 := gvalid.Check(context.TODO(), value2, rule, nil) - err3 := gvalid.Check(context.TODO(), value3, rule, nil) - err4 := gvalid.Check(context.TODO(), value4, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), value1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), value2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), value3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), value4, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -304,10 +304,10 @@ func Test_Email(t *testing.T) { func Test_Phone(t *testing.T) { gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check(context.TODO(), "1361990897", "phone", nil) - err2 := gvalid.Check(context.TODO(), "13619908979", "phone", nil) - err3 := gvalid.Check(context.TODO(), "16719908979", "phone", nil) - err4 := gvalid.Check(context.TODO(), "19719908989", "phone", nil) + err1 := gvalid.CheckValue(context.TODO(), "1361990897", "phone", nil) + err2 := gvalid.CheckValue(context.TODO(), "13619908979", "phone", nil) + err3 := gvalid.CheckValue(context.TODO(), "16719908979", "phone", nil) + err4 := gvalid.CheckValue(context.TODO(), "19719908989", "phone", nil) t.AssertNE(err1.String(), nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -317,12 +317,12 @@ func Test_Phone(t *testing.T) { func Test_PhoneLoose(t *testing.T) { gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check(context.TODO(), "13333333333", "phone-loose", nil) - err2 := gvalid.Check(context.TODO(), "15555555555", "phone-loose", nil) - err3 := gvalid.Check(context.TODO(), "16666666666", "phone-loose", nil) - err4 := gvalid.Check(context.TODO(), "23333333333", "phone-loose", nil) - err5 := gvalid.Check(context.TODO(), "1333333333", "phone-loose", nil) - err6 := gvalid.Check(context.TODO(), "10333333333", "phone-loose", nil) + err1 := gvalid.CheckValue(context.TODO(), "13333333333", "phone-loose", nil) + err2 := gvalid.CheckValue(context.TODO(), "15555555555", "phone-loose", nil) + err3 := gvalid.CheckValue(context.TODO(), "16666666666", "phone-loose", nil) + err4 := gvalid.CheckValue(context.TODO(), "23333333333", "phone-loose", nil) + err5 := gvalid.CheckValue(context.TODO(), "1333333333", "phone-loose", nil) + err6 := gvalid.CheckValue(context.TODO(), "10333333333", "phone-loose", nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -339,11 +339,11 @@ func Test_Telephone(t *testing.T) { val3 := "86292651" val4 := "028-8692651" val5 := "0830-8692651" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -360,11 +360,11 @@ func Test_Passport(t *testing.T) { val3 := "aaaaa" val4 := "aaaaaa" val5 := "a123_456" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -381,11 +381,11 @@ func Test_Password(t *testing.T) { val3 := "a12345-6" val4 := ">,/;'[09-" val5 := "a123_456" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -404,13 +404,13 @@ func Test_Password2(t *testing.T) { val5 := "a123_456" val6 := "Nant1986" val7 := "Nant1986!" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) - err7 := gvalid.Check(context.TODO(), val7, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -431,13 +431,13 @@ func Test_Password3(t *testing.T) { val5 := "a123_456" val6 := "Nant1986" val7 := "Nant1986!" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) - err7 := gvalid.Check(context.TODO(), val7, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -453,8 +453,8 @@ func Test_Postcode(t *testing.T) { rule := "postcode" val1 := "12345" val2 := "610036" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) }) @@ -468,11 +468,11 @@ func Test_ResidentId(t *testing.T) { val3 := "311128500121201" val4 := "510521198607185367" val5 := "51052119860718536x" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -486,8 +486,8 @@ func Test_BankCard(t *testing.T) { rule := "bank-card" val1 := "6230514630000424470" val2 := "6230514630000424473" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) }) @@ -501,11 +501,11 @@ func Test_QQ(t *testing.T) { val3 := "10000" val4 := "38996181" val5 := "389961817" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -515,31 +515,31 @@ func Test_QQ(t *testing.T) { } func Test_Ip(t *testing.T) { - if m := gvalid.Check(context.TODO(), "10.0.0.1", "ip", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "10.0.0.1", "ip", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "10.0.0.1", "ipv4", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "10.0.0.1", "ipv4", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "0.0.0.0", "ipv4", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "0.0.0.0", "ipv4", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "1920.0.0.0", "ipv4", nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "1920.0.0.0", "ipv4", nil); m == nil { t.Error("ipv4校验失败") } - if m := gvalid.Check(context.TODO(), "1920.0.0.0", "ip", nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "1920.0.0.0", "ip", nil); m == nil { t.Error("ipv4校验失败") } - if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { t.Error(m) } } @@ -552,11 +552,11 @@ func Test_IPv4(t *testing.T) { val3 := "1.1.1.1" val4 := "255.255.255.0" val5 := "127.0.0.1" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -573,11 +573,11 @@ func Test_IPv6(t *testing.T) { val3 := "1030::C9B4:FF12:48AA:1A2B" val4 := "2000:0:0:0:0:0:0:1" val5 := "0000:0000:0000:0000:0000:ffff:c0a8:5909" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -592,9 +592,9 @@ func Test_MAC(t *testing.T) { val1 := "192.168.1.1" val2 := "44-45-53-54-00-00" val3 := "01:00:5e:00:00:00" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -608,10 +608,10 @@ func Test_URL(t *testing.T) { val2 := "https://www.baidu.com" val3 := "http://127.0.0.1" val4 := "file:///tmp/test.txt" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -638,7 +638,7 @@ func Test_Domain(t *testing.T) { } var err error for k, v := range m { - err = gvalid.Check(context.TODO(), k, "domain", nil) + err = gvalid.CheckValue(context.TODO(), k, "domain", nil) if v { //fmt.Println(k) t.Assert(err, nil) @@ -652,10 +652,10 @@ func Test_Domain(t *testing.T) { func Test_Length(t *testing.T) { rule := "length:6,16" - if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "12345", rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } } @@ -665,18 +665,18 @@ func Test_MinLength(t *testing.T) { msgs := map[string]string{ "min-length": "地址长度至少为:min位", } - if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "12345", rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } - if m := gvalid.Check(context.TODO(), "12345", rule, msgs); m == nil { + if m := gvalid.CheckValue(context.TODO(), "12345", rule, msgs); m == nil { t.Error("长度校验失败") } rule2 := "min-length:abc" - if m := gvalid.Check(context.TODO(), "123456", rule2, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { t.Error("长度校验失败") } } @@ -686,31 +686,31 @@ func Test_MaxLength(t *testing.T) { msgs := map[string]string{ "max-length": "地址长度至大为:max位", } - if m := gvalid.Check(context.TODO(), "12345", rule, nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "1234567", rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "1234567", rule, nil); m == nil { t.Error("长度校验失败") } - if m := gvalid.Check(context.TODO(), "1234567", rule, msgs); m == nil { + if m := gvalid.CheckValue(context.TODO(), "1234567", rule, msgs); m == nil { t.Error("长度校验失败") } rule2 := "max-length:abc" - if m := gvalid.Check(context.TODO(), "123456", rule2, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { t.Error("长度校验失败") } } func Test_Between(t *testing.T) { rule := "between:6.01, 10.01" - if m := gvalid.Check(context.TODO(), 10, rule, nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), 10, rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), 10.02, rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), 10.02, rule, nil); m == nil { t.Error("大小范围校验失败") } - if m := gvalid.Check(context.TODO(), "a", rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "a", rule, nil); m == nil { t.Error("大小范围校验失败") } } @@ -723,11 +723,11 @@ func Test_Min(t *testing.T) { val3 := "100" val4 := "1000" val5 := "a" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -735,7 +735,7 @@ func Test_Min(t *testing.T) { t.AssertNE(err5, nil) rule2 := "min:a" - err6 := gvalid.Check(context.TODO(), val1, rule2, nil) + err6 := gvalid.CheckValue(context.TODO(), val1, rule2, nil) t.AssertNE(err6, nil) }) } @@ -748,11 +748,11 @@ func Test_Max(t *testing.T) { val3 := "100" val4 := "1000" val5 := "a" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -760,7 +760,7 @@ func Test_Max(t *testing.T) { t.AssertNE(err5, nil) rule2 := "max:a" - err6 := gvalid.Check(context.TODO(), val1, rule2, nil) + err6 := gvalid.CheckValue(context.TODO(), val1, rule2, nil) t.AssertNE(err6, nil) }) } @@ -774,12 +774,12 @@ func Test_Json(t *testing.T) { val4 := "[]" val5 := "[1,2,3,4]" val6 := `{"list":[1,2,3,4]}` - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -798,12 +798,12 @@ func Test_Integer(t *testing.T) { val4 := "1" val5 := "100" val6 := `999999999` - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -822,12 +822,12 @@ func Test_Float(t *testing.T) { val4 := "1.0" val5 := "1.1" val6 := `0.1` - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -846,12 +846,12 @@ func Test_Boolean(t *testing.T) { val4 := "1" val5 := "true" val6 := `off` - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) - err5 := gvalid.Check(context.TODO(), val5, rule, nil) - err6 := gvalid.Check(context.TODO(), val6, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -875,9 +875,9 @@ func Test_Same(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.AssertNE(err1, nil) t.Assert(err2, nil) t.Assert(err3, nil) @@ -898,9 +898,9 @@ func Test_Different(t *testing.T) { "id": 100, "name": "john", } - err1 := gvalid.Check(context.TODO(), val1, rule, nil, params1) - err2 := gvalid.Check(context.TODO(), val1, rule, nil, params2) - err3 := gvalid.Check(context.TODO(), val1, rule, nil, params3) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) t.Assert(err1, nil) t.AssertNE(err2, nil) t.AssertNE(err3, nil) @@ -914,10 +914,10 @@ func Test_In(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) @@ -932,10 +932,10 @@ func Test_NotIn(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -947,10 +947,10 @@ func Test_NotIn(t *testing.T) { val2 := "1" val3 := "100" val4 := "200" - err1 := gvalid.Check(context.TODO(), val1, rule, nil) - err2 := gvalid.Check(context.TODO(), val2, rule, nil) - err3 := gvalid.Check(context.TODO(), val3, rule, nil) - err4 := gvalid.Check(context.TODO(), val4, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) t.Assert(err1, nil) t.Assert(err2, nil) t.AssertNE(err3, nil) @@ -960,10 +960,10 @@ func Test_NotIn(t *testing.T) { func Test_Regex1(t *testing.T) { rule := `regex:\d{6}|\D{6}|length:6,16` - if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { t.Error(m) } - if m := gvalid.Check(context.TODO(), "abcde6", rule, nil); m == nil { + if m := gvalid.CheckValue(context.TODO(), "abcde6", rule, nil); m == nil { t.Error("校验失败") } } @@ -974,9 +974,9 @@ func Test_Regex2(t *testing.T) { str1 := "" str2 := "data" str3 := "data:image/jpeg;base64,/9jrbattq22r" - err1 := gvalid.Check(context.TODO(), str1, rule, nil) - err2 := gvalid.Check(context.TODO(), str2, rule, nil) - err3 := gvalid.Check(context.TODO(), str3, rule, nil) + err1 := gvalid.CheckValue(context.TODO(), str1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), str2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), str3, rule, nil) t.AssertNE(err1, nil) t.AssertNE(err2, nil) t.Assert(err3, nil) diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index ac65bd8b4..14a80fb3a 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -406,7 +406,7 @@ func Test_CheckStruct_InvalidRule(t *testing.T) { }) } -func TestValidator_CheckStructWithParamMap(t *testing.T) { +func TestValidator_CheckStructWithData(t *testing.T) { gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { Uid int64 `v:"required"` @@ -416,7 +416,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { Uid: 1, Nickname: "john", } - t.Assert(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) + t.Assert(gvalid.CheckStructWithData(context.TODO(), data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -424,7 +424,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { Nickname string `v:"required-with:uid"` } data := UserApiSearch{} - t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -434,7 +434,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { data := UserApiSearch{ Uid: 1, } - t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { @@ -448,7 +448,7 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { StartTime: nil, EndTime: nil, } - t.Assert(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{}, nil), nil) + t.Assert(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) }) gtest.C(t, func(t *gtest.T) { type UserApiSearch struct { @@ -461,6 +461,6 @@ func TestValidator_CheckStructWithParamMap(t *testing.T) { StartTime: gtime.Now(), EndTime: nil, } - t.AssertNE(gvalid.CheckStructWithParamMap(context.TODO(), data, g.Map{"start_time": gtime.Now()}, nil), nil) + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{"start_time": gtime.Now()}, nil), nil) }) } diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go index e4a7f45a6..9fd7caf62 100644 --- a/util/gvalid/gvalid_z_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -31,9 +31,9 @@ func Test_CustomRule1(t *testing.T) { }) gtest.Assert(err, nil) gtest.C(t, func(t *gtest.T) { - err := gvalid.Check(context.TODO(), "123456", rule, "custom message") + err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message") t.Assert(err.String(), "custom message") - err = gvalid.Check(context.TODO(), "123456", rule, "custom message", g.Map{"data": "123456"}) + err = gvalid.CheckValue(context.TODO(), "123456", rule, "custom message", g.Map{"data": "123456"}) t.Assert(err, nil) }) // Error with struct validation. @@ -77,8 +77,8 @@ func Test_CustomRule2(t *testing.T) { // Check. gtest.C(t, func(t *gtest.T) { errStr := "data map should not be empty" - t.Assert(gvalid.Check(context.TODO(), g.Map{}, rule, errStr).String(), errStr) - t.Assert(gvalid.Check(context.TODO(), g.Map{"k": "v"}, rule, errStr), nil) + t.Assert(gvalid.CheckValue(context.TODO(), g.Map{}, rule, errStr).String(), errStr) + t.Assert(gvalid.CheckValue(context.TODO(), g.Map{"k": "v"}, rule, errStr), nil) }) // Error with struct validation. gtest.C(t, func(t *gtest.T) { @@ -121,9 +121,9 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { // Check. gtest.C(t, func(t *gtest.T) { errStr := "error" - t.Assert(gvalid.Check(context.TODO(), "", rule, errStr), nil) - t.Assert(gvalid.Check(context.TODO(), "gf", rule, errStr), nil) - t.Assert(gvalid.Check(context.TODO(), "gf2", rule, errStr).String(), errStr) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, errStr), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "gf", rule, errStr), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "gf2", rule, errStr).String(), errStr) }) // Error with struct validation. gtest.C(t, func(t *gtest.T) { diff --git a/util/gvalid/gvalid_z_unit_customerror_test.go b/util/gvalid/gvalid_z_unit_customerror_test.go index ecccf7e4d..75bc8f21a 100755 --- a/util/gvalid/gvalid_z_unit_customerror_test.go +++ b/util/gvalid/gvalid_z_unit_customerror_test.go @@ -20,7 +20,7 @@ func Test_Map(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(context.TODO(), val, rule, nil) + err = gvalid.CheckValue(context.TODO(), val, rule, nil) msg = map[string]string{ "ipv4": "The value must be a valid IPv4 address", } @@ -34,7 +34,7 @@ func Test_FirstString(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(context.TODO(), val, rule, nil) + err = gvalid.CheckValue(context.TODO(), val, rule, nil) n = err.FirstString() ) t.Assert(n, "The value must be a valid IPv4 address") @@ -47,7 +47,7 @@ func Test_CustomError1(t *testing.T) { "integer": "请输入一个整数", "length": "参数长度不对啊老铁", } - e := gvalid.Check(context.TODO(), "6.66", rule, msgs) + e := gvalid.CheckValue(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { @@ -67,7 +67,7 @@ func Test_CustomError1(t *testing.T) { func Test_CustomError2(t *testing.T) { rule := "integer|length:6,16" msgs := "请输入一个整数|参数长度不对啊老铁" - e := gvalid.Check(context.TODO(), "6.66", rule, msgs) + e := gvalid.CheckValue(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { diff --git a/util/gvalid/gvalid_z_unit_i18n_test.go b/util/gvalid/gvalid_z_unit_i18n_test.go index 16bc2d809..5294c7e3c 100644 --- a/util/gvalid/gvalid_z_unit_i18n_test.go +++ b/util/gvalid/gvalid_z_unit_i18n_test.go @@ -24,14 +24,14 @@ func TestValidator_I18n(t *testing.T) { validator = gvalid.New().I18n(i18nManager) ) gtest.C(t, func(t *gtest.T) { - err = validator.Check("", "required", nil) + err = validator.Rules("required").CheckValue("") t.Assert(err.String(), "The field is required") - err = validator.Ctx(ctxCn).Check("", "required", nil) + err = validator.Ctx(ctxCn).Rules("required").CheckValue("") t.Assert(err.String(), "字段不能为空") }) gtest.C(t, func(t *gtest.T) { - err = validator.Ctx(ctxCn).Check("", "required", "CustomMessage") + err = validator.Ctx(ctxCn).Rules("required").Messages("CustomMessage").CheckValue("") t.Assert(err.String(), "自定义错误") }) gtest.C(t, func(t *gtest.T) { @@ -44,7 +44,7 @@ func TestValidator_I18n(t *testing.T) { Page: 1, Size: 10, } - err := validator.Ctx(ctxCn).CheckStruct(obj, nil) + err := validator.Ctx(ctxCn).CheckStruct(obj) t.Assert(err.String(), "项目ID必须大于等于1并且要小于等于10000") }) } From 420e0b9ca4cca71cb29f2da419aef750be96387a Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 13:29:40 +0800 Subject: [PATCH 41/70] improve package gvalid --- .../util/gvalid/gvalid_checkstructwithdata.go | 28 +++++++ util/gvalid/gvalid.go | 70 +++++++++++++++--- util/gvalid/gvalid_error.go | 73 +++++++++++++------ util/gvalid/gvalid_validator_check.go | 4 +- util/gvalid/gvalid_validator_check_map.go | 2 +- util/gvalid/gvalid_validator_check_struct.go | 4 +- util/gvalid/gvalid_validator_message.go | 57 +-------------- util/gvalid/gvalid_z_unit_basic_all_test.go | 14 ++-- 8 files changed, 152 insertions(+), 100 deletions(-) create mode 100644 .example/util/gvalid/gvalid_checkstructwithdata.go diff --git a/.example/util/gvalid/gvalid_checkstructwithdata.go b/.example/util/gvalid/gvalid_checkstructwithdata.go new file mode 100644 index 000000000..7e1636f88 --- /dev/null +++ b/.example/util/gvalid/gvalid_checkstructwithdata.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gvalid" +) + +func main() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + data := g.Map{ + "name": "john", + } + user := User{} + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + err := gvalid.CheckStructWithData(context.TODO(), user, data, nil) + // 也可以使用 + // err := g.Validator().Data(data).CheckStruct(user) + if err != nil { + g.Dump(err.Items()) + } +} diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 532b6fcfd..275b17771 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -70,14 +70,14 @@ type apiNoValidation interface { } const ( - // regular expression pattern for single validation rule. - singleRulePattern = `^([\w-]+):{0,1}(.*)` - invalidRulesErrKey = "invalid_rules" - invalidParamsErrKey = "invalid_params" - invalidObjectErrKey = "invalid_object" - - // no validation tag name for struct attribute. - noValidationTagName = "nv" + singleRulePattern = `^([\w-]+):{0,1}(.*)` // regular expression pattern for single validation rule. + internalRulesErrRuleName = "InvalidRules" // rule name for internal invalid rules validation error. + internalParamsErrRuleName = "InvalidParams" // rule name for internal invalid params validation error. + internalObjectErrRuleName = "InvalidObject" // rule name for internal invalid object validation error. + internalErrorMapKey = "__InternalError__" // error map key for internal errors. + internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule. + ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content. + noValidationTagName = "nv" // no validation tag name for struct attribute. ) var ( @@ -87,9 +87,9 @@ var ( // all internal error keys. internalErrKeyMap = map[string]string{ - invalidRulesErrKey: invalidRulesErrKey, - invalidParamsErrKey: invalidParamsErrKey, - invalidObjectErrKey: invalidObjectErrKey, + internalRulesErrRuleName: internalRulesErrRuleName, + internalParamsErrRuleName: internalParamsErrRuleName, + internalObjectErrRuleName: internalObjectErrRuleName, } // regular expression object for single rule // which is compiled just once and of repeatable usage. @@ -168,6 +168,54 @@ var ( "off": {}, "no": {}, } + // defaultMessages is the default error messages. + // Note that these messages are synchronized from ./i18n/en/validation.toml . + defaultMessages = map[string]string{ + "required": "The :attribute field is required", + "required-if": "The :attribute field is required", + "required-unless": "The :attribute field is required", + "required-with": "The :attribute field is required", + "required-with-all": "The :attribute field is required", + "required-without": "The :attribute field is required", + "required-without-all": "The :attribute field is required", + "date": "The :attribute value is not a valid date", + "date-format": "The :attribute value does not match the format :format", + "email": "The :attribute value must be a valid email address", + "phone": "The :attribute value must be a valid phone number", + "telephone": "The :attribute value must be a valid telephone number", + "passport": "The :attribute value is not a valid passport format", + "password": "The :attribute value is not a valid passport format", + "password2": "The :attribute value is not a valid passport format", + "password3": "The :attribute value is not a valid passport format", + "postcode": "The :attribute value is not a valid passport format", + "resident-id": "The :attribute value is not a valid resident id number", + "bank-card": "The :attribute value must be a valid bank card number", + "qq": "The :attribute value must be a valid QQ number", + "ip": "The :attribute value must be a valid IP address", + "ipv4": "The :attribute value must be a valid IPv4 address", + "ipv6": "The :attribute value must be a valid IPv6 address", + "mac": "The :attribute value must be a valid MAC address", + "url": "The :attribute value must be a valid URL address", + "domain": "The :attribute value must be a valid domain format", + "length": "The :attribute value length must be between :min and :max", + "min-length": "The :attribute value length must be equal or greater than :min", + "max-length": "The :attribute value length must be equal or lesser than :max", + "between": "The :attribute value must be between :min and :max", + "min": "The :attribute value must be equal or greater than :min", + "max": "The :attribute value must be equal or lesser than :max", + "json": "The :attribute value must be a valid JSON string", + "xml": "The :attribute value must be a valid XML string", + "array": "The :attribute value must be an array", + "integer": "The :attribute value must be an integer", + "float": "The :attribute value must be a float", + "boolean": "The :attribute value field must be true or false", + "same": "The :attribute value must be the same as field :field", + "different": "The :attribute value must be different from field :field", + "in": "The :attribute value is not in acceptable range", + "not-in": "The :attribute value is not in acceptable range", + "regex": "The :attribute value is invalid", + internalDefaultRuleName: "The :attribute value is invalid", + } ) // CheckValue checks single value with specified rules. diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index c55da3f56..64e2ae688 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -20,6 +20,7 @@ type Error interface { FirstItem() (key string, messages map[string]string) FirstRule() (rule string, err string) FirstString() (err string) + Items() (items []map[string]map[string]string) Map() map[string]string Maps() map[string]map[string]string String() string @@ -30,7 +31,7 @@ type Error interface { type validationError struct { rules []string // Rules by sequence, which is used for keeping error sequence. errors map[string]map[string]string // Error map:map[field]map[rule]message - firstKey string // The first error rule key(nil in default). + firstKey string // The first error rule key(empty in default). firstItem map[string]string // The first error rule value(nil in default). } @@ -54,7 +55,7 @@ func newError(rules []string, errors map[string]map[string]string) *validationEr // newErrorStr creates and returns a validation error by string. func newErrorStr(key, err string) *validationError { return newError(nil, map[string]map[string]string{ - "__gvalid__": { + internalErrorMapKey: { key: err, }, }) @@ -77,6 +78,34 @@ func (e *validationError) Maps() map[string]map[string]string { return e.errors } +// Items retrieves and returns error items array in sequence if possible, +// or else it returns error items with no sequence . +func (e *validationError) Items() (items []map[string]map[string]string) { + if e == nil { + return []map[string]map[string]string{} + } + items = make([]map[string]map[string]string, 0) + // By sequence. + if len(e.rules) > 0 { + for _, v := range e.rules { + name, _, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { + items = append(items, map[string]map[string]string{ + name: errorItemMap, + }) + } + } + return items + } + // No sequence. + for name, errorRuleMap := range e.errors { + items = append(items, map[string]map[string]string{ + name: errorRuleMap, + }) + } + return +} + // FirstItem returns the field name and error messages for the first validation rule error. func (e *validationError) FirstItem() (key string, messages map[string]string) { if e == nil { @@ -89,10 +118,10 @@ func (e *validationError) FirstItem() (key string, messages map[string]string) { if len(e.rules) > 0 { for _, v := range e.rules { name, _, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { + if errorItemMap, ok := e.errors[name]; ok { e.firstKey = name - e.firstItem = m - return name, m + e.firstItem = errorItemMap + return name, errorItemMap } } } @@ -113,21 +142,21 @@ func (e *validationError) FirstRule() (rule string, err string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { - for _, rule := range strings.Split(rule, "|") { - array := strings.Split(rule, ":") - rule = strings.TrimSpace(array[0]) - if err, ok := m[rule]; ok { - return rule, err + name, ruleStr, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { + for _, ruleItem := range strings.Split(ruleStr, "|") { + array := strings.Split(ruleItem, ":") + ruleItem = strings.TrimSpace(array[0]) + if err, ok = errorItemMap[ruleItem]; ok { + return ruleStr, err } } } } } // No sequence. - for _, m := range e.errors { - for k, v := range m { + for _, errorItemMap := range e.errors { + for k, v := range errorItemMap { return k, v } } @@ -178,18 +207,18 @@ func (e *validationError) Strings() (errs []string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { + name, ruleStr, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { // validation error checks. - for _, rule := range strings.Split(rule, "|") { - rule = strings.TrimSpace(strings.Split(rule, ":")[0]) - if err, ok := m[rule]; ok { + for _, ruleItem := range strings.Split(ruleStr, "|") { + ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) + if err, ok := errorItemMap[ruleItem]; ok { errs = append(errs, err) } } // internal error checks. for k, _ := range internalErrKeyMap { - if err, ok := m[k]; ok { + if err, ok := errorItemMap[k]; ok { errs = append(errs, err) } } @@ -198,8 +227,8 @@ func (e *validationError) Strings() (errs []string) { return errs } // No sequence. - for _, m := range e.errors { - for _, err := range m { + for _, errorItemMap := range e.errors { + for _, err := range errorItemMap { errs = append(errs, err) } } diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index ec2e66a83..fd1d78f20 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -71,8 +71,8 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) } else { return newErrorStr( - invalidRulesErrKey, - invalidRulesErrKey+": "+rules, + internalRulesErrRuleName, + internalRulesErrRuleName+": "+rules, ) } } else { diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index e829ddcec..cefb987ca 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -73,7 +73,7 @@ func (v *Validator) doCheckMap(params interface{}) Error { data := gconv.Map(params) if data == nil { return newErrorStr( - "invalid_params", + internalParamsErrRuleName, "invalid params type: convert to map failed", ) } diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 62c578caf..55e0d7c33 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -26,7 +26,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { ) fieldMap, err := structs.FieldMap(object, aliasNameTagPriority, true) if err != nil { - return newErrorStr("invalid_object", err.Error()) + return newErrorStr(internalObjectErrRuleName, err.Error()) } // It checks the struct recursively the its attribute is an embedded struct. for _, field := range fieldMap { @@ -49,7 +49,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. tagField, err := structs.TagFields(object, structTagPriority) if err != nil { - return newErrorStr("invalid_object", err.Error()) + return newErrorStr(internalObjectErrRuleName, err.Error()) } // If there's no struct tag and validation rules, it does nothing and returns quickly. if len(tagField) == 0 && v.messages == nil { diff --git a/util/gvalid/gvalid_validator_message.go b/util/gvalid/gvalid_validator_message.go index e1737dce9..7701e5f96 100644 --- a/util/gvalid/gvalid_validator_message.go +++ b/util/gvalid/gvalid_validator_message.go @@ -6,59 +6,6 @@ package gvalid -const ( - ruleMessagePrefixForI18n = "gf.gvalid.rule." -) - -// defaultMessages is the default error messages. -// Note that these messages are synchronized from ./i18n/en/validation.toml . -var defaultMessages = map[string]string{ - "required": "The :attribute field is required", - "required-if": "The :attribute field is required", - "required-unless": "The :attribute field is required", - "required-with": "The :attribute field is required", - "required-with-all": "The :attribute field is required", - "required-without": "The :attribute field is required", - "required-without-all": "The :attribute field is required", - "date": "The :attribute value is not a valid date", - "date-format": "The :attribute value does not match the format :format", - "email": "The :attribute value must be a valid email address", - "phone": "The :attribute value must be a valid phone number", - "telephone": "The :attribute value must be a valid telephone number", - "passport": "The :attribute value is not a valid passport format", - "password": "The :attribute value is not a valid passport format", - "password2": "The :attribute value is not a valid passport format", - "password3": "The :attribute value is not a valid passport format", - "postcode": "The :attribute value is not a valid passport format", - "resident-id": "The :attribute value is not a valid resident id number", - "bank-card": "The :attribute value must be a valid bank card number", - "qq": "The :attribute value must be a valid QQ number", - "ip": "The :attribute value must be a valid IP address", - "ipv4": "The :attribute value must be a valid IPv4 address", - "ipv6": "The :attribute value must be a valid IPv6 address", - "mac": "The :attribute value must be a valid MAC address", - "url": "The :attribute value must be a valid URL address", - "domain": "The :attribute value must be a valid domain format", - "length": "The :attribute value length must be between :min and :max", - "min-length": "The :attribute value length must be equal or greater than :min", - "max-length": "The :attribute value length must be equal or lesser than :max", - "between": "The :attribute value must be between :min and :max", - "min": "The :attribute value must be equal or greater than :min", - "max": "The :attribute value must be equal or lesser than :max", - "json": "The :attribute value must be a valid JSON string", - "xml": "The :attribute value must be a valid XML string", - "array": "The :attribute value must be an array", - "integer": "The :attribute value must be an integer", - "float": "The :attribute value must be a float", - "boolean": "The :attribute value field must be true or false", - "same": "The :attribute value must be the same as field :field", - "different": "The :attribute value must be different from field :field", - "in": "The :attribute value is not in acceptable range", - "not-in": "The :attribute value is not in acceptable range", - "regex": "The :attribute value is invalid", - "__default__": "The :attribute value is invalid", -} - // 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. @@ -79,9 +26,9 @@ func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[strin } // If there's no configured rule message, it uses default one. if content == "" { - content = v.i18nManager.GetContent(v.ctx, `gf.gvalid.rule.__default__`) + content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+internalDefaultRuleName) if content == "" { - content = defaultMessages["__default__"] + content = defaultMessages[internalDefaultRuleName] } } return content diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 6bdf61a6f..fa687880c 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -27,9 +27,9 @@ func Test_Check(t *testing.T) { err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) - t.Assert(err1, "invalid_rules: abc:6,16") - t.Assert(err2, "invalid_rules: abc:6,16") - t.Assert(err3, "invalid_rules: abc:6,16") + t.Assert(err1, "InvalidRules: abc:6,16") + t.Assert(err2, "InvalidRules: abc:6,16") + t.Assert(err3, "InvalidRules: abc:6,16") }) } @@ -995,9 +995,9 @@ func Test_InternalError_String(t *testing.T) { aa := a{Name: "2"} err := gvalid.CheckStruct(context.TODO(), &aa, nil) - t.Assert(err.String(), "invalid_rules: hh") - t.Assert(err.Strings(), g.Slice{"invalid_rules: hh"}) - t.Assert(err.FirstString(), "invalid_rules: hh") - t.Assert(gerror.Current(err), "invalid_rules: hh") + t.Assert(err.String(), "InvalidRules: hh") + t.Assert(err.Strings(), g.Slice{"InvalidRules: hh"}) + t.Assert(err.FirstString(), "InvalidRules: hh") + t.Assert(gerror.Current(err), "InvalidRules: hh") }) } From fac9ab5c010f28243e7121548e2ec1cf01016c66 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 19:00:34 +0800 Subject: [PATCH 42/70] improve package gvalid --- os/gres/testdata/data/data.go | 2 +- os/gres/testdata/testdata.go | 2 +- util/gvalid/gvalid_error.go | 2 +- util/gvalid/gvalid_z_unit_checkmap_test.go | 5 +++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/os/gres/testdata/data/data.go b/os/gres/testdata/data/data.go index 099d514e3..ff96670de 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("H4sIAAAAAAAC/7RaeTiU6/9+sq8hSyohYpAxtkiyFLKMfYtSGgxxGGHsLShlSUqh0nZQpGObiCMplX3LlrQoCpF9ZAv9rkjed4wl5/ftuuIfz33fn/t9tvfz3oZoSioOQAfowCDW0RBA/q0D9MDWFWfv6ICa/SWBd3VxNjOlBms6dtofEqvQRaK0xMsMM8wM62pKa/QaUShUE0q7ClmqLa6NNG8Lk9DRKxM/325saGioVSZebQ7uycpKN8m8lHkpkyUtI5fTIGVIgUYghB0SbS7KmVJQAfDjhyGalu76swt25gAAewDA4vKY5+W5+Eo44hz/X5UZzyszn1c2FqimSKoMgNM2ksZQZTS/lc1IGmumOPxzMPRPFi9rw+/BSFtPD7yry//MfKP5Es3mSzRVl3u7vPkcC1T+D56BybzAffMCJWnbTy//DJhJBS77KAC47HYTViQdoAd2ju5SKA9Pm5nhze9SDq38SXJBhv/8j8RjPfBSEngf/LxLEnp1SG2khJSpVnXNNu2qbWt+l1nw4NLO9QAAtiU5GOY4ZrDnccljhlCqTLPPmLOUdVS/MFfvmPR/c0wa5pg0eceMF1TnH8lLuWLHpGcck4Y7thBTT4VQsmLHpFex3JkAPXCU2oFDYXHwFd5WrlVVVrYvw8xwCEhdzi0nhrsbq04eZVWjZ50TZ+uNVOUDAPCvjMEJsxiD37sd4271n0si7vPJSxx7l3so9P6uJyII6q1Eu5KsThP/y2DNHKeRA7+XFABg28o43T0X48yT/GHzPHjzermDr/0ytX8Riud3PjsfzqwTAH7XuCPvLyQAQGRJPpY5Pr8jSDV9OOV8lQTvwvo7/tGzXj6lULyrdOtNAhXdHFVXS3mEEABgy4qpTPf9P1KRn1Y/qVaxEBl/DUXaObqjsLhVrkUIAuoI1tnZdakpOj9H3L862XIAAFj/iMDb1d3ZjoRArAZVrm2UYWbIQAsluMHmiSAlWMYDJ8x/9cAJ88sDX8wCD3KMF66h+XWaby9myT9ztP8J2awfMLIZOzKNDf0Itm+I95Qgy+TmbmtnrpnZuWJH3D1X4QgnHOGXI9CDf5EFPu+G87EtIptn6v0Tolk3YDeMubmRl0G6aWC/9j5av+z8WAulmNk4VmHIhgUgvzxx8nDF/daa0vHzmmM46w3BeH5/cFrze1IXbGHMW36WkOGbtYYc36xFhPkJY5U9bxJDL3qalG95k0z3rcIk7gUgv0zygUxuQmZ6aZWRCBopVl1Z88BMukHG0FirCl1tUFaJzjETQ2rXZxsScj4RymSzSh98+llfRtnM2vOfNdNleMsuiU3HMxhUf5fI6scVaQAAMPhTdbOW/kd1ojOr1X/WerkJVt7YCGULav45cRPCutH6JOIWngF0EHGruF6wzQ23d3TG/u/uGHAaJ8xK98jZY79IZ3dX2XnHmqQRkTUUc8QcxRUWPy8aoksSs8KI3T1XshUtetd4ZoyaEgcAIJZ99ZmnnL1wLFx8C5Y6XjWofVxDQfRw4eb6mwa9Rm0p1L9vmcqGqIcSAADxJYnZSYlN9/2/L6AQXd8TRvph7y/NiMysPTuQ6zxc6De/S5WpltdjAACOS85aeqjUVb5HuLu64lG2Hh6r2G/WQYajPPC+zliJOaCfRtUZJ8/sj1oVNdtqkdX/6GQbS8qJJWfQMMYkml/+Itxhd7fTUnRrUkd0IpelRyPF7zkp1mLTvR0AIL2keIY5dkcXjAN2FfLZYQAoZ1cHV4mjOIff+p1jfXc0/LWpJEk6OlRgZ8zLK+qW9pWnudUwkiMupcay9mWl4vbC/s0l7obszQ84yqK2HzAVOK3AQRD6HmVb7hV15/qou36/b41nC9GqiVjSP31cZXS0IP37dGdo5kNZLirtIAACbtPTuDACwPpo9OMJAPg9lPMoAOjBvNn4lVLn0Z2fZ7fK5k8vqMDa0J4WpYBhXTdw2FV0w9dS9yxJzTZHDpHzqh6IPVMi+Kd9u8e3TIngGS4GU343yk/WTby19jMLdVS5et8ZDOsA37pX2zeHJARvfx1QzB/U+LeToVmIBB0TTQI31bo6LpdhSgdWrZ2sLFzh508XHjt+/O8Ch79li824rdBoxrthAVtYxZgotIO7DLCtk6bJlZdOCo+78speDHW40CUuHcHQMlX8b33aAzz/Bb5rKjWRO0WHn5xLruQ72OrIcYXv/NpTamaYHzRsAyelhr8ypavoCH7EqKMEgrWwUpijIpPWClxX1Se8c96rnHzTNR2pNm70RYuShq1EIN5NPF5iD2fanfiUtPiUwGDBCw5sWrl5MgwjLLVPWmVpYvYS6xrfZlNuurhB3Xrjp43szoeTRuQnz37hy6eOUBUe/3Hr8U7jg5S+5sSzUx/UTz6n99kaWnz7o0WeHUOfGNvepEMahdPrJ+Pcc/wTOU/pJhKOPlKrD044/GP99/4AzI8rA7e+Un7PVMnj5+hJP9hAjXw03E8JVAuFjyj9QztwKYGRWVHEbVzsm1j23eMqOWZWu4/t/yJi+OG8gVTPiKD/o0OCJv4hBVMbvk8jzOWV28I9NTKDNZ0fspzbJX/3xZndApFBCKX+igphujGLK6fufx0fCDyk9OBdFnWnS1rMuYB8oZaeSqehnfK9Z+p9eomjtvvQ1Q5hsbpf/D7jzp7ujLG+Ynn9K2gxHhG0E41lVEhnO3Mi1uJkQdCWhtLYJkuV+5aSZpyb3vnbDIx62lx4IX/l3WU6pbwvxA1itIyXs2U34rTzsm7y8agp1ga4BPP60NUOeLkKYxnTvr44jIsro2hqxvO3I+P3ve42Dm5KfH41hKnilMDVnB4xwVf5m8XT2IrcTl0MOy+tg2D2SeJswOtphiOLNOiuh5qaP37tx7O/T7Y3qI3VS1nz/V4Vcd1U0+mq2vO56jj2QINzGMGU9tp6Hvv7RyptbQPMfY2FakVzqI3bnqgrx3zQlH4+ZR88Uv2Gm00Ubz3McsSwje5xReWjhM8Xf6A36rgHnjQIMx66S8N9cDueS8LrfndnZDCPnmD952gcUXtANOv7t9pXHlG3H8ZMbdDQ+nYuhGXPgy43lZqJAU5uo620rP+WI5CONWrPH1M7dB9N0vU5r03jrPAyx8BboG7c+FF6X5fpTh6Lb2UHMQNWrp1OVqkPRp4PixSuC89jpn30XHiNWq+Kwmmr6afPY68iULa9hJH1B1+bsg+uieMpD7coutqK/WgqPoxvvZePfjkY2C0ldeRzerDaVgdE+GvvjQirl4gj6TI4/j24orhqpwHVQtozOoLMjROb37GMC/xlvxcR3GprIZ81/N6MX8W8M+kWVoH76DUF9bsjsQ1yu7d8s2VMfKjae6bxr9I8xU25CsObKQKfZR5CpzRXqCWsk3TH8BcGnW1i7r/gtumOuZZvtq1io1h6sJtKo2WZ+dnNsTYfjG7u3Jo/2RHxcnrKsLmHdxLVpWyQct/az+FQaE1oz18tFt2EVq1r+DPh3bIjavWGx7W+lSVzvcNPXrzpj2RnP1z2YJ04H59WRZQ/9d6kwfdTTnL+n6yCLZ+YTR8N8y5+i6jw846/YemAOiHuyy7IFJZYk/+Q3sZLYaA4Ze3Hs61Xr01rvi2ZTIj6Xq17ryTksnyUxpn7DOeQD/I+hAw84CshOLrveNB6WLTqQhrH61MEgqDSE//rj/7NfvNtMPjDAXxyAbGtPSCMKvWMhZefeQNvq8ydNTXxkRhV3rGso7ly/s6V397qntCxqgofXK9ZHbk9hDqPmp5vhwBRzF03tle2O2TK+YaDVgtHZE6oJgNv51B2jtbxqiMvHjdx12MH9Fj8iUMK8t47o+W8dGLd1/sGqmVnEffu3btncO8g0eAf1YxJKgPFSsVGuwyNApNYOpYo1xoR8cGWNh2eAj2f3E4n+b+G3na82Vv3dOT7X9h/mZn7/51EFlOarYks5Dp4Tb6JicMkT5ivibdYb/hqo2PKmdJch5MT2c0fr8kpRaMk+W9Lhdhad8bk7dfkTG15iX469gN3jb0uvLT4iF1yeljh9WcjtRvV2N+WMPOZ4+3kPBk8CkwRZsV5N1Is44fCWyKZI+J16Xa5ynGytHS5H+hKe1ohpVi5Gydmb/50jJFtIzUq3gBFredq6reWZvh1vb73EcGLVpqflQQ/qbp4Bx4LEsisai4qajuq+SAp1V6xtK/WKvmF7uV2Z+24qU/x1xKVLD4e7o9x2GJOeN2s0GfFOjAVQsmq6ZySkx8pcElajiW+jw9Jl8PrM5RLEGDf/i3L7t2djvKPzTdDQ5nenOilxKYhLW2UxxwfHn47FfciB7MhtX2/5wcuW8Gwa592pBfF/sP4NZm/aUvRjZ7eWN0nqJyguAgDKrSeuP6rgtemfFua2iVPSx1TDzRhjjDpZHqJodpxsv+szOeiUOXxBB3vKqG91uLja5UenyiVPhA7dG2oYG9GJ1Er8Vq1rNKhvm8yhl5N9uPoi3Vu2RRf8zpaJnLHc/E5DGcJcXt6cxIPlLwPuvPk5I++1sEJHf9nSX+HbWLWRwzeFb3ZlZBbvyPpBnHwXPox76nLvXHtJeUWbZk1dXERdu0V+8zzk2gv9GTJqfXgEbue3zxGzfb99qsKXOSEscCT5J7r6a937Us68rhrs772eAPjs3vq0wFJpxt5DxWcS5Wydabl9LNKPS9Vd+J0oL5lvSndQP8lAd9dYQO6uiwX1eI43nRlxVQr8h1/sxkx2euv0a0RJ8BvwYFXRTcc9OGXl2zd/8L27bo3idzJgx29ee22DaX8OyzM918q9XvTQ5BoMI7mkcphkfbm76jVsvUvfn5PngFJQFSNVkeljX6x387dTfxg5xBagxhn2adfQZ+5fkr1jE/EOl2rOAHlraNf0ZGVpoHB8bHl0m6sIevZA/hLtl0vUL9d+vmG4unvd/FWj5kaDhyg7BFpVDRxP1a9kVfq/bP+7W8ZXLEbKeIKhLmtfdrX4IuFm5rVDQYk3+10KRvDKn8Z1tpKTIgZLbx0Kq91tJKvsuPhk+Zj7UQd3aIMO2EEa9F7r3f+xzJv47OH9yhb04iVDFbzj+Ad23I41/ffFH4kkUaMMBIcVmadbHEfkZpips7PKcS2pmVVlidFDezAtqa1vKq+8s+VU8Y6Vp7xhOEHkoe2Zrndr0B+Q3t+JPbLxERUmOFy8y039RwfczUvG6a9p4coq0Gl+nTcEfH1oGpQSZa+/aK92dpgwsq+2rAtJ10pcxLPLdCcXpOpcPe9LuJS7kvHeBfLKaeQbTdc2ngPpr/CHNRO29qgiTg7UaDB/GJfVyXl83RUecaBEcuIBsUBDaHS/F3u+syjrYEjbwuR/677J+SY0+FH6g7XvGmnYnsPmOAIeRPrWrWPmpz2DrjI87f/bn1cJkeqcDi3L16mdquIcolH/9vgdUVBfkaT/E9aNzUU8764je1zaXrZbPO53NUj6FqCtfvZpFO+iu+VpzKEZAOpdwjkjdT2buuuu65xMZUG2d9sMnbswgsLJGJ98OF3pfUn2Ao27vjwge8GftDGzRPvLHX35ai8Y5aiQSIuKvJD4UR5UIPJDzWB0Om04zS8V0w4fTmln6U9H1Ut6mtpoylqTmWPLt4jNp3GzExhzsKJq/ym9eW1HLcXl6vJCb1Ht7xik33YEv7JyR5pLB5qtEddo+GLduNplfEQw+XRXT8h+QorKbVLqckifUwrO0Cf8dCtzQeY8KonaToaGDd87Erd+0V3Oj/5jF5UZjTGZpyDpXNiyD3m0GOCzf3Qg/20eabhxl5vY827TlXWfRbOPI8f0s/UwmXrEMKDFJU7zzxrqDk15jC6P7xDHc3ryRdNX3P6htnhfslIbCxRXLuaJzwgU4eloCtMNruyOSKcgVVJga2IpkGjHY0l3mpIKrze945HaNCgzuRSYjYrLtTk+iHpbXVCkZ05Q7d3CVp94tmJeiOcd2IIoR87Ush5P0PTgcYqkOL4fVP9o8Ty3EDZqdefej6dfUt1pZhtWGAf5mywDbqAg3oXQ49s37B05AaPHBHWxocjTo/S6S+9d0ejT3TfHIlpLJY+3lCb3QPi3XrUVRjLt9tR+b3GyuhZazyWQXsjy7epnHvZHHKFMVLVg8UtTNk1deRQQJv0Yc24JhU0rfONpM17VJJ4C7Imd1b6pNn272OM7LeJQ6Brzu4dyOlXjtZlQtbemdj3bJr9d9ev/3ZABhMAl5mXa67Pvvng7LA+EkfwkNdb8epal2J+JqqPDtLBRWCQ4oxonupGngyGLc/Mo52tL04bxPQnGmgW7Y8IT3h2ICzcw7R9MrBwu092EoHLnmhIw1fYnzJ292tad+HN8FPyBcwf+BCn+UIL/P069IY2ae+n2ew/UPew0sK/76unPR+9w5PcdAGG+MPBu6tfyPMkyJee8rzR/WTvWN8U41xFmN4x03MAgIfL9vB/VrSK3s3P10A81uWoMwaPJWfIkLgRylykTMustBKtnWoo8uvbqplWRaV2I+Xv5sLjGBEgCADYvORLKyuUzBnj6+r563Pin7268pGBQdm64vAYRxzWHa4/JU1fW6+iEm1mqFNds027shKNNE5JbSeUSWZ5jIwyug0PezATamsksz6lpOlXoe+lzjdMhGjf9ssDAKSWFMNDToy9qyt+CSUVNSjteRnEURa3xTSYFitqLf8ljKyGI1iM3eIayrX0dGc1zPZoJLOIRz3xi+qY8L9Qvx0AgPpzHbO/4ToIRvhB77WhTEfNSkXKqjI/EQKN7oWKelkxMDAwyAldFVK459U3jtMI/fceg3Jo9PgdIbkJVqOMsO7uqIZ3QddfRJlSC10oaBC482a/xKbjIRvktMdFow0TjKUrOKI81oWHu0RHEYk662SIxuGXJZl3lza+2iPbnWQXg7l2MeaWdVAjSKZKwW06HoJ2Hi48QfW7yPLqi1uiAAATfzqJpVcxicl4JU123pDMkfk1t995uIqT5HPMCnnIzQ0CfA7M84grNe9aJQ+5Z5+SVl2JrhbV056bhHUVImVVRsYv76e27yntahe27wjm+Hybftud9s5Y/r/mP5NTjXfS/pyD+ks+Hk5yOlwwjqv5PMm/GNbMDyky/nkOD8tkwe2r1Oud+VC59j9QSS9CRfKkbFR820mpyHf756iWODHWUHBQzmuFxqXWAfrff5cY+PPnEgkuUiBouokZBnRrHogk5zOPQT4HNbcwf+xWWAMWpqLgAqDZow0wAV6/By+ViiLFg0aFOGB4PBRg6fzSUnUxw+p6TApFpjzyHe45jN7daygBuaQRvBpoIogLVo0qZPgiSSNSMGgUiAEGljgHBo8ULWUIFcyQsV8Aq/BBhgqQyw/BpUNzPnAf3CHDF8kPkYJBAz5wH17NgcGDQiv3QYwakKaC4NzQb2tMMG4sNSCfCiKFgMZx4BAlcxCksZ8FKiAJGziEIg0gn+IhhYDGWVhgEPfnIMgEc1aOwkULFs3crPx5BP5CWdG8ZITNy/xfQxfGaeAlQJMv8Kk5DUcgF6chBYOmXOBg1nRguejMymt7CgWDxWTgcqApFrgcCnqwXEyGFAyaV4GDuZKAkYnBrLy2OigYLPAClwPNpHDC5HAwgGUCL6RY0PAJHOs4CdbCTMtSla2FVfYRikUaXiE5NyDBEvipKsQIVhBeIcWDBkfgeNEL8ciEU1ZeJDUTWDx8AhcFjXpww0TpLAAhEz4hhYOGM+BwXxbCLUyLLLUp0cE2pWBmQC7fsfhBwQZT82JuONl8BykONFMBx9m2FiwR4CDFgQYlWOGXBxgOSR6DFAYae4Bfz9azgKUzFqRQ0FgCOwzqAikUSWpiqUdFD3tUiqyAbKhhpZcbf1ZALtQALwQaMYBf5vMgw8mEGpbSwQDTsYMNkM8nkOyJkI4d3FJfGMDCfAIpErRTBj/YPXjBov2+lR/sGnyAtMcGFwBtgcFLCeADS/bYlnKVFeZqLxRpYQMNLgjav+KDCZLlBytuoC14fYJ0pHhgqIXkUMl1NBbsg5DmEhxSYwtYWWOLFBLayoFDVpGDJNenWPlTIQiApTpCcGXQrg1c2TgZmBX5B23QwCExgmBlzR9SSGivBQ5ZRg7yT/3jhPlntBUs27KBy4P2VPhh8m4shkWuZUMKC+2fwGHphMDK2zMrP5g9ILCQjYWa5ucfKANl4M8GAOHn+wr4vwAAAP//lfLuQkM3AAA="); err != nil { + if err := gres.Add("H4sIAAAAAAAC/7RaeTiU6/9+sq8hSyohYpAxtkiyFLKMfYtSGgxxGGHsLShlSUqh0nZQpGObiCMplX3LlrQoCpF9RIR+V4T3HWPJ+X27rvjHc9/3536f7f28tyGakooD0AE6MIh1NASQf+sAPbB1xdk7OqBmfkngXV2czUypwZqOnfaHxCp0kSgt8TLDDDPDuprSGr1GFArVhNKuQpZqi2sjzdvCJHT0ysTPtxsbGhpqlYlXm4N7srLSTTIvZV7KZEnLyOU0SBlSoBEIYYdEm4typhRUAPz8aYimpbv+7IKdOQDAHgCwuDzmeXkuvhKOOMf/V2XG88rM55V9D1RTJFUGwDmZB8ZQZTRzyqYl0WckHv41GPoni5e1YW4w0tbTA+/q8j8z32i+RLP5Ek3V5d4ubz7HApX/g2dgMi9w37xASdr208s/A2ZSgcs+CgAuu92EFUkH6IGdo7sUysPTZnp487uUQyt/klyQ4b/+I/FYD7yUBN4HP++ShF4dUhspIWWqVV2zTbtq25q5MgseXNq5HgDAtiQHwyzHNPY8LnnMEEqVKfZpc5ayjuo35uodk/5vjknDHJMm75jxgur8I3kpV+yY9LRj0nDHFmLqqRBKVuyY9CqWOxOgB45SO3AoLA6+wtvKtarKyvZlmBkOAanLueXEcHdj1YmjrGr0rLPibL2RqnwAAP6VMThhFmPwe7djzK3+c0nEfT55iWPvcg+F3t/1RARBvZVoV5LVaeJ/GayZ5TRy4PeSAgBsWxmnu+dinHmSP22eB29eL3fwtV+m9m9C8fzOZ+fDmXUCwFyNO/L+QgIARJbkY5nl8zuCVNOHU85XSfAurL/jHz3j5VMKxbtKt94kUNHNUnW1lEcIAQC2rJjKdN//IxX5afWLahULkfH3UKSdozsKi1vlWoQgoI5gnZ1dl5qi83PE/auTLQcAgPWPCLxd3Z3tSAjEalDl2kYZZoYMtFCCG2yeCFKCZTxwwvxXD5wwvz3wxSzwIMd44RqaX6f59mKW/NNH+5+QzfgBI5u2I9PY0I9g+4Z4TwmyTG7utnbmmp6dK3bE3XMVjnDCEX47Aj34F1ng8244H9sisnm63j8hmnEDdsOYnRt5GaSbBvZr76P1y86PtVCK6Y1jFYZsWADy2xMnD1fcnNaUjl/XHMMZbwjG8/uD05q5SV2whTFv+VlChm/GGnJ8MxYR5ieMVfa8SQy96ClSvuVNMt23CpO4F4D8NskHMrkJmemlVUYiaKRYdWXNAzPpBhlDY60qdLVBWSU6x0wMqV2fbUjI+UQok80qffDpV30ZZdNrz3/GTJfhLbskNh3PYFCdK5HVjyvSAABg8KfqZiz9j+pEp1er/4z1cuOsvLERyhbU/LPixoV1o/VJxC08A+gg4lZxvWCbHW7v6Iz9390x4DROmJXukTPHfpHO7q6y8441SSMiayhmiTmKKyx+XTRElyRmhRG7e65kK1r0rvHMGDUpDgBALPvqM085c+FYuPgWLHW8alD7mIaC6OHCzfU3DXqN2lKo526ZyoaohxIAAPElidlJiU33/b8voBBd3xNG+mHvL02LzKw9O5DrPFzoN79LlamW12MAAI5Lzlp6qNRVvke4u7riUbYeHqvYb9ZBhqM88L7OWIlZoF9G1RknT++PWhU122qR1f/oZBtLyoklZ9AwxiSaX/4i3GF3t9NSdGtSR3Qil6VHI8XcnBRrseneDgCQXlI8wyy7owvGAbsK+ewwAJSzq4OrxFGcw5x+51jfHQ1/bSpJko4OFdgZ8/KKuqV95WluNYzkiEupsax9Wam4vbB/c4m7IXvzA46yqO0HTAVOK3AQhH5E2ZZ7Rd25Puqu3+9b49lCtGoilvRPHVcZHS1I/zHVGZr5UJaLSjsIgIDb9DQujACwPhr9eAIAfg/lPAoAejBvNn6l1Hl059fZrbL50wsqsDa0p0UpYFjXDRx2Fd3wtdQ9S1KzzZFD5LyqB2LPpAj+ad/usS2TIniGi8GUP4zyk3UTb639zEIdVa7edwbDOsC37tX2zSEJwdtfBxTzBzX+7WRoFiJBx0STwE21ro7LZZjSgVVrJysLV/j504XHjh//u8Dhb9liM24rNJrxbljAFlYxJgrt4C4DbOuEaXLlpZPCY668shdDHS50iUtHMLRMFv9bn/YAz3+B75pKTeRO0eEn55Ir+Q62OnJc4Tu/9pSaGeYnDdvASanhr0zpKjqCHzHqKIFgLawU5qjIhLUC11X1ce+c9yon33RNRaqNGX3RoqRhKxGIdxOPl9jDmXYnPiUtPiUwWPCCA5tWbp4MwwhL7ZNWWZqYvcS6xrfZlJsublC33vhpI7vz4aQR+YmzX/jyqSNUhcd+3nq80/ggpa858ezkB/WTz+l9toYW3/5okWfH0CfGtjfpkEbh1PqJOPcc/0TOU7qJhKOP1OqDEw7/XP+jPwDz88rAra+UPzJV8vg5etIPNlAjHw33UwLVQuEjSv/QDlxKYGRWFHEbE/smln33uEqOmdXuY/u/iBh+OG8g1TMi6P/okKCJf0jB5IYfUwhzeeW2cE+NzGBN54cs53bJ331xZrdAZBBCqb+iQpjuu8WVU/e/jg0EHlJ68C6LutMlLeZcQL5QS0+l09BO+d4z9T69xFHbfehqh7BY3S9+n3FnT3fGWF+xvP4VtBiPCNqJxjIqpLOdORFrcbIgaEtDaWyTpcp9S0kzzk3v/G0GRj1tLryQv/LuMp1S3hfiBjFaxsvZshtx2nlZN/l41BRrA1yCeX3oage8XIWxjGlfXxzGxZVRNDXj+duR8ftedxsHNyU+vxrCVHFK4GpOj5jgq/zN4mlsRW6nLoadl9ZBMPskcTbg9TTDkUUadNdDTc0fv/bj2d8n2xvUxuqlrPl+r4q4bqrpVFXt+Vx1HHugwTmMYEp7bT2P/f0jlba2Aea+xkK1ojnUxm1P1JVjPmhKP5+0Dx6pfsPNJoq3HmY5YthG97ii8lHC54s/0Rt13ANPGoQZD92l4T64Hc8l4XW/uzMymEdPsP5zNI6oPSCa9eNb7SuPqNsPYyY3aGh9OxfCsudBl5tKzfgAJ7fRVlrWf8sRSMcateePqR26jybp+pzXpnFWeJlj4C1QN2b8KL2vy3Qnj8W3soOYASvXTier1Acjz4dFCteF5zHTPnouvEatV0XhtNXU0+exVxEo217CyPqDr03ZB9fE8ZSHWxRdbcV+NBUfxrfey0e/HAzslpI68jk9WG2rAyL8tfdGhNVLxJF0GRz/HlxRXLXTgGoh7RkdQebG8c3vWMYE/rLfiwhutbWQzxp+b8avYt6ZdAurwH30moL63ZHYBrndW77ZMiY+VO090/hXaZ7iplyF4c0Ugc8yD6FTmivUEtZJumP4C4PONjH3X3DbdMdcyzfbVrFRLD3YTaXRssz87OZYmw9GN3duzZ/oiHg5NWnY3MM7gepSNki5b+3ncCi0JrTnrxaLbkKr1jX8mfBu2RG1esPjWt/Kkrne4Scu3vRHsrMfLnuwTpyPT6siyp96b9Lg+0knOf9PVsGWT8ymjoZ5F79FVPh5x9+wdECdEPdlF2QKS6zJf0hv46UwUJyy9uPZ1qvXpjTflkwkRP2o1r1XEnJZPkrjzH2Gc8gHeR9CBh7wlRAc3Xc8aD0sWnUhjeP1KQJBUOmJ//VH/2a/+TYY/OEAPrmA2NYeEEaVesbCy8+8gbdV5s6amvhIjCrv96yjuXL+zpXf3uqe0LGqCh9cr1kduT2EOo+anm+HAFHMXTe2V7Y7ZNL5hoNWC0dkTqgmA2/nUHaO1vGqIy8eN3HXYwf0WPyJQwry3juj5bx0Yt3X+waqZWcR9+7du2dw7yDR4B/VjAkqA8VKxUa7DI0Ck1g6lijXGhHxwZY2HZ4CPZ/cTif5v4bedrzZW/d05Mdf2H+Zmfv/nUAWU5qtiSzkOnhNvomJwyRPmK+Jt1hv+GqjY8qZ0lyHk+PZzR+vySlFoyT5b0uF2Fp3xuTt1+RMbXmJfvr9J+4ae114afERu+T0sMLrz0ZqN6qxvy1h5jPH28l5MngUmCLMivNupFjGD4W3RDJHxOvS7XKV42Rp6XI/0JX2tEJKsXI3Tsze/Ol3RraN1Kh4AxS1nqup31qa4df1+t5HBC9aaX5WEvyk6uIdeCxIILOquaio7ajmg6RUe8XSvlqr5Be6l9udteMmP8VfS1Sy+Hi4P8ZhiznhdbNCnxXrwGQIJaumc0pOfqTAJWk5lvg+PiRdDq/PUC5BgH37tyy7d3c6yj823wwNZXpzopcSm4a0tFH+7vjw8NvJuBc5mA2p7fs9P3DZCoZd+7QjvSj2H8avyfxNW4pu9PTG6j5B5QTFRRhQofXE9V8VvDbl29LULnla6ph6oAlzhEkn00sM1Y6T/WdlPheFKo8l6HhXCe21Fh9bq/T4RKn0gdiha0MFezM6iVqJ16pllQ71fZMx9GqyH0NfrHPLpvia19EynjuWi89hOEuI29Obk3ig5H3QnScnf/a1Do7r+D9L+jtsE7M+YvCu6M2uhNz6HUk3iIPn0o95T17ujWsvKbdoy6ypi4uwa6/YZ56fRHuhJ0tOrQeP2PX85jFqth+3X1XgIseNBZ4k91xPf71rX9KRx12b9bXHGhif3VOfCkg63ch7qOBcqpStMy2nn1Xqeam6E6cD9S3rTekG+i8J+O4KG9DVZbmoFsfxpisrplqR7/ibzYiJXn+Nbo04AX4LDrwquuGgD7+8ZOv+F7Zv171J5E4e7OjNa7dtKOXfYWG+/1Kp35segkSDcTSPVA6LtDd/R62WrX/x83vyDEgComq0Oipt9Iv9du5u4gc7h9AaxBjLPv0K+sz1k6pnfCLW6VrFCShvHf2Kjqw0DQyOjy2XdmMNWc8ewF+y7XqB+u3SzzcUT/+4i7d6zNRw4ABlj0ijoon7seqNvFLvn/Vvf8vgit1IEVcgzG3t074GXyzc1KxuMCD5bqdL2Xes8pdhra3EhJjRwkun8lpHK/kqOx4+aT7WTtTRLcqwE0awFr33eud/LPM2Pnt4j7I1jVjJYDX/CN6xLYdzff9N4UcSacQII8FhZdaJFvcRqUlm6vycQmxrWlZleVLUwA5sa1rLq+or/1w5Zaxj5RlPGH4geWhrltv9CuQ3tOdHYr9MTESFGS4333JTz/HvruZlw7T39BBlNahUn447Ir4eVA0qydK3X7Q3WxuMW9lXG7blpCtlTuC5BZrTazIV7r7XRVzKfekY72I56RSy7YZLG+/B9FeYg9ppWxs0EWfHCzSYX+zrqqR8no4qzzgwYhnRoDigIVSav8tdn3m0NXDkbSHy33X/hBxzOvxI3eGaN+1kbO8BExwhb3xdq/ZRk9PeARd5/vbfrY/L5EgVDuf2xcvUbhVRLvHofxu8rijIz2iC/0nrpoZi3he3sX0uTS+bbT6Xu3oEXUuwdj+bdMpX8b3yZIaQbCD1DoG8kdrebd111zUuptIg+5tNvh+78MICiVgffPhdaf0JtoKNOz584LuBH7Rx88Q7S919OSrvmKVokIiLivxQOF4e1GDyU00gdCrtOA3vFRNOX07pZ2nPR1WL+lraaIqaU9mji/eITaUxM1OYs3DiKr9pfXktx+3F5WpyQu/RLa/YZB+2hH9yskcai4ca7VHXaPii3XhaZTzEcHl0109IvsJKSu1SarJI/66VHaDPeOjW5gNMeNWTNB0NjBs+dqXu/aI7lZ98Ri8qMxpjM8bB0jk+5B5z6DHB5n7owX7aPNNwY6+3seZdpyrrPgtnnscP6Wdq4bJ1COFBisqdZ5411Jz67jC6P7xDHc3ryRdNX3P6htnhfslIbCxRXLuaJzwgU4eloCtMNruyOSKcgVVJga2IpkGjHY0l3mpIKrze945HaNCgzuRSYjYrLtTk+iHpbXVCkZ05Q7d3CVp94tmJeiOcd2IIoR87Ush5P0PTgcYqkOL4fVP9o8Ty3EDZydefej6dfUt1pZhtWGAf5mywDbqAg3oXQ49s37B05AaPHBHWxocjTo/S6S+9d0ejT3TfHIlpLJY+3lCb3QPi3XrUVRjLt9tR+b3GyuhZazyWQXsjy7epnHvZHHKFMVLVg8UtTNk1deRQQJv0Yc24JhU0rfONpM17VJJ4C7Imdlb6pNn272OM7LeJQ6Brzu4dyOlXjtZlQtbeGd/3bIp9ruvXfzsggwmAy8zLNddn3nxwdlgfiSN4yOuteHWtSzE/E9VHB+ngIjBIcUY0T3UjTwbDlmfm0c7WF6cMYvoTDTSL9keEJzw7EBbuYdo+EVi43Sc7icBlTzSk4SvsT/l+92tad+HN8FPyBcwf+BCn+UIL/P069IY2ae+n2ew/UPew0sK/76unPR+9w5PcdAGG+MPBu6tfyPMkyJee8rzR/WTv975JxtmKML3fTc8BAB4u28P/VdEqeje/XgPxWJejzhg8lpwhQ+JGKHORMi2z0kq0dqqhyO9vq2ZaFZXajZRzzYXHMSJAEACwecmXVlYomTPG19Xz9+fEP3t15SMDg7J1xeExjjisO1x/Spq+tl5FJdrMUKe6Zpt2ZSUaaZyS2k4ok8zyGBlldBse9mAm1NZIZn1KSdOvQt9LnW+YCNG+7ZcHAEgtKYaHnBh7V1f8EkoqalDa8zKIoyxui2kwLVbUWv5LGFkNR7AYu8U1lGvp6c5omOnRSGYRj3riF9Ux7n+hfjsAAPXnOmZ+w3UQjPCD3mtDmY6alYqUVWV+IgQa3QsV9bJiYGBgkBO6KqRwz6tvDKcR+u89BuXQ6LE7QnLjrEYZYd3dUQ3vgq6/iDKlFrpQ0CBw581+iU3HQzbIaY+JRhsmGEtXcER5rAsPd4mOIhJ11skQjcMvSzLvLm18tUe2O8kuBnPtYswt66BGkEyVgtt0PATtPFx4gmquyPLqi1uiAADjfzqJpVcxicl4JU123pDMkfk1t995uIqT5HPMCnnIzQ0CfA7M84grNe9aJQ+5Z5+SVl2JrhbV056dhHUVImVVRsYv76e27yntahe27wjm+Hybftud9s5Y/r/mP5NTjXXS/pqD+ks+Hk5yOlwwjqv5PMm/GNb0Dyky/nkOD8tkwe2r1Oud/lC59j9QSS9CRfKkbFR820mpyHf7Z6mWODHWUHBQzmuFxqXWAfq5v0sM/PVziQQXKRA03cQMA7o1D0SS85nHIJ+Dml2YP3crrAELU1FwAdDs0QaYAK+5wUulokjxoFEhDhgeDwVYOr+0VF3MsLoek0KRKY98h3sWo3f3GkpALmkErwaaCOKCVaMKGb5I0ogUDBoFYoCBJc6CwSNFSxlCBTPk+2+AVfggQwXI5Yfg0qE5H7gP7pDhi+SHSMGgAR+4D69mweBBoZX7IEYNSFNBcG7otzUmGDeWGpBPBZFCQOM4cIiSWQjS2M8CFZCEDRxCkQaQT/GQQkDjLCwwiPuzEGSCOStH4aIFi2ZuVv48An+jrGheMsLmZf7voQvjNPASoMkX+NScgiOQi9OQgkFTLnAwazqwXHRm5bU9hYLBYjJwOdAUC1wOBT1YLiZDCgbNq8DBXEnAyMRgVl5bHRQMFniBy4FmUjhhcjgYwDKBF1IsaPgEjnWcBGthpmWpytbCKvsIxSINr5CcG5BgCfxUFWIEKwivkOJBgyNwvOiFeGTCKSsvkpoJLB4+gYuCRj24YaJ0FoCQCZ+QwkHDGXC4LwvhFqZFltqU6GCbUjAzIJfvWPygYIOpeTE7nGy+gxQHmqmA42xbC5YIcJDiQIMSrPDLAwyHJI9BCgONPcCvZ+tZwNIZC1IoaCyBHQZ1gRSKJDWx1KOihz0qRVZANtSw0suNPysgF2qAFwKNGMAv83mQ4WRCDUvpYIDp2MEGyOcTSPZESMcObqkvDGBhPoEUCdopgx/sHrxg0X7fyg92DT5A2mODC4C2wOClBPCBJXtsS7nKCnO1F4q0sIEGFwTtX/HBBMnygxU30Ba8PkE6Ujww1EJyqOQ6Ggv2QUhzCQ6psQWsrLFFCglt5cAhq8hBkutTrPypEATAUh0huDJo1waubIwMzIr8gzZo4JAYQbCy5g8pJLTXAocsIwf5p/5xwvwz2gqWbdnA5UF7KvwweTcWwyLXsiGFhfZP4LB0QmDl7ZmVH8weEFjIxkJN8+sPlIEy8GcDgPDrfQX8XwAAAP//frYA1UM3AAA="); 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 b228bde1c..37c21f2a6 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("H4sIAAAAAAAC/7SaCTTU6xvH3+xZQpYUIWKQMQyRZCnKMpaxRimNNWKEka0FpSxJKVTaLop0rRFJuir7viUpRSGyj2yh/ykX8xszmnT/nVNTJ+fzfN/v+7y/5ZkvGkVNwwkYAABX3G6hAcEvZrAa2Ds623kgbFyx9o4Opia0YFXL2+SDaBQ9A+EPkkdwECHgNp4eOFeXX5IYwLCdI4QkQJr077+kcK4uzj+pXdvtD0pU6MIRWpJl6HRTdH1NaY1eEwKBaEZoV8FLtSW14WYdoVI6emWSFzqN0Gi0VplktRm4LyeHbJatla2VzULKyuc0yhhSoWAwUYcE60vyplQ0AHz//kOriYZ8qxkAwH5ZrRvIaHXxkXLEOv6nMo0XZe5dlClN33nm1zJ5iGT+37xEL4o0WRR54/lF21+LJO6g/4eJRov6zBb1TQSoKxHrW9rhjAv6bB3dZVZwRNZAAAgPT+sVnA6+JZAfv+E4Ow+cjBTOG7dol5RePVwbLiVjolVds0W7asuqhfUWPLy8fR0AgH3ZSmzQSj8rLNJJk4OpVWc5AAAslDuJ/FMnkf+Fk0iIk0jSThotWa9fBD/1bzqJ/OkkEurkUrKeambJ7zjpKLMNS4GTxNLWQABwW0f3BWGUbwf7EgjCDvtHO0LAQRy2c3Z2hV6qOsq1qsrK9qabokeAzBWwat409y9ONpw/Df/9Ml6u7s62RGUkahDl2obppmhGesIyN9k9YcRlKHLFCfPfuOKE+dcVH8wSV3KM0L5vt026NXwqCX8goCB1/G3uwdVs89Lz7SUsBAEA61dQcs4hSMmfBmUYoX0zbd7g7yvr+IP5Qrd2WjlzAwBYf9cjd88VeMRLivOvR4T3kIW+yZP+bv0iaOM6+QOvfTO0F/1xPr5JbCMAgHsF5eb8gdyy5vsnL737+YUwFgJ/7L70P1n3yx7iXFrI9zBcXf+PHqugqH9dcvJwxS7oTu76cQ9Fz7mV+WN7vQob7vpF5TqtWjgEBZuY8n7dSWSrzplFquqcaZmLTWWZvWgbYz9qlrgqpbaZ7F2BbfxkUP/a5k1wGDIz0kqrDMVQcInqypqHpshGWbSRVhWq2qCsEpVjKgHXbshGZ+Z8zCyTyyp9+PHHWtPLfp5Yvzl7XUY37ZDiPZHOqLawXDZf7ggDAIDByjTOmfyHGsV/nnG/uc2Qn2LjjwlXMacVnJc4JaobpU8kcemOsEIl/vjrCjZj3VIKwg673L0htxwf5m6kNn2UTX3xkNt4wdUEAACCv13MCUPpJTfkwY5nYrAinZ09ZRccaxLHxFZRzZfnLK4wlwEAiC9bnptEeXdPSq5mc6VpJfOJrznPjRAzkgAAGIXvNIuFf55ZEqd1yRUCpxbYOblbUfxQ4caGWwb9hh3JtAuPNCpoxCMpAIDksuXXky5vsvc/P2vBuj4nDfVD313+KTWj7txQrvNooe/iJa5MrbwBAwBwXFbwWojg/18/EtVxwpCrQ6YXaTfjbUuyuo39CJ5kDB0Ej/1oxS2/U9ndk1xlCvvQZlveETgAQGzZqlzQqnMtCCm8uOL5+9NPd/+hUrqnfPtNPA3DfMGetvJwEQDApt8saLL3Pyy43NO7u6sr7o/eg34AEDYeHiu4qPIsgSA8cD7OdlLzuB8rrzdK+nl31qqo2VIHr/5bJ9tIWl4iKZ2OKTrB7Mpn0S7be90W4psTu6ISuC08mqgWLnYSbda9WwEAyGUXwgbV4OiCcVjJ/WE9CQzC2dXBVeoo1mFhLc4xPtsaj/CWJCKjQoS2R9de1bCwrzzDo46RHnMpNZKzLyuVtBf1aylxR3O0POQsi9y630TojCJnpsi3SJvyY5F3b4y76w/61Hi24S2b8SWDsydUx8cL0r7NdodkPJLjptEOBMD/zmo6FyYA2J6MfzgJgKCHSh4VAH2YNxu+UOs8ufvj6VJ148eXNGBNSF+bsv+orhs45Cq+/kupe5a0Zocjp9gFNQ/Yrhkx3D8DOyc3zYjhGC8FUX8zzE/STbi95hMrbWS5xsBZDNuQwNpXWzcGxwdtfe1fLBjY9JcT2jRYioGZLp6HZm09t8sotQOb1nY2Vu6wC2cKj5848VeBw19yxaY8ligU071Q/01sEsxU2kE9Bnbt0yZJlZdPiU668stdCnG42COJDGdsmyl+3JD6ECd4UeC6ak3EdvHRZ+eTKgUOtDtyXhW4sOa0uinmOx370CmZ0S/Maao6wh8wGgihIC07GcxRsWkrRe5rGlNeOe9UT73pmY1QnzT8rEVNx14iFOcmGSe1iyv1blxyalxyQJDwRQd2rdw8WcYx1rpn7XJ00Xvw9U2t2dS8l9ZrWG34uIHD+VDimML0uc8C+bThaqKT328/3W50gNrHDH9u5r3GqRervTeHFN/5YJ5nyzggwb4n8eDuwtl107HuOX4JXKd1EzKPPlFvCIo/9H3dt0F/zPerQ7e/UH/LUM0T5OxLO9BIC38yOkgN1ApFDyv/TT90OZ6JRUnMbVLiq0T2vROqOaaWO4/v+yyGfn/BQKZvTNjvyUFhY7/ggpn132ZhZgoqHWGeuzOCNJ0fsZ7foXDv5dmdQhGBMOXBigpRhgnzq6cffJkcCjio/PBtFm23S2r0ef98kba+SqeR7Qr9Zxu8+/HjNntR1Q6hMbqffT9hz53pjra6anHjC2gzGhO2FY9hUkxjP3syxvxUQeCmxtKYZgvVBxbSply8b/2sh8Y9rS++VLj69gqDct5n/HoJeqYr2XIbsNp5WbcE+NSV6vxdgvi9GeqGjrmK2jGlfnl5CBtbRtXcghPshMftfd1rFNSc8OJaMHPFaaFrOX0Swq/yN0qmshe5nb4UegGpA2PxTuRqxOlphsGLdjPcCDExe/ral2/fgFx/YAfbMRXNd3tUJXVTTGar6i7kamA5AgzOY4STO+sa+OwfHK60sfE38zESqRPPoTXqeKahEv1eE/lixj5orPoND7s4zmqU9TC6g+FpReWT+E+XvqM26LgHnDIINRq5R8dzYCuOW+rYg97uiCA+PeGGT1FYvPaQeNa3r3WvPCLvPIqeWb9b6+v5YNZdD3vcVGumhrh4DDfTsz0uh8Eda9RfPKV16D2aqOt9QZvOWbE2x8BLqH7S6EnaQI/Jdj7zr2UHMEOWrt1OlikPx16MihWuDctjoX/yQnSVer+q4hnL2X9exFyDIWz6M8fWHXhtwjG8KpavPMy86Fq73QcTyVFc+/18VO1wQK+MzOFPaUHqmx1gYa+9NsAsa2GH02SxgruwRbHVTkNqhfRndYRZmqY2vmWdFDpivwcW1G5jrpA1+s5UUNWsO/G2nSLP0euKGvfGYhrld276asOU8Eit/2zTkdI8Jd5cxdGNVAHPMw6iklsq1OPXSrtjBAsDzzWzDF50471rpuWTbaPUJJEW5KbaZFFmdm5jjPV7w1vbN+dPd4XXzs6gW/r4pxE9KgbJD6x8HQ6G1IT0HWkz781s17qOOxvWKzem3oA+ofW1LIn7LW760i0/OAfHobKHayUFBLQqIv1o9yQOv5txkvf7aBlk8cx09mioV3ErrMLXK+6mhQPipKQPhzBzaEJN/qPV1scUh4qT13w4137t+qxma8l0fOS3at37JcFXFCJ3n33AeB7+MO998NBDgZJMR/dtD9sPiVddTOV8fTozU1j5md+NJ4+z33wdDnq/H5dUgO/o9A+lSTlrfszXrJG/Xfbuqpq4CIwa/0TW0Vx5P+fKr626J3Usq8KG12lWR2wNps2jXS2wTQgv4a4b0y/XGzzjfNNBq40zIidEk5G/eyQ7R+tE1eGXT5t5GuyG9Fj98COKCl7bo+SP6cS4r/MJUM/Owu/Zs2fX8J5hvMHfaunTNAZKlUpNtum7C4xjGFgjXWvEJIfbOnT4CvS8c7udFI6MtHa92VP/z9i3I3aPWVgGH0/Di6lNV0UUch+4rtDMzGmcJyrQzF+sN3qtyTH5bGmuw6mp7JYP1+WVoxDSgndkgm2suqPz9mlypbTVov6Z+I69zlEfVlp82DYpLbTwxvOxug3qHK0lLAJmOFt5T0aPAhOYaXHezWSLuJGwtgiW8Dhdhh2u8lysbT3u+3tS/6mQUarciZWwN/tngol9Ay0izgBBq+dq4ruGbvR1g77XYeFLlpqflIU/qrl4BRwPFMqoaikq6jiq+TAxxV6pdKDOMuml7pVOZ+3YmY9x1xOUzT8cGox22GSW+bpFccCSbWgmmJpN0zk5Jz9C6DJSnjVuQADOkMPvPZKbKcSx9WuW7du7XeUfWm6FhDC/OdlPbZcKt7BWmXB8dKh1JvZlDmZ9Suc+z/fcNsKh1z9uSyuK+ZvpS5Jg86aim339MbrPEDmBseEGNCg9Sf1XBa9NBDY1d0qfkTmuEWDMEm7czVyLodl2avCc7KeiEJXJeB2vKpE9VpKTa5SfnixF7o8ZuT5SsCe9G6+VcL1aTvngwFdZ9LFm+0nUpXq3bKoveV1tU7mTubgcxnOZsbv6cxL2l7wLvPvs1PeB9uEpHb/niX+F8rLow4bvid/qic9t2JZ4Ez98Pu2418yV/tjOknLzjoya+thw286KvWb5ifQX+7Lk1ftwsB0vbh2nZf9251UFNmLKSOhZUt+NtNc79iYeftqzUV97spHp+X2NWf/EM038BwvOp8jYONNz+VqmXJCpP3kmQN+iwYRhaPCykM+O0CFdXdZL6rGcb3qyoquVBE682Qib7vfb3bs7VkjQnBOnhmo84C2oIN2+76VN69o3CTxJw139eZ02jaWC28zN9l0u9X3TlynVaBTFJ5PDivQS7KrTsvErfnFfgRGeCasar45MHf9sv5WnF//e1iGkBjbJule/YnXGuhm1s97ha3UtY4VUNo9/QUVUmgQExcWUI93Ygtdx+AuWbLlRoHGn9NNNpTPf7uEsnzI37t9P3SfWpGTsfrx6A7/Mu+eDW1sZXe02UMUWiPJYeXeuwhWLNrdoGAxJv93uUjZhp/J5VGszPj56vPDy6bz28UqByq5Hz1qOd+J1dIvSbUVhbEXvjr31O55xB5c9ukvFik6iZLhacAzn2JHDtW7wlugTqVR8uKHwqArbdJv7mMwMC21+TqFde2pWZXli5NA2u/bUtlfVV/++etpIx9IzLnP0ofTBzVluDyrgX1GeH/CDstHhFabY3HwL3r4TE65mZaP09/VgZTWIFO+uu2I+HjSNqknIOy87W6wMpiztq9EdOWnKGdM4HqGWtJoMxXvvdGGXc2sd41wsZpyCt9x06eA/kPYKc0A7dXOjJuzcVMFulpd7eyqpX6QhytP3j1mENyoN7RYpzd/hrs8y3h4w1loIf7z27+DjToeeaDhc96Kfienfb4zNzJta26591PiMl/8lvr/8dupjMzhTRMN4fHCydZvFVEo8BluD1hYF+hpOCz5r520s5n95x27Apbm2xfpTuatH4PV4K/dziad9lN6pzKSLyAXQbhPKG6vr39Jbf2P3pRQ6+GCL8cTxiy/N4bB1QYfeljacZC/YsO39e4GbuGFrN0+cs8y92nEFxywlgwRsZMT7wqnywEbj7+pCIbOpJ+j4rxpz+XAhn6e+GFcrGmjroCtqSeGIKt4lMZvKwkJlxsqFrfyq9fm1PM8xblfjk3pPbh+LSfJmj/87J3usqXikyR5xnU4gyo2vXdZDApvHcOOk9Cs7aZkdys3maRNa2f76TAdvb9zPjFM7RdfVyLT+Q0/Kns+6s/lJZ/UiM6Iw1pOcrN1TI+7RB59mWj8IOTBIn2cSZnSsNcas53Rl/SfRjAu4Ef0MLWy2TmZYoJJK99nnjTWnJxzG94V1aaD4PQWiVtecuWl6aFA6wi4GL6ldzRfmn6HDWtATKpdd2RIexsimrMheRNe4uxNlh7/dmFh4Y+Atn8iwQb3x5YRsNmyI8Y2DyC31IhHdOSN3dghbfuTbjngjmndyBKYfM1bI9SBd04HOMoDqxAMT/aP48twAuZnXH/s+nmuluVrMPiq0F3MuyBpVwEm7g7FPbmAUGbHeI0eMrenRmNOTtNWX37mjUCd7b41FNxUjTzTWZfeBOLc+DVWm8q22NL6v7WT1rHY/lUV5wcu3qJ6vbQm+yhSh5sHqFqrimjJ20L8DeUgztlkVRe98M3HjLtVE/oKs6e2V3qk2g3uZIgatY2GomnN7hnIGVaJ0meF1d6f2Pp/lWJhLD97xT2cG4AoLZW+wc+8/WFs7b6nDOII3WMnqOpdiQWaaDw7IoCIwTHVWPE9tA18646bnZlHOVpdmDaIHEww0i/aFh8U/3x8a5mHSOR1QuNU7OzGT2x6PphMoHEyeuPcltbfwVthphQKW9wKwMwIhBX6+XXojvNr76Db6DdU/qjT3G/jiaS+w2uFZbpoQY9yhoJ3VLxX44hVKT3ve7H22Z2Jghml+XZj+CZPzAIBHFL7n4uxcjjpjcH/2cjgPIWXQiKQhwkysTMu0tBKlnYIW+/frZVOtikrtJuqFWdbTaDEgDADYuKxu7qUlnTE+rp44mRXoFyELQ9i4YnEYR6ydO3Qtyan62noVlShTtE51zRbtykoU3Cg5pTOzTDrLY2ycyW101IMls65GOutjcqp+Fep+yuKsToS+dVABACCzrCQh8pLsXV1xy+ipqEFoL4rBj7O6kVNiUqyk9esR1TJKDtthbMkrKdfS051TMjcklM7CH/XEkVUz5XexYSsAALFSNXOfUDWZhrhhrzUhzEdNS8XKqjI+ZgYY3g8RP2bJyMjIKC9yTUTx/rGBSezukMf3GVVCoibvishPsRmmh/b2Rja+DbzxMtKEVuRiQaPQ3Tf7pHhPBK+X154Uj0LHGyErOCM91oaFuURF4vE6a2XxRmFXpFl2lja92iXXm2gbjbl+Kfq2VWATSKJJxvKeCEY5jxaepFlYann1pU2RAICplbU4cgUtTtY3JMl+IuqdxdO5z3m0iovoy8ffqkaqZzKhvbFYTVK5ZccfVSPVE8mp1ZWoanE97fkWra8QK6syNKp9kNK5q7SnU9S+K4jz053VW+52dscIHlkMDtBMdtP/6FD9ZbeNl7waF4zjSr68F12e+PMPGRKOeo6OymZBDa3U6//5Nf6aPy6IJFOQaAetVX06iQsCcMZa2oiwIO18wZ+8iRaqQ0vdWUXFSU0+Yzc/7Ozf+eOTTOKOPIIDglAnQixN3M2T5rwjTLQJLJAASAgIJ00imRIjphIG0DZAqIqrACXZOGIgYViMBwL8QASkSB9htosDgguiAsvmzZbbCUbITohQA9LJMPKANRCAPQRAlGeCrocwu8UHWc+DJRAyyTBiJGFoiw2C5KIB5CNglPvjSohBrsCfdAhgWX8IE1lQf74ugZDJexEjCaNYUH+saAH5YBfl/jxewBCluBYBpHJbi4ApCIAoxUVeBTsEokoHlk1xQU0hjFpBfT5HikMqxUWMJIxVQZEDJJEkEluUr9aUHiybzoJKI4xNQaXdJMUhlc4iRhIGpKBINgZAWfqK8tX6LEVCclZQaYQhKF6ItEJSHBI5K2IiYdoJSpReDSiKUi23Vk7IWq8tJRJnpoguqQRJJuiN8RMZFKnMFDGVMKkEpe5jBBRnoihfdi1JKiTzBBVImC3ihwjkZQKUZp6IoYRpICj0GTno0pDScotmhSzamhksEyuCSiNMWayDSEtaSlkS4yCmEYZ4oDQuFvDL3BAxjTCZww2hXSFBI4oBEcMIczbQRzKWNYCSaA8xkDAHsx4CPE8aSBTWWW4n1kJ4yqxguSwNMYgwvgIF3YWCiMMySxQRJFKgIAE2sFz2hRhEGPzggoCioCAScRbKWXTs4BdJFcofPrwWWEQhFEofznIhAKIQCnRFhGEQ6LP9zBIIiRDKcprYIJoC1oLl8iREtzuC2Sq0vUtJYJbmSYh5hDNN6L6VCoBfzGcpt/2OICA/E4UKIhxWQhfYuQTyu6K4IaLMN4FfDzyh4ggnjSLQM0wWRm7gueSVl2B2KARhbxMCvzO5XHKbIxgDQsHPyYNJDZWIwYRDNygYLQx+Z6ZI+Z4pbQa/nuBBVRLO16Aqg8nCKPKVcJQGBY+RB1PiK+FUDAq2FQG/M5dbzldeiK8j5MFEIzaoVMLplyhEqoYo+N0RGzGccNIFhWdTAEeS94LUsGzei+87RWFgyeiMlu7H/6oCVbCFAwDrH48+4H8BAAD//4eDbUOjOgAA"); err != nil { + if err := gres.Add("H4sIAAAAAAAC/7SaCTTU6xvH3+xZQpYUIWKQMQyRZCnKMpaxRimNNWKEka0FpSxJKVTaLop07USSrsq+b0lKUYjsIyL0P+VifmNGk+6/c2rq5Hye7/t9n/e3PPNFo6hpOAEDAOCK2y00IPjFDFYDe0dnOw+EjSvW3tHB1IQWrGp9k3wQjaJnIPxB8ggOIgTcxtMD5+rySxIDGLFzhJAESJP+/ZcUztXF+Se1e7v9QYlKXThCS7IcnW6Kbqgtq9VrRiAQLQjtaniZtqQ23KwzVEpHr1zyQpcRGo3WKpesMQP35eSQLbJ1snWy2UhZ+dwmGUMqFAwm6pBgfUnelIoGgO/ff2g10ZBvMwMA2C+rdQMZrS4+Uo5Yx/9UpvGizL2LMqXpu878WiYPkcz/m5foRZEmiyJvPLto+2uRxB30/zDRaFGf2aK+rwHqSsT6lnY444I+W0d3mRUckTUQAMLD03oFp4NvCeTHbzjOzgMnI4Xzxi3aJaXXANeGS8mYaNXUbtGu3rJqYb2FWZe3rwMAsC9biQ1a6WeFRTppcjC16iwHAICFcieRf+ok8r9wEglxEknaSaMl6/WL4Kf+TSeRP51EQp1cStZTzSz9HScdZbZhKXCSWNoaCABu6+i+IIzy7WBfAkHYYf9oRwg4iMN2zs6u0EtVZ4VWdXn53nRT9CiQuQJWzZvm/tnJhvOn4b9fxsvV3dmWqIxELaJC2zDdFM1IT1jmJrsnjLgMRa44Yf4bV5ww/7rig1niSq4R2vfNtkm3xo+l4Q8EFKSOv8k7uJptXnqBvYSFIABg/QpKzjkEKfnToAwjtG+mzWv8fWUdfzBf6NZOK2duAADr73rk7rkCj3hJcf71iPAestA3+dLfrZ8HbVwnf+CVb4b2oj/OxzeJbQQAcK+g3Jw/kFvWfP/kp/c8uxDGQuCP3eeBx+t+2UOcSwv5Hoar6//RYxUU9a9LTh6u2AXdyd0/7qHoObcyf2yvV1HjXb+oPKdVC4egcBNT/q87iWzVObNIVZ0zLXOxqSxzFm1jHEDNElel1DaTvSuwjZ8M6l/bvAkOQ2ZGWlm1oRgKLlFTVZtlimySRRtpVaNqDMqrULmmEnDtxhx0Zu6HzHK57LKsDz/Wml7+88T6zdnrMrZphxTviXRGtYXlsvlyRxgAAAxWpnHO5D/UKP7zjPvNbYb8FBt/TLiKOa3gvMQpUd0ofSKJS3eEFSrxx19XsBnrllIQdtjl7g15FfgwdyO16aNs6ouH3MYLriYAABD87WJOGEovuSEPdjwVgxXr7Owtv+BYmzgutopqvjxnSaW5DABAfNny3CTKu3tScjWbK00rWUB8zXlmhJiRBADAKHynWSz888ySOK1LrhA4tcCuyd2K4oeKNjbeMhgw7EymXXikUUEjHkoBACSXLb+edHmTvf/5WQvW9TlpqB/69vJPqRn154bznMeKfBcvceVqFY0YAIDjsoLXQgT///qRqI4ThlwdMr1IuxlvW5rdY+xH8CRj6CB47Ecrbvmdyu6e5CpT2Ic22/KPwAEAYstW5YJWnWtBSOHFFc/fn366+w+V0j3l26/jaRjmC/a2V4SLAAA2/WZBk73/YcHlnt7dXV1xf/Qe9AOAsPHwWMFFlWcJBOGB83G2k5rH/Vh5g1HSz7uzVmXtlnp4zd86OUbS8hJJ6XRM0QlmVz6Jdtve67EQ35zYHZXAbeHRTLVwsZNot+7bCgBALrsQNqgGRxeMw0ruD+tJYBDOrg6uUkexDgtrcY7x2dZ0hLc0ERkVIrQ9uu6qhoV91RkedYz0uEuZkZx9eZmkvahfa6k7mqM1i7M8cut+E6EzipyZIt8ibSqORd69MeGuP+RT69mOt2zBlw7NnlCdmChM+zbbE5LxUI6bRjsQAP87q+lcmABgezzx/iQAgh4q+VQA9GNeb/hMrfP47o+nS9WNH17QgDUh/e3K/mO6buCQq/j6z2Xu2dKanY6cYhfUPGC7ZsRw/wzunNw0I4ZjvBRE/c2wIEk34faaj6y0kRUag2cxbMMCa19u3RgcH7T1lX+JYGDzX05o02ApBma6eB6atQ3cLmPUDmxa29lYucMunCk6fuLEX4UOf8mVmPJYolBM90L9N7FJMFNpB/Ua2HVMmyRVXT4lOunKL3cpxOFiryQynLF9puRRY2oWTvCiwHXV2ojt4mNPzydVCRzocOS8KnBhzWl1U8x3OvbhUzJjn5nTVHWE32M0EEJBWnYymKNi01aK3Nc0prxy36qeet07G6E+afhJi5qOvVQozk0yTmoXV+rduOTUuOSAIOGLDuxaefmyjOOs9U875Oii9+AbmttyqHkvrdew2vBhA4fzocRxhelznwQKaMPVRCe/336y3egAtY8Z/tzMO41Tz1d7bw4pufPePN+WcVCCfU/iwd1Fs+umY91z/RK4TusmZB59rN4YFH/o+7pvQ/6Y71eHb3+m/pahmi/I2Z92oIkW/nhsiBqoFYkeVv6bfvhyPBOLkpjbpMQXiZx7J1RzTS13Ht/3SQz97oKBTP+4sN/jg8LGfsGFM+u/zcLMFFQ6wzx3ZwRpOj9kPb9D4d6LszuFIgJhykOVlaIMX82vnn7weXI44KBy1pts2h6X1Ojz/gUi7f1VTqPbFQbONnoP4Cds9qJqHEJjdD/5fsSeO9MTbXXV4sZn0G40LmwrHsOkmMZ+9mSM+anCwE1NZTEtFqoPLKRNuXjf+FkPT3haX3yhcPXNFQbl/E/49RL0TFdy5DZgtfOzbwnwqSvV+7sE8Xsz1A8fcxW1Y0r9/OIQNracqqUVJ9gFj9v7qs8oqCXh+bVg5srTQtdy+yWEXxZslExlL3Y7fSn0AlIHxuKdyNWE09MMgxfvZrgRYmL25JUv375BuYHATrZjKppv96hK6qaYzFbXX8jTwHIEGJzHCCd31Tfy2T84XGVj42/mYyRSL55La9T5VEMl+p0m8vmMfdB4zWsednGc1RjrYXQnw5PKqsfxHy99R23QcQ84ZRBqNHqPjufAVhy31LEHfT0RQXx6wo0fo7B47WHx7G9f6l96RN55GD2zfrfWl/PBrLuyet1Ua6eGuXgMN9OzPaqAwR1r1Z8/oXXoO5qo631Bm85ZsS7XwEuoYdLocdpgr8l2PvMv5Qcww5auPU6WKVnjz8fEitaG5bPQP34uukp9QFXxjOXsP89jrsEQNgOZ4+sOvDLhGFkVy1cRZl58rcPuvYnkGK7jfgGqbiSgT0bm8Me0IPXNDrCwV14bYJZ1sMNpsljBXdji2BqnYbUi+rM6wizNUxvfsE4KHbHfAwvqsDFXyB57ayqoataTeNtOkefodUWNe+MxTfI7N32xYUp4qDZwtvlIWb4Sb57i2EaqgGcZB1HJrZXq8Wul3TGCRYHnWliGLrrx3jXT8smxUWqWSAtyU222KDc7tzHG+p3hre2bC6a7w+tmZ9Ct/fzTiF4Vg+QHVr4OB0NqQ/qPtJv3ZXZoXcedDeuTG1dvRJ/Q+lKexP0GN33plh+cg+NQedZaSQEBrcpIP9o9iSNvZ5zk/T5YBlk8NZ09GupV0gar9PWKu2nhgDgp6cMhzByaUFvwcLX1McXhkuQ17891XLs+q9lWOh0f+a1G935p8BWFyN1nHzCeh2flvwsezhIozXR035bVcUi8+mIq56vTmZnCyk/9bjx+lPP6y0jQu/24pEJ8Z5d/KE3KWfNjvmZN/B2yd1fVxkVg1Pi/Zh/Nk/dzrvrSpntSx7I6bGSdZk3E1mDafNrVAtuE8BLuujEDcn3BM843HbTaOSNyQzQZ+XtGc3K1TlQffvGkhafRbliP1Q8/qqjgtT1K/phOjPs6nwD1nGz8nj17do3sGcEb/K2WPk1joFSl1GybvrvQOIaBNdK1VkxypL1Th69Qzzuvx0nhyGhb9+s9Df+Mfzti94iFZejRNLyE2nRVRBH3gesKLcycxvmiAi38JXpj15odk8+W5TmcmsppfX9dXjkKIS14RybYxqonOn+fJldKex3qn6/fsdc5GsLKSg7bJqWFFt14Nl6/QZ2jrZRFwAxnK+/J6FFoAjMtyb+ZbBE3GtYewRIep8uww1Wei7W9131/b+o/lTJKVTuxEvZm/3xlYt9Ai4gzQNDquZr4rqEbe9Wo73VY+JKl5kdl4Q9qLl4BxwOFMqpbi4s7j2pmJabYK5UN1lsmvdC90uWsHTvzIe56grL5+0ND0Q6bzDJftSoOWrINzwRTs2k6J+cWRAhdRsqzxg0KwBly+b1H8zKFOLZ+ybZ9c7e74n3rrZAQ5tcnB6jtUuEW1ipfHR8eapuJfZGLWZ/Stc/zHbeNcOj1D9vSimP+ZvqcJNiyqfhm/0CM7lNEbmBsuAENSk9S/2XhKxOBTS1d0mdkjmsEGLOEG/cw12Fotp0aOif7sThEZTJex6taZI+V5OQa5Scny5D7Y0avjxbuSe/BayVcr5FTPjj4RRZ9rMV+EnWpwS2H6nN+d/tU3mQeLpfxXGbsroHchP2lbwPvPj31fbBjZErH71niX6G8LPqwkXvit3rj8xq3Jd7Ej5xPO+41c2Ugtqu0wrwzo7YhNty2q3KvWUEi/cX+bHn1fhxsx/Nbx2nZv915WYmNmDISeprUfyPt1Y69iYef9G7U155sYnp2X2PWP/FMM//BwvMpMjbO9Fy+likXZBpOngnQt2g0YRgeuizksyN0WFeX9ZJ6LOfr3uzoGiWBE683wqYH/Hb37Y4VEjTnxKmhmg54CypId+x7YdO29nUCT9JI90B+l01TmeA2c7N9l8t8X/dnSjUZRfHJ5LIivQS767Vs/Eqe31dghGfCqidqIlMnPtlv5enDv7N1CKmFTbLu1a9cnbFuRu2sd/haXctYIZXNE59REVUmAUFxMRVIN7bgdRz+gqVbbhRq3Cn7eFPpzLd7OMsnzE3791P3izUrGbsfr9nAL/P22dDWNkZXuw1UsYWiPFbeXatwJaItrRoGw9JvtruUf7VT+TSmtRkfHz1RdPl0fsdElUBV98Onrce78Dq6xem2ojC24rfH3vgdz7iDyxnbpWJFJ1E6UiM4jnPszOVaN3RL9LFUKj7cUHhMhW263X1cZoaFtiC3yK4jNbuqIjFyeJtdR2r7y5qrf189baRj6RmXOZYlfXBzttuDSvgXlOd7/JBsdHilKTavwIK3/8RXV7PyMfr7erDyWkSKd/ddMR8PmibVJOSdF12tVgZTlvY16M7cNOWMaRyPUGtabYbivbe6sMt5dY5xLhYzTsFbbrp08h9Ie4k5oJ26uUkTdm6qcDfLi729VdTP0xAV6fvHLcKblIZ3i5QV7HDXZ5noCBhvK4I/Wvt38HGnQ481HK570c/EDOw3xmbmT63t0D5qfMbL/xLfX3479bEZnCmiYTw+ONn6zWIqpR5DbUFriwN9DacFn3bwNpXwv7hjN+jSUtdq/bHC1SPweryV+7nE0z5Kb1Vm0kXkAmi3CeWP1w9s6Wu4sftSCh18qNX46/GLL8zhsHVBh96UNZ5kL9yw7d07gZu4EWs3T5yzzL26CQXHbCWDBGxkxLuiqYrAJuPv6kIhs6kn6PivGnP5cCGfpT6fUCsebO+kK25N4Ygq2SUxm8rCQmXGyoWt+qL16ZU8zzFuV+OTeo9vH4tJ8maP/zs3Z7y5ZLTZHnGdTiDKja9D1kMCm89w46T0SztpmR3KLeZpX7Vy/PWZDt7euJ8Zp3aKrruJaf373pQ9n3RnC5LO6kVmRGGsJzlZe6ZG3aMPPsm0fhByYIg+3yTM6FhbjFnv6aqGj6IZF3Cj+hla2BydzLBAJZWes8+aak9/dZjYF9atgeL3FIhaXXvmpumhIekIuxi8pHYNX5h/hg5rYW+oXE5Va3gYI5uyInsxXdPuLpQd/nZTYtGNwTd8IiMGDcaXE3LYsCHGNw4itzSIRPTkjt7ZIWz5gW874rVo/slRmH7MeBHXg3RNBzrLAKoTD0z0j+Ir8gLkZl596P9wro3magn7mNBezLkga1QhJ+0Oxn65wTFkxHqPXDG25ofjTo/TVl9+645Cney7NR7dXII80VSf0w/i3Po1VJkqttrS+L6yk9Wz2v1EFuUFr9iier6uNfgqU4SaB6tbqIpryvhB/07kIc3YFlUUvfPNxI27VBP5C7Ont1d5p9oM7WWKGLKOhaFqz+0Zzh1SidJlhtffndr7bJZjYS49dMc/nRmAKyyUvcHOvf9gbe28pQ7jCN5gJWvqXUoEmWneOyCDisEI1VnxfLUNfOmMm56ZRTlbXZo1iB5KMNAs3hceFv9sf2iYh0nXdEDRVu+cxExuezyaTqBoKPnrvc+pfUW3wk4rFLK8E4CdEQgp9PPt1hvl1d5Ht9FvuOFhlbnf4GdPe4HVDk/z0oQY4w4F7ax5ocAXr1B22vNm39M9XwdnmObXhRn4anIeAPCQwvdcnJ3LUWcM7s9eDuchpAwalTREmImVa5mWVaG0U9Bi/369bKpVWaXdTL0wy3oSLQaEAQAbl9XNvbSkM8bH1RMnswL9ImRhCBtXLA7jiLVzh64lOVVfW6+yCmWK1qmp3aJdVYWCGyWndGWWS2d7jE8wuY2NebBk1tdKZ39ITtWvRt1PWZzVidC3DSkAAGSWlSREXpK9qytuGT2VtQjtRTH4CVY3ckpMSpS0fj2iWkbJYTuMLXklFVp6unNK5oaE0tn4o544smqm/C42bgUAIFaqZu4TqibTEDfitSaE+ahpmVh5dcaHzADD+yHixywZGRkZ5UWuiSjePzY4id0d8ug+o0pI1ORdEfkpNsP00L6+yKY3gTdeRJrQilwsbBK6+3qfFO+J4PXy2pPiUeh4I2QlZ6TH2rAwl6hIPF5nrSzeKOyKNMvOsuaXu+T6Em2jMdcvRd+2CmwGSTTJWN4TwSjnsaKTNAtLrai5tCkSADC1shZHrqDFyfqGJNlPRL2zeDr3OY9VcxF9+fhb1Uj1TCa0NxarSSq37vijaqR6Ijm1pgpVI66nPd+iDZVi5dWGRnUPUrp2lfV2idp3B3F+vLN6y92unhjBI4vBAZrJHvofHaq/7LbxklfjgnFcyZf3ossTf/4hQ8JRz7Ex2WyooVV6Az+/xl/zxwWRZAoS7aC1qk8XcUEAzstmGREWpJ0v+JO3Oj3h0FJ3VlFxUpPP2M0POwd2/vgkk7gjj+CAINSJEEsTd/OkOe8IE20CCyQAEgLCSZNIpsSIqYQBtA0QquIqQEk2jhhIGBbjgQDfEwEp0keY7eKA4IKowLJ5s+V2ghGyEyLUgHQyjDxgDQRgDwEQ5Zmg6yHMbvFB1vNgCYRMMowYSRjaYoMguWgA+QgY5f64EmKQK/AnHQJY1h/CRBbUny9LIGTyXsRIwigW1B8rWkA+2EW5P48WMEQprkUAqdzWImAKAiBKcZFXwQ6BqNKBZVNcUFMIo1ZQn8+R4pBKcREjCWNVUOQgSSSJxBblqzWlB8ums6DSCGNTUGk3SXFIpbOIkYQBKSiSjQFQlr6ifLU+S5GQnBVUGmEIihcirYgUh0TOiphImHaCEqVXA4qiVMutlROy1mtLicSZKaJLKkGSCXpj/EgGRSozRUwlTCpBqfsYAcWZKMqXXUeSCsk8QQUSZov4IQJ5mQClmSdiKGEaCAp9Sg66NKS03KJZIYu2ZgbLxIqg0ghTFusg0pKWUpbEOIhphCEeKI2LBfwyN0RMI0zmcENoV0jQiGJAxDDCnA30kYxlDaAk2kMMJMzBrIcAz5MGEoV1ltuJtRCeMitYLktDDCKMr0BBd6Eg4rDMEkUEiRQoSIANLJd9IQYRBj+4IKAoKIhEnIVyFh07+EVShfKHD68FFlEIhdKHszwIgCiEAl0RYRgE+mw/swRCIoSynCY2iKaAtWC5PAnR7Y5gtgpt7zISmKV5EmIe4UwTum9lAuAX81nKbb8jCMjPRKGCCIeV0AV2LYH8rihuiCjzTeDXA0+oOMJJowj0DJOFkRt4LnnlJZgdCkHY24TA70wul9zmCMaAUPAz8mBSQyViMOHQDQpGC4PfmSlSvmdKm8GvJ3hQlYTzNajKYLIwinwlHKVBwePkwZT4SjgVg4JtRcDvzOWW85UX4usoeTDRiA0qlXD6JQqRqiEKfnfERgwnnHRB4TkUwJHkvSA1LJv34vtOURhYMjqjpfvxv6pAFWzhAMD6x6MP+F8AAAD//+csdAyjOgAA"); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 64e2ae688..08c3b498e 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -148,7 +148,7 @@ func (e *validationError) FirstRule() (rule string, err string) { array := strings.Split(ruleItem, ":") ruleItem = strings.TrimSpace(array[0]) if err, ok = errorItemMap[ruleItem]; ok { - return ruleStr, err + return ruleItem, err } } } diff --git a/util/gvalid/gvalid_z_unit_checkmap_test.go b/util/gvalid/gvalid_z_unit_checkmap_test.go index c74ab2925..dbb693e24 100755 --- a/util/gvalid/gvalid_z_unit_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_checkmap_test.go @@ -191,6 +191,11 @@ func Test_Sequence(t *testing.T) { t.Assert(err.Map()["length"], "账号长度应当在6到16之间") t.Assert(len(err.Maps()), 2) + t.Assert(len(err.Items()), 2) + t.Assert(err.Items()[0]["passport"]["length"], "账号长度应当在6到16之间") + t.Assert(err.Items()[0]["passport"]["required"], "账号不能为空") + t.Assert(err.Items()[1]["password"]["same"], "两次密码输入不相等") + t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间; 两次密码输入不相等") t.Assert(err.Strings(), []string{"账号不能为空", "账号长度应当在6到16之间", "两次密码输入不相等"}) From ab2ef13d993b135978386eacb6dbd3cae13d0822 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 20:33:19 +0800 Subject: [PATCH 43/70] improve transaction for package gdb --- database/gdb/gdb.go | 1 + database/gdb/gdb_core.go | 40 +++++++++++++++++--- database/gdb/gdb_z_mysql_transaction_test.go | 8 ++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 8421900d5..138eb65b8 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -84,6 +84,7 @@ type DB interface { Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert. InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore. + InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId. Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace. Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save. diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 099ada0c3..1b6d36ee8 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -11,11 +11,12 @@ import ( "context" "database/sql" "fmt" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/text/gstr" "reflect" "strings" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/container/gvar" @@ -387,15 +388,34 @@ func (c *Core) Begin() (*TX, error) { // ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().TranTimeout) // defer cancelFunc() //} - if tx, err := master.Begin(); err == nil { + var ( + sqlStr = "BEGIN" + mTime1 = gtime.TimestampMilli() + rawTx, err = master.Begin() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "DB.Begin", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + } + ) + c.db.addSqlToTracing(c.db.GetCtx(), sqlObj) + if c.db.GetDebug() { + c.db.writeSqlToLogger(sqlObj) + } + if err == nil { return &TX{ db: c.db, - tx: tx, + tx: rawTx, master: master, }, nil - } else { - return nil, err } + return nil, err } } @@ -464,6 +484,14 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R return c.Model(table).Data(data).InsertIgnore() } +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (c *Core) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).InsertAndGetId() + } + return c.Model(table).Data(data).InsertAndGetId() +} + // Replace does "REPLACE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it deletes the record // and inserts a new one. diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index 264d59b47..ab1b96af3 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -8,9 +8,10 @@ package gdb_test import ( "fmt" + "testing" + "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/errors/gerror" - "testing" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" @@ -119,7 +120,7 @@ func Test_TX_Insert(t *testing.T) { if err != nil { gtest.Error(err) } - user := tx.Table(table) + user := tx.Model(table) if _, err := user.Data(g.Map{ "id": 1, "passport": "t1", @@ -129,7 +130,6 @@ func Test_TX_Insert(t *testing.T) { }).Insert(); err != nil { gtest.Error(err) } - if _, err := tx.Insert(table, g.Map{ "id": 2, "passport": "t1", @@ -140,7 +140,7 @@ func Test_TX_Insert(t *testing.T) { gtest.Error(err) } - if n, err := tx.Table(table).Count(); err != nil { + if n, err := tx.Model(table).Count(); err != nil { gtest.Error(err) } else { t.Assert(n, 2) From c94dee8191646a796688e34d6d460992779789b6 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 21:11:51 +0800 Subject: [PATCH 44/70] improve nested transaction feature for package gdb --- database/gdb/gdb.go | 7 +- database/gdb/gdb_core.go | 13 ++- database/gdb/gdb_transaction.go | 35 ++++++- database/gdb/gdb_z_example_test.go | 4 +- .../gdb_z_mysql_association_scanlist_test.go | 6 +- database/gdb/gdb_z_mysql_model_test.go | 16 ++-- database/gdb/gdb_z_mysql_transaction_test.go | 95 ++++++++++++++++--- 7 files changed, 145 insertions(+), 31 deletions(-) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 138eb65b8..8ccb29a03 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -11,9 +11,10 @@ import ( "context" "database/sql" "fmt" + "time" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gcmd" - "time" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/internal/intlog" @@ -139,8 +140,8 @@ type DB interface { // Transaction. // =========================================================================== - Begin() (*TX, error) // See Core.Begin. - Transaction(f func(tx *TX) error) (err error) // See Core.Transaction. + Begin() (*TX, error) // See Core.Begin. + Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) error // See Core.Transaction. // =========================================================================== // Configuration methods. diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 1b6d36ee8..9dbb37ad3 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -54,7 +54,7 @@ func (c *Core) GetCtx() context.Context { if c.ctx != nil { return c.ctx } - return context.Background() + return context.TODO() } // GetCtxTimeout returns the context and cancel function for specified timeout type. @@ -426,12 +426,19 @@ func (c *Core) Begin() (*TX, error) { // // Note that, you should not Commit or Rollback the transaction in function `f` // as it is automatically handled by this function. -func (c *Core) Transaction(f func(tx *TX) error) (err error) { +func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { var tx *TX + // Check transaction object from context. + tx = TXFromCtx(ctx) + if tx != nil { + return tx.Transaction(ctx, f) + } tx, err = c.db.Begin() if err != nil { return err } + // Inject transaction object into context. + ctx = WithTX(ctx, tx) defer func() { if err == nil { if e := recover(); e != nil { @@ -448,7 +455,7 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) { } } }() - err = f(tx) + err = f(ctx, tx) return } diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index 63031b815..1973a4e57 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -7,11 +7,13 @@ package gdb import ( + "context" "database/sql" "fmt" + "reflect" + "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" - "reflect" "github.com/gogf/gf/text/gregex" ) @@ -26,8 +28,27 @@ type TX struct { const ( transactionPointerPrefix = "transaction" + contextTransactionKey = "TransactionObject" ) +// WithTX injects given transaction object into context and returns a new context. +func WithTX(ctx context.Context, tx *TX) context.Context { + return context.WithValue(ctx, contextTransactionKey, tx) +} + +// TXFromCtx retrieves and returns transaction object from context. +// It is usually used in nested transaction feature, and it returns nil if it is not set previously. +func TXFromCtx(ctx context.Context) *TX { + if ctx == nil { + return nil + } + v := ctx.Value(contextTransactionKey) + if v != nil { + return v.(*TX) + } + return nil +} + // Commit commits current transaction. // Note that it releases previous saved transaction point if it's in a nested transaction procedure, // or else it commits the hole transaction. @@ -128,7 +149,7 @@ func (tx *TX) transactionKey() string { // // Note that, you should not Commit or Rollback the transaction in function `f` // as it is automatically handled by this function. -func (tx *TX) Transaction(f func(tx *TX) error) (err error) { +func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { err = tx.Begin() if err != nil { return err @@ -149,7 +170,7 @@ func (tx *TX) Transaction(f func(tx *TX) error) (err error) { } } }() - err = f(tx) + err = f(ctx, tx) return } @@ -297,6 +318,14 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re return tx.Model(table).Data(data).InsertIgnore() } +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (tx *TX) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { + if len(batch) > 0 { + return tx.Model(table).Data(data).Batch(batch[0]).InsertAndGetId() + } + return tx.Model(table).Data(data).InsertAndGetId() +} + // Replace does "REPLACE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it deletes the record // and inserts a new one. diff --git a/database/gdb/gdb_z_example_test.go b/database/gdb/gdb_z_example_test.go index 4ede0e233..8b692a788 100644 --- a/database/gdb/gdb_z_example_test.go +++ b/database/gdb/gdb_z_example_test.go @@ -7,12 +7,14 @@ package gdb_test import ( + "context" + "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" ) func Example_transaction() { - db.Transaction(func(tx *gdb.TX) error { + db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // user result, err := tx.Insert("user", g.Map{ "passport": "john", diff --git a/database/gdb/gdb_z_mysql_association_scanlist_test.go b/database/gdb/gdb_z_mysql_association_scanlist_test.go index 4aa3b11b8..54bf0ad9e 100644 --- a/database/gdb/gdb_z_mysql_association_scanlist_test.go +++ b/database/gdb/gdb_z_mysql_association_scanlist_test.go @@ -7,11 +7,13 @@ package gdb_test import ( + "context" "fmt" + "testing" + "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" - "testing" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" @@ -84,7 +86,7 @@ CREATE TABLE %s ( // Initialize the data. var err error gtest.C(t, func(t *gtest.T) { - err = db.Transaction(func(tx *gdb.TX) error { + err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { r, err := tx.Model(tableUser).Save(EntityUser{ Name: "john", }) diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 55ca3aa30..78db30e4f 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -7,16 +7,18 @@ package gdb_test import ( + "context" "database/sql" "fmt" + "testing" + "time" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/encoding/gparser" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/util/gutil" - "testing" - "time" "github.com/gogf/gf/database/gdb" @@ -2618,8 +2620,8 @@ func Test_Model_Cache(t *testing.T) { t.AssertNil(err) t.Assert(n, 1) - err = db.Transaction(func(tx *gdb.TX) error { - one, err := tx.Table(table).Cache(time.Second, "test3").FindOne(3) + err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { + one, err := tx.Model(table).Cache(time.Second, "test3").FindOne(3) t.AssertNil(err) t.Assert(one["passport"], "user_300") return nil @@ -2642,13 +2644,13 @@ func Test_Model_Cache(t *testing.T) { t.AssertNil(err) t.Assert(n, 1) - err = db.Transaction(func(tx *gdb.TX) error { + err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // Cache feature disabled. - one, err := tx.Table(table).Cache(time.Second, "test4").FindOne(4) + one, err := tx.Model(table).Cache(time.Second, "test4").FindOne(4) t.AssertNil(err) t.Assert(one["passport"], "user_400") // Update the cache. - r, err := tx.Table(table).Data("passport", "user_4000"). + r, err := tx.Model(table).Data("passport", "user_4000"). Cache(-1, "test4").WherePri(4).Update() t.AssertNil(err) n, err := r.RowsAffected() diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index ab1b96af3..1fa33ea23 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -7,6 +7,7 @@ package gdb_test import ( + "context" "fmt" "testing" @@ -718,7 +719,8 @@ func Test_Transaction(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - err := db.Transaction(func(tx *gdb.TX) error { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { if _, err := tx.Replace(table, g.Map{ "id": 1, "passport": "USER_1", @@ -740,7 +742,8 @@ func Test_Transaction(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { - err := db.Transaction(func(tx *gdb.TX) error { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { if _, err := tx.Replace(table, g.Map{ "id": 1, "passport": "USER_1", @@ -767,7 +770,8 @@ func Test_Transaction_Panic(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - err := db.Transaction(func(tx *gdb.TX) error { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { if _, err := tx.Replace(table, g.Map{ "id": 1, "passport": "USER_1", @@ -826,19 +830,22 @@ func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { }) } -func Test_Transaction_Nested_TX_Transaction(t *testing.T) { +func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { table := createTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - var err error - err = db.Transaction(func(tx *gdb.TX) error { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { // commit - err = tx.Transaction(func(tx *gdb.TX) error { - err = tx.Transaction(func(tx *gdb.TX) error { - err = tx.Transaction(func(tx *gdb.TX) error { - err = tx.Transaction(func(tx *gdb.TX) error { - err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { _, err = tx.Model(table).Data(g.Map{ "id": 1, "passport": "USER_1", @@ -863,7 +870,71 @@ func Test_Transaction_Nested_TX_Transaction(t *testing.T) { }) t.AssertNil(err) // rollback - err = tx.Transaction(func(tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + }) +} + +func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = db.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { _, err = tx.Model(table).Data(g.Map{ "id": 2, "passport": "USER_2", From 3e2662582c2e5470148e8f294b925be6476fb019 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 21:17:21 +0800 Subject: [PATCH 45/70] improve nested transaction feature for package gdb --- database/gdb/gdb_transaction.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index 1973a4e57..187e9e9ea 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -150,6 +150,11 @@ func (tx *TX) transactionKey() string { // Note that, you should not Commit or Rollback the transaction in function `f` // as it is automatically handled by this function. func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + // Check transaction object from context. + if TXFromCtx(ctx) == nil { + // Inject transaction object into context. + ctx = WithTX(ctx, tx) + } err = tx.Begin() if err != nil { return err From 7d3233c7ada58d63426df96125b2ec6ce1762a56 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 22:33:50 +0800 Subject: [PATCH 46/70] add context for custom validation rule function;add function WhereNot for gdb.Model --- database/gdb/gdb_model_condition.go | 5 ++ util/gvalid/gvalid_custom_rule.go | 4 +- util/gvalid/gvalid_validator_check.go | 9 +- util/gvalid/gvalid_z_example_test.go | 84 ++++++++++++++++++- util/gvalid/gvalid_z_unit_custom_rule_test.go | 9 +- 5 files changed, 98 insertions(+), 13 deletions(-) diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index b71288647..775858ced 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -93,6 +93,11 @@ func (m *Model) WhereNotLike(column string, like interface{}) *Model { return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like) } +// WhereNot builds `xxx != x` statement. +func (m *Model) WhereNot(column string, value interface{}) *Model { + return m.Where(fmt.Sprintf(`%s != ?`, m.db.QuoteWord(column)), value) +} + // WhereNotIn builds `xxx NOT IN (x)` statement. func (m *Model) WhereNotIn(column string, in interface{}) *Model { return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in) diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go index 83fd4c2bb..85e431f58 100644 --- a/util/gvalid/gvalid_custom_rule.go +++ b/util/gvalid/gvalid_custom_rule.go @@ -6,13 +6,15 @@ package gvalid +import "context" + // RuleFunc is the custom function for data validation. // The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc. // The parameter `value` specifies the value for this rule to validate. // The parameter `message` specifies the custom error message or configured i18n message for this rule. // The parameter `params` specifies all the parameters that needs. You can ignore parameter `params` if // you do not really need it in your custom validation rule. -type RuleFunc func(rule string, value interface{}, message string, params map[string]interface{}) error +type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error var ( // customRuleFuncMap stores the custom rule functions. diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index fd1d78f20..9c01f21c2 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -8,6 +8,10 @@ package gvalid import ( "errors" + "strconv" + "strings" + "time" + "github.com/gogf/gf/internal/json" "github.com/gogf/gf/net/gipv4" "github.com/gogf/gf/net/gipv6" @@ -15,9 +19,6 @@ import ( "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "strconv" - "strings" - "time" ) type apiTime interface { @@ -103,7 +104,7 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me if len(paramMap) > 0 && paramMap[0] != nil { dataMap = gconv.Map(paramMap[0]) } - if err := f(ruleItems[index], value, message, dataMap); err != nil { + if err := f(v.ctx, ruleItems[index], value, message, dataMap); err != nil { match = false errorMsgArray[ruleKey] = err.Error() } else { diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index d52175aca..423957391 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -10,12 +10,13 @@ import ( "context" "errors" "fmt" + "math" + "reflect" + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gvalid" - "math" - "reflect" ) func ExampleCheckMap() { @@ -115,7 +116,7 @@ func ExampleCheckStruct3() { func ExampleRegisterRule() { rule := "unique-name" - gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { var ( id = gconv.Int(params["Id"]) name = gconv.String(value) @@ -147,7 +148,7 @@ func ExampleRegisterRule() { func ExampleRegisterRule_OverwriteRequired() { rule := "required" - gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { reflectValue := reflect.ValueOf(value) if reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() @@ -190,3 +191,78 @@ func ExampleRegisterRule_OverwriteRequired() { // // } + +func ExampleValidator_Rules() { + data := g.Map{ + "password": "123", + } + err := g.Validator().Data(data).Rules("required-with:password").Messages("请输入确认密码").CheckValue("") + fmt.Println(err.String()) + + // Output: + // 请输入确认密码 +} + +func ExampleValidator_CheckValue() { + err := g.Validator().Rules("min:18").Messages("未成年人不允许注册哟").CheckValue(16) + fmt.Println(err.String()) + + // Output: + // 未成年人不允许注册哟 +} + +func ExampleValidator_CheckMap() { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + messages := map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在:min到:max之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + err := g.Validator().Messages(messages).Rules(rules).CheckMap(params) + if err != nil { + g.Dump(err.Maps()) + } + + // May Output: + //{ + // "passport": { + // "length": "账号长度应当在6到16之间", + // "required": "账号不能为空" + //}, + // "password": { + // "same": "两次密码输入不相等" + //} + //} +} + +func ExampleValidator_CheckStruct() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + data := g.Map{ + "name": "john", + } + user := User{} + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + err := g.Validator().Data(data).CheckStruct(user) + if err != nil { + fmt.Println(err.Items()) + } + + // Output: + // [map[Type:map[required:请选择用户类型]]] +} diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go index 9fd7caf62..c9493c861 100644 --- a/util/gvalid/gvalid_z_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -9,9 +9,10 @@ package gvalid_test import ( "context" "errors" + "testing" + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" - "testing" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/gvalid" @@ -19,7 +20,7 @@ import ( func Test_CustomRule1(t *testing.T) { rule := "custom" - err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { pass := gconv.String(value) if len(pass) != 6 { return errors.New(message) @@ -66,7 +67,7 @@ func Test_CustomRule1(t *testing.T) { func Test_CustomRule2(t *testing.T) { rule := "required-map" - err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { m := gconv.Map(value) if len(m) == 0 { return errors.New(message) @@ -110,7 +111,7 @@ func Test_CustomRule2(t *testing.T) { func Test_CustomRule_AllowEmpty(t *testing.T) { rule := "allow-empty-str" - err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { s := gconv.String(value) if len(s) == 0 || s == "gf" { return nil From 017c6e4e1f5ac755a53b6bad5893ec9b5e60c0ff Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 19 May 2021 23:17:13 +0800 Subject: [PATCH 47/70] add example for i18n feature of package gvalid --- .example/util/gvalid/gvalid_i18n.go | 36 ++++++++++++++++++++++++++++ .example/util/gvalid/i18n/cn.toml | 14 ----------- .example/util/gvalid/i18n/en.toml | 15 ++++++++++++ .example/util/gvalid/i18n/zh-CN.toml | 15 ++++++++++++ 4 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 .example/util/gvalid/gvalid_i18n.go delete mode 100644 .example/util/gvalid/i18n/cn.toml create mode 100644 .example/util/gvalid/i18n/en.toml create mode 100644 .example/util/gvalid/i18n/zh-CN.toml diff --git a/.example/util/gvalid/gvalid_i18n.go b/.example/util/gvalid/gvalid_i18n.go new file mode 100644 index 000000000..dccde019c --- /dev/null +++ b/.example/util/gvalid/gvalid_i18n.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gconv" +) + +func main() { + type User struct { + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + } + var ( + data = g.Map{ + "name": "john", + } + user = User{} + ctxEn = gi18n.WithLanguage(context.TODO(), "en") + ctxCh = gi18n.WithLanguage(context.TODO(), "zh-CN") + ) + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + // 英文 + if err := g.Validator().Ctx(ctxEn).Data(data).CheckStruct(user); err != nil { + g.Dump(err.String()) + } + // 中文 + if err := g.Validator().Ctx(ctxCh).Data(data).CheckStruct(user); err != nil { + g.Dump(err.String()) + } +} diff --git a/.example/util/gvalid/i18n/cn.toml b/.example/util/gvalid/i18n/cn.toml deleted file mode 100644 index 498f60e24..000000000 --- a/.example/util/gvalid/i18n/cn.toml +++ /dev/null @@ -1,14 +0,0 @@ - - -"gf.gvalid.required" = "字段不能为空" - - - - - - - - - - - diff --git a/.example/util/gvalid/i18n/en.toml b/.example/util/gvalid/i18n/en.toml new file mode 100644 index 000000000..bcca1267d --- /dev/null +++ b/.example/util/gvalid/i18n/en.toml @@ -0,0 +1,15 @@ + + +"gf.gvalid.required" = "字段不能为空" + + +"ReuiredUserName" = "Please input user name" +"ReuiredUserType" = "Please select user type" + + + + + + + + diff --git a/.example/util/gvalid/i18n/zh-CN.toml b/.example/util/gvalid/i18n/zh-CN.toml new file mode 100644 index 000000000..750787e56 --- /dev/null +++ b/.example/util/gvalid/i18n/zh-CN.toml @@ -0,0 +1,15 @@ + + +"gf.gvalid.required" = "字段不能为空" + +"ReuiredUserName" = "请输入用户名称" +"ReuiredUserType" = "请选择用户类型" + + + + + + + + + From 4e41d8aff8d25f83d98053a7acc65eeac3275af3 Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 21 May 2021 13:25:53 +0800 Subject: [PATCH 48/70] improve context and nested transaction feature for package gdb --- database/gdb/gdb.go | 44 +--- database/gdb/gdb_core.go | 234 ++++++------------ database/gdb/gdb_core_config.go | 11 +- database/gdb/gdb_core_structure.go | 5 +- database/gdb/gdb_driver_mssql.go | 18 +- database/gdb/gdb_driver_mysql.go | 19 +- database/gdb/gdb_driver_oracle.go | 49 ++-- database/gdb/gdb_driver_pgsql.go | 18 +- database/gdb/gdb_driver_sqlite.go | 18 +- database/gdb/gdb_func.go | 17 +- database/gdb/gdb_model.go | 19 +- database/gdb/gdb_model_cache.go | 2 +- database/gdb/gdb_model_condition.go | 42 ++-- database/gdb/gdb_model_delete.go | 8 +- database/gdb/gdb_model_fields.go | 4 +- database/gdb/gdb_model_insert.go | 9 +- database/gdb/gdb_model_join.go | 5 +- database/gdb/gdb_model_select.go | 19 +- database/gdb/gdb_model_time.go | 8 +- database/gdb/gdb_model_update.go | 6 +- database/gdb/gdb_model_utility.go | 13 +- database/gdb/gdb_model_with.go | 5 +- database/gdb/gdb_statement.go | 4 +- database/gdb/gdb_transaction.go | 242 ++++++++++++++----- database/gdb/gdb_z_driver_test.go | 8 +- database/gdb/gdb_z_mysql_model_test.go | 4 +- database/gdb/gdb_z_mysql_transaction_test.go | 10 +- os/glog/glog_logger_config.go | 25 +- 28 files changed, 472 insertions(+), 394 deletions(-) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 8ccb29a03..8d634359e 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -96,19 +96,6 @@ type DB interface { Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update. Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete. - // =========================================================================== - // Internal APIs for CURD, which can be overwrote for custom CURD implements. - // =========================================================================== - - DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery. - DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll. - DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec. - DoPrepare(link Link, sql string) (*Stmt, error) // See Core.DoPrepare. - DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoInsert. - DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoBatchInsert. - DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate. - DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete. - // =========================================================================== // Query APIs for convenience purpose. // =========================================================================== @@ -167,40 +154,25 @@ type DB interface { // Utility methods. // =========================================================================== - GetCtx() context.Context // See Core.GetCtx. - GetChars() (charLeft string, charRight string) // See Core.GetChars. - GetMaster(schema ...string) (*sql.DB, error) // See Core.GetMaster. - GetSlave(schema ...string) (*sql.DB, error) // See Core.GetSlave. - QuoteWord(s string) string // See Core.QuoteWord. - QuoteString(s string) string // See Core.QuoteString. - QuotePrefixTableName(table string) string // See Core.QuotePrefixTableName. - Tables(schema ...string) (tables []string, err error) // See Core.Tables. - TableFields(link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. - HasTable(name string) (bool, error) // See Core.HasTable. - FilteredLinkInfo() string // See Core.FilteredLinkInfo. + GetCtx() context.Context // See Core.GetCtx. + GetCore() *Core // See Core.GetCore + GetChars() (charLeft string, charRight string) // See Core.GetChars. + Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables. + TableFields(ctx context.Context, link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. + FilteredLinkInfo() string // See Core.FilteredLinkInfo. // HandleSqlBeforeCommit is a hook function, which deals with the sql string before // it's committed to underlying driver. The parameter `link` specifies the current // database connection operation object. You can modify the sql string `sql` and its // arguments `args` as you wish before they're committed to driver. // Also see Core.HandleSqlBeforeCommit. - HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) - - // =========================================================================== - // Internal methods, for internal usage purpose, you do not need consider it. - // =========================================================================== - - mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) // See Core.mappingAndFilterData. - convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} // See Core.convertFieldValueToLocalValue. - convertRowsToResult(rows *sql.Rows) (Result, error) // See Core.convertRowsToResult. - addSqlToTracing(ctx context.Context, sql *Sql) // See Core.addSqlToTracing. - writeSqlToLogger(v *Sql) // See Core.writeSqlToLogger. + HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) } // Core is the base struct for database management. type Core struct { db DB // DB interface object. - ctx context.Context // Context for chaining operation only. + ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. group string // Configuration group name. debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. cache *gcache.Cache // Cache manager, SQL result cache only. diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 9dbb37ad3..14a14917c 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -15,9 +15,8 @@ import ( "strings" "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" @@ -25,6 +24,10 @@ import ( "github.com/gogf/gf/util/gconv" ) +func (c *Core) GetCore() *Core { + return c +} + // Ctx is a chaining function, which creates and returns a new DB that is a shallow copy // of current DB object and with given context in it. // Note that this returned DB object can be used only once, so do not assign it to @@ -33,6 +36,11 @@ func (c *Core) Ctx(ctx context.Context) DB { if ctx == nil { return c.db } + // It is already set context in previous chaining operation. + if c.ctx != nil { + return c.db + } + // It makes a shallow copy of current db and changes its context for next chaining operation. var ( err error newCore = &Core{} @@ -41,9 +49,10 @@ func (c *Core) Ctx(ctx context.Context) DB { *newCore = *c newCore.ctx = ctx newCore.db, err = driverMap[configNode.Type].New(newCore, configNode) - // Seldom error, just log it. if err != nil { - c.db.GetLogger().Ctx(ctx).Error(err) + // It is really a serious error here. + // Do not let it continue. + panic(err) } return newCore.db } @@ -60,7 +69,7 @@ func (c *Core) GetCtx() context.Context { // GetCtxTimeout returns the context and cancel function for specified timeout type. func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Context, context.CancelFunc) { if ctx == nil { - ctx = c.db.GetCtx() + ctx = c.GetCtx() } else { ctx = context.WithValue(ctx, "WrappedByGetCtxTimeout", nil) } @@ -102,15 +111,14 @@ func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error if err != nil { return nil, err } - return c.db.DoQuery(link, sql, args...) + return c.DoQuery(c.GetCtx(), link, sql, args...) } // DoQuery commits the sql string and its arguments to underlying driver // through given link object and returns the execution result. -func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { +func (c *Core) DoQuery(ctx context.Context, 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() + sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) if c.GetConfig().QueryTimeout > 0 { ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) } @@ -127,9 +135,10 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro End: mTime2, Group: c.db.GetGroup(), } + // Tracing and logging. c.addSqlToTracing(ctx, sqlObj) if c.db.GetDebug() { - c.writeSqlToLogger(sqlObj) + c.writeSqlToLogger(ctx, sqlObj) } if err == nil { return rows, nil @@ -146,15 +155,14 @@ func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err err if err != nil { return nil, err } - return c.db.DoExec(link, sql, args...) + return c.DoExec(c.GetCtx(), link, sql, args...) } // DoExec commits the sql string and its arguments to underlying driver // through given link object and returns the execution result. -func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) { +func (c *Core) DoExec(ctx context.Context, 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() + sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) if c.GetConfig().ExecTimeout > 0 { var cancelFunc context.CancelFunc ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) @@ -178,9 +186,10 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re End: mTime2, Group: c.db.GetGroup(), } + // Tracing and logging. c.addSqlToTracing(ctx, sqlObj) if c.db.GetDebug() { - c.writeSqlToLogger(sqlObj) + c.writeSqlToLogger(ctx, sqlObj) } return result, formatError(err, sql, args...) } @@ -207,12 +216,11 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) { return nil, err } } - return c.db.DoPrepare(link, sql) + return c.DoPrepare(c.GetCtx(), link, sql) } -// doPrepare calls prepare function on given link object and returns the statement object. -func (c *Core) DoPrepare(link Link, sql string) (*Stmt, error) { - ctx := c.db.GetCtx() +// DoPrepare calls prepare function on given link object and returns the statement object. +func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) { if c.GetConfig().PrepareTimeout > 0 { // DO NOT USE cancel function in prepare statement. ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout) @@ -232,9 +240,10 @@ func (c *Core) DoPrepare(link Link, sql string) (*Stmt, error) { Group: c.db.GetGroup(), } ) + // Tracing and logging. c.addSqlToTracing(ctx, sqlObj) if c.db.GetDebug() { - c.writeSqlToLogger(sqlObj) + c.writeSqlToLogger(ctx, sqlObj) } return &Stmt{ Stmt: stmt, @@ -245,23 +254,23 @@ func (c *Core) DoPrepare(link Link, sql string) (*Stmt, error) { // GetAll queries and returns data records from database. func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) { - return c.db.DoGetAll(nil, sql, args...) + return c.DoGetAll(c.GetCtx(), nil, sql, args...) } // DoGetAll queries and returns data records from database. -func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) { +func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) { if link == nil { link, err = c.db.Slave() if err != nil { return nil, err } } - rows, err := c.db.DoQuery(link, sql, args...) + rows, err := c.DoQuery(ctx, link, sql, args...) if err != nil || rows == nil { return nil, err } defer rows.Close() - return c.db.convertRowsToResult(rows) + return c.convertRowsToResult(rows) } // GetOne queries and returns one record from database. @@ -279,7 +288,7 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) { // GetArray queries and returns data values as slice from database. // Note that if there are multiple columns in the result, it returns just one column values randomly. func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) { - all, err := c.db.DoGetAll(nil, sql, args...) + all, err := c.DoGetAll(c.GetCtx(), nil, sql, args...) if err != nil { return nil, err } @@ -374,91 +383,6 @@ func (c *Core) PingSlave() error { } } -// Begin starts and returns the transaction object. -// You should call Commit or Rollback functions of the transaction object -// if you no longer use the transaction. Commit or Rollback functions will also -// close the transaction automatically. -func (c *Core) Begin() (*TX, error) { - if master, err := c.db.Master(); err != nil { - return nil, err - } else { - //ctx := c.db.GetCtx() - //if c.GetConfig().TranTimeout > 0 { - // var cancelFunc context.CancelFunc - // ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().TranTimeout) - // defer cancelFunc() - //} - var ( - sqlStr = "BEGIN" - mTime1 = gtime.TimestampMilli() - rawTx, err = master.Begin() - mTime2 = gtime.TimestampMilli() - sqlObj = &Sql{ - Sql: sqlStr, - Type: "DB.Begin", - Args: nil, - Format: sqlStr, - Error: err, - Start: mTime1, - End: mTime2, - Group: c.db.GetGroup(), - } - ) - c.db.addSqlToTracing(c.db.GetCtx(), sqlObj) - if c.db.GetDebug() { - c.db.writeSqlToLogger(sqlObj) - } - if err == nil { - return &TX{ - db: c.db, - tx: rawTx, - master: master, - }, nil - } - return nil, err - } -} - -// Transaction wraps the transaction logic using function `f`. -// It rollbacks the transaction and returns the error from function `f` if -// it returns non-nil error. It commits the transaction and returns nil if -// function `f` returns nil. -// -// Note that, you should not Commit or Rollback the transaction in function `f` -// as it is automatically handled by this function. -func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { - var tx *TX - // Check transaction object from context. - tx = TXFromCtx(ctx) - if tx != nil { - return tx.Transaction(ctx, f) - } - tx, err = c.db.Begin() - if err != nil { - return err - } - // Inject transaction object into context. - ctx = WithTX(ctx, tx) - defer func() { - if err == nil { - if e := recover(); e != nil { - err = fmt.Errorf("%v", e) - } - } - if err != nil { - if e := tx.Rollback(); e != nil { - err = e - } - } else { - if e := tx.Commit(); e != nil { - err = e - } - } - }() - err = f(ctx, tx) - return -} - // Insert does "INSERT INTO ..." statement for the table. // If there's already one unique record of the data in the table, it returns error. // @@ -536,7 +460,7 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e return c.Model(table).Data(data).Save() } -// doInsert inserts or updates data for given table. +// DoInsert inserts or updates data for given table. // This function is usually used for custom interface definition, you do not need call it manually. // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: @@ -548,8 +472,8 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e // 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; // 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; // 3: ignore: if there's unique/primary key in the data, it ignores the inserting; -func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { - table = c.db.QuotePrefixTableName(table) +func (c *Core) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { + table = c.QuotePrefixTableName(table) var ( fields []string values []string @@ -564,10 +488,10 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b } switch reflectKind { case reflect.Slice, reflect.Array: - return c.db.DoBatchInsert(link, table, data, option, batch...) + return c.DoBatchInsert(ctx, link, table, data, option, batch...) case reflect.Struct: if _, ok := data.(apiInterfaces); ok { - return c.db.DoBatchInsert(link, table, data, option, batch...) + return c.DoBatchInsert(ctx, link, table, data, option, batch...) } else { dataMap = ConvertDataForTableRecord(data) } @@ -616,15 +540,11 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b return nil, err } } - return c.db.DoExec( - link, - fmt.Sprintf( - "%s INTO %s(%s) VALUES(%s) %s", - operation, table, strings.Join(fields, ","), - strings.Join(values, ","), updateStr, - ), - params..., - ) + return c.DoExec(ctx, link, fmt.Sprintf( + "%s INTO %s(%s) VALUES(%s) %s", + operation, table, strings.Join(fields, ","), + strings.Join(values, ","), updateStr, + ), params...) } // BatchInsert batch inserts data. @@ -665,8 +585,8 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu // DoBatchInsert batch inserts/replaces/saves data. // This function is usually used for custom interface definition, you do not need call it manually. -func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { - table = c.db.QuotePrefixTableName(table) +func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { + table = c.QuotePrefixTableName(table) var ( keys []string // Field names. values []string // Value holder string array, like: (?,?,?) @@ -777,16 +697,12 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i } valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")") if len(valueHolder) == batchNum || (i == listMapLen-1 && len(valueHolder) > 0) { - r, err := c.db.DoExec( - link, - fmt.Sprintf( - "%s INTO %s(%s) VALUES%s %s", - operation, table, keysStr, - gstr.Join(valueHolder, ","), - updateStr, - ), - params..., - ) + r, err := c.DoExec(ctx, link, fmt.Sprintf( + "%s INTO %s(%s) VALUES%s %s", + operation, table, keysStr, + gstr.Join(valueHolder, ","), + updateStr, + ), params...) if err != nil { return r, err } @@ -821,10 +737,10 @@ func (c *Core) Update(table string, data interface{}, condition interface{}, arg return c.Model(table).Data(data).Where(condition, args...).Update() } -// doUpdate does "UPDATE ... " statement for the table. +// DoUpdate does "UPDATE ... " statement for the table. // This function is usually used for custom interface definition, you do not need call it manually. -func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) { - table = c.db.QuotePrefixTableName(table) +func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) { + table = c.QuotePrefixTableName(table) var ( rv = reflect.ValueOf(data) kind = rv.Kind() @@ -849,7 +765,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str if value.Value != 0 { column := k if value.Field != "" { - column = c.db.QuoteWord(value.Field) + column = c.QuoteWord(value.Field) } fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) params = append(params, value.Value) @@ -858,16 +774,16 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str if value.Value != 0 { column := k if value.Field != "" { - column = c.db.QuoteWord(value.Field) + column = c.QuoteWord(value.Field) } fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) params = append(params, value.Value) } default: if s, ok := v.(Raw); ok { - fields = append(fields, c.db.QuoteWord(k)+"="+gconv.String(s)) + fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s)) } else { - fields = append(fields, c.db.QuoteWord(k)+"=?") + fields = append(fields, c.QuoteWord(k)+"=?") params = append(params, v) } } @@ -888,11 +804,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str return nil, err } } - return c.db.DoExec( - link, - fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), - args..., - ) + return c.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...) } // Delete does "DELETE FROM ... " statement for the table. @@ -912,14 +824,14 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{}) // DoDelete does "DELETE FROM ... " statement for the table. // This function is usually used for custom interface definition, you do not need call it manually. -func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { +func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { if link == nil { if link, err = c.db.Master(); err != nil { return nil, err } } - table = c.db.QuotePrefixTableName(table) - return c.db.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) + table = c.QuotePrefixTableName(table) + return c.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) } // convertRowsToResult converts underlying data record type sql.Rows to Result type. @@ -955,7 +867,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) { if value == nil { row[columnNames[i]] = gvar.New(nil) } else { - row[columnNames[i]] = gvar.New(c.db.convertFieldValueToLocalValue(value, columnTypes[i])) + row[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i])) } } records = append(records, row) @@ -977,19 +889,23 @@ func (c *Core) MarshalJSON() ([]byte, error) { // writeSqlToLogger outputs the sql object to logger. // It is enabled only if configuration "debug" is true. -func (c *Core) writeSqlToLogger(v *Sql) { - s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format) - if v.Error != nil { - s += "\nError: " + v.Error.Error() - c.logger.Ctx(c.db.GetCtx()).Error(s) +func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { + var transactionIdStr string + if v := ctx.Value(transactionIdForLoggerCtx); v != nil { + transactionIdStr = fmt.Sprintf(`[%d] `, v.(uint64)) + } + s := fmt.Sprintf("[%3d ms] [%s] %s%s", sql.End-sql.Start, sql.Group, transactionIdStr, sql.Format) + if sql.Error != nil { + s += "\nError: " + sql.Error.Error() + c.logger.Ctx(ctx).Error(s) } else { - c.logger.Ctx(c.db.GetCtx()).Debug(s) + c.logger.Ctx(ctx).Debug(s) } } // HasTable determine whether the table name exists in the database. func (c *Core) HasTable(name string) (bool, error) { - tableList, err := c.db.Tables() + tableList, err := c.db.Tables(c.GetCtx()) if err != nil { return false, err } diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 1f70a0164..8ce85dc8e 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -8,15 +8,12 @@ package gdb import ( "fmt" - "github.com/gogf/gf/os/gcache" "sync" "time" - "github.com/gogf/gf/os/glog" -) + "github.com/gogf/gf/os/gcache" -const ( - DefaultGroupName = "default" // Default group name. + "github.com/gogf/gf/os/glog" ) // Config is the configuration management object. @@ -53,6 +50,10 @@ type ConfigNode struct { TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature. } +const ( + DefaultGroupName = "default" // Default group name. +) + // configs is internal used configuration object. var configs struct { sync.RWMutex diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index 8e28f239b..02b9d11ac 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -7,10 +7,11 @@ package gdb import ( - "github.com/gogf/gf/util/gutil" "strings" "time" + "github.com/gogf/gf/util/gutil" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/os/gtime" @@ -149,7 +150,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s // mappingAndFilterData automatically mappings the map key to table field and removes // all key-value pairs that are not the field of given table. func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) { - if fieldsMap, err := c.db.TableFields(nil, table, schema); err == nil { + if fieldsMap, err := c.db.TableFields(c.GetCtx(), nil, table, schema); err == nil { fieldsKeyMap := make(map[string]interface{}, len(fieldsMap)) for k, _ := range fieldsMap { fieldsKeyMap[k] = nil diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index f288187c9..fde84e762 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -12,12 +12,14 @@ package gdb import ( + "context" "database/sql" "fmt" - "github.com/gogf/gf/errors/gerror" "strconv" "strings" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" @@ -77,7 +79,7 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) { } // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) { var index int // Convert place holder char '?' to string "@px". str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string { @@ -183,14 +185,14 @@ func (d *DriverMssql) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.db.GetSlave(schema...) + link, err := d.GetSlave(schema...) if err != nil { return nil, err } - result, err = d.db.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`) + result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`) if err != nil { return } @@ -205,7 +207,7 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) { // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverMssql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -224,7 +226,7 @@ func (d *DriverMssql) TableFields(link Link, table string, schema ...string) (fi result Result ) if link == nil { - link, err = d.db.GetSlave(checkSchema) + link, err = d.GetSlave(checkSchema) if err != nil { return nil } @@ -260,7 +262,7 @@ ORDER BY a.id,a.colorder`, strings.ToUpper(table), ) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) - result, err = d.db.DoGetAll(link, structureSql) + result, err = d.DoGetAll(ctx, link, structureSql) if err != nil { return nil } diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index 9f6f6bf45..ee87ecd94 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -7,8 +7,10 @@ package gdb import ( + "context" "database/sql" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gregex" @@ -75,19 +77,19 @@ func (d *DriverMysql) GetChars() (charLeft string, charRight string) { } // HandleSqlBeforeCommit handles the sql before posts it to database. -func (d *DriverMysql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +func (d *DriverMysql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) { return sql, args } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.db.GetSlave(schema...) + link, err := d.GetSlave(schema...) if err != nil { return nil, err } - result, err = d.db.DoGetAll(link, `SHOW TABLES`) + result, err = d.DoGetAll(ctx, link, `SHOW TABLES`) if err != nil { return } @@ -111,7 +113,7 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) { // // It's using cache feature to enhance the performance, which is never expired util the // process restarts. -func (d *DriverMysql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -130,14 +132,13 @@ func (d *DriverMysql) TableFields(link Link, table string, schema ...string) (fi result Result ) if link == nil { - link, err = d.db.GetSlave(checkSchema) + link, err = d.GetSlave(checkSchema) if err != nil { return nil } } - result, err = d.db.DoGetAll( - link, - fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.db.QuoteWord(table)), + result, err = d.DoGetAll(ctx, link, + fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)), ) if err != nil { return nil diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index f8011b91a..b067de9b4 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -12,17 +12,19 @@ package gdb import ( + "context" "database/sql" "fmt" + "reflect" + "strconv" + "strings" + "time" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" - "reflect" - "strconv" - "strings" - "time" ) // DriverOracle is the driver for oracle database. @@ -83,7 +85,7 @@ func (d *DriverOracle) GetChars() (charLeft string, charRight string) { } // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) { +func (d *DriverOracle) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) { var index int // Convert place holder char '?' to string ":vx". newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { @@ -164,9 +166,9 @@ func (d *DriverOracle) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. // Note that it ignores the parameter `schema` in oracle database, as it is not necessary. -func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) { +func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - result, err = d.db.DoGetAll(nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") + result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") if err != nil { return } @@ -181,7 +183,7 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) { // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverOracle) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -211,12 +213,12 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, ) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) if link == nil { - link, err = d.db.GetSlave(checkSchema) + link, err = d.GetSlave(checkSchema) if err != nil { return nil } } - result, err = d.db.DoGetAll(link, structureSql) + result, err = d.DoGetAll(ctx, link, structureSql) if err != nil { return nil } @@ -264,7 +266,7 @@ func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[ return } -func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { +func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { var ( fields []string values []string @@ -279,7 +281,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio } switch kind { case reflect.Slice, reflect.Array: - return d.db.DoBatchInsert(link, table, data, option, batch...) + return d.DoBatchInsert(ctx, link, table, data, option, batch...) case reflect.Map: fallthrough case reflect.Struct: @@ -353,20 +355,17 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2, strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","), ) - return d.db.DoExec(link, tmp, params...) + return d.DoExec(ctx, link, tmp, params...) case insertOptionIgnore: - return d.db.DoExec(link, - fmt.Sprintf( - "INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)", - table, strings.Join(indexes, ","), table, strings.Join(fields, ","), strings.Join(values, ","), - ), - params...) + return d.DoExec(ctx, link, fmt.Sprintf( + "INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)", + table, strings.Join(indexes, ","), table, strings.Join(fields, ","), strings.Join(values, ","), + ), params...) } } - return d.db.DoExec( - link, + return d.DoExec(ctx, link, fmt.Sprintf( "INSERT INTO %s(%s) VALUES(%s)", table, strings.Join(fields, ","), strings.Join(values, ","), @@ -374,7 +373,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio params...) } -func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { +func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { var ( keys []string values []string @@ -435,7 +434,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, ) if option != insertOptionDefault { for _, v := range listMap { - r, err := d.db.DoInsert(link, table, v, option, 1) + r, err := d.DoInsert(ctx, link, table, v, option, 1) if err != nil { return r, err } @@ -463,7 +462,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, values = append(values, valueHolderStr) intoStr = append(intoStr, fmt.Sprintf(" INTO %s(%s) VALUES(%s) ", table, keyStr, valueHolderStr)) if len(intoStr) == batchNum { - r, err := d.db.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) + r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) if err != nil { return r, err } @@ -479,7 +478,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, } // The leftover data. if len(intoStr) > 0 { - r, err := d.db.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) + r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) if err != nil { return r, err } diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index 95cf65087..2d0bd76f6 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -12,12 +12,14 @@ package gdb import ( + "context" "database/sql" "fmt" + "strings" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" - "strings" "github.com/gogf/gf/text/gregex" ) @@ -75,7 +77,7 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) { } // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +func (d *DriverPgsql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) { var index int // Convert place holder char '?' to string "$x". sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { @@ -88,9 +90,9 @@ func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interf // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.db.GetSlave(schema...) + link, err := d.GetSlave(schema...) if err != nil { return nil, err } @@ -98,7 +100,7 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { if len(schema) > 0 && schema[0] != "" { query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0]) } - result, err = d.db.DoGetAll(link, query) + result, err = d.DoGetAll(ctx, link, query) if err != nil { return } @@ -113,7 +115,7 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverPgsql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -141,12 +143,12 @@ ORDER BY a.attnum`, ) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) if link == nil { - link, err = d.db.GetSlave(checkSchema) + link, err = d.GetSlave(checkSchema) if err != nil { return nil } } - result, err = d.db.DoGetAll(link, structureSql) + result, err = d.DoGetAll(ctx, link, structureSql) if err != nil { return nil } diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index ba000fbbb..c78a3eb27 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -11,13 +11,15 @@ package gdb import ( + "context" "database/sql" "fmt" + "strings" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" - "strings" ) // DriverSqlite is the driver for sqlite database. @@ -67,20 +69,20 @@ func (d *DriverSqlite) GetChars() (charLeft string, charRight string) { // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. // TODO 需要增加对Save方法的支持,可使用正则来实现替换, // TODO 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE) -func (d *DriverSqlite) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +func (d *DriverSqlite) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) { return sql, args } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) { +func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.db.GetSlave(schema...) + link, err := d.GetSlave(schema...) if err != nil { return nil, err } - result, err = d.db.DoGetAll(link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`) + result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`) if err != nil { return } @@ -95,7 +97,7 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) { // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverSqlite) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -114,12 +116,12 @@ func (d *DriverSqlite) TableFields(link Link, table string, schema ...string) (f result Result ) if link == nil { - link, err = d.db.GetSlave(checkSchema) + link, err = d.GetSlave(checkSchema) if err != nil { return nil } } - result, err = d.db.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) + result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) if err != nil { return nil } diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index ec2b7d7dc..13db19d09 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -9,6 +9,11 @@ package gdb import ( "bytes" "fmt" + "reflect" + "regexp" + "strings" + "time" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/internal/json" @@ -16,10 +21,6 @@ import ( "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gmeta" "github.com/gogf/gf/util/gutil" - "reflect" - "regexp" - "strings" - "time" "github.com/gogf/gf/internal/structs" @@ -504,7 +505,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( // Eg: Where/And/Or("uid>=", 1) newWhere += "?" } else if gregex.IsMatchString(regularFieldNameRegPattern, newWhere) { - newWhere = db.QuoteString(newWhere) + newWhere = db.GetCore().QuoteString(newWhere) if len(newArgs) > 0 { if utils.IsArray(newArgs[0]) { // Eg: @@ -543,9 +544,9 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new for i := 0; i < len(where); i += 2 { str = gconv.String(where[i]) if buffer.Len() > 0 { - buffer.WriteString(" AND " + db.QuoteWord(str) + "=?") + buffer.WriteString(" AND " + db.GetCore().QuoteWord(str) + "=?") } else { - buffer.WriteString(db.QuoteWord(str) + "=?") + buffer.WriteString(db.GetCore().QuoteWord(str) + "=?") } if s, ok := where[i+1].(Raw); ok { buffer.WriteString(gconv.String(s)) @@ -558,7 +559,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new // formatWhereKeyValue handles each key-value pair of the parameter map. func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} { - quotedKey := db.QuoteWord(key) + quotedKey := db.GetCore().QuoteWord(key) if buffer.Len() > 0 { buffer.WriteString(" AND ") } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index ba834a457..45084a106 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -9,9 +9,10 @@ package gdb import ( "context" "fmt" - "github.com/gogf/gf/text/gregex" "time" + "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" ) @@ -98,10 +99,10 @@ func (c *Core) Model(tableNameOrStruct ...interface{}) *Model { if len(tableNames) > 1 { tableStr = fmt.Sprintf( - `%s AS %s`, c.db.QuotePrefixTableName(tableNames[0]), c.db.QuoteWord(tableNames[1]), + `%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]), ) } else if len(tableNames) == 1 { - tableStr = c.db.QuotePrefixTableName(tableNames[0]) + tableStr = c.QuotePrefixTableName(tableNames[0]) } return &Model{ db: c.db, @@ -148,9 +149,21 @@ func (m *Model) Ctx(ctx context.Context) *Model { } model := m.getModel() model.db = model.db.Ctx(ctx) + if m.tx != nil { + model.tx = model.tx.Ctx(ctx) + } return model } +// GetCtx returns the context for current Model. +// It returns `context.Background()` is there's no context previously set. +func (m *Model) GetCtx() context.Context { + if m.tx != nil && m.tx.ctx != nil { + return m.tx.ctx + } + return m.db.GetCtx() +} + // As sets an alias name for current table. func (m *Model) As(as string) *Model { if m.tables != "" { diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go index 0529bf5f6..b9ba72873 100644 --- a/database/gdb/gdb_model_cache.go +++ b/database/gdb/gdb_model_cache.go @@ -38,6 +38,6 @@ func (m *Model) Cache(duration time.Duration, name ...string) *Model { // cache feature is enabled. func (m *Model) checkAndRemoveCache() { if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 { - m.db.GetCache().Ctx(m.db.GetCtx()).Remove(m.cacheName) + m.db.GetCache().Ctx(m.GetCtx()).Remove(m.cacheName) } } diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 775858ced..641fee5d7 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -61,53 +61,53 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { // WhereBetween builds `xxx BETWEEN x AND y` statement. func (m *Model) WhereBetween(column string, min, max interface{}) *Model { - return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) + return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max) } // WhereLike builds `xxx LIKE x` statement. func (m *Model) WhereLike(column string, like interface{}) *Model { - return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like) + return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like) } // WhereIn builds `xxx IN (x)` statement. func (m *Model) WhereIn(column string, in interface{}) *Model { - return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in) + return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in) } // WhereNull builds `xxx IS NULL` statement. func (m *Model) WhereNull(columns ...string) *Model { model := m for _, column := range columns { - model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column))) + model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column))) } return model } // WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement. func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model { - return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) + return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max) } // WhereNotLike builds `xxx NOT LIKE x` statement. func (m *Model) WhereNotLike(column string, like interface{}) *Model { - return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like) + return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like) } // WhereNot builds `xxx != x` statement. func (m *Model) WhereNot(column string, value interface{}) *Model { - return m.Where(fmt.Sprintf(`%s != ?`, m.db.QuoteWord(column)), value) + return m.Where(fmt.Sprintf(`%s != ?`, m.db.GetCore().QuoteWord(column)), value) } // WhereNotIn builds `xxx NOT IN (x)` statement. func (m *Model) WhereNotIn(column string, in interface{}) *Model { - return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in) + return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in) } // WhereNotNull builds `xxx IS NOT NULL` statement. func (m *Model) WhereNotNull(columns ...string) *Model { model := m for _, column := range columns { - model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column))) + model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))) } return model } @@ -128,48 +128,48 @@ func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model { // WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions. func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) + return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max) } // WhereOrLike builds `xxx LIKE x` statement in `OR` conditions. func (m *Model) WhereOrLike(column string, like interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like) + return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like) } // WhereOrIn builds `xxx IN (x)` statement in `OR` conditions. func (m *Model) WhereOrIn(column string, in interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in) + return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in) } // WhereOrNull builds `xxx IS NULL` statement in `OR` conditions. func (m *Model) WhereOrNull(columns ...string) *Model { model := m for _, column := range columns { - model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column))) + model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column))) } return model } // WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions. func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max) + return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max) } // WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions. func (m *Model) WhereOrNotLike(column string, like interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like) + return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like) } // WhereOrNotIn builds `xxx NOT IN (x)` statement. func (m *Model) WhereOrNotIn(column string, in interface{}) *Model { - return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in) + return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in) } // WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions. func (m *Model) WhereOrNotNull(columns ...string) *Model { model := m for _, column := range columns { - model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column))) + model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))) } return model } @@ -177,7 +177,7 @@ func (m *Model) WhereOrNotNull(columns ...string) *Model { // Group sets the "GROUP BY" statement for the model. func (m *Model) Group(groupBy string) *Model { model := m.getModel() - model.groupBy = m.db.QuoteString(groupBy) + model.groupBy = m.db.GetCore().QuoteString(groupBy) return model } @@ -215,7 +215,7 @@ func (m *Model) Order(orderBy ...string) *Model { return m } model := m.getModel() - model.orderBy = m.db.QuoteString(strings.Join(orderBy, " ")) + model.orderBy = m.db.GetCore().QuoteString(strings.Join(orderBy, " ")) return model } @@ -225,7 +225,7 @@ func (m *Model) OrderAsc(column string) *Model { return m } model := m.getModel() - model.orderBy = m.db.QuoteWord(column) + " ASC" + model.orderBy = m.db.GetCore().QuoteWord(column) + " ASC" return model } @@ -235,7 +235,7 @@ func (m *Model) OrderDesc(column string) *Model { return m } model := m.getModel() - model.orderBy = m.db.QuoteWord(column) + " DESC" + model.orderBy = m.db.GetCore().QuoteWord(column) + " DESC" return model } diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go index 4c56cdcd4..2ce680444 100644 --- a/database/gdb/gdb_model_delete.go +++ b/database/gdb/gdb_model_delete.go @@ -9,6 +9,7 @@ package gdb import ( "database/sql" "fmt" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" @@ -32,10 +33,11 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { ) // Soft deleting. if !m.unscoped && fieldNameDelete != "" { - return m.db.DoUpdate( + return m.db.GetCore().DoUpdate( + m.GetCtx(), m.getLink(true), m.tables, - fmt.Sprintf(`%s=?`, m.db.QuoteString(fieldNameDelete)), + fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)), conditionWhere+conditionExtra, append([]interface{}{gtime.Now().String()}, conditionArgs...), ) @@ -44,5 +46,5 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { if !gstr.ContainsI(conditionStr, " WHERE ") { return nil, gerror.New("there should be WHERE condition statement for DELETE operation") } - return m.db.DoDelete(m.getLink(true), m.tables, conditionStr, conditionArgs...) + return m.db.GetCore().DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...) } diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index be082b070..46d7af223 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -114,7 +114,7 @@ func (m *Model) GetFieldsStr(prefix ...string) string { } newFields += prefixStr + k } - newFields = m.db.QuoteString(newFields) + newFields = m.db.GetCore().QuoteString(newFields) return newFields } @@ -158,7 +158,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string { } newFields += prefixStr + k } - newFields = m.db.QuoteString(newFields) + newFields = m.db.GetCore().QuoteString(newFields) return newFields } diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index d67978b72..3062354fa 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -8,12 +8,13 @@ package gdb import ( "database/sql" + "reflect" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "reflect" ) // Batch sets the batch operation number for the model. @@ -194,7 +195,8 @@ func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) { list[k] = v } } - return m.db.DoBatchInsert( + return m.db.GetCore().DoBatchInsert( + m.GetCtx(), m.getLink(true), m.tables, newData, @@ -219,7 +221,8 @@ func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) { data[fieldNameUpdate] = nowString } } - return m.db.DoInsert( + return m.db.GetCore().DoInsert( + m.GetCtx(), m.getLink(true), m.tables, newData, diff --git a/database/gdb/gdb_model_join.go b/database/gdb/gdb_model_join.go index 847c11abd..10e4c9255 100644 --- a/database/gdb/gdb_model_join.go +++ b/database/gdb/gdb_model_join.go @@ -8,6 +8,7 @@ package gdb import ( "fmt" + "github.com/gogf/gf/text/gstr" ) @@ -72,13 +73,13 @@ func (m *Model) doJoin(operator string, table ...string) *Model { joinStr = "(" + joinStr + ")" } } else { - joinStr = m.db.QuotePrefixTableName(table[0]) + joinStr = m.db.GetCore().QuotePrefixTableName(table[0]) } } if len(table) > 2 { model.tables += fmt.Sprintf( " %s JOIN %s AS %s ON (%s)", - operator, joinStr, m.db.QuoteWord(table[1]), table[2], + operator, joinStr, m.db.GetCore().QuoteWord(table[1]), table[2], ) } else if len(table) == 2 { model.tables += fmt.Sprintf( diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index bafd477bc..3536174dc 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -8,13 +8,14 @@ package gdb import ( "fmt" + "reflect" + "github.com/gogf/gf/container/gset" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" - "reflect" ) // Select is alias of Model.All. @@ -66,7 +67,7 @@ func (m *Model) getFieldsFiltered() string { if m.fieldsEx == "" { // No filtering. if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") { - return m.db.QuoteString(m.fields) + return m.db.GetCore().QuoteString(m.fields) } return m.fields } @@ -105,7 +106,7 @@ func (m *Model) getFieldsFiltered() string { if len(newFields) > 0 { newFields += "," } - newFields += m.db.QuoteWord(k) + newFields += m.db.GetCore().QuoteWord(k) } return newFields } @@ -367,7 +368,7 @@ func (m *Model) Min(column string) (float64, error) { if len(column) == 0 { return 0, nil } - value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.QuoteWord(column))).Value() + value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.GetCore().QuoteWord(column))).Value() if err != nil { return 0, err } @@ -379,7 +380,7 @@ func (m *Model) Max(column string) (float64, error) { if len(column) == 0 { return 0, nil } - value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.QuoteWord(column))).Value() + value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.GetCore().QuoteWord(column))).Value() if err != nil { return 0, err } @@ -391,7 +392,7 @@ func (m *Model) Avg(column string) (float64, error) { if len(column) == 0 { return 0, nil } - value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.QuoteWord(column))).Value() + value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.GetCore().QuoteWord(column))).Value() if err != nil { return 0, err } @@ -403,7 +404,7 @@ func (m *Model) Sum(column string) (float64, error) { if len(column) == 0 { return 0, nil } - value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.QuoteWord(column))).Value() + value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.GetCore().QuoteWord(column))).Value() if err != nil { return 0, err } @@ -474,7 +475,7 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error { // doGetAllBySql does the select statement on the database. func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) { cacheKey := "" - cacheObj := m.db.GetCache().Ctx(m.db.GetCtx()) + cacheObj := m.db.GetCache().Ctx(m.GetCtx()) // Retrieve from cache. if m.cacheEnabled && m.tx == nil { cacheKey = m.cacheName @@ -496,7 +497,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } } } - result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...) + result, err = m.db.GetCore().DoGetAll(m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...) // Cache the result. if cacheKey != "" && err == nil { if m.cacheDuration < 0 { diff --git a/database/gdb/gdb_model_time.go b/database/gdb/gdb_model_time.go index e167fbb35..e4047e4a1 100644 --- a/database/gdb/gdb_model_time.go +++ b/database/gdb/gdb_model_time.go @@ -140,7 +140,7 @@ func (m *Model) getConditionForSoftDeleting() string { } // Only one table. if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" { - return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName)) + return fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(fieldName)) } return "" } @@ -163,12 +163,12 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string { return "" } if len(array1) >= 3 { - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[2]), m.db.QuoteWord(field)) + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[2]), m.db.GetCore().QuoteWord(field)) } if len(array1) >= 2 { - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[1]), m.db.QuoteWord(field)) + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[1]), m.db.GetCore().QuoteWord(field)) } - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(table), m.db.QuoteWord(field)) + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field)) } // getPrimaryTableName parses and returns the primary table name. diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index ad65e723f..8252789e3 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -9,12 +9,13 @@ package gdb import ( "database/sql" "fmt" + "reflect" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "reflect" ) // Update does "UPDATE ... " statement for the model. @@ -81,7 +82,8 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro if !gstr.ContainsI(conditionStr, " WHERE ") { return nil, gerror.New("there should be WHERE condition statement for UPDATE operation") } - return m.db.DoUpdate( + return m.db.GetCore().DoUpdate( + m.GetCtx(), m.getLink(true), m.tables, newData, diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 944946bb6..4dcd80a66 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -8,6 +8,8 @@ package gdb import ( "fmt" + "time" + "github.com/gogf/gf/container/gset" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/os/gtime" @@ -15,7 +17,6 @@ import ( "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "time" ) // TableFields retrieves and returns the fields information of specified table of current @@ -29,12 +30,12 @@ func (m *Model) TableFields(table string, schema ...string) (fields map[string]* if m.tx != nil { link = m.tx.tx } else { - link, err = m.db.GetSlave(schema...) + link, err = m.db.GetCore().GetSlave(schema...) if err != nil { return } } - return m.db.TableFields(link, table, schema...) + return m.db.TableFields(m.GetCtx(), link, table, schema...) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns @@ -111,7 +112,7 @@ func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, erro // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) { var err error - data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter) + data, err = m.db.GetCore().mappingAndFilterData(m.schema, m.tables, data, m.filter) if err != nil { return nil, err } @@ -187,13 +188,13 @@ func (m *Model) getLink(master bool) Link { } switch linkType { case linkTypeMaster: - link, err := m.db.GetMaster(m.schema) + link, err := m.db.GetCore().GetMaster(m.schema) if err != nil { panic(err) } return link case linkTypeSlave: - link, err := m.db.GetSlave(m.schema) + link, err := m.db.GetCore().GetSlave(m.schema) if err != nil { panic(err) } diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index bd6d37883..1fbd891d3 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -8,12 +8,13 @@ package gdb import ( "fmt" + "reflect" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" - "reflect" ) // With creates and returns an ORM model based on meta data of given object. @@ -38,7 +39,7 @@ func (m *Model) With(objects ...interface{}) *Model { model := m.getModel() for _, object := range objects { if m.tables == "" { - m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object)) + m.tables = m.db.GetCore().QuotePrefixTableName(getTableNameFromOrmTag(object)) return model } model.withArray = append(model.withArray, object) diff --git a/database/gdb/gdb_statement.go b/database/gdb/gdb_statement.go index e856ba0b0..cc74e858c 100644 --- a/database/gdb/gdb_statement.go +++ b/database/gdb/gdb_statement.go @@ -9,6 +9,7 @@ package gdb import ( "context" "database/sql" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" ) @@ -72,9 +73,10 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf Group: s.core.db.GetGroup(), } ) + // Tracing and logging. s.core.addSqlToTracing(ctx, sqlObj) if s.core.db.GetDebug() { - s.core.writeSqlToLogger(sqlObj) + s.core.writeSqlToLogger(ctx, sqlObj) } return result, err } diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index 187e9e9ea..abc46a7da 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -12,50 +12,184 @@ import ( "fmt" "reflect" + "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/guid" "github.com/gogf/gf/text/gregex" ) // TX is the struct for transaction management. type TX struct { - db DB // db is the current gdb database manager. - tx *sql.Tx // tx is the raw and underlying transaction manager. - master *sql.DB // master is the raw and underlying database manager. - transactionCount int // transactionCount marks the times that Begins. + db DB // db is the current gdb database manager. + tx *sql.Tx // tx is the raw and underlying transaction manager. + ctx context.Context // ctx is the context for this transaction only. + master *sql.DB // master is the raw and underlying database manager. + transactionId string // transactionId is an unique id generated by this object for this transaction. + transactionCount int // transactionCount marks the times that Begins. } const ( - transactionPointerPrefix = "transaction" - contextTransactionKey = "TransactionObject" + transactionPointerPrefix = "transaction" + contextTransactionKeyPrefix = "TransactionObjectForGroup_" + transactionIdForLoggerCtx = "TransactionId" ) +var ( + transactionIdGenerator = gtype.NewUint64() +) + +// Begin starts and returns the transaction object. +// You should call Commit or Rollback functions of the transaction object +// if you no longer use the transaction. Commit or Rollback functions will also +// close the transaction automatically. +func (c *Core) Begin() (tx *TX, err error) { + return c.doBeginCtx(c.GetCtx()) +} + +func (c *Core) doBeginCtx(ctx context.Context) (*TX, error) { + if master, err := c.db.Master(); err != nil { + return nil, err + } else { + var ( + tx *TX + sqlStr = "BEGIN" + mTime1 = gtime.TimestampMilli() + rawTx, err = master.Begin() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "DB.Begin", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + } + ) + if err == nil { + tx = &TX{ + db: c.db, + tx: rawTx, + ctx: context.WithValue(ctx, transactionIdForLoggerCtx, transactionIdGenerator.Add(1)), + master: master, + transactionId: guid.S(), + } + ctx = tx.ctx + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return tx, err + } +} + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + var ( + tx *TX + ) + if ctx == nil { + ctx = c.GetCtx() + } + // Check transaction object from context. + tx = TXFromCtx(ctx, c.db.GetGroup()) + if tx != nil { + return tx.Transaction(ctx, f) + } + tx, err = c.doBeginCtx(ctx) + if err != nil { + return err + } + // Inject transaction object into context. + tx.ctx = WithTX(tx.ctx, tx) + defer func() { + if err == nil { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + } + if err != nil { + if e := tx.Rollback(); e != nil { + err = e + } + } else { + if e := tx.Commit(); e != nil { + err = e + } + } + }() + err = f(tx.ctx, tx) + return +} + // WithTX injects given transaction object into context and returns a new context. func WithTX(ctx context.Context, tx *TX) context.Context { - return context.WithValue(ctx, contextTransactionKey, tx) + if tx == nil { + return ctx + } + // Check repeat injection from given. + group := tx.db.GetGroup() + if tx := TXFromCtx(ctx, group); tx != nil && tx.db.GetGroup() == group { + return ctx + } + dbCtx := tx.db.GetCtx() + if tx := TXFromCtx(dbCtx, group); tx != nil && tx.db.GetGroup() == group { + return dbCtx + } + // Inject transaction object and id into context. + ctx = context.WithValue(ctx, transactionKeyForContext(group), tx) + return ctx } // TXFromCtx retrieves and returns transaction object from context. // It is usually used in nested transaction feature, and it returns nil if it is not set previously. -func TXFromCtx(ctx context.Context) *TX { +func TXFromCtx(ctx context.Context, group string) *TX { if ctx == nil { return nil } - v := ctx.Value(contextTransactionKey) + v := ctx.Value(transactionKeyForContext(group)) if v != nil { - return v.(*TX) + tx := v.(*TX) + tx.ctx = ctx + return tx } return nil } +// transactionKeyForContext forms and returns a string for storing transaction object of certain database group into context. +func transactionKeyForContext(group string) string { + return contextTransactionKeyPrefix + group +} + +// transactionKeyForNestedPoint forms and returns the transaction key at current save point. +func (tx *TX) transactionKeyForNestedPoint() string { + return tx.db.GetCore().QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount)) +} + +// Ctx sets the context for current transaction. +func (tx *TX) Ctx(ctx context.Context) *TX { + tx.ctx = ctx + return tx +} + // Commit commits current transaction. // Note that it releases previous saved transaction point if it's in a nested transaction procedure, // or else it commits the hole transaction. func (tx *TX) Commit() error { if tx.transactionCount > 0 { tx.transactionCount-- - _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey()) + _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKeyForNestedPoint()) return err } var ( @@ -74,9 +208,9 @@ func (tx *TX) Commit() error { Group: tx.db.GetGroup(), } ) - tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj) + tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) if tx.db.GetDebug() { - tx.db.writeSqlToLogger(sqlObj) + tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj) } return err } @@ -87,7 +221,7 @@ func (tx *TX) Commit() error { func (tx *TX) Rollback() error { if tx.transactionCount > 0 { tx.transactionCount-- - _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey()) + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKeyForNestedPoint()) return err } var ( @@ -106,16 +240,16 @@ func (tx *TX) Rollback() error { Group: tx.db.GetGroup(), } ) - tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj) + tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) if tx.db.GetDebug() { - tx.db.writeSqlToLogger(sqlObj) + tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj) } return err } // Begin starts a nested transaction procedure. func (tx *TX) Begin() error { - _, err := tx.Exec("SAVEPOINT " + tx.transactionKey()) + _, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint()) if err != nil { return err } @@ -126,22 +260,17 @@ func (tx *TX) Begin() error { // SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point. // The parameter `point` specifies the point name that will be saved to server. func (tx *TX) SavePoint(point string) error { - _, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point)) + _, err := tx.Exec("SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) return err } // RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction. // The parameter `point` specifies the point name that was saved previously. func (tx *TX) RollbackTo(point string) error { - _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point)) + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) return err } -// transactionKey forms and returns the transaction key at current save point. -func (tx *TX) transactionKey() string { - return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount)) -} - // Transaction wraps the transaction logic using function `f`. // It rollbacks the transaction and returns the error from function `f` if // it returns non-nil error. It commits the transaction and returns nil if @@ -150,10 +279,13 @@ func (tx *TX) transactionKey() string { // Note that, you should not Commit or Rollback the transaction in function `f` // as it is automatically handled by this function. func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + if ctx != nil { + tx.ctx = ctx + } // Check transaction object from context. - if TXFromCtx(ctx) == nil { + if TXFromCtx(tx.ctx, tx.db.GetGroup()) == nil { // Inject transaction object into context. - ctx = WithTX(ctx, tx) + tx.ctx = WithTX(tx.ctx, tx) } err = tx.Begin() if err != nil { @@ -175,20 +307,20 @@ func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *T } } }() - err = f(ctx, tx) + err = f(tx.ctx, tx) return } // Query does query operation on transaction. // See Core.Query. func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { - return tx.db.DoQuery(tx.tx, sql, args...) + return tx.db.GetCore().DoQuery(tx.ctx, tx.tx, sql, args...) } // Exec does none query operation on transaction. // See Core.Exec. func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { - return tx.db.DoExec(tx.tx, sql, args...) + return tx.db.GetCore().DoExec(tx.ctx, tx.tx, sql, args...) } // Prepare creates a prepared statement for later queries or executions. @@ -197,7 +329,7 @@ func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { // The caller must call the statement's Close method // when the statement is no longer needed. func (tx *TX) Prepare(sql string) (*Stmt, error) { - return tx.db.DoPrepare(tx.tx, sql) + return tx.db.GetCore().DoPrepare(tx.ctx, tx.tx, sql) } // GetAll queries and returns data records from database. @@ -207,7 +339,7 @@ func (tx *TX) GetAll(sql string, args ...interface{}) (Result, error) { return nil, err } defer rows.Close() - return tx.db.convertRowsToResult(rows) + return tx.db.GetCore().convertRowsToResult(rows) } // GetOne queries and returns one record from database. @@ -248,8 +380,8 @@ func (tx *TX) GetStructs(objPointerSlice interface{}, sql string, args ...interf // If parameter `pointer` is type of struct pointer, it calls GetStruct internally for // the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally // for conversion. -func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) error { - t := reflect.TypeOf(objPointer) +func (tx *TX) GetScan(pointer interface{}, sql string, args ...interface{}) error { + t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { return fmt.Errorf("params should be type of pointer, but got: %v", k) @@ -257,9 +389,9 @@ func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) e k = t.Elem().Kind() switch k { case reflect.Array, reflect.Slice: - return tx.db.GetStructs(objPointer, sql, args...) + return tx.GetStructs(pointer, sql, args...) case reflect.Struct: - return tx.db.GetStruct(objPointer, sql, args...) + return tx.GetStruct(pointer, sql, args...) default: return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) } @@ -302,9 +434,9 @@ func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) { // The parameter `batch` specifies the batch operation count when given data is slice. func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(data).Batch(batch[0]).Insert() + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Insert() } - return tx.Model(table).Data(data).Insert() + return tx.Model(table).Ctx(tx.ctx).Data(data).Insert() } // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. @@ -318,17 +450,17 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, // The parameter `batch` specifies the batch operation count when given data is slice. func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(data).Batch(batch[0]).InsertIgnore() + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertIgnore() } - return tx.Model(table).Data(data).InsertIgnore() + return tx.Model(table).Ctx(tx.ctx).Data(data).InsertIgnore() } // InsertAndGetId performs action Insert and returns the last insert id that automatically generated. func (tx *TX) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { if len(batch) > 0 { - return tx.Model(table).Data(data).Batch(batch[0]).InsertAndGetId() + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertAndGetId() } - return tx.Model(table).Data(data).InsertAndGetId() + return tx.Model(table).Ctx(tx.ctx).Data(data).InsertAndGetId() } // Replace does "REPLACE INTO ..." statement for the table. @@ -345,9 +477,9 @@ func (tx *TX) InsertAndGetId(table string, data interface{}, batch ...int) (int6 // `batch` specifies the batch operation count. func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(data).Batch(batch[0]).Replace() + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Replace() } - return tx.Model(table).Data(data).Replace() + return tx.Model(table).Ctx(tx.ctx).Data(data).Replace() } // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. @@ -363,45 +495,45 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, // `batch` specifies the batch operation count. func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(data).Batch(batch[0]).Save() + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Save() } - return tx.Model(table).Data(data).Save() + return tx.Model(table).Ctx(tx.ctx).Data(data).Save() } // BatchInsert batch inserts data. // The parameter `list` must be type of slice of map or struct. func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(list).Batch(batch[0]).Insert() + return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Insert() } - return tx.Model(table).Data(list).Insert() + return tx.Model(table).Ctx(tx.ctx).Data(list).Insert() } // BatchInsertIgnore batch inserts data with ignore option. // The parameter `list` must be type of slice of map or struct. func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(list).Batch(batch[0]).InsertIgnore() + return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).InsertIgnore() } - return tx.Model(table).Data(list).InsertIgnore() + return tx.Model(table).Ctx(tx.ctx).Data(list).InsertIgnore() } // BatchReplace batch replaces data. // The parameter `list` must be type of slice of map or struct. func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(list).Batch(batch[0]).Replace() + return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Replace() } - return tx.Model(table).Data(list).Replace() + return tx.Model(table).Ctx(tx.ctx).Data(list).Replace() } // BatchSave batch replaces data. // The parameter `list` must be type of slice of map or struct. func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { - return tx.Model(table).Data(list).Batch(batch[0]).Save() + return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Save() } - return tx.Model(table).Data(list).Save() + return tx.Model(table).Ctx(tx.ctx).Data(list).Save() } // Update does "UPDATE ... " statement for the table. @@ -419,7 +551,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul // "age IN(?,?)", 18, 50 // User{ Id : 1, UserName : "john"} func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { - return tx.Model(table).Data(data).Where(condition, args...).Update() + return tx.Model(table).Ctx(tx.ctx).Data(data).Where(condition, args...).Update() } // Delete does "DELETE FROM ... " statement for the table. @@ -434,5 +566,5 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args // "age IN(?,?)", 18, 50 // User{ Id : 1, UserName : "john"} func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) { - return tx.Model(table).Where(condition, args...).Delete() + return tx.Model(table).Ctx(tx.ctx).Where(condition, args...).Delete() } diff --git a/database/gdb/gdb_z_driver_test.go b/database/gdb/gdb_z_driver_test.go index 80686d203..e166856d9 100644 --- a/database/gdb/gdb_z_driver_test.go +++ b/database/gdb/gdb_z_driver_test.go @@ -7,11 +7,13 @@ package gdb_test import ( + "context" + "testing" + "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/test/gtest" - "testing" ) // MyDriver is a custom database driver, which is used for testing only. @@ -41,9 +43,9 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { // HandleSqlBeforeCommit handles the sql before posts it to database. // It here overwrites the same method of gdb.DriverMysql and makes some custom changes. -func (d *MyDriver) HandleSqlBeforeCommit(link gdb.Link, sql string, args []interface{}) (string, []interface{}) { +func (d *MyDriver) HandleSqlBeforeCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (string, []interface{}) { latestSqlString.Set(sql) - return d.DriverMysql.HandleSqlBeforeCommit(link, sql, args) + return d.DriverMysql.HandleSqlBeforeCommit(ctx, link, sql, args) } func init() { diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 78db30e4f..b3087a4ea 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -2910,13 +2910,13 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.HasTable(table) + result, err := db.GetCore().HasTable(table) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { - result, err := db.HasTable("table12321") + result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) }) diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index 1fa33ea23..b22b2a765 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -833,7 +833,7 @@ func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { table := createTable() defer dropTable(table) - + db.SetDebug(true) gtest.C(t, func(t *gtest.T) { var ( err error @@ -897,7 +897,7 @@ func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { table := createTable() defer dropTable(table) - + db.SetDebug(true) gtest.C(t, func(t *gtest.T) { var ( err error @@ -910,7 +910,7 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { - _, err = db.Model(table).Data(g.Map{ + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ "id": 1, "passport": "USER_1", "password": "PASS_1", @@ -933,9 +933,10 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { return err }) t.AssertNil(err) + // rollback err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { - _, err = tx.Model(table).Data(g.Map{ + _, err = tx.Model(table).Ctx(ctx).Data(g.Map{ "id": 2, "passport": "USER_2", "password": "PASS_2", @@ -943,6 +944,7 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { "create_time": gtime.Now().String(), }).Insert() t.AssertNil(err) + // panic makes this transaction rollback. panic("error") return err }) diff --git a/os/glog/glog_logger_config.go b/os/glog/glog_logger_config.go index dd619d20e..f28e6862a 100644 --- a/os/glog/glog_logger_config.go +++ b/os/glog/glog_logger_config.go @@ -9,14 +9,15 @@ package glog import ( "errors" "fmt" + "io" + "strings" + "time" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "io" - "strings" - "time" ) // Config is the configuration object for logger. @@ -165,6 +166,24 @@ func (l *Logger) SetCtxKeys(keys ...interface{}) { l.config.CtxKeys = keys } +// AppendCtxKeys appends extra keys to logger. +// It ignores the key if it is already appended to the logger previously. +func (l *Logger) AppendCtxKeys(keys ...interface{}) { + var isExist bool + for _, key := range keys { + isExist = false + for _, ctxKey := range l.config.CtxKeys { + if ctxKey == key { + isExist = true + break + } + } + if !isExist { + l.config.CtxKeys = append(l.config.CtxKeys, key) + } + } +} + // GetCtxKeys retrieves and returns the context keys for logging. func (l *Logger) GetCtxKeys() []interface{} { return l.config.CtxKeys From 406b6bf410147f7331f44f6754079a4ef1184286 Mon Sep 17 00:00:00 2001 From: jflyfox Date: Fri, 21 May 2021 15:30:21 +0800 Subject: [PATCH 49/70] infract internal link --- database/gdb/gdb.go | 42 ++-- database/gdb/gdb_core.go | 181 +++-------------- database/gdb/gdb_core_link.go | 31 +++ ...transaction.go => gdb_core_transaction.go} | 57 +++--- database/gdb/gdb_core_underlying.go | 186 ++++++++++++++++++ database/gdb/gdb_core_utility.go | 24 ++- database/gdb/gdb_driver_mssql.go | 10 +- database/gdb/gdb_driver_mysql.go | 10 +- database/gdb/gdb_driver_oracle.go | 12 +- database/gdb/gdb_driver_pgsql.go | 10 +- database/gdb/gdb_driver_sqlite.go | 10 +- database/gdb/gdb_model_utility.go | 10 +- database/gdb/gdb_statement.go | 18 +- database/gdb/gdb_z_mysql_transaction_test.go | 120 ++++++++++- 14 files changed, 465 insertions(+), 256 deletions(-) create mode 100644 database/gdb/gdb_core_link.go rename database/gdb/{gdb_transaction.go => gdb_core_transaction.go} (95%) create mode 100644 database/gdb/gdb_core_underlying.go diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 8d634359e..5b18d5639 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -113,8 +113,8 @@ type DB interface { // Master/Slave specification support. // =========================================================================== - Master() (*sql.DB, error) // See Core.Master. - Slave() (*sql.DB, error) // See Core.Slave. + Master(schema ...string) (*sql.DB, error) // See Core.Master. + Slave(schema ...string) (*sql.DB, error) // See Core.Slave. // =========================================================================== // Ping-Pong. @@ -187,16 +187,28 @@ type Driver interface { New(core *Core, node *ConfigNode) (DB, error) } +// Link is a common database function wrapper interface. +type Link interface { + Query(sql string, args ...interface{}) (*sql.Rows, error) + Exec(sql string, args ...interface{}) (sql.Result, error) + Prepare(sql string) (*sql.Stmt, error) + QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) + ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error) + PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error) + IsTransaction() bool +} + // Sql is the sql recording struct. type Sql struct { - Sql string // SQL string(may contain reserved char '?'). - Type string // SQL operation type. - Args []interface{} // Arguments for this sql. - Format string // Formatted sql which contains arguments in the sql. - Error error // Execution result. - Start int64 // Start execution timestamp in milliseconds. - End int64 // End execution timestamp in milliseconds. - Group string // Group is the group name of the configuration that the sql is executed from. + Sql string // SQL string(may contain reserved char '?'). + Type string // SQL operation type. + Args []interface{} // Arguments for this sql. + Format string // Formatted sql which contains arguments in the sql. + Error error // Execution result. + Start int64 // Start execution timestamp in milliseconds. + End int64 // End execution timestamp in milliseconds. + Group string // Group is the group name of the configuration that the sql is executed from. + IsTransaction bool // IsTransaction marks whether this sql is executed in transaction. } // TableField is the struct for table field. @@ -211,16 +223,6 @@ type TableField struct { Comment string // Comment. } -// Link is a common database function wrapper interface. -type Link interface { - Query(sql string, args ...interface{}) (*sql.Rows, error) - Exec(sql string, args ...interface{}) (sql.Result, error) - Prepare(sql string) (*sql.Stmt, error) - QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) - ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error) - PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error) -} - // Counter is the type for update count. type Counter struct { Field string diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 14a14917c..9c0786935 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -19,7 +19,6 @@ import ( "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/container/gvar" - "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" ) @@ -94,162 +93,26 @@ func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Cont // Master creates and returns a connection from master node if master-slave configured. // It returns the default connection if master-slave not configured. -func (c *Core) Master() (*sql.DB, error) { - return c.getSqlDb(true, c.schema.Val()) +func (c *Core) Master(schema ...string) (*sql.DB, error) { + useSchema := "" + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] + } else { + useSchema = c.schema.Val() + } + return c.getSqlDb(true, useSchema) } // Slave creates and returns a connection from slave node if master-slave configured. // It returns the default connection if master-slave not configured. -func (c *Core) Slave() (*sql.DB, error) { - return c.getSqlDb(false, c.schema.Val()) -} - -// Query commits one query SQL to underlying driver and returns the execution result. -// It is most commonly used for data querying. -func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { - link, err := c.db.Slave() - if err != nil { - return nil, err - } - return c.DoQuery(c.GetCtx(), link, sql, args...) -} - -// DoQuery commits the sql string and its arguments to underlying driver -// through given link object and returns the execution result. -func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { - sql, args = formatSql(sql, args) - sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) - if c.GetConfig().QueryTimeout > 0 { - ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) - } - mTime1 := gtime.TimestampMilli() - rows, err = link.QueryContext(ctx, sql, args...) - mTime2 := gtime.TimestampMilli() - sqlObj := &Sql{ - Sql: sql, - Type: "DB.QueryContext", - Args: args, - Format: FormatSqlWithArgs(sql, args), - Error: err, - Start: mTime1, - End: mTime2, - Group: c.db.GetGroup(), - } - // Tracing and logging. - c.addSqlToTracing(ctx, sqlObj) - if c.db.GetDebug() { - c.writeSqlToLogger(ctx, sqlObj) - } - if err == nil { - return rows, nil +func (c *Core) Slave(schema ...string) (*sql.DB, error) { + useSchema := "" + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] } else { - err = formatError(err, sql, args...) + useSchema = c.schema.Val() } - return nil, err -} - -// Exec commits one query SQL to underlying driver and returns the execution result. -// It is most commonly used for data inserting and updating. -func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) { - link, err := c.db.Master() - if err != nil { - return nil, err - } - return c.DoExec(c.GetCtx(), link, sql, args...) -} - -// DoExec commits the sql string and its arguments to underlying driver -// through given link object and returns the execution result. -func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) { - sql, args = formatSql(sql, args) - sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) - if c.GetConfig().ExecTimeout > 0 { - var cancelFunc context.CancelFunc - ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) - defer cancelFunc() - } - - mTime1 := gtime.TimestampMilli() - if !c.db.GetDryRun() { - result, err = link.ExecContext(ctx, sql, args...) - } else { - result = new(SqlResult) - } - mTime2 := gtime.TimestampMilli() - sqlObj := &Sql{ - Sql: sql, - Type: "DB.ExecContext", - Args: args, - Format: FormatSqlWithArgs(sql, args), - Error: err, - Start: mTime1, - End: mTime2, - Group: c.db.GetGroup(), - } - // Tracing and logging. - c.addSqlToTracing(ctx, sqlObj) - if c.db.GetDebug() { - c.writeSqlToLogger(ctx, sqlObj) - } - return result, formatError(err, sql, args...) -} - -// Prepare creates a prepared statement for later queries or executions. -// Multiple queries or executions may be run concurrently from the -// returned statement. -// The caller must call the statement's Close method -// when the statement is no longer needed. -// -// The parameter `execOnMaster` specifies whether executing the sql on master node, -// or else it executes the sql on slave node if master-slave configured. -func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) { - var ( - err error - link Link - ) - if len(execOnMaster) > 0 && execOnMaster[0] { - if link, err = c.db.Master(); err != nil { - return nil, err - } - } else { - if link, err = c.db.Slave(); err != nil { - return nil, err - } - } - return c.DoPrepare(c.GetCtx(), link, sql) -} - -// DoPrepare calls prepare function on given link object and returns the statement object. -func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) { - if c.GetConfig().PrepareTimeout > 0 { - // DO NOT USE cancel function in prepare statement. - ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout) - } - var ( - mTime1 = gtime.TimestampMilli() - stmt, err = link.PrepareContext(ctx, sql) - mTime2 = gtime.TimestampMilli() - sqlObj = &Sql{ - Sql: sql, - Type: "DB.PrepareContext", - Args: nil, - Format: FormatSqlWithArgs(sql, nil), - Error: err, - Start: mTime1, - End: mTime2, - Group: c.db.GetGroup(), - } - ) - // Tracing and logging. - c.addSqlToTracing(ctx, sqlObj) - if c.db.GetDebug() { - c.writeSqlToLogger(ctx, sqlObj) - } - return &Stmt{ - Stmt: stmt, - core: c, - sql: sql, - }, err + return c.getSqlDb(false, useSchema) } // GetAll queries and returns data records from database. @@ -260,7 +123,7 @@ func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) { // DoGetAll queries and returns data records from database. func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) { if link == nil { - link, err = c.db.Slave() + link, err = c.SlaveLink() if err != nil { return nil, err } @@ -536,7 +399,7 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, data inter updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr) } if link == nil { - if link, err = c.db.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return nil, err } } @@ -641,7 +504,7 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list return result, gerror.New("data list cannot be empty") } if link == nil { - if link, err = c.db.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return } } @@ -800,7 +663,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter } // If no link passed, it then uses the master link. if link == nil { - if link, err = c.db.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return nil, err } } @@ -826,7 +689,7 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{}) // This function is usually used for custom interface definition, you do not need call it manually. func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { if link == nil { - if link, err = c.db.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return nil, err } } @@ -891,8 +754,10 @@ func (c *Core) MarshalJSON() ([]byte, error) { // It is enabled only if configuration "debug" is true. func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { var transactionIdStr string - if v := ctx.Value(transactionIdForLoggerCtx); v != nil { - transactionIdStr = fmt.Sprintf(`[%d] `, v.(uint64)) + if sql.IsTransaction { + if v := ctx.Value(transactionIdForLoggerCtx); v != nil { + transactionIdStr = fmt.Sprintf(`[%d] `, v.(uint64)) + } } s := fmt.Sprintf("[%3d ms] [%s] %s%s", sql.End-sql.Start, sql.Group, transactionIdStr, sql.Format) if sql.Error != nil { diff --git a/database/gdb/gdb_core_link.go b/database/gdb/gdb_core_link.go new file mode 100644 index 000000000..06ea3190b --- /dev/null +++ b/database/gdb/gdb_core_link.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 gdb + +import ( + "database/sql" +) + +// dbLink is used to implement interface Link for DB. +type dbLink struct { + *sql.DB +} + +// txLink is used to implement interface Link for TX. +type txLink struct { + *sql.Tx +} + +// IsTransaction returns if current Link is a transaction. +func (*dbLink) IsTransaction() bool { + return false +} + +// IsTransaction returns if current Link is a transaction. +func (*txLink) IsTransaction() bool { + return true +} diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_core_transaction.go similarity index 95% rename from database/gdb/gdb_transaction.go rename to database/gdb/gdb_core_transaction.go index abc46a7da..4636d9166 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_core_transaction.go @@ -59,14 +59,15 @@ func (c *Core) doBeginCtx(ctx context.Context) (*TX, error) { rawTx, err = master.Begin() mTime2 = gtime.TimestampMilli() sqlObj = &Sql{ - Sql: sqlStr, - Type: "DB.Begin", - Args: nil, - Format: sqlStr, - Error: err, - Start: mTime1, - End: mTime2, - Group: c.db.GetGroup(), + Sql: sqlStr, + Type: "DB.Begin", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: true, } ) if err == nil { @@ -198,14 +199,15 @@ func (tx *TX) Commit() error { err = tx.tx.Commit() mTime2 = gtime.TimestampMilli() sqlObj = &Sql{ - Sql: sqlStr, - Type: "TX.Commit", - Args: nil, - Format: sqlStr, - Error: err, - Start: mTime1, - End: mTime2, - Group: tx.db.GetGroup(), + Sql: sqlStr, + Type: "TX.Commit", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + IsTransaction: true, } ) tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) @@ -230,14 +232,15 @@ func (tx *TX) Rollback() error { err = tx.tx.Rollback() mTime2 = gtime.TimestampMilli() sqlObj = &Sql{ - Sql: sqlStr, - Type: "TX.Rollback", - Args: nil, - Format: sqlStr, - Error: err, - Start: mTime1, - End: mTime2, - Group: tx.db.GetGroup(), + Sql: sqlStr, + Type: "TX.Rollback", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + IsTransaction: true, } ) tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) @@ -314,13 +317,13 @@ func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *T // Query does query operation on transaction. // See Core.Query. func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { - return tx.db.GetCore().DoQuery(tx.ctx, tx.tx, sql, args...) + return tx.db.GetCore().DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...) } // Exec does none query operation on transaction. // See Core.Exec. func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { - return tx.db.GetCore().DoExec(tx.ctx, tx.tx, sql, args...) + return tx.db.GetCore().DoExec(tx.ctx, &txLink{tx.tx}, sql, args...) } // Prepare creates a prepared statement for later queries or executions. @@ -329,7 +332,7 @@ func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { // The caller must call the statement's Close method // when the statement is no longer needed. func (tx *TX) Prepare(sql string) (*Stmt, error) { - return tx.db.GetCore().DoPrepare(tx.ctx, tx.tx, sql) + return tx.db.GetCore().DoPrepare(tx.ctx, &txLink{tx.tx}, sql) } // GetAll queries and returns data records from database. diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go new file mode 100644 index 000000000..316127df0 --- /dev/null +++ b/database/gdb/gdb_core_underlying.go @@ -0,0 +1,186 @@ +// 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 gdb + +import ( + "context" + "database/sql" + + "github.com/gogf/gf/os/gtime" +) + +// Query commits one query SQL to underlying driver and returns the execution result. +// It is most commonly used for data querying. +func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { + return c.DoQuery(c.GetCtx(), nil, sql, args...) +} + +// DoQuery commits the sql string and its arguments to underlying driver +// through given link object and returns the execution result. +func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { + // Transaction checks. + if link == nil { + if link, err = c.SlaveLink(); err != nil { + return nil, err + } + } else if !link.IsTransaction() { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + // Link execution. + sql, args = formatSql(sql, args) + sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) + if c.GetConfig().QueryTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) + } + mTime1 := gtime.TimestampMilli() + rows, err = link.QueryContext(ctx, sql, args...) + mTime2 := gtime.TimestampMilli() + sqlObj := &Sql{ + Sql: sql, + Type: "DB.QueryContext", + Args: args, + Format: FormatSqlWithArgs(sql, args), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + if err == nil { + return rows, nil + } else { + err = formatError(err, sql, args...) + } + return nil, err +} + +// Exec commits one query SQL to underlying driver and returns the execution result. +// It is most commonly used for data inserting and updating. +func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) { + return c.DoExec(c.GetCtx(), nil, sql, args...) +} + +// DoExec commits the sql string and its arguments to underlying driver +// through given link object and returns the execution result. +func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) { + // Transaction checks. + if link == nil { + if link, err = c.MasterLink(); err != nil { + return nil, err + } + } else if !link.IsTransaction() { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + // Link execution. + sql, args = formatSql(sql, args) + sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args) + if c.GetConfig().ExecTimeout > 0 { + var cancelFunc context.CancelFunc + ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) + defer cancelFunc() + } + + mTime1 := gtime.TimestampMilli() + if !c.db.GetDryRun() { + result, err = link.ExecContext(ctx, sql, args...) + } else { + result = new(SqlResult) + } + mTime2 := gtime.TimestampMilli() + sqlObj := &Sql{ + Sql: sql, + Type: "DB.ExecContext", + Args: args, + Format: FormatSqlWithArgs(sql, args), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return result, formatError(err, sql, args...) +} + +// Prepare creates a prepared statement for later queries or executions. +// Multiple queries or executions may be run concurrently from the +// returned statement. +// The caller must call the statement's Close method +// when the statement is no longer needed. +// +// The parameter `execOnMaster` specifies whether executing the sql on master node, +// or else it executes the sql on slave node if master-slave configured. +func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) { + var ( + err error + link Link + ) + if len(execOnMaster) > 0 && execOnMaster[0] { + if link, err = c.MasterLink(); err != nil { + return nil, err + } + } else { + if link, err = c.SlaveLink(); err != nil { + return nil, err + } + } + return c.DoPrepare(c.GetCtx(), link, sql) +} + +// DoPrepare calls prepare function on given link object and returns the statement object. +func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) { + if link != nil && !link.IsTransaction() { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + if c.GetConfig().PrepareTimeout > 0 { + // DO NOT USE cancel function in prepare statement. + ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout) + } + var ( + mTime1 = gtime.TimestampMilli() + stmt, err = link.PrepareContext(ctx, sql) + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sql, + Type: "DB.PrepareContext", + Args: nil, + Format: FormatSqlWithArgs(sql, nil), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + ) + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return &Stmt{ + Stmt: stmt, + core: c, + link: link, + sql: sql, + }, err +} diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go index 3528d873e..0eaeb16c4 100644 --- a/database/gdb/gdb_core_utility.go +++ b/database/gdb/gdb_core_utility.go @@ -7,22 +7,26 @@ package gdb -import ( - "database/sql" -) - -// GetMaster acts like function Master but with additional `schema` parameter specifying +// MasterLink acts like function Master but with additional `schema` parameter specifying // the schema for the connection. It is defined for internal usage. // Also see Master. -func (c *Core) GetMaster(schema ...string) (*sql.DB, error) { - return c.getSqlDb(true, schema...) +func (c *Core) MasterLink(schema ...string) (Link, error) { + db, err := c.db.Master(schema...) + if err != nil { + return nil, err + } + return &dbLink{db}, nil } -// GetSlave acts like function Slave but with additional `schema` parameter specifying +// SlaveLink acts like function Slave but with additional `schema` parameter specifying // the schema for the connection. It is defined for internal usage. // Also see Slave. -func (c *Core) GetSlave(schema ...string) (*sql.DB, error) { - return c.getSqlDb(false, schema...) +func (c *Core) SlaveLink(schema ...string) (Link, error) { + db, err := c.db.Slave(schema...) + if err != nil { + return nil, err + } + return &dbLink{db}, nil } // QuoteWord checks given string `s` a word, if true quotes it with security chars of the database diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index fde84e762..d0bb93cfb 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -187,7 +187,7 @@ func (d *DriverMssql) parseSql(sql string) string { // It's mainly used in cli tool chain for automatically generating the models. func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } @@ -213,20 +213,20 @@ func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string, if gstr.Contains(table, " ") { return nil, gerror.New("function TableFields supports only single table operations") } - checkSchema := d.db.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } tableFieldsCacheKey := fmt.Sprintf( `mssql_table_fields_%s_%s@group:%s`, - table, checkSchema, d.GetGroup(), + table, useSchema, d.GetGroup(), ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( result Result ) if link == nil { - link, err = d.GetSlave(checkSchema) + link, err = d.SlaveLink(useSchema) if err != nil { return nil } diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index ee87ecd94..724b353ec 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -85,7 +85,7 @@ func (d *DriverMysql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql // It's mainly used in cli tool chain for automatically generating the models. func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } @@ -119,20 +119,20 @@ func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string, if gstr.Contains(table, " ") { return nil, gerror.New("function TableFields supports only single table operations") } - checkSchema := d.schema.Val() + useSchema := d.schema.Val() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } tableFieldsCacheKey := fmt.Sprintf( `mysql_table_fields_%s_%s@group:%s`, - table, checkSchema, d.GetGroup(), + table, useSchema, d.GetGroup(), ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( result Result ) if link == nil { - link, err = d.GetSlave(checkSchema) + link, err = d.SlaveLink(useSchema) if err != nil { return nil } diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index b067de9b4..be356f176 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -189,13 +189,13 @@ func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string, if gstr.Contains(table, " ") { return nil, gerror.New("function TableFields supports only single table operations") } - checkSchema := d.db.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } tableFieldsCacheKey := fmt.Sprintf( `oracle_table_fields_%s_%s@group:%s`, - table, checkSchema, d.GetGroup(), + table, useSchema, d.GetGroup(), ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( @@ -213,7 +213,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, ) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) if link == nil { - link, err = d.GetSlave(checkSchema) + link, err = d.SlaveLink(useSchema) if err != nil { return nil } @@ -340,7 +340,7 @@ func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, da } if link == nil { - if link, err = d.db.Master(); err != nil { + if link, err = d.MasterLink(); err != nil { return nil, err } } @@ -416,7 +416,7 @@ func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table strin return result, gerror.New("empty data list") } if link == nil { - if link, err = d.db.Master(); err != nil { + if link, err = d.MasterLink(); err != nil { return } } diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index 2d0bd76f6..8fb6ad243 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -92,7 +92,7 @@ func (d *DriverPgsql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql // It's mainly used in cli tool chain for automatically generating the models. func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } @@ -122,13 +122,13 @@ func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string, return nil, gerror.New("function TableFields supports only single table operations") } table, _ = gregex.ReplaceString("\"", "", table) - checkSchema := d.db.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } tableFieldsCacheKey := fmt.Sprintf( `pgsql_table_fields_%s_%s@group:%s`, - table, checkSchema, d.GetGroup(), + table, useSchema, d.GetGroup(), ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( @@ -143,7 +143,7 @@ ORDER BY a.attnum`, ) structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) if link == nil { - link, err = d.GetSlave(checkSchema) + link, err = d.SlaveLink(useSchema) if err != nil { return nil } diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index c78a3eb27..69f4a42a5 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -77,7 +77,7 @@ func (d *DriverSqlite) HandleSqlBeforeCommit(ctx context.Context, link Link, sql // It's mainly used in cli tool chain for automatically generating the models. func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } @@ -103,20 +103,20 @@ func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string, if gstr.Contains(table, " ") { return nil, gerror.New("function TableFields supports only single table operations") } - checkSchema := d.db.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } tableFieldsCacheKey := fmt.Sprintf( `sqlite_table_fields_%s_%s@group:%s`, - table, checkSchema, d.GetGroup(), + table, useSchema, d.GetGroup(), ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( result Result ) if link == nil { - link, err = d.GetSlave(checkSchema) + link, err = d.SlaveLink(useSchema) if err != nil { return nil } diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 4dcd80a66..9efee81cf 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -28,9 +28,9 @@ func (m *Model) TableFields(table string, schema ...string) (fields map[string]* link Link ) if m.tx != nil { - link = m.tx.tx + link = &txLink{m.tx.tx} } else { - link, err = m.db.GetCore().GetSlave(schema...) + link, err = m.db.GetCore().SlaveLink(schema...) if err != nil { return } @@ -176,7 +176,7 @@ func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEm // The parameter `master` specifies whether using the master node if master-slave configured. func (m *Model) getLink(master bool) Link { if m.tx != nil { - return m.tx.tx + return &txLink{m.tx.tx} } linkType := m.linkType if linkType == 0 { @@ -188,13 +188,13 @@ func (m *Model) getLink(master bool) Link { } switch linkType { case linkTypeMaster: - link, err := m.db.GetCore().GetMaster(m.schema) + link, err := m.db.GetCore().MasterLink(m.schema) if err != nil { panic(err) } return link case linkTypeSlave: - link, err := m.db.GetCore().GetSlave(m.schema) + link, err := m.db.GetCore().SlaveLink(m.schema) if err != nil { panic(err) } diff --git a/database/gdb/gdb_statement.go b/database/gdb/gdb_statement.go index cc74e858c..81f14affe 100644 --- a/database/gdb/gdb_statement.go +++ b/database/gdb/gdb_statement.go @@ -26,6 +26,7 @@ import ( type Stmt struct { *sql.Stmt core *Core + link Link sql string } @@ -63,14 +64,15 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf var ( timestampMilli2 = gtime.TimestampMilli() sqlObj = &Sql{ - Sql: s.sql, - Type: stmtType, - Args: args, - Format: FormatSqlWithArgs(s.sql, args), - Error: err, - Start: timestampMilli1, - End: timestampMilli2, - Group: s.core.db.GetGroup(), + Sql: s.sql, + Type: stmtType, + Args: args, + Format: FormatSqlWithArgs(s.sql, args), + Error: err, + Start: timestampMilli1, + End: timestampMilli2, + Group: s.core.db.GetGroup(), + IsTransaction: s.link.IsTransaction(), } ) // Tracing and logging. diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index b22b2a765..906bc6aca 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -833,7 +833,10 @@ func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { table := createTable() defer dropTable(table) + db.SetDebug(true) + defer db.SetDebug(false) + gtest.C(t, func(t *gtest.T) { var ( err error @@ -887,17 +890,75 @@ func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { }) t.AssertNil(err) - all, err := db.Model(table).All() + all, err := db.Ctx(ctx).Model(table).All() t.AssertNil(err) t.Assert(len(all), 1) t.Assert(all[0]["id"], 1) + + // another record. + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + // rollback + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) }) } func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { table := createTable() defer dropTable(table) + db.SetDebug(true) + defer db.SetDebug(false) + gtest.C(t, func(t *gtest.T) { var ( err error @@ -952,11 +1013,66 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { return nil }) t.AssertNil(err) - all, err := db.Model(table).All() t.AssertNil(err) t.Assert(len(all), 1) t.Assert(all[0]["id"], 1) + + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Ctx(ctx).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + // panic makes this transaction rollback. + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) }) } From 6d81aa4462d3483ac1a60212ecf72079ee81caa3 Mon Sep 17 00:00:00 2001 From: jflyfox Date: Fri, 21 May 2021 15:38:56 +0800 Subject: [PATCH 50/70] infract internal link --- database/gdb/gdb_core.go | 7 +------ database/gdb/gdb_model_utility.go | 13 +------------ 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 9c0786935..6aa55414c 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -23,6 +23,7 @@ import ( "github.com/gogf/gf/util/gconv" ) +// GetCore returns the underlying *Core object. func (c *Core) GetCore() *Core { return c } @@ -122,12 +123,6 @@ func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) { // DoGetAll queries and returns data records from database. func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) { - if link == nil { - link, err = c.SlaveLink() - if err != nil { - return nil, err - } - } rows, err := c.DoQuery(ctx, link, sql, args...) if err != nil || rows == nil { return nil, err diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 9efee81cf..362219965 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -24,18 +24,7 @@ import ( // // Also see DriverMysql.TableFields. func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { - var ( - link Link - ) - if m.tx != nil { - link = &txLink{m.tx.tx} - } else { - link, err = m.db.GetCore().SlaveLink(schema...) - if err != nil { - return - } - } - return m.db.TableFields(m.GetCtx(), link, table, schema...) + return m.db.TableFields(m.GetCtx(), m.getLink(false), table, schema...) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns From 64a9b06e64cd09ffc0e599d802ee0c7897ba4e33 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 23 May 2021 00:02:49 +0800 Subject: [PATCH 51/70] comment update --- database/gdb/gdb_core.go | 1 + database/gredis/gredis.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 6aa55414c..a275ef2bd 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -48,6 +48,7 @@ func (c *Core) Ctx(ctx context.Context) DB { ) *newCore = *c newCore.ctx = ctx + // It creates a new DB object, which is commonly a wrapper for object `Core`. newCore.db, err = driverMap[configNode.Type].New(newCore, configNode) if err != nil { // It is really a serious error here. diff --git a/database/gredis/gredis.go b/database/gredis/gredis.go index 744ba4da6..13211d81d 100644 --- a/database/gredis/gredis.go +++ b/database/gredis/gredis.go @@ -32,14 +32,14 @@ type Redis struct { ctx context.Context // Context. } -// Redis connection. +// Conn is redis connection. type Conn struct { redis.Conn ctx context.Context redis *Redis } -// Redis configuration. +// Config is redis configuration. type Config struct { Host string `json:"host"` Port int `json:"port"` @@ -54,7 +54,7 @@ type Config struct { TLSSkipVerify bool `json:"tlsSkipVerify"` // Disables server name verification when connecting over TLS. } -// Pool statistics. +// PoolStats is statistics of redis connection pool. type PoolStats struct { redis.PoolStats } @@ -189,7 +189,7 @@ func (r *Redis) Conn() *Conn { } } -// Alias of Conn, see Conn. +// GetConn is alias of Conn, see Conn. // Deprecated, use Conn instead. func (r *Redis) GetConn() *Conn { return r.Conn() From 211e62a8e7e55a5822d80411d98e9e85cd04605d Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 24 May 2021 13:30:04 +0800 Subject: [PATCH 52/70] add timestamptz type support for pgsql for package gdb --- database/gdb/gdb_core_structure.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index 02b9d11ac..be34cf062 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -100,7 +100,8 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s case "datetime", - "timestamp": + "timestamp", + "timestamptz": if t, ok := fieldValue.(time.Time); ok { return gtime.NewFromTime(t) } From 5903eb8ceb0474d2dcd0dbe91a848c52bcdb7f79 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 25 May 2021 09:56:23 +0800 Subject: [PATCH 53/70] fix issue #1254 --- database/gdb/gdb_z_mysql_struct_test.go | 23 ++++++++++++++++++++ util/gconv/gconv.go | 29 ++++++++++++++++++++++++- util/gconv/gconv_z_unit_slice_test.go | 2 ++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index 090a991a7..dbcf48d97 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -429,3 +429,26 @@ func Test_Model_Scan_UnmarshalValue(t *testing.T) { t.Assert(users[9].CreateTime.String(), CreateTime) }) } + +func Test_Model_Scan_Map(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(table).Order("id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[0].Passport, "user_1") + t.Assert(users[0].Password, "") + t.Assert(users[0].Nickname, "name_1") + t.Assert(users[0].CreateTime.String(), CreateTime) + + t.Assert(users[9].Id, 10) + t.Assert(users[9].Passport, "user_10") + t.Assert(users[9].Password, "") + t.Assert(users[9].Nickname, "name_10") + t.Assert(users[9].CreateTime.String(), CreateTime) + }) +} diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index af74e7369..83d140625 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -13,6 +13,7 @@ import ( "fmt" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/os/gtime" + "math" "reflect" "strconv" "strings" @@ -299,6 +300,32 @@ func Bytes(any interface{}) []byte { if f, ok := value.(apiBytes); ok { return f.Bytes() } + var ( + reflectValue = reflect.ValueOf(any) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Array, reflect.Slice: + var ( + ok = true + bytes = make([]byte, reflectValue.Len()) + ) + for i, _ := range bytes { + int32Value := Int32(reflectValue.Index(i).Interface()) + if int32Value < 0 || int32Value > math.MaxUint8 { + ok = false + break + } + bytes[i] = byte(int32Value) + } + if ok { + return bytes + } + } return gbinary.Encode(any) } } @@ -308,7 +335,7 @@ func Rune(any interface{}) rune { if v, ok := any.(rune); ok { return v } - return rune(Int32(any)) + return Int32(any) } // Runes converts `any` to []rune. diff --git a/util/gconv/gconv_z_unit_slice_test.go b/util/gconv/gconv_z_unit_slice_test.go index 062b6fc17..c67f4bf3f 100644 --- a/util/gconv/gconv_z_unit_slice_test.go +++ b/util/gconv/gconv_z_unit_slice_test.go @@ -19,6 +19,8 @@ func Test_Slice(t *testing.T) { gtest.C(t, func(t *gtest.T) { value := 123.456 t.AssertEQ(gconv.Bytes("123"), []byte("123")) + t.AssertEQ(gconv.Bytes([]interface{}{1}), []byte{1}) + t.AssertEQ(gconv.Bytes([]interface{}{300}), []byte("[300]")) t.AssertEQ(gconv.Strings(value), []string{"123.456"}) t.AssertEQ(gconv.Ints(value), []int{123}) t.AssertEQ(gconv.Floats(value), []float64{123.456}) From fab6c4048d18d1685a425709cac721d57a134353 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 25 May 2021 22:16:55 +0800 Subject: [PATCH 54/70] comment update --- debug/gdebug/gdebug_caller.go | 4 ++-- util/gconv/gconv.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/debug/gdebug/gdebug_caller.go b/debug/gdebug/gdebug_caller.go index a70ffda6a..485a61228 100644 --- a/debug/gdebug/gdebug_caller.go +++ b/debug/gdebug/gdebug_caller.go @@ -42,13 +42,13 @@ func init() { } } -// CallerPath returns the function name and the absolute file path along with its line +// Caller returns the function name and the absolute file path along with its line // number of the caller. func Caller(skip ...int) (function string, path string, line int) { return CallerWithFilter("", skip...) } -// CallerPathWithFilter returns the function name and the absolute file path along with +// CallerWithFilter returns the function name and the absolute file path along with // its line number of the caller. // // The parameter is used to filter the path of the caller. diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 83d140625..be6b027ac 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -40,7 +40,7 @@ var ( "false": {}, } - // Priority tags for Map*/Struct* functions. + // StructTagPriority defines the default priority tags for Map*/Struct* functions. // Note, the "gconv", "param", "params" tags are used by old version of package. // It is strongly recommended using short tag "c" or "p" instead in the future. StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"} From fc88001a8cdfa423c03d2a21eebad42cf98039a9 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 26 May 2021 09:55:33 +0800 Subject: [PATCH 55/70] revert name from Job to Entry for package gtimer/gcron --- os/gcron/gcron.go | 12 +-- os/gcron/gcron_cron.go | 28 +++---- os/gcron/gcron_entry.go | 60 +++++++-------- os/gcron/gcron_unit_2_test.go | 2 +- os/gtimer/gtimer.go | 22 +++--- os/gtimer/{gtimer_job.go => gtimer_entry.go} | 74 +++++++++---------- os/gtimer/gtimer_timer.go | 40 +++++----- os/gtimer/gtimer_timer_loop.go | 12 +-- os/gtimer/gtimer_z_unit_api_test.go | 8 +- ...ob_test.go => gtimer_z_unit_entry_test.go} | 0 os/gtimer/gtimer_z_unit_timer_test.go | 2 +- 11 files changed, 130 insertions(+), 130 deletions(-) rename os/gtimer/{gtimer_job.go => gtimer_entry.go} (62%) rename os/gtimer/{gtimer_z_unit_job_test.go => gtimer_z_unit_entry_test.go} (100%) diff --git a/os/gcron/gcron.go b/os/gcron/gcron.go index b3a17f426..7f45c4b2c 100644 --- a/os/gcron/gcron.go +++ b/os/gcron/gcron.go @@ -50,7 +50,7 @@ func GetLogLevel() int { // Add adds a timed task to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func Add(pattern string, job func(), name ...string) (*Job, error) { +func Add(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.Add(pattern, job, name...) } @@ -58,21 +58,21 @@ func Add(pattern string, job func(), name ...string) (*Job, error) { // A singleton timed task is that can only be running one single instance at the same time. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddSingleton(pattern string, job func(), name ...string) (*Job, error) { +func AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.AddSingleton(pattern, job, name...) } // AddOnce adds a timed task which can be run only once, to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddOnce(pattern string, job func(), name ...string) (*Job, error) { +func AddOnce(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.AddOnce(pattern, job, name...) } // AddTimes adds a timed task which can be run specified times, to default cron object. // A unique can be bound with the timed task. // It returns and error if the is already used. -func AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) { +func AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { return defaultCron.AddTimes(pattern, times, job, name...) } @@ -100,7 +100,7 @@ func DelayAddTimes(delay time.Duration, pattern string, times int, job func(), n // Search returns a scheduled task with the specified . // It returns nil if no found. -func Search(name string) *Job { +func Search(name string) *Entry { return defaultCron.Search(name) } @@ -115,7 +115,7 @@ func Size() int { } // Entries return all timed tasks as slice. -func Entries() []*Job { +func Entries() []*Entry { return defaultCron.Entries() } diff --git a/os/gcron/gcron_cron.go b/os/gcron/gcron_cron.go index de9493fcc..28e2b8522 100644 --- a/os/gcron/gcron_cron.go +++ b/os/gcron/gcron_cron.go @@ -60,20 +60,20 @@ func (c *Cron) GetLogLevel() int { // Add adds a timed task. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) Add(pattern string, job func(), name ...string) (*Job, error) { +func (c *Cron) Add(pattern string, job func(), name ...string) (*Entry, error) { if len(name) > 0 { if c.Search(name[0]) != nil { return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0])) } } - return c.addJob(pattern, job, false, name...) + return c.addEntry(pattern, job, false, name...) } // AddSingleton adds a singleton timed task. // A singleton timed task is that can only be running one single instance at the same time. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Job, error) { +func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -85,7 +85,7 @@ func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Job, e // AddOnce adds a timed task which can be run only once. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Job, error) { +func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -97,7 +97,7 @@ func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Job, error) // AddTimes adds a timed task which can be run specified times. // A unique can be bound with the timed task. // It returns and error if the is already used. -func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) { +func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { if entry, err := c.Add(pattern, job, name...); err != nil { return nil, err } else { @@ -146,9 +146,9 @@ func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job // Search returns a scheduled task with the specified . // It returns nil if no found. -func (c *Cron) Search(name string) *Job { +func (c *Cron) Search(name string) *Entry { if v := c.entries.Get(name); v != nil { - return v.(*Job) + return v.(*Entry) } return nil } @@ -182,7 +182,7 @@ func (c *Cron) Stop(name ...string) { // Remove deletes scheduled task which named . func (c *Cron) Remove(name string) { if v := c.entries.Get(name); v != nil { - v.(*Job).Close() + v.(*Entry).Close() } } @@ -197,10 +197,10 @@ func (c *Cron) Size() int { } // Entries return all timed tasks as slice(order by registered time asc). -func (c *Cron) Entries() []*Job { +func (c *Cron) Entries() []*Entry { array := garray.NewSortedArraySize(c.entries.Size(), func(v1, v2 interface{}) int { - entry1 := v1.(*Job) - entry2 := v2.(*Job) + entry1 := v1.(*Entry) + entry2 := v2.(*Entry) if entry1.Time.Nanosecond() > entry2.Time.Nanosecond() { return 1 } @@ -208,13 +208,13 @@ func (c *Cron) Entries() []*Job { }, true) c.entries.RLockFunc(func(m map[string]interface{}) { for _, v := range m { - array.Add(v.(*Job)) + array.Add(v.(*Entry)) } }) - entries := make([]*Job, array.Len()) + entries := make([]*Entry, array.Len()) array.RLockFunc(func(array []interface{}) { for k, v := range array { - entries[k] = v.(*Job) + entries[k] = v.(*Entry) } }) return entries diff --git a/os/gcron/gcron_entry.go b/os/gcron/gcron_entry.go index e0c33e474..68fd6d14c 100644 --- a/os/gcron/gcron_entry.go +++ b/os/gcron/gcron_entry.go @@ -17,29 +17,29 @@ import ( "github.com/gogf/gf/util/gconv" ) -// Timed task entry. -type Job struct { +// Entry is timing task entry. +type Entry struct { cron *Cron // Cron object belonged to. - job *gtimer.Job // Associated gtimer.Job. + entry *gtimer.Entry // Associated gtimer.Entry. schedule *cronSchedule // Timed schedule object. jobName string // Callback function name(address info). times *gtype.Int // Running times limit. - Name string // Job name. + Name string // Entry name. Job func() `json:"-"` // Callback function. Time time.Time // Registered time. } -// addJob creates and returns a new Job object. +// addEntry creates and returns a new Entry object. // Param is the callback function for timed task execution. // Param specifies whether timed task executing in singleton mode. // Param names this entry for manual control. -func (c *Cron) addJob(pattern string, job func(), singleton bool, name ...string) (*Job, error) { +func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...string) (*Entry, error) { schedule, err := newSchedule(pattern) if err != nil { return nil, err } // No limit for , for gtimer checking scheduling every second. - entry := &Job{ + entry := &Entry{ cron: c, schedule: schedule, jobName: runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(), @@ -57,57 +57,57 @@ func (c *Cron) addJob(pattern string, job func(), singleton bool, name ...string // It should start running after the entry is added to the entries map, // to avoid the task from running during adding where the entries // does not have the entry information, which might cause panic. - entry.job = gtimer.AddJob(time.Second, entry.check, singleton, -1, gtimer.StatusStopped) + entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, -1, gtimer.StatusStopped) c.entries.Set(entry.Name, entry) - entry.job.Start() + entry.entry.Start() return entry, nil } // IsSingleton return whether this entry is a singleton timed task. -func (entry *Job) IsSingleton() bool { - return entry.job.IsSingleton() +func (entry *Entry) IsSingleton() bool { + return entry.entry.IsSingleton() } // SetSingleton sets the entry running in singleton mode. -func (entry *Job) SetSingleton(enabled bool) { - entry.job.SetSingleton(true) +func (entry *Entry) SetSingleton(enabled bool) { + entry.entry.SetSingleton(true) } // SetTimes sets the times which the entry can run. -func (entry *Job) SetTimes(times int) { +func (entry *Entry) SetTimes(times int) { entry.times.Set(times) } // Status returns the status of entry. -func (entry *Job) Status() int { - return entry.job.Status() +func (entry *Entry) Status() int { + return entry.entry.Status() } // SetStatus sets the status of the entry. -func (entry *Job) SetStatus(status int) int { - return entry.job.SetStatus(status) +func (entry *Entry) SetStatus(status int) int { + return entry.entry.SetStatus(status) } // Start starts running the entry. -func (entry *Job) Start() { - entry.job.Start() +func (entry *Entry) Start() { + entry.entry.Start() } // Stop stops running the entry. -func (entry *Job) Stop() { - entry.job.Stop() +func (entry *Entry) Stop() { + entry.entry.Stop() } // Close stops and removes the entry from cron. -func (entry *Job) Close() { +func (entry *Entry) Close() { entry.cron.entries.Remove(entry.Name) - entry.job.Close() + entry.entry.Close() } -// Timed task check execution. -// The running times limits feature is implemented by gcron.Job and cannot be implemented by gtimer.Job. -// gcron.Job relies on gtimer to implement a scheduled task check for gcron.Job per second. -func (entry *Job) check() { +// Timing task check execution. +// The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry. +// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second. +func (entry *Entry) check() { if entry.schedule.meet(time.Now()) { path := entry.cron.GetLogPath() level := entry.cron.GetLogLevel() @@ -125,7 +125,7 @@ func (entry *Job) check() { // Running times check. times := entry.times.Add(-1) if times <= 0 { - if entry.job.SetStatus(StatusClosed) == StatusClosed || times < 0 { + if entry.entry.SetStatus(StatusClosed) == StatusClosed || times < 0 { return } } @@ -139,7 +139,7 @@ func (entry *Job) check() { } else { glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) } - if entry.job.Status() == StatusClosed { + if entry.entry.Status() == StatusClosed { entry.Close() } }() diff --git a/os/gcron/gcron_unit_2_test.go b/os/gcron/gcron_unit_2_test.go index 6fca7c849..5956dc8bd 100644 --- a/os/gcron/gcron_unit_2_test.go +++ b/os/gcron/gcron_unit_2_test.go @@ -16,7 +16,7 @@ import ( "github.com/gogf/gf/test/gtest" ) -func TestCron_Job_Operations(t *testing.T) { +func TestCron_Entry_Operations(t *testing.T) { gtest.C(t, func(t *gtest.T) { var ( cron = gcron.New() diff --git a/os/gtimer/gtimer.go b/os/gtimer/gtimer.go index 21474a498..3ffd36326 100644 --- a/os/gtimer/gtimer.go +++ b/os/gtimer/gtimer.go @@ -80,11 +80,11 @@ func SetInterval(interval time.Duration, job JobFunc) { } // Add adds a timing job to the default timer, which runs in interval of . -func Add(interval time.Duration, job JobFunc) *Job { +func Add(interval time.Duration, job JobFunc) *Entry { return defaultTimer.Add(interval, job) } -// AddJob adds a timing job to the default timer with detailed parameters. +// AddEntry adds a timing job to the default timer with detailed parameters. // // The parameter specifies the running interval of the job. // @@ -95,22 +95,22 @@ func Add(interval time.Duration, job JobFunc) *Job { // exits if its run times exceeds the . // // The parameter specifies the job status when it's firstly added to the timer. -func AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { - return defaultTimer.AddJob(interval, job, singleton, times, status) +func AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { + return defaultTimer.AddEntry(interval, job, singleton, times, status) } // AddSingleton is a convenience function for add singleton mode job. -func AddSingleton(interval time.Duration, job JobFunc) *Job { +func AddSingleton(interval time.Duration, job JobFunc) *Entry { return defaultTimer.AddSingleton(interval, job) } // AddOnce is a convenience function for adding a job which only runs once and then exits. -func AddOnce(interval time.Duration, job JobFunc) *Job { +func AddOnce(interval time.Duration, job JobFunc) *Entry { return defaultTimer.AddOnce(interval, job) } // AddTimes is a convenience function for adding a job which is limited running times. -func AddTimes(interval time.Duration, times int, job JobFunc) *Job { +func AddTimes(interval time.Duration, times int, job JobFunc) *Entry { return defaultTimer.AddTimes(interval, times, job) } @@ -120,10 +120,10 @@ func DelayAdd(delay time.Duration, interval time.Duration, job JobFunc) { defaultTimer.DelayAdd(delay, interval, job) } -// DelayAddJob adds a timing job after delay of duration. -// Also see AddJob. -func DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { - defaultTimer.DelayAddJob(delay, interval, job, singleton, times, status) +// DelayAddEntry adds a timing job after delay of duration. +// Also see AddEntry. +func DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { + defaultTimer.DelayAddEntry(delay, interval, job, singleton, times, status) } // DelayAddSingleton adds a timing job after delay of duration. diff --git a/os/gtimer/gtimer_job.go b/os/gtimer/gtimer_entry.go similarity index 62% rename from os/gtimer/gtimer_job.go rename to os/gtimer/gtimer_entry.go index 39fc938ae..1cfca8d62 100644 --- a/os/gtimer/gtimer_job.go +++ b/os/gtimer/gtimer_entry.go @@ -11,8 +11,8 @@ import ( "math" ) -// Job is the timing job. -type Job struct { +// Entry is the timing job. +type Entry struct { job JobFunc // The job function. timer *Timer // Belonged timer. ticks int64 // The job runs every ticks. @@ -26,21 +26,21 @@ type Job struct { type JobFunc = func() // Status returns the status of the job. -func (j *Job) Status() int { - return j.status.Val() +func (entry *Entry) Status() int { + return entry.status.Val() } // Run runs the timer job asynchronously. -func (j *Job) Run() { - leftRunningTimes := j.times.Add(-1) +func (entry *Entry) Run() { + leftRunningTimes := entry.times.Add(-1) if leftRunningTimes < 0 { - j.status.Set(StatusClosed) + entry.status.Set(StatusClosed) return } // This means it does not limit the running times. // I know it's ugly, but it is surely high performance for running times limit. if leftRunningTimes < 2000000000 && leftRunningTimes > 1000000000 { - j.times.Set(math.MaxInt32) + entry.times.Set(math.MaxInt32) } go func() { defer func() { @@ -48,35 +48,35 @@ func (j *Job) Run() { if err != panicExit { panic(err) } else { - j.Close() + entry.Close() return } } - if j.Status() == StatusRunning { - j.SetStatus(StatusReady) + if entry.Status() == StatusRunning { + entry.SetStatus(StatusReady) } }() - j.job() + entry.job() }() } // doCheckAndRunByTicks checks the if job can run in given timer ticks, // it runs asynchronously if the given `currentTimerTicks` meets or else // it increments its ticks and waits for next running check. -func (j *Job) doCheckAndRunByTicks(currentTimerTicks int64) { +func (entry *Entry) doCheckAndRunByTicks(currentTimerTicks int64) { // Ticks check. - if currentTimerTicks < j.nextTicks.Val() { + if currentTimerTicks < entry.nextTicks.Val() { return } - j.nextTicks.Set(currentTimerTicks + j.ticks) + entry.nextTicks.Set(currentTimerTicks + entry.ticks) // Perform job checking. - switch j.status.Val() { + switch entry.status.Val() { case StatusRunning: - if j.IsSingleton() { + if entry.IsSingleton() { return } case StatusReady: - if !j.status.Cas(StatusReady, StatusRunning) { + if !entry.status.Cas(StatusReady, StatusRunning) { return } case StatusStopped: @@ -85,50 +85,50 @@ func (j *Job) doCheckAndRunByTicks(currentTimerTicks int64) { return } // Perform job running. - j.Run() + entry.Run() } // SetStatus custom sets the status for the job. -func (j *Job) SetStatus(status int) int { - return j.status.Set(status) +func (entry *Entry) SetStatus(status int) int { + return entry.status.Set(status) } // Start starts the job. -func (j *Job) Start() { - j.status.Set(StatusReady) +func (entry *Entry) Start() { + entry.status.Set(StatusReady) } // Stop stops the job. -func (j *Job) Stop() { - j.status.Set(StatusStopped) +func (entry *Entry) Stop() { + entry.status.Set(StatusStopped) } // Close closes the job, and then it will be removed from the timer. -func (j *Job) Close() { - j.status.Set(StatusClosed) +func (entry *Entry) Close() { + entry.status.Set(StatusClosed) } // Reset reset the job, which resets its ticks for next running. -func (j *Job) Reset() { - j.nextTicks.Set(j.timer.ticks.Val() + j.ticks) +func (entry *Entry) Reset() { + entry.nextTicks.Set(entry.timer.ticks.Val() + entry.ticks) } // IsSingleton checks and returns whether the job in singleton mode. -func (j *Job) IsSingleton() bool { - return j.singleton.Val() +func (entry *Entry) IsSingleton() bool { + return entry.singleton.Val() } // SetSingleton sets the job singleton mode. -func (j *Job) SetSingleton(enabled bool) { - j.singleton.Set(enabled) +func (entry *Entry) SetSingleton(enabled bool) { + entry.singleton.Set(enabled) } // Job returns the job function of this job. -func (j *Job) Job() JobFunc { - return j.job +func (entry *Entry) Job() JobFunc { + return entry.job } // SetTimes sets the limit running times for the job. -func (j *Job) SetTimes(times int) { - j.times.Set(times) +func (entry *Entry) SetTimes(times int) { + entry.times.Set(times) } diff --git a/os/gtimer/gtimer_timer.go b/os/gtimer/gtimer_timer.go index 00e700d4d..21bf19112 100644 --- a/os/gtimer/gtimer_timer.go +++ b/os/gtimer/gtimer_timer.go @@ -27,11 +27,11 @@ func New(options ...TimerOptions) *Timer { } // Add adds a timing job to the timer, which runs in interval of . -func (t *Timer) Add(interval time.Duration, job JobFunc) *Job { - return t.createJob(interval, job, false, defaultTimes, StatusReady) +func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry { + return t.createEntry(interval, job, false, defaultTimes, StatusReady) } -// AddJob adds a timing job to the timer with detailed parameters. +// AddEntry adds a timing job to the timer with detailed parameters. // // The parameter specifies the running interval of the job. // @@ -42,23 +42,23 @@ func (t *Timer) Add(interval time.Duration, job JobFunc) *Job { // exits if its run times exceeds the . // // The parameter specifies the job status when it's firstly added to the timer. -func (t *Timer) AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { - return t.createJob(interval, job, singleton, times, status) +func (t *Timer) AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { + return t.createEntry(interval, job, singleton, times, status) } // AddSingleton is a convenience function for add singleton mode job. -func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Job { - return t.createJob(interval, job, true, defaultTimes, StatusReady) +func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Entry { + return t.createEntry(interval, job, true, defaultTimes, StatusReady) } // AddOnce is a convenience function for adding a job which only runs once and then exits. -func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Job { - return t.createJob(interval, job, true, 1, StatusReady) +func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Entry { + return t.createEntry(interval, job, true, 1, StatusReady) } // AddTimes is a convenience function for adding a job which is limited running times. -func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Job { - return t.createJob(interval, job, true, times, StatusReady) +func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Entry { + return t.createEntry(interval, job, true, times, StatusReady) } // DelayAdd adds a timing job after delay of duration. @@ -69,11 +69,11 @@ func (t *Timer) DelayAdd(delay time.Duration, interval time.Duration, job JobFun }) } -// DelayAddJob adds a timing job after delay of duration. -// Also see AddJob. -func (t *Timer) DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { +// DelayAddEntry adds a timing job after delay of duration. +// Also see AddEntry. +func (t *Timer) DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) { t.AddOnce(delay, func() { - t.AddJob(interval, job, singleton, times, status) + t.AddEntry(interval, job, singleton, times, status) }) } @@ -116,8 +116,8 @@ func (t *Timer) Close() { t.status.Set(StatusClosed) } -// createJob creates and adds a timing job to the timer. -func (t *Timer) createJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job { +// createEntry creates and adds a timing job to the timer. +func (t *Timer) createEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { if times <= 0 { times = defaultTimes } @@ -130,7 +130,7 @@ func (t *Timer) createJob(interval time.Duration, job JobFunc, singleton bool, t intervalTicksOfJob = 1 } nextTicks := t.ticks.Val() + intervalTicksOfJob - j := &Job{ + entry := &Entry{ job: job, timer: t, ticks: intervalTicksOfJob, @@ -139,6 +139,6 @@ func (t *Timer) createJob(interval time.Duration, job JobFunc, singleton bool, t singleton: gtype.NewBool(singleton), nextTicks: gtype.NewInt64(nextTicks), } - t.queue.Push(j, nextTicks) - return j + t.queue.Push(entry, nextTicks) + return entry } diff --git a/os/gtimer/gtimer_timer_loop.go b/os/gtimer/gtimer_timer_loop.go index d2e9909d7..61e2ee672 100644 --- a/os/gtimer/gtimer_timer_loop.go +++ b/os/gtimer/gtimer_timer_loop.go @@ -50,19 +50,19 @@ func (t *Timer) proceed(currentTimerTicks int64) { if value == nil { break } - job := value.(*Job) + entry := value.(*Entry) // It checks if it meets the ticks requirement. - if jobNextTicks := job.nextTicks.Val(); currentTimerTicks < jobNextTicks { + if jobNextTicks := entry.nextTicks.Val(); currentTimerTicks < jobNextTicks { // It push the job back if current ticks does not meet its running ticks requirement. - t.queue.Push(job, job.nextTicks.Val()) + t.queue.Push(entry, entry.nextTicks.Val()) break } // It checks the job running requirements and then does asynchronous running. - job.doCheckAndRunByTicks(currentTimerTicks) + entry.doCheckAndRunByTicks(currentTimerTicks) // Status check: push back or ignore it. - if job.Status() != StatusClosed { + if entry.Status() != StatusClosed { // It pushes the job back to queue for next running. - t.queue.Push(job, job.nextTicks.Val()) + t.queue.Push(entry, entry.nextTicks.Val()) } } } diff --git a/os/gtimer/gtimer_z_unit_api_test.go b/os/gtimer/gtimer_z_unit_api_test.go index 60fdb0837..debc25d9c 100644 --- a/os/gtimer/gtimer_z_unit_api_test.go +++ b/os/gtimer/gtimer_z_unit_api_test.go @@ -39,10 +39,10 @@ func TestSetInterval(t *testing.T) { }) } -func TestAddJob(t *testing.T) { +func TestAddEntry(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.AddJob(200*time.Millisecond, func() { + gtimer.AddEntry(200*time.Millisecond, func() { array.Append(1) }, false, 2, gtimer.StatusReady) time.Sleep(1100 * time.Millisecond) @@ -86,10 +86,10 @@ func TestDelayAdd(t *testing.T) { }) } -func TestDelayAddJob(t *testing.T) { +func TestDelayAddEntry(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() { + gtimer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() { array.Append(1) }, false, 2, gtimer.StatusReady) time.Sleep(300 * time.Millisecond) diff --git a/os/gtimer/gtimer_z_unit_job_test.go b/os/gtimer/gtimer_z_unit_entry_test.go similarity index 100% rename from os/gtimer/gtimer_z_unit_job_test.go rename to os/gtimer/gtimer_z_unit_entry_test.go diff --git a/os/gtimer/gtimer_z_unit_timer_test.go b/os/gtimer/gtimer_z_unit_timer_test.go index 7be8eeef5..3faf7e908 100644 --- a/os/gtimer/gtimer_z_unit_timer_test.go +++ b/os/gtimer/gtimer_z_unit_timer_test.go @@ -158,7 +158,7 @@ func TestTimer_DelayAddJob(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - timer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() { + timer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() { array.Append(1) }, false, 100, gtimer.StatusReady) time.Sleep(250 * time.Millisecond) From 8acb921ee3cc17ed79a7801f815de49f3d4feda1 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 27 May 2021 13:10:10 +0800 Subject: [PATCH 56/70] improve package gtimer for times limitation --- os/gtimer/gtimer_entry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/gtimer/gtimer_entry.go b/os/gtimer/gtimer_entry.go index 1cfca8d62..beebf21a6 100644 --- a/os/gtimer/gtimer_entry.go +++ b/os/gtimer/gtimer_entry.go @@ -33,13 +33,13 @@ func (entry *Entry) Status() int { // Run runs the timer job asynchronously. func (entry *Entry) Run() { leftRunningTimes := entry.times.Add(-1) + // Running times exceeding checks. if leftRunningTimes < 0 { entry.status.Set(StatusClosed) return } // This means it does not limit the running times. - // I know it's ugly, but it is surely high performance for running times limit. - if leftRunningTimes < 2000000000 && leftRunningTimes > 1000000000 { + if leftRunningTimes == math.MaxInt32-1 { entry.times.Set(math.MaxInt32) } go func() { From 5100e0e8b74e8e867fe21f7e9947c156d8009684 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 27 May 2021 22:18:16 +0800 Subject: [PATCH 57/70] add sub-query feature for orm --- database/gdb/gdb.go | 4 +- database/gdb/gdb_func.go | 35 ++++++- database/gdb/gdb_model.go | 77 ++++++++------ database/gdb/gdb_model_condition.go | 108 ++++++++++++++++++++ database/gdb/gdb_model_select.go | 71 ++++++++----- database/gdb/gdb_model_utility.go | 116 ++-------------------- database/gdb/gdb_z_mysql_subquery_test.go | 66 ++++++++++++ 7 files changed, 305 insertions(+), 172 deletions(-) create mode 100644 database/gdb/gdb_z_mysql_subquery_test.go diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 5b18d5639..726f9ae43 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -274,12 +274,12 @@ var ( // regularFieldNameRegPattern is the regular expression pattern for a string // which is a regular field name of table. - regularFieldNameRegPattern = `^[\w\.\-\_]+$` + regularFieldNameRegPattern = `^[\w\.\-]+$` // regularFieldNameWithoutDotRegPattern is similar to regularFieldNameRegPattern but not allows '.'. // Note that, although some databases allow char '.' in the field name, but it here does not allow '.' // in the field name as it conflicts with "db.table.field" pattern in SOME situations. - regularFieldNameWithoutDotRegPattern = `^[\w\-\_]+$` + regularFieldNameWithoutDotRegPattern = `^[\w\-]+$` // internalCache is the memory cache for internal usage. internalCache = gcache.New() diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 13db19d09..fc2c4d1ca 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -444,14 +444,14 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa return handleArguments(sql, args) } -// formatWhere formats where statement and its arguments. +// formatWhere formats where statement and its arguments for `Where` and `Having` statements. func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) { var ( buffer = bytes.NewBuffer(nil) rv = reflect.ValueOf(where) kind = rv.Kind() ) - if kind == reflect.Ptr { + for kind == reflect.Ptr { rv = rv.Elem() kind = rv.Kind() } @@ -491,7 +491,36 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( } default: - buffer.WriteString(gconv.String(where)) + // Usually a string. + var ( + i = 0 + whereStr = gconv.String(where) + ) + for { + if i >= len(args) { + break + } + // Sub query, which is always used along with a string condition. + if model, ok := args[i].(*Model); ok { + var ( + index = -1 + ) + whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string { + index++ + if i+len(newArgs) == index { + sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(queryTypeNormal, false) + newArgs = append(newArgs, holderArgs...) + // Automatically adding the brackets. + return "(" + sqlWithHolder + ")" + } + return s + }) + args = gutil.SliceDelete(args, i) + continue + } + i++ + } + buffer.WriteString(whereStr) } if buffer.Len() == 0 { diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 45084a106..627051adb 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -9,6 +9,7 @@ package gdb import ( "context" "fmt" + "github.com/gogf/gf/util/gconv" "time" "github.com/gogf/gf/text/gregex" @@ -28,7 +29,7 @@ type Model struct { fieldsEx string // Excluded operation fields, multiple fields joined using char ','. withArray []interface{} // Arguments for With feature. withAll bool // Enable model association operations on all objects that have "with" tag in the struct. - extraArgs []interface{} // Extra custom arguments for sql. + extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. whereHolder []*whereHolder // Condition strings for where operation. groupBy string // Used for "group by" statement. orderBy string // Used for "order by" statement. @@ -57,52 +58,63 @@ type whereHolder struct { } const ( - OPTION_OMITEMPTY = 1 // Deprecated, use OptionOmitEmpty instead. - OPTION_ALLOWEMPTY = 2 // Deprecated, use OptionAllowEmpty instead. - OptionOmitEmpty = 1 - OptionAllowEmpty = 2 - linkTypeMaster = 1 - linkTypeSlave = 2 - whereHolderWhere = 1 - whereHolderAnd = 2 - whereHolderOr = 3 + OptionOmitEmpty = 1 + OptionAllowEmpty = 2 + linkTypeMaster = 1 + linkTypeSlave = 2 + whereHolderWhere = 1 + whereHolderAnd = 2 + whereHolderOr = 3 ) // Table is alias of Core.Model. // See Core.Model. // Deprecated, use Model instead. -func (c *Core) Table(tableNameOrStruct ...interface{}) *Model { - return c.db.Model(tableNameOrStruct...) +func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model { + return c.db.Model(tableNameQueryOrStruct...) } // Model creates and returns a new ORM model from given schema. -// The parameter `tableNameOrStruct` can be more than one table names, and also alias name, like: +// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like: // 1. Model names: // Model("user") // Model("user u") // Model("user, user_detail") // Model("user u, user_detail ud") // 2. Model name with alias: Model("user", "u") -func (c *Core) Model(tableNameOrStruct ...interface{}) *Model { +func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { var ( - tableStr = "" - tableName = "" - tableNames = make([]string, len(tableNameOrStruct)) + tableStr string + tableName string + extraArgs []interface{} + tableNames = make([]string, len(tableNameQueryOrStruct)) ) - for k, v := range tableNameOrStruct { - if s, ok := v.(string); ok { - tableNames[k] = s - } else if tableName = getTableNameFromOrmTag(v); tableName != "" { - tableNames[k] = tableName + // Model creation with sub-query. + if len(tableNameQueryOrStruct) > 1 { + conditionStr := gconv.String(tableNameQueryOrStruct[0]) + if gstr.Contains(conditionStr, "?") { + tableStr, extraArgs = formatWhere( + c.db, conditionStr, tableNameQueryOrStruct[1:], false, + ) } } + // Normal model creation. + if tableStr == "" { + for k, v := range tableNameQueryOrStruct { + if s, ok := v.(string); ok { + tableNames[k] = s + } else if tableName = getTableNameFromOrmTag(v); tableName != "" { + tableNames[k] = tableName + } + } - if len(tableNames) > 1 { - tableStr = fmt.Sprintf( - `%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]), - ) - } else if len(tableNames) == 1 { - tableStr = c.QuotePrefixTableName(tableNames[0]) + if len(tableNames) > 1 { + tableStr = fmt.Sprintf( + `%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]), + ) + } else if len(tableNames) == 1 { + tableStr = c.QuotePrefixTableName(tableNames[0]) + } } return &Model{ db: c.db, @@ -113,6 +125,7 @@ func (c *Core) Model(tableNameOrStruct ...interface{}) *Model { offset: -1, option: OptionAllowEmpty, filter: true, + extraArgs: extraArgs, } } @@ -123,14 +136,14 @@ func (c *Core) With(objects ...interface{}) *Model { // Table is alias of tx.Model. // Deprecated, use Model instead. -func (tx *TX) Table(tableNameOrStruct ...interface{}) *Model { - return tx.Model(tableNameOrStruct...) +func (tx *TX) Table(tableNameQueryOrStruct ...interface{}) *Model { + return tx.Model(tableNameQueryOrStruct...) } // Model acts like Core.Model except it operates on transaction. // See Core.Model. -func (tx *TX) Model(tableNameOrStruct ...interface{}) *Model { - model := tx.db.Model(tableNameOrStruct...) +func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model { + model := tx.db.Model(tableNameQueryOrStruct...) model.db = tx.db model.tx = tx return model diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 641fee5d7..5135bd3a5 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -8,6 +8,7 @@ package gdb import ( "fmt" + "github.com/gogf/gf/util/gconv" "strings" ) @@ -303,3 +304,110 @@ func (m *Model) Page(page, limit int) *Model { func (m *Model) ForPage(page, limit int) *Model { return m.Page(page, limit) } + +// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. +// Note that this function does not change any attribute value of the `m`. +// +// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. +func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { + if len(m.whereHolder) > 0 { + for _, v := range m.whereHolder { + switch v.operator { + case whereHolderWhere: + if conditionWhere == "" { + newWhere, newArgs := formatWhere( + m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, + ) + if len(newWhere) > 0 { + conditionWhere = newWhere + conditionArgs = newArgs + } + continue + } + fallthrough + + case whereHolderAnd: + newWhere, newArgs := formatWhere( + m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, + ) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + + case whereHolderOr: + newWhere, newArgs := formatWhere( + m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, + ) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + } + } + } + // Soft deletion. + softDeletingCondition := m.getConditionForSoftDeleting() + if !m.unscoped && softDeletingCondition != "" { + if conditionWhere == "" { + conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) + } else { + conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) + } + } else { + if conditionWhere != "" { + conditionWhere = " WHERE " + conditionWhere + } + } + // GROUP BY. + if m.groupBy != "" { + conditionExtra += " GROUP BY " + m.groupBy + } + // HAVING. + if len(m.having) > 0 { + havingStr, havingArgs := formatWhere( + m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0, + ) + if len(havingStr) > 0 { + conditionExtra += " HAVING " + havingStr + conditionArgs = append(conditionArgs, havingArgs...) + } + } + // ORDER BY. + if m.orderBy != "" { + conditionExtra += " ORDER BY " + m.orderBy + } + // LIMIT. + if !isCountStatement { + if m.limit != 0 { + if m.start >= 0 { + conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) + } else { + conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit) + } + } else if limit1 { + conditionExtra += " LIMIT 1" + } + + if m.offset >= 0 { + conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset) + } + } + + if m.lockInfo != "" { + conditionExtra += " " + m.lockInfo + } + return +} diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 3536174dc..041cb65e9 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -18,6 +18,11 @@ import ( "github.com/gogf/gf/util/gconv" ) +const ( + queryTypeNormal = "NormalQuery" + queryTypeCount = "CountQuery" +) + // Select is alias of Model.All. // See Model.All. // Deprecated, use All instead. @@ -46,19 +51,8 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) { if len(where) > 0 { return m.Where(where[0], where[1:]...).All() } - conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false) - // DO NOT quote the m.fields where, in case of fields like: - // DISTINCT t.user_id uid - return m.doGetAllBySql( - fmt.Sprintf( - "SELECT %s%s FROM %s%s", - m.distinct, - m.getFieldsFiltered(), - m.tables, - conditionWhere+conditionExtra, - ), - conditionArgs..., - ) + sqlWithHolder, holderArgs := m.getFormattedSqlAndArgs(queryTypeNormal, limit1) + return m.doGetAllBySql(sqlWithHolder, holderArgs...) } // getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will @@ -332,18 +326,10 @@ func (m *Model) Count(where ...interface{}) (int, error) { if len(where) > 0 { return m.Where(where[0], where[1:]...).Count() } - countFields := "COUNT(1)" - if m.fields != "" && m.fields != "*" { - // DO NOT quote the m.fields here, in case of fields like: - // DISTINCT t.user_id uid - countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields) - } - conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true) - s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra) - if len(m.groupBy) > 0 { - s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) - } - list, err := m.doGetAllBySql(s, conditionArgs...) + var ( + sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(queryTypeCount, false) + list, err = m.doGetAllBySql(sqlWithHolder, holderArgs...) + ) if err != nil { return 0, err } @@ -497,7 +483,9 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } } } - result, err = m.db.GetCore().DoGetAll(m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...) + result, err = m.db.GetCore().DoGetAll( + m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)..., + ) // Cache the result. if cacheKey != "" && err == nil { if m.cacheDuration < 0 { @@ -512,3 +500,34 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } return result, err } + +func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) { + switch queryType { + case queryTypeCount: + countFields := "COUNT(1)" + if m.fields != "" && m.fields != "*" { + // DO NOT quote the m.fields here, in case of fields like: + // DISTINCT t.user_id uid + countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields) + } + conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true) + sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra) + if len(m.groupBy) > 0 { + sqlWithHolder = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", sqlWithHolder) + } + return sqlWithHolder, conditionArgs + + default: + conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false) + // DO NOT quote the m.fields where, in case of fields like: + // DISTINCT t.user_id uid + sqlWithHolder = fmt.Sprintf( + "SELECT %s%s FROM %s%s", + m.distinct, + m.getFieldsFiltered(), + m.tables, + conditionWhere+conditionExtra, + ) + return sqlWithHolder, conditionArgs + } +} diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 362219965..bf29b0a9c 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -7,7 +7,6 @@ package gdb import ( - "fmt" "time" "github.com/gogf/gf/container/gset" @@ -15,7 +14,6 @@ import ( "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" ) @@ -24,6 +22,13 @@ import ( // // Also see DriverMysql.TableFields. func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { + charL, charR := m.db.GetChars() + if charL != "" || charR != "" { + table = gstr.Trim(table, charL+charR) + } + if !gregex.IsMatchString(regularFieldNameRegPattern, table) { + return nil, nil + } return m.db.TableFields(m.GetCtx(), m.getLink(false), table, schema...) } @@ -209,113 +214,6 @@ func (m *Model) getPrimaryKey() string { return "" } -// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. -// Note that this function does not change any attribute value of the `m`. -// -// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. -func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { - if len(m.whereHolder) > 0 { - for _, v := range m.whereHolder { - switch v.operator { - case whereHolderWhere: - if conditionWhere == "" { - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, - ) - if len(newWhere) > 0 { - conditionWhere = newWhere - conditionArgs = newArgs - } - continue - } - fallthrough - - case whereHolderAnd: - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, - ) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - - case whereHolderOr: - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, - ) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - } - } - } - // Soft deletion. - softDeletingCondition := m.getConditionForSoftDeleting() - if !m.unscoped && softDeletingCondition != "" { - if conditionWhere == "" { - conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) - } else { - conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) - } - } else { - if conditionWhere != "" { - conditionWhere = " WHERE " + conditionWhere - } - } - // GROUP BY. - if m.groupBy != "" { - conditionExtra += " GROUP BY " + m.groupBy - } - // HAVING. - if len(m.having) > 0 { - havingStr, havingArgs := formatWhere( - m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0, - ) - if len(havingStr) > 0 { - conditionExtra += " HAVING " + havingStr - conditionArgs = append(conditionArgs, havingArgs...) - } - } - // ORDER BY. - if m.orderBy != "" { - conditionExtra += " ORDER BY " + m.orderBy - } - // LIMIT. - if !isCountStatement { - if m.limit != 0 { - if m.start >= 0 { - conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) - } else { - conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit) - } - } else if limit1 { - conditionExtra += " LIMIT 1" - } - - if m.offset >= 0 { - conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset) - } - } - - if m.lockInfo != "" { - conditionExtra += " " + m.lockInfo - } - return -} - // mergeArguments creates and returns new arguments by merging and given `args`. func (m *Model) mergeArguments(args []interface{}) []interface{} { if len(m.extraArgs) > 0 { diff --git a/database/gdb/gdb_z_mysql_subquery_test.go b/database/gdb/gdb_z_mysql_subquery_test.go new file mode 100644 index 000000000..2f292085b --- /dev/null +++ b/database/gdb/gdb_z_mysql_subquery_test.go @@ -0,0 +1,66 @@ +// 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 gdb_test + +import ( + "github.com/gogf/gf/frame/g" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_Model_SubQuery_Where(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Where( + "id in ?", + db.Model(table).Fields("id").Where("id", g.Slice{1, 3, 5}), + ).OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 3) + t.Assert(r[0]["id"], 1) + t.Assert(r[1]["id"], 3) + t.Assert(r[2]["id"], 5) + }) +} + +func Test_Model_SubQuery_Having(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Where( + "id in ?", + db.Model(table).Fields("id").Where("id", g.Slice{1, 3, 5}), + ).Having( + "id > ?", + db.Model(table).Fields("MAX(id)").Where("id", g.Slice{1, 3}), + ).OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 1) + t.Assert(r[0]["id"], 5) + }) +} + +func Test_Model_SubQuery_Model(t *testing.T) { + table := createInitTable() + defer dropTable(table) + db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + subQuery1 := db.Model(table).Where("id", g.Slice{1, 3, 5}) + subQuery2 := db.Model(table).Where("id", g.Slice{5, 7, 9}) + r, err := db.Model("? AS a, ? AS b", subQuery1, subQuery2).Fields("a.id").Where("a.id=b.id").OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 1) + t.Assert(r[0]["id"], 5) + }) +} From b2d3c7d1fd7b6c88db1dfdadd4fb331f6528edbc Mon Sep 17 00:00:00 2001 From: wanna Date: Fri, 28 May 2021 11:34:51 +0800 Subject: [PATCH 58/70] "gvalid length" compatible with only one parameter --- util/gvalid/gvalid_validator_rule_length.go | 2 ++ util/gvalid/gvalid_z_unit_basic_all_test.go | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/util/gvalid/gvalid_validator_rule_length.go b/util/gvalid/gvalid_validator_rule_length.go index 33edfa97a..d692cc0bd 100644 --- a/util/gvalid/gvalid_validator_rule_length.go +++ b/util/gvalid/gvalid_validator_rule_length.go @@ -31,6 +31,8 @@ func (v *Validator) checkLength(value, ruleKey, ruleVal string, customMsgMap map if len(array) > 0 { if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil { min = v + // compatible with only one parameter + max = v } } if len(array) > 1 { diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index fa687880c..7bfd14a30 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -658,6 +658,17 @@ func Test_Length(t *testing.T) { if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } + // only one parameter + rule2 := "length:5" + if m := gvalid.CheckValue(context.TODO(), "1234", rule2, nil); m == nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "12345", rule2, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { + t.Error("长度校验失败") + } } func Test_MinLength(t *testing.T) { From d7b8a2684a0efd718a5e28aec6ca9928090b0a09 Mon Sep 17 00:00:00 2001 From: wanna Date: Sat, 29 May 2021 11:28:26 +0800 Subject: [PATCH 59/70] Revert ""gvalid length" compatible with only one parameter" This reverts commit b2d3c7d1fd7b6c88db1dfdadd4fb331f6528edbc. --- util/gvalid/gvalid_validator_rule_length.go | 2 -- util/gvalid/gvalid_z_unit_basic_all_test.go | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/util/gvalid/gvalid_validator_rule_length.go b/util/gvalid/gvalid_validator_rule_length.go index d692cc0bd..33edfa97a 100644 --- a/util/gvalid/gvalid_validator_rule_length.go +++ b/util/gvalid/gvalid_validator_rule_length.go @@ -31,8 +31,6 @@ func (v *Validator) checkLength(value, ruleKey, ruleVal string, customMsgMap map if len(array) > 0 { if v, err := strconv.Atoi(strings.TrimSpace(array[0])); err == nil { min = v - // compatible with only one parameter - max = v } } if len(array) > 1 { diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 7bfd14a30..fa687880c 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -658,17 +658,6 @@ func Test_Length(t *testing.T) { if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { t.Error("长度校验失败") } - // only one parameter - rule2 := "length:5" - if m := gvalid.CheckValue(context.TODO(), "1234", rule2, nil); m == nil { - t.Error(m) - } - if m := gvalid.CheckValue(context.TODO(), "12345", rule2, nil); m != nil { - t.Error(m) - } - if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { - t.Error("长度校验失败") - } } func Test_MinLength(t *testing.T) { From fa1814ff5405d7ab772662b5b299c240faf0341f Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 11:30:34 +0800 Subject: [PATCH 60/70] add custom rule fucntions feature for package gvalid --- .example/i18n/gi18n/http_view_i18n.go | 2 +- .example/i18n/gi18n/i18n/zh-CN.toml | 4 +- util/gvalid/gvalid_custom_rule.go | 6 +- util/gvalid/gvalid_validator.go | 47 ++++-- util/gvalid/gvalid_validator_check_map.go | 4 +- util/gvalid/gvalid_validator_check_struct.go | 18 ++- ...eck.go => gvalid_validator_check_value.go} | 53 ++++--- util/gvalid/gvalid_z_example_test.go | 33 ++-- util/gvalid/gvalid_z_unit_custom_rule_test.go | 143 ++++++++++++++++-- 9 files changed, 236 insertions(+), 74 deletions(-) rename util/gvalid/{gvalid_validator_check.go => gvalid_validator_check_value.go} (87%) diff --git a/.example/i18n/gi18n/http_view_i18n.go b/.example/i18n/gi18n/http_view_i18n.go index 1f1cc2302..cbf0b6e36 100644 --- a/.example/i18n/gi18n/http_view_i18n.go +++ b/.example/i18n/gi18n/http_view_i18n.go @@ -10,7 +10,7 @@ func main() { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { group.Middleware(func(r *ghttp.Request) { - r.SetCtx(gi18n.WithLanguage(r.Context(), "zh-CN")) + r.SetCtx(gi18n.WithLanguage(r.Context(), r.GetString("lang", "zh-CN"))) r.Middleware.Next() }) group.ALL("/", func(r *ghttp.Request) { diff --git a/.example/i18n/gi18n/i18n/zh-CN.toml b/.example/i18n/gi18n/i18n/zh-CN.toml index 80acf06de..20406d93a 100644 --- a/.example/i18n/gi18n/i18n/zh-CN.toml +++ b/.example/i18n/gi18n/i18n/zh-CN.toml @@ -1 +1,3 @@ -OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" \ No newline at end of file +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +hello = "你好" +world = "世界" \ No newline at end of file diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go index 85e431f58..d1e3589bb 100644 --- a/util/gvalid/gvalid_custom_rule.go +++ b/util/gvalid/gvalid_custom_rule.go @@ -12,9 +12,9 @@ import "context" // The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc. // The parameter `value` specifies the value for this rule to validate. // The parameter `message` specifies the custom error message or configured i18n message for this rule. -// The parameter `params` specifies all the parameters that needs. You can ignore parameter `params` if -// you do not really need it in your custom validation rule. -type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error +// The parameter `data` specifies the `data` which is passed to the Validator. It might be type of map/struct or a nil value. +// You can ignore the parameter `data` if you do not really need it in your custom validation rule. +type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error var ( // customRuleFuncMap stores the custom rule functions. diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index a1592e362..e46c70295 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -13,21 +13,23 @@ import ( // Validator is the validation manager for chaining operations. type Validator struct { - ctx context.Context // Context containing custom context variables. - i18nManager *gi18n.Manager // I18n manager for error message translation. - key string // Single validation key. - value interface{} // Single validation value. - data interface{} // Validation data, which is usually a map. - rules interface{} // Custom validation data. - messages interface{} // Custom validation error messages, which can be string or type of CustomMsg. - useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`. + ctx context.Context // Context containing custom context variables. + i18nManager *gi18n.Manager // I18n manager for error message translation. + key string // Single validation key. + value interface{} // Single validation value. + data interface{} // Validation data, which is usually a map. + rules interface{} // Custom validation data. + messages interface{} // Custom validation error messages, which can be string or type of CustomMsg. + ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator. + useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`. } // New creates and returns a new Validator. func New() *Validator { return &Validator{ - ctx: context.TODO(), // Initialize an empty context. - i18nManager: gi18n.Instance(), // Use default i18n manager. + ctx: context.TODO(), // Initialize an empty context. + i18nManager: gi18n.Instance(), // Use default i18n manager. + ruleFuncMap: make(map[string]RuleFunc), // Custom rule function storing map. } } @@ -77,3 +79,28 @@ func (v *Validator) Messages(messages interface{}) *Validator { newValidator.messages = messages return newValidator } + +// RuleFunc registers one custom rule function to current Validator. +func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator { + newValidator := v.Clone() + newValidator.ruleFuncMap[rule] = f + return newValidator +} + +// RuleFuncMap registers multiple custom rule functions to current Validator. +func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator { + newValidator := v.Clone() + for k, v := range m { + newValidator.ruleFuncMap[k] = v + } + return newValidator +} + +// getRuleFunc retrieves and returns the custom rule function for specified rule. +func (v *Validator) getRuleFunc(rule string) RuleFunc { + ruleFunc := v.ruleFuncMap[rule] + if ruleFunc == nil { + ruleFunc = customRuleFuncMap[rule] + } + return ruleFunc +} diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index cefb987ca..e7e88d67e 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -96,7 +96,7 @@ func (v *Validator) doCheckMap(params interface{}) Error { value = v } // It checks each rule and its value in loop. - if e := v.doCheckValue(key, value, rule, customMsgs[key], data); e != nil { + if e := v.doCheckValue(key, value, rule, customMsgs[key], params, data); e != nil { _, item := e.FirstItem() // =========================================================== // Only in map and struct validations, if value is nil or empty @@ -112,7 +112,7 @@ func (v *Validator) doCheckMap(params interface{}) Error { break } // Custom rules are also required in default. - if _, ok := customRuleFuncMap[k]; ok { + if f := v.getRuleFunc(k); f != nil { required = true break } diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 55e0d7c33..2c921568b 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -57,12 +57,16 @@ func (v *Validator) doCheckStruct(object interface{}) Error { } var ( - inputParamMap map[string]interface{} - checkRules = make(map[string]string) - customMessage = make(CustomMsg) - fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names. - errorRules = make([]string, 0) // Sequence rules. + inputParamMap map[string]interface{} + checkRules = make(map[string]string) + customMessage = make(CustomMsg) + checkValueData = v.data + fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names. + errorRules = make([]string, 0) // Sequence rules. ) + if checkValueData == nil { + checkValueData = object + } switch v := v.rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. @@ -194,7 +198,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { for key, rule := range checkRules { _, value = gutil.MapPossibleItemByKey(inputParamMap, key) // It checks each rule and its value in loop. - if e := v.doCheckValue(key, value, rule, customMessage[key], inputParamMap); e != nil { + if e := v.doCheckValue(key, value, rule, customMessage[key], checkValueData, inputParamMap); e != nil { _, item := e.FirstItem() // =================================================================== // Only in map and struct validations, if value is nil or empty string @@ -210,7 +214,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { break } // Custom rules are also required in default. - if _, ok := customRuleFuncMap[k]; ok { + if f := v.getRuleFunc(k); f != nil { required = true break } diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check_value.go similarity index 87% rename from util/gvalid/gvalid_validator_check.go rename to util/gvalid/gvalid_validator_check_value.go index 9c01f21c2..1a6493c3e 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -29,11 +29,24 @@ type apiTime interface { // CheckValue checks single value with specified rules. // It returns nil if successful validation. func (v *Validator) CheckValue(value interface{}) Error { - return v.doCheckValue("", value, gconv.String(v.rules), v.messages, v.data) + return v.doCheckValue("", value, gconv.String(v.rules), v.messages, v.data, gconv.Map(v.data)) } // doCheckSingleValue does the really rules validation for single key-value. -func (v *Validator) doCheckValue(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error { +// +// The parameter `rules` specifies the validation rules string, like "required", "required|between:1,100", etc. +// The parameter `value` specifies the value for this rules to be validated. +// The parameter `messages` specifies the custom error messages for this rule, which is usually type of map/slice. +// The parameter `dataRaw` specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value. +// The parameter `dataMap` specifies the map that is converted from `dataRaw`. It is usually used internally +func (v *Validator) doCheckValue( + key string, + value interface{}, + rules string, + messages interface{}, + dataRaw interface{}, + dataMap map[string]interface{}, +) Error { // If there's no validation rules, it does nothing and returns quickly. if rules == "" { return nil @@ -41,12 +54,8 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me // It converts value to string and then does the validation. var ( // Do not trim it as the space is also part of the value. - data = make(map[string]interface{}) errorMsgArray = make(map[string]string) ) - if len(paramMap) > 0 && paramMap[0] != nil { - data = gconv.Map(paramMap[0]) - } // Custom error messages handling. var ( msgArray = make([]string, 0) @@ -66,7 +75,7 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me for i := 0; ; { array := strings.Split(ruleItems[i], ":") _, ok := allSupportedRules[array[0]] - if !ok && customRuleFuncMap[array[0]] == nil { + if !ok && v.getRuleFunc(array[0]) == nil { if i > 0 && ruleItems[i-1][:5] == "regex" { ruleItems[i-1] += "|" + ruleItems[i] ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) @@ -85,26 +94,26 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me } for index := 0; index < len(ruleItems); { var ( - err error - match = false - results = ruleRegex.FindStringSubmatch(ruleItems[index]) - ruleKey = strings.TrimSpace(results[1]) - rulePattern = strings.TrimSpace(results[2]) + err error + match = false + results = ruleRegex.FindStringSubmatch(ruleItems[index]) + ruleKey = strings.TrimSpace(results[1]) + rulePattern = strings.TrimSpace(results[2]) + customRuleFunc RuleFunc ) if len(msgArray) > index { customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index]) } - if f, ok := customRuleFuncMap[ruleKey]; ok { + // Custom rule handling. + // 1. It firstly checks and uses the custom registered rules functions in the current Validator. + // 2. It secondly checks and uses the globally registered rules functions. + // 3. It finally checks and uses the build-in rules functions. + customRuleFunc = v.getRuleFunc(ruleKey) + if customRuleFunc != nil { // It checks custom validation rules with most priority. - var ( - dataMap map[string]interface{} - message = v.getErrorMessageByRule(ruleKey, customMsgMap) - ) - if len(paramMap) > 0 && paramMap[0] != nil { - dataMap = gconv.Map(paramMap[0]) - } - if err := f(v.ctx, ruleItems[index], value, message, dataMap); err != nil { + message := v.getErrorMessageByRule(ruleKey, customMsgMap) + if err := customRuleFunc(v.ctx, ruleItems[index], value, message, dataRaw); err != nil { match = false errorMsgArray[ruleKey] = err.Error() } else { @@ -112,7 +121,7 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me } } else { // It checks build-in validation rules if there's no custom rule. - match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap) + match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, dataMap, customMsgMap) if !match && err != nil { errorMsgArray[ruleKey] = err.Error() } diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index 423957391..d11fb4e9f 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -115,21 +115,6 @@ func ExampleCheckStruct3() { } func ExampleRegisterRule() { - rule := "unique-name" - gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { - var ( - id = gconv.Int(params["Id"]) - name = gconv.String(value) - ) - n, err := g.Table("user").Where("id != ? and name = ?", id, name).Count() - if err != nil { - return err - } - if n > 0 { - return errors.New(message) - } - return nil - }) type User struct { Id int Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"` @@ -140,6 +125,22 @@ func ExampleRegisterRule() { Name: "john", Pass: "123456", } + + rule := "unique-name" + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + var ( + id = data.(*User).Id + name = gconv.String(value) + ) + n, err := g.Model("user").Where("id != ? and name = ?", id, name).Count() + if err != nil { + return err + } + if n > 0 { + return errors.New(message) + } + return nil + }) err := gvalid.CheckStruct(context.TODO(), user, nil) fmt.Println(err.Error()) // May Output: @@ -148,7 +149,7 @@ func ExampleRegisterRule() { func ExampleRegisterRule_OverwriteRequired() { rule := "required" - gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { reflectValue := reflect.ValueOf(value) if reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go index c9493c861..0a4e694b4 100644 --- a/util/gvalid/gvalid_z_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -20,16 +20,20 @@ import ( func Test_CustomRule1(t *testing.T) { rule := "custom" - err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { - pass := gconv.String(value) - if len(pass) != 6 { - return errors.New(message) - } - if params["data"] != pass { - return errors.New(message) - } - return nil - }) + err := gvalid.RegisterRule( + rule, + func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + m := gconv.Map(data) + if m["data"] != pass { + return errors.New(message) + } + return nil + }, + ) gtest.Assert(err, nil) gtest.C(t, func(t *gtest.T) { err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message") @@ -67,7 +71,7 @@ func Test_CustomRule1(t *testing.T) { func Test_CustomRule2(t *testing.T) { rule := "required-map" - err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { m := gconv.Map(value) if len(m) == 0 { return errors.New(message) @@ -111,7 +115,7 @@ func Test_CustomRule2(t *testing.T) { func Test_CustomRule_AllowEmpty(t *testing.T) { rule := "allow-empty-str" - err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error { + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { s := gconv.String(value) if len(s) == 0 || s == "gf" { return nil @@ -153,3 +157,118 @@ func Test_CustomRule_AllowEmpty(t *testing.T) { t.Assert(err.String(), "自定义错误") }) } + +func TestValidator_RuleFunc(t *testing.T) { + ruleName := "custom_1" + ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + if m := gconv.Map(data); m["data"] != pass { + return errors.New(message) + } + return nil + } + gtest.C(t, func(t *gtest.T) { + err := g.Validator().Rules(ruleName).Messages("custom message").RuleFunc(ruleName, ruleFunc).CheckValue("123456") + t.Assert(err.String(), "custom message") + err = g.Validator(). + Rules(ruleName). + Messages("custom message"). + Data(g.Map{"data": "123456"}). + RuleFunc(ruleName, ruleFunc). + CheckValue("123456") + t.AssertNil(err) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123", + Data: "123456", + } + err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123456", + Data: "123456", + } + err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st) + t.AssertNil(err) + }) +} + +func TestValidator_RuleFuncMap(t *testing.T) { + ruleName := "custom_1" + ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + if m := gconv.Map(data); m["data"] != pass { + return errors.New(message) + } + return nil + } + gtest.C(t, func(t *gtest.T) { + err := g.Validator(). + Rules(ruleName). + Messages("custom message"). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckValue("123456") + t.Assert(err.String(), "custom message") + err = g.Validator(). + Rules(ruleName). + Messages("custom message"). + Data(g.Map{"data": "123456"}). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }). + CheckValue("123456") + t.AssertNil(err) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123", + Data: "123456", + } + err := g.Validator(). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckStruct(st) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123456", + Data: "123456", + } + err := g.Validator(). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckStruct(st) + t.AssertNil(err) + }) +} From 7bfd48e2ab7cde5f79ee3fcc55fd83fbc6506d82 Mon Sep 17 00:00:00 2001 From: wanna Date: Sat, 29 May 2021 12:15:37 +0800 Subject: [PATCH 61/70] valid field length --- .example/util/gvalid/gvalid.go | 3 +++ .example/util/gvalid/gvalid_i18n.go | 8 +++++--- .example/util/gvalid/i18n/en.toml | 1 + .example/util/gvalid/i18n/zh-CN.toml | 1 + util/gvalid/gvalid.go | 3 +++ util/gvalid/gvalid_validator_check.go | 3 ++- util/gvalid/gvalid_validator_rule_length.go | 7 +++++++ util/gvalid/gvalid_z_unit_basic_all_test.go | 10 ++++++++++ util/gvalid/i18n/cn/validation.toml | 1 + util/gvalid/i18n/en/validation.toml | 1 + util/gvalid/testdata/i18n/cn/validation.toml | 1 + util/gvalid/testdata/i18n/en/validation.toml | 1 + 12 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.example/util/gvalid/gvalid.go b/.example/util/gvalid/gvalid.go index a21b63f77..4d6212876 100644 --- a/.example/util/gvalid/gvalid.go +++ b/.example/util/gvalid/gvalid.go @@ -48,11 +48,13 @@ func main() { "passport": "john", "password": "123456", "password2": "1234567", + "name": "gf", } rules := map[string]string{ "passport": "required|length:6,16", "password": "required|length:6,16|same:password2", "password2": "required|length:6,16", + "name": "size:5", } msgs := map[string]interface{}{ "passport": "账号不能为空|账号长度应当在:min到:max之间", @@ -60,6 +62,7 @@ func main() { "required": "密码不能为空", "same": "两次密码输入不相等", }, + "name": "名字长度必须为:size", } if e := gvalid.CheckMap(context.TODO(), params, rules, msgs); e != nil { g.Dump(e.Maps()) diff --git a/.example/util/gvalid/gvalid_i18n.go b/.example/util/gvalid/gvalid_i18n.go index dccde019c..cb2a5e905 100644 --- a/.example/util/gvalid/gvalid_i18n.go +++ b/.example/util/gvalid/gvalid_i18n.go @@ -10,12 +10,14 @@ import ( func main() { type User struct { - Name string `v:"required#ReuiredUserName"` - Type int `v:"required#ReuiredUserType"` + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + Project string `v:"size:10#MustSize"` } var ( data = g.Map{ - "name": "john", + "name": "john", + "project": "gf", } user = User{} ctxEn = gi18n.WithLanguage(context.TODO(), "en") diff --git a/.example/util/gvalid/i18n/en.toml b/.example/util/gvalid/i18n/en.toml index bcca1267d..7d1f481c8 100644 --- a/.example/util/gvalid/i18n/en.toml +++ b/.example/util/gvalid/i18n/en.toml @@ -5,6 +5,7 @@ "ReuiredUserName" = "Please input user name" "ReuiredUserType" = "Please select user type" +"MustSize" = "Size of :attribute must be :size" diff --git a/.example/util/gvalid/i18n/zh-CN.toml b/.example/util/gvalid/i18n/zh-CN.toml index 750787e56..3db393e20 100644 --- a/.example/util/gvalid/i18n/zh-CN.toml +++ b/.example/util/gvalid/i18n/zh-CN.toml @@ -4,6 +4,7 @@ "ReuiredUserName" = "请输入用户名称" "ReuiredUserType" = "请选择用户类型" +"MustSize" = ":attribute长度必须为:size" diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 275b17771..7d8155136 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -47,6 +47,7 @@ import ( // length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. +// siz format: size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // between format: between:min,max brief: Range between :min and :max. It supports both integer and float. // min format: min:min brief: Equal or greater than :min. It supports both integer and float. // max format: max:max brief: Equal or lesser than :max. It supports both integer and float. @@ -143,6 +144,7 @@ var ( "length": {}, "min-length": {}, "max-length": {}, + "size": {}, "between": {}, "min": {}, "max": {}, @@ -200,6 +202,7 @@ var ( "length": "The :attribute value length must be between :min and :max", "min-length": "The :attribute value length must be equal or greater than :min", "max-length": "The :attribute value length must be equal or lesser than :max", + "size": "The :attribute value length must be :size", "between": "The :attribute value must be between :min and :max", "min": "The :attribute value must be equal or greater than :min", "max": "The :attribute value must be equal or lesser than :max", diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index 9c01f21c2..089f95044 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -163,7 +163,8 @@ func (v *Validator) doCheckBuildInRules( case "length", "min-length", - "max-length": + "max-length", + "size": if msg := v.checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { return match, errors.New(msg) } else { diff --git a/util/gvalid/gvalid_validator_rule_length.go b/util/gvalid/gvalid_validator_rule_length.go index 33edfa97a..7bd5b188e 100644 --- a/util/gvalid/gvalid_validator_rule_length.go +++ b/util/gvalid/gvalid_validator_rule_length.go @@ -58,6 +58,13 @@ func (v *Validator) checkLength(value, ruleKey, ruleVal string, customMsgMap map msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) } + + case "size": + size, err := strconv.Atoi(ruleVal) + if valueLen != size || err != nil { + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) + msg = strings.Replace(msg, ":size", strconv.Itoa(size), -1) + } } return msg } diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index fa687880c..75f22609b 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -702,6 +702,16 @@ func Test_MaxLength(t *testing.T) { } } +func Test_Size(t *testing.T) { + rule := "size:5" + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m == nil { + t.Error("长度校验失败") + } +} + func Test_Between(t *testing.T) { rule := "between:6.01, 10.01" if m := gvalid.CheckValue(context.TODO(), 10, rule, nil); m != nil { diff --git a/util/gvalid/i18n/cn/validation.toml b/util/gvalid/i18n/cn/validation.toml index fab6dfba6..fb6c7c787 100644 --- a/util/gvalid/i18n/cn/validation.toml +++ b/util/gvalid/i18n/cn/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符" "gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min" "gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max" +"gf.gvalid.rule.size" = ":attribute 字段长度必须为:size" "gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max" "gf.gvalid.rule.min" = ":attribute 字段最小值为:min" "gf.gvalid.rule.max" = ":attribute 字段最大值为:max" diff --git a/util/gvalid/i18n/en/validation.toml b/util/gvalid/i18n/en/validation.toml index 7dc56cab0..4de5880d2 100644 --- a/util/gvalid/i18n/en/validation.toml +++ b/util/gvalid/i18n/en/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max" "gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min" "gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max" +"gf.gvalid.rule.size" = "The :attribute value length must be :size" "gf.gvalid.rule.between" = "The :attribute value must be between :min and :max" "gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min" "gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max" diff --git a/util/gvalid/testdata/i18n/cn/validation.toml b/util/gvalid/testdata/i18n/cn/validation.toml index 0cc409eb5..531aaa2ca 100644 --- a/util/gvalid/testdata/i18n/cn/validation.toml +++ b/util/gvalid/testdata/i18n/cn/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符" "gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min" "gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max" +"gf.gvalid.rule.size" = ":attribute 字段长度为必须为:size" "gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max" "gf.gvalid.rule.min" = ":attribute 字段最小值为:min" "gf.gvalid.rule.max" = ":attribute 字段最大值为:max" diff --git a/util/gvalid/testdata/i18n/en/validation.toml b/util/gvalid/testdata/i18n/en/validation.toml index 7dc56cab0..4de5880d2 100644 --- a/util/gvalid/testdata/i18n/en/validation.toml +++ b/util/gvalid/testdata/i18n/en/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max" "gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min" "gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max" +"gf.gvalid.rule.size" = "The :attribute value length must be :size" "gf.gvalid.rule.between" = "The :attribute value must be between :min and :max" "gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min" "gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max" From c1db01425ea2f5a288ea61ecfa7fa455e9a7ae65 Mon Sep 17 00:00:00 2001 From: wanna Date: Sat, 29 May 2021 12:25:50 +0800 Subject: [PATCH 62/70] fix remarks --- util/gvalid/gvalid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 7d8155136..cac7abe74 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -47,7 +47,7 @@ import ( // length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. -// siz format: size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. +// size format: size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // between format: between:min,max brief: Range between :min and :max. It supports both integer and float. // min format: min:min brief: Equal or greater than :min. It supports both integer and float. // max format: max:max brief: Equal or lesser than :max. It supports both integer and float. From 1441ce7f5f4253f7c41e93ff5c00ef6fe4900754 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 15:47:07 +0800 Subject: [PATCH 63/70] add examples for package gproc --- .example/os/gproc/signal/signal_handler.go | 54 +++++++++++++++++++ .../os/gproc/signal/signal_handler_gproc.go | 27 ++++++++++ frame/g/g_object.go | 7 --- os/gproc/gproc_signal.go | 8 +-- 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 .example/os/gproc/signal/signal_handler.go create mode 100644 .example/os/gproc/signal/signal_handler_gproc.go diff --git a/.example/os/gproc/signal/signal_handler.go b/.example/os/gproc/signal/signal_handler.go new file mode 100644 index 000000000..a1d495347 --- /dev/null +++ b/.example/os/gproc/signal/signal_handler.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +func signalHandlerForMQ() { + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") + return + } +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + go signalHandlerForMQ() + + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) + return + } +} diff --git a/.example/os/gproc/signal/signal_handler_gproc.go b/.example/os/gproc/signal/signal_handler_gproc.go new file mode 100644 index 000000000..e8e473484 --- /dev/null +++ b/.example/os/gproc/signal/signal_handler_gproc.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/os/gproc" + "os" + "time" +) + +func signalHandlerForMQ(sig os.Signal) { + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") +} + +func signalHandlerForMain(sig os.Signal) { + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + gproc.AddSigHandlerShutdown( + signalHandlerForMQ, + signalHandlerForMain, + ) + gproc.Listen() +} diff --git a/frame/g/g_object.go b/frame/g/g_object.go index d3dfb888c..571d47247 100644 --- a/frame/g/g_object.go +++ b/frame/g/g_object.go @@ -81,13 +81,6 @@ func Log(name ...string) *glog.Logger { return gins.Log(name...) } -// Database is alias of DB. -// See DB. -// Deprecated, use DB instead. -func Database(name ...string) gdb.DB { - return gins.Database(name...) -} - // DB returns an instance of database ORM object with specified configuration group name. func DB(name ...string) gdb.DB { return gins.Database(name...) diff --git a/os/gproc/gproc_signal.go b/os/gproc/gproc_signal.go index 6a2b9b700..08d059e00 100644 --- a/os/gproc/gproc_signal.go +++ b/os/gproc/gproc_signal.go @@ -47,9 +47,11 @@ func AddSigHandler(handler SigHandler, signals ...os.Signal) { // syscall.SIGKILL, // syscall.SIGTERM, // syscall.SIGABRT. -func AddSigHandlerShutdown(handler SigHandler) { - for sig, _ := range shutdownSignalMap { - signalHandlerMap[sig] = append(signalHandlerMap[sig], handler) +func AddSigHandlerShutdown(handler ...SigHandler) { + for _, h := range handler { + for sig, _ := range shutdownSignalMap { + signalHandlerMap[sig] = append(signalHandlerMap[sig], h) + } } } From d9bc8b05e1883a1c5ac73ced7af62c3f4fd79570 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 16:45:08 +0800 Subject: [PATCH 64/70] add example for package gvalid --- .example/util/gvalid/config.toml | 14 ++++++++ .example/util/gvalid/gvalid_struct_meta.go | 40 ++++++++++++++++++++++ .example/util/gvalid/i18n/en.toml | 9 ----- 3 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 .example/util/gvalid/config.toml create mode 100644 .example/util/gvalid/gvalid_struct_meta.go diff --git a/.example/util/gvalid/config.toml b/.example/util/gvalid/config.toml new file mode 100644 index 000000000..26beac3a7 --- /dev/null +++ b/.example/util/gvalid/config.toml @@ -0,0 +1,14 @@ + + + +# MySQL. +[database] + [database.default] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug = true + + + + + + diff --git a/.example/util/gvalid/gvalid_struct_meta.go b/.example/util/gvalid/gvalid_struct_meta.go new file mode 100644 index 000000000..a08e8d8ca --- /dev/null +++ b/.example/util/gvalid/gvalid_struct_meta.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +type UserCreateReq struct { + gmeta.Meta `v:"UserCreateReq"` + Name string + Pass string +} + +func UserCreateReqChecker(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + user := &UserCreateReq{} + if v, ok := data.(*UserCreateReq); ok { + user = v + } + // SELECT COUNT(*) FROM `user` WHERE `name` = xxx + count, err := g.Model("user").Ctx(ctx).Where("name", user.Name).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.Newf(`The name "%s" is already token`, user.Name) + } + return nil +} + +func main() { + user := &UserCreateReq{ + Name: "john", + Pass: "123456", + } + err := g.Validator().RuleFunc("UserCreateReq", UserCreateReqChecker).CheckStruct(user) + fmt.Println(err) +} diff --git a/.example/util/gvalid/i18n/en.toml b/.example/util/gvalid/i18n/en.toml index bcca1267d..8aee475de 100644 --- a/.example/util/gvalid/i18n/en.toml +++ b/.example/util/gvalid/i18n/en.toml @@ -1,15 +1,6 @@ - - "gf.gvalid.required" = "字段不能为空" "ReuiredUserName" = "Please input user name" "ReuiredUserType" = "Please select user type" - - - - - - - From 578e7d634bbc92ad17d8ae74557473b50ddaf3ff Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 16:47:39 +0800 Subject: [PATCH 65/70] comment updates for package gvalid --- util/gvalid/gvalid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index cac7abe74..5b390a125 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -47,7 +47,7 @@ import ( // length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. -// size format: size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. +// size format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // between format: between:min,max brief: Range between :min and :max. It supports both integer and float. // min format: min:min brief: Equal or greater than :min. It supports both integer and float. // max format: max:max brief: Equal or lesser than :max. It supports both integer and float. From fe142c93fdfc25c3cd076ab03c0a625e7d2510dd Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 17:07:49 +0800 Subject: [PATCH 66/70] add function Transaction for gdb.Model --- database/gdb/gdb_model_transaction.go | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/gdb/gdb_model_transaction.go diff --git a/database/gdb/gdb_model_transaction.go b/database/gdb/gdb_model_transaction.go new file mode 100644 index 000000000..6aaf933bf --- /dev/null +++ b/database/gdb/gdb_model_transaction.go @@ -0,0 +1,28 @@ +// 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 gdb + +import ( + "context" +) + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (m *Model) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + if ctx == nil { + ctx = m.GetCtx() + } + if m.tx != nil { + return m.tx.Transaction(ctx, f) + } + return m.db.Transaction(ctx, f) +} From 392c81ad691997319bdb71f75dcf6e2584a59eca Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 29 May 2021 17:26:08 +0800 Subject: [PATCH 67/70] remove Link parameter from function TableFields for package gdb --- database/gdb/gdb.go | 12 ++++++------ database/gdb/gdb_core_structure.go | 2 +- database/gdb/gdb_driver_mssql.go | 12 +++++------- database/gdb/gdb_driver_mysql.go | 16 ++++++---------- database/gdb/gdb_driver_oracle.go | 12 +++++------- database/gdb/gdb_driver_pgsql.go | 12 +++++------- database/gdb/gdb_driver_sqlite.go | 12 +++++------- database/gdb/gdb_model_utility.go | 2 +- database/gdb/gdb_z_mysql_subquery_test.go | 2 +- 9 files changed, 35 insertions(+), 47 deletions(-) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 726f9ae43..d543bdb11 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -154,12 +154,12 @@ type DB interface { // Utility methods. // =========================================================================== - GetCtx() context.Context // See Core.GetCtx. - GetCore() *Core // See Core.GetCore - GetChars() (charLeft string, charRight string) // See Core.GetChars. - Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables. - TableFields(ctx context.Context, link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. - FilteredLinkInfo() string // See Core.FilteredLinkInfo. + GetCtx() context.Context // See Core.GetCtx. + GetCore() *Core // See Core.GetCore + GetChars() (charLeft string, charRight string) // See Core.GetChars. + Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables. + TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. + FilteredLinkInfo() string // See Core.FilteredLinkInfo. // HandleSqlBeforeCommit is a hook function, which deals with the sql string before // it's committed to underlying driver. The parameter `link` specifies the current diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index be34cf062..e7aa76b67 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -151,7 +151,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s // mappingAndFilterData automatically mappings the map key to table field and removes // all key-value pairs that are not the field of given table. func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) { - if fieldsMap, err := c.db.TableFields(c.GetCtx(), nil, table, schema); err == nil { + if fieldsMap, err := c.db.TableFields(c.GetCtx(), table, schema); err == nil { fieldsKeyMap := make(map[string]interface{}, len(fieldsMap)) for k, _ := range fieldsMap { fieldsKeyMap[k] = nil diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index d0bb93cfb..33d3f8732 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -207,7 +207,7 @@ func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []st // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverMssql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -223,13 +223,11 @@ func (d *DriverMssql) TableFields(ctx context.Context, link Link, table string, ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( - result Result - ) - if link == nil { + result Result link, err = d.SlaveLink(useSchema) - if err != nil { - return nil - } + ) + if err != nil { + return nil } structureSql := fmt.Sprintf(` SELECT diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index 724b353ec..d39078c75 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -113,7 +113,7 @@ func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []st // // It's using cache feature to enhance the performance, which is never expired util the // process restarts. -func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -129,20 +129,16 @@ func (d *DriverMysql) TableFields(ctx context.Context, link Link, table string, ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( - result Result - ) - if link == nil { + result Result link, err = d.SlaveLink(useSchema) - if err != nil { - return nil - } - } - result, err = d.DoGetAll(ctx, link, - fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)), ) if err != nil { return nil } + result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table))) + if err != nil { + return nil + } fields = make(map[string]*TableField) for i, m := range result { fields[m["Field"].String()] = &TableField{ diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index be356f176..9d8543936 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -183,7 +183,7 @@ func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []s // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverOracle) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -200,6 +200,7 @@ func (d *DriverOracle) TableFields(ctx context.Context, link Link, table string, v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( result Result + link, err = d.SlaveLink(useSchema) structureSql = fmt.Sprintf(` SELECT COLUMN_NAME AS FIELD, @@ -211,13 +212,10 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table), ) ) - structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) - if link == nil { - link, err = d.SlaveLink(useSchema) - if err != nil { - return nil - } + if err != nil { + return nil } + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) result, err = d.DoGetAll(ctx, link, structureSql) if err != nil { return nil diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index 8fb6ad243..b0695f8c0 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -115,7 +115,7 @@ func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []st // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -133,6 +133,7 @@ func (d *DriverPgsql) TableFields(ctx context.Context, link Link, table string, v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( result Result + link, err = d.SlaveLink(useSchema) structureSql = fmt.Sprintf(` SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t @@ -141,13 +142,10 @@ ORDER BY a.attnum`, strings.ToLower(table), ) ) - structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) - if link == nil { - link, err = d.SlaveLink(useSchema) - if err != nil { - return nil - } + if err != nil { + return nil } + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) result, err = d.DoGetAll(ctx, link, structureSql) if err != nil { return nil diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index 69f4a42a5..99ae55fcf 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -97,7 +97,7 @@ func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []s // TableFields retrieves and returns the fields information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -113,13 +113,11 @@ func (d *DriverSqlite) TableFields(ctx context.Context, link Link, table string, ) v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { var ( - result Result - ) - if link == nil { + result Result link, err = d.SlaveLink(useSchema) - if err != nil { - return nil - } + ) + if err != nil { + return nil } result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) if err != nil { diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index bf29b0a9c..7b1c36b60 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -29,7 +29,7 @@ func (m *Model) TableFields(table string, schema ...string) (fields map[string]* if !gregex.IsMatchString(regularFieldNameRegPattern, table) { return nil, nil } - return m.db.TableFields(m.GetCtx(), m.getLink(false), table, schema...) + return m.db.TableFields(m.GetCtx(), table, schema...) } // getModel creates and returns a cloned model of current model if `safe` is true, or else it returns diff --git a/database/gdb/gdb_z_mysql_subquery_test.go b/database/gdb/gdb_z_mysql_subquery_test.go index 2f292085b..6cb76abf2 100644 --- a/database/gdb/gdb_z_mysql_subquery_test.go +++ b/database/gdb/gdb_z_mysql_subquery_test.go @@ -53,7 +53,7 @@ func Test_Model_SubQuery_Having(t *testing.T) { func Test_Model_SubQuery_Model(t *testing.T) { table := createInitTable() defer dropTable(table) - db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { subQuery1 := db.Model(table).Where("id", g.Slice{1, 3, 5}) subQuery2 := db.Model(table).Where("id", g.Slice{5, 7, 9}) From 3e6b9864d52ffade73169b5d76761c8b179b545e Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 1 Jun 2021 19:47:02 +0800 Subject: [PATCH 68/70] fix issue #1256 --- util/gconv/gconv_interface.go | 7 +++++++ util/gconv/gconv_time.go | 9 +++++++++ util/gconv/gconv_z_unit_all_test.go | 7 ------- util/gconv/gconv_z_unit_time_test.go | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/util/gconv/gconv_interface.go b/util/gconv/gconv_interface.go index 8ddbe7c3b..cce763cc0 100644 --- a/util/gconv/gconv_interface.go +++ b/util/gconv/gconv_interface.go @@ -6,6 +6,8 @@ package gconv +import "github.com/gogf/gf/os/gtime" + // apiString is used for type assert api for String(). type apiString interface { String() string @@ -92,3 +94,8 @@ type apiUnmarshalText interface { type apiSet interface { Set(value interface{}) (old interface{}) } + +// apiGTime is the interface for gtime.Time converting. +type apiGTime interface { + GTime(format ...string) *gtime.Time +} diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index 98aac418d..20d9b77b7 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -51,11 +51,20 @@ func GTime(any interface{}, format ...string) *gtime.Time { if any == nil { return nil } + if v, ok := any.(apiGTime); ok { + return v.GTime(format...) + } // It's already this type. if len(format) == 0 { if v, ok := any.(*gtime.Time); ok { return v } + if t, ok := any.(time.Time); ok { + return gtime.New(t) + } + if t, ok := any.(*time.Time); ok { + return gtime.New(t) + } } s := String(any) if len(s) == 0 { diff --git a/util/gconv/gconv_z_unit_all_test.go b/util/gconv/gconv_z_unit_all_test.go index 2441e8e6b..a358c2362 100644 --- a/util/gconv/gconv_z_unit_all_test.go +++ b/util/gconv/gconv_z_unit_all_test.go @@ -647,13 +647,6 @@ func Test_Convert_All(t *testing.T) { }) } -func Test_Time_All(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - t.AssertEQ(gconv.Duration(""), time.Duration(int64(0))) - t.AssertEQ(gconv.GTime(""), gtime.New()) - }) -} - func Test_Slice_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { value := 123.456 diff --git a/util/gconv/gconv_z_unit_time_test.go b/util/gconv/gconv_z_unit_time_test.go index 99d3e0115..f44acc035 100644 --- a/util/gconv/gconv_z_unit_time_test.go +++ b/util/gconv/gconv_z_unit_time_test.go @@ -7,6 +7,7 @@ package gconv_test import ( + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "testing" "time" @@ -17,6 +18,11 @@ import ( ) func Test_Time(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.AssertEQ(gconv.Duration(""), time.Duration(int64(0))) + t.AssertEQ(gconv.GTime(""), gtime.New()) + }) + gtest.C(t, func(t *gtest.T) { s := "2011-10-10 01:02:03.456" t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s)) @@ -42,6 +48,15 @@ func Test_Time(t *testing.T) { t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s)) t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time) }) + gtest.C(t, func(t *gtest.T) { + t1 := gtime.NewFromStr("2021-05-21 05:04:51.206547+00") + t2 := gconv.GTime(gvar.New(t1)) + t3 := gvar.New(t1).GTime() + t.AssertEQ(t1, t2) + t.AssertEQ(t1.Local(), t2.Local()) + t.AssertEQ(t1, t3) + t.AssertEQ(t1.Local(), t3.Local()) + }) } func Test_Time_Slice_Attribute(t *testing.T) { From db943468637c1f93469da20809dfb9731ffb83b1 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 1 Jun 2021 19:59:57 +0800 Subject: [PATCH 69/70] gitee/github template update --- .gitee/ISSUE_TEMPLATE.MD | 5 ++++- .github/ISSUE_TEMPLATE.MD | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitee/ISSUE_TEMPLATE.MD b/.gitee/ISSUE_TEMPLATE.MD index 1197badda..4d24425c3 100644 --- a/.gitee/ISSUE_TEMPLATE.MD +++ b/.gitee/ISSUE_TEMPLATE.MD @@ -1,4 +1,7 @@ - + + + + ### 1. 您当前使用的`Go`版本,及系统版本、系统架构? diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD index 09e907f4e..364044d16 100644 --- a/.github/ISSUE_TEMPLATE.MD +++ b/.github/ISSUE_TEMPLATE.MD @@ -1,5 +1,10 @@ + + + + + ### 1. What version of `Go` and system type/arch are you using? -### 3. Can this issue be reproduced with the latest release? +### 3. Can this issue be re-produced with the latest release? From 1c09846d3efa4eb5f7dce0e36a6f4e363857ebc2 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 1 Jun 2021 20:09:52 +0800 Subject: [PATCH 70/70] fix issue #1272 --- util/gvalid/gvalid_validator_check_value.go | 11 +---------- util/gvalid/gvalid_z_unit_basic_all_test.go | 7 +++++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go index e470d96dc..a72cee3b3 100644 --- a/util/gvalid/gvalid_validator_check_value.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -208,16 +208,7 @@ func (v *Validator) doCheckBuildInRules( if v, ok := value.(apiTime); ok { return !v.IsZero(), nil } - // Standard date string, which must contain char '-' or '.'. - if _, err := gtime.StrToTime(valueStr); err == nil { - match = true - break - } - // Date that not contains char '-' or '.'. - if _, err := gtime.StrToTime(valueStr, "Ymd"); err == nil { - match = true - break - } + match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr) // Date rule with specified format. case "date-format": diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 75f22609b..30f44e470 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -236,6 +236,7 @@ func Test_Date(t *testing.T) { val5 := "2010.11.01" val6 := "2010/11/01" val7 := "2010=11=01" + val8 := "123" err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) @@ -243,13 +244,15 @@ func Test_Date(t *testing.T) { err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) - t.Assert(err1, nil) - t.Assert(err2, nil) + err8 := gvalid.CheckValue(context.TODO(), val8, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) t.Assert(err3, nil) t.Assert(err4, nil) t.Assert(err5, nil) t.Assert(err6, nil) t.AssertNE(err7, nil) + t.AssertNE(err8, nil) }) }