From 7cd672d22c8e6fecc64d938c7555c4db20409523 Mon Sep 17 00:00:00 2001 From: Hunk Zhu Date: Thu, 5 Dec 2024 14:44:21 +0800 Subject: [PATCH] feat(cmd/gf): add interface functions generating for embedded struct of logic struct in command `gen service` (#3802) --- .../cmd/cmd_z_unit_gen_service_test.go | 2 + cmd/gf/internal/cmd/genservice/genservice.go | 63 +++++++++-- .../cmd/genservice/genservice_ast_parse.go | 102 +++++++++++++++++- .../cmd/genservice/genservice_calculate.go | 75 +++++++++++++ .../testdata/genservice/logic/base/base.go | 17 +++ .../genservice/logic/base/base_destory.go | 13 +++ .../testdata/genservice/logic/base/sub/sub.go | 14 +++ .../testdata/genservice/logic/logic_expect.go | 1 + .../genservice/logic/user/supper_vip_user.go | 27 +++++ .../testdata/genservice/logic/user/user.go | 2 + .../genservice/logic/user/vip_user.go | 29 +++++ .../cmd/testdata/genservice/service/base.go | 32 ++++++ .../cmd/testdata/genservice/service/user.go | 68 +++++++++++- 13 files changed, 436 insertions(+), 9 deletions(-) create mode 100644 cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go create mode 100644 cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go create mode 100644 cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go create mode 100644 cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go create mode 100644 cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go create mode 100644 cmd/gf/internal/cmd/testdata/genservice/service/base.go diff --git a/cmd/gf/internal/cmd/cmd_z_unit_gen_service_test.go b/cmd/gf/internal/cmd/cmd_z_unit_gen_service_test.go index e2362bbb5..8aeff4bb5 100644 --- a/cmd/gf/internal/cmd/cmd_z_unit_gen_service_test.go +++ b/cmd/gf/internal/cmd/cmd_z_unit_gen_service_test.go @@ -57,6 +57,7 @@ func Test_Gen_Service_Default(t *testing.T) { t.AssertNil(err) t.Assert(files, []string{ dstFolder + filepath.FromSlash("/article.go"), + dstFolder + filepath.FromSlash("/base.go"), dstFolder + filepath.FromSlash("/delivery.go"), dstFolder + filepath.FromSlash("/user.go"), }) @@ -65,6 +66,7 @@ func Test_Gen_Service_Default(t *testing.T) { testPath := gtest.DataPath("genservice", "service") expectFiles := []string{ testPath + filepath.FromSlash("/article.go"), + testPath + filepath.FromSlash("/base.go"), testPath + filepath.FromSlash("/delivery.go"), testPath + filepath.FromSlash("/user.go"), } diff --git a/cmd/gf/internal/cmd/genservice/genservice.go b/cmd/gf/internal/cmd/genservice/genservice.go index e02ed91ea..568782a6c 100644 --- a/cmd/gf/internal/cmd/genservice/genservice.go +++ b/cmd/gf/internal/cmd/genservice/genservice.go @@ -94,6 +94,20 @@ const ( genServiceFileLockSeconds = 10 ) +type fileInfo struct { + PkgItems []pkgItem + FuncItems []funcItem +} + +type folderInfo struct { + SrcPackageName string + SrcImportedPackages *garray.SortedStrArray + SrcStructFunctions *gmap.ListMap + DstFilePath string + + FileInfos []*fileInfo +} + func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) { in.SrcFolder = filepath.ToSlash(in.SrcFolder) in.SrcFolder = gstr.TrimRight(in.SrcFolder, `/`) @@ -163,7 +177,12 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe return nil, err } // it will use goroutine to generate service files for each package. - var wg = sync.WaitGroup{} + var ( + folderInfos []folderInfo + wg = sync.WaitGroup{} + allStructItems = make(map[string][]string) + ) + for _, srcFolderPath := range srcFolderPaths { if !gfile.IsDir(srcFolderPath) { continue @@ -175,7 +194,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe if len(files) == 0 { continue } - // Parse single logic package folder. + var ( srcPackageName = gfile.Basename(srcFolderPath) srcImportedPackages = garray.NewSortedStrArray().SetUnique(true) @@ -184,14 +203,46 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go", ) ) - generatedDstFilePathSet.Add(dstFilePath) - // if it were to use goroutine, - // it would cause the order of the generated functions in the file to be disordered. + + folder := folderInfo{ + SrcPackageName: srcPackageName, + SrcImportedPackages: srcImportedPackages, + SrcStructFunctions: srcStructFunctions, + DstFilePath: dstFilePath, + } + for _, file := range files { - pkgItems, funcItems, err := c.parseItemsInSrc(file) + pkgItems, structItems, funcItems, err := c.parseItemsInSrc(file) if err != nil { return nil, err } + for k, v := range structItems { + allStructItems[k] = v + } + folder.FileInfos = append(folder.FileInfos, &fileInfo{ + PkgItems: pkgItems, + FuncItems: funcItems, + }) + } + + folderInfos = append(folderInfos, folder) + } + + folderInfos = c.calculateStructEmbeddedFuncInfos(folderInfos, allStructItems) + + for _, folder := range folderInfos { + // Parse single logic package folder. + var ( + srcPackageName = folder.SrcPackageName + srcImportedPackages = folder.SrcImportedPackages + srcStructFunctions = folder.SrcStructFunctions + dstFilePath = folder.DstFilePath + ) + generatedDstFilePathSet.Add(dstFilePath) + // if it were to use goroutine, + // it would cause the order of the generated functions in the file to be disordered. + for _, file := range folder.FileInfos { + pkgItems, funcItems := file.PkgItems, file.FuncItems // Calculate imported packages for service generating. err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages) diff --git a/cmd/gf/internal/cmd/genservice/genservice_ast_parse.go b/cmd/gf/internal/cmd/genservice/genservice_ast_parse.go index c4205cc58..974ec7e6c 100644 --- a/cmd/gf/internal/cmd/genservice/genservice_ast_parse.go +++ b/cmd/gf/internal/cmd/genservice/genservice_ast_parse.go @@ -10,8 +10,10 @@ import ( "go/ast" "go/parser" "go/token" + "strings" "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gstructs" "github.com/gogf/gf/v2/text/gstr" ) @@ -32,7 +34,7 @@ type funcItem struct { // parseItemsInSrc parses the pkgItem and funcItem from the specified file. // It can't skip the private methods. // It can't skip the imported packages of import alias equal to `_`. -func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, funcItems []funcItem, err error) { +func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, structItems map[string][]string, funcItems []funcItem, err error) { var ( fileContent = gfile.GetContents(filePath) fileSet = token.NewFileSet() @@ -43,11 +45,107 @@ func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, funcI return } + structItems = make(map[string][]string) + pkg := node.Name.Name + pkgAliasMap := make(map[string]string) ast.Inspect(node, func(n ast.Node) bool { switch x := n.(type) { case *ast.ImportSpec: // parse the imported packages. - pkgItems = append(pkgItems, c.parseImportPackages(x)) + pkgItem := c.parseImportPackages(x) + pkgItems = append(pkgItems, pkgItem) + pkgPath := strings.Trim(pkgItem.Path, "\"") + pkgPath = strings.ReplaceAll(pkgPath, "\\", "/") + tmp := strings.Split(pkgPath, "/") + srcPkg := tmp[len(tmp)-1] + if srcPkg != pkgItem.Alias { + pkgAliasMap[pkgItem.Alias] = srcPkg + } + case *ast.TypeSpec: // type define + switch xType := x.Type.(type) { + case *ast.StructType: // define struct + // parse the struct declaration. + var structName = pkg + "." + x.Name.Name + var structEmbeddedStruct []string + for _, field := range xType.Fields.List { + if len(field.Names) > 0 || field.Tag == nil { // not anonymous field + continue + } + + tagValue := strings.Trim(field.Tag.Value, "`") + tagValue = strings.TrimSpace(tagValue) + if len(tagValue) == 0 { // not set tag + continue + } + tags := gstructs.ParseTag(tagValue) + + if v, ok := tags["gen"]; !ok || v != "extend" { + continue + } + + var embeddedStruct string + switch v := field.Type.(type) { + case *ast.Ident: + if embeddedStruct, err = c.astExprToString(v); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + case *ast.StarExpr: + if embeddedStruct, err = c.astExprToString(v.X); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + case *ast.SelectorExpr: + var pkg string + if pkg, err = c.astExprToString(v.X); err != nil { + embeddedStruct = "" + break + } + if v, ok := pkgAliasMap[pkg]; ok { + pkg = v + } + if embeddedStruct, err = c.astExprToString(v.Sel); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + } + + if embeddedStruct == "" { + continue + } + structEmbeddedStruct = append(structEmbeddedStruct, embeddedStruct) + + } + if len(structEmbeddedStruct) > 0 { + structItems[structName] = structEmbeddedStruct + } + case *ast.Ident: // define ident + var ( + structName = pkg + "." + x.Name.Name + typeName = pkg + "." + xType.Name + ) + structItems[structName] = []string{typeName} + case *ast.SelectorExpr: // define selector + var ( + structName = pkg + "." + x.Name.Name + selecotrPkg string + typeName string + ) + if selecotrPkg, err = c.astExprToString(xType.X); err != nil { + break + } + if v, ok := pkgAliasMap[selecotrPkg]; ok { + selecotrPkg = v + } + if typeName, err = c.astExprToString(xType.Sel); err != nil { + break + } + typeName = selecotrPkg + "." + typeName + structItems[structName] = []string{typeName} + } case *ast.FuncDecl: // parse the function items. diff --git a/cmd/gf/internal/cmd/genservice/genservice_calculate.go b/cmd/gf/internal/cmd/genservice/genservice_calculate.go index 53553a040..15f575c04 100644 --- a/cmd/gf/internal/cmd/genservice/genservice_calculate.go +++ b/cmd/gf/internal/cmd/genservice/genservice_calculate.go @@ -150,3 +150,78 @@ func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr stri } return } + +func (c CGenService) getStructFuncItems(structName string, allStructItems map[string][]string, funcItemsWithoutEmbed map[string][]*funcItem) (funcItems []*funcItem) { + funcItemNameSet := map[string]struct{}{} + + if items, ok := funcItemsWithoutEmbed[structName]; ok { + funcItems = append(funcItems, items...) + for _, item := range items { + funcItemNameSet[item.MethodName] = struct{}{} + } + } + + embeddedStructNames, ok := allStructItems[structName] + if !ok { + return + } + + for _, embeddedStructName := range embeddedStructNames { + items := c.getStructFuncItems(embeddedStructName, allStructItems, funcItemsWithoutEmbed) + + for _, item := range items { + if _, ok := funcItemNameSet[item.MethodName]; ok { + continue + } + funcItemNameSet[item.MethodName] = struct{}{} + funcItems = append(funcItems, item) + } + } + + return +} + +func (c CGenService) calculateStructEmbeddedFuncInfos(folderInfos []folderInfo, allStructItems map[string][]string) (newFolerInfos []folderInfo) { + funcItemsWithoutEmbed := make(map[string][]*funcItem) + funcItemMap := make(map[string]*([]funcItem)) + funcItemsWithoutEmbedMap := make(map[string]*funcItem) + + newFolerInfos = append(newFolerInfos, folderInfos...) + + for _, folder := range newFolerInfos { + for k := range folder.FileInfos { + fi := folder.FileInfos[k] + for k := range fi.FuncItems { + item := &fi.FuncItems[k] + receiver := folder.SrcPackageName + "." + strings.ReplaceAll(item.Receiver, "*", "") + funcItemMap[receiver] = &fi.FuncItems + funcItemsWithoutEmbed[receiver] = append(funcItemsWithoutEmbed[receiver], item) + funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiver, item.MethodName)] = item + } + } + } + + for receiver, structItems := range allStructItems { + receiverName := strings.ReplaceAll(receiver, "*", "") + for _, structName := range structItems { + // Get the list of methods for the corresponding structName. + for _, funcItem := range c.getStructFuncItems(structName, allStructItems, funcItemsWithoutEmbed) { + if _, ok := funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiverName, funcItem.MethodName)]; ok { + continue + } + if funcItemsPtr, ok := funcItemMap[receiverName]; ok { + newFuncItem := *funcItem + newFuncItem.Receiver = getReceiverName(receiver) + (*funcItemsPtr) = append((*funcItemsPtr), newFuncItem) + } + } + } + } + + return +} + +func getReceiverName(receiver string) string { + ss := strings.Split(receiver, ".") + return ss[len(ss)-1] +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go b/cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go new file mode 100644 index 000000000..b125e658a --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go @@ -0,0 +1,17 @@ +package base + +type Base = sBase + +type sBase struct { + baseDestory `gen:"extend"` +} + +// sBase Init +func (*sBase) Init() { + +} + +// sBase Destory +func (*sBase) Destory() { + +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go b/cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go new file mode 100644 index 000000000..030051828 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go @@ -0,0 +1,13 @@ +package base + +type baseDestory struct{} + +// baseDestory Destory +func (baseDestory) Destory() { + +} + +// baseDestory BeforeDestory +func (baseDestory) BeforeDestory() { + +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go b/cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go new file mode 100644 index 000000000..447d141fa --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go @@ -0,0 +1,14 @@ +package sub + +type SubBase struct { +} + +// subbase init +func (*SubBase) Init() { + +} + +// subbase GetSubBase +func (*SubBase) GetSubBase() { + +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/logic_expect.go b/cmd/gf/internal/cmd/testdata/genservice/logic/logic_expect.go index 6efcac5e8..df1ebdae9 100644 --- a/cmd/gf/internal/cmd/testdata/genservice/logic/logic_expect.go +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/logic_expect.go @@ -6,6 +6,7 @@ package logic import ( _ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/article" + _ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base" _ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/delivery" _ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/user" ) diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go b/cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go new file mode 100644 index 000000000..bb498295b --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go @@ -0,0 +1,27 @@ +package user + +import ( + "context" + + "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service" +) + +func init() { + service.RegisterSuperVipUser(&sSuperVipUser{ + sVipUser: &sVipUser{}, + }) +} + +type sSuperVipUser struct { + *sVipUser `gen:"extend"` +} + +// Get supper vip user level +func (s sSuperVipUser) GetVipLevel(ctx context.Context) (vipLevel int, err error) { + return 1, nil +} + +// Set supper vip user level +func (s *sSuperVipUser) SetVipLevel(ctx context.Context, id int, vipLevel int) (err error) { + return nil +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/user/user.go b/cmd/gf/internal/cmd/testdata/genservice/logic/user/user.go index a95e22740..1f0b57baf 100644 --- a/cmd/gf/internal/cmd/testdata/genservice/logic/user/user.go +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/user/user.go @@ -9,6 +9,7 @@ package user import ( "context" + "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base" "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service" ) @@ -17,6 +18,7 @@ func init() { } type sUser struct { + base.Base } func New() *sUser { diff --git a/cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go b/cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go new file mode 100644 index 000000000..70516dfd8 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go @@ -0,0 +1,29 @@ +package user + +import ( + "context" + + bbb "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base" + "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service" +) + +func init() { + service.RegisterVipUser(&sVipUser{}) +} + +type mybase = bbb.Base + +type sVipUser struct { + sUser `gen:"extend"` + mybase `gen:"extend"` +} + +// Create creates a new vip user. +func (s *sVipUser) Create(ctx context.Context, name string, vipLevel int) (id int, err error) { + return 0, nil +} + +// Get vip user level +func (s *sVipUser) GetVipLevel() (vipLevel int, err error) { + return 1, nil +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/service/base.go b/cmd/gf/internal/cmd/testdata/genservice/service/base.go new file mode 100644 index 000000000..a9eede1ff --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/genservice/service/base.go @@ -0,0 +1,32 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +type ( + IBase interface { + // sBase Init + Init() + // sBase Destory + Destory() + // baseDestory BeforeDestory + BeforeDestory() + } +) + +var ( + localBase IBase +) + +func Base() IBase { + if localBase == nil { + panic("implement not found for interface IBase, forgot register?") + } + return localBase +} + +func RegisterBase(i IBase) { + localBase = i +} diff --git a/cmd/gf/internal/cmd/testdata/genservice/service/user.go b/cmd/gf/internal/cmd/testdata/genservice/service/user.go index 731ee87c5..925d36103 100644 --- a/cmd/gf/internal/cmd/testdata/genservice/service/user.go +++ b/cmd/gf/internal/cmd/testdata/genservice/service/user.go @@ -10,6 +10,28 @@ import ( ) type ( + ISuperVipUser interface { + // Get supper vip user level + GetVipLevel(ctx context.Context) (vipLevel int, err error) + // Set supper vip user level + SetVipLevel(ctx context.Context, id int, vipLevel int) (err error) + // Create creates a new vip user. + Create(ctx context.Context, name string, vipLevel int) (id int, err error) + // GetOne retrieves user by id. + GetOne(ctx context.Context, id int) (name string, err error) + // GetList retrieves user list. + GetList(ctx context.Context) (names []string, err error) + // Update updates user by id. + Update(ctx context.Context, id int) (name string, err error) + // Delete deletes user by id. + Delete(ctx context.Context, id int) (err error) + // sBase Init + Init() + // sBase Destory + Destory() + // baseDestory BeforeDestory + BeforeDestory() + } IUser interface { // Create creates a new user. Create(ctx context.Context, name string) (id int, err error) @@ -22,12 +44,45 @@ type ( // Delete deletes user by id. Delete(ctx context.Context, id int) (err error) } + IVipUser interface { + // Create creates a new vip user. + Create(ctx context.Context, name string, vipLevel int) (id int, err error) + // Get vip user level + GetVipLevel() (vipLevel int, err error) + // GetOne retrieves user by id. + GetOne(ctx context.Context, id int) (name string, err error) + // GetList retrieves user list. + GetList(ctx context.Context) (names []string, err error) + // Update updates user by id. + Update(ctx context.Context, id int) (name string, err error) + // Delete deletes user by id. + Delete(ctx context.Context, id int) (err error) + // sBase Init + Init() + // sBase Destory + Destory() + // baseDestory BeforeDestory + BeforeDestory() + } ) var ( - localUser IUser + localSuperVipUser ISuperVipUser + localUser IUser + localVipUser IVipUser ) +func SuperVipUser() ISuperVipUser { + if localSuperVipUser == nil { + panic("implement not found for interface ISuperVipUser, forgot register?") + } + return localSuperVipUser +} + +func RegisterSuperVipUser(i ISuperVipUser) { + localSuperVipUser = i +} + func User() IUser { if localUser == nil { panic("implement not found for interface IUser, forgot register?") @@ -38,3 +93,14 @@ func User() IUser { func RegisterUser(i IUser) { localUser = i } + +func VipUser() IVipUser { + if localVipUser == nil { + panic("implement not found for interface IVipUser, forgot register?") + } + return localVipUser +} + +func RegisterVipUser(i IVipUser) { + localVipUser = i +}