go-zero教程——User API Gateway

6,856 阅读4分钟

User API Gateway 创建

cdFoodGuides 目录下。创建 api 文件夹

mkdir -p usermanage/api && cd usermanage/api

创建user.api文件

goctl api -o user.api

定义api服务

info(
	title: // UserApi
	desc: // 用户服务相关API
)

type LoginRequest struct {
	Email 	 	string `json:"email"`
	Password 	string `json:"password"`
}

type LoginResponse struct {
	UserReply
}

type RegisterRequest struct {
	Username    string `json:"username"`
	Email 	    string `json:"email"`
	Password    string `json:"password"`
}

type RegisterResponse struct {
	UserReply
}

type UserinfoRequest struct {
	Userid 		string `json:"userid"`
	Token 		string `json:"token"`
}

type UserinfoResponse struct {
	UserReply
}

type UserReply {
	Id		 	int64 `json:"id"`
	Username 	string `json:"username"`
	Email 		string `json:"email"`
	JwtToken
}

type JwtToken {
	AccessToken  string `json:"accessToken,omitempty"`
	AccessExpire int64 `json:"accessExpire,omitempty"`
	RefreshAfter int64 `json:"refreshAfter,omitempty"`
}

service user-api {
	@handler Login    // 用户登录
	post /users/login(LoginRequest) returns(LoginResponse)
	
	@handler Register // 用户注册
	post /users/register(RegisterRequest) returns(RegisterResponse)
	
	@handler UserInfo // 用户信息
	post /users/userinfo(UserinfoRequest) returns(UserinfoResponse)
}

我们定义了三个 user-apiLogin Register UserInfo

生成user-api服务

goctl api go -api user.api -dir .

查看一下 api 目录

➜  api git:(master) ✗ tree
.
├── etc
│   └── user-api.yaml
├── internal
│   ├── config
│   │   └── config.go
│   ├── handler
│   │   ├── loginhandler.go
│   │   ├── registerhandler.go
│   │   ├── routes.go
│   │   └── userinfohandler.go
│   ├── logic
│   │   ├── loginlogic.go
│   │   ├── registerlogic.go
│   │   └── userinfologic.go
│   ├── svc
│   │   └── servicecontext.go
│   └── types
│       └── types.go
├── user.api
└── user.go

启动服务,注意 在启动服务钱,需要确保 上一篇文章用到的 ningxi-compose正常运行起来。

go run user.go -f etc/user-api.yaml

理解服务是怎么跑起来的

goctl 工具可以很方便快捷的帮助我们创建api服务。但是如果不能理解api服务是如何跑起来的,看着项目结构就会很懵逼。

  • api/etc 下的user-api.yaml 文件。该文件配置了api服务所需的一些变量,像 服务名称 Name 、接口地址 Host、端口号 Port 等信息,MySQLRedisrpc 等配置也是写在这里的。
  • api 下的user.api 文件。该文件定义了api服务所提供的接口信息,之后的接口增加同样是在这里处理。然后 调用 goctl 重新生成服务。
  • api 下的user.go 文件。该文件是 api 服务的入口文件,一切都是从这里开始。

internal 文件夹

api 服务的内部实现代码都放在了该文件夹下面。

internal/config 下的config.go文件。你会发现,该文件的定义和 user-api.yaml 的定义类似。是的。user-api.yamluser.go 入口文件在 main 方法里,就被解析成了 config 对象。所以他们的值是一一对应的。

internal/handler 下的 routers.go 文件。该文件为 api 服务的路由文件,定义了各个接口的请求方式,接口路径,以及接口触发的方法。例如:客户端以 post 方式请求了 http://localhost:8888/users/register ,则 api 服务将会触发 RegisterHandler() 方法。

func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
	engine.AddRoutes(
		[]rest.Route{
			{
				Method:  http.MethodPost,
				Path:    "/users/login",
				Handler: LoginHandler(serverCtx),
			},
			{
				Method:  http.MethodPost,
				Path:    "/users/register",
				Handler: RegisterHandler(serverCtx),
			},
			{
				Method:  http.MethodPost,
				Path:    "/users/userinfo",
				Handler: UserInfoHandler(serverCtx),
			},
		},
	)
}

internal/handler 下的 xxxhandler.go 文件。各个接口触发方法的具体实现都写在了这里文件里。

func RegisterHandler(ctx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.RegisterRequest
		if err := httpx.Parse(r, &req); err != nil {
			httpx.Error(w, err)
			return
		}

		l := logic.NewRegisterLogic(r.Context(), ctx)
		resp, err := l.Register(req)
		if err != nil {
			httpx.Error(w, err)
		} else {
			httpx.OkJson(w, resp)
		}
	}
}

可以看到 RegisterHandler 首先对接收到的参数进行了解析。然后调用了 logic.NewRegisterLogic(),可以发现RegisterHandler 还并不是最终的实现,最终的业务处理其实是在 logic 文件夹下的各个 logic.go 文件中。

internal/logic 下的 xxxlogic.go 文件。我们将最终在各个 logic 的实现方法里去调用对应的 rpc 服务。

internal/svc 下的 servicecontext.go 文件。该文件保存了 api 服务的 config 对象,并且实例化了各个 rpc 服务的对象。然后 svc 对象会从 handle 传递到 logic 方法里。最后 logic 在调用 rpc 服务的时候也是通过 svc 对象 来实现的。

func (l *LoginLogic) Login(req types.LoginRequest) (*types.LoginResponse, error) {
	
	resp,err := l.svcCtx.User.Login(l.ctx, &user.LoginRequest{  // 通过svcCtx 来调用了user rpc服务
		Email: req.Email,
		Password: req.Password,
	});

}

internal/types 下的 types.go 文件。该文件定义了 我们在 user.api 模板文件里声明的各个结构体。

调用过程梳理

以客户端调用 login 接口为例。

user.go 入口文件 通过 yaml 配置文件,实例化 config 对象。

var configFile = flag.String("f", "etc/user-api.yaml", "the config file")
func main() {
	flag.Parse()
	var c config.Config
	conf.MustLoad(*configFile, &c)
	}

实例化 ServiceContext 对象

ctx := svc.NewServiceContext(c)

ctx 内部保存了 config 对象。并且 初始化了 user rpx 服务。于是 ctx 就具备了调用 rpc 服务的能力。

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

实例化 Server 对象。

server := rest.MustNewServer(c.RestConf)

路由实现, 注意 ctx 被传递到 handlers 内部了。

handler.RegisterHandlers(server, ctx)

api 服务 跑起来

server.Start()

当客户端调用 login 接口。 触发 LoginHandler 方法

func LoginHandler(ctx *svc.ServiceContext) http.HandlerFunc {
		
		l := logic.NewLoginLogic(r.Context(), ctx)
		resp, err := l.Login(req)
		
}

然后 LoginHandler 调用 LoginLogic 方法

func (l *LoginLogic) Login(req types.LoginRequest) (*types.LoginResponse, error) {

	resp,err := l.svcCtx.User.Login(l.ctx, &user.LoginRequest{
		Email: req.Email,
		Password: req.Password,
	});
	
}

Login 里。我们通过 l.svcCtx.调用了 User rpc 服务里的 Login 方法。

处理完数据后,接口逐层响应回去,最终完成客户端接口的调用。

在理解过程中,出现了有关 rpc 的调用。 是上面的例子还没出现的内容,大家在看完后续文字后,再来理解一下调用过程,能够更好的理解。

上一篇《go-zero教程——服务划分与项目创建》

下一篇《go-zero教程——User rpc - Login》