go-zero 實戰(zhàn)之 blog 系統(tǒng)

go-zero 實戰(zhàn)項目:blog

本文以 blog 的網(wǎng)站后臺為例榨咐,著重介紹一下如何使用 go-zero 開發(fā) blog 的用戶模塊漂坏。

本文涉及的所有資料都已上傳 github 倉庫 kougazhang/go-zero-demo帆吻,感興趣的同學可以自行下載展运。

用戶模塊是后臺管理系統(tǒng)常見的模塊,它的功能大家也非常熟悉铅忿。管理用戶涉及到前端操作剪决,用戶信息持久化又離不開數(shù)據(jù)庫。所以用戶模塊可謂是 "麻雀雖小五臟俱全"檀训。本文將詳細介紹一下如何使用 go-zero 完成用戶模塊功能柑潦,如:用戶登錄、添加用戶峻凫、刪除用戶渗鬼、修改用戶、查詢用戶 等(完整的 api 文件請參考倉庫代碼)荧琼。

blog 整體架構(gòu)

blog 系統(tǒng)整體架構(gòu)圖

最上面是 api 網(wǎng)關層譬胎。go-zero 需要 api 網(wǎng)關層來代理請求,把 request 通過 gRPC 轉(zhuǎn)發(fā)給對應的 rpc 服務去處理命锄。這塊把具體請求轉(zhuǎn)發(fā)到對應的 rpc 服務的業(yè)務邏輯堰乔,需要手寫。

接下來是 rpc 服務層累舷。上圖 rpc 服務中的 user 就是接下來向大家演示的模塊浩考。每個 rpc 服務可以單獨部署。服務啟動后會把相關信息注冊到 ETCD被盈,這樣 api 網(wǎng)關層就可以通過 ECTD 發(fā)現(xiàn)具體服務的地址析孽。rpc 服務處理具體請求的業(yè)務邏輯搭伤,需要手寫。

最后是 model 層袜瞬。model 層封裝的是數(shù)據(jù)庫操作的相關邏輯怜俐。如果是查詢類的相關操作,會先查詢 redis 中是否有對應的緩存邓尤。非查詢類操作拍鲤,則會直接操作 MySQL。goctl 能通過 sql 文件生成普通的 CRDU 代碼汞扎。目前 goctl 這部分功能支持 MySQL季稳、PostgreSQL、MongoDB澈魄。

下面演示如何使用 go-zero 開發(fā)一個 blog 系統(tǒng)的用戶模塊递沪。

api 網(wǎng)關層

編寫 blog.api 文件

  • 生成 blog.api 文件

執(zhí)行命令 goctl api -o blog.api齿梁,創(chuàng)建 blog.api 文件逞泄。

  • api 文件的作用

api 文件的詳細語法請參閱文檔 https://go-zero.dev/cn/api-grammar.html担租,本文按照個人理解談一談 api 文件的作用和基礎語法。

api 文件是用來生成 api 網(wǎng)關層的相關代碼的鲫构。

  • api 文件的語法

api 文件的語法和 Golang 語言非常類似浓恶,type 關鍵字用來定義結(jié)構(gòu)體,service 部分用來定義 api 服務结笨。

type 定義的結(jié)構(gòu)體包晰,主要是用來聲明請求的入?yún)⒑头祷刂档模?request 和 response.

service 定義的 api 服務炕吸,則聲明了路由杜窄,handler,request 和 response.

具體內(nèi)容請結(jié)合下面的默認的生成的 api 文件進行理解算途。

// 聲明版本,可忽略
syntax = "v1"

// 聲明一些項目信息蚀腿,可忽略
info(
   title: // TODO: add title
   desc: // TODO: add description
   author: "zhao.zhang"
   email: "zhao.zhang@upai.com"
)

// 重要配置
// request 是結(jié)構(gòu)體的名稱嘴瓤,可以使用 type 關鍵詞定義新的結(jié)構(gòu)體
type request {
   // TODO: add members here and delete this comment
   // 與 golang 語言一致,這里聲明結(jié)構(gòu)體的成員
}

// 語法同上莉钙,只是業(yè)務含義不同廓脆。response 一般用來聲明返回值。
type response {
   // TODO: add members here and delete this comment
}

// 重要配置
// blog-api 是 service 的名稱.
service blog-api {
   // GetUser 是處理請求的視圖函數(shù)
   @handler GetUser // TODO: set handler name and delete this comment
   // get 聲明了該請求使用 GET 方法
   // /users/id/:userId 是 url磁玉,:userId 表明是一個變量
   // request 就是上面 type 定義的那個 request, 是該請求的入?yún)?   // 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 相關代碼

  • 生成相關的代碼

執(zhí)行命令 goctl api go -api blog.api -dir . 席赂,生成 api 相關代碼吮铭。

  • 目錄介紹
├── blog.api # api 文件
├── blog.go # 程序入口文件
├── etc
│   └── blog-api.yaml # api 網(wǎng)關層配置文件
├── go.mod
├── go.sum
└── internal
    ├── config
    │   └── config.go # 配置文件
    ├── handler # 視圖函數(shù)層, 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 中定義的結(jié)構(gòu)體映射為真正的 golang 結(jié)構(gòu)體
        └── types.go
  • 文件間的調(diào)用關系
img

因為到此時還沒涉及到 rpc 服務颅停,所以 api 內(nèi)各模塊的調(diào)用關系就是非常簡單的單體應用間的調(diào)用關系谓晌。routers.go 是路由,根據(jù) request Method 和 url 把請求分發(fā)到對應到的 handler 上癞揉,handler 內(nèi)部會去調(diào)用對應的 logic. logic 文件內(nèi)是我們注入代碼邏輯的地方纸肉。

小結(jié)

Api 層相關命令:

  • 執(zhí)行命令 goctl api -o blog.api, 創(chuàng)建 blog.api 文件。
  • 執(zhí)行命令 goctl api go -api blog.api -dir .喊熟,生成 api 相關代碼柏肪。
  • 加參數(shù) goctl 也可以生成其他語言的 api 層的文件,比如 java芥牌、ts 等烦味,這里就不展開了。

rpc 服務

編寫 proto 文件

  • 生成 user.proto 文件

使用命令 goctl rpc template -o user.proto, 生成 user.proto 文件

  • user.proto 文件的作用

user.proto 的作用是用來生成 rpc 服務的相關代碼胳泉。

protobuf 的語法已經(jīng)超出了 go-zero 的范疇了拐叉,這里就不詳細展開了。

  • 編寫 user.proto 文件

鑒于文章篇幅考慮完整的 user.proto 文件請參考 gitee 上的倉庫扇商。

生成 rpc 相關代碼

  • 生成 user rpc 服務相關代碼

使用命令 goctl rpc proto -src user.proto -dir . 生成 user rpc 服務的代碼凤瘦。

小結(jié)

rpc 服務相關命令:

  • 使用命令 goctl rpc template -o user.proto, 生成 user.proto 文件
  • 使用命令 goctl rpc proto -src user.proto -dir . 生成 user rpc 服務的代碼。

api 服務調(diào)用 rpc 服務

A:為什么本節(jié)要安排在 rpc 服務的后面案铺?

Q:因為 logic 部分的內(nèi)容主體就是調(diào)用對應的 user rpc 服務蔬芥,所以我們必須要在 user rpc 的代碼已經(jīng)生成后才能開始這部分的內(nèi)容。

A:api 網(wǎng)關層調(diào)用 rpc 服務的步驟

Q:對這部分目錄結(jié)構(gòu)不清楚的控汉,可以參考前文 “api 網(wǎng)關層-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) 創(chuàng)建了一個 grpc 客戶端
      User:   users.NewUsers(zrpc.MustNewClient(c.User)),
   }
}
  • 編輯各個 logic 文件乎婿,這里以 internal/logic/loginlogic.go 為例
func (l *LoginLogic) Login(req types.ReqUser) (*types.RespLogin, error) {
   // 調(diào)用 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 文件

編寫創(chuàng)建表的 SQL 文件 user.sql, 并在數(shù)據(jù)庫中執(zhí)行。

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 ., 會生成操作數(shù)據(jù)庫的 CRDU 的代碼街佑。

此時的 model 目錄:

├── user.sql # 手寫
├── usermodel.go # 自動生成
└── vars.go # 自動生成

model 生成的代碼注意點

  • model 這塊代碼使用的是拼接 SQL 語句谢翎,可能會存在 SQL 注入的風險。
  • 生成 CRUD 的代碼比較初級沐旨,需要我們手動編輯 usermodel.go 文件森逮,自己拼接業(yè)務需要的 SQL。參見 usermdel.go 中的 FindByName 方法磁携。

rpc 調(diào)用 model 層的代碼

rpc 目錄結(jié)構(gòu)

rpc 服務我們只需要關注下面加注釋的文件或目錄即可褒侧。

├── etc
│   └── user.yaml # 配置文件,數(shù)據(jù)庫的配置寫在這
├── internal
│   ├── config
│   │   └── config.go # config.go 是 yaml 對應的結(jié)構(gòu)體
│   ├── logic # 填充業(yè)務邏輯的地方
│   │   ├── 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 調(diào)用 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
}

微服務運行演示

我們是在單機環(huán)境下運行整個微服務烟央,需要啟動以下服務:

  • Redis
  • Mysql
  • Etcd
  • go run blog.go -f etc/blog-api.yaml
  • go run user.go -f etc/user.yaml

在上述服務中,rpc 服務要先啟動这吻,然后網(wǎng)關層再啟動吊档。

在倉庫中我封裝了 start.sh 和 stop.sh 腳本來分別在單機環(huán)境下運行和停止微服務。

好了唾糯,通過上述六個步驟怠硼,blog 用戶模塊的常見功能就完成了。

最后再幫大家強調(diào)下重點移怯,除了 goctl 常用的命令需要熟練掌握香璃,go-zero 文件命名也是有規(guī)律可循的。配置文件是放在 etc 目錄下的 yaml 文件舟误,該 yaml 文件對應的結(jié)構(gòu)體在 internal/config/config.go 中葡秒。依賴管理一般會在 internal/svc/servicecontext.go 中進行封裝。需要我們填充業(yè)務邏輯的地方是 internal/logic 目錄下的文件嵌溢。

致謝

感謝 又拍云 供稿眯牧!

項目地址

https://github.com/zeromicro/go-zero

歡迎使用 go-zerostar 支持我們!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赖草,一起剝皮案震驚了整個濱河市学少,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秧骑,老刑警劉巖版确,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異乎折,居然都是意外死亡绒疗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門骂澄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吓蘑,“玉大人,你說我怎么就攤上這事坟冲∈啃蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵樱衷,是天一觀的道長。 經(jīng)常有香客問我酒唉,道長矩桂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮侄榴,結(jié)果婚禮上雹锣,老公的妹妹穿的比我還像新娘。我一直安慰自己癞蚕,他們只是感情好蕊爵,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桦山,像睡著了一般攒射。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恒水,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天会放,我揣著相機與錄音,去河邊找鬼钉凌。 笑死咧最,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的御雕。 我是一名探鬼主播矢沿,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酸纲!你這毒婦竟也來了捣鲸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤福青,失蹤者是張志新(化名)和其女友劉穎摄狱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體无午,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡媒役,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宪迟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酣衷。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖次泽,靈堂內(nèi)的尸體忽然破棺而出穿仪,到底是詐尸還是另有隱情,我是刑警寧澤意荤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布啊片,位于F島的核電站,受9級特大地震影響玖像,放射性物質(zhì)發(fā)生泄漏紫谷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笤昨。 院中可真熱鬧祖驱,春花似錦、人聲如沸瞒窒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崇裁。三九已至匕坯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寇壳,已是汗流浹背醒颖。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壳炎,地道東北人泞歉。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像匿辩,于是被迫代替她去往敵國和親腰耙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容