Parcourir la source

search-service
es 太重, 使用 lucene 代替

reghao il y a 9 mois
Parent
commit
eeaa930a1f
21 fichiers modifiés avec 1088 ajouts et 444 suppressions
  1. 2 2
      search/search-service/src/main/java/cn/reghao/tnb/search/app/config/SpringLifecycle.java
  2. 10 17
      search/search-service/src/main/java/cn/reghao/tnb/search/app/controller/SearchController.java
  3. 17 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/db/repository/WenshuDocRepository.java
  4. 31 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/hibernate/HibernateLucene.java
  5. 75 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/hibernate/HibernateQuery.java
  6. 36 7
      search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneDocument.java
  7. 48 78
      search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneIndex.java
  8. 24 226
      search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneQuery.java
  9. 257 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneSearch.java
  10. 52 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/Wenshu.java
  11. 85 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuDoc.java
  12. 21 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuLegal.java
  13. 26 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuLucene.java
  14. 2 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/ElasticQuery.java
  15. 19 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/WenshuDocProjection.java
  16. 3 1
      search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/WenshuResult.java
  17. 2 2
      search/search-service/src/main/java/cn/reghao/tnb/search/app/rpc/DataSearchServiceImpl.java
  18. 89 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/service/WenshuSearchService.java
  19. 217 0
      search/search-service/src/main/java/cn/reghao/tnb/search/app/service/WenshuService.java
  20. 2 2
      search/search-service/src/test/java/SearchTest.java
  21. 70 109
      search/search-service/src/test/java/WenshuTest.java

+ 2 - 2
search/search-service/src/main/java/cn/reghao/tnb/search/app/config/SpringLifecycle.java

@@ -28,11 +28,11 @@ public class SpringLifecycle implements ApplicationRunner, DisposableBean {
         if (!dir.exists()) {
             dir.mkdirs();
         }
-        log.info("SearchService 启动...");
+        log.info("WenshuSearchService 启动...");
     }
 
     @Override
     public void destroy() {
-        log.info("SearchService 停止...");
+        log.info("WenshuSearchService 停止...");
     }
 }

+ 10 - 17
search/search-service/src/main/java/cn/reghao/tnb/search/app/controller/SearchController.java

@@ -2,17 +2,12 @@ package cn.reghao.tnb.search.app.controller;
 
 import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.web.WebResult;
-import cn.reghao.tnb.search.app.es.QueryService;
 import cn.reghao.tnb.search.app.model.po.Wenshu;
-import cn.reghao.tnb.search.app.model.vo.ElasticQuery;
 import cn.reghao.tnb.search.app.model.vo.WenshuResult;
-import org.springframework.data.domain.Page;
+import cn.reghao.tnb.search.app.service.WenshuSearchService;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * 裁判文书搜索接口
  *
@@ -22,18 +17,17 @@ import java.util.stream.Collectors;
 @RestController
 @RequestMapping("/api/search1/wenshu")
 public class SearchController {
-    private String indexName = "wenshu";
-    private int pageSize = 12;
-    private final QueryService<Wenshu> queryService;
+    private final WenshuSearchService wenshuSearchService;
 
-    public SearchController(QueryService<Wenshu> queryService) {
-        this.queryService = queryService;
+    public SearchController(WenshuSearchService wenshuSearchService) {
+        this.wenshuSearchService = wenshuSearchService;
     }
 
     @GetMapping(value = "/query", produces = MediaType.APPLICATION_JSON_VALUE)
     public String searchWenshu(@RequestParam(value = "pn") int pn,
                                @RequestParam(value = "keyword") String keyword) {
-        ElasticQuery elasticQuery = new ElasticQuery.Builder()
+        PageList<WenshuResult> pageList1 = wenshuSearchService.search(pn, keyword);
+        /*ElasticQuery elasticQuery = new ElasticQuery.Builder()
                 .pageSize(pageSize)
                 .pageNumber(pn)
                 .indexName(indexName)
@@ -41,20 +35,19 @@ public class SearchController {
                 .otherFiledNames(List.of("caseName", "cause", "parties"))
                 .queryString(keyword)
                 .build();
-
         Page<Wenshu> page = queryService.queryWithHighlight(elasticQuery, Wenshu.class);
-//        Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "createTime"));
         int pageNumber = page.getNumber();
         int pageSize = page.getSize();
         int total = (int) page.getTotalElements();
         List<WenshuResult> list = page.getContent().stream().map(WenshuResult::new).collect(Collectors.toList());
-        PageList<WenshuResult> pageList = PageList.pageList(pageNumber, pageSize, total, list);
-        return WebResult.success(pageList);
+        PageList<WenshuResult> pageList = PageList.pageList(pageNumber, pageSize, total, list);*/
+
+        return WebResult.success(pageList1);
     }
 
     @GetMapping(value = "/detail/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getWenshuDetail(@PathVariable("id") String id) {
-        Wenshu wenshu = queryService.queryById(indexName, id, Wenshu.class);
+        Wenshu wenshu = wenshuSearchService.queryById(id);
         return WebResult.success(wenshu);
     }
 }

+ 17 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/db/repository/WenshuDocRepository.java

@@ -0,0 +1,17 @@
+package cn.reghao.tnb.search.app.db.repository;
+
+import cn.reghao.tnb.search.app.model.po.WenshuDoc;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * @author reghao
+ * @date 2025-08-21 09:29:18
+ */
+public interface WenshuDocRepository extends JpaRepository<WenshuDoc, Integer>, JpaSpecificationExecutor<WenshuDoc> {
+    Page<WenshuDoc> findByRegion(String region, Pageable pageable);
+    Page<WenshuDoc> findByCourt(String court, Pageable pageable);
+    WenshuDoc findByWenshuId(long wenshuId);
+}

+ 31 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/hibernate/HibernateLucene.java

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.search.app.hibernate;
+
+import cn.reghao.tnb.search.app.model.po.WenshuDoc;
+import org.hibernate.search.mapper.orm.Search;
+import org.hibernate.search.mapper.orm.session.SearchSession;
+import org.springframework.stereotype.Service;
+
+import javax.persistence.EntityManagerFactory;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-04-02 09:49:21
+ */
+@Service
+public class HibernateLucene {
+    private final SearchSession searchSession;
+
+    public HibernateLucene(EntityManagerFactory entityManagerFactory) {
+        this.searchSession = Search.session(entityManagerFactory.createEntityManager());
+    }
+
+    public void createIndexes() {
+        List<Class<?>> classList = List.of(WenshuDoc.class);
+        searchSession.massIndexer(classList).start();
+    }
+
+    public void resetIndexes() {
+        createIndexes();
+    }
+}

+ 75 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/hibernate/HibernateQuery.java

@@ -0,0 +1,75 @@
+package cn.reghao.tnb.search.app.hibernate;
+
+import cn.reghao.tnb.search.app.db.repository.WenshuDocRepository;
+import cn.reghao.tnb.search.app.model.po.Wenshu;
+import cn.reghao.tnb.search.app.model.po.WenshuDoc;
+import cn.reghao.tnb.search.app.model.vo.WenshuDocProjection;
+import cn.reghao.tnb.search.app.model.vo.WenshuResult;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
+import org.hibernate.search.engine.search.query.SearchResult;
+import org.hibernate.search.mapper.orm.Search;
+import org.hibernate.search.mapper.orm.session.SearchSession;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+
+import javax.persistence.EntityManagerFactory;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2023-04-15 03:47:50
+ */
+@Slf4j
+@Service
+public class HibernateQuery {
+    private final SearchSession searchSession;
+    private final Analyzer analyzer;
+    private final SimpleHTMLFormatter formatter;
+    private WenshuDocRepository wenshuDocRepository;
+
+    public HibernateQuery(EntityManagerFactory entityManagerFactory, WenshuDocRepository wenshuDocRepository) {
+        this.searchSession = Search.session(entityManagerFactory.createEntityManager());
+        this.analyzer = new IKAnalyzer();
+        this.formatter = new SimpleHTMLFormatter("<span style='color:red;'>", "</span>");
+        this.wenshuDocRepository = wenshuDocRepository;
+    }
+
+    public Page<WenshuResult> search(String keyword, Pageable pageable) {
+        int pn = pageable.getPageNumber();
+        int ps = pageable.getPageSize();
+        SearchResult<WenshuDocProjection> highlightResult = searchSession.search(WenshuDoc.class)
+                .select(f -> f.composite()
+                        .from(f.field("wenshuId", String.class),
+                                f.highlight("caseName"))
+                        .as(WenshuDocProjection::new))
+                .where(f -> f.bool(b -> {
+                    b.must(f.matchAll())
+                            .should(f.match().field("caseName").matching(keyword).boost(3));
+                }))
+                .highlighter(f -> f.plain().tag("<span style='color:red;'>", "</span>"))
+                .fetch(pn*ps, ps);
+
+        long totalHitCount = highlightResult.total().hitCount();
+        List<WenshuDocProjection> list1 = highlightResult.hits();
+        List<WenshuResult> wenshuResultList = list1.stream().map(wenshuDocProjection -> {
+            String wenshuId = wenshuDocProjection.getWenshuId();
+            WenshuDoc wenshuDoc = wenshuDocRepository.findByWenshuId(Long.parseLong(wenshuId));
+            Wenshu wenshu = new Wenshu(wenshuDoc);
+            WenshuResult wenshuResult = new WenshuResult(wenshu);
+
+            List<String> highlightCaseName = wenshuDocProjection.getHighlightCaseName();
+            if (!highlightCaseName.isEmpty()) {
+                String caseName = highlightCaseName.get(0);
+                wenshuResult.setCaseName(caseName);
+            }
+            return wenshuResult;
+        }).collect(Collectors.toList());
+        return new PageImpl<>(wenshuResultList, pageable, totalHitCount);
+    }
+}

+ 36 - 7
search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneDocument.java

@@ -1,9 +1,9 @@
 package cn.reghao.tnb.search.app.lucene;
 
 import cn.reghao.tnb.search.app.model.po.VideoText;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.StringField;
-import org.apache.lucene.document.TextField;
+import cn.reghao.tnb.search.app.model.po.Wenshu;
+import cn.reghao.tnb.search.app.model.po.WenshuLucene;
+import org.apache.lucene.document.*;
 import org.springframework.stereotype.Service;
 
 import java.lang.reflect.Field;
@@ -48,13 +48,42 @@ public class LuceneDocument {
         return object;
     }
 
-    public Document getVideoTextDoc(VideoText videoText) {
+    public Document getDocumentByVideoText(VideoText videoText) {
         Document doc = new Document();
         doc.add(new StringField("id", videoText.getId(), org.apache.lucene.document.Field.Store.YES));
-        //doc.add(new StringField("videoId", videoText.getVideoId(), org.apache.lucene.document.Field.Store.YES));
         doc.add(new TextField("title", videoText.getTitle(), org.apache.lucene.document.Field.Store.YES));
-        doc.add(new TextField("description", videoText.getDescription(), org.apache.lucene.document.Field.Store.NO));
-        doc.add(new StringField("scope", ""+videoText.getScope(), org.apache.lucene.document.Field.Store.YES));
+        // 对 int 型字段索引(只索引不存储)
+        doc.add(new IntPoint("scope", videoText.getScope()));
+        return doc;
+    }
+
+    public Document getDocumentByWenshuLucene(WenshuLucene wenshuLucene) {
+        Document doc = new Document();
+        /*doc.add(new NumericDocValuesField("id", wenshuLucene.getId()));
+        doc.add(new StoredField("id", wenshuLucene.getId()));*/
+        doc.add(new StringField("id", wenshuLucene.getId(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new TextField("caseName", wenshuLucene.getCaseName(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new TextField("fullText", wenshuLucene.getFullText(), org.apache.lucene.document.Field.Store.YES));
+        return doc;
+    }
+
+    public Document getDocumentByWenshu(Wenshu wenshu) {
+        Document doc = new Document();
+        doc.add(new NumericDocValuesField("id", Long.parseLong(wenshu.getId())));
+        doc.add(new StoredField("id", wenshu.getId()));
+        doc.add(new StringField("caseId", wenshu.getCaseId(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new TextField("caseName", wenshu.getCaseName(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("court", wenshu.getCourt(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("region", wenshu.getRegion(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("caseType", wenshu.getCaseType(), org.apache.lucene.document.Field.Store.YES));
+        //doc.add(new StringField("caseTypeId", wenshu.getCaseTypeId(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("procedure", wenshu.getProcedure(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("judgmentDate", wenshu.getJudgmentDate(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("publicDate", wenshu.getPublicDate(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("parties", wenshu.getParties(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("cause", wenshu.getCause(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new StringField("legalBasis", wenshu.getLegalBasis(), org.apache.lucene.document.Field.Store.YES));
+        doc.add(new TextField("fullText", wenshu.getFullText(), org.apache.lucene.document.Field.Store.YES));
         return doc;
     }
 }

+ 48 - 78
search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneIndex.java

@@ -1,6 +1,5 @@
 package cn.reghao.tnb.search.app.lucene;
 
-import cn.reghao.tnb.search.app.model.po.VideoText;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.*;
@@ -21,75 +20,37 @@ import java.util.List;
  */
 @Service
 public class LuceneIndex {
-    private final LuceneDocument luceneDocument;
-    private final String indexDirPath;
-    private final Analyzer luceneAnalyzer = new IKAnalyzer();
-    private final Directory directory;
-    private final IndexWriter indexWriter;
-
-    public LuceneIndex(LuceneDocument luceneDocument) throws IOException {
-        this.luceneDocument = luceneDocument;
-        this.indexDirPath = "/opt/data/bntdata/jsearch";
-        this.directory = FSDirectory.open(Paths.get(indexDirPath));
-        this.indexWriter = getIndexWriter();
-    }
+    private String nativeLuceneDir = "/opt/data/search_data/native_lucene";
+    private String hibernateLuceneDir = "/opt/data/search_data/hibernate_lucene";
+    private final Analyzer luceneAnalyzer;
 
-    public IndexWriter getIndexWriter() throws IOException {
-        File indexDir = new File(indexDirPath);
-        Directory indexDirectory = FSDirectory.open(indexDir.toPath());
+    public LuceneIndex() {
+        this.luceneAnalyzer = new IKAnalyzer();
+    }
 
+    private IndexWriter getIndexWriter(String indexName) throws IOException {
+        String indexDir = String.format("%s/%s", nativeLuceneDir, indexName);
+        File dir = new File(indexDir);
+        Directory indexDirectory = FSDirectory.open(dir.toPath());
         IndexWriterConfig indexWriterConfig = new IndexWriterConfig(luceneAnalyzer);
         return new IndexWriter(indexDirectory, indexWriterConfig);
     }
 
-    private IndexReader getIndexReader() throws IOException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        IndexReader indexReader = DirectoryReader.open(directory);
-        return indexReader;
-    }
-
-    public void createVideoTextIndex(VideoText videoText) throws IOException {
-        Document document = luceneDocument.getVideoTextDoc(videoText);
-        indexWriter.addDocument(document);
-        indexWriter.commit();
-    }
-
-    public void updateIndex(String videoId, Document document) throws IOException {
-        if (document != null) {
-            // Lucene 没有提供更新接口, 更新操作实际是先删除后再添加
-            indexWriter.updateDocument(new Term("videoId", videoId), document);
-            indexWriter.commit();
-        }
-    }
-
-    public Document getDocument(String videoId) throws IOException {
-        IndexReader indexReader = DirectoryReader.open(directory);
-        Query query = new TermQuery(new Term("videoId", videoId));
-        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-        TopDocs topDocs = indexSearcher.search(query, 1);
-        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-        if (scoreDocs.length == 1) {
-            Document document = indexReader.document(scoreDocs[0].doc);
-            return document;
-        }
-
-        return null;
-    }
-
-    public void deleteIndex(String videoId) throws IOException {
-        indexWriter.deleteDocuments(new Term("videoId", videoId));
-        indexWriter.commit();
+    private IndexReader getIndexReader(String indexName) throws IOException {
+        String indexDir = String.format("%s/%s", nativeLuceneDir, indexName);
+        Directory directory = FSDirectory.open(Paths.get(indexDir));
+        return DirectoryReader.open(directory);
     }
 
-    public void createIndex(Document document) throws IOException {
-        IndexWriter indexWriter = getIndexWriter();
+    public void createIndex(String indexName, Document document) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
         indexWriter.addDocument(document);
         indexWriter.commit();
         indexWriter.close();
     }
 
-    public void createIndex(List<Document> list) throws IOException {
-        IndexWriter indexWriter = getIndexWriter();
+    public void createIndex(String indexName, List<Document> list) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
         list.forEach(doc -> {
             try {
                 indexWriter.addDocument(doc);
@@ -102,40 +63,48 @@ public class LuceneIndex {
         indexWriter.close();
     }
 
-    public void updateIndex(Document document) throws IOException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        IndexWriterConfig config = new IndexWriterConfig(luceneAnalyzer);
-        IndexWriter indexWriter = new IndexWriter(directory, config);
-
-        indexWriter.updateDocument(new Term("name", "李白"), document);
+    public void updateIndex(String indexName, Document document) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
+        // Lucene 没有提供更新接口, 更新操作实际是先删除后再添加
+        String fieldName = "id";
+        String fieldValue = document.get("id");
+        indexWriter.updateDocument(new Term(fieldName, fieldValue), document);
         indexWriter.commit();
         indexWriter.close();
     }
 
-    public void deleteIndex() throws IOException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        Analyzer analyzer = new IKAnalyzer();
-        IndexWriterConfig config = new IndexWriterConfig(analyzer);
-        IndexWriter indexWriter = new IndexWriter(directory, config);
+    public void addOrUpdateIndex(String indexName, Document document) throws IOException {
+        IndexReader indexReader = getIndexReader(indexName);
+        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
 
-        indexWriter.deleteDocuments(new Term("name", "李白"));
+        String id = document.get("id");
+        Query query = new TermQuery(new Term("id", id));
+        TopDocs topDocs = indexSearcher.search(query, 1);
+        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+        if (scoreDocs.length == 1) {
+            updateIndex(indexName, document);
+        } else {
+            createIndex(indexName, document);
+        }
+    }
+
+    public void deleteIndex(String indexName, String id) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
+        indexWriter.deleteDocuments(new Term("id", id));
         indexWriter.commit();
         indexWriter.close();
     }
 
-    public void deleteAllIndex() throws IOException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        Analyzer analyzer = new IKAnalyzer();
-        IndexWriterConfig config = new IndexWriterConfig(analyzer);
-        IndexWriter indexWriter = new IndexWriter(directory, config);
-
+    public void deleteAll(String indexName) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
         indexWriter.deleteAll();
         indexWriter.commit();
         indexWriter.close();
     }
 
-    public void check() throws IOException {
-        IndexReader indexReader = DirectoryReader.open(directory);
+    public void check(String indexName) throws IOException {
+        IndexReader indexReader = getIndexReader(indexName);
+
         // 总共的索引文档
         int maxDocs = indexReader.maxDoc();
         // 有效的索引文档
@@ -154,7 +123,8 @@ public class LuceneIndex {
      * @return
      * @date 2025-04-02 11:31:44
      */
-    public void forceDelete() throws IOException {
+    public void forceDelete(String indexName) throws IOException {
+        IndexWriter indexWriter = getIndexWriter(indexName);
         indexWriter.forceMergeDeletes();
     }
 }

+ 24 - 226
search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneQuery.java

@@ -1,12 +1,6 @@
 package cn.reghao.tnb.search.app.lucene;
 
-import cn.reghao.tnb.search.app.model.po.VideoText;
-import cn.reghao.tnb.search.app.model.vo.SearchResult;
-import cn.reghao.jutil.jdk.db.PageList;
-import cn.reghao.tnb.search.app.model.vo.VideoCard;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.LongPoint;
 import org.apache.lucene.index.DirectoryReader;
@@ -16,238 +10,19 @@ 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.*;
-import org.apache.lucene.search.highlight.Formatter;
-import org.apache.lucene.search.highlight.*;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.stereotype.Service;
 import org.wltea.analyzer.lucene.IKAnalyzer;
 
 import java.io.IOException;
 import java.nio.file.Paths;
-import java.util.*;
 
 /**
  * @author reghao
- * @date 2023-03-02 09:29:04
+ * @date 2025-08-20 17:32:59
  */
 @Slf4j
-@Service
 public class LuceneQuery {
-    private final static String indexDirPath = "/opt/data/bntdata/jsearch";
-    private final Analyzer luceneAnalyzer;
-    private final SimpleHTMLFormatter formatter;
-    private final Directory directory;
-
-    public LuceneQuery() throws IOException {
-        this.luceneAnalyzer = new IKAnalyzer();
-        this.formatter = new SimpleHTMLFormatter("<span style='color:red;'>", "</span>");
-        this.directory = FSDirectory.open(Paths.get(indexDirPath));
-    }
-
-    private IndexReader getIndexReader() throws IOException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        IndexReader indexReader = DirectoryReader.open(directory);
-        return indexReader;
-    }
-
-    public Page<VideoText> queryWithHighlight(String index, String queryString, Integer pn, Integer ps) {
-        try {
-            IndexReader indexReader = DirectoryReader.open(directory);
-            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-            String field = "title";
-            QueryParser queryParser = new QueryParser(field, luceneAnalyzer);
-            Query query = queryParser.parse(queryString);
-
-            long total;
-            TopDocs topDocs;
-            if (pn == 1) {
-                topDocs = indexSearcher.search(query, ps);
-                //topDocs = indexSearcher.searchAfter(null, query, pageSize);
-                total = topDocs.totalHits.value;
-            } else {
-                int count = (pn-1)*ps;
-                TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
-                total = prevTopDocs.totalHits.value;
-
-                ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
-                ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
-                topDocs = indexSearcher.searchAfter(after, query, ps);
-            }
-
-            List<VideoText> list = new ArrayList<>();
-            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-            QueryScorer queryScorer = new QueryScorer(query);
-            Highlighter highlighter = new Highlighter(formatter, queryScorer);
-            for (ScoreDoc scoreDoc : scoreDocs) {
-                Document document = indexReader.document(scoreDoc.doc);
-                String videoId = document.get("videoId");
-                String title = document.get("title");
-                String htmlTitle = highlighter.getBestFragment(luceneAnalyzer, field, title);
-                int scope = Integer.parseInt(document.get("scope"));
-                long publishTime = Long.parseLong(document.get("publishTime"));
-                list.add(new VideoText(videoId, htmlTitle, scope, publishTime));
-            }
-
-            PageRequest pageRequest = PageRequest.of(pn-1, ps);
-            return new PageImpl<>(list, pageRequest, total);
-        } catch (IOException | ParseException | InvalidTokenOffsetsException e) {
-            e.printStackTrace();
-        }
-
-        return Page.empty();
-    }
-
-    public Document getDocument(String videoId) {
-        try {
-            IndexReader indexReader = DirectoryReader.open(directory);
-            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-
-            Query query = new TermQuery(new Term("videoId", videoId));
-            TopDocs topDocs = indexSearcher.search(query, 1);
-            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-            if (scoreDocs.length == 1) {
-                Document document = indexReader.document(scoreDocs[0].doc);
-                return document;
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        return null;
-    }
-
-    public PageList<VideoCard> searchByTitle(String keyword, int pageSize, int pageNumber) {
-        try {
-            SearchResult searchResult = search(keyword, pageSize, pageNumber);
-            long total = searchResult.getTotal();
-            Map<String, String> result = searchResult.getResult();
-            Set<String> videoIds = result.keySet();
-            if (!videoIds.isEmpty()) {
-                /*List<VideoCard> list = redisHash.multiGet("video:card:hash", videoIds);
-                return PageList.pageList(pageNumber, pageSize, (int) total, list);*/
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        return PageList.empty();
-    }
-
-    public SearchResult search(String keyword, int pageSize, int pageNumber)
-            throws IOException, InvalidTokenOffsetsException, ParseException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        IndexReader indexReader = DirectoryReader.open(directory);
-        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-
-        String field = "title";
-        Analyzer analyzer = new StandardAnalyzer();
-        QueryParser queryParser = new QueryParser(field, analyzer);
-        Query query = queryParser.parse(keyword);
-        Query query1 = new TermQuery(new Term(field, keyword));
-
-        long total;
-        TopDocs topDocs;
-        if (pageNumber == 1) {
-            // topDocs = indexSearcher.search(query, pageSize);
-            topDocs = indexSearcher.searchAfter(null, query, pageSize);
-            total = topDocs.totalHits.value;
-        } else {
-            int count = (pageNumber-1)*pageSize;
-            TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
-            total = prevTopDocs.totalHits.value;
-
-            ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
-            ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
-            topDocs = indexSearcher.searchAfter(after, query, pageSize);
-        }
-
-        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-        Map<String, String> map = new HashMap<>();
-        Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
-        QueryScorer queryScorer = new QueryScorer(query);
-        Highlighter highlighter = new Highlighter(formatter, queryScorer);
-        for (ScoreDoc scoreDoc : scoreDocs) {
-            Document document = indexReader.document(scoreDoc.doc);
-            String videoId = document.get("videoId");
-            String title = document.get("title");
-            String htmlTitle = highlighter.getBestFragment(analyzer, field, title);
-            map.put(videoId, htmlTitle);
-        }
-        return new SearchResult(total, map);
-    }
-
-    public SearchResult highlighter(String keyword, int pageSize, int pageNumber)
-            throws IOException, InvalidTokenOffsetsException, ParseException {
-        Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-        IndexReader indexReader = DirectoryReader.open(directory);
-        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-
-        String field = "title";
-        Analyzer analyzer = new IKAnalyzer();
-
-        QueryParser queryParser = new QueryParser(field, analyzer);
-        Query query = queryParser.parse(keyword);
-        TermQuery termQuery = new TermQuery(new Term(field, keyword));
-        FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term(field, keyword), 1);
-        PhraseQuery.Builder builder = new PhraseQuery.Builder();
-        builder.add(new Term(field, keyword), 1);
-        PhraseQuery phraseQuery = builder.build();
-
-        long total;
-        TopDocs topDocs;
-        if (pageNumber == 1) {
-            topDocs = indexSearcher.search(termQuery, pageSize);
-            //topDocs = indexSearcher.searchAfter(null, query, pageSize);
-            total = topDocs.totalHits.value;
-        } else {
-            int count = (pageNumber-1)*pageSize;
-            TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
-            total = prevTopDocs.totalHits.value;
-
-            ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
-            ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
-            topDocs = indexSearcher.searchAfter(after, query, pageSize);
-        }
-
-        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-        Map<String, String> map = new HashMap<>();
-        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span style='color:red;'>", "</span>");
-        QueryScorer queryScorer = new QueryScorer(query);
-        Highlighter highlighter = new Highlighter(formatter, queryScorer);
-        for (ScoreDoc scoreDoc : scoreDocs) {
-            Document document = indexReader.document(scoreDoc.doc);
-            String videoId = document.get("videoId");
-            String title = document.get("title");
-            String htmlTitle = highlighter.getBestFragment(analyzer, field, title);
-            map.put(videoId, htmlTitle);
-        }
-        return new SearchResult(total, map);
-    }
-
-    static void query(Query query) {
-        try {
-            Directory directory = FSDirectory.open(Paths.get(indexDirPath));
-            IndexReader indexReader = DirectoryReader.open(directory);
-            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
-
-            // 查询前 100 条数据
-            TopDocs topDocs = indexSearcher.search(query, 100);
-            log.info("本次搜索共找到" + topDocs.totalHits.value + "条数据");
-            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
-            for (ScoreDoc scoreDoc : scoreDocs) {
-                Document document = indexReader.document(scoreDoc.doc);
-                log.info(document.toString());
-                //log.info("id={},name={},poems={},success={},score={}", document.get("id"), document.get("name"), document.get("poems"), document.get("success"), scoreDoc.score);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
     static void termQuery() {
         Query query = new TermQuery(new Term("name", "李白"));
         query(query);
@@ -321,4 +96,27 @@ public class LuceneQuery {
         query = multiFieldQueryParser.parse("李白和子美");
         query(query);
     }
+
+    public static void query(Query query) {
+        String nativeLuceneDir = "/opt/data/search_data/native_lucene";
+        String indexName = "wenshu_lucene";
+        try {
+            String indexDir = String.format("%s/%s", nativeLuceneDir, indexName);
+            Directory directory = FSDirectory.open(Paths.get(indexDir));
+            IndexReader indexReader = DirectoryReader.open(directory);
+            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+
+            // 查询前 100 条数据
+            TopDocs topDocs = indexSearcher.search(query, 100);
+            log.info("本次搜索共找到" + topDocs.totalHits.value + "条数据");
+            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+            for (ScoreDoc scoreDoc : scoreDocs) {
+                Document document = indexReader.document(scoreDoc.doc);
+                log.info(document.toString());
+                //log.info("id={},name={},poems={},success={},score={}", document.get("id"), document.get("name"), document.get("poems"), document.get("success"), scoreDoc.score);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
 }

+ 257 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/lucene/LuceneSearch.java

@@ -0,0 +1,257 @@
+package cn.reghao.tnb.search.app.lucene;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.search.app.model.vo.ElasticQuery;
+import cn.reghao.tnb.search.app.model.vo.SearchResult;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+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.*;
+import org.apache.lucene.search.highlight.*;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.springframework.stereotype.Service;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2023-03-02 09:29:04
+ */
+@Slf4j
+@Service
+public class LuceneSearch {
+    private String nativeLuceneDir = "/opt/data/search_data/native_lucene";
+    private Analyzer luceneAnalyzer;
+    private SimpleHTMLFormatter formatter;
+
+    public LuceneSearch() {
+        this.luceneAnalyzer = new IKAnalyzer();
+        this.formatter = new SimpleHTMLFormatter("<span style='color:red;'>", "</span>");
+    }
+
+    public IndexSearcher getIndexSearcher(String indexName) throws IOException {
+        String indexDir = String.format("%s/%s", nativeLuceneDir, indexName);
+        Directory directory = FSDirectory.open(Paths.get(indexDir));
+        IndexReader indexReader = DirectoryReader.open(directory);
+        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+        return indexSearcher;
+    }
+
+    public IndexReader getIndexReader(String indexName) throws IOException {
+        String indexDir = String.format("%s/%s", nativeLuceneDir, indexName);
+        Directory directory = FSDirectory.open(Paths.get(indexDir));
+        return DirectoryReader.open(directory);
+    }
+
+    public PageList queryWithHighlight(ElasticQuery elasticQuery, Class clazz) {
+        int pageSize = elasticQuery.getPageSize();
+        int pageNumber = elasticQuery.getPageNumber();
+        String indexName = elasticQuery.getIndexName();
+        String highlightFieldName = elasticQuery.getHighlightFieldName();
+        List<String> otherFiledNames = elasticQuery.getOtherFiledNames();
+        String queryString = elasticQuery.getQueryString();
+        try {
+            Set<String> queryFields = new HashSet<>(otherFiledNames);
+            queryFields.add(highlightFieldName);
+
+            Query query;
+            if (otherFiledNames.isEmpty()) {
+                // 单字段查询
+                QueryParser queryParser = new QueryParser(highlightFieldName, luceneAnalyzer);
+                query = queryParser.parse(queryString);
+            } else {
+                // 多字段查询
+                List<String> fieldList = new ArrayList<>(otherFiledNames);
+                fieldList.add(highlightFieldName);
+                String[] fields = fieldList.toArray(new String[0]);
+                MultiFieldQueryParser multiFieldQuery = new MultiFieldQueryParser(fields, luceneAnalyzer);
+                query = multiFieldQuery.parse(queryString);
+            }
+
+            IndexReader indexReader = getIndexReader(indexName);
+            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+            long total;
+            TopDocs topDocs;
+            if (pageNumber == 1) {
+                int count = pageSize;
+                topDocs = indexSearcher.search(query, count);
+                total = topDocs.totalHits.value;
+            } else {
+                int count = (pageNumber-1)*pageSize;
+                TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
+                total = prevTopDocs.totalHits.value;
+
+                ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
+                ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
+                topDocs = indexSearcher.searchAfter(after, query, pageSize);
+            }
+
+            QueryScorer queryScorer = new QueryScorer(query);
+            Highlighter highlighter = new Highlighter(formatter, queryScorer);
+
+            Map<String, Field> map = Arrays.stream(clazz.getDeclaredFields())
+                    .collect(Collectors.toMap(Field::getName, k -> k));
+            //List<T> list = new ArrayList<>();
+            List<Object> list = new ArrayList<>();
+            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+            for (ScoreDoc scoreDoc : scoreDocs) {
+                Document document = indexReader.document(scoreDoc.doc);
+
+                //T tObject = null;
+                Object object = null;
+                for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
+                    int len = constructor.getParameterTypes().length;
+                    if (len == 0) {
+                        object = constructor.newInstance();
+                    }
+                }
+
+                for (Map.Entry<String, Field> entry : map.entrySet()) {
+                    String fieldName = entry.getKey();
+                    Field field0 = clazz.getDeclaredField(fieldName);
+
+                    String fieldValue = document.get(fieldName);
+                    /*if (fieldName.equals(highlightFieldName)) {
+                        fieldValue = highlighter.getBestFragment(luceneAnalyzer, highlightFieldName, fieldValue);
+                    }*/
+                    if (queryFields.contains(fieldName)) {
+                        fieldValue = highlighter.getBestFragment(luceneAnalyzer, fieldName, fieldValue);
+                    }
+
+                    field0.setAccessible(true);
+                    Class<?> fieldType = field0.getType();
+                    if (fieldType.equals(String.class)) {
+                        field0.set(object, fieldValue);
+                    } else if (field0.getType().equals(int.class) || field0.getType().equals(Integer.class)) {
+                        field0.set(object, Integer.parseInt(fieldValue));
+                    } else if (field0.getType().equals(long.class) || field0.getType().equals(Long.class)) {
+                        field0.set(object, Long.parseLong(fieldValue));
+                    } else if (field0.getType().equals(double.class) || field0.getType().equals(Double.class)) {
+                        field0.set(object, Double.parseDouble(fieldValue));
+                    }
+                }
+                list.add(object);
+            }
+
+            return PageList.pageList(pageNumber, pageSize, (int) total, list);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return PageList.empty();
+    }
+
+    public SearchResult search(String indexName, String keyword, int pageNumber, int pageSize) throws Exception {
+        String field = "fullText";
+        QueryParser queryParser = new QueryParser(field, luceneAnalyzer);
+        Query query = queryParser.parse(keyword);
+        Query query1 = new TermQuery(new Term(field, keyword));
+
+        IndexReader indexReader = getIndexReader(indexName);
+        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+        long total;
+        TopDocs topDocs;
+        if (pageNumber == 1) {
+            // topDocs = indexSearcher.search(query, pageSize);
+            topDocs = indexSearcher.searchAfter(null, query, pageSize);
+            total = topDocs.totalHits.value;
+        } else {
+            int count = (pageNumber-1)*pageSize;
+            TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
+            total = prevTopDocs.totalHits.value;
+
+            ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
+            ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
+            topDocs = indexSearcher.searchAfter(after, query, pageSize);
+        }
+
+        QueryScorer queryScorer = new QueryScorer(query);
+        Highlighter highlighter = new Highlighter(formatter, queryScorer);
+
+        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+        Map<String, String> map = new HashMap<>();
+        for (ScoreDoc scoreDoc : scoreDocs) {
+            Document document = indexReader.document(scoreDoc.doc);
+            String id = document.get("id");
+            String caseName = document.get("caseName");
+            String fullText = document.get("fullText");
+            String caseNameHighlight = highlighter.getBestFragment(luceneAnalyzer, field, fullText);
+        }
+
+        return new SearchResult(total, map);
+    }
+
+    public SearchResult highlighter(String indexName, String keyword, int pageSize, int pageNumber)
+            throws IOException, InvalidTokenOffsetsException, ParseException {
+        String field = "title";
+        QueryParser queryParser = new QueryParser(field, luceneAnalyzer);
+        Query query = queryParser.parse(keyword);
+        TermQuery termQuery = new TermQuery(new Term(field, keyword));
+        FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term(field, keyword), 1);
+        PhraseQuery.Builder builder = new PhraseQuery.Builder();
+        builder.add(new Term(field, keyword), 1);
+        PhraseQuery phraseQuery = builder.build();
+
+        IndexReader indexReader = getIndexReader(indexName);
+        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+        long total;
+        TopDocs topDocs;
+        if (pageNumber == 1) {
+            topDocs = indexSearcher.search(termQuery, pageSize);
+            total = topDocs.totalHits.value;
+        } else {
+            int count = (pageNumber-1)*pageSize;
+            TopDocs prevTopDocs = indexSearcher.searchAfter(null, query, count);
+            total = prevTopDocs.totalHits.value;
+
+            ScoreDoc[] prevScoreDocs = prevTopDocs.scoreDocs;
+            ScoreDoc after = prevScoreDocs[prevScoreDocs.length-1];
+            topDocs = indexSearcher.searchAfter(after, query, pageSize);
+        }
+
+        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+        Map<String, String> map = new HashMap<>();
+        QueryScorer queryScorer = new QueryScorer(query);
+        Highlighter highlighter = new Highlighter(formatter, queryScorer);
+        for (ScoreDoc scoreDoc : scoreDocs) {
+            Document document = indexReader.document(scoreDoc.doc);
+            String videoId = document.get("videoId");
+            String title = document.get("title");
+            String htmlTitle = highlighter.getBestFragment(luceneAnalyzer, field, title);
+            map.put(videoId, htmlTitle);
+        }
+        return new SearchResult(total, map);
+    }
+
+    public Document findDocumentById(String indexName, String id) {
+        try {
+            IndexReader indexReader = getIndexReader(indexName);
+            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+
+            Query query = new TermQuery(new Term("id", id));
+            TopDocs topDocs = indexSearcher.search(query, 1);
+            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+            if (scoreDocs.length == 1) {
+                Document document = indexReader.document(scoreDocs[0].doc);
+                return document;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 52 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/Wenshu.java

@@ -1,12 +1,16 @@
 package cn.reghao.tnb.search.app.model.po;
 
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 
+import java.util.stream.Collectors;
+
 /**
  * @author reghao
  * @date 2025-03-18 09:50:13
  */
+@NoArgsConstructor
 @Setter
 @Getter
 public class Wenshu {
@@ -25,6 +29,8 @@ public class Wenshu {
     private String caseType;
     // 案件类型编码
     private String caseTypeId;
+    // 来源
+    private String source;
     // 审理程序
     private String procedure;
     // 裁判日期
@@ -39,4 +45,50 @@ public class Wenshu {
     private String legalBasis;
     // 全文
     private String fullText;
+
+    public Wenshu(WenshuDoc wenshuDoc, WenshuLucene wenshuLucene) {
+        this.id = wenshuDoc.getWenshuId() + "";
+        this.originalUrl = wenshuDoc.getOriginalUrl();
+        this.caseId = wenshuDoc.getCaseId();
+        this.caseName = wenshuLucene.getCaseName();
+        this.court = wenshuDoc.getCourt();
+        this.region = wenshuDoc.getRegion();
+        this.caseType = wenshuDoc.getCaseType();
+        this.caseTypeId = wenshuDoc.getCaseTypeId();
+        this.source = "";
+        this.procedure = wenshuDoc.getProcedures();
+        this.judgmentDate = wenshuDoc.getJudgmentDate();
+        this.publicDate = wenshuDoc.getPublicDate();
+        this.parties = wenshuDoc.getParties();
+        this.cause = wenshuDoc.getCause();
+        //this.legalBasis = wenshuDoc.getLegalBasis();
+        this.legalBasis = wenshuDoc.getLegalBasisList().stream()
+                .map(WenshuLegal::getLegalBasis)
+                .collect(Collectors.toList())
+                .toString().replace("[", "").replace("]", "");
+        this.fullText = wenshuLucene.getFullText();
+    }
+
+    public Wenshu(WenshuDoc wenshuDoc) {
+        this.id = wenshuDoc.getWenshuId() + "";
+        this.originalUrl = wenshuDoc.getOriginalUrl();
+        this.caseId = wenshuDoc.getCaseId();
+        this.caseName = wenshuDoc.getCaseName();
+        this.court = wenshuDoc.getCourt();
+        this.region = wenshuDoc.getRegion();
+        this.caseType = wenshuDoc.getCaseType();
+        this.caseTypeId = wenshuDoc.getCaseTypeId();
+        this.source = "";
+        this.procedure = wenshuDoc.getProcedures();
+        this.judgmentDate = wenshuDoc.getJudgmentDate();
+        this.publicDate = wenshuDoc.getPublicDate();
+        this.parties = wenshuDoc.getParties();
+        this.cause = wenshuDoc.getCause();
+        //this.legalBasis = wenshuDoc.getLegalBasis();
+        this.legalBasis = wenshuDoc.getLegalBasisList().stream()
+                .map(WenshuLegal::getLegalBasis)
+                .collect(Collectors.toList())
+                .toString().replace("[", "").replace("]", "");
+        this.fullText = "";
+    }
 }

+ 85 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuDoc.java

@@ -0,0 +1,85 @@
+package cn.reghao.tnb.search.app.model.po;
+
+import cn.reghao.tnb.search.app.util.BaseEntity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
+import org.hibernate.search.engine.backend.types.Highlightable;
+import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
+import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
+
+import javax.persistence.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-08-21 09:29:18
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+@Indexed(index = "wenshu_doc")
+@Entity
+@Table(name = "search_wenshu_doc")
+public class WenshuDoc extends BaseEntity {
+    @Column(unique = true)
+    private long wenshuId;
+    // 原始链接
+    private String originalUrl;
+    // 案号
+    private String caseId;
+    // 案件名称
+    @FullTextField(analyzer = "ikAnalyzer", highlightable = Highlightable.ANY)
+    private String caseName;
+    // 法院
+    private String court;
+    // 所属地区
+    private String region;
+    // 案件类型
+    private String caseType;
+    // 案件类型编码
+    private String caseTypeId;
+    // 审理程序
+    // procedure 触发 mysql 关键字
+    private String procedures;
+    // 裁判日期
+    private String judgmentDate;
+    // 发布日期
+    private String publicDate;
+    // 当事人
+    @Column(columnDefinition = "text")
+    private String parties;
+    // 案由
+    private String cause;
+    // 法律依据
+    private transient String legalBasis;
+    @ElementCollection(targetClass = WenshuLegal.class)
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @CollectionTable(name = "search_wenshu_legals")
+    private List<WenshuLegal> legalBasisList;
+
+    public WenshuDoc(long wenshuId, Wenshu wenshu) {
+        this.wenshuId = wenshuId;
+        this.originalUrl = wenshu.getOriginalUrl();
+        this.caseId = wenshu.getCaseId();
+        this.caseName = wenshu.getCaseName();
+        this.court = wenshu.getCourt();
+        this.region = wenshu.getRegion();
+        this.caseType = wenshu.getCaseType();
+        this.caseTypeId = wenshu.getCaseTypeId();
+        this.procedures = wenshu.getProcedure();
+        this.judgmentDate = wenshu.getJudgmentDate();
+        this.publicDate = wenshu.getJudgmentDate();
+        this.parties = wenshu.getParties();
+        this.cause = wenshu.getCause();
+        // this.legalBasis = wenshu.getLegalBasis();
+        this.legalBasisList = Arrays.stream(wenshu.getLegalBasis().split(";"))
+                .map(WenshuLegal::new)
+                .filter(wenshuLegal -> !wenshuLegal.getLegalBasis().isBlank())
+                .collect(Collectors.toList());
+    }
+}

+ 21 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuLegal.java

@@ -0,0 +1,21 @@
+package cn.reghao.tnb.search.app.model.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Embeddable;
+
+/**
+ * @author reghao
+ * @date 2025-08-21 14:17:28
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Setter
+@Getter
+@Embeddable
+public class WenshuLegal {
+    private String legalBasis;
+}

+ 26 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/po/WenshuLucene.java

@@ -0,0 +1,26 @@
+package cn.reghao.tnb.search.app.model.po;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2025-08-20 16:04:37
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class WenshuLucene {
+    private String id;
+    // 案件名称
+    private String caseName;
+    // 全文
+    private String fullText;
+
+    public WenshuLucene(String id, Wenshu wenshu) {
+        this.id = id;
+        this.caseName = wenshu.getCaseName();
+        this.fullText = wenshu.getFullText();
+    }
+}

+ 2 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/ElasticQuery.java

@@ -15,6 +15,8 @@ public class ElasticQuery {
     private int pageSize;
     private int pageNumber;
     private String indexName;
+    private List<String> queryFiledNames;
+    private List<String> highlightFiledNames;
     private String highlightFieldName;
     private List<String> otherFiledNames;
     private String queryString;

+ 19 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/WenshuDocProjection.java

@@ -0,0 +1,19 @@
+package cn.reghao.tnb.search.app.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-03-20 15:02:58
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+public class WenshuDocProjection {
+    private String wenshuId;
+    private List<String> highlightCaseName;
+}

+ 3 - 1
search/search-service/src/main/java/cn/reghao/tnb/search/app/model/vo/WenshuResult.java

@@ -2,6 +2,7 @@ package cn.reghao.tnb.search.app.model.vo;
 
 import cn.reghao.tnb.search.app.model.po.Wenshu;
 import lombok.Getter;
+import lombok.Setter;
 
 import java.util.Arrays;
 import java.util.List;
@@ -10,6 +11,7 @@ import java.util.List;
  * @author reghao
  * @date 2025-08-20 14:52:09
  */
+@Setter
 @Getter
 public class WenshuResult {
     private long id;
@@ -49,7 +51,7 @@ public class WenshuResult {
         this.court = wenshu.getCourt();
         this.region = wenshu.getRegion();
         this.caseType = wenshu.getCaseType();
-        this.caseTypeId = wenshu.getCaseTypeId();
+        //this.caseTypeId = wenshu.getCaseTypeId();
         this.procedure = wenshu.getProcedure();
         this.judgmentDate = wenshu.getJudgmentDate();
         this.publicDate = wenshu.getPublicDate();

+ 2 - 2
search/search-service/src/main/java/cn/reghao/tnb/search/app/rpc/DataSearchServiceImpl.java

@@ -6,7 +6,7 @@ import cn.reghao.tnb.search.app.es.ElasticService;
 import cn.reghao.tnb.search.app.es.VideoTextDocument;
 import cn.reghao.tnb.search.app.es.VideoTextQuery;
 import cn.reghao.tnb.search.app.lucene.LuceneIndex;
-import cn.reghao.tnb.search.app.lucene.LuceneQuery;
+import cn.reghao.tnb.search.app.lucene.LuceneSearch;
 import cn.reghao.tnb.search.app.model.po.VideoText;
 import cn.reghao.jutil.jdk.db.PageList;
 import lombok.extern.slf4j.Slf4j;
@@ -37,7 +37,7 @@ public class DataSearchServiceImpl implements DataSearchService {
     private final LuceneIndex luceneIndex;
     private final VideoTextQuery videoTextQuery;
 
-    public DataSearchServiceImpl(ElasticService elasticService, LuceneQuery luceneQuery,
+    public DataSearchServiceImpl(ElasticService elasticService, LuceneSearch luceneSearch,
                                  LuceneIndex luceneIndex, VideoTextQuery videoTextQuery) {
         this.videoTextDocument = new VideoTextDocument(elasticService);
         this.luceneIndex = luceneIndex;

+ 89 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/service/WenshuSearchService.java

@@ -0,0 +1,89 @@
+package cn.reghao.tnb.search.app.service;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.search.app.db.repository.WenshuDocRepository;
+import cn.reghao.tnb.search.app.es.QueryService;
+import cn.reghao.tnb.search.app.lucene.LuceneSearch;
+import cn.reghao.tnb.search.app.model.po.Wenshu;
+import cn.reghao.tnb.search.app.model.po.WenshuDoc;
+import cn.reghao.tnb.search.app.model.po.WenshuLucene;
+import cn.reghao.tnb.search.app.model.vo.ElasticQuery;
+import cn.reghao.tnb.search.app.model.vo.WenshuResult;
+import org.apache.lucene.document.Document;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-08-21 15:22:31
+ */
+@Service
+public class WenshuSearchService {
+    private int pageSize = 10;
+    private String indexName = "wenshu_lucene";
+    private LuceneSearch luceneSearch;
+    private WenshuDocRepository wenshuDocRepository;
+    private final QueryService<Wenshu> queryService;
+
+    public WenshuSearchService(LuceneSearch luceneSearch, WenshuDocRepository wenshuDocRepository,
+                               QueryService<Wenshu> queryService) {
+        this.luceneSearch = luceneSearch;
+        this.wenshuDocRepository = wenshuDocRepository;
+        this.queryService = queryService;
+    }
+
+    public PageList<WenshuResult> search(int pageNumber, String queryString) {
+        ElasticQuery elasticQuery = new ElasticQuery.Builder()
+                .pageSize(pageSize)
+                .pageNumber(pageNumber)
+                .indexName(indexName)
+                .highlightFieldName("caseName")
+                .otherFiledNames(List.of("fullText"))
+                .queryString(queryString)
+                .build();
+
+        PageList<WenshuLucene> pageList = luceneSearch.queryWithHighlight(elasticQuery, WenshuLucene.class);
+        long total = pageList.getTotalSize();
+        List<WenshuResult> wenshuResultList = new ArrayList<>();
+        for (WenshuLucene wenshuLucene : pageList.getList()) {
+            String wenshuId = wenshuLucene.getId();
+            WenshuDoc wenshuDoc = wenshuDocRepository.findByWenshuId(Long.parseLong(wenshuId));
+            Wenshu wenshu = new Wenshu(wenshuDoc, wenshuLucene);
+            WenshuResult wenshuResult = new WenshuResult(wenshu);
+            wenshuResultList.add(wenshuResult);
+        }
+        return PageList.pageList(pageNumber, pageSize, (int) total, wenshuResultList);
+    }
+
+    public PageList<WenshuResult> search1(int pageNumber, String queryString) {
+        ElasticQuery elasticQuery = new ElasticQuery.Builder()
+                .pageSize(pageSize)
+                .pageNumber(pageNumber)
+                .indexName(indexName)
+                .highlightFieldName("caseName")
+                .otherFiledNames(List.of("caseName", "cause", "parties"))
+                .queryString(queryString)
+                .build();
+        Page<Wenshu> page = queryService.queryWithHighlight(elasticQuery, Wenshu.class);
+        int pageSize = page.getSize();
+        int total = (int) page.getTotalElements();
+        List<WenshuResult> list = page.getContent().stream().map(WenshuResult::new).collect(Collectors.toList());
+        return PageList.pageList(pageNumber, pageSize, total, list);
+    }
+
+    public Wenshu queryById(String wenshuId) {
+        //queryService.queryById("wenshu", wenshuId, Wenshu.class);
+        long wenshuIdLong = Long.parseLong(wenshuId);
+        Document document = luceneSearch.findDocumentById("wenshu_lucene", wenshuId);
+        String fullText = document.get("fullText");
+
+        WenshuDoc wenshuDoc = wenshuDocRepository.findByWenshuId(wenshuIdLong);
+        Wenshu wenshu = new Wenshu(wenshuDoc);
+        wenshu.setFullText(fullText);
+        return wenshu;
+    }
+}

+ 217 - 0
search/search-service/src/main/java/cn/reghao/tnb/search/app/service/WenshuService.java

@@ -0,0 +1,217 @@
+package cn.reghao.tnb.search.app.service;
+
+import cn.reghao.jutil.tool.id.SnowFlake;
+import cn.reghao.tnb.search.app.db.repository.WenshuDocRepository;
+import cn.reghao.tnb.search.app.lucene.LuceneDocument;
+import cn.reghao.tnb.search.app.lucene.LuceneIndex;
+import cn.reghao.tnb.search.app.model.po.Wenshu;
+import cn.reghao.tnb.search.app.model.po.WenshuDoc;
+import cn.reghao.tnb.search.app.model.po.WenshuLucene;
+import cn.reghao.tnb.search.app.util.ClassUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.document.Document;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-08-20 17:45:35
+ */
+@Slf4j
+@Service
+public class WenshuService {
+    private String indexName = "wenshu_lucene";
+    private SnowFlake idGenerator = new SnowFlake(1, 1);;
+    private WenshuDocRepository wenshuDocRepository;
+    private LuceneIndex luceneIndex;
+    private LuceneDocument luceneDocument;
+
+    public WenshuService(WenshuDocRepository wenshuDocRepository, LuceneIndex luceneIndex, LuceneDocument luceneDocument) {
+        this.wenshuDocRepository = wenshuDocRepository;
+        this.luceneIndex = luceneIndex;
+        this.luceneDocument = luceneDocument;
+    }
+
+    public void processFile(String filePath) throws IOException {
+        // 10MiB
+        int bufSize = 1024*1024*10;
+        BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)), bufSize);
+
+        int count = 0;
+        List<Wenshu> list = new ArrayList<>();
+        // 忽略 CSV 文件的第一行
+        String line = in.readLine();
+        while((line = in.readLine()) != null) {
+            Wenshu wenshu = parseLineByWenshu(line);
+            if (wenshu != null) {
+                list.add(wenshu);
+                if (list.size() > 10_000) {
+                    //documentService.batchAddWenshu(indexName, list);
+                    addLuceneIndex(list);
+                    list.clear();
+                    count++;
+                }
+            }
+
+            if (count > 10) {
+                break;
+            }
+        }
+        in.close();
+    }
+
+    @AllArgsConstructor
+    @Getter
+    static class Tuple {
+        String str;
+        String line;
+    }
+
+    Tuple getField(String line) {
+        try {
+            String str = "";
+            if (line.startsWith(mark)) {
+                line = line.substring(1);
+                int idx = line.indexOf(mark);
+                str = line.substring(0, idx);
+                line = line.substring(idx+2);
+            } else {
+                int idx = line.indexOf(",");
+                str = line.substring(0, idx);
+                line = line.substring(idx+1);
+            }
+
+            return new Tuple(str, line);
+        } catch (Exception e) {
+            //e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    int errorCount = 0;
+    String mark = "\"";
+    private Wenshu parseLineByWenshu(String line) {
+        String line1 = line;
+        try {
+            Tuple tuple = getField(line1);
+            String str1 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str2 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str3 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str4 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str5 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str6 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str7 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str8 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str9 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str10 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str11 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str12 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str13 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            tuple = getField(line1);
+            String str14 = tuple.getStr();
+            line1 = tuple.getLine();
+
+            String str15 = line1;
+
+            List<String> fields = new ArrayList<>();
+            fields.add("110120119");
+            fields.add(str1);
+            fields.add(str2);
+            fields.add(str3);
+            fields.add(str4);
+            fields.add(str5);
+            fields.add(str6);
+            fields.add(str7);
+            fields.add(str8);
+            fields.add(str9);
+            fields.add(str10);
+            fields.add(str11);
+            fields.add(str12);
+            fields.add(str13);
+            fields.add(str14);
+            fields.add(str15);
+            Object object = ClassUtil.getObject(Wenshu.class, fields.toArray(new String[0]));
+            return (Wenshu) object;
+        } catch (Exception e) {
+            log.error("error line -> {}", ++errorCount);
+            // ignore
+        }
+        return null;
+    }
+
+    private void addLuceneIndex(List<Wenshu> wenshuList) {
+        List<WenshuDoc> wenshuDocList = new ArrayList<>();
+        List<WenshuLucene> wenshuLuceneList = new ArrayList<>();
+        for (Wenshu wenshu : wenshuList) {
+            long id = idGenerator.nextId();
+            WenshuDoc wenshuDoc = new WenshuDoc(id, wenshu);
+            WenshuLucene wenshuLucene = new WenshuLucene(""+id, wenshu);
+
+            wenshuDocList.add(wenshuDoc);
+            wenshuLuceneList.add(wenshuLucene);
+        }
+
+        wenshuDocRepository.saveAll(wenshuDocList);
+
+        List<Document> luceneDocumentList = wenshuLuceneList.stream()
+                .map(wenshuLucene -> luceneDocument.getDocumentByWenshuLucene(wenshuLucene))
+                .collect(Collectors.toList());
+        try {
+            luceneIndex.createIndex(indexName, luceneDocumentList);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        log.info("index {} documents...", wenshuList.size());
+    }
+
+    public void deleteAll() throws IOException {
+        wenshuDocRepository.deleteAll();
+        luceneIndex.deleteAll(indexName);
+    }
+}

+ 2 - 2
search/search-service/src/test/java/SearchTest.java

@@ -7,7 +7,7 @@ import cn.reghao.tnb.search.app.SearchApplication;
 import cn.reghao.tnb.search.app.es.*;
 import cn.reghao.tnb.search.app.lucene.LuceneDocument;
 import cn.reghao.tnb.search.app.lucene.LuceneIndex;
-import cn.reghao.tnb.search.app.lucene.LuceneQuery;
+import cn.reghao.tnb.search.app.lucene.LuceneSearch;
 import cn.reghao.tnb.search.app.model.po.VideoText;
 import co.elastic.clients.elasticsearch._types.mapping.Property;
 import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
@@ -64,7 +64,7 @@ public class SearchTest {
     }
 
     @Autowired
-    LuceneQuery luceneQuery;
+    LuceneSearch luceneSearch;
     @Autowired
     LuceneIndex luceneIndex;
     @Autowired

+ 70 - 109
search/search-service/src/test/java/WenshuTest.java

@@ -1,26 +1,25 @@
 import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.Logger;
 import ch.qos.logback.classic.LoggerContext;
+import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.tool.id.SnowFlake;
-import cn.reghao.tnb.content.api.constant.PostScope;
 import cn.reghao.tnb.search.app.SearchApplication;
 import cn.reghao.tnb.search.app.es.*;
 import cn.reghao.tnb.search.app.lucene.LuceneDocument;
 import cn.reghao.tnb.search.app.lucene.LuceneIndex;
-import cn.reghao.tnb.search.app.lucene.LuceneQuery;
-import cn.reghao.tnb.search.app.model.po.VideoText;
+import cn.reghao.tnb.search.app.lucene.LuceneSearch;
 import cn.reghao.tnb.search.app.model.po.Wenshu;
+import cn.reghao.tnb.search.app.model.po.WenshuLucene;
 import cn.reghao.tnb.search.app.model.vo.ElasticQuery;
+import cn.reghao.tnb.search.app.service.WenshuService;
 import cn.reghao.tnb.search.app.util.ClassUtil;
-import co.elastic.clients.elasticsearch._types.mapping.Property;
-import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.lucene.document.Document;
 import org.junit.jupiter.api.Test;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
 import org.springframework.test.context.ActiveProfiles;
 
 import java.io.File;
@@ -29,6 +28,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -57,124 +57,57 @@ public class WenshuTest {
     QueryService<Wenshu> queryService;
     String indexName = Wenshu.class.getSimpleName().toLowerCase(Locale.ROOT);
 
-    void readByFileChannel(String filePath, DocumentService documentService) {
-        List<Wenshu> list = new ArrayList<>();
-        File file = new File(filePath);
-        try {
-            FileInputStream fis = new FileInputStream(file);
-            FileChannel fileChannel = fis.getChannel();
-
-            int total = 0;
-            // 10MB
-            int capacity = 10*1024*1024;
-            ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
-            StringBuffer buffer = new StringBuffer();
-            while(fileChannel.read(byteBuffer) != -1) {
-                //读取后,将位置置为0,将limit置为容量, 以备下次读入到字节缓冲中,从0开始存储
-                byteBuffer.clear();
-                byte[] bytes = byteBuffer.array();
-
-                String str = new String(bytes);
-                buffer.append(str);
-                String[] strArray = buffer.toString().split(System.lineSeparator());
-                for (int i = 0; i < strArray.length-1; i++) {
-                    String line = strArray[i];
-                    Wenshu wenshu = parseLine(line);
-                    if (wenshu != null) {
-                        list.add(wenshu);
-                    } else {
-                        log.error("error parse line: {}", ++total);
-                    }
-
-                    if (list.size() > 10_000) {
-                        documentService.batchAddWenshu(indexName, list);
-                        log.info("add {} documents to es", list.size());
-                        list.clear();
-                    }
-                }
-
-                String lastLine = strArray[strArray.length-1];
-                if (!lastLine.endsWith("}")) {
-                    buffer = new StringBuffer();
-                    buffer.append(strArray[strArray.length-1]);
-                }
+    private void addLuceneIndex(List<Wenshu> wenshuList) {
+        long id = idGenerator.nextId();
+        List<Document> luceneDocumentList = wenshuList.stream()
+                .map(wenshu -> new WenshuLucene(""+id, wenshu))
+                .map(wenshuLucene -> luceneDocument.getDocumentByWenshuLucene(wenshuLucene))
+                .collect(Collectors.toList());
+
+        List<Document> wenshuDocumentList = wenshuList.stream()
+                .map(wenshu -> luceneDocument.getDocumentByWenshu(wenshu))
+                .collect(Collectors.toList());
+
+        /*luceneDocumentList.forEach(doc -> {
+            try {
+                luceneIndex.createIndex(doc);
+            } catch (IOException e) {
+                e.printStackTrace();
             }
-        } catch (IOException e) {
-            e.printStackTrace();
-        } finally {
-            // TODO close 处理
-        }
-    }
+        });*/
 
-    private Wenshu parseLine(String line) {
-        String[] arr = line.split(",");
+        String indexName = "wenshu";
         try {
-            List<String> fields = new ArrayList<>();
-            String id = idGenerator.nextId()+"";
-            fields.add(id);
-
-            String originalUrl = arr[0];
-            String caseId = arr[1];
-            String caseName = arr[2];
-            String court = arr[3];
-            String region = arr[4];
-            String caseType = arr[5];
-            String caseTypeId = arr[6];
-            fields.addAll(Arrays.asList(arr).subList(0, 7));
-            String procedure = arr[8];
-            fields.add(procedure);
-            String judgmentDate = arr[9];
-            fields.add(judgmentDate);
-            String publicDate = arr[10];
-            fields.add(publicDate);
-            String parties = arr[11];
-            fields.add(parties);
-
-            String cause = "";
-            String legalBasis = "";
-            String fullText = "";
-            if (arr.length == 13) {
-                cause = arr[12];
-            } else if (arr.length == 14) {
-                cause = arr[12];
-                legalBasis = arr[13];
-            } else if (arr.length == 15) {
-                cause = arr[12];
-                legalBasis = arr[13];
-                fullText = arr[14];
-            } else if (arr.length > 15) {
-                cause = arr[12];
-                legalBasis = arr[13];
-                String str = line.split(legalBasis)[1];
-                fullText = str.substring(1);
-            }
-
-            fields.add(cause);
-            fields.add(legalBasis);
-            fields.add(fullText);
-
-            Object object = ClassUtil.getObject(Wenshu.class, fields.toArray(new String[0]));
-            if (object instanceof  Wenshu) {
-                return (Wenshu) object;
-            }
-        } catch (Exception e) {
+            luceneIndex.createIndex(indexName, wenshuDocumentList);
+        } catch (IOException e) {
             e.printStackTrace();
         }
-
-        return null;
     }
 
+    @Autowired
+    LuceneIndex luceneIndex;
+    @Autowired
+    LuceneSearch luceneSearch;
+    @Autowired
+    LuceneDocument luceneDocument;
+    @Autowired
+    WenshuService wenshuService;
     @Test
-    public void addDocTest() throws IOException {
+    public void addTest() throws IOException {
         String indexName = "wenshu";
         /*indexService.deleteIndex(indexName);
         Map<String, Property> propertyMap = mappingService.getPropertyMapByWenshu();
         indexService.createIndex(indexName, propertyMap);*/
 
         //documentService.deleteAllDocument(indexName);
-
         String filePath = "/home/reghao/Downloads/2021年07月裁判文书数据.csv";
-        readByFileChannel(filePath, documentService);
+        //readByFileChannel(filePath, documentService);
+        wenshuService.processFile(filePath);
+    }
+
+    @Test
+    public void deleteAllTest() throws IOException {
+        wenshuService.deleteAll();
     }
 
     @Test
@@ -207,4 +140,32 @@ public class WenshuTest {
             pn++;
         }
     }
+
+    @Test
+    public void queryTest1() throws Exception {
+        setLogLevel();
+
+        String indexName = "wenshu_lucene";
+        int pn = 1;
+        int ps = 10;
+        String queryString = "拐卖";
+        luceneSearch.search(indexName, queryString, pn, ps);
+
+        ElasticQuery elasticQuery = new ElasticQuery.Builder()
+                .pageSize(10)
+                .pageNumber(1)
+                .indexName(indexName)
+                .highlightFieldName("fullText")
+                .otherFiledNames(List.of("caseName"))
+                .queryString(queryString)
+                .build();
+
+        PageList<WenshuLucene> pageList = luceneSearch.queryWithHighlight(elasticQuery, WenshuLucene.class);
+        long total = pageList.getTotalSize();
+        List<WenshuLucene> list = pageList.getList();
+        WenshuLucene wenshu = list.get(0);
+        String id = wenshu.getId();
+        String caseName = wenshu.getCaseName();
+        System.out.printf("%s -> %s\n", id, caseName);
+    }
 }