之前我们简单介绍过 Go-zero 详见《Go-zero:开箱即用的微服务框架》。这次我们从动手实现一个 Blog 项目的用户模块出发,详细讲述 Go-zero 的使用。

特别说明本文涉及的所有资料都已上传 Github 仓库 “kougazhang/go-zero-demo”, 感兴趣的同学可以自行下载。

Go-zero 实战项目:blog

本文以 blog 的网站后台为例,着重介绍一下如何使用 Go-zero 开发 blog 的用户模块。

用户模块是后台管理系统常见的模块,它的功能大家也非常熟悉。管理用户涉及到前端操作,用户信息持久化又离不开数据库。所以用户模块可谓是 "麻雀虽小五脏俱全"。本文将详细介绍一下如何使用 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。

下面演示如何使用 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 文件进行理解。

  1. // 声明版本,可忽略
  2. syntax = "v1"
  3. // 声明一些项目信息,可忽略
  4. info(
  5. title: // TODO: add title
  6. desc: // TODO: add description
  7. author: "zhao.zhang"
  8. email: "zhao.zhang@upai.com"
  9. )
  10. // 重要配置
  11. // request 是结构体的名称,可以使用 type 关键词定义新的结构体
  12. type request {
  13. // TODO: add members here and delete this comment
  14. // 与 golang 语言一致,这里声明结构体的成员
  15. }
  16. // 语法同上,只是业务含义不同。response 一般用来声明返回值。
  17. type response {
  18. // TODO: add members here and delete this comment
  19. }
  20. // 重要配置
  21. // blog-api 是 service 的名称.
  22. service blog-api {
  23. // GetUser 是处理请求的视图函数
  24. @handler GetUser // TODO: set handler name and delete this comment
  25. // get 声明了该请求使用 GET 方法
  26. // /users/id/:userId 是 url,:userId 表明是一个变量
  27. // request 就是上面 type 定义的那个 request, 是该请求的入参
  28. // response 就是上面 type 定义的那个 response, 是该请求的返回值。
  29. get /users/id/:userId(request) returns(response)
  30. @handler CreateUser // TODO: set handler name and delete this comment
  31. post /users/create(request)
  32. }
  • 编写 blog.api 文件

鉴于文章篇幅考虑完整的 blog.api 文件请参考 gitee 上的仓库。下面生成的代码是按照仓库上的 blog.api 文件生成的。

api 相关代码

  • 生成相关的代码

执行命令 goctl api go -api blog.api -dir . ,生成 api 相关代码。

  • 目录介绍

  1. ├── blog.api # api 文件
  2. ├── blog.go # 程序入口文件
  3. ├── etc
  4. └── blog-api.yaml # api 网关层配置文件
  5. ├── go.mod
  6. ├── go.sum
  7. └── internal
  8. ├── config
  9. └── config.go # 配置文件
  10. ├── handler # 视图函数层, handler 文件与下面的 logic 文件一一对应
  11. ├── adduserhandler.go
  12. ├── deleteuserhandler.go
  13. ├── getusershandler.go
  14. ├── loginhandler.go
  15. ├── routes.go
  16. └── updateuserhandler.go
  17. ├── logic # 需要手动填充代码的地方
  18. ├── adduserlogic.go
  19. ├── deleteuserlogic.go
  20. ├── getuserslogic.go
  21. ├── loginlogic.go
  22. └── updateuserlogic.go
  23. ├── svc # 封装 rpc 对象的地方,后面会将
  24. └── servicecontext.go
  25. └── types # 把 blog.api 中定义的结构体映射为真正的 golang 结构体
  26. └── 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 服务的相关信息。

  1. Name: blog-api
  2. Host: 0.0.0.0
  3. Port: 8888
  4. # 新增 user rpc 服务.
  5. User:
  6. Etcd:
  7. # Hosts 是 user.rpc 服务在 etcd 中的 value 值
  8. Hosts:
  9. - localhost:2379
  10. # Key 是 user.rpc 服务在 etcd 中的 key 值
  11. Key: user.rpc
  • 编辑文件 config/config.go

  1. type Config struct {
  2. rest.RestConf
  3. // 手动添加
  4. // RpcClientConf 是 rpc 客户端的配置, 用来解析在 blog-api.yaml 中的配置
  5. User zrpc.RpcClientConf
  6. }
  • 编辑文件 internal/svc/servicecontext.go
  1. type ServiceContext struct {
  2. Config config.Config
  3. // 手动添加
  4. // users.Users 是 user rpc 服务对外暴露的接口
  5. User users.Users
  6. }
  7. func NewServiceContext(c config.Config) *ServiceContext {
  8. return &ServiceContext{
  9. Config: c,
  10. // 手动添加
  11. // zrpc.MustNewClient(c.User) 创建了一个 grpc 客户端
  12. User: users.NewUsers(zrpc.MustNewClient(c.User)),
  13. }
  14. }
  • 编辑各个 logic 文件,这里以 internal/logic/loginlogic.go 为例
  1. func (l *LoginLogic) Login(req types.ReqUser) (*types.RespLogin, error) {
  2. // 调用 user rpc 的 login 方法
  3. resp, err := l.svcCtx.User.Login(l.ctx, &users.ReqUser{Username: req.Username, Password: req.Password})
  4. if err != nil {
  5. return nil, err
  6. }
  7. return &types.RespLogin{Token: resp.Token}, nil
  8. }

model 层

编写 sql 文件

编写创建表的 SQL 文件 user.sql, 并在数据库中执行。

  1. CREATE TABLE `user`
  2. (
  3. `id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
  4. `username` varchar(255) NOT NULL UNIQUE COMMENT 'username',
  5. `password` varchar(255) NOT NULL COMMENT 'password',
  6. PRIMARY KEY(`id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

生成 model 相关代码

运行命令 goctl model mysql ddl -c -src user.sql -dir ., 会生成操作数据库的 CRDU 的代码。

此时的 model 目录:

  1. ├── user.sql # 手写
  2. ├── usermodel.go # 自动生成
  3. └── vars.go # 自动生成

model 生成的代码注意点

  • model 这块代码使用的是拼接 SQL 语句,可能会存在 SQL 注入的风险。

  • 生成 CRUD 的代码比较初级,需要我们手动编辑 usermodel.go 文件,自己拼接业务需要的 SQL。参见 usermdel.go 中的 FindByName 方法。

rpc 调用 model 层的代码

rpc 目录结构

rpc 服务我们只需要关注下面加注释的文件或目录即可。


  1. ├── etc
  2. └── user.yaml # 配置文件,数据库的配置写在这
  3. ├── internal
  4. ├── config
  5. └── config.go # config.go 是 yaml 对应的结构体
  6. ├── logic # 填充业务逻辑的地方
  7. ├── createlogic.go
  8. ├── deletelogic.go
  9. ├── getalllogic.go
  10. ├── getlogic.go
  11. ├── loginlogic.go
  12. └── updatelogic.go
  13. ├── server
  14. └── usersserver.go
  15. └── svc
  16. └── servicecontext.go # 封装各种依赖
  17. ├── user
  18. └── user.pb.go
  19. ├── user.go
  20. ├── user.proto
  21. └── users
  22. └── users.go

rpc 调用 model 层代码的步骤

  • 编辑 etc/user.yaml 文件
  1. Name: user.rpc
  2. ListenOn: 127.0.0.1:8080
  3. Etcd:
  4. Hosts:
  5. - 127.0.0.1:2379
  6. Key: user.rpc
  7. # 以下为手动添加的配置
  8. # mysql 配置
  9. DataSource: root:1234@tcp(localhost:3306)/gozero
  10. # 对应的表
  11. Table: user
  12. # redis 作为换存储
  13. Cache:
  14. - Host: localhost:6379
  • 编辑 internal/config/config.go 文件
  1. type Config struct {
  2. // zrpc.RpcServerConf 表明继承了 rpc 服务端的配置
  3. zrpc.RpcServerConf
  4. DataSource string // 手动代码
  5. Cache cache.CacheConf // 手动代码
  6. }
  • 编辑 internal/svc/servicecontext.go, 把 model 等依赖封装起来。

  1. type ServiceContext struct {
  2. Config config.Config
  3. Model model.UserModel // 手动代码
  4. }
  5. func NewServiceContext(c config.Config) *ServiceContext {
  6. return &ServiceContext{
  7. Config: c,
  8. Model: model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache), // 手动代码
  9. }
  10. }
  • 编辑对应的 logic 文件,这里以 internal/logic/loginlogic.go 为例:
  1. func (l *LoginLogic) Login(in *user.ReqUser) (*user.RespLogin, error) {
  2. // todo: add your logic here and delete this line
  3. one, err := l.svcCtx.Model.FindByName(in.Username)
  4. if err != nil {
  5. return nil, errors.Wrapf(err, "FindUser %s", in.Username)
  6. }
  7. if one.Password != in.Password {
  8. return nil, fmt.Errorf("user or password is invalid")
  9. }
  10. token := GenTokenByHmac(one.Username, secretKey)
  11. return &user.RespLogin{Token: token}, nil
  12. }

微服务演示运行

我们是在单机环境下运行整个微服务,需要启动以下服务:

  • 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 文件对应的结构体在 interval/config/config.go 中。依赖管理一般会在 interval/svc/xxcontext.go 中进行封装。需要我们填充业务逻辑的地方是 interval/logic 目录下的文件。

开箱即用的微服务框架 Go-zero(进阶篇)的更多相关文章

  1. go-zero:开箱即用的微服务框架

    go-zero 是一个集成了各种工程实践的 Web 和 rpc 框架,它的弹性设计保障了大并发服务端的稳定性,并且已经经过了充分的实战检验. go-zero 在设计时遵循了 "工具大于约定和 ...

  2. 深入浅出微服务框架dubbo(一):基础篇

    一.基础篇 1.1 开篇说明 dubbo是一个分布式服务框架,致力于提供高性能透明化RPC远程调用方案,提供SOA服务治理解决方案.本文旨在将对dubbo的使用和学习总结起来,深入源码探究原理,以备今 ...

  3. 微服务框架Lagom介绍之一

    背景 Lagom是JAVA系下响应式 微服务框架,在阅读本文之前请先阅读微服务架构设计,Lagom与其他微服务框架相比,与众不同的特性包括: 目前,大多数已有的微服务框架关注于简化单个微服务的构建-- ...

  4. go-zero:微服务框架

    go-zero 是一个集成了各种工程实践的 Web 和 rpc 框架,它的弹性设计保障了大并发服务端的稳定性,并且已经经过了充分的实战检验. go-zero 在设计时遵循了 "工具大于约定和 ...

  5. 为构建大型复杂系统而生的微服务框架 Erda Infra

    作者|宋瑞国(尘醉) 来源|尔达 Erda 公众号 ​ 导读:Erda Infra 微服务框架是从 Erda 项目演进而来,并且完全开源.Erda 基于 Erda Infra 框架完成了大型复杂项目的 ...

  6. 如何基于gRPC沟通微服务框架

    本文我们来讲解一下如何使用 gRPC构建微服务,gRPC是一个开源框架,可用于构建可扩展且高性能的微服务并创建服务之间的通信. 背景 随着企业越来越多地转向微服务,对构建这些微服务的低延迟和可扩展框架 ...

  7. 基于thrift的微服务框架

    前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...

  8. 基于spring-boot的rest微服务框架

    周末在家研究spring-boot,参考github上的一些开源项目,整了一个rest微服务框架,取之于民,用之于民,在github上开源了,地址如下: https://github.com/yjmy ...

  9. [goa]golang微服务框架学习--安装使用

      当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码, ...

随机推荐

  1. NumPy之:理解广播

    目录 简介 基础广播 广播规则 简介 广播描述的是NumPy如何计算不同形状的数组之间的运算.如果是较大的矩阵和较小的矩阵进行运算的话,较小的矩阵就会被广播,从而保证运算的正确进行. 本文将会以具体的 ...

  2. prometheus nginx-module-vts删除内存区数据

    项目地址:https://github.com/vozlt/nginx-module-vts 删除所zone内存中的数据 curl localhost/status/control?cmd=delet ...

  3. [bug] Python:“TabError: inconsistent use of tabs and spaces in indentation”

    原因 代码中混用了Tab和4个空格 参考 https://blog.csdn.net/dongdong9223/article/details/82745068

  4. [Java] javaEE

    定义 面向企业级应用中一些通用模块制定的标准 避免重复开发,解决代码可靠性问题 13种规范 JDBC(JavaDatabase Connectivity):数据库连接 以统一方式访问数据库的API J ...

  5. linux服务之NTP及chrony时间同步

    博客园 首页 联系 管理   linux服务之NTP及chrony时间同步   一.NTP时间同步 NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步 ...

  6. stress工具使用指南和结果分析(好好好测试通过)

    stress工具使用指南和结果分析 佛心看世界关注 0.1152019.05.13 09:17:35字数 547阅读 1,112 #stress `stress' imposes certain ty ...

  7. 11.6 mpstat:CPU信息统计

        mpstat 是Multiprocessor Statistics的缩写,是一种实时系统监控工具.mpstat命令会输出CPU的一些统计信息,这些信息存放在/proc/stat文件中.在多CP ...

  8. gcc 编译过程详解-(转自CarpenterLee)

    前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...

  9. Django(39)使用redis配置缓存

    前言   动态网站的基本权衡是,它们是动态的.每次用户请求页面时,Web服务器都会进行各种计算 - 从数据库查询到模板呈现再到业务逻辑 - 以创建站点访问者看到的页面.从处理开销的角度来看,这比标准的 ...

  10. 学习Git的一些总结

    Git是以后公司工作必不可少的,所以早点了解使用它是很有必要的 一般国外的开源是GitHub 国内的是码云Gitee 至于git的安装教程,这里就不啰嗦啦,面向百度即可,安装完成鼠标右键会多几个选项: ...