安装
通过此命令下载安装,需要设置代理后下载否则会失败。
go get -u github.com/gin-gonic/gin
Gin 示例
package main
import "github.com/gin-gonic/gin"
func sayhello(c *gin.Context) {
c.JSON(200, gin.H{
"message": "success",
})
}
func main() {
r := gin.Default()
// 指定路由
r.GET("/hello", sayhello)
// 启动服务 默认 8080 端口
r.Run()
}
RESTful API
Gin 支持 RESTful API 风格的API
func main() {
r := gin.Default()
// 指定路由
r.GET("/demo", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"method": "Get Request",
})
})
r.POST("/demo", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"method": "POST Request",
})
})
r.PUT("/demo", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"method": "PUT Request",
})
})
r.DELETE("/demo", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"method": "DELETE Request",
})
})
// 启动服务
r.Run()
}
模板渲染
Go 语言自带了两个模板库,用于文本渲染的 text/template
和用于 HTML渲染的 http/template
。他们的规则如下:
- 模板文件通常为 .tmpl 和 .tpl 为后缀(也可以使用其他后缀),必须为 UTF-8 编码。
- 模板文件使用
{{}}
标识需要传入的数据。 - 传给模板的数据可以使用
.
来访问,如果数据是复杂类型,还可以使用{{.FieldName}}
来访问。 - 除了模板标签包裹的内容外,其他内容均原样输出。
mian 代码如下:
func sayHello(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("template Err:%v\n", err)
}
err = t.Execute(w, "张三")
if err != nil {
fmt.Println("render Err:%v\n", err)
}
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Println("HTTP server start Err:%v\n", err)
}
}
hello.tmpl 代码如下:
<h1><p>Hello {{ . }}</p></h1>
请求 Request
请求参数
GET 请求
func main() {
r := gin.Default()
r.Any("/", func(c *gin.Context) {
// 获取 kay 为 name 的 Get 参数
name := c.Query("name")
// 获取 kay 为 name 的 Get 参数,如果 name 不存在则使用默认值 lisi
name = c.DefaultQuery("name", "lisi")
// 获取 Kay 为 name 的 Get 参数,如果不存在 第二个返回值为 false;上面两个是基于此方法封装的
name, ok := c.GetQuery("name")
if !ok {
name = "lisi"
}
c.JSON(200, gin.H{"name": name})
})
r.Run()
}
POST 请求
func main() {
r := gin.Default()
r.Any("/", func(c *gin.Context) {
// 获取 Kay 为 name 的 POST 请求参数
name := c.PostForm("name")
// 获取 Kay 为 name 的 POST 请求参数,如果不传则使用第二个参数作为默认值
name = c.DefaultPostForm("name", "lisi")
// 获取 Kay 为 name 的 POST 请求参数,如未传递 name 则第二个返回值为 false
name, ok := c.GetPostForm("name")
if !ok {
name = "wangwu"
}
c.JSON(200, gin.H{"name": name})
})
r.Run()
}
JSON 请求
func main() {
r := gin.Default()
r.Any("/", func(c *gin.Context) {
// 获取 JSON 请求参数,只能获取所有然后自行解析
b, _ := c.GetRawData()
var m map[string]interface{}
// b 是一个 base64 编码,需要进行转码
_ = json.Unmarshal(b, &m)
c.JSON(200, m)
})
r.Run()
}
PATH 参数
func main() {
r := gin.Default()
r.Any("/user/:name", func(c *gin.Context) {
// 获取 URL 中的 PATH 参数
name := c.Param("name")
c.JSON(200, gin.H{"name": name})
})
r.Run()
}
参数绑定
使用参数绑定,可以将用户提交的参数快速映射到结构体而不需要单独赋值。
// 定义结构体
type UserInfo struct {
Username string `form:"username"`
Password string `form:"password"`
Age uint8
}
func main() {
r := gin.Default()
r.Any("/login", func(context *gin.Context) {
var u UserInfo
// 绑定参数 支持 Get、Post Json 等多种格式
_ = context.ShouldBind(&u)
// {"status":"OK","userInfo":{"Username":"zhangsan","Password":"123456","Age":0}}
context.JSON(200, gin.H{"status": "OK", "userInfo": u})
})
r.Run()
}
ShouldBind
按照下面的顺序解析请求中的数据:
- 如果是 GET 请求,只使用 Form 绑定引擎(query)。
- 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)
文件上传
单文件上传
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// 读取上传文件
f, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 拼接服务器保存目录
dst := path.Join("./", f.Filename)
// 保存上传文件到指定目录
_ = c.SaveUploadedFile(f, dst)
// 返回成功
c.JSON(http.StatusBadRequest, gin.H{
"status": "OK",
})
})
router.Run()
}
多文件上传
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// 读取多个上传文件
form, _ := c.MultipartForm()
files := form.File["files"]
// 遍历多个文件
for _, file := range files {
// 拼接服务器保存目录
dst := path.Join("./", file.Filename)
// 保存上传文件到指定目录
_ = c.SaveUploadedFile(file, dst)
}
// 返回成功
c.JSON(200, gin.H{
"status": "OK",
})
})
router.Run()
}
:::alert-info
通过 router.MaxMultipartMemory = 8 << 20
可以将提交文件的内存限制设置为 8 MiB,默认为 32 MiB。
注意:这不是上传文件的大小限制,而是上传文件后会先暂存到内存中,超过内存限制后,会将内存里的数据先写入磁盘,然后后续上传文件继续暂存到内存中
:::
重定向
HTTP 重定向
func main() {
router := gin.Default()
router.GET("/index", func(context *gin.Context) {
// 301 重定向到某个网页
context.Redirect(http.StatusMovedPermanently, "https://www.dbkuaizi.com")
})
router.Run()
}
路由重定向
与 HTTP 重定向不同的是,走 "/b" 的路由,但客户端不会发生跳转行为,同时也只有一次请求行为。
func main() {
router := gin.Default()
router.GET("/a", func(context *gin.Context) {
context.Request.URL.Path = "/b"
router.HandleContext(context)
})
router.GET("/b", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{"hello": "world"})
})
router.Run()
}
路由
普通路由
func main() {
router := gin.Default()
// 匹配 Get 请求
router.GET("/user", func(context *gin.Context) {...})
// 匹配 Post 请求
router.POST("/user", func(context *gin.Context) {...})
// 匹配 PUT 请求
router.PUT("/user", func(context *gin.Context) {...})
// 匹配 Delete 请求
router.DELETE("/user", func(context *gin.Context) {...})
// 匹配所有请求类型
router.Any("/user", func(context *gin.Context) {...})
// 当所有路由都未命中,则触发此方法 也就是 404
router.NoRoute(func(context *gin.Context) {
context.JSON(404, gin.H{"message": "404 Not FounD"})
})
router.Run()
}
路由组
可以将共同前缀的URL划分为一个路由组。
子路由使用 {}
包裹只是为了看着清晰,不使用 {}
也不会有任何区别。
func main() {
r := gin.Default()
userGroup := r.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
r.Run()
}
当然路由组也能嵌套:
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
// 嵌套路由组
xx := shopGroup.Group("xx")
xx.GET("/oo", func(c *gin.Context) {...})
}
中间件
Gin 允许开发者在处理请求的过程中,加入用户自己的钩子(Hook) 函数。这个钩子函数就叫做中间件,可以用来处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin 的中间件必须是一个 gin.HandlerFunc 类型(既函数必须接收一个 *gin.Context 类型的参数)。
func middleware(c *gin.Context) {
name, _ := c.GetQuery("name")
start := time.Now()
// 只有管理员才可以访问
if name != "admin" {
c.JSON(403, gin.H{"msg": "权限不足"})
// 拦截,不继续执行
c.Abort()
} else {
// 执行后面的内容
c.Next()
}
cost := time.Since(start)
fmt.Printf("耗时:%v\n", cost)
}
注册中间件
Gin 框架中可以注册任意数量的中间件。
func main() {
router := gin.Default()
// 全局中间件
router.Use(middleware)
// 单个中间件
router.GET("/index", middleware, func(context *gin.Context) {
context.JSON(200, gin.H{"msg": "index"})
})
// 路由组中间件
userGroup := router.Group("/user",middleware)
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
// 路由组中间件 另一种方式
userGroup.Use(middleware)
router.Run()
}
:::alert-warning
通过 gin.Default()
创建的路由,默认使用了 Logger
和 Recovery
中间件,如果不想使用这两个默认的中间件,可以使用 gin.New()
创建一个没有任何默认中间件的路由。
:::
中间件传参
上面的认证举例,管理员认证通过后,需要将管理员id 传递给后续的逻辑使用,我们可以通过 context.Set(key,value)
写入,然后在后续的逻辑中通过 context.Get(key)
等方式获取。
func middleware(c *gin.Context) {
name, _ := c.GetQuery("name")
// 只有管理员才可以访问
if name != "admin" {
c.JSON(403, gin.H{"msg": "权限不足"})
// 拦截,不继续执行
c.Abort()
} else {
// 设置登录用户名称
c.Set("name", name)
// 执行后面的内容
c.Next()
}
}
func main() {
router := gin.Default()
router.GET("/index", middleware, func(context *gin.Context) {
// 获取登录用户名称,返回两个参数用于判断获取是否成功
name, ok := context.Get("name")
if !ok {
name = "匿名用户"
}
// 如果不想判断直接获取
name = context.MustGet("name")
// 还可以获取不同的内建类型,在返回时会自动转一下
name = context.GetString("name")
context.JSON(200, gin.H{"msg": "index", "username": name})
})
router.Run()
}