第51天:通用库与工具使用
一、学习目标
类别 |
工具/库 |
用途 |
命令行工具 |
cobra |
构建命令行应用 |
JSON处理 |
gjson |
高效JSON解析 |
HTTP客户端 |
resty |
HTTP请求处理 |
日期处理 |
carbon |
时间日期操作 |
配置管理 |
viper |
配置文件处理 |
二、详细实现
让我们通过具体示例来学习这些库的使用:
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
)
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.`,
}
var getDataCmd = &cobra.Command{
Use: "getdata",
Short: "Get data from API",
Run: func(cmd *cobra.Command, args []string) {
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)
}
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
})
},
}
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,
)
}
}
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
}
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))
api := r.Group("/api")
api.Use(APIAuth())
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"
)
type Worker struct {
id int
jobs chan Job
limiter *rate.Limiter
resultCh chan<- Result
}
type Job struct {
id int
data interface{}
}
type Result struct {
jobID int
workerID int
data interface{}
processed bool
error error
}
type WorkerPool struct {
workers []*Worker
jobs chan Job
results chan Result
limiter *rate.Limiter
wg sync.WaitGroup
}
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[