原文地址?基本路由 · Go語言中文文檔
一、簡介
? ??Gin是一個golang的輕量級web框架,性能不錯,API友好。
? ? Gin支持Restful風格的API,可以直接從URL路徑上接收api參數或者URL參數,也可是使用json或者表單 數據綁定的方式接收參數。
? ? Gin響應前端的方式,可以使用的方式包括 json、結構體、protobuff的、XML、YAML。
? ? Gin可以使用路由分組的方式管理API,支持api路徑重定向。
? ? Gin的路由庫用的是 httprouter,路由節點的數據結構是壓縮字典樹,提高路由效率。
? ? Gin支持全局中間件和局部中間件,支持中間件分段邏輯的Next方法。
? ? Gin支持cookie和Session的方法去識別和驗證客戶端。
? ? Gin支持 Air 實時監聽代碼文件自動重新編譯執行。
二、gin路由
1.?基本路由
????gin 框架中采用的路由庫是基于 httprouter 做的,是第三方HTTP路由包,特點高性能、可擴展
(1) 使用方式:
? ? ?New()函數生成了一個 Router對象 。
? ? ?GET()方法 或 POST()方法 注冊一個適配路徑的響應函數??
? ? ?將 Router對象 作為參數傳給 ListenAndServe()函數 啟動HTTP服務。
router := httprouter.New()
router.GET("/", handleFunc)
http.ListenAndServe(":8080", router)
(2)?httprouter?為常用的HTTP方法提供快捷使用方式:
? ? ?獲取:? func (r *Router) GET(path string, handle Handle)
? ? ?添加:? func (r *Router) POST(path string, handle Handle)
? ? ?修改:? func (r *Router) PUT(path string, handle Handle)
? ? ?刪除:? func (r *Router) DELETE(path string, handle Handle)
(3)?httprouter?包中對URL使用兩種匹配模式:
? ? ?形如/user/name的精確匹配
? ? ?形如/user/*name的匹配所有的模式
(4)?可以處理二級域名。先 根據域名獲取對應的Handler路由,然后調用分發機制去處理。
func main() {......//分別處理不同的二級域名hs := make(HostMap)hs["sub1.localhost:8080"] = userRouterhs["sub2.localhost:8080"] = dataRouter//一級域名:localhost,二級域名:sub1.localhosthttp.ListenAndServe(":8080", hs)
}type HostMap map[string]http.Handlerfunc (hs HostMap) ServeHTTP(w http.ResponseWriter, r *http.Request) {//先根據域名獲取對應的Handler路由,然后調用處理if handler := hs[r.Host]; handler != nil {handler.ServeHTTP(w, r)}
}
2.?Restful風格的API
? ??gin支持Restful風格的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
3.?API參數
? ? Api參數就是在Api地址上輸入的參數。
? ? 可以通過Context的Param方法來獲取API參數
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)})r.Run(":8000") //監聽8080端口
}
?在瀏覽器輸入:? ?http://localhost:8080/user/枯藤/doview,獲得name參數"枯藤",action參數 "doview"
4.?URL參數
? ?URL參數就是在用Get方式,在?后面輸入的參數。
? ?可以通過DefaultQuery()或Query()方法獲取URL參數
r.GET("/user", func(c *gin.Context) {name := c.DefaultQuery("name", "枯藤") //name是參數名稱,枯藤是默認值c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})
?在瀏覽器輸入:? ?http://localhost:8080/user?name=烏鴉,獲得name參數"烏鴉"
5.?表單參數
(1) 表單傳輸為 post請求。http常見的傳輸格式:
? ? application/json
? ? application/x-www-form-urlencoded (默認)
? ? multipart/form-data
(2)?表單參數可以通過 PostForm() 方法獲取
r.POST("/form", func(c *gin.Context) {username := c.PostForm("username")password := c.PostForm("userpassword")c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s", username, password))
})
6.?上傳單個文件
(1) multipart/form-data格式用于文件上傳
(2) 通過 FormFile() 方法獲得文件對象?
r.POST("/upload", func(c *gin.Context) {file, err := c.FormFile("file")c.String(http.StatusOK, file.Filename)})
7.?上傳多個文件
(1) 上傳多個文件也是用 multipart/form-data格式
(2)?通過 MultipartForm() 方法獲得文件對象列表
r.POST("/upload", func(c *gin.Context) {form, err := c.MultipartForm()files := form.File["files"] // 獲取所有圖片for _, file := range files { // 遍歷所有圖片c.SaveUploadedFile(file, file.Filename)}
})
8.?routes group
??routes group(路由分組) 可以分組管理相同的URL。?
? ?如下代碼,v1和v2分別是兩個路由組。
func main() {r := gin.Default() //創建默認路由v1 := r.Group("/v1") //路由組1{ // {} 是書寫規范v1.GET("/login", login)v1.GET("submit", submit)}v2 := r.Group("/v2") //路由組2{v2.POST("/login", login)v2.POST("/submit", submit)}r.Run(":8000")
}
9.?路由原理
??httprouter使用的數據結構是壓縮字典樹。
??普通的字典樹:每個字母都建立一個路由節點
??壓縮字典樹:只對每個有效前綴建立路由節點。如下圖所示:
三、gin數據解析和綁定
? ? ?支持三種方式的數據解析,json、表單 和 url參數
1.?Json 數據解析和綁定
? ? ?使用 context 的?ShouldBindJSON 方法
type Login struct {User string `json:"user" binding:"required"`Pssword string `json:"password" binding:"required"`
}func main() {r := gin.Default()r.POST("loginJSON", func(c *gin.Context) {// 聲明接收的變量var json Login// 將request的body中的數據,自動按照json格式解析到結構體if err := c.ShouldBindJSON(&json); err != nil {......}......})......
}
2.?表單數據解析和綁定
? ?使用 context 的 Bind?方法
type Login struct {User string `form:"username" binding:"required"`Pssword string `form:"password" binding:"required"`
}func main() {r := gin.Default()r.POST("/loginForm", func(c *gin.Context) {var form Login// Bind()默認解析并綁定form格式// 根據請求頭中content-type自動推斷if err := c.Bind(&form); err != nil {......}......})......
}
3.?URI數據解析和綁定
? ?使用 context 的?ShouldBindUri 方法
type Login struct {User string `uri:"user" xml:"user" binding:"required"`Pssword string `uri:"password" xml:"password" binding:"required"`
}func main() {r := gin.Default()r.GET("/:user/:password", func(c *gin.Context) {if err := c.ShouldBindUri(&login); err != nil {.......}.......}).......
}
四、gin 渲染
1.?各種數據格式的響應
(1) json 響應
c.JSON(200, gin.H{"message": "someJSON", "status": 200})
(2) 結構體 響應
var msg struct {Name stringMessage string
}
msg.Name = "root"
msg.Message = "message"
c.JSON(200, msg)
(3) XML,YAML 響應
c.XML(200, gin.H{"message": "abc"})
c.YAML(200, gin.H{"name": "zhangsan"})
(4)?protobuf?響應
data := &protoexample.Test{Label: &label,Reps: reps,
}
c.ProtoBuf(200, data)
2.?HTML模板渲染
? ?gin支持加載 HTML模板, 然后根據模板參數進行配置并返回相應的數據。
? ?本質上就是字符串替換。
func main() {r := gin.Default()r.LoadHTMLGlob("tem/*") //加載模板文件r.GET("/index", func(c *gin.Context) {//index.html就是具體的模板文件c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是測試", "ce": "123456"})})r.Run()
}
3. 重定向
? 可以使用 context 的??Redirect 方法進行重定向
func main() {r := gin.Default()r.GET("/redirect_me", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")})r.Run()
}
??當客戶端請求 /redirect_me 時,服務器將重定向到 https://www.5lmh.com
4.?同步異步
??goroutine機制可以實現異步處理,但不應該使用原始上下文,必須使用它的只讀副本。
func main() {r := gin.Default()r.GET("/long_async", func(c *gin.Context) { copyContext := c.Copy() // 需要拷貝一個副本go func() { // 異步處理time.Sleep(3 * time.Second)log.Println("異步執行:" + copyContext.Request.URL.Path)}()})r.Run(":8000")
}
五、gin中間件
1.?全局中間件
? ??所有請求都經過此中間件。
? ? 實現代碼如下:
func MiddleWare() gin.HandlerFunc {......
}func main() {r := gin.Default()r.Use(MiddleWare())r.Run()
}
2.?Next()方法
? ? 當程序執行到context.Next時,會轉而先去執行具體的業務邏輯。
? ? 執行完業務邏輯處理函數之后,程序會再次回到context.Next處,繼續執行中間件后續的代碼。
func MiddleWare() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("中間件開始執行了")c.Next() //轉去執行業務邏輯//業務邏輯執行完,返回此處,繼續執行中間件代碼.......fmt.Println("中間件執行完畢", status)}
}func main() {r := gin.Default()r.Use(MiddleWare()) // 注冊中間件r.GET("/ce", func(c *gin.Context) {......})r.Run()
}
? 解釋原文?Golang gin 中間件、context.Next函數_gin context.next-CSDN博客
3.?局部中間件
? ? 中間件只在具體某個路由生效,不使用 r.use() 定義的中間件。
func main() {r := gin.Default()//只在 /ce 的路由里使用局部中間件 PartMiddleWare()r.GET("/ce", PartMiddleWare(), func(c *gin.Context) { ......})r.Run()
}
??解釋原文?局部生效的中間件_如何使用局部中間件-CSDN博客
六、會話控制
?1. Cookie介紹
(1)?HTTP是無狀態協議,服務器不能記錄瀏覽器的訪問狀態,不能區分兩次請求是否由同一個客戶端發出。
(2)?Cookie 就是一段可以區分不同客戶端的信息,由服務器創建,發送給瀏覽器,瀏覽器保存。
(3)?瀏覽器向服務器發送請求時,都會同時將Cookie發送給服務器,服務器根據處理請求。
2.?Cookie的使用
? ?登錄時設置cookie,其他請求檢驗cookie,代碼如下:
func AuthMiddleWare() gin.HandlerFunc {return func(c *gin.Context) {//2.獲取cookie并校驗if cookie, err := c.Cookie("abc"); err == nil {if cookie == "123" {c.Next() //3.cookie檢驗通過,執行請求邏輯return}}......return}
}func main() {r := gin.Default()r.GET("/login", func(c *gin.Context) {c.SetCookie("abc", "123", 60, "/", "localhost", false, true) //1.登錄成功,設置cookiec.String(200, "Login success!")})......
}
3.?Cookie的缺點
(1)?不安全,明文,容易泄露信息
(2) 可以被瀏覽器禁用。
(3) 有數量限制。某些瀏覽器對某個域名的cookie有數量限制,大概50個。
4.?Sessions
(1) session和cookie 實現的目標是一致的,但實現的方法不同;
(2) session 是另一種記錄客戶狀態的機制,?不同的是 Cookie 保存在客戶端瀏覽器中,而 session保存在服務器
(3)?瀏覽器第一次訪問服務器時,服務器端會創建一個 session 對象,生成一個類似于 key,value 的鍵值對, 然后將 value 保存到服務器,將 key(cookie)返回到瀏覽器(客戶)端。
? ? ? 瀏覽器下次訪問時會攜帶 key(cookie),找到對應的 session(value)。
(4) sessions包地址:?github.com/gorilla/sessions
七、參數驗證
1.?結構體驗證
? ?用gin框架的數據驗證,可以不用解析數據,減少if else,會簡潔許多。
? ?比如,在定義結構體時,添加如下?binding:"required,gt=10",表示不能為空并且大于10
type Person struct {//不能為空并且大于10Age int `form:"age" binding:"required,gt=10"`
}
?2.?自定義驗證
? ? 可以自定義驗證函數,并注冊到 validator中。
? ? 包地址:gopkg.in/go-playground/validator.v9/translations
type Person struct {//1.在binding上使用自定義的 參數名稱 stringCheckNameName string `form:"name" binding:"stringCheckName"`
}
//2.自定義的校驗方法
func checkStringValid(v *validator.Validate, ......) bool {if value, ok := field.Interface().(string); ok {return value != "" && !("5lmh" == value) //字段不能為空,并且不等于5lmh}return true
}
func main() {r := gin.Default()// 3、將我們自定義的校驗方法 和 參數名稱 注冊到 validator中if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("stringCheckName", checkStringValid)}
}
3.?多語言翻譯驗證
? ?如需要自定義返回信息的語言,比如部分用戶返回中文,部分返回英文。
? ?可以使用??validator.v9/translations
import (ut "github.com/go-playground/universal-translator"en_translations "gopkg.in/go-playground/validator.v9/translations/en"
)
func startPage(c *gin.Context) {locale := c.DefaultQuery("locale", "zh") //1.獲得前端使用語言//2.獲得翻譯器en := en.New()zh := zh.New()Uni = ut.New(en, zh)trans, _ := Uni.GetTranslator(locale) //3.注冊翻譯器Validate = validator.New()en_translations.RegisterDefaultTranslations(Validate, trans) //4.解析結構體,獲得翻譯過的內容user := User{}c.ShouldBind(&user)err := Validate.Struct(user)
}
八、其他
1. 日志
? 使用 io.MultiWriter ?和?gin.DefaultWriter
import ("io""os"
)
func main() {f, _ := os.Create("gin.log")gin.DefaultWriter = io.MultiWriter(f) //將日志寫入文件//gin.DefaultWriter = io.MultiWriter(f, os.Stdout) //將日志同時寫入文件和控制臺
}
2. Air 實時加載
? ??在開發調試的時候,變更代碼之后需要按下Ctrl+C停止程序并重新編譯再執行,不是很方便。
? ??Air能夠實時監聽項目的代碼文件,在代碼發生變更之后自動重新編譯并執行,提高開發效率。
3.?gin驗證碼
? ?為了防止接口被惡意頻繁調用,可以采用加驗證碼的方式。
? ?庫地址:github.com/dchest/captcha