├── conf
│   └── app.ini
├── go.mod
├── go.sum
├── middleware
├── models
├── pkg
│   └── setting.go
├── routers
└── runtime

編寫API錯誤碼包

建立錯誤碼的e模塊,在go-gin-examplepkg目錄下新建e目錄(注意新增 replace 配置),新建code.gomsg.go文件,寫入內容:

1、 code.go:

package e

const (
SUCCESS = 200
ERROR = 500
INVALID_PARAMS = 400

ERROR_EXIST_TAG = 10001
ERROR_NOT_EXIST_TAG = 10002
ERROR_NOT_EXIST_ARTICLE = 10003

ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
ERROR_AUTH_TOKEN = 20003
ERROR_AUTH = 20004
)

2、 msg.go:

package e

var MsgFlags = map[int]string {
SUCCESS : "ok",
ERROR : "fail",
INVALID_PARAMS : "請求參數錯誤",
ERROR_EXIST_TAG : "已存在該標簽名稱",
ERROR_NOT_EXIST_TAG : "該標簽不存在",
ERROR_NOT_EXIST_ARTICLE : "該文章不存在",
ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鑒權失敗",
ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超時",
ERROR_AUTH_TOKEN : "Token生成失敗",
ERROR_AUTH : "Token錯誤",
}

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

return MsgFlags[ERROR]
}

編寫工具包

go-gin-examplepkg目錄下新建util目錄(注意新增 replace 配置),并拉取com的依賴包,如下:

$ go get -u github.com/unknwon/com

編寫分頁頁碼的獲取方法

util目錄下新建pagination.go,寫入內容:

package util

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

"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func GetPage(c *gin.Context) int {
result := 0
page, _ := com.StrTo(c.Query("page")).Int()
if page > 0 {
result = (page - 1) * setting.PageSize
}

return result
}

編寫 models init

拉取gorm的依賴包,如下:

$ go get -u github.com/jinzhu/gorm

拉取mysql驅動的依賴包,如下:

$ go get -u github.com/go-sql-driver/mysql

完成后,在go-gin-examplemodels目錄下新建models.go,用于models的初始化使用

package models

import (
"log"
"fmt"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"

"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

var db *gorm.DB

type Model struct {
ID int gorm:"primary_key" json:"id"
CreatedOn int json:"created_on"
ModifiedOn int json:"modified_on"
}

func init() {
var (
err error
dbType, dbName, user, password, host, tablePrefix string
)

sec, err := setting.Cfg.GetSection("database")
if err != nil {
log.Fatal(2, "Fail to get section 'database': %v", err)
}

dbType = sec.Key("TYPE").String()
dbName = sec.Key("NAME").String()
user = sec.Key("USER").String()
password = sec.Key("PASSWORD").String()
host = sec.Key("HOST").String()
tablePrefix = sec.Key("TABLE_PREFIX").String()

db, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
user,
password,
host,
dbName))

if err != nil {
log.Println(err)
}

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return tablePrefix + defaultTableName;
}

db.SingularTable(true)
db.LogMode(true)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
}

func CloseDB() {
defer db.Close()
}

編寫項目啟動、路由文件

最基礎的準備工作完成啦,讓我們開始編寫 Demo 吧!

編寫 Demo

go-gin-example下建立main.go作為啟動文件(也就是main包),我們先寫個Demo,幫助大家理解,寫入文件內容:

package main

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"

"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func main() {
router := gin.Default()
router.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "test",
})
})

s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.HTTPPort),
Handler: router,
ReadTimeout: setting.ReadTimeout,
WriteTimeout: setting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}

s.ListenAndServe()
}

執行go run main.go,查看命令行是否顯示

[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 /test --> main.main.func1 (3 handlers)

在本機執行curl 127.0.0.1:8000/test,檢查是否返回{"message":"test"}

那么,我們來延伸一下 Demo 所涉及的知識點!

標準庫
Gin
&http.Server 和 ListenAndServe?

1、http.Server:

type Server struct {
Addr string
Handler Handler
TLSConfig *tls.Config
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
}

2、 ListenAndServe:

func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

開始監聽服務,監聽 TCP 網絡地址,Addr 和調用應用程序處理連接上的請求。

我們在源碼中看到Addr是調用我們在&http.Server中設置的參數,因此我們在設置時要用&,我們要改變參數的值,因為我們ListenAndServe和其他一些方法需要用到&http.Server中的參數,他們是相互影響的。

3、 http.ListenAndServe和 連載一 的r.Run()有區別嗎?

我們看看r.Run的實現:

func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()

address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}

通過分析源碼,得知本質上沒有區別,同時也得知了啟動gin時的監聽 debug 信息在這里輸出。

4、 為什么 Demo 里會有WARNING

首先我們可以看下Default()的實現

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}

大家可以看到默認情況下,已經附加了日志、恢復中間件的引擎實例。并且在開頭調用了debugPrintWARNINGDefault(),而它的實現就是輸出該行日志

func debugPrintWARNINGDefault() {
debugPrint([WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.<br>)
}

而另外一個Running in "debug" mode. Switch to "release" mode in production.,是運行模式原因,并不難理解,已在配置文件的管控下 :-),運維人員隨時就可以修改它的配置。

5、 Demo 的router.GET等路由規則可以不寫在main包中嗎?

我們發現router.GET等路由規則,在 Demo 中被編寫在了main包中,感覺很奇怪,我們去抽離這部分邏輯!

go-gin-examplerouters目錄新建router.go文件,寫入內容:

package routers

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

"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

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

r.Use(gin.Logger())

r.Use(gin.Recovery())

gin.SetMode(setting.RunMode)

r.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "test",
})
})

return r
}

修改main.go的文件內容:

package main

import (
"fmt"
"net/http"

"github.com/EDDYCJY/go-gin-example/routers"
"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

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

s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.HTTPPort),
Handler: router,
ReadTimeout: setting.ReadTimeout,
WriteTimeout: setting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}

s.ListenAndServe()
}

當前目錄結構:

go-gin-example/
├── conf
│   └── app.ini
├── main.go
├── middleware
├── models
│   └── models.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── setting
│   │   └── setting.go
│   └── util
│   └── pagination.go
├── routers
│   └── router.go
├── runtime

重啟服務,執行 curl 127.0.0.1:8000/test查看是否正確返回。

下一節,我們將以我們的 Demo 為起點進行修改,開始編碼!

參考

本系列示例代碼:https://github.com/EDDYCJY/go-gin-example

文章轉自微信公眾號@SDL安全

上一篇:

從gin框架看如何構建自己的http服務框架

下一篇:

Gin系列二:Gin搭建Blog API's (二)
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費