当前位置: 首页>后端>正文

GO使用gin路由

框架介绍

gin框架是Go语言进行web开发(api开发,微服务开发)框架中,功能和Martini框架似的API,但是性能却特别好的一个框架(比Martini快将近40倍吧),所以如果你特别在乎性能,那么Gin会是一个比较好的选择。

gin框架主要基于httprouter模块进行实现。gin框架和httprouter都是一个开源的框架。

微服务本身即是一种开发模式,将业务拆分成为一个个细小的微服务模块,然后以api(rpc)方式对外提供实现,实现的功能是一个独立的业务模块,那么使用轻量级的gin便是一个不错的选择。

gin框架包含的功能模块

gin框架包含了以下主要功能:

http请求的Context上下文

基础的auth认证模块

带颜色的logger模块

运行模式mode设定

响应处理的responsewriter模块

以及路由组routergroup

下载使用

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

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

HTTP服务

func main() {
    router := gin.Default()
    //路由
    router.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello gin",
        })
    })
    router.Run(":8080")
}

路由绑定

router.GET("/", index)表示用GET方式接收路由,如果路由是根目录,那么直接执行index控制器方法,index控制器必须含有gin.Context参数,也可以向上面一样将index控制器的内容写成匿名函数。

router.GET("/", index)

func index(c *gin.Context) {
    c.JSON(200, gin.H{
        "msg": "hello gin",
    })
}

路由分离

为了更好的管理路由,最好将路由和控制器分开不同的文件

在根目录下新建router.go

package gin

import (
    "github.com/gin-gonic/gin"
    "os"
    "path/filepath"
)

func initRouter() *gin.Engine {
    //路由
    router := gin.Default()
    router.GET("/", index)
    return router
}

main方法中进行初始化

func main() {
    router := initRouter()
    router.Run(":8080")
}

此时目录结构如下

--src
    --gin
        --gin.go     #用于存放控制器
        --router.go  #用于存放路由
    --main.go

路由组

一些情况下,我们会有统一前缀的 url 的需求,典型的如 Api 接口版本号 /v1/something。Gin 可以使用 Group 方法统一归类到路由组中

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

    // /v1/login 就会匹配到这个组
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.GET("/read", readEndpoint)
    }

    // 不用花括号包起来也是可以的。上面那种只是看起来会统一一点。看你个人喜好
    v2 := router.Group("/v2")
    v2.POST("/login", loginEndpoint)
    v2.POST("/submit", submitEndpoint)
    v2.GET("/read", readEndpoint)

    router.Run(":8080")
}

异步处理

goroutine 机制可以方便地实现异步处理

func main() {
    r := gin.Default()
    //1\. 异步
    r.GET("/long_async", func(c *gin.Context) {
        // goroutine 中只能使用只读的上下文 c.Copy()
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)

            // 注意使用只读上下文
            log.Println("Done! in path " + cCp.Request.URL.Path)
        }()
    })
    //2\. 同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(5 * time.Second)

        // 注意可以使用原始上下文
        log.Println("Done! in path " + c.Request.URL.Path)
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

接收参数

接收GET参数

访问链接: http://localhost:8080/user?firstname=jian&lastname=chen

路由

router.GET("/user", hello)

控制器 (这里用的接收方法是Query)

func hello(c *gin.Context) {
    firstname := c.DefaultQuery("firstname", "Guest") //设置默认参数值,没有这个参数时得到默认值
    lastname := c.Query("lastname")                   //获取参数值,c.Request.URL.Query().Get("lastname")的缩写
    c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
}

或者

访问链接:http://localhost:8080/user/jian/eat

路由 (参数名用:号标记)

router.GET("/user/:name/:action", user)

或者

router.GET("/user/:name/*action", user)

上面这个写法将会匹配/user/:name/开头的所有路由

控制器 (这里的接收方法是Param)

func user(c *gin.Context) {
    name := c.Param("name")
    action := c.Param("action")
    message := name + " is " + action
    c.String(http.StatusOK, message)
}

接收POST参数

路由

router := gin.Default()
router.POST("login", login)
router.Run(":8080")

控制器

func login(c *gin.Context) {
    username := c.PostForm("username") 
    password := c.PostForm("password")                  
    c.String(http.StatusOK, "登录 %s %s", username, password)
}

web文件

<body>
    <h1>登录</h1>
    <form action="http://127.0.0.1:8080/login" method="post" enctype="application/x-www-form-urlencoded">
        用户名:
        <input type="text" name="username">
        <br>
        密码:
        <input type="text" name="password">
        <br>
        <input type="submit" value="登录">
    </form>
</body>
GO使用gin路由,第1张

文件上传

单文件上传

路由

router := gin.Default()
router.POST("/upload", upload)
router.Run(":8080")

控制器

func upload(c *gin.Context) {
    file, _ := c.FormFile("file") //表单的文件name="file"
    //文件上传路径和文件名
    c.SaveUploadedFile(file, "./upload/"+file.Filename)
    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
}

web文件

<body>
<h1>上传文件</h1>
<form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
    图像:
    <input type="file" name="file">
    <br>
    <input type="submit" value="提交">
</form>
</body>

在浏览器运行web文件

GO使用gin路由,第2张

选择图片后点击提交上传成功

GO使用gin路由,第3张

在项目目录中可以看到图片已经上传并保存成功

GO使用gin路由,第4张

多文件上传

注意多文件上传表单<form>标签需要注明属性enctype="multipart/form-data" method="post"<input>标签的name属性值必须相同,例如全部为name="files"

路由

router := gin.Default()
router.POST("/multiupload", multiupload)
router.Run(":8080")

控制器

func multiupload(c *gin.Context) {
    // 文件上传大小限制 8 MB,在路由注册时设定
    //router.MaxMultipartMemory = 8 << 20
    formdata, err := c.MultipartForm()
    if err != nil {
        c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
        return
    }
    files := formdata.File["files"] //表单的文件name="files"
    for _, file := range files {
        err := c.SaveUploadedFile(file, "./upload/" + file.Filename)
        if err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
            return
        }
    }
    c.String(http.StatusOK, fmt.Sprintf("upload ok %d files", len(files)))
}

web文件

<body>
<h1>上传文件</h1>
<form action="http://127.0.0.1:8080/multiupload" method="post" enctype="multipart/form-data">
    图像:
    <input type="file" name="files" multiple>
    <br>
    <input type="submit" value="提交">
</form>
</body>

视图模板

  • 目录结构

    在根目录下新建templates文件夹用于存放html页面,为了便于管理在templates目录下再创建一个index文件夹存放与index控制器相关的页面,在index目录下新建index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>
        {{.title}}
    </h1>
</body>
</html>

此时目录结构如下

- src
    --gin
        --main.go
        --router.go
        --templates
            --index
                --index.html

加载视图

在初始化路由的位置加载所有视图模板,其中**表示各个控制器或路由组对应的视图目录,*表示该目录下所有文件

router := gin.Default()
//加载模板
router.LoadHTMLGlob(filepath.Join(os.Getenv("GOPATH"), "./src/gin/templates/**/*"))
router.GET("/", index)

控制器绑定视图

func index(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{
        "title": "Main website",
    })
}

静态文件

网页开发离不开css和图片等静态资源文件,我们必须设置好路径才能正确访问,例如我的目录结构为(缩进表示二级目录)

--src
    --gin
        --static
            --css
                --index.css
        --templates
            --index
                --index.html
        --router.go
        --main.go

如果index.html文件需要引入index.css文件,则在路由申请的地方声明

//加载模板
router.LoadHTMLGlob(filepath.Join(os.Getenv("GOPATH"), "./src/gin/templates/**/*"))
//加载静态文件
router.Static("/static", filepath.Join(os.Getenv("GOPATH"), "./src/gin/static"))

然后index.css文件中这样调用就可以了,其他静态资源用法类似

<link rel="stylesheet" href="/static/css/index.css">

参数传递

在控制器传递参数

c.HTML(http.StatusOK, "index.html", gin.H{
    "title": "Main website",
})

在视图渲染参数

<h1>
    {{.title}}
</h1>

重定向

r.GET("/redirect", func(c *gin.Context) {
    //支持内部和外部的重定向
    c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})

中间件

中间件详解

所谓中间件,在正式处理http请求之前,做一层逻辑控制,可以校验token合法性,可以打印好请求数据的日志,可以捕获错误的异常等等

gin可以构建中间件,但它只对注册过的路由函数起作用
对于分组路由,嵌套使用中间件,可以限定中间件的作用范围
中间件分为全局中间件,单个路由中间件和群组中间件
gin中间件必须是一个gin.HandlerFunc类型

使用中间件

router := gin.Default()
router.Use(middleware.IPLimit()) //使用自定义中间件:IP验证

中间件定义和 实现

package middleware

import (
    "core"
    "github.com/gin-gonic/gin"
    "net/http"
    "strings"
)

//访问ip限制
func IPLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP()
        ipList := strings.Split(core.Config["allow_ip"], "|")
        flag := false
        for i := 0; i < len(ipList); i++ {
            if ip == ipList[i] {
                flag = true
                break
            }
        }

        if !flag {
            c.Abort()
            c.JSON(http.StatusUnauthorized, gin.H{
                "code": 0,
                "msg":  "IP " + ip + " 没有访问权限",
                "data": nil})
            return // return也是可以省略的,执行了abort操作,会内置在中间件defer前,return,写出来也只是解答为什么Abort()之后,还能执行返回JSON数据
        } 
    }
}

数据绑定和验证

使用 c.ShouldBind方法,可以将参数自动绑定到 struct,该方法是会检查 Url 查询字符串和 POST 的数据,而且会根据 content-type类型,优先匹配JSON或者 XML,之后才是 Form。数据绑定可以用来做数据验证,例如

路由

router.POST("/binding", binding)

控制器

//数据结构体,username为表单字段,required表示必须参数,可选的话binding留空即可
type Login struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

//控制器
func binding(c *gin.Context) {
    var form Login
    // 验证数据并绑定
    if err := c.ShouldBind(&form); err == nil {
        if form.Username == "manu" && form.Password == "123" {
            c.JSON(http.StatusOK, gin.H{"msg": "Login successfully"})
        } else {
            c.JSON(http.StatusUnauthorized, gin.H{"msg": "username or password error"})
        }
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}

  • 当没有接收到参数时返回

    {
        "error": "Key: 'Login.Username' Error:Field validation for 'Username' failed on the 'required' tag"
    }
    
    
  • 当参数错误时返回

    {
        "msg": "username or password error"
    }
    
    
  • 当接收到参数且正确时放回

    {
        "msg": "Login successfully"
    }
    
    

    不用在接收参数时用if逐个进行验证,除了binding:"required"属性外还有更多的校验规则,可以参考 https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags

数据响应

返回数据给客户端有几种方式

1.json响应

router.GET("respond", func (c *gin.Context) {
        c.JSON(200, gin.H {
            "status" : 200,
            "message" : "message",
            "number" : 123})
})
GO使用gin路由,第5张

2.结构体响应

router.GET("/respond", func (c *gin.Context) {
        var msg struct {
            Name string
            Message string
            Number int
        }

        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })
GO使用gin路由,第6张

3.xml响应

router.GET("respond", func (c *gin.Context) {
        c.XML(200, gin.H {
            "status" : 200,
            "message" : "xml message",
            "number" : 123})
    })
GO使用gin路由,第7张

4.YAML响应

router.GET("respond", func (c *gin.Context) {
        c.YAML(200, gin.H {
            "status" : 200,
            "message" : "YAML message",
            "number" : 123})
    })

访问会直接弹出下载框

GO使用gin路由,第8张

用记事本打开下载的文件

GO使用gin路由,第9张

5.protobuf格式响应,谷歌开发的高效存储读取的工具

需要导入库 "github.com/gin-gonic/gin/testdata/protoexample"

router.GET("respond", func (c *gin.Context) {
        resp := []int64{int64(1), int64(2)}
        label := "label"
        data := &protoexample.Test {
            Label: &label,
            Reps: resp,
        }
        c.ProtoBuf(200, data)
    })

访问同样会下载一个文件

GO使用gin路由,第10张

记事本打开

GO使用gin路由,第11张

cookie

客户端和服务器之间的请求,可以携带cookie,cookie可以保存在客户端本地,下次请求的时候携带着给服务端,服务端可以拿到这个cookie的信息,同时服务端也可以设置cookie返回给客户端。

最常见的cookie使用就是服务端做中间件,对用户的登录信息做校验,检测用户是否登录。

cookie使用


router.GET("/cookie", cookie)

func cookie(c *gin.Context) {
    // 获取本次请求携带的cookie
    cookie, err := c.Cookie("key_cookie")
    if err != nil {
        cookie = "NotSet"
        // 给客户端设置cookie
        // name: cookie名
        // value: cookie的值
        // maxAge: 过期时间,单位秒
        // path: cookie所在目录
        // domain: 域名
        // secure: 是否只能通过https访问
        // httpOnly: 是否允许别人通过js获取自己的cookie
        c.SetCookie("key_cookie",  "value_cookie", 180, "/", "localhost", false, true)
    }
    fmt.Printf("cookie的值是:%s\n", cookie)
}

浏览器访问http://localhost:8080/cookie,会得到cookie

GO使用gin路由,第12张

cookie的缺点

1.明文
2.增加带宽消耗
3.可以被禁用
4.内容大小有上限

Session

Session可以弥补Cookie的不足,Session必须依赖于Cookie才能使用,生成一个SessionId放在Cookie里传给客户端就可以。
Cookie里只会存储SessionId,真正的内容是存在服务端的,找的时候就拿这个sessionId去查,所以相对更安全。

日志

自带日志

Gin日志默认只在控制台显示,如果要写入文件需要在main方法中声明

gin.DisableConsoleColor()   //关掉控制台颜色,可省略
f, _ := os.Create("gin.log")    //日志文件
//gin.DefaultWriter = io.MultiWriter(f) //将日志写入文件
gin.DefaultWriter = io.MultiWriter(f, os.Stdout) //将日志写入文件同时在控制台输出

Logrus 日志库

Gin自带的日志系统只支持简单的功能,需要更强大的功能还需要用到第三方日志库,这里选择github上面star最多的Logrus

下载

go get github.com/sirupsen/logrus

使用

package main

import (
    log "github.com/Sirupsen/logrus"
)

func main() {
    log.Trace("Something very low level.")
    log.Debug("Useful debugging information.")
    log.Info("Something noteworthy happened!")
    log.Warn("You should probably take a look at this.")
    log.Error("Something failed but I'm not quitting.")
    // Calls os.Exit(1) after logging
    log.Fatal("Bye.")
    // Calls panic() after logging
    log.Panic("I'm bailing.")
}

数据库

Gin框架没有自带的数据库封装,导入的数据库驱动由开发使用的数据库类型决定,例如开发使用mysql就直接import _ "github.com/go-sql-driver/mysql";但是访问数据库都是直接用写 sql,取出结果然后自己拼成对象,使用上面不是很方便,可读性也不好。这里使用目前githubstar数量最多的https://github.com/jinzhu/gorm

gorm的详细教程参考http://gorm.book.jasperxu.com/models.html#md

下载

go get -u github.com/jinzhu/gorm

连接mysql

package gin

import (
    "github.com/jinzhu/gorm"
)

func Index() {
    db, err := gorm.Open("mysql", "root:root@(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    defer db.Close()
}

数据表

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "time"
)

//定义数据表模型
type User struct {
    Id      uint      `gorm:"primary_key;AUTO_INCREMENT"` // 自增主键
    Name    string    `gorm:"size:255"`                   // string默认长度为255, 使用这种tag重设。
    Address string    `gorm:"not null;unique"`            // 设置字段为非空并唯一
    Addtime time.Time `gorm:"default:'2019-03-11 13:19:40'"` //默认值
}

func Index() {
    //连接数据库
    db, err := gorm.Open("mysql", "root:root@(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    //关闭数据库
    defer db.Close()

    //设置默认表名前缀
    gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
        return "test_" + defaultTableName
    }

    //创建表
    if !db.HasTable(&User{}) { //检查表是否存在
        if err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&User{}).Error; err != nil {
            panic(err)
        }
    }

    // 删除表users
    db.DropTable("test_users")

    // 修改模型`User`的"addtime"列类型
    db.Model(&User{}).ModifyColumn("addtime", "text")

    // 删除模型`User`的addtime列
    db.Model(&User{}).DropColumn("addtime")

    // 为`name`列添加索引`idx_user_name`
    db.Model(&User{}).AddIndex("idx_user_name", "name")

    // 删除索引
    db.Model(&User{}).RemoveIndex("idx_user_name")
}

添加记录

//添加记录
user := User{Name: "jian", Address: "1234", Addtime: time.Now()}
if err := db.Create(&user).Error; err != nil {
panic(err)
}

查询数据

无条件查询
// 获取第一条记录,按主键排序
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取最后一条记录,按主键排序
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 获取所有记录
db.Find(&users)
// SELECT * FROM users;

// 使用主键获取记录
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

Where查询条件
user := User{}

// 获取第一个匹配记录
db.Where("name = ?", "jinzhu").First(&user)
fmt.Println(user.Name)  //读取数据
// SELECT * FROM users WHERE name = 'jinzhu' limit 1;

// 获取最后一条记录,按主键排序
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 获取所有匹配记录
db.Where("name = ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu';

db.Where("name <> ?", "jinzhu").Find(&users)

// IN
db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)

// AND
db.Where("name = AND age >= ?", "jinzhu", "22").Find(&users)

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)

db.Where("created_at BETWEEN AND ?", lastWeek, today).Find(&users)

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主键的Slice
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

Not条件查询
db.Not("name", "jinzhu").First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;

// Not In
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3);

db.Not([]int64{}).First(&user)
// SELECT * FROM users;

// Plain SQL
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT(name = "jinzhu");

// Struct
db.Not(User{Name: "jinzhu"}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu";

Or条件查询
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)

指定字段和表
db.Select("name, age").Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

Order条件查询
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

Limit条件查询
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

Offset条件查询

指定在开始返回记录之前要跳过的记录数

db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;

Count条件查询
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)

多条件查询
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)
db.Where("name <> ?","jinzhu").Where("age >= and role <> ?",20,"admin").Find(&users)

更新数据

 // 使用组合条件更新单个属性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

// 使用`map`更新多个属性,只会更新这些更改的字段
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

// 使用`struct`更新多个属性,只会更新这些更改的和非空白字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})

删除数据

db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// DELETE from emails where email LIKE "%jinhu%";

db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinhu%";

执行原生SQL语句

Scan()是将结果扫描到另一个结构中。

db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=WHERE id IN (?)", time.Now, []int64{11,22,33})

db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}

连接池

db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)

锁行

注意:加行锁的表必须是InnoDB并且要加索引,否则无效;语句必须在事务里面,必须提交或回滚

// 为Select语句添加扩展SQL选项
db.Set("gorm:query_option", "FOR UPDATE").First(&user, 10)
// SELECT * FROM users WHERE id = 10 FOR UPDATE;

锁表

db.Exec("LOCK TABLES real_table WRITE, insert_table WRITE;")  //锁定real_table和insert_table表
UNLOCK TABLES;  //解锁

日志

// 启用Logger,显示详细日志
db.LogMode(true)

// 禁用日志记录器,不显示任何日志
db.LogMode(false)

// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})

//自定义日志
db.SetLogger(gorm.Logger{revel.TRACE})
db.SetLogger(log.New(os.Stdout, "\r\n", 0))

事务

// 开始事务
tx := db.Begin()
// 注意,一旦你在一个事务中,使用tx作为数据库句柄,而不再是上面的db
// 在事务中做一些数据库操作(从这一点使用'tx',而不是'db')
tx.Create(...)

// ...

// 发生错误时回滚事务
tx.Rollback()

// 提交事务
tx.Commit()

参考文章:

go语言Gin框架教程


https://www.xamrdz.com/backend/36d1923413.html

相关文章: