栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

gin框架快速入门

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

gin框架快速入门

文章目录
  • 背景
  • 安装使用
  • 非常简单的例子
  • Restful API 的实现
    • Get/Post
  • 配置路由
    • 配置路由参数
    • 配置分组路由
  • 接收 Query 参数
    • [传入]Query/DefaultQuery 接收单个参数
    • [传入]QueryArray 接收多个参数
    • [传入]QueryMap 接收多个参数
    • [传入]请求提 query 转 struct
  • 接收表单数据
    • [传入]PostForm 接收表单数据
    • [传入]请求体 form 表单转 struct
  • 文件上传
    • [传入]单文件上传
    • [传入]多文件上传
  • JSON 的使用
    • [输出]struct 转 json
    • [输出]自定义 json 字段名
    • [输出]json 数组
    • [输出]json 格式美化
    • [输出]json 输出不自动转义
    • [输出]中文字符转成 unicode
    • [传入]请求体 json 转 struct
  • req/resp结构体中的tag
    • 综合使用
    • form/json/query
    • required:必填
    • omitempty:非必填
    • neither requierd nor omitempty:
    • binding
    • gorm的一些tag
    • thrift的一些tag
  • 跨域问题
    • JSONP
    • CORS
    • 代理
  • XML 的使用
    • [输出]返回 map
    • [输出]struct 转 xml
    • [输出]自定义 xml 字段命名
    • [输出]xml 数组
  • HTML 渲染
    • html/template
    • 模版界定符
    • LoadHTMLFiles
    • LoadHTMLGlob
    • 自定义函数
  • 托管文件
    • StaticFile & Static
    • 实现一个 FTP Server
    • 自定义托管类型
  • 重定向
    • Redirect 重定向
  • 同步和异步
    • 同步
    • 异步
  • 中间件
    • Logger & Recovery
    • HTTP Basic Authorization
    • 自定义全局中间件
    • 自定义单个路由中间件
  • 结合 MySQL
    • 原生 database/sql
    • gorm 工具
  • 结合 Redis
  • Thrift 的使用
  • 项目实战
  • 其他

背景

golang 语言学完了,然后了解一下经典网站建站的 mvc 方式之后,我们就可以开始从 gin 框架官网 doc 文档入手,开始了解学习 gin 框架,官网文档入口:https://gin-gonic.com/zh-cn/docs/

我们对比下主流的一些框架,gin 是性能很优秀的框架,当然还有其他优秀的框架,下面筛出了比较优秀的几个框架

框架在一定的时间内实现的总调用数
[越高越好]
单次操作耗时(ns/op)
[越低越好]
堆内存分配 (B/op)
[越低越好]
每次操作的平均内存分配次数(allocs/op)
[越低越好]
gin4355027364 ns/op0 B/op0 allocs/op
Aero5763220648 ns/op0 B/op0 allocs/op
HttpRoute5593821360 ns/op0 B/op0 allocs/op
LARS4777925084 ns/op0 B/op0 allocs/op
Beego7407243496 ns/op71456 B/op609 allocs/op
Martini3313444706 ns/op226551 B/op2325 allocs/op

默认你已经有了如下基础:

  • 学些了 golang 语言
  • 安装了 go 并且配置了 GOPATH 和 module
  • 了解了 web 的 mvc 模式
  • 已经安装了 go 的 ide 工具
安装使用

安装 gin 到 $GOPATH/pkg 中

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

gin 引入代码中

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

(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包

import "net/http"
非常简单的例子

我们创建好一个 project 之后,我们再创建一个 main.go 文件

这里 gin.Default() 创建一个路由 Handler,路由 Handler 可以调用 GET/POST 等多种方法(做了封装),并且 gin.Context 中封装了 request 和 response 信息,最后使用 Run() 函数来监听端口

package main

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

func main() {
	r := gin.Default()
  
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
  
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

然后我们运行起来这个服务,打开我们的浏览器http://localhost:8080/ping就能正常展示返回的 json 信息了

我们看下 server 控制台的信息

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
Restful API 的实现 Get/Post

最常见的请求方式有 Get 和 Post,实现样例代码如下,这里指给出样例但是会忽略具体业务逻辑部分

type User struct {
  ID   uint64
  Name string
}

func main() {
  // 从数据库中取到用户信息
  users := []User{{ID: 123, Name: "张三"}, {ID: 456, Name: "李四"}}
  
  // 获取一个 gin 实例 
  r := gin.Default()
  
  // get 请求
  r.GET("/users", func(c *gin.Context) {
    c.JSON(200, users)
  })
  
  
  
  // 服务启动
  r.Run(":8080")
}
配置路由 配置路由参数

gin 路由采用 httprouter 我们先看下面一个例子

func main() {
  // 创建 gin 实例
  r := gin.Default()

  // get 请求
  r.GET("/users/:id", func(c *gin.Context) {
    // 拿到路由中的 id 值
    id := c.Param("id")
    // 响应值
    c.String(200, "The user id is  %s", id)
  })
  
  // 运行服务
  r.Run(":8080")
}

我们浏览器中输入localhost:8080/users/222或者输入localhost:8080/users/呃呃呃都是可以匹配的。还有要注意相同模式的路由不能重复注册

/users/:id
:id 的路由可以匹配数字或字符串

/users/*id
*id 表示后面有几层路径或者没有路径,比如说我路径是 /user/333 那么其实 id 就是 “/333”,不建议使用,因为能匹配到的太多了
配置分组路由

假如我们要升级版本 API,但是旧版本我们也要保留来兼容老用户,我们可以通过 Group 方法这么做

func main() {
  // 一个 gin 实例
	r := gin.Default()

  // V1 版本 API
	v1Group := r.Group("/v1")
	v1Group.GET("/users", func(c *gin.Context) {
		c.String(200, "/v1/users")
	})
	v1Group.GET("/products", func(c *gin.Context) {
		c.String(200, "/v1/products")
	})

  // V2 版本 API
	v2Group := r.Group("/v2")
	v2Group.GET("/users", func(c *gin.Context) {
		c.String(200, "/v2/users")
	})
	v2Group.GET("/products", func(c *gin.Context) {
		c.String(200, "/v2/products")
	})

  // 运行服务
	r.Run(":8080")
}

然后我们可以细看一下这个 Group 函数,第一个参数就是分组路由命名空间,第二个参数是分组路由的中间件,即分组路由下的子路由在执行请求的时候会调用这个中间件方法

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup	

那么这个中间件具体有什么好处呢,比如说某分组路由下面都需要经过校验处理:

v1Group := r.Group("/admin", func(c *gin.Context) {
  fmt.Println("/admin分组路由中间件,在请求/admin的子路由时候将会打印出来")
})
接收 Query 参数 [传入]Query/DefaultQuery 接收单个参数

我们看一个例子

func main() {
  // 一个 gin 实例
  r := gin.Default()

  // get 请求
  r.GET("/user", func(c *gin.Context) {
    c.String(200, c.DefaultQuery("id", "0"))
  })
  
  // 运行服务
  r.Run(":8080")
}

然后我们在浏览器输入localhost:8080/user?id=1,上面使用了 DeafaultQuery 方法在请求时候如果去不到就用默认值 0 来代替,如果是直接字符串的输入我们可以用 Query 方法即可

c.Query("name")

// 返回两个参数,底层是:c.Request.URL.Query().Get(key)
v, ok := c.GetQuery("name")
[传入]QueryArray 接收多个参数
// http://localhost:8080/?name=xiaoming&name=zhanghua
c.QueryArray("name")
[传入]QueryMap 接收多个参数

不常用

// http://localhost:8080/map?name[a]=liming&name[b]=zhanghua&name[c]=xiaohua
c.QueryMap("name")
[传入]请求提 query 转 struct

使用 BindQuery 或者是 ShouldBindQuery 函数

var usr User
// 这一步实现接收的 query 绑定到 stuct 实例中
if c.ShouldBindQuery(&usr) != nil {
  // ...
}
接收表单数据 [传入]PostForm 接收表单数据
func main() {
  // 获取 gin 实例
  r := gin.Default()
  
  // post 请求
  r.POST("/", func(c *gin.Context) {
    // PostForm 接收表单数据 name
    usr := c.PostForm("usr")
    c.String(200, usr)
  })

  // 运行服务
  r.Run(":8080")
}
[传入]请求体 form 表单转 struct

使用 Bind 函数来将表单信息绑定在一个结构体实例上

type User struct {
  Name string `form: "name"`
  Age int 		`form: "age"`
}

func main() {
  // 创建一个 gin 实例
  r := gin.Default()

  // post 请求
  r.Post("/form", func(c *gin.Context) {
	  // 一个表单实例    
    var usr User
    
    // Bind 函数绑定表单,并进行错误处理
    if err := c.Bind(&usr); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    
    // 判断表单
    if usr.Name != "zhanghua" || usr.Age != 12 {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
      return
    }
    // 最后正常返回
    c.JSON(http.StatusOK, gin.H{"status": "you are right"})
  })
  
}

一般数据绑定是自动根据请求的 content-type 来确定类型的,如果你明确知道数据类型到底是传入的 json 还是传入的 form 形式,我们也可以用 BindWith 函数

c.BindWith(&form, binding.Form)
文件上传 [传入]单文件上传 [传入]多文件上传 JSON 的使用

gin 框架中提供了两种 json 解析器,一种默认的使用 golang 语言中的内置 json,另一种使用 jsoniter,如果要使用 jsoniter

go build -tags=jsoniter .
[输出]struct 转 json

这样输出的时候一个结构体也是以 json 的形式来输出了

type User struct {
  Name string
  age int
}

func main() {
  // 一个 gin 实例
  r := gin.Default()
  
  // 一个 get 请求
  r.Get("/users/zhanghua", func(c *gin.Context) {
    c.Json(200, User{Name: "zhanghua", Age: 12})
  })
  
  // 运行服务
  r.Run(":8080")
}

输出结果如下:

{"Name": "zhanghua", "Age": 12}
[输出]自定义 json 字段名

我们可以看到上面的例子,发现最后网站上输出的都是大写的参数名,但是我们平常看到的 json 参数名其实都是小写的,所以我们需要把 struct 中的参数名转成小写的,gin 是支持字段名字重新命名的,通过在 tag 中重写名字即可

type User struct {
  Name string `json:"name"`
  Age int `json:"age"`
}
[输出]json 数组
type User struct {
  Name string `json:"name"`
  Age int `json:"age"`
}

func main() {
  // 一个 gin 实例
  r := gin.Default()
  
  // 用户数组
  users := []user{
    {Name: "zhanghua", Age: 12},
    {Name: "liming", Age: 23}
  }
  
  // get 请求
  r.Get("/users", func(c *gin.Context) {
    c.IntentedJSON(200, users)
  })
}

最后输出

[
  {
    "name": "zhanghua",
    "age": 12
  },
  {
    "name": "liming",
    "age": 23
  }
]
[输出]json 格式美化
// 没有缩紧美化效果
c.JSON()

// 有缩紧美化效果
c.IntentedJSON()
[输出]json 输出不自动转义

gin 框架对于输出 json,对于 json 中的值是可以自动转义的,但是如果使用 PureJSON() 方法就可以不自动转义了

func main() {
  // 创建一个 gin 实例
  r := gin.Default()
  
  // 一个 get 请求
  r.Get("/pureJson", func(c *gin.Context) {
    c.PureJSON(200, gin.H{
      "name": "zhanghua"
    })
  })
}

因为我是用了 PureJson() 函数,所以最后输出依然含有,而不是被自动转义成了xxxx的形式

[输出]中文字符转成 unicode

如果要把非 Ascll 字符转成 unicode 编码,避免后续乱码,比如说把中文字符最后输出都转成xxxx形式的 unicode 编码,gin 中可以使用 AsciiJSON() 函数来实现

[传入]请求体 json 转 struct

也称为 json 绑定,实际就是把请求体中的 json 数据按照 json 格式进行解析,解析后存储到 struct 中,json 反序列化方法有 BindJSON 和 ShouldBindJSON

type User struct {
  Name string	`form: "name" json: "name"`
  Age int			`form: "age" json: "age"`
}

func main() {
  // 创建一个 gin 实例
  r := gin.Default()
  
  // post 请求
  r.POST("/bindjson", func(c *gin.Context) {
    // 声明一个实例
    var usr User
    
    // 把请求体 json 转成一个实例 struct,并进行错误判断
    if err := c.ShouldBindJSON(&usr); err != null {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    
    // 判断是否匹配
    if usr.Name != "zhanghua" || usr.Age != 12 {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
      return
    }
    // 最后匹配
    c.JSON(http.StatusOK, gin.H{"status": "you are right"})
  })
  
  // 运行服务
  r.Run(":8080")
}

实际请求是怎么做的呢,通过请求提传递 json 参数

{"name": "zhanghua", "age": 12}
req/resp结构体中的tag 综合使用

常见 req 和 resp 中会有,一般这里举的例子,如下用的最多

type Bar struct {
	Name string `form:"name,required" json:"name,required" query:"name,required"`
	Age int `form:"age" json:"age,omitempty" query:"age"`
}
form/json/query

这些 tag 非常常见,看上方例子,这里不赘述

required:必填

必填写字段

omitempty:非必填

如果omitempty标记的字段为"",那么最后接受者会发现没有omitempty标记的字段

neither requierd nor omitempty: binding

例子:

Name string `form:"name" json:"name" binding:"required,min=3,max=10"`

后面代码用 ShouldBindJson 函数来承接

gorm的一些tag

如下例子用的非常多

Id int64 `gorm:"primary_key;column:user_id;type:bigint(20);not null"
thrift的一些tag 跨域问题

gin 对 JSONP 提供了很好的支持,但是并不推荐使用,更加推荐使用的是 CORS 和代理的方式

JSONP

下面为服务端代码,这样的话{"name": "zhanghua"}就可以作为 data 参数传递给前端 sayHello 函数了

func main() {
  r := gin.Default()
	r.GET("/jsonp", func(c *gin.Context) {
		c.JSONP(200, gin.H{"name": "zhanghua"})
	})
	r.Run(":8080")
}

gin 是怎么避免