Meteor是一個(gè)基于Nodejs+Websocket+MongoDB的Web應(yīng)用程序開(kāi)發(fā)平臺(tái)。
Metror的七個(gè)特性
1.只有數(shù)據(jù):不在網(wǎng)絡(luò)上傳輸HTML,只傳輸數(shù)據(jù)并讓客戶端決定如何渲染
2.一種語(yǔ)言:前后端接口統(tǒng)一使用js
3.無(wú)處不在的數(shù)據(jù)庫(kù):客服端,服務(wù)器使用相同,透明的API訪問(wèn)數(shù)據(jù)庫(kù)
4.延遲補(bǔ)償:客戶端上使用預(yù)讀和模式模擬技術(shù),使它看起來(lái)與數(shù)據(jù)庫(kù)的連接是零延遲的
5.完整的響應(yīng)式:默認(rèn)實(shí)時(shí)模式,從數(shù)據(jù)庫(kù)到模板的所有層面上,都要使事件驅(qū)動(dòng)的接口有效
6.包容一切:開(kāi)源的,能與現(xiàn)有的工具和框架整合
7.簡(jiǎn)單等于生產(chǎn)力
Meteor 1.1開(kāi)始,支持 Windows/MongoDB 3.0
我們先以win7下的meteor安裝為例
安裝器下載地址為:
https://install.meteor.com/windows
點(diǎn)擊打開(kāi)之后會(huì)自動(dòng)下載安裝,時(shí)間有點(diǎn)長(zhǎng),之后需要注冊(cè)Meteor開(kāi)發(fā)者賬號(hào),可以跳過(guò)

這樣就安裝成功了

通過(guò)在命令行輸入
meteor help
可以查看所有的Meteor指令

下面我們先來(lái)創(chuàng)建一個(gè)meteor項(xiàng)目
meteor create mypro

之后根據(jù)提示輸入下列指令來(lái)啟動(dòng)meteor
cd mypro
meteor
meteor會(huì)先啟動(dòng)proxy,再啟動(dòng)mongoDB,最后啟動(dòng)應(yīng)用程序

之后在瀏覽器輸入http://localhost:3000/就可以訪問(wèn)meteor應(yīng)用了

雖然Meteor是基于nodejs的,但是nodejs的包不能直接在Meteor中使用,在Meteor目錄下運(yùn)行如下指令可以查看當(dāng)前可用的包
meteor list

下面我們通過(guò)實(shí)例來(lái)分析meteor項(xiàng)目的結(jié)構(gòu),通過(guò)
https://github.com/meteor/meteor
下載meteor的源碼,在examples里我們進(jìn)入leaderboard目錄,執(zhí)行
meteor
在瀏覽器中打開(kāi)這個(gè)項(xiàng)目

這是一個(gè)簡(jiǎn)單的meteor應(yīng)用,點(diǎn)擊名字,下面會(huì)出現(xiàn)給這個(gè)人加5分的按鈕,點(diǎn)擊之后會(huì)加5分并且自動(dòng)重新排序
首先,我們看一下leaderboard.html
Meteor默認(rèn)使用Handelbars模板系統(tǒng)
1.模板的加載
{{> 模板名}}
2.模板的定義
<template name="模板名">...</template>
3.遍歷數(shù)據(jù)集合
{{#each 集合名}}...{{/each}}
4.判斷
{{#if 條件為真}}...{{/if}}.....{{#unless 條件為假}}...{{/unless}}
{{#if 條件為真}}...{{/else}}...{{/if}}
Meteor的數(shù)據(jù)操作共享和延遲補(bǔ)償技術(shù)說(shuō)明
每個(gè)meteor客戶端包含一個(gè)內(nèi)存數(shù)據(jù)庫(kù)緩存Minimongo,在操作客戶端的數(shù)據(jù)變更時(shí),其實(shí)是操作的Minimongo,同時(shí)客戶端會(huì)向服務(wù)器發(fā)送一個(gè)變更請(qǐng)求,Meteor使用DDP(Distributed Data Protocol 分布式數(shù)據(jù)協(xié)議)來(lái)傳遞數(shù)據(jù)。
Meteor有個(gè)機(jī)制,客戶端向服務(wù)器端發(fā)起一個(gè)寫(xiě)操作時(shí),會(huì)立即更新本地緩存而不必等待服務(wù)器的響應(yīng),如果服務(wù)器拒絕變更請(qǐng)求,Meteor會(huì)修正客戶端緩存,這種技術(shù)就叫“延遲補(bǔ)償”,客戶端持有最新的數(shù)據(jù),同時(shí)服務(wù)器對(duì)于變更請(qǐng)求有最終的定奪的權(quán)利。
在客戶端修改數(shù)據(jù)庫(kù)確實(shí)方便,不過(guò)會(huì)有安全性問(wèn)題,所以一般發(fā)布到生產(chǎn)環(huán)境會(huì)移除Meteor的autopublish和insecure包,它們能夠模擬每個(gè)客戶端對(duì)服務(wù)器上的數(shù)據(jù)擁有完全讀/寫(xiě)權(quán)限的效果。
下面來(lái)看一下核心代碼leaderboard.js
1._id規(guī)則
Players = new Mongo.Collection("players");
Meteor在MongoDB中生成的_id是17位的簡(jiǎn)化id,如果要用mongodb默認(rèn)的24位id則可以通過(guò)
new Meteor.Collection(name,{idGeneration:'MONGO'});
也可以通過(guò)下列方法創(chuàng)建ObjectID
new Meteor.Collection.ObjectID(hexString)
2.客戶端服務(wù)器運(yùn)行不同的代碼
因?yàn)閘eaderboard.js是客戶端和服務(wù)器都加載的,所以如果要判斷是客戶端的操作還是服務(wù)器端的操作需要通過(guò)
if(Meteor.isClient){
//客戶端執(zhí)行的代碼
}
if(Meteor.isServer){
//服務(wù)器端執(zhí)行的代碼
}
目錄說(shuō)明:
服務(wù)器端
JavaScript/所有js
private/所有js
server/所有js
客戶端
client/所有js
公用資源
public/資源文件
其他目錄下的文件會(huì)被客戶端和服務(wù)器端兩者加載
3.Meteor可以使用的數(shù)據(jù)庫(kù)操作API
find,findOne,insert,update,upsert,remove,allow,deny
4.Session
Session只能用于客戶端
Session.set(key,value); //設(shè)置key的value
Session.setDefault(key,value); //設(shè)置key默認(rèn)的value
Session.get(key) //獲取key的value
Session.equals(key,value) //判斷key的值是否等于value
5.startup
Meteor.startup()是Meteor提供的核心API,可以用在客戶端和服務(wù)器端,當(dāng)用在服務(wù)器端時(shí),會(huì)在服務(wù)器進(jìn)程啟動(dòng)完畢后立即執(zhí)行,客戶端時(shí),會(huì)在DOM準(zhǔn)備就緒時(shí)立即執(zhí)行。
通常,Meteor.startup()用來(lái)做一些初始化的事情,比如往數(shù)據(jù)庫(kù)里插入文檔或從數(shù)據(jù)庫(kù)讀取文檔并進(jìn)行展示。
Meteor信奉響應(yīng)式編程的理念
我們可以在控制臺(tái)直接給Session賦值,就可以在頁(yè)面上直接看到效果

能夠響應(yīng)式計(jì)算的有
Templates
Meteor.render,Meteor.renderList
Deps.autorun
能夠觸發(fā)變化的響應(yīng)式數(shù)據(jù)源有
Session內(nèi)的變量
Collections內(nèi)的數(shù)據(jù)庫(kù)查詢
Meteor.status
Meteor.subscribe內(nèi)的ready()方法
Meteor.user
Meteor.userId
Meteor.loggingIn
insecure包
模擬每個(gè)客戶端對(duì)服務(wù)器上的數(shù)據(jù)庫(kù)擁有完全讀/寫(xiě)權(quán)限的效果,通常生產(chǎn)環(huán)境需要移除這個(gè)包
meteor remove insecure
這時(shí)如果給玩家加5分,會(huì)發(fā)現(xiàn)玩家排名和分?jǐn)?shù)上移后又瞬間回到原樣了,控制臺(tái)顯示update failed: Access denied

這就是延遲補(bǔ)償,去掉了insecure包,需要修改代碼
方法一
在服務(wù)器端執(zhí)行的代碼增加
Players.allow({
update: function(userId,doc,fieldNames,modifier){
return true;
}
});
如果返回true,允許客戶端執(zhí)行update操作,false時(shí)拒絕,也可以使用collection.deny方法來(lái)拒絕客戶端修改數(shù)據(jù)庫(kù)的請(qǐng)求
如果只允許得分大于30分的進(jìn)行修改
Players.allow({
update: function(userId,doc,fieldNames,modifier){
return doc.score >= 30 ? true : false;
}
});
只有在客戶端試圖修改數(shù)據(jù)時(shí)才會(huì)觸發(fā),而服務(wù)器端不受這個(gè)限制
方法二.
通過(guò)Meteor.methods和Meteor.call的組合來(lái)實(shí)現(xiàn)客戶端修改數(shù)據(jù)
//server
Meteor.methods({
add5:function(selectedPlayer){
Players.update(selectedPlayer,{$inc:{score:5}});
}
});
//client
Template.leaderboard.events({
'click .inc': function () {
Meteor.call('add5',Session.get("selectedPlayer"));
}
});
autopublish包
使用autopublish包,Meteor會(huì)客戶端Minimongo中的內(nèi)容和服務(wù)器端的MongoDB同步(除了users集合部分的內(nèi)容和索引)通常生產(chǎn)環(huán)境也需要移除這個(gè)包
meter remove autopublish
這時(shí)候客戶端和服務(wù)器端的數(shù)據(jù)庫(kù)就不同步了,需要修改代碼
//server
Meteor.publish("leaderboard",function(){
return Players.find();
});
//client
Meteor.subscribe("leaderboard");
這樣就可以了,假如我們有多個(gè)集合,可以選擇性地從服務(wù)器端發(fā)布,然后從客戶端訂閱
Meteor.publish只能用于服務(wù)器端
Meteor.subscribe只能用于客戶端
這里要說(shuō)一下,leaderboard.js所有的修改之后Meteor服務(wù)器會(huì)自動(dòng)重啟應(yīng)用,這樣就能看到最新的修改結(jié)果,還是很方便的
Meteor文件的加載順序
lib目錄下的文件先加載
main.*的文件最后加載
子目錄的文件在根目錄的文件之前加載
同目錄的文件按文件名的字母順序加載
下面我們以搭建一個(gè)微博為例子來(lái)講解需要注意的地方
我們采用的meteor組件是jquery+bootstrap+backbone+accounts-password
Meteor添加包
Meteor添加bootstrap依賴包
meteor add bootstrap
無(wú)須在<head>中引入bootstrap即可使用它,也無(wú)需引用jquery,jquery已經(jīng)內(nèi)置在Meteor的核心包中
在Meteor的頁(yè)面里必須包含head,body標(biāo)簽,不然會(huì)報(bào)錯(cuò),在template模板頁(yè)面里不需要

我們不用在html頁(yè)面里引入只含有template標(biāo)簽的模板頁(yè)面,因?yàn)镸eteor會(huì)將每個(gè)模板轉(zhuǎn)化成js函數(shù)并加載它們
添加backbone包來(lái)實(shí)現(xiàn)路由功能
meteor add backbone
backbone的詳細(xì)使用可以參考
http://backbonejs.org/
這里需要注意meteor session的用法
session只能用于客戶端,當(dāng)session值改變時(shí),如果需要更新頁(yè)面,需要在helpers里返回
Session.setDefault("currentUrl",{index:"active",reg:""});
var urlRouter = Backbone.Router.extend({
routes:{
"":"index",
"reg":"reg"
},
index:function(){
console.log("index");
Session.set("currentUrl",{index:"active",reg:""});
},
reg:function(){
console.log("reg");
Session.set("currentUrl",{index:"",reg:"active"});
},
redirect:function(url){
this.navigate(url,true);
}
});
Router = new urlRouter;
Template.container.helpers({
currentUrl: function () {
return Session.get('currentUrl');
}
});
Meteor.startup(function(){
Backbone.history.start({pushState:true});
});
這樣在html里才能訪問(wèn)到currentUrl
{{#if currentUrl.index}}
{{> index}}
{{/if}}
{{#if currentUrl.reg}}
{{> reg}}
{{/if}}
還有一種方法是直接通過(guò)模板名下的變量名來(lái)返回,但是瀏覽器會(huì)警告
Template.info.info = function(){
return Session.get("info");
}

添加accounts-password包構(gòu)建用戶認(rèn)證系統(tǒng),他可以幫助你快速的完成用戶的注冊(cè),登陸
meteor add accounts-password
Template.reg.events({
'click #submit': function(e){
e.preventDefault();
Accounts.createUser({username:$("#username").val(),password:$("#password").val()});
}
});
這樣就會(huì)在數(shù)據(jù)庫(kù)中保存用戶名和密碼,密碼是經(jīng)過(guò)SRP加密的

{{currentUser}}存儲(chǔ)了當(dāng)前在線用戶的信息(其實(shí)是調(diào)用了Meteor.user()),可以用來(lái)判斷用戶是否在線,不在線返回null
{{#if currentUser}}
<a href="/logout" class="btn btn-primary-large">退出</a>
{{else}}
<a href="/reg" class="btn btn-primary-large">注冊(cè)</a>
{{/if}}
Meteor.userId()可以用來(lái)判斷用戶是否在線,存儲(chǔ)當(dāng)前在線用戶的_id,不在線返回null
logout:function(){
if(Meteor.userId()){
Meteor.logout();
this.navigate("/",true);
Session.set("info",{success:"退出成功",error:""});
}else{
this.navigate("/",true);
Session.set("info",{success:"",error:"用戶不在線"});
}
},
Meteor.loginWithPassword可以實(shí)現(xiàn)用戶登錄功能
Meteor.loginWithPassword($("#username").val(),$("#password").val(),function(err){
if(err){
Session.set("info",{success:"",error:err.reason});
}else{
Session.set("info",{success:"登陸成功",error:""});
Router.redirect("/");
}
});
如果密碼錯(cuò)誤,err.reason會(huì)返回

如果用戶不存在,err.reason返回

這些認(rèn)證判斷都是Meteor為我們做好的
Meteor對(duì)html標(biāo)簽的閉合要求是很高的,如果有沒(méi)有閉合的標(biāo)簽,就會(huì)提示出錯(cuò),還是很智能的

如果我們?cè)诳蛻舳说膉s文件里定義了數(shù)據(jù)集
Posts = new Meteor.Collection("posts");
之后直接調(diào)用insert方法會(huì)報(bào)錯(cuò)

因?yàn)閿?shù)據(jù)集在服務(wù)器端沒(méi)有定義,我們需要的服務(wù)器端的js文件里也增加一句,然后可以通過(guò)allow來(lái)控制誰(shuí)可以操作數(shù)據(jù)
Posts = new Meteor.Collection("posts");
Posts.allow({
insert:function(userId,doc){
return userId && (doc.user._id === userId);
}
});
添加markdown支持
Meteor提供了一個(gè)showdown包,只需將Markdown文本添加到{{#markdown}}...{{/markdown}}標(biāo)簽內(nèi)
meteor add showdown
該Meteor微博的完整源碼我已上傳到Github上
https://github.com/ccfromstar/meteor_weibo
最后說(shuō)一下Meteor的部署
有兩種方法
1.部署到Meteor提供的免費(fèi)服務(wù)器上
meteor deploy weibocc.meteor.com

刪除站點(diǎn)重新部署,如果想清空數(shù)據(jù)庫(kù)
meteor deploy weibocc.meteor.com -D
如果希望是自己私有的,可以設(shè)置一個(gè)密碼,這樣下次更新代碼時(shí)候就需要密碼才能更新
meteor deploy weibocc.meteor.com -P
2.將應(yīng)用打包部署到自己的服務(wù)器上
meteor bundle weibocc.tgz
要運(yùn)行這個(gè)應(yīng)用,需要Nodejs和MongoDB
PORT=3000 MONGO_URL=mongodb://localhost:27017/weibo node bundle/main.js
這里需要注意的是我們必須重新編譯fibers包
cd bundle/programs/server/node_modules
rm -r fibers
npm install fibers@1.0.1
這樣就可以了