diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index d55885f5e..386047350 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -60,6 +60,7 @@ const ( OrmTagForWith = "with" OrmTagForWithWhere = "where" OrmTagForWithOrder = "order" + OrmTagForDto = "dto" ) var ( @@ -70,9 +71,25 @@ var ( structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...) ) -// isForDaoModel checks and returns whether given type is for dao model. -func isForDaoModel(t reflect.Type) bool { - return gstr.HasSuffix(t.String(), modelForDaoSuffix) +// isDtoStruct checks and returns whether given type is a DTO struct. +func isDtoStruct(object interface{}) bool { + // It checks by struct name like "XxxForDao", to be compatible with old version. + // TODO remove this compatible codes in future. + reflectType := reflect.TypeOf(object) + if gstr.HasSuffix(reflectType.String(), modelForDaoSuffix) { + return true + } + // It checks by struct meta for DTO struct in version. + if ormTag := gmeta.Get(object, OrmTagForStruct); !ormTag.IsEmpty() { + match, _ := gregex.MatchString( + fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForDto), + ormTag.String(), + ) + if len(match) > 1 { + return gconv.Bool(match[1]) + } + } + return false } // getTableNameFromOrmTag retrieves and returns the table name from struct object. @@ -424,7 +441,7 @@ func formatWhereHolder(db DB, in formatWhereHolderInput) (newWhere string, newAr case reflect.Struct: // If the `where` parameter is defined like `xxxForDao`, it then adds `OmitNil` option for this condition, // which will filter all nil parameters in `where`. - if isForDaoModel(reflect.TypeOf(in.Where)) { + if isDtoStruct(in.Where) { in.OmitNil = true } // If `where` struct implements iIterator interface, diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 0333c2445..7c8679e52 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -73,10 +73,10 @@ func (m *Model) Data(data ...interface{}) *Model { switch reflectInfo.OriginKind { case reflect.Slice, reflect.Array: if reflectInfo.OriginValue.Len() > 0 { - // If the `data` parameter is defined like `xxxForDao`, + // If the `data` parameter is a DTO struct, // it then adds `OmitNilData` option for this condition, // which will filter all nil parameters in `data`. - if isForDaoModel(reflectInfo.OriginValue.Index(0).Elem().Type()) { + if isDtoStruct(reflectInfo.OriginValue.Index(0).Interface()) { model = model.OmitNilData() model.option |= optionOmitNilDataInternal } @@ -88,10 +88,10 @@ func (m *Model) Data(data ...interface{}) *Model { model.data = list case reflect.Struct: - // If the `data` parameter is defined like `xxxForDao`, + // If the `data` parameter is a DTO struct, // it then adds `OmitNilData` option for this condition, // which will filter all nil parameters in `data`. - if isForDaoModel(reflect.TypeOf(value)) { + if isDtoStruct(value) { model = model.OmitNilData() } if v, ok := data[0].(iInterfaces); ok { diff --git a/database/gdb/gdb_z_mysql_feature_model_for_dao_test.go b/database/gdb/gdb_z_mysql_feature_model_dto_test.go similarity index 52% rename from database/gdb/gdb_z_mysql_feature_model_for_dao_test.go rename to database/gdb/gdb_z_mysql_feature_model_dto_test.go index 5d4dc7934..e722595fd 100644 --- a/database/gdb/gdb_z_mysql_feature_model_for_dao_test.go +++ b/database/gdb/gdb_z_mysql_feature_model_dto_test.go @@ -13,6 +13,144 @@ import ( "github.com/gogf/gf/v2/test/gtest" ) +func Test_Model_Insert_Data_DTO(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + type User struct { + g.Meta `orm:"dto:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + CreateTime interface{} + } + data := User{ + Id: 1, + Passport: "user_1", + Password: "pass_1", + } + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 1) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one[`id`], `1`) + t.Assert(one[`passport`], `user_1`) + t.Assert(one[`password`], `pass_1`) + t.Assert(one[`nickname`], ``) + t.Assert(one[`create_time`], ``) + }) +} + +func Test_Model_Insert_Data_LIst_DTO(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + type User struct { + g.Meta `orm:"dto:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + CreateTime interface{} + } + data := g.Slice{ + User{ + Id: 1, + Passport: "user_1", + Password: "pass_1", + }, + User{ + Id: 2, + Passport: "user_2", + Password: "pass_2", + }, + } + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 2) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one[`id`], `1`) + t.Assert(one[`passport`], `user_1`) + t.Assert(one[`password`], `pass_1`) + t.Assert(one[`nickname`], ``) + t.Assert(one[`create_time`], ``) + + one, err = db.Model(table).WherePri(2).One() + t.AssertNil(err) + t.Assert(one[`id`], `2`) + t.Assert(one[`passport`], `user_2`) + t.Assert(one[`password`], `pass_2`) + t.Assert(one[`nickname`], ``) + t.Assert(one[`create_time`], ``) + }) +} + +func Test_Model_Update_Data_DTO(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + type User struct { + g.Meta `orm:"dto:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + CreateTime interface{} + } + data := User{ + Id: 1, + Passport: "user_100", + Password: "pass_100", + } + _, err := db.Model(table).Data(data).WherePri(1).Update() + t.AssertNil(err) + + one, err := db.Model(table).WherePri(1).One() + t.AssertNil(err) + t.Assert(one[`id`], `1`) + t.Assert(one[`passport`], `user_100`) + t.Assert(one[`password`], `pass_100`) + t.Assert(one[`nickname`], `name_1`) + }) +} + +func Test_Model_Where_DTO(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + type User struct { + g.Meta `orm:"dto:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + CreateTime interface{} + } + where := User{ + Id: 1, + Passport: "user_1", + Password: "pass_1", + } + one, err := db.Model(table).Where(where).One() + t.AssertNil(err) + t.Assert(one[`id`], `1`) + t.Assert(one[`passport`], `user_1`) + t.Assert(one[`password`], `pass_1`) + t.Assert(one[`nickname`], `name_1`) + }) +} + func Test_Model_Insert_Data_ForDao(t *testing.T) { table := createTable() defer dropTable(table) diff --git a/os/gres/gres.go b/os/gres/gres.go index 123dd7e0a..7bb9e815d 100644 --- a/os/gres/gres.go +++ b/os/gres/gres.go @@ -78,8 +78,8 @@ func ScanDirFile(path string, pattern string, recursive ...bool) []*File { } // Export exports and saves specified path `src` and all its sub files to specified system path `dst` recursively. -func Export(src, dst string) error { - return defaultResource.Export(src, dst) +func Export(src, dst string, option ...ExportOption) error { + return defaultResource.Export(src, dst, option...) } // Dump prints the files of the default resource object. diff --git a/os/gres/gres_resource.go b/os/gres/gres_resource.go index 74a10686a..1a925d444 100644 --- a/os/gres/gres_resource.go +++ b/os/gres/gres_resource.go @@ -17,6 +17,7 @@ import ( "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" ) type Resource struct { @@ -224,15 +225,33 @@ func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFi return files } -// Export exports and saves specified path `src` and all its sub files to specified system path `dst` recursively. -func (r *Resource) Export(src, dst string) error { +// ExportOption is the option for function Export. +type ExportOption struct { + RemovePrefix string // Remove the prefix of file name from resource. +} + +// Export exports and saves specified path `srcPath` and all its sub files to specified system path `dstPath` recursively. +func (r *Resource) Export(src, dst string, option ...ExportOption) error { var ( - err error - path string - files = r.doScanDir(src, "*", true, false) + err error + name string + path string + exportOption ExportOption + files = r.doScanDir(src, "*", true, false) ) + if len(option) > 0 { + exportOption = option[0] + } for _, file := range files { - path = gfile.Join(dst, file.Name()) + name = file.Name() + if exportOption.RemovePrefix != "" { + name = gstr.TrimLeftStr(name, exportOption.RemovePrefix) + } + name = gstr.Trim(name, `\/`) + if name == "" { + continue + } + path = gfile.Join(dst, name) if file.FileInfo().IsDir() { err = gfile.Mkdir(path) } else { diff --git a/os/gres/gres_z_unit_test.go b/os/gres/gres_z_unit_test.go index 0e3e514fb..6ace54c95 100644 --- a/os/gres/gres_z_unit_test.go +++ b/os/gres/gres_z_unit_test.go @@ -249,4 +249,22 @@ func Test_Export(t *testing.T) { name := `template/index.html` t.Assert(gfile.GetContents(gfile.Join(dst, name)), gres.GetContent(name)) }) + gtest.C(t, func(t *gtest.T) { + var ( + src = `template` + dst = gfile.TempDir(gtime.TimestampNanoStr()) + err = gres.Export(src, dst, gres.ExportOption{ + RemovePrefix: `template`, + }) + ) + defer gfile.Remove(dst) + t.AssertNil(err) + files, err := gfile.ScanDir(dst, "*", true) + t.AssertNil(err) + t.Assert(len(files), 13) + + nameInRes := `template/index.html` + nameInSys := `index.html` + t.Assert(gfile.GetContents(gfile.Join(dst, nameInSys)), gres.GetContent(nameInRes)) + }) }