目录

Go-Zero微服务案例

本文主要讲一下如何基于Go-Zero写一个简易的微服务案例,并结合ETCD进行部署,为了方便,直接把客户端和服务端写在同一个项目中。

项目分为2块,service是微服务提供两个方法,分别是获取token和解析token;client是客户端,封装了API,接收参数去调用微服务提供的两个方法,总体目录如下:

1
2
3
4
5
6
7
.
├── client # 存放API相关代码,封装为API,内部调用微服务返回数据
├── define # 配置内容
├── go.mod # mod依赖
├── go.sum
├── service # 存放微服务相关代码
└── utils # 一些工具包,包括格式化返回数据,token的加解密

提前准备

创建ETCD容器并启动

为了方便这里直接用Docker来创建ETCD容器,镜像用的是bitnami/etcd。

1
docker run -d --name etcd-server --network app-tier --publish 2379:2379 --publish 2380:2380 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 bitnami/etcd:latest

安装goctl

goctl 的最早功能是为了解决 GRPC 内网调试问题,后面逐渐成为一个代码生成脚手架。goctl 的代码生成覆盖了Go、Java、Android、iOS、TS 等多门语言,通过 goctl 可以一键生成各端代码,让开发者将精力集中在业务开发上。

Goctl 安装有两种方式,分别是 go get 或者 go install,其次是 docker。本人电脑是Mac,直接brew进行安装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Go 1.16 之前版本
$ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero/tools/goctl@latest

# Go 1.16 及以后版本
$ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest

# macOS
$ brew install goctl

# 验证
$ goctl --version
goctl version 1.3.5 darwin/amd64

# 查看 goctl
$ cd $GOPATH/bin && ls | grep "goctl"

创建一个项目

1
mkdir go-zero-microservice-demo

初始化mod

1
2
cd go-zero-microservice-demo
go mod init
关于go mod

go mod是目前比较流行的包管理工具,需要go版本1.11以上,开启配置如下

1
2
3
go env -w GOBIN=/usr/local/go/bin              #配置下go bin
go env -w GO111MODULE=on                       #开启go mod
go env -w GOPROXY=https://goproxy.cn,direct    #七牛云代理

创建服务端

创建user服务

1
2
3
4
# 此时在service文件夹下
mkdir user
cd user
vim user.proto

user.proto代码

 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
syntax = "proto3";

package user;
option go_package="./service"; //注意这个参数要跟包名一致

message CommonResponse{
string code = 1;
string msg = 2;
string data = 3;
}

message GetTokenRequest{
  int32 userId = 1;
  string groupId = 2;
  string userName = 3;
}

message GetTokenResponse{
string token = 1;
}

message AuthRequest{
string token = 1;
}

message AuthResponse{
int32 userId = 1;
string groupId = 2;
string userName = 3;
string expireTime = 4;
}

service User {
rpc GetToken(GetTokenRequest) returns (GetTokenResponse);
rpc JwtAuth(AuthRequest) returns (AuthResponse);
}

生成代码

1
goctl rpc protoc user.proto --go_out=./core --go-grpc_out=./core --zrpc_out=. -style go-zero 

此时service目录结构如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
.
├── core
│   └── service # RPC代码桩内容,用于给客户端调用方法
│       ├── user.pb.go
│       └── user_grpc.pb.go
├── etc
│   └── user.yaml # 配置文件,启动时需要添加ETCD相关参数
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   ├── get-token-logic.go # 获取token服务的代码
│   │   └── jwt-auth-logic.go # 解析token服务的代码
│   ├── server
│   │   └── user-server.go
│   └── svc
│       └── service-context.go
├── user
│   └── user.go # 客户端文件,使用时调用里面的newuser方法进行初始化
├── user.go
└── user.proto

编写服务内容

get-token-logic.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func (l *GetTokenLogic) GetToken(in *service.GetTokenRequest) (*service.GetTokenResponse, error) {
	token, err := common.GenerateToken(int(in.UserId), in.UserName, in.GroupId)
	if err != nil {
		return nil, err
	}

	return &service.GetTokenResponse{
		Token: token,
	}, nil
}

jwt-auth-logic.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (l *JwtAuthLogic) JwtAuth(in *service.AuthRequest) (*service.AuthResponse, error) {
	uc, err := common.AnalyzeToken(in.Token)
	if err != nil {
		return nil, err
	}

	return &user.AuthResponse{
		UserId:     int32(uc.Id),
		GroupId:    uc.GroupId,
		UserName:   uc.Name,
		ExpireTime: "2023-01-01 00:00:00",
	}, nil
}

修改启动配置文件

1
2
3
4
5
6
7
Name: user.rpc
ListenOn: 0.0.0.0:8080
#Mode: dev
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc # 这个key后续要用于客户端

启动服务

1
2
# 此时在service文件夹下
go run user.go -f ./etc/user.yaml

创建客户端,并封装API对外提供服务

创建API服务

1
2
3
cd client
goctl api new core
cd core

编写API内容

core.api

 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
type GetTokenRequest {
	UserId   int    `json:"user_id"`
	UserName string `json:"user_name"`
	GroupId  string `json:"group_id"`
}

type GetTokenResponse {
	Token string `json:"token"`
}

type UserInfoRequest {
	Token string `json:"token"`
}

type UserInfoResponse {
	UserName string `json:"user_ame"`
	GroupId  string `json:"group_id"`
}

service core-api {
	@handler GettokenHandler
	post /token/gettoken(GetTokenRequest) returns (GetTokenResponse)
	
	@handler UserInfoHandler
	post /user/userinfo(UserInfoRequest) returns (UserInfoResponse)
}

生成API代码

1
goctl api go -api core.api -dir . -style go_zero

此时core目录结构如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.
├── core.api # API模板
├── core.go # 服务启动入口
├── etc
│   └── core-api.yaml # 启动的配置信息
└── internal
    ├── config
    │   └── config.go # 配置文件
    ├── handler # 存放生成的API路由代码
    │   ├── gettoken_handler.go
    │   ├── routes.go
    │   └── user_info_handler.go
    ├── logic # 存放实际的业务代码
    │   ├── gettoken_logic.go
    │   └── user_info_logic.go
    ├── middleware
    ├── svc
    │   └── service_context.go # svc对象,使用微服务需要在这里初始化
    └── types
        └── types.go

微服务客户端初始化

修改service_context.go

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

import (
	"github.com/zeromicro/go-zero/zrpc"
	"go-zero-microservice-demo/client/core/internal/config"
	"go-zero-microservice-demo/service/user" // 切记这里是上面提到的客户端
)

type ServiceContext struct {
	Config config.Config
	UserRpc user.User
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
	}
}

添加业务逻辑代码

gettoken_logic.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (l *GettokenLogic) Gettoken(req *types.GetTokenRequest) (resp *types.GetTokenResponse, err error) {
	uc, err := l.svcCtx.UserRpc.GetToken(l.ctx, &user.GetTokenRequest{
		UserId:   int32(req.UserId),
		GroupId:  req.GroupId,
		UserName: req.UserName,
	})
	if err != nil {
		return nil, err
	}
	resp = &types.GetTokenResponse{}
	resp.Token = uc.Token
	return
}

user_info_logic.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (l *UserInfoLogic) UserInfo(req *types.UserInfoRequest) (resp *types.UserInfoResponse, err error) {
	uc, err := l.svcCtx.UserRpc.JwtAuth(l.ctx, &user.AuthRequest{
		Token: req.Token,
	})
	if err != nil {
		return nil, err
	}
	resp = &types.UserInfoResponse{}
	resp.UserName = uc.UserName
	resp.GroupId = uc.GroupId
	return
}

修改启动服务文件

core-api.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Name: core-api
Host: 0.0.0.0
Port: 8888

# 日志配置
Log:
  Mode: file
  Path: ./demo-logs
  KeepDays: 15
  MaxSize: 10
  Rotation: daily

UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc
    # 弱依赖,连接不上不会panic
    NonBlock: true

启动服务

1
2
# 此时在core文件夹下
go run core.go -f ./etc/core-api.yaml

调用截图

获取token /images/20230918/img.png 解析token /images/20230918/img_1.png

demo下载地址

https://github.com/kayoon/go-zero-microservice-demo

注意事项

  • 如果缺少相关依赖包,可以在项目根目录下用go mod tidy进行补充