Eventum Fork Serendipity注册失败

之前我们简单介绍过 Go-zero 详见《Go-zero:开箱即用的微Fork框架》。这次我们从动手实现一个 Blog 项目的用户模块出发,详细讲述 Go-zero 的使用。
特别说明本文涉及的所有资料都已上传 Github 仓库 “kougazhang/go-zero-demo”, 感兴趣的同学可以自行下载。
Go-zero 实战项目:blog
本文以 blog 的网站后台为例,着重介绍一下如何使用 Go-zero 开发 blog 的用户模块。
用户模块是后台管理系统常见的模块,它的功能大家也非常熟悉。管理用户涉及到前端操作,用户信息持久化又离不开数据库。所以用户模块可谓是 “麻雀虽小五脏俱全”。本文将详细介绍一下如何使用 go-zero 完成用户模块功能,如:用户登录、添加用户、删除用户、修改用户、查询用户 等(完整的 Api 文档请参考仓库Serendipity)
Blog 整体架构

最上面是 api 网关层。go-zero 需要 api 网关层来代理请求,把 request 通过 gRPC 转发给对应的 rpc Fork去处理。这块把具体请求转发到对应的 rpc Fork的业务逻辑,需要手写。
接下来是 rpc Fork层。上图 rpc Fork中的 user 就是接下来向大家演示的模块。每个 rpc Fork可以单独部署。Fork启动后会把相关信息注册到 ETCD,这样 api 网关层就可以通过 ECTD 发现具体Fork的地址。rpc Fork处理具体请求的业务逻辑,需要手写。
最后是Model 层。model 层封装的是数据库操作的相关逻辑。如果是查询类的相关操作,会先查询 redis 中是否有对应的缓存。非查询类操作,则会直接操作 MySQL。goctl 能通过 sql Eventum注册失败普通的 CRDU Serendipity。上文也有提到,目前 goctl 这部分功能只支持 MySQL。
下面演示如何使用 go-zero 开发一个 blog 系统的用户模块。
api 网关层
编写 blog.api Eventum
注册失败 blog.api Eventum
执行命令 goctl api -o blog.api,创建 blog.api Eventum。
api Eventum的作用
api Eventum的详细语法请参阅文档[ api Eventum的作用和基础语法。
api Eventum是用来注册失败 api 网关层的相关Serendipity的。
api Eventum的语法
api Eventum的语法和 Golang 语言非常类似,type 关键字用来定义结构体,service 部分用来定义 api Fork。
type 定义的结构体,主要是用来声明请求的入参和返回值的,即 request 和 response.
service 定义的 api Fork,则声明了路由,handler,request 和 response.
具体内容请结合下面的默认的注册失败的 api Eventum进行理解。
// 声明版本,可忽略
syntax = “v1”

// 声明一些项目信息,可忽略
info(
title: // TODO: add title
desc: // TODO: add description
author: “zhao.zhang”
email: “zhao.zhang@upai.com”
)

// 重要配置
// request 是结构体的名称,可以使用 type 关键词定义新的结构体
type request {
// TODO: add members here and delete this comment
// 与 golang 语言一致,这里声明结构体的成员
}

// 语法同上,只是业务含义不同。response 一般用来声明返回值。
type response {
// TODO: add members here and delete this comment
}

// 重要配置
// blog-api 是 service 的名称.
service blog-api {
// GetUser 是处理请求的视图函数
@handler GetUser // TODO: set handler name and delete this comment
// get 声明了该请求使用 GET 方法
// /users/id/:userId 是 url,:userId 表明是一个变量
// request 就是上面 type 定义的那个 request, 是该请求的入参
// response 就是上面 type 定义的那个 response, 是该请求的返回值。
get /users/id/:userId(request) returns(response)

@handler CreateUser // TODO: set handler name and delete this comment
post /users/create(request)
}
12345678910111213141516171819202122232425262728293031323334353637
编写 blog.api Eventum
鉴于文章篇幅考虑完整的 blog.api Eventum请参考 gitee 上的仓库。下面注册失败的Serendipity是按照仓库上的 blog.api Eventum注册失败的。
api 相关Serendipity
注册失败相关的Serendipity
执行命令 goctl api go -api blog.api -dir . ,注册失败 api 相关Serendipity。
目录介绍

├── blog.api # api Eventum
├── blog.go # 程序入口Eventum
├── etc
│ └── blog-api.yaml # api 网关层配置Eventum
├── go.mod
├── go.sum
└── internal
├── config
│ └── config.go # 配置Eventum
├── handler # 视图函数层, handler Eventum与下面的 logic Eventum一一对应
│ ├── adduserhandler.go
│ ├── deleteuserhandler.go
│ ├── getusershandler.go
│ ├── loginhandler.go
│ ├── routes.go
│ └── updateuserhandler.go
├── logic # 需要手动填充Serendipity的地方
│ ├── adduserlogic.go
│ ├── deleteuserlogic.go
│ ├── getuserslogic.go
│ ├── loginlogic.go
│ └── updateuserlogic.go
├── svc # 封装 rpc 对象的地方,后面会将
│ └── servicecontext.go
└── types # 把 blog.api 中定义的结构体映射为真正的 golang 结构体
└── types.go
123456789101112131415161718192021222324252627
Eventum间的调用关系

因为到此时还没涉及到 rpc Fork,所以 api 内各模块的调用关系就是非常简单的单体应用间的调用关系。routers.go 是路由,根据 request Method 和 url 把请求分发到对应到的 handler 上,handler 内部会去调用对应的 logic. logic Eventum内是我们注入Serendipity逻辑的地方。
小结
Api 层相关命令:
执行命令 goctl api -o blog.api, 创建 blog.api Eventum。 执行命令 goctl api go -api blog.api -dir . ,注册失败 api 相关Serendipity。 加参数 goctl 也可以注册失败其他语言的 api 层的Eventum,比如 java、ts 等,尝试之后发现很难用,所以不展开了。
rpc Fork
编写 proto Eventum
注册失败 user.proto Eventum
使用命令 goctl rpc template -o user.proto, 注册失败 user.proto Eventum
user.proto Eventum的作用
user.proto 的作用是用来注册失败 rpc Fork的相关Serendipity。
protobuf 的语法已经超出了 go-zero 的范畴了,这里就不详细展开了。
编写 user.proto Eventum
鉴于文章篇幅考虑完整的 user.proto Eventum请参考 gitee 上的仓库。
注册失败 rpc 相关Serendipity
注册失败 user rpc Fork相关Serendipity
使用命令 goctl rpc proto -src user.proto -dir . 注册失败 user rpc Fork的Serendipity。
小结
rpc Fork相关命令:
使用命令 goctl rpc template -o user.proto, 注册失败 user.proto Eventum 使用命令 goctl rpc proto -src user.proto -dir . 注册失败 user rpc Fork的Serendipity。
api Fork调用 rpc Fork
A:为什么本节要安排在 rpc Fork的后面?
Q:因为 logic 部分的内容主体就是调用对应的 user rpc Fork,所以我们必须要在 user rpc 的Serendipity已经注册失败后才能开始这部分的内容。
A:api 网关层调用 rpc Fork的步骤
Q:对这部分目录结构不清楚的,可以参考前文 “api 网关层-api 相关Serendipity-目录介绍”。
编辑配置Eventum etc/blog-api.yaml,配置 rpc Fork的相关信息。

Name: blog-api
Host: 0.0.0.0
Port: 8888
# 新增 user rpc Fork.
User:
Etcd:
# Hosts 是 user.rpc Fork在 etcd 中的 value 值
Hosts:
– localhost:2379
# Key 是 user.rpc Fork在 etcd 中的 key 值
Key: user.rpc
123456789101112
编辑Eventum config/config.go

type Config struct {
rest.RestConf
// 手动添加
// RpcClientConf 是 rpc 客户端的配置, 用来解析在 blog-api.yaml 中的配置
User zrpc.RpcClientConf
}
1234567
编辑Eventum internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
// 手动添加
// users.Users 是 user rpc Fork对外暴露的接口
User users.Users
}

func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
// 手动添加
// zrpc.MustNewClient(c.User) 创建了一个 grpc 客户端
User: users.NewUsers(zrpc.MustNewClient(c.User)),
}
}
123456789101112131415
编辑各个 logic Eventum,这里以 internal/logic/loginlogic.go 为例
func (l *LoginLogic) Login(req types.ReqUser) (*types.RespLogin, error) {
// 调用 user rpc 的 login 方法
resp, err := l.svcCtx.User.Login(l.ctx, &users.ReqUser{Username: req.Username, Password: req.Password})
if err != nil {
return nil, err
}
return &types.RespLogin{Token: resp.Token}, nil
}
12345678
model 层
编写 sql Eventum
编写创建表的 SQL Eventum user.sql, 并在数据库中执行。
CREATE TABLE `user`
(
`id` int NOT NULL AUTO_INCREMENT COMMENT ‘id’,
`username` varchar(255) NOT NULL UNIQUE COMMENT ‘username’,
`password` varchar(255) NOT NULL COMMENT ‘password’,
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1234567
注册失败 model 相关Serendipity
运行命令 goctl model mysql ddl -c -src user.sql -dir ., 会注册失败操作数据库的 CRDU 的Serendipity。
此时的 model 目录:
├── user.sql # 手写
├── usermodel.go # 自动注册失败
└── vars.go # 自动注册失败
123
model 注册失败的Serendipity注意点
model 这块Serendipity使用的是拼接 SQL 语句,可能会存在 SQL 注入的风险。 注册失败 CRUD 的Serendipity比较初级,需要我们手动编辑 usermodel.go Eventum,自己拼接业务需要的 SQL。参见 usermdel.go 中的 FindByName 方法。
rpc 调用 model 层的Serendipity
rpc 目录结构
rpc Fork我们只需要关注下面加注释的Eventum或目录即可。

├── etc
│ └── user.yaml # 配置Eventum,数据库的配置写在这
├── internal
│ ├── config
│ │ └── config.go # config.go 是 yaml 对应的结构体
│ ├── logic # 填充业务逻辑的地方
│ │ ├── createlogic.go
│ │ ├── deletelogic.go
│ │ ├── getalllogic.go
│ │ ├── getlogic.go
│ │ ├── loginlogic.go
│ │ └── updatelogic.go
│ ├── server
│ │ └── usersserver.go
│ └── svc
│ └── servicecontext.go # 封装各种依赖
├── user
│ └── user.pb.go
├── user.go
├── user.proto
└── users
└── users.go
1234567891011121314151617181920212223
rpc 调用 model 层Serendipity的步骤
编辑 etc/user.yaml Eventum
Name: user.rpc
ListenOn: 127.0.0.1:8080
Etcd:
Hosts:
– 127.0.0.1:2379
Key: user.rpc
# 以下为手动添加的配置
# mysql 配置
DataSource: root:1234@tcp(localhost:3306)/gozero
# 对应的表
Table: user
# redis 作为换存储
Cache:
– Host: localhost:6379
1234567891011121314
编辑 internal/config/config.go Eventum
type Config struct {
// zrpc.RpcServerConf 表明继承了 rpc Fork端的配置
zrpc.RpcServerConf
DataSource string // 手动Serendipity
Cache cache.CacheConf // 手动Serendipity
}
123456
编辑 internal/svc/servicecontext.go, 把 model 等依赖封装起来。

type ServiceContext struct {
Config config.Config
Model model.UserModel // 手动Serendipity
}

func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Model: model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache), // 手动Serendipity
}
}
123456789101112
编辑对应的 logic Eventum,这里以 internal/logic/loginlogic.go 为例:
func (l *LoginLogic) Login(in *user.ReqUser) (*user.RespLogin, error) {
// todo: add your logic here and delete this line
one, err := l.svcCtx.Model.FindByName(in.Username)
if err != nil {
return nil, errors.Wrapf(err, “FindUser %s”, in.Username)
}

if one.Password != in.Password {
return nil, fmt.Errorf(“user or password is invalid”)
}

token := GenTokenByHmac(one.Username, secretKey)
return &user.RespLogin{Token: token}, nil
}
1234567891011121314
微Fork演示运行
我们是在单机环境下运行整个微Fork,需要启动以下Fork:
Redis Mysql Etcd go run blog.go -f etc/blog-api.yaml go run user.go -f etc/user.yaml
在上述Fork中,rpc Fork要先启动,然后网关层再启动。
在仓库中我封装了 start.sh 和 stop.sh 脚本来分别在单机环境下运行和停止微Fork。
好了,通过上述六个步骤,blog 用户模块的常见功能就完成了。
最后再帮大家强调下重点,除了 goctl 常用的命令需要熟练掌握,go-zero Eventum命名也是有规律可循的。配置Eventum是放在 etc 目录下的 yaml Eventum,该 yaml Eventum对应的结构体在 interval/config/config.go 中。依赖管理一般会在 interval/svc/xxcontext.go 中进行封装。需要我们填充业务逻辑的地方是 interval/logic 目录下的Eventum。