gin的使用

安装

  1. go install github.com/gin-gonic/gin@latest
  2. // go get -u github.com/gin-gonic/gin

引入

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

开始

生成实例:

  1. // 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
  2. r := gin.Default()

运行

  1. r.Run()
  2. // 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
  3. // r.Run(":8888") 传入参数切换端口

路由

  1. r.GET("/someGet", getting)
  2. r.POST("/somePost", posting)
  3. r.PUT("/somePut", putting)
  4. r.DELETE("/someDelete", deleting)
  5. r.PATCH("/somePatch", patching)
  6. r.HEAD("/someHead", head)
  7. r.OPTIONS("/someOptions", options)

注:

对于每一种路由都是传入两个参数,一个是路由的路径,第二个是处理请求的函数

对于请求函数例如:

  1. func(c *gin.Context) {
  2. // 其他处理代码
  3. ...
  4. // 其他处理代码
  5. // 响应
  6. c.String(http.StatusOK, "hello world")
  7. }

需要传入一个参数 c 为gin的上下文类型

gin中存在多种不同的响应类型

  1. 返回JSON数据
  1. func(c *gin.Context) {
  2. c.JSON(http.StatusOK, gin.H{
  3. "message": "hello world",
  4. "status": "success",
  5. })
  6. }
  1. 返回HTML模板
  1. func(c *gin.Context) {
  2. c.HTML(http.StatusOK, "index.tmpl", gin.H{
  3. "title": "Main website",
  4. })
  5. }
  6. // gin.H{} 传递给模板,模板中使用{{.title}}使用,其他的更多用法比如循环,分支,之类可以查找go官方文档
  7. // https://pkg.go.dev/text/template
  8. // https://pkg.go.dev/html/template

需要先设置模板目录:

  1. router := gin.Default()
  2. router.LoadHTMLGlob("templates/*")
  1. 返回XML数据
  1. func(c *gin.Context) {
  2. c.XML(http.StatusOK, gin.H{
  3. "message": "hello world",
  4. "status": "success",
  5. })
  6. }
  1. 返回YAML数据
  1. func(c *gin.Context) {
  2. c.YAML(http.StatusOK, gin.H{
  3. "message": "hello world",
  4. "status": "success",
  5. })
  6. }
  1. 返回ProtoBuf数据
  1. func(c *gin.Context) {
  2. data := &protoexample.Test{
  3. Label: proto.String("hello"),
  4. Reps: []int64{1, 2, 3},
  5. }
  6. c.ProtoBuf(http.StatusOK, data)
  7. }
  1. 返回文件
  1. func(c *gin.Context) {
  2. c.File("/path/to/file")
  3. }
  1. 返回文件流
  1. func(c *gin.Context) {
  2. c.FileFromFS("/path/to/file", http.Dir("/var/www"))
  3. }
  1. 返回数据流
  1. func(c *gin.Context) {
  2. reader := strings.NewReader("some data")
  3. c.DataFromReader(http.StatusOK, int64(reader.Len()), "text/plain", reader, nil)
  4. }
  1. 重定向
  1. func(c *gin.Context) {
  2. c.Redirect(http.StatusMovedPermanently, "<https://www.google.com/>")
  3. }
  4. func(c *gin.Context) {
  5. c.Request.URL.Path = "/"
  6. r.HandleContext(c)
  7. }
  1. 返回纯文本
  1. func(c *gin.Context) {
  2. c.String(http.StatusOK, "hello world")
  3. }

关于Gin框架中的gin.H

gin.H是Gin框架中一个非常实用的快捷方式,它实际上是一个类型别名(type alias),定义如下:

  1. type H map[string]interface{}

详细解释

  1. 本质gin.H就是一个map[string]interface{}的别名

    • string是键的类型
    • interface{}是值的类型,表示可以接受任何类型的值
  2. 用途

    • 主要用于构建JSON/XML/YAML等格式的响应数据
    • 提供了一种简洁的方式来创建动态数据结构
  3. 示例
  1. // 创建一个简单的JSON响应
  2. data := gin.H{
  3. "name": "John",
  4. "age": 30,
  5. "tags": []string{"golang", "gin"},
  6. }
  7. c.JSON(http.StatusOK, data)
  1. 优点

    • 比直接使用map[string]interface{}更简洁
    • 提高了代码可读性
    • 是Gin框架中的惯用写法
  2. 与其他方式的对比
  1. // 使用gin.H
  2. gin.H{"key": "value"}
  3. // 等同于
  4. map[string]interface{}{"key": "value"}
  5. // 也等同于
  6. struct {
  7. Key string `json:"key"`
  8. }{Key: "value"}
  1. 嵌套使用
  1. data := gin.H{
  2. "user": gin.H{
  3. "name": "Alice",
  4. "age": 25,
  5. },
  6. "status": "active",
  7. }
  1. 注意事项

    • 虽然方便,但对于复杂数据结构,有时使用自定义结构体可能更合适
    • 在性能敏感的场景,直接使用结构体可能比gin.H更高效

gin.H是Gin框架中一个简单但强大的工具,它简化了动态数据的构建过程,特别是在构建API响应时非常有用。

在Gin中使用自定义结构体的示例

当然可以。在Gin框架中,使用自定义结构体作为响应通常比gin.H更规范,特别是在处理复杂数据结构时。以下是几个示例:

  1. 基本结构体示例
  1. // 定义自定义结构体
  2. type User struct {
  3. ID int `json:"id"`
  4. Username string `json:"username"`
  5. Email string `json:"email"`
  6. }
  7. func getUser(c *gin.Context) {
  8. user := User{
  9. ID: 1,
  10. Username: "john_doe",
  11. Email: "john@example.com",
  12. }
  13. c.JSON(http.StatusOK, user)
  14. }
  1. 嵌套结构体示例
  1. type Address struct {
  2. City string `json:"city"`
  3. Country string `json:"country"`
  4. }
  5. type UserProfile struct {
  6. Name string `json:"name"`
  7. Age int `json:"age"`
  8. Address Address `json:"address"`
  9. }
  10. func getProfile(c *gin.Context) {
  11. profile := UserProfile{
  12. Name: "Alice",
  13. Age: 28,
  14. Address: Address{
  15. City: "Shanghai",
  16. Country: "China",
  17. },
  18. }
  19. c.JSON(http.StatusOK, profile)
  20. }
  1. 带切片的结构体
  1. type Post struct {
  2. ID int `json:"id"`
  3. Title string `json:"title"`
  4. Tags []string `json:"tags"`
  5. }
  6. func getPosts(c *gin.Context) {
  7. posts := []Post{
  8. {
  9. ID: 1,
  10. Title: "Gin框架入门",
  11. Tags: []string{"golang", "web"},
  12. },
  13. {
  14. ID: 2,
  15. Title: "Go语言并发编程",
  16. Tags: []string{"golang", "concurrency"},
  17. },
  18. }
  19. c.JSON(http.StatusOK, posts)
  20. }
  1. 作为请求体和响应体
  1. type LoginRequest struct {
  2. Username string `json:"username" binding:"required"`
  3. Password string `json:"password" binding:"required"`
  4. }
  5. type LoginResponse struct {
  6. Token string `json:"token"`
  7. Exp int64 `json:"exp"`
  8. }
  9. func login(c *gin.Context) {
  10. var req LoginRequest
  11. if err := c.ShouldBindJSON(&req); err != nil {
  12. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  13. return
  14. }
  15. // 验证逻辑...
  16. resp := LoginResponse{
  17. Token: "generated_token_here",
  18. Exp: time.Now().Add(24 * time.Hour).Unix(),
  19. }
  20. c.JSON(http.StatusOK, resp)
  21. }
  1. 带omitempty标签的示例
  1. type Product struct {
  2. ID int `json:"id"`
  3. Name string `json:"name"`
  4. Price float64 `json:"price"`
  5. Description string `json:"description,omitempty"` // 为空时不显示
  6. Stock int `json:"-"` // 永远不序列化
  7. }
  8. func getProduct(c *gin.Context) {
  9. product := Product{
  10. ID: 101,
  11. Name: "Laptop",
  12. Price: 999.99,
  13. // Description和Stock未赋值
  14. }
  15. c.JSON(http.StatusOK, product)
  16. // 输出: {"id":101,"name":"Laptop","price":999.99}
  17. }

Gin框架中的参数传递方式

一、路由传入的参数类型

  1. 路径参数 (Path Parameters)
  1. router.GET("/users/:id", func(c *gin.Context) {
  2. id := c.Param("id") // 获取路径参数
  3. })
  1. 查询参数 (Query Parameters)
  1. // 请求: /search?q=gin&page=1
  2. q := c.Query("q") // "gin"
  3. page := c.DefaultQuery("page", "1") // 带默认值
  1. 表单参数 (Form Data)
  1. // Content-Type: application/x-www-form-urlencoded
  2. name := c.PostForm("name")
  3. age := c.DefaultPostForm("age", "18") // 带默认值
  1. 多部分表单 (Multipart/Form-Data)

用于文件上传:

  1. file, _ := c.FormFile("file")
  2. // 处理文件上传
  3. if file, err := c.FormFile("avatar"); err == nil {
  4. // 保存文件逻辑
  5. user.Avatar = "/uploads/" + file.Filename
  6. }
  1. Header 参数
  1. token := c.GetHeader("Authorization")
  1. Cookie 参数
  1. value, err := c.Cookie("cookie_name")
  1. 原始Body (Raw Body)
  1. body, _ := ioutil.ReadAll(c.Request.Body)

二、POST请求可传递的参数类型

POST请求可以通过以下Content-Type传递不同类型的参数:

  1. application/x-www-form-urlencoded
  • 传统的表单提交格式
  • 通过c.PostForm()获取
  • 示例:
  1. // 客户端发送: name=John&age=30
  2. name := c.PostForm("name")
  1. multipart/form-data
  • 用于文件上传和表单混合数据
  • 通过c.FormFile()获取文件
  • 通过c.PostForm()获取普通字段
  • 示例:
  1. file, _ := c.FormFile("avatar")
  2. name := c.PostForm("username")
  1. application/json
  • 最常用的API数据格式
  • 通过c.ShouldBindJSON()绑定到结构体
  • 示例:
  1. type User struct {
  2. Name string `json:"name"`
  3. Age int `json:"age"`
  4. }
  5. var user User
  6. if err := c.ShouldBindJSON(&user); err != nil {
  7. // 错误处理
  8. }
  9. //例如:
  10. // 绑定JSON或Form数据
  11. //var user User
  12. //if err := c.ShouldBind(&user); err != nil {
  13. // c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  14. // return
  15. //}
  1. text/xml / application/xml
  • XML格式数据
  • 通过c.ShouldBindXML()绑定
  • 示例:
  1. if err := c.ShouldBindXML(&obj); err != nil {
  2. // 错误处理
  3. }
  1. application/protobuf
  • Protocol Buffers格式
  • 高性能二进制格式
  • 示例:
  1. var message pb.Message
  2. if err := proto.Unmarshal(c.Request.Body, &message); err != nil {
  3. // 错误处理
  4. }
  1. 其他自定义格式

可以通过c.GetRawData()获取原始数据后自行解析:

  1. data, _ := c.GetRawData()
  2. // 自定义解析逻辑

三、参数绑定最佳实践

  1. 对于简单参数:直接使用Query(), PostForm()等方法
  2. 对于复杂结构:使用ShouldBind系列方法绑定到结构体
  3. 文件上传:使用FormFile()
  4. API开发:优先使用JSON格式
  5. 性能敏感场景:考虑使用protobuf

四、完整示例

  1. type User struct {
  2. ID int `json:"id" form:"id"`
  3. Username string `json:"username" form:"username"`
  4. Avatar string `json:"avatar"` // 文件上传单独处理
  5. }
  6. func updateUser(c *gin.Context) {
  7. // 路径参数
  8. id := c.Param("id")
  9. // 绑定JSON或Form数据
  10. var user User
  11. if err := c.ShouldBind(&user); err != nil {
  12. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  13. return
  14. }
  15. // 处理文件上传
  16. if file, err := c.FormFile("avatar"); err == nil {
  17. // 保存文件逻辑
  18. user.Avatar = "/uploads/" + file.Filename
  19. }
  20. // 处理其他逻辑...
  21. }

分组路由(Grouping Routes)

如果有一组路由,前缀都是/api/v1开头,是否每个路由都需要加上/api/v1这个前缀呢?答案是不需要,分组路由可以解决这个问题。利用分组路由还可以更好地实现权限控制,例如将需要登录鉴权的路由放到同一分组中去,简化权限控制。

  1. // group routes 分组路由
  2. defaultHandler := func(c *gin.Context) {
  3. c.JSON(http.StatusOK, gin.H{
  4. "path": c.FullPath(),
  5. })
  6. }
  7. // group: v1
  8. v1 := r.Group("/v1")
  9. {
  10. v1.GET("/posts", defaultHandler)
  11. v1.GET("/series", defaultHandler)
  12. }
  13. // group: v2
  14. v2 := r.Group("/v2")
  15. {
  16. v2.GET("/posts", defaultHandler)
  17. v2.GET("/series", defaultHandler)
  18. }

中间件使用

示例:

  1. // 作用于全局
  2. r.Use(gin.Logger())
  3. r.Use(gin.Recovery())
  4. // 作用于单个路由
  5. r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
  6. // 作用于某个组
  7. authorized := r.Group("/")
  8. authorized.Use(AuthRequired())
  9. {
  10. authorized.POST("/login", loginEndpoint)
  11. authorized.POST("/submit", submitEndpoint)
  12. }

参考文档:

https://gin-gonic.com/zh-cn/docs/

https://geektutu.com/post/quick-go-gin.html

添加新评论