主頁 > 後端開發 > Gin框架

Gin框架

2023-04-18 07:28:19 後端開發

Gin

環境:https://goproxy.cn,driect

github.com/gin-gonic/gin

介紹

Gin 是一個用 Go (Golang) 撰寫的 Web 框架, 它具有類似 martini 的 API,性能要好得多,多虧了 httprouter,速度提高了 40 倍, 如果您需要性能和良好的生產力,您一定會喜歡 Gin,

在本節中,我們將介紹 Gin 是什么,它解決了哪些問題,以及它如何幫助你的專案,

或者, 如果你已經準備在專案中使用 Gin,請訪問快速入門.

原始碼分析

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

實作

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   c := engine.pool.Get().(*Context)
   c.writermem.reset(w)
   c.Request = req
   c.reset()

   engine.handleHTTPRequest(c)

   engine.pool.Put(c)
}
  • 通過物件池來減少記憶體申請和GC回收的消耗
  • 取出來要用的時候再初始化

gin

特性

快速

基于 Radix 樹的路由,小記憶體占用,沒有反射,可預測的 API 性能,

支持中間件

傳入的 HTTP 請求可以由一系列中間件和最終操作來處理, 例如:Logger,Authorization,GZIP,最終操作 DB,

Crash 處理

Gin 可以 catch 一個發生在 HTTP 請求中的 panic 并 recover 它,這樣,你的服務器將始終可用,例如,你可以向 Sentry 報告這個 panic!

JSON 驗證

Gin 可以決議并驗證請求的 JSON,例如檢查所需值的存在,

路由組

更好地組織路由,是否需要授權,不同的 API 版本…… 此外,這些組可以無限制地嵌套而不會降低性能,

錯誤管理

Gin 提供了一種方便的方法來收集 HTTP 請求期間發生的所有錯誤,最終,中間件可以將它們寫入日志檔案,資料庫并通過網路發送,

內置渲染

Gin 為 JSON,XML 和 HTML 渲染提供了易于使用的 API,

可擴展性

新建一個中間件非常簡單,去查看示例代碼吧,

快速入門

package main

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

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run(":8000")// 監聽并在 0.0.0.0:8080 上啟動服務
}
  • gin.Default()默認使用了LoggerRecover中間件
  • Logger是負責進行列印輸出日志的中間件,方便開發者進行程式的除錯
  • Recover如果程式執行程序中遇到了panic中斷了服務,則Recover會恢復程式的運行并回傳500的內部錯誤,

Engine

type Engine struct {
	RouterGroup
	RedirectTrailingSlash bool
	RedirectFixedPath bool
	HandleMethodNotAllowed bool
	ForwardedByClientIP bool
	AppEngine bool
	UseRawPath bool
	UnescapePathValues bool
	RemoveExtraSlash bool
	RemoteIPHeaders []string
	TrustedPlatform string
	MaxMultipartMemory int64
	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

請求處理

HTTP 協議的 8 種請求型別介紹

HTTP 協議中共定義了八種方法或者叫“動作”來表明對Request-URI指定的資源的不同操作方式,具體介紹如下:

  • OPTIONS:回傳服務器針對特定資源所支持的HTTP請求方法,也可以利用向Web服務器發送'*'的請求來測驗服務器的功能性,
  • HEAD:向服務器索要與GET請求相一致的回應,只不過回應體將不會被回傳,這一方法可以在不必傳輸整個回應內容的情況下,就可以獲取包含在回應訊息頭中的元資訊,
  • GET:向特定的資源發出請求,
  • POST:向指定資源提交資料進行處理請求(例如提交表單或者上傳檔案),資料被包含在請求體中,POST請求可能會導致新的資源的創建和/或已有資源的修改,
  • PUT:向指定資源位置上傳其最新內容,
  • DELETE:請求服務器洗掉 Request-URI 所標識的資源,
  • TRACE:回顯服務器收到的請求,主要用于測驗或診斷,
  • CONNECT:HTTP/1.1 協議中預留給能夠將連接改為管道方式的代理服務器,

通用處理

Handle方法

func (group *RouterGroup)Handle(httpMethod,relativePath string,handler...Handler)
  • httpMethod:表示要處理的HTTP請求型別,8種請求方式之一
  • relativePath:表示要決議的介面,由開發者定義
  • handlers:處理對應的請求的代碼定義

Restful風格的API

  • gin支持Restful風格的API
  • 即Representational State Transfer的縮寫,直接翻譯的意思是”表現層狀態轉化”,是一種互聯網應用程式的API設計理念:URL定位資源,用HTTP描述操作

1.獲取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.洗掉 /blog/delXxxx DELETE blog/Xxx

GET請求處理

路徑引數- /:xx - Param("xx")

對于類似這樣的請求:http://127.0.0.1:8080/index/12,那么如何獲取最后路徑中12的值呢?

package main

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

func Index(ctx *gin.Context) {
    id := ctx.Param("id")
    fmt.Println(id)
    ctx.String(http.StatusOK, "success!")
}

func main() {
    router := gin.Default()
    
    // 路徑引數獲取,如:http://127.0.0.1:8080/index/12,獲取12
    router.GET("/index/:id", Index)

    router.Run(":8080")
}

在掛載路由時需要通過":"來進行匹配,然后在視圖函式中通過ctx.Param方法獲取,

查詢引數-/hello?xx=..? -Query()

對于類似這樣的請求:http://127.0.0.1:8080/index1?id=12,那么如何獲取最后路徑中12的值呢?

1、ctx.Query

傳參:http://127.0.0.1:8080/index1?id=12

路由:router.GET("/index1", Index1)

視圖函式獲取:ctx.Query("id")

2、ctx.DefaultQuery

傳參:http://127.0.0.1:8080/index2

路由:router.GET("/index2", Index2)

視圖函式獲取:ctx.DefaultQuery("id", "0")

如果沒有獲取到id,就得到默認值0.

3、ctx.QueryArray

傳參:http://127.0.0.1:8080/index3?id=1,2,3,4,5

路由:router.GET("/index3", Index3)

視圖函式獲取:ctx.QueryArray("id")

4、ctx.QueryMap

傳參:http://127.0.0.1:8080/index4?user[name]="lily"&user[age]=15

路由:router.GET("/index4", Index4)

視圖函式獲取:ctx.QueryMap("user")

通用引數匹配 /user/:name/*action

當我們需要動態引數的路由時,如 /user/:id,通過呼叫不同的引數 :id 動態獲取相應的用戶資訊,其中 /user/:id/*type*type 的引數為可選,

package main

import (
    "net/http"
    "strings"

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

func main() {
    r := gin.Default()
    r.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        //截取/
        action = strings.Trim(action, "/")
        c.String(http.StatusOK, name+" is "+action)
    })
    //默認為監聽8080埠
    r.Run(":8000")
}

正常的結果:

http://localhost:8080/api/hjz/HJZ114152
hjz is HJZ114152

注釋掉:Trim()后

http://localhost:8080/api/hjz/HJZ114152
hjz is /HJZ114152

POST請求處理

表單傳輸為post請求,http常見的傳輸格式為四種:

  • application/json
  • application/x-www-form-urlencoded
  • application/xml
  • multipart/form-data

表單引數可以通過PostForm()方法獲取,該方法默認決議的是x-www-form-urlencoded或from-data格式的引數

普通方式提交表單-post -PostForm("xxx")

1、ctx.PostForm

  • 表單
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index" method="post">
    <p>用戶名:<input type="text" name="username"></p>
    <p>密 碼:<input type="password" name="password"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后臺處理
...
func PostIndex(ctx *gin.Context)  {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Printf("用戶名:%s, 密碼:%s", username, password)
    ctx.String(http.StatusOK, "提交成功!")
}
...

2、ctx.PostFormMap

  • 表單
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index1" method="post">
    <p>用戶名:<input type="text" name="user[username]"></p>
    <p>密 碼:<input type="password" name="user[password]"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后臺處理
...
func PostIndex1(ctx *gin.Context)  {
    userMap := ctx.PostFormMap("user")
    fmt.Println(userMap)
    ctx.String(http.StatusOK, "提交成功!")
}
...

3、ctx.DefaultPostForm、ctx.PostFormArray

  • 表單
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index2" method="post">
    <p>用戶名:<input type="text" name="username"></p>
    <p>密 碼:<input type="password" name="password"></p>
    <p>愛 好:
        讀書<input type="checkbox" name="hobby" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/1">
        看電影<input type="checkbox" name="hobby" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/2">
        音樂<input type="checkbox" name="hobby" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/3">
    </p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后臺處理
...
func PostIndex2(ctx *gin.Context)  {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    age := ctx.DefaultPostForm("age", "0")
    hobby := ctx.PostFormArray("hobby")
    fmt.Printf("用戶名:%s, 密碼:%s, 年齡:%s, 愛好:%s", username, password, age, hobby)
    ctx.String(http.StatusOK, "提交成功!")
}
...

例子

package main

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

func GetIndex(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index.html", nil)
}

func PostIndex(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Printf("用戶名:%s, 密碼:%s", username, password)
    ctx.String(http.StatusOK, "提交成功!")
}

func GetIndex1(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index1.html", nil)
}

func PostIndex1(ctx *gin.Context) {
    userMap := ctx.PostFormMap("user")
    fmt.Println(userMap)
    ctx.String(http.StatusOK, "提交成功!")
}

func GetIndex2(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index2.html", nil)
}

func PostIndex2(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    age := ctx.DefaultPostForm("age", "0")
    hobby := ctx.PostFormArray("hobby")
    fmt.Printf("用戶名:%s, 密碼:%s, 年齡:%s, 愛好:%s", username, password, age, hobby)
    ctx.String(http.StatusOK, "提交成功!")
}

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

    router.LoadHTMLGlob("template/*")

    // ctx.PostForm
    router.GET("/get_index", GetIndex)
    router.POST("/post_index", PostIndex)
    // ctx.PostFormMap
    router.GET("/get_index1", GetIndex1)
    router.POST("/post_index1", PostIndex1)
    // ctx.DefaultPostForm、ctx.PostFormArray
    router.GET("/get_index2", GetIndex2)
    router.POST("/post_index2", PostIndex2)

    router.Run(":8080")
}

Ajax方式提交表單

ajax的后臺處理邏輯與普通的表單的提交的處理方式基本相同,只不過在回傳的時候需要回傳json資料,前臺使用回呼函式進行處理,

  • 前臺
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://www.cnblogs.com/static/js/jquery-3.6.0.js"></script>
</head>
<body>

<form>
    <p>用戶名:<input id="username" type="text"></p>
    <p>密碼:<input id="password" type="password"></p>
    <p><input type="button" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/提交" id="btn_submit"></p>
</form>

<script>
    var btn = document.getElementById("btn_submit")
    btn.onclick = function (ev) {
        var username = document.getElementById("username").value
        var password = document.getElementById("password").value

        $.ajax({
            url: '/post_index',
            type: 'POST',
            data: {
                username: username,
                password: password
            },
            success: function (data) {
                alert(data) // 回應成功的回呼函式
            },
            fail: function (data) {

            }
        })
    }


</script>
</body>
</html>
  • 后臺
package main

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

func GetIndex(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index.html", nil)
}

func PostIndex(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Println(username, password)
    data := map[string]interface{}{
        "code":    2000,
        "message": "成功",
    }
    ctx.JSON(http.StatusOK, data)
}

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

    router.LoadHTMLGlob("template/*")
    router.Static("/static", "static")

    router.GET("/get_index", GetIndex)
    router.POST("/post_index", PostIndex)

    router.Run(":8080")
}

引數系結

無論時get請求的引數還是post請求的請求體,在后臺都需要通過對應的方法來獲取對應引數的值,那么有沒有一種方式能夠讓我們定義好請求資料的格式,然后自動進行獲取,這里可以通過引數系結的方式來進行處理,它能夠基于請求自動提取JSON、form表單和QueryString型別的資料,并把值系結到指定的結構體物件

這里以get請求的查詢引數為例:

  • 請求格式
http://127.0.0.1:8080/index?username=%22llkk%22&password=%22123%22
  • 后臺處理
package main

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

type User struct {
    Username string `form:"username" json:"username"`
    Password string `form:"password" json:"password"`
}

func Index(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBind(&user)
    fmt.Println(err)
    fmt.Println(user.Username, user.Password) // "llkk" "123"

    ctx.String(http.StatusOK, "success")

}

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

    router.GET("/index", Index)

    router.Run(":8080")
}

檔案處理

上傳單個檔案

  • multipart/form-data格式用于檔案上傳
  • gin檔案上傳與原生的net/http方法類似,不同在于gin把原生的request封裝到c.Request中
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
          上傳檔案:<input type="file" name="file" >
          <input type="submit" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/提交">
    </form>
</body>
</html>
package main

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

func main() {
    r := gin.Default()
    //限制上傳最大尺寸
    r.MaxMultipartMemory = 8 << 20
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.String(500, "上傳圖片出錯")
        }
        // c.JSON(200, gin.H{"message": file.Header.Context})
        c.SaveUploadedFile(file, file.Filename)
        c.String(http.StatusOK, file.Filename)
    })
    r.Run()
}

上傳多個檔案

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
          上傳檔案:<input type="file" name="files" multiple>
          <input type="submit" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/提交">
    </form>
</body>
</html>
package main

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

// gin的helloWorld

func main() {
   // 1.創建路由
   // 默認使用了2個中間件Logger(), Recovery()
   r := gin.Default()
   // 限制表單上傳大小 8MB,默認為32MB
   r.MaxMultipartMemory = 8 << 20
   r.POST("/upload", func(c *gin.Context) {
      form, err := c.MultipartForm()
      if err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
      }
      // 獲取所有圖片
      files := form.File["files"]
      // 遍歷所有圖片
      for _, file := range files {
         // 逐個存
         if err := c.SaveUploadedFile(file, file.Filename); err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
            return
         }
      }
      c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
   })
   //默認埠號是8080
   r.Run(":8000")
}

快速分析

GET

get請求的引數會放在地址欄里,用明文的形式提交給后臺 .

例如:

http://127.0.0.1:8081/path/123 
r.GET("/path/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"success": true,
		})
	})

獲得URL里面的引數

http://127.0.0.1:8081/path/123?user=abc&pwd=123456
r.GET("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //獲取占位運算式后面的引數
		//user和pwd放在地址欄后面,所以叫query傳參
		user := c.Query("user") //
		pwd := c.Query("pwd")
		c.JSON(200, gin.H{
			"id":   id,
			"user": user,
			"pwd":  pwd,
		})
	})

設定一個引數的默認值

r.GET("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //獲取占位運算式后面的引數
		//user和pwd放在地址欄后面,所以叫query傳參
		user := c.DefaultQuery("user", "kaka") //設定user的默認值為kaka
		pwd := c.Query("pwd")
		c.JSON(200, gin.H{
			"id":   id,
			"user": user,
			"pwd":  pwd,
		})
	})

POST

post的請求引數放在form或者body里,form即表單,body是以當前最流行的json格式進行互動,

r.POST("/path", func(c *gin.Context) {
		user := c.DefaultPostForm("user", "aaa")
		pwd := c.PostForm("pwd")
		c.JSON(200, gin.H{
			"user": user,
			"pwd":  pwd,
		})
	})

Delete

delete請求一般為uri,同樣也可以用body

r.DELETE("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //獲取占位運算式后面的引數
		c.JSON(200, gin.H{
			"id": id,
		})
	})

delete請求實際作業中用的不多,傳參和取參方法和get類似

PUT

引數在form、body或者uri里

r.PUT("/path", func(c *gin.Context) {
		user := c.DefaultPostForm("user", "aaa")
		pwd := c.PostForm("pwd")
		c.JSON(200, gin.H{
			"user": user,
			"pwd":  pwd,
		})
	})

put傳參和獲取引數的方法和post基本一樣

回傳資料

[]byte

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "請求路徑: "+context.FullPath()
    fmt.Println(fullPath)
    context.Writer.Writer([]byte(fullPath))
})
engine.Run()
...

呼叫了http.ResponseWriter中包含的方法

string

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "請求路徑: "+context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriterString([]byte(fullPath))
})
engine.Run()
...

JSON

map

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "請求路徑: "+context.FullPath()
    fmt.Println(fullPath)
    context.JSON(200,map[string]interface{}{
        "code":1,
        "message":"OK",
        "data":FullPath
    })
})
engine.Run()
...

結構體

type Resopnse type{
    Code int		`json:"code"`
    Message string 	 `json:"message"`
    Data interface{} `json:"data"`
}
...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "請求路徑: "+context.FullPath()
    fmt.Println(fullPath)
    context.JSON(200,&Resopnse{
        Code:1,
        Message:"OK",
        Data:FullPath
    })
})
engine.Run()
...

資料決議

Json 資料決議和系結

ShouldBindJSON("&xx")

  • 客戶端傳參,后端接收并決議到結構體
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

// 定義接收資料的結構體
type Login struct {
   // binding:"required"修飾的欄位,若接收為空值,則報錯,是必須欄位
   User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
   Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
   // 1.創建路由
   // 默認使用了2個中間件Logger(), Recovery()
   r := gin.Default()
   // JSON系結
   r.POST("loginJSON", func(c *gin.Context) {
      // 宣告接收的變數
      var json Login
      // 將request的body中的資料,自動按照json格式決議到結構體
      if err := c.ShouldBindJSON(&json); err != nil {
         // 回傳錯誤資訊
         // gin.H封裝了生成json資料的工具
         c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
         return
      }
      // 判斷用戶名密碼是否正確
      if json.User != "root" || json.Pssword != "admin" {
         c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
         return
      }
      c.JSON(http.StatusOK, gin.H{"status": "200"})
   })
   r.Run(":8000")
} 

表單資料決議和系結

Bind("&xx")

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
        用戶名<input type="text" name="username"><br>
        密碼<input type="password" name="password">
        <input type="submit" value="https://www.cnblogs.com/HJZ114152/archive/2023/04/17/提交">
    </form>
</body>
</html>
package main

import (
    "net/http"

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

// 定義接收資料的結構體
type Login struct {
    // binding:"required"修飾的欄位,若接收為空值,則報錯,是必須欄位
    User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    // JSON系結
    r.POST("/loginForm", func(c *gin.Context) {
        // 宣告接收的變數
        var form Login
        // Bind()默認決議并系結form格式
        // 根據請求頭中content-type自動推斷
        if err := c.Bind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // 判斷用戶名密碼是否正確
        if form.User != "root" || form.Pssword != "admin" {
            c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "200"})
    })
    r.Run(":8000")
}

URI資料決議和系結

ShouldBindUri()

package main

import (
    "net/http"

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

// 定義接收資料的結構體
type Login struct {
    // binding:"required"修飾的欄位,若接收為空值,則報錯,是必須欄位
    User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    // JSON系結
    r.GET("/:user/:password", func(c *gin.Context) {
        // 宣告接收的變數
        var login Login
        // Bind()默認決議并系結form格式
        // 根據請求頭中content-type自動推斷
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // 判斷用戶名密碼是否正確
        if login.User != "root" || login.Pssword != "admin" {
            c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "200"})
    })
    r.Run(":8000")
}

視圖渲染

各種資料格式的回應

  • json、結構體、XML、YAML類似于java的properties、ProtoBuf

JSON

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

結構體

// 2. 結構體回應
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })

XML

// 3.XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })

YAML

    // 4.YAML回應
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "zhangsan"})
    })

protoduf

 // 5.protobuf格式,谷歌開發的高效存盤讀取的工具
    // 陣列?切片?如果自己構建一個傳輸格式,應該是什么格式?
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        // 定義資料
        label := "label"
        // 傳protobuf格式資料
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

Gin框架使用HTML模板渲染

package main

import (
	"net/http"

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

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("./templates/*")
	r.GET("/demo", func(ctx *gin.Context) {
		ctx.HTML(http.StatusOK, "test.html", gin.H{
			"name": "admin",
			"pwd":  "123456",
		})
	})
	r.Run()
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    賬號是:{{.name}}<br>
    密碼是:{{.pwd}}
</body>
</html>

重定向

package main

import (
    "net/http"

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

func main() {
    r := gin.Default()
    r.GET("/index", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
    })
    r.Run()
}

同步異步

  • goroutine機制可以方便地實作異步處理
  • 另外,在啟動新的goroutine時,不應該使用原始背景關系,必須使用它的只讀副本
package main

import (
    "log"
    "time"

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

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    // 1.異步
    r.GET("/long_async", func(c *gin.Context) {
        // 需要搞一個副本
        copyContext := c.Copy()
        // 異步處理
        go func() {
            time.Sleep(3 * time.Second)
            log.Println("異步執行:" + copyContext.Request.URL.Path)
        }()
    })
    // 2.同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(3 * time.Second)
        log.Println("同步執行:" + c.Request.URL.Path)
    })

    r.Run(":8000")
} 

日志的列印上:

[GIN] 2022/08/01 - 10:48:56 | 200 |            0s |             ::1 | GET      "/long_async"
2022/08/01 10:48:59 異步執行:/long_async
2022/08/01 10:49:17 同步執行:/long_sync
[GIN] 2022/08/01 - 10:49:17 | 200 |    3.0071153s |             ::1 | GET      "/long_sync"

中間件

全域中間件

  • 所有請求都經過此中間件
package main

import (
    "fmt"
    "time"

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

// 定義中間
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中間件開始執行了")
        // 設定變數到Context的key中,可以通過Get()取
        c.Set("request", "中間件")
        status := c.Writer.Status()
        fmt.Println("中間件執行完畢", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    // 注冊中間件
    r.Use(MiddleWare())
    // {}為了代碼規范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 頁面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}
中間件開始執行了
中間件執行完畢 200
time: 516.1μs
request: 中間件
[GIN] 2022/10/02 - 14:56:20 | 200 |       516.1μs |             ::1 | GET      "/ce"

Next()方法

package main

import (
    "fmt"
    "time"

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

// 定義中間
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中間件開始執行了")
        // 設定變數到Context的key中,可以通過Get()取
        c.Set("request", "中間件")
        // 執行函式
        c.Next()
        // 中間件執行完后續的一些事情
        status := c.Writer.Status()
        fmt.Println("中間件執行完畢", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    // 注冊中間件
    r.Use(MiddleWare())
    // {}為了代碼規范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 頁面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}
中間件開始執行了
request: 中間件   
中間件執行完畢 200
time: 235.9μs     
[GIN] 2022/10/02 - 14:57:46 | 200 |       754.8μs |             ::1 | GET      "/ce"

區域中間件

package main

import (
    "fmt"
    "time"

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

// 定義中間
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中間件開始執行了")
        // 設定變數到Context的key中,可以通過Get()取
        c.Set("request", "中間件")
        // 執行函式
        c.Next()
        // 中間件執行完后續的一些事情
        status := c.Writer.Status()
        fmt.Println("中間件執行完畢", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.創建路由
    // 默認使用了2個中間件Logger(), Recovery()
    r := gin.Default()
    //區域中間鍵使用
    r.GET("/ce", MiddleWare(), func(c *gin.Context) {
        // 取值
        req, _ := c.Get("request")
        fmt.Println("request:", req)
        // 頁面接收
        c.JSON(200, gin.H{"request": req})
    })
    r.Run()
}

在中間件中使用Goroutine

當在中間件或 handler 中啟動新的 Goroutine 時,不能使用原始的背景關系,必須使用只讀副本,

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

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

	r.GET("/long_async", func(c *gin.Context) {
		// 創建在 goroutine 中使用的副本
		tmp := c.Copy()
		go func() {
			// 用 time.Sleep() 模擬一個長任務,
			time.Sleep(5 * time.Second)

			// 請注意您使用的是復制的背景關系 "tmp",這一點很重要
			log.Println("Done! in path " + tmp.Request.URL.Path)
		}()
	})

	r.GET("/long_sync", func(c *gin.Context) {
		// 用 time.Sleep() 模擬一個長任務,
		time.Sleep(5 * time.Second)

		// 因為沒有使用 goroutine,不需要拷貝背景關系
		log.Println("Done! in path " + c.Request.URL.Path)
	})
	r.Run()
}

會話控制

Cookie介紹

  • HTTP是無狀態協議,服務器不能記錄瀏覽器的訪問狀態,也就是說服務器不能區分兩次請求是否由同一個客戶端發出
  • Cookie就是解決HTTP協議無狀態的方案之一,中文是小甜餅的意思
  • Cookie實際上就是服務器保存在瀏覽器上的一段資訊,瀏覽器有了Cookie之后,每次向服務器發送請求時都會同時將該資訊發送給服務器,服務器收到請求后,就可以根據該資訊處理請求
  • Cookie由服務器創建,并發送給瀏覽器,最終由瀏覽器保存

Cookie的用途

  • 測驗服務端發送cookie給客戶端,客戶端請求時攜帶cookie

Cookie的使用

  • 測驗服務端發送cookie給客戶端,客戶端請求時攜帶cookie
package main

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

func main() {
   // 1.創建路由
   // 默認使用了2個中間件Logger(), Recovery()
   r := gin.Default()
   // 服務端要給客戶端cookie
   r.GET("cookie", func(c *gin.Context) {
      // 獲取客戶端是否攜帶cookie
      cookie, err := c.Cookie("key_cookie")
      if err != nil {
         cookie = "NotSet"
         // 給客戶端設定cookie
         //  maxAge int, 單位為秒
         // path,cookie所在目錄
         // domain string,域名
         //   secure 是否智能通過https訪問
         // httpOnly bool  是否允許別人通過js獲取自己的cookie
         c.SetCookie("key_cookie", "value_cookie", 60, "/",
            "localhost", false, true)
      }
      fmt.Printf("cookie的值是: %s\n", cookie)
   })
   r.Run(":8000")
}

Gin設定日志

在Gin框架中記錄日志方法如下

package main

import (
	"io"
	"os"
	"github.com/gin-gonic/gin"
)

func main() {
	// 禁用控制臺顏色,將日志寫入檔案時不需要控制臺顏色,
	gin.DisableConsoleColor()

	// 記錄到檔案,
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)

	// 如果需要同時將日志寫入檔案和控制臺,請使用以下代碼,
	// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	r.Run()
}

以上代碼執行結果如下影片

Cookie的聯系

  • 模擬實作權限驗證中間件
    • 有2個路由,login和home
    • login用于設定cookie
    • home是訪問查看資訊的請求
    • 在請求home之前,先跑中間件代碼,檢驗是否存在cookie
  • 訪問home,會顯示錯誤,因為權限校驗未通過
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

func AuthMiddleWare() gin.HandlerFunc {
   return func(c *gin.Context) {
      // 獲取客戶端cookie并校驗
      if cookie, err := c.Cookie("abc"); err == nil {
         if cookie == "123" {
            c.Next()
            return
         }
      }
      // 回傳錯誤
      c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
      // 若驗證不通過,不再呼叫后續的函式處理
      c.Abort()
      return
   }
}

func main() {
   // 1.創建路由
   r := gin.Default()
   r.GET("/login", func(c *gin.Context) {
      // 設定cookie
      c.SetCookie("abc", "123", 60, "/",
         "localhost", false, true)
      // 回傳資訊
      c.String(200, "Login success!")
   })
   r.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
      c.JSON(200, gin.H{"data": "home"})
   })
   r.Run(":8000")
}

Cookie的缺點

  • 不安全,明文
  • 增加帶寬消耗
  • 可以被禁用
  • cookie有上限

Sessions

gorilla/sessions為自定義session后端提供cookie和檔案系統session以及基礎結構,

主要功能是:

  • 簡單的API:將其用作設定簽名(以及可選的加密)cookie的簡便方法,
  • 內置的后端可將session存盤在cookie或檔案系統中,
  • Flash訊息:一直持續讀取的session值,
  • 切換session持久性(又稱“記住我”)和設定其他屬性的便捷方法,
  • 旋轉身份驗證和加密密鑰的機制,
  • 每個請求有多個session,即使使用不同的后端也是如此,
  • 自定義session后端的介面和基礎結構:可以使用通用API檢索并批量保存來自不同商店的session,

代碼:

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/sessions"
)

// 初始化一個cookie存盤物件
// something-very-secret應該是一個你自己的密匙,只要不被別人知道就行
var store = sessions.NewCookieStore([]byte("something-very-secret"))

func main() {
    http.HandleFunc("/save", SaveSession)
    http.HandleFunc("/get", GetSession)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("HTTP server failed,err:", err)
        return
    }
}

func SaveSession(w http.ResponseWriter, r *http.Request) {
    // Get a session. We're ignoring the error resulted from decoding an
    // existing session: Get() always returns a session, even if empty.

    // 獲取一個session物件,session-name是session的名字
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 在session中存盤值
    session.Values["foo"] = "bar"
    session.Values[42] = 43
    // 保存更改
    session.Save(r, w)
}
func GetSession(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    foo := session.Values["foo"]
    fmt.Println(foo)
}

洗掉session的值:

    // 洗掉
    // 將session的最大存盤時間設定為小于零的數即為洗掉
    session.Options.MaxAge = -1
    session.Save(r, w)

引數驗證

結構體驗證

用gin框架的資料驗證,可以不用決議資料,減少if else,會簡潔許多,

package main

import (
    "fmt"
    "time"

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

//Person ..
type Person struct {
    //不能為空并且大于10
    Age      int       `form:"age" binding:"required,gt=10"`
    Name     string    `form:"name" binding:"required"`
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func main() {
    r := gin.Default()
    r.GET("/5lmh", func(c *gin.Context) {
        var person Person
        if err := c.ShouldBind(&person); err != nil {
            c.String(500, fmt.Sprint(err))
            return
        }
        c.String(200, fmt.Sprintf("%#v", person))
    })
    r.Run()
}

自定義驗證

都在代碼里自己看吧

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "gopkg.in/go-playground/validator.v9"
)

/*
    對系結決議到結構體上的引數,自定義驗證功能
    比如我們需要對URL的接受引數進行判斷,判斷用戶名是否為root如果是root通過否則回傳false
*/
type Login struct {
    User    string `uri:"user" validate:"checkName"`
    Pssword string `uri:"password"`
}

// 自定義驗證函式
func checkName(fl validator.FieldLevel) bool {
    if fl.Field().String() != "root" {
        return false
    }
    return true
}
func main() {
    r := gin.Default()
    validate := validator.New()
    r.GET("/:user/:password", func(c *gin.Context) {
        var login Login
         //注冊自定義函式,與struct tag關聯起來
        err := validate.RegisterValidation("checkName", checkName)
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        err = validate.Struct(login)
        if err != nil {
            for _, err := range err.(validator.ValidationErrors) {
                fmt.Println(err)
            }
            return
        }
        fmt.Println("success")
    })
    r.Run()
}

示例2:

package main

import (
    "net/http"
    "reflect"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v8"
)

// Booking contains binded and validated data.
type Booking struct {
    //定義一個預約的時間大于今天的時間
    CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    //gtfield=CheckIn退出的時間大于預約的時間
    CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
    v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
    //field.Interface().(time.Time)獲取引數值并且轉換為時間格式
    if date, ok := field.Interface().(time.Time); ok {
        today := time.Now()
        if today.Unix() > date.Unix() {
            return false
        }
    }
    return true
}

func main() {
    route := gin.Default()
    //注冊驗證
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        //系結第一個引數是驗證的函式第二個引數是自定義的驗證函式
        v.RegisterValidation("bookabledate", bookableDate)
    }

    route.GET("/5lmh", getBookable)
    route.Run()
}

func getBookable(c *gin.Context) {
    var b Booking
    if err := c.ShouldBindWith(&b, binding.Query); err == nil {
        c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}

// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-09-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-01"

自定義驗證v10

介紹

Validator 是基于 tag(標記)實作結構體和單個欄位的值驗證庫,它包含以下功能:

  • 使用驗證 tag(標記)或自定義驗證器進行跨欄位和跨結構體驗證,
  • 關于 slice、陣列和 map,允許驗證多維欄位的任何或所有級別,
  • 能夠深入 map 鍵和值進行驗證,
  • 通過在驗證之前確定介面的基礎型別來處理型別介面,
  • 處理自定義欄位型別(如 sql 驅動程式 Valuer),
  • 別名驗證標記,它允許將多個驗證映射到單個標記,以便更輕松地定義結構體上的驗證,
  • 提取自定義的欄位名稱,例如,可以指定在驗證時提取 JSON 名稱,并在生成的 FieldError 中使用該名稱,
  • 可自定義 i18n 錯誤訊息,
  • Web 框架 gin 的默認驗證器,

安裝:

使用 go get:

go get github.com/go-playground/validator/v10

然后將 Validator 包匯入到代碼中:

import "github.com/go-playground/validator/v10"

變數驗證

Var 方法使用 tag(標記)驗證方式驗證單個變數,

func (*validator.Validate).Var(field interface{}, tag string) error

它接收一個 interface{} 空介面型別的 field 和一個 string 型別的 tag,回傳傳遞的非法值得無效驗證錯誤,否則將 nil 或 ValidationErrors 作為錯誤,如果錯誤不是 nil,則需要斷言錯誤去訪問錯誤陣列,例如:

validationErrors := err.(validator.ValidationErrors)


如果是驗證陣列、slice 和 map,可能會包含多個錯誤,

示例代碼:

func main() {
  validate := validator.New()
  // 驗證變數
  email := "admin#admin.com"
  email := ""
  err := validate.Var(email, "required,email")
  if err != nil {
    validationErrors := err.(validator.ValidationErrors)
    fmt.Println(validationErrors)
    // output: Key: '' Error:Field validation for '' failed on the 'email' tag
    // output: Key: '' Error:Field validation for '' failed on the 'required' tag
    return
  }
}

結構體驗證

結構體驗證結構體公開的欄位,并自動驗證嵌套結構體,除非另有說明,

func (*validator.Validate).Struct(s interface{}) error

它接收一個 interface{} 空介面型別的 s,回傳傳遞的非法值得無效驗證錯誤,否則將 nil 或 ValidationErrors 作為錯誤,如果錯誤不是 nil,則需要斷言錯誤去訪問錯誤陣列,例如:

validationErrors := err.(validator.ValidationErrors)

實際上,Struct 方法是呼叫的 StructCtx 方法,因為本文不是原始碼講解,所以此處不展開贅述,如有興趣,可以查看原始碼,

示例代碼:

func main() {
  validate = validator.New()
  type User struct {
    ID     int64  `json:"id" validate:"gt=0"`
    Name   string `json:"name" validate:"required"`
    Gender string `json:"gender" validate:"required,oneof=man woman"`
    Age    uint8  `json:"age" validate:"required,gte=0,lte=130"`
    Email  string `json:"email" validate:"required,email"`
  }
  user := &User{
    ID:     1,
    Name:   "frank",
    Gender: "boy",
    Age:    180,
    Email:  "[email protected]",
  }
  err = validate.Struct(user)
  if err != nil {
    validationErrors := err.(validator.ValidationErrors)
    // output: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
    // fmt.Println(validationErrors)
    fmt.Println(validationErrors.Translate(trans))
    return
  }
}

細心的讀者可能已經發現,錯誤輸出資訊并不友好,錯誤輸出資訊中的欄位不僅沒有使用備用名(首字母小寫的欄位名),也沒有翻譯為中文,通過改動代碼,使錯誤輸出資訊變得友好,

注冊一個函式,獲取結構體欄位的備用名稱:

validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
    name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    if name == "-" {
      return "j"
    }
    return name
  })

錯誤資訊翻譯為中文:

zh := zh.New()
uni = ut.New(zh)
trans, _ := uni.GetTranslator("zh")
_ = zh_translations.RegisterDefaultTranslations(validate, trans)

標簽

通過以上章節的內容,讀者應該已經了解到 Validator 是一個基于 tag(標簽),實作結構體和單個欄位的值驗證庫,

本章節列舉一些比較常用的標簽:

標簽 描述
eq 等于
gt 大于
gte 大于等于
lt 小于
lte 小于等于
ne 不等于
max 最大值
min 最小值
oneof 其中一個
required 必需的
unique 唯一的
isDefault 默認值
len 長度
email 郵箱格式

轉自: Golang語言開發堆疊

多語言翻譯驗證

當業務系統對驗證資訊有特殊需求時,例如:回傳資訊需要自定義,手機端回傳的資訊需要是中文而pc端發揮回傳的資訊需要時英文,如何做到請求一個介面滿足上述三種情況,

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    "github.com/go-playground/locales/zh_Hant_TW"
    ut "github.com/go-playground/universal-translator"
    "gopkg.in/go-playground/validator.v9"
    en_translations "gopkg.in/go-playground/validator.v9/translations/en"
    zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)

var (
    Uni      *ut.UniversalTranslator
    Validate *validator.Validate
)

type User struct {
    Username string `form:"user_name" validate:"required"`
    Tagline  string `form:"tag_line" validate:"required,lt=10"`
    Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}

func main() {
    en := en.New()
    zh := zh.New()
    zh_tw := zh_Hant_TW.New()
    Uni = ut.New(en, zh, zh_tw)
    Validate = validator.New()

    route := gin.Default()
    route.GET("/5lmh", startPage)
    route.POST("/5lmh", startPage)
    route.Run(":8080")
}

func startPage(c *gin.Context) {
    //這部分應放到中間件中
    locale := c.DefaultQuery("locale", "zh")
    trans, _ := Uni.GetTranslator(locale)
    switch locale {
    case "zh":
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "en":
        en_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "zh_tw":
        zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
        break
    default:
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    }

    //自定義錯誤內容
    Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
        return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
    }, func(ut ut.Translator, fe validator.FieldError) string {
        t, _ := ut.T("required", fe.Field())
        return t
    })

    //這塊應該放到公共驗證方法中
    user := User{}
    c.ShouldBind(&user)
    fmt.Println(user)
    err := Validate.Struct(user)
    if err != nil {
        errs := err.(validator.ValidationErrors)
        sliceErrs := []string{}
        for _, e := range errs {
            sliceErrs = append(sliceErrs, e.Translate(trans))
        }
        c.String(200, fmt.Sprintf("%#v", sliceErrs))
    }
    c.String(200, fmt.Sprintf("%#v", "user"))
}


正確的鏈接:

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=33&locale=zh

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=en 回傳英文的驗證資訊

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=zh 回傳中文的驗證資訊

查看更多的功能可以查看官網 gopkg.in/go-playground/validator.v9

檔案操作

Gin 并沒有提供檔案的創建,洗掉,讀寫這個操作的專門的介面,所以采用的是常用的ioutil這個包進行檔案的讀寫操作,使用os這個包進行檔案的創建和洗掉,

檔案的創建,寫入內容,讀取內容,洗掉.(此實體使用的是txt檔案):

-controller
	+file.go
-router
	+router.go
main.go

//檔案的創建洗掉和讀寫
router.GET("/cont/filerw", controllers.Filerwhtml)       //獲取檔案api操作資訊
router.POST("/cont/addfile", controllers.FilerCreate)    //創建檔案
router.POST("/cont/writefile", controllers.FilerWrite)   //寫入檔案
router.POST("/cont/readfile", controllers.FilerRead)     //讀取檔案
router.POST("/cont/deletefile", controllers.FilerDelete) //洗掉檔案
package controllers

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
    "os"
)

// 定義接收資料的結構體
type FileData struct {
    // binding:"required"修飾的欄位,若接收為空值,則報錯,是必須欄位
    FileName string `form:"filename" json:"filename" uri:"filename" xml:"filename" binding:"required"`
    Content  string `form:"content" json:"content" uri:"content" xml:"content"`
}

//檔案操作介面資訊
type Data struct {
    Api    string `json:"api"`
    Params string `json:"params"`
    Remark string `json:"remark"`
}

/**檔案讀寫操作介面資訊**/
func Filerwhtml(c *gin.Context) {
    list := []Data{
        Data{
            "/cont/addfile",
            "filename",
            "創建檔案",
        },
        Data{
            "/cont/writefile",
            "filename,content",
            "寫入檔案",
        },
        Data{
            "/cont/readfile",
            "filename",
            "讀取檔案",
        },
        Data{
            "/cont/deletefile",
            "filename",
            "洗掉檔案",
        },
    }
    //回傳結果
    c.JSON(http.StatusOK, gin.H{"code": 0, "list": list})
    return
}

創建檔案

/**創建檔案**/
func FilerCreate(c *gin.Context) {
    // 宣告接收的變數
    var data FileData
    // 將request的body中的資料,自動按照json格式決議到結構體
    if err := c.ShouldBindJSON(&data); err != nil {
        // 回傳錯誤資訊
        // gin.H封裝了生成json資料的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    //創建檔案
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    f, err := os.Create(path)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "創建檔案失敗"})
        fmt.Print(err.Error())
        return
    }
    defer f.Close()
    //回傳結果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "創建成功", "filename": data.FileName})
    return
}

寫入檔案

/**將內容寫入檔案**/
func FilerWrite(c *gin.Context) {
    // 宣告接收的變數
    var data FileData
    // 將request的body中的資料,自動按照json格式決議到結構體
    if err := c.ShouldBindJSON(&data); err != nil {
        // 回傳錯誤資訊
        // gin.H封裝了生成json資料的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    //需要寫入到檔案的內容
    content := data.Content
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    d1 := []byte(content)
    err := ioutil.WriteFile(path, d1, 0644)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "寫入內容失敗"})
        fmt.Print(err.Error())
        return
    }
    //回傳結果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "寫入內容成功", "filename": data.FileName, "content": content})
    return
}

讀取檔案

/**讀取檔案內容**/
func FilerRead(c *gin.Context) {
    // 宣告接收的變數
    var data FileData
    // 將request的body中的資料,自動按照json格式決議到結構體
    if err := c.ShouldBindJSON(&data); err != nil {
        // 回傳錯誤資訊
        // gin.H封裝了生成json資料的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    //檔案讀取任務是將檔案內容讀取到記憶體中,
    info, err := ioutil.ReadFile(path)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "讀取檔案內容失敗"})
        fmt.Print(err.Error())
        return
    }
    fmt.Println(info)
    result := string(info)
    //回傳結果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "讀取內容成功", "filename": data.FileName, "content": result})
    return
}

洗掉檔案

/**洗掉檔案**/
func FilerDelete(c *gin.Context) {
    // 宣告接收的變數
    var data FileData
    // 將request的body中的資料,自動按照json格式決議到結構體
    if err := c.ShouldBindJSON(&data); err != nil {
        // 回傳錯誤資訊
        // gin.H封裝了生成json資料的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName) //源檔案路徑
    //洗掉檔案
    cuowu := os.Remove(path)
    fmt.Print(path)
    if cuowu != nil {
        //如果洗掉失敗則輸出 file remove Error!
        fmt.Println("file remove Error!")
        //輸出錯誤詳細資訊
        fmt.Printf("%s", cuowu)
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "洗掉檔案失敗"})
        return
    } else {
        //如果洗掉成功則輸出 file remove OK!
        fmt.Print("file remove OK!")
    }
    //回傳結果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "洗掉檔案成功", "filename": data.FileName})
    return
}
檔案上傳下載
- controller
	+file.go
-uploadFile
	+.....
-router
	+router.go
-main.go

package controller

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

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

// AddUploads 上傳檔案
func AddUploads(c *gin.Context) {
	username := c.PostForm("username")
	// 單個檔案
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err.Error(),
		})
		return
	}

	log.Println(file.Filename)
	// dst := fmt.Sprintf("D:/桌面/檔案/updateFile/%s", username+"-"+file.Filename)
	dst := fmt.Sprintf(".updateFile/%s", username+"-"+file.Filename)
	// 上傳檔案到指定的目錄
	c.SaveUploadedFile(file, dst)
	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"message":  fmt.Sprintf("'%s' uploaded!", file.Filename),
	})
}

// DownFile
func DownFile(c *gin.Context) {
	fileName := "hjz-開題報告.docx"
	filepath := "./updateFile/" + fileName
	list := strings.Split(fileName, "-")
	downFileName := list[1]
	c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downFileName))
	c.Writer.Header().Add("Content-Type", "application/octet-stream")
	fmt.Println(filepath)
	c.File(filepath)
}

基本問題:

類型轉換

string -- int64

func DeletedFileOne(c *gin.Context) {
	fidStr := c.Param("fid")
	fid, err := strconv.ParseInt(fidStr, 10, 64)
	if err != nil {
		ResponseError(c, CodeInvalidParam)
		return
	}
	if err2 := logic.DeletedFileOne(fid); err2 != nil {
		zap.L().Error("logic.DeletedFileOne () failed ", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	ResponseSuccess(c, CodeSuccess)
}

int64--string

strconv.FormatInt(v.NucleicAcidID, 10)

得到當前時間戳

// GetNowTime 得到現在時間的年月日的時間戳
func GetNowTime() int64 {
	tm := time.Now().Format("2006-01-02")
	tt, _ := time.ParseInLocation("2006-01-02", tm, time.Local)
	return tt.Unix()
}

時間戳---time

time.Unix(v.TodayTime, 0)
// 秒級時間戳轉time
func UnixSecondToTime(second int64) time.Time {
	return time.Unix(second, 0)
}

// 毫秒級時間戳轉time
func UnixMilliToTime(milli int64) time.Time {
	return time.Unix(milli/1000, (milli%1000)*(1000*1000))
}

// 納秒級時間戳轉time
func UnixNanoToTime(nano int64) time.Time {
	return time.Unix(nano/(1000*1000*1000), nano%(1000*1000*1000))
}

Gin框架決議

路由決議

中間件決議

微信小程式

	uni.login({
				        provider: "weixin",
				        success: function (res) {
						uni.request({
							  method:'POST',	
							  url: 'http://106.15.65.147:8081/api/v1/wx/openid', //僅為示例,并非真實介面地址,
							  data: {
							      "app_id": "wx8d36d8370b6e82f0",
							      "code": res.code,
							      "method": "get",
							      "secret": "092d4b45d6b6c8d2b99bf82c6e23657e",
							      "url": "https://api.weixin.qq.com/sns/jscode2session" 
							  },
							  header: {
							      'content-type': 'application/json' //自定義請求頭資訊
							  },
							  success: (res) => {
								  var result = res.data
								  	uni.setStorageSync('open_id',result.data.open_id)
									uni.setStorageSync('session_key',result.data.session_key)
							  }
							});
				        },
				      });

轉發請求

post

// 發送post請求
func main() {
router := gin.Default()
router.GET("/test", func(c *gin.Context) {
   var body = strings.NewReader("name=test&jab=teache")
   response, err := http.Post("http://localhost:8888/base/captcha","application/json; charset=utf-8", body)
   if err != nil || response.StatusCode != http.StatusOK {
      c.Status(http.StatusServiceUnavailable)
      return
   }

   reader := response.Body
   contentLength := response.ContentLength
   contentType := response.Header.Get("Content-Type")

   extraHeaders := map[string]string{
      //"Content-Disposition": `attachment; filename="gopher.png"`,
   }

   c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":8080")
}

golang發送get請求第三方資料

func main() {
   router := gin.Default()
   router.GET("/test", func(c *gin.Context) {
      response, err := http.Get("https://baidu.com")
      if err != nil || response.StatusCode != http.StatusOK {
         c.Status(http.StatusServiceUnavailable)
         return
      }

      reader := response.Body
      contentLength := response.ContentLength
      contentType := response.Header.Get("Content-Type")

      extraHeaders := map[string]string{
         //"Content-Disposition": `attachment; filename="gopher.png"`,
      }

      c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
   })
   router.Run(":8080")
}

跨域:

package router

import (
	"net/http"

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

func CORSMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") //允許所有IP訪問
		ctx.Writer.Header().Set("Access-Control-Max-Age", "86400")
		ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*")
		ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		ctx.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")

		if ctx.Request.Method == http.MethodOptions {
			ctx.AbortWithStatus(200)
		} else {
			ctx.Next()
		}
	}
}

實作404頁面

package main

import (
    "fmt"
    "net/http"

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

func main() {
    r := gin.Default()
    r.GET("/user", func(c *gin.Context) {
        //指定默認值
        //http://localhost:8080/user 才會列印出來默認的值
        name := c.DefaultQuery("name", "枯藤")
        c.String(http.StatusOK, fmt.Sprintf("hello %s", name))2020-08-05 09:22:11 星期三
    })
    r.NoRoute(func(c *gin.Context) {
        c.String(http.StatusNotFound, "404 not found2222")
    })
    r.Run()
}

JSON序列化和反序列化

package main

import (
	"encoding/json"
	"fmt"
	"math"
)

type User struct {
	UserID   int64  `json:"id"`
	UserName string `json:"name"`
}

// 第一層次
// 第二層次
// 第五層次
func main() {
	//json序列化
	user := User{
		UserID:   math.MaxInt64,
		UserName: "hjz",
	}
	b, err := json.Marshal(user)
	if err != nil {
		print(err)
	}
	fmt.Println(string(b))
	//使用Json的反序列化
	s := `{"id":9223372036854775807,"name":"hjz"}`
	var data User
	err = json.Unmarshal([]byte(s), &data)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("userId:%d,userName:%s", user.UserID, user.UserName)
}

翻譯Gin框架的日志

-controller
  + validator.go
- models
  + params.go

package controller

//翻譯Gin框架的日志
import (
	"fmt"
	"reflect"
	"strings"
	"fileWeb/models"

	"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"
)

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

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

		// 為SignUpParam注冊自定義校驗方法
		v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{})
		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
}

// RemoveTopStruct 去除提示資訊中的結構體名稱
func RemoveTopStruct(fields map[string]string) map[string]string {
	res := map[string]string{}
	for field, err := range fields {
		res[field[strings.Index(field, ".")+1:]] = err
	}
	return res
}

// SignUpParamStructLevelValidation 自定義SignUpParam結構體校驗函式
func SignUpParamStructLevelValidation(sl validator.StructLevel) {
	su := sl.Current().Interface().(models.ParamSignUp)

	if su.Password != su.RePassword {
		// 輸出錯誤提示資訊,最后一個引數就是傳遞的param
		sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password")
	}
}
// ParamSignUp 注冊請求引數
type ParamSignUp struct {
	Username   string `json:"username" binding:"required"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"re_password" binding:"required"`
	Phone      string `json:"phone" binding:"required"`
	Name       string `json:"name" binding:"required"`
}

雪花演算法

-pkg
 - snowflake
 	+ snowflake.go

package snowflake

//雪花演算法
import (
	"time"

	"github.com/bwmarrin/snowflake"
)

var node *snowflake.Node

func Init(startTime string, machineID int64) (err error) {
	var st time.Time
	//指定時間因子-startTime
	st, err = time.Parse("2006-01-02", startTime)
	if err != nil {
		return
	}
	snowflake.Epoch = st.UnixNano() / 1000000
	node, err = snowflake.NewNode(machineID)
	return
}
func GenID() int64 {
	return node.Generate().Int64()
}

jwtToken

- pkg
  -jwt
  	+ jwt.go

package jwt

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v4"
)

// token的過期時間
const TokenExpireDuration = time.Hour * 2

// token的sercet用于簽名的字串
var CustomSecret []byte = []byte("疫情小程式簽名")

type CustomClaims struct {
	jwt.RegisteredClaims        // 內嵌標準的宣告
	UserID               int64  `json:"user_id"`
	Username             string `json:"username"`
}

// GenToken 生成JWT
func GenToken(userID int64, username string) (string, error) {
	// 創建一個我們自己的宣告
	claims := CustomClaims{
		UserID:   userID,
		Username: username, // 自定義欄位
	}
	claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(TokenExpireDuration))
	claims.Issuer = "my-project"
	// 使用指定的簽名方法創建簽名物件
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 使用指定的secret簽名并獲得完整的編碼后的字串token
	return token.SignedString(CustomSecret)
}

// ParseToken 決議JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
	// 決議token
	// 如果是自定義Claim結構體則需要使用 ParseWithClaims 方法
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		// 直接使用標準的Claim則可以直接使用Parse方法
		//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		return CustomSecret, nil
	})
	if err != nil {
		return nil, err
	}
	// 對token物件中的Claim進行型別斷言
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { // 校驗token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

Code 回傳的錯誤

- controller 
	+ Code.go

package controller

// ResCode 定義回傳值型別
type ResCode int64

const (
	CodeSuccess ResCode = 1000 + iota
	CodeInvalidParam
	CodeUserExist
	CodeUserNotExist
	CodeInvalidPassword
	CodeServerBusy

	CodeNeedLogin
	CodeInvalidToken
)

var codeMsgMap = map[ResCode]string{
	CodeSuccess:         "success",
	CodeInvalidParam:    "請求引數錯誤",
	CodeUserExist:       "用戶名已存在",
	CodeUserNotExist:    "用戶名不存在",
	CodeInvalidPassword: "用戶名或密碼錯誤",
	CodeServerBusy:      "服務器繁忙",

	CodeNeedLogin:    "需要登錄",
	CodeInvalidToken: "無效的token",
}

// GetMsg 得到對應的錯誤
func (r ResCode) GetMsg() string {
	msg, ok := codeMsgMap[r]
	if !ok {
		msg = codeMsgMap[CodeServerBusy]
	}
	return msg
}

Response 回傳回應方法

- controller 
	+ resopnse.go

package controller

import (
	"net/http"

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

/*
	{
		"code":1001,//程式中的錯誤碼
		"msg":xx,提示資訊
		"data":{},//資料
	}
*/
type Response struct {
	Code ResCode     `json:"code"`
	Msg  interface{} `json:"msg"`
	Data interface{} `json:"data,omitempty"`
}

// ResponseError 回傳錯誤型別
func ResponseError(c *gin.Context, code ResCode) {
	resp := &Response{
		Code: code,
		Msg:  code.GetMsg(),
		Data: nil,
	}
	c.JSON(http.StatusOK, resp)
}

// ResponseSuccess 回傳請求成功
func ResponseSuccess(c *gin.Context, data interface{}) {
	resp := &Response{
		Code: CodeSuccess,
		Msg:  CodeSuccess.GetMsg(),
		Data: data,
	}
	c.JSON(http.StatusOK, resp)
}

// 自定義的回傳錯誤
func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) {
	resp := &Response{
		Code: code,
		Msg:  msg,
		Data: nil,
	}
	c.JSON(http.StatusOK, resp)
}

// ResponseSuccessLayUi 回傳Layui 資料
func ResponseSuccessLayUi(c *gin.Context, code int, msg string, count int, data interface{}) {
	c.JSON(http.StatusOK, gin.H{
		"code":  code,
		"count": count,
		"data":  data,
		"msg":   msg,
	})
}

Request 決議請求操作

- controller
	+ request.go

package controller

import (
	"errors"
	"strconv"

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

const CtxUserIDkey = "userID"

var ErrorUserNotLogin = errors.New("用戶未登錄")

// GetCyrrentUserID 獲取當前用戶的ID
func GetCyrrentUserID(c *gin.Context) (userID int64, err error) {
	uid, ok := c.Get(CtxUserIDkey)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	userID, ok = uid.(int64)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	return
}

// GetPageInfo 處理分頁請求的引數
func GetPageInfo(c *gin.Context) (page, page_size int64, err error) {
	//獲得分頁引數offer和limit
	pageStr := c.Query("page")
	page_sizeStr := c.Query("size")
	page, err = strconv.ParseInt(pageStr, 10, 32)
	if err != nil {
		page = 1
	}
	page_size, err = strconv.ParseInt(page_sizeStr, 10, 32)
	if err != nil {
		page_size = 10
	}
	return
}

基本請求

1.引數處理
2.業務邏輯
3.回傳資料

import (
	"errors"
	"strconv"
	"webGin/dao/mysql"
	"webGin/logic"
	"webGin/models"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10" //注意這條正確
	"go.uber.org/zap"
)

// SingUpHandlerInstructor 輔導員注冊
func SingUpHandlerInstructor(c *gin.Context) {
	p := new(models.ParamSignUp)
	if err := c.ShouldBindJSON(p); err != nil {
		zap.L().Error("SingUpHandlerInstructor with invalid param ", zap.Error(err))
		//判斷是否是校驗錯誤
		errs, ok := err.(validator.ValidationErrors)
		if !ok {
			ResponseError(c, CodeInvalidParam)
			return
		} else {
			ResponseErrorWithMsg(c, CodeInvalidParam, RemoveTopStruct(errs.Translate(trans)))
			return
		}
	}
	//業務處理
	if err := logic.SingUp(p); err != nil {
		zap.L().Error("logic.SingUp() failed", zap.Error(err))
		//依據錯誤的型別進行回傳
		if errors.Is(err, mysql.ErrorUserExist) {
			ResponseError(c, CodeUserExist)
			return
		} else {
			ResponseError(c, CodeServerBusy)
			return
		}
	}
	ResponseSuccess(c, CodeSuccess)
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550304.html

標籤:其他

上一篇:Disruptor-原始碼解讀

下一篇:Spring AOP官方檔案學習筆記(二)之基于注解的Spring AOP

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more