0%

go-zero入门实践

说明

本次教程主要来自于这里

安装依赖

1
2
3
4
5
E:\proj\gowork\studyGoZero>go env
set GO111MODULE=on
set GOPATH=C:\Users\Administrator\go
set GOROOT=E:\app\Go
....
  • C:\Users\Administrator\go\bin\goctl.exe 拷贝到GOROOT\bin目录下
  • 查看版本成功
1
2
E:\proj\gowork> goctl --version
goctl version 1.5.4 windows/amd64
  • 安装protoc ,查看他的作用
1
goctl env check --install --verbose --force
  • 安装protoc 成功后,把C:\Users\Administrator\go\bin\ 的相关文件拷贝到GOROOT\bin

go-zero单体服务

  • 创建并初始化项目,生成greet服务为api层(对外接口)
1
2
3
4
5
6
7
E:\proj\gowork>mkdir go-zero-demo
E:\proj\gowork>cd go-zero-demo
E:\proj\gowork\go-zero-demo>go mod init go-zero-demo
go: creating new go.mod: module go-zero-demo
E:\proj\gowork\go-zero-demo>goctl api new greet
Done.
E:\proj\gowork\go-zero-demo>go mod tidy
  • 查看greet的服务目录
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
E:\proj\gowork\go-zero-demo>tree /f

│ go.mod
│ go.sum

└─greet
│ greet.api
│ greet.go

├─etc
│ greet-api.yaml

└─internal
├─config
│ config.go

├─handler
│ greethandler.go
│ routes.go

├─logic
│ greetlogic.go

├─svc
│ servicecontext.go

└─types
types.go

  • greet\greet.api 中就是对外的接口
1
2
3
4
5
6
7
8
9
10
11
12
type Request {
Name string `path:"name,options=you|me"`
}

type Response {
Message string `json:"message"`
}

service greet-api {
@handler GreetHandler
get /from/:name(Request) returns (Response) // 对外访问接口路径为:/from/name
}

编写逻辑

E:\proj\gowork\go-zero-demo\greet\internal\logic\greetlogic.go1 代码中编写如下逻辑:

1
2
3
4
5
func (l *GreetLogic) Greet(req *types.Request) (resp *types.Response, err error) {
return &types.Response{
Message: "Hello go-zero",
}, nil
}

启动并访问服务

  • 启动服务
1
2
go run .\greet.go 
Starting server at 0.0.0.0:8888...
  • 浏览器访问:http://localhost:8888/from/you

image-20230726113435097

go-zero 微服务

  • api部分其实和单体服务的创建逻辑是一样的,只是在单体服务中没有服务间的通讯而已, 且微服务中api服务会多一些rpc调用的配置。

  • 假设我们在开发一个商城项目,而开发者小明负责**用户模块(user)订单模块(order)**的开发,我们姑且将这两个模块拆分成两个微服务

演示功能目标

  • 订单服务(order)提供一个查询接口
  • 用户服务(user)提供一个方法供订单服务获取用户信息

服务设计分析

根据情景提要我们可以得知,订单是直接面向用户,通过http协议访问数据,而订单内部需要获取用户的一些基础数据,既然我们的服务是采用微服务的架构设计, 那么两个服务(user, order)就必须要进行数据交换,服务间的数据交换即服务间的通讯,到了这里,采用合理的通讯协议也是一个开发人员需要 考虑的事情,可以通过http,rpc等方式来进行通讯,这里我们选择rpc来实现服务间的通讯,相信这里我已经对”rpc服务存在有什么作用?”已经作了一个比较好的场景描述。 当然,一个服务开发前远不止这点设计分析,我们这里就不详细描述了。从上文得知,我们需要一个

  • user rpc
  • order api

两个服务来初步实现这个小demo。

创建工程

1
2
3
E:\proj\gowork>mkdir study-gozero-demo
E:\proj\gowork>cd study-gozero-demo
E:\proj\gowork\study-gozero-demo>go mod init study-gozero-demo

创建user rpc服务

  • 创建user rpc服务的目录里
1
E:\proj\gowork\study-gozero-demo\>mkdir -p mall/user/rpc

注意在win中,我使用的git窗口编写的mkdri命令,在cmd中这样创建有问题

  • 添加user.proto文件,增加getUser方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
syntax = "proto3";

package user;

// protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成
option go_package = "./user";

message IdRequest {
string id = 1;
}

message UserResponse {
// 用户id
string id = 1;
// 用户名称
string name = 2;
// 用户性别
string gender = 3;
}

service User {
rpc getUser(IdRequest) returns(UserResponse); //对外开发的接口
}
  • 生成rpc目录和代码
1
2
3
4
E:\proj\gowork\study-gozero-demo\mall\user\rpc> goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.


Done.

vs code ide的终端中执行的此命令.最终生成的go服务源代码文件在目录:E:\proj\gowork\study-gozero-demo\mall\user\rpc\userclient\user.go

  • 填充业务逻辑其实就是user.proto中生成的getUser,目录为:E:\proj\gowork\study-gozero-demo\mall\user\rpc\internal\logic\getuserlogic.go
1
2
3
4
5
6
7
8
9
10
func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
// todo: add your logic here and delete this line

// 新增的代码
return &user.UserResponse{
Id: "1",
Name: "test",
}, nil
}

若发现这里引用包报错,可以使用go mod tidy 下载依赖

创建order api服务

  • 创建 order api服务
1
2
3
Administrator@WIN-20230710BAT MINGW64 /e/proj/gowork/study-gozero-demo/mall
$ mkdir -p order/api && cd order/api

git 窗口执行

  • 编写添加api文件目录为:E:\proj\gowork\study-gozero-demo\mall\order\api\order.api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type(
OrderReq {
Id string `path:"id"`
}

OrderReply {
Id string `json:"id"`
Name string `json:"name"`
}
)
service order {
@handler getOrder
get /api/order/get/:id (OrderReq) returns (OrderReply)
}
  • 生成order服务,非rpc
1
PS E:\proj\gowork\study-gozero-demo\mall\order\api> goctl api go -api order.api -dir .
  • 添加user rpc配置目录地址为

    E:\proj\gowork\study-gozero-demo\mall\order\api\internal\config\config.go

1
2
3
4
5
6
7
8
9
10
11
12
package config

import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
rest.RestConf
UserRpc zrpc.RpcClientConf // 这个就是新增的user rpc服务
}

  • 添加yaml配置目录为E\proj\gowork\study-gozero-demo\mall\order\api\etc\order.yaml
1
2
3
4
5
6
7
8
9
Name: order
Host: 0.0.0.0
Port: 8888
UserRpc: -- 从这里开始为新增
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc

  • 完善服务依赖目录为

    E:\proj\gowork\study-gozero-demo\mall\order\api\internal\svc\servicecontext.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package svc

import (
"study-gozero-demo/mall/order/api/internal/config"
"study-gozero-demo/mall/user/rpc/userclient" // 引用最终生成user服务代码路径

"github.com/zeromicro/go-zero/zrpc"
)

type ServiceContext struct {
Config config.Config
UserRpc userclient.User // 引用User服务中的GetUser函数
}

func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
//加入user rpc服务
UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
}
}

  • 添加order演示逻辑

    E:\proj\gowork\study-gozero-demo\mall\order\api\internal\logic\getorderlogic 添加业务逻辑

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package logic

import (
"context"
"errors"

"study-gozero-demo/mall/order/api/internal/svc"
"study-gozero-demo/mall/order/api/internal/types"

// 引用
"study-gozero-demo/mall/user/rpc/user"

"github.com/zeromicro/go-zero/core/logx"
)

type GetOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrderLogic {
return &GetOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx, // 引用order的服务,然后才能调用order服务中的user rpc
}
}

func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (resp *types.OrderReply, err error) {
// todo: add your logic here and delete this line

// 新增代码,注意这里的&user,应用的是user rpc的user/user.pb.go中内容,IdRequest其实就是对应入参(user.proto定义的)
// 获取订单之前,需要盘点用户的id是否存在,若不存在则返回用户不存在报错,若存在则返回test order
user, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
Id: "1",
})
if err != nil {
return nil, err
}

if user.Name != "test" {
return nil, errors.New("用户不存在")
}

return &types.OrderReply{
Id: req.Id,
Name: "test order",
}, nil
}

启动服务并验证

  • 启动本地etcd

注意本地安装etcd,打开E:\app\etcd-v3.4.27-windows-amd64\etcd.exe启动服务

etcd是一个分布式一致性键值存储,其主要用于分布式系统的共享配置和服务发现。

  • 启动user rpc
1
E:\proj\gowork\study-gozero-demo\mall\user\rpc> go run .\user.go
  • 启动order api
1
2
3
PS E:\proj\gowork\study-gozero-demo\mall\order\api> go run order.go
Starting server at 0.0.0.0:8888...
{"@timestamp":"2023-07-26T16:36:58.057+08:00","caller":"stat/usage.go:61","content":"CPU: 0m, MEMORY: Alloc=3.2Mi, TotalAlloc=6.1Mi, Sys=17.9Mi, NumGC=3","level":"stat"}
  • 访问order api

image-20230726164009157

总结

如上代码展示了,单体服务和微服务分别运用,下一篇文章我们将会加上数据库、jwt鉴权、以及具体的增删改查