前提
由於業務需要,需要開發遊戲的管理後臺,由於之前未接觸過前端,只對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框架的其他好用功能還在不斷探索之中