很多人推薦MEAN來開發站點。MEAN就是M:mongodb,E:expressjs、A:angular最后的N:nodejs。 但是如果你親身的體會到了js的嵌套回調的話你就會想換換別的辦法了。雖然說可以用promise等框架 破解深深的嵌套,但畢竟不是語言本身支持的。
為什么說用Golang呢?因為Golang是一個有着動態語言的特點的靜態語言。語法簡單,語法糖就是盡量減少語法。 編譯的時候就可以把錯誤排查掉很多。不用像動態語言那樣運行的時候才掉進坑里。
在Golang眾多的Web框架中發現了Martini。 他后來覺得這個框架用了太多反射影響了效率,就有寫了另一個框架或者按照作者的說法“中間件”, 叫做Negroni。具體的開發原因看這里。 可能是這個作者愛喝酒把他寫的框架都叫了個酒水的名字。Margini這個框架是作者受了Expressjs的啟發寫出來的。 所以在很多使用上的東西非常類似,比如route的綁定語法,支持類中間件的綁定語法等。也看過Golang的其他框架比如Revel, 有很多的酷炫的功能,和一站式的很多的支持,但是因為Martini的靈活決定選用Margini。
數據庫MongoDB
數據庫方面,對於事物和數據的一致性要求沒有那么強的話,還是選用比較簡單的NoSQL數據庫比較合適。所以,就用MongoDB了。 也許在嚴謹的工作中不應該這么用。但是MongoDB可以簡單到,只要把Http POST過來的json數據直接寫入數據庫中。 如果json比較簡單的話,完全不用做什么設計!當然,實際工作的時候是不可以這樣的。我們討論的只是這樣的一個可行性。
那么我們就開始用Martini(golang)和MongoDB來打造一個web APP。這個app的名稱就叫做navigator(領航者)。 是用來接受、存儲和分析用戶反饋以及用戶的各種問題的,比如各種客戶反饋回來的用戶問題。這些問題的上下文信息十分有限。 所以我們讓用戶通過手機客戶端來調用navigator的接口來同時提交相關的上下文信息。 關於Golang的一些基礎的東西就不在這里探討了。 go get獲取Martini,下載MongoDB。之后使用命令run起來MongoDB。給DB起一個名字:NDB。然后在里面填充一個document。 MongoDB如果沒有insert一個Document的話,那么這個數據庫其實是沒有創建的。這個Document就是一個Json。格式為:
{ "feedbackcode" : "123", "username" : "", "phonenum" : "", "deviceinfo" : { "systemtype" : "", "systemver" : "", "appver" : "" }, "imagelist" : [ ], "desc" : "testing", "feedbacktype" : "", "other" : "" }
使用feedbackCode
來標示一個唯一的feedback。數據庫的問題就徹底解決了。
站點的目錄結構
下面開始我們的站點部分。首先了解一下目錄結構:
|———conf
|-————–Conf.go # 配置信息
|—————–ConfigError.go # 錯誤名稱
|———controllers
|—————–FeedbackController.go
|—————–IndexController.go
|–––models
|––––––FeedbackModel.go
server.go
這個目錄結構式典型的REST API的MVC模式體現。我們這里不需要處理View,也就是Html的東西。但是, 還是顯示的區分了Model和Controller。view其實還是有的,只不過是最后render后的json。
RUN起來
首先,讓我們的web app跑起來。 給server.go添加如下的代碼:
package main import "github.com/go-martini/martini" func main() { m := martini.Classic() m.Get("/", func() string { return "Hello world!" }) m.Run() }
go run server.go
命令就可以讓這個web app運行起來了。默認的是運行在3000端口上的。 在頁面上也只能看到光禿禿的幾個字Hello world! martini.Classic()
調用之后,Martini會為你默認提供一些工具。其中最重要的一個就是路由。 在這之后就可以使用m.Get("/", func()string{return "Hello, world!"}
來指定什么路徑映射到什么處理方法上。 最后調用m.Run()
方法在3000端口上運行整個app。
現在開始要真正的開發這個app了。首先我們需要有一個簡單的需求分析,我們到底是要做什么。 如前所述,用戶可以用這個API發送feedback上來,然后在另外一個接口里把這些內容以列表的形式展示出來。 需求就是這么簡單。我們下面來一步一步實現這些功能。
Controllers
由於insert一個feedback,需要的信息量還是很大的。我們用POST得方法發送所需要的feedback數據。 所以,我們需要一個POST的路徑和處理函數Handler(以后都叫做Handler)。
m.Post("/", func(req *http.Request) string{ // 從req中取出參數,插入到數據庫 })
現實feedback的列表的接口只需要響應一個GET請求。所以是一個GET的路徑和Handler。 看起來應該是這樣的:
m.Get("/", func() string { // 假設最后的字典會被轉換成Json串 return []map[string]string{map[string]string{"feedbackcode": "1", "username": "John"}, map[string]string{"feedbackcode": "2", "username": "Tony"}} }
以上列出的Handler都是對於API添加數據和數據列表的一些模擬。下面開始開始正式寫Controller。
列表Controller
先看代碼,然后再來詳細說明:
package controllers import ( // "fmt" "fmt" "github.com/martini-contrib/render" "navigator/conf" "navigator/models" "net/http" ) type FeedbackController struct { } func (c *FeedbackController) FeedbackList(req *http.Request, r render.Render) { f := new(models.FeedbackModel) r.JSON(200, map[string]interface{}{"data": f.ModelList(nil)}) }
引入github.com/martini-contrib/render
, render是一個渲染的包。 如果有html的視圖的話,可以把數據轉換成html模板可用的數據。我們這里用這個包來把字典轉換成json數據。 r.JSON(200, map[string]interface{}{"data": f.ModelList(nil)})
中200,是狀態碼。 需要轉換的字典是一個string為key,Model列表為值的字典。
這里可以先了解一部分feedback的Model類:
type DeviceInfo struct { SystemType string `json: "systemType"` // ios or android SystemVer string `json: "systemVersion"` // system version eg, ios 9.1 AppVer string `json: "appVersion"` // app version eg, 2.9.0 } type FeedbackModel struct { FeedbackCode string `json: "feedbackCode"` UserName string `json: "userName"` PhoneNum string `json: "phoneNum"` DeviceInfo DeviceInfo `json: "deviceInfo"` ImageList []string `json: "imageList"` Desc string `json: "description"` FeedbackType string `json: "feedbackType"` // normal feedback or a problem in use Other string `json: "other"` // other infomation }
在最開始的json數據定義,在Golang的Model類定義中可以定義為上面的代碼。 用"``"標示出來的,是在Model轉換為json字符串的過程中Model字段和json串的key值得對應關系。 最后Controller的功能是需要處理請求過來的數據的, 也就是Controller的方法會作為Get或者Post的Handler的。這兩者之間需要關聯起來:
package main import ( // other packages "navigator/controllers" ) func main() { var m = martini.Classic() var r = martini.NewRouter() feedbackController := new(controllers.FeedbackController) r.Get("/nav/feedback", feedbackController.FeedbackList) r.Post("/nav/feedback", feedbackController.Feedback) m.Action(r.Handle) m.RunOnAddr(":9090") }
這里r.Get("/nav/feedback", feedbackController.FeedbackList)
,修改了之前的訪問路徑。/nav/feedback是在路徑上加了一個項目的名稱簡寫作為限定。因為直接叫做feedback有可能會和其他的同名接口混了。 也就是接口的污染。所以加一個項目名稱來避免這個問題。feedbackController := new(controllers.FeedbackController)
初始化FeedbackController。 把feedbackController
的方法FeedbackList
和訪問的路徑綁定到一起。 GET的Http訪問和FeedbackList
方法綁定,POST的Http訪問和Feedback
方法綁定。 最后m.RunOnAddr(":9090")
代替了之前使用的m.Run()
。之前app是運行在3000端口上的。 現在通過m.RunOnAddr(":9090")
則app將運行在9090端口上。也就是可以用這一語句來制定app的運行端口。
Models和MongoDB
處理MongoDB我們用mgo這個庫。Golang雖然這么多年了已經。還出了這么多的明星產品。 但是,處理MongoDB的成熟的庫還是可以說沒有。完全沒有想python、nodejs等語言的庫那么豐富。 有標准的,還有很多github上的其他開發者開發的庫。Golang里也就是mgo相對來說這個庫比較可用的了。
當POST請求的feedback的json字符串傳上來的時候,會被轉換為一個FeedbackModel
的對象。 Golang的json庫會幫助我們做到這一點。 json.Unmarshal([]byte(dict), &f)
這里的Unmarshal
方法會把一個json字符串轉化為FeedbackModel對象。 這里需要用到一個工廠方法。生成一個Model對象,並且這個對象應該是一個富Model對象。 后面這個Model對象將具有可以操作操作數據庫,增、刪、改、查的方法。代碼:
func NewFeedbackModel(dict string) *FeedbackModel { var f FeedbackModel err := json.Unmarshal([]byte(dict), &f) if err != nil { return nil } return &f }
以上代碼會用到庫"encoding/json"
。當收到feedback的json串的時候就用這個方法將其轉換為FeedbackModel對象。 上文說過,會給這個Model增加數據庫操作的方法。那么我們就可以通過json串得到的Model來執行數據的操作。 代碼:
func (m *FeedbackModel) ModelList(conditions map[string]string) []FeedbackModel { confInstance := conf.ConfigInstance() mgoUri := conf.ConfigInstance().MongoDBConnectionString() // var f FeedbackModel session, err := mgo.Dial(mgoUri) if err != nil { fmt.Printf("can not connect to server %v\n", err) panic(err) } defer session.Close() var feedbackModelList []FeedbackModel collection := session.DB(confInstance.DBName).C("feedback") err = collection.Find(conditions).All(&feedbackModelList) return feedbackModelList } func (m *FeedbackModel) GetModel(modelId string) (*FeedbackModel, error) { //此處省略一部分代碼 collection := session.DB(confInstance.DBName).C("feedback") err = collection.Find(bson.M{"feedbackcode": modelId}).One(&f) return &f, nil } func (m *FeedbackModel) InsertModel(model *FeedbackModel) error { //此處省略一部分代碼 f := session.DB(confInstance.DBName).C("feedback") err = f.Insert(model) return err }
這里需要稍微的提到一部分MongoDB的知識。在MongoDB中,庫還是庫,但是表不叫做表,叫做Collection。 關鍵字不翻譯。翻譯出來的看着太爛,還不能清楚表達意思。每一條記錄也不叫記錄,叫做Document。 一個Document的大小不能超過2M。
在MongoDB里查找是這樣的:
如果在find里什么都不寫,那么就是無條件的全部查找。插入是這樣的:
Golang的mgo當然在具體操作數據庫方面和MongoDB本身是不會差太多了,除了這是Go語法的封裝。
這樣就得到了一個Collection。查找、插入、修改等動作都是在Collection上執行的。所以,查找就是這樣的:
查找到的列表會存放在一個事先聲明好的feedback的Model列表里。查找一個可以這樣:
插入:
insert的時候,這個model需要是指針類型的,也就是:var model *FeedbackModel
。
到這里就可以把整個的feedback從請求到返回的過程都串聯起來了。
全部串聯起來
在整個的app入口處。請求的路徑和具體的Controller處理函數綁定在了一起。每一個請求來了之后 ,如果路徑和綁定的路徑是對應好了的。就會被發送給Controller的某個綁定好的函數里處理。
這個負責處理http請求的Controller方法會把http請求發送上來的json字符串發送給Model模塊處理。 Model模塊會把json字符串轉換為一個Model實例。這個Model是一個富Model, 可以調用其函數把Model本身的數據插入到Mongo數據庫中,當然也可以從數據庫中查詢這些數據。
到這里
到這里你已經熟悉了如何用Martini這個Golang框架開發一個簡單地站點,好吧,其實是一個REST Api。 使用我們文中提到的Render框架,你可以非常非常容易的把Model數據轉換之后並顯示在html的template中。 而且你已經熟悉了MongoDB數據庫。一個非常簡單易用的數據庫。無論你是要開發一個REST Api還是要開發一個html的站點。 需要存儲的東西都可以存如MongoDB數據庫中。到這里你已經可以順利的繼續下面的功能的開發了。