功能

1. jwt

jwt在目前登录,鉴权等场景应用非常广泛

jwt是加密的字符串,需要一个秘钥,并且可以通过设置过期时间来使jwt生成的token失效。

我们在userapi中进行演示

1.1 配置

type Config struct {
	rest.RestConf
	UserRpc zrpc.RpcClientConf
	Auth    struct {
		AccessSecret string
		AccessExpire int64
	}
}
1
2
3
4
5
6
7
8
Auth:
  AccessSecret: "!@#$#@asdasds"
  AccessExpire: 604800
1
2
3

1.2 实现

生成token:

func (l *UserLogic) getToken(secretKey string, iat, seconds, userId int64) (string, error) {
	claims := make(jwt.MapClaims)
	claims["exp"] = iat + seconds
	claims["iat"] = iat
	claims["userId"] = userId
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = claims
	return token.SignedString([]byte(secretKey))
}

1
2
3
4
5
6
7
8
9
10

在api文件

@server(
	jwt: Auth
)
service userapi-api {
	@handler UserapiHandler
	get /from/:name(Request) returns (Response)
}
1
2
3
4
5
6
7

生成

goctl api go -api userapi.api -dir ./gen
1

通过生成的代码,我们知道其是在RegisterHandlers注册路由时,在路由上加了jwt验证

// Code generated by goctl. DO NOT EDIT.
package handler

import (
	"net/http"

	"userapi/internal/svc"

	"github.com/zeromicro/go-zero/rest"
)

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
	handler := NewUserHandler(serverCtx)
	server.AddRoutes(
		[]rest.Route{
			{
				Method:  http.MethodPost,
				Path:    "/register",
				Handler: handler.Register,
			},
			{
				Method:  http.MethodPost,
				Path:    "/login",
				Handler: handler.Login,
			},
		},
	)
	server.AddRoutes(
		[]rest.Route{
			{
				Method:  http.MethodGet,
				Path:    "/user/get/:id",
				Handler: handler.GetUser,
			},
		},
		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
	)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

login:

func (l *UserLogic) Login(t *types.LoginRequest) (string, error) {
	auth := l.svcCtx.Config.Auth
	return l.getToken(auth.AccessSecret, time.Now().Unix(), auth.AccessExpire, 100)
}

1
2
3
4
5
func (l *UserLogic) GetUser(req *types.IdRequest) (*types.Response, error) {
	value := l.ctx.Value("userId")
	logx.Infof("get token content: %s \n ", value)
	userResponse, err := l.svcCtx.UserRpc.GetUser(context.Background(), &user.IdRequest{
		Id: req.Id,
	})
	if err != nil {
		return nil, err
	}
	return &types.Response{
		Message: "success",
		Data:    userResponse,
	}, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

验证:

不加认证

image-20230224115842418

加上认证

image-20230224120123569

image-20230224120206032

2. 中间件

2.1 路由中间件

@server(
    jwt: Auth
    middleware: Example // 路由中间件声明
)
1
2
3
4
goctl api go -api userapi.api -dir ./gen
1

主要看一下其主要在哪生成的,知道应该在哪写中间件。

package middlewares

import (
	"github.com/zeromicro/go-zero/core/logx"
	"net/http"
)

type UserMiddleware struct {
}

func NewUserMiddleware() *UserMiddleware {
	return &UserMiddleware{}
}

func (*UserMiddleware) RegAndLoginHandler(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		logx.Info("login 和 register 前面执行")
		next(w, r)
		logx.Info("login 和 register 后面面执行")
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package svc

import (
	"github.com/zeromicro/go-zero/zrpc"
	"user/userclient"
	"userapi/internal/config"
	"userapi/internal/middlewares"
)

type ServiceContext struct {
	Config         config.Config
	UserRpc        userclient.User
	UserMiddleware *middlewares.UserMiddleware
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config:         c,
		UserRpc:        userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
		UserMiddleware: middlewares.NewUserMiddleware(),
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Code generated by goctl. DO NOT EDIT.
package handler

import (
	"net/http"

	"userapi/internal/svc"

	"github.com/zeromicro/go-zero/rest"
)

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
	handler := NewUserHandler(serverCtx)
	server.AddRoutes(
		rest.WithMiddlewares(
			[]rest.Middleware{
				serverCtx.UserMiddleware.RegAndLoginHandler,
			},
			rest.Route{
					Method:  http.MethodPost,
					Path:    "/register",
					Handler: handler.Register,
			},
			rest.Route{
				Method:  http.MethodPost,
				Path:    "/login",
				Handler: handler.Login,
			},
	))
	server.AddRoutes(
		[]rest.Route{
			{
				Method:  http.MethodGet,
				Path:    "/user/get/:id",
				Handler: handler.GetUser,
			},
		},
		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
	)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

2.2 全局中间件

server.Use(serverCtx.UserMiddleware.GlobalHandler)
1

3. 自定义错误

之前:

if req.Id == "1" {
		return nil, errors.New("id参数不正确")
	}
1
2
3

仅仅会返回简单的error信息

image-20230224173147035

如果想要返回json格式的错误,在go-zero中如何做呢?

package errorx

var ParamsError = New(1101001, "参数不正确")

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

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

func New(code int, msg string) *BizError {
	return &BizError{
		Code: code,
		Msg:  msg,
	}
}

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

func (e *BizError) Data() interface{} {
	return &ErrorResponse{
		e.Code,
		e.Msg,
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

处理自定义错误

httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, interface{}) {
		switch e := err.(type) {
		case *errorx.BizError:
			return http.StatusOK, e.Data()
		default:
			return http.StatusInternalServerError, nil
		}
	})
1
2
3
4
5
6
7
8
if req.Id == "1" {
		return nil, errorx.ParamsError
	}
1
2
3

4. 修改模板

go-zero生成代码都是基于模板去生成的,如果生成的代码,不符合期望,你可以自行去修改模板代码

初始化模板

goctl template init
1

你会看到模板的地址

然后根据自己需求修改即可。