内容


用 Cloudant 在 Bluemix 上构建一个简单的文字游戏应用程序

Comments

Bluemix 是一种新开放平台,用于开发和部署 Web 和移动应用程序。本文将介绍如何使用 Bluemix 的和它基于云的开发环境:DevOps Services 创建一个简单的 GuesstheWord 游戏应用程序。您将从头开始创建,最后得到一个可在 Web 浏览器中运行的简单游戏,以及在云中运行的服务器代码。

完成本文中的步骤后,您将有能力自行开发任何规模的 Bluemix 应用程序。

尽管此应用程序很简单,但我将介绍在开发更大型应用程序时至关重要的各个方面,比如设置一个良好的开发环境来启用本地调试。我的目的是使用一种适合开发大型 Bluemix 应用程序的方法,演示一个小型 Bluemix 应用程序的开发。完成本文中的步骤后,您将有能力自行开发任何规模的 Bluemix 应用程序。

运行应用程序获取代码

实现该应用程序的前提条件

要学习本文,您需要一个 Bluemix 帐户和一个 DevOps Services 帐户。所以如果还没有这些帐户,请立即创建它们。另外,如果这是您第一次听到 Bluemix,您可能希望阅读这篇简短的 简介

启动一个开发项目时,选择正确的技术很重要。Bluemix 支持多种开发应用程序的技术。事实上,此灵活性是该平台的主要优势之一,因为它允许您选择哪些技术最适合您想要开发的应用程序。对于我们的 Guess-the-Word 游戏,我们将使用以下技术:

  • Node.js:对于服务器端代码,我们将使用 Node.js。使用 Node.js 实现的 Web 服务器能快速启动,很容易在本地开发和调试。为服务器端和客户端代码使用同一种语言(比如 JavaScript)也是一种优势。我们还会将 Express 框架与 Node.js 结合使用,因为它在实现 Web 服务器时提供了有用的功能。
  • Cloudant:为了持久保存服务器数据(游戏分数排行榜),我们将使用一个 Cloudant 数据库。像 Cloutant 这样的 NoSQL 数据库很容易用于存储 JavaScript 和 JSON 编码的数据。
  • HTM、CSS 和 JavaScript:游戏的 UI 将使用 HTML、CSS 和 JavaScript 实现。考虑到现代 Web 浏览器支持 HTML5 和 CSS3,这是一种自然的选择,因为除了计算机之外,它会使得我们的游戏也能在电话和平板电脑上运行。
  • Bootstrap 和 JQuery:我们还将使用 Bootstrap 和 JQuery JavaScript 框架,它们提供了不错的 Web UI 开发功能。
  • Jade:为了节省一些录入工作,我们将对 HTML 页面使用一种模板语言。有多种模板语言可用于 Node.js。我决定使用 Jade,因为它常常用于 Express 框架。

决定这些事项后,就可以开始编码了。

第 1 步. 编写服务器代码

  1. 首先实现 Node.js 服务器的核心部分,以便能够在实现 UI 之前测试它。我们将使用 Eclipse 进行编码,但截至编写本文时,Eclipse 中仍仅有对 Node.js 的基本支持,尤其是在调试方面。所以我们将使用一个叫做 node-inspector 的实用程序调试 Node.js 服务器代码。您可以使用您最喜爱的文本编辑器来编写以下代码:
    /**
     * Server for the GuessTheWord app
     */
     
    var express = require('express');
    var app = express();
    var http = require('http');
    
    var host = "localhost";
    var port = 3030;
    
    // Set path to Jade template directory
    app.set('views', __dirname + '/views');
    
    // Set path to JavaScript files
    app.set('js', __dirname + '/js');
    
    // Set path to CSS files
    app.set('css', __dirname + '/css');
    
    // Set path to image files
    app.set('images', __dirname + '/images');
    
    // Set path to sound files
    app.set('sounds', __dirname + '/sounds');
    
    // Set path to static files
    app.use(express.static(__dirname + '/public'));
    
    // Bind the root '/' URL to the hiscore page
    app.get('/', function(req, res){
      res.render('hiscores.jade', {title: 'Hiscores'});
    });
    
    // Bind the '/play' URL to the main game page
    app.get('/play', function(req, res){	
      res.render('main.jade', {title: 'Guess the Word'});
    });
    
    var server = app.listen(port, function() {
      console.log('Server running on port %d on host %s', server.address().port, host);
    });
    
    process.on('exit', function() {
      console.log('Server is shutting down!');
    });
  2. 将此代码保存在一个名为 server.js 的文件中,然后创建一个名为 views 的文件夹来包含两个 Jade 文件(hiscores.jade 和 main.jade),这两个文件已在上述代码中引用,如下所示。这些 Jade 文件定义了该游戏使用的两个 Web 页面的内容。
  3. hiscores.jade 文件显示了分数排行榜。这是游戏的初始页面,已被绑定到根 URL '/'
    doctype html
    html(lang="en")
      head
        title= title
        link(rel="stylesheet", href="css/bootstrap.css", type="text/css")
        link(rel="stylesheet", href="css/hiscores.css", type="text/css")
      body(style="background-image:url(/images/background.jpg)")
    
        div(class="container")
          h1(id="header") High Scores
          table(id="hiscore_table")
            tr()
              th(class="table-header") Score
              th(class="table-header") Name
              th(class="table-header") Date
          div(style="padding-top:30px;")
            a(class="btn btn-default", href="/play") Play!
          
        script(src="js/jquery.js", type="text/javascript")
        script(src="js/bootstrap.js", type="text/javascript")
        script(src="js/hiscores.js", type="text/javascript")
  4. main.jade 文件是显示游戏的页面。它被绑定到 URL '/play'
    doctype html
    html(lang="en")
      head
        title= title
        link(rel="stylesheet", href="css/bootstrap.css", type="text/css")
        link(rel="stylesheet", href="css/main.css", type="text/css")
      body(style="background-image:url(/images/background.jpg)")
    
        div(class="container")
          h1(class="game-text") Guess the secret word!
          div(class="row")
            div(class="col-xs-8")
              div(id="word-description")        
            div(class="col-xs-4")
               div(id="score") Score:        
               
          div(class="row")
            div(class="col-xs-8")
              div(class="word-area")
                table()
                  tr()        
            div(class="col-xs-4")
              div(id="power") Power:            
          div(class="row")
            div(class="col-xs-8")
              div(id="help-text", class="game-text") Click on a ?-box above and type a letter          
            div(class="col-xs-4")
              div(class="row")
                div(class="col-xs-12")
                  button(id="skip-button" class="btn btn-info btn-xs") Skip this word
                div(class="col-xs-12")
                  button(id="help-letter-button" class="btn btn-info btn-xs") Give me a letter
              
        
        audio(id="tick-sound", src="sounds/Tick.mp3")
        audio(id="skip-sound", src="sounds/Falcon.mp3")
        audio(id="applause-sound", src="sounds/Applause.mp3")
          
        script(src="js/jquery.js", type="text/javascript")
        script(src="js/bootstrap.js", type="text/javascript")
        script(src="js/main.js", type="text/javascript")

可以看到,这些 Jade 文件引用了许多静态文件,比如 CSS 和 JavaScript 文件。我们稍后将添加这些文件,但首先让我们测试一下目前已实现的功能。

第 2 步. 运行和调试服务器代码

  1. 如果尚未在本地安装 Node.js,现在正是时候。从 Nodejs.org 下载并按照以下说明进行操作。我们将 Node.js 安装在本地,所以在将服务器代码部署到 Bluemix 之前,可以在本地运行和调试它。
  2. 您还需要安装适用于 Node.js 的 Express 和 Jade 模块。实现此操作的最简单的方法是首先在根文件夹下创建一个 package.json 文件,它指定与这些库的依赖关系。
    {
    	"name": "GuessTheWord",
    	"version": "0.0.1",
    	"description": "GuessTheWord package.json file",
    	"dependencies": {
    		"express": ">=3.4.7 <4",
    		"jade": ">=1.1.4"
    	},
    	"engines": {
    		"node": ">=0.10.0"
    	},
    	"repository": {}
    }

    现在,您可以在根文件夹下的命令行提示符下输入以下命令,安装 Express 和 Jade:

    > npm install

    在阅读本文时,可能已经有了多个比上述版本更新的 Express 和 Jade 版本。当然您可以使用这些新版本。只要应用程序的所有依赖关系都已在 package.json 中提及,在本地开发和随后部署到 Bluemix 时将使用相同的库版本。此结果很不错,因为您避免了在不同环境中使用不同库版本所导致的意外。

  3. 使用以下命令第一次运行您的应用程序:
    > node --debug server.js
  4. 打开一个 Web 浏览器并访问 http://localhost:3030/。 该图显示了 http://localhost:3030
  5. 在继续进行游戏开发之前,需要确保我们可以调试该应用程序。首先安装 node-inspector。打开一个新命令行并键入以下命令:
    > npm install -g node-inspector
  6. 因为我们使用了 --debug 标志来启动该应用程序,所以现在可以按以下方式启动 node-inspector:
    > node-debug server.js

    这会在默认 Web 浏览器(应为 Chrome 或 Opera)中打开一个 JavaScript 调试器。如果您的默认浏览器是另一个 Web 浏览器,那么可以使用以下命令:

    > node-inspector

    在 Chrome 或 Opera 中打开 http://127.0.0.1:8080/debug?port=5858 开始进行调试。如果之前从未使用过这个调试器,那么您可能希望阅读此 简介

第 3 步. 实现客户端代码

  1. 现在让我们创建 Jade 文件引用的缺少的文件。所有二进制文件(JPG 和 MP3)都可从完成的 GuessTheWord DevOps Services 项目下载,其中包括本文中引用的所有文件。下载它们的最简单的方法是转到 EDIT CODE 选项卡,右键单击包含文件夹,将其中的内容导出为 zip 文件供下载。 该图显示了创建缺少的文件的过程
    该图显示了创建缺少的文件的过程

    当然,您也可以选择创建您自己的图像和声音,为游戏提供更个性化的效果。

  2. 以下文件构成了 JQuery 和 Bootstrap JavaScript 库。它们也可从 GuessTheWord DevOps Services 项目或从互联网进行下载。
    1. 下载到文件夹 public/js:
      • jquery.js(JQuery JavaScript 库的最新版本)
      • bootstrap.js(Bootstrap JavaScript 库的最新版本)
    2. 下载到文件夹 public/css:
  3. 现在创建以下两个 CSS 文件,它们定义了游戏中使用的样式:
  4. 再次在本地运行该应用程序,让这些更改生效。转到应用程序的根文件夹(确保它是本地 Git 存储库连接到的文件夹),然后运行:
    > node-debug server.js

    该游戏现在比之前好看一点:

    该图显示了游戏的创建进度
  5. 接下来编写实现游戏逻辑的客户端 JavaScript。我们等会儿再实现 hiscores.js,因为填充分数排行榜需要一个数据库,我们稍后才会添加该数据库。使用此 代码段 中的内容创建文件 public/js/main.js,基本上讲,该文件包含所有游戏逻辑。它负责从服务器获取一个随机的英文单词和一段描述,处理玩家的键盘输入,统计分数,管理电源等。

    您可以看到,函数 getNewSecretWord() 在 Web 服务器上调用 /randomword 服务来获取这个秘密单词。

  6. 我们将以下代码添加到 server.js 中(放在初始变量声明之后)来实现此服务。
    /**
     * Lookup the word in the wordnik online dictionary and return a description for it.
     * @param word {String} Word to lookup description for
     * @param cb_description {function} Callback with the description as argument. 
     * If the word was not found in the dictionary the description is empty.
     */
    function wordLookup(word, cb_description) {
      http.request(
        {
          host: "api.wordnik.com",
          path: "/v4/word.json/" + word +
            "/definitions?limit=1&api_key=a2a73e7b926c924fad7001ca3111acd55af2ffabf50eb4ae5"
        },	function (res) {
          var str = '';
          res.on('data', function(d) {
            str += d;
          });
          res.on('end', function() {
          var wordList = JSON.parse(str);
          cb_description(wordList.length > 0 ? wordList[0].text : "");
        });
      }).end();	
    }
    
    app.get('/randomword', function(request, response) {
      http.request(
        {
          host: "api.wordnik.com",
          path: "/v4/words.json/randomWord?hasDictionaryDef=false&minCorpusCount=0&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=5&maxLength=-1&api_key=a2a73e7b926c924fad7001ca3111acd55af2ffabf50eb4ae5"
        }, function (res) {
          var str = '';
          res.on('data', function(d) {
            str += d;
          });
          res.on('end', function() {
            var wordObj = JSON.parse(str);
            wordLookup(wordObj.word, function(descr) {
            var randomWordObj = { word : wordObj.word, description : descr };
            response.send(JSON.stringify(randomWordObj));		
          });								
        });
      }).end();
    });

    此代码将 /randomword URL 绑定到使用 Wordnik.com(一个在线英语词典)的代码,以便获取一个随机单词。然后,它将调用 wordLookup(),后者则会调用 Wordnik API 来获取该单词的描述。最后,这个秘密单词和它的描述被编码为 JSON,并作为 HTTP 请求的响应返回。

    备注:上述代码段中包含的 API 密钥用于一个由许多人共享的免费帐户,所以只可以在小范围内使用它。为了避免此限制,您可以 在 Wordnik 上注册获得自己的 API 密钥

  7. 现在,您可能希望在本地运行一些调试会话,单步调试客户端和服务器端代码,以确认游戏能按预期运行。 该图显示了验证游戏是否按预期运行的过程
    该图显示了验证游戏是否按预期运行的过程

    客户端 JavaScript 使用了嵌入式 Chrome 调试器来调试。服务器端 JavaScript 使用了前面介绍的 node-inspector 来进行调试。请注意,node-inspector 使用同一个 Chrome 调试器来进行服务器端 Node.js JavaScript 调试。

第 4 步. 创建一个 DevOps Services 项目

现在我们已经有一个可在本地运行和调试的正常的应用程序,下一步是将目前编写的代码存储在一个 DevOps Services 存储库中。这样做的好处有很多,其中包括:

  • 代码被存储在云中,这比将代码存储在本地计算机上更安全。
  • 其他人可以向应用程序的开发做出贡献。
  • 我们可实现向 Bluemix 的自动部署。
  1. 登录到 DevOps Services,单击 Start coding 为我们的应用程序创建一个项目。选择 Jazz SCM 和 Git SCM 属于主观意愿,通常,具体的选择取决于您最熟悉哪个 SCM 系统。我们将为该项目选择一个 Git 存储库,并勾选 Deploy to Bluemix 复选框,因为我们会将应用程序部署到 Bluemix。选择一个 Bluemix 组织和空间,然后单击 CREATE该图显示了创建该项目的过程
    该图显示了创建该项目的过程
  2. 除了两个默认文件之外,新项目最初是空的。要向项目中添加代码,可以单击 EDIT CODE,然后从您的文件系统将文件拖放到项目的文件分层结构(左侧显示的树)中。

    如果使用 Eclipse 编写代码,那么可以直接从 Eclipse Project Explorer 拖放这些文件。请注意,随后您应该选择 Eclipse 下的各个文件和文件夹(而不是项目本身),将它们拖到项目文件分层结构中的顶级节点(标为红色)上。您还可能注意到,Eclipse 隐藏了一些文件,例如 .project 文件,所以您可能仍然需要从文件系统拖放一些文件。

    您的项目文件分层结构现在应该显示应用程序的所有文件。

    请注意,尽管您已将这些文件从本地计算机复制到 DevOps Services 项目中,但您执行的更改仍然会提交并推送到 Git 存储库。这意味着其他任何人都无法看到您添加的文件。您可以将 DevOps Services 项目下显示的文件分层结构视为您的个人工作区域,这与您的本地文件系统非常相像,惟一的不同是它存储在云中。

  3. 如果单击存储库图标,则会看到一些未提交的更改,您添加的每个文件都对应一条更改。这些文件被显示为 “Unstaged”,这个术语是 Git 针对还未提交到存储库的更改而使用的术语。 该图显示了存储库图标
  4. 要提交更改,可以在每个文件上执行 Stage the change 命令: 该图显示了暂存按钮

    这会将文件从 Unstaged 转移到 Staged。单击 COMMIT 时,Staged 下显示的所有更改都被作为一个更改集提交。对于本示例,我们希望在一个更改集中添加所有文件,但您在以后可能希望将一些更改作为一个更改集提交,将另一些更改作为另一个更改集提交。每个更改集应将逻辑上统一的更改分组到一起。

    Commit 对话框中的 Commit message 应该描述了该更改集,可能还提供了更改的原因。

    该图显示了提交过程
    该图显示了提交过程
  5. 单击 SUBMIT 时,更改集会提交到 Git 并显示为 Commits 部分下的单个更改集。 该图显示了已经提交但尚未推送的更改
    该图显示了已经提交但尚未推送的更改
  6. 最后一步是将此更改集推送到主分支,以便其他人可从这里获取它。单击 PUSH,然后单击 OK 推送您的更改。 该图显示了已推送的更改
  7. 此刻,代码的本地副本是冗余的,您应当考虑删除它。如果继续在本地副本上开发,请记住将所有已修改的文件上传到 DevOps Services 项目,以上述方式提供更改。您可以采用这种工作方式,但可能容易混淆,而且有些单调。

    一种更实际的方法是完全放弃本地开发,使用 DevOps Services 中的 Web 编辑器编辑代码,或者设置一个连接到 DevOps Services Git 存储库的本地 Git 存储库。我选择第二种备用方案,因为我们希望能够在本地开发和调试,此方法目前还无法使用 DevOps Services Web 编辑器来实现。

    您可以通过两种方式设置一个连接到 DevOps Services Git 存储库的本地 Git 存储库。如果使用 Eclipse 或 Visual Studio 等 IDE,那么可以使用针对这些 IDE 的 Git 插件将 DevOps Services Git 存储库导入 IDE 中。如果在没有 Git 插件的环境中进行开发,那么可以从命令行使用 Git。无论采用哪种方法,都需要使用 DevOps Services Git 存储库的 URL。您可以从 DevOps Services 项目的概述页面获取此 URL。

    要从 EDIT CODE 选项卡访问项目的概述页面,可以单击左上角的项目链接。

    该图显示了项目概述页面

    然后单击 Git URL 链接:

    该图显示了 Git URL
    该图显示了 Git URL
  8. 将此 URL 克隆到一个本地 Git 存储库中后,可以继续在本地开发应用程序,并直接将更改提交到 DevOps Services Git 存储库,而不经过 DevOps Services Web 界面。如果使用 Eclipse 和 EGit 插件,那么您的项目视图将类似于下面的屏幕截图。如果 Eclipse 未包含 EGit 插件,那么可以轻松地 安装 它。如果不熟悉 EGit,那么可以查阅 用户指南该图显示了一个 Eclipse 项目,其中显示了一个 Git 存储库中存储的该应用程序的文件
    该图显示了一个 Eclipse 项目,其中显示了一个 Git 存储库中存储的该应用程序的文件

第 5 步. 将应用程序部署到 Bluemix

此时,一个不错的想法是:首次将应用程序部署到 Bluemix 上,以便在继续开发游戏之前确保此应用程序能够正常运行。

  1. 部署是通过一个名为 manifest.yml 的文件进行控制的,所以我们需要在应用程序的根文件夹中创建此文件。
    ---
    applications:
    - name: GuessTheWord
      framework: node
      runtime: node08
      memory: 64M
      instances: 1
      host: GuessTheWord
      path: .     
      command: node server.js

    这些设置告诉 Bluemix 如何部署应用程序。例如,我们在部署应用程序的 URL 中指定了运行时 (Node.js)、为应用程序保留的内存量,以及我们希望使用的主机名。请注意,主机名必须是全局惟一的,所以需要挑选一个不同于 “GuessTheWord” 的主机名。

  2. 在 DevOps Services Web 编辑器或本地开发环境中创建此文件时,不要忘记将新文件推送到 DevOps Services Git 存储库。
  3. 要开始部署,可以单击 DevOps Services 项目上的 BUILD & DEPLOY 按钮。 该图显示了 Build &amp; Deploy 选项卡
    该图显示了 Build &amp; Deploy 选项卡

    然后,需要将页面顶部的开关从 OFF 切换到 SIMPLE,以部署到 Bluemix。

    该图显示了自动部署按钮
  4. 完成这一步后,DevOps Services 会立即尝试将应用程序部署到 Bluemix。 该图显示已经启用了简单自动部署
    该图显示已经启用了简单自动部署

    同样地,只要有人向 DevOps Services Git 存储库推送了更改,就会自动发生一次新部署。这很方便,因为它使我们始终能访问应用程序的最新部署版本,比如用于运行测试的应用程序的最新部署版本。

  5. 大约 30 秒后,您应该看到应用程序已正常部署,没有错误。 该图显示了自动部署消息
    该图显示了自动部署消息
  6. 要访问部署在 Bluemix 中的应用程序,可以单击列表上方的链接。 该图显示了部署的应用程序的链接
    该图显示了部署的应用程序的链接

第 6 步. 排除 Bluemix 部署的问题

尝试访问部署的应用程序时,您可能会看到以下消息:

404 Not Found:Requested route ('guesstheword.mybluemix.net') does not exist.

这似乎不对劲。显然某处存在问题,而且这个问题似乎与在 Bluemix 上运行的应用程序相关,因为该应用程序能够在本地正常运行。

这证明了一个重要事实:应用程序可能已成功部署,但在您尝试访问它时,仍无法使用它。绿色的状态指示器和 Recent Auto-Deployments 列表中的 OK 结果,意味着应用程序已成功部署和启动。但是,举例而言,它可能在启动后不久就终止了,或者在您尝试访问它的 Bluemix URL 时不可用。

为了排除这些类型的问题,能够在 Bluemix 上调试服务器代码会很不错。此功能可能会在未来实现,但 Bluemix 目前还不支持这么做。但是您可以:

  1. 查看 DevOps Services 中的日志文件,以了解哪里出错了。有时还可使用 Bluemix cf CLI 工具获取额外的信息。例如:
    > cf app GuessTheWord
    Showing health and status for app GuessTheWord in org mattias.mohlin@se.ibm.com
    / space dev as mattias.mohlin@se.ibm.com...
    OK
    
    requested state: started
    instances: 0/1
    usage: 64M x 1 instances
    urls: GuessTheWord.mybluemix.net
    
         state      since                    cpu    memory   disk
    #0   crashing   2014-04-24 01:10:37 PM   0.0%   0 of 0   0 of 0

    这里我们可以看到,应用程序的状态为 crashing,这意味着它被莫名其妙地终止了。Web 服务器绝不应终止,所以我们需要找到该服务器在 Bluemix 上终止运行的原因。

  2. 在 DevOps Services 中的 EDIT CODE 选项卡上,有一个 DEPLOY 按钮,但暂时不要单击它。 该图显示了 Deploy 按钮

    此按钮用于手动将您的个人 DevOps Services 项目区域的内容部署到 Bluemix。这在排除问题时很有用,因为它支持添加您不希望提交到主分支的临时日志或其他试验。单击 DEPLOY 之前,我们需要获取所有传入的更改集,让 DevOps Services 项目区域与主分支保持同步。

  3. 转到 Git Status 页面并单击 FETCH 按钮。传入的更改集显示在 Commits 部分下。单击 MERGE 按钮将这些传入的更改集合并到 DevOps Services 项目区域中。
  4. 现在我们可以单击 DEPLOY 按钮进行手动部署。应用程序部署到 Bluemix 后,您将看到: 该图显示已准备好手动部署
    该图显示已准备好手动部署
  5. 单击上述消息中的根文件夹页面来查看手动部署信息。 该图显示部署在 Bluemix 上的应用程序没有运行
    该图显示部署在 Bluemix 上的应用程序没有运行

    我们看到一个红色的指示器,证实该应用程序没有运行。单击 Logs 链接,查看应用程序在被终止之前生成的日志。

    该图显示了日志文件
  6. 打开 stdout.log 文件。 该图显示了文件 stdout.log 的内容
    该图显示了文件 stdout.log 的内容

    根据此日志,您可能会立即意识到哪里出错了。我们的 Web 服务器尝试使用一个硬编码的主机名 (localhost) 和端口号 (3030)。这在本地运行服务器时没有问题,但在 Bluemix 上行不通。

  7. 解决方案是将以下代码添加到 server.js 文件中的 hostport 变量的声明之后:
    if (process.env.hasOwnProperty("VCAP_SERVICES")) {
      // Running on Bluemix. Parse out the port and host that we've been assigned.
      var env = JSON.parse(process.env.VCAP_SERVICES);
      var host = process.env.VCAP_APP_HOST;
      var port = process.env.VCAP_APP_PORT;	
    }

    Bluemix 使用了一个称为 VCAP_SERVICES 的环境变量将环境消息传递给服务器。hostport 是这些信息的示例。请注意,我们在本地运行服务器时不会执行上面的 if 语句,因为那时未设置 VCAP_SERVICES。因此,我们甚至可以在执行此更改后在本地运行和调试我们的应用程序。

  8. 我们将此更改推送到 Git 存储库并运行自动部署功能。我们看到部署已成功完成。 该图显示了更改的自动部署并未使用硬编码的主机和端口
    该图显示了更改的自动部署并未使用硬编码的主机和端口
  9. 如果现在单击部署的应用程序的链接,应该会成功打开该链接,让我们小小地庆祝一下。 该图显示 GuessTheWord 游戏已在 Bluemix 上运行
    该图显示 GuessTheWord 游戏已在 Bluemix 上运行

    好了!我们的游戏正在 Bluemix 上运行。

  10. 现在稍微休息一下,玩玩该游戏。看看在电量耗尽之前您能猜出多少个单词。

第 7 步. 添加一个数据库服务

您是否获得了一个高分?恭喜您!糟糕的是您的分数没有保存,其他人无法看到您的猜字游戏玩得多好。让我们来解决这个问题,将分数持久保存在一个数据库中。您可能认为使用一个数据库来保存这么少的数据有点大材小用。我们能否将排行榜列表保存在服务器上的一个文本文件中?可以,但这不是个好主意,因为 Bluemix 服务器可能并不总是在同一个物理机器上运行(由于负载平衡原因)。因此,不推荐使用文件来持久保存数据,而是应该使用某种类型的云服务来持久保存数据。

有许多云存储解决方案可供选择,我们希望保持简单。我们向应用程序添加一个 Cloudant 数据库服务:

  1. 单击 BUILD & DEPLOY 选项卡中的 MANAGE 按钮,转到 Bluemix Web 应用程序。 该图显示了 Manage 按钮
    该图显示了 Manage 按钮
  2. 这会调出 Bluemix 中的 GuessTheWord 应用程序。我们可以看到,我们的应用程序正在运行,但它没有关联的服务。单击 Add a service 添加一个新服务。在 Bluemix 目录的 Data Management 部分中,选择 Cloudant JSONDB该图显示了 Bluemix 中的 Cloudant 图标
  3. 单击 Create and add to app 调出以下对话框。 该图显示了添加 Cloudant 数据库服务的对话框
    该图显示了添加 Cloudant 数据库服务的对话框

    如果已经有了一个 Cloudant 帐户,那么您可以输入您的用户名、密码和 URL https://<username>.cloudant.com。否则,您需要先 注册 一个 Cloudant 帐户,然后才能继续操作。

    单击 CREATE 时,应用程序将会重新启动,此过程应该只需花费几秒钟的时间。

  4. 您现在应该看到,已经有一个 Cloudant 数据库作为 Bluemix 应用程序的服务。 该图显示已将 Cloudant 以服务形式添加到 Bluemix 应用程序中
    该图显示已将 Cloudant 以服务形式添加到 Bluemix 应用程序中

第 8 步. 使用 Cloudant 数据库

  1. 打开一个浏览器并登录到 Cloudant UI:https://<username>.cloudant.com。
  2. 创建一个名为 guess_the_word_hiscores 的新数据库。然后单击创建一个新辅助索引的按钮。将它存储在一个名为 top_scores 的文档中,并将该索引命名为 top_scores_index该图显示已在 Cloudant DB 中创建了辅助索引
    该图显示已在 Cloudant DB 中创建了辅助索引
  3. 映射函数定义了该索引对数据库中的哪些对象进行了分类,以及我们希望检索这些对象的哪些信息。我们使用分数作为索引键(emit 的第一个参数),然后 emit 一个包含该分数、玩家名称和该分数获得日期的对象。以下是映射函数的 JavaScript 实现,您可复制/粘贴这些代码。
    function(doc) {
     emit(doc.score, {score : doc.score, name : doc.name, date : doc.date});
    }
  4. 我们编写了将分数存储在 Cloudant 数据库所需的代码。这一次将使用 DevOps Services Web 编辑器。您需要决定使用哪个 Node.js 库来访问 Cloudant。我选择使用 nano 库。

    单击 DevOps Services 文件分层结构中的 package.json 文件,然后添加行 "nano" :"*",如下所示。这会将 nano 库的最新版本包含在应用程序中。

    	"dependencies": {
    		"express": ">=3.4.7 <4",
    		"jade": ">=1.1.4",
    		"nano" : "*"
    	},

    与主机和端口一样,Bluemix 使用 VCAP_SERVICES 环境变量向应用程序告知 Cloudant 数据库服务的信息(它在何处运行、使用哪些凭据登录到它,等等)。将下面几行斜体的代码添加到 server.js 文件中,将 <username><password> 替换为您的 Cloudant 用户名和密码。

    var express = require('express');
    var app = express();
    var http = require('http');
    
    var host = "localhost";
    var port = 3030;
     cloudant = {
    		username : "<username>", // : Update
    		password : "<password>", // : Update
    		url : "https://<username>.cloudant.com" // : Update
    };if (process.env.hasOwnProperty("VCAP_SERVICES")) {
      // Running on Bluemix. Parse out the port and host that we've been assigned.
      var env = JSON.parse(process.env.VCAP_SERVICES);
      var host = process.env.VCAP_APP_HOST;
      var port = process.env.VCAP_APP_PORT;
    
      // Also parse out Cloudant settings.
      cloudant = env['user-provided'][0].credentials;  
    } nano = require('nano')('https://' + cloudant.username + ':' + cloudant.password + '@' + cloudant.url.substring(8));
     db = nano.db.use('guess_the_word_hiscores');

    您可以看到,上述代码在应用程序不在 Bluemix 上运行时也能工作。在本地运行时,该应用程序可以像在 Bluemix 上运行一样使用 Cloudant 数据库。

  5. 现在要处理服务器中的两个新 URL:
    • /hiscores(用于从数据库获取高分榜)
    • /save_score(用于将新的高分保存到数据库中)

    该代码如下所示:

    app.get('/hiscores', function(request, response) {
      db.view('top_scores', 'top_scores_index', function(err, body) {
      if (!err) {
        var scores = [];
          body.rows.forEach(function(doc) {
            scores.push(doc.value);		      
          });
          response.send(JSON.stringify(scores));
        }
      });
    });
    
    app.get('/save_score', function(request, response) {
      var name = request.query.name;
      var score = request.query.score;
    
      var scoreRecord = { 'name': name, 'score' : parseInt(score), 'date': new Date() };
      db.insert(scoreRecord, function(err, body, header) {
        if (!err) {       
          response.send('Successfully added one score to the DB');
        }
      });
    });
  6. 我们尚未对这些新 URL 执行任何调用,但仍然可以直接从浏览器调用上述 URL 来测试数据库连接。

    单击 Git Status,将所有更改推送到 DevOps Services Git 存储库,然后等待应用程序部署到 Bluemix。然后在浏览器中输入以下 URL,将 URL 中的 guesstheword 替换为您为应用程序选择的主机名。

    http://guesstheword.mybluemix.net/save_score?name=Bob&score=4

    您应看到一条成功消息。输入以下 URL,再次将 guesstheword 替换为您的应用程序主机名。

    http://guesstheword.mybluemix.net/hiscores

    您刚添加的条目应显示为 JSON 编码格式。

    [{"score":4,"name":"Bob","date":"2014-05-07T12:12:31.093Z"}]

    这表明我们使用该数据库的代码正在正常工作。

  7. 现在创建缺少的 JavaScript 文件,用于填写游戏主页上的分数表。在 Web 编辑器中,右键单击文件夹 public/js 并选择 New > File该图显示了在 Web 编辑器中创建一个文件的过程

    将该文件命名为 hiscores.js 并添加以下内容。

    /**
     * Hiscores
     */
    
    function createTableRow(name, score, date) {
      var dateObj = new Date(date);
      var formattedDate = dateObj.toLocaleDateString() + " " + dateObj.toLocaleTimeString();
      return '<tr> <td>' + score + '</td><td>' + name + '</td><td>' + formattedDate + '</td></tr>';
    }
    
    /**
     * Populate the hiscore table by retrieving top 10 scores from the DB. 
     * Called when the DOM is fully loaded.
     */
    function populateTable() {	
      var table = $("#hiscore_table tr");
      $.get("/hiscores", function (data) {
        var hiscores = JSON.parse(data);
        hiscores.forEach(function (hiscore) {
          var html = createTableRow(hiscore.name, hiscore.score, hiscore.date);
          table.last().after(html);		
        });
      });	
    }
    
    $(populateTable);

    此代码在该表中填入了从数据库获取的高分。将更改推送到 DevOps Services Git 存储库,等待应用程序部署在 Bluemix 上。现在,当我们访问该应用程序时,应该会看到之前添加到数据库中的虚假的分数条目。

    该图显示了高分表
    该图显示了高分表
  8. 我们还有最后一些问题需要解决,以便在游戏结束时保存分数。在 main.js 文件中,找到下面这一行:
    // TODO: Save name and score in DB

    将它替换为:

    saveScore(name, score);

    添加 saveScore() 函数:

    function saveScore(name, score) {
      $.ajax({url: "/save_score?name=" + name + "&score=" + score, cache : false}).done(function(data) {
        window.location.replace("/"); // Go to hiscore page
      });
    }
  9. 最后,通过添加以下代码作为 main.jade 文件中的第一个 div,创建一个 Bootstrap “Game Over” 对话框:
    div(id="name-dialog", class="modal fade")
      div(class="modal-dialog")
        div(class="modal-content")
          div(class="modal-header")
            h4(class="modal-title")
          div(class="modal-body")
            div(class="divDialogElements")
              label(for="name-input", style="display:table-cell; width:100%") Enter your name:
              input(class="xlarge", id="name-input", name="xlInput", type="text", style="display:table-cell; width:100%")            				
          div(class="modal-footer")
            button(type="button", class="btn btn-ok") OK
  10. 推送更改,等待自动部署,然后再玩一次该游戏,确认您的分数已保存在数据库中并显示在主页上的表中。 该图显示了最终完成的游戏
    该图显示了最终完成的游戏

结束语

本文使用 IBM DevOps Services 构建了一个 GuesstheWord 游戏,并将该应用程序部署到 Bluemix 上。本文还介绍了如何在 Web 编辑器或 Eclipse(或另一个 IDE)中开发代码,然后将它们推送到 IBM DevOps Services Git 存储库。此外,还解释了如何设置自动部署,使应用程序会在推送新更改后立即重新部署。您还学习了如何在本地调试应用程序,以及如何排除应用程序在 Bluemix 上运行时可能出现的问题。您现在已经为开发自己的 Bluemix 应用程序做好了准备。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing
ArticleID=981327
ArticleTitle=用 Cloudant 在 Bluemix 上构建一个简单的文字游戏应用程序
publish-date=08282014