路由参数 #
一、参数类型概述 #
1.1 参数分类 #
| 类型 | 来源 | 获取方法 |
|---|---|---|
| 路径参数 | URL路径 | c.Param() |
| 查询参数 | URL查询字符串 | c.Query() |
| 表单参数 | 请求体 | c.PostForm() |
| JSON参数 | 请求体JSON | c.BindJSON() |
| 文件参数 | multipart表单 | c.FormFile() |
1.2 参数位置示意 #
text
GET /users/123?page=1&size=10
↑ ↑
路径参数 查询参数
POST /users
Content-Type: application/json
{"name": "Alice", "age": 25}
↑
JSON参数
二、路径参数 #
2.1 基本用法 #
go
func main() {
r := gin.Default()
// 单个路径参数
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.String(200, "用户ID: %s", id)
})
r.Run()
}
2.2 多个路径参数 #
go
func main() {
r := gin.Default()
// 多个路径参数
r.GET("/users/:userId/posts/:postId", func(c *gin.Context) {
userId := c.Param("userId")
postId := c.Param("postId")
c.JSON(200, gin.H{
"userId": userId,
"postId": postId,
})
})
r.Run()
}
2.3 可选路径参数 #
go
func main() {
r := gin.Default()
// *filepath 匹配所有后续路径
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(200, "文件路径: %s", filepath)
})
// /files/ → filepath = "/"
// /files/a.txt → filepath = "/a.txt"
// /files/dir/a.txt → filepath = "/dir/a.txt"
r.Run()
}
2.4 参数验证 #
go
func main() {
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
// 验证参数
if id == "" {
c.JSON(400, gin.H{"error": "ID不能为空"})
return
}
// 验证是否为数字
if _, err := strconv.Atoi(id); err != nil {
c.JSON(400, gin.H{"error": "ID必须是数字"})
return
}
c.JSON(200, gin.H{"id": id})
})
r.Run()
}
三、查询参数 #
3.1 获取单个参数 #
go
func main() {
r := gin.Default()
r.GET("/search", func(c *gin.Context) {
// 获取查询参数
keyword := c.Query("keyword")
c.String(200, "搜索关键词: %s", keyword)
})
// 访问: /search?keyword=gin
// 输出: 搜索关键词: gin
r.Run()
}
3.2 带默认值的参数 #
go
func main() {
r := gin.Default()
r.GET("/users", func(c *gin.Context) {
// 获取参数,如果不存在则使用默认值
page := c.DefaultQuery("page", "1")
size := c.DefaultQuery("size", "10")
c.JSON(200, gin.H{
"page": page,
"size": size,
})
})
// 访问: /users
// 输出: {"page": "1", "size": "10"}
// 访问: /users?page=2&size=20
// 输出: {"page": "2", "size": "20"}
r.Run()
}
3.3 获取所有查询参数 #
go
func main() {
r := gin.Default()
r.GET("/query", func(c *gin.Context) {
// 获取所有查询参数
values := c.Request.URL.Query()
result := make(map[string]string)
for key, val := range values {
result[key] = val[0]
}
c.JSON(200, result)
})
// 访问: /query?name=gin&version=1.9
// 输出: {"name": "gin", "version": "1.9"}
r.Run()
}
3.4 多值参数 #
go
func main() {
r := gin.Default()
r.GET("/filter", func(c *gin.Context) {
// 获取多值参数
tags := c.QueryArray("tag")
c.JSON(200, gin.H{
"tags": tags,
})
})
// 访问: /filter?tag=go&tag=gin&tag=web
// 输出: {"tags": ["go", "gin", "web"]}
r.Run()
}
3.5 参数绑定到结构体 #
go
type SearchParams struct {
Keyword string `form:"keyword" binding:"required"`
Page int `form:"page" binding:"min=1"`
Size int `form:"size" binding:"min=1,max=100"`
}
func main() {
r := gin.Default()
r.GET("/search", func(c *gin.Context) {
var params SearchParams
if err := c.ShouldBindQuery(¶ms); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, params)
})
r.Run()
}
四、表单参数 #
4.1 获取单个表单参数 #
go
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
c.JSON(200, gin.H{
"username": username,
"password": password,
})
})
r.Run()
}
4.2 带默认值的表单参数 #
go
func main() {
r := gin.Default()
r.POST("/form", func(c *gin.Context) {
name := c.DefaultPostForm("name", "Guest")
email := c.DefaultPostForm("email", "")
c.JSON(200, gin.H{
"name": name,
"email": email,
})
})
r.Run()
}
4.3 获取所有表单参数 #
go
func main() {
r := gin.Default()
r.POST("/form", func(c *gin.Context) {
// 获取所有表单参数
form, err := c.MultipartForm()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
values := form.Value
c.JSON(200, values)
})
r.Run()
}
4.4 表单绑定到结构体 #
go
type LoginForm struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required,min=6"`
}
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
var form LoginForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"username": form.Username,
"message": "登录成功",
})
})
r.Run()
}
五、JSON参数 #
5.1 绑定JSON到结构体 #
go
type User struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=150"`
}
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
5.2 绑定到Map #
go
func main() {
r := gin.Default()
r.POST("/json", func(c *gin.Context) {
var data map[string]interface{}
if err := c.BindJSON(&data); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, data)
})
r.Run()
}
5.3 获取原始JSON #
go
func main() {
r := gin.Default()
r.POST("/raw", func(c *gin.Context) {
// 读取原始JSON数据
data, err := c.GetRawData()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.Data(200, "application/json", data)
})
r.Run()
}
六、URI参数绑定 #
6.1 绑定路径参数 #
go
type UserParams struct {
ID uint `uri:"id" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
var params UserParams
if err := c.ShouldBindUri(¶ms); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"id": params.ID})
})
r.Run()
}
6.2 绑定多个路径参数 #
go
type PostParams struct {
UserID uint `uri:"userId" binding:"required"`
PostID uint `uri:"postId" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/users/:userId/posts/:postId", func(c *gin.Context) {
var params PostParams
if err := c.ShouldBindUri(¶ms); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, params)
})
r.Run()
}
七、参数绑定总结 #
7.1 ShouldBind vs MustBind #
go
func main() {
r := gin.Default()
// ShouldBind: 绑定失败返回error,不自动响应
r.POST("/should", func(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
// MustBind: 绑定失败自动响应400,不返回error
r.POST("/must", func(c *gin.Context) {
var user User
if c.Bind(&user) == nil {
c.JSON(200, user)
}
})
r.Run()
}
7.2 绑定方法对比 #
| 方法 | 说明 | 失败处理 |
|---|---|---|
| ShouldBind | 根据Content-Type自动绑定 | 返回error |
| ShouldBindJSON | 绑定JSON | 返回error |
| ShouldBindXML | 绑定XML | 返回error |
| ShouldBindQuery | 绑定查询参数 | 返回error |
| ShouldBindUri | 绑定路径参数 | 返回error |
| Bind | 自动绑定 | 自动响应400 |
7.3 绑定优先级 #
text
ShouldBind 绑定优先级:
1. URL查询参数
2. 表单参数
3. JSON/XML等请求体
八、参数验证 #
8.1 内置验证规则 #
go
type User struct {
Name string `binding:"required"`
Email string `binding:"required,email"`
Password string `binding:"required,min=6,max=20"`
Age int `binding:"gte=0,lte=150"`
Phone string `binding:"omitempty,len=11"`
URL string `binding:"omitempty,url"`
}
8.2 常用验证规则 #
| 规则 | 说明 |
|---|---|
| required | 必填 |
| min=n | 最小值/长度 |
| max=n | 最大值/长度 |
| gte=n | 大于等于 |
| lte=n | 小于等于 |
| 邮箱格式 | |
| url | URL格式 |
| len=n | 长度等于n |
| oneof=a b c | 值为a、b、c之一 |
| omitempty | 可选字段 |
8.3 自定义验证错误消息 #
go
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
// 解析验证错误
var verr validator.ValidationErrors
if errors.As(err, &verr) {
errors := make(map[string]string)
for _, fe := range verr {
switch fe.Field() {
case "Name":
errors["name"] = "名称不能为空"
case "Email":
errors["email"] = "邮箱格式不正确"
case "Password":
errors["password"] = "密码长度必须在6-20之间"
}
}
c.JSON(400, gin.H{"errors": errors})
return
}
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
九、总结 #
9.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 路径参数 | c.Param(“key”) |
| 查询参数 | c.Query(“key”) |
| 表单参数 | c.PostForm(“key”) |
| JSON参数 | c.ShouldBindJSON(&struct) |
| 参数验证 | binding标签 |
9.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 使用结构体绑定 | 类型安全,自动验证 |
| 使用ShouldBind | 更灵活的错误处理 |
| 添加验证规则 | 确保数据有效性 |
| 统一错误响应 | 提升用户体验 |
9.3 下一步 #
现在你已经掌握了路由参数,接下来让我们学习 路由分组,组织更复杂的路由结构!
最后更新:2026-03-28