go-zero 实战我的项目:blog
本文以 blog
的网站后盾为例,着重介绍一下如何应用 go-zero
开发 blog
的用户模块。
本文波及的所有材料都已上传 github 仓库 kougazhang/go-zero-demo
,感兴趣的同学能够自行下载。
用户模块是后盾管理系统常见的模块,它的性能大家也十分相熟。治理用户波及到前端操作,用户信息长久化又离不开数据库。所以用户模块堪称是 “ 麻雀虽小五脏俱全 ”。本文将具体介绍一下如何应用 go-zero 实现用户模块性能,如:用户登录、增加用户、删除用户、批改用户、查问用户 等(残缺的 api 文件请参考仓库代码)。
blog 整体架构
最下面是 api 网关层。go-zero 须要 api 网关层来代理申请,把 request 通过 gRPC 转发给对应的 rpc 服务去解决。这块把具体申请转发到对应的 rpc 服务的业务逻辑,须要手写。
接下来是 rpc 服务层。上图 rpc 服务中的 user 就是接下来向大家演示的模块。每个 rpc 服务能够独自部署。服务启动后会把相干信息注册到 ETCD,这样 api 网关层就能够通过 ECTD 发现具体服务的地址。rpc 服务解决具体申请的业务逻辑,须要手写。
最初是 model
层。model 层封装的是数据库操作的相干逻辑。如果是查问类的相干操作,会先查问 redis 中是否有对应的缓存。非查问类操作,则会间接操作 MySQL。goctl 能通过 sql 文件生成一般的 CRDU 代码。目前 goctl 这部分性能反对 MySQL、PostgreSQL、MongoDB。
上面演示如何应用 go-zero 开发一个 blog 零碎的用户模块。
api 网关层
编写 blog.api 文件
- 生成 blog.api 文件
执行命令 goctl api -o blog.api
,创立 blog.api 文件。
- api 文件的作用
api 文件的具体语法请参阅文档 https://go-zero.dev/cn/api-grammar.html,本文依照集体了解谈一谈 api 文件的作用和根底语法。
api 文件是用来生成 api 网关层的相干代码的。
- api 文件的语法
api 文件的语法和 Golang 语言十分相似,type 关键字用来定义构造体,service 局部用来定义 api 服务。
type 定义的构造体,次要是用来申明申请的入参和返回值的,即 request 和 response.
service 定义的 api 服务,则声明了路由,handler,request 和 response.
具体内容请联合上面的默认的生成的 api 文件进行了解。
// 申明版本,可疏忽
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)
}
- 编写 blog.api 文件
鉴于文章篇幅思考残缺的 blog.api 文件请参考 gitee 上的仓库。上面生成的代码是依照仓库上的 blog.api 文件生成的。
api 相干代码
- 生成相干的代码
执行命令 goctl api go -api blog.api -dir .,生成 api 相干代码。
- 目录介绍
├── blog.api # api 文件
├── blog.go # 程序入口文件
├── etc
│ └── blog-api.yaml # api 网关层配置文件
├── go.mod
├── go.sum
└── internal
├── config
│ └── config.go # 配置文件
├── handler # 视图函数层, handler 文件与上面的 logic 文件一一对应
│ ├── adduserhandler.go
│ ├── deleteuserhandler.go
│ ├── getusershandler.go
│ ├── loginhandler.go
│ ├── routes.go
│ └── updateuserhandler.go
├── logic # 须要手动填充代码的中央
│ ├── adduserlogic.go
│ ├── deleteuserlogic.go
│ ├── getuserslogic.go
│ ├── loginlogic.go
│ └── updateuserlogic.go
├── svc # 封装 rpc 对象的中央,前面会将
│ └── servicecontext.go
└── types # 把 blog.api 中定义的构造体映射为真正的 golang 构造体
└── types.go
- 文件间的调用关系
因为到此时还没波及到 rpc 服务,所以 api 内各模块的调用关系就是非常简单的单体利用间的调用关系。routers.go 是路由,依据 request Method 和 url 把申请散发到对应到的 handler 上,handler 外部会去调用对应的 logic. logic 文件内是咱们注入代码逻辑的中央。
小结
Api 层相干命令:
- 执行命令
goctl api -o blog.api
, 创立 blog.api 文件。 - 执行命令
goctl api go -api blog.api -dir .
,生成 api 相干代码。 - 加参数 goctl 也能够生成其余语言的 api 层的文件,比方 java、ts 等,这里就不开展了。
rpc 服务
编写 proto 文件
- 生成 user.proto 文件
应用命令 goctl rpc template -o user.proto
, 生成 user.proto 文件
- user.proto 文件的作用
user.proto 的作用是用来生成 rpc 服务的相干代码。
protobuf 的语法曾经超出了 go-zero 的领域了,这里就不具体开展了。
- 编写 user.proto 文件
鉴于文章篇幅思考残缺的 user.proto 文件请参考 gitee 上的仓库。
生成 rpc 相干代码
- 生成 user rpc 服务相干代码
应用命令 goctl rpc proto -src user.proto -dir .
生成 user rpc 服务的代码。
小结
rpc 服务相干命令:
- 应用命令
goctl rpc template -o user.proto
, 生成 user.proto 文件 - 应用命令
goctl rpc proto -src user.proto -dir .
生成 user rpc 服务的代码。
api 服务调用 rpc 服务
A:为什么本节要安顿在 rpc 服务的前面?
Q:因为 logic 局部的内容主体就是调用对应的 user rpc 服务,所以咱们必须要在 user rpc 的代码曾经生成后能力开始这部分的内容。
A:api 网关层调用 rpc 服务的步骤
Q:对这部分目录构造不分明的,能够参考前文“api 网关层 -api 相干代码 - 目录介绍”。
- 编辑配置文件 etc/blog-api.yaml,配置 rpc 服务的相干信息。
Name: blog-api
Host: 0.0.0.0
Port: 8888
# 新增 user rpc 服务.
User:
Etcd:
# Hosts 是 user.rpc 服务在 etcd 中的 value 值
Hosts:
- localhost:2379
# Key 是 user.rpc 服务在 etcd 中的 key 值
Key: user.rpc
- 编辑文件 config/config.go
type Config struct {
rest.RestConf
// 手动增加
// RpcClientConf 是 rpc 客户端的配置, 用来解析在 blog-api.yaml 中的配置
User zrpc.RpcClientConf
}
- 编辑文件 internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
// 手动增加
// users.Users 是 user rpc 服务对外裸露的接口
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)),
}
}
- 编辑各个 logic 文件,这里以 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
}
model 层
编写 sql 文件
编写创立表的 SQL 文件 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
生成 model 相干代码
运行命令 goctl model mysql ddl -c -src user.sql -dir .
, 会生成操作数据库的 CRDU 的代码。
此时的 model 目录:
├── user.sql # 手写
├── usermodel.go # 主动生成
└── vars.go # 主动生成
model 生成的代码留神点
- model 这块代码应用的是拼接 SQL 语句,可能会存在 SQL 注入的危险。
- 生成 CRUD 的代码比拟高级,须要咱们手动编辑 usermodel.go 文件,本人拼接业务须要的 SQL。参见 usermdel.go 中的 FindByName 办法。
rpc 调用 model 层的代码
rpc 目录构造
rpc 服务咱们只须要关注上面加正文的文件或目录即可。
├── etc
│ └── user.yaml # 配置文件,数据库的配置写在这
├── 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
rpc 调用 model 层代码的步骤
- 编辑 etc/user.yaml 文件
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
- 编辑 internal/config/config.go 文件
type Config struct {
// zrpc.RpcServerConf 表明继承了 rpc 服务端的配置
zrpc.RpcServerConf
DataSource string // 手动代码
Cache cache.CacheConf // 手动代码
}
- 编辑 internal/svc/servicecontext.go, 把 model 等依赖封装起来。
type ServiceContext struct {
Config config.Config
Model model.UserModel // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Model: model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache), // 手动代码
}
}
- 编辑对应的 logic 文件,这里以 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
}
微服务运行演示
咱们是在单机环境下运行整个微服务,须要启动以下服务:
- Redis
- Mysql
- Etcd
- go run blog.go -f etc/blog-api.yaml
- go run user.go -f etc/user.yaml
在上述服务中,rpc 服务要先启动,而后网关层再启动。
在仓库中我封装了 start.sh 和 stop.sh 脚本来别离在单机环境下运行和进行微服务。
好了,通过上述六个步骤,blog 用户模块的常见性能就实现了。
最初再帮大家强调下重点,除了 goctl
罕用的命令须要熟练掌握,go-zero
文件命名也是有法则可循的。配置文件是放在 etc 目录下的 yaml 文件,该 yaml 文件对应的构造体在 internal/config/config.go
中。依赖治理个别会在 internal/svc/servicecontext.go
中进行封装。须要咱们填充业务逻辑的中央是 internal/logic
目录下的文件。
致谢
感激 又拍云
供稿!
我的项目地址
https://github.com/zeromicro/go-zero
欢送应用 go-zero
并 star 反对咱们!