级别: 中级 Philippe Randour (philippe@randour.net), 软件和系统工程师, Krauthammer International
2008 年 1 月 18 日 在网站或 Intranet 中有大量的可用信息,这些信息需要特殊的工具来快速方便地索引其内容并提供对它的访问。本文学习如何在耦合了 ColdFusion 和 Microsoft® Windows® Indexing Service 等成熟技术的 Ajax 库的帮助下实现上述操作并提供最新的搜索工具。
几个月前,我和一些同事讨论关于将一个搜索工具添加到 Intranet 中(作为一个向导)并将其扩展到企业网站中的可能性。此提议的主要目标之一是弄清楚哪些访问者在查找 Web 内容并相应地改变了内容。我们中的一些人构想了一个完全自定义的解决方案(我不是在开玩笑)。他们建议在数据库服务器上构建表以存放关键字以及与其相关的页面地址。关键字的提取将是一个手动进程,关键字的选择完全取决于执行选择的人。对索引的查询将使用标准结构化查询语言(Structured Query Language,SQL)来执行。
但是随后出现了一种不同的看法。我们手头已经有了实现此功能所需的一切:用于所有服务器的完整 Windows 环境和内部开发已经使用的 Web 开发工具 (ColdFusion)。惟一缺少的一件东西是有关如何使用此工具来实现此功能的文档。我们可以获得许多有关 Active Server Pages (ASP) 的信息,但是却缺少有关 ColdFusion 的资源。作为我们团队中惟一一个具有必要技能的成员,我捋起了袖子,构建了用于 ASP 的 ColdFusion 等价物,它成为我们的搜索工具。在这个过程中,我决定与社区一起分享。下面开始介绍给大家。
 |
本文讲什么?
对有效且有用的搜索工具感兴趣吗?学习如何通过将 Windows Indexing Service 与 Ajax 和成熟服务器技术相结合,来开发这种工具。 |
|
介绍此工具
在我讲述如何构建搜索工具的细节之前,本节介绍贯穿于全文使用的工具。每个工具都在最后的解决方案中扮演着特定的角色:Rico JavaScript 增强显示结果时的用户体验,ColdFusion MX 7 访问索引并构建搜索结果,Windows Indexing Service 提供索引基础设施。
Rico JavaScript 库
 | |
请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
|
|
Rico JavaScript 库是在 Apache 2.0 Licens 下可用的开源 Asynchronous JavaScript + XML (Ajax) 框架。它着眼于简单性,即通过提供单个 JavaScript 对象来添加对任何 HTML 页面的支持。
要开始使用它,必须在超文本标记语言 (Hypertext Markup Language, HTML) 页面标题中包括提供必要功能的 JavaScript 库:
<script src="prototype.js"></script>
<script src="rico.js"></script>
|
这样做会导致自动创建 Ajax 引擎的一个实例,并通过一个名为 ajaxEngine 的对象使其可访问。
除了拖放支持和动画效果之外,此 Ajax 框架还提供了我在本文使用的下列关键特征:
- 基本 Ajax 响应的标准 Extensible Markup Language (XML) 定义。
- 将响应链接到特定 HTML 元素的功能
- 使用响应内容自动更新目标 HTML 元素的
innerHTML 属性
ColdFusion MX 7
ColdFusion MX 7 是一个可用于开发高性能动态网站的应用服务器和 Web 开发框架。ColdFusion 的强大都来自于它的灵活语言:ColdFusion Markup Language (CFML)。此语言的语法模仿 HTML,因此很容易掌握。CFML 提供了大量的标记来封装或扩展 HTML 并执行条件处理、与本地文件系统交互、执行 HTTP 或 FTP 操作、动态生成 PDF 文件、连接到外部应用程序创建的各种数据,等等。使用所有的内置功能以及与其他应用程序或服务集成的灵活性,CFML 可以立即满足您的所有 Web 开发需求。
当然,您可以使用其他语言来提供本文展示的搜索页面。您可以使用任何 Web 语言,比如 PHP、 ASP 或 Ruby on Rails 等,只要这种语言能够访问 Component Object Model (COM) 对象。这是使用 Windows Indexing Service 访问索引内容时的惟一需求。
Windows Indexing Service
Windows Indexing Service 是安装在 Windows XP 或 Windows Server 2003 系统上的基础组件。它的角色是分析文件系统上或 Web 服务器中的文档属性和内容,然后构建一个索引目录以方便对此数据的搜索。
可以在 Add/Remove Programs 工具(可以通过 Control Panel 直接访问)的 Add/Remove Windows Components 区段中检查其安装(参见图 1):
图 1. 添加/删除 Windows 组件
一旦安装完成,在 Services and Applications 区段中,可以通过 Windows Administrative Tools 中的 Computer Management 控制台访问并管理此组件(参见图 2):
图 2. Computer Management 控制台
此工具是系统的一部分并完全集成,因此当您在完全 Windows 环境中工作时,此工具是首选。
如果您需要跨平台的支持,可以在开源许可下使用一些替代方案。例如 Apache Lucene(高性能、功能完全的文本搜索引擎库)和 Apache Solr(基于 Lucene 搜索库的搜索服务器)。
索引 IIS 服务器的内容
在开始编码搜索工具之前,需要在 Web 服务器上激活索引,如果有必要,在目录的帮助下将其范围限制为文件子集。
激活索引
默认情况下,索引系统是活动的,并自动创建两个目录:
-
System:通过此目录来索引计算机上的所有文档。也可以在 Windows 标准搜索工具的帮助下查询此目录。
-
Web:通过此目录来索引 Web 服务器上的所有文档;所有文档存储在 c:\inetpub\wwwroot 目录下。
在搜索 Web 服务器上的内容时,如果没有指定任何内容,则默认使用 Web 目录。大多数时候,这足够了。然而,您可能想创建附加目录,例如,如果想在您的站点上提供几个搜索页面,每个都限制到站点的一个子集中,或者如果几个网站都位于同一个 Microsoft Internet Information Services (IIS) 服务器上。
添加新的目录
在 Computer Management 控制台的 Indexing Service 子区段中,通过在上下文菜单中选择 New > Catalog 来创建新目录。然后指定新目录的名称以及在磁盘上的位置(参见图 3):
图 3. 添加新目录
名为 catalog.wci 的隐藏文件夹在指定位置自动创建,并在索引期间存放所生成的文件。在此操作完成后,指定要在索引中索引或排除的文件夹,并将目录与 Web 服务器相关联(在目录属性的 Tracking 选项卡上),以便可以正确生成虚拟路径(vpath)(参见图 4):
图 4. 目录属性
访问索引内容
因为本文讲述如何构建搜索页面,所以在客户端需要有一个页面(参见 清单 1)来充当搜索工具的用户界面。此页面呈现给用户一个基本表单,以便他们输入搜索条件。在表单下面是一个空白区域(div),稍后将显示搜索结果(参见图 5):
图 5. 搜索表单
清单 1. 搜索工具的用户界面 (ajax_search.cfm)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Search</title>
<script src="prototype.js"></script>
<script src="rico.js"></script>
<script language="JavaScript">
var CurrentPage = 1;
function onLoad() {
ajaxEngine.registerRequest('getSearchResults','ajax_results.cfm');
ajaxEngine.registerAjaxElement('SearchResults');
}
function getResults(RequestedPage) {
var SearchString = document.SearchForm.qu.value;
CurrentPage = RequestedPage;
ajaxEngine.sendRequest('getSearchResults',
"qu=" + SearchString,
"pg=" + RequestedPage);
}
function getPreviousResults() {
getResults(CurrentPage-1);
}
function getNextResults() {
getResults(CurrentPage+1);
}
</script>
</head>
<body onLoad="onLoad()">
<h1>Search</h1>
<cfform name="SearchForm" method="post" action="#CGI.SCRIPT_NAME#"
onSubmit="getResults(1); return false;">
<cfinput type="text" name="qu" size="50" maxlength="100" value="">
<cfinput type="submit" name="" value="Search">
</cfform>
<div id="SearchResults">
</div>
</body>
</html>
|
第一次加载此页面时 (onLoad event),请求处理程序使用 Ajax 引擎进行注册。此处理程序将服务器端页面(此页面处理此请求)的 URL 与在客户端用作其标识符的一个名称相关联。此外,因为此页面提供了一个由 Ajax 引擎使用搜索结果进行更新的 div 区段,所以此页面必须注册为一个元素:
ajaxEngine.registerRequest('getSearchResults','ajax_results.cfm');
ajaxEngine.registerAjaxElement('SearchResults');
|
要避免信息超载,显示结果被限制为预定义的条目数;并提供了 JavaScript 函数以进行向前或向后导航。其中每个函数都生成一个 Ajax 请求来调用服务端的页面,并将其传递给 URL 上的两个参数: qu(要查找的字符串)和 pg(要显示结果的页面数)。
在服务器端,此页面(参见 清单 2)检索 URL 参数,查询索引内容,按客户机请求选择结果的正确子集,并将典型的 ajax-response 以 XML 格式打包返回给浏览器。在 ColdFusio 中,我使用 cfcontent 标记来指定要返回给浏览器的内容类型。
清单 2. ajax_results.cfm 服务器页面
<cfcontent type="text/xml">
<ajax-response>
<response type="element" id="SearchResults">
<h1>Search results</h1>
<cfscript>
PageSize = 10;
MaxRecords = 50;
if (IsDefined("URL.qu")) {
SearchString = URL.qu;
}
else {
SearchString = "";
}
if (IsDefined("URL.pg") and IsNumeric(URL.pg)) {
RequestedPage = Val(URL.pg);
}
else {
RequestedPage = 1;
}
</cfscript>
<cfif SearchString eq "">
<p>Your search did not match any documents.</p>
<cfelse>
<cfscript>
QueryString = SearchString & " AND ##filename *.htm?";
ixQuery = CreateObject("COM","ixsso.Query");
ixQuery.Query = QueryString;
ixQuery.Columns = "filename,size,rank,characterization,vpath,DocTitle,DocAuthor";
ixQuery.SortBy = "rank[d], DocTitle";
ixQuery.MaxRecords = MaxRecords;
RS = ixQuery.CreateRecordSet("nonsequential");
if (RS.EOF) {
WriteOutput("<p>Your search did not match any documents.</p>");
}
else {
RS.PageSize = PageSize;
PageCount = RS.PageCount;
if ((RequestedPage lt 1) or (RequestedPage gt PageCount)) {
RequestedPage = 1;
}
RS.AbsolutePage = RequestedPage;
if (RS.RecordCount gt 0) {
FirstRecordOnPage = RS.AbsolutePosition;
LastRecordOnPage = FirstRecordOnPage + PageSize - 1;
if (LastRecordOnPage gt RS.RecordCount) {
LastRecordOnPage = RS.RecordCount;
}
WriteOutput("<p>");
WriteOutput("Results <strong>" & FirstRecordOnPage & " - " & LastRecordOnPage);
WriteOutput("</strong> of <strong>" & RS.RecordCount & "</strong> (page ");
WriteOutput(RequestedPage & " of " & PageCount & ")");
if (RequestedPage gt 1) {
WriteOutput(" <a href='javascript:getPreviousResults()'>Previous results</a>");
}
if ((PageCount gt 1) and (RequestedPage neq PageCount)) {
WriteOutput(" <a href='javascript:getNextResults()'>Next results</a>");
}
WriteOutput("</p>");
}
while (not (RS.EOF or (RS.AbsolutePage neq RequestedPage))) {
WriteOutput("<p>");
WriteOutput("<a href='" & RS.Fields.Item("vpath").Value & "' target='_blank'>");
WriteOutput(XmlFormat(RS.Fields.Item("DocTitle").Value) & "</a>");
WriteOutput("<br />");
WriteOutput(XmlFormat(RS.Fields.Item("characterization").Value));
WriteOutput("</p>");
RS.MoveNext();
}
}
RS.Close();
ReleaseComObject(RS);
ReleaseComObject(ixQuery);
</cfscript>
</cfif>
</response>
</ajax-response>
|
查询目录
可以使用 Indexing Service 查询语言或使用 SQL 查询 Windows Indexing Service 目录。其中每个语言都支持一些 Application Programming Interfaces (API)。我在本文使用的技术基于 Query Helper,这是一种高级的 API,为访问 Windows Indexing Service 数据提供了一种面向对象的接口。此接口允许构建查询并将其提交,同时生成 ActiveX Data Object (ADO) Recordset 作为结果。
使用 ColdFusion,按照下列步骤执行上述操作:
<cfscript>
QueryString = SearchString & " AND ##filename *.htm?";
ixQuery = CreateObject("COM","ixsso.Query");
ixQuery.Query = QueryString;
ixQuery.Columns = "filename,size,rank,characterization,vpath,DocTitle,DocAuthor";
ixQuery.SortBy = "rank[d], DocTitle";
ixQuery.MaxRecords = MaxRecords;
RS = ixQuery.CreateRecordSet("nonsequential");
</cfscript>
|
此处使用的此对象的最重要的参数如下:
-
Query:要提交给 Windows Indexing Service 的请求,也称为限制。它是词与参数的组合,用于确定返回作为一部分搜索结果的文档。此请求可以使用不同的方言表达:Dialect 1、Dialect 2 或 SQL。在最简单的形式中,它仅包括要查找的词。要细化搜索,可以添加参数以限制结果的范围。例如,
#filename *.htm 仅搜索 HTML 文件,#vpath \docs* 仅考虑 docs 文件夹。可以使用布尔操作符 (AND, OR, NOT) 组合这些表达式 (AND, OR, NOT)。
-
Columns: 要在搜索结果中返回的字段列表。其中包括下列字段:
-
filename:文档名称。
-
rank:基于搜索词的频率指定结果中文档的值。
-
characterization:文档摘要。
-
vpath: 访问文档的虚拟路径。
-
DocTitle:文档标题。
-
SortBy:基于指定的列,搜索结果的排列顺序。
[d] 选项指明其中一个字段必须按降序排列。
-
MaxRecords:从索引内容中检索的最大记录数。
-
Dialect:Indexing Service 查询语言的方言。文字值 “1” 指示方言 1,“2” 指示方言 2(默认)。
-
Catalog:用于限制搜索的一个或多个目录的名称(用逗号隔开)。目录名称是与本地计算机上隐藏目录相关联的名称。它使用类似 URL 的语法:
query://hostname/indexname. The hostname 是目录所在计算机的名称;indexname 是此计算机上的目录名称。如果没有为此属性提供任何名称,则使用此计算机上的默认目录(在此情况下使用 Web,因为安装了 IIS)。
执行请求并使用 CreateRecordset 方法生成 ADO Recordset 对象。然后,使用此记录集迭代遍历搜索结果以显示给定的子集。
构建 Ajax 响应
通常,Rico Ajax 响应以格式良好的 XML 格式表示,由
<ajax-response></ajax-response> 标记来定界。响应内容也由 <response></response> 标记来定界;参见清单 3:
清单 3. 示例 Ajax 响应
<ajax-response>
<response type="element" id="SearchResults">
<h1>Search results</h1>
<p>Results <strong>1 - 10</strong> of <strong>50</strong>
(page 1 of 5) <a href='javascript:getNextResults()'>Next results</a></p>
<p><a href='/cfdocs/htmldocs/introa.htm' target='_blank'></a><br />
Getting Started Building Blackstone Applications is intended for anyone
who needs to begin programming in the Macromedia Blackstone development environment.</p>
<p><a href='/cfdocs/htmldocs/introb.htm' target='_blank'></a><br />
CFML Reference is your primary ColdFusion Markup Language (CFML) reference.
Use this book to learn about CFML tags and functions, ColdFusion expressions, and using
JavaScript objects for WDDX in Macromedia ColdFusion MX.</p>
</response>
</ajax-response>
|
一个响应存放两个属性:type 和 id。这些参数指定此响应旨在更新由上述 id 标识的 (type="element") 页面上的 HTML 元素的内容。此响应的内容必须代表有效的 XHTML 代码。在示例中,在搜索页面上要更新的元素是与 id 属性(与 Ajax 属性中的相同)相关联的 div 区段。
显示搜索结果
搜索结果以 ADO Recordset 格式返回。您必须做的事情是循环遍历此对象以访问并显示其结果(参见图 6):
图 6. 显示的搜索结果
搜索结果作为一个列表显示,其中包括作为超链接的文档名称 (DocTitle) 和文档摘要 (characterization)。
为了确保目录中的数据以有效的 XML 格式传送,可以使用 ColdFusion 函数 XmlFormat。它的角色是对字符串中的特殊 XML 字符进行转义,以便字符串可以用作 XML 文档中的简单文本。
翻页查看搜索结果
在搜索页面层(在客户机上),当前显示结果的页面数目保存在内存中 (CurrentPage)。两个 JavaScript 函数启动对前一页面 (getPreviousResults) 和下一页面 (getNextResults) 的请求。
在服务器端,使用记录集的 PageSize 属性将搜索结果分割成区块,以限制在浏览器中同时显示的结果数。设置此属性之后,可以检索记录集中可用的页面数 (PageCount)。这样,就可以将必要的导航链接添加到返回给浏览器的 XHTML 代码中。
正如在清单 2 中所看到的,还可以在 Recordset 的 AbsolutePage 属性的帮助下指定要显示的搜索结果子集(页面)。赋予此属性的值被传递到 URL 上的页面。
结束语
Windows Indexing Service 提供了一种开箱即用的向网站或 Intranet 中添加搜索功能的简单方法。通过将此方法与 Ajax 和成熟的服务器技术相结合,可以将此功能转化成一种有效的用户友好的搜索工具。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 本文使用的 ColdFusion 脚本 | wa-aj-rico.zip | 2KB | HTTP |
|---|
参考资料 学习
获得产品和技术
- 下载
IBM 产品评估版,并从 DB2®、Lotus®、Rational®、Tivoli® 和
WebSphere® 获得应用程序开发工具和中间件产品。
讨论
关于作者
对本文的评价
|