Gin 文档学习
1.自定義模板渲染器
// 自定義html模板渲染器,要指定所有的html路徑,不推薦html := template.Must(template.ParseFiles("templates/login.html","templates/users/index.html","templates/center/index.html",))
//應(yīng)用這些模板router.SetHTMLTemplate(html)router.GET("/users/index", func(context *gin.Context) {context.HTML(http.StatusOK, "users/index.html", gin.H{"title": "users/index.html",})})
2.自定義模板功能
func formatAsDate(t time.Time) string {year, month, day := t.Date()return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}func main() {router := gin.Default()//用于代替模板里的 {{ }}(調(diào)用后端變量)的用法router.Delims("<{", "}>")//自定義模板函數(shù) 注意要把這個(gè)函數(shù)放在加載模板前router.SetFuncMap(template.FuncMap{"formatAsDate": formatAsDate,})//加載指定的模板文件router.LoadHTMLFiles("./templates/raw.html")router.GET("/raw", func(c *gin.Context) {c.HTML(http.StatusOK, "raw.html", map[string]interface{}{"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),})})router.Run(":9999")
}
raw.html
<body>
<!--等同于 formatAsDate .now-->
date: <{.now | formatAsDate}>
</body>
3.Multipart/Urlencoded 綁定
package mainimport ("github.com/gin-gonic/gin"
)type LoginForm struct {User string `form:"user" binding:"required"`Password string `form:"password" binding:"required"`
}func main() {**加粗樣式**router := gin.Default()router.POST("/login", func(c *gin.Context) {// 你可以使用顯式綁定聲明綁定 multipart form://c.ShouldBindWith(&form, binding.Form)// 或者簡(jiǎn)單地使用 ShouldBind 方法自動(dòng)綁定:var form LoginForm// 在這種情況下,將自動(dòng)選擇合適的綁定if c.ShouldBind(&form) == nil {if form.User == "Winnie-OCEAN" && form.Password == "789" {c.JSON(200, gin.H{"status": "you are logged in","user": form.User,"password": form.Password,})} else {c.JSON(401, gin.H{"status": "unauthorized"})}}})router.Run()
}
或者
router.POST("/login", func(c *gin.Context) {// 或者簡(jiǎn)單地使用 ShouldBind 方法自動(dòng)綁定://c.ShouldBind(&form)var form LoginForm// 你可以使用顯式綁定聲明綁定 multipart form:if c.ShouldBindWith(&form, binding.Form) == nil {if form.User == "OCEAN" && form.Password == "123456789" {c.JSON(200, gin.H{"status": "you are logged in","user": form.User,"password": form.Password,})} else {c.JSON(401, gin.H{"status": "unauthorized"})}}})
4.Multipart/Urlencoded 表單
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {engine := gin.Default()engine.POST("/post_form", func(context *gin.Context) {//獲取post過來的名為user的值user := context.PostForm("user")//獲取post過來的名為password的值,第二個(gè)參數(shù)為默認(rèn)值password := context.DefaultPostForm("password", "123456")context.JSON(http.StatusOK, gin.H{"success": "login in","username": user,"password": password,})})engine.Run()
}
5.PureJSON
JSON 使用 unicode 替換特殊 HTML 字符,例如 < 變?yōu)?\ u003c。如果要按字面對(duì)這些字符進(jìn)行編碼,則可以使用 PureJSON。
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()//結(jié)果為 {"html":"\u003cb\u003eHello, world!\u003c/b\u003e"}// 提供 unicode 實(shí)體r.GET("/json", func(c *gin.Context) {c.JSON(200, gin.H{"html": "<b>Hello, world!</b>",})})//輸出結(jié)果{"html":"<b>Hello, world!</b>"}// 提供字面字符r.GET("/purejson", func(c *gin.Context) {c.PureJSON(200, gin.H{"html": "<b>Hello, world!</b>",})})// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":8080")
}
6.提取url參數(shù)同時(shí)獲取post或來的數(shù)據(jù) Query 和 post form
package mainimport "github.com/gin-gonic/gin"func main() {engine := gin.Default()engine.POST("/post_query", func(context *gin.Context) {username := context.Query("username")password := context.DefaultQuery("password", "123")age := context.PostForm("age")page := context.PostForm("page")context.JSON(200, gin.H{"username": username,"password": password,"age": age,"page": page,})})engine.Run(":9999")
}
7.SecureJSON 防止 json 劫持
JSON劫持,其實(shí)就是惡意網(wǎng)站,通過<script>標(biāo)簽獲取你的JSON數(shù)據(jù),因?yàn)镴SON數(shù)組默認(rèn)為是可執(zhí)行的JS,所以通過這種方式,可以獲得你的敏感數(shù)據(jù)。最前面有個(gè)while(1);前綴,這就可以在<script>標(biāo)簽執(zhí)行我們返回的數(shù)據(jù)時(shí),就可以無限循環(huán),阻止后面數(shù)組數(shù)據(jù)的執(zhí)行,防止數(shù)據(jù)被劫持。
func main() {r := gin.Default()r.GET("/someJSON", func(c *gin.Context) {names := []string{"winnie", "ocean", "22"}// 將輸出:while(1);["winnie", "ocean", "22"]c.SecureJSON(http.StatusOK, names)})// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":8080")
}
Gin默認(rèn)的防JSON劫持的前綴是while(1);我們可以改變,通過r.SecureJsonPrefix方法設(shè)置即可,如:
func main() {r := gin.Default()//你也可以使用自己的 SecureJSON 前綴r.SecureJsonPrefix(")]}',\n")r.GET("/someJSON", func(c *gin.Context) {names := []string{"winnie", "ocean", "22"}// 將輸出://)]}',//["winnie","ocean","22"]c.SecureJSON(http.StatusOK, names)})r.Run(":8080")
}
8. xml json yaml protobuf
package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample""net/http"
)func main() {r := gin.Default()// gin.H 是 map[string]interface{} 的一種快捷方式r.GET("/someJSON", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/moreJSON", func(c *gin.Context) {// 使用一個(gè)結(jié)構(gòu)體var msg struct {// 注意 msg.Name 在 JSON 中變成了 "user"Name string `json:"user"`Message stringNumber int}msg.Name = "Lena"msg.Message = "hey"msg.Number = 123// 將輸出:{"user": "Lena", "Message": "hey", "Number": 123}c.JSON(http.StatusOK, msg)})r.GET("/someXML", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具體定義寫在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps: reps,}// 請(qǐng)注意,數(shù)據(jù)在響應(yīng)中變?yōu)槎M(jìn)制數(shù)據(jù)// 將輸出(產(chǎn)生) data被ProtoBuf序列化了 的數(shù)據(jù),生成一個(gè)二進(jìn)制文件// 序列化:將數(shù)據(jù)結(jié)構(gòu)或者對(duì)象轉(zhuǎn)化成二進(jìn)制串的過程c.ProtoBuf(http.StatusOK, data)})r.Run()
}
9.上傳文件
9.1 單文件
func main() {engine := gin.Default()// 為 multipart forms 設(shè)置較低的內(nèi)存限制 (默認(rèn)是 32 MiB)// 限制處理該文件所占用的最大內(nèi)存engine.MaxMultipartMemory = 8 << 20 // 8 MiBengine.POST("/post", func(context *gin.Context) {//單文件file, err := context.FormFile("picture")if err != nil {log.Fatal(err)}//打印文件名log.Println(file.Filename)//設(shè)置上傳路徑 沒有就默認(rèn)當(dāng)前目錄下dst := "./" + file.Filename// 上傳文件至指定的完整文件路徑context.SaveUploadedFile(file, dst)context.String(200, "upload ok!!")})engine.Run()
}
9.2 多文件
func main() {engine := gin.Default()engine.MaxMultipartMemory = 8 << 20 //8Mibengine.POST("/files", func(context *gin.Context) {//MultipartForm是經(jīng)過解析的 多部分表單,包括文件上傳。form, _ := context.MultipartForm() //多部份表單files := form.File["uploads[]"] //收集名為 uploads[]的多個(gè)文件//循環(huán)遍歷輸出文件名for _, file := range files {log.Println(file.Filename)//上傳文件 至 指定路徑 沒有路徑,默認(rèn)根目錄。context.SaveUploadedFile(file, file.Filename)}context.String(200, "upload files ok!!")})engine.Run()
}
10.從 reader 讀取數(shù)據(jù)
訪問http://localhost:8080/test后,將下載url地址的圖片并將其命名為123.png 但現(xiàn)在將其下載卻打不開
func main() {router := gin.Default()router.GET("/test", func(c *gin.Context) {response, err := http.Get("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%BC%A0%E7%B4%AB%E5%AE%81%E5%9B%BE%E7%89%87&hs=0&pn=1&spn=0&di=7136437450519347201&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2643363613%2C2106712228&os=3028774708%2C1970574687&simid=2643363613%2C2106712228&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%BC%A0%E7%B4%AB%E5%AE%81%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fn.sinaimg.cn%2Fsinakd20111%2F539%2Fw1960h2579%2F20210619%2F9e65-74af1bfc3b1873479616e5a37bb490eb.jpg%26refer%3Dhttp%3A%2F%2Fn.sinaimg.cn%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1666405458%26t%3D02eb7dfb10fad465e8d88e94d0074659&fromurl=ippr_z2C%24qAzdH3FAzdH3Fh_z%26e3Bftgw_z%26e3Bv54_z%26e3BvgAzdH3Fw6ptvsj_0n9lnd0n8n_8kma11c18aa8aapj4p_z%26e3Bip4s&gsm=2&islist=&querylist=&dyTabStr=MCwzLDYsMiwxLDQsNSw4LDcsOQ%3D%3D.jpg")// StatusCode 響應(yīng)體的狀態(tài)碼if err != nil || response.StatusCode != http.StatusOK {//Status設(shè)置HTTP響應(yīng)碼 503c.Status(http.StatusServiceUnavailable)return}reader := response.Body //響應(yīng)體contentLength := response.ContentLengthcontentType := response.Header.Get("Content-Type") //獲得 響應(yīng)體數(shù)據(jù)類型extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="123.png"`,}// DataFromReader 將指定的讀取器寫入主體流 并 更新HTTP代碼c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)})router.Run(":8080")
}
11. BasicAuth 中間件 驗(yàn)證用戶登錄
func main() {r := gin.Default()// 路由組使用 gin.BasicAuth() 中間件// gin.Accounts 是 map[string]string 的一種快捷方式authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{//用戶名 密碼"foo": "bar","austin": "1234","lena": "hello2","manu": "4321",}))// /admin/secrets 端點(diǎn)// 觸發(fā) "localhost:8080/admin/secretsauthorized.GET("/secrets", func(c *gin.Context) {// 獲取用戶,它是由 BasicAuth 中間件設(shè)置的//如果給定鍵存在,MustGet將返回該鍵的值,否則將出現(xiàn)panic。user := c.MustGet(gin.AuthUserKey).(string) //獲取輸入的用戶名//secret 對(duì)應(yīng)鍵名為user的值 也就是用戶數(shù)據(jù)if secret, ok := secrets[user]; ok {c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})} else {c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})}})// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":8080")
}
12.嵌套路由組
func main() {// 新建一個(gè)沒有任何默認(rèn)中間件的路由r := gin.New()authorized := r.Group("/")authorized.Use(readEndpoint){authorized.POST("/login", loginEndpoint)}// 嵌套路由組//訪問 http://localhost:8080/testing/analytics 得到結(jié)果testing := authorized.Group("testing")testing.GET("/analytics", analyticsEndpoint)// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":8080")
}
13.只綁定 url 查詢字符串
ShouldBindQuery 函數(shù)只綁定 url 查詢參數(shù)而忽略 post 數(shù)據(jù)。
type Person struct {Name string `form:"name"`Address string `form:"address"`
}func main() {route := gin.Default()//任何都注冊(cè)一個(gè)與所有HTTP方法匹配的路由。獲取,發(fā)布,放置,修補(bǔ),頭部,選項(xiàng),刪除,連接,跟蹤route.Any("/testing", startPage)route.Run()
}func startPage(c *gin.Context) {var person Person//ShouldBindQuery是c.ShouldBindWith(obj, binding.Query)的快捷方式。if c.ShouldBindQuery(&person) == nil {log.Println("====== Only Bind By Query String ======")log.Println(person.Name)log.Println(person.Address)}c.String(200, "Success\n")c.JSON(200, gin.H{"address": person.Address,"name": person.Name,})
}
14 在中間件中使用 Goroutine
當(dāng)在中間件或 handler 中啟動(dòng)新的 Goroutine 時(shí),不能使用原始的上下文,必須使用只讀副本。
func main() {r := gin.Default()r.GET("/long_async", func(c *gin.Context) {// 創(chuàng)建在 goroutine 中使用的副本cCp := c.Copy()go func() {// 用 time.Sleep() 模擬一個(gè)長(zhǎng)任務(wù)。time.Sleep(5 * time.Second)// 請(qǐng)注意您使用的是復(fù)制的上下文 "cCp",這一點(diǎn)很重要log.Println("Done! in path " + cCp.Request.URL.Path)}()})r.GET("/long_sync", func(c *gin.Context) {// 用 time.Sleep() 模擬一個(gè)長(zhǎng)任務(wù)。time.Sleep(5 * time.Second)// 因?yàn)闆]有使用 goroutine,不需要拷貝上下文log.Println("Done! in path " + c.Request.URL.Path)})// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":8080")
}
15 如何記錄日志
func main() {// 禁用控制臺(tái)顏色,將日志寫入文件時(shí)不需要控制臺(tái)顏色。gin.DisableConsoleColor()// Create 創(chuàng)建一個(gè)名為 Winne.log 的文件f, _ := os.Create("2.log")// DefaultWriter=os.Stdout 指向系統(tǒng)的標(biāo)準(zhǔn)輸出//將日志記錄到文件 fgin.DefaultWriter = io.MultiWriter(f)// 如果需要同時(shí)將日志寫入文件和控制臺(tái),請(qǐng)使用以下代碼。//gin.DefaultWriter = io.MultiWriter(f, os.Stdout)router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}
16 將 request body 綁定到不同的結(jié)構(gòu)體中
要想多次綁定,可以使用 c.ShouldBindBodyWith. 只有某些格式需要此功能,如 JSON, XML, MsgPack, ProtoBuf。
type formA struct {Foo string `json:"foo" xml:"foo" binding:"required"`
}type formB struct {Bar string `json:"bar" xml:"bar" binding:"required"`
}func SomeHandler(c *gin.Context) {objA := formA{}objB := formB{}// 讀取 c.Request.Body 并將結(jié)果存入上下文。if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 這時(shí), 復(fù)用存儲(chǔ)在上下文中的 body。} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {c.String(http.StatusOK, `the body should be formB JSON`)// 可以接受其他格式} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {c.String(http.StatusOK, `the body should be formB XML`)} else {...}
}
17 映射查詢字符串或表單參數(shù)
func main() {router := gin.Default()router.POST("/post", func(c *gin.Context) {ids := c.QueryMap("ids") //url參數(shù)names := c.PostFormMap("names")//post過來的數(shù)據(jù)fmt.Printf("ids: %v; names: %v", ids, names)c.JSON(200, ids)c.JSON(200, names)})router.Run()
}
18 查詢字符串參數(shù) Query DefaultQuery
func main() {router := gin.Default()// 使用現(xiàn)有的基礎(chǔ)請(qǐng)求對(duì)象解析查詢字符串參數(shù)。// 示例 URL: /welcome?firstname=winnie&lastname=jinerouter.GET("/welcome", func(c *gin.Context) {firstname := c.DefaultQuery("firstname", "Guest")lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的一種快捷方式c.String(http.StatusOK, "Hello %s %s", firstname, lastname)})router.Run(":8080")
}
19 模型綁定和驗(yàn)證
19.1 JSON
// 綁定 JSON
type Login struct {User string `form:"user" json:"user" xml:"user" binding:"required"`Password string `form:"password" json:"password" xml:"password" binding:"required"`
}func main() {router := gin.Default()// 綁定 JSON ({"user": "winnie", "password": "123"})router.POST("/login", func(c *gin.Context) {var json Loginif err := c.ShouldBindJSON(&json); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if json.User != "winnie" || json.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return}c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})c.JSON(200, json)})router.Run()
}
19.2 XML
// 綁定 JSON
type Login struct {User string `form:"user" json:"user" xml:"user" binding:"required"`Password string `form:"password" json:"password" xml:"password" binding:"required"`
}func main() {router := gin.Default()router.POST("/loginXML", func(c *gin.Context) {var xml Loginif err := c.ShouldBindXML(&xml); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if xml.User != "OCEAN" || xml.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return}c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})c.JSON(200, xml)})router.Run()
}
19.3 綁定html表單
type Login struct {User string `form:"user" json:"user" xml:"user" binding:"required"`Password string `form:"password" json:"password" xml:"password" binding:"required"`
}func main() {router := gin.Default()// 綁定 HTML 表單 (user=manu&password=123)router.POST("/loginForm", func(c *gin.Context) {var form Login// 根據(jù) Content-Type Header 推斷使用哪個(gè)綁定器。if err := c.ShouldBind(&form); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}if form.User != "manu" || form.Password != "123" {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})return}c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})c.JSON(200, form)})router.Run()
}
20 綁定 Uri
type Person struct {ID string `uri:"id" binding:"required"`Name string `uri:"name" binding:"required"`
}func main() {route := gin.Default()route.GET("/:name/:id", func(c *gin.Context) {var person Personif err := c.ShouldBindUri(&person); err != nil {c.JSON(400, gin.H{"msg": err.Error()})return}c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})})route.Run()
}
21 綁定查詢字符串或表單數(shù)據(jù)
type Person struct {Name string `form:"name"`Address string `form:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}func main() {route := gin.Default()route.GET("/testing", startPage)route.Run()
}func startPage(c *gin.Context) {var person Person// 如果是 `GET` 請(qǐng)求,只使用 `Form` 綁定引擎(`query`)。// 如果是 `POST` 請(qǐng)求,首先檢查 `content-type` 是否為 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)。// 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88if c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}c.JSON(200, person)c.String(200, "Success")
}
如果 采用post方法
22 綁定表單數(shù)據(jù)至自定義結(jié)構(gòu)體
func main() {r := gin.Default()r.GET("/getb", func(context *gin.Context) {var b StructB//shouldbind與bind作用一致,但是 shouldbind相比較于bind能更好的控制綁定context.ShouldBind(&b)context.JSON(200, gin.H{"f_a": b.NestedStruct.FieldA,"f_b": b.FiledB,})})r.GET("/getc", func(context *gin.Context) {var c StructCcontext.ShouldBind(&c)context.JSON(200, gin.H{//"f_a": (*(c.NestedStructPointer)).FieldA"f_a": c.NestedStructPointer.FieldA,"f_c": c.FieldC,})})r.GET("/getd", func(context *gin.Context) {var d StructDcontext.ShouldBind(&d)context.JSON(200, gin.H{"f_x": d.NestedAnnoyStruct.FieldX,"f_d": d.FieldD,})})r.Run()
}
目前僅支持沒有 form 的嵌套結(jié)構(gòu)體 ,例如 下列 不支持以下格式的結(jié)構(gòu)體
type StructX struct {X struct {} `form:"name_x"` // 有 form
}type StructY struct {Y StructX `form:"name_y"` // 有 form
}type StructZ struct {Z *StructZ `form:"name_z"` // 有 form
}
23 自定義 HTTP 配置
func main() {router := gin.Default()router.GET("/", func(context *gin.Context) {context.String(200, "自定義配置成功")})http.ListenAndServe(":8080", router)
}
func main() {router := gin.Default()router.GET("/", func(context *gin.Context) {context.String(200, "配置成功")})s := &http.Server{Addr: ":9999",Handler: router,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}s.ListenAndServe()
}
24 自定義中間件
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 設(shè)置 example 變量c.Set("example", "12345")// 請(qǐng)求前c.Next()// 請(qǐng)求后// Since返回從 t 開始經(jīng)過的時(shí)間。latency := time.Since(t)log.Print(latency)// 獲取發(fā)送的 status Status返回當(dāng)前請(qǐng)求的HTTP響應(yīng)狀態(tài)碼。status := c.Writer.Status()log.Println(status)}
}func main() {r := gin.New()//或者 r.Use(Logger())r.GET("/test", Logger(), func(c *gin.Context) {//如果給定鍵存在,MustGet將返回該鍵的值,example := c.MustGet("example").(string)// 打印:"12345"log.Println(example)})// 監(jiān)聽并在 0.0.0.0:8080 上啟動(dòng)服務(wù)r.Run(":9999")
}
25 自定義日志文件
func main() {router := gin.New()// LoggerWithFormatter 中間件會(huì)寫入日志到 gin.DefaultWriter// 默認(rèn) gin.DefaultWriter = os.Stdout//LoggerWithFormatter實(shí)例 是 一個(gè)Logger中間件,具有 指定的日志格式功能。//LogFormatterParams是任何格式化程序在需要記錄日志時(shí)都會(huì)提交的結(jié)構(gòu)router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {// 你的自定義格式return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",param.ClientIP,param.TimeStamp.Format(time.RFC1123),param.Method,param.Path,param.Request.Proto,param.StatusCode,param.Latency,param.Request.UserAgent(),param.ErrorMessage,)}))router.Use(gin.Recovery())router.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})router.Run(":8080")
}
26 自定義驗(yàn)證器
這個(gè)自定義驗(yàn)證器 驗(yàn)證流程有兩個(gè)要素
一:需要傳入兩次時(shí)間 分別是 checkin與checkout 每傳入一次, bookableDate 都需要驗(yàn)證一次
二:gtfield=CheckIn 用來約束 check_out時(shí)間 大于 check_in時(shí)間。
// Booking 包含綁定和驗(yàn)證的數(shù)據(jù)。
type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`// gtfield 是一個(gè)默認(rèn)規(guī)則,意思是要大于某個(gè)字段的值. gtfield=CheckIn表明 check_out的值要大于check_in的值CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}// 定義一個(gè)驗(yàn)證方法,用來驗(yàn)證時(shí)間是否合法
// 驗(yàn)證方法返回值應(yīng)該是個(gè)布爾值
// type Func func(fl FieldLevel) bool
// FieldLevel 包含驗(yàn)證字段的所有信息和幫助函數(shù)
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {// Field 返回當(dāng)前字段進(jìn)行驗(yàn)證// 將值以 interface{} 類型返回date, ok := fl.Field().Interface().(time.Time)if ok {today := time.Now()//判斷today是否在date之后 如果是 返回 true,否則返回 falseif today.After(date) {return false}}return true
}func main() {route := gin.Default()// Validator 是實(shí)現(xiàn) StructValidator 接口的 默認(rèn)驗(yàn)證器.// Engine 返回為StructValidator實(shí)現(xiàn)提供動(dòng)力 的 底層驗(yàn)證器引擎。// Validate 包含 驗(yàn)證器設(shè)置和緩存// if語句 表示 是否驗(yàn)證器設(shè)置成功if v, ok := binding.Validator.Engine().(*validator.Validate); ok {//注冊(cè)一個(gè)自定義驗(yàn)證方法 bookabledatev.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085")
}func getBookable(c *gin.Context) {var b Bookingif 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()})}
}
27 設(shè)置獲取 cookie
func main() {router := gin.Default()router.GET("/cookie", func(c *gin.Context) {// 獲取cookiecookie, err := c.Cookie("gin_cookie")//表示 獲取出錯(cuò)if err != nil {cookie = "NotSet"c.SetCookie("gin_cookie", "winnie", 3600, "/", "localhost", false, true)}fmt.Printf("Cookie value: %s \n", cookie)})router.Run()
}
第一次的 Cookie value: NotSet 原因:剛開始沒獲取到,所以cookie 值設(shè)為NotSet,并且重新設(shè)置了cookie,因?yàn)橹匦略O(shè)置后并沒有獲取cookie,所以 cookie的值仍然是NotSet。輸出為 Cookie value: NotSet
第二次 獲取到cookie了 所以打印成功。
28 路由參數(shù)
func main() {router := gin.Default()// 此 handler 將匹配 /user/john 但不會(huì)匹配 /user/ 或者 /userrouter.GET("/user/:name", func(c *gin.Context) {name := c.Param("name")c.String(http.StatusOK, "Hello %s", name)})// 此 handler 將匹配 /user/john/ 和 /user/john/send// 如果沒有其他路由匹配過/user/john,它將重定向到 /user/john/router.GET("/user/:name/*action", func(c *gin.Context) {name := c.Param("name")action := c.Param("action")message := name + " is " + actionc.String(http.StatusOK, message)})router.Run(":8080")
}
29 一個(gè)程序是否可以服務(wù)兩個(gè)端口
下面這個(gè)代碼是只能運(yùn)行80端口,不能運(yùn)行90端口。
func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(200, "我是8080")})router.Run(":8080") // data servicesrouterAdmin := gin.Default()routerAdmin.GET("/", func(c *gin.Context) {c.JSON(200, "我是8090")})routerAdmin.Run(":8090") // admin and monitor services
}
下面這個(gè)代碼能同時(shí)運(yùn)行80 90端口
func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(200, "我是80端口")})go router.Run(":8080") // 開啟一個(gè)攜程routerAdmin := gin.Default()routerAdmin.GET("/", func(c *gin.Context) {c.JSON(200, "我是90端口")})routerAdmin.Run(":8090") // admin and monitor services
}
gin文檔示例代碼
func main() {server01 := &http.Server{Addr: ":8080",Handler: router01(),ReadTimeout: 5 * time.Second,WriteTimeout: 10 * time.Second,}server02 := &http.Server{Addr: ":8081",Handler: router02(),ReadTimeout: 5 * time.Second,WriteTimeout: 10 * time.Second,}g.Go(func() error {return server01.ListenAndServe()})g.Go(func() error {return server02.ListenAndServe()})// Wait 等待阻塞,直到所有來自Go方法的函數(shù)調(diào)用都返回,然后從它們返回第一個(gè)非nil錯(cuò)誤(如果有的話)。if err := g.Wait(); err != nil {log.Fatal(err)}
}
30
31
32
33
34
35
36
37
38
39
總結(jié)
- 上一篇: C#与mongoDB初始环境搭建
- 下一篇: 一加Open渲染图曝光:短胖小尺寸机身+