本文以 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 系統的用戶模塊。
編寫 blog.api 文件
執行命令 goctl api -o blog.api
,創建 blog.api 文件。
api 文件的詳細語法請參閱文檔 https://go-zero.dev/cn/api-grammar.html,本文按照個人理解談一談 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 文件請參考 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 相關代碼。編寫 proto 文件
使用命令 goctl rpc template -o user.proto
, 生成 user.proto 文件
user.proto 的作用是用來生成 rpc 服務的相關代碼。
protobuf 的語法已經超出了 go-zero 的范疇了,這里就不詳細展開了。
鑒于文章篇幅考慮完整的 user.proto 文件請參考 gitee 上的倉庫。
生成 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 服務的代碼。A:為什么本節要安排在 rpc 服務的后面?
Q:因為 logic 部分的內容主體就是調用對應的 user rpc 服務,所以我們必須要在 user rpc 的代碼已經生成后才能開始這部分的內容。
A:api 網關層調用 rpc 服務的步驟
Q:對這部分目錄結構不清楚的,可以參考前文 “api 網關層-api 相關代碼-目錄介紹”。
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
type?Config?struct?{ ???rest.RestConf ???//?手動添加 ???//?RpcClientConf?是?rpc?客戶端的配置,?用來解析在?blog-api.yaml?中的配置 ???User?zrpc.RpcClientConf }
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)), ???} }
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 }
編寫 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 生成的代碼注意點
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 層代碼的步驟
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
type?Config?struct?{ ???//?zrpc.RpcServerConf?表明繼承了?rpc?服務端的配置 ???zrpc.RpcServerConf ???DataSource?string??????????//?手動代碼 ???Cache??????cache.CacheConf?//?手動代碼 }
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),?//?手動代碼 ???} }
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 }
我們是在單機環境下運行整個微服務,需要啟動以下服務:
在上述服務中,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
https://gitee.com/kevwan/go-zero
歡迎使用 go-zero
并 star 支持我們!
|