用go-module作為包管理器搭建go的web伺服器

detectiveHLH發表於2019-06-10

本篇部落格主要介紹瞭如何從零開始,使用Go Module作為依賴管理,基於Gin來一步一步搭建Go的Web伺服器。並使用Endless來使伺服器平滑重啟,使用Swagger來自動生成Api文件。

原始碼在此處:專案原始碼

大家可以先檢視原始碼,然後再根據本篇文章,來了解搭建過程中伺服器的一些細節。

搭建環境

以下所有的步驟都基於MacOS。

安裝go

在這裡推薦使用homebrew進行安裝。當然你也可以使用原始碼安裝。

brew install go

跑完命令之後,在命令列輸入go。如果在命令列看到如下輸出,則代表安裝成功。

Go is a tool for managing Go source code.
Usage:
    go <command> [arguments]
The commands are:
...
...

需要注意的是,go的版本需要在1.11之上,否則無法使用go module。以下是我的go的版本。

go version
# go version go1.12.5 darwin/amd64

IDE

推薦使用GoLand

配置GOPATH

開啟GoLand,在GoLand的設定中找到Global GOPATH,將其設定為$HOME/go$HOME目錄就是你的電腦的使用者目錄,如果該目錄下沒有go目錄的話,也不需要新建,當我們在後面的操作中初始化模組的時候,會自動的在使用者目錄下新建go目錄。

啟用GO Module

同樣,在GoLand中設定中找到Go Modules (vgo)。勾選Enable Go Modules (vgo) integration前的選擇框來啟用Go Moudle

搭建專案框架

新建目錄

在你常用的工作區新建一個目錄,如果你有github的專案,可以直接clone下來。

初始化go module

go mod init $MODULE_NAME

在剛剛新建的專案的根目錄下,使用上述命令來初始化go module。該命令會在專案根目錄下新建一個go.mod的檔案。

如果你的專案是從github上clone下來的,$MODULE_NAME這個引數就不需要了。它會預設為github.com/$GITHUB_USER_NAME/$PROJECT_NAME

例如本專案就是github.com/detectiveHLH/go-backend-starter;如果是在本地新建的專案,則必須要加上最後一個引數。否則就會遇到如下的錯誤。

go: cannot determine module path for source directory /Users/hulunhao/Projects/go/test/src (outside GOPATH, no import comments)

初始化完成之後的go.mod檔案內容如下。

module github.com/detectiveHLH/go-backend-starter

go 1.12

新建main.go

在專案的根目錄下新建main.go。程式碼如下。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("This works")
}

執行main.go

在根目錄下使用go run main.go,如果看到命令列中輸出This works則代表基礎的框架已經搭建完成。接下來我們開始將Gin引入框架。

引入Gin

Gin是一個用Go實現的HTTP Web框架,我們使用Gin來作為starter的Base Framework。

安裝Gin

直接通過go get命令來安裝

go get github.com/gin-gonic/gin

安裝成功之後,我們可以看到go.mod檔案中的內容發生了變化。

並且,我們在設定的GOPATH下,並沒有看到剛剛安裝的依賴。實際上,依賴安裝到了$GOPATH/pkg/mod下。

module github.com/detectiveHLH/go-backend-starter

go 1.12

require github.com/gin-gonic/gin v1.4.0 // indirect

同時,也生成了一個go.sum檔案。內容如下。

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

用過Node的人都知道,在安裝完依賴之後會生成一個package-lock.json檔案,來鎖定依賴的版本。以防止後面重新安裝依賴時,安裝了新的版本,但是與現有的程式碼不相容,這會帶來一些不必要的BUG。

但是這個go.sum檔案並不是這個作用。我們可以看到go.mod中只記錄了一個Gin的依賴,而go.sum中則有非常多。是因為go.mod中只記錄了最頂層,就是我們直接使用命令列安裝的依賴。但是要知道,一個開源的包通常都會依賴很多其他的依賴包。

而go.sum就是記錄所有頂層和其中間接依賴的依賴包的特定版本的檔案,為每一個依賴版本生成一個特定的雜湊值,從而在一個新環境啟用該專案時,可以做到對專案依賴的100%還原。go.sum還會保留一些過去使用過的版本的資訊。

在go module下,不需要vendor目錄來保證可重現的構建,而是通過go.mod檔案來對專案中的每一個依賴進行精確的版本管理。

如果之前的專案用的是vendor,那麼重新用go.mod重新編寫不太現實。我們可以使用go mod vendor命令將之前專案所有的依賴拷貝到vendor目錄下,為了保證相容性,在vendor目錄下的依賴並不像go.mod一樣。拷貝之後的目錄不包含版本號。

而且通過上面安裝gin可以看出,通常情況下,go.mod檔案是不需要我們手動編輯的,當我們執行完命令之後,go.mod也會自動的更新相應的依賴和版本號。

下面我們來了解一下go mod的相關命令。

  • init 初始化go module
  • download 下載go.mod中的依賴到本地的快取目錄中($GOPATH/pkg/mod)下
  • edit 編輯go.mod,通過命令列手動升級和獲取依賴
  • vendor 將專案依賴拷貝到vendor下
  • tidy 安裝缺少的依賴,捨棄無用的依賴
  • graph 列印模組依賴圖
  • verify 驗證依賴是否正確

還有一個命令值得提一下,go list -m all可以列出當前專案的構建列表。

修改main.go

修改main.go的程式碼如下。

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    fmt.Println("This works.")
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "success": true,
            "code": 200,
            "message": "This works",
            "data": nil,
        })
    })
    r.Run()
}

上述的程式碼引入了路由,熟悉Node的應該可以看出,這個與koa-router的用法十分相似。

啟動伺服器

照著上述執行main.go的步驟,執行main.go。就可以在控制檯看到如下的輸出。

This works.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /hello                    --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

此時,伺服器已經在8080埠啟動了。然後在瀏覽器中訪問http://localhost:8080/hello,就可以看到伺服器的正常返回。同時,伺服器這邊也會列印相應的日誌。

[GIN] 2019/06/08 - 17:41:34 | 200 |     214.213µs |             ::1 | GET      /hello

構建路由

新建路由模組

在根目錄下新建router目錄。在router下,新建router.go檔案,程式碼如下。

package router

import "github.com/gin-gonic/gin"

func InitRouter() *gin.Engine {
    router := gin.New()
    apiVersionOne := router.Group("/api/v1/")
    apiVersionOne.GET("hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "success": true,
            "code": 200,
            "message": "This works",
            "data": nil,
        })
    })
    return router
}

在這個檔案中,匯出了一個InitRouter函式,該函式返回gin.Engine型別。該函式還定義了一個路由為/api/v1/hello的GET請求。

在main函式中引入路由

將main.go的程式碼改為如下。

package main

import (
    "fmt"
    "github.com/detectiveHLH/go-backend-starter/router"
)

func main() {
    r := router.InitRouter()
    r.Run()
}

然後執行main.go,啟動之後,訪問http://localhost:8080/api/v1/hello,可以看到,與之前訪問/hello路由的結果是一樣的。

到此為止,我們已經擁有了一個擁有簡單功能的Web伺服器。那麼問題來了,這樣的一個開放的伺服器,只要知道了地址,你的伺服器就知道暴露給其他人了。這樣會帶來一些安全隱患。所以我們需要給介面加上鑑權,只有通過認證的呼叫方,才有許可權呼叫伺服器介面。所以接下來,我們需要引入JWT。

引入JWT鑑權

使用go get命令安裝jwt-go依賴。

go get github.com/dgrijalva/jwt-go

新建jwt鑑權檔案

在根目錄下新建middleware/jwt目錄,在jwt目錄下新建jwt.go檔案,程式碼如下。

package jwt

import (
  "github.com/detectiveHLH/go-backend-starter/consts"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func Jwt() gin.HandlerFunc {
    return func(c *gin.Context) {
        var code int
        var data interface{}

        code = consts.SUCCESS
        token := c.Query("token")
        if token == "" {
            code = consts.INVALID_PARAMS
        } else {
            claims, err := util.ParseToken(token)
            if err != nil {
                code = consts.ERROR_AUTH_CHECK_TOKEN_FAIL
            } else if time.Now().Unix() > claims.ExpiresAt {
                code = consts.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
            }
        }

        if code != consts.SUCCESS {
            c.JSON(http.StatusUnauthorized, gin.H{
                "code": code,
                "msg":  consts.GetMsg(code),
                "data": data,
            })

            c.Abort()
            return
        }

        c.Next()
    }
}

引入常量

此時,程式碼中會有錯誤,是因為我們沒有宣告consts這個包,其中的變數SUCCESS、INVALID_PARAMS和ERROR_AUTH_CHECK_TOKEN_FAIL是未定義的。根據code獲取伺服器返回資訊的函式GetMsg也沒定義。同樣沒有定義的還有util.ParseToken(token)和claims.ExpiresAt。所以我們要新建consts包。我們在根目錄下新建consts目錄,並且在consts目錄下新建code.go,將定義好的一些常量引進去,程式碼如下。

新建const檔案

const (
    SUCCESS        = 200
    ERROR          = 500
    INVALID_PARAMS = 400
)

新建message檔案

再新建message.go檔案,程式碼如下。

var MsgFlags = map[int]string{
    SUCCESS:                         "ok",
    ERROR:                           "fail",
    INVALID_PARAMS:                  "請求引數錯誤",
}

func GetMsg(code int) string {
    msg, ok := MsgFlags[code]
    if ok {
        return msg
    }
    return MsgFlags[ERROR]
}

新建util

在根目錄下新建util,並且在util下新建jwt.go,程式碼如下。

package util

import (
    "github.com/dgrijalva/jwt-go"
    "time"
)

var jwtSecret = []byte(setting.AppSetting.JwtSecret)

type Claims struct {
    Username string `json:"username"`
    Password string `json:"password"`
    jwt.StandardClaims
}

func GenerateToken(username, password string) (string, error) {
    nowTime := time.Now()
    expireTime := nowTime.Add(3 * time.Hour)
    claims := Claims{
        username,
        password,
        jwt.StandardClaims {
            ExpiresAt : expireTime.Unix(),
            Issuer : "go-backend-starter",
        },
    }
    tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    token, err := tokenClaims.SignedString(jwtSecret)

    return token, err
}

func ParseToken(token string) (*Claims, error) {
    tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })
    if tokenClaims != nil {
        if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
            return claims, nil
        }
    }

    return nil, err
}

新建setting包

在上面的util中,setting包並沒有定義,所以在這個步驟中我們需要定義setting包。

使用go get命令安裝依賴。

go get gopkg.in/ini.v1

在專案根目錄下新建setting目錄,並在setting目錄下新建setting.go檔案,程式碼如下。

package setting

import (
    "gopkg.in/ini.v1"
    "log"
)

type App struct {
    JwtSecret string
}
type Server struct {
    Ip   string
    Port string
}
type Database struct {
    Type        string
    User        string
    Password    string
    Host        string
    Name        string
    TablePrefix string
}

var AppSetting = &App{}
var ServerSetting = &Server{}
var DatabaseSetting = &Database{}
var config *ini.File

func Setup() {
    var err error
    config, err = ini.Load("config/app.ini")
    if err != nil {
        log.Fatal("Fail to parse 'config/app.ini': %v", err)
    }
    mapTo("app", AppSetting)
    mapTo("server", ServerSetting)
    mapTo("database", DatabaseSetting)
}

func mapTo(section string, v interface{}) {
    err := config.Section(section).MapTo(v)
    if err != nil {
        log.Fatalf("Cfg.MapTo RedisSetting err: %v", err)
    }
}

新建配置檔案

在專案根目錄下新建config目錄,並新建app.ini檔案,內容如下。

[app]
JwtSecret = 233
[server]
Ip : localhost
Port : 8000
Url : 127.0.0.1:27017
[database]
Type = mysql
User = $YOUR_USERNAME
Password = $YOUR_PASSWORD
Host = 127.0.0.1:3306
Name = golang_test
TablePrefix = golang_test_

實現登入介面

新增登入介面

到此為止,通過jwt token進行鑑權的邏輯已經全部完成,剩下的就需要實現登入介面來將token在使用者登入成功之後返回給使用者。

使用go get命令安裝依賴。

go get github.com/astaxie/beego/validation

在router下新建login.go,程式碼如下。

package router

import (
    "github.com/astaxie/beego/validation"
    "github.com/detectiveHLH/go-backend-starter/consts"
    "github.com/detectiveHLH/go-backend-starter/util"
    "github.com/gin-gonic/gin"
    "net/http"
)

type auth struct {
    Username string `valid:"Required; MaxSize(50)"`
    Password string `valid:"Required; MaxSize(50)"`
}


func Login(c *gin.Context) {
    appG := util.Gin{C: c}
    valid := validation.Validation{}
    username := c.Query("username")
    password := c.Query("password")

    a := auth{Username: username, Password: password}
    ok, _ := valid.Valid(&a)
    if !ok {
        appG.Response(http.StatusOK, consts.INVALID_PARAMS, nil)
        return
    }

    authService := authentication.Auth{Username: username, Password: password}
    isExist, err := authService.Check()
    if err != nil {
        appG.Response(http.StatusOK, consts.ERROR_AUTH_CHECK_TOKEN_FAIL, nil)
        return
    }

    if !isExist {
        appG.Response(http.StatusOK, consts.ERROR_AUTH, nil)
        return
    }

    token, err := util.GenerateToken(username, password)
    if err != nil {
        appG.Response(http.StatusOK, consts.ERROR_AUTH_TOKEN, nil)
        return
    }

    appG.Response(http.StatusOK, consts.SUCCESS, map[string]string{
        "token": token,
    })
}

新增返回類

在util包下新增response.go檔案,程式碼如下。

package util

import (
    "github.com/detectiveHLH/go-backend-starter/consts"
    "github.com/gin-gonic/gin"
)

type Gin struct {
    C *gin.Context
}

func (g *Gin) Response(httpCode, errCode int, data interface{}) {
    g.C.JSON(httpCode, gin.H{
        "code": httpCode,
        "msg":  consts.GetMsg(errCode),
        "data": data,
    })

    return
}

新增鑑權邏輯

除了返回類,login.go中還有關鍵的鑑權邏輯還沒有實現。在根目錄下新建service/authentication目錄,在該目錄下新建auth.go檔案,程式碼如下。

package authentication

import "fmt"

type Auth struct {
    Username string
    Password string
}

func (a *Auth) Check() (bool, error) {
    userName := a.Username
    passWord := a.Password
  // todo:實現自己的鑑權邏輯
    fmt.Println(userName, passWord)
    return true, nil
}

在此處,需要自己真正的根據業務去實現對使用者呼叫介面的合法性校驗。例如,可以根據使用者的使用者名稱和密碼去資料庫做驗證。

修改router.go

修改router.go中的程式碼如下。

package router

import (
    "github.com/detectiveHLH/go-backend-starter/middleware/jwt"
    "github.com/gin-gonic/gin"
)

func InitRouter() *gin.Engine {
    router := gin.New()

    router.GET("/login", Login)
    apiVersionOne := router.Group("/api/v1/")
  
    apiVersionOne.Use(jwt.Jwt())
  
    apiVersionOne.GET("hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "success": true,
            "code": 200,
            "message": "This works",
            "data": nil,
        })
    })
    return router
}

可以看到,我們在路由檔案中加入了/login介面,並使用了我們自定義的jwt鑑權的中介軟體。只要是在v1下的路由,請求之前都會先進入jwt中進行鑑權,鑑權通過之後才能繼續往下執行。

執行main.go

到此,我們使用go run main.go啟動伺服器,訪問http://localhost:8080/api/v1/hello會遇到如下錯誤。

{
    "code": 400,
    "data": null,
    "msg": "請求引數錯誤"
}

這是因為我們加入了鑑權,凡是需要鑑權的介面,都需要帶上引數token。而要獲取token則必須要先要登入,假設我們的使用者名稱是Tom,密碼是123。以此來呼叫登入介面。

http://localhost:8080/login?username=Tom&password=123

在瀏覽器中訪問如上的url之後,可以看到返回如下。

{
    "code": 200,
    "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlRvbSIsInBhc3N3b3JkIjoiMTIzIiwiZXhwIjoxNTYwMTM5MTE3LCJpc3MiOiJnby1iYWNrZW5kLXN0YXJ0ZXIifQ.I-RSi-xVV1Tk_2iBWolF1u94Y7oVBQXnHh6OI2YKJ6U"
    },
    "msg": "ok"
}

有了token之後,我們再呼叫hello介面,可以看到資料正常的返回了。

http://localhost:8080/api/v1/hello?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlRvbSIsInBhc3N3b3JkIjoiMTIzIiwiZXhwIjoxNTYwMTM5MTE3LCJpc3MiOiJnby1iYWNrZW5kLXN0YXJ0ZXIifQ.I-RSi-xVV1Tk_2iBWolF1u94Y7oVBQXnHh6OI2YKJ6U

一般的處理方法是,前端拿到這個token,利用持久化儲存存下來,然後之後的每次請求都將token寫在header中發給後端。後端先通過header中的token來校驗呼叫介面的合法性,驗證通過之後才進行真正的介面呼叫。

而在這我將token寫在了request param中,只是為了做一個例子來展示。

引入swagger

完成了基本的框架之後,我們就開始為介面引入swagger文件。寫過java的同學應該對swagger不陌生。往常寫API文件,都是手寫。即每個介面的每一個引數,都需要手打。

而swagger不一樣,swagger只需要你在介面上打上幾個註解(Java中的操作),就可以自動為你生成swagger文件。而在go中,我們是通過註釋的方式來實現的,接下來我們安裝gin-swagger

安裝依賴

go get github.com/swaggo/gin-swagger
go get -u github.com/swaggo/gin-swagger/swaggerFiles
go get -u github.com/swaggo/swag/cmd/swag
go get github.com/ugorji/go/codec
go get github.com/alecthomas/template

在router中注入swagger

引入依賴之後,我們需要在router/router.go中注入swagger。在import中加入_ "github.com/detectiveHLH/go-backend-starter/docs"

並在router := gin.New()之後加入如下程式碼。

router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

為介面編寫swagger註釋

在router/login.go中的Login函式上方加上如下注釋。

// @Summary 登入
// @Produce  json
// @Param username query string true "username"
// @Param password query string true "password"
// @Success 200 {string} json "{"code":200,"data":{},"msg":"ok"}"
// @Router /login [get]

初始化swagger

在專案根目錄下使用swag init命令來初始化swagger文件。該命令將會在專案根目錄生成docs目,內容如下。

.
├── docs.go
├── swagger.json
└── swagger.yaml

檢視swagger文件

執行main.go,然後在瀏覽器訪問http://localhost:8080/swagger/index.html就可以看到swagger根據註釋自動生成的API文件了。

引入Endless

安裝Endless

go get github.com/fvbock/endless

修改main.go

package main

import (
    "fmt"
    "github.com/detectiveHLH/go-backend-starter/router"
    "github.com/fvbock/endless"
    "log"
    "syscall"
)

func main() {
    r := router.InitRouter()

  address := fmt.Sprintf("%s:%s", setting.ServerSetting.Ip, setting.ServerSetting.Port)
    server := endless.NewServer(address, r)
    server.BeforeBegin = func(add string) {
        log.Printf("Actual pid is %d", syscall.Getpid())
    }

  err := server.ListenAndServe()
    if err != nil {
        log.Printf("Server err: %v", err)
    }
}

寫在後面

對比起沒有go module的依賴管理,現在的go module更像是Node.js中的package.json,也像是Java中的pom.xml,唯一不同的是pom.xml需要手動更新。

當我們拿到有go module專案的時候,不用擔心下來依賴時,因為版本問題可能導致的一些相容問題。直接使用go mod中的命令就可以將制定了版本的依賴全部安裝,其效果類似於Node.js中的npm install

go module定位module的方式,與Node.js尋找依賴的邏輯一樣,Node會從當前命令執行的目錄開始,依次向上查詢node_modules中是否有這個依賴,直到找到。go則是依次向上查詢go.mod檔案,來定位一個模組。

相信之後go之後的依賴管理,會越來越好。

Happy hacking.

參考:

往期文章:

相關:

  • 個人網站: Lunhao Hu
  • 微信公眾號: SH的全棧筆記(或直接在新增公眾號介面搜尋微訊號LunhaoHu)

相關文章