Go语言-Gin框架
Gin是一个用Go编写的web微框架,和php的laravel相比的话,更像是一个库,短小精致,灵活,需要根据使用情况,自己组合装载其他依赖扩展。
一:安装:
我这里用 go mod方式,go mod相当于php的composer,node的npm。
1:首先创建一个项目目录,我这里叫:dao
2:开启对mod支持
set GO111MODULE=on //windows export GO111MODULE=on //linux go env -w GO111MODULE=on //或者直接这样,大于等于1.13不用上面那么麻烦GO111MODULE三个参数:
off:无模块支持,go 会从 GOPATH 和 vendor 文件夹寻找包。
on:模块支持,go 会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod下载依赖。
auto:在 $GOPATH/src外面且根目录有 go.mod文件时,开启模块支持。
3:初始化mod,进入dao目录,执行:
go mod init daoprotobuf //建议这里取项目名称,我这里是dao
可以看到,这里生成go.mod文件,打开看下:
module dao go 1.18先不管,然后我们编辑go.mod文件:
在以上目录中执行命令行:
go mod edit -require github.com/gin-gonic/gin@v1.7.7 #v1.7.7版本 go mod edit -require github.com/gin-gonic/gin@latest # 或者执行最新版本
再打开看一下,里面多了一段require:
module dao go 1.18 require github.com/gin-gonic/gin v1.7.7也可以用编辑器直接编辑go.mod。
4:我们把官方的代码搞进去,这里新建一个main.go文件:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
//1.创建路由
r := gin.Default()
//2.绑定路由规则,执行的函数
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "Hello World!")
})
//3.监听端口,默认8080
r.Run(":8080")
}5:执行一下:
go run main.go这里会自动拉取main.go中的包,我这里报错。

可以看到 我这里访问这个路径不通
go env -w GO111MODULE=on go env -w GOPROXY="https://goproxy.io,direct"然后再执行一下依赖:
go mod tidy #整理包,根据项目中的代码增加缺少的module,删除无用的module
执行成功,这时候可以看看go.mod文件,里面多了一些东西,另外多了一个go.sum文件,细心的人可能会问,这些包下载到啥地方去了?
go env
这些包在GOPATH目录下,那可不可以移到项目中呢?很简单,执行一下:go mod vendor
再看一下,多了一个目录,所有的依赖都复制过来了。执行:go run main.go

二:路由分组
userRouter := r.Group("/v1") //分组
userRouter.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "v1-Hello World!")
}) 三:拆分单个路由
在实际开发中,如果把路由都写在main中,显得很臃肿,为了目录清晰,我们搞一下分拆。
1:新建一个routers目录,在里面创建一个routers.go文件(文件名可以随便取,为了清晰最好统一),编辑routers.go文件
package routers
import (
"net/http"
"github.com/gin-gonic/gin"
)
//首字母大写表示公有方法
func SetRouter() *gin.Engine {
r := gin.Default()
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "Hello World!")
})
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
return r
}
其实就是我们把main中写的路由移过来,当然要稍微改一下。然后回到main.go文件:
package main
import (
"dao/routers" // 导入我的路由包,routers 是目录名、也是包名
"fmt"
)
func main() {
r := routers.SetRouter()
if err := r.Run(":8080"); err != nil {
fmt.Printf("startup service failed, err:%v\n", err)
}
}
这样就可以了。
四:路由中间件
全局路由中间件,单个路由中间件,分组中间件,三个路由中间件主要看怎么使用,并不是看定义,在所有路由之前注册就是全局中间件。
package routers
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
/**
定义一个中间件方法,获取程序执行时间,返回类型 gin.HandlerFunc
*/
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中, 可以通过Get()取
c.Set("exp", "中间件添加的变量")
// 程序继续往下走
c.Next()
status := c.Writer.Status() // 返回当前请求的HTTP响应状态代码
fmt.Println("中间件执行完毕", status)
diffTime := time.Since(start) // 时间差
fmt.Println("time:", diffTime)
}
}
/**
定义一个局部中间件,name 不是李小龙,那么拦截
*/
func MiddleTestName() gin.HandlerFunc {
return func(c *gin.Context) {
name := c.Param("name")
if name != "李小龙" {
c.Abort()
c.JSON(http.StatusUnauthorized, gin.H{"message": "身份验证失败"})
} else {
c.JSON(http.StatusOK, gin.H{"message": "身份验证成功"})
c.Next()
}
}
}
//首字母大写表示公有方法
func SetRouter() *gin.Engine {
r := gin.Default()
r.Use(MiddleWare()) // 注册 全局中间件,因为是在路由前注册,所以可以拦截一下路由,否则也不能全局
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "Hello World!")
})
// 此路由调用 MiddleTestName() 中间件
r.GET("/user/:name", MiddleTestName(), func(c *gin.Context) {
name := c.Param("name")
exp, _ := c.Get("exp") // 获取上面中间件添加的Context的key,这里返回两个参数
c.String(http.StatusOK, "Hello %s%s", name, exp)
})
userRouter := r.Group("/v1")
userRouter.Use(MiddleTestName()) // 群组路由中间件
userRouter.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "v1-Hello World!")
})
return r
}
五:功能模块之控制器
我这里按照MVC模式干,这里相当于控制器代码,把目录再整理一下

我这里先建立两个控制器文件
注意:同一个目录下的文件,package必须相同。这里有个问题,因为package相同,不同文件的方法也必须不同,不然就冲突报错。可以用struct或者interface解决这个问题。
下面在两个控制定义两个相同的方法:
1:GoodsController.go
package controller
import "github.com/gin-gonic/gin"
type Goods struct{}
//指针写法
func (g *Goods) Index(context *gin.Context) {
context.JSON(200, gin.H{"msg": "hello goods"})
}
//非指针
func (Goods) List(context *gin.Context) {
context.JSON(200, gin.H{"msg": "goods list"})
}
2:HomeController.go
package controller
import "github.com/gin-gonic/gin"
type Home struct{}
//指针写法
func (h *Home) Index(context *gin.Context) {
context.JSON(200, gin.H{"msg": "hello home"})
}
//非指针
func (Home) List(context *gin.Context) {
context.JSON(200, gin.H{"msg": "home list"})
} 虽然方法名相同,但struct不一样,所以不会传统,然后路由关联:首先import导入 "dao/app/controller"
r.GET("/goods", (&controller.Goods{}).Index)
r.GET("/home", (&controller.Home{}).Index)
r.GET("/list", (controller.Home{}).List)六:DB
1:我这里用go-gorm,地址:https://gorm.io/,GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
我们把db连接放在lib\database\Db.go,因为寡人用go mod管理包,直接导入就行
package db import ( _ "gorm.io/driver/mysql" #选择数据库驱动,我这里是mysql _ "gorm.io/gorm" )
或者直接命令下载依赖包
go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite
然后执行:
go mod tidy #拉取包 go mod vendor #同步到目录
继续Db.go数据库连接包:
package db
import (
"gorm.io/gorm/schema"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 返回数据库连接句柄
func InitDb() *gorm.DB {
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "root:123456@tcp(127.0.0.1:3306)/blog?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{ // 更多设置 https://gorm.io/docs/gorm_config.html
NamingStrategy: schema.NamingStrategy{ // 设置这个,导入包 gorm.io/gorm/schema
// TablePrefix:"" //表名前缀
SingularTable: true, //不让表名自动加s
//NoLowerCase: true, //关闭驼峰表名转下划线
},
})
if err != nil {
panic(err)
}
return db
}
2:使用,我这里在控制器调用db
package controller
import (
"dao/app/lib/db"
"github.com/gin-gonic/gin"
)
type Goods struct{}
/**
* 结构体名称为:驼峰写法,JsNew,对应数据库表:js_new
* 字段,首字母大写
*/
type JsNew struct {
Id int `gorm:"primaryKey" json:"id"` //主键,json 设置别名,要不然输出为大写开头
Title string `json:"title"`
IgnoreMe int `gorm:"-"` // 忽略字段
}
//指针写法
func (g *Goods) Index(context *gin.Context) {
var news []JsNew
db.InitDb().Select("id", "title").First(&news) //结构体映射,获取一条记录,更多用户见官方说明
context.JSON(200, gin.H{"msg": news})
} 输出:
// 也可以不用结构体映射。Raw原生写mysql,然后Scan映射map结构
news := make([]map[string]interface{}, 0)
db.InitDb().Raw("SELECT id,title FROM js_new LIMIT 1").Scan(&news)如果使用原生sql,查询用:Raw和Scan,写入用Exec。
七:模板
1:设置,这里是全局设置,设置所有的模板都在app/views/*/下
导入os和path两个包,用于设置绝对路径,go mod运行的路径不是golang路径,用相对路径不好使。
import ( "dao/app/controller" "fmt" "net/http" "os" "path/filepath" "time" "github.com/gin-gonic/gin" )设置模板,我这里设置不同的模板在不同的目录,统一都在wiews下,这样好区分。
r := gin.Default()
rootViews, _ := os.Getwd()
r.Delims("{{", "}}") //自定义模板分隔符,默认这个
r.LoadHTMLGlob(filepath.Join(rootViews, "app/views/**/*")) //模板路径 类似这样:app/views/goods/index.html
app/views/home/index.html
接着上面的控制器写:
func (g *Goods) Index(context *gin.Context) {
context.HTML(http.StatusOK, "goods/index.html", gin.H{
"title": "我是 goods目录下的index.html",
})
}模板:
我的模板文件结构:

这里有个关键的地方,要对应起来,否则找不到模板:


2:layout布局
有些公共的模板,比如头部、尾部等等,我们一般单独整,需要的页面加载进来就行了。

编辑 top.html
然后加入到我们的模板文件中中
{{template "layout/top" .}} 后面这个 点,表示当前页面的变量、top.html中也可以使用。

3:加载静态资源
比如js,css,图片等等,首先在路由中设置:
rootViews, _ := os.Getwd()
r.Static("/static", filepath.Join(rootViews, "app/static")) // 加载静态资源路径,css,js
r.Static("/temp", filepath.Join(rootViews, "app/temp")) // 加载静态资源路径,图片 第一个参数为 api标识,也就是我们在html里面就用这个当成文件路径,第二个标识是我这里的文件绝对路径。那么模板里面:
static/assets/css/amazeui.min.css">
static/assets/css/app.css">
static/assets/css/common.css">