開箱即用的微服務框架 Go-zero(進階篇)

以前咱們簡單介紹過 Go-zero 詳見《Go-zero:開箱即用的微服務框架》。此次咱們從動手實現一個 Blog 項目的用戶模塊出發,詳細講述 Go-zero 的使用。html

特別說明本文涉及的全部資料都已上傳 Github 倉庫 「kougazhang/go-zero-demo」, 感興趣的同窗能夠自行下載。前端

Go-zero 實戰項目:blog

本文以 blog 的網站後臺爲例,着重介紹一下如何使用 Go-zero 開發 blog 的用戶模塊。java

用戶模塊是後臺管理系統常見的模塊,它的功能你們也很是熟悉。管理用戶涉及到前端操做,用戶信息持久化又離不開數據庫。因此用戶模塊可謂是 "麻雀雖小五臟俱全"。本文將詳細介紹一下如何使用 go-zero 完成用戶模塊功能,如:用戶登陸、添加用戶、刪除用戶、修改用戶、查詢用戶 等(完整的 Api 文檔請參考倉庫代碼)mysql

Blog 總體架構git

blog 系統總體架構圖

最上面是 api 網關層。go-zero 須要 api 網關層來代理請求,把 request 經過 gRPC 轉發給對應的 rpc 服務去處理。這塊把具體請求轉發到對應的 rpc 服務的業務邏輯,須要手寫。golang

接下來是 rpc 服務層。上圖 rpc 服務中的 user 就是接下來向你們演示的模塊。每一個 rpc 服務能夠單獨部署。服務啓動後會把相關信息註冊到 ETCD,這樣 api 網關層就能夠經過 ECTD 發現具體服務的地址。rpc 服務處理具體請求的業務邏輯,須要手寫。redis

最後是Model 層。model 層封裝的是數據庫操做的相關邏輯。若是是查詢類的相關操做,會先查詢 redis 中是否有對應的緩存。非查詢類操做,則會直接操做 MySQL。goctl 能經過 sql 文件生成普通的 CRDU 代碼。上文也有提到,目前 goctl 這部分功能只支持 MySQL。sql

下面演示如何使用 go-zero 開發一個 blog 系統的用戶模塊。數據庫

api 網關層

編寫 blog.api 文件api

  • 生成 blog.api 文件

執行命令 goctl api -o blog.api,建立 blog.api 文件。

  • api 文件的做用

api 文件的詳細語法請參閱文檔[https://go-zero.dev/cn/api-gr...],本文按照我的理解談一談 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 文件對應的結構體在 interval/config/config.go 中。依賴管理通常會在 interval/svc/xxcontext.go 中進行封裝。須要咱們填充業務邏輯的地方是 interval/logic 目錄下的文件。

推薦閱讀

實操筆記:爲 NSQ 配置監控服務的心路歷程

go-zero:開箱即用的微服務框架

相關文章
相關標籤/搜索