go-zero实战

文档地址

官方examples

前提:

安装 protoc, protoc-gen-go, goctl

api

  1. clone 项目或者 生成目录, init go mod
mkdir zeroService && cd zeroService && go mod init zeroService
  1. 限制grpc版本, 打开go.mod 加入replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
module zeroService

go 1.15

replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
  1. 创建api和rpc目录
mkdir rpc && mkdir api
  1. 创建api文档, api语法
cd api
vim user.api

type HttpResponse {
        Code int         `json:"code"`
        Msg  string      `json:"msg"`
        Data interface{} `json:"data"`
}

type (
        RegisterReq {
                UserName string `form:"username"`
                Pwd      string `form:"pwd"`
                NickName string `form:"nickname"`
                Age      int    `form:"age"`
        }
        RegisterRsp {
                Rid int `json:"rid"`
        }
)

type (
        InfoReq {
                Rid int `form:"rid"`
        }
        InfoRsp {
                Rid      int    `json:"rid"`
                UserName string `json:"username"`
                Pwd      string `json:"pwd"`
                NickName string `json:"nickname"`
                Age      int    `json:"age"`
        }
)

// 用户相关api
service user-api{
        @doc "用户注册"
        @handler register
        post /register (RegisterReq) returns (HttpResponse)
        
        @doc "获取用户信息"
        @handler info
        get /info (InfoReq) returns (HttpResponse)
}


  1. 生成项目
goctl api go -api user.api -dir .


./
├── api
│   ├── etc
│   │   └── user-api.yaml //配置文件
│   ├── internal
│   │   ├── config
│   │   │   └── config.go
│   │   ├── handler
│   │   │   ├── infohandler.go
│   │   │   ├── registerhandler.go
│   │   │   └── routes.go
│   │   ├── logic
│   │   │   ├── infologic.go //业务逻辑
│   │   │   └── registerlogic.go //业务逻辑
│   │   ├── svc
│   │   │   └── servicecontext.go
│   │   └── types
│   │       └── types.go
│   ├── user.api
│   └── user.go
├── go.mod
└── rpc
  1. 修改配置文件
vim etc/user-api.yaml

Name: user-api
Host: 0.0.0.0
Port: 48888
DataSource: zmwb:realize2012@tcp(127.0.0.1:3306)/zero
Cache:
  - Host: 127.0.0.1:6379
Log:
  Model: console
  
vim internal/config/config.go

//加入下面两个配置声明
type Config struct {
        rest.RestConf
        DataSource string //新加
        Cache      cache.CacheConf //新加
}
  1. 设计数据库
CREATE TABLE `userinfo` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(255) not null,
`nickname` varchar(255) not null,
`age` int(10) not null default 0,
`pwd` varchar(255) not null,
`created_at` datetime NOT NULL,
`updated_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
key `n_idx` (`username`),
key `q_idx` (`age`, `nickname`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


//生成model文件, 目录在api/model里
goctl model mysql datasource -url="zmwb:realize2012@tcp(127.0.0.1:3306)/zero" -table="*"  -dir="./model"

//修改服务上下文, 注入model
vim internal/svc/servicecontext.go


type ServiceContext struct {
        Config config.Config
        Model  model.UserinfoModel //新加
}

func NewServiceContext(c config.Config) *ServiceContext {
        return &ServiceContext{
                Config: c,
                Model:  model.NewUserinfoModel(sqlx.NewMysql(c.DataSource)), //新加
        }
}

  1. 实现业务逻辑, 两个接口, 用这种方式实现info接口, 另一个接口后面用rpc方式实现
vim internal/logic/infologic.go

func (l *InfoLogic) Info(req types.InfoReq) (*types.HttpResponse, error) {
        // 加入实现逻辑
        userinfo, err := l.svcCtx.Model.FindOne(int64(req.Rid))
        rsp := types.HttpResponse{}
        if err != nil {
                rsp.Code = 2
                rsp.Msg = "not found"
                return &rsp, err
        }
        rsp.Data = types.InfoRsp{
                Rid:      int(userinfo.Id),
                UserName: userinfo.Username,
                NickName: userinfo.Nickname,
                Age:      int(userinfo.Age),
        }
        return &rsp, nil
}

  1. 启动服务
go run user.go

//进行接口测试
curl -v http://127.0.0.1:48888/info\?rid\=1

rpc

  1. 编写pb文件
cd ../rpc
vim userService.proto


syntax = "proto3";

package userService;


message RegisterRequest {
    string username = 1;
    string nickname = 2;
    string pwd = 3;
    int64  age = 4;
}

message RegisterResponse {
    int64 rid = 1;
}

service UserService {
    //注册
    rpc Register (RegisterRequest) returns (RegisterResponse);

}

  1. 根据pb文件生成rpc代码
goctl rpc proto -src userService.proto  -dir .

//执行后项目结构
./
├── api
│   ├── etc
│   │   └── user-api.yaml
│   ├── internal
│   │   ├── config
│   │   │   └── config.go
│   │   ├── handler
│   │   │   ├── infohandler.go
│   │   │   ├── registerhandler.go
│   │   │   └── routes.go
│   │   ├── logic
│   │   │   ├── infologic.go
│   │   │   └── registerlogic.go
│   │   ├── svc
│   │   │   └── servicecontext.go
│   │   ├── table.sql
│   │   └── types
│   │       └── types.go
│   ├── model
│   │   ├── userinfomodel.go
│   │   └── vars.go
│   ├── user.api
│   └── user.go
├── go.mod
├── go.sum
└── rpc
    ├── etc
    │   └── userservice.yaml
    ├── internal
    │   ├── config
    │   │   └── config.go
    │   ├── logic
    │   │   └── registerlogic.go
    │   ├── server
    │   │   └── userserviceserver.go
    │   └── svc
    │       └── servicecontext.go
    ├── userService
    │   └── userService.pb.go
    ├── userService.proto
    ├── userservice.go
    └── userserviceclient
        └── userservice.go
  1. 修改配置文件
vim etc/userservice.yaml

Name: userservice.rpc
ListenOn: 127.0.0.1:48080
Etcd:
  Hosts:
  - 127.0.0.1:2379 //可以指向自己部署的etcd, 用于服务发现
  Key: userservice.rpc
DataSource: zmwb:realize2012@tcp(127.0.0.1:3306)/zero
Cache:
  - Host: 127.0.0.1:6379
Log:
  Model: console

  
vim internal/config/config.go

//加入下面两个配置声明
type Config struct {
        zrpc.RpcServerConf
        DataSource string          //新加
        Cache      cache.CacheConf //新加
}
  1. 注入model等
//修改服务上下文, 注入model
vim internal/svc/servicecontext.go


type ServiceContext struct {
        Config config.Config
        Model  model.UserinfoModel //新加
}

func NewServiceContext(c config.Config) *ServiceContext {
        return &ServiceContext{
                Config: c,
                Model:  model.NewUserinfoModel(sqlx.NewMysql(c.DataSource)), //新加
        }
}

  1. 实现业务逻辑
vim internal/logic/registerlogic.go

// 注册
func (l *RegisterLogic) Register(in *userService.RegisterRequest) (*userService.RegisterResponse, error) {
        user := model.Userinfo{
                Username: in.Username,
                Nickname: in.Nickname,
                Pwd: in.Pwd,
                Age: in.Age,
        }
        res, err := l.svcCtx.Model.Insert(user)
        if err != nil {
                return nil, err
        }
        rid, err := res.LastInsertId()
        if err != nil {
                return nil, err
        }
        return &userService.RegisterResponse{Rid: rid}, nil
}

  1. api 中注册rpc客户端
cd ../api

vim etc/user-api.yaml
//加入以下几行服务发现配置
Rpc:
  Etcd:
    Hosts:
    - 127.0.0.1:2379
    Key: userservice.rpc
        

vim internal/config/config.go 
type Config struct {
        rest.RestConf
        DataSource string
        Cache      cache.CacheConf
        Rpc        zrpc.RpcClientConf //新加
}

vim internal/svc/servicecontext.go

type ServiceContext struct {
        Config         config.Config
        Model          model.UserinfoModel
        UserServiceRpc userserviceclient.UserService //新加
}

func NewServiceContext(c config.Config) *ServiceContext {
        return &ServiceContext{
                Config:         c,
                Model:          model.NewUserinfoModel(sqlx.NewMysql(c.DataSource)),
                UserServiceRpc: userserviceclient.NewUserService(zrpc.MustNewClient(c.Rpc)), //新加
        }
}

  1. api 中实现register逻辑
vim internal/logic/registerlogic.go 

func (l *RegisterLogic) Register(req types.RegisterReq) (*types.HttpResponse, error) {

        in := &userService.RegisterRequest{
                Username: req.UserName,
                Nickname: req.NickName,
                Pwd:      req.Pwd,
                Age:      int64(req.Age),
        }
        regRsp, err := l.svcCtx.UserServiceRpc.Register(l.ctx, in)
        if err != nil {
                return nil, err
        }
        rsp := types.HttpResponse{}

        rsp.Data = types.RegisterRsp{
                Rid: int(regRsp.Rid),
        }
        return &rsp, nil
}

  1. 启动api 和 rpc 服务
go run user.go &
go run userservice.go &

//进行接口测试
  1. docker 部署
goctl docker -go rpc/userService.go -port 48080
goctl docker -go api.user.go -port 48888
//会在两个目录中生成两个dockerfile
//build 两个镜像
docker build -t user.service:v1 
docker build -t user.api:v1 .
//build 失败, 看Dockerfile发现很多目录写死, 可能是要固定的机器上把目录生成好, 才能build成功

//启动两个容器

总结

  1. goctl功能很好用
  2. 部分细节比较固定, 比如, mysql的time.Time有些情况不行, 大部分是ok, goctl docker功能参数太少了, 如果要使用的话可能需要改一改
  3. 看介绍, goctl model生成如果配置了缓存的话, 会自动缓存很多数据, 减轻数据库压力, 这一块算是个特色