使用Lucene+Paoding构建SSH2系统的站内搜索

http://jnotnull.iteye.com/blog/275327


目标:创建一个具有高度可移植的,定时创建索引的站内搜索。

途径:dic和index都放到程序中去。

准备:
1   Lucene
Lucene Java(以下简称Lucene)目前可用版本是2.4.0,关于Lucene的详细信息请查看 http://lucene.apache.org/java/docs/index.html。


2 Paoding
Qieqie同学的伟大作品、优秀的Lucene中文分词组件,目前的版本为paoding-analysis-2.0.4-beta,对应的Lucene的版本为2.2。关于Paoding的具体信息请查看 http://code.google.com/p/paoding/。


3 下载最新的paoding-analysis-2.0.4-beta版本(里面包含了lucene-core-2.2.0.jar, lucene-analyzers-2.2.0.jar,lucene-highlighter-2.2.0.jar, junit.jar, commons-logging.jar)。


>>>>> 本文为原创,需要转载的朋友请注明: http://jnotnull.iteye.com  谢谢支持!<<<<<

开始工作:
   1 试运行
打开下载包中的examples文件夹,运行一下吧(注意一下编码)。


   2 集成到SSH2系统中去 (系统结构Action->service->dao)
1)  由于SSH2系统是web系统,因此在配置Paoding上就有可能和第一步有些不同。
直接把paoding文件夹下的src文件夹下的所有文件和dic文件夹复制到你的项目中去。打开paoding-dic-home.properties文件,修改paoding.dic.home.config-fisrt=this,使得程序知道该配置文件,修改paoding.dic.home=classpath:dic,使得字典在该项目中。保存就可以了。在这里我使用了classpath:dic是为了增加可移植性。如果使用绝对路径没有什么可说的了,但是如果你是制定为classpath:dic,则需要修改一下Paoding中的代码了。找到PaodingMaker.java的setDicHomeProperties方法,修改File dicHomeFile = getFile(dicHome);为

Java代码 复制代码  收藏代码
  1. File dicHomeFile2 = getFile(dicHome);   
  2.         String path="";   
  3.         try {   
  4.             path = URLDecoder.decode(dicHomeFile2.getPath(),"UTF-8");   
  5.         } catch (UnsupportedEncodingException e) {   
  6.             e.printStackTrace();   
  7.         }   
  8.     File dicHomeFile = new File(path);  

目的是解码,不然如果你的词典路径中有空格和汉字会出现找不到字典的异常。

2)表结构

Sql代码 复制代码  收藏代码
  1. CREATE TABLE `news` (   
  2.   `id` int(11) NOT NULL auto_increment,   
  3.   `title` varchar(255) default NULL,   
  4.   `details` mediumtext,   
  5.   `author` varchar(255) default NULL,   
  6.   `publisher` varchar(100) default NULL,   
  7.   `clicks` int(11) default NULL,   
  8.   `source` varchar(255) default NULL,   
  9.   `addtime` datetime default NULL,   
  10.   ` category ` varchar(100) default NULL,   
  11.   `keywords` varchar(255) default NULL,   
  12.   PRIMARY KEY  (`id`)   
  13. ) ENGINE=InnoDB DEFAULT CHARSET=gbk;  



    3 正式实施编码
       编写站内搜索分为两步:创建索引和进行搜索,所需类:SearchAction.java和TaskAction.java(同一目录)
1) 创建索引
主要任务:从已有的txt文件中读取上一次进行索引的最后一条新闻的id号,然后从业务逻辑中查找大于这个id号的所有新闻进行索引,最后把这次最后的一条新闻id写入txt文件中。在这里要处理好路径的问题。在这里所有的记录id号的txt文件都放到了action目录下面。
新建TaskAction,增加如下方法

Java代码 复制代码  收藏代码
  1. public void createIndex() {   
  2.         String path;   
  3.         try {      
  4. //两个参数:创建索引的位置  和 上一次创建索引最后的新闻id所在文件   
  5.     createNewsIndex(getPath(TaskAction.class"date/index/news"),"newsid.txt");   
  6.         } catch (Exception e) {   
  7.             e.printStackTrace();   
  8.         }   
  9.     }   
  10.   
  11. public String getPath(Class clazz, String textName)   
  12.             throws IOException {   
  13.         String path = (URLDecoder.decode(   
  14.                 clazz.getResource(textName).toString(), "UTF-8")).substring(6);        
  15.         return path;   
  16.     }   
  17.   
  18. public void createNewsIndex(String path,String textName) throws Exception {   
  19.         String newsId = "0";   
  20.            
  21.         newsId = readText(TaskAction.class, textName);   
  22.         if (null ==newsId || "".equals(newsId))   
  23.             newsId = "0";   
  24.   
  25.         // 使用paoding中文分析器   
  26.         Analyzer analyzer = new PaodingAnalyzer();   
  27.         FSDirectory directory = FSDirectory.getDirectory(path);   
  28.         System.out.println(directory.toString());   
  29.         IndexWriter writer = new IndexWriter(directory, analyzer, isEmpty(TaskAction.class, textName));   
  30.         Document doc = new Document();   
  31.   
  32.         // 从业务逻辑层读取大于当前id的信息   
  33.         List list = newsManageService.getNewsBigId(Integer.parseInt(newsId));   
  34.         Iterator iterator = list.iterator();   
  35.         News news = new News();   
  36.         while (iterator.hasNext()) {   
  37.             doc = new Document();   
  38.             news = (News) iterator.next();   
  39.             doc.add(new Field("id""" + news.getId(), Field.Store.YES,   
  40.                     Field.Index.UN_TOKENIZED));   
  41.             doc.add(new Field("title""" + news.getTitle(), Field.Store.YES,   
  42.                     Field.Index.TOKENIZED));   
  43.             doc.add(new Field("author""" + news.getAuthor(), Field.Store.YES,   
  44.                     Field.Index.TOKENIZED));   
  45.             doc.add(new Field("details"""  
  46.                     + Constants.splitAndFilterString(news.getDetails()),   
  47.                     Field.Store.YES, Field.Index.TOKENIZED,   
  48.                     Field.TermVector.WITH_POSITIONS_OFFSETS));   
  49.             doc.add(new Field("addtime""" + news.getAddtime(),   
  50.                     Field.Store.YES, Field.Index.TOKENIZED));   
  51.             doc.add(new Field("keywords""" + news.getKeywords(),   
  52.                     Field.Store.YES, Field.Index.TOKENIZED));   
  53.             System.out.println("Indexing file " + news.getName() + "...");   
  54.             articleId = String.valueOf(news.getId());   
  55.             try {   
  56.                 writer.addDocument(doc);   
  57.             } catch (IOException e) {   
  58.                 e.printStackTrace();   
  59.             }   
  60.         }   
  61.         // 优化并关闭   
  62.         writer.optimize();   
  63.         writer.close();   
  64.   
  65.         // 将我索引的最后一篇文章的id写入文件   
  66.         String content = WriteText(TaskAction.class,   
  67.                 textName, newsId);   
  68.     }      
  69.   
  70. public boolean isEmpty(Class clazz, String textName) throws Exception {   
  71.         String articleId = "0";   
  72.         boolean isEmpty = true;   
  73.         articleId = ContentReader.readText(clazz, textName);   
  74.         if (null == articleId || "".equals(articleId))   
  75.             articleId = "0";   
  76.         if (!articleId.equals("0"))   
  77.             isEmpty = false;   
  78.         System.out.println(clazz.getName()+" "+isEmpty);   
  79.         return isEmpty;   
  80.     }   
  81.   
  82. //该方法参考了paoding中example中的一个方法。   
  83. public String readText(Class clazz, String textName)   
  84.             throws IOException {   
  85.         InputStream in = clazz.getResourceAsStream(textName);   
  86.         Reader re = new InputStreamReader(in, "UTF-8");   
  87.         char[] chs = new char[1024];   
  88.         int count;   
  89.         String content = "";   
  90.         while ((count = re.read(chs)) != -1) {   
  91.             content = content + new String(chs, 0, count);   
  92.         }   
  93.         return content;   
  94.     }   
  95.   
  96. public String WriteText(Class clazz, String textName, String text)   
  97.             throws IOException {   
  98.         String path = (URLDecoder.decode(   
  99.                 clazz.getResource(textName).toString(), "UTF-8")).substring(6);   
  100.         System.out.println(path);   
  101.         File file = new File(path);   
  102.         BufferedWriter bw = new BufferedWriter(new FileWriter(file));   
  103.         String temp = text;   
  104.         bw.write(temp);   
  105.         bw.close();   
  106.         return temp;   
  107.     }  


2)进行搜索

Java代码 复制代码  收藏代码
  1. public void searchIndex(String path, String keywords) throws Exception {   
  2.         String[] FIELD = { "title""details" };   
  3.         String QUERY = keywords;   
  4.   
  5.         Analyzer analyzer = new PaodingAnalyzer();   
  6.         FSDirectory directory = FSDirectory.getDirectory(path);   
  7.         IndexReader reader = IndexReader.open(directory);   
  8.         String queryString = QUERY;   
  9.         BooleanClause.Occur[] flags = new BooleanClause.Occur[] {   
  10.                 BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD };   
  11.         Query query = MultiFieldQueryParser.parse(queryString, FIELD, flags,   
  12.                 analyzer);   
  13.   
  14.         Searcher searcher = new IndexSearcher(directory);   
  15.         query = query.rewrite(reader);   
  16.         System.out.println("Searching for: " + query.toString());   
  17.         Hits hits = searcher.search(query);   
  18.   
  19.         NewsDTO news = new NewsDTO();   
  20.         String highLightText = "";   
  21.   
  22.         for (int i = 0; i < hits.length(); i++) {   
  23.   
  24.             Document doc = hits.doc(i);   
  25.             String title1 = doc.get("title");   
  26.             String contents1 = doc.get("details");   
  27.   
  28.             SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(   
  29.                     """");   
  30.   
  31.             Highlighter highlighter = new Highlighter(simpleHTMLFormatter,   
  32.                     new QueryScorer(query));   
  33.             highlighter.setTextFragmenter(new SimpleFragmenter(200));   
  34.   
  35.             if (contents1 != null) {   
  36.                 TokenStream tokenStream = analyzer.tokenStream("details",   
  37.                         new StringReader(contents1));   
  38.                 highLightText = highlighter.getBestFragment(tokenStream,   
  39.                         contents1);   
  40.             }   
  41.             news = new NewsDTO();   
  42.             news.setId(Integer.parseInt(doc.get("id")));   
  43.             news.setName(doc.get("title"));   
  44.             news.setDetails(highLightText);   
  45.             news.setAddtime(doc.get("addtime"));   
  46.             news.setAuthor(doc.get("author"));   
  47.             searchResultItem.add(news);   
  48.         }   
  49.         reader.close();   
  50.   
  51.     }  

   核心代码已经基本完成了,还有一个加亮显示,非常不错的哦。

3)再来一个定时创建索引:
   定义一下bean

Java代码 复制代码  收藏代码
  1.            
  2. <bean id="myTask" class="edu.cumt.jnotnull.action.TaskAction">   
  3.         <property name="newsManageService">   
  4.             <ref bean="newsManageService" />   
  5.         </property>   
  6.     </bean>   
  7.   
  8.     <bean id="entity"  
  9.         class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">   
  10.         <property name="targetObject">   
  11.             <ref local="myTask" />   
  12.         </property>   
  13.         <property name="targetMethod">   
  14.             <value>createIndex</value>   
  15.         </property>   
  16.     </bean>   
  17.   
  18.     <bean id="cron"  
  19.         class="org.springframework.scheduling.quartz.CronTriggerBean">   
  20.         <property name="jobDetail">   
  21.             <ref bean="entity" />   
  22.         </property>   
  23.         <property name="cronExpression">   
  24.             <value>0 0-5 2 * * ?</value>   
  25.         </property>   
  26.     </bean>   
  27.   
  28.     <bean autowire="no"  
  29.         class="org.springframework.scheduling.quartz.SchedulerFactoryBean">   
  30.         <property name="triggers">   
  31.             <list>   
  32.                 <ref local="cron" />   
  33.             </list>   
  34.         </property>   
  35.     </bean>      
  36.            
  37.       


这样就可以在夜里面让他自动促发了。


相关讨论:
权限管理问题:创建索引应该是管理员才可以调用的。如何在定时执行下进行访问控制呢。 

你可能感兴趣的:(使用Lucene+Paoding构建SSH2系统的站内搜索)