瀏覽代碼

全局搜索 Lucene初始化索引

wucl 1 年之前
父節點
當前提交
88197cda69

+ 65 - 0
biz-base/src/main/java/com/dayou/controller/PersonalController.java

@@ -1,10 +1,28 @@
 package com.dayou.controller;
 
+import cn.hutool.core.util.StrUtil;
+import com.dayou.annotation.IgnoreAuth;
 import com.dayou.dto.TaskRecordDTO;
 import com.dayou.dto.WorkNodeCommit;
+import com.dayou.vo.LuceneSearchVO;
 import com.dayou.vo.PersonalVO;
 import com.dayou.vo.TaskTodoVO;
 import com.dayou.workflow.annotation.FinishTask;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
+import org.apache.lucene.queryparser.classic.ParseException;
+import org.apache.lucene.queryparser.classic.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.highlight.*;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,11 +38,17 @@ import org.springframework.web.bind.annotation.*;
 import com.dayou.utils.ConvertUtil;
 import com.dayou.utils.HttpKit;
 import com.dayou.exception.ErrorCode;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.file.FileSystems;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.http.MediaType;
 import org.springframework.web.multipart.MultipartFile;
+import org.wltea.analyzer.lucene.IKAnalyzer;
 
 import javax.validation.Valid;
 
@@ -157,5 +181,46 @@ public class PersonalController extends BaseController {
         Page<PersonalVO> pages=personalService.saveFileDone(page,personal);
         return RestResponse.data(pages);
     }
+
+    @IgnoreAuth
+    @GetMapping("/search/{keyword}")
+    public RestResponse<List<LuceneSearchVO>> searchText(@PathVariable("keyword") String keyword) throws IOException, ParseException, InvalidTokenOffsetsException {
+
+        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("G:\\luceneIndex"));
+        DirectoryReader reader = DirectoryReader.open(directory);
+        IndexSearcher indexSearcher = new IndexSearcher(reader);
+        QueryParser parser = new QueryParser ("result", new IKAnalyzer());
+        Query query = parser.parse(keyword);
+        TopDocs topDocs = indexSearcher.search(query, 15);
+
+        //加入高亮显示的
+        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
+        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
+        //高亮后的段落范围在100字内
+        Fragmenter fragmenter = new SimpleFragmenter(50);
+        highlighter.setTextFragmenter(fragmenter);
+
+
+        log.info("本次搜索共找到" + topDocs.totalHits + "条数据");
+        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+        List<LuceneSearchVO> results = new ArrayList<>();
+        for (ScoreDoc scoreDoc :scoreDocs){
+            LuceneSearchVO luceneSearchVO = new LuceneSearchVO();
+            // 取出文档编号
+            int docId = scoreDoc.doc;
+            Document doc = reader.document(docId);
+            luceneSearchVO.setId(doc.get("id"));
+            String result = doc.get("result");
+            if (StrUtil.isNotBlank(result)){
+                result = highlighter.getBestFragment(new IKAnalyzer(), "result", result);
+            }
+            luceneSearchVO.setResult(result);
+            luceneSearchVO.setBusinessEnum(doc.get("businessEnum"));
+            luceneSearchVO.setMenuName(doc.get("menuName"));
+            luceneSearchVO.setUrl(doc.get("url"));
+            results.add(luceneSearchVO);
+        }
+        return RestResponse.data(results);
+    }
 }
 

+ 4 - 3
biz-base/src/main/resources/application-local.yml

@@ -19,11 +19,12 @@ spring:
 
 
 dfs:
-  path: E:\local-upload
-  domain: E:\local-upload
+  path: /opt/dfs
+  domain: /dfs
   domainName: https://kps.scdayou.com/dfs
-  code: E:\local-upload\code
+  code: /code
   domainRoot: localhost
+  luceneDir: G:\luceneIndex
 
 advice:
   file:

+ 161 - 0
biz-base/src/test/java/lucene/LuceneTest.java

@@ -0,0 +1,161 @@
+package lucene;
+
+import com.dayou.BaseApplication;
+import com.dayou.annotation.LuceneResource;
+import com.dayou.annotation.LuceneSearchable;
+import com.dayou.entity.Major;
+import com.dayou.entity.Personal;
+import com.dayou.enums.MainBusinessEnum;
+import com.dayou.service.ILuceneSearchService;
+import com.dayou.service.IMajorService;
+import com.dayou.service.IPersonalService;
+import com.dayou.utils.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.ClassUtils;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.util.*;
+
+/**
+ * 类说明:
+ *
+ * @author: wucl
+ * @since: 2024/4/25
+ * created with IntelliJ IDEA.
+ */
+@Slf4j
+@SpringBootTest(classes = BaseApplication.class)
+@RunWith(value = SpringJUnit4ClassRunner.class)
+public class LuceneTest {
+
+    @Autowired
+    private IPersonalService personalService;
+
+    @Autowired
+    private IMajorService majorService;
+
+    @Autowired
+    private ILuceneSearchService luceneSearchService;
+
+
+
+    @Test
+    public void createIndex() throws NoSuchFieldException {
+        List<Personal> list = personalService.list();
+        List<Major> majors = majorService.list();
+        Collection<Document> docs = new ArrayList<>();
+        java.lang.reflect.Field[] personalFields = Personal.class.getDeclaredFields();
+        java.lang.reflect.Field[] majorFields = Major.class.getDeclaredFields();
+        for (Personal personal : list) {
+            Object id = ReflectUtils.getFieldValue(personal, "id");
+            for (java.lang.reflect.Field field : personalFields) {
+                if (field.getAnnotation(LuceneSearchable.class) != null) {
+                    Document document = new Document();
+                    Object fieldValue = ReflectUtils.getFieldValue(personal, field.getName());
+                    if (fieldValue != null) {
+                        document.add(new StringField("id", String.valueOf(id), Field.Store.YES));
+                        document.add(new TextField("result", String.valueOf(fieldValue), Field.Store.YES));
+                        document.add(new TextField("businessEnum", MainBusinessEnum.PERSONAL_BUSINESS.getMsg(), Field.Store.YES));
+                        document.add(new TextField("menuName", "正在进行", Field.Store.YES));
+                        document.add(new TextField("url", "/personal/pending/list", Field.Store.YES));
+                        docs.add(document);
+                    }
+
+                }
+            }
+        }
+
+        for (Major major : majors) {
+            Object id = ReflectUtils.getFieldValue(major, "id");
+            for (java.lang.reflect.Field field : majorFields) {
+                if (field.getAnnotation(LuceneSearchable.class) != null) {
+                    Document document = new Document();
+                    Object fieldValue = ReflectUtils.getFieldValue(major, field.getName());
+                    if (fieldValue != null) {
+                        document.add(new StringField("id", String.valueOf(id), Field.Store.YES));
+                        document.add(new TextField("result", String.valueOf(fieldValue), Field.Store.YES));
+                        document.add(new TextField("businessEnum", MainBusinessEnum.MAJOR_BUSINESS.getMsg(), Field.Store.YES));
+                        document.add(new TextField("menuName", "正在进行", Field.Store.YES));
+                        document.add(new TextField("url", "/major/list", Field.Store.YES));
+                        docs.add(document);
+                    }
+
+                }
+            }
+
+
+        }
+        Analyzer analyzer = new IKAnalyzer();
+        IndexWriterConfig config = new IndexWriterConfig(analyzer);
+        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+
+
+        try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("G:\\luceneIndex"));
+             IndexWriter indexWriter = new IndexWriter(directory, config)) {
+            indexWriter.addDocuments(docs);
+            indexWriter.commit();
+        } catch (Exception e) {
+            log.error("create index error");
+        }
+    }
+
+    private final String BASE_PACKAGE = "com.dayou.entity";
+    private final String RESOURCE_PATTERN = "/**/*.class";
+
+    @Test
+    public void scaningEntity() {
+
+        Map<String, Class> handlerMap = new HashMap<String, Class>();
+
+        //spring工具类,可以获取指定路径下的全部类
+        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+        try {
+            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
+                    ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
+            Resource[] resources = resourcePatternResolver.getResources(pattern);
+            //MetadataReader 的工厂类
+            MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
+            for (Resource resource : resources) {
+                //用于读取类信息
+                MetadataReader reader = readerfactory.getMetadataReader(resource);
+                //扫描到的class
+                String classname = reader.getClassMetadata().getClassName();
+                Class<?> clazz = Class.forName(classname);
+                //判断是否有指定主解
+                LuceneResource anno = clazz.getAnnotation(LuceneResource.class);
+                if (anno != null) {
+                    //将注解中的类型值作为key,对应的类作为 value
+                    handlerMap.put(classname, clazz);
+                }
+            }
+        } catch (IOException | ClassNotFoundException e) {
+        }
+    }
+
+    @Test
+    public void testQuery(){
+        luceneSearchService.initializeIndex();
+    }
+}

+ 10 - 1
common/src/main/java/com/dayou/configuration/DfsConfig.java

@@ -30,7 +30,16 @@ public class DfsConfig
 
     private String domainRoot;
 
-    //生产环境建议用nginx绑定域名映射
+    private String luceneDir;
+
+    public String getLuceneDir() {
+        return luceneDir;
+    }
+
+    public void setLuceneDir(String luceneDir) {
+        this.luceneDir = luceneDir;
+    }
+//生产环境建议用nginx绑定域名映射
 
 
     public String getCode() {

+ 33 - 0
common/src/main/java/com/dayou/utils/MybatisPlusUtils.java

@@ -0,0 +1,33 @@
+package com.dayou.utils;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
+import org.apache.poi.ss.formula.functions.T;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * 类说明:
+ *
+ * @author: wucl
+ * @since: 2024/4/26
+ * created with IntelliJ IDEA.
+ */
+public class MybatisPlusUtils {
+
+    /**
+     * 只返回 指定字段
+     */
+    public static <T> LambdaQueryWrapper<T>  fieldValues(List<String> fieldValues, LambdaQueryWrapper<T> queryWrapper, Class<T> aClass) {
+            if (CollectionUtil.isNotEmpty(fieldValues)){
+                Predicate<TableFieldInfo> predicate = null;
+                for (String field : fieldValues) {
+                    predicate = predicate == null ? p -> p.getColumn().equals(field) : predicate.or(p -> p.getColumn().equals(field));
+                }
+                return queryWrapper.select(aClass, predicate);
+            }
+            return null;
+    }
+}

+ 20 - 0
common/src/main/java/com/dayou/utils/SpringContextHolder.java

@@ -99,4 +99,24 @@ public class SpringContextHolder implements ApplicationContextAware, DisposableB
         HttpServletRequest request = servletRequestAttributes.getRequest();
         return request;
     }
+
+    /**
+     * 通过bean的id获取bean对象
+     * @param beanName
+     * @return
+     */
+    public static Object getBean(String beanName){
+        return applicationContext.getBean(beanName);
+    }
+
+    /**
+     * 根据bean的id和类型获取bean对象
+     * @param beanName
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    public static <T> T getBean(String beanName,Class<T> clazz){
+        return clazz.cast(getBean(beanName));
+    }
 }

+ 8 - 4
dao/src/main/resources/mapper/PersonalMapper.xml

@@ -101,10 +101,14 @@
         <include refid="Common_query_sql" />
         WHERE
             wf.business_type = "PERSONAL_BUSINESS"
-          AND wf.state = 'PENDING'
-          AND wf.deleted = 0
-          AND wn.deleted = 0
-          AND p.deleted = 0
+            AND wf.state = 'PENDING'
+            AND wf.deleted = 0
+            AND wn.deleted = 0
+            AND p.deleted = 0
+            <if test="personal.id!=null">
+                AND p.id = #{personal.id}
+            </if>
+
     </select>
 
     <select id="getDetail" parameterType="java.lang.Long" resultType="com.dayou.vo.PersonalVO">

+ 32 - 0
domain/src/main/java/com/dayou/annotation/LuceneResource.java

@@ -0,0 +1,32 @@
+package com.dayou.annotation;
+
+import com.dayou.enums.MainBusinessEnum;
+
+import java.lang.annotation.*;
+
+/**
+ * 类说明:
+ *
+ * @author: wucl
+ * @since: 2024/4/26
+ * created with IntelliJ IDEA.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface LuceneResource {
+
+    MainBusinessEnum value() ;
+
+    /**
+     * 对应实体对象ServiceImpl层的BeanName
+     * @return
+     */
+    String serviceImplClassName();
+
+    String url () default "";
+
+    String menuName() default "";
+
+    String checkField() default "";
+}

+ 25 - 0
domain/src/main/java/com/dayou/annotation/LuceneSearchable.java

@@ -0,0 +1,25 @@
+package com.dayou.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 类说明:可文档搜索
+ *
+ * @author: wucl
+ * @since: 2024/4/25
+ * created with IntelliJ IDEA.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface LuceneSearchable {
+
+    String value() default "";
+
+    String url() default "";
+    String menuName() default "";
+
+
+}

+ 1 - 0
domain/src/main/java/com/dayou/common/BaseEntity.java

@@ -2,6 +2,7 @@ package com.dayou.common;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.dayou.annotation.LuceneSearchable;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 

+ 9 - 8
domain/src/main/java/com/dayou/entity/Major.java

@@ -1,19 +1,15 @@
 package com.dayou.entity;
 import java.math.BigDecimal;
-import java.util.Date;
 
 import com.baomidou.mybatisplus.annotation.FieldFill;
-import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
+import com.dayou.annotation.*;
 import com.dayou.common.BaseEntity;
-import com.dayou.dto.WorkNodeCommit;
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import com.dayou.annotation.ExcelSheet;
-import com.dayou.annotation.ExportCell;
-import com.dayou.annotation.ImportCell;
+
+import static com.dayou.enums.MainBusinessEnum.MAJOR_BUSINESS;
+
 /**
  * <p>
  * 大中型项目
@@ -25,6 +21,7 @@ import com.dayou.annotation.ImportCell;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ExcelSheet(sheetName = "大中型项目")
+@LuceneResource(value = MAJOR_BUSINESS,serviceImplClassName = "majorServiceImpl",url = "/major/list",menuName = "正在进行")
 public class Major extends BaseEntity {
 
     private static final long serialVersionUID=1L;
@@ -35,6 +32,7 @@ public class Major extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "项目名称")
+    @LuceneSearchable("name")
     private String name;
 
     /**
@@ -42,6 +40,7 @@ public class Major extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "项目编号")
+    @LuceneSearchable("order_id")
     private String orderId;
 
     /**
@@ -205,6 +204,7 @@ public class Major extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "委托人")
+    @LuceneSearchable("bailor")
     private String bailor;
 
     /**
@@ -233,6 +233,7 @@ public class Major extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "产权人")
+    @LuceneSearchable("owner")
     private String owner;
 
     /**

+ 11 - 3
domain/src/main/java/com/dayou/entity/Personal.java

@@ -3,13 +3,14 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Date;
 
+import com.dayou.annotation.*;
 import com.dayou.common.BaseEntity;
 import com.baomidou.mybatisplus.annotation.TableField;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import com.dayou.annotation.ExcelSheet;
-import com.dayou.annotation.ExportCell;
-import com.dayou.annotation.ImportCell;
+
+import static com.dayou.enums.MainBusinessEnum.PERSONAL_BUSINESS;
+
 /**
  * <p>
  * 个贷业务订单
@@ -21,6 +22,8 @@ import com.dayou.annotation.ImportCell;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ExcelSheet(sheetName = "个贷业务订单")
+@LuceneResource(value = PERSONAL_BUSINESS,serviceImplClassName = "personalServiceImpl",
+        url = "/personal/pending/list",menuName = "正在进行",checkField="ifSaveFile")
 public class Personal extends BaseEntity {
 
     private static final long serialVersionUID=1L;
@@ -30,6 +33,7 @@ public class Personal extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "订单编号")
+    @LuceneSearchable("order_id")
     private String orderId;
 
     /**
@@ -37,6 +41,7 @@ public class Personal extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "坐落")
+    @LuceneSearchable
     private String location;
 
     /**
@@ -123,6 +128,7 @@ public class Personal extends BaseEntity {
     @TableField("bailorA")
     @ImportCell
     @ExportCell(columnName = "委托人1")
+    @LuceneSearchable("bailorA")
     private String bailorA;
 
     /**
@@ -131,6 +137,7 @@ public class Personal extends BaseEntity {
     @TableField("bailorB")
     @ImportCell
     @ExportCell(columnName = "委托人2")
+    @LuceneSearchable("bailorB")
     private String bailorB;
 
     /**
@@ -212,6 +219,7 @@ public class Personal extends BaseEntity {
      */
     @ImportCell
     @ExportCell(columnName = "是否归档")
+    @LuceneSearchable(value="if_save_file",url = "/personal/saveFile",menuName = "已归档项目")
     private Boolean ifSaveFile;
 
     /**

+ 25 - 0
domain/src/main/java/com/dayou/vo/LuceneSearchVO.java

@@ -0,0 +1,25 @@
+package com.dayou.vo;
+
+import com.dayou.enums.MainBusinessEnum;
+import lombok.Data;
+
+/**
+ * 类说明:全局搜索页面VO
+ *
+ * @author: wucl
+ * @since: 2024/4/25
+ * created with IntelliJ IDEA.
+ */
+@Data
+public class LuceneSearchVO {
+
+    private String businessEnum;
+
+    private String id;
+
+    private String result;
+
+    private String menuName;
+
+    private String url;
+}

+ 33 - 4
service/pom.xml

@@ -44,15 +44,11 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
-
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
 
-
-
         <dependency>
             <groupId>com.fasterxml.jackson.datatype</groupId>
             <artifactId>jackson-datatype-jsr310</artifactId>
@@ -79,6 +75,39 @@
             <artifactId>fr.opensagres.poi.xwpf.converter.pdf-gae</artifactId>
             <version>2.0.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-core</artifactId>
+            <version>7.6.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-analyzers-common</artifactId>
+            <version>7.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-queryparser</artifactId>
+            <version>7.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-analyzers-smartcn</artifactId>
+            <version>7.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.lucene</groupId>
+            <artifactId>lucene-highlighter</artifactId>
+            <version>7.6.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.janeluo/ikanalyzer -->
+        <dependency>
+            <groupId>com.jianggujin</groupId>
+            <artifactId>IKAnalyzer-lucene</artifactId>
+            <version>8.0.0</version>
+        </dependency>
+
     </dependencies>
     <build>
         <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

+ 39 - 0
service/src/main/java/com/dayou/service/ILuceneSearchService.java

@@ -0,0 +1,39 @@
+package com.dayou.service;
+
+import com.dayou.vo.LuceneSearchVO;
+
+import java.util.List;
+
+/**
+ * 类说明:
+ *
+ * @author: wucl
+ * @since: 2024/4/26
+ * created with IntelliJ IDEA.
+ */
+public interface ILuceneSearchService {
+
+    /**
+     * 初始化Lucene索引
+     */
+    void initializeIndex();
+
+    /**
+     * 搜索
+     * @param keyword
+     * @return
+     */
+    List<LuceneSearchVO> queryIndex(String keyword);
+
+    /**
+     * 添加索引
+     */
+    void addIndex();
+
+    /**
+     * 删除索引
+     */
+    void removeIndex();
+
+
+}

+ 198 - 0
service/src/main/java/com/dayou/service/impl/LuceneSearchServiceImpl.java

@@ -0,0 +1,198 @@
+package com.dayou.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.dayou.annotation.LuceneResource;
+import com.dayou.annotation.LuceneSearchable;
+import com.dayou.configuration.DfsConfig;
+import com.dayou.service.ILuceneSearchService;
+import com.dayou.utils.MybatisPlusUtils;
+import com.dayou.utils.ReflectUtils;
+import com.dayou.utils.SpringContextHolder;
+import com.dayou.vo.LuceneSearchVO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.poi.ss.formula.functions.T;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.ClassUtils;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.FileSystems;
+import java.util.*;
+
+/**
+ * 类说明:全局搜索服务
+ *
+ * @author: wucl
+ * @since: 2024/4/26
+ * created with IntelliJ IDEA.
+ */
+@Slf4j
+@Service
+public class LuceneSearchServiceImpl implements ILuceneSearchService {
+
+    private final String BASE_PACKAGE = "com.dayou.entity";
+
+    private final String RESOURCE_PATTERN = "/**/*.class";
+
+    @Autowired
+    private DfsConfig dfsConfig;
+
+
+
+    @Override
+    public void initializeIndex() {
+        Map<String, Class> luceneResourceEntity = scanLuceneEntity();
+        if (CollectionUtil.isNotEmpty(luceneResourceEntity)){
+            Collection<Document> docs = new ArrayList<>();
+            for(Map.Entry<String,Class> entry :luceneResourceEntity.entrySet()){
+                Class clazz = entry.getValue();
+                String beanName = entry.getKey();
+                ServiceImpl serviceBean = (ServiceImpl) SpringContextHolder.getBean(beanName);
+                Field[] fields = clazz.getDeclaredFields();
+                List<String> searchAbleFields = new ArrayList<>();
+                for (Field field :fields){
+                    LuceneSearchable annotation = field.getAnnotation(LuceneSearchable.class);
+                    if (annotation!=null){
+                        String value = annotation.value();
+                        if (StrUtil.isBlank(value)){
+                            value = field.getName();
+                        }
+                        searchAbleFields.add(value);
+                    }
+                }
+                LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
+                LambdaQueryWrapper wrapper = MybatisPlusUtils.fieldValues(searchAbleFields, queryWrapper, clazz);
+                if (wrapper!=null){
+                    List list = serviceBean.list(wrapper);
+                    try {
+                        doCreatedLuceneIndex(list,docs);
+                    } catch (NoSuchFieldException e) {
+                        log.error("!!!create index field error");
+                    }
+                }
+
+            }
+        }
+    }
+
+    @Override
+    public List<LuceneSearchVO> queryIndex(String keyword) {
+        return null;
+    }
+
+    @Override
+    public void addIndex() {
+
+    }
+
+    @Override
+    public void removeIndex() {
+
+    }
+
+
+    private Map<String, Class> scanLuceneEntity(){
+        Map<String, Class> luceneResourceEntity = new HashMap<>();
+        //spring工具类,可以获取指定路径下的全部类
+        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+        try {
+            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
+                    ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
+            Resource[] resources = resourcePatternResolver.getResources(pattern);
+            //MetadataReader 的工厂类
+            MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
+            for (Resource resource : resources) {
+                //用于读取类信息
+                MetadataReader reader = readerfactory.getMetadataReader(resource);
+                //扫描到的class
+                String classname = reader.getClassMetadata().getClassName();
+                Class<?> clazz = Class.forName(classname);
+                //判断是否有指定主解
+                LuceneResource anno = clazz.getAnnotation(LuceneResource.class);
+                if (anno != null) {
+                    //将注解中的类型值作为key,对应的类作为 value
+                    String serviceImplClassName = anno.serviceImplClassName();
+                    luceneResourceEntity.put(serviceImplClassName, clazz);
+                }
+            }
+        } catch (IOException | ClassNotFoundException e) {
+        }
+        return luceneResourceEntity;
+    }
+
+    private void doCreatedLuceneIndex(List<Object> luceneData, Collection<Document> docs ) throws NoSuchFieldException {
+        Class aClass = luceneData.get(0).getClass();
+        LuceneResource annotation = (LuceneResource) aClass.getAnnotation(LuceneResource.class);
+        String businessType = annotation.value().getMsg();
+        String menuName = annotation.menuName();
+        String url = annotation.url();
+        String checkField = annotation.checkField();
+        Field[] declaredFields = aClass.getDeclaredFields();
+        for (Object t : luceneData) {
+            Object id = ReflectUtils.getFieldValue(t, "id");
+            String xUrl = url;
+            String xMenuName = menuName;
+            Boolean checkValue = false;
+            if (StrUtil.isNotBlank(checkField)){
+                checkValue = ReflectUtils.getFieldValue(t, checkField);
+                if (checkValue){
+                    LuceneSearchable declaredField = aClass.getDeclaredField(checkField).getAnnotation(LuceneSearchable.class);
+                     xUrl = declaredField.url();
+                     xMenuName = declaredField.menuName();
+                }
+
+            }
+            for (Field field : declaredFields) {
+                LuceneSearchable anno = field.getAnnotation(LuceneSearchable.class);
+                if (anno != null) {
+                    Object fieldValue = ReflectUtils.getFieldValue(t, field.getName());
+                    if (fieldValue != null) {
+                        Document document = new Document();
+                        document.add(new StringField("id", String.valueOf(id), org.apache.lucene.document.Field.Store.YES));
+                        document.add(new TextField("result", String.valueOf(fieldValue), org.apache.lucene.document.Field.Store.YES));
+                        document.add(new TextField("businessEnum", businessType, org.apache.lucene.document.Field.Store.YES));
+                        if (checkValue){
+                            document.add(new TextField("menuName", xMenuName, org.apache.lucene.document.Field.Store.YES));
+                            document.add(new TextField("url", xUrl, org.apache.lucene.document.Field.Store.YES));
+                        }else{
+                            document.add(new TextField("menuName", menuName, org.apache.lucene.document.Field.Store.YES));
+                            document.add(new TextField("url", url, org.apache.lucene.document.Field.Store.YES));
+                        }
+                        docs.add(document);
+                    }
+                }
+            }
+        }
+
+        Analyzer analyzer = new IKAnalyzer();
+        IndexWriterConfig config = new IndexWriterConfig(analyzer);
+        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+
+        try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(dfsConfig.getLuceneDir()));
+             IndexWriter indexWriter = new IndexWriter(directory, config)) {
+            indexWriter.addDocuments(docs);
+            indexWriter.commit();
+        } catch (Exception e) {
+            log.error("!!!create index error");
+        }
+    }
+}