Gin框架笔记

环境搭建安装

  1. 设置代理镜像

    由于访问GitHub速度缓慢且可能有权限问题,我们需要设置一个代理

    go env -w GOPOXY=https://goproxy.io,direct
    
  2. 下载gin框架

    go get -u github.com/gin-gonic/gin
    
  3. 第一个代码

    func main() {
        r := gin.Default()//引擎初始化
        r.GET("/hello", func(c *gin.Context) {//context为文本工具
            fmt.Println(c.FullPath())	//打印端口号
            c.Writer.Write([]byte("Hello gin\n"))
            //面对get请求写入一个切片数据
        })
        r.Run() // 监听并在 0.0.0.0:8080 上启动服务
    }
    

    若要指定端口

    r.Run(":8090")	//在 :8090端口上运行
    

请求处理

引擎创建

下列两个语句都是用来创建引擎的。

engine1 = gin.Default()
engine2 = gin.New()

两个语句的区别是 gin.Default() 也会使用 gin.New() 创建 engine 实例,不过会默认调用LoggerRecovery 中间件更方便于操作。

Logger 是负责进行打印并输出日志的中间件,方便开发者进行程序调试;REcovery 会恢复程序执行,并返回服务器500内部错误。通常情况我们都默认使用 gin.Default() 创建 Engine 实例。

处理语句

Handle通用请求
engine.Handle(`方法`"GET"`path`"/hello",func(c *gin.Context){
    
})
GET请求
基本框架
engine.GET("`路径`path",func(context *gin.Context`上下文结构体`){
    "相关操作"
})
相关操作
//http://localhost:8080/hello?name=Goland

//调用路径
fmt.Println(context.FullPath())

//解析路径值的两种方法
name := context.DefaultQuery(`key值:`"name",`默认值:`"hello")
name := context.Query(`key值:`"name")

POST请求
基本框架
engine.POST("`路径`path",func(context *gin.Context){
    
})
相关操作
//http://localhost:8080/login
//内容在表单中

fmt.Println(context.FullPath())

//获取对应值
username := context.PostForm("username")

//获取对应值,并返回是否获取成功
password,exist:= context.GETPostForm("password")
if exist{
   context.Writer.Write([]byte("成功获取密码"))
}
DELETE请求
基本框架

由于id是变化的,所以用:去获取id的值。

engine.DELETE("/user/:id",func(context *gin.Context){
    
})
相关操作
//获取ID为例位置的变量
userID := context.Param(`key值`"id")

综合实例

//http://localhost:8080/hello?name=Goland
engine := gin.Default()

//GET请求
engine.GET("path",func(context *gin.Context){
    fmt.Println(context.FullPath())
    
    //第一种,如果查询不到name的值就给name一个默认值
    name := context.DefaultQuery(`key:`"name",`default:`"hello")
    
    //第二种,直接查询没有默认值
    name := context.Query(`key:`"name")
})

//POST请求
engine.POST("path",fun(context *gin.Context){
    fmt.Println(context.FullPath())
    
   	//第一种只获取对应值
    username := context.PostForm("对应key值(例如username)") 
    
   	//第二种会返回第二参数,表示是否获取到了对应值
    username,exist := context.GetPostForm(`key:`"username")
    
    //判断是否获取到了,然后做处理
    if exist{
        "对应处理"
        fmt.Println(username)
    }
})

//DELETE请求
engine.DELETE("/user/:id",func(context *gin.Context){
    //获取id位置变量
    uesID := context.Param("id")
   
})

请求参数绑定和多数据处理格式

前导

我们通过请求处理的基本学习,可以了解到如何获得表单数据,但是我们的样例中只有usernamepassword 两个数据需要获取,但实际项目中我们需要获取多个数据,这样一次获取一个数据开发效率就相对较慢,且封装性差。

Gin框架就提供了表单实体绑定的功能,可以将表单数据与我们自己定义的结构进行绑定。

表单实体绑定

Gin框架提供了数据结构体和表单提交数据绑定的功能,提高表达数据获取的效率。如下所示:

以一个学生学习获取为实例。表单提交的时候包含两个数据:Name和Classes

type Student struct{
    Name string `form:"name" binding:"required"`
    Classes string `form:"classes" binding:"required"`
}

创建一个Student结构用于接受学生数据,通过 tag 标签的方式设置结构体中每个数据对应字段所 form (绑定)的属性名,binding 则是设置属性是否必须,默认是必须。

这里要注意一个要点,结构体中的变量定义必须是大写,否则无法成功获取到对应绑定的值。

ShouldBindQuery

使用 ShouldBindQuery 可以实现 GET 方式的数据请求的绑定,实现如下:

func main(){
    
    engine := gin.Default()
    
    //htpp://localhos:8080/hello?name=davie&classes=软件工程
    engine.GET("/hello",func(context *gin.Context){
        fmt.Println(context.FullPath())
        //定义结构体变量
        var stu Student
        //定义错误返回信息,成功绑定stu则无返回值
        err := context.ShouldBindQuery(&stu)
        if err != nil{
            //绑定失败输出错误日志
            log.Fatal(err.Error())
            return
        }
        
        fmt.Println(stu.Name)
        fmt.Println(stu.Classes)
        context.Writer.Write([]byte("hello," + stu.Name))
    })
    
}

type Student struct{
    Name string `form:"name"`
    Classes string `form:"classes"`
}

这里触及到 log 日志的包语句,log.Fatal(输出内容) 用来输出日志,方便开发者查询错误。

ShouldBind

使用ShouldBind可以实现Post方式的提交数据的绑定工作,具体如下:

func main() {

    engine := gin.Default()

    engine.POST("/register", func(context *gin.Context) {
        fmt.Println(context.FullPath())
        //定义结构体变量
        var reg Register
        //绑定并判断是否绑定成功
        if err := context.ShouldBind(&reg); err != nil {
            log.Fatal(err.Error())
            return
        }

        fmt.Println(reg.UserName)
        fmt.Println(reg.Phone)
        context.Writer.Write([]byte(reg.UserName + " Register "))

    })

    engine.Run()
}

type Register struct {
    UserName string form:"name"
    Phone    string form:"phone"
    Password string form:"pwd"
}
ShouldBindJson

当客户端使用 Json 格式进行数据提交时,可以采用 ShouldBindJson 对数据进行绑定

func main() {

    engine := gin.Default()

    engine.POST("/addstudent", func(context *gin.Context) {
        fmt.Println(context.FullPath())
        //1
        var person Person
        //2
        if err := context.BindJSON(&person); err != nil {
            log.Fatal(err.Error())
            return
        }

        fmt.Println("姓名:" + person.Name)
        fmt.Println("年龄:", person.Age)
        context.Writer.Write([]byte(" 添加记录:" + person.Name))
    })

    engine.Run()
}

type Person struct {
    Name string form:"name"
    Sex  string form:"sex"
    Age  int    form:"age"
}

多数据格式返回请求

[]byte

返回格式为byte切片型,我们一般通过context.Writer.Write 方法写入[]byte 数据。

...
engine := gin.Default()
engine.GET("/hellobyte", func(context *gin.Context) {
        fullPath := "请求路径:" + context.FullPath()
        fmt.Println(fullPath)
        context.Writer.Write([]byte(fullPath))
})
engine.Run()
...

如上我们使用了Writer 方法写入数据,Writer 是gin框架中封装的一个ResponseWriter 接口类型。

源码如下:

type ResponseWriter interface {
    //writer在http.ResponseWriter包中的一个方法
    //以下为4个http包
    http.ResponseWriter	
    http.Hijacker
    http.Flusher
    http.CloseNotifier

    // Returns the HTTP response status code of the current request.
    Status() int

    // Returns the number of bytes already written into the response http body.
    // See Written()
    Size() int

    // Writes the string into the response body.
    WriteString(string) (int, error)

    // Returns true if the response body was already written.
    Written() bool

    // Forces to write the http header (status code + headers).
    WriteHeaderNow()

    // get the http.Pusher for server push
    Pusher() http.Pusher
}

string

除了Writer 方法意外,ResponseWriter 自身还封装了WriteString 方法返回数据。

...
engine.GET("/hellostring", func(context *gin.Context) {
        fullPath := "请求路径:" + context.FullPath()
        fmt.Println(fullPath)
        context.Writer.WriteString(fullPath)
})
...

JSON

除了使用context.Writer对象返回[]bytestring类型的数据以外。在项目开发中,JSON格式规范使用的更为普遍。gin为了方便开发者更方便的使用该框架进行项目开发,直接支持将返回数据组装成JSON格式进行返回。

  • map类型返回
context.JSON(`状态码`200,map[string]interface{}{
    //数据定义与赋值
})

以下为实例

...
engine := gin.Default()
engine.GET("/hellojson", func(context *gin.Context) {
    fullPath := "请求路径:" + context.FullPath()
    fmt.Println(fullPath)

    context.JSON(200, map[string]interface{}{
        "code":    1,
        "message": "OK",
        "data":    fullPath,
    })
})
engine.Run(":9000") 
...
  • 结构体类型返回
//通用请求返回结构体定义
type Response struct {
    Code    int         json:"code"
    Message string      json:"msg"
    Data    interface{} json:"data"
}

engine.GET("/jsonstruct", func(context *gin.Context) {
    fullPath := "请求路径:" + context.FullPath()
    fmt.Println(fullPath)
    resp := Response{Code: 1, Message: "Ok", Data: fullPath}
    //这里返回需要取地址
    context.JSON(200, &resp)
})

html

gin框架也支持HTML格式的数据返回,可以直接渲染HTML页面。

...
engine := gin.Default()
//设置html的目录
engine.LoadHTMLGlob("./html/*")

engine.GET("/hellohtml", func(context *gin.Context) {
    fullPath := "请求路径:" + context.FullPath()
	//设置了加载目录,则该路径不用加`./html`前缀了
    context.HTML(http.StatusOK, "index.html", gin.H{
        "title":    "Gin教程",
        "fullpath": fullPath,
    })
})
engine.Run(":9000")
...

HTML页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>
</head>
<body>
<h1>Gin语言教程</h1>
{{.fullpath}}
</body>
</html>

加载静态资源文件

在上面index.html的基础上,添加一张img图片进行展示。需要将img所在的目录进行静态资源路径设置才可以生效

engine.Static("/img","./img")

第一个路径是前端请求的路径,第二个是本地工程的一个路径,做了一个文件映射。

以下为html模板

<div>
    <img src="../img/img1.jpg">
</div>

同理,在项目开发时,一些静态的资源文件如html、js、css等可以通过静态资源文件设置的方式来进行设置。

使用路由组分类处理请求

背景

在实际的项目开发中,均是模块化开发。同一模块内的功能接口,往往会有相同的接口前缀。比如如下所示:

注册: http://localhost:9000/user/register
登录: http://localhost:9000/user/login
用户信息: http://localhost:9000/user/info
删除: http://localhost:9000/user/1001

类似这种接口前缀统一,均属于相同模块的功能接口。可以使用路由组进行分类处理。

Group

gin框架中可以使用路由组来实现对路由的分类。

路由组是router.Group 中的一个方法,用于对请求进行分组。如下列所示:

...
engine := gin.Default()
//类似指针作用,指向了/user前缀的路径
userGroup := engine.Group("/user")
//分组对/user后续路径分类处理
userGroup.GET("/register", registerHandle)
userGroup.GET("/login", loginHandle)
userGroup.GET("/info", infoHandle)
engine.Run(":9000")
...

func registerHandle(context *gin.Context) {
    fullPath := " 用户注册功能 " + context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriteString(fullPath)
}

func loginHandle(context *gin.Context) {
    fullPath := " 用户登录功能 " + context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriteString(fullPath)
}

func infoHandle(context *gin.Context) {
    fullPath := " 信息查看功能 " + context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriteString(fullPath)
}

Group返回一个 RouterGroup 指针对象,而 RouterGroup 是gin框架中的一个路由组结构体定义。

RouterGroup 的定义如下所示:

type RouterGroup struct{
    Handlers HandlersChain
    basePath string
    engine *Engine
    root bool
}

RouterGroup 实现了 IRoutes 中定义的方法,包含统一处理请求的Handle和分类型处理的GET、POST等。IRoutes 定义如下:

type IRoutes interface {
    Use(...HandlerFunc) IRoutes

    Handle(string, string, ...HandlerFunc) IRoutes
    Any(string, ...HandlerFunc) IRoutes
    GET(string, ...HandlerFunc) IRoutes
    POST(string, ...HandlerFunc) IRoutes
    DELETE(string, ...HandlerFunc) IRoutes
    PATCH(string, ...HandlerFunc) IRoutes
    PUT(string, ...HandlerFunc) IRoutes
    OPTIONS(string, ...HandlerFunc) IRoutes
    HEAD(string, ...HandlerFunc) IRoutes

    StaticFile(string, string) IRoutes
    Static(string, string) IRoutes
    StaticFS(string, http.FileSystem) IRoutes
}

RouterGroup相当于指向路由节点,然后让该节点成为该Group的初始路径(相对路径和绝对路径的概念),实例如下:

engine := gin.Default()

//指向 '/user'路径
routerGroup  := engine.Group("/user")

routerGroup.POST("/register", func(context *gin.Context){
    fullPath := "用户注册" + context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriteString(fullPath)
})
// '/user'相当于新的初始路径,然后我们对分支路径进行直接处理
//等同于
engine.POST("/user/register",func(context *gin.Context){
    fullPath := "用户注册" + context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriteString(fullPath)    
})

此外,我们如果不需要函数名称我们可以用以上方式简写,若需要则可以如第一段代码段中方式处理,即先命名函数再对函数实现。后者封装性好,main函数简洁。

中间件

Gin的中间件定义

在gin中,中间件称之为middleware,中间件的类型定义如下所示:

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

HandlerFunc是一个函数类型,接收一个Context参数。用于编写程序处理函数并返回HandleFunc类型,作为中间件的定义。

中间件Use用法

gin.Default() 创建引擎为例子

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}
//Log中间件
func Logger() HandlerFunc {
    return LoggerWithConfig(LoggerConfig{})
}
//Recovery中间件
func Recovery() HandlerFunc {
    return RecoveryWithWriter(DefaultErrorWriter)
}

Use函数接收一个可变参数,类型为HandlerFunc,恰为中间件的类型。Use方法定义如下:

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}
自定义中间件

根据上文的介绍,可以自己定义实现一个特殊需求的中间件,中间件的类型是函数,有两条标准:

  • func函数
  • 返回值类型为HandlerFunc

比如,我们自定义一个自己的中间件。在前面所学的内容中,我们在处理请求时,为了方便代码调试,通常都将请求的一些信息打印出来。有了中间件以后,为了避免代码多次重复编写,使用统一的中间件来完成。定义一个名为RequestInfos的中间件,在该中间件中打印请求的pathmethod。具体代码实现如下所示:

func RequestInfos() gin.HandlerFunc {
    return func(context *gin.Context) {
        path := context.FullPath()
        method := context.Request.Method
        fmt.Println("请求Path:", path)
        fmt.Println("请求Method:", method)
    }
}

func main() {

    engine := gin.Default()
    engine.Use(RequestInfos())

    engine.GET("/query", func(context *gin.Context) {
        context.JSON(200, map[string]interface{}{
            "code": 1,
            "msg":  context.FullPath(),
        })
    })
    engine.Run(":9000")
}

context.Next函数

通过一个例子和使用场景来说明Next函数的作用。

在上文自定义的中间件RequestInfos中,打印了请求了请求的path和method,接着去执行了正常的业务处理函数。如果我们想输出业务处理结果的信息,该如何实现呢。答案是使用context.Next函数。

context.Next函数可以将中间件代码的执行顺序一分为二,Next函数调用之前的代码在请求处理之前之前,当程序执行到context.Next时,会中断向下执行,转而先去执行具体的业务逻辑,执行完业务逻辑处理函数之后,程序会再次回到context.Next处,继续执行中间件后续的代码。具体用法如下:

func main() {
    engine := gin.Default()
    engine.Use(RequestInfos())
    engine.GET("/query", func(context *gin.Context) {
        fmt.Println(" 中间件的使用方法  ")
        context.JSON(404, map[string]interface{}{
            "code": 1,
            "msg":  context.FullPath(),
        })
    })
    engine.Run(":9000")
}

func RequestInfos() gin.HandlerFunc {
    return func(context *gin.Context) {
        path := context.FullPath()
        method := context.Request.Method
        fmt.Println("请求Path:", path)
        fmt.Println("请求Method:", method)
        context.Next()
        fmt.Println(context.Writer.Status())
    }
}

执行程序,输出结果如下:

请求Path: /query
请求Method: GET
中间件的使用方法
404

通过打印的顺序可以看到,Next函数将中间件程序的代码执行分为了前后连个部分,Next之前的按照顺序执行,Next之后会在业务逻辑处理完毕后再执行。

Next函数的作用及代码执行流程示意图如下图所示:

img

  • 1、程序先执行①和②。
  • 2、执行到③时,转而去执行业务处理程序。
  • 3、返回到中间件中,执行④。

大白话就是 context.Next 提前执行下一步骤即处理程序,执行完Next程序后再继续执行中间件。也可以理解默认的 context.Next 在中间件结尾。我们可以将这个 context.Next 写到其他我们想要的地方。

个人理解:

我们使用中间件的基本目的是减少代码的冗余

例如我们对GETPOST请求之前都需要进行一系列操作如打印访问路径和请求方式,如果在GETPOST中分别写就会有重复性

那么我们交给中间件。

  1. GETPOST请求执行前,先进入中间件中处理
  2. 然后处理GET、POST
  3. 最后GET\POST将结果返回到中间件中
  4. 再由中间件将自身处理结果和收到结果一同返回给请求方。

img

总得来说,中间件就是将请求中具有重复性的语句提出来,打包成一个函数,并且这个函数可以在请求执行前自动执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值