c *gin.Context#
c *gin.Context是什么?#
你可以把一次来自浏览器、前端的HTTP请求想象成:
一个客户打电话到你的公司客服部,要办理一项业务。
这个场景里:
- 「客户端」= 打电话的客户;
- 「你的 Gin 处理函数」= 接电话的客服人员;
-
c *gin.Context = 客服手里的「客户沟通手册」+「业务办理工具包」。
这本 “手册 + 工具包” 里包含:
- 客户的所有信息: 手机号(请求 IP)、来电渠道(请求头)、要办的业务内容(请求参数 / 请求体);
- 客服能用到的所有工具: 给客户回复结果(返回 JSON / 字符串)、转接到其他部门(重定向)、拒绝办理(终止请求)等。
定义:gin.Context是每一次独立的 HTTP 请求的「专属上下文对象」,一次请求对应一个 Context,请求结束后这个对象就会销毁。你要拿客户端的请求数据,必须通过它;你要给客户端返回响应,也必须通过它。
常用方法#
第一类:拿客户端的请求数据#
| 方法 | 通俗解释 | 示例代码 |
|---|---|---|
c.Query("参数名") | 拿 URL 里的 GET 参数(比如/user?name=张三里的name),没传就返回空字符串(附加信息,可选填) | name := c.Query("name") // 拿到"张三" |
c.DefaultQuery("参数名", "默认值") | 同上,但如果客户端没传这个参数,就用你给的默认值 | age := c.DefaultQuery("age", "18") // 没传age就默认18 |
c.Param("参数名") | 拿 URL 路径里的动态参数(比如/user/:id里的id) (必填) | // 路由:r.GET("/user/:id", handler)id := c.Param("id") // 拿到路径里的id,比如/user/123 → "123" |
c.PostForm("参数名") | 拿 POST 表单里的参数(比如前端表单提交的用户名 / 密码) | username := c.PostForm("username") |
c.ShouldBindJSON(&结构体) | 把客户端传的 JSON 请求体,直接解析到你定义的结构体里 | // 解析JSON到u里//现在u.Name就是客户端传的name,u.Age就是传的ageif err := c.ShouldBindJSON(&u); err != nil { c.JSON(400, gin.H{"msg": "参数错了"}) return} |
c.GetHeader("头名称") | 拿请求头里的信息(比如 Token、Content-Type) | token := c.GetHeader("Authorization") // 拿登录令牌 |
FormFile(name string) (*multipart.FileHeader, error) | 处理用户上传的单个文件 | 参数 name:前端表单中文件上传框的 name 属性值(比如前端<input type="file" name="avatar">,这里name就传"avatar");返回值 1 *multipart.FileHeader:文件的 “头信息”,包含文件名、大小、临时存储路径等核心信息;返回值 2 error:错误信息(比如前端没传文件、表单格式不对,都会返回非 nil 的错误)。 |
第二类:给客户端返回响应#
这是第二常用的场景,比如返回 JSON、普通字符串、网页等。
| 方法 | 通俗解释 | 示例代码 |
|---|---|---|
c.JSON(状态码, 数据) | 返回 JSON 格式的响应(前后端交互最常用),自动设置响应头为application/json | // 200是HTTP状态码,gin.H等价于map[string]anyc.JSON(200, gin.H{ "code": 0, "msg": "操作成功", "data": "张三"}) |
c.String(状态码, 字符串) | 返回普通字符串响应(简单提示用) | c.String(200, "欢迎访问我的接口!") |
c.HTML(状态码, 模板名, 数据) | 返回 HTML 页面(比如渲染登录页、首页) | // 假设你有templates/index.html模板,使用前需要先加载r.LoadHTMLGlob("templates/*")c.HTML(200, "index.html", gin.H{"title": "首页"}) |
c.File("文件路径") | 返回文件给客户端(比如下载图片、文档) | c.File("./static/1.jpg") // 客户端会下载这张图片 |
第三类:其他高频操作#
| 方法 | 通俗解释 | 示例代码 |
|---|---|---|
c.Set("键", 值) | 在 Context 里存数据(比如中间件查了用户信息,存起来给后续处理函数用) | // 中间件里存数据c.Set("user_level", "VIP") |
c.Get("键") | 取上面Set存的数据(返回 “值 + 是否存在”) | level, exists := c.Get("user_level")if !exists { c.JSON(400, gin.H{"msg": "没找到用户等级"}) return} |
c.Redirect(状态码, 目标URL) | 重定向客户端到另一个 URL(比如客户访问 /login,跳转到百度) | c.Redirect(302, "https://www.baidu.com") |
c.Abort() | 终止请求处理(比如客户没传 Token,直接拒绝,不执行后续逻辑) | if token == "" { c.JSON(401, gin.H{"msg": "未登录"}) c.Abort() // 终止后续中间件/处理函数 return} |
第四类:文件响应#
| 核心作用 | 客户端表现 | |
|---|---|---|
c.File("本地文件路径") | 直接返回文件 | 浏览器能预览的文件(图片 / HTML / 视频)会直接展示,不能预览的(zip/Excel)会下载 |
c.FileAttachment("本地文件路径", "下载文件名") | 强制下载文件 | 无论文件能否预览,都会触发 “下载” 操作,且文件名是你指定的(不是原文件名)// 1. 本地文件的路径(这里用相对路径,也可以用绝对路径) localFilePath := "./static/订单报表.xlsx" // 2. 客户端下载时显示的文件名(可以和原文件名不同) downloadFileName := "2026年1月订单报表.xlsx" // 3. 强制下载文件(核心方法:FileAttachment) c.FileAttachment(localFilePath, downloadFileName) |
c.Static("URL前缀", "本地文件夹路径") | 批量托管静态文件 | 把整个文件夹的文件暴露给客户端,比如前端的 CSS/JS/ 图片,客户端可通过 URL 直接访问r.Static("/", "./dist") 表示:所有以/开头的 URL,都去./dist文件夹找对应的文件(比如访问/css/app.css → 找./dist/css/app.css); |
请求体格式#
(对应header中的Content-Type字段)
1. x-www-form-urlencoded 键值对#
name=jack&age=1234
出现场景:简单表单提交,拼在url中
2. form-data(multipart/form-data)分隔符#
用分隔符(boundary) 做隔板,把不同参数(文本 / 图片 / Excel)分开
----------------------------853882779395683818968400 // 分隔符(boundary)
Content-Disposition: form-data; name="name" // 说明这个格子装的是name参数
枫枫 // 参数值(空一行后是值)
----------------------------853882779395683818968400 // 下一个隔板
Content-Disposition: form-data; name="age"
1234
----------------------------853882779395683818968400-- // 结束符(--结尾)plaintext3. JSON格式#
{
"name": "枫枫", // 键用双引号,值是字符串
"age": 23 // 值可以是数字(不用引号)
}json
绑定方法#
| 方法 | 绑定来源 | 核心特点 | 适用场景 |
|---|---|---|---|
| ShouldBindQuery | 仅 URL 查询参数(? 后) | 只查查询参数,优先级高 | GET 请求传参 |
| ShouldBindUri | 仅 URL 路径动态参数(/:xxx) | 只查路径参数,必须路由匹配 | 路径传动态标识(比如 /user/:id) |
| ShouldBind | 自动识别 Content-Type | 智能匹配(表单 / JSON / 查询参数) | POST/PUT 请求传复杂参数 |
| ShouldBindJSON | 仅 JSON 请求体 | 只解析 JSON,效率更高 | 前后端分离的 JSON 接口 |
绑定规则#
一、基础规则#
1. required:必填字段#
- 含义:参数必须传,且不能是空值(比如空字符串
""、0 不算空,但空数组算); - 通俗理解:收快递必须填收件人姓名,没填就拒收;
- 示例:
type User struct {
Name string `form:"name" binding:"required"` // 必须传name,且不能是空字符串
Age int `form:"age" binding:"required"` // 必须传age(0也可以,int的空值是0)
}go- 校验失败场景:客户端没传
name参数,或传name=(空字符串)。
2. -:忽略字段#
- 含义:Gin 既不绑定这个字段,也不做任何校验;
- 通俗理解:这个字段不需要验收,直接跳过;
- 示例:
type User struct {
ID string `form:"id" binding:"-"` // 完全忽略,不绑定也不校验
Note string `form:"note"` // 不写binding标签,等价于binding:"-"(只绑定,不校验)
}go二、字符串规则#
1. 长度规则:min=5 /max=10 /len=6#
-
含义:
-
min=5:字符串长度≥5; -
max=10:字符串长度≤10; -
len=6:字符串长度必须等于 6(比如验证码);
-
-
示例:
type User struct {
Nickname string `form:"nickname" binding:"required,min=2,max=10"` // 昵称2-10个字符
Code string `form:"code" binding:"required,len=6"` // 验证码必须6位
}go2. 内容匹配规则:contains /excludes/startswith /endswith#
-
含义:
-
contains=fengfeng:字符串必须包含 “fengfeng”; -
excludes=test:字符串不能包含 “test”; -
startswith=138:字符串前缀是 138(比如手机号); -
endswith=8888:字符串后缀是 8888;
-
-
示例:
type User struct {
Phone string `form:"phone" binding:"required,startswith=138,endswith=8888"` // 138xxxx8888
Remark string `form:"remark" binding:"contains=fengfeng,excludes=垃圾"` // 包含fengfeng,不含“垃圾”
}go三、数字规则(int/float 等数值类型)#
含义(对比关系):#
-
eq=3:等于 3; -
ne=12:不等于 12; -
gt=10:大于 10(>); -
gte=10:大于等于 10(≥); -
lt=10:小于 10(<); -
lte=10:小于等于 10(≤);
示例(商品价格 / 库存校验):#
type Goods struct {
Price int `form:"price" binding:"required,gt=0,lte=9999"` // 价格>0且≤9999
Stock int `form:"stock" binding:"eq=0"` // 库存必须为0(售罄标识)
Type int `form:"type" binding:"ne=3"` // 类型不能是3
}go四、字段对比规则(和同级字段比)#
1. eqfield = 字段名:等于另一个字段的值#
- 核心场景:确认密码(两次输入的密码必须一致);
2. nefield = 字段名:不等于另一个字段的值#
- 核心场景:新密码不能和旧密码相同;
示例(注册 / 改密码):#
type Register struct {
Password string `form:"password" binding:"required,min=6"` // 新密码≥6位
ConfirmPassword string `form:"confirm_pwd" binding:"required,eqfield=Password"` // 和密码一致
OldPassword string `form:"old_pwd" binding:"nefield=Password"` // 不能和新密码相同
}go五、枚举规则:oneof = 值 1 值 2#
- 含义:参数值只能是指定的几个值之一(注意:值之间用空格分隔,值本身不能有空格);
- 示例:
type Color struct {
Name string `form:"name" binding:"required,oneof=red green blue"` // 只能是这三个值
}go- 校验失败场景:传
name=yellow(不在枚举列表里)。
六、数组专属规则:dive#
- 含义:
dive是 “穿透” 的意思,后面的规则会作用于数组的每一个元素; - 示例:
type ScoreList struct {
// 数组必填,每个元素必须>0且<100(比如考试分数)
Scores []int `form:"scores" binding:"required,dive,gt=0,lt=100"`
// 每个标签长度≥2(比如["java","go"]合法,["c"]不合法)
Tags []string `form:"tags" binding:"dive,min=2"`
}go- 关键:
dive必须写在数组规则的前面,后面跟元素的校验规则。
七、网络格式规则(字符串类型)#
含义(区分 URI 和 URL):#
-
ip:合法 IP(IPv4/IPv6 都可以); -
ipv4:仅合法 IPv4(比如 192.168.1.1); -
ipv6:仅合法 IPv6(比如 2001:0db8:85a3:0000:0000:8a2e:0370:7334); -
uri:统一资源标识符(比如/user/1001,只标识资源,不指定访问方式); -
url:统一资源定位符(比如https://www.baidu.com,包含访问方式 + 路径);
示例:#
type NetInfo struct {
IP string `form:"ip" binding:"required,ipv4"` // 必须是IPv4地址
URL string `form:"url" binding:"url"` // 必须是合法URL(比如https://xxx)
URI string `form:"uri" binding:"uri"` // 必须是合法URI(比如/api/user)
}go八、日期格式规则:datetime = 格式#
- 示例:
type Order struct {
// 必须是YYYY-MM-DD格式(比如2026-01-21)
CreateDate string `form:"create_date" binding:"required,datetime=2006-01-02"`
// 必须是YYYY-MM-DD HH:mm:ss格式(比如2026-01-21 14:30:00)
CreateTime string `form:"create_time" binding:"datetime=2006-01-02 15:04:05"`
}go
中间件执行流程#
c.Next()#
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func Home(c *gin.Context) {
fmt.Println("Home 处理函数执行")
c.String(200, "Home")
}
func GM1(c *gin.Context) {
fmt.Println("GM1 请求部分(前处理)")
c.Next() // 暂停GM1,先执行后续的GM2和Home
fmt.Println("GM1 响应部分(后处理)")
}
func GM2(c *gin.Context) {
fmt.Println("GM2 请求部分(前处理)")
c.Next() // 暂停GM2,先执行Home
fmt.Println("GM2 响应部分(后处理)")
}
func main() {
r := gin.Default()
g := r.Group("api")
g.Use(GM1, GM2)
g.GET("users", Home)
r.Run(":8080")
}go访问http://127.0.0.1:8080/api/users,打印结果:#
GM1 请求部分(前处理)
GM2 请求部分(前处理)
Home 处理函数执行
GM2 响应部分(后处理)
GM1 响应部分(后处理)plaintext
(c.Abort())#
func GM1(c *gin.Context) {
fmt.Println("GM1 请求部分(前处理)")
c.Abort() // 终止后续所有逻辑
fmt.Println("GM1 响应部分(后处理)")
}go访问/api/users,打印结果:#
GM1 请求部分(前处理)
GM1 响应部分(后处理)plaintextGM2 和 Home 都没有执行(流水线被终止);