go 整合网关Hertz

1. 简介

Hertz 网关是一个微服务网关,由CloudWeGo团队开发,于2021年正式发布。它基于Go语言编写,支持HTTP/gRPC/Thrift协议,具备高并发、高性能、高可用等特性,可以部署在Kubernetes环境中。Hertz 网关具有动态路由、限流、熔断、黑白名单、认证鉴权等功能,并且支持可插拔的扩展机制。

2. 先上脚手架

  • 安装依赖
go install github.com/cloudwego/hertz/cmd/hz@latest
  • 搭建脚手架
hz new
  1. 包的结构目录

3. 包结构解释

  • handle包: 处理接口的处理器
  • router.go:路由配置器
  • router_gen.go: 路由注册(将router.go中所有的路由都给注册)
  • main.go:主方法,开启服务,配置中间件,注册路由,并监听服务

4. 简单构建一个接口

  • 处理器中编写函数结构为fun(ctx context.Context, c *app.RequestContext)
func Ping(ctx context.Context, c *app.RequestContext) {
	c.JSON(consts.StatusOK, utils.H{
		"message": "pong",
	})
}
  • 路由绑定
func customizedRegister(r *server.Hertz) {
	r.GET("/ping", handler.Ping)
	// your code ...
}

5. 参数的获取和校验

  • 参数获取
func HelloPerson(ctx context.Context, c *app.RequestContext) {
	name := c.Query("name")
	age := c.Param("age")
    // 请求体,自行转换成json
    body, _ := c.Body()
	c.JSON(200, utils.H{
		"age":  age,
		"name": name,
	})
}
  • 参数校验
func PersonBind(ctx context.Context, c *app.RequestContext) {
	type person struct {
		Age  int    `path:"age" json:"age" `                   // 从路径中获取参数
		Name string `query:"name" json:"name" vd:"$!='Hertz'"` // 从query中获取参数
		City string `json:"city"`                              // 从body中获取参数
	}
	var p person
	// 参数校验
	err := c.BindAndValidate(&p)
	// 参数校验出错就在全局上下文中加一个错误信息
	if err != nil {
		fmt.Printf("%v", err.Error())
		_ = c.Error(errors.WithStack(err))
		return
	}
	c.JSON(200, utils.H{
		"person": p,
	})
}

标签

说明

示例

required

必填字段

Name string vd:"required"`

minmax

最小和最大长度

Name string vd:"min=3,max=20"`

regexp

正则表达式匹配

Name string vd:"regexp=^[a-zA-Z0-9]*$"`

minmax

数值范围

Age int vd:"min=0,max=100"`

自定义验证函数

使用自定义的验证函数

Name string vd:"myCustomValidator"`

6. 中间件的使用

  • 使用中间件(tip:中间件的先后顺序和use的顺序密切相关)
func main() {
	h := server.Default(
		// 修改监听的端口
		server.WithHostPorts("127.0.0.1:8080"),
	)
	// TODO 跨域报错
	// 跨域的中间件
	mw.Cors(h)
	// 启动jtw的中间件
	mw.Jwt(h)
	// use方法启动中间件
	h.Use(mw.MyMiddleware)
	h.Use(mw.LoggingMiddleware)
	// 注册路由
	register(h)
	// 开启监听
	h.Spin()
}
  • 全局异常处理器中间件
func loggingMiddleware(ctx context.Context, c *app.RequestContext) {
    c.Next(ctx)
    if len(c.Errors) == 0 {
        // 没有收集到异常直接返回
        fmt.Println("retun")
        return
    }
    hertzErr := c.Errors[0]
    // 获取errors包装的err
    err := hertzErr.Unwrap()
    // 打印异常堆栈
    logger.CtxErrorf(ctx, "%+v", err)
    // 获取原始err
    err = errors.Unwrap(err)
    // todo 进行错误代码进行判断
    c.JSON(500, utils.H{
        "code":    consts.StatusOK,
        "message": err.Error(),
    })
}
  • jwt中间件
// Jwt jwt校验
func Jwt(h *server.Hertz) {
	// the jwt middleware
	authMiddleware, err := jwt.New(&jwt.HertzJWTMiddleware{
		Realm:       "test zone",
		Key:         []byte("secret key"),
		Timeout:     time.Hour,
		MaxRefresh:  time.Hour,
		IdentityKey: identityKey,
		// 登录成功之后的生成jwt的token
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		// TODO token校验出现一些问题,我存放在请求头里面但是还是有问题
		IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
			claims := jwt.ExtractClaims(ctx, c)
			return &User{
				UserName: claims[identityKey].(string),
			}
		},
		// 用于校验登录的,用户名和密码
		Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
			var loginVals login
			if err := c.BindAndValidate(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
				return &User{
					UserName:  userID,
					LastName:  "Hertz",
					FirstName: "CloudWeGo",
				}, nil
			}
			return nil, jwt.ErrFailedAuthentication
		},
		// 执行权限的校验
		Authorizator: func(data interface{}, ctx context.Context, c *app.RequestContext) bool {
			if v, ok := data.(*User); ok && v.UserName == "admin" {
				return true
			}
			return false
		},
		// 未认证的时候返回的数据
		Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
			c.JSON(code, map[string]interface{}{
				"code":    code,
				"message": "请先进行登录",
			})
		},
	})

	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}

	// When you use jwt.New(), the function is already automatically called for checking,
	// which means you don't need to call it again.
	errInit := authMiddleware.MiddlewareInit()

	if errInit != nil {
		log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
	}

	h.POST("/login", authMiddleware.LoginHandler)
	// 配置404页面
	h.NoRoute(authMiddleware.MiddlewareFunc(), func(ctx context.Context, c *app.RequestContext) {
		claims := jwt.ExtractClaims(ctx, c)
		log.Printf("NoRoute claims: %#v\n", claims)
		c.JSON(404, map[string]string{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
	})

	auth := h.Group("/auth")
	// Refresh time can be longer than token timeout
	auth.GET("/refresh_token", authMiddleware.RefreshHandler)
	auth.Use(authMiddleware.MiddlewareFunc())
	{
		auth.GET("/ping", PingHandler)
	}
}

Hertz代码生成工具(重要,重要,重要!!!!)

ⅰ. 创建一个空项目,项目下面放一个包idl
ⅱ. 编写模版文件(hello.thrift)
namespace go hello.ledger

struct HelloReq{
    1: required string Id (api.query="id");
    2: optional i32 age;
    3: required list<string> hobbies
}

struct HelloResp{
    1:string RespBody;
}

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


struct HelloReq2{
    1: required string Id (api.query="id");
    2: optional i32 age;
    3: required list<string> hobbies
}

struct HelloResp2{
    1:string RespBody;
}

service HelloService2{
    HelloResp HelloMethod2(1:HelloReq request)(api.get="/hello2")
}
ⅲ. 执行命令
hz new -module ledger -idl idl/hello.thrift
ⅳ. 新增接口之后使用的是跟新命令
hz update -module ledger -idl idl/hello.thrift
注意点:
  1. 修改handler的代码,然后修改thrift源文件,之后使用命令重新生成结构体,不会被覆盖重写
  2. 注册路由要在使用中间件之后,不然中间件失效

.thrift文件的编写

  • namespace go (hello.ledger) 生成包名
  • struct 表示结构体
    • required 表示参数必须携带(生成的结构体类型不为指针)
    • optional 表示参数可选(生成的结构体的字段类型为指针)
    • (api.query="id") query查询的参数映射为id
    • list<string> 切片参数
    • i32 具体标明int参数
  • service 表示方法
    • HelloResp 响应内容
    • HelloMethod 方法名称
    • (1:HelloReq request) 第一个参数
    • (api.get="/hello") get请求,路径是/hello
namespace go hello.ledger

struct HelloReq{
    1: required string Id (api.query="id");
    2: optional i32 age;
    3: required list<string> hobbies
}

struct HelloResp{
    1:string RespBody;
}

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


struct HelloReq2{
    1: required string Id (api.query="id");
    2: optional i32 age;
    3: required list<string> hobbies
}

struct HelloResp2{
    1:string RespBody;
}

service HelloService2{
    HelloResp HelloMethod2(1:HelloReq request)(api.get="/hello2")
}

hello_service.go分析

  • HelloMethod 方法
    • context.Context,携带参数上下文(在函数之间传递截止日期、取消信号和其他请求范围值的方法)和
    • *app.RequestContext,包含与当前请求相关的信息
  • var req ledger.HelloReq 声明一个包下面的请求参数变量
    • c.BindAndValidate(&req) 校验并赋值
  • resp := new(ledger.HelloResp) 构造一个响应的参数
    • 赋值
  • c.JSON(consts.StatusOK, resp) 写入json 的响应格式,之后返回resp
func HelloMethod(ctx context.Context, c *app.RequestContext) {
	var req ledger.HelloReq
	err := c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}
	resp := new(ledger.HelloResp)

	resp.RespBody = "id: " + req.Id +
		" age: " + strconv.FormatInt(int64(*req.Age), 10) +
		" hobbies: " + strings.Join(req.Hobbies, ",")

	c.JSON(consts.StatusOK, resp)
}

hello.go(router)

  • 路由映射的地方
func Register(r *server.Hertz) {

	root := r.Group("/", rootMw()...)
	root.GET("/hello", append(_hellomethodMw(), ledger.HelloMethod)...)
	root.GET("/hello2", append(_hellomethod2Mw(), ledger.HelloMethod2)...)
}

middleware.go

  • rootMw 全局的中间件配置
  • _hellomethodMw 对HelloMethod方法单独的中间件
  • _hellomethod2Mw 对HelloMethod2方法单独的中间件
func rootMw() []app.HandlerFunc {
    // your code...
    return nil
}

func _hellomethodMw() []app.HandlerFunc {
    // your code...
    return nil
}

func _hellomethod2Mw() []app.HandlerFunc {
    // your code...
    return []app.HandlerFunc{
        TestMw,
    }
}
  • 中间件的实现很简单,编写一个这样的函数就可以了
type HandlerFunc func(c context.Context, ctx *RequestContext)

处理器的编写

  • 第一个参数context.Context(用于携带截止日期、取消信号、请求范围值以及其他请求相关的元数据)
func HelloMethod(ctx context.Context, c *app.RequestContext) {
    // 示例:从上下文中获取截止日期
    deadline, ok := ctx.Deadline()
    if ok {
        // 处理截止日期
    }

    // 示例:通过上下文传递值
    value := ctx.Value("key")
    if value != nil {
        // 处理传递的值
    }

    // 示例:处理上下文的取消信号
    select {
    case <-ctx.Done():
        // 上下文已取消,执行清理操作
    default:
        // 继续处理请求
    }
    
    // 其他可能的上下文方法和用法...
}
  • 第二个参数*app.RequestContext 与请求相关的参数
// Host 返回请求的主机信息,以字节切片形式表示。
func (ctx *RequestContext) Host() []byte

// FullPath 返回完整的请求路径。
func (ctx *RequestContext) FullPath() string

// SetFullPath 设置请求的完整路径。
func (ctx *RequestContext) SetFullPath(p string)

// Path 返回请求路径的字节切片形式。
func (ctx *RequestContext) Path() []byte

// Param 根据给定的键返回请求参数的值。
func (ctx *RequestContext) Param(key string) string

// Query 根据给定的键返回请求的查询参数值。
func (ctx *RequestContext) Query(key string) string

// DefaultQuery 根据给定的键返回请求的查询参数值,如果参数不存在则返回默认值。
func (ctx *RequestContext) DefaultQuery(key, defaultValue string) string

// GetQuery 返回请求的查询参数值以及一个布尔值,指示参数是否存在。
func (ctx *RequestContext) GetQuery(key string) (string, bool)

// QueryArgs 返回请求的查询参数,以 protocol.Args 的形式。
func (ctx *RequestContext) QueryArgs() *protocol.Args

// URI 返回请求的 URI(Uniform Resource Identifier)信息。
func (ctx *RequestContext) URI() *protocol.URI
  • 第二个参数*app.RequestContext 与响应相关的参数
// SetContentType 设置响应的内容类型。
func (ctx *RequestContext) SetContentType(contentType string)

// SetContentTypeBytes 以字节切片形式设置响应的内容类型。
func (ctx *RequestContext) SetContentTypeBytes(contentType []byte)

// SetConnectionClose 设置响应头,要求在完成响应后立即关闭连接。
func (ctx *RequestContext) SetConnectionClose()

// SetStatusCode 设置响应的状态码。
func (ctx *RequestContext) SetStatusCode(statusCode int)

// Status 设置响应的状态码。
func (ctx *RequestContext) Status(code int)

// NotFound 设置响应状态码为404 Not Found。
func (ctx *RequestContext) NotFound()

// NotModified 设置响应状态码为304 Not Modified。
func (ctx *RequestContext) NotModified()

// Redirect 发送重定向响应。
func (ctx *RequestContext) Redirect(statusCode int, uri []byte)

// Header 设置响应头中的键值对。
func (ctx *RequestContext) Header(key, value string)

// SetCookie 设置响应的cookie。
func (ctx *RequestContext) SetCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly bool)

// AbortWithStatus 中止请求并返回指定的HTTP状态码。
func (ctx *RequestContext) AbortWithStatus(code int)

// AbortWithError 中止请求并返回指定的HTTP状态码和错误信息。
func (ctx *RequestContext) AbortWithError(code int, err error) *errors.Error
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值