在云中创建和部署您自己的单页交互式 Meteor 应用程序(实时更新),只需使用浏览器即可完成该操作。在 IBM DevOps Services 上编写代码,使用 Bluemix MongoDB 服务实现服务器端存储,并将应用程序部署到 Bluemix。
Meteor 是一个新的 Web 应用程序开发平台,正在国际上得到广泛采用。Meteor 不仅仅是一个 JavaScript 编码框架,它还提供了一种创新方式来构建可伸缩、交互式的富 Web 应用程序。通过简化编码模型和减少开发人员必须编写的代码量,Meteor 具有加速开发周期的潜力。使用 Meteor,经验丰富的 Web 应用程序架构师和开发人员只需花费几天或几星期的时间,就可以完成从概念到全面部署的整个过程,而不像平常一样需要几个月或更长的时间。
关于安装 Meteor 平台并开始使用它进行开发的分步指南,developerWorks 文章 “使用 Meteor 轻松开发实时网站” 是一个不错的参考资料。在这篇最新的文章中,我将通过两个详细的应用程序示例更深入地剖析 Meteor 开发,并概述 Meteor 的架构。借助这些知识,您能够自行判断 Meteor 上的快速 Web 应用程序创建是否适合您。
从过去的角度看未来
Meteor 提出的方法从某种意义上讲是革命性的,但它也有一些方面是通过进化而来的。它继续采用了与计算机史中的重大成功案例相同的 IT 路径:电子表格软件。图 1 给出了一个典型的电子表格示例 —一个包含饼图的 Sales by Region 电子表格:
图 1. Sales by Regions 电子表格
如果修改 Sales by Region 电子表格中的任何地区销量数据,总销量数据(未显示)也会发生更改,饼图会立即被重绘,以反映各个切片的新的相对比例。
现在,这种电子表格既不新颖也不怎么有趣。但回想 1983 年 Lotus 1-2-3 向早期的 PC 用户揭示这些特性时的情景,您就能体会到它的影响力。在此之前,任何人都无法以如此少的编程完成如此多的工作。尽管电子表格软件最初不是很直观,但大多数用户都在几天之后就可以熟练地操作它。电子表格软件仍然是推动全球的 PC 销售的杀手级应用程序之一。
快进 30 年
在第一款电子表格软件问世 30 年后,您可以看到电子表格在 Meteor 上得到了发展。图 2 显示了使用 Meteor 在 2013 年创建的 Sales Portal Web 应用程序:
图 2. Sales Portal Web 应用程序
Sales Portal 显示了最新的地区销量数据和一个相应的饼图。作为虚构的 CEO,您可以监视销量数据,每个地区销售团队都可以定期更新其销量数据。
如果已经安装 Meteor,那么您可以 下载 Sales Portal 应用程序并动手体验它。更改该下载的 sales_nologin 子目录并键入 meteor run
。在浏览器实例中输入 http://localhost:3000/,地区销量数据和饼图应该就会出现。双击任何销量数据即可更改它。在您确认更改后,饼图会立即更新。如果在多个浏览器实例中打开 Sales Portal,所有这些实例都会更新,以显示最新的销量数据,而且您可以从任一个浏览器实例中修改这些数据。(如果无法安装 Meteor,那么您可以尝试应用程序的托管版本;请参阅 参考资料。)
图 3 显示了 US Central 团队正在选择和更新其销量数据:
图 3. 更新 US Central 销量
图 4 显示了更新的 US Central 销量数据和最新的饼图。任何在更新的同时访问 Sales Portal 的用户都会立即看到所做的更改。
图 4. 更新的饼图比例反映了新的 US Central 销量数据
无需手动更新,因此您还可以想象一个后端,其中的销量数据由更新前自动生成和合并的子集组成。Sales Portal 应用程序的显示效果与电子表格的表示相同,但 Sales Portal 现在还具有:
- 通过普遍存在的浏览器利用全球互联网进行访问的能力
- 多个用户同时访问的能力
- 可选的自动后端数据聚合和合并
如果您打算使用标准的企业技术(比如一个基于 Java™的工具链)设计、编码和部署这样一个系统,则需要大量的工作。Meteor 显著减少了这一工作,您可在后面的练习代码中看到。
反应性思维
电子表格的一个重要特性就是它的反应性。在 Sales by Region 示例中,当一个地区销量数据更新时,依赖于该数据的其他所有数据都会动态地重新计算。如果依赖的组件要呈现图形输出,比如饼图,则会立即使用更新的切片大小重新绘制该图。您无需编写管理依赖关系的代码(这可能很复杂)或更新饼图等组件的代码。您只需声明这些反应性元素(销量数据)和它们的依赖关系(在本例中为总销量和饼图)。电子表格负责处理所有事情。
现在想象使用一个 Web 应用程序执行相同的操作,您就会很好地了解 Meteor 提出的方法如何简化基于 Web 的系统创建。
设计一个 Meteor 应用程序时,您需要确定反应性元素,比如地区销量数据集合。然后,使用标准 HTML、CSS、客户端 JavaScript 库和组件(比如 jQuery、jQuery UI 或 Underscore),以及 Handlebars 等模板技术(在概念上类似于 JavaServer Pages,通常在客户端上运行;请参见 参考资料),布局您的表示层。Meteor 跟踪反应性元素的所有依赖关系,然后重新呈现视觉元素,并重新计算依赖关系,以反映最新更新的值。
此方法大大减少了您需要编写、调试和测试的基础架构代码量。您无需编写自定义的后端 Web 服务来同步更新请求,无需编写代码来更新数据库或数据存储,无需编写代码将更改通知推送到其他连接的客户端,也无需编写代码在收到通知后从后端抓取更新的值。
回页首
深入剖析 Sales Portal 代码
清单 1 显示了 sales.js 文件,它包含 Sales Portal 应用程序背后的所有服务器端和客户端逻辑。这是我需要为此应用程序编写的惟一的 JavaScript 代码。(可在代码 下载的 sales_nologin 目录中找到 sales.js。)
清单 1. Sales Portal 的客户端和服务器端逻辑:sales.js
Sales2013 = new Meteor.Collection("regional_sales"); if (Meteor.is_client) { Template.salesdata.dataset = function () { return Sales2013.find({}); }; Template.datapoint.selected = function () { return Session.equals("selected_datapoint", this._id) ? "selected" : ''; }; Template.datapoint.events = { 'click': function () { Session.set("selected_datapoint", this._id); } }; Template.salesdata.rendered = function() { $('.editable').editable(function(value, settings) { Sales2013.update(Session.get("selected_datapoint"), {$set: {total: parseInt(value)}}); return(value); }, { type : 'text', style : 'inherit', width : 100, submit : 'OK', }); var cur = Sales2013.find(); if (cur.count() === 0) // do not render pie if no data return; var data = []; cur.forEach( function(sale) { data.push( [sale.region, sale.total]); }); var plot1 = $.jqplot ('chart', [data], { seriesDefaults: { // Make this a pie chart. renderer: $.jqplot.PieRenderer, rendererOptions: { // Put data labels on the pie slices. // By default, labels show the percentage of the slice. showDataLabels: true } }, legend: { show:true, location: 'e' } } ); } } if (Meteor.is_server) { Meteor.startup(function () { Sales2013.remove({}); Sales2013.insert({region:"US East", total: 2032333}); Sales2013.insert({region:"US Central", total: 150332}); Sales2013.insert({region:"US West", total: 1202412}); Sales2013.insert({region:"Asia Pacific", total: 701223}); }); }
观察 清单 1 中围绕 Meteor.is_client
和 Meteor.is_server
变量的条件。这些是 Meteor 核心提供的运行时上下文指标,可用在您代码中的任何地方。在本例中,它们支持将客户端和服务器端代码组合到同一个 source.js 文件中。条件外部的任何代码都同时在客户端和服务器上运行。
您也可以将客户端和服务器源代码完全分开,将客户端代码放在一个名为 client 的子目录中,将服务器端代码放在一个名为 server 的子目录中。在这种情况下,可将客户端和服务器都需要的共有内容放在一个名为 public 的子目录中。Sales Portal 应用程序的一个更加安全的版本(您在本文后面将会看到)使用了这种目录结构。
识别反应性数据
Sales Portal 应用程序的一个反应性数据源是对 Sales2013
Meteor 集合的一次查询。可在清单 1 中的这个客户端代码段中看到它的用法:
Template.salesdata.dataset = function () {
return Sales2013.find({});
};
因为该查询是反应性的,所以它的所有依赖关系都会在查询结果集更改时重新计算或重新呈现。这是在所有浏览器实例中更新销量数据和饼图的方式。清单 2 给出了相关的 HTML 模板代码,可在代码 下载的 sales_nologin 目录中的 sales.html 文件中找到:
清单 2. 客户端 HTML 和模板:sales.html
<head> <title>Sales by Region</title> </head> <body> <div id="title"> <h1>Global Sales 2013</h1> </div> <div id="container"> <div id="salestable"> {{> salesdata}} </div> <div id="chart"> </div> </div> </body> <template name="salesdata"> <div class="salesdata"> {{#each dataset}} {{> datapoint}} {{/each}} </div> </template> <template name="datapoint"> <div class="datapoint {{selected}}"> <span class="region">{{region}}</span> <span class="sales editable">{{total}}</span> </div> </template>
清单 2 中的 HTML 文件是一个 Handlebars 模板,Meteor 目前支持该模板。在 {{ }}
中可以看到 Handlebars 表达式。通过它的 Spark 引擎(将在本文的 现代 Web 应用程序的一种架构 一节中描述),Meteor 可处理其他 JavaScript 模板组件。
销售数据行通过 salesdata
模板代码呈现,这些代码已在 清单 2 中以粗体形式显示。此模板依赖于 dataset
帮助函数(如 清单 1 中所示),所以在每次查询发生更改时都会重新呈现。
在服务器上提供抽样数据
Sales Portal 的最初的地区销量数据由清单 3 中所示的服务器端代码(来自 清单 1 )提供:
清单 3. 提供 MongoDB 中的数据的服务器端代码
if (Meteor.is_server) { Meteor.startup(function () { Sales2013.remove({}); Sales2013.insert({region:"US East", total: 2032333}); Sales2013.insert({region:"US Central", total: 150332}); Sales2013.insert({region:"US West", total: 1202412}); Sales2013.insert({region:"Asia Pacific", total: 701223}); }); }
在 Meteor 服务器上,运行着一个完整的 MongoDB 实例。这个完整实例可接受来自 Meteor 以外的客户端的查询和更新。
在 Meteor 客户端上,可以使用相同的 JavaScript MongoDB API。这统一了客户端和服务器编码,实现了客户端和服务器上的代码重用。客户端 API 由一个称为 Minimongo 的智能 stub 提供。Minimongo 使用 延迟补偿来反映数据库更改。因为 Minimongo 通常处理小型的客户端数据集,所以它不支持索引。
使用一种发布 - 订阅模型来控制 MongoDB 服务器与 Minimongo 客户端之间同步的数据。默认情况下,所有服务器端 Meteor 集合都会被发布。Meteor 使用 DDP(Distributed Data Protocol,分布式数据协议)在客户端与服务器之间移动数据。(可为其他数据库创建 stub 并提供程序形式的 DDP 驱动程序;Meteor 社区的持续工作包括一个即将推出的 MySQL 驱动程序。)
集成 jQuery 插件
Sales Portal 使用 jqPlot jQuery 插件呈现饼图。饼图的呈现和重新呈现是反应性的,由 Sales2013
集合中的数据更改推动。您之前已经看到,每次 Sales2013
集合更改时,都会重新呈现 salesdata
模板。清单 4 显示了在 salesdata
模板的 rendered
事件触发时,重新绘制饼图的客户端函数(来自 清单 1 ):
清单 4. 使用 jqPlot 插件呈现饼图的 jQuery 代码
Template.salesdata.rendered= function() { $('.editable').editable(function(value, settings) { Sales2013.update(Session.get("selected_datapoint"), {$set: {total: parseInt(value)}}); return(value); }, { type : 'text', style : 'inherit', width : 100, submit : 'OK', }); var cur = Sales2013.find(); if (cur.count() === 0) // do not render pie if no data return; var data = []; cur.forEach( function(sale) { data.push( [sale.region, sale.total]); }); var plot1 = $.jqplot ('chart', [data], { seriesDefaults: { // Make this a pie chart. renderer: $.jqplot.PieRenderer, rendererOptions: { // Put data labels on the pie slices. // By default, labels show the percentage of the slice. showDataLabels: true } }, legend: { show:true, location: 'e' } } ); }
Sales Portal 使用 Jeditable jQuery 插件实现销量数据的就地编辑 (in-place editing)。处理编辑的代码位于 清单 4 中的Template.salesdata.rendered = function()
和 var cur = Sales2013.find();
行之间。
请参见 参考资料,获取 jQuery、jqPlot 和 Jeditable 插件的更多信息。
理解 Meteor 的电子表格和脚本加载顺序
要成功加载 jQuery 插件,按照正确的顺序加载与它们有关联的 CSS 文件和 JavaScript 代码至关重要。
请注意,在 清单 2 中,sales.html 文件不包含任何 <script>
标记或 <link type="text/css" ... >
样式表。相反,Meteor 通过扫描目录来自动加载客户端脚本和样式表:从最深的目录开始,然后在每个目录中按字母顺序依次扫描。
为了利用这个加载顺序(和脚本或 CSS 文件的名称无关紧要的事实),我重新命名了一些脚本,以确保它们的加载位置不变。例如,jquery.jeditable.mini.js 重命名为 client/js 目录下的 zjquery.jeditable.mini.js,以确保它最后被加载。我还将 jqplot.pieRenderer.min.js 重命名为 yjqplot.pieRenderer.min.js,以确保它在 jquery.jqplot.min.js 之后加载。我将来自插件的 CSS 文件放在 client/css 子目录中,确保它们首先被加载。
回页首
提高 Sales Portal 的访问安全
目前为止,任何拥有 Sales Portal URL 的人都可看到甚至更改销量数据。尽管这对实际用例而言权限太宽松了,但 Meteor 所支持的默认原型模式是您应用程序最初快速发展的理想选择。在此阶段,您可在快速迭代过程中修改交互、UI 甚至是应用程序逻辑,这通常不会涉及任何敏感数据。在拥有原型设计阶段的开放访问模型下,您能够与协作开发人员和审核用户共享该 URL,从而收集反馈。
下一个自然步骤是打开 Meteor 的安全特性,锁定应用程序。Sales Portal 的一个安全得多(因此更符合实际)的版本的代码可在 下载部分的 sales 子目录中找到。它添加的安全特性包括:
- 一个用于身份验证系统,仅允许授权的用户访问门户
- 确保仅一个地区的销量数据的官方所有者可修改该数据的代码
- 更好的源代码组织,用于确保没有向部署的客户端公开服务器端代码
从现在开始,我所指的 Sales Portal 应用程序都是 sales 目录中的安全版本。
删除客户端修改服务器数据的能力
锁定应用程序的一个不错起点是阻止任何人修改该数据。在这种情况下,您需要做的是使用以下命令删除 insecure
Smart Package:
meteor remove insecure
基本上,insecure
Smart Package 会告诉服务器在读取或更改数据之前不要检查访问规则。这个 Smart Package 默认情况下已经安装,它允许进行所有访问。删除它之后,任何客户端都不可以修改任何服务器数据。如果回到某个浏览器实例并尝试修改任何销量数据,您会注意到,虽然应用程序会尝试更改该数据,但它很快会撤销此操作,这反映了来自服务器的拒绝访问。(这是 延迟补偿的一个实际应用示例。该数据会在客户端中更新片刻,但一旦经过授权的服务器副本到达,就会覆盖客户端的数据。)
删除 insecure
包之后,您必须添加访问规则来显式允许(特定用户)访问特定的数据片段。但目前还没有用户。接下来必须添加一个用户数据库和登录授权系统。
确保只有授权的用户才能查看销量数据
添加用户授权系统之前,确保没有人可以看到销量数据。(授权的用户被允许在以后看到它。)现在,即使他们无法修改该数据,仍然可通过访问 Sales Portal URL 来查看它。
删除默认的 autopublish
Smart Package,预防任何 Meteor 集合数据从服务器发布到客户端(除了服务器显式发布的数据和客户端显式订阅的数据):
meteor remove autopublish
如果现在访问 Sales Portal URL,地区销量数据和饼图是不可见的。
通过 accounts
Smart Package 添加用户登录名
Meteor 提供了 Smart Package 来简化用户登录和授权系统的添加。accounts
Smart Package 涵盖端到端工作流;它包含所需的前端 UI、后端数据库和客户端到服务器 API。您可以使用一个命令将所有这些特性添加到 Sales Portal:
meteor add accounts-password accounts-ui
account-password
Smart Package 支持通过熟悉的电子邮件地址加密码的登录方式来创建用户和登录名。该实现使用了 Secure Remote Password 协议(参阅 参考资料),明文密码绝不会在客户端与服务器之间发送。
除了基于密码的登录,也可让用户通过 Facebook、Twitter、微博、GitHub、Google 和 Meetup 登录,只需向应用程序添加一个或多个 Smart Package 即可。社交网络 OAuth 的登录支持目前在企业内部网环境中可能不是很有用,但这些特性对面向消费者的 Web 或移动应用程序很有价值。
用于登录的插入式 UI
accounts-ui
包提供了一组预先构建的 CSS 样式的 UI 小部件(和支持性 JavaScript 代码),以处理用户登录、新用户创建和密码丢失恢复。要添加它们,可添加{{loginButton}}
Handlebars 帮助器。清单 5 显示了添加到 Sales Portal 应用程序的 sales/sales.html 文件中的登录系统:
清单 5. 添加一个用户登录和授权系统
<body> <div id="title"> <div class="header"> <div class="span5"> <h1 style="margin-bottom: 0px">Sales Portal</h1> </div> <div class="span5"> <div style="float: right"> {{loginButtons align="right"}} </div> </div> </div> </div>
代码 下载中的 sales 目录包含具有用户访问控制权的已完成的 Sales Portal 应用程序。可运行此版本来尝试登录。启动一个浏览器实例,请注意,现在右上角有一个 Sign in链接。单击它就会看到如图 5 所示的对话框:
图 5. 来自 accounts-ui
Smart Package 的登录对话框(在 Firefox 中显示)
accounts Smart Package 使用 Meteor 集合和发布 - 订阅(您也可以在子集的代码中手动使用相同的工具)来实现用户数据库。在当前的 Sales Portal 版本中,我在该数据库中创建了两组用户凭据,如表 1 所示:
表 1. 现有的 Sales Portal 用户凭据
电子邮件 | 密码 |
---|---|
joe@dwtestonly.com | abc123 |
sing@dwtestonly.com | abc123 |
打开两个浏览器实例并分别使用一个凭据登录。
可单击 Sign in对话框中的 Create account链接创建更多用户。图 6 显示了 accounts-ui
Smart Package 中包含的创建新用户的对话框:
图 6. accounts-ui
Smart Package 中创建新用户帐户的对话框(在 Chrome 中显示)
向地区销量数据添加 owner 字段
在当前的 Sales Portal 版本中,最初的数据库内容已被修改。我使用了清单 6 中的服务器端代码来提供该数据:
清单 6. 服务器端数据提供代码
Sales2013.remove({}); Sales2013.insert({region:"US East", total: 2032333}); Sales2013.insert({region:"US Central", total: 150332, owner: joe._id}); Sales2013.insert({region:"US West", total: 1202412}); Sales2013.insert({region:"Asia Pacific", total: 701223});
将一个新 owner
字段添加到 清单 6 中。在本例中,owner
字段包含拥有 US Central 地区数据的用户的 userId
(joe@dwtestonly.com)。这个字段用于将地区销量数据更新仅限制在 joe@dwtestonly.com。可查询 Meteor.users
集合来获取 userId
的值。
细粒度的选择性服务器数据发布
删除 autopublish
Smart Package 之后,有必要显式地从服务器发布数据,并显式地从客户端订阅它。
对于 Sales Portal,服务器使用清单 7 中的代码发布 global_sales
集合,该代码包含在 sales/server/sales.js 文件中:
清单 7. 从服务器选择性地发布数据
Meteor.publish("global_sales", function () { if (this.userId) { // only visible to logged in users // do not include the owner field for client access return Sales2013.find({}, {fields: {"region": 1, "total":1 }}); } });
请注意,在 清单 7 中,使用了 this.userId
确保有效的用户登录到客户端会话。当 Meteor 代表用户执行服务器代码时,this.userId
始终包含当前已登录用户的惟一 ID。如果当前浏览器实例没有已登录的用户,则 this.userId
是空的,并且没有发布任何数据。而且,在 清单 7 中,不是一个地区销量数据文档(一个 文档实质上是一条包含 MongoDB 实例中可变数量的字段的记录)中的所有字段都会在发送给客户端的集合中返回。具体而言,文档的 owner
字段已对客户端隐藏。这样,在使用一个查询时,您只能发布包含某个字段子集的集合子集,只能将它发布到具有授权的已登录用户的客户端。此技术对确保客户端浏览器无法访问某些文档中的敏感数据字段至关重要。
客户端数据订阅
Sales Portal 客户端代码显式订阅服务器发布的 global_sales
集合,如清单 8 所示:
清单 8. 客户端订阅来自服务器的一个集合
Meteor.subscribe("global_sales"); Template.salesdata.dataset = function () { return Sales2013.find({}); };
添加访问规则以允许更新地区销量数据
删除 insecure
Smart Package 后,实际上会拒绝所有用户更新销量数据。假设不同的地区销量数据归不同的用户所有,可添加一条访问规则来允许 joe@dwtestonly.com 更新 US Central 数据。清单 9 给出了名为 model.js 的服务器端源文件中的这条访问规则:
清单 9. 允许所有者更新销量数据的服务器端访问规则
Sales2013.allow({ update: function (userId, sales, fields, modifier) { if (userId !== sales.owner) return false; // not the owner var allowed = ["total"]; if (_.difference(fields, allowed).length) return false; // tried to write to forbidden field return true; }, });
如果允许更新,update
操作的访问规则函数会返回 true
,否则会返回 false
。清单 9 中的代码首先执行检查,确保用户是所有者并且仅修改了 total
字段。
打开 Sales Portal 并以 joe@dwtestonly.com 身份登录。尝试修改 US West 数据,您可能注意到它会失败,随后尝试修改 US Central 数据。因为 joe@dwtestonly.com 是此数据的所有者,所以您可修改它。
启动另一个浏览器实例并以 sing@dwtestonly.com 身份登录。尝试修改任何销量数据,您可能注意到它会失败。因为 sing@dwtestonly.com 不是任何销量数据的所有者,所以服务器会拒绝来自该用户的所有修改请求。
如果用户未登录,可使用客户端 currentUser
函数避免呈现模板。将清单 10 中的代码添加到 HTML 文件中:
清单 10. 消除呈现空模板的尝试
<div id="container"> <div id="salestable"> {{#if currentUser}} {{> salesdata}} {{/if}} </div> <div id="chart"> </div> </div>
现在,启动一个新的 Sales Portal 浏览器实例。请注意,您无法看到任何数据。以 sing@dwtestonly.com 身份登录,您会注意到,现在您可以看到数据和饼图。再次尝试修改一个字段;您任然无法修改它,因为您不是所有者。
启动另一个浏览器实例,以 joe@dwtestonly.com 身份登录,您可能注意到,现在您可以看到该数据。修改 US East 数据,饼图也会被更新。确认 sing@dwtestonly.com 会话中的饼图已经发生更改。注销两个会话,您可能注意到,该数据现在消失了。
回页首
应用程序部署:云和私有云
为了简化部署,消除您在创建演示或试验 Meteor 时设置自己的服务器的需求,Meteor 团队设计了单个命令,将应用程序部署到其云托管服务器上。在编写本文时,此服务是免费的。您需要做的是从您的应用程序目录发出此命令:
meteor deploy applicationname.meteor.com
应用程序名称必须是惟一的,因为它将以 http://applicationname.meteor.com 格式(通过互联网向全球)公开。本文的应用程序部署在 Meteor.com 上;请参见 参考资料,获取它们的链接。
如果希望用户能够通过您自己公司的域名访问您的应用程序,比如通过 http://applicationname.mycompany.com/,那么您需要创建一个 CNAME(别名)DNS 记录,并将它指向 origin.meteor.com。
要在您自己的服务器基础架构上托管应用程序,需要一个预先安装了 node.js 和 MongoDB 工具的服务器。可使用以下命令创建应用程序的一个可部署软件包:
meteor bundle applicationname.tgz
在编写本文时,Meteor 0.6.3.1 要求在与最终的部署系统相同的操作系统上创建自部署软件包。这个要求源于原生编译代码的依赖性。
Meteor 服务器端代码在 node.js fiber上运行,提供了一个虚拟环境,您可以在该环境进行编码,就像一个线程处理一个传入请求(具有不可共享的状态)那样。此方法可简化服务器端 JavaScript 逻辑的编码。
因为服务器端可部署对象是一个 node.js 应用程序,所以您可以针对自己的具体的互操作或扩展需求来自定义部署拓扑结构。
回页首
Foto Share:一种移动照片共享服务
现在您已看到使用 Meteor 代码可进行的一些设计和规划,您可能已经在考虑启动一两个项目。下面的示例为您提供了在为移动设备创建 Meteor 应用程序时可采用的更多想法。
Foto Share 是一个面向移动电话用户的具有实验性质的照片共享服务,用法既简单又直观:用户可使用他们的电话浏览照片集合,并单击 Share 按钮与其好友分享照片。图 7 显示了在 Apple iPhone 上运行的 Foto Share:
图 7. Apple iPhone 上的 Foto Share
就像在 Sales Portal 项目中一样,出于同样的安全原因,autopublish
和 insecure
Smart Package 已从 Foto Share 中删除。而且为了实现基于密码的登录,Foto Share 添加了 accounts-ui
和 accounts-password
Smart Package。代码 下载 中的 Foto Share 应用程序也添加了两个与 Sales Portal 相同的用户。
要试用 Foto Share,首先需要从代码 下载 中的 fotoshare 目录运行该应用程序。如果有两个电话,可将每个电话的浏览器指向 Foto Share。否则,可继续使用 PC 浏览器。以 sing@dwtestonly.com 身份在一个浏览器上登录,以 joe@dwtestonly.com 身份在另一个浏览器上登录(使用用于 Sales Portal 的相同密码)。可以通过滑动 Sing 的照片来浏览它们,或者触摸任意一侧的覆盖图。每张新照片会划入视图中。您会发现,Sing 的所有图片都是来自夏威夷的场景,而 Joe 的照片是阿兹特克和玛雅的。
准备好测试共享后,选择从 Joe 的集合中选择一张图片并触摸电话上的 Share 按钮。在 Sing 的电话上,Meteor 会反应性地更新订阅的集合,Joe 共享的图片现在可在 Sing 的电话上看到。
Foto Share 用户登录 UI
Meteor 目前还没有一个针对移动设备的 accounts-ui
Smart Package。像 Meteor Web 应用程序一样,能够 “插入” 一个可自定义的移动登录 UI 会很不错。对于 Foto Share,我使用了 accounts-ui
Web UI。图 8 显示了一个移动电话上出现的登录对话框:
图 8. Foto Share 登录屏幕
用户通常不会介意每次访问 Web 应用程序都进行登录,但他们期望在首次输入密码后,任何电话应用程序会代表他们自动登录。一个针对移动 Meteor 应用程序的可能解决方案是,将客户端代码包装在一个原生应用程序包装器中,比如流行的 Apache Cordova 平台(参见 参考资料)。然后可以访问电话自己的登录配置文件,避免每次都要求用户登录。此外,您可以直接访问存储在电话的内置图库或相册应用程序中的用户照片集合。
集成 jQuery Mobile 和页码插件
利用移动浏览器与 HTML5 的兼容性越来越高,在 jQuery Mobile 框架上开发的应用程序通常可在最新的主流移动操作系统上运行。Sales Portal 应用程序已展示了 Meteor 如何与 jQuery 和 UI 插件(比如 jqPlot)集成。与之前一样,您需要留意 jQuery Mobile 和分页插件库的 CSS 样式表和 JavaScript 文件的加载顺序。
因为 jQuery Mobile 和 Meteor 的 LiveHTML(反应性重新呈现)需要通过浏览器的文档对象模型 (DOM) 增强和操作 HTML 元素,所以您必须确保它们按照可兼容的顺序进行操作。在 Foto Share 中,jQuery Mobile 执行的页面初始化必须延迟到 Meteor 的操作完成之后。aadelaybind.js 文件(包含以下代码)在 jQuery Mobile 之前加载,以延迟其页面初始化:
$(document).bind("mobileinit", function(){ $.mobile.autoInitializePage = false; });
在 Meteor 的 LiveHTML 完成它的工作后,pages
模板的 rendered
事件帮助器会触发实际的页面初始化。这个帮助器函数的相关部分如下:
Template.pages.rendered = function() { ... $.mobile.initializePage(); ... };
识别 Foto Share 中的反应性数据
在概念上,被设计为最自然的反应性的数据集合是用户的照片集。这么做会使得 Meteor 在每次有人共享其照片时都更新和重新呈现照片列表。这是实验中采用的方法。对于更大的系统,根据图像的后端存储架构,您可能希望仅将照片的元数据而不是图像本身设计为反应性的。
查看服务器端数据提供代码(如清单 11 所示),以了解照片的存储方式:
清单 11. Foto Share 服务器端数据提供和集合发布代码
Meteor.startup(function () { ... Fotos.remove({}); Fotos.insert({name:"pic1", img: readPic('pic1.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic2", img: readPic('pic2.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic3", img: readPic('pic3.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic4", img: readPic('pic4.jpg'), owner: joe._id, shared:false}); Fotos.insert({name:"pic5", img: readPic('pic5.jpg'), owner: joe._id, shared:false}); Fotos.insert({name:"pic6", img: readPic('pic6.jpg'), owner: joe._id, shared:false}); Meteor.publish("photos", function () { if (this.userId) { // only visible to logged in users return Fotos.find( {$or : [{owner: this.userId}, {shared: true}]}, {fields: {"name": 1, "img":1 , "owner": 1}}); } }); });
服务器发布的集合是 Fotos
。Fotos
中表示照片的每个文档都拥有 name
、img
和 owner
字段。img
字段是从相应的本地存储的 JPG 文件中读取的。同样请注意,一个订阅客户端收到的数据仅包含他或她自己的照片,加上它的所有者共享的其他任何照片。这里还应用了选择性的字段过滤,从客户端收到的 Foto 集合中删除 shared
字段。owner
字段未过滤掉,因为客户端可能希望显示某个共享图片的所有者名称。清单 12 给出了 readPic()
帮助器函数。它使用同步 fs 来将图像读取到内存中,然后将二进制系统编码为 base64,以便存储在 img
字段中。此格式可方便地用于对从客户端获取照片后显示照片。
清单 12. 读取用于 MongoDB 存储的 JPG 图像的帮助器函数
function readPic(infile) { var fs = Npm.require('fs'); var path = Npm.require('path'); var base = path.resolve('.'); var deployLoc = 'public/images/' var data = fs.readFileSync(path.join(base, deployLoc, infile)); var tp = data.toString('base64'); return 'data:image/jpeg;base64,' + tp; }
当从数据库再现图像时,模板代码会利用大多数现代浏览器上的 <IMG>
标记中的 数据 URL支持(参见 参考资料)。<IMG>
标记的 SRC
属性上的数据 URL 支持通过一个 base64 编码的字符串实现了图像的二进制位的动态设置。清单 13 显示了 photopage
模板的一部分。在这个模板中,来自照片的 base64 编码的 img
字段用于在一个 jQuery Mobile 页面上呈现该图像。
清单 13. 使用数据 URL 设置一个 IMG 标记的 SRC
属性
<template name="photopage"> <div data-role="page" id="p{{index}}"> ... <div data-role="content" class="apic"> <img src="{{img}}" /> <ul data-role="pagination"> {{#if indexIsZero}} <li class="ui-pagination-next"><a href="#p{{indexNext}}">Next</a></li> {{else}} <li class="ui-pagination-prev"><a href="#p{{indexPrev}}">Prev</a></li> <li id="x{{index}}" class="ui-pagination-next"> <a href="#p{{indexNext}}">Next</a></li> {{/if}} </ul> </div> <!-- /content --> ... </div> <!-- /page --> </template>
回页首
Meteor Remote Methods:自定义 RPC 非常简单
尽管 insecure
Smart Package 已被删除,但仍未在 Foto Share 中创建访问规则。没有访问规则,任务客户端都无法通过 Minimongo 访问数据。但是,当单击 Share 按钮时,照片的 share
字段必须通过某种方式进行更新。如何更新呢?答案是 Meteor Methods。
Meteor Methods 是一种远程过程调用 (RPC) 机制。只需两个简单的步骤,就可以创建从客户端到服务器的 RPC 调用:
- 在服务器端定义一个 JavaScript 函数。
- 使用
Meteor.call()
远程调用该服务器函数,还可以有选择地传递参数。
Meteor 会负责这期间的所有端点设置、准备和数据编组工作。
单击 Foto Share 中的 Share 按钮时,客户端调用了服务器上一个名为 shareThisPhoto
的 Meteor Remote Method,将照片的 ID 以参数形式传递。在服务器端,代码首先检查调用方是否是照片的所有者,它仅在所有者调用该方法时更新照片的 shared
字段。清单 14 给出了服务器端 shareThisPhoto
代码:
清单 14. 服务器端上更新照片的 shared
字段的 Meteor Remote Method
Meteor.methods({ shareThisPhoto: function (photoId) { console.log('called shareThisPhoto'); console.log(photoId); var curPhoto = Fotos.findOne({_id: photoId}); if (this.userId !== curPhoto.owner) { return "Cannot share this photo."; } else { Fotos.update({_id: photoId}, {$set :{shared: true}}); return "Photo shared!"; } }, });
清单 15 给出了在您单击 Share 按钮时,调用该远程方法的客户端代码:
清单 15. 单击 Share 按钮时调用远程方法的客户端代码
Template.photopage.events({ 'click .fs-logoff': function () { Meteor.logout(function() { location.reload(); }); }, 'click .fs-share': function() { Meteor.call('shareThisPhoto', this._id, function (error, retval) { console.log(retval); }); } });
我选择了 RPC 方法来演示 Meteor Remote Method。也可以定义一条访问规则来允许所有者更新 shared
字段。在这种情况下,当用户单击 Shere 按钮时,您必须随后在本地更新照片的 shared
字段。Meteor 的 Minimongo 将更新推送到服务器,然后将其推送到其他所有已订阅的客户端。
回页首
现代 Web 应用程序的一种架构
现在您已查看了本文的示例应用程序,您应该已经认识到,Meteor 是为具有图 9 中所表示的特定架构的 Web 应用程序而设计的:
图 9. 富客户端交互式 Web 应用程序架构
这些类型的应用程序通常包含一个高度交互式的单页 UI。用户通常不会遇到新页面加载;所显示页面的一部分会立即更新来响应用户交互,具有很短的或者甚至没有网络往返延迟。单页界面绝不会限制应用程序,因为页面的各部分能够以多种多样的方式进行更新。这暗指一种独立的桌面应用程序,比如文字处理器或电子表格。
这些类型的应用程序通常会在客户端浏览器上加载 JavaScript 应用程序代码。此代码会管理与用户之间的交互,通过动态操作浏览器的 DOM,修改 CSS 样式,生成新的 HTML 元素 / 代码 / 样式,以及利用其他浏览器提供的 API。与用户的所有交互都由客户端代码控制,除了初始应用程序加载之外,不会通过网络加载额外的 HTML 或样式。相同的代码还会在客户端与服务器之间来回传输数据,以实现应用程序特性。浏览器实质上加载和运行的是一个使用 JavaScript 编写的富客户端(有时称为胖客户端)应用程序。
在服务器端,设置了端点从客户端安全地获取和同步数据。遗留的后端可能拥有 RPC、基于 XML 的 Web 服务、RESTful 服务或其他 JSON 样式的 RPC 调用。现代的后端可能提供了专用的协议,旨在高效地通过网络传输数据,在偶尔断开连接后迅速恢复,支持各种流行的传输方式,灵活地伸缩拓扑结构。
图 10 显示了 Meteor 0.6.3.1 版内部的主要组件:
图 10. Meteor 内部组件
您已知道,DDP 提供了客户端与服务器实例之间的双向数据流,Minimongo 是一个在客户端上具有本地数据缓存的智能 stub ,向客户端代码提供了熟悉的 MongoDB 查询 API。Spark 是一个流行的 HTML 引擎,它使用 (Handlebars) 模板和依赖关系管理来提供 Meteor 的反应性重新呈现特性。(请参见 参考资料,获取官方 Meteor 文档的链接,您可在其中找到 Meteor 组件的详细说明。)
潜在的大数据应用程序架构
Meteor 的单页、实时、高度交互式的模式几乎是为解决某一类问题而量身定做的。可视化大数据的某一方面离不开交互式仪表板,交互式仪表板在结果可用时立即执行更新。该仪表板也可用于对 MapReduce 作业进行排队并监视它们的实时进度。
一种反应性 Meteor 应用程序可潜在地提供数 TB 或 PB 数据的交互式界面,就像电子表格可提供相对较小的数据集的交互式的合并、摘要、下钻或自定义视图一样。图 11 显示了这样一个系统的架构:
图 11. 一个用于大数据的 Meteor 仪表板应用程序的架构
在 图 11 中,客户端 Meteor 代码提供了大数据存储库的一个反应性仪表板视图。用户交互会在服务器上生成自定义的 MapReduce 任务,它们可能通过 Meteor Methods 进行排队并在 Hadoop 集群上执行。执行完任务后,结果会通过 Meteor 合并到 MongoDB 实例中。Meteor 服务器的发布 - 订阅组件会检测数据更改,将更新的摘要数据推送到已订阅的客户端。
原文地址:http://www.ibm.com/developerworks/cn/web/wa-meteor-webapps/index.html
声明: 此文观点不代表本站立场;转载须要保留原文链接;版权疑问请联系我们。