少女祈祷中...

友情提示,本篇博客的代码都可以从这里获取。

Gin的基本使用

首先用go mod建立一个项目,比如就叫ginnote吧。

1
go mod init ginnote

然后获取一下gin包,即便是已经装了gin包,也要获取,因为要配置go.mod文件。

1
go get github.com/gin-gonic/gin

除此以外,还需要另一项工具,可以提供热加载,也就是可以一边写一边加载网页,每次保存项目都会自动重新编译运行。

1
go get github.com/pilu/fresh

执行完之后,文件夹里会多一个tmp文件夹,这就表示成功了。

然后新建一个main.go,用这样一段代码就可以生成一个网页。

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

import (
"net/http"

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

func main() {
r := gin.Default()
r.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello World!") //http.StatusOK就等于200。
})
r.Run() //默认端口是8000。括号里可以指定端口号,接收字符串类型,比如":4000"。注意":"不能省略!
}

在终端中输入下面这行即可开启热加载,Ctrl+C退出。

1
go run github.com/pilu/fresh

Gin的目录结构

假如,现在有这么一个项目:

  • 网页主要分为三部分,包含default,admin,api。
  • 每部分各有自己的首页,default包含了普通人能看到的页面,admin有一些高级权限,api包含一些功能。
  • 项目里包含了静态资源,css,js,image。

那么整个项目的目录大概是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
project

└───controllers
│ │
│ └───admin
│ │ │ articleController.go
│ │ │ indexController.go
│ │ │ userController.go
│ │
│ └───api
│ │ │ apiController.go
│ │
│ └───defaults
│ │ defaultController.go

└───routers
│ │ adminRouters.go
│ │ apiRouters.go
│ │ defaultRouters.go

└───static
│ │
│ └───css
│ │ │ base.css
│ │
│ └───image
│ │ │ pm.jpg
│ │
│ └───js
│ │ base.js

└───templates
│ │
│ └───admin
│ │ │ index.html
│ │ │ news.html
│ │
│ └───default
│ │ │ index.html
│ │ │ news.html
│ │ │ user.html
│ │
│ └───public
│ │ page_header.html

└───tmp
│ │ runner-build.exe
│ │ runner-build.exe~

│ go.mod
│ go.sum
│ main.go

很吓人对吧,但是别急。
从下往上看,go.mod,go.sum都是自动生成的,不用管,main.go是我们自己写的。tmp文件夹和里面的exe都是前面go get github.com/pilu/fresh自动生成的。
templates文件夹里面存放的是html文件,也就是模板,page_header.html代表被继承的父模板。
static存放静态资源,包括css,js和图片。
重点就在于最上面这两个文件夹。
routers中的.go文件是路由组,包含了那一部分网页的全部路由。并且只包含路由的路径,而不包括具体实现方法。所有的实现方法都写在controllers下的文件中。

这里以controllers/admin中的文件举例,比如这个indexController.go。

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

import (
"net/http"

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

type IndexController struct {
}

func (con IndexController) Index(ctx *gin.Context) {
ctx.String(http.StatusOK, "首页--")
}

在之前的Gin的基本使用里给的例子中,是直接把逻辑写在了匿名函数里作为参数,而这里是直接把函数抽离出来,以便后续维护。如果在测试过程中出现了什么bug,那么就找到对应的函数进行修改即可。这里的函数并不是独立的,而是作为结构体IndexController的方法而存在,这是为了继承,并且可以将函数分类,增强代码的可读性。

再看articleController.go中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package admin

import (
"net/http"

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

type ArticleController struct {
}

func (con ArticleController) Index(ctx *gin.Context) {
ctx.String(http.StatusOK, "文章列表--")
}

func (con ArticleController) Add(ctx *gin.Context) {
ctx.String(http.StatusOK, "增加文章--")
}

func (con ArticleController) Edit(ctx *gin.Context) {
ctx.String(http.StatusOK, "修改文章--")
}

这里定义了名为ArticleController的结构体及其相关方法,这表明这些方法都是和Article相关的。

定义好这些方法以后,就可以在routers/adminRouters.go中调用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package routers

import (
"ginpro2/controllers/admin"
"net/http"

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

func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin")
{
adminRouters.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "后台首页")
})
adminRouters.GET("/user", admin.UserController{}.Index) //先实例化结构体,再调用方法。
adminRouters.GET("/user/add", admin.UserController{}.Add)
adminRouters.GET("/user/edit", admin.UserController{}.Edit)
adminRouters.GET("/article", admin.ArticleController{}.Index)
adminRouters.GET("/article/add", admin.ArticleController{}.Add)
adminRouters.GET("/article/edit", admin.ArticleController{}.Edit)
}
}

这里就是一个路由组,把”/admin”路径下的所有路由集合在一起,虽然很多方法命重名了,但它们都是不同结构体的方法,所以也是不同的功能。要注意的是,路由组自身也是一个函数,它要在main.go中被调用,就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"ginnote/routers"
"net/http"

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

func main() {
r := gin.Default()
r.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello World!") //http.StatusOK就等于200。
})
routers.AdminRoutersInit(r) //调用路由组。
r.Run(":4000") //默认端口是8000。括号里可以指定端口号,接收字符串类型,比如":4000"。注意":"不能省略!
}

于是就形成了这样一种关系:main.go调用路由组,路由组adminRouters.go包含了路由路径并调用了方法,在controllers/admin中实现了具体的方法。

渲染模板和静态资源

我们决定在default部分渲染一个html网页。
在defaultController.go中写好渲染模板的方法。这里要注意的是,package后面不能直接写default,因为default是go中的关键词,所以写成了defaults,或者写个别的包名也行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package defaults

import (
"net/http"

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

type DefaultController struct {
}

type Article struct {
Title string `json:"title"`
Content string `json:"content"`
}

func (con DefaultController) Index(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "default/index.html", gin.H{ //这里的路径是"default/index.html"
"title": "首页",
"msg": "我是msg",
"score": 89,
"hobby": []string{"吃饭", "睡觉", "写代码"},
"newsList": []interface{}{
Article{
Title: "新闻标题1",
Content: "新闻内容1",
},
Article{
Title: "新闻标题2",
Content: "新闻内容2",
},
},
"testSlice": []string{},
"news": Article{
Title: "新闻标题3",
Content: "新闻内容3",
},
"date": 1629423555,
})
}

func (con DefaultController) News(ctx *gin.Context) {
ctx.String(http.StatusOK, "新闻")
}

在defaultRouters.go中调用方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package routers

import (
"ginnote/controllers/defaults"

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

func DefaultRoutersInit(r *gin.Engine) {
defaultRouters := r.Group("/")
{
defaultRouters.GET("/", defaults.DefaultController{}.Index)
defaultRouters.GET("/news", defaults.DefaultController{}.News)
}
}

除此之外,我们还要在main.go中加载模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"ginnote/routers"
"html/template"
"time"

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

func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}

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

//自定义模板函数
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
})

//加载模板
r.LoadHTMLGlob("templates/**/*")
//这里括号里参数的意思就是加载目录"templates/xxx/"下的所有模板。
//如果换成"templates/*"就变成的加载目录"templates/"下的所有模板。

//配置静态web服务,第一个参数表示路由,第二个参数表示映射的目录。
r.Static("/static", "./static")

routers.AdminRoutersInit(r) //调用路由组
routers.DefaultRoutersInit(r)
routers.ApiRoutersInit(r)
r.Run() //默认端口是8000。括号里可以指定端口号,接收字符串类型,比如":4000"。注意":"不能省略!
}

上面代码中多了一些奇奇怪怪的东西,现在大可以忽略或者照抄。其中routers.ApiRoutersInit(r)中的定义与之前的routers.AdminRoutersInit(r)类似,不再赘述。

在templates/default中新建一个index.html,在里面随便写点什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{{define "default/index.html"}}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/css/base.css">
</head>
<body>

{{template "public/page_header.html" .}}
<h2>index</h2>
<h2>{{.title}}</h2>
<!-- 定义变量 -->
{{$t := .title}}
<hr>
<h4>
{{$t}}
</h4>
<!-- 条件判断 -->
{{if ge .score 60}}
<p>及格</p>
{{else}}
<p>不及格</p>
{{end}}

{{if gt .score 90}}
<p>优秀</p>
{{else if gt .score 80}}
<p>良好</p>
{{else if gt .score 60}}
<p>及格</p>
{{else}}
<p>不及格</p>
{{end}}

<!-- 循环遍历数据 -->
<ul>
{{range $key,$value := .hobby}}
<li>{{$key}}-----{{$value}}</li>
{{end}}
</ul>

<ul>
{{range $key,$value := .newsList}}
<li>{{$key}}-----{{$value.Title}}----{{$value.Content}}</li>
{{end}}
</ul>
<hr>
<ul>
{{range $key,$value := .hobby}}
<li>{{$key}}-----{{$value}}</li>
{{else}}
<li>切片没有数据</li>
{{end}}
</ul>
<!-- 解构结构体 -->
<hr>
<p>
{{with.news}}
{{.Title}}
{{.Content}}
{{end}}
</p>
<hr>
{{len .title}}
<hr>
{{.date}}
<hr>
{{UnixToTime .date}}
<img src="/static/image/pm.jpg" alt="">
</body>
</html>
{{end}}

这里开头写一句{{define "default/index.html"}}意思是在调用这个模板时要用的路径。


这还没完,我们还希望引入一些静态资源。可以看到,我们在html中已经写了一行导入外部css,,但这样直接导入会失败。所以所以前面的mian.go中已经加了一行代码r.Static(“/static”, “./static”)。第二个参数”./static”表示我们引入了相对于main.go文件的”./static”目录下的资源,并把他的路由链接设定为”/static”。(这里我在写博客的时候,我这里搞了好一会儿一直都是即便故意不配置也能加载css和图片。后来发现是浏览器的缓存原因。)

总结

至此,Gin的大致结构就是这样,主要就包含这么几个部分。

  • 用于启动的main.go
  • 用于存放模板的templates
  • 用于存放静态资源的static
  • 用于集合路由的路由组routers
  • 用于实现方法逻辑的controllers

最后这篇文章里还有一些没有解释的地方,以后再解释。