springboot+lucene实现公众号关键词回复智能问答

一、场景简介

  最近在做公众号关键词回复方面的智能问答相关功能,发现用户输入提问内容和我们运营配置的关键词匹配回复率极低,原因是我们采用的是数据库的Like匹配。

这种模糊匹配首先不是很智能,而且也没有具体的排序功能。为了解决这一问题,我引入了分词器+Lucene来实现智能问答。

二、功能实现

本功能采用springboot项目中引入Lucene相关包,然后实现相关功能。前提大家对springboot要有一定了解。

POM引入Lucene依赖

<!--lucene核心包-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!--对分词索引查询解析-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- smartcn中文分词器 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-smartcn</artifactId>
            <version>7.6.0</version>
        </dependency>

初始化Lucene相关配置Bean

初始化bean类需要知道的几点:

1.实例化 IndexWriter,IndexSearcher 都需要去加载索引文件夹,实例化是是非常消耗资源的,所以我们希望只实例化一次交给spring管理。

2.IndexSearcher 我们一般通过SearcherManager管理,因为IndexSearcher 如果初始化的时候加载了索引文件夹,那么

后面添加、删除、修改的索引都不能通过IndexSearcher 查出来,因为它没有与索引库实时同步,只是第一次有加载。

3.ControlledRealTimeReopenThread创建一个守护线程,如果没有主线程这个也会消失,这个线程作用就是定期更新让SearchManager管理的search能获得最新的索引库,下面是每25S执行一次。

5.要注意引入的lucene版本,不同的版本用法也不同,许多api都有改变。

/**
 * @author mazhq
 * @Title: LuceneConfig
 * @date 2019/9/5 11:29
 */
@Configuration
public class LuceneConfig {
    /**
     * lucene索引,存放位置
     */
    private static final String LUCENE_INDEX_PATH = "lucene/indexDir/";
    /**
     * 创建一个 Analyzer 实例
     */
    @Bean
    public Analyzer analyzer() {
        return new SmartChineseAnalyzer();
    }
    /**
     * 索引位置
     */
    @Bean
    public Directory directory() throws IOException {
        Path path = Paths.get(LUCENE_INDEX_PATH);
        File file = path.toFile();
        if (!file.exists()) {
            //如果文件夹不存在,则创建
            file.mkdirs();
        }
        return FSDirectory.open(path);
    }
    /**
     * 创建indexWriter
     */
    @Bean
    public IndexWriter indexWriter(Directory directory, Analyzer analyzer) throws IOException {
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
        // 清空索引
        indexWriter.deleteAll();
        indexWriter.commit();
        return indexWriter;
    }
    /**
     * SearcherManager管理
     * ControlledRealTimeReopenThread创建一个守护线程,如果没有主线程这个也会消失,
     * 这个线程作用就是定期更新让SearchManager管理的search能获得最新的索引库,下面是每25S执行一次。
     */
    @Bean
    public SearcherManager searcherManager(Directory directory, IndexWriter indexWriter) throws IOException {
        SearcherManager searcherManager = new SearcherManager(indexWriter, false, false, new SearcherFactory());
        ControlledRealTimeReopenThread cRTReopenThead = new ControlledRealTimeReopenThread(indexWriter, searcherManager,
                5.0, 0.025);
        cRTReopenThead.setDaemon(true);
        //线程名称
        cRTReopenThead.setName("更新IndexReader线程");
        // 开启线程
        cRTReopenThead.start();
        return searcherManager;
    }
}

初始化索引库

项目启动后,重建索引库中所有的索引。

@Component
@Order(value = 1)
public class AutoReplyMsgRunner implements ApplicationRunner {
    @Autowired
    private LuceneManager luceneManager;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        luceneManager.createAutoReplyMsgIndex();
    }
}

从数据库中查出所有配置的消息回复内容,并创建这些内容的索引。

索引相关介绍:

我们知道,mysql对每个字段都定义了字段类型,然后根据类型保存相应的值。

那么lucene的存储对象是以document为存储单元,对象中相关的属性值则存放到Field(域)中;

Field类的常用类型

Field类 数据类型 是否分词 index是否索引 Stored是否存储 说明
StringField 字符串 N Y Y/N 构建一个字符串的Field,但不会进行分词,将整串字符串存入索引中,适合存储固定(id,身份证号,订单号等)
FloatPoint
LongPoint
DoublePoint
数值型 Y Y N 这个Field用来构建一个float数字型Field,进行分词和索引,比如(价格)

StoredField 重载方法,,支持多种类型 N N Y 这个Field用来构建不同类型Field,不分析,不索引,但要Field存储在文档中

TextField 字符串或者流 Y Y Y/N 一般此对字段需要进行检索查询

commit()的用法

commit()方法,indexWriter.addDocuments(docs);只是将文档放在内存中,并没有放入索引库,没有commit()的文档,我从索引库中是查询不出来的;

许多博客代码中,都没有进行commit(),但仍然能查出来,因为每次插入,他都把IndexWriter关闭.close(),Lucene关闭前,都会把在内存的文档,提交到索引库中,索引能查出来,在spring中IndexWriter是单例的,不关闭,所以每次对索引都更改时,都需要进行commit()操作;

 

@Service
public class LuceneManager {
    @Autowired
    private IndexWriter indexWriter;
    @Autowired
    private AutoReplyMsgDao autoReplyMsgDao;

    public void createAutoReplyMsgIndex() throws IOException {
        List<AutoReplyMsg> autoReplyMsgList = autoReplyMsgDao.findAllTextConfig();
        if(autoReplyMsgList != null){
            List<Document> docs = new ArrayList<Document>();
            for (AutoReplyMsg autoReplyMsg:autoReplyMsgList) {
                Document doc = new Document();
                doc.add(new StringField("id", autoReplyMsg.getGuid()+"", Field.Store.YES));
                doc.add(new TextField("keywords", autoReplyMsg.getReceiveContent(), Field.Store.YES));
                doc.add(new StringField("replyMsgType", autoReplyMsg.getReplyMsgType()+"", Field.Store.YES));
                doc.add(new StringField("replyContent", autoReplyMsg.getReplyContent()==null?"":autoReplyMsg.getReplyContent(), Field.Store.YES));
                doc.add(new StringField("title", autoReplyMsg.getTitle()==null?"":autoReplyMsg.getTitle(), Field.Store.YES));
                doc.add(new StringField("picUrl", autoReplyMsg.getPicUrl()==null?"":autoReplyMsg.getPicUrl(), Field.Store.YES));
                doc.add(new StringField("url", autoReplyMsg.getUrl()==null?"":autoReplyMsg.getUrl(), Field.Store.YES));
                doc.add(new StringField("mediaId", autoReplyMsg.getMediaId()==null?"":autoReplyMsg.getMediaId(), Field.Store.YES));
                docs.add(doc);
            }
            indexWriter.addDocuments(docs);
            indexWriter.commit();
        }
    }
}

智能查询

searcherManager.maybeRefresh()方法,刷新searcherManager中的searcher,获取到最新的IndexSearcher。

@Service
public class SearchManager {
    @Autowired
    private Analyzer analyzer;
    @Autowired
    private SearcherManager searcherManager;

    public AutoReplyMsg searchAutoReplyMsg(String keyword) throws IOException, ParseException {
        searcherManager.maybeRefresh();
        IndexSearcher indexSearcher = searcherManager.acquire();
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        builder.add(new QueryParser("keywords", analyzer).parse(keyword), BooleanClause.Occur.MUST);
        TopDocs topDocs = indexSearcher.search(builder.build(), 1);
        ScoreDoc[] hits = topDocs.scoreDocs;
        if(hits != null && hits.length > 0){
            Document doc = indexSearcher.doc(hits[0].doc);
            AutoReplyMsg autoReplyMsg = new AutoReplyMsg();
            autoReplyMsg.setGuid(Long.parseLong(doc.get("id")));
            autoReplyMsg.setReceiveContent(keyword);
            autoReplyMsg.setReceiveMsgType(1);
            autoReplyMsg.setReplyMsgType(Integer.valueOf(doc.get("replyMsgType")));
            autoReplyMsg.setReplyContent(doc.get("replyContent"));
            autoReplyMsg.setTitle(doc.get("title"));
            autoReplyMsg.setPicUrl(doc.get("picUrl"));
            autoReplyMsg.setUrl(doc.get("url"));
            autoReplyMsg.setMediaId(doc.get("mediaId"));
            return autoReplyMsg;
        }

        return null;
    }
}

索引维护~删除更新索引

public int delete(AutoReplyMsg autoReplyMsg){
        int resp = autoReplyMsgDao.delete(autoReplyMsg.getGuid());
        try {
            indexWriter.deleteDocuments(new Term("id", autoReplyMsg.getGuid()+""));
            indexWriter.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resp;
    }

  

好了,智能问答查询回复功能基本完成了,大大提高公众号智能回复响应效率。

原文地址:https://www.cnblogs.com/owenma/p/11475161.html

时间: 2024-10-30 23:00:11

springboot+lucene实现公众号关键词回复智能问答的相关文章

Java - 微信公众号 - 消息回复

1.开发接入 (1)更改入门教程中的Controller:  注(验证是属于GET,然而交互是属于POST) @RequestMapping(value = "/getMsg", method = {RequestMethod.GET, RequestMethod.POST}) public void getMsg(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

微信公众号消息回复

参照https://www.kancloud.cn/digest/wechat-java/123962 写了servlet并处理后,发现在服务器的 tomcat/logs/localhost_access.txt的日志里面 总是接收不到 微信端发送的post的请求,因为 微信的 文本消息回复原理是,用户发送消息给微信,微信把这些消息以及用户信息通过post请求发送给服务器.服务器提供post接口的URL,这个URL就是在开发者文档里配置的URL.如下所示 ,而自己为什么没有收到那? 最后发现是没

关于微信公众号被动回复带有表情的文字

表情的消息类型实质是文本消息.每个表情都有与之对应的表情代码.

C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)

原文:C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C#微信公众号开发系列教程四(接收普通消息) C#微信公众号开发系列教程五(接收事件推送与消息排重) C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件) 第四,第五章已经讲了怎么处理用户发送的消息,本章就来讲讲怎么响应用户的

Python微信公众号后台开发&lt;005&gt;:集成智能聊天机器人?

?给公众号集成一个智能聊天机器人 一.前述 ChatterBot是一个基于机器学习的聊天机器人引擎,构建在python上,主要特点是可以自可以从已有的对话中进行学(jiyi)习(pipei). 二.具体 1.安装 是的,安装超级简单,用pip就可以啦 pip install chatterbot 2.流程 大家已经知道chatterbot的聊天逻辑和输入输出以及存储,是由各种adapter来限定的,我们先看看流程图,一会再一起看点例子,看看怎么用. 3.每个部分都设计了不同的“适配器”(Adap

公众号运营规则记录

微信公众号:关键词不区分大小写,没法识别空格 为了防止用户回复一个关键词,匹配多个结果,随机出现结果的问题,所以对于歌曲名和歌手名,设置全部匹配:英文全部小写并去除空格,英文歌曲名歌手名保留多一份小写有空格的,万一腾讯解决了问题(Baby, Don't Cry只需去掉里面的逗号) QQ公众号:关键词区分大小写,能识别空格 为了防止用户回复一个关键词,匹配多个结果,随机出现结果的问题,所以对于歌曲名和歌手名,设置全部匹配:英文全部小写并去除空格,英文歌曲名歌手名保留多一份小写有空格的,万一腾讯解决

微信公众号第三方管理平台哪个比较靠谱?

现在用微信公众号推广的人越来越多了,但是很多人都是用的公众号后台自带的那一套工具,虽然也能满足大多数要求,但是对于有多个公众号要管理的人来说就有点不够用了. 今天给大家推荐一款非常好用的微信公众号客服系统--鱼塘微营销系统,鱼塘微营销系统是一款可以帮助企业更好的运营及维护客户(粉丝)的企业智能微营销SAAS云 服务系统.它的主要特色有:多客服沟通.粉丝CRM功能.用户来源统计以及数据分析.客服管理等多种功能为一体的自媒体营销系统. 一.怎样实现多客服沟通大家都知道微信公众号后台被人所诟病的一点是

我把公众号所有的精华文章都整理出来了

从公众号第一篇推文开始,一路走来,感谢一直以来,各位小伙伴们对民工哥公众号的关注与支持,好多小伙伴们一直以都非常热心帮助转发.点赞.留言加以支持,再次感谢!!2019年也将过去三分之一了,民工哥仍然会坚持自己的初衷,持续输出一系列相关的干货文章(不仅限于运维,更多侧重于各类知识点.技术面的扩充,如:数据库.高并发.大流量.架构类等). 应该很多读者的要求,我把民工哥技术之路的从开始至今的文章,进行了一个分类整理,方便大家分类阅读.欢迎大家转发.分享,感谢支持! 该目录会一直持续更新,大家可以在公

让你的公众号拥有AI能力--微信对话开放平台

前段时间,微信上线了一个「微信对话开放平台」,旨在以对话交互为核心, 为有客服需求的个人.企业和组织提供智能业务服务与用户管理能力的技能配置平台,可利用提供的工具自主完成客服机器人的搭建.今天就来介绍一下这个对话开放平台,并通过简单的教程,让你的公众号具有 AI 能力. 一.创建机器人 1.登陆 官网:https://openai.weixin.qq.com 打开上述官网,点击「开始使用」或者右上角的「登录」,即可使用微信扫码登录系统. 2.创建机器人 登录之后,再次点击「开始使用」,即可创建机