User API Gateway 创建
cd
到 FoodGuides
目录下。创建 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-api
:Login
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
等信息,MySQL
、Redis
、rpc
等配置也是写在这里的。api
下的user.api
文件。该文件定义了api
服务所提供的接口信息,之后的接口增加同样是在这里处理。然后 调用goctl
重新生成服务。api
下的user.go
文件。该文件是api
服务的入口文件,一切都是从这里开始。
internal 文件夹
api
服务的内部实现代码都放在了该文件夹下面。
internal/config
下的config.go
文件。你会发现,该文件的定义和 user-api.yaml
的定义类似。是的。user-api.yaml
在 user.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
的调用。 是上面的例子还没出现的内容,大家在看完后续文字后,再来理解一下调用过程,能够更好的理解。