diff --git a/.vscode/launch.json b/.vscode/launch.json index cab8a98..417ab11 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,19 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "service", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "gen", + "service", + "--path", + "/home/rogee/Projects/quyun_v2/backend/app/services" + ] + }, { "name": "provider", "type": "go", diff --git a/cmd/gen_service.go b/cmd/gen_service.go index aebcc9e..b16a5c0 100644 --- a/cmd/gen_service.go +++ b/cmd/gen_service.go @@ -8,7 +8,10 @@ import ( "text/template" "github.com/samber/lo" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "go.ipao.vip/atomctl/v2/pkg/ast/provider" + "go.ipao.vip/atomctl/v2/pkg/utils/gomod" "go.ipao.vip/atomctl/v2/templates" ) @@ -19,10 +22,10 @@ func CommandGenService(root *cobra.Command) { Long: `扫描 --path 指定目录(默认 ./app/services)下的 Go 文件,汇总服务名并渲染生成 services.gen.go。 规则: -- 跳过 *_test.go 与 *.gen.go 文件,仅处理普通 .go 文件 -- 以文件名作为服务名来源: - - PascalCase 作为 CamelName,用于导出类型名 - - camelCase 作为 ServiceName,用于变量/字段名 +- 扫描目录中带有 @provider 注释的结构体 +- 以结构体名称作为服务名: + - StructName 作为 ServiceName,用于变量/字段类型 + - PascalCase(StructName) 作为 CamelName,用于导出变量名 - 使用内置模板 services/services.go.tpl 渲染 - 生成完成后会自动运行 gen provider 以补全注入`, RunE: commandGenServiceE, @@ -36,34 +39,53 @@ func CommandGenService(root *cobra.Command) { func commandGenServiceE(cmd *cobra.Command, args []string) error { path := cmd.Flag("path").Value.String() - - files, err := os.ReadDir(path) + absPath, err := filepath.Abs(path) if err != nil { return err } + // Try to parse go.mod from CWD or target path to ensure parser context + wd, _ := os.Getwd() + if err := gomod.Parse(filepath.Join(wd, "go.mod")); err != nil { + // fallback to check if go.mod is in the target path + if err := gomod.Parse(filepath.Join(absPath, "go.mod")); err != nil { + // If both fail, we might still proceed, but parser might lack module info. + // However, for just getting struct names, it might be fine. + // Logging warning could be good but we stick to error if critical. + // provider.ParseDir might depend on it. + } + } + + log := log.WithField("path", absPath) + log.Info("finding service providers...") + + parser := provider.NewGoParser() + providers, err := parser.ParseDir(absPath) + if err != nil { + return err + } + log.Infof("found %d providers", len(providers)) + type srv struct { CamelName string ServiceName string } - // get services from files + // get services from providers var services []srv - for _, file := range files { - if file.IsDir() { - continue - } - name := file.Name() + for _, p := range providers { + name := filepath.Base(p.Location.File) if strings.HasSuffix(name, "_test.go") || strings.HasSuffix(name, ".gen.go") || !strings.HasSuffix(name, ".go") { + log.Warnf("ignore file %s provider, %+v", p.Location.File, p) continue } - name = strings.TrimSuffix(name, ".go") + log.Infof("found service %s", p.StructName) services = append(services, srv{ - CamelName: lo.PascalCase(name), - ServiceName: lo.CamelCase(name), + CamelName: lo.PascalCase(p.StructName), + ServiceName: p.StructName, }) } diff --git a/pkg/ast/provider/builder.go b/pkg/ast/provider/builder.go index 6cc3d75..09bc82c 100644 --- a/pkg/ast/provider/builder.go +++ b/pkg/ast/provider/builder.go @@ -103,6 +103,9 @@ func (pb *ProviderBuilder) BuildFromTypeSpec(typeSpec *ast.TypeSpec, decl *ast.G Imports: make(map[string]string), PkgName: context.PackageName, ProviderFile: context.FilePath, + Location: SourceLocation{ + File: context.FilePath, + }, } // Set return type diff --git a/pkg/ast/provider/parser.go b/pkg/ast/provider/parser.go index 294d78a..e80dba1 100644 --- a/pkg/ast/provider/parser.go +++ b/pkg/ast/provider/parser.go @@ -226,14 +226,26 @@ func (p *MainParser) ParseFile(source string) ([]Provider, error) { // 查找对应的 AST 节点 provider, err := p.buildProviderFromDiscovery(discoveredProvider, node, builderContext) if err != nil { - context.AddError(source, 0, 0, fmt.Sprintf("failed to build provider %s: %v", discoveredProvider.StructName, err), "error") + context.AddError( + source, + 0, + 0, + fmt.Sprintf("failed to build provider %s: %v", discoveredProvider.StructName, err), + "error", + ) continue } // 如果启用严格模式,验证 Provider 配置 if p.config.StrictMode { if err := p.validator.Validate(&provider); err != nil { - context.AddError(source, 0, 0, fmt.Sprintf("validation failed for provider %s: %v", provider.StructName, err), "error") + context.AddError( + source, + 0, + 0, + fmt.Sprintf("validation failed for provider %s: %v", provider.StructName, err), + "error", + ) continue } } @@ -286,7 +298,11 @@ func (p *MainParser) shouldProcessFile(source string) bool { } // buildProviderFromDiscovery builds a complete Provider from a discovered provider annotation -func (p *MainParser) buildProviderFromDiscovery(discoveredProvider Provider, node *ast.File, context *BuilderContext) (Provider, error) { +func (p *MainParser) buildProviderFromDiscovery( + discoveredProvider Provider, + node *ast.File, + context *BuilderContext, +) (Provider, error) { // Find the corresponding type specification in the AST var typeSpec *ast.TypeSpec var genDecl *ast.GenDecl diff --git a/pkg/ast/provider/parser_interface.go b/pkg/ast/provider/parser_interface.go index 94dbf44..b34c204 100644 --- a/pkg/ast/provider/parser_interface.go +++ b/pkg/ast/provider/parser_interface.go @@ -434,7 +434,12 @@ func (p *GoParser) parseFileContent(filePath string, node *ast.File) ([]Provider } // parseProviderDecl parses a provider from an AST declaration -func (p *GoParser) parseProviderDecl(filePath string, fileNode *ast.File, decl ast.Decl, imports map[string]string) (*Provider, error) { +func (p *GoParser) parseProviderDecl( + filePath string, + fileNode *ast.File, + decl ast.Decl, + imports map[string]string, +) (*Provider, error) { genDecl, ok := decl.(*ast.GenDecl) if !ok { return nil, nil @@ -478,6 +483,9 @@ func (p *GoParser) parseProviderDecl(filePath string, fileNode *ast.File, decl a Imports: make(map[string]string), PkgName: fileNode.Name.Name, ProviderFile: filepath.Join(filepath.Dir(filePath), "provider.gen.go"), + Location: SourceLocation{ + File: filePath, + }, } // Set default return type if not specified @@ -518,7 +526,12 @@ func (p *GoParser) parseProviderDecl(filePath string, fileNode *ast.File, decl a } // parseStructFields parses struct fields for injection parameters -func (p *GoParser) parseStructFields(structType *ast.StructType, imports map[string]string, provider *Provider, onlyMode bool) error { +func (p *GoParser) parseStructFields( + structType *ast.StructType, + imports map[string]string, + provider *Provider, + onlyMode bool, +) error { for _, field := range structType.Fields.List { if field.Names == nil { continue @@ -573,7 +586,10 @@ func (p *GoParser) parseStructFields(structType *ast.StructType, imports map[str } // parseFieldType parses a field type and returns its components -func (p *GoParser) parseFieldType(expr ast.Expr, imports map[string]string) (star, pkg, pkgAlias, typ string, err error) { +func (p *GoParser) parseFieldType( + expr ast.Expr, + imports map[string]string, +) (star, pkg, pkgAlias, typ string, err error) { switch t := expr.(type) { case *ast.Ident: typ = t.Name