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 的调用。 是上面的例子还没出现的内容,大家在看完后续文字后,再来理解一下调用过程,能够更好的理解。