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》

相关推荐
mtngt1110 小时前
AI DDD重构实践
go
Grassto2 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉6 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想
asaotomo7 天前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
码界奇点8 天前
基于Gin与GORM的若依后台管理系统设计与实现
论文阅读·go·毕业设计·gin·源代码管理
迷迭香与樱花8 天前
Gin 框架
go·gin
只是懒得想了8 天前
用Go通道实现并发安全队列:从基础到最佳实践
开发语言·数据库·golang·go·并发安全