go-zero的快速搭建流程
1. goctl的安装
1.1 概述
goctl 是 go-zero 的内置脚手架, 是提升 go-zero 开发的一大利器, 可以一键生成代码、文档、
部署 k8s yaml、dockerfile 等。
1.2 golang直装
1.2.1 如果go版本在1.16以前,则使用以下命令安装:
$ go get -u github.com/zeromicro/go-zero/tools/goctl@latest
1.2.2 如果go版本在1.16以后,则使用如下命令安装
& go install github.com/zeromicro/go-zero/tools/goctl@latest
1.2.3 验证安装
& goctl --version
goctl version 1.5.5 linux/amd64
1.3 手动安装
这里以v1.5.6为例, 大家可 前往Github 自行选择
1.3.1 下载Windows64位
1.3.2 下载windows32位
1.3.3 macOS ARM64位
1.3.4 macOS x86-64位
1.3.5 Linux 64位
1.3.6 Linux 32为
1.3.7 安装
解压下载的安装包, 并将其移动到$GOBIN目录, 查看$GOBIN目录
go env GOPATH
$GOBIN为 G O P A T H / b i n , 如果你的 GOPATH/bin, 如果你的 GOPATH/bin,如果你的GOPATH不在系统$PATH中, 将其添加到$PATH中即可
1.3.8 验证
$ goctl --version
1.4 docker安装
1.4.1 pull & run
$ docker pull kevinwan/goctl
$ docker run --rm -it -vpwd
:/app kevinwan/goctl goctl --help
1.4.2 验证
$ docker run --rm -it -v
pwd
:/app kevinwan/goctl:latest goctl --version
2. protoc安装
2.1 一键安装
通过goctl可以一键安装protoc, protoc-gen-go, protoc-gen-go-grpc相关组件, 执行命令如下
$ goctl env check --install --verbose --force
2.2 手动安装
2.2.1 下载
可前往 Github官网 自行选择对应的版本
2.2.2 安装
$ go env GOPATH
$GOBIN为$GOPATH/bin, 如果你的$GOPATH不在系统$PATH中, 将其添加到系统$PATH中
2.2.3 验证
$ goctl env check --verbose
[goctl-env]: preparing to check env
[goctl-env]: looking up “protoc”
[goctl-env]: “protoc” is installed
[goctl-env]: looking up “protoc-gen-go”
[goctl-env]: “protoc-gen-go” is installed
[goctl-env]: looking up “protoc-gen-go-grpc”
[goctl-env]: “protoc-gen-go-grpc” is installed
[goctl-env]: congratulations! your goctl environment is ready!
3. go-zero 安装
3.1 安装
$ mkdir && cd # project name 为具体值
$ go mod init # module
$ go get -u github.com/zeromicro/go-zero@latest
4. DSL介绍
4.1 概述
api 是 go-zero 自研的领域特性语言, 作为生成 http 服务最基本的描述语言. api 领域特性语言包含语法版本, info 块, 结构体声明, 服务描述等几大块语言组成, 其中结
构体和 golang 结构体语法几乎一样, 只是移除了 struct 关键字
4.2 快速入门
示例: 简单的用户服务API文件:
syntax = “v1”
type Base {
Code int `json:”code”`
Message string `json:”message”`
}
type UserInfo {
Id int64 `json:”id”`
Name string `json:”name”`
Desc string `json:”desc”`
}
type (
// 定义登录接口的请求体
LoginReq {
Username string `json:”username”`
Password string `json:”password”`
}
// 定义登录接口的响应体
LoginResp {
Base
Id int64 `json:”id”`
Name string `json:”name”`
Token string `json:”token”`
ExpiredAt int64 `json:”expired_at”`
}
)
type (
// 定义获取用户信息的请求体
GetUserInfoReq {
Id int64 `form:”id”`
}
// 定义获取用户信息的响应体
GetUserInfoResp {
Base
Data UserInfo `json:”data”`
}
// 定义更新用户信息的 json 请求体
UpdateUserInfoReq {
Id int64 `json:”id”`
Name string `json:”name”`
Desc string `json:”desc”`
}
)
// 定义 HTTP 服务
// @server 语法块主要用于控制对 http 服务生成时 mate 信息, 目前支持功能有:
// 1. 路由分组
// 2. 中间件声明
// 3. 路由前缀
// 4. 超时配置
// 5. jwt 鉴权开关
// 所有声明仅对当前 service 中的路由有效
@server (
// 代表当前 service 代码块下的路由生成代码时都会放到 login 目录下
group: login
// 定义路由前缀
prefix: /v1
)
// 微服务名称位 user, 生成的代码目录和配置文件将和 user 相关
service user {
// doc 描述信息
@doc ”登录”
// 定义 http.HandleFunc 转换的 go 文件名称及方法, 每个接口都会跟一个 handler
@handler login
// 定义接口, 请求方式位 post, 路由为/user/login
// 请求体为 LoginReq, 响应体为 LoginResp, 响应体必须有 returns 关键字修饰
post /user/login (LoginReq) returns (LoginResp)
}
@server(
// 代表当前 service 代码块下的所有路由均需要 jwt 鉴权
// goctl 生成代码时会将当前 service 代码块下的接口信息添加上 jwt 相关的代码, // Authorization 值为 jwt 秘钥, 过期等信息配置的 golang 结构体名称
jwt: Authorization
// 代表当前 service 代码块下的路由生成代码时都会被放到 user 目录下
group: user
// 定义路由前缀为”/v1” prefix: /v1
// 定义一个鉴权控制的中间件, 多个中间件以英文逗号分割
middleware: AuthInterceptor
// 定义一个超时时间为 3s 的配置
timeout: 3s
// 定义一个请求体限制在 1MB 以内
maxBytes: 1048576
)
// 注意: 定义多个 service 代码块时, 服务名称必须一致, 因此这里的服务名称必须和
// 上文的 service 名称一样, 为 user 服务
service user {
@handler getUserInfo
get /user/info (GetUserInfoReq) returns (GetUserInfoResp)
@handler updateUserInfo
post /user/info/update (UpdateUserInfoReq)
}
4.3 API语法详情
API语法详情请参考 go-zero 官网 指南中的API定义
5. CLI工具(goctl)
5.1 goctl视图
5.2 goctl 指令详情
注: 如需查看所有指令详情请前往 go-zero官网
6. 快速构建微服务
6.1 准备工作
注: 下面以官方示例bookstore demo进行说明
安装 ETCD, mysql, redis, 请自行安装
安装 protoc-gen-go, 请参考本博客protoc的安装章节
安装工具 goctl, 请参考本博客goctl的安装章节
检查goctl环境
$ goctl env
GOCTL_OS=darwin
GOCTL_ARCH=amd64
GOCTL_HOME=/Users/xxx/.goctl
GOCTL_DEBUG=False
GOCTL_CACHE=/Users/xxx/.goctl/cache
GOCTL_VERSION=1.3.3
PROTOC_VERSION=3.17.3
PROTOC_GEN_GO_VERSION=v1.27.1
PROTO_GEN_GO_GRPC_VERSION=1.1.0
创建工作目录bookstore和bookstore/api
在bookstore目录下执行go mod init bookstore, 初始化go.mod
6.2 编写API Gateway代码
6.2.1 在 bookstore/api目录下通过goctl生成api/bookstore.api
指令如下:
goctl api -o bookstore.api
编辑bookstore.api, 修改如下:
syntax = "v1"
info(
title:
desc:
author:
email:
version: "v1"
)
type (
addReq {
book string `form:"book"`
price int64 `form:"price"`
}
addResp {
ok bool `json:"ok"`
}
)
type (
checkReq {
book string `form:"book"`
}
checkResp {
found bool `json:"found"`
price int64 `json:"price"`
}
)
service bookstore-api {
@handler AddHandler
get /add (addReq) returns (addResp)
@handler CheckHandler
get /check (checkReq) returns (checkResp)
}
6.2.2 使用goctl生成API Gateway代码
指令如下:
goctl api go -api bookstore.api -dir .
结构如下:
api
├── bookstore.api // api定义
├── bookstore.go // main入口定义
├── etc
│ └── bookstore-api.yaml // 配置文件
└── internal
├── config
│ └── config.go // 定义配置
├── handler
│ ├── addhandler.go // 实现addHandler
│ ├── checkhandler.go // 实现checkHandler
│ └── routes.go // 定义路由处理
├── logic
│ ├── addlogic.go // 实现AddLogic
│ └── checklogic.go // 实现CheckLogic
├── svc
│ └── servicecontext.go // 定义ServiceContext
└── types
└── types.go // 定义请求、返回结构体
注: 如果修改了API文档, 可再次执行上面的指令, 重新生成API代码, 不影响未更新部位的代码
6.2.3 在API目录下启动服务
默认端口是8888, 可在etc/bookstore.yaml中修改
启动服务指令如下:
go run bookstore.go -f etc/bookstore-api.yaml
6.2.4 测试API Gateway服务
执行如下指令:
curl -i “http://localhost:8888/check?book=go-zero”
返回:
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 06:46:18 GMT
Content-Length: 25{“found”:false,“price”:0}
6.3 创建 add rpc服务
6.3.1 生成add.proto文件
- 在bookstore下创建rpc/add目录
- 在rpc/add目录下编写add.proto文件
- 可以通过以下命令生成
$ goctl rpc -o add.proto - 修文文件内容如下
syntax = "proto3";
package add;
option go_package = "./add";
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}
6.3.2 生成rpc代码
在rpc/add目录下执行如下代码:
goctl rpc protoc add.proto --go_out=. --go-grpc_out=. --zrpc_out=.
生成的文件格式如下:
rpc/add
├── add // pb.go
│ ├── add.pb.go
│ └── add_grpc.pb.go
├── add.go // main函数入口
├── add.proto // proto源文件
├── adder // rpc client call entry
│ └── adder.go
├── etc // yaml配置文件
│ └── add.yaml
└── internal
├── config // yaml配置文件对应的结构体定义
│ └── config.go
├── logic // 业务逻辑
│ └── addlogic.go
├── server // rpc server
│ └── adderserver.go
└── svc // 资源依赖
└── servicecontext.go
6.3.3 启动add rpc 服务
执行如下指令:
$ go run add.go -f etc/add.yaml
注: 如果修改了proto文件, 可重新生成rpc代码, 不会影响到未更新部分的代码
6.4 创建 check rpc 服务
6.4.1 生成 check.proto 文件
- 创建 rpc/check目录, 在rpc/check目录中生成check.proto文件
可通过如下指令来生成:
$ goctl rpc -o check.proto - 修改check.proto文件
修改后的check.proto文件如下:
syntax = "proto3";
package check;
option go_package = "./check";
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}
6.4.2 使用goctl生成check rpc代码
指令如下:
goctl rpc protoc check.proto --go_out=. --go-grpc_out=. --zrpc_out=.
生成的目录结构如下:
rpc/check
├── check // pb.go
│ ├── check.pb.go
│ └── check_grpc.pb.go
├── check.go // main入口
├── check.proto // proto源文件
├── checker // rpc client call entry
│ └── checker.go
├── etc // yaml配置文件
│ └── check.yaml
└── internal
├── config // yaml配置文件对应的结构体定义
│ └── config.go
├── logic // 业务逻辑
│ └── checklogic.go
├── server // rpc server
│ └── checkerserver.go
└── svc // 资源依赖
└── servicecontext.go
6.4.3 启动 check rpc 服务
- 在etc/check.yaml中修改服务的端口为8081, 因为8080已经被add rpc服务占用
- 执行如下指令启动check服务
$ go run check.go -f etc/check.yaml
6.5 修改API Gateway代码, 以调用 add/check rpc 服务
6.5.1 修改API Gateway的配置文件
在 api/etc/bookstore.yaml中增加如下内容:
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpcCheck:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc
注: 通过etcd自动去发现可用的add/check服务
6.5.2 修改API Gateway的logic配置文件
修改api/internal/config/config.go, 增加add/check 服务依赖, 修改如下:
type Config struct {
rest.RestConf
Add zrpc.RpcClientConf // 手动代码
Check zrpc.RpcClientConf // 手动代码
}
6.5.3 修改API Gateway的服务入口层
修改api/internal/svc/servicecontext.go文件, 如下:
type ServiceContext struct {
Config config.Config
Adder adder.Adder // 手动代码
Checker checker.Checker // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)), // 手动代码
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)), // 手动代码
}
}
注: 通过ServiceContext在不同业务逻辑之间传递依赖
6.5.4 修改API Gateway的业务逻辑层代码
-
修改api/internal/logic/addlogic.go里的Add方法, 如下:
func (l *AddLogic) Add(req *types.AddReq) (resp *types.AddResp, err error) { r, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{ Book: req.Book, Price: req.Price, }) if err != nil { return nil, err } return &types.AddResp{ Ok: r.Ok, }, nil }
注: 通过调用adder的Add方法实现添加图书到bookstore系统
-
修改 api/internal/logic/checklogic.go里的Check方法, 修改如下:
func (l *CheckLogic) Check(req *types.CheckReq) (resp *types.CheckResp,err error) { // 手动代码开始 r, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{ Book: req.Book, }) if err != nil { logx.Error(err) return &types.CheckResp{}, err } return &types.CheckResp{ Found: r.Found, Price: r.Price, }, nil // 手动代码结束 }
注: 通过调用checker的Check方法实现从bookstore系统中查询图书的价格
6.6 定义数据库表schema, 并生成CRUD+cache代码
6.6.1 创建book.sql文件
-
在bookstore下创建 rpc/model目录
-
在rpc/model目录下创建book表的schema, 如下:
CREATE TABLE `book` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', `book` varchar(255) NOT NULL COMMENT 'book name', `price` int NOT NULL COMMENT 'book price', PRIMARY KEY(`id`), KEY `index_book` (`book`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
创建DB 和 table
$ create database gozero;
$ source book.sql;
6.6.2 使用goctl 生成 CRUD+cache 代码
-
使用如下指令来生成model层的代码, -c表示用redis cache:
$ goctl model mysql ddl -c -src book.sql -dir .
注: 也可以使用datasource命令代替ddl来指定数据库链接直接从schema生成
-
生成的目录结构如下:
rpc/model ├── book.sql ├── bookstoremodel.go // CRUD+cache代码 └── vars.go // 定义常量和变量
6.6.3 修改rpc代码, 以调用crud+cache
-
修改rpc/add/etc/add.yaml 和 rpc/check/etc/check.yaml, 增加如下内容:
DataSource: root:@tcp(localhost:3306)/gozero \# mysql链接地址,满足 $user:$password@tcp($ip:$port)/$db?$queries 格式即可 Table: book Cache: - Host: localhost:6379
可以使用多个redis作为cache, 支持redis 单点或者集群
-
修改 etc/add/internal/config/config.go 和 etc/check/internal/config/config.go, 如下:
type Config struct { zrpc.RpcServerConf DataSource string // 手动代码 Cache cache.CacheConf // 手动代码 }
增加mysql 和 redis的配置
-
修改 rpc/add/internal/svc/servicecontext.go 和 rpc/check/internal/svc/servicecontext.go, 如下:
type ServiceContext struct { c config.Config Model model.BookModel // 手动代码 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ c: c, Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache), // 手动代码 } }
-
修改rpc/add/internal/logic/addlogic.go, 如下:
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) { // 手动代码开始 _, err := l.svcCtx.Model.Insert(l.ctx,&model.Book{ Book: in.Book, Price: in.Price, }) if err != nil { return nil, err } return &add.AddResp{ Ok: true, }, nil // 手动代码结束 }
-
修改rpc/check/internal/logic/checklogic.go, 如下:
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) { // 手动代码开始 resp, err := l.svcCtx.Model.FindOne(l.ctx,in.Book) if err != nil { return nil,err } return &check.CheckResp{ Found: true, Price: resp.Price, }, nil // 手动代码结束 }
6.7 完整示例调用
-
add api调用
curl -i “http://localhost:8888/add?book=go-zero&price=10” 返回如下: HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 03 Sep 2020 09:42:13 GMT Content-Length: 11 {"ok":true}
-
check api调用
curl -i “http://localhost:8888/check?book=go-zero” 返回如下: HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 03 Sep 2020 09:47:34 GMT Content-Length: 25 {"found":true,"price":10}
注: 至此, 快速搭建服务演示结束
7. 以下是一个自己根据bookstore改的项目
项目包含 usercenter 和 bookstore两个完整的微服务, 并对response和error进行了封装, 增加了全局错误拦截器, 想要了解的小伙伴可参考Github go-zero-bookstore 项目