diff --git a/cmd/gf/README.MD b/cmd/gf/README.MD
index fa88966b2..374ef4012 100644
--- a/cmd/gf/README.MD
+++ b/cmd/gf/README.MD
@@ -5,6 +5,7 @@
## 1. Install
+## 1) PreCompiled Binary
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
1. `Mac` & `Linux`
@@ -19,6 +20,13 @@ You can also install `gf` tool using pre-built binaries: https://github.com/gogf
3. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
+## 2) Manually Install
+
+ ```shell
+git clone https://github.com/gogf/gf && cd cmd/gf && go install
+ ```
+
+
## 2. Commands
```html
$ gf
diff --git a/cmd/gf/go.mod b/cmd/gf/go.mod
index 430fb4783..94ef8f12a 100644
--- a/cmd/gf/go.mod
+++ b/cmd/gf/go.mod
@@ -6,7 +6,7 @@ require (
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.0.0-rc2
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.0.0-rc2
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.0.0-rc2
- github.com/gogf/gf/v2 v2.0.0-rc
+ github.com/gogf/gf/v2 v2.0.0
github.com/olekukonko/tablewriter v0.0.5
)
diff --git a/cmd/gf/internal/consts/consts_gen_dao_template_dao.go b/cmd/gf/internal/consts/consts_gen_dao_template_dao.go
index f07fd1d55..4fedb480e 100644
--- a/cmd/gf/internal/consts/consts_gen_dao_template_dao.go
+++ b/cmd/gf/internal/consts/consts_gen_dao_template_dao.go
@@ -11,10 +11,13 @@ import (
"{TplImportPrefix}/internal"
)
+// internal{TplTableNameCamelCase}Dao is internal type for wrapping internal DAO implements.
+type internal{TplTableNameCamelCase}Dao = *internal.{TplTableNameCamelCase}Dao
+
// {TplTableNameCamelLowerCase}Dao is the data access object for table {TplTableName}.
// You can define custom methods on it to extend its functionality as you wish.
type {TplTableNameCamelLowerCase}Dao struct {
- *internal.{TplTableNameCamelCase}Dao
+ internal{TplTableNameCamelCase}Dao
}
var (
diff --git a/contrib/drivers/mssql/mssql.go b/contrib/drivers/mssql/mssql.go
index 908aba1a8..f566870ce 100644
--- a/contrib/drivers/mssql/mssql.go
+++ b/contrib/drivers/mssql/mssql.go
@@ -223,7 +223,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
return nil, err
}
- result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
+ result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
if err != nil {
return
}
@@ -289,7 +289,7 @@ ORDER BY a.id,a.colorder`,
table,
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
- result, err = d.DoGetAll(ctx, link, structureSql)
+ result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}
diff --git a/contrib/drivers/oracle/oracle.go b/contrib/drivers/oracle/oracle.go
index 4ea25c96b..ccae47bd5 100644
--- a/contrib/drivers/oracle/oracle.go
+++ b/contrib/drivers/oracle/oracle.go
@@ -194,7 +194,7 @@ func (d *Driver) parseSql(sql string) string {
// Note that it ignores the parameter `schema` in oracle database, as it is not necessary.
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
var result gdb.Result
- result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
+ result, err = d.DoSelect(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
if err != nil {
return
}
@@ -245,7 +245,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
- result, err = d.DoGetAll(ctx, link, structureSql)
+ result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}
diff --git a/contrib/drivers/pgsql/pgsql.go b/contrib/drivers/pgsql/pgsql.go
index 4f87cdc01..31696134e 100644
--- a/contrib/drivers/pgsql/pgsql.go
+++ b/contrib/drivers/pgsql/pgsql.go
@@ -142,7 +142,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
schema[0],
)
}
- result, err = d.DoGetAll(ctx, link, query)
+ result, err = d.DoSelect(ctx, link, query)
if err != nil {
return
}
@@ -198,7 +198,7 @@ ORDER BY a.attnum`,
return nil
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
- result, err = d.DoGetAll(ctx, link, structureSql)
+ result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil
}
diff --git a/contrib/drivers/sqlite/sqlite.go b/contrib/drivers/sqlite/sqlite.go
index bca87fb33..66bba094d 100644
--- a/contrib/drivers/sqlite/sqlite.go
+++ b/contrib/drivers/sqlite/sqlite.go
@@ -106,7 +106,7 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
return nil, err
}
- result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
+ result, err = d.DoSelect(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
if err != nil {
return
}
@@ -141,7 +141,7 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
if link, err = d.SlaveLink(useSchema); err != nil {
return nil
}
- result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
+ result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
return nil
}
diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go
index 82594aa01..a112d16cd 100644
--- a/database/gdb/gdb.go
+++ b/database/gdb/gdb.go
@@ -20,6 +20,7 @@ import (
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gcmd"
+ "github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/grand"
)
@@ -94,15 +95,18 @@ type DB interface {
// Internal APIs for CURD, which can be overwritten by custom CURD implements.
// ===========================================================================
- DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
+ DoSelect(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoSelect.
DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert.
DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
- DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoQuery.
- DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
- DoFilter(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoFilter.
- DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) // See Core.DoCommit.
- DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
+
+ DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoQuery.
+ DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
+
+ DoFilter(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoFilter.
+ DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) // See Core.DoCommit.
+
+ DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
// ===========================================================================
// Query APIs for convenience purpose.
@@ -212,9 +216,6 @@ type Driver interface {
// 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)
@@ -237,10 +238,10 @@ type Sql struct {
// DoInsertOption is the input struct for function DoInsert.
type DoInsertOption struct {
- OnDuplicateStr string
- OnDuplicateMap map[string]interface{}
- InsertOption int // Insert operation.
- BatchCount int // Batch count for batch inserting.
+ OnDuplicateStr string // Custom string for `on duplicated` statement.
+ OnDuplicateMap map[string]interface{} // Custom key-value map from `OnDuplicateEx` function for `on duplicated` statement.
+ InsertOption int // Insert operation in constant value.
+ BatchCount int // Batch count for batch inserting.
}
// TableField is the struct for table field.
@@ -284,9 +285,10 @@ const (
ctxTimeoutTypeExec = iota
ctxTimeoutTypeQuery
ctxTimeoutTypePrepare
- commandEnvKeyForDryRun = "gf.gdb.dryrun"
- modelForDaoSuffix = `ForDao`
- dbRoleSlave = `slave`
+ commandEnvKeyForDryRun = "gf.gdb.dryrun"
+ modelForDaoSuffix = `ForDao`
+ dbRoleSlave = `slave`
+ contextKeyForDB gctx.StrKey = `DBInContext`
)
const (
diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go
index 0dd217829..1bb335d9f 100644
--- a/database/gdb/gdb_core.go
+++ b/database/gdb/gdb_core.go
@@ -45,7 +45,6 @@ func (c *Core) Ctx(ctx context.Context) DB {
configNode = c.db.GetConfig()
)
*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 {
@@ -53,6 +52,7 @@ func (c *Core) Ctx(ctx context.Context) DB {
// Do not let it continue.
panic(err)
}
+ newCore.ctx = WithDB(ctx, newCore.db)
return newCore.db
}
@@ -145,11 +145,11 @@ func (c *Core) Slave(schema ...string) (*sql.DB, error) {
// GetAll queries and returns data records from database.
func (c *Core) GetAll(ctx context.Context, sql string, args ...interface{}) (Result, error) {
- return c.db.DoGetAll(ctx, nil, sql, args...)
+ return c.db.DoSelect(ctx, nil, sql, args...)
}
-// 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) {
+// DoSelect queries and returns data records from database.
+func (c *Core) DoSelect(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
return c.db.DoQuery(ctx, link, sql, args...)
}
@@ -168,7 +168,7 @@ func (c *Core) GetOne(ctx context.Context, sql string, args ...interface{}) (Rec
// 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(ctx context.Context, sql string, args ...interface{}) ([]Value, error) {
- all, err := c.db.DoGetAll(ctx, nil, sql, args...)
+ all, err := c.db.DoSelect(ctx, nil, sql, args...)
if err != nil {
return nil, err
}
diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go
index 0e0ff4403..9dd3beedd 100644
--- a/database/gdb/gdb_core_utility.go
+++ b/database/gdb/gdb_core_utility.go
@@ -8,12 +8,39 @@
package gdb
import (
+ "context"
+
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
+// WithDB injects given db object into context and returns a new context.
+func WithDB(ctx context.Context, db DB) context.Context {
+ if db == nil {
+ return ctx
+ }
+ dbCtx := db.GetCtx()
+ if ctxDb := DBFromCtx(dbCtx); ctxDb != nil {
+ return dbCtx
+ }
+ ctx = context.WithValue(ctx, contextKeyForDB, db)
+ return ctx
+}
+
+// DBFromCtx retrieves and returns DB object from context.
+func DBFromCtx(ctx context.Context) DB {
+ if ctx == nil {
+ return nil
+ }
+ v := ctx.Value(contextKeyForDB)
+ if v != nil {
+ return v.(DB)
+ }
+ return nil
+}
+
// 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.
diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go
index cbfff2434..a7e161185 100644
--- a/database/gdb/gdb_driver_mysql.go
+++ b/database/gdb/gdb_driver_mysql.go
@@ -100,7 +100,7 @@ func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []st
if err != nil {
return nil, err
}
- result, err = d.DoGetAll(ctx, link, `SHOW TABLES`)
+ result, err = d.DoSelect(ctx, link, `SHOW TABLES`)
if err != nil {
return
}
@@ -147,7 +147,7 @@ func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...s
if link, err = d.SlaveLink(useSchema); err != nil {
return nil
}
- result, err = d.DoGetAll(
+ result, err = d.DoSelect(
ctx, link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
)
diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go
index b1c1e8196..94b92a8d9 100644
--- a/database/gdb/gdb_model.go
+++ b/database/gdb/gdb_model.go
@@ -44,6 +44,7 @@ type Model struct {
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
cacheOption CacheOption // Cache option for query statement.
+ hook HookHandler // Hook functions for model hook feature.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go
index 334338f6f..8b27cd945 100644
--- a/database/gdb/gdb_model_delete.go
+++ b/database/gdb/gdb_model_delete.go
@@ -34,18 +34,40 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
- return m.db.DoUpdate(
- m.GetCtx(),
- m.getLink(true),
- m.tables,
- fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
- conditionWhere+conditionExtra,
- append([]interface{}{gtime.Now().String()}, conditionArgs...),
- )
+ in := &HookUpdateInput{
+ internalParamHookUpdate: internalParamHookUpdate{
+ internalParamHook: internalParamHook{
+ db: m.db,
+ link: m.getLink(true),
+ },
+ handler: m.hook.Update,
+ },
+ Table: m.tables,
+ Data: fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)),
+ Condition: conditionWhere + conditionExtra,
+ Args: append([]interface{}{gtime.Now().String()}, conditionArgs...),
+ }
+ return in.Next(m.GetCtx())
}
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
- return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for DELETE operation")
+ return nil, gerror.NewCode(
+ gcode.CodeMissingParameter,
+ "there should be WHERE condition statement for DELETE operation",
+ )
}
- return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
+
+ in := &HookDeleteInput{
+ internalParamHookDelete: internalParamHookDelete{
+ internalParamHook: internalParamHook{
+ db: m.db,
+ link: m.getLink(true),
+ },
+ handler: m.hook.Delete,
+ },
+ Table: m.tables,
+ Condition: conditionStr,
+ Args: conditionArgs,
+ }
+ return in.Next(m.GetCtx())
}
diff --git a/database/gdb/gdb_model_hook.go b/database/gdb/gdb_model_hook.go
new file mode 100644
index 000000000..ca2702c40
--- /dev/null
+++ b/database/gdb/gdb_model_hook.go
@@ -0,0 +1,148 @@
+// 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/v2/text/gstr"
+)
+
+type (
+ HookFuncSelect func(ctx context.Context, in *HookSelectInput) (result Result, err error)
+ HookFuncInsert func(ctx context.Context, in *HookInsertInput) (result sql.Result, err error)
+ HookFuncUpdate func(ctx context.Context, in *HookUpdateInput) (result sql.Result, err error)
+ HookFuncDelete func(ctx context.Context, in *HookDeleteInput) (result sql.Result, err error)
+)
+
+// HookHandler manages all supported hook functions for Model.
+type HookHandler struct {
+ Select HookFuncSelect
+ Insert HookFuncInsert
+ Update HookFuncUpdate
+ Delete HookFuncDelete
+}
+
+// internalParamHook manages all internal parameters for hook operations.
+// The `internal` obviously means you cannot access these parameters outside this package.
+type internalParamHook struct {
+ db DB // Underlying DB object.
+ link Link // Connection object from third party sql driver.
+ handlerCalled bool // Simple mark for custom handler called, in case of recursive calling.
+ removedWhere bool // Removed mark for condition string that was removed `WHERE` prefix.
+}
+
+type internalParamHookSelect struct {
+ internalParamHook
+ handler HookFuncSelect
+}
+
+type internalParamHookInsert struct {
+ internalParamHook
+ handler HookFuncInsert
+}
+
+type internalParamHookUpdate struct {
+ internalParamHook
+ handler HookFuncUpdate
+}
+
+type internalParamHookDelete struct {
+ internalParamHook
+ handler HookFuncDelete
+}
+
+// HookSelectInput holds the parameters for select hook operation.
+type HookSelectInput struct {
+ internalParamHookSelect
+ Table string
+ Sql string
+ Args []interface{}
+}
+
+// HookInsertInput holds the parameters for insert hook operation.
+type HookInsertInput struct {
+ internalParamHookInsert
+ Table string
+ Data List
+ Option DoInsertOption
+}
+
+// HookUpdateInput holds the parameters for update hook operation.
+type HookUpdateInput struct {
+ internalParamHookUpdate
+ Table string
+ Data interface{}
+ Condition string
+ Args []interface{}
+}
+
+// HookDeleteInput holds the parameters for delete hook operation.
+type HookDeleteInput struct {
+ internalParamHookDelete
+ Table string
+ Condition string
+ Args []interface{}
+}
+
+// Next calls the next hook handler.
+func (h *HookSelectInput) Next(ctx context.Context) (result Result, err error) {
+ if h.handler != nil && !h.handlerCalled {
+ h.handlerCalled = true
+ return h.handler(ctx, h)
+ }
+ return h.db.DoSelect(ctx, h.link, h.Sql, h.Args...)
+}
+
+// Next calls the next hook handler.
+func (h *HookInsertInput) Next(ctx context.Context) (result sql.Result, err error) {
+ if h.handler != nil && !h.handlerCalled {
+ h.handlerCalled = true
+ return h.handler(ctx, h)
+ }
+ return h.db.DoInsert(ctx, h.link, h.Table, h.Data, h.Option)
+}
+
+// Next calls the next hook handler.
+func (h *HookUpdateInput) Next(ctx context.Context) (result sql.Result, err error) {
+ if h.handler != nil && !h.handlerCalled {
+ h.handlerCalled = true
+ if gstr.HasPrefix(h.Condition, " WHERE ") {
+ h.removedWhere = true
+ h.Condition = gstr.TrimLeftStr(h.Condition, " WHERE ")
+ }
+ return h.handler(ctx, h)
+ }
+ if h.removedWhere {
+ h.Condition = " WHERE " + h.Condition
+ }
+ return h.db.DoUpdate(ctx, h.link, h.Table, h.Data, h.Condition, h.Args...)
+}
+
+// Next calls the next hook handler.
+func (h *HookDeleteInput) Next(ctx context.Context) (result sql.Result, err error) {
+ if h.handler != nil && !h.handlerCalled {
+ h.handlerCalled = true
+ if gstr.HasPrefix(h.Condition, " WHERE ") {
+ h.removedWhere = true
+ h.Condition = gstr.TrimLeftStr(h.Condition, " WHERE ")
+ }
+ return h.handler(ctx, h)
+ }
+ if h.removedWhere {
+ h.Condition = " WHERE " + h.Condition
+ }
+ return h.db.DoDelete(ctx, h.link, h.Table, h.Condition, h.Args...)
+}
+
+// Hook sets the hook functions for current model.
+func (m *Model) Hook(hook HookHandler) *Model {
+ model := m.getModel()
+ model.hook = hook
+ return model
+}
diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go
index 7039bd38b..4013ca7f7 100644
--- a/database/gdb/gdb_model_insert.go
+++ b/database/gdb/gdb_model_insert.go
@@ -140,7 +140,7 @@ func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
return model
}
-// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs.
+// OnDuplicateEx sets the excluding columns for operations when columns conflict occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// The parameter `onDuplicateEx` can be type of string/map/slice.
// Example:
@@ -310,7 +310,20 @@ func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err err
if err != nil {
return result, err
}
- return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption)
+
+ in := &HookInsertInput{
+ internalParamHookInsert: internalParamHookInsert{
+ internalParamHook: internalParamHook{
+ db: m.db,
+ link: m.getLink(true),
+ },
+ handler: m.hook.Insert,
+ },
+ Table: m.tables,
+ Data: list,
+ Option: doInsertOption,
+ }
+ return in.Next(m.GetCtx())
}
func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) {
diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go
index 386f108de..05049639c 100644
--- a/database/gdb/gdb_model_select.go
+++ b/database/gdb/gdb_model_select.go
@@ -532,9 +532,21 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
}
}
}
- result, err = m.db.DoGetAll(
- m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...,
- )
+
+ in := &HookSelectInput{
+ internalParamHookSelect: internalParamHookSelect{
+ internalParamHook: internalParamHook{
+ db: m.db,
+ link: m.getLink(false),
+ },
+ handler: m.hook.Select,
+ },
+ Table: m.tables,
+ Sql: sql,
+ Args: m.mergeArguments(args),
+ }
+ result, err = in.Next(m.GetCtx())
+
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheOption.Duration < 0 {
diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go
index 064fccceb..f1be33829 100644
--- a/database/gdb/gdb_model_update.go
+++ b/database/gdb/gdb_model_update.go
@@ -74,14 +74,21 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation")
}
- return m.db.DoUpdate(
- m.GetCtx(),
- m.getLink(true),
- m.tables,
- newData,
- conditionStr,
- m.mergeArguments(conditionArgs)...,
- )
+
+ in := &HookUpdateInput{
+ internalParamHookUpdate: internalParamHookUpdate{
+ internalParamHook: internalParamHook{
+ db: m.db,
+ link: m.getLink(true),
+ },
+ handler: m.hook.Update,
+ },
+ Table: m.tables,
+ Data: newData,
+ Condition: conditionStr,
+ Args: m.mergeArguments(conditionArgs),
+ }
+ return in.Next(m.GetCtx())
}
// Increment increments a column's value by a given amount.
diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go
index e5846e883..3e953a034 100644
--- a/database/gdb/gdb_model_with.go
+++ b/database/gdb/gdb_model_with.go
@@ -143,7 +143,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
// Recursively with feature checks.
- model = m.db.With(field.Value)
+ model = m.db.With(field.Value).Hook(m.hook)
if m.withAll {
model = model.WithAll()
} else {
@@ -258,7 +258,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
fieldKeys = structType.FieldKeys()
}
// Recursively with feature checks.
- model = m.db.With(field.Value)
+ model = m.db.With(field.Value).Hook(m.hook)
if m.withAll {
model = model.WithAll()
} else {
diff --git a/database/gdb/gdb_z_mysql_feature_hook_test.go b/database/gdb/gdb_z_mysql_feature_hook_test.go
new file mode 100644
index 000000000..0183dfa1f
--- /dev/null
+++ b/database/gdb/gdb_z_mysql_feature_hook_test.go
@@ -0,0 +1,136 @@
+// 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 (
+ "context"
+ "database/sql"
+ "fmt"
+ "testing"
+
+ "github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/database/gdb"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/test/gtest"
+)
+
+func Test_Model_Hook_Select(t *testing.T) {
+ table := createInitTable()
+ defer dropTable(table)
+
+ gtest.C(t, func(t *gtest.T) {
+ m := db.Model(table).Hook(gdb.HookHandler{
+ Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
+ result, err = in.Next(ctx)
+ if err != nil {
+ return
+ }
+ for i, record := range result {
+ record["test"] = gvar.New(100 + record["id"].Int())
+ result[i] = record
+ }
+ return
+ },
+ })
+ all, err := m.Where(`id > 6`).OrderAsc(`id`).All()
+ t.AssertNil(err)
+ t.Assert(len(all), 4)
+ t.Assert(all[0]["id"].Int(), 7)
+ t.Assert(all[0]["test"].Int(), 107)
+ t.Assert(all[1]["test"].Int(), 108)
+ t.Assert(all[2]["test"].Int(), 109)
+ t.Assert(all[3]["test"].Int(), 110)
+ })
+}
+
+func Test_Model_Hook_Insert(t *testing.T) {
+ table := createTable()
+ defer dropTable(table)
+
+ gtest.C(t, func(t *gtest.T) {
+ m := db.Model(table).Hook(gdb.HookHandler{
+ Insert: func(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) {
+ for i, item := range in.Data {
+ item["passport"] = fmt.Sprintf(`test_port_%d`, item["id"])
+ item["nickname"] = fmt.Sprintf(`test_name_%d`, item["id"])
+ in.Data[i] = item
+ }
+ return in.Next(ctx)
+ },
+ })
+ _, err := m.Insert(g.Map{
+ "id": 1,
+ "nickname": "name_1",
+ })
+ t.AssertNil(err)
+ one, err := m.One()
+ t.AssertNil(err)
+ t.Assert(one["id"].Int(), 1)
+ t.Assert(one["passport"], `test_port_1`)
+ t.Assert(one["nickname"], `test_name_1`)
+ })
+}
+
+func Test_Model_Hook_Update(t *testing.T) {
+ table := createInitTable()
+ defer dropTable(table)
+
+ gtest.C(t, func(t *gtest.T) {
+ m := db.Model(table).Hook(gdb.HookHandler{
+ Update: func(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) {
+ switch value := in.Data.(type) {
+ case gdb.List:
+ for i, data := range value {
+ data["passport"] = `port`
+ data["nickname"] = `name`
+ value[i] = data
+ }
+ in.Data = value
+
+ case gdb.Map:
+ value["passport"] = `port`
+ value["nickname"] = `name`
+ in.Data = value
+ }
+ return in.Next(ctx)
+ },
+ })
+ _, err := m.Data(g.Map{
+ "nickname": "name_1",
+ }).WherePri(1).Update()
+ t.AssertNil(err)
+
+ one, err := m.One()
+ t.AssertNil(err)
+ t.Assert(one["id"].Int(), 1)
+ t.Assert(one["passport"], `port`)
+ t.Assert(one["nickname"], `name`)
+ })
+}
+
+func Test_Model_Hook_Delete(t *testing.T) {
+ table := createInitTable()
+ defer dropTable(table)
+
+ gtest.C(t, func(t *gtest.T) {
+ m := db.Model(table).Hook(gdb.HookHandler{
+ Delete: func(ctx context.Context, in *gdb.HookDeleteInput) (result sql.Result, err error) {
+ return db.Model(table).Data(g.Map{
+ "nickname": `deleted`,
+ }).Where(in.Condition).Update()
+ },
+ })
+ _, err := m.Where(1).Delete()
+ t.AssertNil(err)
+
+ all, err := m.All()
+ t.AssertNil(err)
+ for _, item := range all {
+ t.Assert(item["nickname"].String(), `deleted`)
+ }
+ })
+}
diff --git a/database/gdb/gdb_z_mysql_feature_model_struct_test.go b/database/gdb/gdb_z_mysql_feature_model_struct_test.go
index 78dda93d5..e2886e05a 100644
--- a/database/gdb/gdb_z_mysql_feature_model_struct_test.go
+++ b/database/gdb/gdb_z_mysql_feature_model_struct_test.go
@@ -550,7 +550,7 @@ func Test_Scan_JsonAttributes(t *testing.T) {
}
table := "jfy_gift"
- array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1380.sql`), ";")
+ array := gstr.SplitAndTrim(gtest.DataContent(`issue1380.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
diff --git a/database/gdb/gdb_z_mysql_feature_with_test.go b/database/gdb/gdb_z_mysql_feature_with_test.go
index 3175cb2d9..a0eb72860 100644
--- a/database/gdb/gdb_z_mysql_feature_with_test.go
+++ b/database/gdb/gdb_z_mysql_feature_with_test.go
@@ -10,7 +10,6 @@ import (
"fmt"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
@@ -1644,7 +1643,7 @@ func Test_Table_Relation_With_MultipleDepends1(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
- for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
+ for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@@ -1716,7 +1715,7 @@ func Test_Table_Relation_With_MultipleDepends2(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
- for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
+ for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@@ -1803,7 +1802,7 @@ func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) {
dropTable("table_b")
dropTable("table_c")
}()
- for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
+ for _, v := range gstr.SplitAndTrim(gfile.GetContents(gtest.DataPath("with_multiple_depends.sql")), ";") {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
@@ -1996,7 +1995,7 @@ func Test_With_Feature_Issue1401(t *testing.T) {
table1 = "parcels"
table2 = "parcel_items"
)
- array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1401.sql`), ";")
+ array := gstr.SplitAndTrim(gtest.DataContent(`issue1401.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
@@ -2038,7 +2037,7 @@ func Test_With_Feature_Issue1412(t *testing.T) {
table1 = "parcels"
table2 = "items"
)
- array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1412.sql`), ";")
+ array := gstr.SplitAndTrim(gtest.DataContent(`issue1412.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go
index 4b941e223..01bb5499c 100644
--- a/database/gdb/gdb_z_mysql_model_test.go
+++ b/database/gdb/gdb_z_mysql_model_test.go
@@ -18,7 +18,6 @@ import (
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/database/gdb"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
@@ -2195,7 +2194,7 @@ func Test_Model_FieldsEx_WithReservedWords(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
table = "fieldsex_test_table"
- sqlTpcPath = gdebug.TestDataPath("reservedwords_table_tpl.sql")
+ sqlTpcPath = gtest.DataPath("reservedwords_table_tpl.sql")
sqlContent = gfile.GetContents(sqlTpcPath)
)
t.AssertNE(sqlContent, "")
diff --git a/debug/gdebug/gdebug_testdata.go b/debug/gdebug/gdebug_testdata.go
deleted file mode 100644
index c87f24b13..000000000
--- a/debug/gdebug/gdebug_testdata.go
+++ /dev/null
@@ -1,36 +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 gdebug
-
-import (
- "io/ioutil"
- "path/filepath"
-)
-
-// TestDataPath retrieves and returns the testdata path of current package,
-// which is used for unit testing cases only.
-// The optional parameter `names` specifies the sub-folders/sub-files,
-// which will be joined with current system separator and returned with the path.
-func TestDataPath(names ...string) string {
- path := CallerDirectory() + string(filepath.Separator) + "testdata"
- for _, name := range names {
- path += string(filepath.Separator) + name
- }
- return path
-}
-
-// TestDataContent retrieves and returns the file content for specified testdata path of current package
-func TestDataContent(names ...string) string {
- path := TestDataPath(names...)
- if path != "" {
- data, err := ioutil.ReadFile(path)
- if err == nil {
- return string(data)
- }
- }
- return ""
-}
diff --git a/encoding/gbase64/gbase64_z_unit_test.go b/encoding/gbase64/gbase64_z_unit_test.go
index 4e02ceada..65cbc6561 100644
--- a/encoding/gbase64/gbase64_z_unit_test.go
+++ b/encoding/gbase64/gbase64_z_unit_test.go
@@ -9,7 +9,6 @@ package gbase64_test
import (
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/test/gtest"
)
@@ -66,7 +65,7 @@ func Test_Basic(t *testing.T) {
}
func Test_File(t *testing.T) {
- path := gdebug.TestDataPath("test")
+ path := gtest.DataPath("test")
expect := "dGVzdA=="
gtest.C(t, func(t *gtest.T) {
b, err := gbase64.EncodeFile(path)
diff --git a/encoding/gcompress/gcompress_z_unit_gzip_test.go b/encoding/gcompress/gcompress_z_unit_gzip_test.go
index 4bc86a615..3239e2976 100644
--- a/encoding/gcompress/gcompress_z_unit_gzip_test.go
+++ b/encoding/gcompress/gcompress_z_unit_gzip_test.go
@@ -9,7 +9,6 @@ package gcompress_test
import (
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@@ -43,7 +42,7 @@ func Test_Gzip_UnGzip(t *testing.T) {
}
func Test_Gzip_UnGzip_File(t *testing.T) {
- srcPath := gdebug.TestDataPath("gzip", "file.txt")
+ srcPath := gtest.DataPath("gzip", "file.txt")
dstPath1 := gfile.Temp(gtime.TimestampNanoStr(), "gzip.zip")
dstPath2 := gfile.Temp(gtime.TimestampNanoStr(), "file.txt")
diff --git a/encoding/gcompress/gcompress_z_unit_zip_test.go b/encoding/gcompress/gcompress_z_unit_zip_test.go
index b44069d4b..3d8f51cc4 100644
--- a/encoding/gcompress/gcompress_z_unit_zip_test.go
+++ b/encoding/gcompress/gcompress_z_unit_zip_test.go
@@ -10,7 +10,6 @@ import (
"bytes"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@@ -20,8 +19,8 @@ import (
func Test_ZipPath(t *testing.T) {
// file
gtest.C(t, func(t *gtest.T) {
- srcPath := gdebug.TestDataPath("zip", "path1", "1.txt")
- dstPath := gdebug.TestDataPath("zip", "zip.zip")
+ srcPath := gtest.DataPath("zip", "path1", "1.txt")
+ dstPath := gtest.DataPath("zip", "zip.zip")
t.Assert(gfile.Exists(dstPath), false)
t.Assert(gcompress.ZipPath(srcPath, dstPath), nil)
@@ -42,8 +41,8 @@ func Test_ZipPath(t *testing.T) {
// multiple files
gtest.C(t, func(t *gtest.T) {
var (
- srcPath1 = gdebug.TestDataPath("zip", "path1", "1.txt")
- srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
+ srcPath1 = gtest.DataPath("zip", "path1", "1.txt")
+ srcPath2 = gtest.DataPath("zip", "path2", "2.txt")
dstPath = gfile.Temp(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
@@ -75,8 +74,8 @@ func Test_ZipPath(t *testing.T) {
// one dir and one file.
gtest.C(t, func(t *gtest.T) {
var (
- srcPath1 = gdebug.TestDataPath("zip", "path1")
- srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
+ srcPath1 = gtest.DataPath("zip", "path1")
+ srcPath2 = gtest.DataPath("zip", "path2", "2.txt")
dstPath = gfile.Temp(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
@@ -107,8 +106,8 @@ func Test_ZipPath(t *testing.T) {
})
// directory.
gtest.C(t, func(t *gtest.T) {
- srcPath := gdebug.TestDataPath("zip")
- dstPath := gdebug.TestDataPath("zip", "zip.zip")
+ srcPath := gtest.DataPath("zip")
+ dstPath := gtest.DataPath("zip", "zip.zip")
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@@ -141,10 +140,10 @@ func Test_ZipPath(t *testing.T) {
// multiple directory paths joined using char ','.
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("zip")
- srcPath1 = gdebug.TestDataPath("zip", "path1")
- srcPath2 = gdebug.TestDataPath("zip", "path2")
- dstPath = gdebug.TestDataPath("zip", "zip.zip")
+ srcPath = gtest.DataPath("zip")
+ srcPath1 = gtest.DataPath("zip", "path1")
+ srcPath2 = gtest.DataPath("zip", "path2")
+ dstPath = gtest.DataPath("zip", "zip.zip")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
@@ -181,9 +180,9 @@ func Test_ZipPath(t *testing.T) {
func Test_ZipPathWriter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("zip")
- srcPath1 = gdebug.TestDataPath("zip", "path1")
- srcPath2 = gdebug.TestDataPath("zip", "path2")
+ srcPath = gtest.DataPath("zip")
+ srcPath1 = gtest.DataPath("zip", "path1")
+ srcPath2 = gtest.DataPath("zip", "path2")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go
index 2985d7e28..e3773f121 100644
--- a/encoding/gjson/gjson_api.go
+++ b/encoding/gjson/gjson_api.go
@@ -41,7 +41,7 @@ func (j *Json) IsNil() bool {
}
// Get retrieves and returns value by specified `pattern`.
-// It returns all values of current Json object if `pattern` is given empty or string ".".
+// It returns all values of current Json object if `pattern` is given ".".
// It returns nil if no value found by `pattern`.
//
// We can also access slice item by its index number in `pattern` like:
@@ -71,7 +71,7 @@ func (j *Json) Get(pattern string, def ...interface{}) *gvar.Var {
}
// GetJson gets the value by specified `pattern`,
-// and converts it to a un-concurrent-safe Json object.
+// and converts it to an un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def ...interface{}) *Json {
return New(j.Get(pattern, def...).Val())
}
diff --git a/encoding/gjson/gjson_z_example_load_test.go b/encoding/gjson/gjson_z_example_load_test.go
index efe92e712..41fe9189d 100644
--- a/encoding/gjson/gjson_z_example_load_test.go
+++ b/encoding/gjson/gjson_z_example_load_test.go
@@ -9,17 +9,17 @@ package gjson_test
import (
"fmt"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/gjson"
+ "github.com/gogf/gf/v2/test/gtest"
)
func ExampleLoad() {
- jsonFilePath := gdebug.TestDataPath("json", "data1.json")
+ jsonFilePath := gtest.DataPath("json", "data1.json")
j, _ := gjson.Load(jsonFilePath)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
- notExistFilePath := gdebug.TestDataPath("json", "data2.json")
+ notExistFilePath := gtest.DataPath("json", "data2.json")
j2, _ := gjson.Load(notExistFilePath)
fmt.Println(j2.Get("name"))
@@ -195,7 +195,7 @@ func ExampleIsValidDataType() {
}
func ExampleLoad_Xml() {
- jsonFilePath := gdebug.TestDataPath("xml", "data1.xml")
+ jsonFilePath := gtest.DataPath("xml", "data1.xml")
j, _ := gjson.Load(jsonFilePath)
fmt.Println(j.Get("doc.name"))
fmt.Println(j.Get("doc.score"))
diff --git a/frame/gins/gins_server.go b/frame/gins/gins_server.go
index 2455d7925..08e2277c5 100644
--- a/frame/gins/gins_server.go
+++ b/frame/gins/gins_server.go
@@ -58,10 +58,10 @@ func Server(name ...interface{}) *ghttp.Server {
}
}
}
- // Server configuration.
+ // Automatically retrieve configuration by instance name.
serverConfigMap = Config().MustGet(
ctx,
- fmt.Sprintf(`%s.%s`, configNodeName, server.GetName()),
+ fmt.Sprintf(`%s.%s`, configNodeName, instanceName),
).Map()
if len(serverConfigMap) == 0 {
serverConfigMap = Config().MustGet(ctx, configNodeName).Map()
@@ -81,7 +81,7 @@ func Server(name ...interface{}) *ghttp.Server {
// Server logger configuration checks.
serverLoggerConfigMap = Config().MustGet(
ctx,
- fmt.Sprintf(`%s.%s.%s`, configNodeName, server.GetName(), configNodeNameLogger),
+ fmt.Sprintf(`%s.%s.%s`, configNodeName, instanceName, configNodeNameLogger),
).Map()
if len(serverLoggerConfigMap) > 0 {
if err = server.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil {
diff --git a/frame/gins/gins_z_unit_config_test.go b/frame/gins/gins_z_unit_config_test.go
index ddfc98915..d635ba0c5 100644
--- a/frame/gins/gins_z_unit_config_test.go
+++ b/frame/gins/gins_z_unit_config_test.go
@@ -12,7 +12,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@@ -23,7 +22,7 @@ import (
var (
ctx = context.Background()
configContent = gfile.GetContents(
- gdebug.TestDataPath("config", "config.toml"),
+ gtest.DataPath("config", "config.toml"),
)
)
diff --git a/frame/gins/gins_z_unit_database_test.go b/frame/gins/gins_z_unit_database_test.go
index 5ec575660..3fc466de6 100644
--- a/frame/gins/gins_z_unit_database_test.go
+++ b/frame/gins/gins_z_unit_database_test.go
@@ -10,7 +10,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@@ -20,7 +19,7 @@ import (
func Test_Database(t *testing.T) {
databaseContent := gfile.GetContents(
- gdebug.TestDataPath("database", "config.toml"),
+ gtest.DataPath("database", "config.toml"),
)
gtest.C(t, func(t *gtest.T) {
var err error
diff --git a/frame/gins/gins_z_unit_redis_test.go b/frame/gins/gins_z_unit_redis_test.go
index 237db241c..9ee97d8c1 100644
--- a/frame/gins/gins_z_unit_redis_test.go
+++ b/frame/gins/gins_z_unit_redis_test.go
@@ -10,7 +10,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/gins"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
@@ -20,7 +19,7 @@ import (
func Test_Redis(t *testing.T) {
redisContent := gfile.GetContents(
- gdebug.TestDataPath("redis", "config.toml"),
+ gtest.DataPath("redis", "config.toml"),
)
gtest.C(t, func(t *gtest.T) {
diff --git a/frame/gins/gins_z_unit_server_test.go b/frame/gins/gins_z_unit_server_test.go
new file mode 100644
index 000000000..e212ecce6
--- /dev/null
+++ b/frame/gins/gins_z_unit_server_test.go
@@ -0,0 +1,35 @@
+// 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 gins
+
+//func Test_Server(t *testing.T) {
+// gtest.C(t, func(t *gtest.T) {
+// var (
+// path = gcfg.DefaultConfigFileName
+// serverConfigContent = gtest.DataContent("server", "config.yaml")
+// err = gfile.PutContents(path, serverConfigContent)
+// )
+// t.AssertNil(err)
+// defer gfile.Remove(path)
+//
+// time.Sleep(time.Second)
+//
+// localInstances.Clear()
+// defer localInstances.Clear()
+//
+// s := Server("tempByInstanceName")
+// s.BindHandler("/", func(r *ghttp.Request) {
+// r.Response.Write("hello")
+// })
+// s.SetDumpRouterMap(false)
+// t.AssertNil(s.Start())
+// defer t.AssertNil(s.Shutdown())
+//
+// content := HttpClient().GetContent(gctx.New(), `http://127.0.0.1:8003/`)
+// t.Assert(content, `hello`)
+// })
+//}
diff --git a/frame/gins/gins_z_unit_view_test.go b/frame/gins/gins_z_unit_view_test.go
index bc66d5376..aa8cae13f 100644
--- a/frame/gins/gins_z_unit_view_test.go
+++ b/frame/gins/gins_z_unit_view_test.go
@@ -11,7 +11,6 @@ import (
"fmt"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
@@ -53,7 +52,7 @@ func Test_View(t *testing.T) {
func Test_View_Config(t *testing.T) {
// view1 test1
gtest.C(t, func(t *gtest.T) {
- dirPath := gdebug.TestDataPath("view1")
+ dirPath := gtest.DataPath("view1")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@@ -75,7 +74,7 @@ func Test_View_Config(t *testing.T) {
})
// view1 test2
gtest.C(t, func(t *gtest.T) {
- dirPath := gdebug.TestDataPath("view1")
+ dirPath := gtest.DataPath("view1")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@@ -97,7 +96,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.C(t, func(t *gtest.T) {
- dirPath := gdebug.TestDataPath("view2")
+ dirPath := gtest.DataPath("view2")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
@@ -119,7 +118,7 @@ func Test_View_Config(t *testing.T) {
})
// view2
gtest.C(t, func(t *gtest.T) {
- dirPath := gdebug.TestDataPath("view2")
+ dirPath := gtest.DataPath("view2")
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
defer localInstances.Clear()
diff --git a/frame/gins/testdata/server/config.yaml b/frame/gins/testdata/server/config.yaml
new file mode 100644
index 000000000..3be493af7
--- /dev/null
+++ b/frame/gins/testdata/server/config.yaml
@@ -0,0 +1,4 @@
+server:
+ address: ":8000"
+ tempByInstanceName:
+ address: ":8003"
\ No newline at end of file
diff --git a/i18n/gi18n/gi18n_z_unit_test.go b/i18n/gi18n/gi18n_z_unit_test.go
index d1e56db2a..aec748aad 100644
--- a/i18n/gi18n/gi18n_z_unit_test.go
+++ b/i18n/gi18n/gi18n_z_unit_test.go
@@ -24,7 +24,7 @@ import (
func Test_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
- Path: gdebug.TestDataPath("i18n"),
+ Path: gtest.DataPath("i18n"),
})
i18n.SetLanguage("none")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
@@ -41,7 +41,7 @@ func Test_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
- Path: gdebug.TestDataPath("i18n-file"),
+ Path: gtest.DataPath("i18n-file"),
})
i18n.SetLanguage("none")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
@@ -72,7 +72,7 @@ func Test_TranslateFormat(t *testing.T) {
// Tf
gtest.C(t, func(t *gtest.T) {
i18n := gi18n.New(gi18n.Options{
- Path: gdebug.TestDataPath("i18n"),
+ Path: gtest.DataPath("i18n"),
})
i18n.SetLanguage("none")
t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
@@ -84,7 +84,7 @@ func Test_TranslateFormat(t *testing.T) {
func Test_DefaultManager(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- err := gi18n.SetPath(gdebug.TestDataPath("i18n"))
+ err := gi18n.SetPath(gtest.DataPath("i18n"))
t.AssertNil(err)
gi18n.SetLanguage("none")
diff --git a/net/gclient/gclient_z_unit_test.go b/net/gclient/gclient_z_unit_test.go
index e9328bca7..a1d8dc5c0 100644
--- a/net/gclient/gclient_z_unit_test.go
+++ b/net/gclient/gclient_z_unit_test.go
@@ -15,7 +15,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
@@ -337,7 +336,7 @@ func Test_Client_File_And_Param(t *testing.T) {
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
- path := gdebug.TestDataPath("upload", "file1.txt")
+ path := gtest.DataPath("upload", "file1.txt")
data := g.Map{
"file": "@file:" + path,
"json": `{"uuid": "luijquiopm", "isRelative": false, "fileName": "test111.xls"}`,
diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go
index fff0d387e..1aa63a744 100644
--- a/net/ghttp/ghttp_server_config.go
+++ b/net/ghttp/ghttp_server_config.go
@@ -329,14 +329,18 @@ func (s *Server) SetConfigWithMap(m map[string]interface{}) error {
// SetConfig sets the configuration for the server.
func (s *Server) SetConfig(c ServerConfig) error {
s.config = c
- // Address, check and use a random free port.
+ // Automatically add ':' prefix for address if it is missed.
+ if s.config.Address != "" && !gstr.HasPrefix(s.config.Address, ":") {
+ s.config.Address = ":" + s.config.Address
+ }
+ // It checks and uses a random free port.
array := gstr.Split(s.config.Address, ":")
if s.config.Address == "" || len(array) < 2 || array[1] == "0" {
s.config.Address = gstr.Join([]string{
array[0], gconv.String(gtcp.MustGetFreePort()),
}, ":")
}
- // Static.
+ // Static files root.
if c.ServerRoot != "" {
s.SetServerRoot(c.ServerRoot)
}
diff --git a/net/ghttp/ghttp_z_unit_feature_https_test.go b/net/ghttp/ghttp_z_unit_feature_https_test.go
index f99db9b5d..f7a62426c 100644
--- a/net/ghttp/ghttp_z_unit_feature_https_test.go
+++ b/net/ghttp/ghttp_z_unit_feature_https_test.go
@@ -11,7 +11,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
_ "github.com/gogf/gf/v2/net/ghttp/testdata/https/packed"
@@ -31,8 +30,8 @@ func Test_HTTPS_Basic(t *testing.T) {
})
})
s.EnableHTTPS(
- gdebug.TestDataPath("https", "files", "server.crt"),
- gdebug.TestDataPath("https", "files", "server.key"),
+ gtest.DataPath("https", "files", "server.crt"),
+ gtest.DataPath("https", "files", "server.key"),
)
s.SetDumpRouterMap(false)
s.Start()
@@ -101,8 +100,8 @@ func Test_HTTPS_HTTP_Basic(t *testing.T) {
})
})
s.EnableHTTPS(
- gdebug.TestDataPath("https", "files", "server.crt"),
- gdebug.TestDataPath("https", "files", "server.key"),
+ gtest.DataPath("https", "files", "server.crt"),
+ gtest.DataPath("https", "files", "server.key"),
)
s.SetPort(portHttp)
s.SetHTTPSPort(portHttps)
diff --git a/net/ghttp/ghttp_z_unit_feature_middleware_basic_test.go b/net/ghttp/ghttp_z_unit_feature_middleware_basic_test.go
index bb3841daa..af81b245d 100644
--- a/net/ghttp/ghttp_z_unit_feature_middleware_basic_test.go
+++ b/net/ghttp/ghttp_z_unit_feature_middleware_basic_test.go
@@ -13,7 +13,6 @@ import (
"time"
"github.com/gogf/gf/v2/container/garray"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
@@ -168,7 +167,7 @@ func Test_Middleware_With_Static(t *testing.T) {
})
})
s.SetDumpRouterMap(false)
- s.SetServerRoot(gdebug.TestDataPath("static1"))
+ s.SetServerRoot(gtest.DataPath("static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
@@ -236,7 +235,7 @@ func Test_Middleware_Hook_With_Static(t *testing.T) {
})
})
s.SetDumpRouterMap(false)
- s.SetServerRoot(gdebug.TestDataPath("static1"))
+ s.SetServerRoot(gtest.DataPath("static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
diff --git a/net/ghttp/ghttp_z_unit_feature_request_file_test.go b/net/ghttp/ghttp_z_unit_feature_request_file_test.go
index 509a01897..53f020045 100644
--- a/net/ghttp/ghttp_z_unit_feature_request_file_test.go
+++ b/net/ghttp/ghttp_z_unit_feature_request_file_test.go
@@ -12,7 +12,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
@@ -46,7 +45,7 @@ func Test_Params_File_Single(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath := gdebug.TestDataPath("upload", "file1.txt")
+ srcPath := gtest.DataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
@@ -62,7 +61,7 @@ func Test_Params_File_Single(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath := gdebug.TestDataPath("upload", "file2.txt")
+ srcPath := gtest.DataPath("upload", "file2.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
"randomlyRename": true,
@@ -97,7 +96,7 @@ func Test_Params_File_CustomName(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath := gdebug.TestDataPath("upload", "file1.txt")
+ srcPath := gtest.DataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "my.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
@@ -132,8 +131,8 @@ func Test_Params_File_Batch(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath1 := gdebug.TestDataPath("upload", "file1.txt")
- srcPath2 := gdebug.TestDataPath("upload", "file2.txt")
+ srcPath1 := gtest.DataPath("upload", "file1.txt")
+ srcPath2 := gtest.DataPath("upload", "file2.txt")
dstPath1 := gfile.Join(dstDirPath, "file1.txt")
dstPath2 := gfile.Join(dstDirPath, "file2.txt")
content := client.PostContent(ctx, "/upload/batch", g.Map{
@@ -152,8 +151,8 @@ func Test_Params_File_Batch(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath1 := gdebug.TestDataPath("upload", "file1.txt")
- srcPath2 := gdebug.TestDataPath("upload", "file2.txt")
+ srcPath1 := gtest.DataPath("upload", "file1.txt")
+ srcPath2 := gtest.DataPath("upload", "file2.txt")
content := client.PostContent(ctx, "/upload/batch", g.Map{
"file[0]": "@file:" + srcPath1,
"file[1]": "@file:" + srcPath2,
@@ -205,7 +204,7 @@ func Test_Params_Strict_Route_File_Single(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
- srcPath := gdebug.TestDataPath("upload", "file1.txt")
+ srcPath := gtest.DataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
diff --git a/net/ghttp/ghttp_z_unit_feature_static_test.go b/net/ghttp/ghttp_z_unit_feature_static_test.go
index da5e5df90..840a7e4d3 100644
--- a/net/ghttp/ghttp_z_unit_feature_static_test.go
+++ b/net/ghttp/ghttp_z_unit_feature_static_test.go
@@ -13,7 +13,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
@@ -60,7 +59,7 @@ func Test_Static_ServerRoot(t *testing.T) {
func Test_Static_ServerRoot_Security(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
- s.SetServerRoot(gdebug.TestDataPath("static1"))
+ s.SetServerRoot(gtest.DataPath("static1"))
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
diff --git a/net/ghttp/ghttp_z_unit_feature_template_test.go b/net/ghttp/ghttp_z_unit_feature_template_test.go
index 8c41f443f..ab64c506b 100644
--- a/net/ghttp/ghttp_z_unit_feature_template_test.go
+++ b/net/ghttp/ghttp_z_unit_feature_template_test.go
@@ -13,7 +13,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/ghtml"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
@@ -24,7 +23,7 @@ import (
func Test_Template_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- v := gview.New(gdebug.TestDataPath("template", "basic"))
+ v := gview.New(gtest.DataPath("template", "basic"))
s := g.Server(guid.S())
s.SetView(v)
s.BindHandler("/", func(r *ghttp.Request) {
@@ -47,7 +46,7 @@ func Test_Template_Basic(t *testing.T) {
func Test_Template_Encode(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- v := gview.New(gdebug.TestDataPath("template", "basic"))
+ v := gview.New(gtest.DataPath("template", "basic"))
v.SetAutoEncode(true)
s := g.Server(guid.S())
s.SetView(v)
@@ -71,7 +70,7 @@ func Test_Template_Encode(t *testing.T) {
func Test_Template_Layout1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- v := gview.New(gdebug.TestDataPath("template", "layout1"))
+ v := gview.New(gtest.DataPath("template", "layout1"))
s := g.Server(guid.S())
s.SetView(v)
s.BindHandler("/layout", func(r *ghttp.Request) {
@@ -99,7 +98,7 @@ func Test_Template_Layout1(t *testing.T) {
func Test_Template_Layout2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- v := gview.New(gdebug.TestDataPath("template", "layout2"))
+ v := gview.New(gtest.DataPath("template", "layout2"))
s := g.Server(guid.S())
s.SetView(v)
s.BindHandler("/main1", func(r *ghttp.Request) {
diff --git a/net/ghttp/ghttp_z_unit_issue_test.go b/net/ghttp/ghttp_z_unit_issue_test.go
index f8e3b437e..0e5a1ca5f 100644
--- a/net/ghttp/ghttp_z_unit_issue_test.go
+++ b/net/ghttp/ghttp_z_unit_issue_test.go
@@ -12,7 +12,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
@@ -45,7 +44,7 @@ func Test_Issue1611(t *testing.T) {
s := g.Server(guid.S())
v := g.View(guid.S())
content := "This is header"
- gtest.AssertNil(v.SetPath(gdebug.TestDataPath("issue1611")))
+ gtest.AssertNil(v.SetPath(gtest.DataPath("issue1611")))
s.SetView(v)
s.BindHandler("/", func(r *ghttp.Request) {
gtest.AssertNil(r.Response.WriteTpl("index/layout.html", g.Map{
diff --git a/os/gcfg/gcfg_adapter_file.go b/os/gcfg/gcfg_adapter_file.go
index c7d42ccbe..d98ac0fd8 100644
--- a/os/gcfg/gcfg_adapter_file.go
+++ b/os/gcfg/gcfg_adapter_file.go
@@ -146,6 +146,22 @@ func (c *AdapterFile) Get(ctx context.Context, pattern string) (value interface{
return nil, nil
}
+// Set sets value with specified `pattern`.
+// It supports hierarchical data access by char separator, which is '.' in default.
+// It is commonly used for updates certain configuration value in runtime.
+// Note that, it is not recommended using `Set` configuration at runtime as the configuration would be
+// automatically refreshed if underlying configuration file changed.
+func (c *AdapterFile) Set(pattern string, value interface{}) error {
+ j, err := c.getJson()
+ if err != nil {
+ return err
+ }
+ if j != nil {
+ return j.Set(pattern, value)
+ }
+ return nil
+}
+
// Data retrieves and returns all configuration data as map type.
func (c *AdapterFile) Data(ctx context.Context) (data map[string]interface{}, err error) {
j, err := c.getJson()
diff --git a/os/gcfg/gcfg_adapter_file_path.go b/os/gcfg/gcfg_adapter_file_path.go
index bd83a3fe5..1db5c0189 100644
--- a/os/gcfg/gcfg_adapter_file_path.go
+++ b/os/gcfg/gcfg_adapter_file_path.go
@@ -85,7 +85,17 @@ func (c *AdapterFile) SetPath(path string) (err error) {
}
// AddPath adds an absolute or relative path to the search paths.
-func (c *AdapterFile) AddPath(path string) (err error) {
+func (c *AdapterFile) AddPath(paths ...string) (err error) {
+ for _, path := range paths {
+ if err = c.doAddPath(path); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// doAddPath adds an absolute or relative path to the search paths.
+func (c *AdapterFile) doAddPath(path string) (err error) {
var (
isDir = false
realPath = ""
@@ -139,6 +149,11 @@ func (c *AdapterFile) AddPath(path string) (err error) {
return nil
}
+// GetPaths returns the searching path array of current configuration manager.
+func (c *AdapterFile) GetPaths() []string {
+ return c.searchPaths.Slice()
+}
+
// doGetFilePath 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.
diff --git a/os/gcfg/gcfg_z_unit_adapter_file_test.go b/os/gcfg/gcfg_z_unit_adapter_file_test.go
index c0036a9f0..c1e896b0f 100644
--- a/os/gcfg/gcfg_z_unit_adapter_file_test.go
+++ b/os/gcfg/gcfg_z_unit_adapter_file_test.go
@@ -12,6 +12,7 @@ import (
"testing"
"github.com/gogf/gf/v2/os/gcfg"
+ "github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
)
@@ -99,3 +100,22 @@ func TestAdapterFile_With_UTF8_BOM(t *testing.T) {
t.Assert(c.MustGet(ctx, "test.testStr"), "test")
})
}
+
+func TestAdapterFile_Set(t *testing.T) {
+ config := `log-path = "logs"`
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ path = gcfg.DefaultConfigFileName
+ err = gfile.PutContents(path, config)
+ )
+ t.Assert(err, nil)
+ defer gfile.Remove(path)
+
+ c, err := gcfg.New()
+ t.Assert(c.MustGet(ctx, "log-path").String(), "logs")
+
+ err = c.GetAdapter().(*gcfg.AdapterFile).Set("log-path", "custom-logs")
+ t.Assert(err, nil)
+ t.Assert(c.MustGet(ctx, "log-path").String(), "custom-logs")
+ })
+}
diff --git a/os/gcfg/gcfg_z_unit_instance_test.go b/os/gcfg/gcfg_z_unit_instance_test.go
index 3cc5997b4..47bf3cbda 100644
--- a/os/gcfg/gcfg_z_unit_instance_test.go
+++ b/os/gcfg/gcfg_z_unit_instance_test.go
@@ -13,7 +13,6 @@ import (
"testing"
"github.com/gogf/gf/v2/container/gmap"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
@@ -60,7 +59,7 @@ func Test_Instance_AutoLocateConfigFile(t *testing.T) {
// Automatically locate the configuration file with supported file extensions.
gtest.C(t, func(t *gtest.T) {
pwd := gfile.Pwd()
- t.AssertNil(gfile.Chdir(gdebug.TestDataPath()))
+ t.AssertNil(gfile.Chdir(gtest.DataPath()))
defer gfile.Chdir(pwd)
t.Assert(Instance("c1") != nil, true)
t.Assert(Instance("c1").MustGet(ctx, "my-config"), "1")
@@ -69,7 +68,7 @@ func Test_Instance_AutoLocateConfigFile(t *testing.T) {
// Automatically locate the configuration file with supported file extensions.
gtest.C(t, func(t *gtest.T) {
pwd := gfile.Pwd()
- t.AssertNil(gfile.Chdir(gdebug.TestDataPath("folder1")))
+ t.AssertNil(gfile.Chdir(gtest.DataPath("folder1")))
defer gfile.Chdir(pwd)
t.Assert(Instance("c2").MustGet(ctx, "my-config"), 2)
})
@@ -77,7 +76,7 @@ func Test_Instance_AutoLocateConfigFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
localInstances.Clear()
pwd := gfile.Pwd()
- t.AssertNil(gfile.Chdir(gdebug.TestDataPath("default")))
+ t.AssertNil(gfile.Chdir(gtest.DataPath("default")))
defer gfile.Chdir(pwd)
t.Assert(Instance().MustGet(ctx, "my-config"), 1)
@@ -90,7 +89,7 @@ func Test_Instance_AutoLocateConfigFile(t *testing.T) {
func Test_Instance_EnvPath(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- genv.Set("GF_GCFG_PATH", gdebug.TestDataPath("envpath"))
+ genv.Set("GF_GCFG_PATH", gtest.DataPath("envpath"))
defer genv.Set("GF_GCFG_PATH", "")
t.Assert(Instance("c3") != nil, true)
t.Assert(Instance("c3").MustGet(ctx, "my-config"), "3")
@@ -101,7 +100,7 @@ func Test_Instance_EnvPath(t *testing.T) {
func Test_Instance_EnvFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- genv.Set("GF_GCFG_PATH", gdebug.TestDataPath("envfile"))
+ genv.Set("GF_GCFG_PATH", gtest.DataPath("envfile"))
defer genv.Set("GF_GCFG_PATH", "")
genv.Set("GF_GCFG_FILE", "c6.json")
defer genv.Set("GF_GCFG_FILE", "")
diff --git a/os/gfile/gfile_z_unit_scan_test.go b/os/gfile/gfile_z_unit_scan_test.go
index 42e80faf5..151488313 100644
--- a/os/gfile/gfile_z_unit_scan_test.go
+++ b/os/gfile/gfile_z_unit_scan_test.go
@@ -10,13 +10,12 @@ import (
"testing"
"github.com/gogf/gf/v2/container/garray"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_ScanDir(t *testing.T) {
- teatPath := gdebug.TestDataPath()
+ teatPath := gtest.DataPath()
gtest.C(t, func(t *gtest.T) {
files, err := gfile.ScanDir(teatPath, "*", false)
t.AssertNil(err)
@@ -35,7 +34,7 @@ func Test_ScanDir(t *testing.T) {
}
func Test_ScanDirFunc(t *testing.T) {
- teatPath := gdebug.TestDataPath()
+ teatPath := gtest.DataPath()
gtest.C(t, func(t *gtest.T) {
files, err := gfile.ScanDirFunc(teatPath, "*", true, func(path string) string {
if gfile.Name(path) != "file1" {
@@ -50,7 +49,7 @@ func Test_ScanDirFunc(t *testing.T) {
}
func Test_ScanDirFile(t *testing.T) {
- teatPath := gdebug.TestDataPath()
+ teatPath := gtest.DataPath()
gtest.C(t, func(t *gtest.T) {
files, err := gfile.ScanDirFile(teatPath, "*", false)
t.AssertNil(err)
@@ -67,7 +66,7 @@ func Test_ScanDirFile(t *testing.T) {
}
func Test_ScanDirFileFunc(t *testing.T) {
- teatPath := gdebug.TestDataPath()
+ teatPath := gtest.DataPath()
gtest.C(t, func(t *gtest.T) {
array := garray.New()
files, err := gfile.ScanDirFileFunc(teatPath, "*", false, func(path string) string {
diff --git a/os/glog/glog_logger.go b/os/glog/glog_logger.go
index 8dd985759..97acbe557 100644
--- a/os/glog/glog_logger.go
+++ b/os/glog/glog_logger.go
@@ -117,7 +117,6 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) {
input = &HandlerInput{
Logger: l,
Buffer: bytes.NewBuffer(nil),
- Ctx: ctx,
Time: now,
Color: defaultLevelColor[level],
Level: level,
@@ -221,13 +220,13 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) {
if l.config.Flags&F_ASYNC > 0 {
input.IsAsync = true
err := asyncPool.Add(ctx, func(ctx context.Context) {
- input.Next()
+ input.Next(ctx)
})
if err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
} else {
- input.Next()
+ input.Next(ctx)
}
}
diff --git a/os/glog/glog_logger_handler.go b/os/glog/glog_logger_handler.go
index bdeb4cdfc..5c7d9932c 100644
--- a/os/glog/glog_logger_handler.go
+++ b/os/glog/glog_logger_handler.go
@@ -17,30 +17,29 @@ type Handler func(ctx context.Context, in *HandlerInput)
// HandlerInput is the input parameter struct for logging Handler.
type HandlerInput struct {
- Logger *Logger // Logger.
- Ctx context.Context // Context.
- Buffer *bytes.Buffer // Buffer for logging content outputs.
- Time time.Time // Logging time, which is the time that logging triggers.
- TimeFormat string // Formatted time string, like "2016-01-09 12:00:00".
- Color int // Using color, like COLOR_RED, COLOR_BLUE, etc.
- Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc.
- LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc.
- CallerFunc string // The source function name that calls logging.
- CallerPath string // The source file path and its line number that calls logging.
- CtxStr string // The retrieved context value string from context.
- Prefix string // Custom prefix string for logging content.
- Content string // Content is the main logging content that passed by you.
- IsAsync bool // IsAsync marks it is in asynchronous logging.
- handlerIndex int // Middleware handling index for internal usage.
+ Logger *Logger // Logger.
+ Buffer *bytes.Buffer // Buffer for logging content outputs.
+ Time time.Time // Logging time, which is the time that logging triggers.
+ TimeFormat string // Formatted time string, like "2016-01-09 12:00:00".
+ Color int // Using color, like COLOR_RED, COLOR_BLUE, etc.
+ Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc.
+ LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc.
+ CallerFunc string // The source function name that calls logging.
+ CallerPath string // The source file path and its line number that calls logging.
+ CtxStr string // The retrieved context value string from context.
+ Prefix string // Custom prefix string for logging content.
+ Content string // Content is the main logging content that passed by you.
+ IsAsync bool // IsAsync marks it is in asynchronous logging.
+ handlerIndex int // Middleware handling index for internal usage.
}
// Next calls the next logging handler in middleware way.
-func (i *HandlerInput) Next() {
+func (i *HandlerInput) Next(ctx context.Context) {
if len(i.Logger.config.Handlers)-1 > i.handlerIndex {
i.handlerIndex++
- i.Logger.config.Handlers[i.handlerIndex](i.Ctx, i)
+ i.Logger.config.Handlers[i.handlerIndex](ctx, i)
} else {
- defaultHandler(i.Ctx, i)
+ defaultHandler(ctx, i)
}
}
diff --git a/os/glog/glog_z_unit_logger_handler_test.go b/os/glog/glog_z_unit_logger_handler_test.go
index aeca8983e..b36999d1a 100644
--- a/os/glog/glog_z_unit_logger_handler_test.go
+++ b/os/glog/glog_z_unit_logger_handler_test.go
@@ -21,7 +21,7 @@ var arrayForHandlerTest1 = garray.NewStrArray()
func customHandler1(ctx context.Context, input *glog.HandlerInput) {
arrayForHandlerTest1.Append(input.String(false))
- input.Next()
+ input.Next(ctx)
}
func TestLogger_SetHandlers1(t *testing.T) {
diff --git a/os/gres/gres_z_unit_test.go b/os/gres/gres_z_unit_test.go
index 2a5b424c7..b1dc65d82 100644
--- a/os/gres/gres_z_unit_test.go
+++ b/os/gres/gres_z_unit_test.go
@@ -12,7 +12,6 @@ import (
_ "github.com/gogf/gf/v2/os/gres/testdata/data"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gres"
@@ -23,7 +22,7 @@ import (
func Test_PackToGoFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
goFilePath = gfile.Temp(gtime.TimestampNanoStr(), "testdata.go")
pkgName = "testdata"
err = gres.PackToGoFile(srcPath, goFilePath, pkgName)
@@ -36,7 +35,7 @@ func Test_PackToGoFile(t *testing.T) {
func Test_Pack(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
data, err = gres.Pack(srcPath)
)
t.AssertNil(err)
@@ -51,7 +50,7 @@ func Test_Pack(t *testing.T) {
func Test_PackToFile(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
dstPath = gfile.Temp(gtime.TimestampNanoStr())
err = gres.PackToFile(srcPath, dstPath)
)
@@ -69,7 +68,7 @@ func Test_PackToFile(t *testing.T) {
func Test_PackMulti(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
goFilePath = gfile.Temp(gtime.TimestampNanoStr(), "data.go")
pkgName = "data"
array, err = gfile.ScanDir(srcPath, "*", false)
@@ -84,7 +83,7 @@ func Test_PackMulti(t *testing.T) {
func Test_PackWithPrefix1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
goFilePath = gfile.Temp(gtime.TimestampNanoStr(), "testdata.go")
pkgName = "testdata"
err = gres.PackToGoFile(srcPath, goFilePath, pkgName, "www/gf-site/test")
@@ -97,7 +96,7 @@ func Test_PackWithPrefix1(t *testing.T) {
func Test_PackWithPrefix2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- srcPath = gdebug.TestDataPath("files")
+ srcPath = gtest.DataPath("files")
goFilePath = gfile.Temp(gtime.TimestampNanoStr(), "testdata.go")
pkgName = "testdata"
err = gres.PackToGoFile(srcPath, goFilePath, pkgName, "/var/www/gf-site/test")
diff --git a/os/gview/gview_z_unit_config_test.go b/os/gview/gview_z_unit_config_test.go
index f8c125b5e..f138203c0 100644
--- a/os/gview/gview_z_unit_config_test.go
+++ b/os/gview/gview_z_unit_config_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gview"
"github.com/gogf/gf/v2/test/gtest"
@@ -19,7 +18,7 @@ import (
func Test_Config(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
config := gview.Config{
- Paths: []string{gdebug.TestDataPath("config")},
+ Paths: []string{gtest.DataPath("config")},
Data: g.Map{
"name": "gf",
},
@@ -46,7 +45,7 @@ func Test_ConfigWithMap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
view := gview.New()
err := view.SetConfigWithMap(g.Map{
- "Paths": []string{gdebug.TestDataPath("config")},
+ "Paths": []string{gtest.DataPath("config")},
"DefaultFile": "test.html",
"Delimiters": []string{"${", "}"},
"Data": g.Map{
diff --git a/os/gview/gview_z_unit_feature_encode_test.go b/os/gview/gview_z_unit_feature_encode_test.go
index 55a3665ba..786f98a4b 100644
--- a/os/gview/gview_z_unit_feature_encode_test.go
+++ b/os/gview/gview_z_unit_feature_encode_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gview"
@@ -20,7 +19,7 @@ import (
func Test_Encode_Parse(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
- v.SetPath(gdebug.TestDataPath("tpl"))
+ v.SetPath(gtest.DataPath("tpl"))
v.SetAutoEncode(true)
result, err := v.Parse(context.TODO(), "encode.tpl", g.Map{
"title": "my title",
@@ -33,7 +32,7 @@ func Test_Encode_Parse(t *testing.T) {
func Test_Encode_ParseContent(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
- tplContent := gfile.GetContents(gdebug.TestDataPath("tpl", "encode.tpl"))
+ tplContent := gfile.GetContents(gtest.DataPath("tpl", "encode.tpl"))
v.SetAutoEncode(true)
result, err := v.ParseContent(context.TODO(), tplContent, g.Map{
"title": "my title",
diff --git a/os/gview/gview_z_unit_i18n_test.go b/os/gview/gview_z_unit_i18n_test.go
index 5b22b8250..dfd1e4026 100644
--- a/os/gview/gview_z_unit_i18n_test.go
+++ b/os/gview/gview_z_unit_i18n_test.go
@@ -23,7 +23,7 @@ func Test_I18n(t *testing.T) {
expect2 := `john says "こんにちは世界!"`
expect3 := `john says "{#hello}{#world}!"`
- g.I18n().SetPath(gdebug.TestDataPath("i18n"))
+ g.I18n().SetPath(gtest.DataPath("i18n"))
g.I18n().SetLanguage("zh-CN")
result1, err := g.View().ParseContent(context.TODO(), content, g.Map{
diff --git a/os/gview/gview_z_unit_test.go b/os/gview/gview_z_unit_test.go
index 10468557d..cfead2fc9 100644
--- a/os/gview/gview_z_unit_test.go
+++ b/os/gview/gview_z_unit_test.go
@@ -14,7 +14,6 @@ import (
"testing"
"time"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/encoding/ghtml"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
@@ -534,7 +533,7 @@ func Test_BuildInFuncDivide(t *testing.T) {
func Test_Issue1416(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
- err := v.SetPath(gdebug.TestDataPath("issue1416"))
+ err := v.SetPath(gtest.DataPath("issue1416"))
t.AssertNil(err)
r, err := v.ParseOption(context.TODO(), gview.Option{
File: "gview.tpl",
diff --git a/test/gtest/gtest_util.go b/test/gtest/gtest_util.go
index 6ecb4bd9a..8cc511770 100644
--- a/test/gtest/gtest_util.go
+++ b/test/gtest/gtest_util.go
@@ -345,11 +345,11 @@ func AssertNil(value interface{}) {
AssertNE(value, nil)
}
-// TestDataPath retrieves and returns the testdata path of current package,
+// DataPath retrieves and returns the testdata path of current package,
// which is used for unit testing cases only.
// The optional parameter `names` specifies the sub-folders/sub-files,
// which will be joined with current system separator and returned with the path.
-func TestDataPath(names ...string) string {
+func DataPath(names ...string) string {
_, path, _ := gdebug.CallerWithFilter([]string{pathFilterKey})
path = filepath.Dir(path) + string(filepath.Separator) + "testdata"
for _, name := range names {
@@ -358,9 +358,9 @@ func TestDataPath(names ...string) string {
return path
}
-// TestDataContent retrieves and returns the file content for specified testdata path of current package
-func TestDataContent(names ...string) string {
- path := TestDataPath(names...)
+// DataContent retrieves and returns the file content for specified testdata path of current package
+func DataContent(names ...string) string {
+ path := DataPath(names...)
if path != "" {
data, err := ioutil.ReadFile(path)
if err == nil {
diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go
index 9041886d3..415f47634 100644
--- a/util/gconv/gconv_slice_any.go
+++ b/util/gconv/gconv_slice_any.go
@@ -9,6 +9,7 @@ package gconv
import (
"reflect"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
)
@@ -62,9 +63,13 @@ func Interfaces(any interface{}) []interface{} {
array[k] = v
}
case []uint8:
- array = make([]interface{}, len(value))
- for k, v := range value {
- array[k] = v
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]interface{}, len(value))
+ for k, v := range value {
+ array[k] = v
+ }
}
case []uint16:
array = make([]interface{}, len(value))
diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go
index 4816d7044..3d7b49949 100644
--- a/util/gconv/gconv_slice_float.go
+++ b/util/gconv/gconv_slice_float.go
@@ -9,6 +9,7 @@ package gconv
import (
"reflect"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
)
@@ -81,9 +82,13 @@ func Float32s(any interface{}) []float32 {
array = append(array, Float32(v))
}
case []uint8:
- array = make([]float32, len(value))
- for k, v := range value {
- array[k] = Float32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]float32, len(value))
+ for k, v := range value {
+ array[k] = Float32(v)
+ }
}
case []uint16:
array = make([]float32, len(value))
@@ -201,9 +206,13 @@ func Float64s(any interface{}) []float64 {
array = append(array, Float64(v))
}
case []uint8:
- array = make([]float64, len(value))
- for k, v := range value {
- array[k] = Float64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]float64, len(value))
+ for k, v := range value {
+ array[k] = Float64(v)
+ }
}
case []uint16:
array = make([]float64, len(value))
diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go
index 1ca0ab4d6..f28e7fd18 100644
--- a/util/gconv/gconv_slice_int.go
+++ b/util/gconv/gconv_slice_int.go
@@ -9,6 +9,7 @@ package gconv
import (
"reflect"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
)
@@ -69,9 +70,13 @@ func Ints(any interface{}) []int {
array[k] = int(v)
}
case []uint8:
- array = make([]int, len(value))
- for k, v := range value {
- array[k] = int(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int, len(value))
+ for k, v := range value {
+ array[k] = int(v)
+ }
}
case []uint16:
array = make([]int, len(value))
@@ -194,9 +199,13 @@ func Int32s(any interface{}) []int32 {
array[k] = int32(v)
}
case []uint8:
- array = make([]int32, len(value))
- for k, v := range value {
- array[k] = int32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int32, len(value))
+ for k, v := range value {
+ array[k] = int32(v)
+ }
}
case []uint16:
array = make([]int32, len(value))
@@ -319,9 +328,13 @@ func Int64s(any interface{}) []int64 {
array[k] = int64(v)
}
case []uint8:
- array = make([]int64, len(value))
- for k, v := range value {
- array[k] = int64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int64, len(value))
+ for k, v := range value {
+ array[k] = int64(v)
+ }
}
case []uint16:
array = make([]int64, len(value))
diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go
index 31e766f52..c085d271f 100644
--- a/util/gconv/gconv_slice_str.go
+++ b/util/gconv/gconv_slice_str.go
@@ -9,6 +9,7 @@ package gconv
import (
"reflect"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
)
@@ -57,9 +58,13 @@ func Strings(any interface{}) []string {
array[k] = String(v)
}
case []uint8:
- array = make([]string, len(value))
- for k, v := range value {
- array[k] = String(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]string, len(value))
+ for k, v := range value {
+ array[k] = String(v)
+ }
}
case []uint16:
array = make([]string, len(value))
diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go
index e77dfc2c0..a1ffa7617 100644
--- a/util/gconv/gconv_slice_uint.go
+++ b/util/gconv/gconv_slice_uint.go
@@ -10,6 +10,7 @@ import (
"reflect"
"strings"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
)
@@ -76,9 +77,13 @@ func Uints(any interface{}) []uint {
case []uint:
array = value
case []uint8:
- array = make([]uint, len(value))
- for k, v := range value {
- array[k] = uint(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint, len(value))
+ for k, v := range value {
+ array[k] = uint(v)
+ }
}
case []uint16:
array = make([]uint, len(value))
@@ -210,9 +215,13 @@ func Uint32s(any interface{}) []uint32 {
array[k] = uint32(v)
}
case []uint8:
- array = make([]uint32, len(value))
- for k, v := range value {
- array[k] = uint32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint32, len(value))
+ for k, v := range value {
+ array[k] = uint32(v)
+ }
}
case []uint16:
array = make([]uint32, len(value))
@@ -341,9 +350,13 @@ func Uint64s(any interface{}) []uint64 {
array[k] = uint64(v)
}
case []uint8:
- array = make([]uint64, len(value))
- for k, v := range value {
- array[k] = uint64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint64, len(value))
+ for k, v := range value {
+ array[k] = uint64(v)
+ }
}
case []uint16:
array = make([]uint64, len(value))
diff --git a/util/grand/grand.go b/util/grand/grand.go
index 77478cee4..34554a2f5 100644
--- a/util/grand/grand.go
+++ b/util/grand/grand.go
@@ -60,16 +60,12 @@ func N(min, max int) int {
return min
}
if min >= 0 {
- // Because Intn dose not support negative number,
- // so we should first shift the value to left,
- // then call Intn to produce the random number,
- // and finally shift the result back to right.
- return Intn(max-(min-0)+1) + (min - 0)
+ return Intn(max-min+1) + min
}
if min < 0 {
- // Because Intn dose not support negative number,
+ // As `Intn` dose not support negative number,
// so we should first shift the value to right,
- // then call Intn to produce the random number,
+ // then call `Intn` to produce the random number,
// and finally shift the result back to left.
return Intn(max+(0-min)+1) - (0 - min)
}
diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go
index ba252d8c6..c489f710e 100644
--- a/util/gvalid/gvalid_error.go
+++ b/util/gvalid/gvalid_error.go
@@ -9,6 +9,7 @@ package gvalid
import (
"strings"
+ "github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
@@ -32,7 +33,7 @@ type Error interface {
// validationError is the validation error for validation result.
type validationError struct {
code gcode.Code // Error code.
- rules []fieldRule // Rules by sequence, which is used for keeping error sequence.
+ rules []fieldRule // Rules by sequence, which is used for keeping error sequence only.
errors map[string]map[string]error // Error map:map[field]map[rule]message
firstKey string // The first error rule key(empty in default).
firstItem map[string]error // The first error rule value(nil in default).
@@ -52,6 +53,16 @@ func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap ma
}
fieldRuleErrorMap[field] = ruleErrorMap
}
+ // Filter repeated sequence rules.
+ var ruleNameSet = gset.NewStrSet()
+ for i := 0; i < len(rules); {
+ if !ruleNameSet.AddIfNotExist(rules[i].Name) {
+ // Delete repeated rule.
+ rules = append(rules[:i], rules[i+1:]...)
+ continue
+ }
+ i++
+ }
return &validationError{
code: code,
rules: rules,
diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go
index a625d6f66..944774f3c 100644
--- a/util/gvalid/gvalid_validator.go
+++ b/util/gvalid/gvalid_validator.go
@@ -19,15 +19,15 @@ import (
// Validator is the validation manager for chaining operations.
type Validator struct {
- i18nManager *gi18n.Manager // I18n manager for error message translation.
- data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
- assoc interface{} // Associated data, which is usually a map, for union validation.
- 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`.
- bail bool // Stop validation after the first validation error.
- caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
+ i18nManager *gi18n.Manager // I18n manager for error message translation.
+ data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
+ assoc interface{} // Associated data, which is usually a map, for union validation.
+ 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.
+ useAssocInsteadOfObjectAttributes bool // Using `assoc` as its validation source instead of attribute values from `Object`.
+ bail bool // Stop validation after the first validation error.
+ caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
}
// New creates and returns a new Validator.
@@ -125,14 +125,14 @@ func (v *Validator) Data(data interface{}) *Validator {
// Assoc is a chaining operation function, which sets associated validation data for current operation.
// The optional parameter `assoc` is usually type of map, which specifies the parameter map used in union validation.
-// Calling this function with `assoc` also sets `useDataInsteadOfObjectAttributes` true
+// Calling this function with `assoc` also sets `useAssocInsteadOfObjectAttributes` true
func (v *Validator) Assoc(assoc interface{}) *Validator {
if assoc == nil {
return v
}
newValidator := v.Clone()
newValidator.assoc = assoc
- newValidator.useDataInsteadOfObjectAttributes = true
+ newValidator.useAssocInsteadOfObjectAttributes = true
return newValidator
}
diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go
index e4cee548d..38d570b34 100644
--- a/util/gvalid/gvalid_validator_check_struct.go
+++ b/util/gvalid/gvalid_validator_check_struct.go
@@ -109,13 +109,13 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
return nil
}
// Input parameter map handling.
- if v.assoc == nil || !v.useDataInsteadOfObjectAttributes {
+ if v.assoc == nil || !v.useAssocInsteadOfObjectAttributes {
inputParamMap = make(map[string]interface{})
} else {
inputParamMap = gconv.Map(v.assoc)
}
// Checks and extends the parameters map with struct alias tag.
- if !v.useDataInsteadOfObjectAttributes {
+ if !v.useAssocInsteadOfObjectAttributes {
for nameOrTag, field := range fieldMap {
inputParamMap[nameOrTag] = field.Value.Interface()
if nameOrTag != field.Name() {
@@ -147,7 +147,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// It here extends the params map using alias names.
// Note that the variable `name` might be alias name or attribute name.
if _, ok := inputParamMap[name]; !ok {
- if !v.useDataInsteadOfObjectAttributes {
+ if !v.useAssocInsteadOfObjectAttributes {
inputParamMap[name] = field.Value.Interface()
} else {
if name != fieldName {
@@ -226,7 +226,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// Temporary variable for value.
var value interface{}
- // It checks the struct recursively if its attribute is an embedded struct.
+ // It checks the struct recursively if its attribute is a struct/struct slice.
for _, field := range fieldMap {
// No validation interface implements check.
if _, ok := field.Value.Interface().(iNoValidation); ok {
diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go
index ae6808791..bd69f9f77 100644
--- a/util/gvalid/gvalid_validator_check_value.go
+++ b/util/gvalid/gvalid_validator_check_value.go
@@ -15,10 +15,10 @@ import (
"time"
"github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/net/gipv4"
"github.com/gogf/gf/v2/net/gipv6"
"github.com/gogf/gf/v2/os/gtime"
@@ -538,11 +538,11 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBui
}
type doCheckValueRecursivelyInput struct {
- Value interface{}
- Type reflect.Type
- OriginKind reflect.Kind
- ErrorMaps map[string]map[string]error
- ResultSequenceRules *[]fieldRule
+ Value interface{} // Value to be validated.
+ Type reflect.Type // Struct/map/slice type which to be recursively validated.
+ OriginKind reflect.Kind // Struct/map/slice kind to be asserted in following switch case.
+ ErrorMaps map[string]map[string]error // The validated failed error map.
+ ResultSequenceRules *[]fieldRule // The validated failed rule in sequence.
}
func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValueRecursivelyInput) {
@@ -563,13 +563,16 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
}
case reflect.Map:
- var dataMap = gconv.Map(in.Value)
+ var (
+ dataMap = gconv.Map(in.Value)
+ mapTypeElem = in.Type.Elem()
+ mapTypeKind = mapTypeElem.Kind()
+ )
for _, item := range dataMap {
- originTypeAndKind := reflection.OriginTypeAndKind(item)
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
- Type: originTypeAndKind.InputType,
- OriginKind: originTypeAndKind.OriginKind,
+ Type: mapTypeElem,
+ OriginKind: mapTypeKind,
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
@@ -580,16 +583,20 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
}
case reflect.Slice, reflect.Array:
- array := gconv.Interfaces(in.Value)
+ var array []interface{}
+ if gjson.Valid(in.Value) {
+ array = gconv.Interfaces(gconv.Bytes(in.Value))
+ } else {
+ array = gconv.Interfaces(in.Value)
+ }
if len(array) == 0 {
return
}
for _, item := range array {
- originTypeAndKind := reflection.OriginTypeAndKind(item)
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
- Type: originTypeAndKind.InputType,
- OriginKind: originTypeAndKind.OriginKind,
+ Type: in.Type.Elem(),
+ OriginKind: in.Type.Elem().Kind(),
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
diff --git a/util/gvalid/gvalid_z_unit_feature_i18n_test.go b/util/gvalid/gvalid_z_unit_feature_i18n_test.go
index 2554f1837..d43d4c566 100644
--- a/util/gvalid/gvalid_z_unit_feature_i18n_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_i18n_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/i18n/gi18n"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gvalid"
@@ -19,7 +18,7 @@ import (
func TestValidator_I18n(t *testing.T) {
var (
err gvalid.Error
- i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")})
+ i18nManager = gi18n.New(gi18n.Options{Path: gtest.DataPath("i18n")})
ctxCn = gi18n.WithLanguage(context.TODO(), "cn")
validator = gvalid.New().I18n(i18nManager)
)
diff --git a/util/gvalid/gvalid_z_unit_feature_recursive_test.go b/util/gvalid/gvalid_z_unit_feature_recursive_test.go
index b441f7c70..269d2eab4 100755
--- a/util/gvalid/gvalid_z_unit_feature_recursive_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_recursive_test.go
@@ -214,3 +214,47 @@ func Test_CheckMap_Recursive_SliceStruct(t *testing.T) {
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `4` must be the same as field Pass1"})
})
}
+
+func Test_CheckStruct_Recursively_SliceAttribute(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students []Student `v:"required"`
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ "students": `[{"age":2}, {"name":"jack", "age":4}]`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, `Student Name is required`)
+ })
+}
+
+func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students map[string]Student `v:"required"`
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ "students": `{"john":{"age":18}}`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, `Student Name is required`)
+ })
+}