[Go框架三件套详解 | 青训营笔记]

114 阅读4分钟

[Go框架三件套详解 | 青训营笔记]

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

一、本课重点内容

  • 课程介绍
    • 课程目标介绍
    • 三件套(Gorm、Kitex、Hertz)介绍
  • 三件套的使用
    • 基本用法与常用 API 详解
  • 实战案例介绍
    • 实战项目介绍
    • 实战项目功能介绍
    • 实战项目关键代码讲解

二、详细知识点介绍

2.1 课程介绍

2.1.1 课程目标介绍

  • 将前面几节课的知识运用到项目中
  • 掌握 Gorm/Kitex/Hertz 的基本用法
  • 通过学习实战案例,完成日常后端开发任务

2.1.2 三件套介绍

  • Gorm:ORM 框架
  • Kitex:Golang 微服务 RFC 框架
  • Hertz:HTTP 框架

2.2 Gorm 的基本使用

Gorm 的约定(默认)
  • 使用 ID 的字段作为主键
  • 使用结构体的蛇形负数作为表名
  • 字段名的蛇形作为列名
  • 使用 CreateAt UpdatedAt 字段作为创建、更新时间
Gorm 支持的数据库
  • MySQL SQLServer PostgreSQL SQLite
  • 通过驱动来连接数据库,可以复用/自行开发驱动
Gorm 创建数据
p := &Product{Code: "D42", ID: 1}
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)

使用 clause.OnConflict 处理数据冲突,使用 default 标签为字段定义默认值。

Gorm 查询数据
  • 使用 Find 查询多条数据,查询不到数据不会返回错误
  • 当使用结构体作为条件查询时,GORM 只会查询非零值字段
Gorm 更新数据

使用 struct 更新时,只会更新非零值。如果需要更新零值可以使用 Map 或者 select 选择字段。

Gorm 删除数据
  • 物理删除 db.delete(&User{}, 10)
  • 软删除 gorm.DeleteAt,使用 Unscoped 可以查询被软删除的数据
Gorm 事务
  • Begin Rollback Commit 用于使用事务
  • Tansaction 用于自动提交事务
Gorm Hook
  • Gorm 提供了 CURD 的 Hook 能力

2.3 Kitex

安装 Kitex 代码生成工具
$ go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
$ go install github.com/cloudwego/thriftgo@latest
定义 IDL

使用 IDL 定义服务与接口

namespace go api
struct Request {
    1: string message
}
struct Response {
    1: string message
}
service Echo {
    Response echo(1: Request req)
}
Kitex 生成代码

kitex -module example -service example echo.thrift

  • build.sh 构建脚本
  • kitex_gen IDL 内容相关的生成代码
  • main.go 程序入口
  • handler.go 用户在该文件里实现 IDL service 定义的方法
创建 Client
c, err := echo.NewClient("example", client.WithHostPort("0.0.0.0:8888"))
if err != nil{
    log.Fatal(err)
}
发起请求
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)

2.4 Hertz

Hertz 的基本使用

使用 Hertz 实现,服务监听 8080端口并注册了一个 GET 方法的路由函数

func main() {
    h := server.Default(server.WithHostPosts("0.0.0.0:8080"))
    h.GET("/ping",
          func(c context.Context, ctx *app.RequestContext) {
              ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
          })
    h.Spin()
}
Hertz 路由

Hertz 提供了 GET、POST、PUT、DELETE、ANY 等方法用于注册路由

Hertz 路由
  • Hertz 提供了路由组(Group)的能力,用于支持路由分组的功能
  • 提供了参数路由和通配路由,路由的优先级为:静态路由 >> 命名路由 >> 通配路由
Hertz 参数绑定

Hertz 提供了 Bind、Validate、BindAndValidate 函数用于进行参数绑定和校验

Hertz 中间件

Hertz 中间件主要分为客户端中间件和服务端中间件。

Hertz Client

Hertz 提供了 Hertz Client 用于帮助用户发送 HTTP 请求

Hertz 代码生成工具

Hertz 提供了代码生成工具 Hz,通过定义 IDL 文件即可生成对应的基础服务代码

三、实践练习例子

3.1 项目介绍

笔记项目是由 Hertz、Kitex、Gorm 搭建出来的具有一定业务逻辑的后端 API 项目。

3.2 Hertz 关键代码

func CreateNote(ctx context.Context, c *app.RequestContext) {
	var noteVar NoteParam
	if err := c.Bind(&noteVar); err != nil {
		SendResponse(c, errno.ConvertErr(err), nil)
		return
	}

	if len(noteVar.Title) == 0 || len(noteVar.Content) == 0 {
		SendResponse(c, errno.ParamErr, nil)
		return
	}

	claims := jwt.ExtractClaims(ctx, c)
	userID := int64(claims[constants.IdentityKey].(float64))
	err := rpc.CreateNote(context.Background(), &notedemo.CreateNoteRequest{
		UserId:  userID,
		Content: noteVar.Content, Title: noteVar.Title,
	})
	if err != nil {
		SendResponse(c, errno.ConvertErr(err), nil)
		return
	}
	SendResponse(c, errno.Success, nil)
}

3.3 Kitex Client 关键代码

func initNoteRpc() {
	r, err := etcd.NewEtcdResolver([]string{constants.EtcdAddress})
	if err != nil {
		panic(err)
	}

	c, err := noteservice.NewClient(
		constants.NoteServiceName,
		client.WithMiddleware(middleware.CommonMiddleware),
		client.WithInstanceMW(middleware.ClientMiddleware),
		client.WithMuxConnection(1),                       // mux
		client.WithRPCTimeout(3*time.Second),              // rpc timeout
		client.WithConnectTimeout(50*time.Millisecond),    // conn timeout
		client.WithFailureRetry(retry.NewFailurePolicy()), // retry
		client.WithSuite(trace.NewDefaultClientSuite()),   // tracer
		client.WithResolver(r),                            // resolver
	)
	if err != nil {
		panic(err)
	}
	noteClient = c
}

// CreateNote create note info
func CreateNote(ctx context.Context, req *notedemo.CreateNoteRequest) error {
	resp, err := noteClient.CreateNote(ctx, req)
	if err != nil {
		return err
	}
	if resp.BaseResp.StatusCode != 0 {
		return errno.NewErrNo(resp.BaseResp.StatusCode, resp.BaseResp.StatusMessage)
	}
	return nil
}

3.4 Kitex Server 关键代码

type CreateNoteService struct {
	ctx context.Context
}

// NewCreateNoteService new CreateNoteService
func NewCreateNoteService(ctx context.Context) *CreateNoteService {
	return &CreateNoteService{ctx: ctx}
}

// CreateNote create note info
func (s *CreateNoteService) CreateNote(req *notedemo.CreateNoteRequest) error {
	noteModel := &db.Note{
		UserID:  req.UserId,
		Title:   req.Title,
		Content: req.Content,
	}
	return db.CreateNote(s.ctx, []*db.Note{noteModel})
}

3.5 Gorm 关键代码

type Note struct {
	gorm.Model
	UserID  int64  `json:"user_id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

func (n *Note) TableName() string {
	return constants.NoteTableName
}

// CreateNote create note info
func CreateNote(ctx context.Context, notes []*Note) error {
	if err := DB.WithContext(ctx).Create(notes).Error; err != nil {
		return err
	}
	return nil
}

// MGetNotes multiple get list of note info
func MGetNotes(ctx context.Context, noteIDs []int64) ([]*Note, error) {
	var res []*Note
	if len(noteIDs) == 0 {
		return res, nil
	}

	if err := DB.WithContext(ctx).Where("id in ?", noteIDs).Find(&res).Error; err != nil {
		return res, err
	}
	return res, nil
}

// UpdateNote update note info
func UpdateNote(ctx context.Context, noteID, userID int64, title, content *string) error {
	params := map[string]interface{}{}
	if title != nil {
		params["title"] = *title
	}
	if content != nil {
		params["content"] = *content
	}
	return DB.WithContext(ctx).Model(&Note{}).Where("id = ? and user_id = ?", noteID, userID).
		Updates(params).Error
}

// DeleteNote delete note info
func DeleteNote(ctx context.Context, noteID, userID int64) error {
	return DB.WithContext(ctx).Where("id = ? and user_id = ? ", noteID, userID).Delete(&Note{}).Error
}

四、课后个人总结

本课重点学习了 Go 语言三件套的基本用法。