mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
feat(cmd/gf): add interface functions generating for embedded struct of logic struct in command gen service (#3802)
This commit is contained in:
@ -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"),
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
17
cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go
vendored
Normal file
17
cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go
vendored
Normal file
@ -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() {
|
||||
|
||||
}
|
||||
13
cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go
vendored
Normal file
13
cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package base
|
||||
|
||||
type baseDestory struct{}
|
||||
|
||||
// baseDestory Destory
|
||||
func (baseDestory) Destory() {
|
||||
|
||||
}
|
||||
|
||||
// baseDestory BeforeDestory
|
||||
func (baseDestory) BeforeDestory() {
|
||||
|
||||
}
|
||||
14
cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go
vendored
Normal file
14
cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package sub
|
||||
|
||||
type SubBase struct {
|
||||
}
|
||||
|
||||
// subbase init
|
||||
func (*SubBase) Init() {
|
||||
|
||||
}
|
||||
|
||||
// subbase GetSubBase
|
||||
func (*SubBase) GetSubBase() {
|
||||
|
||||
}
|
||||
@ -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"
|
||||
)
|
||||
|
||||
27
cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go
vendored
Normal file
27
cmd/gf/internal/cmd/testdata/genservice/logic/user/supper_vip_user.go
vendored
Normal file
@ -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
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
29
cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go
vendored
Normal file
29
cmd/gf/internal/cmd/testdata/genservice/logic/user/vip_user.go
vendored
Normal file
@ -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
|
||||
}
|
||||
32
cmd/gf/internal/cmd/testdata/genservice/service/base.go
vendored
Normal file
32
cmd/gf/internal/cmd/testdata/genservice/service/base.go
vendored
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user