golang輕量級(jí)框架-Gin入門

本來自己打算繼續(xù)學(xué)下beanFactory源碼的,但是放假了自己也沒什么精神,看源碼又要求注意力很集中,所以想著看點(diǎn)簡(jiǎn)單點(diǎn)的內(nèi)容吧,然后就想到了golang的另一個(gè)框架-Gin。假期過后可能就要開啟加班生活了,不是很開心,昨天收到老大郵件,我原來項(xiàng)目組基本上解散了,人員分到了不同項(xiàng)目組,而我到了ebay項(xiàng)目組去做微服務(wù)(如果不用加班我還是期待的),自己浪了一個(gè)月也該收收心了。還是回歸正題,gin框架和前面學(xué)習(xí)的beego框架都是比較流行的框架,但是beego比較傳統(tǒng),模塊多功能全,而gin可以看作是一個(gè)單獨(dú)模塊的框架,官方介紹說的是:Gin 是一個(gè) Go (Golang) 語言框架。 它是一個(gè)擁有更好性能的 martini-like API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 那么你會(huì)愛上 Gin。自己感覺gin更像是beego中的controller,主要針對(duì)用戶的request和response。gin官網(wǎng),個(gè)人感覺文檔稍顯粗糙,不過勝在支持中文,還是很良心的。

一、安裝和開始

要想使用gin必須要下載和安裝它,切換到自己的工作空間,執(zhí)行g(shù)o命令

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

但是因?yàn)榫W(wǎng)絡(luò)問題可能會(huì)失敗,實(shí)在不行就直接通過github下載也可以。
安裝好之后就可以直接使用了,打開ide創(chuàng)建一個(gè)新的項(xiàng)目helloGin,創(chuàng)建main.go

func main()  {
    // Engin
    router := gin.Default()
    //router := gin.New()

    router.GET("/hello", func(context *gin.Context) {
        log.Println(">>>> hello gin start <<<<")
        context.JSON(200,gin.H{
            "code":200,
            "success":true,
        })
    })
    // 指定地址和端口號(hào)
    router.Run("localhost:9090")

在main函數(shù)里面首先通過調(diào)用gin.Default()函數(shù)返回的是一個(gè)Engin指針,Engin代表的是整個(gè)框架的一個(gè)實(shí)例,它包含了多路復(fù)用、中間件和配置的設(shè)置,其實(shí)就是封裝了我們需要的內(nèi)容。一般創(chuàng)建Engin都是使用Default()或者New(),當(dāng)然Default()本身內(nèi)部也是調(diào)用的New()函數(shù)。
接著調(diào)用Engin的GET方法,這個(gè)方法兩個(gè)參數(shù),一個(gè)是相對(duì)路徑,一個(gè)是多個(gè)handler,即針對(duì)用戶一個(gè)請(qǐng)求地址,我可以指定多個(gè)handler來處理用戶請(qǐng)求。但是一般情況下我們都是一個(gè)handler處理一個(gè)請(qǐng)求。上面的代碼里使用了一個(gè)匿名函數(shù)處理"/hello"請(qǐng)求。然后以JSON格式的數(shù)據(jù)響應(yīng)用戶請(qǐng)求,這個(gè)方法有兩個(gè)參數(shù),第一個(gè)是狀態(tài),第二個(gè)是結(jié)果。我這里直接指定200,表示成功,或者也可以用http包的常量值http.StatusOK;gin.H其實(shí)是一個(gè)map的數(shù)據(jù)結(jié)構(gòu),然后將其轉(zhuǎn)成json格式輸出。
最后是router.Run("localhost:9090"),這個(gè)方法是指定服務(wù)的主機(jī)和端口號(hào),不過一般直接指定端口號(hào)就行了。
下面啟動(dòng)項(xiàng)目,并訪問"localhost:9090/hello",訪問結(jié)果如下圖所示:


圖-1.png

二、創(chuàng)建demo

接下來創(chuàng)建項(xiàng)目來學(xué)習(xí)gin的使用,主要就是controller的使用,即將用戶請(qǐng)求和handler進(jìn)行映射,然后獲取不同方式請(qǐng)求參數(shù)。構(gòu)建項(xiàng)目結(jié)構(gòu)如下所示


圖-2.png

config主要是配置相關(guān)的文件;controller包主要放handler;database包數(shù)據(jù)庫(kù)相關(guān)代碼,因?yàn)槲疫@里沒有用ORM框架,所以只是數(shù)據(jù)庫(kù)連接的代碼;main包下只有main.go一個(gè)文件;model就是數(shù)據(jù)模型,即自己定義的一些結(jié)構(gòu)體;static下放置的是靜態(tài)文件;template包下是html頁面。


剛才上面處理"hello"請(qǐng)求使用的是一個(gè)匿名函數(shù),下面為非匿名函數(shù)來處理,代碼修改成下面:

func main()  {
    // Engin
    router := gin.Default()
    router.GET("/hello", hello) // hello函數(shù)處理"/hello"請(qǐng)求
    // 指定地址和端口號(hào)
    router.Run(":9090")
}
func hello(context *gin.Context) {
    println(">>>> hello function start <<<<")
    
    context.JSON(http.StatusOK,gin.H{
        "code":200,
        "success":true,
    })
}

這樣好了一點(diǎn)點(diǎn),但是想想spring controller,一般會(huì)在類上加上一個(gè)@requestMapping注解,然后方法上也會(huì)加上一個(gè)@requestMapping注解,之所以在類上加@requestMapping主要是這個(gè)controller處理的是同一類型問題,比如和用戶相關(guān)的controller,請(qǐng)求路徑都是/user/....,同樣gin也支持,這就是路由組,我們看下官方文檔的示例:

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

    // Simple group: v1
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    // Simple group: v2
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}

根據(jù)這個(gè)事例,將代碼重新構(gòu)建,這里構(gòu)建兩個(gè)路由組。并且在controller包下新建了UserController和FileController文件,分別處理不同路由組請(qǐng)求,分別作一些不同的操作,另外將每個(gè)路由對(duì)應(yīng)的函數(shù)按照路由組進(jìn)行劃分,另外有兩個(gè)靜態(tài)的html頁面,做form表單提交的操作。gin提供了兩個(gè)方法用戶加載靜態(tài)html,即LoadHTMLGlob()或LoadHTMLFiles(),第一個(gè)方法制定一個(gè)通配符路徑即可,而后面的方法則是需要指定所有需要加載的html文件名稱。修改后代碼如下:

func main()  {
    // Engin
    //router := gin.Default()
    router := gin.New()
        // 加載html文件,即template包下所有文件
    router.LoadHTMLGlob("template/*")
    router.GET("/hello", hello)
    // 路由組
    user := router.Group("/user")
    {   // 請(qǐng)求參數(shù)在請(qǐng)求路徑上
        user.GET("/get/:id/:username",controller.QueryById)
        user.GET("/query",controller.QueryParam)
        user.POST("/insert",controller.InsertNewUser)
        user.GET("/form",controller.RenderForm)// 跳轉(zhuǎn)html頁面
        user.POST("/form/post",controller.PostForm)
        //可以自己添加其他,一個(gè)請(qǐng)求的路徑對(duì)應(yīng)一個(gè)函數(shù)

        // ...
    }

    file := router.Group("/file")
    {
        // 跳轉(zhuǎn)上傳文件頁面
        file.GET("/view",controller.RenderView) // 跳轉(zhuǎn)html頁面
        // 根據(jù)表單上傳
        file.POST("/insert",controller.FormUpload)
        file.POST("/multiUpload",controller.MultiUpload)
        // base64上傳
        file.POST("/upload",controller.Base64Upload)
    }

    // 指定地址和端口號(hào)
    router.Run(":9090")
}

關(guān)于獲取用戶請(qǐng)求參數(shù)我還是寫了幾種情況,一是傳統(tǒng)的URL查詢參數(shù),例如:localhost:9090/user/query?id=2&name=hello;另外一種就是URL路徑參數(shù),例如localhost:9090/user/2/hello(也是id=2,name=hello)。上面這兩種是get請(qǐng)求,post請(qǐng)求我也寫了兩種形式,一種就是傳統(tǒng)的form表單提交,另外就是json格式參數(shù)提交,等下通過代碼看下。
下面是UserController的代碼內(nèi)容:

func init()  {
    log.Println(">>>> get database connection start <<<<")
    db = database.GetDataBase()
}

// localhost:9090/user/query?id=2&name=hello
func QueryParam(context *gin.Context) {
    println(">>>> query user by url params action start <<<<")
    id := context.Query("id")
    name := context.Request.URL.Query().Get("name")
    var u model.User
    context.Bind(&u)
    println(u.Username)
    rows := db.QueryRow("select username,address,age,mobile,sex from t_user where id = $1 and username = $2",id,name)
    var user model.User
    err := rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)
    checkError(err)

    checkError(err)
    context.JSON(200,gin.H{
        "result":user,
    })

}
// localhost:9090/user/get/2/hello
func QueryById (context *gin.Context) {
    println(">>>> get user by id and name action start <<<<")

    // 獲取請(qǐng)求參數(shù)
    id := context.Param("id")
    name := context.Param("username")

    // 查詢數(shù)據(jù)庫(kù)
    rows := db.QueryRow("select username,address,age,mobile,sex from t_user where id = $1 and username = $2",id,name)

    var user model.User
    err := rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)
    checkError(err)

    context.JSON(200,gin.H{
        "result":user,
    })
}

// json格式數(shù)據(jù)
func InsertNewUser (context *gin.Context) {
    println(">>>> insert controller action start <<<<")
    var user model.User

    // 使用ioutile讀取二進(jìn)制數(shù)據(jù)
    //bytes,err := ioutil.ReadAll(context.Request.Body)
    //if err != nil {
    //  log.Fatal(err)
    //}
    //err = json.Unmarshal(bytes,&user)

    // 直接將結(jié)構(gòu)體和提交的json參數(shù)作綁定
    err := context.ShouldBindJSON(&user)

    // 寫入數(shù)據(jù)庫(kù)
    res,err := db.Exec("insert into t_user (username,sex,address,mobile,age) values ($1,$2,$3,$4,$5)",
        &user.Username,&user.Sex,&user.Address,&user.Mobile,&user.Age)
    var count int64
    count,err = res.RowsAffected()
    checkError(err)
    if count != 1 {
        context.JSON(200,gin.H{
            "success":false,
        })
    } else {
        context.JSON(200,gin.H{
            "success":true,
        })
    }

}

// form表單提交
func PostForm(context *gin.Context) {
    println(">>>> bind form post params action start <<<<")
    var u model.User
    
    // 綁定參數(shù)到結(jié)構(gòu)體
    context.Bind(&u)
    res,err := db.Exec("insert into t_user (username,sex,address,mobile,age) values ($1,$2,$3,$4,$5)",
        &u.Username,&u.Sex,&u.Address,&u.Mobile,&u.Age)
    var count int64
    count,err = res.RowsAffected()
    checkError(err)

    if count != 1 {
        context.JSON(200,gin.H{
            "success":false,
        })
    } else {
        //context.JSON(200,gin.H{
        //  "success":true,
        //})

        // 重定向
        context.Redirect(http.StatusMovedPermanently,"/file/view")
    }

}

// 跳轉(zhuǎn)html
func RenderForm(context *gin.Context) {
    println(">>>> render to html action start <<<<")

    context.Header("Content-Type", "text/html; charset=utf-8")
    context.HTML(200,"insertUser.html",gin.H{})
}

func checkError(e error) {
    if e != nil {
        log.Fatal(e)
    }
}

UserController里面定義一個(gè)init方法,主要獲取數(shù)據(jù)庫(kù)連接,一邊后面的函數(shù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。
在QueryParam函數(shù)中,獲取URL查詢參數(shù)其實(shí)用多種方法,一種直接使用context.Query("參數(shù)名稱"),另外就是context.Request.URL.Query().Get("參數(shù)名稱"),但是明顯第二個(gè)更麻煩一點(diǎn)。此外還有一種就是將參數(shù)綁定到結(jié)構(gòu)體,context.Bind()或者context.ShouldBind()或者ShouldBindQuery(),然后對(duì)結(jié)構(gòu)體進(jìn)行操作就行了,需要注意一點(diǎn)就是ShouldBindQuery()只能綁定GET請(qǐng)求的查詢參數(shù),POST請(qǐng)求不行。其實(shí)使用哪種方式還是看個(gè)人習(xí)慣,參數(shù)少的話感覺第一種更直觀一些。
QueryById函數(shù)獲取的是URL路徑參數(shù),和QueryParam獲取方法不同,可以通過context.Param("參數(shù)名稱")獲取,后來看gin文檔,發(fā)現(xiàn)也提供了一種參數(shù)綁定的方法,即context.ShouldBindUri(),這個(gè)方法也會(huì)把結(jié)構(gòu)體和URL路徑參數(shù)做一個(gè)綁定。
InsertNewUser函數(shù),獲取的是提交的JSON格式參數(shù),使用rest client可以模擬,獲取參數(shù)也不止一種,可以使用比較基礎(chǔ)的方法獲取,即使用ioutil.ReadAll(context.Request.Body),讀取字節(jié)流,然后使用go內(nèi)置的json庫(kù)將數(shù)據(jù)綁定到結(jié)構(gòu)體。最簡(jiǎn)單方法就是調(diào)用ShouldBindJSON(),將用戶提交的JSON參數(shù)綁定結(jié)構(gòu)體。
PostForm函數(shù)就是一個(gè)傳統(tǒng)的form表單提交,使用context.Bind()或者context.ShouldBind()就好了。
關(guān)于Bind和ShouldBind,其實(shí)這兩個(gè)方法基本上都是一樣的,根據(jù)具體的請(qǐng)求頭選擇不同綁定引擎去處理,比如用戶請(qǐng)求的Content-Type為"application/json",那么就由JSON的綁定引擎處理,如果為為"application/xml",就由XML綁定引擎處理。這兩個(gè)方法的差別在于ShouldBind方法不會(huì)將response狀態(tài)值設(shè)為400,當(dāng)請(qǐng)求的json參數(shù)無效的時(shí)候,即請(qǐng)求參數(shù)無法綁定到結(jié)構(gòu)體。
RenderForm函數(shù)主要是跳轉(zhuǎn)到html頁面,當(dāng)時(shí)這里遇到一個(gè)問題,就是context.HTML方法,指定具體html頁面,因?yàn)閙ain函數(shù)使用時(shí)是router.LoadHTMLGlob("template/*"),我覺得可以理解指定了具體html的前綴,所以跳轉(zhuǎn)時(shí)只需要html的相對(duì)template的路徑即可。


FileController主要是處理文件上傳,其實(shí)也沒什么特別內(nèi)容,無非就是單個(gè)上傳還是多個(gè)上傳的問題,另外就是使用base64上傳圖片。代碼如下:

const BASE_NAME = "./static/file/"

func RenderView (context *gin.Context) {
    println(">>>> render to file upload view action start <<<<")
    context.Header("Content-Type", "text/html; charset=utf-8")

    context.HTML(200,"fileUpload.html",gin.H{})
}
// 單個(gè)文件上傳
func FormUpload (context *gin.Context) {
    println(">>>> upload file by form action start <<<<")

    fh,err := context.FormFile("file")
    checkError(err)
    //context.SaveUploadedFile(fh,BASE_NAME + fh.Filename)

    file,err := fh.Open()
    defer file.Close()
    bytes,e := ioutil.ReadAll(file)
    e = ioutil.WriteFile(BASE_NAME + fh.Filename,bytes,0666)
    checkError(e)

    if e != nil {
        context.JSON(200,gin.H{
            "success":false,
        })
    } else {
        context.JSON(200,gin.H{
            "success":true,
        })
    }
}
// 多個(gè)文件上傳
func MultiUpload(context *gin.Context) {
    println(">>>> upload file by form action start <<<<")
    form,err := context.MultipartForm()
    checkError(err)
    files := form.File["file"]

    var er error
    for _,f := range files {

        // 使用gin自帶保存文件方法
        er = context.SaveUploadedFile(f,BASE_NAME + f.Filename)
        checkError(err)
    }
    if er != nil {
        context.JSON(200,gin.H{
            "success":false,
        })
    } else {
        context.JSON(200,gin.H{
            "success":true,
        })
    }

}

func Base64Upload (context *gin.Context) {
    println(">>>> upload file by base64 string action start <<<<")

    bytes,err := ioutil.ReadAll(context.Request.Body)
    if err != nil {
        log.Fatal(err)
    }

    strs := strings.Split(string(bytes),",")
    head := strs[0]
    body := strs[1]
    println(head + " | " + body)
    start := strings.LastIndex(head,"/")
    end := strings.LastIndex(head,";")
    tp := head[start + 1:end]

    err = ioutil.WriteFile(BASE_NAME + strconv.Itoa(time.Now().Nanosecond()) + "." + tp,[]byte(body),0666)
    checkError(err)
    //bys,err := base64.StdEncoding.DecodeString(string(bytes))
    //err = ioutil.WriteFile("./static/file/" + strconv.Itoa(time.Now().Nanosecond()),bys,0666)
    if err != nil {
        context.JSON(200,gin.H{
            "success":false,
        })
    } else {
        context.JSON(200,gin.H{
            "success":true,
        })
    }
}

FormUpload函數(shù)處理單個(gè)文件上傳,先從context.FormFile("file")獲取文件,獲取到的是一個(gè)FileHeader指針,F(xiàn)ileHeader封裝了文件內(nèi)容、名稱、類型、大小等信息,結(jié)構(gòu)如下:

type FileHeader struct {
    Filename string
    Header   textproto.MIMEHeader
    Size     int64

    content []byte
    tmpfile string
}

保存文件可以直接使用SaveUploadedFile方法,也可以使用ioutil相關(guān)方法進(jìn)行保存。
MultiUpload多文件上傳,先通過context.MultipartForm()獲取Form對(duì)象,然后根據(jù)參數(shù)名獲取到多個(gè)FileHeader指針,接下去保存文件和單個(gè)上傳是一樣的。
Base64Upload函數(shù)本來是想通過使用base64上傳圖片,函數(shù)內(nèi)先獲取整個(gè)字符串,然后分割成head和body,然后判斷圖片類型,最后使用ioutil.WriteFile保存文件,但是實(shí)際操作好像出了點(diǎn)問題,文件保存到本地打開顯示內(nèi)容丟失,不知道是怎么回事。

三、總結(jié)

當(dāng)然gin內(nèi)容不止這些,還有一些中間件的內(nèi)容也是值得一看的,比如BasicAuth、Logger等等,但是總感覺gin似乎太輕了一點(diǎn),基本上就是一個(gè)MVC框架,還是要結(jié)合其他框架使用。beego感覺更好一些,但是MVC這部分好像gin更強(qiáng)大點(diǎn),總之都很優(yōu)秀吧,畢竟GitHub上star那么多。今天的學(xué)習(xí)就到這里了,本次學(xué)習(xí)的代碼已經(jīng)上傳到我的GitHub,我已經(jīng)很久沒有提交過代碼了.......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)發(fā)自:http://shanshanpt.github.io/2016/05/03/go-gin.html gi...
    dncmn閱讀 6,213評(píng)論 0 1
  • 所謂框架 框架一直是敏捷開發(fā)中的利器,能讓開發(fā)者很快的上手并做出應(yīng)用,甚至有的時(shí)候,脫離了框架,一些開發(fā)者都不會(huì)寫...
    人世間閱讀 217,096評(píng)論 11 242
  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,874評(píng)論 0 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,674評(píng)論 1 32
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,827評(píng)論 1 45

友情鏈接更多精彩內(nèi)容