Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 久久久久久不卡,男人天堂网2022,天天色综合色

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Go語言編碼規(guī)范

          Go語言編碼規(guī)范


          規(guī)范旨在為日常Go項(xiàng)目開發(fā)提供一個(gè)代碼的規(guī)范指導(dǎo),方便團(tuán)隊(duì)形成一個(gè)統(tǒng)一的代碼風(fēng)格,提高代碼的可讀性,規(guī)范性和統(tǒng)一性。本規(guī)范將從命名規(guī)范,注釋規(guī)范,代碼風(fēng)格和 Go 語言提供的常用的工具這幾個(gè)方面做一個(gè)說明。該規(guī)范參考了 go 語言官方代碼的風(fēng)格制定。

          命名規(guī)范

          命名是代碼規(guī)范中很重要的一部分,統(tǒng)一的命名規(guī)則有利于提高的代碼的可讀性,好的命名僅僅通過命名就可以獲取到足夠多的信息。

          Go在命名時(shí)以字母a到Z或a到Z或下劃線開頭,后面跟著零或更多的字母、下劃線和數(shù)字(0到9)。Go不允許在命名時(shí)中使用@、$和%等標(biāo)點(diǎn)符號(hào)。Go是一種區(qū)分大小寫的編程語言。因此,Manpower和manpower是兩個(gè)不同的命名。

          當(dāng)命名(包括常量、變量、類型、函數(shù)名、結(jié)構(gòu)字段等等)以一個(gè)大寫字母開頭,如:Group1,那么使用這種形式的標(biāo)識(shí)符的對(duì)象就可以被外部包的代碼所使用(客戶端程序需要先導(dǎo)入這個(gè)包),這被稱為導(dǎo)出(像面向?qū)ο笳Z言中的 public)。 命名如果以小寫字母開頭,則對(duì)包外是不可見的,但是他們?cè)谡麄€(gè)包的內(nèi)部是可見并且可用的(像面向?qū)ο笳Z言中的 private )。

          包命名

          保持package的名字和目錄保持一致,盡量采取有意義的包名,簡短,有意義,盡量和標(biāo)準(zhǔn)庫不要沖突。包名應(yīng)該為小寫單詞,不要使用下劃線或者混合大小寫。

          package demo
          
          package main
          

          文件命名

          盡量采取有意義的文件名,簡短,有意義,應(yīng)該為小寫單詞,使用下劃線分隔各個(gè)單詞。

          my_test.go

          結(jié)構(gòu)體命名

          采用駝峰命名法,首字母根據(jù)訪問控制大寫或者小寫。

          struct 申明和初始化格式采用多行,例如下面:

          // 多行申明
          type User struct{
           Username string
           Email string
          }
          ?
          // 多行初始化
          u :=User{
           Username: "avatar",
           Email: "avatar@gmail.com",
          }
          

          接口命名

          命名規(guī)則基本和上面的結(jié)構(gòu)體類型。

          單個(gè)函數(shù)的結(jié)構(gòu)名以 “er” 作為后綴,例如 Reader , Writer 。

          type Reader interface {
           Read(p []byte) (n int, err error)
          }
          

          變量命名

          • 和結(jié)構(gòu)體類似,變量名稱一般遵循駝峰法,首字母根據(jù)訪問控制原則大寫或者小寫,但遇到特有名詞時(shí),需要遵循以下規(guī)則:
          • 如果變量為私有,且特有名詞為首個(gè)單詞,則使用小寫,如 apiClient
          • 其它情況都應(yīng)當(dāng)使用該名詞原有的寫法,如 APIClient、repoID、UserID
          • 錯(cuò)誤示例:UrlArray,應(yīng)該寫成 urlArray 或者 URLArray
          • 若變量類型為 bool 類型,則名稱應(yīng)以 Has, Is, Can 或 Allow 開頭
          var isExist bool
          var hasConflict bool
          var canManage bool
          var allowGitHook bool
          

          常量命名

          常量均需使用全部大寫字母組成,并使用下劃線分詞。

          const APP_VER="1.0"
          

          如果是枚舉類型的常量,需要先創(chuàng)建相應(yīng)類型:

          type Scheme string
          ?
          const (
           HTTP Scheme="http"
           HTTPS Scheme="https"
          )
          

          關(guān)鍵字

          下面的列表顯示了Go中的保留字。這些保留字不能用作常量或變量或任何其他標(biāo)識(shí)符名稱。

          注釋

          Go提供C風(fēng)格的/ /塊注釋和C ++風(fēng)格的//行注釋。行注釋是常態(tài);塊注釋主要顯示為包注釋,但在表達(dá)式中很有用或禁用大量代碼。

          • 單行注釋是最常見的注釋形式,你可以在任何地方使用以 // 開頭的單行注釋
          • 多行注釋也叫塊注釋,均已以 / 開頭,并以 / 結(jié)尾,且不可以嵌套使用,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段

          go 語言自帶的 godoc 工具可以根據(jù)注釋生成文檔,生成可以自動(dòng)生成對(duì)應(yīng)的網(wǎng)站( http://golang.org 就是使用 godoc 工具直接生成的),注釋的質(zhì)量決定了生成的文檔的質(zhì)量。每個(gè)包都應(yīng)該有一個(gè)包注釋,在package子句之前有一個(gè)塊注釋。對(duì)于多文件包,包注釋只需要存在于一個(gè)文件中,任何一個(gè)都可以。包評(píng)論應(yīng)該介紹包,并提供與整個(gè)包相關(guān)的信息。它將首先出現(xiàn)在godoc頁面上,并應(yīng)設(shè)置下面的詳細(xì)文檔。

          詳細(xì)的如何寫注釋可以 參考:http://golang.org/doc/effective_go.html#commentary

          包注釋

          每個(gè)包都應(yīng)該有一個(gè)包注釋,一個(gè)位于package子句之前的塊注釋或行注釋。包如果有多個(gè)go文件,只需要出現(xiàn)在一個(gè)go文件中(一般是和包同名的文件)即可。 包注釋應(yīng)該包含下面基本信息(請(qǐng)嚴(yán)格按照這個(gè)順序,簡介,創(chuàng)建人,創(chuàng)建時(shí)間):

          • 包的基本簡介(包名,簡介)
          • 創(chuàng)建者,格式: 創(chuàng)建人: rtx 名
          • 創(chuàng)建時(shí)間,格式:創(chuàng)建時(shí)間: yyyyMMdd

          例如 util 包的注釋示例如下:

          // util 包, 該包包含了項(xiàng)目共用的一些常量,封裝了項(xiàng)目中一些共用函數(shù)。
          // 創(chuàng)建人: xlli5
          // 創(chuàng)建時(shí)間: 20190522
          

          結(jié)構(gòu)(接口)注釋

          每個(gè)自定義的結(jié)構(gòu)體或者接口都應(yīng)該有注釋說明,該注釋對(duì)結(jié)構(gòu)進(jìn)行簡要介紹,放在結(jié)構(gòu)體定義的前一行,格式為: 結(jié)構(gòu)體名, 結(jié)構(gòu)體說明。同時(shí)結(jié)構(gòu)體內(nèi)的每個(gè)成員變量都要有說明,該說明放在成員變量的后面(注意對(duì)齊),實(shí)例如下:

          // User , 用戶對(duì)象,定義了用戶的基礎(chǔ)信息
          type User struct{
           Username string // 用戶名
           Email string // 郵箱
          }
          

          函數(shù)(方法)注釋

          每個(gè)函數(shù),或者方法(結(jié)構(gòu)體或者接口下的函數(shù)稱為方法)都應(yīng)該有注釋說明,函數(shù)的注釋應(yīng)該包括三個(gè)方面(嚴(yán)格按照此順序撰寫):

          • 簡要說明,格式說明:以函數(shù)名開頭,“,”分隔說明部分
          • 參數(shù)列表:每行一個(gè)參數(shù),參數(shù)名開頭,“,”分隔說明部分
          • 返回值: 每行一個(gè)返回值

          示例如下:

          // NewtAttrModel , 屬性數(shù)據(jù)層操作類的工廠方法
          // 參數(shù):
          // ctx : 上下文信息
          // 返回值:
          // 屬性操作類指針
          func NewAttrModel(ctx *common.Context) *AttrModel {
          }
          

          代碼邏輯注釋

          對(duì)于一些關(guān)鍵位置的代碼邏輯,或者局部較為復(fù)雜的邏輯,需要有相應(yīng)的邏輯說明,方便其他開發(fā)者閱讀該段代碼,實(shí)例如下:

          // 從 Redis 中批量讀取屬性,對(duì)于沒有讀取到的 id , 記錄到一個(gè)數(shù)組里面,準(zhǔn)備從 DB 中讀取
          xxxxx
          xxxxxxx
          xxxxxxx
          

          注釋風(fēng)格

          統(tǒng)一使用中文注釋,對(duì)于中英文字符之間嚴(yán)格使用空格分隔, 這個(gè)不僅僅是中文和英文之間,英文和中文標(biāo)點(diǎn)之間也都要使用空格分隔,例如:

          // 從 Redis 中批量讀取屬性,對(duì)于沒有讀取到的 id , 記錄到一個(gè)數(shù)組里面,準(zhǔn)備從 DB 中讀取
          

          上面 Redis 、 id 、 DB 和其他中文字符之間都是用了空格分隔。

          建議全部使用單行注釋
          和代碼的規(guī)范一樣,單行注釋不要過長,禁止超過 120 字符
          

          代碼風(fēng)格

          縮進(jìn)和折行

          • 縮進(jìn)直接使用 gofmt 工具格式化即可(gofmt 是使用 tab 縮進(jìn)的)
          • 折行方面,一行最長不超過120個(gè)字符,超過的請(qǐng)使用換行展示,盡量保持格式優(yōu)雅

          我們使用Goland開發(fā)工具,可以直接使用快捷鍵:ctrl+alt+L,即可。

          語句的結(jié)尾

          Go語言中是不需要類似于Java需要冒號(hào)結(jié)尾,默認(rèn)一行就是一條數(shù)據(jù)

          如果你打算將多個(gè)語句寫在同一行,它們則必須使用。

          括號(hào)和空格

          括號(hào)和空格方面,也可以直接使用 gofmt 工具格式化(go 會(huì)強(qiáng)制左大括號(hào)不換行,換行會(huì)報(bào)語法錯(cuò)誤),所有的運(yùn)算符和操作數(shù)之間要留空格。

          // 正確的方式
          if a > 0 {
          ?
          }
          ?
          // 錯(cuò)誤的方式
          if a>0 // a ,0 和 > 之間應(yīng)該空格
          { // 左大括號(hào)不可以換行,會(huì)報(bào)語法錯(cuò)誤
          ?
          }
          

          import 規(guī)范

          import在多行的情況下,goimports會(huì)自動(dòng)幫你格式化,但是我們這里還是規(guī)范一下import的一些規(guī)范,如果你在一個(gè)文件里面引入了一個(gè)package,還是建議采用如下格式:

          import (
           "fmt"
          )
          

          如果你的包引入了三種類型的包,標(biāo)準(zhǔn)庫包,程序內(nèi)部包,第三方包,建議采用如下方式進(jìn)行組織你的包:

          import (
           "encoding/json"
           "strings"
          ?
           "myproject/models"
           "myproject/controller"
           "myproject/utils"
          ?
           "github.com/astaxie/beego"
           "github.com/go-sql-driver/mysql"
          )
          

          有順序的引入包,不同的類型采用空格分離,第一種實(shí)標(biāo)準(zhǔn)庫,第二是項(xiàng)目包,第三是第三方包。

          在項(xiàng)目中不要使用相對(duì)路徑引入包:

          // 這是不好的導(dǎo)入
          import "../net"
          ?
          // 這是正確的做法
          import "github.com/repo/proj/src/net"
          

          但是如果是引入本項(xiàng)目中的其他包,最好使用相對(duì)路徑。

          錯(cuò)誤處理

          • 錯(cuò)誤處理的原則就是不能丟棄任何有返回err的調(diào)用,不要使用 _ 丟棄,必須全部處理。接收到錯(cuò)誤,要么返回err,或者使用log記錄下來
          • 盡早return:一旦有錯(cuò)誤發(fā)生,馬上返回
          • 盡量不要使用panic,除非你知道你在做什么
          • 錯(cuò)誤描述如果是英文必須為小寫,不需要標(biāo)點(diǎn)結(jié)尾
          • 采用獨(dú)立的錯(cuò)誤流進(jìn)行處理
          // 錯(cuò)誤寫法
          if err !=nil {
           // error handling
          } else {
           // normal code
          }
          ?
          // 正確寫法
          if err !=nil {
           // error handling
           return // or continue, etc.
          }
          // normal code
          

          測試

          單元測試文件名命名規(guī)范為 example_test.go 測試用例的函數(shù)名稱必須以 Test 開頭,盡量避免使用 main 方法測試。例如:TestExample 每個(gè)重要的函數(shù)都要首先編寫測試用例,測試用例和正規(guī)代碼一起提交方便進(jìn)行回歸測試。

          配置文件

          編寫代碼時(shí)提供三套配置文件,分別是開發(fā)環(huán)境 [dev] , 測試環(huán)境 [test] , 現(xiàn)網(wǎng)環(huán)境 [prod] 。 目錄如下:

          ./
          ├── dev
          │ ├── seelog.xml
          │ └── service.yml
          ├── prod
          │ ├── seelog.xml
          │ └── service.yml
          └── test
           ├── seelog.xml
           └── service.yml
          

          常用工具

          代碼格式化

          go 語言本身在代碼規(guī)范性這方面也做了很多努力,很多限制都是強(qiáng)制語法要求,例如左大括號(hào)不換行,引用的包或者定義的變量不使用會(huì)報(bào)錯(cuò),此外 go 還是提供了很多好用的工具幫助我們進(jìn)行代碼的規(guī)范,

          gofmt 大部分的格式問題可以通過gofmt解決, gofmt 自動(dòng)格式化代碼,保證所有的 go 代碼與官方推薦的格式保持一致,于是所有格式有關(guān)問題,都以 gofmt 的結(jié)果為準(zhǔn)。

          goimport 我們強(qiáng)烈建議使用 goimport ,該工具在 gofmt 的基礎(chǔ)上增加了自動(dòng)刪除和引入包。

          go get golang.org/x/tools/cmd/goimports

          go vet vet工具可以幫我們靜態(tài)分析我們的源碼存在的各種問題,例如多余的代碼,提前return的邏輯,struct的tag是否符合標(biāo)準(zhǔn)等。

          go get golang.org/x/tools/cmd/vet

          使用如下:

          go vet .

          依賴包管理

          GoModule

          Golang官方在1.11版本初步引入的GoModule模塊。1.12版本正式開始支持。GoModule是官方提供的包管理解決方案。通過GoModule,開發(fā)者可以把工程放在GOPATH之外的位置。相比于之前的包管理方案: dep,vendor。GoModule的管理方案更加靈活。 可以運(yùn)行g(shù)o mod help來看看GoModule中有哪些命令。

          • go mod init [module]:初始化.mod 包管理文件到當(dāng)前工程。
          • go mod vendor:vendor版本的解決方案,將依賴復(fù)制到vendor下面。
          • go mod tidy:移除未用的模塊,以及添加缺失的模塊。
          • go mod verify:驗(yàn)證所有模塊是否正確。

          亮點(diǎn):

          使用replace替換無法直接獲取的package 依賴包沖突問題 自動(dòng)查找包依賴

          總結(jié)

          兩個(gè)等級(jí): [S] 建議, [M] 必須。以下是細(xì)節(jié)。

          代碼組織結(jié)構(gòu)

          • [M] 一個(gè)目錄只包含一個(gè)包,模塊復(fù)雜拆分子模塊/子目錄
          • [S] 內(nèi)部項(xiàng)目GOPATH如果指向多個(gè)工作目錄。公開項(xiàng)目為第一個(gè)工作區(qū)間(即 go get 默認(rèn)下載到第一個(gè)目錄)
          • [M] 非測試文件 (*_test.go) 禁止使用,簡化包
          • [M] 禁止相對(duì)路徑導(dǎo)入包
          • [S] 建議goimports或者IDE管理import
          • [S] 建議使用GoModule管理第三方包

          代碼風(fēng)格

          • [M] 提交代碼時(shí) gofmt 格式化代碼, golint 檢查代碼(使用IDE時(shí)默認(rèn)這兩個(gè)工具會(huì)自動(dòng)用到)
          • [S] json 字符串建議使用反單引號(hào)(`)
          • [M] 文件名必須小寫,允許下劃線'_’,但頭尾不能。避免與 _test.go 或者系統(tǒng)相關(guān) _386.go 等沖突
          • [S] 文件名以功能為指引,不需要再出現(xiàn)模塊名
          • [M] 目錄名必須小寫,允許中劃線'-',但頭尾不能
          • [S] 不建議目錄名出現(xiàn)下劃線'_'
          • [M] 包名必須全部小寫,無下劃線,越短越好,盡量不要與標(biāo)準(zhǔn)庫重名,禁止通過中劃線連接多個(gè)單詞
          • [S] 包名盡量與目錄名一致
          • [M] 函數(shù)名和結(jié)構(gòu)體名必須為大小寫駝峰模式,最好不帶特殊字符如劃線等
          • [S] 函數(shù)名建議動(dòng)詞或者動(dòng)賓結(jié)構(gòu)單詞,結(jié)構(gòu)體建議名詞或者動(dòng)名詞
          • [S] 常量和枚舉名,大小寫駝峰法,不允許下劃線,第三方包例外。
          • [M] 函數(shù)參數(shù)首字母小寫,不能有下劃線,按大小駝峰法
          • [S] 函數(shù)參數(shù)按緊密程度安排位置,同類型參數(shù)應(yīng)該相鄰
          • [S] 參數(shù)不大于5個(gè)
          • [M] 變量名不允許下劃線,大小寫駝峰法,局部變量首字母小寫,全局變量首字母大寫
          • [S] 避免全局變量多使用, for 循環(huán)可用單字母
          • [M] 接口名大小寫駝峰法,首字母大寫,不能下劃線,名詞
          • [S] 接口名 'er' 結(jié)尾
          • [M] 復(fù)雜功能請(qǐng)多寫注釋備注,注釋表達(dá)需清晰,不要啰嗦。注釋標(biāo)準(zhǔn)暫時(shí)不強(qiáng)制,最好參考 godoc ,如包注釋使用 /**/ ,首字母大寫,注釋后空一行,函數(shù)注釋寫在函數(shù)上方等。

          總而言之,文件名和目錄名,包名都必須小寫。數(shù)據(jù)類型變量和參數(shù)等定義最好使用駝峰大小寫法,不要使用下劃線或者中劃線

          單元測試/程序效率

          • [S] 建議少使用 main 方法測試,而是使用 _test.go 做測試
          • [M] 與其他語言類似,避免多級(jí) if 或者 for 嵌用,代碼層次需簡單,繞腦層次少
          • [M] 避免有歧義的命名,如 IsTrue 變量,if(!IsTrue).
          • [M] 請(qǐng)熟悉 Go 語言各特征,避免低效用法。

          用了近十年的 C# 轉(zhuǎn)到 Go 是一個(gè)有趣的旅程。有時(shí),我陶醉于 Go 的簡潔[1];也有些時(shí)候,當(dāng)熟悉的 OOP (面向?qū)ο缶幊蹋?strong>模式[2]無法在 Go 代碼中使用的時(shí)候會(huì)感到沮喪。幸運(yùn)的是,我已經(jīng)摸索出了一些寫 HTTP 服務(wù)的模式,在我的團(tuán)隊(duì)中應(yīng)用地很好。

          當(dāng)在公司項(xiàng)目上工作時(shí),我傾向把可發(fā)現(xiàn)性放在最高的優(yōu)先級(jí)上。這些應(yīng)用會(huì)在接下來的 20 年運(yùn)行在生產(chǎn)環(huán)境中,必須有眾多的開發(fā)人員和網(wǎng)站可靠性工程師(可能是指運(yùn)維)來進(jìn)行熱補(bǔ)丁,維護(hù)和調(diào)整工作。因此,我不指望這些模式能適合所有人。

          Mat Ryer 的文章[3]是我使用 Go 試驗(yàn) HTTP 服務(wù)的起點(diǎn)之一,也是這篇文章的靈感來源。

          代碼組成

          Broker

          一個(gè) Broker 結(jié)構(gòu)是將不同的 service 包綁定到 HTTP 邏輯的膠合結(jié)構(gòu)。沒有包作用域結(jié)級(jí)別的變量被使用。依賴的接口得益于了 Go 的組合[4]的特點(diǎn)被嵌入了進(jìn)來。

          type Broker struct {
              auth.Client             // 從外部倉庫導(dǎo)入的身份驗(yàn)證依賴(接口)
              service.Service         // 倉庫的業(yè)務(wù)邏輯包(接口)
          
              cfg    Config           // 該 API 服務(wù)的配置
              router *mux.Router      // 該 API 服務(wù)的路由集
          }
          

          broker 可以使用阻塞[5]函數(shù) New() 來初始化,該函數(shù)校驗(yàn)配置,并且運(yùn)行所有需要的前置檢查。

          func New(cfg Config, port int) (*Broker, error) {
              r := &Broker{
                  cfg: cfg,
              }
          
              ...
          
              r.auth.Client, err = auth.New(cfg.AuthConfig)
              if err != nil {
                  return nil, fmt.Errorf("Unable to create new API broker: %w", err)
              }
          
              ...
          
              return r, nil
          }
          

          初始化后的 Broker 滿足了暴露在外的 Server 接口,這些接口定義了所有的,被 route 和 中間件(middleware)使用的功能。service 包接口被嵌入,這些接口與 Broker 上嵌入的接口相匹配。

          type Server interface {
              PingDependencies(bool) error
              ValidateJWT(string) error
          
              service.Service
          }
          

          web 服務(wù)通過調(diào)用 Start() 函數(shù)來啟動(dòng)。路由綁定通過一個(gè)閉包函數(shù)[6]進(jìn)行綁定,這種方式保證循環(huán)依賴不會(huì)破壞導(dǎo)入周期規(guī)則。

          func (bkr *Broker) Start(binder func(s Server, r *mux.Router)) {
              ...
          
              bkr.router = mux.NewRouter().StrictSlash(true)
              binder(bkr, bkr.router)
          
              ...
          
              if err := http.Serve(l, bkr.router); errors.Is(err, http.ErrServerClosed) {
                  log.Warn().Err(err).Msg("Web server has shut down")
              } else {
                  log.Fatal().Err(err).Msg("Web server has shut down unexpectedly")
              }
          }
          

          那些對(duì)故障排除(比如,Kubernetes 探針[7])或者災(zāi)難恢復(fù)方案方面有用的函數(shù),掛在 Broker 上。如果被 routes/middleware 使用的話,這些僅僅被添加到 webserver.Server 接口上。

          func (bkr *Broker) SetupDatabase() { ... }
          func (bkr *Broker) PingDependencies(failFast bool)) { ... }
          

          啟動(dòng)引導(dǎo)

          整個(gè)應(yīng)用的入口是一個(gè) main 包。默認(rèn)會(huì)啟動(dòng) Web 服務(wù)。我們可以通過傳入一些命令行參數(shù)來調(diào)用之前提到的故障排查功能,方便使用傳入 New() 函數(shù)的,經(jīng)過驗(yàn)證的配置來測試代理權(quán)限以及其他網(wǎng)絡(luò)問題。我們所要做的只是登入運(yùn)行著的 pod 然后像使用其他命令行工具一樣使用它們。

          func main() {
              subCommand := flag.String("start", "", "start the webserver")
          
              ...
          
              srv := webserver.New(cfg, 80)
          
              switch strings.ToLower(subCommand) {
              case "ping":
                  srv.PingDependencies(false)
              case "start":
                  srv.Start(BindRoutes)
              default:
                  fmt.Printf("Unrecognized command %q, exiting.", subCommand)
                  os.Exit(1)
              }
          }
          

          HTTP 管道設(shè)置在 BindRoutes() 函數(shù)中完成,該函數(shù)通過 ser.Start() 注入到服務(wù)(server)中。

          func BindRoutes(srv webserver.Server, r *mux.Router) {
              r.Use(middleware.Metrics(), middleware.Authentication(srv))
              r.HandleFunc("/ping", routes.Ping()).Methods(http.MethodGet)
          
              ...
          
              r.HandleFunc("/makes/{makeID}/models/{modelID}", model.get(srv)).Methods(http.MethodGet)
          }
          

          中間件

          中間件(Middleware)返回一個(gè)帶有 handler 的函數(shù),handler 用來構(gòu)建需要的 http.HandlerFunc。這使得 webserver.Server 接口被注入,同時(shí)所有的安靜檢查只在啟動(dòng)時(shí)執(zhí)行,而不是在所有路由調(diào)用的時(shí)候。

          func Authentication(srv webserver.Server) func(h http.Handler) http.Handler {
              if srv == nil || !srv.Client.IsValid() {
                  log.Fatal().Msg("a nil dependency was passed to authentication middleware")
              }
          
              // additional setup logic
              ...
          
              return func(next http.Handler) http.Handler {
                  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                      token := strings.TrimSpace(r.Header.Get("Authorization"))
                      if err := srv.ValidateJWT(token); err != nil {
                          ...
                          w.WriteHeader(401)
                          w.Write([]byte("Access Denied"))
          
                          return
                      }
          
                      next.ServeHTTP(w, r)
                  }
              }
          }
          

          路由

          路由有著與中間件有著類似的套路——簡單的設(shè)置,但是有著同樣的收益。

          func GetLatest(srv webserver.Server) http.HandlerFunc {
              if srv == nil {
                  log.Fatal().Msg("a nil dependency was passed to the `/makes/{makeID}/models/{modelID}` route")
              }
          
              // additional setup logic
              ...
          
              return func(w http.ResponseWriter, r *http.Request) {
                  ...
          
                  makeDTO, err := srv.Get
              }
          }
          

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

          代碼的目錄結(jié)構(gòu)對(duì)可發(fā)現(xiàn)性進(jìn)行了高度優(yōu)化。

          ├── app/
          |   └── service-api/**
          ├── cmd/
          |   └── service-tool-x/
          ├── internal/
          |   └── service/
          |       └── mock/
          ├── pkg/
          |   ├── client/
          |   └── dtos/
          ├── (.editorconfig, .gitattributes, .gitignore)
          └── go.mod
          
          • app/ 用于項(xiàng)目應(yīng)用——這是新來的人了解代碼傾向的切入點(diǎn)。dd
            • ./service-api/ 是該倉庫的微服務(wù) API;所有的 HTTP 實(shí)現(xiàn)細(xì)節(jié)都在這里。
          • cmd/ 是存放命令行應(yīng)用的地方。
          • internal/ 是不可以被該倉庫以外的項(xiàng)目引入的一個(gè)特殊目錄[8]
            • ./service/ 是所有領(lǐng)域邏輯(domain logic)所在的地方;可以被 service-apiservice-tool-x,以及任何未來直接訪問這個(gè)目錄可以帶來收益的應(yīng)用或者包所引入。
          • pkg/ 用于存放鼓勵(lì)被倉庫以外的項(xiàng)目所引入的包。
            • ./client/ 是用于訪問 service-api 的 client 庫。其他團(tuán)隊(duì)可以使用而不是自己寫一個(gè) client,并且我們可以借助我們?cè)?cmd/ 里面的 CI/CD 工具來 “dogfood it[9]” (使用自己產(chǎn)品的意思)。
            • ./dtos/ 是存放項(xiàng)目的數(shù)據(jù)傳輸對(duì)象,不同包之間共享的數(shù)據(jù)且以 json 形式在線路上編碼或傳輸?shù)慕Y(jié)構(gòu)體定義。沒有從其他倉庫包導(dǎo)出的模塊化的結(jié)構(gòu)體。/internal/service 負(fù)責(zé) 這些 DTO (數(shù)據(jù)傳輸對(duì)象)和自己內(nèi)部模型的相互映射,避免實(shí)現(xiàn)細(xì)節(jié)的遺漏(如,數(shù)據(jù)庫注釋)并且該模型的改變不破壞下游客戶端消費(fèi)這些 DTO。
          • .editorconfig,.gitattributes,.gitignore 因?yàn)?strong>所有的倉庫必須使用 .editorconfig,.gitattributes,.gitignore[10]
          • go.mod 甚至可以在有限制的且官僚的公司環(huán)境[11]工作。

          最重要的:每個(gè)包只負(fù)責(zé)意見事情,一件事情!

          HTTP 服務(wù)結(jié)構(gòu)

          └── service-api/
              ├── cfg/
              ├── middleware/
              ├── routes/
              |   ├── makes/
              |   |   └── models/**
              |   ├── create.go
              |   ├── create_test.go
              |   ├── get.go
              |   └── get_test.go
              ├── webserver/
              ├── main.go
              └── routebinds.go
          
          • ./cfg/ 用于存放配置文件,通常是以 JSON 或者 YAML 形式保存的純文本文件,它們也應(yīng)該被檢入到 Git 里面(除了密碼,秘鑰等)。
          • ./middleware 用于所有的中間件。
          • ./routes 采用類似應(yīng)用的類 RESTFul 形式的目錄對(duì)路由代碼進(jìn)行分組和嵌套。
          • ./webserver 保存所有共享的 HTTP 結(jié)構(gòu)和接口(Broker,配置,Server等等)。
          • main.go 啟動(dòng)應(yīng)用程序的地方(New()Start())。
          • routebinds.go BindRoutes() 函數(shù)存放的地方。

          你覺得呢?

          如果你最終采用了這種模式,或者有其他的想法我們可以討論,我樂意聽到這些想法!


          via: https://www.dudley.codes/posts/2020.05.19-golang-structure-web-servers/

          作者:James Dudley[12]譯者:dust347[13]校對(duì):unknwon[14]

          本文由 GCTT[15] 原創(chuàng)編譯,Go 中文網(wǎng)[16] 榮譽(yù)推出

          參考資料

          [1]

          簡潔: https://www.youtube.com/watch?v=rFejpH_tAHM

          [2]

          模式: https://en.wikipedia.org/wiki/Software_design_pattern

          [3]

          Mat Ryer 的文章: https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html

          [4]

          Go 的組合: https://www.ardanlabs.com/blog/2015/09/composition-with-go.html

          [5]

          阻塞: https://stackoverflow.com/questions/2407589/what-does-the-term-blocking-mean-in-programming

          [6]

          閉包函數(shù): https://gobyexample.com/closures

          [7]

          Kubernetes 探針: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)0

          [8]

          特殊目錄: https://dave.cheney.net/2019/10/06/use-internal-packages-to-reduce-your-public-api-surface

          [9]

          dogfood it: https://en.wikipedia.org/wiki/Eating_your_own_dog_food

          [10]

          所有的倉庫必須使用 .editorconfig,.gitattributes,.gitignore: https://www.dudley.codes/posts/2020.02.16-git-lost-in-translation/

          [11]

          有限制的且官僚的公司環(huán)境: https://www.dudley.codes/posts/2020.04.02-golang-behind-corporate-firewall/

          [12]

          James Dudley: https://www.dudley.codes/

          [13]

          dust347: https://github.com/dust347

          [14]

          unknwon: https://github.com/unknwon

          [15]

          GCTT: https://github.com/studygolang/GCTT

          [16]

          Go 中文網(wǎng): https://studygolang.com/

          想構(gòu)建一個(gè)本地 Go 桌面應(yīng)用程序,有幾種方法可以做到:

          1. Electron[1]:將 Node.js[2]Chromium[3] 瀏覽器綁定在一起,以創(chuàng)建一個(gè)打包的本地 Web 應(yīng)用程序。可與 Go 框架(例如 go-app[4]go-astilectron[5])一起使用。
          2. Lorca[6]:使用本地安裝的 Chrome 瀏覽器,通過 dev-tools communication protocol[7] 實(shí)現(xiàn)。
          3. Webview[8]:使用 webview[9] 創(chuàng)建一個(gè)本地窗口,并使用 CGo 綁定在其中渲染應(yīng)用程序。

          我已經(jīng)寫過有關(guān)構(gòu)建一個(gè)簡單的 electron 應(yīng)用程序的文章[10],因此本文將探討如何使用 Lorca 和 Webview 構(gòu)建應(yīng)用程序,然后比較這三種的不同。

          Lorca

          Go 中一個(gè)簡單的 Lorca[11] 應(yīng)用:

          func main() {
           // Create UI with data URI
           ui, _ := lorca.New("data:text/html,"+url.PathEscape(`
           <html>
            <head><title>Hello</title></head>
            <body><h1>Hello, world!</h1></body>
           </html>
           `), "", 600, 200)
           defer ui.Close()
           // Create a GoLang function callable from JS
           ui.Bind("hello", func() string { return "World!" })
           // Call above `hello` function then log to the JS console
           ui.Eval("hello().then( (x) => { console.log(x) })")
           // Wait until UI window is closed
           <-ui.Done()
          }
          

          因?yàn)閺?fù)雜性被隱藏了,所以看起來非常簡單!上面的代碼打開一個(gè) Chome 窗口,通過 websocket 連接到其 dev-tools[12] 端點(diǎn),發(fā)送要加載的 HTML,并提供 Go 和 JS 之間的通信:

          更酷的是,您可以在 Chrome 中調(diào)用 JS 函數(shù)并在 Go 中獲取輸出

          n := ui.Eval(`Math.random()`).Float()
          fmt.Println(n)
          

          使用這個(gè)庫是如此容易,如此直觀,如此實(shí)用,以至于我剛使用它時(shí)感到困惑。我以為一定有陷阱,不會(huì)這么簡單。但是沒有,它就是這么簡單。

          另外一個(gè)好處是您可以使用 Chrome 開發(fā)工具來幫助調(diào)試任何問題或調(diào)整布局。另外,鑒于我自 2014 年以來[13]一直在寫 Promise,我喜歡使用 JS Promise 在 Go 和 JS 之間實(shí)現(xiàn)異步調(diào)用。

          Lorca 的最大缺點(diǎn)是,由于它使用 Chrome,因此某些應(yīng)用程序詳細(xì)信息(如系統(tǒng)菜單,圖標(biāo),標(biāo)題)無法自定義。然后,需要在應(yīng)用程序優(yōu)化和簡單應(yīng)用程序之間進(jìn)行權(quán)衡。根據(jù)您構(gòu)建內(nèi)容的不同,有好有弊,例如,如果您正在構(gòu)建內(nèi)部工具,那會(huì)很好,但是對(duì)于企業(yè)應(yīng)用程序,這可能看起來并不好。

          Webview

          Webview[14] 是一個(gè)庫,可幫助直接在本地組件之上構(gòu)建 Web 應(yīng)用程序。執(zhí)行此操作的代碼如下:

          func main() {
           w := webview.New(true)
           defer w.Destroy()
           w.SetSize(600, 200, webview.HintNone)
           // Create a GoLang function callable from JS
           w.Bind("hello", func() string { return "World!" })
          
           // Create UI with data URI
           w.Navigate(`data:text/html,
            <!doctype html>
            <html>
             <head><title>Hello</title></head>
             <body><h1>Hello, world!</h1></body>
             <script> hello().then((x) => { console.log(x) }) </script>
            </html>`)
          
           w.Run()
          }
          

          這與 Lorca 非常相似,我認(rèn)為 Lorca 也是基于 Webview 的。盡管與 Lorca 相似,但輸出還是有些不同:

          從上圖可以看到 Webview 應(yīng)用程序窗口沒有陰影,沒有邊框,并且在屏幕的左下角進(jìn)行了初始化。可以通過將Window返回一個(gè) unsafe.Pointer到 OS 依賴的窗口對(duì)象的方法(在 macOS 中是 NSWindow)進(jìn)行定制。這是開始難的地方。

          要使用該 Window 對(duì)象,我們必須將 Go 的綁定寫入本地組件。舉例來說,如果我們希望我們的窗口居中啟動(dòng),我們會(huì)調(diào)用 NSWindow 的 Center 方法。因此,我們需要在三個(gè)文件中寫綁定(改編自 gogoa[15]):

          ns_window.go

          package main
          // #cgo CFLAGS: -x objective-c
          // #cgo LDFLAGS: -framework Cocoa
          //#include "ns_window.h"
          import "C"
          import "unsafe"
          type NSWindow struct {
           ptr unsafe.Pointer
          }
          func (self *NSWindow) Center() {
           C.Center(self.ptr)
          }
          

          ns_window.h

          #include <Cocoa/Cocoa.h>
          void Center(void *);
          

          ns_window.m

          #include "ns_window.h"
          void Center(void *self) {
            NSWindow *window = self;
            [window center];
          }
          

          然后在main()函數(shù)中,我們可以將窗口居中:

          window := NSWindow {w.Window()}
          window.Center()
          

          與 Lorca 不同,Webview 可以針對(duì)我們的應(yīng)用程序進(jìn)行完全自定義。問題在于它需要一些工作。

          Webview 的一些其他部分使得用它變得有些困難:

          1. 如果使用 Bazel 和 gazelle,則webview生成的Build.bazel文件不正確,clinkopts=["-framework WebKit"] 必須對(duì)其進(jìn)行修補(bǔ)。
          2. 調(diào)用 w.Init 僅在w.Navigate被調(diào)用時(shí)有效,但隨后w.Eval調(diào)用將停止工作。
          3. 要設(shè)置標(biāo)題,您可以如上所述編寫綁定,或者您必須使用Dispatch方法w.Dispatch(func() { w.SetTitle("Title") })。

          我不確定有多少是Webview,有多少是 NSWindow。我需要進(jìn)行更多的調(diào)查和學(xué)習(xí),才能更清楚地說明這些發(fā)生的原因。

          Electron

          之前的文章[16]是關(guān)于構(gòu)建一個(gè)簡單的 Electron 應(yīng)用程序的,該應(yīng)用程序如下所示:

          Electron 用于許多大型產(chǎn)品,例如 VSCode。這可能是因?yàn)閷⑺袃?nèi)容捆綁到一個(gè)應(yīng)用程序中使可移植性變得更加簡單,并且可以廣泛地定制應(yīng)用程序。將應(yīng)用程序與瀏覽器和 Node.js 捆綁在一起的不利之處在于,它導(dǎo)致程序 非常龐大

          讓 Go 與 Electron 一起工作也有些困難。但有一些框架可以簡化[17]此過程,例如 go-astilectron[18],不過這些框架很復(fù)雜,并且大多數(shù)功能不完整。另一種方法可能是使用我之前寫過的[19] Go 編譯為 WASM ,但這也不是簡單的解決方案。

          Electron 的優(yōu)勢在于它是便攜式的,可定制的,并且經(jīng)過了應(yīng)用程序分發(fā)的嚴(yán)格測試。只是和 Go 結(jié)合有點(diǎn)復(fù)雜。

          三者比較

          我認(rèn)為要進(jìn)行的主要比較是可定制性與簡單性。到目前為止,Lorca 是最簡單的,其可定制性非常有限,Webview 可以完全自定義,但有些困難,而 Electron 則可以完全自定義,但很難與 Go 一起使用。

          同樣,框架之間的捆綁包大小也有很大差異。Lorca 的二進(jìn)制文件大小為 8.7 MB,Webview 的大小為 3.7Mb,Electron 的大小為 157Mb

          調(diào)試工具也有所不同:Lorca 和 Electron 使用 Chrome 開發(fā)工具,而 Webview 使用 Safari 開發(fā)工具。

          結(jié)論

          Lorca 和 Webview 都可以與 Go 一起很好地使用,最終二進(jìn)制較小,并且具有類似的 API。主要區(qū)別在于基礎(chǔ)渲染器(本機(jī))和調(diào)試工具。

          我認(rèn)為 Electron 與 Go 一起使用可能太復(fù)雜了,但沒有太多困難。

          一個(gè)潛在的工作流程是在開發(fā)和 Webview 分發(fā)期間使用 Lorca。Lorca 提供了用于調(diào)試和開發(fā)的熟悉工具,其中 Webview 提供了可分發(fā)的可定制性。Lorca 也是很好的備份,可以交叉編譯到 Webview 不支持的其他操作系統(tǒng)。

          注意:還有更多類似的選項(xiàng),wails[20]gotk[21] 可以提供其他方式來構(gòu)建/分發(fā)應(yīng)用程序。

          作者:Graham Jenson

          原文鏈接:https://maori.geek.nz/golang-desktop-app-webview-vs-lorca-vs-electron-a5e6b2869391

          譯者:polaris

          參考資料

          [1]

          Electron: https://www.electronjs.org/

          [2]

          Node.js: https://nodejs.org/

          [3]

          Chromium: https://www.chromium.org/

          [4]

          go-app: https://github.com/maxence-charriere/go-app

          [5]

          go-astilectron: https://github.com/asticode/go-astilectron

          [6]

          Lorca: https://github.com/zserge/lorca

          [7]

          dev-tools communication protocol: https://chromedevtools.github.io/devtools-protocol/

          [8]

          Webview: https://github.com/webview/webview

          [9]

          webview: https://developer.apple.com/documentation/webkit/webview

          [10]

          構(gòu)建一個(gè)簡單的 electron 應(yīng)用程序的文章: https://maori.geek.nz/building-an-electron-app-with-bazel-d124ed550957

          [11]

          Lorca: https://github.com/zserge/lorca

          [12]

          dev-tools: https://chromedevtools.github.io/devtools-protocol/

          [13]

          自 2014 年以來: https://maori.geek.nz/jquery-promises-and-deferreds-i-promise-this-will-be-short-d10275f82717

          [14]

          Webview: https://github.com/webview/webview

          [15]

          gogoa: https://github.com/alediaferia/gogoa

          [16]

          之前的文章: https://maori.geek.nz/building-an-electron-app-with-bazel-d124ed550957

          [17]

          簡化: https://github.com/asticode/go-astilectron

          [18]

          go-astilectron: https://github.com/asticode/go-astilectron

          [19]

          之前寫過的: https://maori.geek.nz/a-web-app-using-bazel-golang-wasm-and-proto-c020914f4341

          [20]

          wails: https://github.com/wailsapp/wails

          [21]

          gotk: https://github.com/gotk3/gotk3


          主站蜘蛛池模板: 精品国产免费观看一区| 亚洲AV无码一区二区大桥未久| 精品无码综合一区二区三区| 无码精品前田一区二区| 国产经典一区二区三区蜜芽| 久久久久人妻一区精品| 国产在线无码视频一区| 麻豆精品人妻一区二区三区蜜桃| 久久精品视频一区| 香蕉视频一区二区三区| 日韩精品一区二三区中文| 精品国产区一区二区三区在线观看 | 无码人妻精品一区二区三区99不卡| 韩国一区二区视频| 中文字幕无线码一区二区| 亚洲国产精品一区二区久久| 少妇无码AV无码一区| 亚洲综合av永久无码精品一区二区| 一区二区免费电影| 久久精品国产一区二区| 国产一区二区三区在线影院 | 亚洲制服中文字幕第一区| 国产自产在线视频一区| 久久精品一区二区三区中文字幕| 一区二区在线视频观看| 国产一区二区三区在线影院| 中文字幕日本一区| 国产精品一区二区久久不卡| 国精产品一区一区三区| 色欲精品国产一区二区三区AV| 精品无码一区二区三区爱欲 | 成人毛片一区二区| 国产免费一区二区三区VR| 国产免费一区二区三区VR| 久久综合精品国产一区二区三区| 97精品国产福利一区二区三区| 国产精品丝袜一区二区三区 | 久久se精品一区二区影院| 一夲道无码人妻精品一区二区| 国产AV午夜精品一区二区三区| 污污内射在线观看一区二区少妇|