查询参数

查询参数是 URL 中 ? 后面的部分,比如 /search?q=golang&page=1。Gin 提供了多种方法来获取查询参数。

获取单个参数

使用 Query() 获取查询参数:

r.GET("/search", func(c *gin.Context) {
    query := c.Query("q")
    c.JSON(200, gin.H{"query": query})
})

访问 /search?q=golangquery 的值是 golang

如果参数不存在,返回空字符串。

获取带默认值的参数

DefaultQuery() 可以设置默认值:

r.GET("/search", func(c *gin.Context) {
    page := c.DefaultQuery("page", "1")
    size := c.DefaultQuery("size", "10")
    c.JSON(200, gin.H{
        "page": page,
        "size": size,
    })
})

访问 /searchpagesize 都是默认值。

访问 /search?page=2&size=20,会使用传入的值。

检查参数是否存在

Query() 返回空字符串时,可能是参数不存在,也可能是值为空。用 GetQuery() 可以区分:

r.GET("/search", func(c *gin.Context) {
    query, exists := c.GetQuery("q")
    
    if !exists {
        c.JSON(400, gin.H{"error": "缺少查询参数 q"})
        return
    }

    c.JSON(200, gin.H{"query": query})
})

获取所有查询参数

QueryMap() 返回所有查询参数:

r.GET("/search", func(c *gin.Context) {
    params := c.QueryMap()
    
    for key, values := range params {
        for _, value := range values {
            fmt.Printf("%s = %s\n", key, value)
        }
    }

    c.JSON(200, params)
})

获取数组参数

URL 中可以有同名参数,比如 ?tags=go&tags=web

r.GET("/filter", func(c *gin.Context) {
    tags := c.QueryArray("tags")
    c.JSON(200, gin.H{"tags": tags})
})

访问 /filter?tags=go&tags=web&tags=api,会得到 ["go", "web", "api"]

参数类型转换

查询参数都是字符串,使用时需要转换:

import "strconv"

r.GET("/users", func(c *gin.Context) {
    pageStr := c.DefaultQuery("page", "1")
    limitStr := c.DefaultQuery("limit", "10")

    page, err := strconv.Atoi(pageStr)
    if err != nil {
        c.JSON(400, gin.H{"error": "无效的页码"})
        return
    }

    limit, err := strconv.Atoi(limitStr)
    if err != nil {
        c.JSON(400, gin.H{"error": "无效的限制数量"})
        return
    }

    c.JSON(200, gin.H{
        "page":  page,
        "limit": limit,
    })
})

分页查询

r.GET("/posts", func(c *gin.Context) {
    page := c.DefaultQuery("page", "1")
    pageSize := c.DefaultQuery("page_size", "10")
    sortBy := c.DefaultQuery("sort_by", "created_at")
    order := c.DefaultQuery("order", "desc")

    c.JSON(200, gin.H{
        "page":      page,
        "page_size":  pageSize,
        "sort_by":    sortBy,
        "order":      order,
        "posts":     []string{},
    })
})

搜索过滤

r.GET("/products", func(c *gin.Context) {
    keyword := c.Query("keyword")
    category := c.Query("category")
    minPrice := c.Query("min_price")
    maxPrice := c.Query("max_price")

    filters := gin.H{}
    if keyword != "" {
        filters["keyword"] = keyword
    }
    if category != "" {
        filters["category"] = category
    }
    if minPrice != "" {
        filters["min_price"] = minPrice
    }
    if maxPrice != "" {
        filters["max_price"] = maxPrice
    }

    c.JSON(200, gin.H{
        "filters": filters,
        "products": []string{},
    })
})

完整示例

package main

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

type Pagination struct {
    Page     int `json:"page"`
    PageSize int `json:"page_sizePageSize"`
}

type Filter struct {
    Keyword  string `json:"keyword"`
    Category string `json:"category"`
}

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

    r.GET("/posts", listPosts)
    r.GET("/products", listProducts)

    r.Run(":8080")
}

func listPosts(c *gin.Context) {
    pagination, err := parsePagination(c)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "pagination": pagination,
        "posts":     []string{},
    })
}

func listProducts(c *gin.Context) {
    pagination, err := parsePagination(c)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    filter := parseFilter(c)

    c.JSON(http.StatusOK, gin.H{
        "pagination": pagination,
        "filter":     filter,
        "products":   []string{},
    })
}

func parsePagination(c *gin.Context) (*Pagination, error) {
    pageStr := c.DefaultQuery("page", "1")
    pageSizeStr := c.DefaultQuery("page_size", "10")

    page, err := strconv.Atoi(pageStr)
    if err != nil {
        return nil, fmt.Errorf("无效的页码")
    }

    pageSize, err := strconv.Atoi(pageSizeStr)
    if err != nil {
        return nil, fmt.Errorf("无效的每页数量")
    }

    if page < 1 {
        page = 1
    }

    if pageSize < 1 || pageSize > 100 {
        pageSize = 10
    }

    return &Pagination{
        Page:     page,
        PageSize: pageSize,
    }, nil
}

func parseFilter(c *gin.Context) *Filter {
    return &Filter{
        Keyword:  c.Query("keyword"),
        Category: c.Query("category"),
    }
}

URL 编码

查询参数中的特殊字符需要 URL 编码:

import "net/url"

r.GET("/search", func(c *gin.Context) {
    query := c.Query("q")
    
    encoded := url.QueryEscape(query)
    decoded, _ := url.QueryUnescape(encoded)

    c.JSON(200, gin.H{
        "original": query,
        "encoded":  encoded,
        "decoded":  decoded,
    })
})

小结

这一章学习了:

  • Query() 获取查询参数
  • DefaultQuery() 设置默认值
  • GetQuery() 检查参数是否存在
  • QueryArray() 获取数组参数
  • 参数类型转换和验证