Gin框架学习笔记

文章目录

  • Gin框架笔记
    • 前言
    • 安装Gin框架
    • 1、创建Engine
      • 运行Engine
    • 2、处理HTTP请求
      • HTTP请求类型
      • Context-上下文
      • 通用处理
        • Handle处理GET请求
        • Handle处理POST请求
      • 分类处理
        • engine.GET()处理GET请求
        • engine.POST()处理POST请求
        • engine.DELETE()处理DELETE请求
    • 3、使用路由组分组处理请求
      • 使用场景
      • 什么是路由
      • RouterGroup
      • 路由组的使用
    • 4、请求参数绑定
      • 表单实体绑定
        • ShouldBindQuery-GET请求数据绑定
        • ShouldBind-POST请求数据绑定
      • Json格式数据绑定-ShouldBindJson
    • 5、多数据格式返回前端
      • []byte
      • string
      • JSON
        • map类型
        • 结构体类型
      • HTML模板
    • 6、middleware中间件的编写与使用
      • 中间件
      • engine.Use()方法使用中间件
      • 自定义中间件
      • context.Next函数

Gin框架笔记

前言

最近尝试用Gin框架写一个小程序后端,自己做了一些学习笔记备忘,也供大家参考。作者水平有限,有任何问题欢迎在文章下面留言交流!
主要参考资料:https://www.qfgolang.com/?special=ginkuangjia&pid=2606

安装Gin框架

需要go版本在1.6以上

查看自己go版本

$ go version

安装Gin框架

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

国内直接使用这条命令安装大概率会因网络超时安装失败。

解决办法1:更换阿里云的镜像源

// 启用 Go Modules 功能
$ go env -w GO111MODULE=on
// 配置 GOPROXY 环境变量
$ go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/

但是此方法开启了Go Modules,包将会下载到pkg/mod/目录下,且文件名将会带上版本号。但编辑器似乎不一定能够自动识别路径并导入。

解决办法2:终端下载,需要你有代理工具。

还需要注意,mac的终端默认是不走代理的,即使你的v2ray开了全局模式,终端也还是没翻出去。

mac终端的方法:

1、打开.zprofile(在旧版mac系统中则是.bash_profile)

$ vim ~/.zprofile

添加两个函数

function proxy_on{
	export http_proxy=http://本机http监听host:本机http监听端口
	export https_proxy=http://本机http监听host:本机http监听端口
}
function proxy_off{
	unset http_proxy
	unset https_proxy
}

本机http监听host本机http监听端口可以去代理工具查看,这里以V2RayU为例子:打开V2RayU偏好设置,选择Advance就可以看到。

Gin框架学习笔记_第1张图片

所以在我的电脑上,我添加的两个功能具体写为:

function proxy_on{
	export http_proxy=http://127.0.0.1:1087
	export https_proxy=http://127.0.0.1:1087
}
function proxy_off{
	unset http_proxy
	unset https_proxy
}

每次需要终端时,在终端输入以下命令开启代理:

$ proxy_on

使用完后输入以下命令关闭代理:

$ proxy_off

1、创建Engine

在gin框架中,Engine被定义成为一个结构体,Engine代表gin框架的一个结构体定义,其中包含了路由组、中间件、页面渲染接口、框架配置设置等相关内容。

默认的Engine可以通过**gin.Default**进行创建,或者使用gin.New()同样可以创建。两种方式如下所示:

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

gin.Default()gin.New()的区别在于:

gin.Default也使用gin.New()创建engine实例,但是会默认使用LoggerRecovery中间件。

Logger是负责进行打印并输出日志的中间件,方便开发者进行程序调试。Recovery中间件的作用是如果程序执行过程中遇到panic中断了服务,则Recovery会恢复程序执行,并返回服务器500内部错误。

通常情况下,我们使用默认的gin.Default创建Engine实例。

运行Engine

engine1.Run([主机地址:端口号])

// 例如:

// 不带参数,默认主机地址为localhost,默认端口为8080
engine1.Run()

// 带参数,主机地址为apphost,端口为8090
engine1.Run("apphost:8090")

2、处理HTTP请求

在上面我们创建的engine实例中,包含很多方法可以直接处理不同类型的HTTP请求。

HTTP请求类型

http协议中一共定义了八种方法或者称之为类型来表明对请求网络资源(Request-URI)的不同的操作方式,分别是:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT。

虽然一共有八种请求操作类型,但是实际开发中常用的就:GETPOSTDELETE等几种。

Context-上下文

Context是gin框架中封装的一个结构体,这是gin框架中最重要,最基础的一个结构体对象。

该结构体可以提供我们操作请求,处理请求,获取数据等相关的操作,通常称之为上下文对象,简单说为我们提供操作环境。

可以通过context.Querycontext.DefaultQuery获取GET请求携带的参数。

可以通过context.Writer.Write向请求发起端返回数据。

通用处理

engine中可以直接进行HTTP请求的处理,在engine中使用Handle方法进行http请求的处理。Handle方法包含三个参数,具体如下所示:

func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes
  • httpMethod:第一个参数表示要处理的HTTP的请求类型,是GET、POST、DELETE等8种请求类型中的一种。
  • relativePath:第二个参数表示要解析的接口,由开发者进行定义。
  • handlers:第三个参数是处理对应的请求的代码的定义。

实例:

Handle处理GET请求
...
// 通过Handle方法第一个参数指定处理GET类型的请求,解析的接口是/hello
// url:http://localhost:8080/hello?name=syb
// GET请求附带的参数在url中的?后面
engine.Handle("GET", "/hello", func(context *gin.Context) {
    // 获取请求接口
    fmt.Println(context.FullPath())
    // 获取字符串参数
    name := context.DefaultQuery("name", "")
    fmt.Println(name)

    // 返回给前端输出
    context.Writer.Write([]byte("Hello ," + name))
})
...

context.FullPath():**返回请求接口地址。**上述代码返回的是"/hello"。

context.DefaultQuery(key(string), defaultValue(string)):**读取GET请求附带的参数。**第一个参数指定要读取的参数的键值key,第二个参数是若读取不到要返回的默认字符串。

context.Writer.Write(([]byte)):**给前端返回数据。**数据类型是byte字符数组。

Handle处理POST请求
...
// 通过Handle方法第一个参数指定处理POST类型的请求,解析的接口是/login
// url:http://localhost:8080/login
engine.Handle("POST", "/login", func(context *gin.Context) {

    fmt.Println(context.FullPath())
    // 读取用户名
    username := context.PostForm("username")
    fmt.Println(username)

    // 读取用户密码
    password := context.PostForm("pwd")
    fmt.Println(password)

    context.Writer.Write([]byte("User login"))
})
...

context.PostForm(key(string)):**读取POST请求附带的参数。**参数指定要读取的参数的键值key。

分类处理

除了Engine中包含的通用的处理方法以外,engine还可以按类型进行直接解析。

Engine中包含有get方法、post方法、delete方法等与http请求类型对应的方法。

engine.GET()处理GET请求

engine中包含GET方法处理HTTP的GET类型的请求。engine的GET方法包含两个参数,编程使用如下所示:

...
engine.GET("/hello", func(context *gin.Context) {
    fmt.Println(context.FullPath())

    username := context.Query("name")
    fmt.Println(username)

    context.Writer.Write([]byte("Hello," + username))
})
...

context.Query(key(string)):**读取GET请求附带的参数。**参数指定要读取的参数的键值key。

engine.POST()处理POST请求
...
engine.POST("/login", func(context *gin.Context) {

    fmt.Println(context.FullPath())
    username, exist := context.GetPostForm("username")
    if exist {
        fmt.Println(username)
    }

    password, exists := context.GetPostForm("pwd")
    if exists {
        fmt.Println(password)
    }

    context.Writer.Write([]byte("Hello , " + username))
})
...

context.GetPostForm(key(string)):**读取POST请求附带的参数。**参数指定要读取的参数的键值key。

它与context.PostForm()的区别在于:它除了返回读取的数据之外,还会返回一个bool值,表示所读取的参数是否存在。

engine.DELETE()处理DELETE请求

在项目开发中,通常都是遵循RESTful标准进行接口开发。除了GET、POST以外,还会有DELETE等操作。

比如要执行某个删除操作,会发送DELETE类型的请求,同时需要携带一些操作的参数。比如要删除用户,按照RESTful标准会进行如下所示:

...
// 通过DELETE请求删除一个id为24的用户
// url:http://localhost:8080/user/24
// 在服务端gin中,通过路由的:id来定义一个要删除用户的id变量值,同时使用context.Param进行获取
engine.DELETE("/user/:id", DeleteHandle)
func DeleteHandle(context *gin.Context) {
    fmt.Println(context.FullPath())

    userID := context.Param("id")

    fmt.Println(userID)

    context.Writer.Write([]byte("Delete user's id : " + userID))
}
...

context.Param(key(string)):**读取DELETE请求的参数。**参数指定要读取的参数的键值key。

3、使用路由组分组处理请求

使用场景

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

在系统中有用户模块,用户有不同注册、登录、用户信息、

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

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

什么是路由

在web开发中,**“route”(路由)**是指根据url分配到对应的处理程序。

RouterGroup

之所以engine中包含通用型的Handle和分类处理的GET、POST等类型的方法,是因为Engine中有RouterGroup作为匿名字段。

RouteGroup可以称之为路由组,在gin中定义为结构体:

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

RouterGroup的作用就是为每一个服务请求提供解析功能,并指定每一个请求对应的处理程序。

路由组的使用

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

路由组是engine.Group返回的一个结构体,用于对请求进行分组。

实例:

engine := gin.Default()
routerGroup := engine.Group("/user")
routerGroup.GET("/register", registerHandle)
routerGroup.GET("/login", loginHandle)
routerGroup.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)
}

4、请求参数绑定

但是如果表单数据较多时,使用PostForm和GetPostForm一次获取一个表单数据,开发效率较慢。

Gin框架提供给开发者表单实体绑定的功能,可以将表单数据与结构体绑定。

表单实体绑定

gin框架提供了数据结构体和表单提交数据绑定的功能,提高表单数据获取的效率。

实例:

以一个用户注册功能来进行讲解表单实体绑定操作。

用户注册需要提交表单数据,假设注册时表单数据包含三项,分别为:username、phone和password。

先创建UserRegister结构体用于接收表单数据,通过tag标签的方式设置每个字段对应的form表单中的属性名,通过binding属性用于设置属性是否是必须。

type UserRegister struct {
    Username string `form:"username" binding:"required"`
    Phone    string `form:"phone" binding:"required"`
    Password string `form:"password" binding:"required"`
}
ShouldBindQuery-GET请求数据绑定
func main() {

    engine := gin.Default()

    // http://localhost:8080/hello?name=davie&classes=软件工程
    engine.GET("/hello", func(context *gin.Context) {

        fmt.Println(context.FullPath())

        var student Student
        err := context.ShouldBindQuery(&student)
        if err != nil {
            log.Fatal(err.Error())
        }

        fmt.Println(student.Name)
        fmt.Println(student.Classes)
        context.Writer.Write([]byte("hello," + student.Name))

    })

    engine.Run()
}

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

context.ShouldBindQuery(&结构体):**将GET请求附带的参数传入结构体中。**并返回一个error类型数据。

ShouldBind-POST请求数据绑定
func main(){
    engine := gin.Default()

    engine.POST("/register", func(context *gin.Context){
        fmt.Println(context.FullPath())
        var _register Register
        err := context.ShouldBind(&_register)
        if err != nil{
            log.Fatal(err.Error())
            return
        }

        fmt.Println(_register.UserName)
        fmt.Println(_register.Phone)
        context.Writer.Write([]byte(_register.UserName + " Register "))
	})
    
    engine.Run()
}

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

context.ShouldBind(&结构体):**将POST请求附带的参数传入结构体中。**并返回一个error类型数据。

Json格式数据绑定-ShouldBindJson

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

实例:

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

    engine.POST("/addstudent", func(context *gin.Context) {
        fmt.Println(context.FullPath())
        var person Person
        err := context.BindJSON(&person)
        if 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"`
}

context.BindJSON(&结构体):**将http请求附带的Json格式数据传入结构体中。**并返回一个error类型数据。

5、多数据格式返回前端

在gin框架中,支持返回给前端多种请求数据格式。

[]byte

context.Writer.Write(([]byte))给前端返回[]byte类型数据。

string

context.Writer.WriteString((string))给前端返回string类型数据。

JSON

gin框架中的context.JSON方法可以将map类型和struct结构体类型的数据转换成JSON格式的结构化数据,然后返回给客户端。

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

    // 第一个参数200表示设置请求返回的状态码
    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模板

除了JSON格式以外,gin框架还支持返回HTML格式的数据。可以直接渲染HTML页面。

实例:

package main

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

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

	//设置html目录
	engine.LoadHTMLGlob("./html/*")
    
    // 静态资源路径设置
    // 第一个参数是图片所在目录名称
    // 第二个参数是图片所在目录相对于项目工程文件的路径
	engine.Static("/img", "./img")
	engine.GET("/hellohtml", func(context *gin.Context) {
		fullPath := "请求路径:" + context.FullPath()
		fmt.Println(fullPath)

        // 第一个参数是表示设置请求返回的状态码
        // 第二个参数是html文件名称
        // 第三个参数是向html文件传入的变量
		context.HTML(http.StatusOK, "index.html", gin.H{
			"fullPath":fullPath,
			"title":"gin教程",
		})
	})

	engine.Run()
}

index.html源代码:


<html>
<head>
    
    <title>{{.title}}title>
head>
<h1 align="center">syb的Gin学习h1>

{{.fullPath}}
<br />
<div align="center"><img src="../img/jiandang100zhounian.jpeg">div>
html>

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHRaV0GV-1627695899916)(截屏2021-07-23 下午6.23.56.png)]

6、middleware中间件的编写与使用

中间件

在web应用服务中,完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。

在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。

鉴权认证、权限管理、安全检查、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的所有业务都适用。

由此,在业务开发过程中,为了更好的梳理系统架构,可以将上述描述所涉及的一些通用业务单独抽离并进行开发,然后以插件化的形式进行对接。这种方式既保证了系统功能的完整,同时又有效的将具体业务和系统功能进行解耦,并且,还可以达到灵活配置的目的。

这种通用业务独立开发并灵活配置使用的组件,一般称之为**“中间件”**,因为其位于服务器和实际业务处理程序之间。其含义就是相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。中间件的位置和角色示意图如下图所示:

Gin框架学习笔记_第2张图片

engine.Use()方法使用中间件

engine.Use(<中间件>)

// 如:
engine.Use(Logger(), Recovery())

自定义中间件

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

  • func函数

  • 返回值类型为HandlerFunc

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

具体代码实现如下所示:

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")
}

运行程序,能够得到正确的返回JSON格式的数据:

{
    "code": 1,
    "msg": "/query"
}

context.Next函数

context.Next函数可以将中间件代码的执行顺序一分为二。

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

func main() {
    engine := gin.Default()
    engine.Use(RequestInfos())
    engine.GET("/query", func(context *gin.Context) {
        fmt.Println("使用中间件")
        context.JSON(200, 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
使用中间件  
200

你可能感兴趣的:(GO,go,golang)