构建移动 Web 应用程序来帮助流浪狗,第 1 部分

栏目: 数据库 · 发布时间: 6年前

内容简介:繁华都市中的流浪狗受伤或患病的风险很高。专门帮助流浪狗的组织一定程度上依赖于居民的提醒,向他们告知处于困境的狗。但他们收到的信息通常是不完善的,这妨碍了他们寻找和帮助动物的工作。最近,我开始考虑如何通过技术帮助解决这个问题。我构想了一个移动应用程序,居民可使用它轻松地向中央数据库报告受伤或患病的流浪狗,包括照片和 GPS 位置。但我知道,配置、编排和维护各种组件需要耗费时间和精力,而且我还需要确定在何处托管和如何托管该应用程序。在这期间,我听说了

繁华都市中的流浪狗受伤或患病的风险很高。专门帮助流浪狗的组织一定程度上依赖于居民的提醒,向他们告知处于困境的狗。但他们收到的信息通常是不完善的,这妨碍了他们寻找和帮助动物的工作。

最近,我开始考虑如何通过技术帮助解决这个问题。我构想了一个移动应用程序,居民可使用它轻松地向中央数据库报告受伤或患病的流浪狗,包括照片和 GPS 位置。但我知道,配置、编排和维护各种组件需要耗费时间和精力,而且我还需要确定在何处托管和如何托管该应用程序。

在这期间,我听说了 developerWorks Premium 。这个付费计划包含 12 个月的 IBM Bluemix(我已在以前的许多教程中熟悉的一个平台)订阅,以及针对开发服务器和托管服务的额外的每月贷款,包括对象存储和数据存储服务。我认识到此计划为我提供了足够的贷款来设计应用程序的原型。而且服务的托管性质大大减少了平时处理安全和配置问题时需要投入的工作。

Cloudant 实例将它的内容公开给一个 REST 接口,这使得通过标准 HTTP 请求创建、删除和搜索已存储的文档变得很容易。

在这个由两部分组成的教程中,我将介绍如何构建我的原型。最终得到的代码可利用现代智能电话功能(比如内置 GPS 和摄像头),在所有移动设备上正常运行,而且可以轻松地扩展该应用程序来涵盖其他使用场景。

从移动用户收集数据只是解决方案的一部分。救援机构还需要采用某种方式来查看提交的报告和照片,并在地图上识别指定的位置。因此,示例应用程序还包含一个强大的搜索引擎,它集成了地图,针对移动用途进行了全面优化。

在幕后,应用程序编排各种 Bluemix 和第三方服务。Bluemix 服务包括 Object StorageCloudant NoSQL DB ,前者为上传的照片提供一个安全的存储区域,后者为提交的报告提供一个 NoSQL 数据层。该应用程序还使用 Google Static Maps API 生成一个标明了特定位置的地图。在客户端,您将使用 Bootstrap 为应用程序创建一个移动友好的 UI。在服务器上,您将使用 Silex PHP 微型框架 来管理应用程序流。您可以将应用程序托管在Bluemix上。

而且在学习此教程的过程中,将会构建一些代码。作为参考,您可以单击 获取代码 按钮来查看(或获取)我的完整项目代码。如果想要尝试运行该应用程序,可单击 运行应用程序 按钮。请记住,这是一个公开演示,所以不要向该演示上传任何机密或敏感信息。(您还可以使用该应用程序的方便的 System Reset 按钮来擦除所有上传的数据。)

运行应用程序 获取代码

需要做的准备工作

任何使用 Google Static Maps API 的应用程序都必须遵守 Google APIs 服务条款Google Maps 服务条款Google 隐私政策 ,以及其他任何相关的法律公告或条款。类似地,确保您遵守所有 Bluemix 服务的服务条款。

  • 一个 developerWorks Premium 订阅。
  • 一个 Google 帐户
  • 基本熟悉 BootstrapPHP
  • 基本了解 Cloudant(或 CouchDB )中的常见数据库操作。
  • 一个 Apache/PHP 开发环境,无论是在本地开发环境还是托管在云中。如果更喜欢托管选项,那么您应该知道, developerWorks Premium 会员每月会获得用于一个托管 SoftLayer 开发服务器的贷款
  • Composer (PHP 依赖项管理器)。
  • Cloud Foundry 命令行工具
  • 一个文本编辑器或 IDE。

1

创建存根应用程序

第一步是使用 Silex PHP 微型框架和其他依赖性技术初始化一个基本应用程序。您可以使用 Composer 轻松地下载和安装这些组件。

  1. 将一下内容保存在名为 $APP_ROOT /composer.json 的 Composer 配置文件中,其中 $APP_ROOT 是您的开发环境中的项目目录:
    { 
        "require": { 
            "silex/silex": "*", 
            "twig/twig": "*", 
    
            "symfony/validator": "*", 
            "guzzlehttp/guzzle": "*", 
            "php-opencloud/openstack": "*"
        }, 
        "minimum-stability": "dev", 
        "prefer-stable": true 
     }
  2. 从您的操作系统命令行或 Shell,运行以下命令来下载和安装组件:
    php composer.phar install
  3. $APP_ROOT 下,创建一个名为 public 的目录(存储所有可通过网络访问的文件)、一个名为 views 的目录(存储所有视图)和一个 config.php 文件(存储配置信息)。
  4. Silex 依靠 URL 重写来获得 “友好的 URL”,所以此时也是配置 Web 服务器的 URL 重写规则的好时机。因为该应用程序最终将部署到一个 Apache Web 服务器,可以创建一个包含以下内容的 $APP_ROOT /.htaccess 文件:
    <IfModule mod_rewrite.c> 
        Options -MultiViews 
        RewriteEngine On 
        #RewriteBase /path/to/app 
        RewriteCond %{REQUEST_FILENAME} !-f 
        RewriteRule ^ index.php [QSA,L] 
     </IfModule>
  5. 要使该应用程序更容易访问,还可以在开发环境中定义一个新的虚拟主机,并将它的文档根目录指向 $APP_ROOT /public。尽管是可选操作,但推荐您执行这一步,因为它会在 Bluemix 上创建与目标部署环境较接近的副本。请参阅 Apache Virtual Host 文档 ,了解使用虚拟主机的更多信息。
    • 如果您定义了一个名为 stray-assist.localhost(举例而言)的新虚拟主机,您就能够通过 URL(比如 http://stray-assist.localhost/index)访问您的应用程序。(您可能需要更新网络的本地 DNS 服务器,向其告知这个新主机。)
    • 如果决定不使用虚拟主机,而是将文件存储在 Web 服务器文档根目录下的 stray-assist/ 目录中(举例而言),那么您将能够通过 URL(比如 http://localhost/stray-assist/public/index)访问您的应用程序。
  6. $APP_ROOT /public/index.php 下创建包含以下内容的控制器脚本:
    <?php 
     // use Composer autoloader 
     require '../vendor/autoload.php'; 
    
     // load configuration 
     require '../config.php'; 
    
     // load classes 
     use Symfony\Component\HttpFoundation\Request; 
     use Symfony\Component\HttpFoundation\Response; 
     use Symfony\Component\Validator\Constraints as Assert; 
     use GuzzleHttp\Psr7\Stream; 
     use GuzzleHttp\Psr7; 
     use Silex\Application; 
    
     // initialize Silex application 
     $app = new Application(); 
    
     // turn on application debugging 
     // set to false for production environments 
     $app['debug'] = true; 
    
     // load configuration from file 
     $app->config = $config; 
    
     // register Twig template provider 
     $app->register(new Silex\Provider\TwigServiceProvider(), array( 
      'twig.path' => __DIR__.'/../views', 
     )); 
    
     // register validator service provider 
     $app->register(new Silex\Provider\ValidatorServiceProvider()); 
    
     // register session service provider 
     $app->register(new Silex\Provider\SessionServiceProvider());
    目前为止,脚本代码初始化了 Silex 框架和必要的组件类。该脚本还从应用程序配置文件读入配置数据,初始化 Twig 模板呈现器,并向 Silex 注册它。
  7. 继续完成该脚本,添加对不同路由的路由器回调,并定义要在每个路由与传入的请求匹配时调用的函数 — get() 用于 GET 请求、 post() 用于 POST 请求等:
    // index page handlers 
     $app->get('/', function () use ($app) { 
      return $app->redirect($app["url_generator"]->generate('index')); 
     }); 
    
     $app->get('/index', function () use ($app) { 
      return $app['twig']->render('index.twig', array()); 
     }) 
     ->bind('index'); 
    
     // report submission form 
     $app->get('/report', function (Request $request) use ($app) { 
      // todo 
     }) 
     ->bind('report'); 
    
     // search form 
     $app->get('/search', function (Request $request) use ($app) { 
      // todo 
     }) 
     ->bind('search'); 
    
     // legal page handler 
     $app->get('/legal', function (Request $request) use ($app) { 
     // todo 
     }) 
     ->bind('legal'); 
    
     // reset handler 
     $app->get('/reset-system', function (Request $request) use ($app) { 
      // todo  
     }) 
     ->bind('reset-system'); 
    
     $app->run();
    要匹配的 URL 路由作为第一个参数传递给每个 HTTP 方法;第二个参数是一个函数,指定了将在路由与一个传入请求匹配时执行的操作。例如,在您的脚本中, /index 回调呈现了名为 index.twig 的主要应用程序页面模板。

    该脚本还设置了一个对 / 路由的路由器回调,以重定向到 /index 路由。类似地,为 /search/report 等路由定义了其他回调。在学习教程的过程中,将填入这些回调。

  8. 作为准备工作的最后一部分,创建一个基于 Bootstrap 的简单 UI,其中包含页眉、页脚和内容区域。这是一个示例,您将为后续代码清单中给出的所有应用程序视图使用它:
    <!DOCTYPE html> 
     <html lang="en"> 
      <head> 
        <meta charset="utf-8"> 
        <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
        <meta name="viewport" content="width=device-width, initial-scale=1"> 
        <title>Stray Assist</title> 
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> 
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> 
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> 
        <!--[if lt IE 9]> 
          <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> 
          <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> 
        <![endif]-->    
      </head> 
      <body> 
    
        <div class="container"> 
          <div class="panel panel-default"> 
            <div class="panel-heading clearfix"> 
              <h4 class="pull-left">Stray Assist</h4> 
            </div> 
          </div> 
    
          {% for message in app.session.flashbag.get('success') %} 
          <div class="alert alert-success"> 
            <strong>Success!</strong> {{ message }} 
          </div> 
          {% endfor %}      
    
          {% for message in app.session.flashbag.get('error') %} 
          <div class="alert alert-danger"> 
            <strong>Error!</strong> {{ message }} 
          </div> 
          {% endfor %} 
    
          <div> 
            <!-- content body -->      
          </div> 
        </div> 
        
        <div class="container"> 
          <p class="text-center"> 
            <a href="{{ app.url_generator.generate('legal') }}" role="button" class="btn btn-default btn-sm">Legal</a> 
            <a href="{{ app.url_generator.generate('legal') }}" role="button" class="btn btn-danger btn-sm">System Reset</a> 
        </div> 
        
      </body> 
     </html>

完成所有准备工作后,现在可以开始构建应用程序了。

2

创建应用程序欢迎页面。

在第 1 步中,您在对 //index 路由的回调中填充了必要的代码来呈现索引模板。现在,使用来自第 1 步的 Bootstrap 模板在 $APP_ROOT /views/index.twig 中创建相应的视图脚本,并在内容区域填入一个基本菜单:

<div class="panel panel-default"> 
        <div class="panel-heading">Report a stray dog near you that needs help.</div> 
        <div class="btn-group btn-group-justified"> 
          <a role="button" class="btn btn-primary" href="{{ app.url_generator.generate('report') }}">Report</a> 
        </div> 
      </div> 
      
      <div class="panel panel-default"> 
        <div class="panel-heading">Search for previous reports and track them on a map.</div> 
        <div class="btn-group btn-group-justified"> 
          <a role="button" class="btn btn-primary" href="{{ app.url_generator.generate('search') }}">Search</a> 
        </div> 
      </div>

请注意,使用了 Silex 的 URL 生成器来为 /search/report 路由动态创建 URL。此方法可确保无论应用程序托管在域根目录还是域根目录的子目录中,应用程序路由都会保持有效。

打开您的浏览器并访问 http://stray-assist.localhost/index(或您的开发环境中的等效 URL)来查看欢迎页面。该页面包含一个访问报告功能的按钮和一个访问搜索功能的按钮:

构建移动 Web 应用程序来帮助流浪狗,第 1 部分

构建移动 Web 应用程序来帮助流浪狗,第 1 部分

3

创建报告接口

现在所有基本部分都在和谐运行,是时候做点更有意义的事了。用户需要采用某种方式来提交他们遇到的受伤流浪狗的报告,而且重要的是,这些报告是结构化的,可使用多种条件进行全面搜索。

  1. 创建一个视图脚本 $APP_ROOT /views/report.twig,并以以下代码开头:
    <div> 
      <form method="post" action="{{ app.url_generator.generate('report') }}"> 
    
        <input type="hidden" name="latitude" value="{{ latitude }}" /> 
        <input type="hidden" name="longitude" value="{{ longitude }}" />
  2. 添加以下 3 个主要代码段:
    • 在 Identification 部分,用户在其中输入有关流浪狗的细节,比如毛色、性别、年龄和任何标识标记:
      <div class="panel panel-default"> 
            <div class="panel-heading clearfix"> 
              <h4 class="pull-left">Identification</h4> 
            </div> 
            <div class="panel-body"> 
              <div class="form-group"> 
                <label for="color">Color</label> 
                <input type="text" class="form-control" id="color" name="color" required="true"></input> 
              </div> 
              <div class="form-group"> 
                <label for="gender">Sex</label> 
                <select name="gender" id="gender" class="form-control" required="true"> 
                  <option value="male">Male</option> 
                  <option value="female">Female</option> 
                  <option value="unknown">Unknown</option> 
      
                </select> 
              </div> 
              <div class="form-group"> 
                <label for="gender">Age</label> 
                <select name="age" id="age" class="form-control" required="true"> 
                  <option value="pup">Pup</option> 
                  <option value="adult">Adult</option> 
                  <option value="unknown">Unknown</option> 
      
                </select> 
              </div> 
              <div class="form-group"> 
                <label for="identifiers">Identifying marks</label> 
                <textarea name="identifiers" id="identifiers" class="form-control" rows="3"></textarea> 
              </div> 
            </div> 
          </div>
    • 在 Details 部分,支持采用自由格式对问题或损伤进行描述:
      <div class="panel panel-default"> 
            <div class="panel-heading clearfix"> 
              <h4 class="pull-left">Details</h4> 
            </div> 
            <div class="panel-body"> 
              <div class="form-group"> 
                <label for="description">Description of injury</label> 
                <textarea name="description" id="identifiers" class="form-control" rows="3" required="true"></textarea> 
              </div>               
            </div> 
          </div>
    • 在 Reporter 部分,会询问用户的姓名、电话号码和电子邮件:
      <div class="panel panel-default"> 
            <div class="panel-heading clearfix"> 
              <h4 class="pull-left">Reporter</h4> 
            </div> 
            <div class="panel-body"> 
              <div class="form-group"> 
                <label for="name">Name</label> 
                <input type="text" class="form-control" id="name" name="name" required="true"></input> 
              </div> 
              <div class="form-group"> 
                <label for="phone">Phone number</label> 
                <input type="text" class="form-control" id="phone" name="phone" required="true"></input> 
              </div>              
              <div class="form-group"> 
                <label for="email">Email address</label> 
                <input type="email" class="form-control" id="email" name="email" required="true"></input> 
              </div>              
              <div class="form-group"> 
                <button type="submit" name="submit" class="btn btn-primary">Submit</button> 
              </div>          
            </div> 
          </div> 
        </form> 
       </div>
  3. 要使用用户的当前位置给每个报告标上地理标记,可使用大多数浏览器所包含的 HTML5 地理位置 API。将此代码添加到顶部附近,以便更新视图脚本:
    {% if not latitude and not longitude %}    
     <script> 
     $(document).ready(function() { 
      if (navigator.geolocation) { 
        navigator.geolocation.getCurrentPosition(handle_geo_query, handle_error); 
      } 
      
      function handle_geo_query(location) { 
        window.location = '?latitude=' + location.coords.latitude + '&longitude=' + location.coords.longitude; 
      } 
      
      function handle_error(e) { 
        alert('An error occurred during geolocation.'); 
      } 
     }); 
     </script>        
     {% endif %}
    通常,当地理标记代码在浏览器中运行时,会弹出一个对话框,向用户请求公开当前位置的权限: 构建移动 Web 应用程序来帮助流浪狗,第 1 部分
    构建移动 Web 应用程序来帮助流浪狗,第 1 部分

    用户必须显式允许此公开权限,应用程序才能收到当前位置,该位置通常通过最近的蜂窝塔或设备上的 GPS 系统进行标识。如果用户同意,脚本将会调用 handle_geo_query 函数,该函数会读入经纬度并重新加载页面 URL,这一次会将这些值作为 GET 参数附加到 URL。

    如果出现错误(例如如果用户拒绝共享位置的权限或该位置无法识别),则会调用 handle_error 函数来显示一个提醒框。(您可以根据错误类型来调节错误消息。)在这里,报告使用了纬度 0 和经度 0 来进行地理标记。

  4. 更新 $APP_ROOT /public/index.php 中的控制器脚本,向 /report 路由的现有存根回调函数添加以下代码:
    <?php 
    
     // Silex application initialization  snipped 
    
     // report submission form 
     // get lat/long from browser and set as hidden form fields 
     $app->get('/report', function (Request $request) use ($app) { 
      $latitude = $request->get('latitude'); 
      $longitude = $request->get('longitude'); 
      return $app['twig']->render('report.twig', array('latitude' => $latitude, 'longitude' => $longitude)); 
     }) 
     ->bind('report'); 
    
     // other callbacks  snipped 
    
     $app->run();
    该回调检查 URL 来获得通过地理位置获取的经纬度,将这些值作为模板变量传输到视图脚本,然后呈现相关表格(您可以在 http://stray-assist.localhost/report 或您开发环境中的等效 URL 中看到该表格)。

4

初始化 Cloudant 数据库实例

用户现在可以与应用程序交互并提交报告。Bluemix 中的 Cloudant NoSQL DB 服务使得将报告存储在数据库中变得很容易。该服务配置了一个可以绑定到您的应用程序的空 Cloudant 数据库实例。这个数据库实例将它的内容公开给一个 REST 接口,使得通过标准 HTTP 请求创建、删除和搜索已存储的文档变得很容易。默认服务只提供了有限的免费存储配额,但借助您的 developerWorks Premium 订阅,您通常可以拥有足够的贷款来满足大部分合理的数据存储需求。

  1. 登录到您的 Bluemix 帐户。在控制台中,从可用服务中选择 Data & Analytics
  2. 单击 + 按钮,然后选择 Cloudant NoSQL DB 服务。
  3. 确保 Connect to 字段已设置为 Leave unbound ,而且您使用了共享计划。(因为此服务实例最初将在未绑定状态下运行,所以您可以在不同的主机上开发应用程序,将数据库服务实例托管在 Bluemix 上。)单击 Create
  4. 单击 LAUNCH 初始化 Cloudant 数据库服务实例。
  5. 在服务信息页面上,单击 Service Credentials 选项卡来查看服务实例的用户名和密码: 构建移动 Web 应用程序来帮助流浪狗,第 1 部分
    构建移动 Web 应用程序来帮助流浪狗,第 1 部分
  6. 将相关的细节从 JSON 凭证块中复制并粘贴到 $APP_ROOT /config.php 上您应用程序的配置文件中。然后,在同一个 Cloudant 页面上,选择 Manage 选项卡并单击 LAUNCH 启动 Cloudant 仪表板。
  7. 单击顶部菜单栏中的 Create Database 按钮,输入 stray_assist 作为新数据库的名称,然后单击 Create

您的 Cloudant 数据库实例现在已经激活,而且您的应用程序已经配置好,可以使用该实例。在下一步中,将会使用此数据库存储用户报告。

5

验证和保存报告

有了持久数据存储后,您现在的任务是添加一个表单处理器,用于接受和验证用户提交内容并将其保存到数据库中。将这一步的所有代码添加到 $APP_ROOT /public/index.php。该代码定义了一个回调来通过 POST 处理表单提交。

表单代码首先收集各种输入参数:毛色、性别、年龄、描述和联系信息,并使用 Symfony 的各种验证器来验证每一项:

<?php 

 // Silex application initialization  snipped 

 // initialize HTTP client 
 $guzzle = new GuzzleHttp\Client([ 
  'base_uri' => $app->config['settings']['db']['uri'] . '/', 
 ]); 

 // report submission handler 
 $app->post('/report', function (Request $request) use ($app, $guzzle) { 
  // collect input parameters 
  $params = array( 
    'color' => strip_tags(trim(strtolower($request->get('color')))), 
    'gender' => strip_tags(trim($request->get('gender'))), 
    'age' => strip_tags(trim($request->get('age'))), 
    'identifiers' => strip_tags(trim($request->get('identifiers'))), 
    'description' => strip_tags(trim($request->get('description'))), 
    'email' => strip_tags(trim($request->get('email'))), 
    'name' => strip_tags(trim($request->get('name'))), 
    'phone' => (int)strip_tags(trim($request->get('phone'))), 
    'latitude' => (float)strip_tags(trim($request->get('latitude'))), 
    'longitude' => (float)strip_tags(trim($request->get('longitude'))) 
  ); 
  
  // define validation constraints 
  $constraints = new Assert\Collection(array( 
    'color' => new Assert\NotBlank(array('groups' => 'report')), 
    'gender' => new Assert\Choice(array('choices' => array('male', 'female', 'unknown'
 ), 'groups' => 'report')), 
    'age' => new Assert\Choice(array('choices' => array('pup', 'adult', 'unknown'
 ), 'groups' => 'report')), 
    'description' => new Assert\NotBlank(array('groups' => 'report')), 
    'email' =>  new Assert\Email(array('groups' => 'report')), 
    'name' => new Assert\NotBlank(array('groups' => 'report')), 
    'phone' => new Assert\Type(array('type' => 'numeric', 'groups' => 'report')), 
    'latitude' => new Assert\Type(array('type' => 'float', 'groups' => 'report')), 
    'longitude' => new Assert\Type(array('type' => 'float', 'groups' => 'report')), 
    'identifiers' => new Assert\Type(array('type' => 'string', 'groups' => 'report')) 
  )); 
  
  // validate input and set errors if any as flash messages 
  // if errors, redirect to input form 
  $errors = $app['validator']->validate($params, $constraints, array('report')); 
  if (count($errors) > 0) { 
    foreach ($errors as $error) { 
      $app['session']->getFlashBag()->add('error', 'Invalid input in field ' . $error->getPropertyPath()); 
    } 
    return $app->redirect($app["url_generator"]->generate('report')); 
  }

如果输入有效,输入值会格式化为适合插入 Cloudant 中的 JSON 文档:

// if input passes validation 
  // produce JSON document with input values 
  $doc = [ 
    'type' => 'report', 
    'latitude' => $params['latitude'], 
    'longitude' => $params['longitude'], 
    'color' => $params['color'], 
    'gender' => $params['gender'], 
    'age' => $params['age'], 
    'identifiers' => $params['identifiers'], 
    'description' => $params['description'], 
    'name' => $params['name'], 
    'phone' => $params['phone'], 
    'email' => $params['email'] 
    'datetime' => time() 
  ];

然后使用 Guzzle HTTP 客户端制定一个包含该 JSON 文档的 POST 请求,并将它发送到 Cloudant REST API;这会创建一个新文档并保存在 Cloudant 数据库中:

// save document to database 
  // retrieve unique document identifier 
  $response = $guzzle->post($app->config['settings']['db']['name'], [ 'json' => $doc ]); 
  $result = json_decode($response->getBody()); 
  $id = $result->id;

最后,客户端浏览器被重新定向回应用程序的索引页并显示一条成功通知:

$app['session']->getFlashBag()->add('success', 'Report added.'); 
  return $app->redirect($app["url_generator"]->generate('index')); 
 }); 

 // other callbacks  snipped 

 $app->run();

要查看表单处理器的实际运行情况,可尝试通过该应用程序添加一个报告。然后浏览回 Bluemix 控制台,启动 Cloudant 仪表板并查看您数据库中的 所有文档 ,查看一条包含新添加的报告的细节的新记录:

构建移动 Web 应用程序来帮助流浪狗,第 1 部分

构建移动 Web 应用程序来帮助流浪狗,第 1 部分

6

初始化 Object Storage 实例

要向报告系统添加的下一个特性是附加照片的能力。在这一步中,可以借助 Bluemix Object Storage 服务来实现照片附加。Object Storage 使您能轻松地将非结构化数据存储在云中。该服务支持 OpenStack Swift API 并按照 Swift 的 3 层分层结构来组织数据:

  • 分层结构中的主要单元是 帐户 。帐户对应于用户;要访问某个帐户,用户必须提供身份验证凭证。
  • 一个帐户可托管多个 容器 ,这些容器大体等效于传统文件系统中的文件夹或子目录。
  • 每个容器可存储多个 对象 ,对象可以是文件或数据。对象可以拥有用户定义的额外的元数据。通常,您可存储无限多个对象。

此描述仅是冰山一角。要全面了解 Swift(以及作为延伸的 Object Storage)概念,请查阅 Joe Arnold 撰写的图书 OpenStack Swift (O'Reilly,ISBN 978-1-4919-0082-6)。作为 developerWorks Premium 计划的一部分,这本书以及数百本其他编程图书可通过 Safari Books Online 获得。

要在 Bluemix 上初始化一个新 Object Storage 服务实例:

  1. 登录到您的 Bluemix 帐户。从控制台中的服务列表中选择 Storage
  2. 单击 + 按钮,然后选择 Object Storage 服务。
  3. 确保 Connect to 字段已设置为 Leave unbound ,并选择免费计划或标准计划。(与第 4 步中的 Cloudant 服务实例一样,在未绑定状态下运行该服务,使得在一个单独的主机上开发应用程序并将对象存储托管在 Bluemix 上成为可能。)单击 Create
  4. 服务实例初始化后,单击 Service Credentials 选项卡查看服务实例的 URL、地区、用户名、密码和其他凭证: 构建移动 Web 应用程序来帮助流浪狗,第 1 部分
    构建移动 Web 应用程序来帮助流浪狗,第 1 部分
    将相关的细节从 JSON 凭证块中复制并粘贴到 $APP_ROOT /config.php 上您应用程序的配置文件中。

7

在报告中支持照片附加

您现在已准备好向应用程序添加照片上传支持:

  1. 编辑 $APP_ROOT /views/report.twig 上的表单,添加以下两段代码。(请参阅我的项目中的 report.twig 文件,查看在文件中的何处添加这段新代码。)
    • 更新表单定义来支持文件上传:
      <form method="post" enctype="multipart/form-data" action="{{ app.url_generator.generate('report') }}"> 
          <input type="hidden" name="MAX_FILE_SIZE" value="300000000" />
    • 添加一个新文件上传字段:
      <div class="form-group"> 
        <label for="upload">Photo</label> 
        <span class="btn btn-default btn-file"> 
          <input type="file" name="upload" /> 
        </span> 
       </div>
  2. php-opencloud package 已在第 1 步中包含在 Composer 依赖文件中,所以您已将它安装在开发系统中。

    要与 Object Storage API 进行交互,最简单的解决方案是使用 php-opencloud ,这是一个用于基于 OpenStack 的部署的 PHP SDK。这个 SDK 围绕 Swift API 方法提供了一个方便的 PHP 包装器,所以您只需调用合适的方法,例如 createContainer()listContainers() ,客户端库会负责为您创建请求和解码响应。要使用 php-opencloud ,可将以下代码(使用 Silex 依赖注入容器来初始化一个新 OpenStack 客户端)添加到 $APP_ROOT/public/index.php:
    <?php 
    
     // Silex application initialization  snipped 
    
     // initialize OpenStack client 
     $openstack = new OpenStack\OpenStack(array( 
      'authUrl' => $app->config['settings']['object-storage']['url'], 
      'region'  => $app->config['settings']['object-storage']['region'], 
      'user'    => array( 
        'id'       => $app->config['settings']['object-storage']['user'], 
        'password' => $app->config['settings']['object-storage']['pass'] 
     ))); 
     $objectstore = $openstack->objectStoreV1(); 
    
     // other callbacks  snipped 
    
     $app->run();
  3. 向 index.php 添加以下 3 段代码来更新对 /report POST

    处理函数的回调。(请参阅我的项目中的 index.php 文件,查看在何处添加这些代码。)

    • 'upload' => $request->files->get('upload') 
        );
    • 'upload' => new Assert\Image(array('groups' => 'report')) 
      'file' => !is_null($params['upload']) ? trim($params['upload']->getClientOriginalName()) : '',
    • // if report includes photo 
       // create container in object storage service 
       // with name = document identifier 
       // and upload photo to it 
       if (!is_null($params['upload'])) { 
        $container = $objectstore->createContainer(array( 
          'name' => $id 
        )); 
        $stream = new Stream(fopen($params['upload']->getRealPath(), 'r')); 
        $options = array( 
          'name'   => trim($params['upload']->getClientOriginalName()), 
          'stream' => $stream, 
        ); 
        $container->createObject($options);  
       }

    借助这些更新,该脚本就可以检查有效的文件上传。如果该文件存在,该脚本会使用 OpenStack 客户端将文件传输到 Object Storage 服务实例。

    现在(将报告作为新文档保存到 Cloudant 数据库中后),OpenStack 客户端的 createContainer() 方法会创建一个与文档标识符匹配的新容器,上传的文件会通过客户端的 createObject()

    方法保存在该容器中。图像文件名称作为一个额外的属性存储在 Cloudant 文档中。

  4. 要查看照片附加支持的实际运用,可尝试通过该应用程序添加一个报告,并使用新的文件上传字段向其添加一张照片。浏览回 Bluemix 控制台并启动 Object Storage 服务实例的仪表板。您可以看到一个新文件夹,其中包含您上传的照片: 构建移动 Web 应用程序来帮助流浪狗,第 1 部分
    构建移动 Web 应用程序来帮助流浪狗,第 1 部分

结束语

现在,您已有了一个系统的有效原型,该系统允许用户报告受伤的流浪狗,并使用地理标记和照片加以完善。

在本教程的第二部分也即最后一部分中,我将介绍如何为应用程序构建一个搜索接口,以及如何使用 Google Maps 将报告映射到实际的街道地址。这些特性旨在使救援机构更容易获得报告,通过搜索条件或关键词帮助他们快速找到相关记录。最后,我将展示如何将应用程序部署到 Bluemix 云中的一个安全的、稳健的和可扩展的环境中。


以上所述就是小编给大家介绍的《构建移动 Web 应用程序来帮助流浪狗,第 1 部分》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java和Android开发学习指南(第二版)

Java和Android开发学习指南(第二版)

Budi Kurniawan / 李强 / 人民邮电出版社 / 2016-3 / 69.00元

本书是Java语言学习指南,特别针对使用Java进行Android应用程序开发展开了详细介绍。 全书共50章,分为两大部分。第1部分(第1章到第22章)主要介绍Java语言基础知识及其功能特性。第2部分(第23章到第50章)主要介绍如何有效地构建Android应用程序。 本书适合任何想要学习Java语言的读者阅读,特别适合想要成为Android应用程序开发人员的读者学习参考。一起来看看 《Java和Android开发学习指南(第二版)》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具