Gin 框架的基本使用

Gin框架介绍

Go世界里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。

 

Gin框架安装与使用

安装

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

第一个Gin示例:

复制代码
package main
import (
    "github.com/gin-gonic/gin"
)
func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // GET:请求方式;/hello:请求的路径
    // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
    r.GET("/hello", func(c *gin.Context) {
        // c.JSON:返回JSON格式的数据
        c.JSON(200, gin.H{
            "message": "Hello world!",
        })
    })
    // 启动HTTP服务,默认在0.0.0.0:8080启动服务
    r.Run()
}
复制代码

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。

 

RESTful API

请求方法URL含义
GET /book 查询书籍信息
POST /book 创建书籍记录
PUT /book 更新书籍信息
DELETE /book 删除书籍信息

 

 

 

 

 

 

Gin框架支持开发RESTful API的开发。

复制代码
func main() {
    r := gin.Default()
    r.GET("/book", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "GET",
        })
    })

    r.POST("/book", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "POST",
        })
    })

    r.PUT("/book", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "PUT",
        })
    })

    r.DELETE("/book", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "DELETE",
        })
    })
}
复制代码

 

Gin渲染

1 HTML渲染

我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

复制代码
{{define "posts/index.html"}}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>posts/index</title>
</head>
<body>
    {{.title}}
</body>
</html>
{{end}}
复制代码

users/index.html文件的内容如下:

复制代码
{{define "users/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>users/index</title>
</head>
<body>
    {{.title}}
</body>
</html>
{{end}}
复制代码

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。

复制代码
func main() {
    r := gin.Default()
    r.LoadHTMLGlob("templates/**/*")
    //r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")
    r.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.html", gin.H{
            "title": "posts/index",
        })
    })

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

    r.Run(":8080")
}
复制代码

 

 

3 JSON渲染

复制代码
gin.H 是map[string]interface{}的缩写

func main() {
  // 1. 创建一个默认的路由引擎
  r := gin.Default()

  r.GET("/someJson", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
    "message": "Hello world",
    "status": 200,
    })
  })
  // 启动 HTTP 服务, 默认在127.0.0.1:8080 启动服务
  r.Run()
}

 

方法二 使用结构体

func main() {
  // 1. 创建一个默认的路由引擎
  r := gin.Default()

  // 定义一个结构体
  var msg struct {
    Name string `json:"name"`
    Message string `json:"message"`
    Age int64 `json:"age"`
  }

  msg.Age = 18
  msg.Message = "小王子"
  msg.Name = "hello, world"

  r.GET("/someJson", func(c *gin.Context) {
  c.JSON(http.StatusOK, msg)
})
  // 启动 HTTP 服务, 默认在127.0.0.1:8080 启动服务
  r.Run()
}

复制代码

 

XML渲染

复制代码
func main() {
    r := gin.Default()
    // gin.H 是map[string]interface{}的缩写
    r.GET("/someXML", func(c *gin.Context) {
        // 方式一:自己拼接JSON
        c.XML(http.StatusOK, gin.H{"message": "Hello world!"})
    })
    r.GET("/moreXML", func(c *gin.Context) {
        // 方法二:使用结构体
        type MessageRecord struct {
            Name    string
            Message string
            Age     int
        }
        var msg MessageRecord
        msg.Name = "小王子"
        msg.Message = "Hello world!"
        msg.Age = 18
        c.XML(http.StatusOK, msg)
    })
    r.Run(":8080")
}
复制代码

YMAL渲染

r.GET("/someYAML", func(c *gin.Context) {
    c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})

 

获取参数

获取querystring参数 【get请求】

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

复制代码
func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.GET("/user/search", func(c *gin.Context) {
        username := c.DefaultQuery("username", "小王子")
        //username := c.Query("username")
        address := c.Query("address")
        //输出json结果给调用方
        c.JSON(http.StatusOK, gin.H{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })
    r.Run()
}
复制代码

获取form参数 【post 请求】

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

复制代码
func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.POST("/user/search", func(c *gin.Context) {
        // DefaultPostForm取不到值时会返回指定的默认值
        //username := c.DefaultPostForm("username", "小王子")
        username := c.PostForm("username")
        address := c.PostForm("address")
        //输出json结果给调用方
        c.JSON(http.StatusOK, gin.H{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })
    r.Run(":8080")
}
复制代码

获取json参数

当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:

复制代码
r.POST("/json", func(c *gin.Context) {
    // 注意:下面为了举例子方便,暂时忽略了错误处理
    b, _ := c.GetRawData()  // 从c.Request.Body读取请求数据
    // 定义map或结构体
    var m map[string]interface{}
    // 反序列化
    _ = json.Unmarshal(b, &m)

    c.JSON(http.StatusOK, m)
})
复制代码

获取path参数

请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

复制代码
func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.GET("/user/search/:username/:address", func(c *gin.Context) {
        username := c.Param("username")
        address := c.Param("address")
        //输出json结果给调用方
        c.JSON(http.StatusOK, gin.H{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })

    r.Run(":8080")
}
复制代码

参数绑定

ShouldBind

想要使用 bind 那么就必须设置 tag

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

复制代码
// Binding from JSON
type Login struct {
    User     string `form:"user" json:"user" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

func main() {
    router := gin.Default()

    // 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var login Login

        if err := c.ShouldBind(&login); err == nil {
            fmt.Printf("login info:%#v\n", login)
            c.JSON(http.StatusOK, gin.H{
                "user":     login.User,
                "password": login.Password,
            })
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
    })

    // 绑定form表单示例 (user=q1mi&password=123456)
    router.POST("/loginForm", func(c *gin.Context) {
        var login Login
        // ShouldBind()会根据请求的Content-Type自行选择绑定器
        if err := c.ShouldBind(&login); err == nil {
            c.JSON(http.StatusOK, gin.H{
                "user":     login.User,
                "password": login.Password,
            })
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
    })

    // 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
    router.GET("/loginForm", func(c *gin.Context) {
        var login Login
        // ShouldBind()会根据请求的Content-Type自行选择绑定器
        if err := c.ShouldBind(&login); err == nil {
            c.JSON(http.StatusOK, gin.H{
                "user":     login.User,
                "password": login.Password,
            })
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
    })

    // Listen and serve on 0.0.0.0:8080
    router.Run(":8080")
}
复制代码

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  2. 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Formform-data)。

 

ShouldBindJSON

传入一个json的body

复制代码
package main

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

type PostParams struct {
    Name string `json:"name"` 
    Age  int    `json:"age"`
    Sex  bool   `json:"sex"`
}

func main() {
    r := gin.Default()
    r.POST("/testBind", func(c *gin.Context) {
        var p PostParams
        err := c.ShouldBindJSON(&p) //将传过来的json赋给p
        if err != nil {
            c.JSON(200, gin.H{
                "msg":  "bindjson报错了",
                "data": gin.H{},
            })
        } else {
            c.JSON(200, gin.H{
                "msg":  "请求成功",
                "data": p, //如果请求成功则把p的值打印出来
            })
        }
    })
    r.Run(":8080")
}
复制代码

在postman发送一个请求,得到的结果如下

 

 

 

ShouldBindUri

shouldbinduri的请求参数应该写在uri里

复制代码
package main

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

type PostParams struct{
    Name string `json:"name"  uri:"name"`
    Age int `json:"age" uri:"age"`
    Sex bool `json:"sex" uri:"sex"`
}

func main(){
    r := gin.Default()
    r.POST("/testBind/:name/:age/:sex", func(c *gin.Context){
        var p PostParams
        err := c.ShouldBindUri(&p)
        if err !=nil{
            c.JSON(200, gin.H{
                "msg": "报错了",
                "data": gin.H{},
            })
        }else{
            c.JSON(200, gin.H{
                "msg": "binduri请求成功",
                "data": p,
            })
        }
    })
    r.Run(":8080")
}
复制代码

这里记得要在PostParams struct的定义里写上uri: “xxx”

uri中按顺序写入name,age和sex的参数

 

 

 

ShouldBindQuery

shouldbindquery的请求参数也是写在uri里,跟shouldbinduri稍微有些区别

复制代码
package main

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

type PostParams struct{
    Name string `json:"name"  uri:"name" form:"name"`
    Age int `json:"age" uri:"age" form:"age"`
    Sex bool `json:"sex" uri:"sex" form:"sex"`
}
//http://localhost:8080/testBind?age=18&name=kaka&sex=true
func main(){
    r := gin.Default()
    r.POST("/testBind", func(c *gin.Context){
        var p PostParams
        err := c.ShouldBindQuery(&p)
        if err !=nil{
            c.JSON(200, gin.H{
                "msg": "报错了",
                "data": gin.H{},
            })
        }else{
            c.JSON(200, gin.H{
                "msg": "bindquery请求成功",
                "postdata": p,
            })
        }
    })
    r.Run(":8080")
}
复制代码

 

 

 

 

 总结

bind就是先创立一个结构体,然后把扔过来的参数通过一种绑定的形式直接映射到某一个结构体的实例上去

bind模式如何使用(一般用shouldbind)

  • bind模式一定要设置tag
  • bind可以绑json、form、uri

shouldbind

   表单验证

  • 即binding:“required”

  自定义验证

  • 即上面的那个限制18岁的传参

shouldbind

  ShouldBindJSON使用方法

  • post一个json的

  ShouldBindJQuery使用方法

  • post http://127.0.0.1:8080/testBind?name=kaka&age=19&sex=false

  ShouldBindUri使用方法

  • post http://127.0.0.1:8080/testBind/jack/18/true

  ShouldBind使用方法

  • shouldbind会根据header里面的Content-Type来自动选择绑定器

 

 

 

 

 

 

 

 

 

 

 

文件上传

单个文件上传

文件上传前端页面代码:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="上传">
</form>
</body>
</html>
复制代码

后端gin框架部分代码:

复制代码
func main() {
    router := gin.Default()
    // 处理multipart forms提交文件时默认的内存限制是32 MiB
    // 可以通过下面的方式修改
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // 单个文件
        file, err := c.FormFile("f1")
        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),
        })
    })
    router.Run()
}
复制代码

 

 

 

多个文件上传

复制代码
func main() {
    router := gin.Default()
    // 处理multipart forms提交文件时默认的内存限制是32 MiB
    // 可以通过下面的方式修改
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // Multipart form
        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)),
        })
    })
    router.Run()
}
复制代码

 

 

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/test", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

路由重定向,使用HandleContext

r.GET("/test", func(c *gin.Context) {
    // 指定重定向的URL
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

 

 

Gin路由

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

此外,还有一个可以匹配所有请求方法的Any方法如下:

r.Any("/test", func(c *gin.Context) {...})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
        c.HTML(http.StatusNotFound, "views/404.html", nil)
    })
 

路由组

我们可以将拥有共同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中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

 
复制代码
// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
        // 调用该请求的剩余处理程序
        c.Next()
        // 不调用该请求的剩余处理程序
        // c.Abort()
        // 计算耗时
        cost := time.Since(start)
        log.Println(cost)
    }
}
复制代码

 

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

 

为全局路由注册

复制代码
func main() {
    // 新建一个没有任何默认中间件的路由
    r := gin.New()
    // 注册一个全局中间件
    r.Use(StatCost())
    
    r.GET("/test", func(c *gin.Context) {
        name := c.MustGet("name").(string) // 从上下文取值
        log.Println(name)
        c.JSON(http.StatusOK, gin.H{
            "message": "Hello world!",
        })
    })
    r.Run()
}
复制代码

为某个路由单独注册

// 给/test2路由单独注册中间件(可注册多个)
    r.GET("/test2", StatCost(), func(c *gin.Context) {
        name := c.MustGet("name").(string) // 从上下文取值
        log.Println(name)
        c.JSON(http.StatusOK, gin.H{
            "message": "Hello world!",
        })
    })

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:

shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2:

shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

 

 

 

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

 

中间件注册的顺序 【中间件与 handler 的顺序】

复制代码
    r := gin.New()
    // 接收gin框架默认的日志
    // recover掉项目可能出现的panic,并使用zap记录相关日志
    r.Use(logger.GinLogger(), logger.GinRecovery(true))
    // 注册业务路由
    r.POST("/signup", controller.SignUpHandler)
    r.Use(middlewares.JWTAuthMiddleware())
    // 登录
    r.GET("/login", controller.LoginHandler)
复制代码

路由 signup,有两个中间件 GinLogger 和 GinRecovery

路由 login,有三个中间件 GinLogger 和 GinRecovery 以及 JWTAuthMiddleware

 

posted @   dogRuning  阅读(586)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示