使用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框架的其他好用功能还在不断探索之中

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