简介

在之前的文章中咱们运用 Hertz 编写了一个简略的 demo 协助快速上手 Hertz 这款 Golang HTTP 结构,本节咱们将加入 Gorm 结构和 Hertz 结构一起学习一个简略的官方 demo,同时带你了解 Hertz 一些有意思的特性。

假如你还不知道 Hertz 是什么,那么能够查看我之前的文章快速上手。

咱们将着重学习以下特性:

  • 运用 thrift IDL 界说 HTTP 接口
  • 运用 hz 生成脚手架代码
  • 运用 Hertz 的参数绑定校验
  • 运用 GormMySQL 编写耐久层

获取 demo

履行以下指令获取这个 demo:

git clone https://github.com/cloudwego/hertz-examples.git
cd bizdemo/hertz_gorm

项目结构

hertz_gorm
├── biz
|   ├── dal             // Logic code that interacts with the database
│   ├── handler         // Main logical code that handles HTTP requests                  
│   ├── hertz_gen       // Scaffolding generated by hertz from idl files
|   ├── model           // Go struct corresponding to the database table
|   ├── pack            // Transformation between database model and response model
|   ├── router          // Middleware and mapping of routes to handlers
├── go.mod              // go.mod                 
├── idl                 // thift idl                  
├── main.go             // Initialize and start the server              
├── router.go           // Sample route registration
├── router_gen.go       // Route registration    
├── docker-compose.yml  // docker-compose.yml
├── Makefile            // Makefile

这是整个项目的根本结构,十分的简洁,hz 为咱们生成了很多的脚手架代码。

界说 IDL

hz是由Hertz结构供给的用于生成代码的东西。现在,hz能够依据 thrift 和 protobuf IDL为 Hertz 项目生成脚手架。

一个优秀的 IDL 文件的界说在运用 Hertz 开发中起着重要的作用,咱们将运用thrift IDL作为这个项目的示例。

咱们能够运用 api 注解让 hz 协助咱们进行参数绑定和验证,路由注册代码生成等。

hz 将依据以下api 注解生成 go tag,以便 Hertz 运用反射检索这些值并解析它们。

字段注解

Hertz 运用 go-tagexpr 开源库用于参数绑定和字段注释的校验,如下面的CreateUserRequest示例所示:

// api.thrift
struct CreateUserRequest{
    1: string name      (api.body="name", api.form="name",api.vd="(len($) > 0 && len($) < 100)")
    2: Gender gender    (api.body="gender", api.form="gender",api.vd="($ == 1||$ == 2)")
    3: i64    age       (api.body="age", api.form="age",api.vd="$>0")
    4: string introduce (api.body="introduce", api.form="introduce",api.vd="(len($) > 0 && len($) < 1000)")
}

form 注解答应 hz 自动为咱们以 HTTP 恳求体的方式绑定参数,省去了咱们运用 PostForm 等办法手动绑定参数的费事。

vd 注解答应参数验证。例如,CreateUserRequest 运用 vd 注解来保证 gender 字段只要 1 或 2。

你能够参阅 这儿 了解更多关于参数验证语法的信息。

办法注解

办法注解可用于生成路由注册的代码。

// api.thrift
service UserService {
   UpdateUserResponse UpdateUser(1:UpdateUserRequest req)(api.post="/v1/user/update/:user_id")
   DeleteUserResponse DeleteUser(1:DeleteUserRequest req)(api.post="/v1/user/delete/:user_id")
   QueryUserResponse  QueryUser(1: QueryUserRequest req)(api.post="/v1/user/query/")
   CreateUserResponse CreateUser(1:CreateUserRequest req)(api.post="/v1/user/create/")
}

咱们运用 POST 注解界说了 POST 办法和路由,hz 将依据界说的路由生成对应的路由组,Handler 结构以及中间件结构等。如biz/router/user_gorm/api.go 所示,以及 biz/handler/user_gorm/user_service.go

咱们也能够在idl文件中界说业务过错代码:

// api.thrift
enum Code {
     Success         = 1
     ParamInvalid    = 2
     DBErr           = 3
}

hz 将依据这些为咱们生成常量和相关办法:

// biz/hertz_gen/user_gorm/api.go
type Code int64
​
const (
    Code_Success      Code = 1
    Code_ParamInvalid Code = 2
    Code_DBErr        Code = 3
)

运用 hz 生成代码

在咱们完结 IDL 的编写后,咱们能够运用 hz 为咱们生成脚手架代码。

履行下面的指令来生成代码:

hz new --model_dir biz/hertz_gen -mod github.com/cloudwego/hertz-examples/bizdemo/hertz_gorm -idl idl/api.thrift

假如在第一次生成之后编辑了IDL,履行下面的指令来更新代码:

hz update --model_dir biz/hertz_gen -idl idl/api.thrift

当然,项目现已为您生成了代码,因而您不需要履行它。当你实际运用 Hertz 进行 web 开发时,我相信你会发现它是一个十分有用和有趣的东西。

中间件的运用

在这个项目中,咱们装备根路由组为一切路由运用 gzip 中间件以进步功能。

// biz/router/user_gorm/middleware.go
func rootMw() []app.HandlerFunc {
    // your code...
    // use gzip middleware
    return []app.HandlerFunc{gzip.Gzip(gzip.DefaultCompression)}
}

只需在生成的脚手架代码中添加一行代码,十分简略。你能够参阅 hertz-contrib/gzip 以获取更多自界说装备的信息。

运用 Gorm 操作数据库

装备 Gorm

要在数据库中运用 GORM,你首先需要运用驱动程序连接数据库并装备 GORM,如 biz/dal/mysql/init.go 所示。

// biz/dal/mysql/user.go
package mysql
​
import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
)
​
var dsn = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local"var DB *gorm.DB
​
func Init() {
   var err error
   DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
      SkipDefaultTransaction: true,
      PrepareStmt:            true,
      Logger:                 logger.Default.LogMode(logger.Info),
   })
   if err != nil {
      panic(err)
   }
}

这儿咱们经过 DSN 连接 MySQL 数据库,并维护一个大局数据库操作目标 DB

在GORM装备方面,因为本项目不涉及同时操作多张表,咱们能够将 SkipDefaultTransaction 装备为 true 来越过默认事务,并经过PrepareStmt 启用缓存以进步效率。

咱们还运用了默认的 logger,以便咱们能够清楚地看到 GORM 为咱们生成的 SQL

操作 MySQL

GORM 经过拼接 SQL 句子的方式来履行CRUD操作,因而代码十分简洁且易于阅读,一切的数据库操作都在 biz/dal/mysql/user.go中。

咱们还声明了一个对应于数据库表的模型,gorm.Modelgorm.Model包括一些公共字段,GORM 能够自动为咱们填充这些字段,并支持软删除等操作。

// biz/model/user.go
type User struct {
   gorm.Model
   Name      string `json:"name" column:"name"`
   Gender    int64  `json:"gender" column:"gender"`
   Age       int64  `json:"age" column:"age"`
   Introduce string `json:"introduce" column:"introduce"`
}

处理 HTTP 恳求

在本节中,咱们将探索处理程序(biz/handler/user_gorm/user_service.go),这是首要的业务逻辑代码。

CreateUser & DeleteUser & UpdateUser

CreateUser

因为咱们在 thift IDL中运用了api 注解,BindAndValidate 将为咱们完结参数绑定和验证。一切有用的参数都会被注入到CreateUserRequest中,这十分方便。

假如呈现过错,咱们能够运用 JSON 办法以 JSON 格式回来数据。无论是 CreateUserResponse 仍是业务代码,咱们都能够直接运用hz生成的代码。

之后,咱们能够经过在 dal层调用CreateUser来插入一个新用户到 MySQL 中,传入封装好的参数。

假如呈现过错,就像一开始那样,回来包括过错代码和信息的JSON。否则,回来正确的服务代码,表示成功创建了用户。

// biz/handler/user_gorm/user_service.go
// CreateUser .
// @router /v1/user/create/ [POST]
func CreateUser(ctx context.Context, c *app.RequestContext) {
   var err error
   var req user_gorm.CreateUserRequest
   err = c.BindAndValidate(&req)
   if err != nil {
      c.JSON(200, &user_gorm.CreateUserResponse{Code: user_gorm.Code_ParamInvalid, Msg: err.Error()})
      return
   }
   if err = mysql.CreateUser([]*model.User{
      {
         Name:      req.Name,
         Gender:    int64(req.Gender),
         Age:       req.Age,
         Introduce: req.Introduce,
      },
   }); err != nil {
      c.JSON(200, &user_gorm.CreateUserResponse{Code: user_gorm.Code_DBErr, Msg: err.Error()})
      return
   }
​
   resp := new(user_gorm.CreateUserResponse)
   resp.Code = user_gorm.Code_Success
   c.JSON(200, resp)
}

DeleteUser

DeleteUserCreateUser 的逻辑几乎是相同的:绑定并验证参数,运用mysql.DeleteUser 删除用户,假如有过错则回来,否则回来成功。

// biz/handler/user_gorm/user_service.go
// DeleteUser .
// @router /v1/user/delete/:user_id [POST]
func DeleteUser(ctx context.Context, c *app.RequestContext) {
   var err error
   var req user_gorm.DeleteUserRequest
   err = c.BindAndValidate(&req)
   if err != nil {
      c.JSON(200, &user_gorm.DeleteUserResponse{Code: user_gorm.Code_ParamInvalid, Msg: err.Error()})
      return
   }
   if err = mysql.DeleteUser(req.UserID); err != nil {
      c.JSON(200, &user_gorm.DeleteUserResponse{Code: user_gorm.Code_DBErr, Msg: err.Error()})
      return
   }
​
   c.JSON(200, &user_gorm.DeleteUserResponse{Code: user_gorm.Code_Success})
}

UpdateUser

UpdateUser 与此根本相同,仅仅进行了从接收 HTTP 恳求参数的目标到对应于数据库表的数据拜访目标的模型转化。

// biz/handler/user_gorm/user_service.go
// UpdateUser .
// @router /v1/user/update/:user_id [POST]
func UpdateUser(ctx context.Context, c *app.RequestContext) {
    var err error
    var req user_gorm.UpdateUserRequest
    err = c.BindAndValidate(&req)
    if err != nil {
            c.JSON(200, &user_gorm.UpdateUserResponse{Code: user_gorm.Code_ParamInvalid, Msg: err.Error()})
            return
    }
​
    u := &model.User{}
    u.ID = uint(req.UserID)
    u.Name = req.Name
    u.Gender = int64(req.Gender)
    u.Age = req.Age
    u.Introduce = req.Introduce
​
    if err = mysql.UpdateUser(u); err != nil {
            c.JSON(200, &user_gorm.UpdateUserResponse{Code: user_gorm.Code_DBErr, Msg: err.Error()})
            return
    }
​
    c.JSON(200, &user_gorm.UpdateUserResponse{Code: user_gorm.Code_Success})
}

QueryUser

QueryUser中值得注意的是,咱们进行了分页和从 model.Useruser_gorm.User 的转化。这与咱们刚刚在 UpdateUser 中提到的操作相反。

运用简略的分页公式 startIndex = (currentPage - 1) * pageSize,咱们能够在查询数据时对其进行分页

这次咱们将转化模型包装在 biz/pack/user.go 中。

// biz/pack/user.go
// Users Convert model.User list to user_gorm.User list
func Users(models []*model.User) []*user_gorm.User {
    users := make([]*user_gorm.User, 0, len(models))
    for _, m := range models {
            if u := User(m); u != nil {
                    users = append(users, u)
            }
    }
    return users
}
​
// User Convert model.User to user_gorm.User
func User(model *model.User) *user_gorm.User {
    if model == nil {
            return nil
    }
    return &user_gorm.User{
            UserID:    int64(model.ID),
            Name:      model.Name,
            Gender:    user_gorm.Gender(model.Gender),
            Age:       model.Age,
            Introduce: model.Introduce,
    }
}
// biz/handler/user_gorm/user_service.go
// QueryUser .
// @router /v1/user/query/ [POST]
func QueryUser(ctx context.Context, c *app.RequestContext) {
    var err error
    var req user_gorm.QueryUserRequest
    err = c.BindAndValidate(&req)
    if err != nil {
            c.JSON(200, &user_gorm.QueryUserResponse{Code: user_gorm.Code_ParamInvalid, Msg: err.Error()})
            return
    }
​
    users, total, err := mysql.QueryUser(req.Keyword, req.Page, req.PageSize)
    if err != nil {
            c.JSON(200, &user_gorm.QueryUserResponse{Code: user_gorm.Code_DBErr, Msg: err.Error()})
            return
    }
    c.JSON(200, &user_gorm.QueryUserResponse{Code: user_gorm.Code_Success, Users: pack.Users(users), Totoal: total})
}

其余的业务逻辑与之前相同,至此咱们完结了一切处理程序函数。

运行 demo

  • 运行 docker
cd bizdemo/hertz_gorm && docker-compose up
  • 生成 MySQL 表

连接 MySQL 并履行 user.sql

  • 运行 demo
cd bizdemo/hertz_gorm
go build -o hertz_gorm && ./hertz_gorm

总结

这就是本文的全部内容。希望它能让你快速了解怎么运用 HertzGORM 进行开发。它们都有很好的官方文档。假如哪块有问题或许疑问欢迎在谈论区提出或许私信我,以上。

参阅列表

  • www.cloudwego.io/docs/hertz/
  • gorm.io/
  • github.com/bytedance/g…
  • github.com/hertz-contr…