当前位置:

首页 > 编程开发 > springboot中集成ES怎么实现磁盘文件全文检索功能

springboot中集成ES怎么实现磁盘文件全文检索功能

整体架构考虑到磁盘文件分布到不同的设备上,所以采用磁盘扫瞄代理的模式构建系统,即把扫描服务以代理的方式部署到目标磁盘所在的服务器上,作为定时任务执行,索引统一建立到ES中,当然ES采用分布式高可用部署方法,搜索服务和扫描代理部署到一起来简化架构并实现分布式能力。磁盘文件快速检索架构部署ESES(elasticsearch)是本项目唯一依赖的第三方软件,ES支持docker方式部署,以下是部署过程dockerpulldocker.elastic.co/elasticsearch/elasticsearch:

本文目录

    整体架构

    考虑到磁盘文件分布到不同的设备上,所以采用磁盘扫瞄代理的模式构建系统,即把扫描服务以代理的方式部署到目标磁盘所在的服务器上,作为定时任务执行,索引统一建立到ES中,当然ES采用分布式高可用部署方法,搜索服务和扫描代理部署到一起来简化架构并实现分布式能力。

    springboot中集成ES怎么实现磁盘文件全文检索功能

    磁盘文件快速检索架构

    部署ES

    ES(elasticsearch)是本项目唯一依赖的第三方软件,ES支持docker方式部署,以下是部署过程

    docker pull docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name es01 docker.elastic.co/elasticsearch/elasticsearch:6.3.2

    部署完成后,通过浏览器打开http://localhost:9200,如果正常打开,出现如下界面,则说明ES部署成功。

    springboot中集成ES怎么实现磁盘文件全文检索功能

    ES界面

    工程结构

    springboot中集成ES怎么实现磁盘文件全文检索功能

    工程结构

    依赖包

    本项目除了引入springboot的基础starter外,还需要引入ES相关包

      
        
          org.springframework.boot
          spring-boot-starter-data-elasticsearch
        
        
          io.searchbox
          jest
          5.3.3
        
        
          net.sf.jmimemagic
          jmimemagic
          0.1.4
        
      

    配置文件

    需要将ES的访问地址配置到application.yml里边,同时为了简化程序,需要将待扫描磁盘的根目录(index-root)配置进去,后面的扫描任务就会递归遍历该目录下的全部可索引文件。

    server:
     port: @elasticsearch.port@
    spring:
     application:
      name: @project.artifactId@
     profiles:
      active: dev
     elasticsearch:
      jest:
       uris: http://127.0.0.1:9200
    index-root: /Users/crazyicelee/mywokerspace

    索引结构数据定义

    因为要求文件所在目录、文件名、文件正文都有能够检索,所以要将这些内容都作为索引字段定义,而且添加ES client要求的JestId来注解id。

    package com.crazyice.lee.accumulation.search.data;
    
    import io.searchbox.annotations.JestId;
    import lombok.Data;
    
    @Data
    public class Article {
      @JestId
      private Integer id;
      private String author;
      private String title;
      private String path;
      private String content;
      private String fileFingerprint;
    }

    扫描磁盘并创建索引

    因为要扫描指定目录下的全部文件,所以采用递归的方法遍历该目录,并标识已经处理的文件以提升效率,在文件类型识别方面采用两种方式可供选择,一个是文件内容更为精准判断(Magic),一种是以文件扩展名粗略判断。这部分是整个系统的核心组件。

    这里有个小技巧

    对目标文件内容计算MD5值并作为文件指纹存储到ES的索引字段里边,每次在重建索引的时候判断该MD5是否存在,如果存在就不用重复建立索引了,可以避免文件索引重复,也能避免系统重启后重复遍历文件。

    package com.crazyice.lee.accumulation.search.service;
    
    import com.alibaba.fastjson.JSONObject;
    import com.crazyice.lee.accumulation.search.data.Article;
    import com.crazyice.lee.accumulation.search.utils.Md5CaculateUtil;
    import io.searchbox.client.JestClient;
    import io.searchbox.core.Index;
    import io.searchbox.core.Search;
    import io.searchbox.core.SearchResult;
    import lombok.extern.slf4j.Slf4j;
    import net.sf.jmimemagic.*;
    import org.apache.poi.hwpf.extractor.WordExtractor;
    import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
    import org.apache.poi.xwpf.usermodel.XWPFDocument;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    @Component
    @Slf4j
    public class DirectoryRecurse {
    
      @Autowired
      private JestClient jestClient;
    
      //读取文件内容转换为字符串
      private String readToString(File file, String fileType) {
        StringBuffer result = new StringBuffer();
        switch (fileType) {
          case "text/plain":
          case "java":
          case "c":
          case "cpp":
          case "txt":
            try (FileInputStream in = new FileInputStream(file)) {
              Long filelength = file.length();
              byte[] filecontent = new byte[filelength.intValue()];
              in.read(filecontent);
              result.append(new String(filecontent, "utf8"));
            } catch (FileNotFoundException e) {
              log.error("{}", e.getLocalizedMessage());
            } catch (IOException e) {
              log.error("{}", e.getLocalizedMessage());
            }
            break;
          case "doc":
            //使用HWPF组件中WordExtractor类从Word文档中提取文本或段落
            try (FileInputStream in = new FileInputStream(file)) {
              WordExtractor extractor = new WordExtractor(in);
              result.append(extractor.getText());
            } catch (Exception e) {
              log.error("{}", e.getLocalizedMessage());
            }
            break;
          case "docx":
            try (FileInputStream in = new FileInputStream(file); XWPFDocument doc = new XWPFDocument(in)) {
              XWPFWordExtractor extractor = new XWPFWordExtractor(doc);
              result.append(extractor.getText());
            } catch (Exception e) {
              log.error("{}", e.getLocalizedMessage());
            }
            break;
        }
        return result.toString();
      }
    
      //判断是否已经索引
      private JSONObject isIndex(File file) {
        JSONObject result = new JSONObject();
        //用MD5生成文件指纹,搜索该指纹是否已经索引
        String fileFingerprint = Md5CaculateUtil.getMD5(file);
        result.put("fileFingerprint", fileFingerprint);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery("fileFingerprint", fileFingerprint));
        Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex("diskfile").addType("files").build();
        try {
          //执行
          SearchResult searchResult = jestClient.execute(search);
          if (searchResult.getTotal() > 0) {
            result.put("isIndex", true);
          } else {
            result.put("isIndex", false);
          }
        } catch (IOException e) {
          log.error("{}", e.getLocalizedMessage());
        }
        return result;
      }
    
      //对文件目录及内容创建索引
      private void createIndex(File file, String method) {
        //忽略掉临时文件,以~$起始的文件名
        if (file.getName().startsWith("~$")) return;
    
        String fileType = null;
        switch (method) {
          case "magic":
            Magic parser = new Magic();
            try {
              MagicMatch match = parser.getMagicMatch(file, false);
              fileType = match.getMimeType();
            } catch (MagicParseException e) {
              //log.error("{}",e.getLocalizedMessage());
            } catch (MagicMatchNotFoundException e) {
              //log.error("{}",e.getLocalizedMessage());
            } catch (MagicException e) {
              //log.error("{}",e.getLocalizedMessage());
            }
            break;
          case "ext":
            String filename = file.getName();
            String[] strArray = filename.split("\\.");
            int suffixIndex = strArray.length - 1;
            fileType = strArray[suffixIndex];
        }
    
        switch (fileType) {
          case "text/plain":
          case "java":
          case "c":
          case "cpp":
          case "txt":
          case "doc":
          case "docx":
            JSONObject isIndexResult = isIndex(file);
            log.info("文件名:{},文件类型:{},MD5:{},建立索引:{}", file.getPath(), fileType, isIndexResult.getString("fileFingerprint"), isIndexResult.getBoolean("isIndex"));
    
            if (isIndexResult.getBoolean("isIndex")) break;
            //1. 给ES中索引(保存)一个文档
            Article article = new Article();
            article.setTitle(file.getName());
            article.setAuthor(file.getParent());
            article.setPath(file.getPath());
            article.setContent(readToString(file, fileType));
            article.setFileFingerprint(isIndexResult.getString("fileFingerprint"));
            //2. 构建一个索引
            Index index = new Index.Builder(article).index("diskfile").type("files").build();
            try {
              //3. 执行
              if (!jestClient.execute(index).getId().isEmpty()) {
                log.info("构建索引成功!");
              }
            } catch (IOException e) {
              log.error("{}", e.getLocalizedMessage());
            }
            break;
        }
      }
    
      public void find(String pathName) throws IOException {
        //获取pathName的File对象
        File dirFile = new File(pathName);
    
        //判断该文件或目录是否存在,不存在时在控制台输出提醒
        if (!dirFile.exists()) {
          log.info("do not exit");
          return;
        }
    
        //判断如果不是一个目录,就判断是不是一个文件,时文件则输出文件路径
        if (!dirFile.isDirectory()) {
          if (dirFile.isFile()) {
            createIndex(dirFile, "ext");
          }
          return;
        }
    
        //获取此目录下的所有文件名与目录名
        String[] fileList = dirFile.list();
    
        for (int i = 0; i < fileList.length; i++) {
          //遍历文件目录
          String string = fileList[i];
          File file = new File(dirFile.getPath(), string);
          //如果是一个目录,输出目录名后,进行递归
          if (file.isDirectory()) {
            //递归
            find(file.getCanonicalPath());
          } else {
            createIndex(file, "ext");
          }
        }
      }
    }

    扫描任务

    这里采用定时任务的方式来扫描指定目录以实现动态增量创建索引。

    package com.crazyice.lee.accumulation.search.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    @Configuration
    @Component
    @Slf4j
    public class CreateIndexTask {
      @Autowired
      private DirectoryRecurse directoryRecurse;
    
      @Value("${index-root}")
      private String indexRoot;
    
      @Scheduled(cron = "* 0/5 * * * ?")
      private void addIndex(){
        try {
          directoryRecurse.find(indexRoot);
          directoryRecurse.writeIndexStatus();
        } catch (IOException e) {
          log.error("{}",e.getLocalizedMessage());
        }
      }
    }

    搜索服务

    这里以restFul的方式提供搜索服务,将关键字以高亮度模式提供给前端UI,浏览器端可以根据返回的JSON进行展示。

    package com.crazyice.lee.accumulation.search.web;
    
    import com.alibaba.fastjson.JSONObject;
    import com.crazyice.lee.accumulation.search.data.Article;
    import io.searchbox.client.JestClient;
    import io.searchbox.core.Search;
    import io.searchbox.core.SearchResult;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.lang.NonNull;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    @Slf4j
    public class Controller {
      @Autowired
      private JestClient jestClient;
    
      @RequestMapping(value = "/search/{keyword}",method = RequestMethod.GET)
      @ApiOperation(value = "全部字段搜索关键字",notes = "es验证")
      @ApiImplicitParams(
          @ApiImplicitParam(name = "keyword",value = "全文检索关键字",required = true,paramType = "path",dataType = "String")
      )
      public List search(@PathVariable String keyword){
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(keyword));
    
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //path属性高亮度
        HighlightBuilder.Field highlightPath = new HighlightBuilder.Field("path");
        highlightPath.highlighterType("unified");
        highlightBuilder.field(highlightPath);
        //title字段高亮度
        HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
        highlightTitle.highlighterType("unified");
        highlightBuilder.field(highlightTitle);
        //content字段高亮度
        HighlightBuilder.Field highlightContent = new HighlightBuilder.Field("content");
        highlightContent.highlighterType("unified");
        highlightBuilder.field(highlightContent);
    
        //高亮度配置生效
        searchSourceBuilder.highlighter(highlightBuilder);
    
        log.info("搜索条件{}",searchSourceBuilder.toString());
    
        //构建搜索功能
        Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex( "gf" ).addType( "news" ).build();
        try {
          //执行
          SearchResult result = jestClient.execute( search );
          return result.getHits(Article.class);
        } catch (IOException e) {
          log.error("{}",e.getLocalizedMessage());
        }
        return null;
      }
    }

    搜索restFul结果测试

    这里以swagger的方式进行API测试。其中keyword是全文检索中要搜索的关键字。

    springboot中集成ES怎么实现磁盘文件全文检索功能

    搜索结果

    使用thymeleaf生成UI

    集成thymeleaf的模板引擎直接将搜索结果以web方式呈现。模板包括主搜索页和搜索结果页,通过@Controller注解及Model对象实现。

    
      
        
          
            
            
          
        
                                                   更多       
                          
        
               document.querySelectorAll('.con-more').forEach(item => {         item.onclick = () => {         item.style.cssText = 'display: none';         item.parentNode.querySelector('.con-preview').style.cssText = 'max-height: none;';       }});         本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 es
    上一篇: mysql怎么删除从库
    下一篇: MySQL数据存储路径如何修改
    相关文章 更多
    如何使用PHP和ES6进行现代化Web开发
    如何使用PHP和ES6进行现代化Web开发

    随着Web应用程序的复杂性和用户需求的增加,现代化Web开发变得越来越重要。使用PHP和ES6可以为开发人员提供许多有用的工具,这些工具可以帮助他们更有效地编写、测试和维护现代化的Web应用程序。本文将介绍如何使用PHP和ES6进行现代化Web开发,包括最佳实践、工具和技巧。如果你是一名开发人员,而且想要提高你的Web开发技能,那么这篇文章将对你有所启发。一

    springBoot项目常用目录有哪些
    springBoot项目常用目录有哪些

    springBoot项目常用目录springBoot项目的目录结构及名规范介绍基于SpringBoot开发时的目录结构及命名规范,通过介绍能够为你解决,在实际项目中如何规划目录结构?如何命名目录更规范?各个目录分别代表什么含义?等三个问题。目录说明servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1|-servicex-common//模块2|-servicex-gateway//模块3|-serv

    解析SpringBoot和SpringMVC之间的异同
    解析SpringBoot和SpringMVC之间的异同

    解析SpringBoot和SpringMVC之间的异同SpringBoot和SpringMVC是Java领域中非常重要的开发框架。虽然它们都属于Spring框架的一部分,但是在使用和功能上有一些明显的区别。本文将对SpringBoot和SpringMVC进行比较,解析它们之间的异同。首先,让我们来了解一下SpringBoot。SpringBo

    探索SpringCloud和SpringBoot在微服务领域的不同应用方式
    探索SpringCloud和SpringBoot在微服务领域的不同应用方式

    近年来,随着云计算和分布式架构的兴起,微服务架构的应用越来越广泛。而SpringCloud和SpringBoot作为Java开发中的两个重要框架,对于微服务的实现起到了重要的作用。然而,很多人对于它们在微服务领域的不同应用方式还存在一定的疑惑。本文将从不同的角度探索SpringCloud和SpringBoot在微服务中的应用方式。首先,我们来了解一下Spri

    SpringBoot和SpringMVC有何区别?
    SpringBoot和SpringMVC有何区别?

    SpringBoot与SpringMVC的不同之处在哪里?SpringBoot和SpringMVC是两个非常流行的Java开发框架,用于构建Web应用程序。尽管它们经常分别被使用,但它们之间的不同之处也是很明显的。首先,SpringBoot可以被看作是一个Spring框架的扩展或者增强版。它旨在简化Spring应用程序的初始化和配置过程,以帮助开发人

    比较SpringBoot和SpringMVC的异同
    比较SpringBoot和SpringMVC的异同

    SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

    SpringBoot与SpringMVC的不同点是什么?
    SpringBoot与SpringMVC的不同点是什么?

    SpringBoot和SpringMVC是Java开发中常用的两个框架,它们都是由Spring框架所提供的,但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。一、SpringBoot的特点:简化配置:SpringBoot通过约定优于配置的原则,大大简化了项目的配置过程。它可以自动配置项目所需要的参数,开发人

    比较和选择指南:SpringCloud与SpringBoot的功能比较
    比较和选择指南:SpringCloud与SpringBoot的功能比较

    SpringCloud和SpringBoot是目前Java领域中最热门的开源框架,它们分别提供了一套完善的微服务架构和快速构建应用程序的解决方案。本文将对它们的功能进行比较,并给出选择指南,以帮助读者了解它们的优势和适用场景。SpringBoot是一个用于开发Java应用程序的框架,它提供了一个简化的开发流程,集成了大量常用的功能和组件,减少了开发者的工作量

    SpringCloud与SpringBoot:分析它们之间的相似与不同
    SpringCloud与SpringBoot:分析它们之间的相似与不同

    深入剖析SpringCloud和SpringBoot的异同引言:在构建企业级应用程序时,Spring框架已经成为开发者们的首选。它提供了一种简单而强大的方式来构建独立于平台的应用程序。Spring框架由两个核心模块组成:SpringBoot和SpringCloud。虽然它们都可以帮助开发者快速搭建应用程序,但它们的目标和用途存在一些差异。本文将深入剖析Spr

    从架构角度看SpringCloud与SpringBoot的差异
    从架构角度看SpringCloud与SpringBoot的差异

    从架构角度看SpringCloud与SpringBoot的差异引言:在当今的互联网时代,构建分布式系统已经成为了一种必要的需求。而SpringBoot和SpringCloud正是为了满足这个需求而诞生的。尽管它们都是由Spring框架所提供的解决方案,但从架构角度来看,它们存在着一些重要的差异。本文将从架构的角度出发,对SpringBoot和SpringCl

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。