Go第五天上课

58 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天.

中途春节放假,后期继续跟上最后一节假期前的课程。老师主要讲了三种框架,并将三种框架结合完成一个独立的微服务Demo,主要讲解了Gorm框架,kitex框架,hertz框架。

操作数据库框架Gorm

gorm框架主要是操作底层数据库,对数据库进行基本的增删改查操作,在文件import时需要导入相应的库。本文主要连接mysql数据库,所以同时需要引入mysql的驱动库。

"gorm.io/driver/mysql"
"gorm.io/gorm"

在函数入口,需要对数据库进行连接,本文利用docker将mysql进行pull再对容器进行端口映射使本地数据库可对容器内数据库进行操作,并将容器卷映射到本地防止数据丢失。 image.png Gorm连接数据库时需要根据指定的dsn进行连接,dsn格式为数据库账号@密码tcp(连接地址:端口)/数据库名称?charset=utf8mb4&parseTime=True&loc=Local。连接后利用Open函数进行连接,并可在&gorm.Config{}中进行自定义配置。

dsn := "root:123456@tcp(127.0.0.1:3360)/users?charset=utf8mb4&parseTime=True&loc=Local"
//根据dsn格式找到对应的数据库并打开,&gorm.Config{}可加对应的自定义配置
db,err := gorm.Open(mysql.Open(dsn),&gorm.Config{})
if err!=nil{
   println("connt faild,%v",err)
}

连接完成数据库后需要对数据库进行增删改查操作。所以要确定数据库被操作的的表,以及表内各字段。在Gorm中数据库的字段名需要定义结构体与其一一对应。如下图,在数据库中建立好User表后,各字段分别为ID,Name,Age。所以需要对应结构体保存该字段。

image.png

//结构体
type User struct {//go创建与mysql中对应数据库的结构体
   ID           uint
   Name         string
   Age          uint8
}

//默认返回自定义表名,否则按照蛇形表名返回,可能导致无法找到该表
func (u User)TableName() string {
   return "User"
} 

当结构体字段一一对应后,可以利用结构体存储的信息对数据库进行赋值。类似于用结构体保存好一个完整的对象信息,再将该对象写入数据库。 所以传入时需要确定需要写入的表名,利用方法(u User)TableName()将对应表名进行返回,就可知User用户对应的表名为User表。随后对数据库进行操作。

创建操作

//创建记录操作:
//user := User{Name:"YYYY",Age:134}
//选择对应字段创建信息
//db.Select("name","age").Create(&user)
//忽略对应字段,忽略的字段为默认
//db.Omit("name","age").Create(&user)

利用db.Create操作将需要传入的结构体数据放入函数内即可进行插入操作,该方法不适用于0值

//利用slice记录方式可以默认处理0值,使用select无法处理
//var users = []User{{Name: "cn1",Age: 25},{Name: "cn2"},{Name: "cn3"}}
//db.Create(&users)
//分批创建时可以指定分批数量
//db.CreateInBatches(users,2)
/*for _,us := range(users){
   fmt.Println(us.ID)
}*/ 

在插入操作中同时可以使用切片方式进行批量插入,利用该方式可以处理0值。并且可以利用CreateInBatches函数指定一次插入的数量。利用Select可以选择需要插入的字段,利用Omit忽略该字段进行插入。

查询操作

//将user数据存储到切片缓存中
users := make([]*User,0)
// SELECT * FROM users WHERE id = 1;
result := db.First(&users,1)
// SELECT * FROM users WHERE id in (1,4,8);
//result := db.Find(&users,[]int{1,4,8,11})
// SELECT * FROM users WHERE age > 0;
//result := db.Where("Age> ?","0").Find(&users)
//result := db.Where("name LIKE ?","%sn%").Find(&users)
//us := make([]*User,0)
//利用scan代替find功能
//result := db.Table("User").Select("name","age").Where("name=?","sn1").Scan(&us)
//fmt.Println(result.RowsAffected)
//fmt.Println(result.Error)

因为查询的数据可能不止一条,若查询多条数据需要将数据缓存在切片中,每个切片数据都为单独的对象数据,利用First函数可以查询指定id的信息。Find函数可以查找多条信息,类似于mysql中的in语法。where函数为过滤函数。利用result.RowsAffected可以查询到数据的影响条数。

更新操作

//user := User{}
//db.First(&user,2)
//user.Name = "nihaoma"
//user.Age = 23
//db.Save(&user)
//指定列更新
//db.Model(&User{}).Where("Name LIKE ?", "%sn%").Update("Age", 19)
//多行列更新
//db.Model(&User{}).Where("Name LIKE ?", "%sn%").Updates(map[string]interface{}{"Name": "sh1", "Age": 18})
//选定列值更新
//db.Model(&User{}).Select("Name").Where("Name LIKE ?", "%sh%").Updates(map[string]interface{}{"Name": "hello", "Age": 18})
// UPDATE users SET name='hello' WHERE id=111;

//db.Model(&User{}).Omit("Name").Where("Name LIKE ?", "%sn%").Updates(map[string]interface{}{"Name": "hello", "Age": 180})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; 

更新操作可以利用save函数,更多的是Updates函数,Update用于指定列更新,Updates用于多列更新。

删除操作

// 带额外条件的删除
   //db.Where("Name = ?", "cn1").Delete(&User{})
   // DELETE from emails where id = 10 AND name = "jinzhu";
   //根据主键ID进行删除
// db.Delete(&User{}, 18)
   // DELETE FROM users WHERE id = 10;
// db.Where("ID = 18").Find(&User{})

删除操作主要利用Delete函数对对象整体进行删除。参考Gorm链接gorm.io/zh_CN/docs/…

以上为整个数据库的操作,基本的增删改查。但是在现实工程中会遇到很多问题,不能用简单的这些操作,如删除,工程中大多使用的都是软删,没有真正意义上的将数据进行删除,而是对其进行了标记。再如当数据进行操作时不小心出了错误,此时数据的安全性将会存在问题,所以需要引入事务对数据进行回滚操作,当数据在操作中出现错误进行回滚,直到所有数据执行成功后才开始提交事务,为防止用户在写事务时忘记写回滚函数或是提交函数,直接使用Transaction函数,自带事务处理,发生错误时不会立即提交到数据库。

if err = db.Transaction(func(tx *gorm.DB) error {
   if err = tx.Select("Name","Age").Create(&User{Name: "Name345"}).Error;err!=nil{
      return err
   }
   if err = tx.Create(&User{Name: "Name1"}).Error;err!=nil{
      tx.Rollback()
      return err
   }
   if err = tx.Where("Name = ?", "cn1").Delete(&User{});err!=nil{
      tx.Rollback()
      return err
   }
   return nil
});err!=nil{
   return
}

kitex框架

随后老师讲kitex框架,kitex框架主要是RPC框架,在微服务应用较广。kitex更像是一个扩展,使得服务端与客户端很好的交互,客户端能很方便的使用服务端添加的接口。www.cloudwego.io/zh/docs/kit… kitex使用方法:首先编辑一个IDL文件,kitex可根据IDL文件进行自动生成所需服务端和客户端文件。

//IDL文件
namespace go api

struct Request {
  1: string message
}

struct Response {
  1: string message
}

service Echo {
    Response echo(1: Request req)
} 

该文件创建了一个请求与响应,并且实现了一个打印响应信息的方法,将该文件命令为echo.thrift 利用如下命令可以自动生成所需服务文件。

kitex -module example -service example echo.thrift

image.png 生成文件后执行如下命令更新kitex所需配置

go get github.com/cloudwego/kitex@latest
go mod tidy

编辑handler.go文件,该文件可以查看之前在IDL文件中编写的Echo方法,编写一个返回响应信息即完成一个最简易的服务器返回消息。 image.png 保存文件后执行

sh build.sh

执行后会生成output目录,该目录有编译后的产物,继续执行以下命令即可开启服务。

sh output/bootstrap.sh

服务开启后即创建客户端信息,客户端编辑如下go代码,即可开始与服务器进行交互,当客户端发送请求消息时,可以收到服务器返回的信息。

image.png

image.png

所以一个简易的kitex使用完成,当用户需要新增需求时,服务端可以根据对应需求在IDL中进行编辑,以方便后续微服务的扩展。 最后老师讲了hertz框架,hertz框架是一款http框架,类似的框架有gin框架,老师用该三种框架编写了一个小型微服务用户信息交互,用户没有直接利用api接口调用与数据库进行直接连接,由RPC框架开启服务发现,将所新增的模块添加至etcd中,客户端调用api与该中间服务进行交互,kitex再与数据库进行交互。整体思路清楚,分别可以知道每个框架的作用。很好的展现了接下来项目所需要的结构层次。

对于实体代码需要多练多熟悉,知道大体框架以及内在联系与逻辑后才会对各种框架运用更加熟练。所以在接下来的时间还需要不断的使用并增加使用场景,来慢慢发现这些框架的优势以及找出可改进优化的地方。