Golang gin框架|青训营

359 阅读16分钟

一、Gin 介绍

Gin 是一个 Go (Golang) 编写的轻量级 http web 框架,运行速度非常快,如果你是性能和高效的追求者

Gin 最擅长的就是 Api 接口的高并发,如果项目的规模不大,业务相对简单,这个时候我们也推荐您使用 Gin

Gin 的官网:gin-gonic.com/zh-cn/

Github 地址:github.com/gin-gonic/g…

二、Gin 环境搭建

要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区(demo)

下载并安装 gin

$ go get -u github.com/gin-gonic/gin

进入项目demo根目录, 第三方包用mod.mod 管理

$ go mod init demo

新建 main.go将 gin 引入到代码中

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

(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:

import "net/http"

配置路由, main.go文件内容如下:

package main

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

func main() {
        // 创建一个默认的路由引擎
        r := gin.Default()
        // 配置路由
        r.GET("/", func(c *gin.Context) {
                c.JSON(200, gin.H{
                        "username": "name1",
                        "data": "data1",
                })
        })
        // 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
        r.Run()
}

运行你的项目

$ go run main.go

可改变默认启动端口

r.Run(":9000")

三、golang 程序的热加载

所谓热加载就是当我们对代码进行修改时,程序能够自动重新加载并执行,这在我们开发中 是非常便利的,可以快速进行代码测试,省去了每次手动重新编译

工具一:github.com/gravityblas…

Fresh 是一个命令行工具,可以在您每次保存 Go 或模板文件时构建和(重新)启动您的 Web 应用程序安装fresh 库命令窗口,输入命令 :

go get github.com/pilu/fresh

进入项目的 根目录 fresh

fresh 将会自动运行项目的 main.go 热加载大功告成。

问题:使用fresh命令,总会遇到“ 不是内部或外部命令,也不是可运行的程序或批处理文件。”或者“command not found:fresh”。

解决方案:

第一步:打开任意一处终端,用go env命令查看,如果GO111MODULE=auto,将这一行改成GO111MODULE=on。

第二步:go install github.com/pilu/fresh@latest。

第三步:go mod init<你的项目名称>。

第四步:go get github.com/pilu/fresh。

紧接着直接fresh就行了。

工具二:github.com/codegangsta…

命令:

go get github.com/codegangsta/gin
gin run main.go

四、Gin 框架中的路由

4.1 路由概述

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问RESTful API 是目前比较成熟的一套互联网应用程序的 API 设计理论,所以我们设计我们的路由的时候建议参考 RESTful API 指南。

在 RESTful 架构中,每个网址代表一种资源,不同的请求方式表示执行不同的操作:

|请求资源 |说明|

| 请求资源 | 说明 | | GET (SELECT) | 从服务器取资源 | | POST(CREATE) | 新建资源 | | PUT (UPDATE) | 更新资源 | | DELETE | 删除资源 |

4.2 简单的路由配置

简单的路由配置(可以通过 postman 测试)当用 GET 请求访问一个网址的时候,做什么事情:

r.GET("/", func(c *gin.Context) {
        c.String(200, "hello world")
})

路由里面获取 Get 传值, 域名/news?aid=20

r.GET("/news", func(c *gin.Context) {
	aid := c.Query("aid")
	c.JSON(200, gin.H{
		"news": aid, // aid = 20
	})
})

其中:H 为 map[string]interface{} 类型

4.3 动态路由

域名/user/20

r.GET("/user/:id", func(c *gin.Context) {
	id := c.Param("id")
	c.JSON(200, gin.H{
		"news": id, // id 20
	})
})

4.4 c.String() c.JSON() c.JSONP() c.XML() c.HTML()

4.4.1 返回一个字符串

r.GET("/news", func(c *gin.Context) {
	aid := c.Query("aid")
	c.String(200, "string")
})

4.4.2 返回一个 JSON 数据

r.GET("/json", func(c *gin.Context) {
	aid := c.Query("aid")
	// 方式一:自己拼接 JSON
	c.JSON(http.StatusOK, gin.H{
		"msg": "JSON"
	})
})
r.GET("/structJson", func(c *gin.Context) {
	// 结构体方式
	var msg struct {
		Username string `json:"username"`
		Msg string `json:"msg"`
		Age string `json:"age"`
	}
	msg.Username = "name1"
	msg.Msg = "msg1"
	msg.Age = "18"
	c.JSON(200, msg)
})

4.4.3 返回JSOPN

r.GET("/JSONP", func(c *gin.Context) { 
data := map[string]interface{}{
	"foo": "bar",
}
// /JSONP?callback=x
// 将输出:x({\"foo\":\"bar\"}) 
c.JSONP(http.StatusOK, data)

})

4.4.4 渲染模板

r.GET("/", func(c *gin.Context) {
	c.HTML(
	http.StatusOK, "default/index.html",
	map[string]interface{}{ "title": "前台首页"
	})
})

五、Gin HTML 模板渲染

5.1 全部模板放在一个目录里面的配置方法我们首先在项目根目录新建 templates 文件夹,然后在文件夹中新建 index.html

<!DOCTYPE  html>
<html  lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>这是一个 html 模板</h1>

<h3>{{.title}}</h3>
</body>
</html>

Gin 框架中使用 c.HTML 可以渲染模板, 渲染模板前需要使用 LoadHTMLGlob() 或者LoadHTMLFiles()方法加载模板。

r.GET("/", func(c *gin.Context) {
	c.HTML(http.StatusOK,
		"default/index.html",
		map[string]interface{}{"title": "前台首页"})
})
r.GET("/", func(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{
		"title": "Main website",
	})
})

5.2 模板放在不同目录里面的配置方法

5.3 gin 模板基本语法

六、静态文件服务

当我们渲染的 HTML 文件中引用了静态文件时,我们需要配置静态 web 服务

r.Static(“/static”, “./static”) 前面的/static 表示路由 后面的./static 表示路径
        r := gin.Default()
        r.Static("/static", "./static")
        r.LoadHTMLGlob("templates/**/*")

七、路由详解

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问。

前面章节我们给大家介绍了路由基础以及路由配置,这里我们详细给大家讲讲路由传值、路 由返回值

7.1 GET POST 以及获取 Get Post 传值

7.1.1 Get 请求传值

GET /user?uid=20&page=1

r.GET("/user", func(c *gin.Context) { 
	uid := c.Query("uid")
	page := c.DefaultQuery("page", "0") 
	c.String(200, "uid=%v page=%v", uid, page)
})

7.1.2 动态路由传值

get /user/20

r.GET("/user/:id", func(c *gin.Context) {
	id := c.Param("id")
	c.JSON(200, gin.H{
		"news": id, // id 20
	})
})

7.1.3 Post 请求传值 获取 form 表单数据通过 c.PostForm 接收表单传过来的数据

postman进行数据提交

r.POST("/doAddUser", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")
	age := c.DefaultPostForm("age", "20")
	c.JSON(200, gin.H{
		"usernmae": username, "password": password, "age": age,
	})
})

7.1.4 Get 传值绑定到结构体

/?username=zhangsan&password=123456

type Userinfo struct {
        Username string `form:"username" json:"user"`
        Password string `form:"password" json:"password"`
}

func main() {
        // 创建一个默认的路由引擎
        r := gin.Default()
        // 配置路由
        r.GET("/", func(c *gin.Context) {
                var userinfo Userinfo
                if err := c.ShouldBind(&userinfo); err == nil {
                        c.JSON(http.StatusOK, userinfo)
                } else {
                        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                }
        })
}

返回数据

{"user":"zhangsan","password":"123456"}
7.1.5 Post 传值绑定到结构体
        r.POST("/doLogin", func(c *gin.Context) {
                var userinfo Userinfo
                if err := c.ShouldBind(&userinfo); err == nil {
                        c.JSON(http.StatusOK, userinfo)
                } else {
                        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                }
        })

7.1.6 获取 Post Xml 数据

通过ctx.ShouldBindXML获取xml中的数据

<?xml version="1.0" encoding="UTF-8"?>
<article>
        <content type="string">我是张三</content>
        <title type="string">张三</title>
</article>

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

r.POST("/xml", func(ctx *gin.Context) {
	var article Article
	if err := ctx.ShouldBindXML(&article); err == nil {
		ctx.JSON(http.StatusOK, article)
	}else {
		ctx.JSON(http.StatusBadRequest, gin.H {
			"err": err.Error()})
	}
})

通过ctx.GetRawData()获取数据

7.2 路由分组

r := gin.Default()
apiRouter := r.Group("/api")
{
	apiRouter.GET("/", func(ctx *gin.Context) {
		ctx.String(200, "api")
	})

	apiRouter.GET("articles", func(ctx *gin.Context) {
		ctx.String(200, "/api/articles")
	})
}	

7.3 路由分离

在项目新建文件夹router,然后在router目录下创建apiRouter.go 和adminRouter.go,内容如下:

package router
// apiRouter.go
import "github.com/gin-gonic/gin"

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                apiRouter.GET("/", func(ctx *gin.Context) {
                        ctx.String(200, "api")
                })

                apiRouter.GET("articles", func(ctx *gin.Context) {
                        ctx.String(200, "/api/articles")
                })
        }
}


package router
// adminRouter.go
import (
        "net/http"

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

func AdminRouter(r *gin.Engine) {
        adminRouter := r.Group("/admin")
        {
                adminRouter.GET("/", func(ctx *gin.Context) {
                        ctx.String(200, "admin")
                })

                adminRouter.GET("list", func(ctx *gin.Context) {
                        ctx.String(http.StatusOK, "admin/list")
                })
        }
}

在main.go中引入路由模块

package main

import (
        "socket/router"

        "github.com/gin-gonic/gin"
)
func main() {
        // 创建一个默认的路由引擎
        r := gin.Default()
        // 引入路由模块
        router.AdminRouter(r)
        router.ApiRouter(r)
        // 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
        r.Run()
}

八、Gin 中自定义控制器

8.1 控制器分组

当我们的项目比较大的时候有必要对我们的控制器进行分组 , 业务逻辑放在控制器中在项目文件夹下面新建controller\api\文件夹,创建userController.go

mkdir controller
cd controller
mkdir api

userController.go 内容

package api

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

func UserIndex(c *gin.Context) {
        c.String(200, "api UserIndex")
}

func UserAdd(c *gin.Context)  {
        c.String(200, "api UserAdd")
}

func UserList(c *gin.Context) {
        c.String(200, "api UserList")
}

func UserDelete(c *gin.Context) {
        c.String(200, "api UserDelete")
}

在apiRouter.go中调用userController.go 的函数

import (
        "socket/controller/api"

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

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                apiRouter.GET("/", 
                )

                apiRouter.GET("users", api.UserIndex)
                apiRouter.GET("users/:id", api.UserList)
                apiRouter.POST("users", api.UserAdd)
                apiRouter.PUT("users/:id", api.UserUpdate)
                apiRouter.DELETE("users/:id", api.UserDelete)
        }
}

为了使方法能够继承,我们修改userController.go 内容

package api

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

type UserController struct {

}

func (con UserController) Index(c *gin.Context) {
        c.String(200, "api Index")
}

func (con UserController) Add(c *gin.Context)  {
        c.String(200, "api Add")
}

func (con UserController) List(c *gin.Context) {
        c.String(200, "api List")
}

func (con UserController) Update(c *gin.Context) {
        c.String(200, "api update")
}

func (con UserController) Delete(c *gin.Context) {
        c.String(200, "api Delete")
}

继续修改apiRouter.go

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                apiRouter.GET("/")

                // api.UserController{} 实例化后才可以使用结构体的方法
                apiRouter.GET("users", api.UserController{}.List)
                apiRouter.GET("users/:id", api.UserController{}.List)
                apiRouter.POST("users", api.UserController{}.Add)
                apiRouter.PUT("users/:id", api.UserController{}.Update)
                apiRouter.DELETE("users/:id", api.UserController{}.Delete)
        }
}

8.2 控制器的继承

继承后就可以调用控制器里面的公共方法在api目录下新建baseController.go,内容如下:

package api

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

type BaseController struct{}

func (con BaseController) success(c *gin.Context) {
        c.String(200, "success")
}

func (con BaseController) error(c *gin.Context) {
        c.String(200, "failed")
}

继续修改userController.go

type UserController struct {
        // 继承BaseController
        BaseController
}

func (con UserController) Index(c *gin.Context) {
        // c.String(200, "api Index")
        con.success(c)
}

func (con UserController) Add(c *gin.Context)  {
        c.String(200, "api Add")
}

func (con UserController) List(c *gin.Context) {
        c.String(200, "api List")
}

func (con UserController) Update(c *gin.Context) {
        c.String(200, "api update")
}

func (con UserController) Delete(c *gin.Context) {
        c.String(200, "api Delete")
}

九、Gin 中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等,通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

9.1 路由中间件

9.1.1 初识中间件

Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函数。中间件要放在最后一个回调函数的前面 ,触发的方法都可以称为中间件

// 中间件函数
func InitMiddleWare(c *gin.Context) {
        fmt.Println("init middle ware ")
}
func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                // 中间件要放在最后一个回调函数的前面
                apiRouter.GET("/", InitMiddleWare, api.UserController{}.Index)
        }
}

9.1.2 ctx.Next()调用该请求的剩余处理程序

中间件里面加上 ctx.Next()后,c.Next()的语句后面先不执行,跳转到后面的中间件和回调函数中执行完后,才执行c.Next()后面的语句可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间

func InitMiddleWare(c *gin.Context) {
        fmt.Println("1- init middle ware ")
        start := time.Now().UnixNano()
        // 调用c.Next()请求的剩余处理程序
        // c.Next()的语句后面先不执行,先跳转路由匹配的最后一个回调函数执行后,
        // 才执行c.Next()后面的语句
        c.Next()
        fmt.Println("3-程序执行完成 计算时间")
        //  计算耗时	Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
        end := time.Now().UnixNano()
        fmt.Println(end - start)
}
func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                // 中间件要放在最后一个回调函数的前面
                apiRouter.GET("/", InitMiddleWare, func(ctx *gin.Context) {
                        fmt.Println("2 - 中间件")
                        ctx.String(200, "/api")
                })
        }
}

9.1.3 一个路由配置多个中间件的执行顺序

多个中间件执行顺序

func InitMiddleWareOne(c *gin.Context) {
        fmt.Println("one- init middleware start")
        c.Next()
        fmt.Println("one- init middleware end")
}
func InitMiddleWareTwo(c *gin.Context) {
        fmt.Println("Two- init middleware start")
        c.Next()
        fmt.Println("Two- init middleware end")
}

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                // 中间件要放在最后一个回调函数的前面
                apiRouter.GET("/", InitMiddleWareOne, InitMiddleWareTwo, func(ctx *gin.Context) {
                        fmt.Println("首页")
                        ctx.String(200, "/api")
                })
        }
}

控制台输出的结果

one- init middleware start
Two- init middleware start
首页
Two- init middleware end
one- init middleware end

9.1.4 ctx.Abort()了解

Abort是终止的意思,ctx.Abort()表示终止调用该请求的剩余处理程序Abort()后,中间件后面的回调函数(包括后面的中间件)不执行了,直接执行该中间件这里面的语句

9.2 全局中间件

r.Use(中间件1,中间件2…)
func InitMiddleWareOne(c *gin.Context) {
        fmt.Println("one- init middleware start")
        c.Next()
        fmt.Println("one- init middleware end")
}
func InitMiddleWareTwo(c *gin.Context) {
        fmt.Println("Two- init middleware start")
        c.Next()
        fmt.Println("Two- init middleware end")
}

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        apiRouter.Use(InitMiddleWareOne, InitMiddleWareTwo)
        {
                // 中间件要放在最后一个回调函数的前面
                apiRouter.GET("/", func(ctx *gin.Context) {
                        fmt.Println("首页")
                        ctx.String(200, "/api")
                })
                apiRouter.GET("users", api.UserController{}.Index)
        }
}

9.3 在路由分组中配置中间件

9.4 中间件和对应控制器之间数据共享

中间件中设置值

ctx.Set("username", "张三")

控制器或者中间件中获取值

username, _ := ctx.Get("username")

username 是一个空接口类型,通过转为字符串v, ok := username.(string)

举例: 中间件

func LoginMiddleWare(c *gin.Context) {
        fmt.Println("login middle ware")
        c.Set("username", "张三")
}

控制器

func (con UserController) Index(c *gin.Context) {
        username, _ := c.Get("username")
        fmt.Println(username) 
        // c.String(200, username)

        c.JSON(200, gin.H{
                "username" : username,
        })
        // con.success(c)
}

中间件注意事项

gin默认中间件

gin.Default()默认使用了Logger和Recovery中间件,其中:

Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=releaseRecovery中间件会recover任何panic,如果有panic的话,为写入500响应码如果不想使用上面的默认中间件,可以使用gin.New()新建一个没有任何中间件的路由gin中间件中使用goroutine当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())

func LoginMiddleWare(c *gin.Context) {
        fmt.Println("login middle ware")
        c.Set("username", "张三")

        // 定义一个goroutine统计日志
        cCp := c.Copy()
        go func ()  {
                time.Sleep(2 * time.Second)
                // 用了c.Request.URL.Path 也没有问题?
                fmt.Println("Done in path " + cCp.Request.URL.Path)
        }()
}

十、Gin 中自定义Model

10.1 关于Model

如果我们的应用非常简单的话,我们可以在Controller 里面处理常见的业务逻辑。但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。

Model 是逐步抽象的过程,一般我们会在Model里面封装一些公共的方法让不同Controller 使用,也可以在Model 中实现和数据库打交道

10.2 Model 里面封装公共的方法

新建model/tools.go

package model

import (
        "crypto/md5"
        "fmt"
        "time"
)

//时间戳间戳转换成日期
func UnixToDate(timestamp int) string {
        t := time.Unix(int64(timestamp), 0)
        return t.Format("2006-01-02 15:04:05")
}

//日期转换成时间戳2020-05-02 15:04:05
func DateToUnix(str string) int64 {
        template := "2006-01-02 15:04:05"
        t, err := time.ParseInLocation(template, str, time.Local)
        if err != nil {
                return 0
        }
        return t.Unix()
}

// 获取时间戳
func GetUnix() int64 {
        return time.Now().Unix()
}

// 获取当前日期
func GetDate() string {
        template := "2006-01-02 15:04:05"
        return time.Now().Format(template)
}

// 获取年月日
func GetDay() string {
        template := "20060102"
        return time.Now().Format(template)
}

func Md5(str string) string {
        data := []byte(str)
        return fmt.Sprintf("%x\n", md5.Sum(data))
}

十一、Gin 文件上传

十二、Gin 中的Cookie

12.1 Cookie 介绍

HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用Cookie 或者Session 实现cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名的时候共享数

12.2 Cookie 能实现的功能

保持用户登录状态

保存用户浏览的历史记录

猜你喜欢,智能推荐

电商网站的加入购物车

12.3 设置和获取Cookie

前面我们已经使用过ctx.Set("username") 和 ctx.Get("username")来进行数据的保存和共享,但这个使用的只针对是单页面的数据共享,要想实现多页面的共享,就需要Cookie或者Session

gin-gonic.com/zh-cn/docs/…

12.3.1 设置Cookie

c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
  • 其中:
  • 第一个参数key
  • 第二个参数value
  • 第三个参数过期时间.如果只想设置Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递nil
  • 第四个参数cookie 的路径
  • 第五个参数cookie 的路径Domain 作用域本地调试配置成localhost , 正式上线配置成域名
  • 第六个参数是secure ,当secure 值为true 时,cookie 在HTTP 中是无效,在HTTPS 中才有效
  • 第七个参数httpOnly,是微软对COOKIE 做的扩展。如果在COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到COOKIE 信息,防止XSS 攻击产生

12.3.2 获取Cookie

cookie, err := c.Cookie("name")

12.3.3 删除Cookie

把第三个参数时间设置为-1

12.3.3 举例

func ApiRouter(r *gin.Engine) {
        apiRouter := r.Group("/api")
        {
                apiRouter.GET("/", func(ctx *gin.Context) {
                        // 设置Cookie
                        ctx.SetCookie("username", "张三", 3600, "/", "localhost", false, false)
                        fmt.Println("首页")
                        ctx.String(200, "/api")
                })

                apiRouter.GET("/news", func(ctx *gin.Context) {
                        // 获取Cookie
                        username, _ := ctx.Cookie("username")
                        fmt.Println(username)
                        ctx.String(200, "/news/"+username)
                })
        }
}

12.4 多个二级域名共享cookie

我们想的是用户在a.test.com 中设置Cookie 信息后在b.test.com 中获取刚才设置的cookie,也就是实现多个二级域名共享cookie, 这时候的话我们就可以这样设置cookie

c.SetCookie("usrename", "张三", 3600, "/", ".test.com", false, true)

十三、Gin 中的Session

十四、Gin 中使用GORM 操作mysql 数据库

14.1 GORM 简单介绍

GORM 是Golang 的一个orm 框架。简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用ORM框架可以让我们更方便的操作数据库。

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

14.2 Gorm 特性

全功能ORM

关联(Has One,Has Many,Belongs To,Many To Many,多态,单表继承)Create,Save,Update,Delete,Find 中钩子方法支持Preload、Joins 的预加载事务,嵌套事务,Save Point,Rollback To Saved PointContext、预编译模式、DryRun 模式批量插入,FindInBatches,Find/Create with Map,使用SQL 表达式、Context Valuer 进行CRUDSQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询复合主键,索引,约束Auto Migration自定义Logger灵活的可扩展插件API:Database Resolver(多数据库,读写分离)、Prometheus…每个特性都经过了测试的重重考验开发者友好

官方文档:gorm.io/zh_CN/docs/…

14.3 Gin 中使用GORM

14.3.1 安装

如使用go mod 管理项目的话可以忽略此步骤

go get -u gorm.io/driver/postgres
go get -u gorm.io/gorm
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

14.3.2 Gin 中使用Gorm 连接数据库

在models 下面新建core.go ,建立数据库链接

package model

import (
        "fmt"
        "os"
        "os/signal"
        "gopkg.in/ini.v1"
        "gorm.io/driver/postgres"
        "gorm.io/gorm"
)

var DB *gorm.DB
var err error

func init() {
        fmt.Println("database init start")

        config, err := ini.Load("./config/app.ini")

        if err != nil {
                fmt.Printf("Failed to read file: %v", err)
                os.Exit(1)
        }

        ip := config.Section("postgres").Key("ip").String()
        port := config.Section("postgres").Key("port").String()
        username := config.Section("postgres").Key("username").String()
        password := config.Section("postgres").Key("password").String()
        database := config.Section("postgres").Key("database").String()

        // dsn := "host=localhost user=postgres password=postgres dbname=study_demo port=5432 sslmode=disable TimeZone=Asia/Shanghai"
        dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=Asia/Shanghai", ip, username, password, database, port)
        DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
                DryRun: false, //直接运行生成sql而不执行
                // Logger: logger.Default.LogMode(logger.Info), // 可以打印SQL
                // QueryFields: true, // 使用表的所有字段执行SQL查询
                // 关闭事务,gorm默认是打开的,关闭可以提升性能
                // SkipDefaultTransaction: true,
        })

        c := make(chan os.Signal)
        if err != nil {
                fmt.Println("Connect database failed and Shutdown web server")
                c <- os.Interrupt
                signal.Notify(c, os.Interrupt)

                // 下面是监听所有的信号
                // signal.Notify(c)
                // 致命错误,如果web服务器在它启动前,则不能关闭服务器,所以需要用到上面的signal 来处理
                // log.Fatal("connect database failed", err)
        }
}

14.3.3定义操作数据库的模型

Gorm 官方给我们提供了详细的:gorm.io/zh_CN/docs/…

虽然在gorm 中可以指定字段的类型以及自动生成数据表,但是在实际的项目开发中,我们是先设计数据库表,然后去实现编码的。

在实际项目中定义数据库模型注意以下几点:

结构体的名称必须首字母大写,并和数据库表名称对应。例如:表名称为user 结构体名称定义成User,表名称为article_cate 结构体名称定义成ArticleCate结构体中的字段名称首字母必须大写,并和数据库表中的字段一一对应。例如:下面结构体中的Id 和数据库中的id 对应,Username 和数据库中的username 对应,Age 和数据库中 的age 对应,Email 和数据库中的email 对应,AddTime 和数据库中的add_time 字段对应默认情况表名是结构体名称的复数形式。如果我们的结构体名称定义成User,表示这个模型默认操作的是users 表。

我们可以使用结构体中的自定义方法TableName 改变结构体的默认表名称,如下

package model 

type User struct {
        Id int
        Username string
        Password string
        State int
        AddTime int
}
// 表示把User 结构体默认操作的表改为 gin_user 表
func (User) TableName()string  {
        return "gin_user"
}

新建userController 控制器

package admin

import (
        "demo/model"
        "github.com/gin-gonic/gin"
        "net/http"
)

type UserController struct {
        BaseController
}

func (p UserController) Index(c *gin.Context) {
        userList := []model.User{}
        model.DB.Where("id < ?", 5).Find(&userList)
        c.JSON(http.StatusOK, gin.H{
                "data": userList,
        })
}

14.4 Gin GORM CURD

十五、原生 SQL 和 SQL 生成器

十六、Gin 中使用GORM 实现表关联查询

十七、 GORM 中使用事务

十八、Gin 中使用go-ini 加载.ini 配置文件