位于达拉斯的 IBM 全球解决方案中心团队为零售银行业务开发了一个移动应用程序。该应用程序包含许多零售银行业务功能,比如支行和 ATM 位置、联系人、帐户余额和活动、转账等。设计该应用程序是为了将它用于和部署到 iOS 和 Android 智能电话上,该应用程序是以一种混合方式使用 IBM Worklight Studio 构建的。Dojo Mobile(一个开发跨平台移动 Web 应用程序的 JavaScript 框架)被用作实现移动用户界面和 Worklight 小部件的主要工具包。
本文介绍移动应用程序的设计和开发的重要的技术方面,包括使用 Dojo Mobile 设计和实现移动用户界面、Worklight 项目结构和环境优化、使用 Dojo 减少开发特定于环境的工件的工作,以及开发 Worklight 适配器(用于将应用程序与企业系统集成的一个基本组件)的步骤。与企业系统的集成将在包含的样例代码中演示,该样例代码使用一个 REST 服务来模拟银行系统中的帐户信息。本文还将探讨如何使用 Worklight 身份验证实现一种保护资源(比如 Worklight 适配器)的安全机制。本文还提供了一些样例代码作为整个移动应用程序的一部分,用这些样例代码(在 Worklight Studio Developer Edition v5.0.0.0 上开发和测试)来演示相关操作。
以下信息可帮助您快速入门:
图 1 显示了 iPhone 上的银行应用程序的主屏幕。从这里,用户可访问账户信息并发起交易,查找 ATM 或银行分行,并取得联系以寻求帮助。
可以考虑两种常见的 UI 设计方法,使用户能够在功能之间导航:
- 选项卡栏方法 使用一组常见的功能,每个功能在大部分屏幕的选项卡栏上显示为一个按钮。用户点击一个按钮即可导航到特定的功能。此方法提供了常见功能的快速、一键式导航。缺点是需要为选项卡栏使用额外的屏幕空间,可用于功能区域的屏幕空间会有所减少。
- 功能列表方法以按钮的形式在屏幕上列出大部分(如果不是所有)功能。此方法的优点是每个功能有更多屏幕空间可供使用。主要的缺点是导航需要单击更多次,甚至常用的功能也是如此。
两种方法都已在这里介绍的样例移动应用程序中使用。功能列表方法如图 1 所示,其中以按钮的形式列出了 3 个主要功能(My Accounts、Locations 和 Contact)。当用户在身份验证后导航到 My Accounts 时,底部选项卡栏上会以按钮的形式列出 Accounts、Cash 和 Transfer 等常见功能,如图 2 所示。单击 More 选项卡会显示更多功能。
图 1. Home 屏幕
图 2. Accounts 屏幕
Dojo Mobile 是一个 HTML5 移动 JavaScript™ 框架,支持在现代移动设备上快速开发具有原生外观的移动 Web 应用程序,这些移动设备包括 iPhone、iPod Touch、iPad 以及 Android 和 RIM 智能电话和平板电脑等。使用混合方法,Dojo Mobile 应用程序可使用原生设备 API,比如 GPS 位置或条码扫描,可使用 IBM Worklight Studio 轻松地封装为原生应用程序。
图 3 显示了使用 Dojo Mobile 小部件实现的 Accounts 屏幕(本节中提到的所有小部件都包含在 dojox.mobile 包中):
- 总体视图(未显示)是一个 dojox.mobile.View,它的顶部包含一个 ScrollableView,底部包含一个 TabBar。
- 顶部的 ScrollableView 显示了一个可滚动的帐户列表,包含顶部的 Heading 和底部的 EdgeToEdgeList。
- Heading 显示标题 “Accounts” 并包含一个 Back 按钮和一个 ToolBarButton “Logout”。
- EdgeToEdgeList 包含一组 ListItem,每一项显示一个帐户摘要。
- 底部的 TabBar 包含一组 TabBarButton,每个按钮代表一项常见功能,比如 Accounts、Cash 和 Transfer。
图 3. 包含 Dojo Mobile 小部件的 Account 屏幕
清单 1 显示了用于实现此屏幕上的这些小部件的代码。
清单 1. 图 3 中的 Accounts 屏幕的代码
<div data-dojo-type="dojox.mobile.View" id="LoggedInView" class="mobileClass"
style="height: 100%;">
<div id="AccountsView" data-dojo-type="dojox.mobile.ScrollableView"
data-dojo-props="selected:true" class="mobileClass">
<h2 data-dojo-type="dojox.mobile.Heading"
data-dojo-props="fixed:'top', back:'Back', moveTo:'IndexView', fixed:'top'"
style="font-size: 19px;" align="center">Accounts
<div id="logoutBtn" data-dojo-type="dojox.mobile.ToolBarButton"
data-dojo-props="label:'Logout', moveTo:'IndexView',
transition:'slide',transitionDir:'-1'"
style="width: 45px; float: right;" class="mblColorBlue"></div>
</h2>
<ul id="accountContainer" style="margin-top: 40px;"
data-dojo-type="dojox.mobile.EdgeToEdgeList" class="indexListStyle">
<!-- List of Accounts will be populated at runtime using the custom widget template
defined under folder 'custom/AccountWidget' -->
</ul>
</div>
<ul data-dojo-type="dojox.mobile.TabBar" data-dojo-props="fixed:'bottom'" style="height:
11%; overflow-x: hidden;">
<li data-dojo-type="dojox.mobile.TabBarButton" id="accountsTab" style="width: 20%"
data-dojo-props="icon1:'./images/tabs/icon_128x128_accounts.png',
icon2:'./images/tabs/icon_128x128_accounts_blue.png', selected:'true'">Accounts</li>
<li data-dojo-type="dojox.mobile.TabBarButton" style="width: 20%"
data-dojo-props="icon1:'./images/tabs/icon_128x128_cash_gradient.png',
icon2:'./images/tabs/icon_128x128_cash_blue.png'">Cash</li>
<li data-dojo-type="dojox.mobile.TabBarButton" style="width: 20%"
data-dojo-props="icon1:'./images/tabs/icon_128x128_transfer_solid.png',
icon2:'./images/tabs/icon_128x128_transfer_solid_blue.png'">Transfer</li>
<li data-dojo-type="dojox.mobile.TabBarButton" style="width: 20%"
data-dojo-props=" icon1:'./images/tabs/icon_128x128_billPay.png',
icon2:'./images/tabs/icon_128x128_billPay_blue.png'">Bill Pay</li>
<li data-dojo-type="dojox.mobile.TabBarButton" style="width: 20%"
data-dojo-props="icon1:'./images/tabs/icon_128x128_more.png',
icon2:'./images/tabs/icon_128x128_more_blue.png'">More</li>
</ul>
</div> |
Worklight Studio 为跨平台移动应用程序提供了一个集成开发环境。Worklight 项目结构包含一个 apps 文件夹,其中可能包含一个或多个应用程序。一个应用程序可有多个 Worklight 环境,具体取决于该应用程序支持的平台。图 4 中的 OpenFNApp 包含一个 common 文件夹,以及用于创建要部署到 Android 和 iPhone 设备的应用程序的 android 和 iphone 环境。在在 Worklight 中创建启用了 Dojo 的应用程序时,会自动包含 dojo 文件夹。
图 4. Worklight 项目结构
图 5 中的 common 文件夹包含将在所有环境中共享的工件。它包含一个在应用程序启动时加载的主要 HTML 文件 (OpenFNApp.html),以及其他典型的 Web 资源文件夹,比如 js、css 和 images。
图 5. common 文件夹结构
应在 iphone 和 android 文件夹中适当地包含特定于环境的文件。这些文件夹具有与 common 文件夹相同的文件夹结构。例如,如果需要将一些样式应用到某个特定于设备的环境,那么可在适当的文件夹中创建一个与 common/css 文件夹中相应的文件同名的 CSS 文件。当构建原生项目时,Worklight 会在生成原生代码时将特定于环境的 css 文件附加到具有相同名称的通用 css 文件中。特定于环境的样式将优先于 common 文件夹中包含的样式。类似地,如果主要 HTML 文件位于 android 或 iphone 文件夹下,那么此 HTML 文件将取代 common 文件夹中的主要 HTML 文件,Worklight 会使用该文件来构建原生应用程序。具有相同名称的特定于环境的 JavaScript 文件将彼此附加。在这里介绍的应用程序中,由于 Dojo 中的跨平台功能,大部分代码都位于 common 文件夹中,并在 iphone 和 android 环境中共享。特定于环境的代码只在需要时才会添加。例如,android 和 iphone 环境中的 OpenFN.css 文件包含 AccountsView 的不同高度规则(清单 1),而所有通用的应用程序样式都是在 common/css/OpenFN.css 文件中定义的。
图 6. 特定于环境的文件
在应用程序中使用了一个自定义小部件 AccountWidget 来显示帐户信息。此小部件包含在 common/custom 文件夹中(请参阅 参考资料,了解实现自定义小部件的详细信息)。清单 2 给出了加载该自定义小部件的 JavaScript 代码。
清单 2. 加载自定义小部件的代码
<script>
var base = location.href.split("/");
base.pop();
base = base.join("/");
var dojoConfig = {
isDebug : false,
async : true,
parseOnLoad : false,
packages : [ {
name : "custom",
location : base + "/custom"
} ]
};
</script> |
对于跨平台应用程序开发,dojox/mobile/deviceTheme 模块(清单 3)会自动检测设备,并提供在该设备上呈现的必要样式表。这有助于实现应用程序的原生外观(无论使用何种平台),同时利用相同的代码库。
清单 3. 加载 Dojo 模块
<script type="text/javascript">
require(// module identifiers
["dojo/parser", "dojo/_base/connect", "dojox/mobile",
"dojox/mobile/deviceTheme", "dojox/mobile/compat",
"dojox/mobile/Button", "dojox/mobile/TabBar", "dojox/mobile/TabBarButton",
"dojox/mobile/ScrollableView"],
// Callback function invoked on dependencies evaluation results
function(parser, connect) {
dojo.ready(function() {
parser.parse();
dojo.style("content", "display", "");
connect.connect(dijit.byId("myAccountsListItem"), "onClick", null, getAccounts);
});
});
</script> |
Worklight 适配器为客户端应用程序提供单一的界面与后端系统进行通信。有多种类型的适配器,包括 HTTP 连接、SOAP 服务、SQL 数据库调用等。这些适配器均可受到保护和监视。
这里使用了一个 AccountsAdapter 来调用一个 REST 服务,以检索用户的帐户信息。该适配器包含以下文件(参加图 7):
- 一个配置 XML 文件用于指定连接选项,列出向客户端应用程序或其他适配器公开的过程。
- 一个 JavaScript 文件用于实现在配置 XML 文件中声明的过程。
- 可选的 XSL 文件用于转换检索到的原始 XML 数据。
图 7. Worklight 适配器文件
AccountsAdapter 的配置 XML 文件(清单 4)指定适配器需要的检索帐户信息的连接的连接细节(比如包含协议、域和端口的连接策略)。适配器资源使用 authenticationRealm 特性中定义的领域 (Realm) 来保护,这可确保在调用适配器过程之前对用户进行了身份验证。(身份验证保护将在下一节中进行设置。)
清单 4. AccountsAdapter 的配置 XML
<xml version="1.0" encoding="UTF-8"?>
<wl:adapter name="AccountsAdapter" authenticationRealm="AccountsAuthRealm"
. . . . . . .
<displayName>AccountsAdapter</displayName>
<description>AccountsAdapter</description>
<connectivity>
<!--The configuration below assumes the REST service is deployed
on localhost with port 9080 -->
<connectionPolicy xsi:type="http:HTTPConnectionPolicyType">
<protocol>http</protocol>
<domain>localhost</domain>
<port>9080</port>
</connectionPolicy>
<loadConstraints maxConcurrentConnectionsPerNode="2" />
</connectivity>
<procedure name="getAccounts" requiresAuthentication="true"/>
</wl:adapter> |
适配器 JavaScript 文件(清单 5)包含配置 XML 文件中定义的过程的实现代码。过程 getAccounts 调用了一个 REST 服务来检索一个经过验证的用户的帐户信息。客户端应用程序无需将用户信息传递给适配器过程,因为它可以在验证用户后使用 WL.Server.getActiveUser() 获取该信息。帐户信息 REST 服务是使用 WL.Server.invokeHttp 调用来调用的,这会以 JSON 格式将数据返回到客户端应用程序。
清单 5. 适配器 JavaScript 代码
function getAccounts() {
. . . .
var userObj = WL.Server.getActiveUser();
var serviceURL = "/OFNWebServices/rest/account/getAccounts";
. . . . .
return WL.Server.invokeHttp({
method : 'get',
returnedContentType : 'json',
path : serviceURL,
parameters : {
username : userObj.userId
}
});
} |
Worklight 适配器等实体可以使用一个身份验证流程来保护,该流程定义了在访问某个实体之前执行的步骤。AccountsAdapter 已受到保护,用户在使用后端 REST 服务检索帐户信息之前需要进行身份验证。Worklight 身份验证用于实现此安全措施:
- 首先,在 server/conf 文件夹中的 authenticationConfig.xml 文件中定义一个身份验证领域。这个名为 AccountsAuthRealm 的领域(清单 6)指定了 login-function 和 logout-function 特性。这些特性定义了在服务器端执行登录和注销功能时调用的适配器方法。
清单 6. authenticationConfig.xml 中的身份验证领域<realm name="AccountsAuthRealm" loginModule="AccountsAuthModule"> <className>com.worklight.integration.auth.AdapterAuthenticator</className> <parameter name="login-function" value="AuthenticationAdapter.onAuthRequired" /> <parameter name="logout-function" value="AuthenticationAdapter.onLogout" /> </realm>
- 然后在同一个 authenticationConfig.xml 文件中定义一个登录模块。在清单 7 中,AccountsAuthModule 使用了 com.worklight.core.auth.ext.NonValidatingLoginModule,表明身份验证将在适配器中的一个开发人员定义的流程中执行。
清单 7. authenticationConfig.xml 中的登录模块<loginModule name="AccountsAuthModule" canBeResourceLogin="true" isIdentityAssociationKey="false"> <className>com.worklight.core.auth.ext.NonValidatingLoginModule</className> </loginModule>
登录和注销功能是在名为 AuthenticationAdapter 的适配器中定义的(清单 8)。
清单 8. AuthenticationAdapter-impl.js 中的 onAuthRequired 和 onLogout 函数function onAuthRequired(headers, errorMessage){ errorMessage = errorMessage ? errorMessage : null; return { authRequired: true, errorMessage: errorMessage }; } function onLogout(){ WL.Logger.debug("Logged out"); }
- 在清单 9 中,过程 submitAuthentication 是在 AuthenticationAdapter.xml 文件中定义的。此过程将通过客户端应用程序调用来验证用户凭据。
清单 9. AuthenticationAdapter.xml 中的 submitAuthentication<?xml version="1.0" encoding="UTF-8"?> <wl:adapter name="AuthenticationAdapter" . . . . <procedure name="submitAuthentication"/> </wl:adapter>
过程 submitAuthentication(参见清单 10)获取用户名和密码,使用它们作为可向用户注册表验证的参数。如果成功,则会使用 WL.Server.setActiveUser() 方法创建 userIdentity 对象并将该对象放在活动用户池中。还会返回一个带有参数 authRequired:false 的 JSON 对象,指示验证获得成功。如果用户未通过身份验证,将返回带有参数 authRequired:true 的 JSON 对象,指示仍然需要进行身份验证。
清单 10. AuthenticationAdapter-impl.js 中的 submitAuthenticationfunction submitAuthentication(username, password) { if ((username=="wl" && password == "wl") || (username=="worklight" && password == "worklight") ){ WL.Logger.debug("Auth :: SUCCESS"); var userIdentity = { userId: username, displayName: username, attributes: {} }; WL.Server.setActiveUser("AccountsAuthRealm",userIdentity); return { authRequired: false }; }else{ WL.Logger.debug("Auth :: FAILURE"); return onAuthRequired(null, "Invalid login credentials"); } }
- 在服务器端完成身份验证后,AccountsAdapter 会通过为 AccountsAdapter XML 文件中的 authenticationRealm 特性指定领域 AccountsAuthRealm 来保护(清单 11)。
清单 11. AccountsAdapter.xml 中的 authenticationRealm<?xml version="1.0" encoding="UTF-8"?> <wl:adapter name="AccountsAdapter" authenticationRealm="AccountsAuthRealm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wl="http://www.worklight.com/integration" xmlns:http="http://www.worklight.com/integration/http"> . . . . <procedure name="getAccounts" requiresAuthentication="true"/> </wl:adapter>
现在,让我们看看在客户端上实现的身份验证。
- 系统会自动生成一个 JavaScript 文件 auth.js,以便提供实现 Worklight 身份验证的方法框架。init 函数(清单 12)用于初始化。为 Sign on 按钮(具有 ID submitAuthBtn)的单击事件定义了一个事件处理函数。该事件处理函数通过传递来自输入字段的用户名和密码来调用身份验证适配器过程 AuthenticationAdapter.submitAuthentication。类似地,Logout 按钮(具有 ID logoutBtn)的单击事件处理函数也是通过调用 WL.Client.logout(‘AccountsAuthRealm’) 来定义的。
清单 12. js/auth.js 中的 init 函数init : function() { // Authenticator initialization code require([ "dojo", "dojo/_base/connect" ], function(dojo, connect) { connect.connect(dojo.byId("submitAuthBtn"),"onclick",null,function() { var username = document.getElementById("usernameInputField").value; var password = document.getElementById("passwordInputField").value; var invocationData = { adapter : "AuthenticationAdapter", procedure : "submitAuthentication", parameters : [ username,password ] }; WL.Client.invokeProcedure( invocationData, {onSuccess : signOnSuccess, onFailure : signOnFailure }); }); connect.connect(dojo.byId("logoutBtn"), "onclick", null, function() { WL.Client.logout('AccountsAuthRealm'); }); }); } - 在从服务器端收到响应后就会调用 isLoginFormResponse() 函数(清单 13)。该函数返回一个布尔值,指示身份验证是否获得成功。
清单 13. js/auth.js 中的 isLoginFormResponse 函数isLoginFormResponse : function(response) { if (!response || !response.responseJSON || response.responseText == null) { return false; } return response.responseJSON.authRequired; } - onBeforeLogin() 函数(清单 14)用于清除输入字段并准备显示一个身份验证视图。身份验证视图可能是一个具有用户名和密码输入的登录页面。
清单 14. js/auth.js 中的 onBeforeLogin 函数onBeforeLogin : function(response, username, onSubmit, onCancel) { // clean up the input login fields document.getElementById("usernameInputField").value = ""; document.getElementById("passwordInputField").value = ""; document.getElementById("AuthInfo").innerHTML = response.responseJSON.errorMessage; } - 函数 onShowLogin() 或 onHideLogin()(清单 15)将会显示或隐藏登录页面。它们会基于 isLoginFormResponse 返回的布尔值而自动调用。如果身份验证成功,则会调用 onHideLogin,否则会调用 onShowLogin。在此示例中,onHideLogin 函数没有执行任何操作。相反,指定了一个回调函数 signOnSuccess() 来调用身份验证适配器(清单 12)。函数 signOnSuccess() 调用了另一个函数 getAccounts(),将屏幕从登录视图更改为帐户视图。
清单 15. auth.js 中的 onShowLogin / onHideLogin 函数onShowLogin : function() { require([ "dijit" ], function(dijit) { var currentView = dijit.byId("LoginView").getShowingView(); var currentViewId = currentView.get("id"); if (currentViewId != "LoginView") { currentView.performTransition("LoginView", 1, "slide", null); } }); }, onHideLogin : function() { } . . . . function signOnSuccess(response) { getAccounts(); }
本文描述了使用 Worklight 和 Dojo Mobile 开发端到端移动应用程序的一些体验。文中还介绍了一些技术主题,比如使用 Dojo Mobile 的移动用户界面实现、Worklight 适配器开发和 Worklight 身份验证。
感谢同事 Rod Fleming 和 Catherine McCauley 的支持,正是他们的支持造就了此次项目的成功。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 代码样例 | OFNProjectSampleCode.zip | 30 MB | HTTP |
学习
-
IBM Worklight 用户文档
-
IBM Worklight 特性和获益
-
Dojo 文档
-
Tutorial: Dojo 自定义小部件
-
IBM developerWorks 移动专区
-
IBM developerWorks 中国 WebSphere 专区:为使用 WebSphere 产品的开发人员准备的技术信息和资料。这里提供产品下载、how-to 信息、支持资源以及免费技术库,包含 2000 多份技术文章、教程、最佳实践、IBM Redbook 和在线产品手册。
获得产品和技术
-
下载 IBM Worklight Developer Edition
-
评估 IBM Worklight Developer Edition 5.0
- 最受欢迎的 WebSphere 试用软件下载:下载关键 WebSphere 产品的免费试用版。
- IBM developerWorks 软件下载资源中心:IBM deveperWorks 最新的软件下载。
- IBM developerWorks 工具包:下载关键 WebSphere 最新的产品工具包。
讨论
-
IBM Worklight 论坛
- 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
- 加入 IBM 软件下载与技术交流群组,参与在线交流。