天天看點

GoFrame系列:4、項目結構及開發明細

GoFrame系列:4、項目結構及開發明細

文章目錄

  • ​​GoFrame系列:4、項目結構及開發明細​​
  • ​​1. 前言​​
  • ​​2. 項目結構​​
  • ​​3. 分層設計​​
  • ​​3.1 控制器​​
  • ​​3.2 業務邏輯​​
  • ​​3.3 資料通路​​
  • ​​3.4 模型定義​​
  • ​​3.5 模闆解析​​
  • ​​4. 資料庫設計​​
  • ​​5. 包名設計​​
  • ​​5.1 包名約定​​
  • ​​5.2 包名設計​​
  • ​​6. 控制器實作​​
  • ​​6.1 結構化限制​​
  • ​​6.2 結構體轉換​​
  • ​​6.3 資料校驗​​
  • ​​6.4 資料傳參​​
  • ​​6.4 實作代碼​​
  • ​​7. 上下文變量​​
  • ​​7.1 結構定義​​
  • ​​7.2 邏輯封裝​​
  • ​​7.3 上下文變量注入​​
  • ​​7.4 上下文變量使用​​
  • ​​8. 中間件使用​​
  • ​​8.1 跨域處理​​
  • ​​8.2 鑒權處理​​
  • ​​8.3 上下文注入​​
  • ​​9. 業務邏輯封裝​​
  • ​​9.1 職責劃分​​
  • ​​9.2 資料校驗​​
  • ​​9.3 内部引用​​
  • ​​9.4 實作代碼​​
  • ​​10. 資料檔案建立​​
  • ​​10.1 代碼生成​​
  • ​​10.2 `dao`使用​​
  • ​​10.3 `model`使用​​
  • ​​11. Swagger生成​​
  • ​​11.1 `swagger`編寫​​
  • ​​11.2 `swagger`生成​​
  • ​​11.3 `swagger`插件​​
  • ​​11.4 `swagger`文檔檢視​​
  • ​​12. 基礎類庫存放​​
  • ​​13. 最後​​

1. 前言

上節我們已經安裝demo并測試搭建該架構并運作的demo,也發現該架構搭建web背景是比較友善的,接下來我們了解應用的目錄結構并了解demo是如何進行開發的,以此來看一下我們後續自己開發web如何在該架構中展開。

從業務開發上基本分為四大部分:api、dao、model、service

2. 項目結構

如果是​

​Package​

​源碼包項目,開發者可随意定義目錄結構。

如果是業務類型項目,​

​GoFrame​

​​官方推薦的​

​Go​

​項目目錄結構如下:

/
├── app
│   ├── api
│   ├── dao
│   ├── model
│   └── service
├── boot
├── config
├── docker
├── document
├── i18n
├── library
├── packed
├── public
├── router
├── template
├── Dockerfile
├── go.mod
└── main.go      
目錄/檔案名稱 說明 描述

​app​

業務邏輯層 所有的業務邏輯存放目錄。
- ​

​api​

業務接口 接收/解析使用者輸入參數的入口/接口層。
- ​

​dao​

資料通路 資料庫的通路操作,僅包含最基礎的資料庫​

​CURD​

​方法
- ​

​model​

結構模型 資料結構管理子產品,管理資料實體對象,以及輸入與輸出資料結構定義
- ​

​service​

邏輯封裝 業務邏輯封裝層,實作特定的業務需求,可供不同的包調用。

​boot​

初始化包 用于項目初始化參數設定,往往作為​

​main.go​

​​中第一個被​

​import​

​的包。

​config​

配置管理 所有的配置檔案存放目錄。

​docker​

鏡像檔案

​Docker​

​鏡像相關依賴檔案,腳本檔案等等。

​document​

項目文檔 Documentation項目文檔,如: 設計文檔、幫助文檔等等。

​i18n​

I18N國際化 I18N國際化配置檔案目錄。

​library​

公共庫包 公共的功能封裝包,往往不包含業務需求實作。

​packed​

打包目錄 将資源檔案打包的​

​Go​

​​檔案存放在這裡,​

​boot​

​包初始化時會自動調用。

​public​

靜态目錄 僅有該目錄下的檔案才能對外提供靜态服務通路。

​router​

路由注冊 用于路由統一的注冊管理。

​template​

模闆檔案

​MVC​

​模闆檔案存放的目錄。

​Dockerfile​

鏡像描述 雲原生時代用于編譯生成Docker鏡像的描述檔案。

​go.mod​

依賴管理 使用​

​Go Module​

​包管理的依賴描述檔案。

​main.go​

入口檔案 程式入口檔案。

在實踐中,小夥伴們可以根據實際情況增删目錄。

注意:如果需要提供靜态服務,那麼所有靜态檔案都需要存放到​

​public​

​目錄下,僅有該目錄下的靜态檔案才能被外部直接通路。不推薦将程式目前運作目錄加入到靜态服務中。

項目建立推薦使用​

​GF​

​​工具鍊​

​gf init​

​​指令,具體請參考 ​​開發工具​​ 章節。

3. 分層設計

​GoFrame​

​官方推薦的代碼分層設計。

3.1 控制器

控制器負責接收并響應用戶端的輸入與輸出,包括對輸入參數的過濾、轉換、校驗,對輸出資料結構的維護,并調用​

​service​

​實作業務邏輯處理。

控制器代碼位于​

​/app/api​

​。

3.2 業務邏輯

業務邏輯是需要封裝的,特别是一些可複用的業務邏輯,并被控制器調用實作業務邏輯處理。

邏輯封裝的代碼位于​

​/app/service​

​。

3.3 資料通路

資料通路代碼層負責所有的資料集合(資料表)通路收口,将資料集合按照面向對象的方式進行封裝。

資料通路的代碼位于​

​/app/dao​

​。

3.4 模型定義

模型定義代碼層負責維護所有的資料結構定義,包括所有的輸入輸出資料結構定義。

模型定義代碼層中僅包含資料結構定義,不包含任何的方法定義。

模型定義的代碼位于​

​/app/model​

​。

3.5 模闆解析

模闆解析是可選的,在實踐中往往可以采用​

​MVVM​

​​的模式,例如使用​

​vue​

​​/​

​react​

​​等架構實作模闆解析。如果使用經典的模闆解析,可以通過​

​GoFrame​

​架構強大的模闆引擎實作模闆解析。

模闆檔案的存放于​

​/template​

​。

4. 資料庫設計

我們建立一個簡單的使用者表來做示範。

​​https://github.com/gogf/gf-demos/blob/master/document/sql/create.sql​​

CREATE TABLE `user` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '使用者ID',
    `passport` varchar(45) NOT NULL COMMENT '使用者賬号',
    `password` varchar(45) NOT NULL COMMENT '使用者密碼',
    `nickname` varchar(45) NOT NULL COMMENT '使用者昵稱',
    `create_at` datetime DEFAULT NULL COMMENT '建立時間',
    `update_at` datetime DEFAULT NULL COMMENT '更新時間',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;      

為簡化示例項目的接口實作複雜度,這裡的​

​password​

​沒有做任何加密處理,明文存放密碼資料。

5. 包名設計

5.1 包名約定

根據官方​​《Effective Go》​​​建議,包名盡量采用言簡意赅的名稱(​

​short, concise, evocative​

​​)。并且推薦通過不同的​

​import​

​路徑來區分相同包名的包引入。

5.2 包名設計

如果使用​

​GF​

​​開發業務項目,那麼基本可以不用考慮包名設計的問題,因為從​

​v1.15​

​​版本開始,​

​GF​

​​官方推薦使用面向對象的封裝方式,項目中​

​app​

​​目錄下往往隻存在​

​api​

​​, ​

​dao​

​​, ​

​model​

​​, ​

​service​

​ 四個包名,每個包内部通過對象的形式來封裝具體的資料模型或者業務邏輯。

6. 控制器實作

6.1 結構化限制

控制器的輸入與輸出使用了結構體定義進行限制,結構化維護輸入輸出資料結構是推薦的方式。例如:

// 賬号唯一性檢測請求參數,用于前後端互動參數格式約定
type UserApiCheckPassportReq struct {
    Passport string `v:"required#賬号不能為空"`
}      

雖然隻有一個參數,也采用了結構化定義,我們直接檢視該結構體便可得知該接口的輸入參數格式,而不用進入代碼中去分析,進而極大提高維護效率。

6.2 結構體轉換

結構體轉換可以使用​

​GetStruct​

​​或者​

​Parse​

​​方法,其中​

​Parse​

​同時可以執行資料校驗。結構體轉換方法的參數都可以給定一個結構體的空指針,内部會自動初始化結構體對象,轉換失敗(例如送出參數不存在)不會執行初始化。例如:

var (
    data *model.UserApiSignInReq
)
if err := r.Parse(&data); err != nil {
    response.JsonExit(r, 1, err.Error())
}      

6.3 資料校驗

用戶端送出的資料都是不可信的,必須要做資料校驗。

可以通過給結構體綁定​

​v​

​的标簽進行設定校驗規則以及定義的錯誤提示。例如:

// 登入請求參數,用于前後端互動參數格式約定
type UserApiSignInReq struct {
    Passport string `v:"required#賬号不能為空"`
    Password string `v:"required#密碼不能為空"`
}      

6.4 資料傳參

控制器負責接收、轉換、校驗、處理請求參數後,将所需的參數傳遞給調用的​

​service​

​​對象方法,而不是直接将​

​Request​

​​對象傳遞給​

​service​

​。例如:

func (a *apiUser) SignIn(r *ghttp.Request) {
    var (
        data *model.UserApiSignInReq
    )
    if err := r.Parse(&data); err != nil {
        response.JsonExit(r, 1, err.Error())
    }
    if err := service.User.SignIn(r.Context(), data.Passport, data.Password); err != nil {
        response.JsonExit(r, 1, err.Error())
    } else {
        response.JsonExit(r, 0, "ok")
    }
}      

6.4 實作代碼

​​https://github.com/gogf/gf-demos/blob/master/app/api/user.go​​

7. 上下文變量

上下文變量指的是标準庫的​

​context.Context​

​​,是一個接口對象。主要用于​

​goroutine​

​的異步IO控制,以及流程變量傳遞。

在​

​Go​

​​的​

​HTTP​

​​請求流程中,不存在”全局變量”擷取請求參數的方式,隻有将上下文​

​context​

​​變量傳遞到後續流程的方法中,而​

​context​

​​上下文變量即包含了所有需要傳遞的共享變量。并且該​

​context​

​中的共享變量應當是事先約定的,并且往往存儲為對象指針形式。

7.1 結構定義

在該示例中,我們的上下文變量的資料結構定義為:

​​https://github.com/gogf/gf-demos/blob/master/app/model/context.go​​

// 請求上下文結構
type Context struct {
    Session *ghttp.Session // 目前Session管理對象
    User    *ContextUser   // 上下文使用者資訊
}

// 請求上下文中的使用者資訊
type ContextUser struct {
    Id       uint   // 使用者ID
    Passport string // 使用者賬号
    Nickname string // 使用者名稱
}      

7.2 邏輯封裝

由于該上下文對象也是和業務邏輯相關的,是以我們需要通過​

​service​

​對象将上下文變量封裝起來再供其他子產品使用。

​​https://github.com/gogf/gf-demos/blob/master/app/service/context.go​​

GoFrame系列:4、項目結構及開發明細

7.3 上下文變量注入

上下文的變量必須在請求一開始便注入到請求流程中,以便于其他方法調用,是以我們使用中間件來實作。

​​https://github.com/gogf/gf-demos/blob/168e049e58528413cd248c05c729840e552ece94/app/service/middleware.go#L15​​

GoFrame系列:4、項目結構及開發明細

7.4 上下文變量使用

約定俗成的,方法的第一個參數往往預留給​

​context.Context​

​​類型參數使用,以便接受上下文變量,特别是​

​service​

​層的方法。例如:

​​https://github.com/gogf/gf-demos/blob/master/app/service/user.go​​

GoFrame系列:4、項目結構及開發明細

8. 中間件使用

8.1 跨域處理

允許跨域請求。

// 允許接口跨域請求
func (s *serviceMiddleware) CORS(r *ghttp.Request) {
    r.Response.CORSDefault()
    r.Middleware.Next()
}      

8.2 鑒權處理

隻有在使用者登入後才可通過。

// 鑒權中間件,隻有登入成功之後才能通過
func (s *serviceMiddleware) Auth(r *ghttp.Request) {
    if User.IsSignedIn(r.Context()) {
        r.Middleware.Next()
    } else {
        r.Response.WriteStatus(http.StatusForbidden)
    }
}      

8.3 上下文注入

前一章節已介紹過。

9. 業務邏輯封裝

9.1 職責劃分

所有的業務邏輯實作均封裝于​

​service​

​​層中,不推薦實作于控制器​

​api​

​​中。​

​service​

​​層的包名隻有一個,通過面向對象的方式進行封裝。​

​api​

​層在使用的時候隻會看到幾個公開的業務邏輯封裝對象。

GoFrame系列:4、項目結構及開發明細

9.2 資料校驗

與用戶端定義的輸入接口是由​

​api​

​​層的代碼來做的校驗,​

​service​

​層的代碼僅對内部定義的參數進行必要的校驗;有時往往也不需要做參數校驗,認為内部調用的參數都是可信任的。例如:

注冊邏輯:

GoFrame系列:4、項目結構及開發明細

9.3 内部引用

在​

​service​

​内部的對象之間存在互相引用,直接使用對應的變量即可,例如:

GoFrame系列:4、項目結構及開發明細

9.4 實作代碼

​​https://github.com/gogf/gf-demos/blob/master/app/service/user.go​​

10. 資料檔案建立

10.1 代碼生成

該模型通過​

​GF​

​​工具鍊的​

​gf gen dao​

​指令生成,提供了強大靈活的資料表操作方式。生成的資料檔案:

GoFrame系列:4、項目結構及開發明細

生成的檔案介紹請參考 ​​gen 代碼生成​​ 章節。

10.2 ​

​dao​

​使用

通過​

​dao​

​​包引用對應的對象即可,​

​dao​

​​往往是被​

​service​

​代碼層調用。

查找​

​dao​

​​中的​

​User​

​對象:

GoFrame系列:4、項目結構及開發明細

調用​

​User​

​​對象的​

​Save​

​方法儲存資料到資料表中。

GoFrame系列:4、項目結構及開發明細

10.3 ​

​model​

​使用

通過​

​model​

​包引用對應的資料結構定義即可。

查找​

​model​

​​中使用到​

​api​

​​的請求資料結構​

​ApiUserSignInReq​

​:

GoFrame系列:4、項目結構及開發明細

通過​

​ApiUserSignInReq​

​接受請求參數:

GoFrame系列:4、項目結構及開發明細

11. Swagger生成

如果對使用​

​swagger​

​接口文檔比較感興趣可以參考此章節介紹,不需要的話可以忽略該章節。

​swagger​

​​接口文檔主要用于前後端的接口定義。​

​Golang​

​​的​

​swagger​

​​文檔通過注釋的形式編寫到​

​api​

​​層的代碼中,使得接口文檔可以随着代碼一起維護,降低代碼與文檔不一緻的風險,并通過​

​gf-cli​

​​工具生成:​​swagger API文檔生成​​。

11.1 ​

​swagger​

​編寫

​swagger​

​​的文法請參考第三方倉庫​

​swag​

​:https://github.com/swaggo/swag

目前僅此一家​

​Golang Swagger​

​​元件庫,​

​Golang​

​​的​

​swagger​

​編寫體驗并不是特别友好,聊勝于無吧。
GoFrame系列:4、項目結構及開發明細

11.2 ​

​swagger​

​生成

我們這裡使用以下指令生成:

gf swagger --pack      

其中​

​gf swagger​

​​指令解析并生成​

​swagger.json​

​​文檔到項目根目錄的​

​swagger​

​​路徑下,同時這裡的​

​--pack​

​​選項将​

​swagger.json​

​​打包為​

​Golang​

​​代碼檔案生成到項目根目錄的​

​packed​

​路徑下。

GoFrame系列:4、項目結構及開發明細

具體請參考 ​​swagger API文檔生成​​ 章節。

11.3 ​

​swagger​

​插件

我們這裡使用到了​

​GoFrame​

​​的​

​swagger​

​插件:https://github.com/gogf/swagger

按照倉庫介紹說明,我們在​

​boot​

​啟動設定子產品中添加插件的注冊:

GoFrame系列:4、項目結構及開發明細

11.4 ​

​swagger​

​文檔檢視

随後可以啟動程式通路檢視​

​swagger​

​接口文檔頁面:http://127.0.0.1:8199/swagger

GoFrame系列:4、項目結構及開發明細

12. 基礎類庫存放

主要固定傳回資料格式及資料結構。

其中​

​JsonExit​

​​與​

​Json​

​​的差別在于,​

​JsonExit​

​​調用時會輸出​

​JSON​

​​資料後直接退出目前的路由方法;而​

​Json​

​在執行輸出後會繼續執行後續的路由方法邏輯。

package response

import (
    "github.com/gogf/gf/net/ghttp"
)

// 資料傳回通用JSON資料結構
type JsonResponse struct {
    Code    int         `json:"code"`    // 錯誤碼((0:成功, 1:失敗, >1:錯誤碼))
    Message string      `json:"message"` // 提示資訊
    Data    interface{} `json:"data"`    // 傳回資料(業務接口定義具體資料結構)
}

// 标準傳回結果資料結構封裝。
func Json(r *ghttp.Request, code int, message string, data ...interface{}) {
    responseData := interface{}(nil)
    if len(data) > 0 {
        responseData = data[0]
    }
    r.Response.WriteJson(JsonResponse{
        Code:    code,
        Message: message,
        Data:    responseData,
    })
}

// 傳回JSON資料并退出目前HTTP執行函數。
func JsonExit(r *ghttp.Request, err int, msg string, data ...interface{}) {
    Json(r, err, msg, data...)
    r.Exit()
}      

13. 最後