Go Web开发:Gin框架入门

Gin是Go语言最流行的Web框架,基于httprouter,性能优异,API简洁。本文从安装到中间件,覆盖Gin的核心用法。

安装与Hello World

go get -u github.com/gin-gonic/gin
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
}

gin.Hmap[string]interface{}的快捷方式。

路由与HTTP方法

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

    r.GET("/users", listUsers)
    r.GET("/users/:id", getUser)
    r.POST("/users", createUser)
    r.PUT("/users/:id", updateUser)
    r.DELETE("/users/:id", deleteUser)

    r.Run(":8080")
}

路径参数

// GET /users/42
func getUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"id": id})
}

查询参数

// GET /users?page=1&size=20&name=alice
func listUsers(c *gin.Context) {
    page := c.DefaultQuery("page", "1")
    size := c.DefaultQuery("size", "10")
    name := c.Query("name") // 没有则返回空字符串

    c.JSON(http.StatusOK, gin.H{
        "page": page,
        "size": size,
        "name": name,
    })
}

参数绑定

用结构体标签自动解析和验证请求参数:

type CreateUserReq 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=1,lte=150"`
    Password string `json:"password" binding:"required,min=6"`
}

func createUser(c *gin.Context) {
    var req CreateUserReq
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    // req已通过验证,可以使用
    c.JSON(http.StatusCreated, gin.H{
        "message": "user created",
        "name":    req.Name,
    })
}

ShouldBindJSON从请求体解析JSON并校验。类似的还有ShouldBindQuery(查询参数)、ShouldBind(根据Content-Type自动选择)。

中间件

内置中间件

r := gin.New() // 空白引擎,不带任何中间件

// 全局中间件
r.Use(gin.Logger())    // 请求日志
r.Use(gin.Recovery())  // panic恢复,返回500而不是crash

自定义中间件

func AuthMiddleware() 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...
        userID, err := validateToken(token)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        // 将用户信息存入上下文,后续handler可以读取
        c.Set("userID", userID)
        c.Next() // 继续执行后续handler
    }
}

请求耗时中间件

func TimingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start)
        
        log.Printf("[%s] %s - %d - %v",
            c.Request.Method,
            c.Request.URL.Path,
            c.Writer.Status(),
            duration,
        )
    }
}

分组路由

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

    // 公开接口
    public := r.Group("/api/v1")
    {
        public.POST("/login", login)
        public.POST("/negister", register)
    }

    // 需要认证的接口
    auth := r.Group("/api/v1")
    auth.Use(AuthMiddleware())
    {
        auth.GET("/profile", getProfile)
        auth.PUT("/profile", updateProfile)
        auth.GET("/orders", listOrders)
        auth.POST("/orders", createOrder)
    }

    // 管理员接口
    admin := r.Group("/api/v1/admin")
    admin.Use(AuthMiddleware(), AdminOnly())
    {
        admin.GET("/users", adminListUsers)
        admin.DELETE("/users/:id", adminDeleteUser)
    }

    r.Run(":8080")
}

分组路由的好处:共享前缀路径和中间件,代码结构清晰。

JSON响应

// 结构体响应
type UserResponse struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

func getUser(c *gin.Context) {
    user := UserResponse{
        ID:        1,
        Name:      "Alice",
        Email:     "alice@example.com",
        CreatedAt: time.Now(),
    }
    c.JSON(http.StatusOK, user)
}

// 统一响应格式
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

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

func Error(c *gin.Context, code int, message string) {
    c.JSON(http.StatusOK, Response{
        Code:    code,
        Message: message,
    })
}

项目结构建议

myapp/
  ├── main.go           # 入口
  ├── router/
  │   └── router.go     # 路由注册
  ├── handler/           # 请求处理
  │   ├── user.go
  │   └── order.go
  ├── middleware/         # 中间件
  │   └── auth.go
  ├── model/             # 数据模型
  │   └── user.go
  ├── service/           # 业务逻辑
  │   └── user.go
  └── config/            # 配置
      └── config.go

Gin的哲学是轻量和快速,核心只做路由和中间件,ORM、配置管理、日志等按需搭配。这种灵活性正是Go社区推崇的风格。