gin学习笔记(二)—— 获取参数和文件上传

发布时间 2024-01-12 20:58:59作者: 昨晚没做梦

获取参数和文件上传


获取参数

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 小于等于同一结构体字段      

自定义验证

  我们可以自定义函数,然后将函数挂载到参数验证中,实现自定义验证:
  1. 首先,我们需要获取获取 validator 库 :go get github.com/go-playground/validator/v10
  2. 导入 import ”github.com/go-playground/validator/v10“。这点需要注意一下:笔者在运行时,不知道编译器为什么给我自动改成了 import ”github.com/go-playground/validator“,后面是没有带v10的,然后不兼容一直报错,最后才发现是这里出了问题。
  3. 开始自定义验证:
// 自定义验证器,验证用户名不能为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 这表示编号为一的书资源,然后对这个资源的不同请求表示不同操作:

  1. GET请求:取得编号为一的书的内容
  2. POST请求:新建编号为一的书的内容
  3. PUT请求:对编号为一的书的内容进行修改
  4. 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)                                                                         //返回文件
    })

  效果: