Files
atomctl/specs/001-pkg-ast-provider/quickstart.md
Rogee e1f83ae469 feat: 重构 pkg/ast/provider 模块,优化代码组织逻辑和功能实现
## 主要改进

### 架构重构
- 将单体 provider.go 拆分为多个专门的模块文件
- 实现了清晰的职责分离和模块化设计
- 遵循 SOLID 原则,提高代码可维护性

### 新增功能
- **验证规则系统**: 实现了完整的 provider 验证框架
- **报告生成器**: 支持多种格式的验证报告 (JSON/HTML/Markdown/Text)
- **解析器优化**: 重新设计了解析流程,提高性能和可扩展性
- **错误处理**: 增强了错误处理和诊断能力

### 修复关键 Bug
- 修复 @provider(job) 注解缺失 __job 注入参数的问题
- 统一了 job 和 cronjob 模式的处理逻辑
- 确保了 provider 生成的正确性和一致性

### 代码质量提升
- 添加了完整的测试套件
- 引入了 golangci-lint 代码质量检查
- 优化了代码格式和结构
- 增加了详细的文档和规范

### 文件结构优化
```
pkg/ast/provider/
├── types.go              # 类型定义
├── parser.go             # 解析器实现
├── validator.go          # 验证规则
├── report_generator.go   # 报告生成
├── renderer.go           # 渲染器
├── comment_parser.go     # 注解解析
├── modes.go             # 模式定义
├── errors.go            # 错误处理
└── validator_test.go    # 测试文件
```

### 兼容性
- 保持向后兼容性
- 支持现有的所有 provider 模式
- 优化了 API 设计和用户体验

This completes the implementation of T025-T029 tasks following TDD principles,
including validation rules implementation and critical bug fixes.
2025-09-19 18:58:30 +08:00

7.5 KiB

Quick Start Guide

概述

本指南展示如何使用重构后的 pkg/ast/provider 包来解析 Go 源码中的 @provider 注释并生成依赖注入代码。

前置条件

  • Go 1.24.0+
  • 理解 Go AST 解析
  • 了解依赖注入概念

基本用法

1. 解析单个文件

package main

import (
    "fmt"
    "log"

    "go.ipao.vip/atomctl/v2/pkg/ast/provider"
)

func main() {
    // 解析单个文件
    providers, err := provider.ParseFile("path/to/your/file.go")
    if err != nil {
        log.Fatal(err)
    }

    // 打印解析结果
    for _, p := range providers {
        fmt.Printf("Provider: %s\n", p.StructName)
        fmt.Printf("  Mode: %s\n", p.Mode)
        fmt.Printf("  Return Type: %s\n", p.ReturnType)
        fmt.Printf("  Inject Params: %d\n", len(p.InjectParams))
    }
}

2. 批量解析目录

package main

import (
    "fmt"
    "log"

    "go.ipao.vip/atomctl/v2/pkg/ast/provider"
)

func main() {
    // 创建解析器配置
    config := provider.ParserConfig{
        StrictMode:    true,
        AllowTestFile: false,
        IgnorePattern: "*.gen.go",
    }

    // 创建解析器
    parser := provider.NewParser(config)

    // 解析目录
    providers, err := parser.ParseDir("./app")
    if err != nil {
        log.Fatal(err)
    }

    // 生成代码
    for _, p := range providers {
        err := provider.Render(p.ProviderFile, []Provider{p})
        if err != nil {
            log.Printf("Failed to render %s: %v", p.StructName, err)
        }
    }
}

支持的 Provider 注释格式

基本格式

// @provider
type UserService struct {
    // ...
}

带模式

// @provider(grpc)
type UserService struct {
    // ...
}

带注入模式

// @provider:only
type UserService struct {
    Repo *UserRepo `inject:"true"`
    Log  *Logger   `inject:"true"`
}

完整格式

// @provider(grpc):only contracts.Initial atom.GroupInitial
type UserService struct {
    Repo *UserRepo `inject:"true"`
    Log  *Logger   `inject:"true"`
}

测试指南

运行测试

# 运行所有测试
go test ./pkg/ast/provider/...

# 运行测试并显示覆盖率
go test -cover ./pkg/ast/provider/...

# 运行基准测试
go test -bench=. ./pkg/ast/provider/...

编写测试

package provider_test

import (
    "testing"

    "go.ipao.vip/atomctl/v2/pkg/ast/provider"
    "github.com/stretchr/testify/assert"
)

func TestParseProvider(t *testing.T) {
    // 准备测试代码
    source := `
package main

// @provider:only contracts.Initial
type TestService struct {
    Repo *TestRepo `inject:"true"`
}
`

    // 创建临时文件
    tmpFile := createTempFile(t, source)
    defer os.Remove(tmpFile)

    // 解析
    providers, err := provider.ParseFile(tmpFile)

    // 验证
    assert.NoError(t, err)
    assert.Len(t, providers, 1)

    p := providers[0]
    assert.Equal(t, "TestService", p.StructName)
    assert.Equal(t, "contracts.Initial", p.ReturnType)
    assert.True(t, p.InjectMode.IsOnly())
}

重构指南

从旧版本迁移

  1. 更新导入路径
// 旧版本
import "go.ipao.vip/atomctl/v2/pkg/ast/provider"

// 新版本(相同的导入路径)
import "go.ipao.vip/atomctl/v2/pkg/ast/provider"
  1. 使用新的 API
// 旧版本
providers := provider.Parse("file.go")

// 新版本(向后兼容)
providers := provider.Parse("file.go") // 仍然支持

// 推荐的新方式
parser := provider.NewParser(provider.DefaultConfig())
providers, err := parser.ParseFile("file.go")

自定义扩展

1. 自定义 Provider 模式

// 实现自定义模式处理器
type CustomModeHandler struct{}

func (h *CustomModeHandler) Handle(ctx *provider.ParserContext, comment *provider.ProviderComment) (*provider.Provider, error) {
    // 自定义处理逻辑
    return &provider.Provider{
        Mode: provider.ProviderMode("custom"),
        // ...
    }, nil
}

// 注册自定义模式
provider.RegisterProviderMode("custom", &CustomModeHandler{})

2. 自定义验证器

// 实现自定义验证器
type CustomValidator struct{}

func (v *CustomValidator) Validate(p *provider.Provider) []error {
    var errors []error
    // 自定义验证逻辑
    if p.StructName == "" {
        errors = append(errors, fmt.Errorf("struct name cannot be empty"))
    }
    return errors
}

// 添加到验证链
parser.AddValidator(&CustomValidator{})

性能优化

1. 并行解析

// 使用并行解析提高性能
func ParseProjectParallel(root string) ([]*provider.Provider, error) {
    files, err := findGoFiles(root)
    if err != nil {
        return nil, err
    }

    var wg sync.WaitGroup
    providers := make([]*provider.Provider, 0, len(files))
    errChan := make(chan error, len(files))

    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()
            ps, err := provider.ParseFile(f)
            if err != nil {
                errChan <- err
                return
            }
            providers = append(providers, ps...)
        }(file)
    }

    wg.Wait()
    close(errChan)

    // 检查错误
    for err := range errChan {
        if err != nil {
            return nil, err
        }
    }

    return providers, nil
}

2. 缓存机制

// 使用缓存避免重复解析
type CachedParser struct {
    cache map[string][]*provider.Provider
    parser *provider.Parser
}

func NewCachedParser() *CachedParser {
    return &CachedParser{
        cache: make(map[string][]*provider.Provider),
        parser: provider.NewParser(provider.DefaultConfig()),
    }
}

func (cp *CachedParser) ParseFile(file string) ([]*provider.Provider, error) {
    if providers, ok := cp.cache[file]; ok {
        return providers, nil
    }

    providers, err := cp.parser.ParseFile(file)
    if err != nil {
        return nil, err
    }

    cp.cache[file] = providers
    return providers, nil
}

故障排除

常见错误

  1. 解析错误
error: failed to parse provider comment: invalid mode format

解决方案:检查 @provider 注释格式是否正确

  1. 导入错误
error: cannot resolve import path "github.com/unknown/pkg"

解决方案:确保所有导入的包都存在

  1. 验证错误
error: provider struct has invalid return type

解决方案:确保返回类型是有效的 Go 类型

调试技巧

  1. 启用详细日志
provider.SetLogLevel(logrus.DebugLevel)
  1. 使用解析器上下文
ctx := provider.NewParserContext(provider.ParserConfig{
    StrictMode:    true,
    ErrorHandler: func(err error) {
        log.Printf("Parse error: %v", err)
    },
})
  1. 验证生成的代码
if err := provider.ValidateGeneratedCode(code); err != nil {
    log.Printf("Generated code validation failed: %v", err)
}

最佳实践

  1. 保持注释简洁
// 推荐
// @provider:only contracts.Initial

// 不推荐
// @provider:only contracts.Initial atom.GroupInitial // 这是一个复杂的服务
  1. 使用明确的类型
// 推荐
type UserService struct {
    Repo *UserRepository `inject:"true"`
}

// 不推荐
type UserService struct {
    Repo interface{} `inject:"true"`
}
  1. 合理组织代码
// 将相关的 provider 放在同一个文件中
// 使用明确的包名和结构名
// 避免循环依赖

下一步