Go语言gin框架基础

1.简介

  • 基于httprouter开发的web框架,http://github.com/julienschmidt/httprouter
  • 提供Martini风格的API,但比Martini要快40倍
  • 非产妇轻量级,使用起来非常简洁

2.安装与使用

1.安装

go get -u github.com/gin-gonic/gin

2.使用

package main

import "github.com/gin-gonic/gin"

func main() {
    // 初始化一个默认的路由引擎
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
	// 输出json结果给调用方
	c.JSON(200, gin.H{
	    "message": "pong",
	})
    })
    // 监听8080端口
    // 可以自己设置:0.0.0.0:9000
    r.Run("0.0.0.0:9000")
}
  • 修改全局默认404异常返回数据
r := gin.Default() // Grouping routes 
r.NoRoute(func(c *gin.Context) {
    c.JSON(http.StatusNotFound, gin.H{     
       "status": 404,
       "error": "404, page not exists!", 
   })
})

3.查询参数的获取

  • c.Query
http://localhost:9000/user/search?id=1
id := c.Query("id")  // 1
http://localhost:9000/user/search?ids=1,2,3
ids := c.Query("ids")  // 1,2,3
for _, v := range strings.Split(ids[0], ","){
    fmt.Println(v)
}
  • c.GetQuery("id")
id, exist := c.GetQuery("id")  // GetQuery可以查询该参数是否传递
if !exist {
    name = "the key is not exist!" 
}
c.Data(http.StatusOK, "text/plain", []byte(fmt.Sprintf("get success! %s %s", name, pwd)))
return 
  • c.QueryArray
http://localhost:9000/user/search?id=1&id=2&id=3
id := c.QueryArray("id")  // [1 2 3]
for _, v := range id {
    fmt.Println(v)
}
  • c.QueryMap
http://localhost:9000/user/search?user[name]=小李&user[age]=21
userInfo := c.QueryMap("user")
fmt.Println(userInfo["name"])  // "小李"
fmt.Println(userInfo["age"])  // 21
  • c.DefaultQuery
http://localhost:9000/user/search
pwd := c.DefaultQuery("password", "123")
fmt.Println(pwd)  // 123

4.路径参数的获取

  • c.Param
http://localhost:9000/user/1
id := c.Param("id")  // 1
  • c.Params
http://localhost:9000/user/1,2,3
ids := c.Params
fmt.Println(ids)  // [{ids 1,2,3}]
values := ids.ByName("ids")
fmt.Println(values)  // 1,2,3
fmt.Println(reflect.TypeOf(values))  // string
for _, v := range strings.Split(values, ","){
    fmt.Println(v)
}

5.路由冲突

解决方法参考:https://hanggi.me/post/golang/wildcard-conflict/

6.Form表单数据的获取

  • c.PostForm
package main

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

func userCreate(c *gin.Context)  {
    username := c.PostForm("username")
    password := c.PostForm("password")
    data := map[string]string{
	"username": username,
	"password": password,
    }
    c.JSON(http.StatusOK, gin.H{
	"message": "OK",
	"data": data,
    })
}

func main() {
    // 初始化一个默认的路由引擎
    r := gin.Default()
    r.POST("/user/create", userCreate)
    // 监听8080端口
    // 可以自己设置:0.0.0.0:9000
    r.Run("0.0.0.0:9000")
}

  • c.PostFormMap
package main

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

func userCreate(c *gin.Context)  {
    user := c.PostFormMap("user")
    fmt.Println(user["username"])
    fmt.Println(user["password"])
    c.JSON(http.StatusOK, gin.H{
	"message": "OK",
	"data": user,
    })
}

func main() {
    // 初始化一个默认的路由引擎
    r := gin.Default()
    r.POST("/user/create", userCreate)
    // 监听8080端口
    // 可以自己设置:0.0.0.0:9000
    r.Run("0.0.0.0:9000")
}

7.单文件上传

  • c.FormFile
func uploadFile(c *gin.Context)  {
    // 获取文件对象
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
	    "message": err.Error(),
	})
	return
    }

    log.Println(file.Filename)
    // 指定文件存储路径
    dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
    // 保存文件到指定的路径
    c.SaveUploadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
	"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
    })
}

  • c.File

  • c.FileAttachment

  • c.FileFromFS

8.多文件上传

  • c.MultipartForm()
  • form.File["file"]
func manyUpload(c *gin.Context)  {
    form, _ := c.MultipartForm()
    files := form.File["file"]
    for index, file := range files {
	log.Println(file.Filename)
	dst := fmt.Sprintf("C:/tmp/%s+%d", file.Filename, index)
	c.SaveUploadedFile(file, dst)
    }
    c.JSON(http.StatusOK, gin.H{
	"message": fmt.Sprintf("'%d' files uploaded!", len(files)),
    })
}
  • 设置文件处理内存大小(单位byte):默认为32M
    • router.MaxMultipartMemory = 8 << 20
    • 8 << 20 相当于 8 * 2**20

9.文件下载

  • c.File
func downloadFile(c *gin.Context)  {
    c.Header("Content-Type", "application/octet-stream")
    // 关键点
    c.Header("Content-Disposition", "attachment; filename=111.png")
    c.Header("Content-Transfer-Encoding", "binary")
    c.File("C:/tmp/111.png")
}
  • c.FileAttachment
func downloadFile(c *gin.Context)  {
    c.FileAttachment("C:/tmp/111.png", "bbb.jpg")
}

10.路由分组

package main

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

func login(c *gin.Context)  {
    fmt.Println("visit", c.Query("version"), "version api")
    c.JSON(200, gin.H{
	"message": "OK",
	"version": c.Query("version"),
    })
}

func main() {
    router := gin.Default()
    v1 := router.Group("/v1")
    v1.GET("/login", login)
    
    v2 := router.Group("/v2")
    v2.GET("/login", login)
    router.Run()
}

11.参数绑定

1.为什么要参数绑定,本质上是方便,提高开发效率

  • 通过反射机制,自动提取querystringform表单jsonxml等,到struct
  • 通过http协议中的context type,识别是jsonxml或者表单
  • 大部分参数的绑定都可以使用 c.ShouldBind 直接解决
package main

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

type Login struct {
    // 如果通过json格式提交数据,则使用username和password
    // 如果通过form表单提交数据,则使用user和pwd
    User	string `form:"user" json:"username" binding:"required"`
    Password	string `form:"pwd" json:"password" binding:"required"`
}

2.c.ShouldBindJSON:绑定json请求体

func login1(c *gin.Context)  {
    var login Login
    if err := c.ShouldBindJSON(&login); err == nil {
	c.JSON(http.StatusOK, gin.H{
	    "name": login.User,
	    "pwd": login.Password,
	})
    } else {
	c.JSON(http.StatusBadRequest, gin.H{
	    "error": err.Error(),
	})
    }
}

3.c.ShouldBind:绑定Form表单请求体

func login2(c *gin.Context)  {
    var login Login
    if err := c.ShouldBind(&login); err == nil {
	c.JSON(http.StatusOK, gin.H{
	    "name": login.User,
	    "pwd": login.Password,
	})
    } else {
	c.JSON(http.StatusBadRequest, gin.H{
	    "error": err.Error(),
	})
    }
}

4.c.ShouldBindQuery:绑定查询参数

func login3(c *gin.Context)  {
    var login Login
    if err := c.BindQuery(&login); err == nil {
	c.JSON(http.StatusOK, gin.H{
	    "name": login.User,
	    "pwd": login.Password,
	})
    } else {
	c.JSON(http.StatusBadRequest, gin.H{
	    "error": err.Error(),
	})
    }
}

type User struct {
    Name string `binding:"required"` //注意此处添加了binding注解,便于测试
}

func user(c *gin.Context)  {
    var user User
    if err := c.BindQuery(&user); err == nil {
	c.JSON(http.StatusOK, gin.H{
            "name": user.Name,
	})
    } else {
	c.JSON(http.StatusBadRequest, gin.H{
	    "error": err.Error(),
	})
    }
}

4.c.ShouldBindBodyWith

ShouldBindJSON多次绑定bug:https://blog.csdn.net/yes169yes123/article/details/106204252

12.字段验证

13.渲染

1.gin.Context.JSON

  • map形式
func login(c *gin.Context)  {
    // gin.H 是一个 map 对象
    c.JSON(http.StatusOK, gin.H{
        "message": "OK",
        "data": "",
    })
}
  • struct形式
func login(c *gin.Context)  {
    var resp struct {
	Message string `json:"message"`
	Data struct {
	    Username	string `json:"username"`
	    Password	string `json:"password"`
	} `json:"data"`
    }
    resp.Message = "OK"
    resp.Data.Username = "admin"
    resp.Data.Password = "123456"
    c.JSON(http.StatusOK, resp)
    // c.IndentedJSON(http.StatusOK, resp)
}

2.gin.Context.XML

func login(c *gin.Context)  {
    type UserInfo struct {
        Username	string `xml:"username"`
	Password	string `xml:"password"`
    }
    type Login struct {
	Message string `xml:"result"`
	Data UserInfo `xml:"UserInfo"`
    }
    user := Login{
	Message: "OK",
	Data: UserInfo{
	    Username: "admin",
	    Password: "123456",
	},
    }
    c.XML(http.StatusOK, user)
}

3.gin.Context.HTML

func index(c *gin.Context)  {
    c.HTML(http.StatusOK, "index/index.html", gin.H{
	"title": "Posts",
    })
}

func main() {
    router := gin.Default()
    // 加载templates下的所有模板文件
    // 从src下开始查找,所以templates是相对于src的路径
    // 如果templates位于项目根路径
    // 项目路径:src/project
    // ./project/templates/index/index.html
    router.LoadHTMLGlob("./project/templates/**/*")
    router.Run()
}
  • templates/index/index.html
{{ define "index/index.html" }}
<html>
  <h1>
    {{ .title }}
  </h1>
</html>
{{ end }}

14.静态文件代理

func main() {
    router := gin.Default()
    router.Static("/static", "./static/")
    router.Run()
}
http://localhost:8080/static/111.png

15.中间件

1.定义

  • Gin框架允许在请求处理的过程中,加入用户自己的钩子函数,这个钩子函数就叫做中间件
  • 因此,可以使用中间件处理一些公共业务逻辑,比如耗时统计,日志打印,登录校验等

2.使用

  • 中间件的定义
package main

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

func StatCost() gin.HandlerFunc {
    return func(c *gin.Context) {
	t := time.Now()
	// 可以设置一些公共参数
	c.Set("example", "12345")
	// 等待其他中间件先执行
	c.Next()
	// 获取耗时
	latency := time.Since(t)
	fmt.Print("Cost:", latency)
    }
}
  • 中间件引用
func main()  {
    r := gin.New()
    r.Use(StatCost())  // 引用中间件
    r.GET("/index", func(c *gin.Context) {
	example := c.MustGet("example").(string)
	fmt.Println(example)
	c.JSON(http.StatusOK, gin.H{
	    "message": "success",
	})
    })
    r.Run()
}
  • http://localhost:8080/index
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /index                    --> main.main.func1 (2 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on localhost:8080
12345
Cost:998µs
posted @ 2022-10-17 18:29  fatpuffer  阅读(895)  评论(0)    收藏  举报