Gorm、Kitex、Hertz

Kitex

  Kitex是字节跳动内部的Golang微服务RPC框架,具有高性能(使用自研的高性能网络库 Netpoll,性能相较 go net 具有显著优势)、强可扩展的特点(提供了较多的扩展接口以及默认扩展实现,使用者也可以根据需要自行定制扩展,具体见下面的框架扩展),多消息协议(RPC 消息协议默认支持 Thrift、Kitex Protobuf、gRPC),服务治理功能齐全(支持服务注册/发现、负载均衡、熔断、限流、重试、监控、链路跟踪、日志、诊断等服务治理模块,大部分均已提供默认扩展,使用者可选择集成。)Kitex的主要组成:Kitex 的架构主要包括四个部分:Kitex ToolKitex CoreKitex BytedSecond Party Pkg。Kitex Core 是一个携带了一套微服务治理功能的 RPC 框架,它是 Kitex 的核心部分。Kitex Byted 是一套结合了字节跳动内部基础设施的拓展集合。通过这一套拓展集合,Kitex 能够在内部支持业务的发展。Kitex Tool 是一个命令行工具,能够在命令行生成我们的代码以及服务的脚手架,可以提供非常便捷的开发体验。Second Party Pkg,例如 netpoll, netpoll-http2,是 Kitex 底层的网络库,这两个库也开源在 CloudWeGo 组织中。
  Kitex支持基于IDL的项目开发:在 RPC 框架中,我们知道,服务端与客户端通信的前提是远程通信,但这种通信又存在一种关联,那就是通过一套相关的协议(消息、通信、传输等)来规范,但客户端又不用关心底层的技术实现,只要定义好了这种通信方式即可。在 KiteX 中,也提供了一种生成代码的命令行工具:kitex,目前支持 thrift、protobuf 等 IDL,并且支持生成一个服务端项目的骨架。以thrift IDL为例子首先先创建一个thift IDL文件echo.thrift接着定义我们的服务:

namespace go api

struct Request {
  1: string message
}

struct Response {
  1: string message
}

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

完成IDL之后执行:

$ kitex -module example -service example echo.thrift

就会生成一个整体的项目框架,结构如下图所示:
在这里插入图片描述
  完成IDL之后我们可以在设置的对应服务函数中编写响应的业务逻辑。我们需要编写的服务端逻辑都在handler.go这个文件中,我们在IDL中完成了echo方法,对应这这里的Echo函数,我们在这里修改服务端逻辑:

func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
  return &api.Response{Message: req.Message}, nil
}

  接着对这个案例进行编译运行,Kitex工具已经生成了编译运行脚本:

$ sh build.sh
//运行后会生成一个output目录,里面有我们编译的结果
$ sh output/bootstrap.sh
//运行这条指令后Echo服务就开始运行了

  接着编写客户端程序来完成对服务端的调用,创建文件夹进入目录:

//这是为了创建客户端
import "example/kitex_gen/api/echo"
import "github.com/cloudwego/kitex/client"
...
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
  log.Fatal(err)
}

  上述代码中,echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址,接着编写用于发起调用的代码:

import "example/kitex_gen/api"
...
req := &api.Request{Message: "my request"}
resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
if err != nil {
  log.Fatal(err)
}
log.Println(resp)

  我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为。其第二个参数为本次调用的请求。其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时(通常不需要指定,此处仅作演示之用)。
之后对服务端发起调用:

$ go run main.go
//正常会得到如下结果
2021/05/20 16:51:35 Response({Message:my request})

参考文档:Kitex快速开始,Thrift interface description language

GORM

  GORM是go语言数据库框架,是一种ORM(持久层)框架,支持多种数据库的接入,例如 MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于 SQL 语言的掌握程度,使用提供的 API 进行底层数据库的访问。
特性是:
特性:
全功能 ORM
关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
Create,Save,Update,Delete,Find 中钩子方法
支持 Preload、Joins 的预加载
事务,嵌套事务,Save Point,Rollback To Saved Point
Context,预编译模式,DryRun 模式
批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
复合主键,索引,约束
Auto Migration
自定义 Logger
灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
每个特性都经过了测试的重重考验
开发者友好
简单实例:
GORM支持多种数据库接入,以mysql为例:

 go get -u gorm.io/gorm
 go get -u gorm.io/driver/mysql
  package main​
 import (
     "fmt"
     "github.com/jinzhu/gorm"
     _ "github.com/jinzhu/gorm/dialects/mysql"
 )type Product struct { 
    Code  string
    Price uint
 }func(p Product) TableName() string { 
    return"product"// 返回的字符串即表名
 }funcmain() {
    // 连接数据库(需要传递一个 dsn)
    db, err := gorm.Open(
        mysql.Open(dsn:"user:pass@(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
       &gorm.Config{})
    if err != nil {
       panic("failed to connect database")
    }// 创建数据 Create() 方法支持创建一条(传递对象)和多条数据(传递数组)
    db.Create(&Product{Code: "D42", Price: 100})// 查询数据 先声明一个结构体, 然后使用 First() 方法(注意是传递指针)
    var product Product
    db.First(&product, 1)                 // 根据整型主键查找
    db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录​
    // 更新数据 - 用 Update() 方法将 product 的 price 更新为 200
    db.Model(&product).Update("Price", 200)
    // 更新数据 - 用 Updates() 方法更新多个字段
    db.Model(&product).Updates(Product{Price: 200, Code: "F42"})                    // 仅更新非零值字段
    db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) // 传递 map 可以更新零值​
    // 删除数据
    db.Delete(&product, 1)
 }

  GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。遵循 GORM 已有的约定,可以减少配置和代码量。如果约定不符合需求,GORM 允许自定义配置它们。
GORM CRUD接口:GORM通过CRUD接口实现数据的增删查改
创建

//创建单条记录
// -----------------------------插入单条记录---------------------------------
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

// 创建记录并更新给出的字段
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
//批量创建记录
// -----------------------------批量插入多条记录-----------------------------
//要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

//使用 CreateInBatches 分批创建时,你可以指定每批的数量。
var users = []User{{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
// 数量为 100
db.CreateInBatches(users, 100)

// 获取第一条记录(主键升序)。只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

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

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
//查询全部记录
// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error
//主键索引查询
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

//更新指定记录
//-----------------------------保存所有字段-------------------------
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

//-----------------------------更新单个列-------------------------
// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
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;

//-----------------------------更新多列-------------------------
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
//-----------------------------更新选定字段-------------------------
// 使用 Map 进行 Select
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 使用 Struct 进行 Select(会 select 零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select 所有字段(查询包括零值字段的所有字段)
db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select 除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
//更新批量记录
//-----------------------------批量更新-------------------------
// 根据 struct 更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';

// 根据 map 更新
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

//-----------------------------禁止全局更新-------------------------
//如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误。对此,必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式
db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause

db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1

db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"

//-----------------------------更新记录数-------------------------
// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';

result.RowsAffected // 更新的记录数
result.Error        // 更新的错误

//删除指定记录
//-----------------------------删除一条记录-------------------------
//删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

//-----------------------------根据主键删除-------------------------
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

//批量删除记录
//-----------------------------批量删除-------------------------
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

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

//-----------------------------禁止全局删除-------------------------
//如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误。对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式
db.Delete(&User{}).Error // gorm.ErrMissingWhereClause

db.Where("1 = 1").Delete(&User{})
// DELETE FROM `users` WHERE 1=1

db.Exec("DELETE FROM users")
// DELETE FROM users

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users
//软删除
//-----------------------------软删除-------------------------
//By default, gorm.Model uses *time.Time as the value for the DeletedAt field, and it provides other data formats support with plugin gorm.io/plugin/soft_delete
import "gorm.io/plugin/soft_delete"
type User struct {
  ID        uint
  Name      string                `gorm:"uniqueIndex:udx_name"`
  DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
  // DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"`
  //IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// Query
SELECT * FROM users WHERE is_del = 0;

// Delete
UPDATE users SET is_del = 1 WHERE ID = 1;

// user 的 ID 是 `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

//-----------------------------查找被软删除的记录-------------------------
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

//-----------------------------永久删除-------------------------
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

参考资料:GORM文档GORM指令

Hertz

  Hertz是Golang 用于微服务中的 HTTP 框架 ,首先要了解为什么有了RPC框架还需要http框架,因为像Kitex这样的RPC框架主要是用于系统内部服务之间,他能提高系统内部运行的效率并且没有多余的信息,而HTTP框架主要用于前端和后端之间,用于向外暴露接口,更是用于通用的传输协议。Hertz主要采用了4层分层设计(应用层、路由层、协议层、传输层)保证各个层级功能内聚,同时通过层级之间的接口达到灵活扩展的目标。应用层主要包括与用户直接交互的易用的API,主要包括Server、Client和一些其他通用抽象。路由层有路由分组的能力,使用分组路由可以让代码便于管理和扩展,过修改路由分组可以非常轻松扩展出另外一套API接口。协议层负责不同协议的实现和扩展。传输层负责底层的网络库的抽象和实现Hertz支持底层网络库的扩展。
简单实例:

//基于IDL创建项目
// protos/hello/hello.proto
syntax = "proto3";
package hello;
option go_package = "/hello";
import "api.proto";
message HelloReq {
  string Name = 1[(api.query)="name"];
}
message HelloResp {
  string RespBody = 1;
}
message OtherReq {
  string Other = 1[(api.body)="other"];
} 
message OtherResp {
  string Resp = 1;
}
service HelloService {
  rpc Hello(HelloReq) returns(HelloResp) {
    option (api.get) = "/hello";
  }
  rpc Other(OtherReq) returns(OtherResp) {
    option (api.post) = "/other";
  }
}

再使用如下代码生成代码目录:

hz new -I protos -idl protos/hello/hello.proto -mod example

在这里插入图片描述
再编辑hello_service.go增加赋值

// Code generated by hertz generator.
package hello
import (
   "context"
   hello "example/biz/model/hello"
   "github.com/cloudwego/hertz/pkg/app"
)
// Hello .
// @router /hello [GET]
func Hello(ctx context.Context, c *app.RequestContext) {
   var err error
   var req hello.HelloReq
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }
   resp := new(hello.HelloResp)
   resp.RespBody = req.Name  // 修改
   c.JSON(200, resp)
}
// Other .
// @router /other [POST]
func Other(ctx context.Context, c *app.RequestContext) {
   var err error
   var req hello.OtherReq
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }
   resp := new(hello.OtherResp)
   resp.Resp = req.Other   // 修改
   c.JSON(200, resp)
}

接着输入如下命令可以得到最后的输出:

go build -o example && ./example
curl 127.0.0.1:8888/hello?name=idl
输出:{"RespBody":"idl"}
curl 127.0.0.1:8888/other -d "other=idl"
输出:{"Resp":"idl"}

参考资料:Hertz文档Hertz实例

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值