Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

GDS [1] 使用Google桌面搜索构建企业搜索方案

Posted on 2005-04-06 23:18  Flier Lu  阅读(5388)  评论(6编辑  收藏  举报
http://www.blogcn.com/User8/flier_lu/blog/20045179.html


    最近实在太忙,没空更新blog,只好贴点代码先,呵呵

    在建立 企业内容管理服务器时,一个很大的问题是如何处理种类繁多、数量巨大的文件检索和定位问题。虽然大部分关系型数据库提供了 LIKE 语句或面向字段的全 文检索支持,但并非所有数据都适合保存在数据库中。例如一个 SharePoint 建立的知识共享服务器上,存在 DOC, PPT, PDF,  CHM 等不同格式文档文件,面向内容的全文检索如 Lucene 或 MS Search 都无法提供直接的完善支持。而且对检索内容的更新和历史追 述也是大问题,特别是基于文件方式共享时,很难对更新文档的索引实效性和更新的历史做维护。
    好在现在有了 Google 桌面搜索(GDS - Google Desktop Search),让这些恼人的问题可以告一段落了,呵呵。
     我们所需要做的只是在服务器上安装一套 Google 桌面搜索,配置好允许它索引哪些目录,下载或开发一些特定格式文件的支持。Google 桌面搜索 自身就支持 Word, Excel, Pointpoint, Html, Pdf 等常见格式,通过免费的插件还可以支持诸如 CHM 等特定格式, 也可以很方便开发支持自己特定格式的索引插件。(本系列文章下一篇将以 foxmail 的索引器为例介绍如何开发索引器)而 Google 桌面搜索会 自己处理诸如索引实效性和历史版本(快照)的问题。
    而使用 GDS 结果的最简单方法,莫过于直接访问 localhost:4664  的 Web 搜索界面。可以通过 Apache 的 mod_proxy 或类似功能将之转发到对外的页面上。但一个较大问题是这样返回的搜索结果是较难 重用的 HTML 格式,而且路径指向的是服务器上的文件路径,对远程访问不可用。
    好在 GDS 提供了查询服务支持,我们可以通过此接口获取我们感兴趣的查询结果,并翻译成远程可用的地址链接。
    
    主要流程如下:
    
    1.检测 GDS 的安装情况,获取其查询请求的key
    2.向 GDS 以特定格式发送查询请求
    3.解析 GDS 返回的 XML 格式查询结果
    4.将查询结果翻译成远程可用形式,并在门户上展示
    
    第一步检测 GDS 的安装实现很简单,只需要测试特定注册表键的存在与否,以及其指向目录的有效性即可。
以下内容为程序代码:

namespace com.nsfocus.google.search
{
  public class SearchEngine
  {
    private static readonly DirectoryInfo _installDir, _dataDir;
    private static readonly String _url = null;
    
    static SearchEngine()
    {
      using(RegistryKey reg = Registry.CurrentUser.OpenSubKey(@"Software\Google\Google Desktop"[img]/images/wink.gif[/img])
      {
        try
        {
          _installDir = new DirectoryInfo((String)reg.GetValue("install_dir"[img]/images/wink.gif[/img]);
          _dataDir = new DirectoryInfo((String)reg.GetValue("data_dir"[img]/images/wink.gif[/img]);

          if(!_installDir.Exists) _installDir = null;
          if(!_dataDir.Exists) _dataDir = null;

          _url = (string)reg.OpenSubKey("API"[img]/images/wink.gif[/img].GetValue("search_url"[img]/images/wink.gif[/img];
        }
        catch(Exception)
        {
          _url = null;
        }
      }
    }
    
        public static bool IsInstalled
    {
      get
      {
        return _installDir != null && _dataDir != null && _url != null; 
      }            
    }

    public static DirectoryInfo InstallDirectory
    {
      get
      {
        return _installDir;
      }
    }

    public static DirectoryInfo DataDirectory
    {
      get
      {
        return _dataDir;
      }
    }
  }
}
   
     注意 GDS 处于安全性或某方面的考虑,在安装时会为每台机器生成一个唯一的查询  key,在发送查询请求时必须附带此 key,否则查询会失败。这个 key 的内容可以通过  search_url 获取到,如笔者机器上 search_url 内容如下:
以下为引用:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Google\Google Desktop\API]
"search_url"="::URL::http://127.0.0.1:4664/search&s=SUPg4XxVcE8Bx8Atf4uTQd3Z7ic?q="

    

    在得到搜索url后,就可以以特定格式组织查询请求,在一个GET请求中提交所有需要查询的内容。一般来说,查询参数可以分为查询字符串、查询标记、起始记录、返回记录数和返回格式。
     查询字符串指定你要搜索的内容,如果内容中有空格,需要以 + 号替代;如果希望搜索一个连续 匹配,需要用 " 号扩起来,并在发送时编码为 %25。查询字符串的编码支持如下:
以下内容为程序代码:

namespace com.nsfocus.google.search
{
  public class SearchEngine
  {
    public static string EncodeQuery(String query)
    {
      StringBuilder sb = new StringBuilder(query.Length);

      foreach(char ch in query)
      {
        switch(ch)
        {
          case ' ':
          {
            sb.Append('+');
            break;
          }
          case '\"':
          {
            sb.Append("%22"[img]/images/wink.gif[/img];
            break;
          }
          default:
          {
            sb.Append(ch);
            break;
          }
        }
      }

      return sb.ToString();
    }
  }
  
  [TestFixture]
  public class SearchEngineTester
  {
    [Test]
    public void testEncodeQuery()
    {
      Assert.AreEqual("test", SearchEngine.EncodeQuery("test"[img]/images/wink.gif[/img]);
      Assert.AreEqual("test+test", SearchEngine.EncodeQuery("test test"[img]/images/wink.gif[/img]);
      Assert.AreEqual("%22test+test%22+%22test+test%22", SearchEngine.EncodeQuery("\"test test\" \"test test\""[img]/images/wink.gif[/img]);
    }
  }
}
   
    查询标记则包括要查询哪些类型的数据,以什么方式排序等等,如:
以下内容为程序代码:

namespace com.nsfocus.google.search
{
  [Flags]
  public enum SearchFlags : int
  {
    Default         = 0,
    SortByDate      = 8,
    SortByRelevance = 40,
    Chat            = 128,
    Web             = 256,
    File            = 512,
    Email           = 1024,
  }
}
   
    起始记录、返回记录数分别定义返回的查询结果从哪条记录开始,一次返回多少条,跟数据库操作中的分页很类似。返回格式则直接指定为便于二次分析处理的 xml 格式。
    具体完成搜索请求的代码如下:
以下内容为程序代码:

namespace com.nsfocus.google.search
{
  public class SearchEngine
  {
    internal XmlDocument Search(SearchQuery query)
    {
      StringBuilder sb = new StringBuilder(_url);

      sb.Append(EncodeQuery(query.Query));

      if(query.Format != null)
        sb.Append("&format="[img]/images/wink.gif[/img].Append(query.Format);

      if(query.Start > 0)
        sb.Append("&start="[img]/images/wink.gif[/img].Append(query.Start);

      if(query.Number > 0)
        sb.Append("&num="[img]/images/wink.gif[/img].Append(query.Number);

      if(query.Flags != 0)
        sb.Append("&flags="[img]/images/wink.gif[/img].Append(query.Flags);

      XmlDocument doc = new XmlDocument();

      using(Stream stream = _client.OpenRead(sb.ToString()))
      {
        doc.Load(stream);
      }

      return doc;
    }
  }
}
   
    GDS 会根据搜索请求,返回一段 XML 格式的搜索结果,如
以下为引用:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<results count="24945">
<result>
<category>file</category> 
<id>46384</id> 
<title>SDK Developer Documentation</title> 
<url>C:\Documents and Settings\me\My Documents\developerguide.html~</url> 
<time>127543068856350000</time> 
<snippet>SDK Developer Documentation <b>Google</b> Desktop Search SDK Developer Guide For Users Download Plug-ins Desktop Search Forum For Developers</snippet> 
<icon>/file.gif</icon> 
<cache_url>::URL::http://127.0.0.1:4664/...  </cache_url> 
</result>

...

</results>

    
    我们需要对结果集做解析和整理,分析生成一个完整可用的结果列表。
以下内容为程序代码:

namespace com.nsfocus.google.search
{
  public enum ResultCategory
  {
    Email,
    Chat, 
    Web,
    File
  }

  public interface SearchResult
  {
    ResultCategory Category { get; }

    int ID { get; }

    String Title { get; }

    Uri Url { get; }

    DateTime Time { get; }

    String Snippet { get; }

    String Icon { get; }

    Uri CacheUrl { get; }
  }
  
  public interface SearchResultSet : IEnumerable
  {
    SearchQuery Query { get; }

    int Count { get; }

    int TotalNumber { get; }    

    SearchResult this[int idx] { get; }
  }
}
   
    然后就可以很方便的使用此封装库进行 GDS 查询,如
以下内容为程序代码:

    [STAThread]
    static void Main(string[] args)
    {
      ShowCopyright();

      SearchEngine engine = new SearchEngine();

      SearchResultSet results = engine.Search(args[0]);

      Console.Out.WriteLine("共搜索到 {0} 条匹配记录", results.TotalNumber);
      Console.Out.WriteLine();
       Console.Out.WriteLine("其中从第 {0}  条开始的 {1} 条记录为:", results.Query.Start,  results.Query.Number);
      Console.Out.WriteLine();

      foreach(SearchResult result in results)
      {
        Console.Out.WriteLine("========================================"[img]/images/wink.gif[/img];
        Console.Out.WriteLine("Title: " + result.Title);  
        Console.Out.WriteLine();
        Console.Out.WriteLine("URL: " + result.Url.ToString());
        Console.Out.WriteLine();
        Console.Out.WriteLine(result.Snippet);
      }
    }
   
    对企业搜索服务来说,可以将 URL 转换为远程可用的 Web 地址。

    下面是完整的结果集解析代码:
以下内容为程序代码:

namespace com.nsfocus.google.search.desktop
{
  public class SearchResultImpl : SearchResult
  {
    private readonly SearchResultSetImpl _resultSet;

    private readonly ResultCategory _cat;
    private readonly int _id;
    private readonly String _title, _snippet, _icon;
    private readonly Uri _url, _cacheUrl;
    private readonly DateTime _time;

    public SearchResultImpl(SearchResultSetImpl resultSet, XmlNode result)
    {
      _resultSet = resultSet;

      foreach(XmlNode attr in result.ChildNodes)
      {
        String value = attr.InnerText.Trim();

        switch(attr.Name)
        {
          case "category":
          {
            switch(value)
            {
              case "email":
              {
                _cat = ResultCategory.Email;
                break;
              }
              case "chat":
              {
                _cat = ResultCategory.Chat;
                break;
              }
              case "web":
              {
                _cat = ResultCategory.Web;
                break;
              }
              default:
              {
                _cat = ResultCategory.File;
                break;
              }
            }
            break;
          }
          case "id":
          {
            _id = Int32.Parse(value);
            break;
          }
          case "title":
          {
            _title = value;
            break;
          }
          case "url":
          {
            _url = new Uri(value);
            break;
          }
          case "time":
          {
            _time = DateTime.FromFileTime(Int64.Parse(value));
            break;
          }
          case "snippet":
          {
            _snippet = value;
            break;
          }
          case "icon":
          {
            _icon = value;
            break;
          }
          case "cache_url":
          {
            _cacheUrl = new Uri(value);
            break;
          }
        }
      }
    }

    public ResultCategory Category
    {
      get { return _cat; }
    }

    public int ID
    {
      get { return _id; }
    }

    public String Title
    {
      get { return _title; }
    }

    public Uri Url
    {
      get { return _url; }
    }

    public DateTime Time
    {
      get { return _time; }
    }

    public String Snippet
    {
      get { return _snippet; }
    }

    public String Icon
    {
      get { return _icon; }
    }

    public Uri CacheUrl
    {
      get { return _cacheUrl; }
    }
  }
  
  public class SearchResultSetImpl : SearchResultSet, IEnumerable
  {
    private readonly SearchEngine _engine;
    private readonly SearchQuery _query;

    private int _num = 0;
    private ArrayList _results = new ArrayList();    

    public SearchResultSetImpl(SearchEngine engine, SearchQuery query)
    {
      _engine = engine;
      _query = query;

      parse(_engine.Search(_query));
    }

    protected void parse(XmlDocument doc)
    {
      _num = Int32.Parse(doc.GetElementsByTagName("results"[img]/images/wink.gif[/img][0].Attributes["count"].Value);
      
      _results.Clear();

      foreach(XmlNode result in doc.GetElementsByTagName("result"[img]/images/wink.gif[/img])
      {
        try
        {
          _results.Add(new SearchResultImpl(this, result));  
        }
        catch(Exception)
        {
          // do nothing
        }
      }
    }

    public SearchQuery Query
    {
      get { return _query; }
    }

    public int Count
    {
      get { return _results.Count; }
    }

    public int TotalNumber
    {
      get { return _num; }
    }

    public SearchResult this[int idx]
    {
      get { return (SearchResult)_results[idx]; }
    }

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
      return _results.GetEnumerator();
    }

    #endregion
  }
}
       
    
to be continue... GDS [2] 为 Google 桌面搜索增加 Foxmail 支持