红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【go-zero】快速上手

环境配置

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参数

postman

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 运行,通过接口测试工具测试

postman

从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})

且可以自动处理连接池、负载均衡和熔断