一、前言
對(duì)nodejs有了些準(zhǔn)備,希望多了解些后端知識(shí),恰逢公司項(xiàng)目調(diào)整,分析了前后端分離的優(yōu)劣,也做了一個(gè)完整的demo演示,同事都覺(jué)得靠譜,用了兩個(gè)版本的時(shí)間,將公司主站項(xiàng)目用nodejs實(shí)現(xiàn)了前后端分離,在此和大家分享下,以求共同進(jìn)步。案例參見(jiàn)http://www.upopen.cn
二、為何做分離
1、開(kāi)發(fā)體系:架構(gòu)體系決定了后端重于前端,前端做好靜態(tài)頁(yè)后,要轉(zhuǎn)為php或vm等,開(kāi)發(fā)要用eclipce等后端環(huán)境工作,一大堆讓前端迷糊的配置,一旦java人員更新了錯(cuò)誤的文件,會(huì)導(dǎo)致所有人的環(huán)境啟動(dòng)不了,束手無(wú)策,只能等待救援。
2、難維護(hù):頁(yè)面總是會(huì)有php\jsp等非前端代碼,相互干擾、無(wú)法優(yōu)化,時(shí)間越久問(wèn)題越突出。
3、前后端職責(zé)不清晰。
有人會(huì)問(wèn),分離為什么不全部走ajax,頁(yè)面就不需要任何服務(wù)端語(yǔ)言了。但實(shí)際場(chǎng)景并非如此,首先有些數(shù)據(jù)總是要生成頁(yè)面時(shí)就已經(jīng)同步獲取的,且全異步對(duì)SEO不利、純html頁(yè)面沒(méi)有include功能等。 網(wǎng)上還有其它的理解,大致相同就不列舉了。
三、如何做分離
1、產(chǎn)品設(shè)計(jì)確定后,前后端人員共同制定開(kāi)發(fā)接口,為方便接口的制定、顯示、測(cè)試,使用nodejs+mongodb開(kāi)發(fā)了接口平臺(tái)。參見(jiàn)http://www.upopen.cn:8090/interface/index功能:制定接口時(shí)就直接在interface平臺(tái)上新增錄入ActionName、description、 method、 param 及 默認(rèn)值 等并保存到mongodb,當(dāng)后臺(tái)開(kāi)發(fā)完成后,直接用在該頁(yè)面做接口測(cè)試,成功后方可交付,避免聯(lián)調(diào)過(guò)程中的接口反復(fù)。(原本想用interface.upopen.cn 來(lái)測(cè)試同域名下的項(xiàng)目,但因cookie在不同二級(jí)域名下無(wú)法共享的問(wèn)題,暫用了二級(jí)目錄,不過(guò)已有方案,后續(xù)優(yōu)化)
2、從前端角度考慮系統(tǒng)架構(gòu)圖如下:
前后端分離架構(gòu)圖
訪問(wèn)入口 NGINX 代理靜態(tài)資源到 STATIC 服務(wù)器,其它請(qǐng)求則到 NODEJS。
頁(yè)面請(qǐng)求NODEJS直接render,數(shù)據(jù)相關(guān)NODEJS則做預(yù)處理,再發(fā)到后臺(tái)
前端據(jù)已定義的接口,通過(guò)nodejs+mysql模擬后臺(tái)完成數(shù)據(jù)存取,此處會(huì)增加些工作量,但只是為了走通業(yè)務(wù)流程,模擬后臺(tái)數(shù)據(jù)表定義及邏輯無(wú)需嚴(yán)謹(jǐn),只要能正常存取即可,如注冊(cè)用戶save to DB,登錄驗(yàn)證read from DB,所以提供模式化的工具、寫法后,新增表、接口,并不會(huì)多耗時(shí)間,nginx – nodejs – 模擬后臺(tái),即可按實(shí)際使用完成所有前端工作。
QA單獨(dú)測(cè)前端時(shí),修改hostIP指向到測(cè)試工具。
Java開(kāi)發(fā)完成后接口,在 Interface平臺(tái) 上驗(yàn)證所有接口,確認(rèn)無(wú)誤。QA也可以通過(guò)Interface測(cè)試Java接口
nodejs修改config里的hostIP配置到j(luò)ava,理想情況下,修改此步配置即完成聯(lián)調(diào)
每個(gè)模塊的簡(jiǎn)略部署如下:
Nginx.conf:Location~\.(jpg|png|css|js){//靜態(tài)資源代理Root/root/static/;}Location\{//其它接口轉(zhuǎn)發(fā)Proxy_passhttp://upopen.cn;}…Upsteamupopen.cn{Serverhttp://node.upopen.cn;}
STATIC 結(jié)構(gòu)如下:使用require.js做模塊化加載工具,有朋友問(wèn)為什么不用seajs,還問(wèn)這兩個(gè)優(yōu)劣,其實(shí)前兩年的項(xiàng)目用的都是seajs,此處想試試amd vs cmd,所以就用了require.js ,以目前的使用來(lái)看,相互可替代
Core:[核心模塊,主要是引入第三方必用、穩(wěn)定模塊]Base.js[自定義通用函數(shù)]Require.jsJquery.jsBootstrap.jsBackgone.jsSocket.io.js
I18n.js…Public:[業(yè)務(wù)級(jí)公共模塊]Validate.js[表單驗(yàn)證模塊]All.js[所有頁(yè)面需要執(zhí)行的業(yè)務(wù)js,如登錄驗(yàn)證]Zhdoc.js[國(guó)際化文本定義]Reset.css[樣式初始化]common.css…Widget:[自定義組件]Dialog:[彈框組件]Dialog.jsDislog.cssImgs:[彈框組件圖片]Calendar:[日志組件]Calendar.jsCalendar.cssImgs:[日歷組件圖片]…Module:[業(yè)務(wù)模塊]Issue:[靜態(tài)模塊]Index:[首頁(yè)]Index.jsIndex.cssImgs:news:[新聞]news.js
news.css
imgs:…User:[用戶模塊]Register:[注冊(cè)]Register.jsRegister.cssImgs:findPwd:[找回密碼]findPwd.js
findPwd.css
imgs:…Nodejs結(jié)構(gòu)主要如下:express框架App.jsPackage.jsonNode_modules:Routes:Index.js–路由入口,接收所有請(qǐng)求做轉(zhuǎn)發(fā),并做權(quán)限過(guò)濾、404等Issue.js–接收來(lái)自index.js的靜態(tài)請(qǐng)求User.js-接收來(lái)自index.js的用戶請(qǐng)求,頁(yè)面請(qǐng)求render,數(shù)據(jù)請(qǐng)求轉(zhuǎn)發(fā)…Views:[使用ejs框架,接收來(lái)自routes里的頁(yè)面請(qǐng)求]Common:[公用模塊頁(yè)]Header.ejsFooter.ejs…Issue:[靜態(tài)模塊頁(yè)]Index.ejsNews.ejs…User:[用戶模塊頁(yè)]Register.ejs
findPwd.ejs…Controls:[業(yè)務(wù)邏輯模塊]Config.js–公共配置模塊,如hostIP、basePath等,切換環(huán)境修改此配置User.js-接收來(lái)自route/user.js的數(shù)據(jù)請(qǐng)求,向外轉(zhuǎn)發(fā)做邏輯準(zhǔn)備Tool.js-封裝常用函數(shù)如http.request/mailer/md5等Redis.js-redis封裝,對(duì)于單體封裝內(nèi)容比較多的模塊,單獨(dú)成立一個(gè)文件…Log:[日志,采用Log4js,日志是必須的,頁(yè)面開(kāi)發(fā)者常欠缺日志理念]Assets:結(jié)構(gòu)、使用同STATIC,不配置nginx時(shí),調(diào)用此處資源,意義不大…Nodejs+MySql:[模擬后臺(tái)模塊,相對(duì)上面的模塊,主要多了DB]Db:[數(shù)據(jù)處理模塊]Mysql.js–封裝模式化數(shù)據(jù)存儲(chǔ)接口,提供最便捷的新增表、接口的方法
按此結(jié)構(gòu)完成前端所有功能,因目前項(xiàng)目較小,部分模塊還有細(xì)分空間。
四、分離結(jié)果如何
1、開(kāi)發(fā)效率更高,在聯(lián)調(diào)之前,互不干擾,前端開(kāi)發(fā)完成后就是實(shí)際可用的代碼,不需要再轉(zhuǎn)換成后臺(tái)編譯環(huán)境,永遠(yuǎn)不會(huì)被java / php 啟動(dòng)不成功所困擾。
2、部分需要前后端共同開(kāi)發(fā)的功能,如文件上傳,通常需要頁(yè)面端與接收端都進(jìn)行相關(guān)的開(kāi)發(fā)配置,之前較難定位是誰(shuí)配置錯(cuò)誤,現(xiàn)在全部由前端完成,開(kāi)發(fā)、測(cè)試都容易定位,上傳成功后,只要向java發(fā)送文件保存的路徑即可。
3、完全分清了前后端開(kāi)發(fā)人員的職責(zé),任一方開(kāi)發(fā)完成后都可以提測(cè),實(shí)現(xiàn)同步開(kāi)發(fā)、測(cè)試。
4、聯(lián)調(diào)非常簡(jiǎn)單,若雙方接口一致,正常情況下只要修改要接口請(qǐng)求IP即可完成切換。
5、問(wèn)題責(zé)任清晰,聯(lián)調(diào)、測(cè)試、預(yù)發(fā)、上線,每個(gè)過(guò)程都難免會(huì)產(chǎn)生問(wèn)題,前端、后端、運(yùn)維三方責(zé)任邊界清晰,日志中記錄nodejs的請(qǐng)求發(fā)出,nginx請(qǐng)求接收與轉(zhuǎn)發(fā)、java端請(qǐng)求接收與返回,三處任何一處斷點(diǎn),都能馬上定位是哪方的問(wèn)題。
6、前端人員有更高的權(quán)限,頁(yè)面端的展示幾乎全由前端實(shí)現(xiàn),但之前一些配置卻受制于后臺(tái),比如常見(jiàn)的模板功能,純html頁(yè)面雖可以通過(guò)angularjs實(shí)現(xiàn)模板,但實(shí)際效果卻并不理想,網(wǎng)速差時(shí)經(jīng)常會(huì)出現(xiàn)include部分顯示后置、甚至加載不成功的情況,nodejs的ejs框架可以很好的實(shí)現(xiàn)這個(gè)功能。
另外,據(jù)瀏覽器加載不同的css以便實(shí)現(xiàn)瀏覽器兼容,之前處理通常是頁(yè)面加載后,通過(guò)js判斷瀏覽器類型,再去加載不同的css文件,影響渲染效率,并且js判斷瀏覽器類型本身就存在兼容問(wèn)題,用nodejs則可以在render前就完成該判斷,直接用相應(yīng)的瀏覽器樣式做渲染
7、代碼復(fù)用,驗(yàn)證模塊,頁(yè)面端與nodejs端可以直接復(fù)用
五、注意事項(xiàng):
1、前端開(kāi)發(fā)人員不僅需要有扎實(shí)的nodejs知識(shí),還要有一定的服務(wù)端、運(yùn)維知識(shí),對(duì)http通信有更深層次的理解,nginx、redis、socket、buffer等技術(shù)也要掌握,多多益善。
2、開(kāi)發(fā)之初對(duì)功能充分、寬裕的評(píng)估,使用初期不要用nodejs過(guò)多開(kāi)發(fā)新功能。初次使用,難免會(huì)遇到很多意想不同的問(wèn)題,前端開(kāi)發(fā)人員本身對(duì)服務(wù)端知識(shí)有限,java人員又對(duì)nodejs語(yǔ)法不熟,若處理不好會(huì)導(dǎo)致項(xiàng)目延期。我原本認(rèn)為redis同memcached一樣就是connect + set + get,但實(shí)際開(kāi)發(fā)時(shí)遇到一些考慮不周而產(chǎn)生的怪異問(wèn)題,延長(zhǎng)該部分開(kāi)發(fā)時(shí)間,最終導(dǎo)致項(xiàng)目delay。
3、前端開(kāi)發(fā)人員可嘗試用linux系統(tǒng)開(kāi)發(fā),第一版用win7開(kāi)發(fā)時(shí),npm部分模塊的安裝會(huì)不順利,如node-canvas在win7上安裝需要6步,而原本在32上已配置成功了,在64位上的系統(tǒng)上又不成功,后來(lái)該接口暫由java實(shí)現(xiàn)。實(shí)際上即使配置成功了,該模塊在發(fā)布服務(wù)器的linux系統(tǒng)上也無(wú)法使用,需要重新安裝。第二版開(kāi)發(fā)時(shí),我使用了ubuntu,雖然有一定的學(xué)習(xí)成本,但對(duì)后期的效率提升十分有益,多學(xué)習(xí)一種系統(tǒng)操作,和運(yùn)維調(diào)試時(shí)也更加主動(dòng)。不過(guò)ubuntu下沒(méi)有找到做PSD切圖的工具,所以安裝雙系統(tǒng)更合適切換使用。
六、總結(jié):
目前用分離方式做了兩版項(xiàng)目,開(kāi)發(fā)效率提升、協(xié)作溝通便捷上有明顯優(yōu)勢(shì),作為前端也學(xué)習(xí)了很多新知識(shí)、擴(kuò)大影響范圍,是共贏的。目前使用還較初級(jí),要繼續(xù)學(xué)習(xí)、探索。在此貼出以便和同仁共進(jìn)步之。