使用Go寫一個簡易的MVC的Web架構

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。# Bingo 這東西是我最近開始寫的一個玩意兒... 剛從PHP轉過來,對Go的特性還不是很瞭解,適用了一下gin,覺得雖然挺好的,但是一些文法沒有Laravel那麼方便所以想再造個輪子看看... en .... 就醬bingo是一個基於go語言的輕量級API架構,專註構建restfulAPIGitHub地址:[silsuer/bingo](https://github.com/silsuer/bingo)最近我做了很多修改,gayhub上的跟這篇文章很不符,所以在這再把commit的連結貼出來[點這裡這裡~](https://github.com/silsuer/bingo/commit/51504ce73687c73596e3df260724a6203241bbfe)## 目錄結構 - app 放置與網站相關代碼 - core 放置架構核心代碼a - vendor 放置第三方庫,使用glide管理第三方庫 - public 放置html代碼## 開發過程go的net包極其好用,用它開發架構也是極其快速(居然比寫php架構還要快...)首先確定main函數,在main函數中執行個體化一個結構體,然後調用其中的Run函數即可動手操作: ```go func main() { // new 一個bingo對象,然後bingo.Run()即可 // 指定靜態目錄,預設指向index.html ,載入路由檔案 // 載入env檔案 bingo := new(core.Bingo) bingo.Run(":12345") } ``` 接下來去寫bingo檔案: ```go func (b *Bingo) Run(port string) { // 傳入一個連接埠號碼,沒有傳回值,根據連接埠號碼開啟http監聽 // 此處要進行資源初始化,載入所有路由、設定檔等等 // 執行個體化router類,這個類去擷取所有router目錄下的json檔案,然後根據json中的配置,載入資料 // 執行個體化env檔案和config檔案夾下的所有資料,根據配置 // 根據路由列表,開始定義路由,並且根據連接埠號碼,開啟http伺服器 http.ListenAndServe(port, bin) // TODO 監聽平滑升級和重啟 } ``` `Run`函數非常簡單,只有一行代碼,就是開啟一個Http伺服器並監聽傳入的連接埠,由於我們要自己控制各種路由,所以我們不能用net包中內建的http服務,網上有很多原理說的很清楚了我們需要自己實現 `ServeHTTP`方法,以實現Mux這種路由器介面,所以再寫一個`ServeHttp`方法```go func (b *Bingo) ServeHTTP(w http.ResponseWriter, r *http.Request) { flag := false // 這個變數用來標記是否找到了動態路由 // 每一個http請求都會走到這裡,然後在這裡,根據請求的URL,為其分配所需要調用的方法 params := []reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)} for _, v := range RoutesList { // 檢測中介軟體,根據中介軟體首先開啟中介軟體,然後再註冊其他路由 // 檢測路由,根據路由指向需要的資料 if r.URL.Path == v.path && r.Method == v.method { flag = true // 尋找到了對應路由,無需使用靜態伺服器 //TODO 調用一個公用中介軟體,在這個中介軟體中尋找路由以及調用中介軟體收尾等功能 // 檢測該路由中是否存在中介軟體,如果存在,順序調用 for _, m := range v.middleware { if mid, ok := MiddlewareMap[m]; ok { // 判斷是否註冊了這個中介軟體 rmid := reflect.ValueOf(mid) params = rmid.MethodByName("Handle").Call(params) // 執行中介軟體,返回values數組 // 判斷中介軟體執行結果,是否還要繼續往下走 str := rmid.Elem().FieldByName("ResString").String() if str != "" { status := rmid.Elem().FieldByName("Status").Int() // 字串不空,查看狀態代碼,預設返回500錯誤 if status == 0 { status = 500 } w.WriteHeader(int(status)) fmt.Fprint(w,str) return } } } // 檢測成功,開始調用方法 // 擷取一個控制器包下的結構體 if d, ok := ControllerMap[v.controller]; ok { // 存在 c為結構體,調用c上掛載的方法 reflect.ValueOf(d).MethodByName(v.function).Call(params) } // 停止向後執行 return } } // 如果路由列表中還是沒有的話,去靜態伺服器中尋找 if !flag { // 去靜態目錄中尋找 http.ServeFile(w,r,GetPublicPath()+ r.URL.Path) } return }```可以看到,我們使用重新定義了ServeHttp方法,在這個方法中,我們根據瀏覽器訪問的不同URL,通過反射得到不同的控制器或者中介軟體的結構體,並且調用對應的方法,如果訪問的URL我們沒有定義的話,會到靜態檔案夾下去尋找,如果找到了,輸出靜態檔案,否則輸出404頁面(P.S. 因為我們要實現的是一個無狀態的API快速開發架構,所以不需要進行模版渲染,所有資料均通過ajax傳輸到頁面中)注意到在這個函數裡我使用了` MiddlewareMap[m]`以及` ControllerMap[m]` ,這是中介軟體以及控制器的map,在程式初始化的時候就會存入記憶體中具體定義如下:```go // 這裡記錄所有的應該註冊的結構體 // 控制器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包下的結構體,當路由解析完成後會把請求的路徑和這裡的map對應起來,現在我們看看router中解析路由代碼:```gotype route struct {path string // 路徑target string // 對應的控制器路徑 Controller@index 這樣的方法method string // 訪問類型 是get post 或者其他alias string // 路由的別名middleware []string // 中介軟體名稱controller string // 控制器名稱function string // 掛載到控制器上的方法名稱}type route_group struct {root_path string // 路徑root_target string // 對應的控制器路徑 Controller@index 這樣的方法alias string // 路由的別名middleware []string // 中介軟體名稱routes []route // 包含的路由}var Routes []route // 單個的路由集合var RoutesGroups []route_group // 路由群組集合var RoutesList []route // 全部路由列表var R interface{}func init() {// 初始化方法,載入路由檔案// 擷取路由路徑,根據路由路徑擷取所有路由檔案,然後讀取所有檔案,賦值給當前成員變數routes_path := GetRoutesPath()dir_list, err := ioutil.ReadDir(routes_path)Check(err)// 根據dir list 遍曆所有檔案 擷取所有json檔案,拿到所有的路由 路由群組for _, v := range dir_list {fmt.Println("正在載入路由檔案........" + v.Name())// 讀取檔案內容,轉換成json,並且加入數組中content, err := FileGetContents(routes_path + "/" + v.Name())Check(err)err = json.Unmarshal([]byte(content), &R)Check(err)// 開始解析R,將其分類放入全域變數中parse(R)}}```在準備編譯的階段便會執行init函數,擷取到路由檔案夾下的所有路由列表,我們使用json格式來組織路由,解析出來的資料存入RoutesList列表中下面是解析代碼```gofunc parse(r interface{}) {// 拿到了r 我們要解析成實際的資料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{})// 定義一個路由結構體newRoute := route{}for kk, vv := range simpleRoute {switch kk {case "Route":newRoute.path = vv.(string)breakcase "Target":newRoute.target = vv.(string)breakcase "Method":newRoute.method = vv.(string)breakcase "Alias":newRoute.alias = vv.(string)breakcase "Middleware"://newRoute.middleware = vv.([])var mdw []stringvvm := vv.([]interface{})for _, vvv := range vvm {mdw = append(mdw, vvv.(string))}newRoute.middleware = mdwbreakdefault: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 {// 建立一個路由群組結構體var newGroup route_groupswitch kk {case "RootRoute":newGroup.root_path = vv.(string)breakcase "RootTarget":newGroup.root_target = vv.(string)breakcase "Middleware":var mdw []stringvvm := vv.([]interface{})for _, vvv := range vvm {mdw = append(mdw, vvv.(string))}newGroup.middleware = mdwbreakcase "Routes":// 由於涉及到根路由之類的概念,所以不能使用上面的parseRoutes方法,需要再寫一個方法用來解析真實路由rs := parseRootRoute(group)newGroup.routes = rsbreakdefault:break}// 把這個group放到路由群組裡RoutesGroups = append(RoutesGroups,newGroup)}}}// 解析根路由 傳入根路由路徑 目標跟路徑 並且傳入路由inteface列表,返回一個完整的路由集合// 只傳入一個路由群組,返回一個完整的路由集合func parseRootRoute(group map[string]interface{}) []route {// 擷取路由根路徑和目標根路徑,還有公用中介軟體var tmpRoutes []route // 要返回的路由切片var route_root_path stringvar target_root_path stringvar public_middleware []stringfor 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{})// 定義一個路由結構體newRoute := route{}for kk, vv := range simpleRoute {switch kk {case "Route":newRoute.path = route_root_path+ vv.(string)breakcase "Target":newRoute.target = target_root_path+ vv.(string)breakcase "Method":newRoute.method = vv.(string)breakcase "Alias":newRoute.alias = vv.(string)breakcase "Middleware":vvm := vv.([]interface{})for _, vvv := range vvm {newRoute.middleware = append(public_middleware,vvv.(string))// 公用的和新加入的放在一起就是總共的}breakdefault: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檔案中即可與路由列表進行對比了。到此,我們實現了一個簡單的使用GO製作的Web架構目前只能做到顯示靜態頁面以及進行API響應接下來我們要實現的是: 1. 一個便捷的ORM 2. 製作快速添加控制器以及中介軟體的命令3. 實現資料庫遷移4. 實現基於token的API認證5. 資料緩衝6. 隊列7. 鉤子8. 便捷的檔案上傳/儲存功能求star,歡迎PR~哈哈哈([silsuer/bingo](https://github.com/silsuer/bingo))1623 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.