科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道应用软件在 Java Web 开发框架中创建 VoiceXML 页面

在 Java Web 开发框架中创建 VoiceXML 页面

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

对于绝大多数 Web 开发人员来说,输出 HTML、XHTML 乃至 XML 是一项微不足道的任务,它只不过是创建和部署 Web 应用程序的常规流程。从显示 Internet 页面到应答电话呼叫看似一个巨大的飞跃,但实际并非如此。在这篇文章中,您将看到相同的技术如何帮助您动态创建 Web 页面以便接听(虚拟的)电话并应答呼叫。

作者:Brett McLaughlin 来源:IBMDW 2007年8月31日

关键字: java web 框架 VoiceXML

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共19页)

在过去五年中,Web 得到了比以往任何时候都长足的发展。一度主要以文本为基础的软件程序媒介 “Web 浏览器” 现已发展成为一种可供任何具有连通性的设备使用的信息源。最早列入可访问 Web 页面的设备列表的是移动电话,随后是寻呼机、手持设备、个人电子助理和其他任何可通过无线连接连入 Web 的设备。在最近几年,电话也加入了这一阵营,通过普通电话线路使用 Web 程序的呼声也越来越高。

这种最新型的应用程序(用户通过电话访问在线服务)的更恰当的名称是电话应用程序(telephone application)。显而易见,由于电话无法用来 “单击一个链接”,应用程序交互几乎全部是通过语音处理的。用户不是采用单击链接的方式,而是说出 “账户信息” 或使用键盘按预先录制好的指令进行操作。

通过现有(或略加修改的)Web 应用程序为电话提供服务的能力是一种强大的想法,也是许多 Web 开发人员都渴望探索的领域之一。关于 Web 应用程序与电话应用程序,要了解的最重要的一件事就是您实际上可以使用相同的技术组合来创建这两种应用程序。HTML、XHTML 和 XML 是 Web 界面之下最常用的三种底层技术,VoiceXML(或简称为 VXML)则是一种密切相关的技术,它使得电话客户机可以利用 Web 交互。JavaServer Pages 与 servlet、PHP 脚本以及 Ruby 应用程序均可响应电话请求,就像响应那些进入手持设备或 Web 浏览器的请求一样轻松。在这篇文章中,我们主要关注使用 Java 平台为简单的 VoiceXML 应用程序提供服务,但您可将本文介绍的方法同样地应用到 PHP、Perl 或您选择的任何编程语言。

VoiceXML、CCXML 还是 CallXML?

从 Voxeo 开始!

您需要首先建立一个免费的 Voxeo 开发者账户,这样才能在学习本文过程中配合练习。尽管 Voxeo 并不是任何 VoiceXML 的服务所必需的,但它确实提供了一套出色的工具,更有数千份 VoiceXML、CallXML 和 CCXML 文档页面。本文同时介绍 Voxeo 与 VioiceXML 编程,后续 developerWorks 还将为您提供更多有关 Voxeo 的内容。

构建语音应用程序最常用的标准就是 VoiceXML。绝大多数 VXML 浏览器都支持 VoiceXML 2.0,本文通篇将使用这个版本的 VXML。VXML 符合 W3C 规范且发展迅速,目前的版本依然是 v2.1。VXML 3.0 即将推出。

CCXML 是 Call Control XML 的缩写,也是电话标记方面符合 W3C 规范的最新一员。CCXML 比大多数 VoiceXML 实现更为高级,提供了对回叫、事件侦听器和多路及多方会话的支持。但除非您特别需要这些属性,否则最好的选择或许是继续使用 VoiceXML,VoiceXML 更加稳定,应用也更广泛。

CallXML 是特定于 Voxeo 的一种平台。CallXML 学习起来非常轻松,并提供了对按键电话(touchtone)输入的支持(请注意,它并不支持语音识别)。CallXML 的最大缺陷就是特定于厂商。Voxeo 是一个非常出色的站点,具有无数资源,但被一家厂商锁定绝对不是个好主意。此时,大多数开发人员会再次发现,VoiceXML 更适合他们的需求。


VoiceXML 101

在接触 VoiceXML 世界的 Java 方面之前,您应大致理解 VoiceXML 应用程序的工作原理。之后,我将为您介绍一个非常简单的 VoiceXML 应用程序。示例应用程序将使您能够查看 VXML 文件,同时确保您有权访问(且可使用)Voxeo 的 call-assignment 服务,这个服务对本文后面的内容非常关键。

一个简单的 VXML 页面

VoiceXML 最少要以一个 VXML 文件开始,使用 VoiceXML 风格的 XML 版本告诉电话应用程序它们应该以及能够作些什么。清单 1 给出了一个非常简单的 VXML 文件。将这个文件保存到您的本机上(可通过 下载 部分下载完整的示例源代码,但无论如何,您应该养成自己处理此类文件的习惯)。


清单 1. 一个非常简单的 VXML 文件
                

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form>
    <block>
      <prompt>
        Things are working correctly! Congratulations.
      </prompt>
    </block>
  </form>
</vxml>


对于 VoiceXML 来说,这非常基础,如果您对语法的了解还不够清楚,请查看 参考资料 中列出的其他 VoiceXML 文章。清单 1 中的 VXML 文件只包含一条提示信息,未提供任何交互功能,在处理 Java 代码的一节中,您将看到更高级的用法。但目前,使用这个简单的测试用例来确保您的环境工作正常。

上传应用程序

接下来,将您的 VXML 文件放在某个可以访问的位置。如果您有 ISP,只需将 VXML 文件上传到您的 Web 站点,您可能也会希望在 Web 根目录下为您的 VoiceXML 文件创建一个目录,例如 /voicexml 或 /voice。确保这些目录和文件可通过 Web 访问(如果您不清楚如何进行这些操作,请咨询您的系统管理员或 ISP)。

如果您没有 ISP,那么可以在 Voxeo 注册,以便使用该站点的 File Manager。您应已建立了一个 Voxeo 账户,它附带 10 MB 的主机空间,因此这是个不错的免费选择。(10 MB 可以容纳大量 VXML 文件!)

使 VXML 应用程序联机之后,我们还想确定能够通过在 Web 浏览器中输入 URL 来访问它。根据您所使用的具体浏览器不同,可能会要求您下载 XML 文件,也可能会在您的浏览器中以某种形式呈现它。这只是一个测试,确保您的 VXML 可用,因此即使您的计算机没有开始跟您交谈,也不要太过忧虑。VXML 联机后,也就作好了将其与一个电话号码连接的准备。

为您的应用程序分配一个电话号码

最后一次呼吁您使用 Voxeo!

如果您尚未注册获得一个 Voxeo 账户,那么现在就去注册吧!从这里开始,以下的示例都需要您使用 Voxeo 工具。建立账户是免费的,没有任何责任,您更会获得杰出的工具与支持。现在就去 注册获得一个开发者账户吧

与传统的 Web 应用程序不同,您无法直接打开 Web 浏览器然后浏览您的 VXML 文件,至少在您希望获得语音应答时不能这样做。为了测试基于电话的应用程序,您显然需要一台电话,这就意味着一个呼叫号码。有许多高成本的方法可以将号码映射到 VoiceXML 应用程序,但对于测试、登台(staging)和开发而言,Voxeo 提供了一种出色的免费映射服务。

导航到 Voxeo.com,登录(使用页面左上角的字段)。在 Account 菜单中选择 Application Manager,如 图 1 所示。


图 1. 使用 Voxeo Application Manager
选择 Application Manager 选项

选择 Add Application,然后选择 VoiceXML 2.0 作为部署平台。

接下来,提供您的 VXML 文件的 URL,另外还有您的应用程序的名称,您可以按照自己的偏好任选名称。图 2 展示了访问我的 VXML 文件的设置。从 Application Phone Number 下拉菜单中选择 Staging 选项。这将为应用程序分配一个临时登台电话号码(temporary staging phone number),以使您可以真正地用您自己的电话呼叫这个号码。


图 2. 将一个 VXML 文件映射到一个电话号码
向 Voxeo 提供您的应用程序的 URL 和名称

单击 Create Application,Voxeo 将为您的应用程序分配一些电话号码。图 3 展示了最终屏幕(略微向下滚动了一点),以及 VXML 文件的所有访问点。


图 3. 成功映射!
Voxeo 提供了多种访问您的应用程序的途径

这一功能值得您花时间去注册 Voxeo,您现在可以通过长途电话号码、800 免费电话号码和 Skype 访问您的 VXML 文件,而这些方法还只是其中的一小部分。这非常好,因为您不必使用 Voxeo 工具去测试应用程序。更好的是,您可以让您的老板在无需具备 Voxeo 站点账户的情况下完成测试!

测试应用程序

剩下的工作就是呼叫一个 Voxeo 提供的号码。拨号后,您的 VXML 应用程序应获取号码,并让您知道(用一种单调机械的声音):“Things are working correctly! Congratulations.”

好,就是这样:在大约五分钟内,您使您的电话与一个 XML 文件进行了交谈。现在就可以看看 Java 代码了,并了解如何动态地生成 VXML。


Java 和 VXML

这里,大多数 Java 开发人员都试图在自己的 Java Servlet 中手工编码 VXML,添加数百行的 out.println() 语句、为输出的内容类型而操心,通常也会给许多应用程序增加严重且不必要的复杂性。动手处理那些较为复杂的编程任务之前(只要应用得当,它们都是很有用的),请先通过本节了解一些关于 VoiceXML Servlet 编程的最基本的内容。

创建一个 VXML 文件的原型

首先要开发 VXML 文件。不要打开一个 IDE 并开始编写 Java 代码,而是启动一个文本编辑器,忍住立即添加 packageimport 语句的渴望。构建一个简单的 VXML 文件,就像本文前面给出的示例那样。

例如,清单 2 是又一个非常基础的 VXML 文件。它是一个语音识别 VXML 文件,接入一个恰当的设备并提供某些关于呼叫选择的注释。


清单 2. 另外一个基本的 VXML 文件
                

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form id="MainMenu">
    <field name="instrument">
      <prompt>What is your  favorite musical instrument?</prompt>

      <!-- Insert an inline grammar -->
      <grammar type="text/gsl">
        [guitar mandolin dobro (violin fiddle) banjo]
      </grammar>

      <!-- Handle the case when they give no answer -->
      <noinput>
        Did you say something? I didn't hear you.
        <reprompt />
      </noinput>

      <!-- Handle the case when no match is found -->
      <nomatch>
        I suppose that's OK, but it's not on my top five. 
        Want to try again?
        <reprompt />
      </nomatch>
    </field>

    <!-- Handle the various options. -->
    <filled namelist="instrument">
      <if cond="instrument == 'guitar'">
        <prompt>That's right! Hang up and go practice.</prompt>
      <elseif cond="instrument == 'mandolin'" />
        <prompt>Nice... and only four strings to keep in tune.</prompt>
      <elseif cond="instrument == 'dobro'" />
        <prompt>Boy, that's no fun to learn, is it?</prompt>
      <elseif cond="instrument == 'violin'" />
        <prompt>We call that a fiddle, Mr. Fancy Pants.</prompt>
      <elseif cond="instrument == 'fiddle'" />
        <prompt>Does playing classical music on a 
        fiddle make it a violin?</prompt>
      <elseif cond="instrument == 'banjo'" />
        <prompt>Wow, I hope you live alone.</prompt>
      </if>
    </filled>
  </form>
</vxml>



编写这个 VXML、保存它、将它上传到 ISP,然后为它分配一个号码。只有在您完成所有这些步骤后 —— 确保您的 VXML 正常工作,才是准备好了,可以开始 考虑编写 Java 代码。

如果您直接跳到 Java,那么很可能会导致输出中出错,代码中也会出错。结果是要在一个 Web 框架内尝试同步调试一个 VXML 文件(XML)和一个 Servlet(Java),这种调试极其艰难。不要添加所有这些变量(没有双关的意思),务必从一个可正常工作的 VXML 文件入手。然后 准备运行 Java 代码。

读入文件

准备好 VXML 可供使用后,您也就为开始编码作好了最终的准备。首先从一个仅载入 VXML 文件的 Servlet 开始。清单 3 是一个实现此功能的 Servlet —— 载入 清单 2 中开发的 VXML。这段代码没有任何输出,所以期望值暂时不要太高。


清单 3. 载入一个 VXML 文件
                

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class VoiceXMLServlet extends HttpServlet {

  private static final String VXML_FILENAME =
    "simple-voice_recog.xml";

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    String vxmlDir = getServletContext().getInitParameter("vxml-dir");

    BufferedInputStream bis = null;
    ServletOutputStream out = null;

    try {
      // Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Output the VXML file 
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
      }
    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }
}


这段代码非常直观。它载入一个 XML 文件 —— 通过 servlet 的配置上下文中的目录和一个常量文件名指定,然后遍历文件内容。您要将文件的路径硬编码到 servlet 中,但至少将目录名存储到 Web.xml 文件中是一个非常不错的主意,此文件位于 servlet 上下文的 WEB-INF/ 目录下。清单 4 展示了 Web.xml 中的上下文参数。


清单 4. servlet 的上下文参数
                
  <context-param>
    <param-name>vxml-dir</param-name>
    <param-value>/path-to-your-voicexml-dir/voicexml</param-value>
  </context-param>



若编译 servlet 并尝试在 Web 浏览器中载入它,您只会看到一个空白的屏幕,同样,您应确保至少会看到这样的空白屏幕。如果得到错误,就需要予以更正。例如,常常会出现文件访问问题或 VXML 文件路径录入错误。一旦得到了空白屏幕,也就准备好实际输出 VXML 文件了。

从 servlet 中输出 VXML

首先,您需要访问一个输出对象,这样才能向浏览器发送内容。这非常简单:

      
// Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Let the browser know that XML is coming
      out = res.getOutputStream();
            

从文件提取内容也非常简单,只要使用一行代码即可:

      
// Output the VXML file 
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
        out.write(readBytes);
      }


虽然上述代码看似已经足够,但您依然需要告知浏览器您正在向它发送 XML。切记,浏览器用于 HTML,某些浏览器可能无法顺利接收 XML。您可设置内容类型,也可设置内容的长度,只要再次使用 HttpServletResponse 对象即可:

     
 // Let the browser know that XML is coming
      out = res.getOutputStream();
      res.setContentType("text/xml");
      res.setContentLength((int)vxml.length());
            

清单 5 展示了添加到前文介绍的 清单 3 给出的 servlet 中的所有代码。


清单 5. 完整且准备好载入 VXML 文件的 VoiceXMLServlet
                

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class VoiceXMLServlet extends HttpServlet {

  private static final String VXML_FILENAME =
    "simple-voice_recog.xml";

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    String vxmlDir = getServletContext().getInitParameter("vxml-dir");

    BufferedInputStream bis = null;
    ServletOutputStream out = null;

    try {
      // Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Let the browser know that XML is coming
      out = res.getOutputStream();
      res.setContentType("text/xml");
      res.setContentLength((int)vxml.length());

      // Output the VXML file 
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
        out.write(readBytes);
      }
    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }
}

测试 servlet 载入的 VoiceXML

完成上述更改后编译您的 servlet,若需要请重启 servlet 引擎。浏览 servlet,您应看到如 图 4 所示的输出结果。成功!


图 4. VoiceXML servlet 输出 VXML
大多数浏览器都会给为您给出某种 XML,呈现您的 VXML 文件

若您未得到类似输出,确定您的文件是否位于您希望的位置,并确保没有任何权限问题。您还要检查 servlet 引擎的日志或请求系统管理员的帮助。

现在就准备好将电话号码映射到您的 servlet 了。重新回到 Voxeo.com 的 Application Mnager,添加一个新应用程序(可能会看到之前您使用过的应用程序)。确保选中 VoiceXML 2.0,然后输入新应用程序的名称和 servlet 的 URL。Voxeo 将创建应用程序并为其分配一个电话号码。

拨入这个新号码,您应听到 清单 2 中的 VXML 给出的提示。祝贺您!您已经编写好了一个输出 VXML 的 Java servlet 的代码,还在其中挂接了一个电话号码。

部分可选的附加项

您可能希望向 servlet 代码中添加一些小附加项。它们都不是必需的,但都会给现有的版本增加一些健壮性和文档。

首先,您可能想允许用户通过 POST 请求访问 VXML。这可能在用户单击表单上的一个按钮时发生,该表单将对 VoiceXMLServlet 作出一个 POST 请求。在 servlet 中处理这一操作非常简单,只要编写一个委托已有 doGet() 方法的 doPost() 即可,如下所示:

  
public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    doGet(req, res);
  }


另外一个简单的附加项用于允许浏览器切实获知您正在输出一个 VXML 文件的内容。为此,设置 servlet 中的 Content-disposition 响应头,如下:

      
// Let the browser know that XML is coming
      out = res.getOutputStream();
      res.setContentType("text/xml");
      res.setContentLength((int)vxml.length());
      res.addHeader("Content-Disposition",
        "attachment; filename=" + vxml);
            

现在读取您的响应的浏览器(或其他代码)就可以发现所服务的 VXML 文件了。但务必不要包含完整的文件路径,这会造成安全隐患!


动态 VoiceXML

有了输出 VXML 文件的 servlet 之后,将其转换成动态输出 VXML 的 servlet(使用代码作为模型或模板)轻而易举。换句话说,您可以超越简单地载入静态的 VXML 文件,开始通过编程创建 VXML。

当您开始考虑动态 VoiceXML 时,Java 平台就显示出了自己的优势。它提供了轻松输出 XML 的能力,还有与数据库、目录服务器、身份验证存储和会话的交互。此外,它还能够证实,构建动态 VXML 将消除基于语音的系统的部分刻板性。

在这一节中,我将逐步为您介绍创建一个输出动态 VXML 的 Java servlet 的步骤。

通过 out.println() 输出 VXML

您已经了解了如何访问 ServletOutputStream,然后在输出流中插入字节。但如果从源(例如一个静态 VXML 文件)传输到输出流的不仅仅是字节,那么直接处理字节的方式几乎无法管理控制。

如果您希望自行创建 VXML,最好使用 PrintWriter。利用这个类,您可发出整个字符串,使之对于创建和输出动态内容更为有用。这只需要对代码略加修改,如下所示:

  
public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    String vxmlDir = getServletContext().getInitParameter("vxml-dir");

    BufferedInputStream bis = null;
    ServletOutputStream out = null;

    try {
      // Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Let the browser know that XML is coming
      PrintWriter out = res.getOutputStream();
      res.setContentType("text/xml");
      res.setContentLength((int)vxml.length());

      // Output content using PrintWriter
    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }


另外,不要忘记导入 java.io.PrintWriter 类:它不会自动成为对您的 servlet 的代码基可用。

使用 PrintWriter,您现在可以输出基于字符串的内容了。例如,清单 6 输出与 清单 1 相同的 VXML,但是通过 servlet 输出,并未从静态文件载入 VXML 内容。


清单 6. 动态输出 VXML
                

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;

public class DynamicVoiceXMLServlet extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    BufferedInputStream bis = null;
    PrintWriter out = null;

    try {
      // Let the browser know that XML is coming
      out = res.getWriter();
      res.setContentType("text/xml");

      // Output VXML
      out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
      out.println("<vxml version=\"2.1\">");
      out.println(" <form><block><prompt>");
      out.println("  Things are working correctly! Congratulations.");
      out.println(" </prompt></block></form>");
      out.println("</vxml>");

    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    doGet(req, res);
  }
}


现在您可编译这个 servlet、向 Voxeo 注册它,并通过电话访问它,与处理 清单 1 的方法相同。现在我们来看一些示例,从而展示像 Java 这样的语言的动态编程能力。

添加时间提醒

基于 servlet 的 VXML 输出的一项最简单的用途就是添加时间提醒。利用 Java 代码获取当前日期和时间非常轻松,因此这是个不错的起点。

使用 Calendar 类可轻松获得一天中的具体时间(实际上,可以获得与当前日期相关的任何内容)。清单 7 给出了获得 Calendar 类新实例的代码,从而得到一天中的具体时间(以 24 小时的格式返回),然后根据这个时间组合出一条简单的欢迎词。


清单 7. 动态输出 VXML
                

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.*;
import javax.servlet.http.*;

public class DynamicVoiceXMLServlet extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    BufferedInputStream bis = null;
    PrintWriter out = null;

    try {
      // Let the browser know that XML is coming
      out = res.getWriter();
      res.setContentType("text/xml");

      // Output VXML
      out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
      out.println("<vxml version=\"2.1\">");
      out.println(" <form><block><prompt>");

      // Output a greeting based on the time of day
      Calendar cal = Calendar.getInstance();
      int hour = cal.get(Calendar.HOUR_OF_DAY);
      if (hour < 6) {
        out.println("You're up early. Good morning.");
      } else if (hour < 12) {
        out.println("Good morning. How's your day so far?");

      } else if (hour < 18) {
        out.println("Half the day is done... good afternoon!");
      } else{
        out.println("Hope you are enjoying your evening.");
      }

      out.println(" </prompt></block></form>");
      out.println("</vxml>");

    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    doGet(req, res);
  }
}

呼叫者和 VXML 生成者之间的差异

清单 7 也展示了 Voice XML 与动态生成的 VXML 的另外一项特性:呼叫者可能并非 VXML 本身。例如,假设有一名居住在新西兰的用户呼叫 清单 7 所示的应用程序。若当时新西兰时间是 10:00 PM,但输出 VXML 的服务器位于科罗拉多州的丹佛,两者之间问候的消息可能非常古怪,例如 “您起得真早,早上好!” 这完全不恰当,情况还有可能更糟:如果您已为每周的特定日子添加好了问候语,那么实际上总会出现匹配错误。

根本问题源于 VXML 和 Java 是在特定服务器所在的位置和时区运行的,但可为世界各地的呼叫者所用。如果您的 servlet 未考虑到这方面的问题,就会令一些呼叫者倍感困惑。您有以下几种选择:

  • 忽略差异,寄希望于呼叫者了解您的服务器并非在他们所在的时区运行代码。
  • 明确声明日期和时间对应于服务器所在位置。例如,下午问候语可以是 “本地现在是下午。祝您愉快”。
  • 编写代码询问时区或与 GMT 的时差,然后根据这一信息提供问候语。

遗憾的是,这些选择中没有任何一种富于吸引力。第一种选择的所作所为正如所说明的那样:基本忽略呼叫者。不言而喻,忽略呼叫者绝非 赢得和维持业务关系的正确方法。第二种想法 —— 给出本地时间并明确说明这是本地时间 —— 也没有太大的帮助,因为这种方法依然倾向于忽略呼叫者,只是在此过程中考虑得略微周全一些。

最后一种选择乍看上去似乎很有吸引力,可以很容易地编写出 VXML,允许用户提供一个与 GMT 的时差值,然后根据该信息应答。但呼叫者倾向于尽可能迅速地获得信息,应答提示越多,惹恼呼叫者的风险就越大,他们可能会不满地挂断电话。因此,除非您提供的以日期或时间为基础的服务,否则要求呼叫者指明时区就是浪费资源。甚至可能更糟的是,很多呼叫者并不知道自己所在地与 GMT 的时差,因此您要面临提供时区、时区缩写、夏令时……那将是一个冗长烦琐的列表。

为什么要自寻烦恼?

那么我们为什么要介绍这种基于日期的 VXML 生成呢?很大程度上是因为它正展示了这些问题!您需要密切关注您的听众,尽力只提供那些与他们有关的信息,而不是与您的服务器或您所在地有关的信息。

对于基于日期的处理,您要学习的课程就是:或许应该采用一种更好的最终选择来应对呼叫者,除非绝对必要,否则应完全避免采用基于日期或时间的事务。如果您预计到将有来自其他时区的呼叫者,那么提供与时间相关的功能就是自找麻烦。这一原则同样适用于任何随州、国家或陆地标线的不同可能发生变化的数据。

最后,有很多时候使用 servlet 输出 VXML 并非良策。如果您只是要从一个静态文件中提取 VXML,使用 servlet 带来的好处极为有限(可能只有一点灵活性而已),但要添加代码、编译、调试、一个 servlet 引擎和其他许许多多的东西,从而使语音应用程序的复杂性大为增加。在这些简单的情况下,应继续使用静态 VXML 文件。


一些有趣的想法

致此,您已看到,有时 servlet 生成的 VXML 并无 意义。在结束本文之前,考虑以下几种情况,在这些时候使用 Java 这样的语言是绝佳 的电话应用程序解决方案。此处未提供完整的示例,以后的文章中将予以介绍。

从一个数据库中载入 VXML

最显而易见的与 VoiceXML 相关的 Java 应用程序就是:使用数据库提供动态 VXML 输出。这或许也是大部分读者在选择阅读本文时希望了解的内容(但这篇文章中没有核心示例,因此您可能未学到足够多的知识)。无论如何,JDBC 都能使您轻松连接数据库,然后利用 SQL 查询的结果填充 VXML。

例如,您要开发一个表,包含 VXM 的全部语法信息,然后将这些语法载入您所输出的每个 VXML 文件中。您不必为每一个 VXML 文件编码语法,而是可以在类似的文件间共享语法。更好的是,您可在所有 servlet 或一个特定 servlet 的实例中预载入这些语法。从而得益于将语法存储在数据库中,无需浪费成本为每个请求载入语法。

根据用户凭证载入 VXML

另外一项出色的 Java 功能 —— 特别是在与 servlet、JSP 和基于 Web 的编程相关时 —— 就是在会话中存储用户凭证的能力。这为您带来稳定的身份验证和授权,以及高度定制的内容。

例如,考虑一个语音应用程序,从询问用户 ID 号和 PIN 开始(与当今的大多数银行或金融应用程序类似)。您可根据数据库(依靠 Java 平台的强大力量)对这些凭证进行验证,然后将呼叫者的 ID 存储到一个会话变量中。此后,每个处理这名呼叫者的请求的 Java servlet 或 JSP 都可根据这些凭证了解为用户提供哪些选择。

尽管许多 VoiceXML 替代产品都提供了类似的功能性,但很少有产品以与其基于 Web 的应用程序版本之间共享代码为自豪。换言之,Java 平台允许您在 VoiceXML 应用程序及其基于 Web 的版本间共享的不仅仅是数据库,还包括代码组件。生成 VXML 的 servlet 可使用相同的身份验证和授权工具类作为生成 HTML 和 XHTML 的 servlet,应答电话呼叫的 JSP 可与处理 HTTP 请求的 JSP 共享缓存数据库连接。因而,您将得到一个能够处理多种类型客户机的应用基础设施,而不必为每种类型的客户机创建一个完整的应用程序。


结束语

本文蜻蜓点水地介绍了可用 VXML 和 Java 平台实现的功能。介绍了开发 VXML 的过程,然后为您展示了如何将 Java 技术整合到这一过程之中。介绍过程中给出了很多线索,告诉您利用 Java 代码来开发丰富、动态的 VoiceXML 应用程序的所有有趣的方式。

我还说明了 VoiceXML 开发人员在语音应用程序误用 Java 技术的几种常见形式。处理日期和时间时耍小聪明、试图提供地方性的服务或是忘却服务器当地时间和呼叫者当地时间之间的差异无疑会令用户灰心离去。应将 Java 视为 VoiceXML 的一种工具,不要滥用 DateCalendar 类。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章