Gin框架的基本使用 | 青训营笔记

96 阅读6分钟

这是我参与「第五届青训营」伴学笔记的第6天
阅前提示:因为我们小组内部决定使用gin框架作为本次大作业的HTTP框架,所以本文记录了一下gin框架的基本使用~

Gin框架的快速入门

要求

  • Go 1.13 及以上版本

安装

要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。

1.下载并安装 gin:

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

2.将 gin 引入到代码中:

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

3.(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:

import "net/http"
  1. 创建你的项目文件夹并 cd 进去
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
  1. 拷贝一个初始模板到你的项目里
$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
  1. 运行你的项目
$ go run main.go

开始

首先,创建一个名为 example.go 的文件

$ touch example.go

接下来, 将如下的代码写入 example.go 中:

package main

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

func main() {
        //创建一个默认的路由引擎
	r := gin.Default()
        //配置路由
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务(默认)
        //你也可以选择运行在别的端口上
        //r.Run(":8888")
}

然后, 执行 go run example.go 命令来运行代码:

# 运行 example.go 并且在浏览器中访问 HOST_IP:8080/ping
$ go run example.go

路由

路由是由一个URL和一个特定的HTTP方法(如GET,POST等)组成的,涉及到应用如何响应客户端对某个网络节点的访问

RESTful API

这是一套目前比较成熟的互联网应用程序的API理论,用来规范我们的路由写法

请求方法代表操作
GET(SELECT)从服务器取出资源
POST(CREATE)在服务器新建一个资源
PUT(UPDATE)在服务器更新资源(客户端提供改变后的完整资源)
DELETE(DELETE)从服务器删除资源

响应数据

返回一个字符串

//返回一个字符串
r.GET("/?aid=hello", func(c *gin.Context) {
   aid := c.Query("aid")
   c.String(200, "aid=%s", aid)
   //c.String(200, "%s", "hello")
})

返回一个json


type Article struct {
   Title string `json:"title"`
   Desc  string `json:"desc"`
}


//返回一个json
	r.GET("/json", func(c *gin.Context) {
                a := Article{
                   Title: "title",
                   Desc:  "desc",
                }
		//第一个参数是一个状态码;
		//第二个参数是一个空接口类型:表示可以传入任何类型
                //第二个参数还可以是一个结构体
                c.JSON(200, map[string]interface{}{
                   "success": true,
                   "msg":     "hello",
                })
                c.JSON(200, a)
	})

其中,map[string]interface{}可以用gin.H来代替

响应jsonp请求

jsonp主要用来解决跨域问题

r.GET("/jsonp", func(c *gin.Context) {
   a := Article{
      Title: "title",
      Desc:  "desc",
   }
   //第二个参数是一个空接口类型:表示可以传入任何类型
   //第二个参数还可以是一个结构体
   c.JSONP(200, a)
})

返回:
image.png

返回xml数据

r.GET("/xml", func(c *gin.Context) {
   a := Article{
      Title: "title",
      Desc:  "desc",
   }
   //第二个参数是一个空接口类型:表示可以传入任何类型
   //第二个参数还可以是一个结构体
   c.XML(200, a)
})

返回:

<Article>
    <Title>
        title
    </Title>
    <Desc>
        desc
    </Desc>
</Article>

获取GET POST传值

Get请求传值

GET /user?uid=20&page=1

router.GET("/user", func(c *gin.Context) {

uid := c.Query("uid")

page := c.DefaultQuery("page", "0")

c.String(200, "uid=%v page=%v", uid, page)

})

动态路由传值

域名/user/20

r.GET("/user/:uid", func(c *gin.Context) {

uid := c.Param("uid")

c.String(200, "userID=%s", uid)

})

Post 请求传值 获取 form 表单数据

router.GET("/addUser", func(c *gin.Context) {

c.HTML(200, "default/add_user.html", gin.H{})

})

router.POST("/doAddUser", func(c *gin.Context) {

username := c.PostForm("username")

password := c.PostForm("password")
})

获取 GET POST 传递的数据绑定到结构体

.ShouldBind()可以基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象

//注意首字母大写
type Userinfo struct {
Username string `form:"username" json:"user"` Password string `form:"password" json:"password"` }

Get 传值绑定到结构体

/?username=zhangsan&password=123456

 router.GET("/", func(c *gin.Context) {

var userinfo Userinfo

if err := c.ShouldBind(&userinfo); err == nil {

c.JSON(http.StatusOK, userinfo)

} else {

c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

}

})

Post 传值绑定到结构体

router.POST("/doLogin", func(c *gin.Context) {

var userinfo Userinfo

if err := c.ShouldBind(&userinfo); err == nil {

c.JSON(http.StatusOK, userinfo)

} else {

c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

}

})

路由分组

代码如下:

func main() {

router := gin.Default()

// 简单的路由组: v1

v1 := router.Group("/v1")

{

v1.POST("/login", loginEndpoint)

v1.POST("/submit", submitEndpoint)

v1.POST("/read", readEndpoint)

}

// 简单的路由组: v2

v2 := router.Group("/v2")

{

v2.POST("/login", loginEndpoint)

v2.POST("/submit", submitEndpoint)

v2.POST("/read", readEndpoint)

}

router.Run(":8080")

}

这样写仅仅只是简单的将路由组分好,但还是放在了main()中,如果日后要修改还是不太方便,所以我们后续还需要将每一个路由分组抽离:
新建routers.go

package main

import (
   "github.com/gin-gonic/gin"
   "tiktok/controller"
)

func initRouter(r *gin.Engine) {
   // public directory is used to serve static resources
   r.Static("/static", "./public")

   apiRouter := r.Group("/douyin")
   // basic apis
   apiRouter.GET("/user/", controller.UserInfo)
   apiRouter.POST("/user/register/", controller.Register)
   apiRouter.POST("/user/login/", controller.Login)

}

而此时main.go里面是这样的:

func main() {
	go service.RunMessageServer()	
	//创建一个默认的路由引擎
	r := gin.Default()
	//配置路由
	//返回一个字符串
	initRouter(r)
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务(默认)
	//你也可以选择运行在别的端口上
	//r.Run(":8888")
}

中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。 换而言之:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

package main

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

func initMiddleware(ctx *gin.Context) {
    fmt.Println("我是一个中间件")
}
func main() {
    r := gin.Default()
    r.GET("/", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "首页--中间件演示")
    })
    r.GET("/news", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "新闻页面--中间件演示")
    })
    r.Run(":8080")
}

ctx.Next()调用该请求的剩余处理程序

package main
import ( "fmt"
"time"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
    fmt.Println("1-执行中中间件")
    start := time.Now().UnixNano()
    // 调用该请求的剩余处理程序
    ctx.Next()
    fmt.Println("3-程序执行完成 计算时间")
    // 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
    end := time.Now().UnixNano()
    fmt.Println(end - start)
}
func main() {
    r := gin.Default()
    r.GET("/", initMiddleware, func(ctx *gin.Context) {
        fmt.Println("2-执行首页返回数据")
        ctx.String(200, "首页--中间件演示")
    })
    r.GET("/news", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "新闻页面--中间件演示")
    })
    r.Run(":8080")
}

全局中间件

package main
import ( "fmt"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
    fmt.Println("全局中间件 通过 r.Use 配置")
    // 调用该请求的剩余处理程序
    ctx.Next()
}
func main() {
    r := gin.Default()
    r.Use(initMiddleware)
    r.GET("/", func(ctx *gin.Context) {
        ctx.String(200, "首页--中间件演示")
    })
    r.GET("/news", func(ctx *gin.Context) {
        ctx.String(200, "新闻页面--中间件演示")
    })
    r.Run(":8080")
}

在路由分组中配置中间件

为路由组注册中间件

写法 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) {...})
... }

分组路由 AdminRoutes.go 中配置中间件

package routes
import ( "fmt"
"gin_demo/controller/admin"
"net/http"
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) {
    fmt.Println("路由分组中间件")
    // 调用该请求的剩余处理程序
    ctx.Next()
    }
func AdminRoutesInit(router *gin.Engine) {
    adminRouter := router.Group("/admin", initMiddleware)
    {
        adminRouter.GET("/user", admin.UserController{}.Index)
        adminRouter.GET("/user/add", admin.UserController{}.Add)
        adminRouter.GET("/news", func(c *gin.Context) {
            c.String(http.StatusOK, "news")
        })
    }
}

中间件和对应控制器之间共享数据

设置值 ctx.Set("username", "张三") 获取值 username, _ := ctx.Get("username")

中间件设置值

func InitAdminMiddleware(ctx *gin.Context) {
    fmt.Println("路由分组中间件")
    // 可以通过 ctx.Set 在请求上下文中设置值,后续的处理函数能够取到该值
    ctx.Set("username", "张三")
    // 调用该请求的剩余处理程序
    ctx.Next()
}

控制器获取值

func (c UserController) Index(ctx *gin.Context) {
    username, _ := ctx.Get("username")
    fmt.Println(username)
    ctx.String(http.StatusOK, "这是用户首页 111")
}

Cookie

Cookie介绍

  • HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用 Cookie 或者 Session 实现
  • cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名的时候共享数据。 设置 Cookie c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
  • 第一个参数 key
  • 第二个参数 value
  • 第三个参数 过期时间.如果只想设置 Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递 nil
  • 第四个参数 cookie 保存的路径,默认为“/”,表示在当前域都有效
  • 第五个参数 cookie 的路径 Domain 作用域;本地调试配置成 localhost , 正式上线配置成域名
  • 第六个参数是 secure ,当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效
  • 第七个参数 httpOnly,是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生获取 Cookie cookie, err := c.Cookie("name")
package main
import ( "gin_demo/models"
"html/template"
"github.com/gin-gonic/gin"
)
func main() {
    r := gin.Default()
    r.SetFuncMap(template.FuncMap{ "unixToDate": models.UnixToDate, })
    r.GET("/", func(c *gin.Context) {
        c.SetCookie("usrename", "张三", 3600, "/", "localhost", false, true)
        c.String(200, "首页")
    })
    r.GET("/user", func(c *gin.Context) {
        username, _ := c.Cookie("usrename")
        c.String(200, "用户-"+username)
    })
    r.Run(":8080")
}