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

452 阅读7分钟

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

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

零、前言:

新年快乐呀!啊过年摆了两天,这次的内容有点多,也踩了一些坑,希望能记录一下以后遇到了还能解决,结合文档与ppt写一份笔记,但是还是以文档为主,这里只是挑了一点常用的记录了一下。kitex还有很多没弄清楚回头到实际开发上的时候在补充吧。Gorm 的笔记也有根据金柱大佬的课(内部课)的,大佬真的好强

一、本堂课重点内容:

  • Gorm
  • Kitex
  • Hertz

二、详细知识点介绍:

5.1 Gorm

GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

安装:go get -u gorm.io/gorm

以mysql为例:

  • 数据库连接
db, err := gorm.Open(mysql.New(mysql.Config{
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
  DefaultStringSize: 256, // string 类型字段的默认长度
  DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
  DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})
  • 结构体创建

    type User struct {
        gorm.Model  // 包含了ID和三个time
        Username string `gorm:"type:varchar(20);not null " json:"username" validate:"required,min=4,max=12" label:"用户名"`
        Password string `gorm:"type:varchar(500);not null" json:"password" validate:"required,min=6,max=120" label:"密码"`
        Role     int    `gorm:"type:int;DEFAULT:2" json:"role" validate:"required,gte=2" label:"角色码"`
    }
    
  • 自动迁移模式go
db.AutoMigrate(&User{}, &Product{}) // 创建User,Product表

当然也可以使用Migrator但是太麻烦了

  • 增删改查
    // 通过数据的指针来创建
    db.Create(&user) // user可以是切片来实现批量插入
    // 带额外条件的删除
    db.Where("name = ?", "jinzhu").Delete(&email) 
    // 条件更新
    db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
    // 获取第一条记录(主键升序)
    db.First(&user)
    db.Where("name = ?", "jinzhu").First(&user)
    // joins
    db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
    // 分页获取
    DB.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&users).Error
    
  • HOOK

    Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数

    可以使用在比如说密码加密时,在创建密码之前调用一个加密函数

    // 开始事务
    BeforeSave
    BeforeCreate
    // 关联前的 save
    // 插入记录至 db
    // 关联后的 save
    AfterCreate
    AfterSave
    // 提交或回滚事务
    
    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
      u.UUID = uuid.New()
    
      if !u.IsValid() {
        err = errors.New("can't save invalid data")
      }
      return
    }
    

    但是不这么命名好像是默认在创建之前(有懂的可以说一下)

5.1.5 what is IDL

idl 的文件有类似 proto,thrift 可以很明显的知道接口的入参,出参,函数名,可以用来进行RPC调用,也可以给kitex和hertz生成模板框架 (有一说一,居然还能这么搞,写gin都要自己生成文件)

5.2 Kitex

  • 安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest

安装 thriftgo:go install github.com/cloudwego/thriftgo@latest

使用IDL生成kitex框架

IDL:

namespace go echo

struct Request {
    1: string Msg
}

struct Response {
    1: string Msg
}

service EchoService {
    Response Echo(1: Request req); // pingpong method
    oneway void VisitOneway(1: Request req); // oneway method
}

生成代码:

# 若当前目录不在 $GOPATH/src 下,需要加上 -module 参数,一般为 go.mod 下的名字
kitex -module "your_module_name" -service a.b.c hello.thrift

这里 windows 系统应该是会发生(搜索不到文件?之类)的问题所以要用linux来生成,没有服务器或者懒得用vmware的可以用WSL,(106条消息) 史上最全的WSL安装教程_金士顿的博客-CSDN博客_wsl安装

出现WSL的问题可以看看这个 适用于 Linux 的 Windows 子系统文档 | Microsoft Learn

哎这个还是好麻烦希望以后能早早适配吧

结构:

.
└── kitex_gen
    └── echo
        ├── echo.go
        ├── echoservice
        │   ├── client.go
        │   ├── echoservice.go
        │   ├── invoker.go
        │   └── server.go
        └── k-echo.go

在server实现功能调用到client上

5.3 Hertz

  • 安装
    1. go install github.com/cloudwego/hertz/cmd/hz@latest
    2. GO111MODULE=on 的情况下 go install github.com/cloudwego/thriftgo@latest
    3. 可以使用 hz new 来生成测试代码
  • IDL 创建项目
// idl/hello.thrift
namespace go hello.example

struct HelloReq {
    1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
}

struct HelloResp {
    1: string RespBody;
}


service HelloService {
    HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}

service NewService {
    HelloResp NewMethod(1: HelloReq request) (api.get="/new");
}
hz new --idl idl/hello.thrift --module "your_module_name"

注意这里要用管理员权限来执行这条命令,如果使用Goland来运行的话,可以在toolbox(或者其方式也可以),将Goland改为以管理员身份运行。同时,thrift 的路径要写对,否则还是会给你生成,生成的是 hz new 的那个pingpong框架。

  • 项目结构及其含义
.
├── biz                                // business 层,存放业务逻辑相关流程
│   ├── handler                        // 存放 handler 文件
│   │   ├── hello                      // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│   │   │   └── example
│   │   │       ├── hello_service.go   // handler 文件,用户在该文件里实现 IDL service 定义的方法,update 时会查找 当前文件已有的 handler 在尾部追加新的 handler
│   │   │       └── new_service.go     // 同上,idl 中定义的每一个 service 对应一个文件
│   │   └── ping.go                    // 默认携带的 ping handler,用于生成代码快速调试,无其他特殊含义
│   ├── model                          // IDL 内容相关的生成代码
│   │   └── hello                      // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package
│   │     └── example
│   │         └── hello.go             // thriftgo 的产物,包含 hello.thrift 定义的内容的 go 代码,update 时会重新生成
│   └── router                         // idl 中定义的路由相关生成代码
│       ├── hello                      // hello/example 对应 thrift idl 中定义的namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│       │   └── example
│       │       ├── hello.go           // hz 为 hello.thrift 中定义的路由生成的路由注册代码;每次 update 相关 idl 会重新生成该文件
│       │       └── middleware.go      // 默认中间件函数,hz 为每一个生成的路由组都默认加了一个中间件;update 时会查找当前文件已有的 middleware 在尾部追加新的 middleware
│       └── register.go                // 调用注册每一个 idl 文件中的路由定义;当有新的 idl 加入,在更新的时候会自动插入其路由注册的调用;勿动
├── go.mod                             // go.mod 文件,如不在命令行指定,则默认使用相对于GOPATH的相对路径作为 module 名
├── idl                                // 用户定义的idl,位置可任意
│   └── hello.thrift
├── main.go                            // 程序入口
├── router.go                          // 用户自定义除 idl 外的路由方法
└── router_gen.go                      // hz 生成的路由注册代码,用于调用用户自定义的路由以及 hz 生成的路由
  • 基本使用
    • 路由

      用法有点像gin,支持Restful风格的api接口与路由组的设计,路由的优先级:静态路由 > 命名路由 > 通配路由

      Hertz 支持使用 :name 这样的命名参数设置路由

      使用 *path 这样的通配参数设置路由,并且通配参数会匹配所有内容。

      更多细节可以看文档路由 | CloudWeGo

  • Middleware
    • 基本

      // server 级别
      h := server.Default()
      h.Use(GlobalMiddleware())
      
      // 路由级别
      group := h.Group("/group")
      group.Use(GroupMiddleware())
      

      如果有多个中间件按一定顺序执行时可以使用 .Next(ctx)实现

    • 三个停止中间件的函数

      • Abort():终止后续调用
      • AbortWithMsg(msg string, statusCode int):终止后续调用,并设置 response中body,和状态码
      • AbortWithStatus(code int):终止后续调用,并设置状态码
    • 常用中间件

      CORS 跨源资源共享 | CloudWeGo

      JWT JWT认证 | CloudWeGo

      国际化 国际化 | CloudWeGo

  • 参数绑定与校验

    绑定与校验 | CloudWeGo

    hertz 使用开源库 go-tagexpr 进行参数的绑定及验证,下面分别介绍参数绑定和参数验证的用法。

  • 日志

    哦哦这个支持zap,logrus这些常用的日志包,就不用自己写了

    以zap为例:

    go get github.com/hertz-contrib/logger/zap

    代码示例:

    logger := hertzzap.NewLogger()
    hlog.SetLogger(logger)
    // ....
    hlog.Infof("hello %s", "hertz")
    
  • 服务注册与服务发现

    服务注册与发现扩展 | CloudWeGo

    • 服务发现

      通过 server.WithRegistry 指定自己的注册模块和自定义的注册信息。

    h := server.Default(
        server.WithHostPorts(addr),
        server.WithRegistry(r, &registry.Info{
            ServiceName: "hertz.test.demo",
            Addr:        utils.NewNetAddr("tcp", addr),
            Weight:      10,
            Tags:        nil,
        }))
  • 服务注册

    通过使用 Hertz 提供的 Discovery 中间件,指定自定义的服务发现扩展。

三、总结:

这次东西挺多的,课程只是挑重点交了一下后面还需要很多时间自己看文档,特别是kitex还不是很懂,等写项目了再补充一下

四、参考资料:

5. 设计模式之 databasesql与 GORM实践.pdf - 飞书云文档 (feishu.cn)

‍‬⁢‌‬‌​⁤⁣⁣‬‌​⁣‬⁤​‬‬​⁤⁤‍​‌⁡⁣​‌‬⁣⁡⁣⁡​‬​‬⁢‍⁡⁤Go 框架三件套详解.pptx - 飞书云文档 (feishu.cn)

文档 | CloudWeGo

GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.