学习笔记:利用官方示例Demo快速入门Hertz | 青训营

409 阅读7分钟

利用官方示例Demo快速入门Hertz

前备条件

  • 在使用Hertz之前,你需要设置Golang开发环境,并确保Golang版本 >= v1.15。

  • 使用以下命令安装hz:

go install github.com/cloudwego/hertz/cmd/hz@latest
  • 安装好定义IDL的编译器thriftgo

定义IDL

先准备好开发文件夹

mkdir hertz-try
cd hertz-try
mkdir idl && cd idl

定义user.thrift

// idl/user.thrift
namespace go user

struct BaseResp {
    1: i64 StatusCode;
    2: string StatusMsg;
    3: string data;
}

struct RegisterRequest {
    1: string Username;
    2: string Password;
}

struct RegisterResponse {
    1: BaseResp BaseResp;
}

struct LoginRequest {
    1: string Username;
    2: string Password;
}

struct LoginResponse {
    1: BaseResp BaseResp;
}

struct InfoRequest {
    1: string Username;
}

struct InfoResponse {
    1: BaseResp BaseResp;
}

service UserService {
    RegisterResponse Register(1: RegisterRequest req) (api.post="/user/register");
    LoginResponse Login(1: LoginRequest req) (api.post="/user/login");
    InfoResponse Info(1: InfoRequest req) (api.get="/user/:username");
}

该文件以api注解的形式定义了HTTP请求方法和路由。hz将根据这些注释为我们生成对应的Handler

使用hz脚手架生成代码

在项目根目录下执行:

hz new -module hertz/try -idl idl/user.thrift

hz 生成的代码结构都类似,下面以 hz 使用 (thrift) 小节生成的代码结构为例,说明 hz 生成的代码的含义。

.
├── 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 生成的路由

中间件的使用

由上述的代码结构可以知道在/biz/router/user/middleware.go中,hz 为每一个生成的路由组都默认加了一个中间件,我们可以根据需求对中间件函数进行修改。

首先可以在/biz/router/user/user.go中,hz 为 user.thrift 中定义了路由生成的路由注册代码:

// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
func Register(r *server.Hertz) {

	root := r.Group("/", rootMw()...)
	{
		_user := root.Group("/user", _userMw()...)
		_user.GET("/:username", append(_infoMw(), user.Info)...)
		_user.POST("/login", append(_loginMw(), user.Login)...)
		_user.POST("/register", append(_registerMw(), user.Register)...)
	}
}

我们可以针对路由/user/login来增加一个session中间件用来针对特定用户名的登录次数统计。

Session中间件官方博客资料


func _loginMw() []app.HandlerFunc {
	// your code...
	return []app.HandlerFunc{
		sessions.New("usersession", cookie.NewStore([]byte("secret"))),
	}
}

然后我们就可以业务逻辑代码中使用该中间件。

修改Handler文件夹下代码

  • 由代码框架知道,/biz/handler/user/user_service.go文件需要实现user.idl中定义的服务,我们需要在该文件添加我们的逻辑代码。

  • /biz/model/user/user.go存放IDL定义的数据结构,我们需要在上述逻辑代码中根据定义好的服务签名来完成返回对应的数据。

  • 本文章暂不涉及数据库的数据,因此需要手动构造/biz/dal/db.go文件来模拟实现对数据库的增删改查,实际上是对map[]的增删改查,无法实现数据的持久化。


根据以上思想完成代码的编写:

// /biz/dal/db.go
package dal

var passwd = make(map[string]string)
var frequency = make(map[string]int)

func CreateUser(username string, password string) {
	passwd[username] = password
	frequency[username] = 0
}

func CheckUsername(username string) bool {
	_, ok := passwd[username]
	return !ok
}

func CheckPassword(username string, password string) bool {
	if passwd[username] == password {
		return true
	}
	return false
}

func SetFrequency(username string, count int) {
	frequency[username] = count
}

func GetFrequency(username string) int {
	return frequency[username]
}
// /biz/handler/user/user_service.go
// Code generated by hertz generator.

package user

import (
	"context"
	"strconv"

	"hertz/try/biz/dal"
	user "hertz/try/biz/model/user"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/hertz-contrib/sessions"
)

// Register .
// @router /user/register [POST]
func Register(ctx context.Context, c *app.RequestContext) {
	var err error
	var req user.RegisterRequest
	err = c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}

	resp := new(user.RegisterResponse)

	username := c.PostForm("username")
	password := c.PostForm("password")

	if dal.CheckUsername(username) {
		dal.CreateUser(username, password)

		resp.BaseResp = &user.BaseResp{
			StatusCode: 0,
			StatusMsg:  "register success",
		}

		c.JSON(consts.StatusOK, resp.BaseResp)
		return
	}

	resp.BaseResp = &user.BaseResp{
		StatusCode: 1,
		StatusMsg:  "register failed",
	}
	c.JSON(consts.StatusBadRequest, resp.BaseResp)
}

// Login .
// @router /user/login [POST]
func Login(ctx context.Context, c *app.RequestContext) {
	var err error
	var req user.LoginRequest
	err = c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}

	resp := new(user.LoginResponse)
	username := c.PostForm("username")
	password := c.PostForm("password")

	if dal.CheckPassword(username, password) {
		session := sessions.Default(c)
		var count int
		cnt := session.Get(username)
		if cnt == nil {
			count = 0
			dal.SetFrequency(username, count)
		} else {
			count = cnt.(int)
			count++
			dal.SetFrequency(username, count)
		}
		session.Set(username, count)
		_ = session.Save()

		resp.BaseResp = &user.BaseResp{
			StatusCode: 0,
			StatusMsg:  "login success",
		}
		c.JSON(200, resp.BaseResp)
		return
	}
	c.JSON(consts.StatusBadRequest, resp.BaseResp)
	return
}

// Info .
// @router /user/:username [GET]
func Info(ctx context.Context, c *app.RequestContext) {
	var err error
	var req user.InfoRequest
	err = c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}

	resp := new(user.InfoResponse)

	username := c.Param("username")

	frequency := dal.GetFrequency(username)

	resp.BaseResp = &user.BaseResp{
		StatusCode: 0,
		StatusMsg:  "info success",
		Data:       strconv.Itoa(frequency),
	}
	c.JSON(consts.StatusOK, resp.BaseResp)
}

运行

在项目根目录下执行以下命令:

go build -o hertz_try
./hertz_try

应该实现的效果:先注册一个用户,而后该用户每次登录会使data+1,可以使用postman来查看是否实现。

image-20230820234715934.png

总结

Hertz 是一个基于 Go 语言的轻量级 Web 框架,旨在简化和加速构建 Web 应用程序的过程。它提供了一组简单而强大的工具和功能,帮助开发者快速搭建可靠的 Web 服务。

下面是 Hertz 框架的一些关键特点和功能:

  1. 路由和中间件:Hertz 提供了路由功能,通过定义不同的 URL 路径和 HTTP 方法,将请求映射到相应的处理函数。它还支持中间件机制,允许在请求处理前后执行一些公共逻辑,例如身份验证、日志记录等。
  2. 内置 HTTP 服务器:Hertz 包含了一个简单但高效的内置 HTTP 服务器,无需额外配置或依赖。你可以使用 hertz.Run() 方法来启动服务器,并监听指定的端口。
  3. 静态文件服务:Hertz 提供了静态文件服务的功能,可以方便地为应用程序提供静态资源(如图像、CSS 文件、JavaScript 文件等),并自动处理缓存、压缩等相关细节。
  4. 参数绑定和验证:Hertz 提供了方便的参数绑定功能,可以自动将请求中的参数绑定到函数的输入参数上。此外,它还支持对参数进行验证,确保数据的有效性和完整性。
  5. 模板引擎支持:Hertz 内置了一个简单但功能强大的模板引擎,用于生成动态的 HTML 内容。你可以在模板中使用变量、条件语句和循环等功能,从而更方便地构建视图。
  6. 错误处理和日志记录:Hertz 具有良好的错误处理机制,可以捕获和处理应用程序中的错误,并向客户端返回适当的响应。它还支持日志记录功能,可将应用程序的运行日志输出到控制台或指定的日志文件中。
  7. 可扩展性和灵活性:Hertz 允许开发者通过自定义路由、中间件和过滤器等方式来扩展和定制框架的行为。你可以根据实际需求,将 Hertz 框架与其他库和工具集成,以满足特定的业务需求。

总结起来,Hertz 是一个简单、轻量级但功能强大的 Web 框架,适用于快速构建高性能的 Web 应用程序。它提供了路由、中间件、静态文件服务、参数绑定、模板引擎等丰富的功能,同时保持了简洁和易用的设计哲学。