終于可以寫點(diǎn)現(xiàn)在的東西了。




寫了兩年P(guān)HP,想換換口味,目前技術(shù)棧在向Go傾斜,看了Gin和Beego,總是感覺好像差那么一丟丟...




所以再次開始造輪子之路.....




想寫一個可以方便開" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運(yùn)營 > GO制作Web框架——Bingo!

GO制作Web框架——Bingo!

時間:2023-07-05 17:15:01 | 來源:網(wǎng)站運(yùn)營

時間:2023-07-05 17:15:01 來源:網(wǎng)站運(yùn)營

GO制作Web框架——Bingo!:

前言




終于可以寫點(diǎn)現(xiàn)在的東西了。




寫了兩年P(guān)HP,想換換口味,目前技術(shù)棧在向Go傾斜,看了Gin和Beego,總是感覺好像差那么一丟丟...




所以再次開始造輪子之路.....




想寫一個可以方便開發(fā)的框架,語法就像Laravel靠攏,比較方便開發(fā)者從PHP遷移到Go




開始..




Bingo

bingo是一個基于go語言的輕量級API框架,專注構(gòu)建restfulAPI

GitHub地址:silsuer/bingo

目錄結(jié)構(gòu)




- app 放置與網(wǎng)站相關(guān)代碼

- core 放置框架核心代碼

- vendor 放置第三方庫,使用glide管理第三方庫

- public 放置html代碼

開發(fā)過程

go的net包極其好用,用它開發(fā)框架也是極其快速(居然比寫php框架還要快...)

首先確定main函數(shù),在main函數(shù)中實(shí)例化一個結(jié)構(gòu)體,然后調(diào)用其中的Run函數(shù)即可

動手操作:

func main() { // new 一個bingo對象,然后bingo.Run()即可 // 指定靜態(tài)目錄,默認(rèn)指向index.html ,加載路由文件 // 加載env文件 bingo := new(core.Bingo) bingo.Run(":12345") }接下來去寫bingo文件:

func (b *Bingo) Run(port string) { // 傳入一個端口號,沒有返回值,根據(jù)端口號開啟http監(jiān)聽 // 此處要進(jìn)行資源初始化,加載所有路由、配置文件等等 // 實(shí)例化router類,這個類去獲取所有router目錄下的json文件,然后根據(jù)json中的配置,加載數(shù)據(jù) // 實(shí)例化env文件和config文件夾下的所有數(shù)據(jù),根據(jù)配置 // 根據(jù)路由列表,開始定義路由,并且根據(jù)端口號,開啟http服務(wù)器 http.ListenAndServe(port, bin) // TODO 監(jiān)聽平滑升級和重啟 }Run函數(shù)非常簡單,只有一行代碼,就是開啟一個Http服務(wù)器并監(jiān)聽傳入的端口,

由于我們要自己控制各種路由,所以我們不能用net包中自帶的http服務(wù),網(wǎng)上有很多原理說的很清楚了

我們需要自己實(shí)現(xiàn) ServeHTTP方法,以實(shí)現(xiàn)Mux這種路由器接口,所以再寫一個ServeHttp方法

func (b *Bingo) ServeHTTP(w http.ResponseWriter, r *http.Request) { flag := false // 這個變量用來標(biāo)記是否找到了動態(tài)路由 // 每一個http請求都會走到這里,然后在這里,根據(jù)請求的URL,為其分配所需要調(diào)用的方法 params := []reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)} for _, v := range RoutesList { // 檢測中間件,根據(jù)中間件首先開啟中間件,然后再注冊其他路由 // 檢測路由,根據(jù)路由指向需要的數(shù)據(jù) if r.URL.Path == v.path && r.Method == v.method { flag = true // 尋找到了對應(yīng)路由,無需使用靜態(tài)服務(wù)器 //TODO 調(diào)用一個公共中間件,在這個中間件中尋找路由以及調(diào)用中間件收尾等功能 // 檢測該路由中是否存在中間件,如果存在,順序調(diào)用 for _, m := range v.middleware { if mid, ok := MiddlewareMap[m]; ok { // 判斷是否注冊了這個中間件 rmid := reflect.ValueOf(mid) params = rmid.MethodByName("Handle").Call(params) // 執(zhí)行中間件,返回values數(shù)組 // 判斷中間件執(zhí)行結(jié)果,是否還要繼續(xù)往下走 str := rmid.Elem().FieldByName("ResString").String() if str != "" { status := rmid.Elem().FieldByName("Status").Int() // 字符串不空,查看狀態(tài)碼,默認(rèn)返回500錯誤 if status == 0 { status = 500 } w.WriteHeader(int(status)) fmt.Fprint(w,str) return } } } // 檢測成功,開始調(diào)用方法 // 獲取一個控制器包下的結(jié)構(gòu)體 if d, ok := ControllerMap[v.controller]; ok { // 存在 c為結(jié)構(gòu)體,調(diào)用c上掛載的方法 reflect.ValueOf(d).MethodByName(v.function).Call(params) } // 停止向后執(zhí)行 return } } // 如果路由列表中還是沒有的話,去靜態(tài)服務(wù)器中尋找 if !flag { // 去靜態(tài)目錄中尋找 http.ServeFile(w,r,GetPublicPath()+ r.URL.Path) } return }可以看到,我們使用重新定義了ServeHttp方法,在這個方法中,我們根據(jù)瀏覽器訪問的不同URL,通過反射得到不同的控制器或者中間件的結(jié)構(gòu)體,并且

調(diào)用對應(yīng)的方法,如果訪問的URL我們沒有定義的話,會到靜態(tài)文件夾下去尋找,如果找到了,輸出靜態(tài)文件,否則輸出404頁面

(P.S. 因?yàn)槲覀円獙?shí)現(xiàn)的是一個無狀態(tài)的API快速開發(fā)框架,所以不需要進(jìn)行模版渲染,所有數(shù)據(jù)均通過ajax傳輸?shù)巾撁嬷?

注意到在這個函數(shù)里我使用了MiddlewareMap[m]以及ControllerMap[m] ,這是中間件以及控制器的map,在程序初始化的時候就會存入內(nèi)存中

具體定義如下:

// 這里記錄所有的應(yīng)該注冊的結(jié)構(gòu)體 // 控制器map var ControllerMap map[string]interface{} // 中間件map var MiddlewareMap map[string]interface{} func init() { ControllerMap = make(map[string]interface{}) MiddlewareMap = make(map[string]interface{}) // 給這兩個map賦初始值 每次添加完一條路由或中間件,都要在此處把路由或者中間件注冊到這里 // 注冊中間件 MiddlewareMap["WebMiddleware"] =&middleware.WebMiddleware{} // 注冊路由 ControllerMap["Controller"] = &controller.Controller{} }在此處我們用到了app/controller以及middleware包下的結(jié)構(gòu)體,當(dāng)路由解析完成后會把請求的路徑和這里的map對應(yīng)起來,現(xiàn)在我們看看router中解析路由代碼:

type route struct { path string // 路徑 target string // 對應(yīng)的控制器路徑 Controller@index 這樣的方法 method string // 訪問類型 是get post 或者其他 alias string // 路由的別名 middleware []string // 中間件名稱 controller string // 控制器名稱 function string // 掛載到控制器上的方法名稱}type route_group struct { root_path string // 路徑 root_target string // 對應(yīng)的控制器路徑 Controller@index 這樣的方法 alias string // 路由的別名 middleware []string // 中間件名稱 routes []route // 包含的路由}var Routes []route // 單個的路由集合var RoutesGroups []route_group // 路由組集合var RoutesList []route // 全部路由列表var R interface{}func init() { // 初始化方法,加載路由文件 // 獲取路由路徑,根據(jù)路由路徑獲取所有路由文件,然后讀取所有文件,賦值給當(dāng)前成員變量 routes_path := GetRoutesPath() dir_list, err := ioutil.ReadDir(routes_path) Check(err) // 根據(jù)dir list 遍歷所有文件 獲取所有json文件,拿到所有的路由 路由組 for _, v := range dir_list { fmt.Println("正在加載路由文件........" + v.Name()) // 讀取文件內(nèi)容,轉(zhuǎn)換成json,并且加入數(shù)組中 content, err := FileGetContents(routes_path + "/" + v.Name()) Check(err) err = json.Unmarshal([]byte(content), &R) Check(err) // 開始解析R,將其分類放入全局變量中 parse(R) }}在準(zhǔn)備編譯的階段便會執(zhí)行init函數(shù),獲取到路由文件夾下的所有路由列表,我們使用json格式來組織路由,解析出來的數(shù)據(jù)存入RoutesList列表中

下面是解析代碼

func parse(r interface{}) { // 拿到了r 我們要解析成實(shí)際的數(shù)據(jù) m := r.(map[string]interface{}) //newRoute := route{} for k, v := range m { if k == "Routes" { // 解析單個路由 parseRoutes(v) } if k == "RoutesGroups" { // 解析路由組 parseRoutesGroups(v) } }}// 解析json文件中的單一路由的集合func parseRoutes(r interface{}) { m := r.([]interface{}) for _, v := range m { // v 就是單個的路由了 simpleRoute := v.(map[string]interface{}) // 定義一個路由結(jié)構(gòu)體 newRoute := route{} for kk, vv := range simpleRoute { switch kk { case "Route": newRoute.path = vv.(string) break case "Target": newRoute.target = vv.(string) break case "Method": newRoute.method = vv.(string) break case "Alias": newRoute.alias = vv.(string) break case "Middleware": //newRoute.middleware = vv.([]) var mdw []string vvm := vv.([]interface{}) for _, vvv := range vvm { mdw = append(mdw, vvv.(string)) } newRoute.middleware = mdw break default: break } } // 把target拆分成控制器和方法 cf := strings.Split(newRoute.target,"@") if len(cf)==2 { newRoute.controller = cf[0] newRoute.function = cf[1] }else{ fmt.Println("Target格式錯誤!"+newRoute.target) return } // 把這個新的路由,放到單個路由切片中,也要放到路由列表中 Routes = append(Routes, newRoute) RoutesList = append(RoutesList, newRoute) }}func parseRoutesGroups(r interface{}) { // 解析路由組 m := r.([]interface{}) for _, v := range m { group := v.(map[string]interface{}) for kk, vv := range group { // 新建一個路由組結(jié)構(gòu)體 var newGroup route_group switch kk { case "RootRoute": newGroup.root_path = vv.(string) break case "RootTarget": newGroup.root_target = vv.(string) break case "Middleware": var mdw []string vvm := vv.([]interface{}) for _, vvv := range vvm { mdw = append(mdw, vvv.(string)) } newGroup.middleware = mdw break case "Routes": // 由于涉及到根路由之類的概念,所以不能使用上面的parseRoutes方法,需要再寫一個方法用來解析真實(shí)路由 rs := parseRootRoute(group) newGroup.routes = rs break default: break } // 把這個group放到路由組里 RoutesGroups = append(RoutesGroups,newGroup) } }}// 解析根路由 傳入根路由路徑 目標(biāo)跟路徑 并且傳入路由inteface列表,返回一個完整的路由集合// 只傳入一個路由組,返回一個完整的路由集合func parseRootRoute(group map[string]interface{}) []route { // 獲取路由根路徑和目標(biāo)根路徑,還有公共中間件 var tmpRoutes []route // 要返回的路由切片 var route_root_path string var target_root_path string var public_middleware []string for k, v := range group { if k == "RootRoute" { route_root_path = v.(string) } if k == "RootTarget" { target_root_path = v.(string) } if k=="Middleware" { vvm := v.([]interface{}) for _, vvv := range vvm { public_middleware = append(public_middleware, vvv.(string)) } } } // 開始獲取路由 for k, s := range group { if k == "Routes" { m := s.([]interface{}) for _, v := range m { // v 就是單個的路由了 simpleRoute := v.(map[string]interface{}) // 定義一個路由結(jié)構(gòu)體 newRoute := route{} for kk, vv := range simpleRoute { switch kk { case "Route": newRoute.path = route_root_path+ vv.(string) break case "Target": newRoute.target = target_root_path+ vv.(string) break case "Method": newRoute.method = vv.(string) break case "Alias": newRoute.alias = vv.(string) break case "Middleware": vvm := vv.([]interface{}) for _, vvv := range vvm { newRoute.middleware = append(public_middleware,vvv.(string))// 公共的和新加入的放在一起就是總共的 } break default: break } } // 把target拆分成控制器和方法 cf := strings.Split(newRoute.target,"@") if len(cf)==2 { newRoute.controller = cf[0] newRoute.function = cf[1] }else{ fmt.Println("Target格式錯誤!"+newRoute.target) os.Exit(2) } // 把這個新的路由,放到路由列表中,并且返回放到路由集合中,作為返回值返回 RoutesList = append(RoutesList, newRoute) tmpRoutes = append(tmpRoutes,newRoute) } } } return tmpRoutes}通過解析json文件,獲得路由列表,然后在上面的ServeHttp文件中即可與路由列表進(jìn)行對比了。

到此,我們實(shí)現(xiàn)了一個簡單的使用GO制作的Web框架

目前只能做到顯示靜態(tài)頁面以及進(jìn)行API響應(yīng)

接下來我們要實(shí)現(xiàn)的是:

  1. 一個便捷的ORM
  2. 制作快速添加控制器以及中間件的命令
  3. 實(shí)現(xiàn)數(shù)據(jù)庫遷移
  4. 實(shí)現(xiàn)基于token的API認(rèn)證
  5. 數(shù)據(jù)緩存
  6. 隊列
  7. 鉤子
  8. 便捷的文件上傳/存儲功能




上面只是一個簡單的實(shí)現(xiàn)了Web基本功能的小框架,接下來要使用一些高效的第三方包來替換路由以及配置等

未完待續(xù).......




20180408更新

重新編譯glide.exe ,可直接使用

20180409更新

關(guān)鍵詞:

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉