Lucene总结系列(二)--商品检索系统的文字检索业务(lucene项目使用)

继续这个Lucene总结系列吧。今天要讲的是一个Lucene的业务全程操作,然后这系列的以后都是以Lucene优化以及原理为主了。OK,开始!!!

本系列:

(1)SSM框架构建积分系统和基本商品检索系统(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(1)框架整合构建

(2)SSM框架构建积分系统和基本商品检索系统(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(2)建立商品数据库和Lucene的搭建

(3)Redis系列(一)--安装、helloworld以及读懂配置文件

(4) Redis系列(二)--缓存设计(整表缓存以及排行榜缓存方案实现)

(5) Lucene总结系列(一)--认识、helloworld以及基本的api操作。


文章结构:(1)业务说明以及技术说明;(2)业务实现(配合SynonymFilterFactory实现高精度地切割检索);


一、业务说明以及技术说明:

以下是我们要实现的效果喔!
这里写图片描述

这里写图片描述

(1)业务说明:文字检索商品

流程:

1. 我们预先建立商品的索引库在服务器。(根据商品的类别以及商品表的id和名字建立索引)

2. 文字检索商品,先往索引库去查询索引信息。比如:商品id、名字、价格.....

3. 查询出一个list装载着商品索引信息后就根据索引到的id往数据库查询商品详细信息。

(2)技术说明:文字检索商品

1.Lucene索引建立

2.根据建立好的lucene索引去查询

3.得到的索引信息后,再根据索引中的商品id去查询数据库,得到商品的详细信息。


二、业务实现

(1)索引建立:

@RunWith(SpringJUnit4ClassRunner.class) // 使用Springtest测试框架
@ContextConfiguration("/spring/spring-*.xml") // 加载配置
public class GoodIndexAdd {
    private LuceneDao luceneDao = new LuceneDao();
    @Autowired
    private GoodClassifyDao goodClassifyDao;
    @Test
    public void addIndexForAll() throws IOException {
        /**
         * 8-62:商品种类ID的起始Commodity_classification
         * 根据商品种类ID查询所属类别的商品信息,建立你的商品种类和商品索引,原因我只伪造了两个商品种类假数据,就是id=15和16的商品,所以我们只建立对他的索引咯
         * */
        for(int i = 15; i <= 16; i++){
            System.out.println("goodClassifyDao     "+goodClassifyDao);
            List<GoodDetails> list = goodClassifyDao.findGoodDetailsByClassifyID(i);
            System.out.println("junitTest:list.size()="+list.size());
            for (int index = 0; index < list.size(); index++) {
                luceneDao.addIndex(list.get(index));
                System.out.println(list.get(index).toString());
            }
        }
    }
}

联查一个

<!-- 根据商品种类ID查询所属类别的商品信息 ,目前用于建立索引-->
    <select id="findGoodDetailsByClassifyID"
            parameterType="integer" resultType="com.fuzhu.entity.GoodDetails">
        select
        d.Good_ID ,
        d.Classify_ID,
        d.Good_Name
        from
        Commodity_classification c,
        Commodity_list d
        where
        c.Classify_ID=#{value} and d.Classify_ID=c.Classify_ID
    </select>

(2)Controller层:

// 文字检索
    @RequestMapping(value = "/findGoodByName",produces="text/html;charset=UTF-8", method = {RequestMethod.GET,RequestMethod.GET})
    public Object findGoodByName(String goodName, HttpServletResponse response)
            throws Exception {
        response.setHeader("Access-Control-Allow-Origin", "*");//解决跨域问题

        System.out.println("查找商品名参数:" + goodName);
        System.out.println("-------------------------------");

        List<GoodDetails> goodDetailsList = goodService.findIndex(goodName, 0,
                2);// 100
        System.out.println("goodDetailsList=" + goodDetailsList.size());

        String realGoodid = null;
        GoodDetails goodAllDetails = new GoodDetails();
        goodList = new ArrayList<GoodDetails>();

        if (goodDetailsList != null && goodDetailsList.size() > 0) {
            long start = System.nanoTime();
            for (int index = 0; index < goodDetailsList.size(); index++) {
                realGoodid = goodDetailsList.get(index).getGoodId();
                goodAllDetails = goodService.findGoodAllDetailsById(realGoodid);
                if (goodAllDetails == null) {
                    System.out.println("realGoodid=" + realGoodid);
                }
                if (goodAllDetails != null) {
                    goodAllDetails.setGoodName(goodDetailsList.get(index)
                            .getGoodName() + realGoodid);
                    goodList.add(goodAllDetails);
                }
            }
            long time = System.nanoTime() - start;
            System.out.println("测试耗时!!!!"+time);
        }
        System.out.println("现在北京时间是:" + new Date());
        if (goodList != null) {
            System.out.println("根据商品名找到的商品数目" + goodList.size());
        }
        return JSON.toJSONString(goodList);

    }

(3)Service层调用检索索引:

  @Autowired
    private LuceneDao luceneDao;//交给spring管理这个
    @Override
    public List<GoodDetails> findIndex(String keyword, int start, int row) {
//        LuceneDao luceneDao = new LuceneDao();//交给spring管理这个
        System.out.print("luceneDao       "+luceneDao);
        List<GoodDetails> goodDetailsList;
        try {
            goodDetailsList = luceneDao.findIndex(keyword, start, row);
            return goodDetailsList;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

(4)Service层调用根据索引id检索商品细节:

 @Override
    public GoodDetails findGoodAllDetailsById(String goodId) {
        GoodDetails goodDetails = goodDetailsDao.findGoodDetailsById(goodId);
        return goodDetails;
    }

(5)LuceneDao检索索引库细节:

/*
     * 分页:每页10条
     * */
    public List<GoodDetails> findIndex(String keywords, int start, int rows) throws Exception {

        Directory directory = FSDirectory.open(new File(Constant.INDEXURL_ALL));//索引创建在硬盘上。
        IndexSearcher indexSearcher =  LuceneUtils.getIndexSearcherOfSP();

        /**同义词处理*/
//        String result = SynonymAnalyzerUtil.displayTokens(SynonymAnalyzerUtil.convertSynonym(SynonymAnalyzerUtil.analyzeChinese(keywords, true)));
//        Analyzer analyzer4 = new IKAnalyzer(false);// 普通简陋语意分词处理
//        TokenStream tokenstream = analyzer4.tokenStream("goodname", new StringReader(keyword));
        String result = keywords;//不作分词处理直接检索
        //需要根据哪几个字段进行检索...
        String fields[] = {"goodName"};

        //查询分析程序(查询解析)
        QueryParser queryParser = new MultiFieldQueryParser(LuceneUtils.getMatchVersion(), fields, LuceneUtils.getAnalyzer());

        //不同的规则构造不同的子类...
        //title:keywords content:keywords
        Query query = queryParser.parse(result);

        //这里检索的是索引目录,会把整个索引目录都读取一遍
        //根据query查询,返回前N条
        TopDocs topDocs = indexSearcher.search(query, start+rows);

        System.out.println("总记录数="+topDocs.totalHits);

        ScoreDoc scoreDoc[] = topDocs.scoreDocs;

        /**添加设置文字高亮begin*/
        //htmly页面高亮显示的格式化,默认是<b></b>即加粗
        Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
        Scorer scorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter, scorer);

        //设置文字摘要(高亮的部分),此时摘要大小为10
        //int fragmentSize = 10;
        Fragmenter fragmenter = new SimpleFragmenter();
        highlighter.setTextFragmenter(fragmenter);

        /**添加设置文字高亮end*/
        List<GoodDetails> goodDetailslist = new ArrayList<GoodDetails>();
        //防止数组溢出
        int endResult = Math.min(scoreDoc.length, start+rows);
        GoodDetails goodDetails = null;

        for(int i = start;i < endResult ;i++ ){
            goodDetails = new GoodDetails();
            //docID lucene的索引库里面有很多的document,lucene为每个document定义了一个编号,唯一标识,自增长
            int docID = scoreDoc[i].doc;
            System.out.println("标识docID="+docID);
            Document document = indexSearcher.doc(docID);
            /**获取文字高亮的信息begin*/
            System.out.println("==========================");
            TokenStream tokenStream = LuceneUtils.getAnalyzer().tokenStream("goodName", new StringReader(document.get("goodName")));
            String goodName = highlighter.getBestFragment(tokenStream, document.get("goodName"));
            System.out.println("goodName="+goodName);
            System.out.println("==========================");
            /**获取文字高亮的信息end*/

            //备注:document.get("id")的返回值是String
            goodDetails.setGoodId((document.get("id")));
            goodDetails.setGoodName(goodName);
            goodDetailslist.add(goodDetails);
        }
        return goodDetailslist;
    }

(6)检索精确优化,实现中文拆分:

public class SynonymAnalyzerUtil {

    /**
     *
     * 此方法描述的是:进行中文拆分
     */
    public static String analyzeChinese(String input, boolean userSmart) throws IOException {
        StringBuffer sb = new StringBuffer();
        StringReader reader = new StringReader(input.trim());
        // true 用智能分词 ,false细粒度
        IKSegmenter ikSeg = new IKSegmenter(reader, userSmart);
        for (Lexeme lexeme = ikSeg.next(); lexeme != null; lexeme = ikSeg.next()) {
            sb.append(lexeme.getLexemeText()).append(" ");
        }
        return sb.toString();
    }
    /**
     *
     * 此方法描述的是:针对上面方法拆分后的词组进行同义词匹配,返回TokenStream
     * synonyms.txt:同义词表,在resources目录下
     */
    public static TokenStream convertSynonym(String input) throws IOException{
        Version ver = Version.LUCENE_44;
        Map<String, String> filterArgs = new HashMap<String, String>();

        filterArgs.put("luceneMatchVersion", ver.toString());
        filterArgs.put("synonyms", "synonyms.txt");
        filterArgs.put("expand", "true");
        SynonymFilterFactory factory = new SynonymFilterFactory(filterArgs);
        factory.inform(new FilesystemResourceLoader());
        Analyzer IKAnalyzer = new IKAnalyzer();
        TokenStream ts = factory.create(IKAnalyzer.tokenStream("someField", input));
        return ts;
    }

    /**
     *
     * 此方法描述的是:将tokenstream拼成一个特地格式的字符串,交给IndexSearcher来处理,再进行精确度高的检索
     */
    public static String displayTokens(TokenStream ts) throws IOException
    {
        StringBuffer sb = new StringBuffer();
        CharTermAttribute termAttr = ts.addAttribute(CharTermAttribute.class);
        ts.reset();
        while (ts.incrementToken())
        {
            String token = termAttr.toString();
            sb.append(token).append(" ");
            System.out.print(token+"|");
        }
        System.out.println();
        ts.end();
        ts.close();
        return sb.toString();
    }
}

源码下载:Lucene之商品检索系统Demo

好了,Lucene总结系列(二)--商品检索系统的文字检索业务(lucene项目使用)讲完了。本博客系列是项目lucene业务的大致实现,当然一些算法的不能乱给,不过以后有自己的思路出来,写给大家,分享经验给大家。欢迎在下面指出错误,共同学习!!你的点赞是对我最好的支持!!

更多内容,可以访问JackFrost的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容