Gin的结构

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

Gin的基本使用

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

go mod init ginnote

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

go get github.com/gin-gonic/gin

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

go get github.com/pilu/fresh

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

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

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退出。

go run github.com/pilu/fresh

Gin的目录结构

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

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

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

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。

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中的内容。

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中调用了。

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中被调用,就像这样:

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,或者写个别的包名也行。

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中调用方法。

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中加载模板。

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,在里面随便写点什么。

{{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

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

posted @   Luviichann  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示