go-zero 实战 - User API Gateway

User API Gateway 创建

cdFoodGuides 目录下。创建 api 文件夹

go 复制代码
$ mkdir -p usermanage/api 
$ cd usermanage/api

创建 user.api 文件

go 复制代码
$ goctl api -o user.api

当命令行出现 Done. 关键字后,打开 user.api 文件,文件内容如下:

go 复制代码
syntax = "v1"

info (
    title: // TODO: add title
    desc: // TODO: add description
    author: "DESKTOP-4T5UKHP\Owner"
    email: "renpanpan1990@163.com"
)

type request {
    // TODO: add members here and delete this comment
}

type response {
    // TODO: add members here and delete this comment
}

service user-api {
    @handler GetUser // TODO: set handler name and delete this comment
    get /users/id/:userId(request) returns(response)

    @handler CreateUser // TODO: set handler name and delete this comment
    post /users/create(request)
}

生成的 user.api 文件会自动生成一些内容,以帮助我们更快的上手。在本实战教程中,我们想自己提供多个 api,编辑 user.api 文件如下:

go 复制代码
syntax = "v1"

info (
    title: "UserApi"
    desc: "用户服务相关 API"
    author: "DESKTOP-4T5UKHP/Owner"
    email: "renpanpan1990@163.com"
)

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

    LoginResponse {
       UserReply
    }
)

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

    RegisterResponse {
       UserReply
    }
)

type (
    UserInfoRequest {
       Userid string `json:"userid"`
       Token  string `json:"token"`
    }

    UserInfoResponse {
       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)
}

我们定义了三个关于用户的 api,分别为:LoginRegisterUserInfo

cdapi 目录下,执行如下命令,生成 user-api 服务:

go 复制代码
$ goctl api go -api user.api -dir .

如果执行上面的命令时,控制台报如下错误:

arduino 复制代码
user.api line 4:34  mismatched ':', found input '" email: "'
Error: user.api line 4:34  mismatched ':', found input '" email: "'

请检查 info.author 的设置,自动生成的名称为 DESKTOP-4T5UKHP\Owner,我们在生成 user-api 服务前,需要将其修改为 DESKTOP-4T5UKHP/Owner,再重复执行 $ goctl api go -api user.api -dir . 命令。

这个时候,我们可以查看一下 api 目录下都会生成什么文件和内容:

go 复制代码
➜  api 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

我们尝试启动该服务:

go 复制代码
$ go mod tidy // 整理依赖(可选的)
$ go run user.go -f etc/user-api.yaml
Starting server at 0.0.0.0:8888...

当终端输出 Starting server at 0.0.0.0:8888... 信息时,意味着该服务启动成功。(如果要断开服务,请在终端中按下组合键 Ctrl + C

理解服务是怎么跑起来的

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() 方法。

go 复制代码
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 文件。各个接口触发方法的具体实现都写在了这里的文件里。如 registerhandler.go 文件中就自动生成如下方法:

go 复制代码
func RegisterHandler(svcCtx *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.ErrorCtx(r.Context(), w, err)
          return
       }

       l := logic.NewRegisterLogic(r.Context(), svcCtx)
       resp, err := l.Register(&req)
       if err != nil {
          httpx.ErrorCtx(r.Context(), w, err)
       } else {
          httpx.OkJsonCtx(r.Context(), w, resp)
       }
    }
}

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

internal/logic 下的 xxxlogic.go 文件。我们将最终在各个 logic 的实现方法里实现相关服务逻辑。

internal/svc 下的 servicecontext.go 文件。该文件保存了 api 服务的 config 对象。然后 svc 对象会从 handle 传递到 logic 方法里。

go 复制代码
type ServiceContext struct {
    Config config.Config
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
       Config: c,
    }
}

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

调用过程梳理

以客户端调用 login 接口为例。我们先看看自动生成的 user.go 文件中是如何实现的:

go 复制代码
package main

import (
    "flag"
    "fmt"

    "FoodGuides/usermanage/api/internal/config"
    "FoodGuides/usermanage/api/internal/handler"
    "FoodGuides/usermanage/api/internal/svc"

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

var configFile = flag.String("f", "etc/user-api.yaml", "the config file")

func main() {
    flag.Parse()

    var c config.Config
    conf.MustLoad(*configFile, &c)

    server := rest.MustNewServer(c.RestConf)
    defer server.Stop()

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

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

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

go 复制代码
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 对象

go 复制代码
ctx := svc.NewServiceContext(c)

ctx 内部保存了 config 对象。

实例化 Server 对象。

go 复制代码
server := rest.MustNewServer(c.RestConf)

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

go 复制代码
handler.RegisterHandlers(server, ctx)

api 服务 跑起来

go 复制代码
server.Start()

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

go 复制代码
// usermanage\api\internal\handler\loginhandler.go
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
       var req types.LoginRequest
       if err := httpx.Parse(r, &req); err != nil {
          httpx.ErrorCtx(r.Context(), w, err)
          return
       }

       l := logic.NewLoginLogic(r.Context(), svcCtx)
       resp, err := l.Login(&req)
       if err != nil {
          httpx.ErrorCtx(r.Context(), w, err)
       } else {
          httpx.OkJsonCtx(r.Context(), w, resp)
       }
    }
}

然后 LoginHandler 调用 LoginLogic 方法

go 复制代码
// usermanage\api\internal\logic\loginlogic.go
func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
    // todo: add your logic here and delete this line

    return
}

Login 方法里,我们需要稍后完成登录逻辑。处理完数据后,接口逐层响应回去,最终完成客户端接口的调用。

上一篇《go-zero 实战 - 服务划分与项目创建》

下一篇《go-zero 实战 - User Login》

相关推荐
郝同学的测开笔记14 小时前
云原生探索系列(十二):Go 语言接口详解
后端·云原生·go
一点一木1 天前
WebAssembly:Go 如何优化前端性能
前端·go·webassembly
千羽的编程时光2 天前
【CloudWeGo】字节跳动 Golang 微服务框架 Hertz 集成 Gorm-Gen 实战
go
27669582923 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
Moment4 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
唐僧洗头爱飘柔95275 天前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
黑心萝卜三条杠5 天前
【Go语言】深入理解Go语言:并发、内存管理和垃圾回收
google·程序员·go
不喝水的鱼儿5 天前
【LuatOS】基于WebSocket的同步请求框架
网络·websocket·网络协议·go·luatos·lua5.4
微刻时光5 天前
程序员开发速查表
java·开发语言·python·docker·go·php·编程语言
lidenger6 天前
服务认证-来者何人
后端·go