我們從來都不開發代碼,我們只是代碼的搬運工。— 阿飛
希望大家都變卡卡西。 — 啊賤
大家copy愉快,文檔只做參考。如果發現問題或者有好的建議請回復我我回及時更正。
1.Abstract
在學習web開發的過程中會遇到很多困難,因此寫了一篇類似綜述類的文章。作為路線圖從web開發要素的index出發來介紹golang開發的學習流程以及Example代碼。
在描述中多是使用代碼來描述使用方法不會做過多的說明。最後可以方便的copy代碼來實現自己的需求。
本文適應對象:
- 對web開發有一定經驗的人
- 能夠靈活使用ajax的人(至少懂得前後分離)
- golang web 開發有一定了解,至少略讀過一些golang web開發的書籍
看完本文之後您會收獲:
- golang web開發的一些技巧
- golang web開發的一些實用API
本文在描述的時候為了解釋盡量詳細,已經把解釋寫到代碼注釋中。
2.golang web 開發check list
略過的部分:基本流程控制,OOP等基礎語法知識。
3.路由器
路由器是整個網站對外的靈魂,如果路由做的不好URL會非常惡心。所以這部分設計成第一個要說的內容。
路由分兩種一種是手動路由為了通過tul調度固定的功能,另外一點就是資源的獲取,通過url的分析來模仿靜態頁的方式來獲取資源(類似get)
自動路由,主要使用OOP的COMMAND模式來實現。所有功能使用post,統一入口,方便權限管理,安全管理,跨域管理。但是如此強大的功能還是交給框架來做吧。這裡就不給新手做參考了。
3.1手動路由
- package main
- import(
- "log"
- "net/http"
- )
- func main(){
- RouterBinding()// 路由綁定函數
- err := http.ListenAndServe(":9090",nil)//設置監聽的端口
- if err !=nil{
- log.Fatal("ListenAndServe: ", err)
- }
- }
在httpserver運行之前先綁定路由
3.2 手動路由的綁定
3.2.1 靜態文件
- http.Handle("/pages/", http.StripPrefix("/pages/", http.FileServer(http.Dir("./pages"))))
3.2.2 固定函數與資源獲取
他們都是一樣的
- http.HandleFunc("/images/", fileUpload.DownloadPictureAction)
4.頁面加載
4.1 純靜態頁(HTML)
直接交給路由就行了。自動就訪問那個文件夾了。不過生產環境果然還得是cdn,如果自己服務器比較多。可以nginx反向代理。
主要好處前後分離,能上CDN就是通訊次數多了。不過通過優化改善之類的都還ok啦。
4.2 模板頁面的加載
- commonPage, err :=template.ParseFiles("pages/common/head.gtpl",//加載模板
- "pages/common/navbar.gtpl","pages/common/tail.gtpl")
- if err !=nil{
- panic(err.Error())
- }
- navArgs := map[string]string{"Home":"home","User":"yupengfei"}//復雜的參數開始往裡塞
- knowledgePage, err :=template.ParseFiles("pages/knowledge/knowledge.gtpl")
- knowledgeArgs := map[string]interface{}{"Head":"This is a test title",
- "Author":"kun.wang","PublishDatetime":"2014-09-14",
- "Content":template.HTML("<p style=\"text-indent: 2em\">為什麼要用語義呢?</p>")}//不是不好,只是做字符串分析會影響工程效率
- commonPage.ExecuteTemplate(w,"header",nil)// render 開始
- commonPage.ExecuteTemplate(w,"navbar", navArgs)
- knowledgePage.ExecuteTemplate(w,"knowledge", knowledgeArgs)
- commonPage.ExecuteTemplate(w,"tail",nil)
僅提供關鍵代碼。
- 其他的都還挺好,就是頁面渲染用服務器是不是有點太奢侈了。
- 字符串數組作為輸入參數差錯比較困難
- 總結:雖然減少的通訊次數,但是沒辦法上CDN蛋疼,另外,模板的mapping蛋疼。
5.表示層腳本
表示層腳本做的比較困難也不是很好學。但是一旦搞定了,代碼的復用性會有非常可觀的提升。
就普通情況而言JS開發效率是非常高的靈活度高,並且使用的是客戶端的cpu性能好,免費資源多,學習的人也多,好招聘。
5.1 require.js
5.1.1 加載
- <scriptdata-main="/reqmod/login_main"language="JavaScript"deferasync="true"src="js/r.js"></script>
整個網頁之留這麼一個加載腳本的入口(每個頁面最好只有一個js文件)
好處
- js是延遲加載。不會出現網頁卡死的情況
- 最大化使用緩存。(HTTP 304)
- 一個網頁只用一個js
- dom事件綁定,不用在html控件上寫js綁定了
壞處
- 學習比較難
- 網站更新始���有緩存沒更新的浏覽器。造成錯誤(所以有些情況客戶自己就知道多刷新幾次了,已經成用戶習慣了)
參數解釋
- `data-main` 業務邏輯入口,載入當前字符串.js這個文件
- `language` 不解釋
- `defer async` 字面意思
- `src` r.js就是require.js的意思。代碼到處都能搞到。
5.1.2 頁面Business
加載依賴文件
- require.baseUrl ="/"
- require.config({
- baseUrl:require.baseUrl,
- paths:{
- "jquery":"js/jquery-1.10.2.min",
- "domready":"reqmod/domReady",
- "pm":"reqmod/pmodal",
- "cookie":"reqmod/cookie",
- "user":"reqmod/user",
- "bootstrap":"reqmod/bootstrap.min",
- "nav":"reqmod/nav"
- },
- shim:{
- 'bootstrap':{
- deps:['jquery']
- }
- }
- });
- //直接copy全搞定。
執行頁面business
執行裡面做的最多的就是dom跟事件綁定而已。加載各種js庫直接引用。
代碼美觀,開發效率,執行效率都是非常棒的。
- require(['nav','domready','jquery','user','pm'],function(nav,doc, $, user,pm){
- //這個函數的第一個`數組`參數是選擇的依賴的模塊。1. 網站絕對路徑。 2. 使用加載依賴模塊的時候選擇export的內容
- //數組的順序要跟function順序一致,如果有兩個模塊依賴比如說jquery插件,就寫道最後不用變量,直接使用`$`
- doc(function(){// domReady
- pm.load();//加載各種插件HTML模板之類的都ok
- $('#btn_login')[0].onclick =function(){user.login();}//button 事件綁定
- });
- });
頁面MODEL
- define(['jquery','reqmod/cookie','user','bootstrap'],function($,cookie,user){
- //define 函數的參數內容require是一樣的。
- // 這裡依賴的模塊要在調用此模塊中的模塊中有path配置。不然會死的很慘,報錯的時候不會說缺少什麼什麼地方錯了。
- var nav_load =function(){// 注意函數定義的方式copy就行
- $.get('/nav.html',function(result){
- var newNode = document.createElement("div");
- newNode.innerHTML = result;
- $('body')[0].insertBefore(newNode,$('body')[0].firstChild);
- //document.body.innerHTML = result + document.body.innerHTML;
- $('#btn_login')[0].onclick =function(){user.login();}
- $('#btn_reg')[0].onclick =function(){window.location='/register.html'}
- $.post('/login_check',{},function(data){
- if(data==0){
- Form_login.style.display=""
- }
- else{
- form_userInfo.style.display=""
- }
- })
- });
- }
- return{//這裡類似微型路由。非常靈活,非常方便
- load :nav_load
- };
- });
5.2 JQuery
JQ的功能只要require.js引用了之後基本上都是一樣的。
如果有需要可以到w3school上學習一下。
6.業務層
Post分析
- func XXXAction(w http.ResponseWriter, r *http.Request){
- r.parseForm()//有這個才能獲取參數
- r.Form["Email"]// 獲取Email 參數(String)
- // 寫接下來的業務。
- }
資源入口函數資源require分析(url分析固定寫法)
- func Foo(w http.ResponseWriter, r *http.Request){
- queryFile := strings.Split(r.URL.Path,"/")
- queryResource := queryFile[len(queryFile)-1]// 解析文件
- }
- //完成字符串分割之後,按照需求來獲取資源就可以了。
直接輸入object
- data, err := ioutil.ReadAll(r.Body)//直接讀取form為 json 字符串
- if err !=nil{
- utility.SimpleFeedBack(w,10,"failed to read body")
- pillarsLog.PillarsLogger.Print("failed to read body")
- return
- }
- k :=【BUSINESS OBJECT】
- err = json.Unmarshal(data,&k)
- if err !=nil{
- utility.SimpleFeedBack(w,13,"Pramaters failed!")
- pillarsLog.PillarsLogger.Print("Pramaters failed!")
- return
- }
- //方便快捷。再訪問參數的時候,直接調用結構體參數就可以了。
- //注意ajax調用函數的時候需要做出一些調整代碼如下:
- $.ajax([dist],JSON.stringify([data]),function(){},'json');//注意JSON
7.持久層
7.1 Mysql
其實不管什麼語言的Mysql驅動都是從PRO*C來的,所以會PRO*\C之後,啥都好說
Insert/Delete/Update
- stmt, err := mysqlUtility.DBConn.Prepare("INSERT INTO credit (credit_code, user_code, credit_rank) VALUES (?, ?, ?)")
- if err !=nil{
- pillarsLog.PillarsLogger.Print(err.Error())
- returnfalse, err
- }
- defer stmt.Close()
- _, err = stmt.Exec(credit.CreditCode, credit.UserCode, credit.CreditRank)
- if err !=nil{
- returnfalse, err
- }else{
- returntrue, err
- }
- //還是比較方便的
Query
- stmt, err := mysqlUtility.DBConn.Prepare(`SELECT commodity_code, commodity_name, description, picture,
- price, storage, count, status,
- insert_datetime, update_datetime FROM commodity WHERE commodity_code = ?`)
- if err !=nil{
- returnnil, err
- }
- defer stmt.Close()
- result, err := stmt.Query(commodityCode)
- if err !=nil{
- returnnil, err
- }
- defer result.Close()
- var commodity utility.Commodity
- if result.Next(){
- err = result.Scan(&(commodity.CommodityCode),&(commodity.CommodityName),&(commodity.Description),
- &(commodity.Picture),&(commodity.Price),&(commodity.Storage),&(commodity.Count),&(commodity.Status),
- &(commodity.InsertDatetime),&(commodity.UpdateDatetime))
- if err !=nil{
- pillarsLog.PillarsLogger.Print(err.Error())
- returnnil, err
- }
- }
- return&commodity, err
7.2 Mongodb
- err := mongoUtility.PictureCollection.Find(bson.M{"picturecode":*pictureCode}).One(&picture)
這裡只給出最簡單的例子。具體的看mgo的開發文檔就ok。還是比較簡單的。
8.單元測試注意事項
- 測試命令 go test -v (沒有其他參數了!!!) 如果不帶-v只顯示結果,不顯示調試過程,主要是調試開發的時候用
- 文件格式 xxx_test.go 但是建議改成 xxx_test0.go 或者喜歡改成別的也可以。
- 由於測試先行的原則,在開發的時候一次測試也就一兩個函數。
- 這樣相當於把其他測試注釋掉
- 測試的時候的配置文件要放到測試目錄下面。別忘了。
- 心態,錯誤太多一個一個來,要有個好心態。
9.LOG
注意在調試中Log的不可缺失性。
下面api如果不知道從何而來直接doc搜索就可以了。
- package utility
- import"log"
- import"os"
- import"fmt"
- // Logger Model min variable.
- varLogger*log.Logger
- var outFile *os.File
- // init function if Logger if not inited will invoke this function
- func init(){
- ifLogger==nil{
- propertyMap :=ReadProperty("pic.properties")
- logFileName := propertyMap["LogFile"]
- fmt.Println("Initial and Open log file ", logFileName)
- var err error
- outFile, err = os.OpenFile(logFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR,0666)
- if err !=nil{
- panic(err.Error())
- }
- Logger= log.New(outFile,"", log.Ldate|log.Ltime|log.Llongfile)
- }
- }
- // CloseLogFile function : close Logger invoke file.
- func CloseLogFile(){
- outFile.Close()
- }
使用方法:
- utility.Logger.Println("Log test")
總結
- 看完這裡copy代碼日常工作還是能好應付一點。
- 如果是新手看完這個之後,看那麼厚的書就有一定的目標性了。能方便一點
Ubuntu 14.04 上搭建 Golang 開發環境配置 http://www.linuxidc.com/Linux/2015-02/113977.htm
Linux系統入門學習-在Linux中安裝Go語言 http://www.linuxidc.com/Linux/2015-02/113159.htm
Ubuntu 安裝Go語言包 http://www.linuxidc.com/Linux/2013-05/85171.htm
《Go語言編程》高清完整版電子書 http://www.linuxidc.com/Linux/2013-05/84709.htm
Go語言並行之美 -- 超越 “Hello World” http://www.linuxidc.com/Linux/2013-05/83697.htm
我為什麼喜歡Go語言 http://www.linuxidc.com/Linux/2013-05/84060.htm
Go語言內存分配器的實現 http://www.linuxidc.com/Linux/2014-01/94766.htm