← 生态概览 | GORM →

Gin Web 框架 - HTTP 开发实战

Gin 是 Go 最流行的 Web 框架之一,以高性能、简洁 API 和丰富的中间件支持著称。掌握 Gin 是开发 Go Web 应用和 API 服务的基础。

📌 核心概念

🚀

高性能

基于 httprouter

50x 性能提升
🛣️

路由

URL 参数匹配

:param
🔌

中间件

请求处理链

Use()
📝

数据绑定

自动解析请求

ShouldBind()

快速开始

📝 Hello Gin

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    // 创建路由引擎
    r := gin.Default()
    
    // GET 请求
    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "Hello World",
        })
    })
    
    // POST 请求
    r.POST("/hello", func(c *gin.Context) {
        name := c.PostForm("name")
        c.JSON(http.StatusOK, gin.H{
            "message": "Hello " + name,
        })
    })
    
    // 启动服务
    r.Run(":8080") // 监听 :8080
}

💡 Gin 要点

  • gin.Default(): 包含 Logger 和 Recovery 中间件
  • gin.New(): 纯净引擎,需手动添加中间件
  • gin.Context: 请求上下文,核心对象
  • gin.H: map[string]interface{} 快捷方式

路由处理

📝 路由参数和查询

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()
    
    // URL 参数 /users/:id
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(http.StatusOK, gin.H{"id": id})
    })
    
    // 可选参数 /users/:id/posts/:postID
    r.GET("/users/:id/posts/:postID", func(c *gin.Context) {
        userID := c.Param("id")
        postID := c.Param("postID")
        c.JSON(http.StatusOK, gin.H{
            "userID": userID,
            "postID": postID,
        })
    })
    
    // 查询参数 /search?keyword=go&page=1
    r.GET("/search", func(c *gin.Context) {
        keyword := c.Query("keyword")
        page := c.DefaultQuery("page", "1")
        c.JSON(http.StatusOK, gin.H{
            "keyword": keyword,
            "page":    page,
        })
    })
    
    // 表单数据
    r.POST("/form", func(c *gin.Context) {
        name := c.PostForm("name")
        email := c.DefaultPostForm("email", "")
        c.JSON(http.StatusOK, gin.H{
            "name":  name,
            "email": email,
        })
    })
    
    r.Run(":8080")
}

数据绑定与验证

📝 自动绑定和验证

package main

import (
    "net/http"
    
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

// 定义请求结构
type LoginRequest struct {
    Username string `json:"username" binding:"required,min=3,max=20"`
    Password string `json:"password" binding:"required,min=6"`
    Email    string `json:"email" binding:"email"`
}

type SignupRequest struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func main() {
    r := gin.Default()
    
    // JSON 绑定
    r.POST("/login", func(c *gin.Context) {
        var req LoginRequest
        
        // ShouldBindJSON 自动解析并验证
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        
        c.JSON(http.StatusOK, gin.H{
            "message": "Login success",
            "user":    req.Username,
        })
    })
    
    // Form 绑定
    r.POST("/signup", func(c *gin.Context) {
        var req SignupRequest
        
        if err := c.ShouldBind(&req); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        
        c.JSON(http.StatusOK, gin.H{
            "message": "Signup success",
        })
    })
    
    // 自定义验证错误
    if v, ok := r.Validator().Engine().(*validator.Validate); ok {
        v.RegisterValidation("password", func(fl validator.FieldLevel) bool {
            return len(fl.Field().String()) >= 8
        })
    }
    
    r.Run(":8080")
}

中间件

📝 自定义中间件

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
    
    "github.com/gin-gonic/gin"
)

// 日志中间件
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        // 处理请求
        c.Next()
        
        // 记录日志
        latency := time.Since(start)
        status := c.Writer.Status()
        log.Printf("[%d] %s %v\n", status, path, latency)
    }
}

// 认证中间件
func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        
        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Missing token",
            })
            c.Abort()
            return
        }
        
        // 验证 token...
        if token != "valid-token" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Invalid token",
            })
            c.Abort()
            return
        }
        
        // 设置用户信息
        c.Set("userID", "123")
        c.Next()
    }
}

// CORS 中间件
func CORS() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer().Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        c.Writer().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        
        c.Next()
    }
}

func main() {
    r := gin.New()
    
    // 全局中间件
    r.Use(Logger(), CORS())
    
    // 路由组
    public := r.Group("/api")
    {
        public.GET("/health", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"status": "ok"})
        })
    }
    
    // 需要认证的路由
    auth := r.Group("/api")
    auth.Use(Auth())
    {
        auth.GET("/profile", func(c *gin.Context) {
            userID := c.GetString("userID")
            c.JSON(http.StatusOK, gin.H{"userID": userID})
        })
    }
    
    r.Run(":8080")
}

文件上传

📝 处理文件上传

func main() {
    r := gin.Default()
    
    // 单文件上传
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        // 保存到指定路径
        dst := "./uploads/" + file.Filename
        if err := c.SaveUploadedFile(file, dst); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(http.StatusOK, gin.H{
            "message": "File uploaded successfully",
            "filename": file.Filename,
        })
    })
    
    // 多文件上传
    r.POST("/upload/multi", func(c *gin.Context) {
        form, _ := c.MultipartForm()
        files := form.File["files"]
        
        for _, file := range files {
            dst := "./uploads/" + file.Filename
            c.SaveUploadedFile(file, dst)
        }
        
        c.JSON(http.StatusOK, gin.H{
            "message": fmt.Sprintf("%d files uploaded", len(files)),
        })
    })
    
    r.Run(":8080")
}

错误处理

📝 自定义错误处理

func main() {
    r := gin.Default()
    
    // 自定义 404
    r.NoRoute(func(c *gin.Context) {
        c.JSON(http.StatusNotFound, gin.H{
            "error": "Route not found",
            "code":  "NOT_FOUND",
        })
    })
    
    // 自定义 405
    r.NoMethod(func(c *gin.Context) {
        c.JSON(http.StatusMethodNotAllowed, gin.H{
            "error": "Method not allowed",
        })
    })
    
    // 优雅的错误处理
    r.GET("/error", func(c *gin.Context) {
        err := doSomething()
        if err != nil {
            // 记录错误
            c.Error(err)
            
            // 返回错误响应
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": "Internal server error",
            })
            return
        }
        
        c.JSON(http.StatusOK, gin.H{"status": "success"})
    })
    
    r.Run(":8080")
}

func doSomething() error {
    return fmt.Errorf("something went wrong")
}

最佳实践

✅ Gin 使用建议

  • 路由分组: 使用 Group 组织相关路由
  • 中间件复用: 提取通用逻辑为中间件
  • 数据验证: 使用 binding tag 自动验证
  • 错误处理: 统一错误响应格式
  • 配置分离: 使用 Viper 管理配置
  • 结构化日志: 使用 zap 等日志库