环境配置
1
2
3
4
5
6
7
8
# 1. 安装 goctl(代码生成工具)
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 2. 脚手架生成一个 API 服务
goctl api new greet
# 3. 拉取依赖并运行
cd greet && go mod tidy && go run greet.go -f etc/greet-api.yaml在浏览器中打开
http://localhost:8888/from/world,即可看到响应
项目创建
从零开始
1
2
3
4
5
6
7
# HTTP API 服务
goctl api new myservice
cd myservice && go mod tidy
# gRPC 服务
goctl rpc new myservice
cd myservice && go mod tidy已有文件生成
若已有.api或.proto文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 从 .api 文件
mkdir myservice && cd myservice
go mod init myservice
goctl api go -api myservice.api -dir .
go mod tidy
# 从 .proto 文件
mkdir myservice && cd myservice
go mod init myservice
goctl rpc protoc myservice.proto \
--go_out=./pb \
--go-grpc_out=./pb \
--zrpc_out=.
go mod tidy重新生成已有文件
修改文件后重新生成而不覆盖业务逻辑:
1
2
3
4
5
6
7
8
# API:重新生成,保留 internal/logic/
goctl api go -api myservice.api -dir .
# RPC:重新生成,保留 internal/logic/
goctl rpc protoc myservice.proto \
--go_out=./pb \
--go-grpc_out=./pb \
--zrpc_out=.API服务
以一个用户服务为例
编写API DSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
syntax = "v1"
type (
LoginReq {
Username string `json:"username"`
Password string `json:"password"`
}
LoginResp {
Token string `json:"token"`
}
UserInfoReq {
Id int64 `path:"id"`
}
UserInfoResp {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
)
service user-api {
@handler Login
post /user/login (LoginReq) returns (LoginResp)
@handler GetUserInfo
get /user/:id (UserInfoReq) returns (UserInfoResp)
}生成服务
使用命令生成服务:
1
2
3
goctl api go -api user.api -dir .
go mod init user-api
go mod tidy生成的结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user-api/
├── etc/
│ └── user-api.yaml
├── internal/
│ ├── config/config.go
│ ├── handler/
│ │ ├── loginhandler.go # 自动生成,不要编辑
│ │ ├── getuserinfohandler.go # 自动生成,不要编辑
│ │ └── routes.go
│ ├── logic/
│ │ ├── loginlogic.go # ← 在这里实现
│ │ └── getuserinfologic.go # ← 在这里实现
│ ├── svc/servicecontext.go
│ └── types/types.go # 从 DSL 自动生成
└── user.go若在DSL变更后重新申城,直接调用goctl api go -api user.api -dir .,goctl不会覆盖logic/文件
实现逻辑
在internal/logic中实现业务逻辑
编辑loginlogic.go
1
2
3
4
5
6
7
8
func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
if req.Username == "admin" && req.Password == "123456" {
return &types.LoginResp{
Token: "mock-jwt-token-for-" + req.Username,
}, nil
}
return nil, errors.New("invalid username or password")
}编辑getuserinfologic.go
1
2
3
4
5
6
7
func (l *GetUserInfoLogic) GetUserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
return &types.UserInfoResp{
Id: req.Id,
Username: "alice",
Email: "alice@example.com",
}, nil
}运行测试
使用go run user.go运行,并通过测试工具测试
在postman中填入raw json,发送后可以收到200 OK,并读到我们的token
注意,标签是json时需要自己写raw json,而不是填query参数
RPC服务
创建项目
1
2
3
goctl rpc new user
cd user
go mod tidy生成服务骨架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
user/
├── etc/
│ └── user.yaml
├── internal/
│ ├── config/config.go
│ ├── logic/
│ │ └── getuserlogic.go # ← 在这里实现
│ ├── server/userserver.go # gRPC 服务端适配器
│ └── svc/servicecontext.go
├── user/
│ └── user.pb.go # 生成的 protobuf 类型
│ └── user_grpc.pb.go # 生成的 gRPC 桩代码
├── user.go # 主入口
└── user.proto # 源定义并且会自动生成protobuf文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax = "proto3";
package user;
option go_package="./user";
message Request {
string ping = 1;
}
message Response {
string pong = 1;
}
service User {
rpc Ping(Request) returns(Response);
}若protobuf变更后重新生成,使用
1
2
3
4
goctl rpc protoc user.proto \
--go_out=./user \
--go-grpc_out=./user \
--zrpc_out=.实现逻辑
编辑internal/logic文件夹下的文件实现逻辑
1
2
3
4
5
6
func (l *GetUserLogic) GetUser(in *user.GetUserRequest) (*user.GetUserResponse, error) {
return &user.GetUserResponse{
Id: in.Id,
Name: "alice",
}, nil
}配置运行
配置文件位于etc/user.yaml,可以看到rpc服务名称、监听端口等
1
2
Name: user.rpc
ListenOn: 0.0.0.0:8080使用go run user.go 运行,通过接口测试工具测试
从api服务中调用
使用以下指令生成api网关使用的RPC客户端桩代码
1
2
3
4
goctl rpc protoc user.proto \
--go_out=./user \
--go-grpc_out=./user \
--zrpc_out=./client然后在servicecontext.go中导入client包
1
2
3
4
type ServiceContext struct {
Config config.Config
UserRpc userclient.User // 从生成的 client 包中引入
}这样就可以在业务逻辑中像调用本地方法一样发起rpc调用
1
resp, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.GetUserRequest{Id: 1})且可以自动处理连接池、负载均衡和熔断