GoWeb框架Gin學習總結

ice_moss發表於2022-06-30

[toc]

文章介紹

本文我們將從零開始介紹Gin的安裝,Gin的簡單入門,基於Gin框架的登入/登錄檔單驗證例項,Gin中介軟體的原理分析,Gin返回html,靜態檔案的掛載和Gin優雅的退出

什麼是Gin?

官方:Gin 是一個用 Go (Golang) 編寫的 HTTP Web 框架。 它具有類似 Martini 的 API,但效能比 Martini 快 40 倍。如果你需要極好的效能,使用 Gin 吧。

Gin 是 Go語言寫的一個 web 框架,它具有執行速度快,分組的路由器,良好的崩潰捕獲和錯誤處理,非常好的支援中介軟體和 json。總之在 Go語言開發領域是一款值得好好研究的 Web 框架,開源網址:github.com/gin-gonic/gin

安裝

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

快速入門

例項一:

package main

import (
    "net/http"

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

//handle方法
func Pong(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "name":   "ice_moss",
        "age":    18,
        "school": "家裡蹲大學",
    })
}

func main() {
    //初始化一個gin的server物件
    //Default例項化物件具有日誌和返回狀態功能
    r := gin.Default()
  //註冊路由,並編寫處理方法
    r.GET("/ping", Pong)
    //監聽埠:預設埠listen and serve on 0.0.0.0:8080
    r.Run(":8083")
}

接下來我們在瀏覽器中訪問:localhost:8083/ping

可以訪問到:

"name":   "ice_moss",
"age":    18,
"school": "家裡蹲大學",

例項二:gin的GET和POST方法

package main

import (
    "net/http"

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

func GinGet(c *gin.Context) {
    c.JSON(http.StatusOK, map[string]interface{}{
        "name": "ice_moss",
    })
}

func GinPost(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "token": "您好",
    })
}
func main() {
    router := gin.Default()
    router.GET("/GinGet", GinGet)
    router.POST("/GinPost", GinPost)
    router.Run(":8083")
}

我們看到GinGet和GinPost這兩個方法中的c.JSON()第二個引數不一樣,原因:gin.H{}本質就是一個map[string]interface{}

//H is a shortcut for map[string]interface{}
type H map[string]any

然後我們就可以訪問:localhost:8083/GinGet

這裡需要注意我們不能直接在瀏覽器中訪問:localhost:8083/GinPost
因為他是POST方法

所以我們可以使用postman,來傳送POST請求

路由分組

Gin為我們做了很好的路由分組,這樣我們可以方便,對路由進行管理

例項三:

package main

import (
    "net/http"

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

func ProductLists(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "礦泉水":  [5]string{"娃哈哈", "2元", "500"},
        "功能飲料": [3]string{"紅牛", "6元", "200"},
    })
}

func Prouduct1(c *gin.Context) {
    req := c.Param("haha")

    c.JSON(http.StatusOK, gin.H{
        "礦泉水":   [5]string{"娃哈哈礦泉水", "2元", "500"},
        "token": req,
    })
}

func CreateProduct(c *gin.Context) {}

//路由分組
func main() {
    router := gin.Default()

  //未使用路由分組
    //獲取商品列表
    //router.GET("/ProductList", ProductLists)
    //獲取某一個具體商品資訊
    //router.GET("/ProductList/1", Prouduct1)
    //新增商品
    //router.POST("ProductList/Add", CreateProduct)

    //路由分組
    ProductList := router.Group("/Produc")
    {
        ProductList.GET("/list", ProductLists)
        ProductList.GET("/1", Prouduct1)
        ProductList.POST("/Add", CreateProduct)
    }
    router.Run(":8083")
}

URL值的提取

很多時候我們需要對URL中資料的提取,或者動態的URL,我們不可能將URL寫固定
例項四:

package main

import (
    "net/http"

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

func ProductLists(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "礦泉水":  [5]string{"娃哈哈礦泉水", "2元", "500"},
        "功能飲料": [3]string{"紅牛", "6元", "200"},
    })
}

func Prouduct1(c *gin.Context) {
    //獲取url中的引數
    id := c.Param("id")
    action := c.Param("action")
    c.JSON(http.StatusOK, gin.H{
        "礦泉水":    [5]string{"娃哈哈礦泉水", "2元", "500"},
        "id":     id,
        "action": action,
    })
}

func CreateProduct(c *gin.Context) {}

//url取值
func main() {
    router := gin.Default()
    //路由分組
    ProductList := router.Group("/Product")
    {
        ProductList.GET("", ProductLists)
        //使用"/:id"動態匹配引數
        ProductList.GET("/:id/:action", Prouduct1)
        ProductList.POST("", CreateProduct)
    }
    router.Run(":8083")
}

訪問:localhost:8083/Product/01/product1

返回:

{"action":"product1","id":"01","礦泉水":["娃哈哈礦泉水","2元","500","",""]}

當我們訪問:localhost:8083/Product/100/product2...

返回:

{"action":"product2000","id":"100","礦泉水":["娃哈哈礦泉水","2元","500","",""]}

構體體宣告並做約束

例項五:

package main

import (
    "net/http"

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

//結構體宣告,並做一些約束
type Porsen struct {
    ID   int    `uri:"id" binding:"required"`    //uri指在client中的名字為id,binding:"required指必填
    Name string `uri:"name" binding:"required"`  //同理
}

//url引數獲取
func main() {
    router := gin.Default()
    router.GET("/:name/:id", func(c *gin.Context) {
        //使用porsen對資料進行解組
        var porsen Porsen
        if err := c.ShouldBindUri(&porsen); err != nil {
            c.Status(404)
            return
        }
        c.JSON(http.StatusOK, gin.H{
            "name": porsen.Name,
            "id":   porsen.ID,
        })
    })
    router.Run(":8083")
}

當我們訪問:localhost:8083/test100/2000

返回:

{"id":2000,"name":"test100"}

但是我們這樣訪問:localhost:8083/100/test2000

返回:

找不到 localhost 的網頁找不到與以下網址對應的網頁:http://localhost:8083/100/test2000
HTTP ERROR 404

這和我們約束條件一致

URL引數的提取

GET請求引數獲取

URL引數的提取是GET方法常用的方法,URL中有需要的引數,例如我們訪問百度圖片:image.baidu.com/search/index?ct=20...

?後的都是引數引數間用&分隔:?ct=201326592&tn=baiduimage&word=%E5%9B%BE%E7%89%87%E5%A3%81%E7%BA%B8&pn=&spn=&ie=utf-8&oe=utf-8&cl=2&lm=-1&fr=ala&se=&sme=&cs=&os=&objurl=&di=&gsm=1e&dyTabStr=MCwzLDYsMSw0LDIsNSw3LDgsOQ%3D%3D

例項六:

package main

import (
    "net/http"

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

func Welcom(c *gin.Context) {
    //DefaultQuery根據欄位名獲取client請求的資料,client未提供資料則可以設定預設值
    first_name := c.DefaultQuery("first_name", "未知")
    last_mame := c.DefaultQuery("last_name", "未知")
    c.JSON(http.StatusOK, gin.H{
        "firstname": first_name,
        "lastname":  last_mame,
        "work":      [...]string{"公司:Tencent", "職位:Go開發工程師", "工資:20000"},
    })
}

//url引數獲取
func main() {
    //例項化server物件
    router := gin.Default()
    router.GET("/welcom", Welcom)
    router.Run(":8083")
}

接著我們在瀏覽器中訪問:localhost:8083/welcom?first_name=mo...

返回:這樣我們的後臺就拿到了client提供的引數,並做業務處理,然後返回client

{"firstname":"moss","lastname":"ice","work":["公司:Tencent","職位:Go開發工程師","工資:20000"]}
POST表單提交及其資料獲取

在很多時候我們都需要使用post方法來傳輸資料,列如,使用者登入/註冊都需要提交表單等

下面我們來看看簡單的表單提交:

例項七:

package main

import (
    "net/http"

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

func Postform(c *gin.Context) {
    UserName := c.DefaultPostForm("username", "unkown")
    PassWord := c.DefaultPostForm("password", "unkown")
    if UserName == "ice_moss@163.com" && PassWord == "123456" {
        c.JSON(http.StatusOK, gin.H{
            "name":        "ice_moss",
            "username":    UserName,
            "tokenstatus": "認證通過",
        })
    } else {
        c.JSON(http.StatusInternalServerError, gin.H{
            "tokenstatus": "認證未通過",
        })
    }
}

//url引數獲取
func main() {
    //例項化server物件
    router := gin.Default()
    router.POST("/Postform", Postform)
    router.Run(":8083")
}

由於是post請求我們使用postman提交表單:localhost:8083/Postform

GoWeb框架Gin學習總結

後臺輸出:

username ice_moss password 18dfdf
[GIN] 2022/06/23 - 19:24:24 | 500 |     108.029µs |             ::1 | POST     "/Postform"
GET和POST混合使用

直接例項,例項八:

package main

import (
    "fmt"
    "net/http"

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

func GetPost(c *gin.Context) {
    id := c.Query("id")
    page := c.DefaultQuery("page", "未知的")
    name := c.DefaultPostForm("name", "未知的")
    password := c.PostForm("password")
    c.JSON(http.StatusOK, gin.H{
        "id":       id,
        "page":     page,
        "name":     name,
        "password": password,
    })
}

//url引數獲取
func main() {
    //例項化server物件
    router := gin.Default()

    //GET和POST混合使用
    router.POST("/Post", GetPost)
    router.Run(":8083")
}

因為是post方法使用,訪問:localhost:8083/Post?id=1&page=2

GoWeb框架Gin學習總結

返回:

{
    "id": "1",
    "name": "ice_moss@163.com",
    "page": "2",
    "password": "123456"
}

資料格式JSON和ProtoBuf

我們知道前後端資料互動大多數都是以json的格式,Go也同樣滿足,我們知道GRPC的資料互動是以ProtoBuf格式的

下面我們來看看Go是如何處理json的,如何處理ProtoBuf的

例項九

ackage main

import (
    "net/http"

    "StudyGin/HolleGin/ch07/proto"

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

func moreJSON(c *gin.Context) {
    var msg struct {
        Nmae    string `json:"UserName"`
        Message string
        Number  int
    }
    msg.Nmae = "ice_moss"
    msg.Message = "This is a test of JSOM"
    msg.Number = 101

    c.JSON(http.StatusOK, msg)
}

//使用ProtoBuf
func returnProto(c *gin.Context) {
    course := []string{"python", "golang", "java", "c++"}
    user := &proto.Teacher{
        Name:   "ice_moss",
        Course: course,
    }
    //返回protobuf
    c.ProtoBuf(http.StatusOK, user)
}

//使用結構體和JSON對結構體欄位進行標籤,使用protobuf返回值
func main() {
    router := gin.Default()
    router.GET("/moreJSON", moreJSON)
    router.GET("/someProtoBuf", returnProto)
    router.Run(":8083")
}

訪問:localhost:8083/moreJSON

返回:

{"UserName":"ice_moss","Message":"This is a test of JSOM","Number":101}

訪問:localhost:8083//someProtoBuf

GoWeb框架Gin學習總結

返回:然後會將someProtoBuf返回的資料下載,當然我們可以使用GRPC中的方法將資料接收解析出來

Gin解析特殊字元

我們很多時候需要處理特殊的字元,比如:JSON會將特殊的HTML字元替換為對應的unicode字元,比如<替換為\u003c,如果想原樣輸出html,則使用PureJSON

例項十:

package main

import (
    "net/http"

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

//通常情況下,JSON會將特殊的HTML字元替換為對應的unicode字元,比如<替換為\u003c,如果想原樣輸出html,則使用PureJSON
func main() {
    router := gin.Default()
    router.GET("/json", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "html": "<b>您好,世界!</b>",
        })
    })

    router.GET("/pureJSON", func(c *gin.Context) {
        c.PureJSON(http.StatusOK, gin.H{
            "html": "<div><b>您好,世界!</b></div>",
        })
    })
    router.Run(":8083")
}

訪問:localhost:8083/json

返回:

{"html":"\u003cb\u003e您好,世界!\u003c/b\u003e"}

訪問:localhost:8083/pureJSON

返回:

{"html":"<div><b>您好,世界!</b></div>"}

Gin翻譯器的實現

在這段程式碼中,我們是將註冊程式碼實現翻譯功能

例項十一:

package main

import (
    "fmt"
    "net/http"
    "reflect"
    "strings"

    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    enTranslations "github.com/go-playground/validator/v10/translations/en"
    zhTranslations "github.com/go-playground/validator/v10/translations/zh"

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

// 定義一個全域性翻譯器T
var trans ut.Translator

//Login登入業務,欄位新增tag約束條件
type Login struct {
    User     string `json:"user" binding:"required"`     //必填
    Password string `json:"password" binding:"required"` //必填
}

//SignUp註冊業務,欄位新增tag約束條件
type SignUp struct {
    Age        int    `json:"age" binding:"gte=18"`                            //gte大於等於
    Name       string `json:"name" binding:"required"`                         //必填
    Email      string `json:"email" binding:"required,email"`                  //必填郵件
    Password   string `json:"password" binding:"required"`                     //必填
    RePassword string `json:"re_password" binding:"required,eqfield=Password"` //RePassword和Password值一致
}

//RemoveTopStruct去除以"."及其左部分內容
func RemoveTopStruct(fields map[string]string) map[string]string {
    res := map[string]string{}
    for field, value := range fields {
        res[field[strings.Index(field, ".")+1:]] = value
    }
    return res
}

// InitTrans 初始化翻譯器
func InitTrans(locale string) (err error) {
    // 修改gin框架中的Validator引擎屬性,實現自定製
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        //註冊一個獲取json的自定義方法
        v.RegisterTagNameFunc(func(field reflect.StructField) string {
            name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]
            if name == "-" {
                return ""
            }
            return name
        })
        zhT := zh.New() // 中文翻譯器
        enT := en.New() // 英文翻譯器

        // 第一個引數是備用(fallback)的語言環境
        // 後面的引數是應該支援的語言環境(支援多個)
        // uni := ut.New(zhT, zhT) 也是可以的
        uni := ut.New(enT, zhT, enT)

        // locale 通常取決於 http 請求頭的 'Accept-Language'
        var ok bool
        // 也可以使用 uni.FindTranslator(...) 傳入多個locale進行查詢
        trans, ok = uni.GetTranslator(locale)
        if !ok {
            return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
        }

        // 註冊翻譯器
        switch locale {
        case "en":
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        case "zh":
            err = zhTranslations.RegisterDefaultTranslations(v, trans)
        default:
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        }
        return
    }
    return
}

func main() {
    res := map[string]string{
        "ice_moss.habbit": "打球",
        "ice_moss.from":   "貴州 中國",
    }
    fmt.Println(RemoveTopStruct(res))

    //初始化翻譯器, 翻譯器程式碼看不懂不要緊,我們只需知道這樣使用就行
    if err := InitTrans("zh"); err != nil {
        fmt.Println("初始化翻譯器失敗", err)
        return
    }

    router := gin.Default()
    router.POST("/loginJSON", func(c *gin.Context) {
        var login Login
        if err := c.ShouldBind(&login); err != nil {
            fmt.Println(err.Error())
            errs, ok := err.(validator.ValidationErrors)
            if !ok {
                c.JSON(http.StatusOK, gin.H{
                    "msg": err.Error(),
                })
            }
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": errs.Translate(trans),
            })
            return
        }
        c.JSON(http.StatusOK, gin.H{
            "msg": "驗證通過",
        })
    })

    router.POST("/signupJSON", func(c *gin.Context) {
        var signup SignUp
         //ShouldBind()對資料進行繫結,解組
        if err := c.ShouldBind(&signup); err != nil {
            fmt.Println(err.Error())
            //獲取validator.ValidationErrors型別的error
            errs, ok := err.(validator.ValidationErrors)
            if !ok {
                c.JSON(http.StatusOK, gin.H{
                    "msg": err.Error(),
                })
            }
            //validator.ValidationErrors型別錯誤則進行翻譯
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": RemoveTopStruct(errs.Translate(trans)),
            })
            return
        }

        c.JSON(http.StatusOK, gin.H{
            "msg": "註冊成功",
        })
    })
    router.Run(":8083")
}

當我們訪問:localhost:8083/signupJSON
如果引數不滿足tag中的條件,則會返回如下結果:

GoWeb框架Gin學習總結

當我們輸入滿足tag中的條件,就成功返回了:
GoWeb框架Gin學習總結

Gin中介軟體原理及自定義中介軟體

在此之前我們先來看一下Gin例項化server,我們在之前是使用router := gin.Default(),但其實我們是可以直接使用router := gin.New(), 那麼在之前是例項中我們為什麼不使用gin.New()呢?

別急,我們先來看看gin.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
}

我們可以看到Default()其實是對gin.New()做了一層封裝,並且做了其他事情,這裡的其他事情就有“中介軟體”

即:engine.Use(Logger(), Recovery())他呼叫兩個中介軟體( Logger()用來輸出日誌資訊,Recovery 中介軟體會恢復(recovers) 任何恐慌(panics) 如果存在恐慌,中介軟體將會寫入500)

Gin中介軟體原理

我們來看看:engine.Use()

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

入參是HandlerFunc型別,那麼我們接著往下看HandlerFunc

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

其實HandlerFuncfunc(*Context)型別

到這裡中介軟體我們就可以自定義了

自定義中介軟體

我們定義一個監控服務執行時間,執行狀態的中介軟體:

例項十二:

//自定義中介軟體,這裡我們以函式呼叫的形式,對中介軟體進一步封裝
func MyTimeLogger() gin.HandlerFunc {
    return func(c *gin.Context) {          //真正的中介軟體型別
        t := time.Now()
        c.Set("msg", "This is a test of middleware")
        //它執行呼叫處理程式內鏈中的待處理處理程式
        //讓原本執行的邏輯繼續執行
        c.Next()

        end := time.Since(t)
        fmt.Printf("耗時:%D\n", end.Seconds())
        status := c.Writer.Status()
        fmt.Println("狀態監控:", status)
    }
}

我們在main函式中:

func main() {
    router := gin.Default()
    router.Use(MyTimeLogger())   //這裡使用函式呼叫
    router.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "Pong",
        })
    })
    router.Run(":8083")
}

訪問:localhost:8083/ping

返回 :

{"msg":"Pong"}
中介軟體實際應用

基於中介軟體模擬登入:
例項十三:

//自定義中介軟體
func TokenRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        var token string
    //從請求頭中獲取資料
        for k, v := range c.Request.Header {
            if k == "X-Token" {
                token = v[0]
            } else {
                fmt.Println(k, v)
            }
        }
        fmt.Println(token)
        if token != "ice_moss" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "msg": "認證未通過",
            })

      //return在這裡不會有被執行
            c.Abort()   //這裡先不用理解,後面會講解,這裡先理解為return
        }
    //繼續往下執行該執行的邏輯
        c.Next()
    }
}

將中介軟體加入gin中:

func main() {
    router := gin.Default()
    //中介軟體
    router.Use(TokenRequired())
    router.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "Pong",
        })
    })
    router.Run(":8083")
}

我們在postman中進行請求:localhost:8083/ping

將Headers,增加欄位:

GoWeb框架Gin學習總結

正確引數返回:

{
    "msg": "Pong"
}

不正確引數返回:

{
    "msg": "認證未通過"
}

這裡我們需要對中介軟體原理進一步剖析:
現在我們將圍繞兩個方法來解釋
c.Abort() 和 c.Next()

c.Abort()

例項十三中我們看到:

func TokenRequired() gin.HandlerFunc {
   return func(c *gin.Context) {
      var token string
 for k, v := range c.Request.Header {
         if k == "X-Token" {
            token = v[0]
         } else {
            fmt.Println(k, v)
         }
      }
      fmt.Println(token)
      if token != "ice_moss" {
         c.JSON(http.StatusUnauthorized, gin.H{
            "msg": "認證未通過",
  })
         //return 不會被執行,需要使用c.Abort()來結束當前
         c.Abort()
      }
      //繼續執行該執行的邏輯
      c.Next()
   }
}
  1. 為什麼return 不能直接返回,而是使用c.Abort()

原因:當我們啟動服務後,Gin會有一個類似於任務佇列將所有配置的中介軟體和在註冊處理方法壓入佇列中:

GoWeb框架Gin學習總結

在處理業務程式碼之前,會將所有註冊路由中的中介軟體以佇列的執行方式執行,比如上面我們:

GoWeb框架Gin學習總結

當我們在例項十三中執行return他只是將當前函式返回,但是後面的方法仍然是按邏輯執行的,很顯然這不是我們想要的結果,不滿足驗證條件的情況,應該將對此時的client終止服務,如果要終止服務就應該將圖中的箭頭跳過所有方法:

GoWeb框架Gin學習總結

這樣整個服務才是真正的終止,下面再來看看Abort():

func (c *Context) Abort() {
   c.index = abortIndex
}

當程式碼執行到Abort()時,index被賦值為abortIndex,abortIndex是什麼?

const abortIndex int8 = math.MaxInt8 >> 1

可以看到,最後index指向任務末端,這就是const abortIndex int8 = math.MaxInt8 >> 1作用的效果

c.Next()

理解了Abort()Next()自然就好理解了,我們來看看Next()定義

func (c *Context) Next() {
   c.index++
   for c.index < int8(len(c.handlers)) {
      c.handlers[c.index](c)
      c.index++
   }
}

執行過程:

GoWeb框架Gin學習總結

GoWeb框架Gin學習總結

GoWeb框架Gin學習總結

Gin返回html模板

我們使用html模板,將後端獲取到的資料,直接填充至html中
我們先來編寫一個html(例項為tmpl,無影響)

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>{{ .title }}</title>
</head>
<body>
<h1>{{ .menu }}</h1>
</body>
</html>

其中資料以{{ .title }}從web層填充進來
我們需要注意目錄結構,程式的執行入口main需要和模板 templates放置同一目錄下,這樣保證main能讀取檔案html

ch11
├── main.go
└── templates
    └── index.tmpl

例項十四:
main:

package main

import (
   "fmt"
 "net/http" "os" "path/filepath"
 "github.com/gin-gonic/gin")

func main() {
   router := gin.Default()
   //讀取檔案
   router.LoadHTMLFiles("templates/index.tmpl")
   router.GET("/index", func(c *gin.Context) {
       //寫入資料,  key必須要tmpl一致
      c.HTML(http.StatusOK, "index.tmpl", gin.H{
         "title": "購物網",  
         "menu":  "選單欄",
  })
   })
   router.Run(":8085")
}

當我們在瀏覽器中訪問:localhost:8085/index
獲取到:

GoWeb框架Gin學習總結

當然router.LoadHTMLFiles()方法可以載入多個html檔案
例項十五:

package main

import (
   "fmt"
 "net/http" "os" "path/filepath"
 "github.com/gin-gonic/gin")

func main() {
   router := gin.Default()

   //讀取模板檔案,按指定個讀取
  router.LoadHTMLFiles("templates/index.tmpl", "templates/goods.html")
   router.GET("/index", func(c *gin.Context) {
      c.HTML(http.StatusOK, "index.tmpl", gin.H{
         "title": "shop",
         "menu":  "選單欄",
  })
   })

   router.GET("goods", func(c *gin.Context) {
      c.HTML(http.StatusOK, "goods.html", gin.H{
         "title": "goods",
         "goods": [4]string{"礦泉水", "麵包", "薯片", "冰淇淋"},
  })
   })
   router.Run(":8085")
}

這樣就可以訪問:localhost:8085/goods
或者:localhost:8085/index
返回結果:略

當然如果html檔案很多,Gin還提供了```
func (engine *Engine) LoadHTMLGlob(pattern string) {……}
我們只需要這樣呼叫:

//將"templates資料夾下所有檔案載入
router.LoadHTMLGlob("templates/*")

對應二級目錄,我們又是如何處理的呢?

//載入templates目錄下的目錄中的所有檔案
router.LoadHTMLGlob("templates/**/*")

例項十六:

ackage main

import (
   "fmt"
 "net/http" "os" "path/filepath"
 "github.com/gin-gonic/gin")

func main() {
   router := gin.Default()
 router.LoadHTMLGlob("templates/**/*")
   router.GET("user/list", func(c *gin.Context) {
      c.HTML(http.StatusOK, "list.html", gin.H{
         "title": "shop",
         "list":  "使用者列表",
  })
   })

   router.GET("goods/list", func(c *gin.Context) {
      c.HTML(http.StatusOK, "list.html", gin.H{
        "title": "shop",
        "list":  "商品列表",
  })
   })
   router.Run(":8085")
}

這樣我們訪問:localhost:8085/goods/list 或者http://localhost:8085/user/list都能訪問到

Gin靜態檔案的掛載

在web開發中經常需要將js檔案和css檔案,進行掛載,來滿足需求
目錄結構:

ch11
├── main.go
├── static
│   └── style.css
└── templates
    └── user
        └── list.html

html檔案:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>{{ .title }}</title>
 <link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>{{ .list }}</h1>
</body>
</html>

css檔案:

*{
    background-color: aquamarine;
}

靜態檔案掛載方法:

router.Static("/static", "./static")

該方法會去在html檔案中<link>標籤中找到以static開頭的連結,然後去找在當前main所在的目錄下找到以第二個引數./static名稱的目錄下找到靜態檔案,然後掛載

例項十七:

package main

import (
   "fmt"
 "net/http" "os" "path/filepath"
 "github.com/gin-gonic/gin")

func main() {
   router := gin.Default()
   //掛載靜態檔案
   router.Static("/static", "./static")
   router.LoadHTMLGlob("templates/**/*")
   router.GET("user/list", func(c *gin.Context) {
      c.HTML(http.StatusOK, "list.html", gin.H{
         "title": "shop",
  "list":  "使用者列表",
  })
   })

   router.Run(":8085")
}

然後訪問:localhost:8085/user/list
可以看到:

GoWeb框架Gin學習總結

Gin優雅退出

在業務中,我們很多時候涉及到服務的退出,如:各種訂單處理中,使用者突然退出,支付費用時,程式突然退出,這裡我們是需要是我們的服務合理的退出,進而不造成業務上的矛盾
例項十八:

package main

import (
   "fmt"
 "net/http" "os" "os/signal" "syscall"
 "github.com/gin-gonic/gin")

func main() {
   router := gin.Default()
   router.GET("ping", func(c *gin.Context) {
      c.JSON(http.StatusOK, gin.H{
         "msg": "ping",
  })
   })

   go func() {
      router.Run(":8085")
   }()

   qiut := make(chan os.Signal)
   //接收control+c
   //當接收到退出指令時,我們向chan收資料
  signal.Notify(qiut, syscall.SIGINT, syscall.SIGTERM)
   <-qiut

 //服務退出前做處理
   fmt.Println("服務退出中")
   fmt.Println("服務已退出")
}

在terminal中執行:go run main.go
服務啟動後在terminal中退出(control+c)就可以看到:

[GIN-debug] Listening and serving HTTP on :8085
服務退出中
服務已退出
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章