友情提示,本篇博客的代码都可以从这里获取。
Gin的基本使用
首先用go mod建立一个项目,比如就叫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!") }) r.Run() }
|
在终端中输入下面这行即可开启热加载,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!") }) routers.AdminRoutersInit(r) r.Run(":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{ "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/**/*")
r.Static("/static", "./static")
routers.AdminRoutersInit(r) routers.DefaultRoutersInit(r) routers.ApiRoutersInit(r) r.Run() }
|
上面代码中多了一些奇奇怪怪的东西,现在大可以忽略或者照抄。其中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
最后这篇文章里还有一些没有解释的地方,以后再解释。