gin学习笔记(二)—— 获取参数和文件上传
获取参数和文件上传
获取参数
url传参
在我们使用网页时,我们有时会看到地址栏上带有 ?后面还跟着一些数据,这就是 url 传参,?后面携带的就是参数。例如:用必应搜索 what is a url,地址栏为 https://cn.bing.com/search?q=what is a url,携带的参数就是我们输入的 what is a url。
url 传递的参数,我们可以通过 Query 获取:
func main() { r := gin.Default() r.GET("/search", func(c *gin.Context) { question := c.Query("q") //如果url没有这个参数,默认为空 site := c.DefaultQuery("site", "baidu.com") //DefaultQuery 可以设置默认值 c.JSON(200, gin.H{ "search": question, "site": site, }) }) r.Run() }
用 postman 试一下效果:
uri传参
uri 的参数和 url 传参一样会在地址栏上呈现,但有所不同:
- uri 参数是作为 url 的一部分
- url 传参使用?符号将参数附加到URL末尾
直接上例子:https://www.cnblogs.com/Owhy,这里的 Owhy 就是 uri 参数。
uri 参数可以通过 Param 获取:
r.GET("/:user", func(c *gin.Context) { user := c.Param("user") question := c.Query("q") c.JSON(200, gin.H{ "user": user, "search": question, }) })
效果:
获取form参数
客户端发送表单(form),可以通过 PostForm 获取参数:
r.POST("/register", func(c *gin.Context) { user := c.DefaultPostForm("user", "Default") //DefaultPostForm 可设置默认值 pwd := c.PostForm("pwd") c.JSON(200, gin.H{ "user": user, "password": pwd, }) })
效果:
获取body参数
可以用 GetRawData 获取请求体的 body:
r.POST("/login", func(c *gin.Context) { data, _ := c.GetRawData() //从流中获取请求体body //GetRawData()主要操作如下,因此用下面的方法获取请求体body也可以 //buf := make([]byte, 1024) //n, _ := c.Request.Body.Read(buf) //data := buf[:n]
//获取body后,对其进行解析,这里只演示对 json 的解析 var body map[string]interface{} //定义一个map,存放json解析后的数据 _ = json.Unmarshal(data, &body) //解析json //获取json中的key name := body["name"] pwd := body["pwd"] c.JSON(200, gin.H{ "data": data, "body": body, "user": name, "password": pwd, }) })
效果:
获取body参数,除了这个方法,更推荐使用参数绑定的方法来获取。
参数绑定
Gin 提供了 MustBind 和 ShouldBind 两类绑定方法,官方推荐使用 ShouldBind 就可以了。
使用参数绑定,gin 会基于请求的 Content-Type 识别请求数据类型,并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等参数到结构体中:
type Login struct { //form:"..." 表示表单中的字段名字,json:"..." 表示返回的json中的字段名字,binding:"required" 表示必须要有这个字段 User string `form:"name" json:"name" binding:"required"` Password string `form:"pwd" json:"pwd" binding:"required"` } func main() { r := gin.Default() r.POST("/login", func(c *gin.Context) { var login Login if err := c.ShouldBind(&login); err != nil { c.JSON(400, gin.H{ "msg": login, "error": err.Error(), }) return } c.JSON(200, gin.H{ "msg": login, "user": login.User, "password": login.Password, }) }) r.Run("127.0.0.1:8000") }
例一:
通过参数绑定,上面的POST方法可以接收表单、json、xml、Query等数据,接收Query数据的效果如下:
结构体的 tag 没有设置 QueryString 的字段,为什么能正常接收 Query 数据?
答:这是因为在 ShouldBind 中,无法判断数据类型时,会默认是 From 类型,而上面使用的 Query 数据的字段和 tag 设置的 Form 字段一致,所以能执行。
例二:
在 json 数据里,将 pwd 设为空值,效果如下:
因为设置了 binding:"required",pwd这个字段不能为零值或空,否则会 error。
参数验证
在上面的例子中,binding:"required" 就是一个参数验证
参数验证可以对接收的参数进行验证,写在 binding 后。
操作符
,
:且,多个验证之间同时满足|
:或,满足其中一个-
:跳过验证=
:等于
验证标记
一些gin内置验证:
验证范围 | 字符串验证 | ||||
lte | 参数或参数长度小于等于 | bindling:"lte=3"(小于等于3) | contains | 参数子串包含某字符串 | bindling:"contains=Owhy" |
gte | 参数或参数长度大于等于 | 用法同上 | excludes | 参数子串不包含某字符串 | bindling:"excludes=Owhy" |
lt | 参数或参数长度小于 | 用法同上 | startswith | 参数以某字符串为前缀 | bindling:"startswith=hello" |
gt | 参数或参数长度大于 | 用法同上 | endswith | 参数以某字符串为后缀 | bindling:"startswith=world" |
len | 参数或参数长度等于 | 用法同上 | eq | 值等于某字符串 | bindling:"eq=Owhy" |
max | 参数或参数长度最大值 | 用法同上 | 其他验证 | ||
min | 参数或参数长度最小值 | 用法同上 | dive | 对嵌套结构体进行递归验证 | bindling:“dive,required” |
ne | 参数不等于 | 用法同上 | structonly | 嵌套的结构体验证不生效 | binding:"structonly" |
oneof | 参数只能是列举值的其中一个 | bindling:"oneof=red green" |
字段验证 | 网络验证 | |||
eqcsfield | 跨不同结构体字段验证,比如说 struct filed1 与 struct filed2 相等 | ip | 字段值是否包含有效的IP地址 | binding:"ip" |
necsfield | 跨不同结构体字段不相等 | ipv4 | 字段值是否包含有效的ipv4地址 | binding:"ipv4" |
eqfield | 同一结构体字段验证相等,最常见的就是输入2次密码验证 | ipv6 | 字段值是否包含有效的ipv6地址 | binding:"ipv6" |
nefield | 同一结构体字段验证不相等 | uri | 字段值是否包含有效的uri | binding:"uri" |
gtefield | 大于等于同一结构体字段 | url | 字段值是否包含有效的url | binding:"url" |
ltefield | 小于等于同一结构体字段 |
自定义验证
我们可以自定义函数,然后将函数挂载到参数验证中,实现自定义验证:- 首先,我们需要获取获取 validator 库 :go get github.com/go-playground/validator/v10
- 导入 import ”github.com/go-playground/validator/v10“。这点需要注意一下:笔者在运行时,不知道编译器为什么给我自动改成了 import ”github.com/go-playground/validator“,后面是没有带v10的,然后不兼容一直报错,最后才发现是这里出了问题。
- 开始自定义验证:
// 自定义验证器,验证用户名不能为admin func checkUser(fl validator.FieldLevel) bool { if fl.Field().Interface().(string) == "admin" { return false } return true } type Login struct { User string `form:"user" json:"name" binding:"required,checkUser"` //直接在binding中添加即可 Password string `form:"pwd" json:"pwd" binding:"required"` } func main() { r := gin.Default() r.POST("/login", func(c *gin.Context) { //参数绑定 //注册验证函数,和中间件一样,可以全局注册和组注册 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("checkUser", checkUser) } var login Login if err := c.ShouldBind(&login); err != nil { c.JSON(400, gin.H{ "msg": login, "error": err.Error(), }) return } c.JSON(200, gin.H{ "msg": login, "user": login.User, "password": login.Password, }) }) r.Run() }
效果:
Restful风格
Restful 是一种网络应用程序的设计风格,使用JSON或XML格式来表示数据。其设计理念是:URL定位资源,HTTP 请求描述操作。
例如:/books/book001 这表示编号为一的书资源,然后对这个资源的不同请求表示不同操作:
- GET请求:取得编号为一的书的内容
- POST请求:新建编号为一的书的内容
- PUT请求:对编号为一的书的内容进行修改
- DELETE请求:删除编号为一的书的内容
文件上传
前端设置 POST 请求时,Headers 要设置 Content-Type 为 multipart/form-data,表示上传的数据是文件,使用 postman 测试时也要设置。
单文件上传
r.POST("/upload", func(c *gin.Context) { file, err := c.FormFile("file") //获取form上传的字段名为 file 的文件 if err != nil { c.JSON(500, gin.H{ "msg": err.Error(), }) return } dst := fmt.Sprintf("./%s", file.Filename) //设置文件保存路径 c.SaveUploadedFile(file, dst) //保存文件 c.JSON(200, gin.H{ "msg": "上传成功", }) })
效果:
123.txt 被保存到本地:
多文件上传
r.POST("/upload2", func(c *gin.Context) { form, err := c.MultipartForm() // 调用c.MultipartForm()获取multipart form数据 if err != nil { c.JSON(500, gin.H{ "msg": err.Error(), }) return } files := form.File["file"] // 从form中获取名为"file"的文件列表 for _, file := range files { dst := fmt.Sprintf("./%s", file.Filename) c.SaveUploadedFile(file, dst) } c.JSON(200, gin.H{ "message": fmt.Sprintf("%d files uploaded!", len(files)), }) })
效果:
文件返回
r.GET("/download", func(c *gin.Context) { //返回文件 filename := c.Query("filename") dst := "./" filePath := dst + filename if _, err := os.Stat(filePath); os.IsNotExist(err) { c.JSON(404, gin.H{"error": "file not found"}) return } c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) //设置响应头 c.File(dst + filename) //返回文件 })
效果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现