go-zero
go-zero
前置准备
goctl
goctl 是 go-zero 的内置脚手架,是提升开发效率的一大利器,可以一键生成代码、文档、部署 k8s yaml、dockerfile 等。
go install github.com/zeromicro/go-zero/tools/goctl@latest
protoc
protoc 是一个用于生成代码的工具,它可以根据 proto 文件生成C++、Java、Python、Go、PHP 等多重语言的代码,而 gRPC 的代码生成还依赖 protoc-gen-go,protoc-gen-go-grpc 插件来配合生成 Go 语言的 gRPC 代码。
通过 goctl
可以一键安装 protoc
,protoc-gen-go
,protoc-gen-go-grpc
相关组件,你可以执行如下命令:
goctl env check --install --verbose --force
proto 语法
Protocol buffers 是 Google 的语言中立、平台中立、可扩展的结构化数据序列化机制——像 XML,但更小、更快、更简单。您定义了一次数据的结构化方式,然后您可以使用特殊生成的源代码轻松地将结构化数据写入各种数据流并使用各种语言从中读取结构化数据。
示例
// 声明 proto 语法版本,固定值
syntax = "proto3";
// proto 包名
package greet;
// 生成 golang 代码后的包名
option go_package = "example/proto/greet";
// 定义结构体
message SayHelloReq {}
message SayHelloResp {}
message SendMessageReq{
string message = 1;
}
message SendMessageResp{
int32 status = 1;
}
message GetMessageReq{
int32 id = 1;
}
message GetMessageResp{
string message = 1;
}
// 定义 Greet 服务
service Greet {
// 定义客户端流式 rpc
rpc SendMessage(stream SendMessageReq) returns (SendMessageResp);
// 定义服务端流式 rpc
rpc GetMessage(GetMessageReq) returns (stream GetMessageResp);
// 定义双向流式 rpc
rpc PushMessage(stream SendMessageReq) returns (stream GetMessageResp);
}
代码生成
Http api
goctl api new example
执行完指令后,会在当前目录下生成一个 demo 目录,该目录下包含了一个最小化的 HTTP 服务
├── demo.api
├── demo.go
├── etc
│ └── demo-api.yaml
├── go.mod
└── internal
├── config
│ └── config.go
├── handler
│ ├── demohandler.go
│ └── routes.go
├── logic
│ └── demologic.go
├── svc
│ └── servicecontext.go
└── types
└── types.go
Grpc api
goctl rpc new demo
执行完指令后,会在当前目录下生成一个 demo 目录,该目录下包含了一个最小化的 gRPC 服务
├── demo
│ ├── demo.pb.go
│ └── demo_grpc.pb.go
├── demo.go
├── demo.proto
├── democlient
│ └── demo.go
├── etc
│ └── demo.yaml
├── go.mod
└── internal
├── config
│ └── config.go
├── logic
│ └── pinglogic.go
├── server
│ └── demoserver.go
└── svc
└── servicecontext.go
- etc:静态配置文件目录
- main.go:程序启动入口文件
- internal:单个服务内部文件,其可见范围仅限当前服务
- config:静态配置文件对应的结构体声明目录
- handler:handler 目录,可选,一般 http 服务会有这一层做路由管理,
handler
为固定后缀 - logic:业务目录,所有业务编码文件都存放在这个目录下面,
logic
为固定后缀 - svc:依赖注入目录,所有 logic 层需要用到的依赖都要在这里进行显式注入
- types:结构体存放目录
api、rpc、job目录大同小异,具体详情看go-zero目录结构
Mysql model
mysql 代码生成支持从 sql 文件和数据库链接生成, 且支持生成带缓存逻辑代码。
mysql 生成的代码内容有数据表对应的 golang 结构体、CURD 操作方法,缓存逻辑等信息
以下我们以生成一个 User 结构体为例。
生成带缓存的代码
# 进入用户 Home 目录
$ cd ~
# 创建 demo 目录
$ mkdir demo && cd demo
# 生成 mongo 代码
$ goctl model mongo --type User --dir cache --cache
# 查看目录结构
$ tree
.
└── cache
├── error.go
├── usermodel.go
├── usermodelgen.go
└── usertypes.go
1 directory, 4 files
error.go
package model
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/mon"
)
var (
ErrNotFound = mon.ErrNotFound
ErrInvalidObjectId = errors.New("invalid objectId")
)
usermodel.go
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/monc"
)
var _ UserModel = (*customUserModel)(nil)
type (
// UserModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserModel.
UserModel interface {
userModel
}
customUserModel struct {
*defaultUserModel
}
)
// NewUserModel returns a model for the mongo.
func NewUserModel(url, db, collection string, c cache.CacheConf) UserModel {
conn := monc.MustNewModel(url, db, collection, c)
return &customUserModel{
defaultUserModel: newDefaultUserModel(conn),
}
}
usermodelgen.go
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"time"
"github.com/zeromicro/go-zero/core/stores/monc"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var prefixUserCacheKey = "cache:user:"
type userModel interface {
Insert(ctx context.Context, data *User) error
FindOne(ctx context.Context, id string) (*User, error)
Update(ctx context.Context, data *User) error
Delete(ctx context.Context, id string) error
}
type defaultUserModel struct {
conn *monc.Model
}
func newDefaultUserModel(conn *monc.Model) *defaultUserModel {
return &defaultUserModel{conn: conn}
}
func (m *defaultUserModel) Insert(ctx context.Context, data *User) error {
if data.ID.IsZero() {
data.ID = primitive.NewObjectID()
data.CreateAt = time.Now()
data.UpdateAt = time.Now()
}
key := prefixUserCacheKey + data.ID.Hex()
_, err := m.conn.InsertOne(ctx, key, data)
return err
}
func (m *defaultUserModel) FindOne(ctx context.Context, id string) (*User, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectId
}
var data User
key := prefixUserCacheKey + id
err = m.conn.FindOne(ctx, key, &data, bson.M{"_id": oid})
switch err {
case nil:
return &data, nil
case monc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) Update(ctx context.Context, data *User) error {
data.UpdateAt = time.Now()
key := prefixUserCacheKey + data.ID.Hex()
_, err := m.conn.ReplaceOne(ctx, key, bson.M{"_id": data.ID}, data)
return err
}
func (m *defaultUserModel) Delete(ctx context.Context, id string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectId
}
key := prefixUserCacheKey + id
_, err = m.conn.DeleteOne(ctx, key, bson.M{"_id": oid})
return err
}
usertpyes.go
package model
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
}
在以上代码生成中可以看出,每张表生成都会有 4 个文件,其中 xxmodel.go
和 xxmodelgen.go
是 model 代码文件,待有 gen.go
文件后缀的 代码一般都会包含 DO NOT EDIT
标识,因此不能在这个文件中添加自定义代码,当用户需要新增或者修改代码时,可以在 xxmodel.go
中进行编辑, 在 xxmodel.go
中我们提供了 customXXXModel
结构体便于开发者进行扩展,这里接着上文生成的无缓存代码作为示例,我们给 user 表新增一个 List 方法。
- 编辑
usermodel.go
文件 - 在
UserModel
接口中新增方法List(ctx context.Context, page, limit int) ([]*User, error)
- 实现
customUserModel
package nocache
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserModel = (*customUserModel)(nil)
type (
// UserModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserModel.
UserModel interface {
userModel
List(ctx context.Context, page, limit int) ([]*User, error)
}
customUserModel struct {
*defaultUserModel
}
)
func (c *customUserModel) List(ctx context.Context, page, limit int) ([]*User, error) {
query := fmt.Sprintf("select %s from %s limit ?,?", userRows, c.table)
var resp []*User
err := c.conn.QueryRowsCtx(ctx, &resp, query, (page-1)*limit, limit)
return resp, err
}
// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn) UserModel {
return &customUserModel{
defaultUserModel: newUserModel(conn),
}
}