go-zero 实战之 blog 系统
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 支持我们!
go-zero 实战之 blog 系统的更多相关文章
- SpringNote01.基于SpringMVC-Hibernate的Blog系统
最近,在学习Spring,做这样一个简单的blog系统,主要是让自己动手练习使用Spring,熟练的使用才干进一步的深入学习.该项目使用Maven构建,使用git进行代码管理,通过这样一个小项目,熟悉 ...
- 一个JavaWeb搭建的开源Blog系统,整合SSM框架
搬砖有暇,捣鼓了一个简单的Blog系统(项目地址https://github.com/lenve/JavaEETest/tree/master/MyBlog),适合以下人群学习: 1.已经掌握了jsp ...
- 【精编重制版】JavaWeb 入门级项目实战 -- 文章发布系统 (第二节)
说明 本教程是,原文章发布系统教程的精编重制版,会包含每一节的源码,以及修正之前的一些错误.因为之前的教程只做到了评论模块,很多地方还不完美,因此重制版会修复之前的一些谬误和阐述不清的地方,而且,后期 ...
- Spark实战电影点评系统(二)
二.通过DataFrame实战电影点评系统 DataFrameAPI是从Spark 1.3开始就有的,它是一种以RDD为基础的分布式无类型数据集,它的出现大幅度降低了普通Spark用户的学习门槛. D ...
- Spark实战电影点评系统(一)
一.通过RDD实战电影点评系统 日常的数据来源有很多渠道,如网络爬虫.网页埋点.系统日志等.下面的案例中使用的是用户观看电影和点评电影的行为数据,数据来源于网络上的公开数据,共有3个数据文件:uers ...
- 用django搭建一个简易blog系统(翻译)(四)
12. Create the templates 你需要做三件事来去掉TemplateDoesNotExist错误 第一件,创建下面目录 * netmag/netmag/templates * net ...
- 6.2 - BBS + BLOG系统
一.简介 博客系统开发: 1.注册,登录,首页 2.个人站点,分组:(分类,标签,归档)3.文章详细页4.点赞,踩灭5.评论楼,评论树6.后台管理,发布文章,文件上传7.BeautifulSoup8. ...
- Saltstack_实战指南01_系统规划
1. 实战项目GitHub地址 之前<Saltstack_使用指南>详细讲解了saltstack的使用.那么从这节开始实战讲解,当然不会再像之前那样详细说明了.只是讲一些系统规划之类的信息 ...
- 【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第十二节)
好的,那么在上一节中呢,评论功能的后台已经写好了,这一节,先把这部分后台代码和前台对接一下. 1.评论功能实现 我们修改一下保存评论按钮的点击事件,用jQuery的方式获取文本框中的值,然后通过aja ...
随机推荐
- 哲学家就餐问题-Java语言实现死锁避免
哲学家就餐问题-Java语言实现死锁避免 我死锁预防是至少破坏死锁产生的四个必要条件之一,带来的问题就是系统资源利用率低且不符合开发习惯,而死锁避免不是事先釆取某种限制措施破坏死锁的必要条件,只是注意 ...
- 并发编程之:JUC并发控制工具
大家好,我是小黑,一个在互联网苟且偷生的农民工. 在上一期我们讲了Thread.join()方法和CountDownLatch,这两者都可以做到等待一个线程执行完毕之后当前线程继续执行,并且Count ...
- Linux 动态库的编译和使用
1. 动态链接库简介 动态库又叫动态链接库,是程序运行的时候加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序.动态库是目标文件的集合,目标文件在动态库中的组织方式是按特殊的方式组 ...
- 使用Redis Stream来做消息队列和在Asp.Net Core中的实现
写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...
- Java 扫描识别条形码图片
1.条形码扫描识别的实现方法及步骤 本文以Java代码示例介绍如何来扫描和识别条形码图片.这里使用免费条码工具 Free Spire.Barcode for Java,调用BarcodeScanner ...
- Spring5框架学习笔记
Spring5学习笔记 介绍: 1.引入相应jar包 导入: ps:网上下载教程: https://repo.spring.io/release/org/springframework/spring/ ...
- wpf 多表头
WPF多表头技术探索总结 方案一:Grid+TextBlock嵌套DataGrid方式. 该方案是现在项目中已使用的方案.实现起来比较简单,但不具有通用性,不同数据DataGrid需要指定不同的Tex ...
- python学习笔记(十一)-python程序目录工程化
在一个程序当中,一般都会包含文件夹:bin.conf.lib.data.logs,以及readme文件. 所写程序存放到各自的文件夹中,如何进行串联? 首先,通过导入文件导入模块方式,引用其他人写好的 ...
- web自动化:IE11运行Python+selenium程序
from selenium import webdriver # 运行此脚本前必须按要求修改注册表'''[HKEY_CURRENT_USER\Software\Microsoft\Internet E ...
- T183637-变异距离(2021 CoE III C)【单调栈】
正题 题目链接:https://www.luogu.com.cn/problem/T183637 题目大意 给出\(n\)个二元组\((x_i,y_i)\),求最大的 \[|x_i-x_j|\time ...