Skip to content

第05章:路径参数与查询参数 ​

本章目标 ​

学完本章后,你将能够:

  1. 使用 :param 和 *param 获取路径参数
  2. 使用 c.Query() / c.DefaultQuery() 获取查询参数
  3. 正确处理数组参数
  4. 根据场景选择路径参数还是查询参数

前置知识 ​

  • 需要先学习:第03-04章(路由基础与分组)

5.1 路径参数(Path Parameter) ​

路径参数是 URL 路径中的动态部分:

/users/123      → id = 123
/users/abc      → id = abc
/articles/hello-world  → slug = hello-world

基础用法::param ​

go
// :id 匹配单个路径段
r.GET("/users/:id", func(c *gin.Context) {
	id := c.Param("id")  // 获取路径参数
	c.JSON(200, gin.H{
		"message": fmt.Sprintf("查询用户 %s", id),
	})
})
bash
curl http://localhost:8080/users/123
# {"message":"查询用户 123"}

curl http://localhost:8080/users/abc
# {"message":"查询用户 abc"}

多个路径参数 ​

go
// :owner 和 :repo 是独立的参数
api.GET("/repos/:owner/:repo", func(c *gin.Context) {
	owner := c.Param("owner")
	repo := c.Param("repo")
	c.JSON(200, gin.H{
		"owner": owner,
		"repo":  repo,
	})
})
bash
curl http://localhost:8080/api/repos/gin-gonic/gin
# {"owner":"gin-gonic","repo":"gin"}

通配符 *param:匹配剩余全部路径 ​

go
// *filepath 匹配 /static/ 后面的所有内容
r.GET("/static/*filepath", func(c *gin.Context) {
	filepath := c.Param("filepath")  // 返回 "/css/style.css"(包含前导斜杠)
	c.String(200, "请求的文件:%s", filepath)
})
bash
curl http://localhost:8080/static/css/style.css
# 请求的文件:/css/style.css

curl http://localhost:8080/static/js/app.js
# 请求的文件:/js/app.js

5.2 查询参数(Query Parameter) ​

查询参数是 URL 中 ? 后面的 key=value 对:

/users?name=张三&age=25&city=北京
       └──┬──┘ └─┬─┘ └──┬──┘
        name    age    city

获取查询参数 ​

go
r.GET("/users", func(c *gin.Context) {
	// 直接获取(不存在时返回空字符串)
	name := c.Query("name")

	// 带默认值(不存在时返回默认值)
	age := c.DefaultQuery("age", "18")

	// 获取并判断是否存在
	city, exists := c.GetQuery("city")
	if !exists {
		city = "未知"
	}

	c.JSON(200, gin.H{
		"name": name,
		"age":  age,
		"city": city,
	})
})
bash
# 传入所有参数
curl "http://localhost:8080/users?name=张三&age=25&city=北京"
# {"age":"25","city":"北京","name":"张三"}

# 不传 age 和 city
curl "http://localhost:8080/users?name=张三"
# {"age":"18","city":"未知","name":"张三"}

获取多个同名参数(数组) ​

go
r.GET("/users", func(c *gin.Context) {
	// 获取所有同名的 ids 参数
	ids := c.QueryArray("ids")
	// 或者用 map 形式
	queryMap := c.QueryMap("filter")

	c.JSON(200, gin.H{
		"ids":    ids,
		"filter": queryMap,
	})
})
bash
curl "http://localhost:8080/users?ids=1&ids=2&ids=3&filter[name]=张三"
# {"filter":{"name":"张三"},"ids":["1","2","3"]}

5.3 路径参数 vs 查询参数:怎么选 ​

路径参数 /users/:id查询参数 ?name=张三
用于标识资源用于过滤/排序/搜索
是 URL 的一部分,必须传是可选的,不传也能访问
RESTful 风格中表示"哪个资源"RESTful 风格中表示"什么条件"
✅ 好的设计:
GET /users/1                ← 路径参数:标识"1号用户"这个资源
GET /users?status=active    ← 查询参数:筛选活跃用户
GET /users?page=2&page_size=10  ← 查询参数:分页

❌ 不好的设计:
GET /users?id=1             ← 路径参数更适合标识资源
GET /users/active           ← /active 是状态不是资源

5.4 获取所有查询参数 ​

go
r.GET("/debug", func(c *gin.Context) {
	// 获取原始查询字符串
	rawQuery := c.Request.URL.RawQuery
	fmt.Println("原始查询:", rawQuery)

	// 获取解析后的 map
	queryMap := c.Request.URL.Query()
	for key, values := range queryMap {
		fmt.Printf("%s = %v\n", key, values)
	}

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

5.5 完整实战:搜索接口 ​

go
package main

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

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

	// GET /api/v1/users?keyword=张&status=active&page=1&page_size=10
	r.GET("/api/v1/users", func(c *gin.Context) {
		keyword := c.DefaultQuery("keyword", "")
		status := c.DefaultQuery("status", "all")
		page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
		pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))

		c.JSON(http.StatusOK, gin.H{
			"keyword":   keyword,
			"status":    status,
			"page":      page,
			"page_size": pageSize,
			"message":   fmt.Sprintf("搜索关键词=%s, 状态=%s, 第%d页, 每页%d条", keyword, status, page, pageSize),
		})
	})

	r.Run(":8080")
}
bash
curl "http://localhost:8080/api/v1/users?keyword=张&status=active&page=1&page_size=10"
# {"keyword":"张","message":"搜索关键词=张, 状态=active, 第1页, 每页10条","page":1,"page_size":10,"status":"active"}

常见错误 ​

错误1:混淆 : 和 * ​

go
// :id 只匹配一个路径段(不含 /)
r.GET("/files/:filename")  // /files/abc.txt ✅   /files/a/b.txt ❌

// *filepath 匹配所有剩余路径(含 /)
r.GET("/files/*filepath")  // /files/a/b.txt ✅

错误2:Query 参数类型忘了转换 ​

go
// ❌ c.Query 返回的是 string!
age := c.Query("age")  // age 是 "25",不是 25
// 不能直接做数学运算

// ✅ 转换
age, _ := strconv.Atoi(c.DefaultQuery("age", "0"))

错误3:查询参数中有中文忘了 URL 编码 ​

bash
# ❌ 可能乱码
curl "http://localhost:8080/users?name=张三"

# ✅ curl 会自动编码,但如果自己拼接 URL 需要编码
# Go 中:url.QueryEscape("张三") → "%E5%BC%A0%E4%B8%89"

本章小结 ​

  • :param 匹配单段路径,*param 匹配剩余全部路径
  • c.Param("id") 获取路径参数
  • c.Query("key") / c.DefaultQuery("key", "default") 获取查询参数
  • c.QueryArray("ids") 获取数组参数
  • 路径参数标识资源,查询参数表示条件
  • Query 参数都是 string,需要手动类型转换

练习题 ​

  1. 实现 GET /articles/:id,返回文章 ID 和标题。
  2. 实现 GET /search?q=关键词&type=article&limit=10,返回搜索条件。
  3. 实现一个接口同时支持路径参数和查询参数:GET /users/:id/orders?status=paid。
  4. 用 *filepath 实现一个简单的文件浏览接口。
  5. (思考题)GET /users/:id 和 GET /users?id=1 哪个更 RESTful?为什么?

把零散知识,整理成可以反复走的路。