使用gin搭建api后台系统之框架搭建

gin 是golang 中比较流行的框架,很多系统都是在该框架下开发的,这个框架给我的感觉像是Tornado在python中的位置,基础的功能都有,但是如果想要很好的使用,还需要开发很多自己的功能与中间件,在看过不少的教程以后,想要记录一下学习的过程。

本系列简单的实现了一些做后台服务的能用方法,如获取参数,数据库查询等操作,项目的整体还谈不上架构,顶多算是个quick start, 目录结构也不那么讲究,因为初学,所以肯定会有很多问题。以后再一点点的实践一点点的修。

下载 gin

第一篇,简单一些,先把框架搭起来,写个hello world 出来。

项目基于go module 方式,在一个空目录中运行 go mod init gintest 初始化项目,go 会自动生成一个go.mod 文件

在go.mod 文件中添加gin

module gintest

go 1.15

require (
	github.com/gin-gonic/gin v1.7.4
)

之后使用go mod download 命令下载。

或者在项目目录中直接运行 go get github.com/gin-gonic/gin@v1.7.4 命令,也会下载gin包,并且会自动添加到go.mod 文件中。

构建http 服务

新建一个server.go 文件,写入以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

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

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "Msg":"Hello World",
        })
    })
    r.Run(":8080")
}

代码很简单,运行go run server.go 则会在8080端口启动一个http服务,使用浏览器访问 http://127.0.0.1:8080/ 则会返回json的数据

1
2
3
{
  Msg: "Hello world"
}

上面代码中,使用gin.Default() 创建一个默认的gin应用,这个应用底层初始化了很多设置,这里就先不深入探索了。

这里就注册了一个路由,/,它的处理由一个匿名函数来处理, gin 中的GET方法的定义为

1
2
3
4
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

handler 为处理方法,该方法的定义为

1
type HandlerFunc func(*Context)

所以这里需要一个参数为(*Context) 的函数,上面的代码中使用

1
2
3
4
5
func(c *gin.Context) {
  c.JSON(200, gin.H{
    "Msg":"Hello World",
  })
}

这个匿名函数来处理 / 的GET请求。

这里还可以定义POST、DELETE、PUT 等方法,这样可以很方便的编写restful 风格的API,

1
2
3
4
5
r.POST("/", func(c *gin.Context) {
  c.JSON(200, gin.H{
    "Msg":"Hello world POST",
  })
})

404 配置

默认的404处理,会打印 404 not found,更多的时候,我们想自定义404页面的处理,这时可以通过设置r.NoRoute 方法来实现

1
2
3
4
5
r.NoRoute(func(c *gin.Context) {
  c.JSON(404, gin.H{
    "Msg": "The page not found",
  })
})

这时在访问一个不存在的路由时会展示自定义的内容。

路由组

很多时候,我们是有这样的需求,访问的路由前面都有统一的前缀,如/api/v1 , 或者/user ,当然可以在定义路由的时候都统一写上前缀,但是这种方式,如果后期修改了前缀将要改好多地方。这里可以使用路由组的概念。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"Msg": "Hello world",
		})
	})
	apiv1 := r.Group("/api/v1")
	{
		apiv1.GET("/", func(c *gin.Context) {
			c.JSON(200, gin.H{"msg": "hello api v1"})
		})
	}
	r.Run(":8080")
}

以上代码,定义了一个路由组,/api/v1 , 之后又会在该路由组下定义相应的处理方法,这里区分 http://127.0.0.1:8080/ 这个url 返回 "Msg": "Hello world", /api/v1/ 这个路由返回 "msg": "hello api v1"

处理类的封装

上面的路由的处理类都是使用匿名函数,处理的逻辑比较简单的话,可以这么写,但是还是不建议写个匿名函数来处理,之后随着项目越来越大,这里的路由定义会越来越多,如果还有大量的匿名函数的话,看着就会比较乱。

这里我们可以将处理handler 进行封装。

先创建一个handlers 的文件夹,写入以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package handlers

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

type ApiV1 struct {
	
}

func (ApiV1) Get(c *gin.Context)  {
	c.JSON(200, gin.H{"msg": "handlers get request!"})
}

这里定义了一个ApiV1的结构体, 之后定义了一个Get 方法,然后在入口 server.go 文件中,修改原来的代码,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
	r := gin.Default()
	apiv1_h := handlers.ApiV1{}
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"Msg": "Hello world",
		})
	})
	apiv1 := r.Group("/api/v1")
	{
		apiv1.GET("/", apiv1_h.Get)
	}
	r.Run(":8080")
}

使用apiv1_h := handlers.ApiV1{} 初始化一个ApiV1的结构体对象,在之后路由定义的时候,就可以直接使用该对象的Get方法,最好将相同前缀的放入一个括号中{},方法查看

1
2
3
{
  apiv1.GET("/", apiv1_h.Get)
}

这里注意,处理handler 为 apiv1_h.Get , 而不是 apiv1_h.Get(c, *gin.Context)

使用 http.Server 启动服务

上面的代码都是以r.run(":8080") 的方式启动服务,但是有时候我们希望做一些个性化的设置,如一些读写超时,当然也可以调用gin.New() 方法中得到的对象中进行设置,也可以直接使用http.Server 中定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func main() {
	r := gin.Default()
	apiv1_h := handlers.ApiV1{}
	apiv1 := r.Group("/api/v1")
	{
		apiv1.GET("/", apiv1_h.Get)
	}
  s := &http.Server{
		Addr: ":8080",
		Handler: r,
		ReadTimeout: 10*time.Second,
		WriteTimeout: 10*time.Second,
	}
  err := s.ListenAndServe()
	if err != nil {
		return 
	}
}

网页跳转

上面的代码在处理请求的时候,都是返回json数据,由于现在主流开发使用前后端分离的模式,所以这里不太使用html 模板的方式返回数据了。

当使用第三方登录的时候,全牵扯到网页跳转,网页跳转也非常简单, 修改apiv1.go 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package handlers

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

type ApiV1 struct {
	
}

func (*ApiV1) Get(c *gin.Context)  {
	c.Redirect(301, "https://www.baidu.com")
}

这样在方法的对应的路由的时候,就会跳转到百度了。

这里要注意,状态码必须处理300-308之间,否则会报错

1
2
3
4
5
6
7
func (r Redirect) Render(w http.ResponseWriter) error {
	if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
		panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
	}
	http.Redirect(w, r.Request, r.Location, r.Code)
	return nil
}

gin 框架的基本搭建先记录到这里,之后会介绍各种请求参数的获取与校验。