【GO 框架三件套| 青训营笔记】

105 阅读2分钟

这是我参与【第五届青训营】伴学笔记创作活动的第五天,此篇概述一下在青训营学习的go项目框架知识。

image.png

Web/RPC/ORM 三件套

  1. 三件套介绍:Gorm, Kitex, Hertz

  2. Gorm 的基本使用见上一篇文章 【GO 数据库 | 青训营笔记】 - 掘金 (juejin.cn)

    Gorm 通过驱动来连接数据库

    • GORM 事务:提供了 Begin、Commit、Rollback 方法用于使用事务

      db, err := gorm.Open(mysql.Open(""), &gorm.Config{})
      if err != nil {
          panic("failed to connnect datebase")
      }
      ​
      tx := db.Begin()  // 开始事务
      if err = tx.Create(&User{Name: "name"}).Error; err != nil {
          tx.Rollback()   // 错误回滚
          return 
      }
      // 提交事务
      tx.Commit()
      

      提供 Tansaction 方法用于自动提交事务,避免用户漏写Begin、 Commit、Rollback 导致信息泄露等

      if err = db.Transaction(func(tx *gorm.DB) error {
          if err = tx.Create(&User{Name: "name"}).Error; err != nil {
              return err
          }
      }) ; err!=nil {
          return
      }
      

      提供 Hook, 在创建,查询,更新,删除等操作之前之后自动调用的函数,如果任何 Hook 返回错误,GORM 将停止之后的事务并回滚事务。

      func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
          if u.Age < 0 {
              return error.New("can't save invalid data")
          }
          return
      }
      func (u *User) AfterCreate(tx *gorm.DB) (err error) {
          return tx.Create(&User{ID: u.ID, Name: u.Name, Password: u.Password})
      }
      
    • 性能优化

      db, err := gorm.Open(mysql.Open(""), &gorm.Config{
          SkipDefaultTransaction: true,  // 关闭默认事务
          PrepareStmt: true              // 缓存预编译语句
      })
      // 提高调用速度
      
  3. Kitex

    • 使用 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
      
    • 服务器默认监听 8888 端口

      type EchoImpl struct {}
      func(s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Reponse, err error) {
          return
      }
      
    • Kitex Client 发送请求

      // 创建 Client 
      c, err := echo.NewClient("example", client.WithHostPorts("0.0.0:8888"))
      if err != nil {
          log.Fatal(err)
      }
      // 发起请求
      req := &opi.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)
      
    • Kitex 服务注册与发现

      type HelloImpl struct {}
      func(h *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Reponse, err error) {
          resp = &api.Response {
              Message: req.Message
          }
      }
      // 服务端
      func main() {
          r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
          if err != nil {
              log.Fatal(err)
          }
          // 服务注册:服务名 "Hello"
          server := hello.NewServer(new(HelloImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
              ServiceName: "Hello"
          }))
          err = server.Run()
          if err != nil {
              log.Fatal(err)
          }
      }
      ​
      // 客户端
      func main() {
          r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
          if err != nil {
              log.Fatal(err)
          }"
          client := hello.MustNewClient("Hello", client.WithResolver(r))  // 初始化
          for {
              ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)  // 设置超时
              resp, err := client.Echo(ctx, &api.Request{Message: "Hello"})  // 发送请求
              cancel()
              if err != nil {
                  log.Fatal(err)
              }
              log.Println(resp)
              time.Sleep(time.Second)
          }
      }
      
  4. Hertz

    • Hertz 基本使用:

      // 服务监听8080端口,并注册一个GET方法的路由函数
      func main() {
          h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
          h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
              ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
          })
          h.Spin()
      }
      ​
      func RegistreRoute(h *server.Hertz) {   
          // 不同方法注册路由 GET, POST,PUT, 以post为例
          h.POST("/post", func(ctx context.Context, c *app.RequestContext){
              c.String(consts.StatusOK, "post")
          })
      }
      
    • Hertz 中间件

      func MyMiddleware() app.HandlerFunc {
          return func(ctx context.Context, c *app.RequestConstext) {
              fmt.Println("pre-handle")  // 前半段
              c.Next(ctx)                // 执行下一个中间件
              fmt.Println("post-handle") // 后半段
          }
      }
      ​
      func main(){
          // 服务端的中间件示例
          h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
          h.Use(MyMiddleware())
          h.GET("/middleware", func(ctx context.Context, c *app.RequestConstext){
              c.String(consts.StatusOK, "post")
          })
          h.Spin()
      }
      
    • Hertz Client : 提供了 HTTP Client 用于帮助用户发送 HTTP 请求

      c, err := client.NewClient()
      if err != nil {
          return 
      }
      // 发送 http get 请求
      status, body, _ := c.Get(context.Background(), nil, "http://....")
      fmt.Printf("status=%v body=%v\n", status, string(body))
      // 发送 http post 请求
      var postArgs protocol.Args
      postArgs.Set("args", "a")  // 发送post args
      status, body, _ = c.Post(context.Background, nil, "http://....")
      fmt.Printf("status=%v body=%v\n", status, string(body))