Gin框架学习

Golang web开发常用的框架有Gin、Beego、Echo等。

Gin特点:

  • 封装比较好,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json
  • 模块比较少,可以自己由构建

Beego特点:

  • 社区良好,中文开发者很多,资料很丰富
  • 模块很丰富
  • 支持完整MVC、正则路由、restful Controller路由

1、 基本使用

下载Gin框架:

go install github.com/gin-gonic/gin@latest

go install github.com/gin-gonic/gin@v1.7.7

 运行一个案例程序:

package main

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

func main() {
    // 1.创建路由
   r := gin.Default()

   // 2.绑定路由规则
   // gin.Context,封装了request和response
   r.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
   
   // 3.监听端口,默认在8080
   r.Run(":8082")
}

go mod init ginDemo
go get github.com/gin-gonic/gin

go run main.go

2  路由设置

gin 框架中采用的路由库是基于httprouter做的(GitHub - julienschmidt/httprouter: A high performance HTTP request router that scales well

2.1 基本路由形式

Gin 支持 GET、POST、PUT、PATCH、DELETE、OPTIONS 等请求类型。

r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "hello word")
})

// 具体实现可单独定义一个函数
r.POST("/xxxpost",posthandle)
r.PUT("/xxxput")

2.2 PATH 参数处理

Path 路径中参数以:开头,如:/user/:name,匹配情况如下:

/user/123                匹配 123
/user/test                匹配 test
/user/123/test         不匹配
/user/                      不匹配

Path 路径中参数以*开头,如:/user/*action,匹配情况如下:

/user/123                匹配 /123
/user/test                匹配 /test
/user/123/test         匹配 /123/test
/user/                      匹配 /

demo: 

   // 2.绑定路由规则,执行的函数
   r.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		// 截取/
		action = strings.Trim(action, "/")
		//c.String(http.StatusOK, name+" is "+action)
		c.JSON(http.StatusOK, gin.H{
			"message": name+" is "+action,
		})
	})

2.3 查询参数处理

(1)普通参数

可通过 *gin.Context 的 Query 函数获取参数,如:/user?name=test。

可通过 *gin.Context 的 DefaultQuery 函数设置默认值,如:/user。

r.GET("/user", func(c *gin.Context) {
        //指定默认值
        name := c.DefaultQuery("name", "guest")
        c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})

(2)表单参数

r.POST("/form", func(c *gin.Context) {
        type := c.DefaultPostForm("type", "post")
        username := c.PostForm("username")
        password := c.PostForm("password")

        c.JSON(200, gin.H{
            "type":  type,
            "username": username,
            "password": password,
      })
})

(3)数组参数

可通过 *gin.Context 的 QueryMap 函数获取数组参数,如:/user?ids[a]=hello&ids[b]=word

r.GET("/user", func(c *gin.Context) {
  ids := c.QueryMap("ids")
  c.String(200, ids["a"]+" "+ids["b"])
})

 (4)文件上传

可通过 *gin.Context 的 FormFile 函数获取数组参数。


    r := gin.Default()
    //限制上传最大尺寸
    r.MaxMultipartMemory = 8 << 20
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.String(500, "上传图片出错")
        }
   
        c.SaveUploadedFile(file, file.Filename)
        c.String(http.StatusOK, file.Filename)
    })
    r.Run()

其他上传限制:

         _, file, err := c.Request.FormFile("file")
        if err != nil {
            log.Printf("Error when try to get file: %v", err)
        }
        
        if file.Size > 1024*1024*2 {
            fmt.Println("文件太大了")
            return
        }
      
        if file.Header.Get("Content-Type") != "image/png" {
            fmt.Println("只允许上传png图片")
            return
        }
        c.SaveUploadedFile(file, "./video/"+file.Filename)

2.4 路由分组

将具有相同路由 URL 前缀的进行分类处理,常见于不同版本的分组,如:/api/v1/api/v2。此外,还支持多层分组。

   r := gin.Default()
   // 路由组1 ,处理GET请求
   v1 := r.Group("/v1")
   // {} 是书写规范
   {
      v1.GET("/login", login)
      v1.GET("submit", submit)
   }
   v2 := r.Group("/v2")
   {
      v2.POST("/login", login)
      v2.POST("/submit", submit)
   }
   r.Run(":8000")

2.5 路由注册

func helloHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello www.topgoer.com!",
    })
}

func main() {
    r := gin.Default()
    r.GET("/topgoer", helloHandler)
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}

2.6 路由拆分

当项目的规模增大后就不太适合继续在项目的main.go文件中去实现路由注册相关逻辑了,我们会倾向于把路由部分的代码都拆分出来,形成一个单独的文件或包。

1)拆分成单路由文件

gin_demo
├── go.mod
├── go.sum
├── main.go
└── router.go

router.go

func helloHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello www.topgoer.com!",
    })
}

func setupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/topgoer", helloHandler)
    return r
}

main.go

func main() {
    r := setupRouter()
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}

2)拆分成多个文件

gin_demo
├── go.mod
├── go.sum
├── main.go
└── routers
    ├── blog.go
    └── shop.go

routers/shop.go 

func LoadShop(e *gin.Engine)  {
  e.GET("/hello", helloHandler)
  e.GET("/goods", goodsHandler)
  e.GET("/checkout", checkoutHandler)
  ...
}

routers/blog.go

func LoadBlog(e *gin.Engine) {
  e.GET("/post", postHandler)
  e.GET("/comment", commentHandler)
  ...
}

main.go

func main() {
    r := gin.Default()
    routers.LoadBlog(r)
    routers.LoadShop(r)
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}

3)拆分到子app

gin_demo
├── app
│   ├── blog
│   │   └── router.go
│   └── shop
│       └── router.go
├── go.mod
├── go.sum
├── main.go
└── routers
    └── routers.go

其中app/blog/router.go用来定义post相关路由信息,具体内容如下:

func Routers(e *gin.Engine) {
    e.GET("/post", postHandler)
    e.GET("/comment", commentHandler)
}

app/shop/router.go用来定义shop相关路由信息,具体内容如下:

func Routers(e *gin.Engine) {
    e.GET("/goods", goodsHandler)
    e.GET("/checkout", checkoutHandler)
}

routers/routers.go中根据需要定义Include函数用来注册子app中定义的路由,Init函数用来进行路由的初始化操作:

type Option func(*gin.Engine)

var options = []Option{}

// 注册app的路由配置
func Include(opts ...Option) {
    options = append(options, opts...)
}

// 初始化
func Init() *gin.Engine {
    r := gin.New()
    for _, opt := range options {
        opt(r)
    }
    return r
}

main.go中按如下方式先注册子app中的路由,然后再进行路由的初始化:

func main() {
    // 加载多个APP的路由配置
    routers.Include(shop.Routers, blog.Routers)
    // 初始化路由
    r := routers.Init()
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}

转自:路由拆分与注册 · Go语言中文文档

3 中间件

中间件分为全局中间件,单个路由中间件和群组中间件。

3.1 全局中间件

所有请求都要经过此中间件

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
    }
}

func myTime(c *gin.Context) {
    start := time.Now()
    c.Next()
    // 统计时间
    since := time.Since(start)
    fmt.Println("程序用时:", since)
}

func main() {
    // 1.创建路由
    r := gin.Default()
    // 默认使用了2个中间件Logger(), Recovery();可以使用r:= gin.New()不带任何中间件
    // 注册中间件
    r.Use(MiddleWare(),myTime)
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}

如果连续注册几个中间件则会是按照顺序先进后出的执行,遇到c.Next()就去执行下一个中间件里的c.Next()前面方法。

c.Abort() 表示终止调用该请求的剩余处理程序

3.2 局部中间件

    //单个路由中间件

    r.GET("/user", MiddleWare(), func(c *gin.Context) {
        // 取值
        req, _ := c.Get("request")
        fmt.Println("request:", req)
        // 页面接收
        c.JSON(200, gin.H{"request": req})
    })

    //群组中间件
    v1 := r.Group("/v1", gin.Logger(), gin.Recovery())
	{
		v1.GET("/", func(c *gin.Context) {
			c.JSON(200, gin.H{"name": "m1"})
		})
		v1.GET("/test", func(c *gin.Context) {
			c.JSON(200, gin.H{"name": "m1 test"})
		})
	}

3.3 自带中间件

Golang Gin框架 中间件(二)常用中间件(JWT验证、限流) - 掘金

gin.BasicAuth默认验证中间件

1、 全局校验
    r := gin.Default()

    r.Use(gin.BasicAuth(gin.Accounts{
        "admin": "123456",
    }))

    r.GET("/", func(c *gin.Context) {
        c.JSON(200, "首页")
    })

    r.Run(":8080")

2、局部校验
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, "首页")
    })

    adminGroup := r.Group("/admin")
    adminGroup.Use(gin.BasicAuth(gin.Accounts{
        "admin": "123456",
    }))

其他内置中间件:

func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc

 4 数据处理

4.1 数据绑定

Gin提供了两类绑定方法:

  • Must bind:
    • Methods:
      • Bind, BindJSON, BindXML, BindQuery, BindYAML

  • Should bind:
    • Methods:
      • ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML

1)ShouldBindJSON进行JSON格式绑定

// 定义接收数据的结构体
type Login struct {
   // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
   User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
   Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
   // 1.创建路由
   r := gin.Default()
   // JSON绑定
   r.POST("login", func(c *gin.Context) {
      // 声明接收的变量
      var json Login
      // 将request的body中的数据,自动按照json格式解析到结构体
      if err := c.ShouldBindJSON(&json); err != nil {
         // gin.H封装了生成json数据的工具
         c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
         return
      }
      // 判断用户名密码是否正确
      if json.User != "root" || json.Pssword != "admin" {
         c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
         return
      }
      c.JSON(http.StatusOK, gin.H{"status": "200"})
   })
   r.Run(":8000")
}

2)ShouldBindQuery

/bind/query?username=admin&password=123

r.GET("/bind/query", testQuery)

func testQuery(c *gin.Context) {
	var login Login
	if err := c.ShouldBindQuery(&login); err == nil {
		c.JSON(http.StatusOK, login)
	} else {
		c.String(http.StatusBadRequest, "error: %v", err)
		c.Abort()
		return
	}
}

3)Bind,ShouldBindWith进行form绑定

    1)Bind
    r.POST("/loginForm", func(c *gin.Context) {
        var form Login
        if err := c.Bind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
    })

    2)ShouldBindWith
    r.POST("/bind/form", testFrom)

    func testFrom(c *gin.Context) {
	var form Login
	if err := c.ShouldBindWith(&form, binding.Form); err == nil {
		c.JSON(http.StatusOK, form)
	} else {
		c.String(http.StatusBadRequest, "error: %v", err)
		c.Abort()
		return
	}
}

4)ShouldBindUri进行URL格式数据绑定

    r.GET("/:user/:password", func(c *gin.Context) {
        // 声明接收的变量
        var login Login
        
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
    }

4.2 数据响应

    // 1.json
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    })

    // 2. 结构体响应
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })

    // 3.XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })

    // 4.YAML响应
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "zhangsan"})
    })

    // 5.protobuf格式
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        // 定义数据
        label := "label"
        // 传protobuf格式数据
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

    //6.重定向
    r.GET("/index", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
    })

4.3 HTML模版渲染

go文件代码: 

    r.LoadHTMLGlob("tem/**/*")
    r.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "user/index.html", gin.H{"title": "我是测试", "address": "www.5lmh.com"})
    })

user/index.html文件代码:

{{ define "user/index.html" }}
{{template "public/header" .}}
             {{.address}}
{{template "public/footer" .}}
{{ end }}

public/header.html文件代码:

{{define "public/header"}}
<!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>{{.title}}</title>
</head>
    <body>

{{end}}

public/footer.html文件代码:

{{define "public/footer"}}
</body>
</html>
{{ end }}

当然也可以不分开写:

{{ define "user/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>{{.title}}</title>
</head>
    <body>
        {{.address}}
    </body>
</html>
{{ end }}

4.4 参数验证

使用结构体标签binding,结构体标签:Go系列:结构体标签 - 掘金

使用validate

1)结构体验证

type Person struct {
    //不能为空并且大于10
    Age      int       `form:"age" binding:"required,gt=10"`
    Name     string    `form:"name" binding:"required"`
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func main() {
    r := gin.Default()
    r.GET("/5lmh", func(c *gin.Context) {
        var person Person
        if err := c.ShouldBind(&person); err != nil {
            c.String(500, fmt.Sprint(err))
            return
        }
        c.String(200, fmt.Sprintf("%#v", person))
    })
    r.Run()
}

2)自定义验证

"gopkg.in/go-playground/validator.v8"

//定义
func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
    if value, ok := field.Interface().(string); ok {
        return value != "" && ("admin" == value)
    }
    return true
}

//注册
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
        v.RegisterValidation("NotNullAndAdmin", nameNotNullAndAdmin)
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值