es简单实现文章检索功能

news2024/10/10 18:21:46

使用的api是:Elasticsearch Java API client 8.0

官网:Package structure and namespace clients | Elasticsearch Java API Client [8.15] | Elastic

 

1.建立索引库

实现搜索功能字段:

  1. title:文章标题
  2. content:文章内容
  3. category:文章分类
PUT /search_test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_max_word",
        "index": true
      },
      "content":{
        "type": "text",
        "analyzer": "ik_max_word",
         "index": true
      },
      "category":{
        "type": "keyword",
        "index": true
      }
    }
  }
}

2.代码实现

返回实体:

@Data
public class ContentSearchVO {

    @Schema(description = "标题")
    private String title;

    @Schema(description = "高亮内容部分,用于首页展示")
    private String highLightContent;

    @Schema(description = "内容")
    private String content;

}

请求实体:

@Data
public class SearchDTO {
    @Schema(description="搜索词")
    @NotBlank(message = "搜索词不能是空")
    private String text;

    @Schema(description="分类")
    private String category;

    private Integer pageNo;

    private Integer pageSize;

}

2.1基于模板形式

    @Override
    public List<ContentSearchVO> search(SearchDTO dto)  {
        String tags = dto.getCategory();
        if (tags != null){
            tags = String.format(SearchConstants.CATEGORY, tags) ;
        }else {
            tags = "";
        }
        //es内容搜索
        String query = String.format(SearchConstants.SEARCH_MAPPING, dto.getText(), dto.getText(), tags, dto.getPageNo(), dto.getPageSize());
        StringReader stringReader = new StringReader(query);
        SearchRequest searchRequest = SearchRequest.of(builder -> builder.index(SearchConstants.INDEX_NAME)
                .withJson(stringReader)
        );
        SearchResponse<Object> response = EsSearchUtil.search(searchRequest);
        List<ContentSearchVO> searchResult = EsSearchUtil.buildSearchResult(response);

        return searchResult;
    }

模板常量

public class SearchConstants {
    //索引库
    public static final String INDEX_NAME = "search_test";
    //类别模板
    public static final String CATEGORY =   "        ,\"term\": {\n" +
                                            "          \"category\": \"%s\"\n" +
                                            "        },\n";

    //搜索模板
    public static final String SEARCH_MAPPING = "{" +
            "  \"query\": {\n" +
            "    \"bool\": {\n" +
            "      \"must\":[ " +
            "        {\n" +
            "          \"match\": {\n" +
            "            \"title\": \"%s\"\n" +
            "            }\n" +
            "        },\n" +
            "        {\n" +
            "          \"match\": {\n" +
            "             \"content\": \"%s\"\n" +
            "             }\n" +
            "        }\n" +
                     "%s"+
            "     ]}\n" +
            "    },\n" +
            "    \"from\": %s,\n" +
            "    \"size\": %s,\n" +
            "  \"highlight\": {\n" +
            "    \"pre_tags\": \"<em>\",\n" +
            "    \"post_tags\": \"</em>\",\n" +
            "    \"fields\": {\n"+
            "      \"title\": {},\n" +
            "      \"content\": {}\n" +
            "      }\n" +
            "  }\n" +
            "}";

}

EsSearchUtil



@Component
@Slf4j
public class EsSearchUtil {

    private static ElasticsearchClient client;



    @Autowired
    public EsSearchUtil(ElasticsearchClient client) {
        EsSearchUtil.client = client;
    }

    /**
     * 数据检索
     */
    public static  SearchResponse<Object> search(SearchRequest searchRequest) {
        SearchResponse<Object> response;
        try {
            response = client.search(searchRequest, Object.class);
            log.debug("搜索返回结果:" + response.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new RuntimeException("搜索服务出了点小差,请稍后再试", e);
        }
        return response;
    }

    /**
     * 构建搜索结果
     */
    public static <T> List<T> buildSearchResult(SearchResponse<Object> resp, Class<T> clazz) {
        if (resp.hits() == null || resp.hits().hits() == null) {
            return Collections.emptyList();
        }
        List<Hit<Object>> hits = resp.hits().hits();

        List<T> list = new ArrayList<>();
        for(Hit<Object> hit:hits) {
            //格式转换
            T t = ConvertUtil.objToObj(hit.source(), clazz);
            list.add(t);
        }
        return list;
    }


    public  static  List<ContentSearchVO> buildSearchResult(SearchResponse<Object> resp){
        List<Hit<Object>> hits = resp.hits().hits();
        List<ContentSearchVO> list = new ArrayList<>();
        for(Hit<Object> hit:hits){
            ContentSearchVO contentSearchVO = ConvertUtil.objToObj(hit.source(), ContentSearchVO.class);
            Map<String, List<String>> highlight = hit.highlight();

            if (!CollectionUtils.isEmpty(highlight)) {
                //标题
                List<String> highLightTitle = highlight.get("title");
                //内容
                List<String> highLightContent = highlight.get("content");
                //无高亮,就用内容替代
                if (!CollectionUtils.isEmpty(highLightTitle)){
                    StringBuilder highLightTitleStringBuilder = new StringBuilder();
                    for (String titleSegment : highLightTitle) {
                        highLightTitleStringBuilder.append(titleSegment);
                    }
                    contentSearchVO .setTitle(highLightTitleStringBuilder.toString());
                }
                if (!CollectionUtils.isEmpty(highLightContent)){
                    StringBuilder highLightContentStringBuilder = new StringBuilder();
                    for (String contentSegment : highLightContent) {
                        highLightContentStringBuilder.append(contentSegment);
                    }
                    contentSearchVO .setHighLightContent(highLightContentStringBuilder.toString());
                }else {
                    contentSearchVO .setHighLightContent(contentSearchVO.getContent());
                }
            }
            list.add(contentSearchVO);
        }
        return list;
    }


    /**
     *  分词解析
     * @param text 搜索词
     * @param index 索引库
     * @return 分词结果
     */
    public static List<String> analyze (String text,String index) {
        AnalyzeRequest analyzeRequest = new AnalyzeRequest.Builder()
                .index(index)
                .text(text)
                .analyzer("ik_max_word")  // ik_smart
                .build();
        AnalyzeResponse analyzeResponse;
        try {
            analyzeResponse = client.indices().analyze(analyzeRequest);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<AnalyzeToken> tokens = analyzeResponse.tokens();
        List<String> result = new ArrayList<>();
        // 分词结果
        for (AnalyzeToken token : tokens) {
            result.add(token.token());
        }
        return result;
    }
}

2.2 基于搜索api实现




@Service
@Slf4j
@RequiredArgsConstructor
public class ContentSearchServiceImpl implements ContentSearchService {

    private final SearchHotWordsService  searchHotWordsService;

    @Value("${search.highlightWords:500}")
    private Integer highlightWords;

    @Value("${search.words:500}")
    private Integer words;

    @Value("${search.searchWords:200}")
    private Integer searchWords;

    @Override
    public SearchVO search(SearchDTO dto)  {
        SearchVO result = new SearchVO();
       
        //构建bool
        BoolQuery boolQuery =  this.buildBoolQuery(dto);
        //构建高亮字段
        Highlight highlight = this.buildHighlight();
        //构建查询
        SearchRequest searchRequest = SearchRequest.of(s -> s.index(SearchConstants.INDEX_NAME)
                .query(q -> q
                        .bool(boolQuery)
                )
                .from((dto.getPageNo() - 1) * dto.getPageSize())
                .size(dto.getPageSize())
                .highlight(highlight)
        );
        SearchResponse<SearchDocument> response = EsSearchUtil.search(searchRequest,SearchDocument.class);
        List<ContentSearchVO> searchResult = this.buildSearchResult(response);
        if (!CollectionUtils.isEmpty(searchResult)){
            searchResult = this.sortSearchResult(searchResult);
        }
        result.setList(searchResult);
        return result;
    }

 

    /**
     *  构建bool
     * @param dto 查询参数
     * @return BoolQuery
     */
    private BoolQuery buildBoolQuery(SearchDTO dto) {
        String tags = dto.getTags();
        //要查询的字段
        List<String> queryFields = List.of(SearchConstants.FILE_NAME, SearchConstants.CONVERTED_TEXT);
        //构建bool查询
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder().should(s -> s.multiMatch(mu -> mu.fields(queryFields).query(dto.getText())));
        if (tags != null){
            List<FieldValue> v = new ArrayList<>();
            String[] split = tags.split(",");
            for (String s : split) {
                v.add(FieldValue.of(s));
            }
            TermsQuery termsQuery = TermsQuery.of(t -> t.field(SearchConstants.TAGS).terms(tm -> tm.value(v)));
            boolBuilder.must(m -> m.terms(termsQuery));
        }
        return boolBuilder.build();
    }

    /**
     * 构建高亮字段
     * @return Highlight
     */
    private Highlight buildHighlight() {
        Map<String, HighlightField> map = new HashMap<>();
        map.put(SearchConstants.FILE_NAME, HighlightField.of(hf -> hf.preTags(SearchConstants.PRE_TAGS).postTags(SearchConstants.POST_TAGS)));
        map.put(SearchConstants.CONVERTED_TEXT, HighlightField.of(hf -> hf.preTags(SearchConstants.PRE_TAGS).postTags(SearchConstants.POST_TAGS)
                .numberOfFragments(1).fragmentSize(highlightWords)  //numberOfFragments(1),表示将字段分割为最多1个片段,并设置 fragmentSize(300),表示每个片段的大小为300个字符。
        ));

        return Highlight.of(
                h -> h.type(HighlighterType.Unified)
                        .order(HighlighterOrder.Score)
                        .fields(map)
        );

    }

    private List<ContentSearchVO> buildSearchResult(SearchResponse<SearchDocument> resp) {
        List<Hit<SearchDocument>> hits = resp.hits().hits();
        List<ContentSearchVO> list = new ArrayList<>();
        for(Hit<SearchDocument> hit:hits){
            SearchDocument searchDocument = JsonUtil.objToObj(hit.source(), SearchDocument.class);
            ContentSearchVO contentSearchVO =  this.searchAdapter(searchDocument);
            String content = contentSearchVO.getContent();
            Map<String, List<String>> highlight = hit.highlight();

            if (!CollectionUtils.isEmpty(highlight)) {
                //高亮标题
                List<String> highLightTitle = highlight.get(SearchConstants.FILE_NAME);
                //高亮内容
                List<String> highLightContent = highlight.get(SearchConstants.CONVERTED_TEXT);
                //标题
                if (!CollectionUtils.isEmpty(highLightTitle)){
                    StringBuilder highLightTitleStringBuilder = new StringBuilder();
                    for (String titleSegment : highLightTitle) {
                        highLightTitleStringBuilder.append(titleSegment);
                    }
                    contentSearchVO .setTitle(highLightTitleStringBuilder.toString());
                }
                //内容
                if (!CollectionUtils.isEmpty(highLightContent)){
                    StringBuilder highLightContentStringBuilder = new StringBuilder();
                    for (String titleSegment : highLightContent) {
                        highLightContentStringBuilder.append(titleSegment);
                    }
                     contentSearchVO .setContent(highlightContent);
                }else {
                    //无高亮字段---从头开始取一定数量的字
                    String s = this.replaceSymbol(content).replace(SearchConstants.LINE, "");
                    if (s.length() >= words){
                        s = s.substring(0,words);
                    }
                    contentSearchVO .setContent(s);
                }
            }else {
                //无高亮字段---从头开始取一定数量的字
                String s = this.replaceSymbol(content).replace(SearchConstants.LINE, "");
                if (s.length() >= words){
                    s = s.substring(0,words);
                }
                contentSearchVO .setContent(s);
            }
            list.add(contentSearchVO );
        }
        return list;

    }

    private ContentSearchVO searchAdapter(SearchDocument searchDocument) {
        ContentSearchVO contentSearchVO = new ContentSearchVO();
        contentSearchVO .setTitle(searchDocument.getFileName());
        contentSearchVO .setContent(searchDocument.getDocText());
        contentSearchVO .setFileId(searchDocument.getFileId());
        contentSearchVO .setTags(searchDocument.getTags());
        contentSearchVO .setTimestamp(searchDocument.getTimestamp());
        return contentSearchVO ;
    }

   

    private String replaceSymbol(String replaceStr) {
        return replaceStr.replaceAll("\t","")
                .replaceAll(" "," ")
                .replaceAll(" "," ")
                .replaceAll("(\\n\\s*)+", " ") //多重换行只保留一个
                .replaceAll("\\s+"," ")
                .replaceAll("-\\d+-", "") //去掉 页码
                ;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2202707.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

精华帖分享 | 判定策略失效的新方法——统计假设检验

本文来源于量化小论坛策略分享会板块精华帖&#xff0c;作者为元亨利贞&#xff0c;发布于2023年12月25日。 以下为精华帖正文&#xff1a; 1、起因 去年刚入门B圈&#xff0c;由于之前有做商品期货择时的经验&#xff0c;通常来讲&#xff0c;趋势跟随&#xff0c;并且回测结…

谁说电商选品找货源没有捷径,只要你用对工具!

最近跟很多同行聊&#xff0c;都在抱怨选品难的问题&#xff0c;都说7分靠选品&#xff0c;3分靠运营&#xff0c;对于选品来说&#xff0c;并没有捷径可走&#xff0c;但其实是有很多不同的角度的。 现在市面上大部分开发做的选品&#xff0c;“选品方法”或“产品分析方法”…

SiLM266x系列SiLM2661高压电池组前端充/放电高边NFET驱动器 为电池系统保护提供可靠性和设计灵活性

SiLM2661产品概述&#xff1a; SiLM2661能够灵活的应对不同应用场景对锂电池进行监控和保护的需求&#xff0c;为电池系统保护提供可靠性和设计灵活性。是用于电池充电/放电系统控制的低功耗、高边 N 沟道 FET 驱动器&#xff0c;高边保护功能可避免系统的接地引脚断开连接&am…

Pycharm连接AutoDL服务器 文件上传 启动终端

Pycharm AutoDL 需要使用pycharm专业版&#xff08;学生可以通过教育邮箱认证&#xff0c;每年一次&#xff09;。 首先进入AutoDL官网&#xff1a;AutoDL-品质GPU租用平台-租GPU就上AutoDL进行学生注册登录&#xff08;可以领10元的代金券&#xff09;点击右上角的控制台&am…

京东web 京东e卡绑定 第二部分分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…

api测试和接口测试的区别

API测试和接口测试是软件测试中一个非常重要的领域&#xff0c;尤其是在当前Web应用程序和移动应用程序的发展中。虽然它们都测试了Web服务的功能&#xff0c;但是二者在测试方法和测试实施方面存在很大的差异。本文将介绍API测试和接口测试之间的主要区别 API测试的主要关注点…

【WebGis开发 - Cesium】三维可视化项目教程---图层管理基础

目录 引言一、功能设计1. 主体功能2. 细节问题 二、代码实现1. 树形控件2. 全局状态准备3. 创建图层控制方法3.1 加载、卸载方法编写3.2 统一对外暴露入口3.3 提供图层类别的可拓展性3.1 完整代码 4. 效果展示5. hooks函数使用方法 三、总结 引言 本教程主要是围绕Cesium这一开…

华为云服务器公网ip访问不通解决

问题&#xff1a;用弹性公网IP登录超时&#xff0c;ping不通&#xff0c;但是VNC方式可以登陆成功 解决&#xff1a;执行ifconfig&#xff0c;看到eth0网卡没有获取到 ifconfig 执行dhclient自动获取下网卡 &#xff1a; dhclient 再次执行ifconfig&#xff1a;网卡已经获…

【AI论文精读3】RAG论文综述1-P1

AI知识点总结&#xff1a;【AI知识点】 AI论文精读、项目、思考&#xff1a;【AI修炼之路】 简介 论文中英文名 Retrieval-Augmented Generation for Large Language Models: A Survey 面向大型语言模型的检索增强生成&#xff1a;综述 论文地址 arxiv地址&#xff1a;http…

知识蒸馏介绍

一、知识蒸馏介绍 1.1 概念介绍 知识蒸馏&#xff08;knowledge distillation&#xff09;是模型压缩的一种常用的方法&#xff0c;不同于模型压缩中的剪枝和量化&#xff0c;知识蒸馏是通过构建一个轻量化的小模型&#xff0c;利用性能更好的大模型的监督信息&#xff0c;来…

项目经理是怎么慢慢废掉的?这些无意识行为可能会毁了你!

工作久了&#xff0c;每个人都或多或少会有一些无力感和疲惫感。如果没有调整过来&#xff0c;久而久之&#xff0c;会感觉自己好像废掉了&#xff0c;做什么事情都打不起精神。 如果你是项目经理&#xff0c;工作中有这样一些迹象&#xff0c;比如总是拖延时间、丧失自己的判…

【进程间通信(三)】【system V共享内存】

目录 1. 原理2. 编码通信2.1 创建共享内存2.2 shmat && shmdt && shmctl2.3 通信 3. 共享内存的特性3.1 共享内存的属性3.2 加入管道实现同步机制 前面的文章介绍了管道通信&#xff0c;其中包括匿名管道、命名管道。这篇文章介绍另一种进程间通信的方式 -----…

NVP的含义?如何理解其在AEM|FLUKE线缆认证测试中的意义?不同的NVP会出现怎样的结果?

在AEM|FLUKE铜缆认证测试中&#xff0c;有很多朋友对NVP设置有疑问&#xff0c;不知道应该怎么去设置它&#xff0c;并很好的应用它&#xff0c;那我们基于此&#xff0c;做一个简单的分析。 什么是NVP? NVP是Nominal Velocity of Propagation的缩写&#xff1f;简单直接译过…

Java基础-泛型机制

文章目录 为什么引入泛型泛型的基本使用泛型类泛型接口泛型方法泛型数组正确的数组声明使用场景如何理解Java中的泛型是伪泛型&#xff1f;泛型中类型擦除 泛型数组&#xff1a;如何正确的初始化泛型数组实例&#xff1f; 为什么引入泛型 引入泛型的意义在于&#xff1a; 适用…

KEYSIGHT B1500A 半导体器件参数分析仪

新利通 B1500A 半导体器件参数分析仪 ——一体化器件表征分析仪—— 简述 Keysight B1500A 半导体参数分析仪是一款一体化器件表征分析仪&#xff0c;能够测量 IV、CV、脉冲/动态 IV 等参数。 主机和插入式模块能够表征大多数电子器件、材料、半导体和有源/无源元器件。 B…

关于相机的一些零碎知识点

热成像&#xff0c;英文为Thermal Imaging&#xff0c;例如型号500T&#xff0c;其实指的就是热成像500分辨率。 相机的CMOS&#xff0c;英文为Complementary Metal Oxide Semiconductor&#xff0c;是数码相机的核心成像部件&#xff0c;是一种互补金属氧化物导体器件。 DPI…

PVC刻字膜高精度模切应用

PVC刻字膜是一种由聚氯乙烯&#xff08;PVC&#xff09;为主要成分制成的薄膜材料&#xff0c;具有耐磨、耐刮、耐水、耐油以及良好的化学稳定性等特点。这种薄膜在多个行业中得到广泛应用&#xff0c;特别是在服装、鞋业、箱包、汽车内饰等领域&#xff0c;用于制作各种标识、…

NDC美国药品编码目录数据库查询方法

NDC&#xff08;National Drug Code&#xff09;翻译为“国家药品代码”&#xff0c;是美国食品药品监督管理局&#xff08;FDA&#xff09;制定的一种药品标识系统&#xff0c;用于唯一标识药品。这个编码系统主要目的是为精准识别和追踪不同药品而建设&#xff0c;行业人员和…

2024最新【Pycharm】史上最全PyCharm安装教程,图文教程(超详细)

1. PyCharm下载安装 完整安装包下载&#xff08;包含Python和Pycharm专业版注册码&#xff09;&#xff1a;点击这里 1&#xff09;访问官网 https://www.jetbrains.com/pycharm/download/#sectionwindows 下载「社区版 Community」 安装包。 2&#xff09;下载完成后&#…