Skip to content

Gin — 高性能 Web 框架

Gin 是 Go 最流行的 Web 框架,基于 httprouter,性能极高,API 设计简洁。

安装

bash
go get -u github.com/gin-gonic/gin

快速开始

go
package main

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

func main() {
    r := gin.Default()  // 包含 Logger 和 Recovery 中间件

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

    r.Run(":8080")  // 监听 0.0.0.0:8080
}

路由

go
r := gin.New()

// HTTP 方法
r.GET("/users", listUsers)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.PATCH("/users/:id", patchUser)
r.DELETE("/users/:id", deleteUser)

// 路径参数
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"id": id})
})

// 查询参数
r.GET("/search", func(c *gin.Context) {
    q := c.Query("q")
    page := c.DefaultQuery("page", "1")
    c.JSON(200, gin.H{"q": q, "page": page})
})

// 路由组
v1 := r.Group("/api/v1")
{
    v1.GET("/users", listUsers)
    v1.POST("/users", createUser)

    // 嵌套路由组
    admin := v1.Group("/admin")
    admin.Use(adminAuthMiddleware())
    {
        admin.GET("/stats", getStats)
    }
}

请求绑定

go
// 请求体结构体
type CreateUserRequest struct {
    Name     string `json:"name"     binding:"required,min=2,max=50"`
    Email    string `json:"email"    binding:"required,email"`
    Age      int    `json:"age"      binding:"required,gte=0,lte=150"`
    Password string `json:"password" binding:"required,min=8"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest

    // ShouldBindJSON:失败返回错误,不自动响应
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    // 处理业务逻辑
    user, err := userService.Create(c.Request.Context(), req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, user)
}

// 绑定查询参数
type ListUsersQuery struct {
    Page     int    `form:"page"     binding:"min=1"`
    PageSize int    `form:"pageSize" binding:"min=1,max=100"`
    Search   string `form:"search"`
}

func listUsers(c *gin.Context) {
    var query ListUsersQuery
    query.Page = 1
    query.PageSize = 20

    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // ...
}

// 绑定路径参数
type UserURI struct {
    ID uint `uri:"id" binding:"required"`
}

func getUser(c *gin.Context) {
    var uri UserURI
    if err := c.ShouldBindUri(&uri); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // ...
}

中间件

go
// 自定义中间件
func RequestLogger() 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 %s %v",
            status, c.Request.Method, path, latency)
    }
}

// JWT 认证中间件
func JWTAuth(secret string) gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "未提供 token"})
            return
        }

        claims, err := parseJWT(token, secret)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "无效 token"})
            return
        }

        // 将用户信息存入 context
        c.Set("userID", claims.UserID)
        c.Set("userRole", claims.Role)

        c.Next()
    }
}

// 限流中间件
func RateLimit(rps int) gin.HandlerFunc {
    limiter := rate.NewLimiter(rate.Limit(rps), rps)
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.AbortWithStatusJSON(429, gin.H{"error": "请求过于频繁"})
            return
        }
        c.Next()
    }
}

// 使用中间件
r := gin.New()
r.Use(gin.Recovery())
r.Use(RequestLogger())
r.Use(cors.Default())

api := r.Group("/api")
api.Use(JWTAuth(config.JWTSecret))
api.Use(RateLimit(100))

响应

go
func handler(c *gin.Context) {
    // JSON 响应
    c.JSON(200, gin.H{"key": "value"})

    // 带状态码的 JSON
    c.JSON(http.StatusCreated, user)

    // 字符串响应
    c.String(200, "Hello, %s!", name)

    // HTML 响应
    c.HTML(200, "index.html", gin.H{"title": "首页"})

    // 文件下载
    c.File("./files/report.pdf")
    c.FileAttachment("./files/report.pdf", "报告.pdf")

    // 流式响应(SSE)
    c.Stream(func(w io.Writer) bool {
        if msg, ok := <-msgChan; ok {
            c.SSEvent("message", msg)
            return true
        }
        return false
    })

    // 重定向
    c.Redirect(http.StatusMovedPermanently, "https://example.com")
}

统一错误处理

go
// 自定义错误类型
type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func (e *APIError) Error() string { return e.Message }

// 错误处理中间件
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        if len(c.Errors) == 0 {
            return
        }

        err := c.Errors.Last().Err
        var apiErr *APIError
        if errors.As(err, &apiErr) {
            c.JSON(apiErr.Code, apiErr)
            return
        }

        c.JSON(500, gin.H{"error": "服务器内部错误"})
    }
}

// 在处理函数中使用
func getUser(c *gin.Context) {
    user, err := userService.GetByID(c.Request.Context(), id)
    if err != nil {
        c.Error(err)  // 传递给错误处理中间件
        return
    }
    c.JSON(200, user)
}

完整项目结构

myapp/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/        # Gin 处理函数
│   │   ├── user.go
│   │   └── auth.go
│   ├── middleware/     # 中间件
│   │   ├── auth.go
│   │   └── logger.go
│   ├── service/        # 业务逻辑
│   ├── repository/     # 数据访问
│   └── model/          # 数据模型
├── pkg/
│   └── response/       # 统一响应格式
├── config/
└── docs/               # Swagger 文档
go
// pkg/response/response.go — 统一响应格式
type Response struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    any    `json:"data,omitempty"`
}

func Success(c *gin.Context, data any) {
    c.JSON(200, Response{Code: 0, Message: "success", Data: data})
}

func Fail(c *gin.Context, code int, msg string) {
    c.JSON(200, Response{Code: code, Message: msg})
}

性能提示

  • 生产环境设置 gin.SetMode(gin.ReleaseMode)
  • 使用 c.ShouldBind 而非 c.Bind(后者会自动写 400 响应)
  • 路由参数用 :id,通配符用 *path

本站内容由 褚成志 整理编写,仅供学习参考