wx

关注公众号领取Go学习资料

wx

洋葱小店

go-zero 单体应用实践(二)


中间件使用

在go-zero中,中间件可以分为路由中间件和全局中间件,路由中间件是指某一些特定路由需要实现中间件逻辑,其和jwt类似,没有放在jwt:xxx下的路由不会使用中间件功能, 而全局中间件的服务范围则是整个服务。

路由中间件

1、编辑 api 文件 userlogin/userlogin.api 生命接口需要添加的中间件,多个中间件用逗号分隔

@server(
	middleware : Tagging,Version
)
service userlogin-api {
	@handler Tags
	get /api/tags returns (TagResponse)
}

2、goctl api 命令重新执行 生成middleware 文件

  • userlogin/internal/middleware/taggingmiddleware.go
  • userlogin/internal/middleware/versionmiddleware.go

可以看到路由文件中userlogin/internal/handler/routes.go 新增了一下代码

server.AddRoutes(
		rest.WithMiddlewares(
			[]rest.Middleware{serverCtx.Tagging, serverCtx.Version},
			[]rest.Route{
				{
					Method:  http.MethodGet,
					Path:    "/api/tags",
					Handler: TagsHandler(serverCtx),
				},
			}...,
		),
	)

3、 文件中添加中间件依赖 userlogin/internal/svc/servicecontext.go

type ServiceContext struct {
    Config    config.Config
    Tagging   rest.Middleware
    Version   rest.Middleware
    UserModel user.UserModel
}

func NewServiceContext(c config.Config) *ServiceContext {
    conn := sqlx.NewMysql(c.Mysql.DataSource)
    
    return &ServiceContext{
        Config:    c,
        UserModel: user.NewUserModel(conn, c.CacheRedis),
        Tagging:   middleware.NewTaggingMiddleware().Handle,
        Version:   middleware.NewVersionMiddleware().Handle,
    }
}

4、启动测试

http://127.0.0.1:8000/api/tags { “tag”: “tagV111–v1.1.0” }

全局中间件

userlogin/userlogin.go

flag.Parse()

	var c config.Config
	conf.MustLoad(*configFile, &c)
	logx.MustSetup(c.LogConf)
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()
    
    // 全局中间件
	server.Use(func(next http.HandlerFunc) http.HandlerFunc {
		return func(writer http.ResponseWriter, request *http.Request) {

			logx.Info("global middleware")
			next(writer, request)
		}
	})


	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)

	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()

链路追踪

go-zero 框架已实现了链路追踪,并且Jaeger, Zipkin 这两种链路追踪上报工具,只要简单配置就实现链路追踪。

1、编辑配置文件 增加jaeger配置

Telemetry:
    Name: user.api
    Endpoint: http://jaeger:14268/api/traces
    Sampler: 1.0
    Batcher: jaeger

2、userlogin/internal/config/config.go 增加相应的映射

type Config struct {
    Telemetry trace.Config
}

3、启动测试 访问 jaeger UI界面 http://127.0.0.1:5000/search image.png

错误处理

在业务中还会定义一些业务性错误,常用做法都是通过 code、msg 两个字段来进行业务处理结果描述

  1. 自定义错误 userlogin/common/errorx/baseerror.go
package errorx

const defaultCode = 1001
const DBErrorCode = 5001

type CodeError struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

type CodeErrorResponse struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

func NewCodeError(code int, msg string) error {
	return &CodeError{Code: code, Msg: msg}
}

func NewDefaultError(msg string) error {
	return NewCodeError(defaultCode, msg)
}

func (e *CodeError) Error() string {
	return e.Msg
}

func (e *CodeError) Data() *CodeErrorResponse {
	return &CodeErrorResponse{
		Code: e.Code,
		Msg:  e.Msg,
	}
}

2、业务逻辑代码中替换为自定义错误 userlogin/internal/logic/loginlogic.go

if err != nil {
		if err == user.ErrNotFound {
			return nil, errorx.NewDefaultError("用户不存在")
		}
		return nil, errorx.NewCodeError(errorx.DBErrorCode, err.Error())
	}

3、开启自定义错误 userlogin/userlogin.go

httpx.SetErrorHandler(func(err error) (int, interface{}) {
		switch e := err.(type) {
		case *errorx.CodeError:
			return http.StatusOK, e.Data()
		default:
			return http.StatusInternalServerError, nil
		}
	})

4、启动测试

#  curl -i -X POST http://127.0.0.1:8000/api/login -H 'Content-Type: application/json' -d '{"email":"notfound@gmail.com","password":"****"}'
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-d004fc85afc1bcb7f4cf6218de12e518-5eed79e06803e434-01
Date: Mon, 25 Jul 2022 14:07:02 GMT
Content-Length: 37

{"code":1001,"msg":"用户不存在"}

自定义认证错误

curl -i -X GET ‘http://127.0.0.1:8000/api/userinfo’

HTTP/1.1 401 Unauthorized
Traceparent: 00-f1a3a6dee278d0aa8604c5eab2c276a3-f4a8de7774b93cf4-01
Date: Mon, 25 Jul 2022 14:11:32 GMT
Content-Length: 0

修改代码 userlogin/userlogin.go

unauthorized := rest.WithUnauthorizedCallback(func(w http.ResponseWriter, r *http.Request, err error) {
		httpx.WriteJson(w, http.StatusOK, errorx.NewCodeError(http.StatusUnauthorized, err.Error()))
	})
	server := rest.MustNewServer(c.RestConf, unauthorized)

测试

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-f136c8b772b717f394871d447c2ad0af-400e2c6cd45ab383-01
Date: Mon, 25 Jul 2022 14:09:46 GMT
Content-Length: 48

{"code":401,"msg":"no token present in request"}
wx

关注公众号领取Go学习资料

wx

洋葱小店

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo