使用GIN框架重構golang/go開發的無框架管理後臺

前提

由於業務需要,需要開發遊戲的管理後臺,由於之前未接觸過前端,只對GO有比較粗淺的瞭解,所以使用GO來開發後臺

被誤導

我在搜索的時候被誤導了一下,有知乎網友說,Golang就不用框架

的確,書上或者網上的DEMO都比較簡單,對於有編程基礎的人而言,的確很容易上手,但是對於長期做後端的人而言,其實並不會深刻感覺到有坑

開發中遇到的問題

  • 代碼結構不夠清晰,中期有段時間學了一個關於如何開發聊天室的程序,纔想起使用MVC架構;
  • 代碼重複,沒有GO思維,導致了許多重複代碼;
  • 邏輯混亂,前端參數處理,Redis/MySQL操作;
  • 許多原生API使用起來不是很方便;

思考改進

忙碌了一段時間,終於閒下來了,打算再次重構一下,現有的後臺

框架選擇

瞭解了一段時間之後,打算用beego框架,不過嘗試了一個比較小的項目測試DEMO之後,發現一些小問題,雖說beego封裝了好多好用的功能,但是對使用者而言,有時候,並非是好事,例如配置文件,日誌文件這一塊,如果要用beego的話,現有的變動比較大

在Golang交流中羣中請教了一下大家,大家的都建議使用GIN,而不是beego,於是乎選擇了GIN來重構,花費了不到兩天時間,使用GIN框架重構了我所有的代碼,總代碼量(Golang:2萬 JS:2萬)

使用GIN之後的總結

參數獲取更加方便

原先代碼

func Processer(w http.ResponseWriter, r *http.Request) {
    r.ParseForm() //坑死人,如果忘記寫了,死活也獲取不到name
    fmt.Println(r.Form.Get("name"))
}

使用GIN框架之後

func Processer(c *gin.Context) {
    fmt.Println(r.Query("name")) //GET name
    fmt.Println(r.DefaultQuery("name", "Roger")) //GET name 增加了默認參數
    fmt.Println(r.PostForm("name")) //POST name
    fmt.Println(r.DefaultPostForm("name", "luojie")) //POST name 增加了默認參數
}

雖然GIN框架對於GET和POST有區別,但是方便之處增加了默認參數,同時對於開發人員的要求就是要對GET和POST有清晰的認識

專門處理文件上傳的封裝

func uploadConfig(w http.ResponseWriter, r *http.Request) {
		file, handler, err := r.FormFile("uploadfile")
		if err != nil {
			log.Println("form file err: ", err)
			//fmt.Fprintf(w, "文件名一定要輸入")
			return
		}
		defer file.Close()
		fmt.Fprintf(w, "%v", handler.Header)
		//創建上傳的目的文件
		f, err := os.OpenFile("./files/"+handler.Filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
		if err != nil {
			log.Println("open file err: ", err)
			fmt.Fprintf(w, "文件打開失敗")
			return
		}

		defer f.Close()
		//拷貝文件
		io.Copy(f, file)
		//返回
}

GIN框架

    router.POST("/upload", func(c *gin.Context) {
        // 單文件
        file, _ := c.FormFile("file")
        log.Println(file.Filename)

        // 上傳文件到指定的路徑
        c.SaveUploadedFile(file, dst)
        //返回
    })

很明顯代碼簡單了很多,對於做文件上傳,不再感覺到有壓力,因爲GIN極大簡化了我們出錯的機率

路由分組

原來的代碼

type MyServeMux struct{}

func (mux *MyServeMux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
    pattern = "/v1" + pattern
    http.DefaultServeMux.HandleFunc(pattern, handler)
}

func main() {
    mux := &MyServeMux{}
    mux.HandleFunc("/login", loginEndpoint)
    mux.HandleFunc("/submit", submitEndpoint)
    mux.HandleFunc("/read", readEndpoint)
}

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

開發人員,所要關注的東西更少了

中間件用來對於權限的驗證

原來的代碼

func (mux *MyServeMux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
    pattern = "/v1" + pattern
    http.DefaultServeMux.HandleFunc(pattern, handler)
}

func (mux *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //驗證成功之後調用 http.DefaultServeMux.ServeHTTP(w, r)
    //驗證失敗 return
}

func main() {
    mux := &MyServeMux{}
    mux.HandleFunc("/login", loginEndpoint)
    mux.HandleFunc("/submit", submitEndpoint)
    mux.HandleFunc("/read", readEndpoint)
}

GIN框架

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

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

func checkAuth(c *gin.Context) {
    //驗證成功 調用c.Next()
    //驗證失敗 調用c.Abort()
}

方便之處和前一條一致

參數綁定,提高編碼效率

沒有參數綁定之前

func RechargeMakeUp(w http.ResponseWriter, r *http.Request) {
    ip := util.GetRequestIP(r)
    requestData := &model.WebRechargeMakeup{ActUserName: actUserName, ActIP: ip} 

    data, err := ioutil.ReadAll(r.Body)                                    
    if err != nil {
        logger.Logger.Errorf("request data error")
        util.SetErrorAjaxResponse(w, err)
        return
    } else {
        logger.Logger.Debugf("data[%s]", string(data))
        if err := json.Unmarshal(data, requestData); err != nil {
            logger.Logger.Errorf("json Unmarshal error")
            util.SetErrorAjaxResponse(w, err)
            return
        }
        //....
    }
}

可以綁定之後

func RechargeMakeUp(c *gin.Context) { 
    ip := model.GetRequestIP(c.Request)
    requestData := &model.WebRechargeMakeup{ActUserName: actUserName, ActIP: ip} 
    if err := c.ShouldBindJSON(requestData); err != nil {
        model.Logger.Errorf("json Unmarshal error")
        model.SetErrorAjaxResponse(c, err)
        return
    }
    //...
}

極大減少了代碼,提高了我們的工作效率

更加簡單的設置靜態文件

原來的靜態文件設計

func main() {
    http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("static"))))
}

GIN框架

func main() {
    r := gin.Default()
    r.Static(/static/, "./static")
}

講道理,前一個設計太反人類,我曾在這裏面,蛋疼了好久,才正常的將靜態目錄配置正常,而GIN框架裏面簡單明瞭

更加豐富的返回

原來的代碼

func process(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s",  "<h1>Hello World!</h1>") //返回網頁
    fmt.Fprintf(w, "{"message":"hello world"}") //返回JSON 結構體還需要手動定義
    fmt.Fprintf(w, "/static/login.data") //由瀏覽器來解析是不是文件
    //如果返回是網頁的話,還需要使用模板解析非常麻煩
}

GIN框架

func process(c *gin.Context) {
    c.HTML(http.StatusOK, "hello.html", nil) //返回網頁
    c.JSON(http.StatusOK, gin.H{"message":"Hello World!"}) //返回JSON
    c.String(http.StatusOK, "Hello World!") //返回字符串
    c.File("/static/login.data") //返回文件
    //... 還有其它
}

非常喜歡這麼豐富的返回,之前的痛點在於許多Ajax的返回都使用的是JS,每個返回我們必須手動轉化成字符串再返回,雖然可以直接寫一個通用返回將interface轉成字符串,但是欠缺了相應了思維,在不斷了解新框架的過程中,可以不斷完善自己的認知,這種感覺真棒

更好的模板支持

func formatAsDate(t time.Time) string {
    year, month, day := t.Date()
    return fmt.Sprintf("%d%02d/%02d", year, month, day)
}

func main() {
    r := gin.Default()
    r.Delims("{[{", "}]}") //可以自定義模板分隔符
    r.SetFuncMap(template.FuncMap{
        "formatAsDate": formatAsDate,
    }) //傳遞模板函數
	r.LoadHTMLGlob("./view/**/*") //直接讀取所有的模板文件,如果有語法錯誤,啓動就會報錯
}

其他好用的功能

目前對於GIN框架的其他好用功能還在不斷探索之中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章