Total Pageviews

Thursday, 12 August 2021

go-backend-starter

 Based on gin, gorm RESTFUL style backend framework, using go mod as the project's package manager.

中文 | English

介绍

使用Go Module作为依赖管理,基于Gin搭建Go的Web服务器,使用Endless来使服务器平滑重启,使用Swagger来自动生成Api文档。

安装

git clone https://github.com/detectiveHLH/go-backend-starter.git
cd go-backend-starter
go get
go run main.go

注意

  • go的版本需要高于1.11
  • 使用goland时,需要确保Go module是Enable状态

使用

from https://github.com/detectiveHLH/go-backend-starter
-----

用go-module作为包管理器搭建go的web服务器

如何从零开始,使用Go Module作为依赖管理,基于Gin来一步一步搭建Go的Web服务器。并使用Endless来使服务器平滑重启,使用Swagger来自动生成Api文档。

源码在此处:项目源码

大家可以先查看源码,然后再根据本篇文章,来了解搭建过程中服务器的一些细节。

搭建环境

安装go

你可以使用源码安装。

跑完命令之后,在命令行输入go。如果在命令行看到如下输出,则代表安装成功。

  1. Go is a tool for managing Go source code.
  2. Usage:
  3. go <command> [arguments]
  4. The commands are:
  5. ...
  6. ...

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

  1. go version
  2. # 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

  1. 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;如果是在本地新建的项目,则必须要加上最后一个参数。否则就会遇到如下的错误。

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

初始化完成之后的go.mod文件内容如下。

  1. module github.com/detectiveHLH/go-backend-starter
  2. go 1.12

新建main.go

在项目的根目录下新建main.go。代码如下。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. fmt.Println("This works")
  7. }

运行main.go

在根目录下使用go run main.go,如果看到命令行中输出This works则代表基础的框架已经搭建完成。接下来我们开始将Gin引入框架。

引入Gin

Gin是一个用Go实现的HTTP Web框架,我们使用Gin来作为starter的Base Framework。

安装Gin

直接通过go get命令来安装

  1. go get github.com/gin-gonic/gin

安装成功之后,我们可以看到go.mod文件中的内容发生了变化。

并且,我们在设定的GOPATH下,并没有看到刚刚安装的依赖。实际上,依赖安装到了$GOPATH/pkg/mod下。

  1. module github.com/detectiveHLH/go-backend-starter
  2. go 1.12
  3. require github.com/gin-gonic/gin v1.4.0 // indirect

同时,也生成了一个go.sum文件。内容如下。

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

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. )
  6. func main() {
  7. fmt.Println("This works.")
  8. r := gin.Default()
  9. r.GET("/hello", func(c *gin.Context) {
  10. c.JSON(200, gin.H{
  11. "success": true,
  12. "code": 200,
  13. "message": "This works",
  14. "data": nil,
  15. })
  16. })
  17. r.Run()
  18. }

上述的代码引入了路由,熟悉Node的应该可以看出,这个与koa-router的用法十分相似。

启动服务器

照着上述运行main.go的步骤,运行main.go。就可以在控制台看到如下的输出。

  1. This works.
  2. [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
  3. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
  4. - using env: export GIN_MODE=release
  5. - using code: gin.SetMode(gin.ReleaseMode)
  6. [GIN-debug] GET /hello --> main.main.func1 (3 handlers)
  7. [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
  8. [GIN-debug] Listening and serving HTTP on :8080

此时,服务器已经在8080端口启动了。然后在浏览器中访问http://localhost:8080/hello,就可以看到服务器的正常返回。同时,服务器这边也会打印相应的日志。

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

构建路由

新建路由模块

在根目录下新建router目录。在router下,新建router.go文件,代码如下。

  1. package router
  2. import "github.com/gin-gonic/gin"
  3. func InitRouter() *gin.Engine {
  4. router := gin.New()
  5. apiVersionOne := router.Group("/api/v1/")
  6. apiVersionOne.GET("hello", func(c *gin.Context) {
  7. c.JSON(200, gin.H{
  8. "success": true,
  9. "code": 200,
  10. "message": "This works",
  11. "data": nil,
  12. })
  13. })
  14. return router
  15. }

在这个文件中,导出了一个InitRouter函数,该函数返回gin.Engine类型。该函数还定义了一个路由为/api/v1/hello的GET请求。

在main函数中引入路由

将main.go的代码改为如下。

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/detectiveHLH/go-backend-starter/router"
  5. )
  6. func main() {
  7. r := router.InitRouter()
  8. r.Run()
  9. }

然后运行main.go,启动之后,访问http://localhost:8080/api/v1/hello,可以看到,与之前访问/hello路由的结果是一样的。

到此为止,我们已经拥有了一个拥有简单功能的Web服务器。那么问题来了,这样的一个开放的服务器,只要知道了地址,你的服务器就知道暴露给其他人了。这样会带来一些安全隐患。所以我们需要给接口加上鉴权,只有通过认证的调用方,才有权限调用服务器接口。所以接下来,我们需要引入JWT。

引入JWT鉴权

使用go get命令安装jwt-go依赖。

  1. go get github.com/dgrijalva/jwt-go

新建jwt鉴权文件

在根目录下新建middleware/jwt目录,在jwt目录下新建jwt.go文件,代码如下。

  1. package jwt
  2. import (
  3. "github.com/detectiveHLH/go-backend-starter/consts"
  4. "github.com/gin-gonic/gin"
  5. "net/http"
  6. "time"
  7. )
  8. func Jwt() gin.HandlerFunc {
  9. return func(c *gin.Context) {
  10. var code int
  11. var data interface{}
  12. code = consts.SUCCESS
  13. token := c.Query("token")
  14. if token == "" {
  15. code = consts.INVALID_PARAMS
  16. } else {
  17. claims, err := util.ParseToken(token)
  18. if err != nil {
  19. code = consts.ERROR_AUTH_CHECK_TOKEN_FAIL
  20. } else if time.Now().Unix() > claims.ExpiresAt {
  21. code = consts.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
  22. }
  23. }
  24. if code != consts.SUCCESS {
  25. c.JSON(http.StatusUnauthorized, gin.H{
  26. "code": code,
  27. "msg": consts.GetMsg(code),
  28. "data": data,
  29. })
  30. c.Abort()
  31. return
  32. }
  33. c.Next()
  34. }
  35. }

引入常量

此时,代码中会有错误,是因为我们没有声明consts这个包,其中的变量SUCCESS、INVALID_PARAMS和ERROR_AUTH_CHECK_TOKEN_FAIL是未定义的。根据code获取服务器返回信息的函数GetMsg也没定义。同样没有定义的还有util.ParseToken(token)和claims.ExpiresAt。所以我们要新建consts包。我们在根目录下新建consts目录,并且在consts目录下新建code.go,将定义好的一些常量引进去,代码如下。

新建const文件

  1. const (
  2. SUCCESS = 200
  3. ERROR = 500
  4. INVALID_PARAMS = 400
  5. )

新建message文件

再新建message.go文件,代码如下。

  1. var MsgFlags = map[int]string{
  2. SUCCESS: "ok",
  3. ERROR: "fail",
  4. INVALID_PARAMS: "请求参数错误",
  5. }
  6. func GetMsg(code int) string {
  7. msg, ok := MsgFlags[code]
  8. if ok {
  9. return msg
  10. }
  11. return MsgFlags[ERROR]
  12. }

新建util

在根目录下新建util,并且在util下新建jwt.go,代码如下。

  1. package util
  2. import (
  3. "github.com/dgrijalva/jwt-go"
  4. "time"
  5. )
  6. var jwtSecret = []byte(setting.AppSetting.JwtSecret)
  7. type Claims struct {
  8. Username string `json:"username"`
  9. Password string `json:"password"`
  10. jwt.StandardClaims
  11. }
  12. func GenerateToken(username, password string) (string, error) {
  13. nowTime := time.Now()
  14. expireTime := nowTime.Add(3 * time.Hour)
  15. claims := Claims{
  16. username,
  17. password,
  18. jwt.StandardClaims {
  19. ExpiresAt : expireTime.Unix(),
  20. Issuer : "go-backend-starter",
  21. },
  22. }
  23. tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  24. token, err := tokenClaims.SignedString(jwtSecret)
  25. return token, err
  26. }
  27. func ParseToken(token string) (*Claims, error) {
  28. tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
  29. return jwtSecret, nil
  30. })
  31. if tokenClaims != nil {
  32. if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
  33. return claims, nil
  34. }
  35. }
  36. return nil, err
  37. }

新建setting包

在上面的util中,setting包并没有定义,所以在这个步骤中我们需要定义setting包。

使用go get命令安装依赖。

  1. go get gopkg.in/ini.v1

在项目根目录下新建setting目录,并在setting目录下新建setting.go文件,代码如下。

  1. package setting
  2. import (
  3. "gopkg.in/ini.v1"
  4. "log"
  5. )
  6. type App struct {
  7. JwtSecret string
  8. }
  9. type Server struct {
  10. Ip string
  11. Port string
  12. }
  13. type Database struct {
  14. Type string
  15. User string
  16. Password string
  17. Host string
  18. Name string
  19. TablePrefix string
  20. }
  21. var AppSetting = &App{}
  22. var ServerSetting = &Server{}
  23. var DatabaseSetting = &Database{}
  24. var config *ini.File
  25. func Setup() {
  26. var err error
  27. config, err = ini.Load("config/app.ini")
  28. if err != nil {
  29. log.Fatal("Fail to parse 'config/app.ini': %v", err)
  30. }
  31. mapTo("app", AppSetting)
  32. mapTo("server", ServerSetting)
  33. mapTo("database", DatabaseSetting)
  34. }
  35. func mapTo(section string, v interface{}) {
  36. err := config.Section(section).MapTo(v)
  37. if err != nil {
  38. log.Fatalf("Cfg.MapTo RedisSetting err: %v", err)
  39. }
  40. }

新建配置文件

在项目根目录下新建config目录,并新建app.ini文件,内容如下。

  1. [app]
  2. JwtSecret = 233
  3. [server]
  4. Ip : localhost
  5. Port : 8000
  6. Url : 127.0.0.1:27017
  7. [database]
  8. Type = mysql
  9. User = $YOUR_USERNAME
  10. Password = $YOUR_PASSWORD
  11. Host = 127.0.0.1:3306
  12. Name = golang_test
  13. TablePrefix = golang_test_

实现登录接口

新增登录接口

到此为止,通过jwt token进行鉴权的逻辑已经全部完成,剩下的就需要实现登录接口来将token在用户登录成功之后返回给用户。

使用go get命令安装依赖。

  1. go get github.com/astaxie/beego/validation

在router下新建login.go,代码如下。

  1. package router
  2. import (
  3. "github.com/astaxie/beego/validation"
  4. "github.com/detectiveHLH/go-backend-starter/consts"
  5. "github.com/detectiveHLH/go-backend-starter/util"
  6. "github.com/gin-gonic/gin"
  7. "net/http"
  8. )
  9. type auth struct {
  10. Username string `valid:"Required; MaxSize(50)"`
  11. Password string `valid:"Required; MaxSize(50)"`
  12. }
  13. func Login(c *gin.Context) {
  14. appG := util.Gin{C: c}
  15. valid := validation.Validation{}
  16. username := c.Query("username")
  17. password := c.Query("password")
  18. a := auth{Username: username, Password: password}
  19. ok, _ := valid.Valid(&a)
  20. if !ok {
  21. appG.Response(http.StatusOK, consts.INVALID_PARAMS, nil)
  22. return
  23. }
  24. authService := authentication.Auth{Username: username, Password: password}
  25. isExist, err := authService.Check()
  26. if err != nil {
  27. appG.Response(http.StatusOK, consts.ERROR_AUTH_CHECK_TOKEN_FAIL, nil)
  28. return
  29. }
  30. if !isExist {
  31. appG.Response(http.StatusOK, consts.ERROR_AUTH, nil)
  32. return
  33. }
  34. token, err := util.GenerateToken(username, password)
  35. if err != nil {
  36. appG.Response(http.StatusOK, consts.ERROR_AUTH_TOKEN, nil)
  37. return
  38. }
  39. appG.Response(http.StatusOK, consts.SUCCESS, map[string]string{
  40. "token": token,
  41. })
  42. }

新增返回类

在util包下新增response.go文件,代码如下。

  1. package util
  2. import (
  3. "github.com/detectiveHLH/go-backend-starter/consts"
  4. "github.com/gin-gonic/gin"
  5. )
  6. type Gin struct {
  7. C *gin.Context
  8. }
  9. func (g *Gin) Response(httpCode, errCode int, data interface{}) {
  10. g.C.JSON(httpCode, gin.H{
  11. "code": httpCode,
  12. "msg": consts.GetMsg(errCode),
  13. "data": data,
  14. })
  15. return
  16. }

新增鉴权逻辑

除了返回类,login.go中还有关键的鉴权逻辑还没有实现。在根目录下新建service/authentication目录,在该目录下新建auth.go文件,代码如下。

  1. package authentication
  2. import "fmt"
  3. type Auth struct {
  4. Username string
  5. Password string
  6. }
  7. func (a *Auth) Check() (bool, error) {
  8. userName := a.Username
  9. passWord := a.Password
  10. // todo:实现自己的鉴权逻辑
  11. fmt.Println(userName, passWord)
  12. return true, nil
  13. }

在此处,需要自己真正的根据业务去实现对用户调用接口的合法性校验。例如,可以根据用户的用户名和密码去数据库做验证。

修改router.go

修改router.go中的代码如下。

  1. package router
  2. import (
  3. "github.com/detectiveHLH/go-backend-starter/middleware/jwt"
  4. "github.com/gin-gonic/gin"
  5. )
  6. func InitRouter() *gin.Engine {
  7. router := gin.New()
  8. router.GET("/login", Login)
  9. apiVersionOne := router.Group("/api/v1/")
  10. apiVersionOne.Use(jwt.Jwt())
  11. apiVersionOne.GET("hello", func(c *gin.Context) {
  12. c.JSON(200, gin.H{
  13. "success": true,
  14. "code": 200,
  15. "message": "This works",
  16. "data": nil,
  17. })
  18. })
  19. return router
  20. }

可以看到,我们在路由文件中加入了/login接口,并使用了我们自定义的jwt鉴权的中间件。只要是在v1下的路由,请求之前都会先进入jwt中进行鉴权,鉴权通过之后才能继续往下执行。

运行main.go

到此,我们使用go run main.go启动服务器,访问http://localhost:8080/api/v1/hello会遇到如下错误。

  1. {
  2. "code": 400,
  3. "data": null,
  4. "msg": "请求参数错误"
  5. }

这是因为我们加入了鉴权,凡是需要鉴权的接口,都需要带上参数token。而要获取token则必须要先要登录,假设我们的用户名是Tom,密码是123。以此来调用登录接口。

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

在浏览器中访问如上的url之后,可以看到返回如下。

  1. {
  2. "code": 200,
  3. "data": {
  4. "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlRvbSIsInBhc3N3b3JkIjoiMTIzIiwiZXhwIjoxNTYwMTM5MTE3LCJpc3MiOiJnby1iYWNrZW5kLXN0YXJ0ZXIifQ.I-RSi-xVV1Tk_2iBWolF1u94Y7oVBQXnHh6OI2YKJ6U"
  5. },
  6. "msg": "ok"
  7. }

有了token之后,我们再调用hello接口,可以看到数据正常的返回了。

  1. 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

安装依赖

  1. go get github.com/swaggo/gin-swagger
  2. go get -u github.com/swaggo/gin-swagger/swaggerFiles
  3. go get -u github.com/swaggo/swag/cmd/swag
  4. go get github.com/ugorji/go/codec
  5. go get github.com/alecthomas/template

在router中注入swagger

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

并在router := gin.New()之后加入如下代码。

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

为接口编写swagger注释

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

  1. // @Summary 登录
  2. // @Produce json
  3. // @Param username query string true "username"
  4. // @Param password query string true "password"
  5. // @Success 200 {string} json "{"code":200,"data":{},"msg":"ok"}"
  6. // @Router /login [get]

初始化swagger

在项目根目录下使用swag init命令来初始化swagger文档。该命令将会在项目根目录生成docs目,内容如下。

  1. .
  2. ├── docs.go
  3. ├── swagger.json
  4. └── swagger.yaml

查看swagger文档

运行main.go,然后在浏览器访问http://localhost:8080/swagger/index.html就可以看到swagger根据注释自动生成的API文档了。

引入Endless

安装Endless

  1. go get github.com/fvbock/endless

修改main.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/detectiveHLH/go-backend-starter/router"
  5. "github.com/fvbock/endless"
  6. "log"
  7. "syscall"
  8. )
  9. func main() {
  10. r := router.InitRouter()
  11. address := fmt.Sprintf("%s:%s", setting.ServerSetting.Ip, setting.ServerSetting.Port)
  12. server := endless.NewServer(address, r)
  13. server.BeforeBegin = func(add string) {
  14. log.Printf("Actual pid is %d", syscall.Getpid())
  15. }
  16. err := server.ListenAndServe()
  17. if err != nil {
  18. log.Printf("Server err: %v", err)
  19. }
  20. }

写在后面

对比起没有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.

参考:


No comments:

Post a Comment