15分钟学 Go 第 51 天 :通用库与工具使用

第51天:通用库与工具使用

一、学习目标

类别 工具/库 用途
命令行工具 cobra 构建命令行应用
JSON处理 gjson 高效JSON解析
HTTP客户端 resty HTTP请求处理
日期处理 carbon 时间日期操作
配置管理 viper 配置文件处理

二、详细实现

让我们通过具体示例来学习这些库的使用:

// main.go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/go-resty/resty/v2"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
    "github.com/tidwall/gjson"
    "github.com/golang-module/carbon/v2"
)

// 配置结构
type Config struct {
    APIEndpoint string `mapstructure:"api_endpoint"`
    APIKey      string `mapstructure:"api_key"`
    Timeout     int    `mapstructure:"timeout"`
}

var (
    cfgFile string
    config  Config
)

// rootCmd represents the base command
var rootCmd = &cobra.Command{
    Use:   "toolapp",
    Short: "A demo application showing various Go tools",
    Long:  `This application demonstrates the usage of various Go libraries and tools.`,
}

// getDataCmd represents the getData command
var getDataCmd = &cobra.Command{
    Use:   "getdata",
    Short: "Get data from API",
    Run: func(cmd *cobra.Command, args []string) {
        // 创建HTTP客户端
        client := resty.New()
        client.SetTimeout(time.Duration(config.Timeout) * time.Second)

        // 发送请求
        resp, err := client.R().
            SetHeader("Authorization", "Bearer "+config.APIKey).
            Get(config.APIEndpoint)

        if err != nil {
            log.Fatalf("Request failed: %v", err)
        }

        // 使用gjson解析响应
        result := gjson.Parse(resp.String())
        
        // 提取数据
        items := result.Get("data.items")
        items.ForEach(func(key, value gjson.Result) bool {
            fmt.Printf("Item: %s\n", value.Get("name"))
            // 解析时间
            timestamp := value.Get("created_at").String()
            t := carbon.Parse(timestamp)
            fmt.Printf("Created: %s (Humanized: %s)\n", 
                t.ToDateTimeString(), 
                t.DiffForHumans())
            return true
        })
    },
}

// timeCmd represents the time command
var timeCmd = &cobra.Command{
    Use:   "time",
    Short: "Show time operations",
    Run: func(cmd *cobra.Command, args []string) {
        now := carbon.Now()
        
        fmt.Println("Current time operations:")
        fmt.Printf("DateTime: %s\n", now.ToDateTimeString())
        fmt.Printf("Date: %s\n", now.ToDateString())
        fmt.Printf("Time: %s\n", now.ToTimeString())
        fmt.Printf("Timestamp: %d\n", now.Timestamp())
        
        // 时间计算
        fmt.Println("\nTime calculations:")
        future := now.AddDays(7)
        fmt.Printf("7 days later: %s\n", future.ToDateTimeString())
        fmt.Printf("Human readable: %s\n", future.DiffForHumans())
        
        // 时间判断
        fmt.Println("\nTime comparisons:")
        fmt.Printf("Is future? %v\n", future.IsAfter(now))
        fmt.Printf("Is weekend? %v\n", now.IsWeekend())
    },
}

func init() {
    cobra.OnInitialize(initConfig)

    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./config.yaml)")

    rootCmd.AddCommand(getDataCmd)
    rootCmd.AddCommand(timeCmd)
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.SetConfigName("config")
        viper.SetConfigType("yaml")
        viper.AddConfigPath(".")
    }

    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
    }

    if err := viper.Unmarshal(&config); err != nil {
        log.Fatalf("Unable to decode into config struct, %v", err)
    }
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        log.Fatal(err)
    }
}

配置文件示例:

# config.yaml
api_endpoint: "https://api.example.com/data"
api_key: "your-api-key"
timeout: 30

让我们创建一个流程图来说明程序的执行流程:
在这里插入图片描述
让我们添加一个HTTP服务器的示例:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-module/carbon/v2"
)

type Item struct {
    Name      string    `json:"name"`
    CreatedAt time.Time `json:"created_at"`
}

type Response struct {
    Data  struct {
        Items []Item `json:"items"`
    } `json:"data"`
}

func main() {
    r := gin.Default()

    // 添加中间件
    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    // 设置路由
    r.GET("/data", func(c *gin.Context) {
        // 创建示例数据
        now := carbon.Now()
        items := []Item{
            {
                Name:      "Item 1",
                CreatedAt: now.SubDays(2).ToStdTime(),
            },
            {
                Name:      "Item 2",
                CreatedAt: now.SubDays(1).ToStdTime(),
            },
            {
                Name:      "Item 3",
                CreatedAt: now.ToStdTime(),
            },
        }

        response := Response{}
        response.Data.Items = items

        c.JSON(http.StatusOK, response)
    })

    // 启动服务器
    if err := r.Run(":8080"); err != nil {
        log.Fatal("Failed to start server:", err)
    }
}

三、工具库详解

1. Cobra命令行工具

功能 说明 示例
命令定义 定义命令及其参数 cobra.Command
子命令 支持命令嵌套 rootCmd.AddCommand()
参数绑定 绑定命令行参数 cmd.Flags()
自动补全 命令自动补全 cmd.ValidArgsFunction

2. Viper配置管理

功能 说明 示例
配置读取 支持多种配置格式 viper.ReadInConfig()
环境变量 支持环境变量覆盖 viper.AutomaticEnv()
配置监控 配置文件热重载 viper.WatchConfig()
配置绑定 绑定到结构体 viper.Unmarshal()

3. Resty HTTP客户端

功能 说明 示例
请求发送 支持各种HTTP方法 client.R().Get()
重试机制 请求失败自动重试 client.SetRetryCount()
超时控制 设置请求超时 client.SetTimeout()
中间件 请求/响应拦截器 client.OnBeforeRequest()

4. Carbon时间处理

功能 说明 示例
时间解析 支持多种格式解析 carbon.Parse()
时间计算 时间加减操作 carbon.AddDays()
时间比较 比较时间大小 carbon.IsAfter()
人性化显示 友好的时间显示 carbon.DiffForHumans()

5. GJSON JSON处理

功能 说明 示例
路径查询 支持复杂路径查询 result.Get("data.items")
类型转换 自动类型转换 result.Int()
数组处理 遍历JSON数组 result.ForEach()
错误处理 安全的值获取 result.Exists()

四、最佳实践

1. 命令行应用开发建议

  • 使用子命令组织功能
  • 提供详细的帮助信息
  • 支持配置文件
  • 实现命令自动补全

2. HTTP客户端使用建议

  • 复用HTTP客户端实例
  • 设置适当的超时时间
  • 实现重试机制
  • 处理错误响应

3. 配置管理建议

  • 支持多环境配置
  • 使用环境变量覆盖
  • 实现配置验证
  • 支持配置热重载

4. 时间处理建议

  • 统一时间格式
  • 考虑时区问题
  • 使用易读的时间表示
  • 正确处理时间计算

五、进阶用法

1. 中间件实现

让我们实现一个简单的中间件示例:

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-module/carbon/v2"
)

// 请求日志中间件
func RequestLogger() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 开始时间
        startTime := time.Now()

        // 处理请求
        c.Next()

        // 计算处理时间
        duration := time.Since(startTime)

        // 记录请求日志
        log.Printf(
            "[%s] %s %s %d %v",
            c.Request.Method,
            c.Request.URL.Path,
            c.ClientIP(),
            c.Writer.Status(),
            duration,
        )
    }
}

// API认证中间件
func APIAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        apiKey := c.GetHeader("Authorization")
        if apiKey == "" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Missing API key",
            })
            c.Abort()
            return
        }

        // 这里可以添加实际的API key验证逻辑
        if apiKey != "Bearer your-api-key" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Invalid API key",
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

// 错误恢复中间件
func ErrorRecovery() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic recovered: %v", err)
                c.JSON(http.StatusInternalServerError, gin.H{
                    "error": "Internal server error",
                })
            }
        }()

        c.Next()
    }
}

// 限流中间件
func RateLimit(limit int, duration time.Duration) gin.HandlerFunc {
    type client struct {
        count    int
        lastSeen time.Time
    }
    
    clients := make(map[string]*client)
    
    return func(c *gin.Context) {
        ip := c.ClientIP()
        now := time.Now()
        
        if clients[ip] == nil {
            clients[ip] = &client{}
        }
        
        if now.Sub(clients[ip].lastSeen) > duration {
            clients[ip].count = 0
            clients[ip].lastSeen = now
        }
        
        if clients[ip].count >= limit {
            c.JSON(http.StatusTooManyRequests, gin.H{
                "error": "Rate limit exceeded",
            })
            c.Abort()
            return
        }
        
        clients[ip].count++
        clients[ip].lastSeen = now
        c.Next()
    }
}

func main() {
    r := gin.New() // 不使用默认中间件

    // 应用自定义中间件
    r.Use(ErrorRecovery())
    r.Use(RequestLogger())
    r.Use(RateLimit(100, time.Minute)) // 限制每分钟100个请求

    // API路由组
    api := r.Group("/api")
    api.Use(APIAuth()) // 对API路由应用认证中间件

    api.GET("/data", func(c *gin.Context) {
        // 创建示例数据
        now := carbon.Now()
        data := []map[string]interface{}{
            {
                "id":         1,
                "name":      "Item 1",
                "created_at": now.SubDays(2).ToDateTimeString(),
            },
            {
                "id":         2,
                "name":      "Item 2",
                "created_at": now.SubDays(1).ToDateTimeString(),
            },
        }

        c.JSON(http.StatusOK, gin.H{
            "data": data,
            "meta": map[string]interface{}{
                "total":    2,
                "page":     1,
                "per_page": 10,
            },
        })
    })

    // 健康检查路由
    r.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "status":  "healthy",
            "version": "1.0.0",
            "time":    carbon.Now().ToDateTimeString(),
        })
    })

    // 启动服务器
    if err := r.Run(":8080"); err != nil {
        log.Fatal("Failed to start server:", err)
    }
}

2. 并发控制示例

让我们实现一个带并发控制的数据处理示例:

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"

    "golang.org/x/time/rate"
)

// Worker 工作池
type Worker struct {
    id       int
    jobs     chan Job
    limiter  *rate.Limiter
    resultCh chan<- Result
}

// Job 任务定义
type Job struct {
    id   int
    data interface{}
}

// Result 结果定义
type Result struct {
    jobID     int
    workerID  int
    data      interface{}
    processed bool
    error     error
}

// WorkerPool 工作池管理器
type WorkerPool struct {
    workers   []*Worker
    jobs      chan Job
    results   chan Result
    limiter   *rate.Limiter
    wg        sync.WaitGroup
}

// NewWorkerPool 创建工作池
func NewWorkerPool(workerCount int, jobBuffer int, rateLimit float64) *WorkerPool {
    pool := &WorkerPool{
        workers:   make([]*Worker, workerCount),
        jobs:      make(chan Job, jobBuffer),
        results:   make(chan Result, jobBuffer),
        limiter:   rate.NewLimiter(rate.Limit(rateLimit), 1),
    }

    // 创建工作协程
    for i := 0; i < workerCount; i++ {
        worker := &Worker{
            id:       i + 1,
            jobs:     make(chan Job, 1),
            limiter:  rate.NewLimiter(rate.Limit(rateLimit/float64(workerCount)), 1),
            resultCh: pool.results,
        }
        pool.workers[
上一篇:uniapp 上传 base64 图片


下一篇:Android【01】TRTC实现跨应用屏幕录制