在构建 REST 应用程序时,如果使用某个工具来实现 API 组成和数据连接,可以提高您的工作效率。通过这种方式,您可以将更多的精力集中在应用程序的业务逻辑和用户体验上。
在本教程中,您将构建一个工作应用程序来了解如何使用来自 StrongLoop(IBM®的一家子公司)的 API 开发工具。借助开源 LoopBack 框架(该框架构建于 Node.js 之上),您可以轻松地以可视方式开发可扩展的 REST API 并连接到该应用程序所需的数据。 StrongLoop Arc 对 StrongLoop slc 命令行工具进行了补充,包括用于构建、分析和监测 Node.js 应用程序的工具。
您的练习是构建一个简单的混合费用跟踪应用程序,该应用程序使用了您用 StrongLoop 开发的 REST API。该 REST API 使用了 Bluemix 中的 Cloudant NoSQL DB 服务来实现数据集成。您将在本地创建一个服务器应用程序来公开 REST API。为了开发混合应用程序的客户端,可以使用 LoopBack Angular JavaScript SDK(LoopBack 包含的几个软件开发工具中的一个)和 Ionic 框架。作为最后一步,我们会将应用程序的服务器端代码部署到 Bluemix。
如果费用超出了规定的限制,您还可以使用 LoopBack 框架扩展 ExpenseTracker 来发送推送通知 —这方面的练习超出了本教程的讨论范围。
借助 ExpenseTracker 应用程序,用户可以按照类别记录自己的日常开销。客户端会生成一个费用报告,并以图表的形式提供该报告。您可以扩展 API,以便客户端可以根据特定类别或月份来显示图表。
运行服务器应用程序
获取代码
“ 借助开源 LoopBack 框架,您可以轻松地以可视方式开发可扩展的 REST API 并连接到该应用程序所需的数据。 ”
ExpenseTracker
。(如果您尝试使用的名称是不可用的,Bluemix 会告诉您。) expense-tracker
并单击 Create 。 在这一步中,将实现一个服务器端应用程序,该应用程序使用 LoopBack 框架来公开 REST API,以便添加费用信息。
slc loopback
并按下 Enter。 ExpenseTracker
作为应用程序名称并按下 Enter。 服务器端代码是在 ExpenseTracker/server 文件夹中生成的。
阅读: 项目布局引用
阅读: StrongLoop API 文档
ExpenseTracker 应用程序使用 Cloudant NoSQL DB 来实现数据存储。因为 Cloudant 是 CouchDB 的衍生物,所以您可以将 LoopBack CouchDB 连接器用于应用程序:
运行以下命令来安装 LoopBack Couch 连接器:
npm install loopback-connector-couch
阅读: loopback-connector-couch
配置 ExpenseTracker 应用程序,以便连接到您在第 1 步中在 Bluemix 中创建的 Cloudant NoSQL DB 服务:
还可以通过 StrongLoop 命令行工具或 Arc Composer 添加您的 Cloudant 凭证。
在文本编辑器中,打开本地项目的 server/datasources.json 文件并添加 Cloudant 数据库的详细信息:"cloudant": { "host": "yourhost-bluemix.cloudant.com", "port": 443, "url": "https://yourhost-bluemix.cloudant.com", "name": "cloudant", "connector": "couch", "db": "expense-tracker", "protocol": "https", "auth": { "admin": { "username": "cloudant-admin-username", "password": "cloudant-admin-password" }, "reader": { "username": "cloudant-reader-username", "password": "cloudant-reader-password" }, "writer": { "username": "cloudant-writer-username", "password": "cloudant-writer-password" } } }
ExpenseTracker 应用程序使用了一个简单的数据模型。LoopBack 框架中的数据模型配置还可以公开 REST API:
在您的 OS 命令行下,转换到 ExpenseTracker 目录并输入:
slc loopback:model
expense
cloudant
PersistedModel
Yes
expenses
现在,设置模型属性( category
、 item
、 price
和 expensedate
):
slc loopback:property
命令并在提示时提供该属性的详细信息: 属性 | 模型 | 属性名称 | 属性类型 | 是否是必需的 |
---|---|---|---|---|
category | expense | category | string | yes |
item | expense | item | string | yes |
price | expense | price | number | yes |
expensedate | expense | expensedate | date | yes |
另外,您还可以使用 Arc Composer 添加属性。
LoopBack 框架会自动为您的数据模型的所有对象生成 REST 端点。它还会相应地生成 Swagger API 文档,为您提供连接到您的数据的便捷接口。
node
命令来启动 ExpenseTracker 应用程序。
您可以通过修改应用程序的 common/models 文件夹中的 expense.js 文件来扩展模型实现。默认文件内容包括:
module.exports = function(Expense) {};
为了使得 ExpenseTracker 根据类别或月份来检索费用成为可能,可以通过添加一个方法并将该模型模型公开为要使用的客户端的 REST API 来定制模型。在文本编辑器中,打开 common/models/expense.js 文件,并使用下面的代码替换其内容:
module.exports = function(Expense) { //Method to get the category based expenses for the month. Expense.getMonthlyCategoryBasedAnalysis = function(cb) { ONE_MONTH = 30 * 24 * 60 * 60 * 1000; // Month in milliseconds var lastDate = new Date(Date.now() - ONE_MONTH); var result = ""; var jsonArr = []; Expense.find( { expensedate: {gt: lastDate} }, function (err, instance) { var tempArr = [0, 0, 0, 0]; for (var i=0;i<instance.length;i++) { if(instance[i].expensedate > lastDate) { if(instance[i].category == "Food") tempArr[0] = tempArr[0] + instance[i].price; else if (instance[i].category == "Travel") tempArr[1] = tempArr[1] + instance[i].price; else if (instance[i].category == "Education") tempArr[2] = tempArr[2] + instance[i].price; else if (instance[i].category == "Miscellaneous") tempArr[3] = tempArr[3] + instance[i].price; } } for (var i = 0; i < tempArr.length; i++) { var cat = ""; if(i == 0) cat = "Food"; if(i == 1) cat = "Travel"; if(i == 2) cat = "Education"; if(i == 3) cat = "Miscellaneous"; //create the JSON for the client to consume jsonArr.push({ "category": cat, "period": "LastMonth", "price": tempArr[i] }); } console.log("out is "+jsonArr); response = jsonArr ; cb(null, response); }); } //Expose the method as a REST API Expense.remoteMethod ('getMonthlyCategoryBasedAnalysis',{ http: {path: '/getMonthlyCategoryBasedAnalysis', verb: 'get'}, //accepts: {arg: 'categoryName', type: 'string', http: { source: 'query' } }, returns: {arg: 'data', type: 'string'} } ); };
上面的代码使用 find
方法来检索上一个月的基于类别的费用,然后按类别组织数据。 getMonthlyCategoryBasedAnalysis
被公开为客户端的一个远程方法。
阅读: 远程方法
您正在构建的混合应用程序对前端使用了 Ionic 框架和 LoopBack 框架的 AngularJS SDK。
阅读: Ionic 概述
将客户端应用程序创建为一个 Ionic 应用程序:
在您的 OS 命令行下,转到 ExpenseTracker 目录,并运行以下命令来下载应用程序模板(您必须处于联网状态):
ionic start client tabs
阅读: Ionic 项目结构
阅读: AngularJS SDK 简介
LoopBack 框架支持 AngularJS SDK。Ionic 框架使用 Angular 客户端库来调用 REST API。要为您的 LoopBack 应用程序生成 Angular 客户端库,可以使用 LoopBack Angular 命令行工具:
运行以下命令:
lb-ng ../server/server.js www/js/lb-services.js
在上面的命令中,../server/server.js
是主要 LoopBack 服务器脚本的相对路径,而 www/js/lb-services.js
是该工具生成的文件的名称和路径。
urlBase
变量,以便可以调用客户端实现(此更改仅适用于本地测试): var urlBase = "http://localhost:3000/api";
ng-resource
模块添加到应用程序 将 ng-resource
模块添加到客户端应用程序,以便应用程序可以调用 REST API:
在应用程序的客户端目录中,运行以下命令:
bower install angular-resource
如果被询问,请选择最新的版本。该命令会将所需的库添加到 client/www/lib 文件夹。(此外,您可以从某个 AngularJS 版本下载该库。
<head>
部分,将与 angular-resource
相关的 JavaScript 添加到 client/www/index.html 文件:
<script src="lib/angular-resource/angular-resource.js"></script>
应用程序需要基于表单的输入来调用 REST API,让用户可以输入费用。在 controller.js、app.js 和 index.html 模板中执行以下更改:
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])更改为:
angular.module('starter', ['lbServices','ionic', 'starter.controllers'])
.config(function($stateProvider, $urlRouterProvider)
开头的数据块更改为: .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html' }) .state('tab.addexpense', { url: '/addexpense', views: { 'tab-addexpense': { templateUrl: 'templates/tab-addexpense.html', controller: 'ExpenseController' } } }) //This is required for the below step to display charts .state('tab.charts', { url: '/charts', views: { 'tab-charts': { templateUrl: 'templates/tab-charts.html', controller: 'ExpenseController' } } }); // if none of the above states are matched, use this as the fallback $urlRouterProvider.otherwise('/tab/addexpense'); });
<ion-tab>
修改为: <!-- Add Expense Tab--><ion-tab title="Add Expense" icon-off="ion-ios-plus-outline" icon-on="ion-ios-plus" href="#/tab/addexpense"> <ion-nav-view name="tab-addexpense"></ion-nav-view> </ion-tab>
<ion-view view-title="Add Expense"> <ion-content padding="true" class="has-header" scroll="false" overflow-scroll="false"> <div> <br> <form name="expensesForm" ng-submit="addExpense()"> <div class="list"> <label class="item item-input"> <span class="input-label">Category</span> <select class="form-control focus" name="category" required ng-model="newExpense.category"> <option name="Food" id="Food">Food</option> <option name="Travel" id="Travel">Travel</option> <option name="Miscellaneous" id="Cosmetics">Miscellaneous</option> <option name="Education" id="Education">Education</option> </select> </label> <label class="item item-input"> <span class="input-label">Item</span> <input type="text" class="form-control" name="item" placeholder="Expense Item Name" autocomplete="off" required ng-model="newExpense.item"> </label> <label class="item item-input"> <span class="input-label">Price</span> <input type="text" class="form-control" name="price" placeholder="Expense Price" autocomplete="off" required ng-model="newExpense.price"> <input type="hidden" class="form-control" name="date" placeholder="Date" autocomplete="off" required ng-model="newExpense.expensedate"> </label> </div> <br> <button class="btn btn-default" ng-disabled="expensesForm.$invalid">Add Expense</button> </form> </div> </ion-content> </ion-view>
ExpenseController
添加到控制器: .controller('ExpenseController', [ '$scope','$rootScope', 'Expense', function($scope, $rootScope, Expense) { $scope.newExpense = {}; $scope.newExpense.category = 'Food'; $scope.newExpense.expensedate = new Date(); //Method call to create the expense record $scope.addExpense = function() { Expense.create($scope.newExpense).$promise.then(function(expense) { $scope.newExpense = {}; $scope.newExpense.category = 'Food'; }); }; }])
对于要使用您在第 2 步中添加的定制 REST API 的应用程序,必须修改 lb-services.js 文件:
findOne
方法声明,该声明位于 Expense
模型声明之下。 findOne
方法的后面,为第 2 步中在 expense.js 文件中声明的定制方法添加以下代码: "getMonthlyCategoryBasedAnalysis": { url: urlBase + "/expenses/getMonthlyCategoryBasedAnalysis", method: "GET" },
angular-chart
模块添加到应用程序 要在应用程序中显示基于类别的每月费用图表,可添加 angular-chart
模块:
要在 client/www/lib 文件夹中包含所需的库,可以运行以下命令:
bower install angular-chart.js
此外,您还可以从 项目站点下载该库。
<head>
部分,将 Angular SDK 添加到 client/www/index.html 文件: <script src="js/lb-services.js"></script>
<head>
部分,将与 angular-chart
相关的 JavaScript 添加到 client/www/index.html 文件: <script src="lib/Chart.js/Chart.min.js"></script> <script src="lib/angular-chart.js/dist/angular-chart.js"></script>
<head>
部分,将与 angular-chart
相关的 CSS 代码添加到 index.html 文件: <link rel="stylesheet" href="lib/angular-chart.js/dist/angular-chart.css">
chart.js
添加到模块声明,将 chart
模块添加到应用程序: angular.module('starter', ['lbServices','ionic', 'chart.js','starter.controllers'])
chart.js
添加到 controllers.js 的第一行: angular.module('starter.controllers', ['chart.js'])
要检索基于类别的每月费用,必须调用 getMonthlyCategoryBasedAnalysis
方法:
<ion-tab>
更改为: <!-- Charts Tab --> <ion-tab title="Charts" icon-off="ion-load-d" icon-on="ion-load-b" href="#/tab/charts"> <ion-nav-view name="tab-charts"></ion-nav-view> </ion-tab>
<ion-view view-title="Charts" ng-init="drawCategoryMonthlyChart()"> <ion-content> <canvas id="bar" class="chart chart-bar" chart-data="data" chart-series="series" chart-labels="labels" responsive="false" height="100" chart-legend="true" chart-options="{datasetFill: false, scaleBeginAtZero : false}"> </canvas> </ion-content> </ion-view>
ExpenseController
控制器的一部分: //Method call to draw the chart. Calls the REST API & forms the angular chart based variables. $scope.drawCategoryMonthlyChart = function() { Expense.getMonthlyCategoryBasedAnalysis().$promise.then(function(results) { expenseData = results.data; var foodPrice = 0, travelPrice = 0, eduPrice = 0, miscPrice = 0; for(i=0;i<4;i++) { if(expenseData[i].category == "Food") foodPrice = expenseData[i].price; if(expenseData[i].category == "Travel") travelPrice = expenseData[i].price; if(expenseData[i].category == "Education") eduPrice = expenseData[i].price; if(expenseData[i].category == "Miscellaneous") miscPrice = expenseData[i].price; } $rootScope.labels = ['Food','Travel','Education','Miscellaneous']; $rootScope.series = ['ThisMonth']; $rootScope.data = [ [foodPrice, travelPrice, eduPrice, miscPrice] ]; }) };
将服务器应用程序推送到 Bluemix,以便客户端可以调用 REST API:
在您的 OS 命令行下,转到 ExpenseTracker 目录并运行以下代码:
cf push ExpenseTracker -c "node server/server.js"
因为客户端应用程序是一个混合应用程序,所以它可以在一个浏览器、模拟器或移动设备中运行。这一节将介绍如何在一个浏览器或 iOS 设备中运行它。
urlBase
,使之指向 Bluemix 路由: http://app-name.mybluemix.net/api
。 在您的 OS 命令行下,更改到 ExpenseTracker/client 目录并运行以下代码:
ionic serve
在您的 OS 命令行下,转换到应用程序的客户端文件夹并运行以下代码:
ionic platform add ios
此命令将向客户端应用程序添加 iOS 平台支持。 (类似地,ionic platform add android
将向客户端应用程序添加 Android 平台支持。)
ionic build ios
来构建针对 iOS 设备的项目。 阅读: 测试您的 Ionic 应用程序
备注:您的应用程序需要能够访问 HTTP 传输,因为它要调用 REST API。您可能会得到消息 App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure
。您可以通过添加以下代码,通过应用程序的 client/platforms/ios/client/Info.plist 文件处理临时异常:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
阅读: NSAppTransportSecurity
通过使用 StrongLoop LoopBack 框架和 Ionic 框架来开发示例应用程序,您现在知道了(使用正确的工具)公开 REST API 并在混合应用程序使用它们是多么容易。您可以扩展在本教程中学到的知识,通过定义模型之间的关系,创建生产就绪的、企业级的应用程序。
BLUEMIX SERVICE USED IN THIS TUTORIAL: Cloudant NoSQL DB 提供了对始终打开的完全托管 NoSQL JSON 数据层的访问。
相关主题: 移动开发 Node.js