使用 XMPP、SMS、pureXML 和 PHP 创建警报系统

开发一个具有自动更新功能的欧元汇率应用程序

得益于 pureXML® 对 IBM DB2® 开发人员提供的原生 XML 支持,我们可以将 XML 数据直接加载到数据库,无需向自己的应用程序添加此功能。请跟随本教程,将一个包含欧元汇率的 XML 文件导入一个 IBM DB2 数据库,并使用特殊的 XQuery 和 SQL/XML 函数将这个 XML 分割为独立的数据库行。还将创建一个 PHP 脚本,用于每天从 European Central Bank (ECB) Web 站点获取新的汇率。然后,将扩展这个脚本,使用 XMPP 协议将更新警报发送给 Google Talk 用户,以及使用 Clickatell SMS 网关服务、通过 SMS 文本消息发送给移动电话。最后,将创建一个 PHP 脚本,用于生成数据的 PNG (Portable Network Graphics) 图形。

Joe Lennon, 软件开发人员, Freelance

Joe Lennon 是一位来自爱尔兰科克市的软件开发人员,他今年 23 岁。Joe 目前为 Core International 工作,是 Web 应用程序和 Oracle PL/SQL 开发人员。他 2007 年毕业于 University College Cork,并获得商业信息系统专业的学位。他现在和他的女朋友 Jill 生活在科克市。



2010 年 1 月 07 日

开始之前

本教程针对希望开发由 IBM DB2 pureXML 数据库支持的数据库驱动应用程序的 Web 应用程序开发人员。为了跟随本教程,读者应该熟悉基本 PHP 代码并能够使用 Windows 命令提示符。理解本教程还需要一些数据库管理系统和 SQL 语言经验。完成本教程后,您将了解如何创建利用 pureXML 数据库的面向对象的 PHP Web 应用程序。

关于本教程

常用缩略词

  • API:应用程序编程接口
  • HTTP:超文本传输协议
  • RFC:征求意见请求
  • RPC:远程过程调用
  • SMS:短消息服务
  • SOAP:简单对象访问协议
  • TCP:传输控制协议
  • XML:可扩展标记语言
  • XMPP:可扩展消息传递与呈现协议

IBM DB2 Express-C 包含 pureXML。pureXML 是一种用于将 XML 数据原生地存储到关系数据库表中的方法,它允许用户查询、索引及操作这种数据。可以使用一系列函数,以无缝的方式混合使用关系数据和基于 XML 的数据。这允许开发同时发挥关系数据库表和 XML 数据的威力的应用程序。

现在,XML 被广泛用作可移植的数据存储格式,我们经常会找到公共数据和对 XML 格式的数据提供访问的 Web 服务。通过使用 IBM DB2 Express-C,可以开发将这种 XML 数据直接加载到数据库中的应用程序,创建由这种数据驱动的应用程序,无需将数据转换为关系列。当然,有时也需要进行数据转换。本教程中将介绍在 DB2 中处理 XML 的各种技术。

在本教程中,您将了解如何创建一个每天从 European Central Bank Web 站点查询欧元汇率的警报系统。如果发现一组新汇率,这个应用程序将把新的汇率数据加载到一个 DB2 数据库中,并以两种方式发送警报:作为一条 Google Talk (XMPP) 即时消息;作为一条 SMS 文本消息发送到移动电话。您还将了解到如何将大量 XML 数据导入 DB2 数据库,在本例中是加载欧元汇率的整个历史记录,时间一直上溯到 1999 年欧元首次使用时。在本教程的最后部分,还将开发一个生成当前年度各月最大汇率柱状图的 PHP 脚本。

前提条件

要跟随本教程中的步骤,需要安装以下软件:

  • IBM DB2 Express-C 9.5
  • PHP 5.2
  • XMPPHP 库

参见 参考资料 部分提供的链接,这些链接指向下载站点和带领您逐步安装和配置以上软件的文章。

项目文件夹

本教程假定您将所有源代码存储在文件夹 C:\currency 中。如果还没有这样做,那么现在就打开 Windows® Explorer 并导航到 C: 盘根目录来创建这个文件夹。在 C: 盘根目录中右键单击并选择 New>Folder,将文件夹命名为 currency。或者,下载 本教程源代码并将其解压缩到您的 C: 驱动器。

配置 PHP

为跟随本教程,需要对 PHP 配置文件进行一些修改。在 php.ini 中找到类似于 error_reporting = E_ALL 的行并将其更改为 error_reporting = E_ERROR

接下来,找到类似于 display_errors = On 的行并将其更改为 display_errors = Off。最后,向下滚动文件到 PHP 扩展部分。这些扩展中的大部分都是默认禁用的,禁用行的起始处有一个分号,用于注释掉这个特定的特性。要启用某个特性,只需删除该行前面的分号即可。现在,在 清单 1 中找到这些行并确保删除每行前面的分号(;)。

清单 1. 删除分号
extension = php_curl.dll
extension = php_gd2.dll
extension = php_mbstring.dll
extension = php_openssl.dll
extension = php_sockets.dll

创建帐户

在本小节中,将下载 XMPPHP 库并创建 Google Talk 和 Clickatell SMS 网关帐户。

XMPPHP 库

要与 Google Talk 即时消息传递服务通信,需要下载 XMPPHP 库,它允许您使用 PHP 代码向 Google Talk 发送消息。该文件作为一个 .tar.gz 归档文件分发,因此您需要一个支持这种文件格式的归档工具,比如 7-Zip。将这个归档文件解压缩到桌面上,然后复制 XMPPHP 文件夹并将其粘贴到项目文件夹 C:\currency 中。这个库包含在本教程的源代码中(参见 下载),因此,使用这些源代码文件的话,就不必单独下载 XMPPHP 库了。

Google Talk 帐户

要发送 Google Talk 消息,需要两个 Google Talk 帐户:一个用于发送消息的脚本,另一个用于接收消息的脚本。如果拥有一个 Gmail 帐户,就已经拥有了一个 Google Talk 帐户。如果没有,参见 参考资料 获取一个帐户注册链接。

除了需要注册两个 Google Talk 帐户外,还需登录其中一个帐户并添加另一个帐户作为联系人,操作方法是通过 Gmail 或使用 Google Talk 客户机。然后,需要退出该帐户并登录另一个帐户,登录后,您将被询问是否接受您刚才发出的聊天邀请。接受邀请,这两个帐户现在就可以相互通信了。

Clickatell SMS 网关帐户

要发送 SMS 文本消息警报,需要一个 Clickatell SMS 网关服务帐户。本小节中,将了解如何创建并配置 Clickatell 帐户,以便稍后开发警报脚本时能够向移动电话发送文本消息。

浏览到 Clickatell 的 Web 站点的 Buy Now 页面(参考资料 部分提供了一个链接)。您需要从该页面创建一个 Clickatell 帐户。从 New Customers 部分,打开 Elect Product 下拉列表并选择 Clickatell Central (API)。这将打开 Clickatell 注册表单,如 图 1 所示。

图 1. Clickatell 注册表单
注册表单的屏幕截图,用于选择产品、建立帐户和输入个人信息

注册过程包含 4 个步骤。在第 1 步中,Clickatell Central 产品应该已经被选中。第 2 步,为帐户选择想要的覆盖范围类型。如果不确定,则选择 International 选项。输入用户名和密码,如图 1 所示填写安全代码。第 3 步,在所有带星号(*)标记的字段中输入值。选中 Personal Use Only 将隐藏与公司相关的字段。最后,选中 I accept Clickatell's Terms and Conditions 复选框并单击 Continue 按钮。

最后一步是激活帐户并验证身份。Clickatell 将发送一封电子邮件到您在前一个页面中提供的地址(见 图 1),邮件中包含一个激活帐户的链接。单击这个链接将进入 My Account Login 页面,其中,产品、用户名和客户 ID 都已经预先填写好了,如 图 2 所示。

图 2. Clickatell My Account Login
Clickatell My Account Login 页面的屏幕截图,产品、用户名和客户 ID 已经预先填写好了

输入密码并单击 Login 登录您的帐户。现在应该看到一个如 图 3 所示的屏幕。

图 3. Clickatell Central 主页
Clickatell Central 主页测试消息框和移动电话号码验证的屏幕截图

在 Verify your Mobile Number 框中,检查移动电话号码是否正确,单击 Send Activation Code 按钮发送一条 SMS 文本消息到您的移动电话,其中包含一个用于激活的代码。接收到消息后,将消息中的代码输入到文本框中并单击 Verify Now。下一步是注册 HTTP/S API。单击顶端导航区域中的 Manage My Products 链接,从 My Connections 下拉列表中选择 HTTP。应该看到一个如 图 4 所示的屏幕。

图 4. Clickatell HTTP API 表单
Clickatell HTTP API 表单的屏幕截图

在 Name 字段中输入 Currency Alerts,保留其他字段的默认值(在 图 4 中,Dial Prefix 字段值是 Ireland (353),Callback Type 字段值是 HTTPGET,其他字段是空白)。现在单击 Submit 建立 API。现在将看到一个确认屏幕,单击 OK 返回 Manage my Products 屏幕。现在应该看到 Currency Alerts 连接,旁边列出了您的 API ID,如 图 5 所示。(注意:下面的屏幕截图中的 API ID 字段中,我故意空出了我的 API ID 值,以免您尝试使用该值 — 您提交请求时,将会看到自己的 API ID)。

图 5. Clickatell HTTP API ID
Clickatell HTTP API ID 的屏幕截图

现在,通过发送一条文本消息来测试该 API 是否有效。为此,需要以下信息:

  • Clickatell 用户名
  • Clickatell 密码
  • Clickatell HTTP API ID
  • 想要将消息发送到的移动电话号码,格式为国际格式,没有 + 号(例如 353875555555)
  • 要发送的文本(注意,如果使用的是免费测试积分,您的消息将被一个 Clickatell 标准消息替代。这种情况不会发生在使用付款积分发送的消息上。)

打开一个新的浏览器窗口,在地址栏中输入以下 URL(使用您的值替换)。为了展示格式,这个 URL 显示在多个行上。实际使用时,这个 URL 是单个字符串。

http://api.clickatell.com/http/sendmsg?user=xxxx&password=xxxxx
&api_id=xxxxxx&to=xxxxxx&text=xxxxxx

为使在本教程中构建的应用程序中的 SMS 警报生效,需要在 Clickatell Web 站点上购买一些积分。如果不想这样做,只需注释掉发送文本消息的行即可。我将在本教程的相应部分详细介绍这一点。

这个应用程序的所有先决条件都安装并配置好了,下面就可以创建数据库,为这个警报应用程序构建基础了。


构建数据库基础

在本小节中,将了解如何从 European Central Bank (ECB) Web 站点获取包含欧元汇率整个历史记录的 XML 文件,并将其加载到 IBM DB2 数据库中。然后,使用 XMLTABLE 函数将这个大型 XML 文档分割为多个独立文档,它们将会各自存储在数据库中的一行内,从而简化查询并提高查询性能。最后,创建一个允许使用常规 SQL 查询检索数据的数据库视图,不管数据是存储在关系列中还是存储为 XML 文档中的一个属性。

从 ECB 下载历史汇率

尽管本教程的 源代码 中包含了一个带有历史汇率的 XML 文件,但那些数据在您阅读本文时已经过时。因此,更明智的办法是从 ECB 站点下载最新文件(参见 参考资料 部分的链接)。您可以放心地覆盖源代码文件夹中的文件。

右键单击下载链接并选择 Save Target AsSave Link As 将汇率文件下载到自己的计算机。确保将该文件保存到项目文件夹 C:\currency 中。在本文撰写之时,这个文件大小约为 3 MB,但这个文件大小将不断增长,因为每一个工作日都会增加新的汇率数据。

在将这个 XML 文件导入 DB2 数据库之前,需要创建一个逗号分隔的 .del 文件,用于告知数据库如何导入数据。打开您钟爱的文本编辑器并在一个新文件中输入以下行: 1,"<XDS FIL='eurofxref-hist.xml'/>"

将这个文件保存在项目文件夹中,命名为 currency.del。如果使用 Notepad 作为文本编辑器,确保在保存对话框中用双引号括住文件名(如 “currency.del”),以防 Notepad 自动向文件名附加 .txt 扩展名。

创建 currency 数据库

下一步是创建一个新的 DB2 数据库。为此,打开 DB2 Command Editor(Start>Programs>IBM DB2>[DBNAME]>Command Line Tools>Command Editor)并发出以下命令:create database currency using codeset UTF-8 territory US

接下来,连接到这个新创建的数据库:connect to currency

在将数据导入数据库之前,需要创建一个表来存储数据。这个表不会被警报应用程序使用,它只是用于加载 XML 文档的中介表。要创建这个表,发出 清单 2 中的命令。

清单 2. 创建一个表以导入 XML 文件
create table temp_rates (
    id int primary key not null,
    data xml
)

将 XML 文件导入数据库

接下来,使用此前创建的 currency.del 文件将 XML 文档导入数据库。清单 3 将读取这个文件的内容,将逗号之前的值(这里是 1)放置到 temp_rates 表的 id 列中。<XDS> 标记告知 DB2 从何处读取 XML 数据文件并要它将读取的内容存储到数据列中。

清单 3. 将 XML 导入 DB2
import from "C:\currency\currency.del" of del
    xml from "C:\currency"
insert into temp_rates;

如果一切按计划进行,应该能够看到一个成功响应,类似于:SQL3149N "1" rows were processed from the input file. "1" rows were successfully inserted into the table. "0" rows were rejected

通过执行以下 SQL 语句验证数据是否成功插入:select * from temp_rates。这应该自动切换到 Query Results 选项卡,其中显示了一个带有 ID 1 和数据列(指出它包含 XML 内容)的行(参见 图 6)。

图 6. Query Results
数据库的 Query Results 选项卡的屏幕截图

数据列下有一个带有 3 个点的按钮(...),单击这个按钮打开 XML Document Viewer 窗口。在这个窗口中,应该能够浏览刚才导入的汇率数据的 XML 表示(见 图 7)。

图 7. XML Document Viewer
XML Document Viewer 中的 Tree View 选项卡和 Attribute View

确认 XML 数据已经正确导入数据库后,现在可以处理并分割此数据,以便存储在多个行内。这有利于更轻松(且更快速)地在自己的应用程序中查询此数据。

将数据分割到单独的数据库行中

整个文档保存到单个行中将很难查询,并且在每次添加数据时都得重新编写整个 XML 文档,这远远不是理想的方式。如果每天的汇率数据都有一个单独的行来存储就会好得多。这将使查询更轻松快捷,并允许新数据作为一个新行轻松插入。

分割数据的第一步是创建一个新表用于为应用程序存储汇率(见 清单 4)。

清单 4. 创建一个用于存储 XML 分割数据的表
create table rates (
    date date primary key not null,
    rates xml
)

现在,需要将这个大型 XML 文档分割到这个汇率表的各个行中,清单 5 中的命令使用 XMLTABLE 函数来完成此任务。

清单 5. 将数据分割到单独的行
insert into rates(date, rates)
select x.date, x.rates
from temp_rates tr,
    xmltable('$d/*:Envelope/*:Cube/*:Cube'
    passing cast(tr.data as XML) as "d"
    columns
        date date path '@time',
        rates xml path 'document{.}'
    ) as x

这个语句使用 XMLTABLE 函数将每个相关 <Cube> 标记的时间属性映射为相应汇率的日期,该元素及其子节点存储到 rates XML 字段中。要验证该过程是否有效,发出以下命令:select * from rates

这一次,结果美观得多(如 图 8 所示)。每个日期都有自己的行和 XML 文档。

图 8. 分割成大量行的结果
Query Results 选项卡的屏幕截图,结果分割为大量的行

单击 more (...) 按钮只显示那个特定日期的 XML 汇率数据(见 图 9)。

图 9. 一个特定日期的 XML 数据
一个特定日期的 XML 数据的 Tree View 选项卡和 Attribute View

返回到 Query Results 选项卡,您可能注意到只显示了 100 行。难道没有导入上溯到 1999 年的数据吗?如果是那样,肯定有 2,000 多行。先别紧张,DB2 Command Editor 默认一次只获取 100 行。要获取另一个 100 行,单击 Fetch More Rows 按钮。要想检查表中的行数,可使用以下命令:select count(*) from rates

我运行这个命令后,结果显示有 2,783 行。对于从本文撰写之日到您阅读本文之日的每一个工作日,汇率表中都将添加一行。

创建 XML 数据的关系视图

最后一个数据库配置步骤是创建 XML 数据的关系视图,这将便于应用程序查询货币数据。通过创建这个视图,您将能够使用典型的 SQL 查询来过滤、排序和组合结果,将用到聚合函数、WHERE 子句、ORDER BY 子句和 GROUP BY 子句(参见 清单 6)。

清单 6. 创建 rates_view 的 SQL 语句
create view rates_view(seq_no, date, currency, rate, doc)
as select x.seqno, r.date, x.currency, x.rate, x.doc
from rates r,
    xmltable('$d/*:Cube/*:Cube' passing r.rates as "d"
    columns
        seqno for ordinality,
        currency varchar(5) path '@currency',
        rate varchar(20) path '@rate',
        doc xml path 'document{.}'
    ) as x

在本小节末尾,让我们使用一些简单的查询来验证这个视图是否按照预期的方式工作。首先,查询一个特定日期的汇率:select * from rates_view where date = '2009-11-13'

Query Results 选项卡应该显示 33 个结果,如 图 10 所示。

图 10. 按照日期过滤汇率的 Query Results
按照日期 2009-11-13 过滤汇率的 Query Results

这里有一点值得注意:因为星期六和星期日不会有外汇汇率波动,如果通过日期过滤时没有得到任何数据,请检查过滤日期是不是星期六或星期日。

接下来,只查询 USD (United States Dollar) 汇率:select * from rates_view where currency = 'USD'

现在应该看到每个工作日有一个 USD 条目。尽管 Command Editor 一次只显示 100 行,但应该有上溯到 1999 年 1 月的行。最后,我们执行一个同时基于日期和货币的查询:select * from rates_view where currency = 'USD' and date = '1999-12-30'

这一次,结果集只显示一行。如您所见,这次欧元和美元实际上是成对出现。

数据库配置到此结束。在本教程的下一小节中,将创建一个脚本,用于获取当前日期的汇率;如果当前日期对应的行在汇率表中不存在,则在该表中插入一个新行。然后扩展该脚本,使之利用 XMPP 即时消息和 SMS 文本消息发送警报。


创建 PHP 更新脚本

在本教程的这个小节,将开发样例应用程序的核心。这个核心 PHP 脚本将执行 3 个主要功能:

  • 从 ECB Web 站点(见 参考资料)提取今天的汇率,如果汇率还没有添加,就将它们添加到 currency 数据库中的 rates 表中。
  • 使用 XMPP 协议和 XMPPHP 库将消息发送给 Google Talk 用户。
  • 使用 Clickatell SMS 网关服务将消息发送到移动电话。

您也许对将 PHP 作为一种服务器端 Web 应用程序语言进行编程比较熟悉,但在本教程中,您将从 Windows 命令提示符而不是通过 Web 浏览器调用所创建的 PHP 脚本。

要跟随本小节,PHP 安装文件夹需要在 Path 上。参见 参考资料 部分的链接,获取更多信息。

在创建 update.php 脚本之前,需要创建两个 PHP 帮助器类。

创建 PHP 帮助器类

您将创建的所有 PHP 脚本和类都应该存储在 C:\currency 文件夹中。在本小节中,将创建两个 PHP 类:DBGTalk,它们分别存储在文件 db.php 和 gtalk.php 中。DB 类包含连接到 DB2 数据库的所有逻辑,而 GTalk 类处理与 Google Talk XMPP 服务器之间的通信。

首先,创建 DB 类,该类应该存储为 C:\currency\db.php(参见 清单 7)。

清单 7. db.php
<?php
class DB {
    private $conn;

    function __construct() {
        $database = "currency";
        $hostname = "localhost";
        $port = 50000;
        $user = "USERNAME";
        $password = "PASSWORD";

        $db_connect_string = "DRIVER={IBM DB2 ODBC DRIVER};"
            . "DATABASE=$database;"
            . "HOSTNAME=$hostname;PORT=$port;PROTOCOL=TCPIP;"
            . "UID=$user;PWD=$password;";

        $this->conn = db2_connect($db_connect_string, '', '')

        if(!$this->conn) {
            die(db2_conn_errormsg($this->conn));
        }
    }

    function query($sql) {
        $result = db2_exec($this->conn, $sql);
        if(!$result) {
            die(db2_stmt_errormsg());
        } else {
            return $result;
        }
    }

    function get_row($result) {
        return db2_fetch_array($result)
    }
}
?>

确保更改 $user$password 变量的值以匹配您的设置。这个类定义一个构造器,用于连接到 DB2 currency 数据库;一个 query 函数,用于执行 SQL 查询;以及一个 get_row 函数,用于从 DB2 结果集检索结果行。

接下来,创建 GTalk 类,将其存储为 C:\currency\gtalk.php。这个类的代码如 清单 8 所示。

清单 8. gtalk.php
<?php
require_once("XMPPHP/XMPP.php");
class GTalk {
    private $conn;

    function __construct() {
        $this->conn = new XMPPHP_XMPP('talk.google.com', 5222,
            'USERNAME', 'PASSWORD', 'xmpphp', 'gmail.com', 
            $printLog=false, $loglevel=0);
    }

    function connect() {
        try {
            $this->conn->connect();
            $this->conn->processUntil('session_start');
        } catch(XMPPHP_Exception $e) {
            die($e->getMessage());
        }
    }

    function disconnect() {
        try {
            $this->conn->disconnect();
        } catch(XMPPHP_Exception $e) {
            die($e->getMessage());
        }
    }

    function send_message($to, $msg) {
        $this->connect();
        try {
            $this->conn->message($to, $msg);
        } catch(XMPPHP_Exception $e) {
            die($e->getMessage());
        }
        $this->disconnect();
    }
}
?>

同样,应该用自己的帐户设置替换 USERNAMEPASSWORD 值。PHP 帮助器类创建好了,现在可以创建一个脚本来从 ECB Web 站点提取今天的汇率并将它们插入到汇率表的一个新行中。

使用今天的汇率更新汇率表

现在您可以创建一个脚本,它每天使用 curl 扩展从 ECB Web 站点获取欧元外汇汇率的 XML 提要。您将使用 PHP 的 SimpleXML 特性来过滤出需要插入汇率表的日期和汇率列的 XML 文件部分。在汇率实际插入数据库之前,脚本将检查该汇率是否没有被插入,以防存储重复的条目。

创建一个名为 update.php 的新文件并将其存储在 C:\currency 文件夹中,这个文件的内容如 清单 9 所示。

清单 9. update.php
<?php
require_once("db.php");

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.ecb.europa.eu/stats
   /eurofxref/eurofxref-daily.xml");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$xml = curl_exec($ch);
curl_close($ch);

$doc = new SimpleXmlElement($xml, LIBXML_NOCDATA);
$today = $doc->Cube->Cube;
$todayAttr = $today->attributes();
$todayDate = $todayAttr['time'];
$rates = str_replace(array("\n", "\r", "\t"), '', $today->asXML());

$db = new DB;
$sql = "SELECT count(*) from rates "
    ."WHERE date = DATE('".$todayDate."')";
$result = $db->query($sql);
$row = $db->get_row($result);

if($row[0] == 0) {
    $insert_sql = "INSERT INTO rates(date, rates) "
        ."VALUES('".$todayDate."', '".$rates."')";
    $insert_result = $db->query($insert_sql);

    if(!$insert_result) {
        echo "An error occurred while adding rates to the "
            ."database.\n".db2_stmt_errormsg();
    }
} else {
    echo "There are already rates stored for today (or it is the "
        ."weekend!)";
}
?>

这个脚本首先从 ECB Web 站点检索今天的欧元外汇汇率的 XML 文档,然后使用 SimpleXML 函数将该文档解析为 XML,提取时间属性以查明该文档适用的日期,删除不需要的 XML 节点,保留将要插入到汇率表中的 XML。

接下来,脚本通过创建 DB 类的一个实例并发出一个查询(检查汇率表上是否有对应从今天的 XML 文件提取的日期的行),连接到数据库。如果脚本没有发现这样的行,它将在汇率表中插入一个新行,否则脚本将显示一条错误消息,指出这些汇率今天已经导入;或者,当没有新汇率可用时,错误消息指出当前日期是周末。

要执行这个脚本,打开 Windows 命令提示符(Start>Programs>Accessories>Command Prompt)。发出 清单 10 中的命令以切换到 C:\currency 目录(如果已经处于 C: 驱动器,则只需发出第二条命令)。

清单 10. 更改目录
c:
cd \currency

要执行更新脚本,输入以下命令:php update.php

假设 PHP 安装文件夹在您的系统 Path 上,则这个脚本将运行。如果路径有问题,可能会收到一条错误消息,指出 php 不是一个可识别的命令。如果这种情况发生,需要将 PHP 文件夹放置到路径上(参见 参考资料 了解操作细节)。

如果在本教程之初将历史数据加载到数据库中时已从 ECB Web 站点下载了汇率,那么可能会看到一条错误消息,如 图 11 所示。

图 11. 该日期汇率已包含错误
命令提示符窗口中的一条错误消息,指出某个日期的汇率已经存储(或该日期是周末)

这条错误消息的基本意思是:今天的货币数据已经加载到数据库中,因此没有必要插入新行。但是,我敢保证您希望测试脚本是否有效。因此,返回到 DB2 Command Editor,输入以下命令删除数据库中的最新条目:delete from rates where date = (select max(date) from rates)

如果愿意,您可以检查汇率表中的最新日期,确保今天的条目已被删除。现在,返回 Windows 命令提示符窗口并重新输入此前的命令:php update.php

这次,脚本应该正常运行,不会显示错误消息。返回 DB2 Command Editor,使用以下语句验证新的一组汇率已成功插入:select * from rates where date = (select max(date) from rates)

应该看到有一行替代了刚才删除的那一行。单击 rates 列中的 more (...) 按钮验证 XML 已经从 ECB 数据文件成功导入,如 图 12 所示。

图 12. 从 ECB 文件导入的最新汇率
显示从 ECB 文件导入的最新汇率的 Tree View 选项卡和 Attribute View

提示:有一种快速方法可以判断所看到的数据是否是从 ECB 历史文件或每日文件中导入的。使用 DB2 导入时,所有与名称空间相关的属性都被标记到 <Cube> 父节点上,比如 xmlnsxmlns:gesmes。而使用 PHP 脚本添加行时,不存在这些名称空间。

接下来扩展脚本,以使用 XMPP 协议发送更新到 Google Talk 帐户。

通过 XMPP 协议发送消息到 Google Talk 用户

到目前为止,样例应用程序完成的任务无非是存储一些货币汇率数据。下面我们来扩展这个脚本,添加一些不错的功能,通过即时消息将最新的欧元兑美元汇率发送到 Google Talk 帐户。

Google Talk 是一个 XMPP 应用程序示例。XMPP 是一组用于实时通信的开源 XML 技术。Google Talk 是一个典型的即时消息传递平台,就像 MSN Messenger、AIM、Yahoo Messenger 和 ICQ 一样。归功于开源 XMPPHP 库,使用 PHP 与一个 XMPP 服务通信非常简单。

在本小节中,需要修改 update.php 脚本。第一处更改是需要导入 GTalk 库。在行 require_once("db.php"); 下添加这一行:require_once("gtalk.php");

下一处更改是添加一个 else 子句到检查 $insert_result 变量是否错误的 if 语句块。这个语句块现在应该看起来如 清单 11 所示。

清单 11. 更新 update.php 中的 if 语句块
if(!$insert_result) {
    echo "An error occurred while adding rates to the "
        ."database.\n".db2_stmt_errormsg();
} else {
    $usd_sql = "SELECT rate FROM rates_view "
        ."WHERE date = '".$todayDate."' and currency = 'USD'";
    $usd_result = $db->query($usd_sql);
    $usd_row = $db->get_row($usd_result);
    $message = "[$todayDate] 1 Euro = ".$usd_row[0]." USD";

    $gtalk = new GTalk;
    $gtalk->send_message('user@gmail.com', $message);
}

确保将接收者(上面的 user@gmail.com)更改为消息要发送到的 Google Talk 帐户。如果试图在命令提示符中再次运行 update.php 脚本,可能再次收到一条错误消息,表明该日期的汇率已经存在。返回 DB2 Command Editor,再次使用以下语句删除最新的条目:delete from rates where date = (select max(date) from rates)

确保登录到 Google Talk 或 Gmail(否则它将向您发送一封带有相关消息的电子邮件)。现在返回命令提示符窗口并再次输入以下命令:php update.php

这次,不会收到任何错误消息,相反,会打开一个 Google Talk 聊天会话窗口,如 图 13 所示。

图 13. 发送到 Google Talk 的更新
更新发送到 Google Talk 时打开的聊天会话窗口的屏幕截图

好极了,不是吗?接下来,进一步扩展脚本,通过 SMS 文本消息实际发送警报到移动电话。

使用 Clickatell SMS 网关发送 SMS 消息

前面已经学习了如何建立 Clickatell SMS 网关帐户,Clickatell SMS 网关是一个允许您从应用程序发送 SMS 文本消息到移动电话的服务。Clickatell HTTP/S API 使发送文本消息非常简单,现在就深入其中并相应修改您的脚本吧。

同样,需要编辑 update.php 文件。这次,将 清单 12 中的代码添加到 清单 11 中,放在 else 代码块中发送 Google Talk 消息的代码下面。

清单 12. 在 update.php 中发送文本消息
$sms_user = "USERNAME";
$sms_pass = "PASSWORD";    
$sms_api_id = "0000000";
$sms_to = "353875555555";
$sms_msg = urlencode($message);

$ch_sms = curl_init();
curl_setopt($ch_sms, CURLOPT_URL,
    "http://api.clickatell.com/http/sendmsg"
    ."?user=$sms_user&password=$sms_pass&api_id=$sms_api_id"
    ."&to=$sms_to&text=$sms_msg");
curl_exec($ch_sms);
curl_close($ch_sms);

信不信由您,这就是使用 PHP 和 curl 扩展发送文本消息所需的全部代码。同样,确保使用您的实际设置替换占位符变量的值。API ID 可以在您的 Clickatell 帐户设置中找到。测试脚本之前,需要再次返回 DB2 Command Editor 并删除最新的汇率:delete from rates where date = (select max(date) from rates)

返回 Windows 命令提示符,重新运行更新脚本:php update.php

这次,将看到一条来自 Clickatell API 的响应,其中包含此前发送的消息的 ID,如 图 14 所示。

图 14. 来自 update.php 脚本的响应
包含来自 update.php 脚本的响应和消息 ID 的命令提示符窗口的屏幕截图

但更重要的是,您的移动电话上也应该接收到一条如 图 15 所示的 SMS 文本消息。

图 15. 在移动电话上显示的 SMS 更新
在移动电话上显示的 SMS 更新的屏幕截图,其中包含日期、汇率和发送电话

这只涉及到使用 Clickatell API 的功能的皮毛。确保检查 Clickatell Web 站点的 Help 部分,了解关于它们的网关的强大功能的更多信息(参见 参考资料 部分的链接)。接下来,安排脚本每天运行一次,这会保持外汇汇率随时间自动更新,在每个工作日向您发送包含当天欧元兑美元汇率的消息。

安排脚本每天自动运行

这个货币更新脚本任务的最后一个步骤是安排脚本每天运行一次。Windows 中不太为人所知的一条命令是 schtasks,这个比较强大的工具允许您以指定的时间间隔安排批脚本和应用程序的执行。

将这个脚本放置到计划程序上的第一步是创建一个执行该脚本的批脚本。在您钟爱的文本编辑器中创建一个新文件并向其添加以下行:php update.php

将这个文件保存到 C:\currency 文件夹中并命名为 update.bat。现在,在命令提示符下输入以下命令将这个批文件添加到计划程序,要求该文件在每天早晨 8 点运行(注意这条命令全部在一行内):schtasks /create /tn "Currency Update" /tr c:\currency\update.bat /sc daily /st 08:00:00

可能会提示您输入 Windows 密码以确认这个任务。任务创建好之后,应该会看到一条类似于 图 16 中的消息。

图 16. 计划任务成功创建
一个成功创建的计划任务的命令提示符响应的屏幕截图

可以使用以下命令查看当前计划的任务:schtasks

如果想对计划任务执行一次测试运行,可以使用以下命令:schtasks /run /tn "Currency Update"

完成了!这个脚本应该在每天早晨 8 点运行。无需担心星期六和星期日(这两天 ECB 文件不会更新)。脚本检测到没有新汇率可用时,就不会更新或发送警报。当然,有一点值得一提:您的计算机需要启动并使用创建该计划任务的帐户登录,以便该任务能够执行。为了确保更新每天都能得以执行,更明智的方法也许是更频繁地运行脚本,而不是一天运行一次。可以在本教程的 参考资料 部分找到一些关于 schtasks 的有用资源的链接。

鉴于您在本小节创建的脚本包含的代码行相当少,这个脚本的功能已经算比较强大了:它每天查询 ECB 汇率 XML 文件以获取新汇率,如果发现新汇率,它首先添加一个新行到 DB2 数据库 currency 中。然后,它发送一条带有当天欧元兑美元汇率的消息到 Google Talk 用户。最后,它通过一条 SMS 文本消息将相同的消息发送到移动电话。所有这些任务都能自动完成,这要归功于 Windows 命令 schtasks。在本教程的下一小节中,将了解如何使用 GD2 图像 PHP 库来生成一个绘制 currency 数据库中的一些历史数据的图形。


以图形方式表示货币数据

在本教程的前面部分,将大量历史货币数据加载到了 DB2 数据库 currency 中。在本小节中,了解如何使用 PHP GD2 图像库将货币数据信息绘制在一个图形上,从而以图形方式表示信息。您将创建一个可以在命令行上运行的 PHP 脚本,用于检索当前年度各月的欧元兑美元最大汇率并将其绘制在一个图形上。这个图形创建为一个 PNG 图形并以名称 graph.png 存储在项目文件夹(C:\currency)中。

创建图形脚本

我们首先创建图形脚本。在文本编辑器中,创建一个新文件,将其命名为 graph.php 并保存在 C:\currency 文件夹中。这个脚本的首要工作是连接到 DB2 并获取要绘制在图形上的数据。为此,将 清单 13 中的代码添加到脚本。

清单 13. 检索最大汇率,按照当前年度的月份编组
<?php
require_once("db.php");

$db = new DB;

$sql = "select MAX(rate), month(date), monthname(date), year(date) "     
."from rates_view where currency = 'USD' "
    ."and year(date) = year(current timestamp) "
    ."group by year(date),month(date),monthname(date),year(date) "
    ."order by month(date)";

$result = $db->query($sql);

$months = array();
$year = 0;
while($row = $db->get_row($result)) {
    $key = substr($row[2],0,3);
    $value = $row[0];
    $months[$key] = $value;
    $year = $row[3];
}
?>

以上代码从 DB2 数据库 currency 中检索按月编组的数据并将其添加到一个名为 $months 的数组中。现在,修改脚本以将数据绘制在图形上。在 清单 13 中的 ?> 前面添加 清单 14 中的代码。

清单 14. 在 PHP 中生成一个柱状图
$width = 500;
$height = 400;
$hmargin = 30;
$vmargin = 30;

$chart_width = $width - ($hmargin * 2);
$chart_height = $height - ($vmargin * 2); 

$image = imagecreate($width, $height);

$month_width = 30;
$total_months = count($months);
$diff = ($chart_width - ($total_months * $month_width)) / 
    ($total_months +1);

$month_color = imagecolorallocate($image, 51, 102, 51);
$bg_color = imagecolorallocate($image, 235, 245, 235);
$text_color = imagecolorallocate($image, 0, 0, 0);
$out_bg_color = imagecolorallocate($image, 188, 222, 188);
$line_color = imagecolorallocate($image, 186, 220, 186);

imagefilledrectangle($image, 1, 1, $width - 2, $height - 2, 
    $out_bg_color);
imagefilledrectangle($image, $hmargin, $vmargin, $width - 1 - 
    $hmargin, $height - 1 - $vmargin, $bg_color);

$top = max($months)+(max($months)/10);
$gap = $chart_height / $top;

$hlines = 20;
$hgap = $chart_height / $hlines;

for($i=0;$i<=$hlines;$i++){
    $y = $height - $hmargin - $hgap * $i ;
    imageline($image, $hmargin, $y, $width - $hmargin, $y, 
        $line_color);
    $v = round(floatval($hgap * $i / $gap), 2);
    imagestring($image, 0, 5, $y-5, number_format($v, 2, '.', ','), 
        $text_color);
}

for($i=0;$i< $total_months; $i++){ 
    list($key,$value)=each($months); 
    $x1 = $vmargin + $diff + $i * ($diff+$month_width);
    $x2 = $x1 + $month_width; 
    $y1 = $vmargin + $chart_height - intval($value * $gap);
    $y2 = $height - $vmargin;
    imagestring($image, 0, 170, 10, "Max Monthly Values for Year "
        .$year, $text_color);
    imagestring($image, 0, $x1, $y1-10, $value, $text_color);
    imagestring($image, 0, $x1+7, $height-25, $key, $text_color);   
 imagefilledrectangle($image, $x1, $y1, $x2, $y2, $month_color);
}

imagepng($image, "graph.png");

以上代码使用 GD2 图像库生成一个宽 500 像素、高 400 像素的新图像。代码在图像中绘制一系列柱形,每个柱形代表当前年度的一个月,Y 轴显示该月的最大欧元兑美元汇率。

运行脚本并查看图表

在命令提示符窗口中,输入以下命令执行 graph.php 脚本:php graph.php

这将在 C:\currency 文件夹中创建一个名为 graph.png 的新文件。如果打开这个图像,它看起来应该如 图 17 所示。

图 17. 图形脚本的结果
图形脚本的结果的屏幕截图:2009 年各月最大汇率柱状图

在本小节中,了解了如何使用 GD2 图像库将历史数据绘制到一个柱状图上。可以使用这个库创建范围广泛的图形和图像。当然,如果正在寻找更灵活的选项,您也许会寻找一些更强大的 Flash 或 JavaScript 图表库,这些库允许您创建交互式和动画的图表和图形。但 GD2 库允许您创建美观的图像而无需下载外部库或类文件。

改进建议

尽管本教程中的样例应用程序的功能已经比较强大了,但您仍可以轻松地扩展它以执行广泛的、十分有用的功能,比如:

  • 允许多个用户注册以获取警报(发送消息到多个用户)
  • 允许用户选择对哪种货币检索警报
  • 允许用户定义何时接收警报
  • 创建一个 Google Talk 自动程序,允许用户通过发送一条消息到该自动程序来请求特定货币的当前汇率
  • 实现双向 SMS 特性,允许用户发送文本消息到指定号码,然后用户将接收一条包含指定货币的汇率的 SMS
  • 使用一个奖励汇率数据服务来包含除欧元之外的其他货币的外汇汇率数据
  • 允许用户选择要在一个图形上绘制汇率数据的货币
  • 创建更多图形(绘制历史货币数据的饼图和线形图)
  • 创建一个搜索引擎,允许用户搜索一个特定日期的汇率,某个汇率哪一天最高或最低,等等
  • 创建包含货币更新的 RSS 提要

结束语

在本教程中,了解了如何创建一个以原生 XML 格式存储欧元外汇兑换货币数据的 IBM DB2 数据库,如何使用 DB2 的 IMPORT 命令将一个大型 XML 文档导入数据库。然后,使用 SQL/XML 将数据分割到单独的行。接下来,创建了一个查询 ECB 每日汇率 XML 文档以获取新汇率的脚本,然后将新汇率添加到数据库中的一个新行内。然后,扩展了这个脚本以发送消息到 Google Talk 用户,以及发送 SMS 文本消息到移动电话。最后,了解了如何使用 PHP GD2 图像库创建一个脚本,从而为历史货币数据生成一个柱状图。

本教程着重展示了 pureXML 的强大力量,以及以原生方式在数据库中存储 XML 数据如何简化依赖外部 XML 数据源的应用程序的开发。以您在本教程中构建的样例应用程序为基础,应该能够开发出一个功能完整、非常有用的警报应用程序。


下载

描述名字大小
警报应用程序源代码currency.zip309KB

参考资料

学习

获得产品和技术

  • DB2 Express-C:获取一个免费版 IBM DB2 数据库服务器,它是中小企业应用程序开发的优良基础。
  • PHP 5.2:访问这个 PHP 站点并获取这个广泛使用的脚本语言,该语言非常适于 Web 开发并可以嵌入 HTML 中。
  • XMPPHP:从这个项目的 Google Code 站点下载这个 PHP XMPP Library。
  • 创建一个 Google 帐户 — Gmail:注册并获取一个 Google Mail/Google Talk 帐户。
  • Clickatell SMS 网关服务:创建一个帐户。
  • ECB 站点:下载最新的汇率。
  • PHP Binaries and sources Releases:获得 PHP Point 的最新 Windows 二进制文件。本文使用 PHP 5.2.11。不要使用 PHP 5.3,因为它还不支持 PECL 扩展。
  • PHP 的 DB2 扩展(2.6MB):下载这个扩展,以便连接到 IBM DB2 数据库并与数据库中的数据交互。
  • IBM 产品评估版:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Information Management
ArticleID=460507
ArticleTitle=使用 XMPP、SMS、pureXML 和 PHP 创建警报系统
publish-date=01072010