瀏覽代碼

增加了 DkExamples的管理

dwp 5 月之前
父節點
當前提交
9354e3a99a
共有 48 個文件被更改,包括 2749 次插入379 次删除
  1. 1 1
      server/pom.xml
  2. 17 1
      server/src/main/java/com/giantan/ai/util/JsonUtils.java
  3. 4 2
      server/src/main/java/com/giantan/data/common/ICollectionMetaService.java
  4. 6 0
      server/src/main/java/com/giantan/data/dk/Readme.java
  5. 41 0
      server/src/main/java/com/giantan/data/dk/constant/DkExampleConfig.java
  6. 10 10
      server/src/main/java/com/giantan/data/dk/controller/DkCollectionsController.java
  7. 127 126
      server/src/main/java/com/giantan/data/dk/controller/DkDocsController.java
  8. 69 0
      server/src/main/java/com/giantan/data/dk/controller/DkExamplesController.java
  9. 111 0
      server/src/main/java/com/giantan/data/dk/controller/DkSearchContoller.java
  10. 184 0
      server/src/main/java/com/giantan/data/dk/controller/DkTaxonomyController.java
  11. 41 0
      server/src/main/java/com/giantan/data/dk/dto/GTool.java
  12. 18 1
      server/src/main/java/com/giantan/data/dk/dto/GToolExample.java
  13. 0 4
      server/src/main/java/com/giantan/data/dk/dto/KnowledgeChunk.java
  14. 164 0
      server/src/main/java/com/giantan/data/dk/repository/DkExampleIndexer.java
  15. 283 0
      server/src/main/java/com/giantan/data/dk/repository/DkExampleRepository.java
  16. 35 17
      server/src/main/java/com/giantan/data/dk/repository/DkIndexer.java
  17. 356 93
      server/src/main/java/com/giantan/data/dk/repository/DkRepository.java
  18. 26 0
      server/src/main/java/com/giantan/data/dk/repository/IDkExampleIndexer.java
  19. 35 0
      server/src/main/java/com/giantan/data/dk/repository/IDkExampleRepository.java
  20. 6 2
      server/src/main/java/com/giantan/data/dk/repository/IDkIndexer.java
  21. 48 42
      server/src/main/java/com/giantan/data/dk/repository/IGDkRepository.java
  22. 51 5
      server/src/main/java/com/giantan/data/dk/service/DkCollectionService.java
  23. 219 4
      server/src/main/java/com/giantan/data/dk/service/DkDocsService.java
  24. 141 0
      server/src/main/java/com/giantan/data/dk/service/DkExamplesService.java
  25. 382 0
      server/src/main/java/com/giantan/data/dk/service/DkTaxonomyService.java
  26. 5 0
      server/src/main/java/com/giantan/data/dk/service/IDkExamplesService.java
  27. 27 24
      server/src/main/java/com/giantan/data/index/HybridSearch.java
  28. 14 0
      server/src/main/java/com/giantan/data/index/IndexUtils.java
  29. 187 0
      server/src/main/java/com/giantan/data/index/MilvusSearchRequestBuilder.java
  30. 4 4
      server/src/main/java/com/giantan/data/index/Vectorization.java
  31. 2 2
      server/src/main/java/com/giantan/data/kvs/repository/GDynamicRepository.java
  32. 2 2
      server/src/main/java/com/giantan/data/kvs/repository/GRepository.java
  33. 3 3
      server/src/main/java/com/giantan/data/kvs/repository/index/GIndexedRepository.java
  34. 6 2
      server/src/main/java/com/giantan/data/kvs/repository/index/IIndexer.java
  35. 1 1
      server/src/main/java/com/giantan/data/mds/MdsApplication.java
  36. 2 2
      server/src/main/java/com/giantan/data/mds/bot/GChatClient.java
  37. 4 4
      server/src/main/java/com/giantan/data/mds/service/impl/MdCollectionsService.java
  38. 2 2
      server/src/main/java/com/giantan/data/mds/task/impl/ChunksTaskHandler.java
  39. 3 3
      server/src/main/java/com/giantan/data/mds/task/impl/MdsTaskHandler.java
  40. 4 9
      server/src/main/java/com/giantan/data/qa/controller/QaSearchContoller.java
  41. 24 5
      server/src/main/java/com/giantan/data/qa/repository/QaIndexer.java
  42. 20 4
      server/src/main/java/com/giantan/data/qa/service/QaCollectionService.java
  43. 2 0
      server/src/main/java/com/giantan/data/qa/service/QaTaxonomyService.java
  44. 2 0
      server/src/main/resources/application.yml
  45. 3 3
      server/src/test/java/com/giantan/data/mds/MapDoubleToInt.java
  46. 42 0
      server/src/test/java/com/giantan/data/mds/dk/DkExampleRepositoryTest.java
  47. 1 1
      server/src/test/java/com/giantan/data/mds/dk/DkRepositoryTest.java
  48. 14 0
      server/src/test/java/com/giantan/data/mds/index/MilvusSearchRequestUtilsTest.java

+ 1 - 1
server/pom.xml

@@ -9,7 +9,7 @@
         <version>1.0.0</version>
     </parent>
 
-    <version>2.5.0</version>
+    <version>3.0.1</version>
     <artifactId>mdserver</artifactId>
 
     <properties>

+ 17 - 1
server/src/main/java/com/giantan/ai/util/JsonUtil.java → server/src/main/java/com/giantan/ai/util/JsonUtils.java

@@ -8,7 +8,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
-public class JsonUtil {
+public class JsonUtils {
     static ObjectMapper objectMapper = new ObjectMapper();
 
     public static String toJson(Object obj) {
@@ -53,6 +53,22 @@ public class JsonUtil {
         return ret;
     }
 
+    public static String toPrettyJson(Object obj) {
+        try {
+            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
+        } catch (Exception e) {
+            return obj.toString();
+        }
+    }
+
+    public static String toSimpleJson(Object obj) {
+        try {
+            return objectMapper.writeValueAsString(obj);
+        } catch (Exception e) {
+            return obj.toString();
+        }
+    }
+
     // 检查并清理控制字符
     public static String cleanControlChars(String input) {
         if (input == null) return null;

+ 4 - 2
server/src/main/java/com/giantan/data/qa/service/IQaCollectionService.java → server/src/main/java/com/giantan/data/common/ICollectionMetaService.java

@@ -1,12 +1,14 @@
-package com.giantan.data.qa.service;
+package com.giantan.data.common;
 
 import java.util.Map;
 
-public interface IQaCollectionService {
+public interface ICollectionMetaService {
 
     Map<String,Object> getAttibutes(String collId);
     Map<String,Object> getAttributesByName(String name);
 
+    int getCollectionId(String name);
+
     String getCollectionName(int collId) ;
 
 }

+ 6 - 0
server/src/main/java/com/giantan/data/dk/Readme.java

@@ -94,5 +94,11 @@ CREATE INDEX idx_gtool_metadata ON gtool USING gin(metadata jsonb_path_ops);
 //CREATE INDEX idx_gtool_extra  ON gtool USING gin(extra jsonb_path_ops);
 
  */
+
+//TODO: 1、DkDocsService各种搜索
+//TODO: 2、DkRepository各种增删改查
+//TODO: 3、GToolExample的增删改查,相似问
+//TODO: 4、DK httpclient
+
 public class Readme {
 }

+ 41 - 0
server/src/main/java/com/giantan/data/dk/constant/DkExampleConfig.java

@@ -0,0 +1,41 @@
+package com.giantan.data.dk.constant;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DkExampleConfig {
+
+    public static List<String> createTable(String schema, String tableName, String refTableName) {
+        List<String> ls = new ArrayList<String>();
+        String sql = """
+                CREATE TABLE IF NOT EXISTS %s.%s (
+                id VARCHAR(64) PRIMARY KEY,
+                tool_gid VARCHAR(64) REFERENCES %s.%s(gid) ON DELETE CASCADE,
+                user_query TEXT NOT NULL,
+                tool_input JSONB,
+                tool_output JSONB,
+                created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
+                );
+                """;
+        String createTable = String.format(sql, schema, tableName, schema, refTableName);
+        ls.add(createTable);
+
+        //CREATE INDEX idx_gtool_example_tool_gid ON gtool_example(tool_gid);
+        String gidIndex = String.format("CREATE INDEX IF NOT EXISTS idx_%s_tool_gid ON %s.%s (tool_gid);",
+                tableName, schema, tableName);
+        ls.add(gidIndex);
+
+        return ls;
+    }
+
+    public static List<String> dropTable(String schema, String tableName) {
+        String sql = String.format("DROP TABLE IF EXISTS %s.%s ;", schema, tableName);
+        return List.of(sql);
+    }
+
+    public static List<String> clearAll(String schema, String tableName) {
+        String sql = String.format("DELETE FROM %s.%s", schema, tableName);
+        return List.of(sql);
+    }
+
+}

+ 10 - 10
server/src/main/java/com/giantan/data/dk/controller/DkCollectionsController.java

@@ -21,55 +21,55 @@ import java.util.Map;
 public class DkCollectionsController {
 
     @Autowired
-    DkCollectionService qaCollectionService;
+    DkCollectionService dkCollectionService;
 
     @PostMapping
     public ResponseEntity<R> createCollection(@RequestParam String name) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.createCollection(name)));
+        return ResponseEntity.ok(R.data(dkCollectionService.createCollection(name)));
     }
 
     @GetMapping
     public ResponseEntity<R> getAllCollections() throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.getAllCollections()));
+        return ResponseEntity.ok(R.data(dkCollectionService.getAllCollections()));
     }
 
     @GetMapping("/{name}")
     public ResponseEntity<R> getCollectionById(@PathVariable String name) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.getKvByName(name)));
+        return ResponseEntity.ok(R.data(dkCollectionService.getKvByName(name)));
     }
 
     @DeleteMapping("/{name}")
     public ResponseEntity<R> deleteCollection(@PathVariable String name) throws Throwable {
        // long ret = collectionService.deleteCollection(id);
-        long ret = qaCollectionService.deleteCollection(name);
+        long ret = dkCollectionService.deleteCollection(name);
         return ResponseEntity.ok(R.data(ret));
     }
 
     @DeleteMapping("/{name}/all")
     public ResponseEntity<R> clearCollection(@PathVariable String name) throws Throwable {
         // long ret = collectionService.deleteCollection(id);
-        long ret = qaCollectionService.clearCollection(name);
+        long ret = dkCollectionService.clearCollection(name);
         return ResponseEntity.ok(R.data(ret));
     }
 
     @PutMapping("/{name}")
     public ResponseEntity<R> update(@PathVariable String name,  @RequestBody Map<String,Object> kvs) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.updateCollection(name,kvs)));
+        return ResponseEntity.ok(R.data(dkCollectionService.updateCollection(name,kvs)));
     }
 
     @GetMapping("/{name}/attributes")
     public ResponseEntity<R> getAttributes(@PathVariable String name) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.getCollectionAttributes(name)));
+        return ResponseEntity.ok(R.data(dkCollectionService.getCollectionAttributes(name)));
     }
 
     @PutMapping("/{name}/attributes")
     public ResponseEntity<R> updateAttributes(@PathVariable String name,  @RequestBody Map<String,Object> attributes) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.updateCollectionAttributes(name,attributes)));
+        return ResponseEntity.ok(R.data(dkCollectionService.updateCollectionAttributes(name,attributes)));
     }
 
     @DeleteMapping("/{name}/attributes")
     public ResponseEntity<R> removeAttribute(@PathVariable String name,@RequestBody List<String> keys) throws Throwable {
-        return ResponseEntity.ok(R.data(qaCollectionService.removeCollectionAttribute(name,keys)));
+        return ResponseEntity.ok(R.data(dkCollectionService.removeCollectionAttribute(name,keys)));
     }
 
 //    @PutMapping("/{name}/attributes")

+ 127 - 126
server/src/main/java/com/giantan/data/dk/controller/DkDocsController.java

@@ -4,6 +4,7 @@ import com.giantan.ai.common.reponse.R;
 import com.giantan.data.dk.constant.DkConstants;
 import com.giantan.data.dk.dto.GTool;
 import com.giantan.data.dk.service.DkDocsService;
+import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -50,27 +51,27 @@ public class DkDocsController {
     @Autowired
     DkDocsService dkDocsService;
 
-    @PostMapping("/docs")
+    @PostMapping("/tools")
     public R createEntry(@PathVariable String collId, @RequestBody GTool data) throws Throwable {
         GTool r = dkDocsService.save(collId, data);
         return R.data(r);
     }
 
-    @GetMapping("/docs/all")
+    @GetMapping("/tools/all")
     public R<List<GTool>> getAll(@PathVariable String collId) throws Throwable {
         List<GTool> ret = null;
         ret = dkDocsService.findAll(collId);
         return R.data(ret);
     }
 
-    @GetMapping("/docs/{id}")
+    @GetMapping("/tools/{id}")
     public R<?> getById(@PathVariable String collId, @PathVariable String id
     ) throws Throwable {
         GTool ret = dkDocsService.findByIdOrGid(collId, id);
         return R.data(ret);
     }
 
-    @DeleteMapping("/docs/{gid}")
+    @DeleteMapping("/tools/{gid}")
     public R<?> delete(@PathVariable String collId, @PathVariable String gid
     ) throws Throwable {
         int deleted = dkDocsService.deleteByIdOrGid(collId, gid);
@@ -80,24 +81,31 @@ public class DkDocsController {
     }
 
 
-    @PutMapping("/docs/{qid}")
+    @PutMapping("/tools/{qid}")
     public R update(@PathVariable String collId, @PathVariable String qid, @RequestBody GTool tool) throws Throwable {
         GTool r = dkDocsService.update(collId, qid, tool);
         return R.data(r);
     }
 
+    @PostMapping("/tools/fulltextSearch")
+    public R fulltextSearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
+        List<GBaseKeyValue> rets = dkDocsService.fulltextSearch(collId, query);
+        return R.data(rets);
+    }
 
-    /*
-
-
-    @PostMapping("/docs/batch")
-    public ResponseEntity<R> createBatch(@PathVariable String collId, @RequestBody List<GBaseKeyValue> kvs) throws Throwable {
-        List<Integer> ret = dkDocsService.saveAll(collId, kvs);
-        return ResponseEntity.ok(R.data(ret));
+    @PostMapping("/tools/similaritySearch")
+    public R similaritySearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
+        List<GBaseKeyValue> rets = dkDocsService.similaritySearch(collId, query);
+        return R.data(rets);
     }
 
+    @PostMapping("/tools/hybridSearch")
+    public R hybridSearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
+        List<GBaseKeyValue> rets = dkDocsService.hybridSearch(collId, query);
+        return R.data(rets);
+    }
 
-    @DeleteMapping("/docs/by-name")
+    @DeleteMapping("/tools/by-name")
     public R<Map<String, Object>> deleteByName(@PathVariable String collId, @RequestParam("name") String name
     ) throws Throwable {
         // 根据 name 删除对应 md
@@ -105,21 +113,123 @@ public class DkDocsController {
         return R.data(Map.of("deleted", deleted));
     }
 
-    @DeleteMapping("/docs/all")
+    @DeleteMapping("/tools/all")
     public R<Map<String, Object>> deleteAll(@PathVariable String collId) throws Exception {
         long removed = dkDocsService.deleteAll(collId);
         return R.data(Map.of("deleted", removed));
     }
 
+    // 获取某些字段的所有值
+    @PostMapping("/tools/fields")
+    public R getAllEntities(@PathVariable String collId, @RequestBody List<String> fields) throws Throwable {
+        List<Map<String, Object>> entities = dkDocsService.getAllEntities(collId, fields);
+        return R.data(entities);
+    }
+
+    ////////////////////
+    //GET /collections/demo/paths/get?path=/a/b/c
+    //DELETE /collections/demo/paths/delete?path=/a/b/c
+    //GET /collections/demo/paths/list?prefix=/a
+    //GET /collections/demo/paths/children?path=/a
+    //GET /collections/demo/paths/descendants?path=/a
+
+    @GetMapping("/tools/by-path")
+    public R getDocsBypPath(@PathVariable String collId, @RequestParam String path) throws Throwable {
+        List<GTool> rets = dkDocsService.findByPath(collId, path);
+        return R.data(rets);
+    }
+
+    @GetMapping("/tools/by-prefix")
+    public R getDocsBypPrefix(@PathVariable String collId, @RequestParam String prefix) throws Throwable {
+        List<GTool> rets = dkDocsService.findByPrefix(collId, prefix);
+        return R.data(rets);
+    }
+
+    @DeleteMapping("/tools/by-path")
+    public R deletePathAndDescendants(@PathVariable String collId, @RequestParam String path) throws Throwable {
+        int rets = dkDocsService.deletePathAndDescendants(collId, path);
+        return R.data(rets);
+    }
+
+    // 获取记录数
+    @GetMapping("/tools/count")
+    public R getCount(@PathVariable String collId) {
+        long count = dkDocsService.count(collId);
+        return R.data(count);
+    }
+
+    @GetMapping("/tools")
+    public R getDocs(
+            @PathVariable String collId,
+            @RequestParam(required = false) String path,
+            @RequestParam(required = false) String prefix,
+            @RequestParam(required = false) String parent) throws Throwable {
+        List<GTool> rets = null;
+        if (path != null) {
+            rets = dkDocsService.findByPath(collId, path);
+        } else if (prefix != null) {
+            rets = dkDocsService.findByPrefix(collId, prefix);
+        } else if (parent != null) {
+            rets = dkDocsService.getChildren(collId, parent);
+        } else {
+            rets = dkDocsService.findAll(collId);
+        }
+        return R.data(rets);
+    }
+
+    @DeleteMapping("/docs")
+    public R deleteDocs(
+            @PathVariable String collId,
+            @RequestParam(required = false) String path,
+            @RequestParam(required = false) String prefix,
+            @RequestParam(required = false) String subtree) throws Throwable {
+        int ret = 0;
+        if (path != null) {
+            ret = dkDocsService.deleteByPath(collId, path);
+        } else if (prefix != null) {
+            ret = dkDocsService.deleteByPrefix(collId, prefix);
+        } else if (subtree != null) {
+            ret = dkDocsService.deletePathAndDescendants(collId, subtree);
+        } else {
+            throw new IllegalArgumentException("Must provide path or prefix to delete");
+        }
+        return R.data(ret);
+    }
+
+
+//    @PostMapping("/tools/{docId}/tags/add")
+//    public R appendTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
+//        List<String> ret = dkDocsService.appendArrayField(collId, docId, "tags", values);
+//        return ResponseEntity.ok(R.data(ret));
+//    }
+//
+//    @PostMapping("/docs/{docId}/tags/set")
+//    public ResponseEntity<R> setTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
+//        List<String> ret = dkDocsService.setArrayField(collId, docId, "tags", values);
+//        return ResponseEntity.ok(R.data(ret));
+//    }
+//
+//    @PostMapping("/docs/{docId}/tags/remove")
+//    public ResponseEntity<R> removeTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
+//        List<String> ret = dkDocsService.removeArrayField(collId, docId, "tags", values);
+//        return ResponseEntity.ok(R.data(ret));
+//    }
+
+    /*
+
+
+    @PostMapping("/docs/batch")
+    public ResponseEntity<R> createBatch(@PathVariable String collId, @RequestBody List<GBaseKeyValue> kvs) throws Throwable {
+        List<Integer> ret = dkDocsService.saveAll(collId, kvs);
+        return ResponseEntity.ok(R.data(ret));
+    }
+
     @PutMapping("/docs")
     public R update(@PathVariable String collId, @RequestBody GBaseKeyValue kv) throws Throwable {
         GBaseKeyValue updatedKv = dkDocsService.update(collId, kv);
         return R.data(updatedKv);
     }
 
-
-
-
     //////////////////
     //整体 metadata 管理	/collections/{collId}/docs/{docId}/metadata
     //单个 metadata 字段	/collections/{collId}/docs/{docId}/metadata/{key}
@@ -180,63 +290,8 @@ public class DkDocsController {
         return R.data(updatedKv);
     }
 
-    // 获取某些字段的所有值
-    @PostMapping("/docs/fields")
-    public R getAllEntities(@PathVariable String collId, @RequestBody List<String> fields) throws Throwable {
-        List<Map<String, Object>> entities = dkDocsService.getAllEntities(collId, fields);
-        return R.data(entities);
-    }
-
-
-    ////////////////////
-    //GET /collections/demo/paths/get?path=/a/b/c
-    //DELETE /collections/demo/paths/delete?path=/a/b/c
-    //GET /collections/demo/paths/list?prefix=/a
-    //GET /collections/demo/paths/children?path=/a
-    //GET /collections/demo/paths/descendants?path=/a
-
-    @GetMapping("/docs/by-path")
-    public R getDocsBypPath(@PathVariable String collId, @RequestParam String path) throws Throwable {
-        List<GBaseKeyValue> rets = dkDocsService.findByPath(collId, path);
-        return R.data(rets);
-    }
-
-    @GetMapping("/docs/by-prefix")
-    public R getDocsBypPrefix(@PathVariable String collId, @RequestParam String prefix) throws Throwable {
-        List<GBaseKeyValue> rets = dkDocsService.findByPrefix(collId, prefix);
-        return R.data(rets);
-    }
-
-    @DeleteMapping("/docs/by-path")
-    public R deletePathAndDescendants(@PathVariable String collId, @RequestParam String path) throws Throwable {
-        int rets = dkDocsService.deletePathAndDescendants(collId, path);
-        return R.data(rets);
-    }
-
-    // 获取记录数
-    @GetMapping("/docs/count")
-    public R getCount(@PathVariable String collId) {
-        long count = dkDocsService.count(collId);
-        return R.data(count);
-    }
-
-    @PostMapping("/docs/{docId}/tags/add")
-    public ResponseEntity<R> appendTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
-        List<String> ret = dkDocsService.appendArrayField(collId, docId, "tags", values);
-        return ResponseEntity.ok(R.data(ret));
-    }
 
-    @PostMapping("/docs/{docId}/tags/set")
-    public ResponseEntity<R> setTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
-        List<String> ret = dkDocsService.setArrayField(collId, docId, "tags", values);
-        return ResponseEntity.ok(R.data(ret));
-    }
 
-    @PostMapping("/docs/{docId}/tags/remove")
-    public ResponseEntity<R> removeTag(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
-        List<String> ret = dkDocsService.removeArrayField(collId, docId, "tags", values);
-        return ResponseEntity.ok(R.data(ret));
-    }
 
     @PostMapping("/docs/{docId}/altlabels/add")
     public ResponseEntity<R> appendAltlabels(@PathVariable String collId, @PathVariable int docId, @RequestBody List<String> values) {
@@ -256,62 +311,8 @@ public class DkDocsController {
         return ResponseEntity.ok(R.data(ret));
     }
 
-    @PostMapping("/docs/fulltextSearch")
-    public ResponseEntity<R> fulltextSearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
-        List<GBaseKeyValue> rets = dkDocsService.fulltextSearch(collId, query);
-        return ResponseEntity.ok(R.data(rets));
-    }
-
-    @PostMapping("/docs/similaritySearch")
-    public ResponseEntity<R> similaritySearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
-        List<GBaseKeyValue> rets = dkDocsService.similaritySearch(collId, query);
-        return ResponseEntity.ok(R.data(rets));
-    }
-
-    @PostMapping("/docs/hybridSearch")
-    public ResponseEntity<R> hybridSearch(@PathVariable String collId, @RequestBody Map<String, Object> query) throws Throwable {
-        List<GBaseKeyValue> rets = dkDocsService.hybridSearch(collId, query);
-        return ResponseEntity.ok(R.data(rets));
-    }
 
     //////////////
-    @GetMapping("/docs")
-    public ResponseEntity<R> getDocs(
-            @PathVariable String collId,
-            @RequestParam(required = false) String path,
-            @RequestParam(required = false) String prefix,
-            @RequestParam(required = false) String parent) throws Throwable {
-        List<GBaseKeyValue> rets = null;
-        if (path != null) {
-            rets = dkDocsService.findByPath(collId, path);
-        } else if (prefix != null) {
-            rets = dkDocsService.findByPrefix(collId, prefix);
-        } else if (parent != null) {
-            rets = dkDocsService.getChildren(collId, parent);
-        } else {
-            rets = dkDocsService.findAll(collId);
-        }
-        return ResponseEntity.ok(R.data(rets));
-    }
-
-    @DeleteMapping("/docs")
-    public ResponseEntity<R> deleteDocs(
-            @PathVariable String collId,
-            @RequestParam(required = false) String path,
-            @RequestParam(required = false) String prefix,
-            @RequestParam(required = false) String subtree) throws Throwable {
-        int ret = 0;
-        if (path != null) {
-            ret = dkDocsService.deleteByPath(collId, path);
-        } else if (prefix != null) {
-            ret = dkDocsService.deleteByPrefix(collId, prefix);
-        } else if (subtree != null) {
-            ret = dkDocsService.deletePathAndDescendants(collId, subtree);
-        } else {
-            throw new IllegalArgumentException("Must provide path or prefix to delete");
-        }
-        return ResponseEntity.ok(R.data(ret));
-    }
 
     @DeleteMapping("/docs/indexes")
     public ResponseEntity<R> deleteDocsIndexes(@PathVariable String collId) throws IOException, InterruptedException {

+ 69 - 0
server/src/main/java/com/giantan/data/dk/controller/DkExamplesController.java

@@ -0,0 +1,69 @@
+package com.giantan.data.dk.controller;
+
+import com.giantan.ai.common.reponse.R;
+import com.giantan.data.dk.constant.DkConstants;
+import com.giantan.data.dk.dto.GToolExample;
+import com.giantan.data.dk.service.DkExamplesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping(DkConstants.API_PREFIX + "/collections/{collId}")
+public class DkExamplesController {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    @Autowired
+    DkExamplesService dkExamplesService;
+
+    @PostMapping("/tool-examples")
+    public R create(@PathVariable String collId, @RequestBody GToolExample data) throws Throwable {
+        GToolExample r = dkExamplesService.save(collId, data);
+        return R.data(r);
+    }
+
+    @GetMapping("/tool-examples/all")
+    public R<List<GToolExample>> getAll(@PathVariable String collId) throws Throwable {
+        List<GToolExample> ret = null;
+        ret = dkExamplesService.findAll(collId);
+        return R.data(ret);
+    }
+
+    @GetMapping("/tool-examples/{id}")
+    public R<?> getById(@PathVariable String collId, @PathVariable String id
+    ) throws Throwable {
+        GToolExample ret = dkExamplesService.findById(collId, id);
+        return R.data(ret);
+    }
+
+    @DeleteMapping("/tool-examples/{id}")
+    public R<?> delete(@PathVariable String collId, @PathVariable String id
+    ) throws Throwable {
+        GToolExample deleted = dkExamplesService.deleteById(collId, id);
+        log.info("Delete dk: {}", id);
+        return R.data(Map.of("deleted", deleted));
+    }
+
+
+    @PutMapping("/tool-examples/{id}")
+    public R update(@PathVariable String collId, @PathVariable String id, @RequestBody GToolExample example) throws Throwable {
+        GToolExample r = dkExamplesService.update(collId, id, example);
+        return R.data(r);
+    }
+
+    @GetMapping("/tool-examples/by-tool/{toolGid}")
+    public R<List<GToolExample>> getByTool(@PathVariable String collId, @PathVariable String toolGid) {
+        List<GToolExample> rs = dkExamplesService.findByToolGid(collId, toolGid);
+        return R.data(rs);
+    }
+
+    @DeleteMapping("/tool-examples/by-tool/{toolGid}")
+    public R<List<GToolExample>> deleteByTool(@PathVariable String collId, @PathVariable String toolGid) {
+        List<GToolExample> rs = dkExamplesService.deleteByToolGid(collId, toolGid);
+        return R.data(rs);
+    }
+}

+ 111 - 0
server/src/main/java/com/giantan/data/dk/controller/DkSearchContoller.java

@@ -0,0 +1,111 @@
+package com.giantan.data.dk.controller;
+
+import com.giantan.data.dk.constant.DkConstants;
+
+import com.giantan.data.dk.repository.DkIndexer;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collections;
+
+@RestController
+@RequestMapping(DkConstants.API_PREFIX + "/collections/{coll}")
+public class DkSearchContoller {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    @Value("${qas.url}")
+    String url = "http://120.78.4.46:7387/v1/collections/";
+
+    @Autowired
+    //QaDocRepository qaDocRepository;
+    DkIndexer dkIndexer;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    //private static final String SRC_PREFIX = "http://127.0.0.1:18211/v1/md";
+    private String TARGET_PREFIX = "http://120.78.4.46:7387/v1";
+
+
+    @RequestMapping("/indexes/**")
+    public ResponseEntity<byte[]> proxyAll(@PathVariable String coll, HttpServletRequest request,
+                                           @RequestBody(required = false) byte[] body) {
+
+        String requestUri = request.getRequestURI(); // 原始 URI
+        String query = request.getQueryString();
+
+        // 替换前缀
+        //String targetPath = requestUri.replaceFirst("/v1/md", "");
+        int ii = requestUri.indexOf(DkConstants.API_PREFIX);
+
+        // 去掉 /indexes
+        String targetPath = requestUri.substring(ii + DkConstants.API_PREFIX.length());
+        targetPath = targetPath.replaceFirst("/collections", "");
+        targetPath = targetPath.replaceFirst("/" + coll, "");
+        targetPath = targetPath.replaceFirst("/indexes", "/documents");
+
+        //TARGET_PREFIX = url.replaceFirst("/collections", "");
+        // 拼接目标 URL
+        //String collName = dkDocsService.getIndexName(coll);
+        String collName = dkIndexer.getMappedIndexNameByName(coll);
+
+        String targetUrl = url + collName + targetPath + (query != null ? "?" + query : "");
+
+        return redirect(request, body, targetUrl);
+    }
+
+
+    public ResponseEntity<byte[]> redirect(HttpServletRequest request, byte[] body, String targetUrl) {
+
+        // 构造请求头
+        HttpHeaders headers = new HttpHeaders();
+        Collections.list(request.getHeaderNames())
+                .forEach(name -> headers.add(name, request.getHeader(name)));
+
+        // 读取 HTTP 方法
+        HttpMethod method = HttpMethod.valueOf(request.getMethod());
+
+        // 打印请求日志
+        int requestSize = body != null ? body.length : 0;
+        log.info("[Dk] " + method + " " + targetUrl + " | request size: " + requestSize + " bytes");
+
+        // 构造请求实体
+        HttpEntity<byte[]> entity = new HttpEntity<>(body, headers);
+
+        // 转发请求
+        ResponseEntity<byte[]> response = restTemplate.exchange(
+                targetUrl,
+                method,
+                entity,
+                byte[].class
+        );
+
+        // 打印响应日志
+        int responseSize = response.getBody() != null ? response.getBody().length : 0;
+        log.info("[Dk] response status: " + response.getStatusCode() + " | response size: " + responseSize + " bytes");
+        HttpHeaders headers1 = response.getHeaders();
+        HttpHeaders headers2 = new HttpHeaders();
+        headers1.forEach((k, v) -> {
+            if (!k.equalsIgnoreCase("Transfer-Encoding")) {
+                headers2.put(k, v);
+            }
+        });
+        return ResponseEntity
+                .status(response.getStatusCode())
+                .headers(headers2)
+                .body(response.getBody());
+    }
+
+
+}

+ 184 - 0
server/src/main/java/com/giantan/data/dk/controller/DkTaxonomyController.java

@@ -0,0 +1,184 @@
+package com.giantan.data.dk.controller;
+
+import com.giantan.ai.common.reponse.R;
+import com.giantan.data.dk.constant.DkConstants;
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.service.DkTaxonomyService;
+import com.giantan.data.taxonomy.model.TaxonomyNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+@RestController
+@RequestMapping(DkConstants.API_PREFIX + "/collections/{collName}")
+public class DkTaxonomyController {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    @Autowired
+    private DkTaxonomyService dkTaxonomyService;
+
+    @GetMapping("/taxonomy/all")
+    public R listAll(@PathVariable String collName) {
+        List<TaxonomyNode> r = dkTaxonomyService.listAll(collName);
+        return R.data(r);
+    }
+
+
+    @GetMapping("/taxonomy/tree")
+    public R listTree(@PathVariable String collName) {
+        List<TaxonomyNode> r = dkTaxonomyService.listTree(collName);
+        return R.data(r);
+    }
+
+
+    @GetMapping("/taxonomy/{nodeId}/children")
+//    public ResponseEntity<?> findChildren(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+//        List<TaxonomyNode> r = dynamicTaxonomyService.findChildren(collName, nodeId);
+//        return ResponseEntity.ok(r);
+//    }
+    public R findChildren(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        List<TaxonomyNode> r = dkTaxonomyService.findChildren(collName, nodeId);
+        return R.data(r);
+    }
+
+
+    @GetMapping("/taxonomy/{nodeId}/tools")
+//    public ResponseEntity<?> findMds(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+//        List<GBaseKeyValue> r = dynamicTaxonomyService.findMds(collName, nodeId);
+//        return ResponseEntity.ok(r);
+//    }
+    public R findMds(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        List<GTool> r = dkTaxonomyService.findTools(collName, nodeId);
+        return R.data(r);
+    }
+
+
+    @PostMapping("/taxonomy")
+//    public ResponseEntity<?> createNode(@PathVariable String collName, @RequestBody Map<String, Object> data) throws Exception {
+//        TaxonomyNode r = dynamicTaxonomyService.createNode(collName, data);
+//        return ResponseEntity.ok(r);
+//    }
+    public R createNode(@PathVariable String collName, @RequestBody Map<String, Object> data) throws Exception {
+        TaxonomyNode r = dkTaxonomyService.createNode(collName, data);
+        return R.data(r);
+    }
+
+
+//    @PostMapping("/{nodeId}/mds")
+//    public R<Map<String, Object>> upload(@PathVariable String collName,
+//                                         @PathVariable String nodeId,
+//                                         @RequestParam("file") MultipartFile file,
+//                                         @RequestParam Map<String, String> params
+//    ) throws Exception {
+//        String taskId = UUID.randomUUID().toString();
+//        //System.out.println("taskId = " + taskId);
+//        //System.out.println("file = " + file.getOriginalFilename());
+//        log.info("上传文件: {}, taskId: {}", file.getOriginalFilename(), taskId);
+//
+//        //taskStatusManager.putProcessing(taskId, new TaskStatus(collId, taskId, "处理中", "", System.currentTimeMillis(), 0));
+//        Map<String, Object> ret = dynamicTaxonomyService.processAsyncDirect(collName, nodeId, taskId, file, params);
+//
+//        return R.data(ret);
+//    }
+
+    @DeleteMapping("/taxonomy/{nodeId}/tools")
+    public R deleteFolder(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        String taskId = UUID.randomUUID().toString();
+        //System.out.println("taskId = " + taskId);
+        //System.out.println("file = " + file.getOriginalFilename());
+        //log.info("上传文件: {}, taskId: {}", file.getOriginalFilename(), taskId);
+
+        //taskStatusManager.putProcessing(taskId, new TaskStatus(collId, taskId, "处理中", "", System.currentTimeMillis(), 0));
+        int ret = dkTaxonomyService.deleteFolder(collName, nodeId, taskId);
+
+        return R.data(ret);
+    }
+
+    @PostMapping("/taxonomy/{nodeId}/rename")
+    public R renameFolder(@PathVariable String collName, @PathVariable String nodeId, @RequestBody Map<String, Object> req) throws Exception {
+        String taskId = UUID.randomUUID().toString();
+        //System.out.println("taskId = " + taskId);
+        //System.out.println("file = " + file.getOriginalFilename());
+        //log.info("上传文件: {}, taskId: {}", file.getOriginalFilename(), taskId);
+
+        //taskStatusManager.putProcessing(taskId, new TaskStatus(collId, taskId, "处理中", "", System.currentTimeMillis(), 0));
+        int ret = dkTaxonomyService.renameFolder(collName, nodeId, req, taskId);
+
+        return R.data(ret);
+    }
+
+    @PostMapping("/taxonomy/{nodeId}/move")
+    public R moveTo(@PathVariable String collName, @PathVariable String nodeId, @RequestBody Map<String, Object> req) throws Exception {
+        String taskId = UUID.randomUUID().toString();
+        //System.out.println("taskId = " + taskId);
+        //System.out.println("file = " + file.getOriginalFilename());
+        //log.info("上传文件: {}, taskId: {}", file.getOriginalFilename(), taskId);
+
+        //taskStatusManager.putProcessing(taskId, new TaskStatus(collId, taskId, "处理中", "", System.currentTimeMillis(), 0));
+        int ret = dkTaxonomyService.moveTo(collName, nodeId, req, taskId);
+
+        return R.data(ret);
+    }
+
+    @GetMapping("/taxonomy/{nodeId}/subtree")
+    public R getSubtree(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        List<TaxonomyNode> r = dkTaxonomyService.findSubtree(collName, nodeId);
+        return R.data(r);
+    }
+
+    @GetMapping("/taxonomy/{nodeId}/descendants")
+    public R getDescendants(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        List<TaxonomyNode> r = dkTaxonomyService.findDescendants(collName, nodeId);
+        return R.data(r);
+    }
+
+    @GetMapping("/taxonomy/{nodeId}/ancestors")
+    public R getAncestors(@PathVariable String collName, @PathVariable String nodeId) throws Exception {
+        List<TaxonomyNode> r = dkTaxonomyService.findAncestors(collName, nodeId);
+        return R.data(r);
+    }
+
+    @PostMapping("/taxonomy/{nodeId}/tools/by-path")
+    public R createDocByPath(@PathVariable String collName, @PathVariable String nodeId, @RequestBody GTool data) throws Throwable {
+        GTool ret = dkTaxonomyService.createTool(collName, nodeId, data);
+        return R.data(ret);
+    }
+
+    ////////////
+
+    @PostMapping("/tools/by-path")
+    public R createEntityByPath(@PathVariable String collName, @RequestBody GTool data) throws Throwable {
+        GTool ret = dkTaxonomyService.createEntityByPath(collName, data);
+        return R.data(ret);
+    }
+
+    @PostMapping("/taxonomy/by-path")
+    public R createNodeByPath(@PathVariable String collName, @RequestBody Map<String, Object> data) throws Exception {
+        TaxonomyNode r = dkTaxonomyService.createNodeByPath(collName, data);
+        return R.data(r);
+    }
+
+    @PutMapping("/taxonomy/by-path")
+    public R updateNodeByPath(@PathVariable String collName, @RequestBody Map<String, Object> data) throws Exception {
+        TaxonomyNode r = dkTaxonomyService.updateNodeByPath(collName, data);
+        return R.data(r);
+    }
+
+    @GetMapping("/taxonomy/by-path")
+    public R findNodeByPath(@PathVariable String collName, @RequestParam String path) throws Exception {
+        TaxonomyNode r = dkTaxonomyService.findNodeByPath(collName, path);
+        return R.data(r);
+    }
+
+    @DeleteMapping("/taxonomy/by-path")
+    public R deleteFolderByPath(@PathVariable String collName, @RequestParam String path) throws Exception {
+        int ret = dkTaxonomyService.deleteFolderByPath(collName, path);
+        return R.data(ret);
+    }
+
+}

+ 41 - 0
server/src/main/java/com/giantan/data/dk/dto/GTool.java

@@ -1,5 +1,6 @@
 package com.giantan.data.dk.dto;
 
+import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import lombok.Data;
 
 import java.time.LocalDateTime;
@@ -77,4 +78,44 @@ public class GTool {
     private Map<String, Object> metadata;
     private Map<String, Object> extra;
 
+    public GBaseKeyValue toGKeyValue(){
+        GBaseKeyValue r = new GBaseKeyValue();
+        r.setGid(gid);
+        r.setName(name);
+        r.setId(Long.toString(id));
+        if (type != null) {
+            r.put("type", type);
+        }
+        if (description != null) {
+            r.put("description", description);
+        }
+        if (path != null) {
+            r.put("path", path);
+        }
+        if (tags != null) {
+            r.put("tags", tags);
+        }
+        if (inputSchema != null) {
+            r.put("inputSchema", inputSchema);
+        }
+        if (outputSchema != null) {
+            r.put("outputSchema", outputSchema);
+        }
+        if (usageExamples != null) {
+            r.put("usageExamples", usageExamples);
+        }
+        if (decisionKnowledge != null) {
+            r.put("decisionKnowledge", decisionKnowledge);
+        }
+        if (createdAt != null) {
+            r.put("createdAt", createdAt);
+        }
+        if (metadata != null) {
+            r.put("metadata", metadata);
+        }
+        if (extra != null) {
+            r.put("extra", extra);
+        }
+        return r;
+    }
 }

+ 18 - 1
server/src/main/java/com/giantan/data/dk/dto/GToolExample.java

@@ -2,8 +2,25 @@ package com.giantan.data.dk.dto;
 
 import lombok.Data;
 
+import java.time.OffsetDateTime;
 import java.util.Map;
 
+/*
+CREATE TABLE gtool_example (
+    id VARCHAR(64) PRIMARY KEY,
+    tool_gid VARCHAR(64) REFERENCES gtool(gid) ON DELETE CASCADE,
+    user_query TEXT NOT NULL,
+    tool_input JSONB,
+    tool_output JSONB,
+    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_gtool_example_tool_gid ON gtool_example(tool_gid);
+
+//embedding VECTOR,                 -- user_query 的向量,存 Milvus
+ */
+
+
 @Data
 public class GToolExample {
     private String id;
@@ -11,5 +28,5 @@ public class GToolExample {
     private String userQuery;
     private Map<String, Object> toolInput;
     private Map<String, Object> toolOutput;
-    private long createdAt;
+    private OffsetDateTime createdAt;
 }

+ 0 - 4
server/src/main/java/com/giantan/data/dk/dto/KnowledgeChunk.java

@@ -1,4 +0,0 @@
-package com.giantan.data.dk.dto;
-
-public class KnowledgeChunk {
-}

+ 164 - 0
server/src/main/java/com/giantan/data/dk/repository/DkExampleIndexer.java

@@ -0,0 +1,164 @@
+package com.giantan.data.dk.repository;
+
+import com.giantan.ai.util.JsonUtils;
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.dto.GToolExample;
+import com.giantan.data.index.IndexUtils;
+import com.giantan.data.index.dto.DocReq;
+import com.giantan.data.index.dto.DocResp;
+import org.springframework.stereotype.Repository;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Repository
+public class DkExampleIndexer extends DkIndexer implements IDkExampleIndexer {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    public static final String SOURCE = "__source";
+    public static final String EXAMPLE = "example";
+
+    public static String toEmbeddingText(GToolExample example) {
+        StringBuilder sb = new StringBuilder();
+        //sb.append("[EXAMPLE]\n");
+
+        if (example.getUserQuery() != null) {
+            //sb.append("User Query: ").append(example.getUserQuery()).append("\n\n");
+            sb.append(example.getUserQuery()).append("\n");
+        }
+
+        if (example.getToolInput() != null && !example.getToolInput().isEmpty()) {
+            sb.append("Tool Input:\n")
+                    .append(toJson(example.getToolInput()))
+                    .append("\n\n");
+        }
+
+        if (example.getToolOutput() != null && !example.getToolOutput().isEmpty()) {
+            sb.append("Tool Output:\n")
+                    .append(toJson(example.getToolOutput()))
+                    .append("\n");
+        }
+
+        return sb.toString().trim();
+    }
+
+    public static String toJson(Object obj) {
+        return JsonUtils.toSimpleJson(obj);
+    }
+
+    private void buildMetadata(String collection, GTool entity, DocReq req) {
+        Map<String, Object> metadata = req.getMetadata();
+        if (metadata == null) {
+            metadata = new HashMap<>();
+            req.setMetadata(metadata);
+        }
+        metadata.put(DOC_ID, entity.getId().toString());
+        metadata.put(COLL_ID, collection);
+        metadata.put(SOURCE, EXAMPLE);
+        if (entity.getPath() != null) {
+            metadata.put(DkIndexer.FIELD_PATH, IndexUtils.normalize(entity.getPath()));
+        }
+    }
+
+
+    private List<DocReq> toDocReq(String coll, GToolExample entity, GTool tool) {
+        List<DocReq> lst = new ArrayList<DocReq>();
+        //**基础描述 chunk**
+        DocReq dr1 = new DocReq();
+        dr1.setId(entity.getId());
+        dr1.setText(toEmbeddingText(entity));
+        dr1.setTags(tool.getTags());
+        buildMetadata(coll, tool, dr1);
+
+        lst.add(dr1);
+
+        return lst;
+    }
+
+    @Override
+    public int onAdd(String collection, GToolExample entity, GTool tool) throws IOException, InterruptedException {
+        log.info("{}{} onAdd: {}", collectionPrefix, collection, entity.getId());
+        List<DocResp> rs = hybridSearch.add(getMappedIndexName(collection), toDocReq(collection, entity, tool));
+        return rs == null ? 0 : rs.size();
+    }
+
+    @Override
+    public int onDelete(String collection, GToolExample entity) {
+        log.info("{}{} onDelete: {}", collectionPrefix, collection, entity.getId());
+        String id = entity.getId();
+        int i = 0;
+        try {
+            //i = hybridSearch.deleteDocumentsByIdFilter(getMappedIndexName(collection), id);
+            i = hybridSearch.delete(getMappedIndexName(collection), List.of(id));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return i;
+    }
+
+    @Override
+    public int onDeleteExamples(String collection, List<GToolExample> examples) {
+        log.info("{}{} onDelete size: {}", collectionPrefix, collection, examples.size());
+        List<String> ids = new ArrayList<>(examples.size());
+        for (GToolExample example : examples) {
+            ids.add(example.getId());
+        }
+        int i = 0;
+        try {
+            i = hybridSearch.delete(getMappedIndexName(collection), ids);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return i;
+    }
+
+    @Override
+    public int onDeleteExamplesByIds(String collection, List<String> ids) {
+        log.info("{}{} onDelete size: {}", collectionPrefix, collection, ids.size());
+        int i = 0;
+        try {
+            i = hybridSearch.delete(getMappedIndexName(collection), ids);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return i;
+    }
+
+    @Override
+    public int onUpdate(String collection, GToolExample entity, GTool tool) throws IOException, InterruptedException {
+        log.info("{}{} onUpdate: {}", collectionPrefix, collection, entity.getId());
+        int i = hybridSearch.delete(getMappedIndexName(collection), List.of(entity.getId()));
+        List<DocResp> rs = hybridSearch.add(getMappedIndexName(collection), toDocReq(collection, entity, tool));
+        return rs == null ? 0 : rs.size();
+    }
+
+    @Override
+    public int onDeleteAllExamples(String collection) {
+        boolean b = false;
+        try {
+            if (isOne2One(collection)) {
+                //b = hybridSearch.clearCollection(getMappedIndexName(collection));
+                String filter = "metadata[\"__source\"] == \"" + EXAMPLE + "\"" ;
+                Map<String, Object> filterMap = Map.of("filterExpression",  filter);
+                int r = hybridSearch.deleteDocumentsByFilter(getMappedIndexName(collection), filterMap);
+                return r;
+            } else {
+                //TODO 特殊处理
+                //  "filterExpression": "metadata[\"__cid\"] == \"qas_2\""
+                String filter = "metadata[\"__source\"] == \"" + EXAMPLE + "\"" +" AND "+
+                        "metadata[\"__cid\"] == \"" + collection + "\"";
+                Map<String, Object> filterMap = Map.of("filterExpression", filter);
+                int r = hybridSearch.deleteDocumentsByFilter(getMappedIndexName(collection), filterMap);
+                return r;
+            }
+        } catch (Exception e) {
+            log.error("Collection {} onDeleteAll: {}", collection, e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 283 - 0
server/src/main/java/com/giantan/data/dk/repository/DkExampleRepository.java

@@ -0,0 +1,283 @@
+package com.giantan.data.dk.repository;
+
+/*
+
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.giantan.ai.util.id.IdGenerator;
+import com.giantan.ai.util.id.UlidGenerator;
+import com.giantan.data.dk.constant.DkExampleConfig;
+import com.giantan.data.dk.dto.GToolExample;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.sql.Array;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Repository
+public class DkExampleRepository implements IDkExampleRepository {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(DkExampleRepository.class);
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    @Autowired
+    protected JdbcTemplate jdbc;
+
+//    @Autowired
+//    private IDkIndexer indexer;
+
+    protected String schema = "dkdb";
+    protected String tablePrefix = "example";
+    protected String indexPrefix = "example";
+
+    protected String refTablePrefix = "dk";
+    private IdGenerator idGenerator = new UlidGenerator();
+
+    protected String tableName(String collId) {
+        return tablePrefix + "_" + collId;// collId.replace("-", "")
+    }
+
+    protected String refTableName(String collId) {
+        return refTablePrefix + "_" + collId;
+    }
+
+    public void setIdGenerator(IdGenerator idGenerator) {
+        this.idGenerator = idGenerator;
+    }
+
+    public void setJdbcTemplate(JdbcTemplate jdbc) {
+        this.jdbc = jdbc;
+    }
+
+    // 工具方法:对象转 JSON
+    private String toJson(Object obj) {
+        try {
+            return obj != null ? mapper.writeValueAsString(obj) : null;
+        } catch (Exception e) {
+            throw new RuntimeException("JSON serialization failed", e);
+        }
+    }
+
+    private <T> T readJson(ResultSet rs, String column, Class<T> clazz) throws SQLException {
+        String json = rs.getString(column);
+        try {
+            return (json != null) ? mapper.readValue(json, clazz) : null;
+        } catch (Exception e) {
+            throw new SQLException("Failed to parse JSON for column: " + column, e);
+        }
+    }
+
+    private String[] toArray(List<String> list) {
+        return list == null ? new String[0] : list.toArray(new String[0]);
+    }
+
+
+    public GToolExample toExample(ResultSet rs) throws SQLException {
+        GToolExample ex = new GToolExample();
+        ex.setId(rs.getString("id"));
+        ex.setToolId(rs.getString("tool_gid"));
+        ex.setUserQuery(rs.getString("user_query"));
+        ex.setCreatedAt(rs.getObject("created_at", OffsetDateTime.class));
+        try {
+            String inputJson = rs.getString("tool_input");
+            String outputJson = rs.getString("tool_output");
+            ex.setToolInput(inputJson != null ? mapper.readValue(inputJson, Map.class) : null);
+            ex.setToolOutput(outputJson != null ? mapper.readValue(outputJson, Map.class) : null);
+        } catch (Exception e) {
+            throw new SQLException("Error parsing JSON fields", e);
+        }
+        return ex;
+    }
+
+    @Override
+    public int createTable(String collId) {
+        List<String> sqls = DkExampleConfig.createTable(schema, tableName(collId), refTableName(collId));
+        String sql2 = String.join("\n", sqls);
+        jdbc.execute(sql2);
+        return 1;
+    }
+
+    @Override
+    public int deleteTable(String collId) {
+        List<String> sqls = DkExampleConfig.dropTable(schema, tableName(collId));
+        String sql2 = String.join("\n", sqls);
+        jdbc.execute(sql2);
+        return 1;
+    }
+
+
+    public GToolExample insert(String collId, GToolExample example) {
+        String sql0 = """
+                INSERT INTO  %s.%s  (
+                        id, tool_gid, user_query, tool_input, tool_output, created_at
+                ) VALUES (
+                        ?, ?, ?, ?::jsonb, ?::jsonb, ?
+                )
+                RETURNING *;
+                """;
+        String sql = String.format(sql0, schema, tableName(collId));
+        // 自动生成ID(如果为空)
+        if (example.getId() == null || example.getId().isEmpty()) {
+            example.setId(idGenerator.generateId());
+        }
+
+        return jdbc.queryForObject(sql,
+                new Object[]{
+                        example.getId(),
+                        example.getToolId(),
+                        example.getUserQuery(),
+                        toJson(example.getToolInput()),
+                        toJson(example.getToolOutput()),
+                        example.getCreatedAt()
+                },
+                (rs, rowNum) -> toExample(rs)
+        );
+    }
+
+
+    @Override
+    public GToolExample save(String collId, GToolExample kvs) throws Throwable {
+        GToolExample ret = insert(collId, kvs);
+        return ret;
+    }
+
+    @Override
+    public GToolExample deleteById(String collId, String id) {
+        String sql = String.format("DELETE FROM %s.%s WHERE id = ? RETURNING *", schema, tableName(collId));
+        List<GToolExample> ret = jdbc.query(sql,
+                new Object[]{id},
+                (rs, rowNum) -> toExample(rs));
+        if (ret != null && !ret.isEmpty()) {
+            return ret.get(0);
+        }
+        return null;
+    }
+
+
+    @Override
+    public List<GToolExample> findAll(String collId) throws Throwable {
+        String sql = String.format("SELECT * FROM %s.%s", schema, tableName(collId));
+        List<GToolExample> rets = jdbc.query(sql, (rs, rowNum) -> toExample(rs));
+        return rets;
+    }
+
+    @Override
+    public GToolExample find(String collId, String id) throws Throwable {
+        String sql = String.format("SELECT * FROM %s.%s WHERE id = ?", schema, tableName(collId));
+        List<GToolExample> ret = jdbc.query(
+                sql,
+                new Object[]{id},
+                (rs, rowNum) -> toExample(rs)
+        );
+        if (ret == null || ret.isEmpty()) {
+            return null;
+        } else {
+            return ret.get(0);
+        }
+    }
+
+
+    @Override
+    public GToolExample update(String collId, GToolExample example) throws Throwable {
+        // 动态拼接要更新的字段
+        StringBuilder sql = new StringBuilder("UPDATE %s.%s SET ");
+        List<Object> params = new ArrayList<>();
+
+//        if (example.getToolId() != null) {
+//            sql.append("tool_gid = ?, ");
+//            params.add(example.getToolId());
+//        }
+        if (example.getUserQuery() != null) {
+            sql.append("user_query = ?, ");
+            params.add(example.getUserQuery());
+        }
+        if (example.getToolInput() != null) {
+            sql.append("tool_input = ?::jsonb, ");
+            params.add(toJson(example.getToolInput()));
+        }
+        if (example.getToolOutput() != null) {
+            sql.append("tool_output = ?::jsonb, ");
+            params.add(toJson(example.getToolOutput()));
+        }
+        if (example.getCreatedAt() != null) {
+            sql.append("created_at = ?, ");
+            params.add(example.getCreatedAt());
+        }
+
+        // 去掉最后一个逗号
+        if (params.isEmpty()) {
+            throw new IllegalArgumentException("No fields to update");
+        }
+        sql.setLength(sql.length() - 2);
+
+        sql.append(" WHERE id = ? RETURNING *;");
+        params.add(example.getId());
+
+        String sql2 = sql.toString();
+        sql2 = String.format(sql2, schema, tableName(collId));
+
+        return jdbc.queryForObject(sql2,
+                params.toArray(),
+                (rs, rowNum) -> toExample(rs)
+        );
+    }
+
+    @Override
+    public long deleteAll(String collId) {
+        String sql = String.format("DELETE FROM %s.%s", schema, tableName(collId));
+        return jdbc.update(sql);
+    }
+
+    @Override
+    public List<GToolExample> findByToolGid(String collId, String toolGid) {
+        String sql = String.format("SELECT * FROM %s.%s WHERE tool_gid = ?", schema, tableName(collId));
+        List<GToolExample> ret = jdbc.query(
+                sql,
+                new Object[]{toolGid},
+                (rs, rowNum) -> toExample(rs)
+        );
+        return ret;
+    }
+
+    @Override
+    public List<GToolExample> deleteByToolGid(String collId, String toolGid) {
+        String sql = String.format("DELETE FROM %s.%s WHERE tool_gid = ? RETURNING *", schema, tableName(collId));
+        List<GToolExample> ret = jdbc.query(sql,
+                new Object[]{toolGid},
+                (rs, rowNum) -> toExample(rs));
+        return ret;
+    }
+
+    @Override
+    public List<String> deleteByToolGids(String collId, List<String> toolGids) {
+        if (toolGids == null || toolGids.isEmpty()) {
+            return List.of();
+        }
+
+        String sql = String.format("""
+            DELETE FROM %s.%s
+            WHERE tool_id = ANY(?)
+            RETURNING id
+        """, schema, tableName(collId));
+
+        List<String> rets = jdbc.query(
+                con -> {
+                    PreparedStatement ps = con.prepareStatement(sql);
+                    Array gidArray = con.createArrayOf("varchar", toolGids.toArray());
+                    ps.setArray(1, gidArray);
+                    return ps;
+                },
+                (rs, rowNum) -> rs.getString("id")
+        );
+        return rets;
+    }
+}

+ 35 - 17
server/src/main/java/com/giantan/data/dk/repository/DkIndexer.java

@@ -4,14 +4,13 @@ import com.giantan.data.dk.constant.DkConstants;
 import com.giantan.data.dk.dto.GTool;
 import com.giantan.data.index.HybridIndexer;
 import com.giantan.data.index.IHybridSearch;
-import com.giantan.data.index.IndexConfig;
+import com.giantan.data.index.IndexUtils;
 import com.giantan.data.index.dto.DocReq;
 import com.giantan.data.index.dto.DocResp;
 //import com.giantan.data.kvs.repository.GEntity;
 //import com.giantan.data.kvs.repository.GEntityConfig;
-import com.giantan.data.qa.service.IQaCollectionService;
+import com.giantan.data.common.ICollectionMetaService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.stereotype.Repository;
 
 import java.io.IOException;
@@ -26,8 +25,9 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
     private static final org.slf4j.Logger log
             = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+    public static final String FIELD_PATH = "path";
 
-    String cellectionPrefix = "dks_";
+    String collectionPrefix = "dks_";
 
     String splitter = "\n";
 
@@ -36,7 +36,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
 
     //    ICollectionMapper collMapper;
-    IQaCollectionService collectionService;
+    ICollectionMetaService collectionService;
 
     public DkIndexer() {
         //collMapper = new DefaultCollectionMapper();
@@ -44,16 +44,16 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
     }
 
     @Override
-    public void init(IQaCollectionService collectionService) {
+    public void init(ICollectionMetaService collectionService) {
         // 根据 params的attributes
         setCollectionService(collectionService);
     }
 
-    public IQaCollectionService getCollectionService() {
+    public ICollectionMetaService getCollectionService() {
         return collectionService;
     }
 
-    public void setCollectionService(IQaCollectionService collectionService) {
+    public void setCollectionService(ICollectionMetaService collectionService) {
         this.collectionService = collectionService;
     }
 
@@ -65,6 +65,8 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 //        this.collMapper = collMapper;
 //    }
 
+
+
     private void buildMetadata(String collection, GTool entity, DocReq req) {
         Map<String, Object> metadata = req.getMetadata();
         if (metadata == null) {
@@ -73,6 +75,9 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
         }
         metadata.put(DOC_ID, entity.getId().toString());
         metadata.put(COLL_ID, collection);
+        if (entity.getPath() != null) {
+            metadata.put(FIELD_PATH, IndexUtils.normalize(entity.getPath()));
+        }
     }
 
     public String indexName(String coll) {
@@ -83,6 +88,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
         return prefix + coll;
     }
 
+    @Override
     public String getMappedIndexName(String collId) {
         String collName = collectionService.getCollectionName(Integer.parseInt(collId));
         String mapped = indexName(collName);
@@ -94,6 +100,18 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
         return mapped;
     }
 
+    @Override
+    public String getMappedIndexNameByName(String collName) {
+        String collId = collectionService.getCollectionId(collName)+"";
+        String mapped = indexName(collName);
+        Map<String, Object> attibutes = collectionService.getAttibutes(collId);
+        String configIndexName = getConfigIndexName(collId, attibutes);
+        if (configIndexName != null) {
+            mapped = configIndexName;
+        }
+        return mapped;
+    }
+
     public boolean isOne2One(String collId) {
         boolean isOne2One = true;
         Map<String, Object> attibutes = collectionService.getAttibutes(collId);
@@ -131,7 +149,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     private List<DocReq> toDocReq(String coll, GTool entity) {
         String chunkMode = getChunkMode(coll);
-        List<DocReq> rets = null;
+//        List<DocReq> rets = null;
 //        if (chunkMode.equals(IndexConfig.CHUNK_MODE_MULTIPLE)) {
 //            rets = toDocReq2(coll, entity);
 //        } else if (chunkMode.equals(IndexConfig.CHUNK_MODE_CUSTOM)) {
@@ -140,7 +158,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 //            rets = toDocReq1(coll, entity);
 //        }
 
-        rets = toDocReq1(coll, entity);
+        List<DocReq> rets = toDocReq1(coll, entity);
 
         return rets;
     }
@@ -357,14 +375,14 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onAdd(String collection, GTool entity) throws IOException, InterruptedException {
-        log.info("{}{} onAdd: {}", cellectionPrefix, collection, entity.getName());
+        log.info("{}{} onAdd: {}", collectionPrefix, collection, entity.getName());
         List<DocResp> rs = hybridSearch.add(getMappedIndexName(collection), toDocReq(collection, entity));
         return rs == null ? 0 : rs.size();
     }
 
     @Override
     public int onAdd(String collection, List<GTool> entities) throws IOException, InterruptedException {
-        log.info("{}{} onAdd: size = {}", cellectionPrefix, collection, entities.size());
+        log.info("{}{} onAdd: size = {}", collectionPrefix, collection, entities.size());
         List<DocReq> ls = new ArrayList<>();
         for (GTool e : entities) {
             List<DocReq> docReqs = toDocReq(collection, e);
@@ -376,7 +394,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onDelete(String collection, GTool entity) {
-        log.info("{}{} onDelete: {}", cellectionPrefix, collection, entity.getName());
+        log.info("{}{} onDelete: {}", collectionPrefix, collection, entity.getName());
         String gid = entity.getGid();
         int i = 0;
         try {
@@ -389,7 +407,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onDelete(String collection, List<GTool> entities) {
-        log.info("{}{} onDelete: size = {}", cellectionPrefix, collection, entities.size());
+        log.info("{}{} onDelete: size = {}", collectionPrefix, collection, entities.size());
         int count = 0;
         try {
             for (GTool e : entities) {
@@ -404,7 +422,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onUpdate(String collection, GTool entity) throws IOException, InterruptedException {
-        log.info("{}{} onUpdate: {}", cellectionPrefix, collection, entity.getName());
+        log.info("{}{} onUpdate: {}", collectionPrefix, collection, entity.getName());
 
         int i = hybridSearch.deleteDocumentsByIdFilter(getMappedIndexName(collection), entity.getGid());
         List<DocResp> rs = hybridSearch.add(getMappedIndexName(collection), toDocReq(collection, entity));
@@ -413,7 +431,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onUpdateField(String collection, GTool entity, String field) {
-        log.info("{}{} onUpdateField: ret = {}, field = {}", cellectionPrefix, collection, entity.getGid(), field);
+        log.info("{}{} onUpdateField: ret = {}, field = {}", collectionPrefix, collection, entity.getGid(), field);
         int count = 0;
         try {
             int i = hybridSearch.deleteDocumentsByIdFilter(getMappedIndexName(collection), entity.getGid());
@@ -428,7 +446,7 @@ public class DkIndexer extends HybridIndexer implements IDkIndexer {
 
     @Override
     public int onUpdateField(String collection, List<GTool> entities, String field) {
-        log.info("{}{} onUpdateField: size = {}, field = {}", cellectionPrefix, collection, entities.size(), field);
+        log.info("{}{} onUpdateField: size = {}, field = {}", collectionPrefix, collection, entities.size(), field);
         int count = 0;
         try {
             for (GTool e : entities) {

+ 356 - 93
server/src/main/java/com/giantan/data/dk/repository/DkRepository.java

@@ -1,24 +1,26 @@
 package com.giantan.data.dk.repository;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.giantan.ai.util.PathUtils;
 import com.giantan.ai.util.id.IdGenerator;
 import com.giantan.ai.util.id.UlidGenerator;
 import com.giantan.data.dk.constant.DkConfig;
 import com.giantan.data.dk.dto.GTool;
 import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 
+import com.giantan.data.kvs.repository.GEntityConfig;
+import com.giantan.data.util.JdbcUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Repository;
 
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 // copy from GIndexedRepository.java
 
@@ -33,8 +35,8 @@ public class DkRepository implements IGDkRepository {
     @Autowired
     protected JdbcTemplate jdbc;
 
-    @Autowired
-    private IDkIndexer indexer;
+//    @Autowired
+//    private IDkIndexer indexer;
 
     protected String schema = "dkdb";
     protected String tablePrefix = "dk";
@@ -43,7 +45,6 @@ public class DkRepository implements IGDkRepository {
     private IdGenerator idGenerator = new UlidGenerator();
 
 
-
     public DkRepository() {
 
     }
@@ -72,13 +73,13 @@ public class DkRepository implements IGDkRepository {
         this.indexPrefix = indexPrefix;
     }
 
-    public IDkIndexer getIndexer() {
-        return indexer;
-    }
-
-    public void setIndexer(IDkIndexer indexer) {
-        this.indexer = indexer;
-    }
+//    public IDkIndexer getIndexer() {
+//        return indexer;
+//    }
+//
+//    public void setIndexer(IDkIndexer indexer) {
+//        this.indexer = indexer;
+//    }
 
 //    protected String fullTableName(String collId) {
 //        return schema + "." + tablePrefix + "_" + collId;// collId.replace("-", "")
@@ -190,7 +191,7 @@ public class DkRepository implements IGDkRepository {
         if (ret == null) {
             return null;
         }
-        int r2 = indexer.onAdd(collId, ret);
+        //int r2 = indexer.onAdd(collId, ret);
         return ret;
     }
 
@@ -207,11 +208,6 @@ public class DkRepository implements IGDkRepository {
         return rets;
     }
 
-    //    @Override
-    public List<GBaseKeyValue> findAllByIds(String collId, List<Integer> ids) throws Throwable {
-        return List.of();
-    }
-
     @Override
     public GTool find(String collId, int id) throws Throwable {
         String sql = String.format("SELECT * FROM %s.%s WHERE id = ?", schema, tableName(collId));
@@ -227,6 +223,22 @@ public class DkRepository implements IGDkRepository {
         }
     }
 
+    @Override
+    public List<GTool> findAllByIds(String collId, List<Integer> ids) throws Throwable {
+        if (ids == null || ids.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String placeholders = ids.stream().map(id -> "?").collect(Collectors.joining(","));
+        String sql = String.format("SELECT * FROM %s.%s WHERE id IN (" + placeholders + ")", schema, tableName(collId));
+
+        List<GTool> rets = jdbc.query(
+                sql,
+                ids.toArray(),
+                (rs, rowNum) -> toTool(rs)
+        );
+        return rets;
+    }
+
     @Override
     public GTool findByGid(String collId, String gid) throws Throwable {
         String sql = String.format("SELECT * FROM %s.%s WHERE gid = ?", schema, tableName(collId));
@@ -243,31 +255,40 @@ public class DkRepository implements IGDkRepository {
     }
 
     @Override
-    public int deleteById(String collId, int id) {
+    public GTool deleteById(String collId, int id) {
         String sql = String.format("DELETE FROM %s.%s WHERE id = ? RETURNING *", schema, tableName(collId));
         List<GTool> ret = jdbc.query(sql,
                 new Object[]{id},
                 (rs, rowNum) -> toTool(rs));
         if (ret == null || ret.isEmpty()) {
-            return 0;
+            return null;
         }
-
-        int r2 = indexer.onDelete(collId, ret.get(0));
-        return 1;
+        return ret.get(0);
+//        int r2 = indexer.onDelete(collId, ret.get(0));
+//        return 1;
     }
 
     @Override
-    public int deleteByGid(String collId, String gid) {
+    public GTool deleteByGid(String collId, String gid) {
         String sql = String.format("DELETE FROM %s.%s WHERE gid = ? RETURNING *", schema, tableName(collId));
         List<GTool> ret = jdbc.query(sql,
                 new Object[]{gid},
                 (rs, rowNum) -> toTool(rs));
         if (ret == null || ret.isEmpty()) {
-            return 0;
+            return null;
         }
+        return ret.get(0);
+//        int r2 = indexer.onDelete(collId, ret.get(0));
+//        return 1;
+    }
 
-        int r2 = indexer.onDelete(collId, ret.get(0));
-        return 1;
+    @Override
+    public List<GTool> deleteByName(String collId, String name) {
+        String sql = String.format("DELETE FROM %s.%s WHERE name = ? RETURNING *", schema, tableName(collId));
+        List<GTool> ret = jdbc.query(sql,
+                new Object[]{name},
+                (rs, rowNum) -> toTool(rs));
+        return ret;
     }
 
     @Override
@@ -332,7 +353,7 @@ public class DkRepository implements IGDkRepository {
         } else if (tool.getId() != null) {
             sql.append(" WHERE id = ? RETURNING *;");
             params.add(tool.getId());
-        }else{
+        } else {
             return null;
         }
 
@@ -344,91 +365,252 @@ public class DkRepository implements IGDkRepository {
                 (rs, rowNum) -> toTool(rs)
         );
 
-        int r2 = indexer.onUpdate(collId, ret);
+        //int r2 = indexer.onUpdate(collId, ret);
         return ret;
     }
 
 
-    //////////////////////////////
-    //    @Override
-    public List<GBaseKeyValue> findByName(String collId, String name) throws Throwable {
-        return List.of();
+    @Override
+    public List<GTool> findByName(String collId, String name) throws Throwable {
+        String sql = String.format("SELECT * FROM %s.%s WHERE name = ?", schema, tableName(collId));
+        List<GTool> rets = jdbc.query(
+                sql,
+                new Object[]{name},
+                (rs, rowNum) -> toTool(rs)
+        );
+        return rets;
+        //List<GBaseKeyValue> rets = GConverter.fromEntity(query);
+        //return rets;
     }
 
-    //    @Override
-    public List<GBaseKeyValue> findByPath(String collId, String path) {
-        return List.of();
+    @Override
+    public List<GTool> findByPath(String collId, String path) {
+        String sql = String.format("SELECT * FROM %s.%s WHERE path = ?", schema, tableName(collId));
+        List<GTool> rets = jdbc.query(
+                sql,
+                new Object[]{path},
+                (rs, rowNum) -> toTool(rs)
+        );
+        return rets;
+        //List<GBaseKeyValue> rets = GConverter.fromEntity(query);
+        //return rets;
     }
 
-    //    @Override
-    public List<GBaseKeyValue> findByPathPrefix(String collId, String prefix) {
-        return List.of();
+    @Override
+    public List<GTool> findByPathPrefix(String collId, String prefix) {
+        String like = prefix + "%";
+        String sql = String.format("SELECT * FROM %s.%s WHERE path LIKE ?", schema, tableName(collId));
+        List<GTool> rets = jdbc.query(
+                sql,
+                new Object[]{like},
+                (rs, rowNum) -> toTool(rs)
+        );
+        return rets;
     }
 
-    //    @Override
+    @Override
     public long count(String collId) {
-        return 0;
+        String sql = String.format("SELECT COUNT(*) FROM %s.%s", schema, tableName(collId));
+        Long l = jdbc.queryForObject(sql, Long.class);
+        return l;
     }
 
 
-    //    @Override
+    @Override
     public long delete(String collId, List<Integer> ids) {
-        return 0;
-    }
+        if (ids == null || ids.isEmpty()) {
+            return 0L;
+        }
+        String placeholders = ids.stream()
+                .map(id -> "?")
+                .collect(Collectors.joining(","));
 
-    //    @Override
-    public long deleteAll(String collId) {
-        return 0;
-    }
+        String sql = String.format("DELETE FROM %s.%s WHERE id IN (" + placeholders + ")", schema, tableName(collId));
 
-    //    @Override
-    public GBaseKeyValue removeAttribute(String collId, Integer id, List<String> keys) throws Throwable {
-        return null;
-    }
+        Object[] params = ids.toArray();
 
-    //    @Override
-    public GBaseKeyValue updateAttribute(String collId, Integer id, String key, Object value) throws Throwable {
-        return null;
+        List<GTool> rets = jdbc.query(sql, params,
+                (rs, rowNum) -> toTool(rs));
+        if (rets == null || rets.isEmpty()) {
+            return 0;
+        }
+        //int r2 = indexer.onDelete(collId, rs);
+        return rets.size();
     }
 
-    //    @Override
-    public GBaseKeyValue updateAttribute(String collId, Integer id, Map<String, Object> attributes) throws Throwable {
-        return null;
+    @Override
+    public long deleteAll(String collId) {
+        String sql = String.format("DELETE FROM %s.%s", schema, tableName(collId));
+        int r = jdbc.update(sql);
+        //int r2 = indexer.onDeleteAll(collId);
+        return r;
     }
 
-    //    @Override
-    public Object findAttribute(String collId, Integer id, String attribute) throws SQLException {
-        return null;
-    }
 
-    //    @Override
-    public long update(String collId, List<GBaseKeyValue> kvs) throws Throwable {
-        return 0;
-    }
+//    //    @Override
+//    public GBaseKeyValue removeAttribute(String collId, Integer id, List<String> keys) throws Throwable {
+//        return null;
+//    }
+//
+//    //    @Override
+//    public GBaseKeyValue updateAttribute(String collId, Integer id, String key, Object value) throws Throwable {
+//        return null;
+//    }
+//
+//    //    @Override
+//    public GBaseKeyValue updateAttribute(String collId, Integer id, Map<String, Object> attributes) throws Throwable {
+//        return null;
+//    }
+//
+//    //    @Override
+//    public Object findAttribute(String collId, Integer id, String attribute) throws SQLException {
+//        return null;
+//    }
+//
+//    //    @Override
+//    public long update(String collId, List<GBaseKeyValue> kvs) throws Throwable {
+//        return 0;
+//    }
+//
 
-    //    @Override
+    @Override
     public List<Map<String, Object>> getAllEntities(String collId, List<String> fields) {
-        return List.of();
+        if (fields == null || fields.size() == 0) {
+            return Collections.emptyList();
+        }
+        String placeholders = String.join(", ", fields);
+
+        String sql = String.format("SELECT %s FROM %s.%s ", placeholders, schema, tableName(collId));
+        // 使用 queryForList 获取结果,返回一个 List<Map<String, Object>>
+        return jdbc.queryForList(sql);
     }
 
-    //    @Override
+    @Override
     public List<Map<String, Object>> getAllEntities(String collId, List<String> fields, String whereClause) {
-        return List.of();
+        if (fields == null || fields.size() == 0) {
+            return Collections.emptyList();
+        }
+        String placeholders = String.join(", ", fields);
+
+        String sql = String.format("SELECT %s FROM %s.%s WHERE %s", placeholders, schema, tableName(collId), whereClause);
+        // 使用 queryForList 获取结果,返回一个 List<Map<String, Object>>
+        return jdbc.queryForList(sql);
     }
 
-    //    @Override
-    public List<String> appendArrayField(String collId, Integer id, String field, List<String> values) {
-        return List.of();
+    @Override
+    public GTool appendArrayField(String collId, Integer id, String field, List<String> values) {
+        if (field == null || values == null || values.isEmpty()) {
+            return null;
+        }
+
+        String sql = String.format(
+                "UPDATE %s.%s SET %s = %s || ?::text[] WHERE id = ? RETURNING *",
+                schema,
+                tableName(collId),
+                field,
+                field
+        );
+
+        List<GTool> rets = jdbc.query(
+                con -> {
+                    PreparedStatement ps = con.prepareStatement(sql);
+                    // 创建 SQL Array 并绑定到参数
+                    //Array sqlArray = con.createArrayOf("text", values.toArray(new String[0]));
+                    //ps.setArray(1, sqlArray);
+                    JdbcUtils.setStringArray(ps, 1, values);
+                    ps.setInt(2, id);
+                    return ps;
+                }, (rs, rowNum) -> toTool(rs));
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        GTool entity = rets.get(0);
+        //indexer.onUpdateField(collId, entity, field);
+        //return getArrayField(entity, field);
+        return entity;
+    }
+
+    private List<String> getArrayField(GTool entity, String field) {
+        List<String> ret2 = null;
+//        if (field.equalsIgnoreCase(GEntityConfig.ALTLABELS)) {
+//            ret2 = entity.getAltlabels();
+//        } else if (field.equalsIgnoreCase(GEntityConfig.TAGS)) {
+//            ret2 = entity.getTags();
+//        }
+        if (field.equalsIgnoreCase(GEntityConfig.TAGS)) {
+            ret2 = entity.getTags();
+        }
+        return ret2;
     }
 
-    //    @Override
-    public List<String> setArrayField(String collId, Integer id, String field, List<String> values) {
-        return List.of();
+    @Override
+    public GTool setArrayField(String collId, Integer id, String field, List<String> values) {
+        if (field == null || values == null) {
+            return null;
+        }
+
+        String sql = String.format(
+                "UPDATE %s.%s SET %s = ? WHERE id = ? RETURNING *",
+                schema, tableName(collId), field);
+
+        List<GTool> rets = jdbc.query(con -> {
+            PreparedStatement ps = con.prepareStatement(sql);
+            // 第 1 个参数:安全设置 array
+            JdbcUtils.setStringArray(ps, 1, values);
+            // 第 2 个参数:id
+            ps.setInt(2, id);
+            return ps;
+        }, (rs, rowNum) -> toTool(rs));
+
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        GTool entity = rets.get(0);
+        //indexer.onUpdateField(collId, entity, field);
+        //return getArrayField(entity, field);
+        return entity;
     }
 
-    //    @Override
-    public List<String> removeArrayField(String collId, Integer id, String field, List<String> values) {
-        return List.of();
+
+    @Override
+    public GTool removeArrayField(String collId, Integer id, String field, List<String> values) {
+        if (field == null || values == null || values.size() == 0) {
+            return null;
+        }
+
+        // 构建动态 SQL:array_remove(array_remove(...), ...)
+        String sql1 = String.format("UPDATE %s.%s SET %s = ", schema, tableName(collId), field);
+        StringBuilder bs = new StringBuilder(sql1);
+
+        StringBuilder bs1 = new StringBuilder();
+        bs1.append("array_remove(").append(field).append(",").append("?)");
+        for (int i = 1; i < values.size(); i++) {
+            bs1.insert(0, "array_remove(");
+            bs1.append(", ?)");
+        }
+
+        //bs.append(bs1).append(" WHERE id = ? RETURNING ").append(field);
+        bs.append(bs1).append(" WHERE id = ? RETURNING *");
+
+        // 参数列表
+        Object[] args = new Object[values.size() + 1];
+        for (int i = 0; i < values.size(); i++) {
+            args[i] = values.get(i);
+        }
+        args[values.size()] = id;
+
+        List<GTool> rets = jdbc.query(
+                bs.toString(),
+                args,
+                (rs, rowNum) -> toTool(rs));
+
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        GTool entity = rets.get(0);
+        //indexer.onUpdateField(collId, entity, field);
+        //return getArrayField(entity, field);
+        return entity;
     }
 
     @Override
@@ -447,23 +629,104 @@ public class DkRepository implements IGDkRepository {
         return 1;
     }
 
-    //    @Override
-    public int deleteByPath(String collId, String path) {
-        return 0;
+    @Override
+    public List<GTool> deleteByPath(String collId, String path) {
+        String sql = String.format("DELETE FROM %s.%s WHERE path = ? RETURNING *", schema, tableName(collId));
+        //return jdbc.update(sql, path);
+        List<GTool> rets = jdbc.query(sql, new Object[]{path}, (rs, rowNum) -> toTool(rs));
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        //int r = indexer.onDelete(collId, rets);
+        //return rets.size();
+        return rets;
+    }
+
+    @Override
+    public List<GTool> deleteByPrefix(String collId, String prefix) {
+        String like = prefix + "%";
+        String sql = String.format("DELETE FROM %s.%s WHERE path LIKE ? RETURNING *", schema, tableName(collId));
+        //return jdbc.update(sql, like);
+        List<GTool> rets = jdbc.query(sql, new Object[]{like}, (rs, rowNum) -> toTool(rs));
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        //int r = indexer.onDelete(collId, rets);
+        //return rets.size();
+        return rets;
     }
 
-    //    @Override
-    public int deleteByPrefix(String collId, String path) {
-        return 0;
+    @Override
+    public List<GTool> updatePathPrefix(String collId, String oldPrefix, String newPrefix) {
+        String table = tableName(collId);
+        String sql = String.format("""
+                    UPDATE %s.%s
+                    SET path = regexp_replace(path, ?, ?)
+                    WHERE path LIKE ?
+                    RETURNING * ;
+                """, schema, table);
+
+        // 正则必须是 ^/a/b/
+        String regex = "^" + oldPrefix;
+        String like = oldPrefix + "%";
+
+        //return jdbc.update(sql, regex, newPrefix, like);
+        List<GTool> rets = jdbc.query(connection -> {
+            PreparedStatement ps = connection.prepareStatement(sql);
+            ps.setString(1, regex);
+            ps.setString(2, newPrefix);
+            ps.setString(3, like);
+            return ps;
+        }, (rs, rowNum) -> toTool(rs));
+        //if (rets == null || rets.isEmpty()) {
+        //    return 0;
+        //}
+        //indexer.onUpdateField(collId, rets, "path");
+        return rets;
     }
 
-    //    @Override
-    public int deletePathAndDescendants(String collId, String path) {
-        return 0;
+    //DELETE FROM %s.%s
+    //WHERE (path LIKE ? OR path = '/a')
+    //RETURNING *;
+    // 上述的deleteByPathPrefix 只要是前缀都删除  例如 删 /ab  那么 /abc 也删了
+    // 而该方法 不会
+    @Override
+    public List<GTool> deletePathAndDescendants(String collId, String path) {
+        String p1 = PathUtils.normalize(path);
+        String like = p1 + "/" + "%";
+        String sql = String.format("DELETE FROM %s.%s WHERE (path = ? OR path LIKE ?) RETURNING *", schema, tableName(collId));
+        //return jdbc.update(sql, like);
+        List<GTool> rets = jdbc.query(sql, new Object[]{p1, like}, (rs, rowNum) -> toTool(rs));
+        if (rets == null || rets.isEmpty()) {
+            return null;
+        }
+        //int r = indexer.onDelete(collId, rets);
+        //return rets.size();
+        return rets;
     }
 
-    //    @Override
-    public List<GBaseKeyValue> getChildren(String collId, String parent) {
-        return List.of();
+    @Override
+    public List<GTool> getChildren(String collId, String parentPath) {
+        String parent = PathUtils.normalize(parentPath);
+
+        // 父路径深度
+        int parentDepth = parent.equals("/") ? 1 : parent.split("/").length;
+
+        String sql = String.format(
+                "SELECT * FROM %s.%s " +
+                        "WHERE path LIKE ? " +
+                        "AND path <> ? " +
+                        "AND (length(path) - length(replace(path, '/', ''))+1) = ?",
+                schema,
+                tableName(collId)
+        );
+
+        // 直接子目录:path 以 parent + '/' 开头,深度 = parentDepth + 1
+        String prefix = parent.equals("/") ? "/%" : parent + "/%";
+        List<GTool> rows = jdbc.query(sql, new Object[]{prefix, parent, parentDepth + 1}, (rs, rowNum) -> toTool(rs));
+        //List<GBaseKeyValue> rets = GConverter.fromEntity(rows);
+        //return rets;
+        return rows;
     }
+
 }

+ 26 - 0
server/src/main/java/com/giantan/data/dk/repository/IDkExampleIndexer.java

@@ -0,0 +1,26 @@
+package com.giantan.data.dk.repository;
+
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.dto.GToolExample;
+import com.giantan.data.common.ICollectionMetaService;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface IDkExampleIndexer {
+
+    // 用于获取collection的attributes 信息
+    void init(ICollectionMetaService collectionService);
+
+    int onAdd(String collection, GToolExample entity, GTool tool) throws IOException, InterruptedException;
+
+    int onDelete(String collection, GToolExample entity);
+
+    int onDeleteExamples(String collection, List<GToolExample> entity);
+
+    int onDeleteExamplesByIds(String collection, List<String> ids);
+
+    int onUpdate(String collection, GToolExample entity, GTool tool) throws IOException, InterruptedException;
+
+    int onDeleteAllExamples(String collection);
+}

+ 35 - 0
server/src/main/java/com/giantan/data/dk/repository/IDkExampleRepository.java

@@ -0,0 +1,35 @@
+package com.giantan.data.dk.repository;
+
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.dto.GToolExample;
+
+import java.util.List;
+
+public interface IDkExampleRepository {
+
+    int createTable(String collId);
+
+    int deleteTable(String collId);
+
+    GToolExample save(String collId, GToolExample kvs) throws Throwable;
+
+    GToolExample deleteById(String collId, String id);
+
+    //GToolExample deleteByGid(String collId, String id);
+
+    List<GToolExample> findAll(String collId) throws Throwable;
+
+    GToolExample find(String collId, String id) throws Throwable;
+
+    //GToolExample findByGid(String collId, String id) throws Throwable;
+
+    GToolExample update(String collId, GToolExample tool) throws Throwable;
+
+    long deleteAll(String collId);
+
+    List<GToolExample> findByToolGid(String collId, String toolGid);
+
+    List<GToolExample> deleteByToolGid(String collId, String toolGid);
+
+    List<String> deleteByToolGids(String collId, List<String> toolGids);
+}

+ 6 - 2
server/src/main/java/com/giantan/data/dk/repository/IDkIndexer.java

@@ -1,13 +1,17 @@
 package com.giantan.data.dk.repository;
 
 import com.giantan.data.dk.dto.GTool;
-import com.giantan.data.qa.service.IQaCollectionService;
+import com.giantan.data.common.ICollectionMetaService;
 
 import java.io.IOException;
 import java.util.List;
 
 public interface IDkIndexer {
 
+    String getMappedIndexName(String collId);
+
+    String getMappedIndexNameByName(String collName);
+
     int onAdd(String collection, GTool entity) throws IOException, InterruptedException;
 
     int onAdd(String collection, List<GTool> entities) throws IOException, InterruptedException;
@@ -30,7 +34,7 @@ public interface IDkIndexer {
 
 
     // 用于获取collection的attributes 信息
-    void init(IQaCollectionService collectionService);
+    void init(ICollectionMetaService collectionService);
 
     int clearCollection(String collection) throws IOException, InterruptedException;
 }

+ 48 - 42
server/src/main/java/com/giantan/data/dk/repository/IGDkRepository.java

@@ -3,6 +3,7 @@ package com.giantan.data.dk.repository;
 import com.giantan.data.dk.dto.GTool;
 
 import java.util.List;
+import java.util.Map;
 
 public interface IGDkRepository {
 
@@ -14,37 +15,65 @@ public interface IGDkRepository {
 
     GTool update(String collId, GTool tool) throws Throwable;
 
+    List<GTool> findByName(String collId, String name) throws Throwable;
+
+    List<GTool> findByPath(String collId, String path);
+
+    List<GTool> findByPathPrefix(String collId, String prefix);
+
+    long count(String collId);
+
+    long delete(String collId, List<Integer> ids);
+
+    long deleteAll(String collId);
+
+    List<GTool> findAllByIds(String collId, List<Integer> ids) throws Throwable;
+
+    List<Map<String, Object>> getAllEntities(String collId, List<String> fields);
+
+    List<Map<String, Object>> getAllEntities(String collId, List<String> fields, String whereClause);
+
+    GTool appendArrayField(String collId, Integer id, String field, List<String> values);
+
+    GTool setArrayField(String collId, Integer id, String field, List<String> values);
+
+    GTool removeArrayField(String collId, Integer id, String field, List<String> values);
+
     int createTable(String collId);
 
     int deleteTable(String collId);
 
     GTool save(String collId, GTool kvs) throws Throwable;
 
-    int deleteById(String collId, int id);
+    GTool deleteById(String collId, int id);
+
+    GTool deleteByGid(String collId, String gid);
+
+    List<GTool> deleteByName(String collId, String name);
+
+    List<GTool> deleteByPath(String collId, String path);
+
+    List<GTool> deleteByPrefix(String collId, String prefix);
+
+    List<GTool> updatePathPrefix(String collId, String oldPrefix, String newPrefix);
+
+    //DELETE FROM %s.%s
+    //WHERE (path LIKE ? OR path = '/a')
+    //RETURNING *;
+    // 上述的deleteByPathPrefix 只要是前缀都删除  例如 删 /ab  那么 /abc 也删了
+    // 而该方法 不会
+    List<GTool> deletePathAndDescendants(String collId, String path);
+
+    ///QQQ 这个 getChildren 仅适用于 path 类似 "/a/b" 不以 "/"结尾的path
+    List<GTool> getChildren(String collId, String parentPath);
+
+
 
-    int deleteByGid(String collId, String gid);
-//    GBaseKeyValue save(String collId, GBaseKeyValue kvs) throws Throwable;
 //
 //    List<Integer> saveAll(String collId, List<GBaseKeyValue> kvs) throws Throwable;
 //
 //    List<GBaseKeyValue> findAllByIds(String collId, List<Integer> ids) throws Throwable;
 //
-//    List<GBaseKeyValue> findByName(String collId, String name) throws Throwable;
-//
-//    List<GBaseKeyValue> findByPath(String collId, String path);
-//
-//    List<GBaseKeyValue> findByPathPrefix(String collId, String prefix);
-//
-//    long count(String collId);
-//
-//    long delete(String collId, Integer id);
-//
-//    long delete(String collId, List<Integer> ids);
-//
-//    long deleteAll(String collId);
-//
-//    GBaseKeyValue update(String collId, GBaseKeyValue kv) throws Throwable;
-//
 //    GBaseKeyValue removeAttribute(String collId, Integer id, List<String> keys) throws Throwable;
 //
 //    GBaseKeyValue updateAttribute(String collId, Integer id, String key, Object value) throws Throwable;
@@ -55,28 +84,5 @@ public interface IGDkRepository {
 //
 //    long update(String collId, List<GBaseKeyValue> kvs) throws Throwable;
 //
-//    List<Map<String, Object>> getAllEntities(String collId, List<String> fields);
-//
-//    List<Map<String, Object>> getAllEntities(String collId, List<String> fields, String whereClause);
-//
-//    List<String> appendArrayField(String collId, Integer id, String field, List<String> values);
-//
-//    List<String> setArrayField(String collId, Integer id, String field, List<String> values);
-//
-//    List<String> removeArrayField(String collId, Integer id, String field, List<String> values);
-//
 
-//
-//    int deleteByPath(String collId, String path);
-//
-//    int deleteByPrefix(String collId, String path);
-//
-//    //DELETE FROM %s.%s
-//    //WHERE (path LIKE ? OR path = '/ab')
-//    //RETURNING *;
-//    // 上述的deleteByPrefix 只要是前缀都删除  例如 删 /ab  那么 /abc 也删了
-//    // 而该方法 不会
-//    int deletePathAndDescendants(String collId, String path);
-//
-//    List<GBaseKeyValue> getChildren(String collId, String parent);
 }

+ 51 - 5
server/src/main/java/com/giantan/data/dk/service/DkCollectionService.java

@@ -6,7 +6,7 @@ import com.giantan.data.kvs.repository.GEntityConfig;
 import com.giantan.data.kvs.repository.GRepository;
 import com.giantan.data.mds.service.CollectionInstance;
 import com.giantan.data.mds.service.impl.MdCores;
-import com.giantan.data.qa.service.IQaCollectionService;
+import com.giantan.data.common.ICollectionMetaService;
 import jakarta.annotation.PostConstruct;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -18,7 +18,7 @@ import java.util.List;
 import java.util.Map;
 
 @Service
-public class DkCollectionService implements IQaCollectionService {
+public class DkCollectionService implements ICollectionMetaService {
     private static final org.slf4j.Logger log
             = org.slf4j.LoggerFactory.getLogger(DkCollectionService.class);
 
@@ -34,12 +34,18 @@ public class DkCollectionService implements IQaCollectionService {
     @Autowired
     DkRepository dkRepository;
 
+    @Autowired
+    DkExampleRepository dkExampleRepository;
+
     @Autowired
     DkTaxonomyRepository dkTaxonomyRepository;
 
     @Autowired
     DkIndexer dkIndexer;
 
+//    @Autowired
+//    DkExampleIndexer dkExampleIndexer;
+
     @Autowired
     DkTaskRepository dkTaskRepository;
 
@@ -73,6 +79,7 @@ public class DkCollectionService implements IQaCollectionService {
         return -1;
     }
 
+    @Override
     public int getCollectionId(String name) {
         CollectionInstance instance = mdCores.getByName(name);
         if (instance != null) {
@@ -109,8 +116,24 @@ public class DkCollectionService implements IQaCollectionService {
         CollectionInstance instance = mdCores.getById(id);
         if (instance != null) {
             return instance.getName();
+        }else{
+            try {
+                GBaseKeyValue kv = collections.find(id);
+                if (kv != null){
+                    instance = CollectionInstance.build(kv.getIntId(), kv.getGid(), kv.getName(), kv.get(GEntityConfig.ATTRIBUTES));
+                    mdCores.put(kv.getName(), instance);
+                    return instance.getName();
+                }else{
+                    log.warn("Collection with id '" + id + "' not exist.");
+                    throw new ResponseStatusException(HttpStatus.NOT_FOUND,
+                            "Collection with id '" + id + "' does not exist.");
+                }
+            } catch (Throwable e) {
+                log.error("Failed to get collection by id={}", id, e);
+                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
+                        "Error occurred while retrieving collection with id '" + id + "'", e);
+            }
         }
-        return null;
     }
 
     public int getCollectionOrNew(String name) {
@@ -224,14 +247,21 @@ public class DkCollectionService implements IQaCollectionService {
         Integer intId = kv.getIntId();
         String id = kv.getId();
 
-        removeFromStore(name);
+        // 因为 constraint example_3_tool_gid_fkey on table dkdb.example_3 depends on table dkdb.dk_3
+        // Use DROP ... CASCADE to drop the dependent objects too.
+        // 或者 先删 example_3
+        try {
+            dkExampleRepository.deleteTable(id);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
 
-        //deleteEntryTable2(KvConfig.ENTRY_SCHEMA, getTableName(intId.toString()));
         try {
             dkRepository.deleteTable(id);
         } catch (Exception e) {
             log.error(e.getMessage());
         }
+
         try {
             dkTaxonomyRepository.deleteTable(id);
         } catch (Exception e) {
@@ -256,6 +286,8 @@ public class DkCollectionService implements IQaCollectionService {
             log.error(e.getMessage());
         }
 
+        removeFromStore(name);
+
         long deleted = collections.delete(intId);
 
         return deleted;
@@ -271,6 +303,12 @@ public class DkCollectionService implements IQaCollectionService {
         } catch (Exception e) {
             log.error(e.getMessage());
         }
+        try {
+            dkExampleRepository.deleteAll(id);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+
         try {
             dkTaxonomyRepository.deleteAll(id);
         } catch (Exception e) {
@@ -282,6 +320,13 @@ public class DkCollectionService implements IQaCollectionService {
         } catch (Exception e) {
             log.error(e.getMessage());
         }
+
+//        try {
+//            dkExampleIndexer.onDeleteAllExamples(id);
+//        } catch (Exception e) {
+//            log.error(e.getMessage());
+//        }
+
         try {
             dkTaskRepository.deleteAll(id);
         } catch (Exception e) {
@@ -318,6 +363,7 @@ public class DkCollectionService implements IQaCollectionService {
 
         //createEntryTable(KvConfig.ENTRY_SCHEMA, getTableName(id));
         dkRepository.createTable(id);
+        dkExampleRepository.createTable(id);
         dkTaxonomyRepository.createTable(id);
         dkIndexer.createCollection(id);
         dkTaskRepository.createTable(id);

+ 219 - 4
server/src/main/java/com/giantan/data/dk/service/DkDocsService.java

@@ -1,11 +1,17 @@
 package com.giantan.data.dk.service;
 
 import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.repository.DkIndexer;
 import com.giantan.data.dk.repository.DkRepository;
+import com.giantan.data.index.IHybridSearch;
+import com.giantan.data.index.dto.DocSearchResp;
+import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
+import java.io.IOException;
+import java.util.*;
 
 @Service
 public class DkDocsService {
@@ -16,6 +22,17 @@ public class DkDocsService {
     @Autowired
     DkRepository dkRepository;
 
+    @Autowired
+    @Qualifier("dkIndexer")
+    DkIndexer indexer;
+
+    @Autowired
+    IHybridSearch hybridSearch;
+
+    @Autowired
+    DkExamplesService dkExamplesService;
+
+
     protected Integer getCollId(String coll) {
         int id = dkCollectionService.getCollectionId(coll);
         if (id <= 0) {
@@ -51,6 +68,9 @@ public class DkDocsService {
     public GTool save(String coll, GTool data) throws Throwable {
         String collId = getStrOfCollId(coll);
         GTool r = dkRepository.save(collId, data);
+        if (r != null) {
+            int r2 = indexer.onAdd(collId, r);
+        }
         return r;
     }
 
@@ -78,13 +98,20 @@ public class DkDocsService {
     public int deleteByIdOrGid(String coll, String qid) {
         String collId = getStrOfCollId(coll);
         int id = isIntId(qid);
-        int r = 0;
+        GTool r = null;
         if (id >= 0) {
             r = dkRepository.deleteById(collId, id);
         } else {
             r = dkRepository.deleteByGid(collId, qid);
         }
-        return r;
+        //return r;
+        if (r != null) {
+            dkExamplesService.deleteByToolGid(coll, r.getGid());
+
+            int r2 = indexer.onDelete(collId, r);
+            return 1;
+        }
+        return 0;
     }
 
     public GTool update(String coll, String qid, GTool tool) throws Throwable {
@@ -92,11 +119,199 @@ public class DkDocsService {
         int id = isIntId(qid);
         if (id >= 0) {
             tool.setId(Long.valueOf(id));
-        }else{
+        } else {
             tool.setGid(qid);
         }
         GTool r = dkRepository.update(collId, tool);
+        if (r != null) {
+            int r2 = indexer.onUpdate(collId, r);
+        }
         return r;
     }
 
+
+    private int toDocId(Object o) {
+        if (o instanceof Integer) {
+            return ((Integer) o).intValue();
+        } else {
+            return Integer.parseInt(o.toString());
+        }
+    }
+
+    protected List<GBaseKeyValue> getEntitiesBySearch(String collId, List<DocSearchResp> resps) throws Throwable {
+        if (resps.isEmpty()) {
+            return List.of();
+        }
+        Set<String> hits = new HashSet<>();
+        List<GBaseKeyValue> rets = new ArrayList<>();
+        for (DocSearchResp resp : resps) {
+            Map<String, Object> metadata = resp.getMetadata();
+            if (metadata != null) {
+                Object o = metadata.get(DkIndexer.COLL_ID);
+                if (o != null && o instanceof String collId1) {
+                    if (collId.equals(collId1)) {
+                        o = metadata.get(DkIndexer.DOC_ID);
+
+                        if (o != null) {
+                            int docId = toDocId(o);
+                            String hid = collId + "_" + docId;
+                            if (hits.contains(hid)) {
+                                continue;
+                            }
+                            hits.add(hid);
+                            GTool r1 = dkRepository.find(collId, docId);
+                            if (r1 != null) {
+                                GBaseKeyValue kv = r1.toGKeyValue();
+                                kv.put("score", resp.getScore());
+                                rets.add(kv);
+                            }
+                        }
+                    }
+//                    if (collName1.equals(indexName)) {
+//                    }
+
+                }
+            }
+        }
+        return rets;
+    }
+
+    public List<GBaseKeyValue> fulltextSearch(String coll, Map<String, Object> query) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        String qasName = indexer.getMappedIndexName(collId);
+        List<DocSearchResp> resps = hybridSearch.fulltextSearch(qasName, query);
+
+        List<GBaseKeyValue> rets = getEntitiesBySearch(collId, resps);
+        return rets;
+    }
+
+
+    public List<GBaseKeyValue> similaritySearch(String coll, Map<String, Object> query) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        String qasName = indexer.getMappedIndexName(collId);
+        List<DocSearchResp> resps = hybridSearch.similaritySearch(qasName, query);
+
+        List<GBaseKeyValue> rets = getEntitiesBySearch(collId, resps);
+        return rets;
+    }
+
+    public List<GBaseKeyValue> hybridSearch(String coll, Map<String, Object> query) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        String qasName = indexer.getMappedIndexName(collId);
+        List<DocSearchResp> resps = hybridSearch.hybridSearch(qasName, query);
+
+        List<GBaseKeyValue> rets = getEntitiesBySearch(collId, resps);
+        return rets;
+    }
+
+    public int deleteByName(String coll, String name) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.deleteByName(collId, name);
+        if (rets != null && rets.size() > 0) {
+            for (GTool r : rets) {
+                dkExamplesService.deleteByToolGid(coll, r.getGid());
+                int r2 = indexer.onDelete(collId, r);
+            }
+            return rets.size();
+        }
+        return 0;
+    }
+
+    public long deleteAll(String coll) {
+        String collId = getStrOfCollId(coll);
+        long l = dkRepository.deleteAll(collId);
+        int r2 = indexer.onDeleteAll(collId);
+        dkExamplesService.deleteAll(coll);
+        return l;
+    }
+
+    public List<Map<String, Object>> getAllEntities(String coll, List<String> fields) {
+        String collId = getStrOfCollId(coll);
+        List<Map<String, Object>> rets = dkRepository.getAllEntities(collId, fields);
+        return rets;
+    }
+
+    public List<GTool> findByPath(String coll, String path) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.findByPath(collId, path);
+        return rets;
+    }
+
+    public List<GTool> findByPrefix(String coll, String prefix) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.findByPathPrefix(collId, prefix);
+        return rets;
+    }
+
+    public int deleteByPath(String coll, String path) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.deleteByPath(collId, path);
+        if (rets != null && rets.size() > 0) {
+            List<String> tids = new ArrayList<>();
+            for (GTool r : rets) {
+                tids.add(r.getGid());
+            }
+            dkExamplesService.deleteByToolGids(collId, tids);
+            indexer.onDelete(collId, rets);
+            return rets.size();
+        }
+        return 0;
+    }
+
+    public int deleteByPrefix(String coll, String prefix) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.deleteByPrefix(collId, prefix);
+        if (rets != null && rets.size() > 0) {
+            List<String> tids = new ArrayList<>();
+            for (GTool r : rets) {
+                tids.add(r.getGid());
+            }
+            dkExamplesService.deleteByToolGids(collId, tids);
+            indexer.onDelete(collId, rets);
+            return rets.size();
+        }
+        return 0;
+    }
+
+
+    public int deletePathAndDescendants(String coll, String path) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.deletePathAndDescendants(collId, path);
+        if (rets != null && rets.size() > 0) {
+            List<String> tids = new ArrayList<>();
+            for (GTool r : rets) {
+                tids.add(r.getGid());
+            }
+            dkExamplesService.deleteByToolGids(collId, tids);
+            indexer.onDelete(collId, rets);
+            return rets.size();
+        }
+        return 0;
+    }
+
+    public long count(String coll) {
+        String collId = getStrOfCollId(coll);
+        long l = dkRepository.count(collId);
+        return l;
+    }
+
+    public List<GTool> getChildren(String coll, String parent) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.getChildren(collId, parent);
+        return rets;
+    }
+
+
+    public List<GTool> updatePathPrefix(String coll, String oldPath, String newPath) {
+        String collId = getStrOfCollId(coll);
+        List<GTool> rets = dkRepository.updatePathPrefix(collId, oldPath, newPath);
+        for (GTool r : rets) {
+            try {
+                indexer.onUpdate(collId, r);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return rets;
+    }
 }

+ 141 - 0
server/src/main/java/com/giantan/data/dk/service/DkExamplesService.java

@@ -0,0 +1,141 @@
+package com.giantan.data.dk.service;
+
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.dto.GToolExample;
+import com.giantan.data.dk.repository.DkExampleRepository;
+import com.giantan.data.dk.repository.DkRepository;
+import com.giantan.data.dk.repository.IDkExampleIndexer;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class DkExamplesService implements IDkExamplesService {
+    @Autowired
+    DkCollectionService dkCollectionService;
+
+    @Autowired
+    DkRepository dkRepository;
+
+    @Autowired
+    DkExampleRepository exampleRepository;
+
+    @Autowired
+    IDkExampleIndexer indexer;
+
+    @PostConstruct
+    public void init() {
+        indexer.init(dkCollectionService);
+    }
+
+    protected String getStrOfCollId(String coll) {
+        int id = dkCollectionService.getCollectionId(coll);
+        if (id <= 0) {
+            return null;
+        }
+        return Integer.toString(id);
+    }
+
+
+    private int isIntId(String mdId) {
+        if (mdId.length() > 12) {
+            return -1;
+        } else {
+            try {
+                int i = Integer.parseInt(mdId);
+                return i;
+            } catch (Exception e) {
+
+            }
+        }
+        return -1;
+    }
+
+
+    public GToolExample save(String coll, GToolExample data) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        GToolExample r = exampleRepository.save(collId, data);
+        if (r != null) {
+            GTool tool = dkRepository.findByGid(collId, r.getToolId());
+            if (tool != null) {
+                int r2 = indexer.onAdd(collId, r, tool);
+            }
+        }
+        return r;
+    }
+
+    public List<GToolExample> findAll(String coll) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        if (collId != null) {
+            List<GToolExample> rets = exampleRepository.findAll(collId);
+            return rets;
+        }
+        return null;
+    }
+
+    public GToolExample findById(String coll, String id) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        GToolExample r = exampleRepository.find(collId, id);
+        ;
+        return r;
+    }
+
+    public GToolExample deleteById(String coll, String id) {
+        String collId = getStrOfCollId(coll);
+
+        GToolExample r = exampleRepository.deleteById(collId, id);
+
+        if (r != null) {
+            int r2 = indexer.onDelete(collId, r);
+        }
+        return r;
+
+    }
+
+    public GToolExample update(String coll, String id, GToolExample example) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        example.setId(id);
+        GToolExample r = exampleRepository.update(collId, example);
+        if (r != null) {
+            GTool tool = dkRepository.findByGid(collId, r.getToolId());
+            if (tool != null) {
+                int r2 = indexer.onUpdate(collId, r, tool);
+            }
+        }
+        return r;
+    }
+
+    public List<GToolExample> findByToolGid(String coll, String toolGid) {
+        String collId = getStrOfCollId(coll);
+        return exampleRepository.findByToolGid(collId, toolGid);
+    }
+
+    public List<GToolExample> deleteByToolGid(String coll, String toolGid){
+        String collId = getStrOfCollId(coll);
+        List<GToolExample> rs = exampleRepository.deleteByToolGid(collId, toolGid);
+        if (rs != null && !rs.isEmpty()) {
+            int r2 = indexer.onDeleteExamples(collId, rs);
+        }
+        return rs;
+    }
+
+    public List<String> deleteByToolGids(String coll, List<String> toolGids){
+        String collId = getStrOfCollId(coll);
+        List<String> rs = exampleRepository.deleteByToolGids(collId, toolGids);
+        if (rs != null && !rs.isEmpty()) {
+            int r2 = indexer.onDeleteExamplesByIds(collId, rs);
+        }
+        return rs;
+    }
+
+
+    @Override
+    public long deleteAll(String coll) {
+        String collId = getStrOfCollId(coll);
+        long l = exampleRepository.deleteAll(collId);
+        indexer.onDeleteAllExamples(collId);
+        return l;
+    }
+}

+ 382 - 0
server/src/main/java/com/giantan/data/dk/service/DkTaxonomyService.java

@@ -0,0 +1,382 @@
+package com.giantan.data.dk.service;
+
+import com.giantan.data.dk.dto.GTool;
+import com.giantan.data.dk.repository.DkRepository;
+import com.giantan.data.dk.repository.DkTaxonomyRepository;
+import com.giantan.data.kvs.repository.GEntityConfig;
+import com.giantan.data.taxonomy.model.TaxonomyNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class DkTaxonomyService {
+    private static final org.slf4j.Logger log
+            = org.slf4j.LoggerFactory.getLogger(DkTaxonomyService.class);
+
+    @Autowired
+    DkCollectionService dkCollectionService;
+
+    @Autowired
+    DkTaxonomyRepository dkTaxonomyRepository;
+
+    @Autowired
+    //DkRepository dkRepository;
+    DkDocsService dkDocsService;
+
+    public DkTaxonomyService() {
+
+    }
+
+    protected String getStrOfCollId(String coll) {
+        int id = dkCollectionService.getCollectionId(coll);
+        if (id <= 0) {
+            return null;
+        }
+        return Integer.toString(id);
+    }
+
+    private int getIntId(String coll, String nodeId) throws Exception {
+        int id = -1;
+        if (nodeId.length() > 12) {
+            //return -1;
+        } else {
+            try {
+                int i = Integer.parseInt(nodeId);
+                id = i;
+            } catch (Exception e) {
+
+            }
+        }
+//        String collId = getStrOfCollId(coll);
+//        GBaseKeyValue r = mdDynamicRepository.findByGid(collId, nodeId);
+//        if (r != null) {
+//            return r.getIntId();
+//        }
+        if (id < 0) {
+            log.error("该 Taxonomy Node ID {} 无效", nodeId);
+            throw new Exception("该 Taxonomy Node ID 无效: " + nodeId);
+        }
+        return id;
+    }
+
+
+    public List<TaxonomyNode> listAll(String coll) {
+        String collId = getStrOfCollId(coll);
+        return dkTaxonomyRepository.listAll(collId);
+    }
+
+    public List<TaxonomyNode> listTree(String coll) {
+        String collId = getStrOfCollId(coll);
+        return dkTaxonomyRepository.listTree(collId);
+    }
+
+    public TaxonomyNode createNode(String coll, Map<String, Object> data) throws Exception {
+        String collId = getStrOfCollId(coll);
+        TaxonomyNode node = dkTaxonomyRepository.createNode(collId, data);
+        return node;
+    }
+
+    public List<TaxonomyNode> findChildren(String coll, String nodeId) throws Exception {
+        String collId = getStrOfCollId(coll);
+        int intId = getIntId(coll, nodeId);
+
+        List<TaxonomyNode> ret = dkTaxonomyRepository.findChildren(collId, intId);
+        return ret;
+    }
+
+    protected String getPath(String collId, int nodeId) {
+        // nodeId不存在时 ancestors size=0
+        List<TaxonomyNode> ancestors = dkTaxonomyRepository.findAncestors(collId, nodeId, true);
+
+        Collections.reverse(ancestors);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < ancestors.size(); i++) {
+            sb.append(ancestors.get(i).getName()).append("/");
+        }
+        String p = sb.toString();
+        if (p.startsWith("//")) {
+            p = p.substring(1);
+        }
+        return p;
+    }
+
+    public List<GTool> findTools(String coll, String nodeId) throws Exception {
+        // 获取父节点
+        String collId = getStrOfCollId(coll);
+        int intId = getIntId(coll, nodeId);
+        String path = getPath(collId, intId);
+        List<GTool> ret = dkDocsService.findByPath(coll, path);
+        return ret;
+    }
+
+//    public Map<String, Object> processAsyncDirect(String coll, String nodeId, String taskId, MultipartFile file, Map<String, String> params) throws Exception {
+//        try {
+//            String collId = getStrOfCollId(coll);
+//            int intId = getIntId(coll, nodeId);
+//            String path = getPath(collId, intId);
+//            params.put("objectPath", path);
+//            Map<String, Object> ret = mdDocsService.processAsyncDirect(coll, taskId, file, params);
+//            return ret;
+//        } catch (Exception e) {
+//
+//        }
+//        return null;
+//
+//    }
+
+    public int deleteFolder(String coll, String nodeId, String taskId) throws Exception {
+        String collId = getStrOfCollId(coll);
+        try {
+            int intId = getIntId(coll, nodeId);
+            String path = getPath(collId, intId);
+
+            int ret = dkDocsService.deletePathAndDescendants(coll, path);
+            dkTaxonomyRepository.deleteSubtree(collId, intId);
+
+            return ret;
+        } catch (Throwable e) {
+            return 0;
+        }
+    }
+
+    public static String replaceLastPathSegment(String path, String newSegment) {
+        if (path == null || !path.endsWith("/")) {
+            throw new IllegalArgumentException("路径为null 或者 路径没有以 '/' 结尾");
+        }
+
+        String trimmed = path.substring(0, path.length() - 1); // 去掉末尾的 "/"
+        int lastSlashIndex = trimmed.lastIndexOf("/");
+
+        if (lastSlashIndex == -1) {
+            // 说明没有上级路径,例如 "/cc/"
+            return "/" + newSegment + "/";
+        }
+
+        String prefix = trimmed.substring(0, lastSlashIndex + 1); // 保留最后一个斜杠
+        return prefix + newSegment + "/";
+    }
+
+    public int renameFolder(String coll, String nodeId, Map<String, Object> req, String taskId) throws Exception {
+        if (req.containsKey("newName")) {
+            String newName = req.get("newName").toString();
+            String collId = getStrOfCollId(coll);
+
+            int intId = getIntId(coll, nodeId);
+            String oldPath = getPath(collId, intId);
+            String newPath = replaceLastPathSegment(oldPath, newName);
+
+            //int count = dkDocRepository.updatePathAndNamePrefix(collId, oldPath, newPath);
+            List<GTool> rets = dkDocsService.updatePathPrefix(coll, oldPath, newPath);
+
+            Map<String, Object> nkv = Map.of("name", newName);
+            TaxonomyNode taxonomyNode = dkTaxonomyRepository.updateFields(collId, intId, nkv);
+            if (rets != null && rets.size() > 0) {
+                return rets.size();
+            }
+        }
+        return 0;
+    }
+
+
+    public int moveTo(String coll, String nodeId, Map<String, Object> req, String taskId) throws Exception {
+        if (req.containsKey("newParentId")) {
+            Object o = req.get("newParentId");
+
+            String collId = getStrOfCollId(coll);
+
+            int intId = getIntId(coll, nodeId);
+            int newId = getIntId(coll, o.toString());
+
+            if (!dkTaxonomyRepository.isValidParentId(collId, intId, newId)) {
+                throw new Exception("Node " + intId + " cannot move to it's descendant " + newId + ".");
+            }
+
+            String oldPath = getPath(collId, intId);
+            String newPrefix = getPath(collId, newId);
+            TaxonomyNode node = dkTaxonomyRepository.findById(collId, intId);
+            String newPath = newPrefix + node.getName() + "/";
+
+            //int count = dkDocRepository.updatePathAndNamePrefix(collId, oldPath, newPath);
+            List<GTool> rets = dkDocsService.updatePathPrefix(coll, oldPath, newPath);
+            TaxonomyNode taxonomyNode = dkTaxonomyRepository.updateParentId(collId, intId, newId);
+
+            if (rets != null && rets.size() > 0) {
+                return rets.size();
+            }
+        }
+        return 0;
+    }
+
+    public List<TaxonomyNode> findSubtree(String coll, String nodeId) throws Exception {
+        String collId = getStrOfCollId(coll);
+        int intId = getIntId(coll, nodeId);
+        return dkTaxonomyRepository.findSubtree(collId, intId);
+    }
+
+
+    public List<TaxonomyNode> findDescendants(String coll, String nodeId) throws Exception {
+        String collId = getStrOfCollId(coll);
+        int intId = getIntId(coll, nodeId);
+        return dkTaxonomyRepository.findAllDescendants(collId, intId);
+    }
+
+    public List<TaxonomyNode> findAncestors(String coll, String nodeId) throws Exception {
+        String collId = getStrOfCollId(coll);
+        int intId = getIntId(coll, nodeId);
+        return dkTaxonomyRepository.findAncestors(collId, intId, true);
+    }
+
+
+    protected List<String> toPathList(String ps) {
+        String[] ss = ps.split("/");
+        List<String> ls = new ArrayList<String>();
+        for (int i = 0; i < ss.length; i++) {
+            if (ss[i] != null && ss[i].length() > 0) {
+                ls.add(ss[i]);
+            }
+        }
+        return ls;
+    }
+
+    protected TaxonomyNode buildPath(String collId, List<String> ls) throws Exception {
+        int parentId = dkTaxonomyRepository.getRoot();
+
+        TaxonomyNode node = null;
+        for (int i = 0; i < ls.size(); i++) {
+            node = dkTaxonomyRepository.findNode(collId, parentId, ls.get(i));
+            if (node == null) {
+                Map<String, Object> kv = new HashMap<String, Object>();
+                kv.put("name", ls.get(i));
+                kv.put("parentId", parentId);
+
+                node = dkTaxonomyRepository.createNode(collId, kv);
+                parentId = node.getId();
+            }else{
+                parentId = node.getId();
+            }
+        }
+        return node;
+    }
+
+    protected TaxonomyNode getPath(String collId, List<String> ls) throws Exception {
+        int parentId = dkTaxonomyRepository.getRoot();
+
+        TaxonomyNode node = null;
+        for (int i = 0; i < ls.size(); i++) {
+            node = dkTaxonomyRepository.findNode(collId, parentId, ls.get(i));
+            if (node != null) {
+                parentId = node.getId();
+            } else {
+                break;
+            }
+        }
+        return node;
+    }
+
+    // 根据 目录id 建立qa
+    public GTool createTool(String coll, String nodeId, GTool entity) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        int intNodeId = getIntId(coll, nodeId);
+        String path = getPath(collId, intNodeId);
+        //entity.put(GEntityConfig.PATH, path);
+        entity.setPath(path);
+        GTool r = dkDocsService.save(coll, entity);
+        return r;
+    }
+
+    ///////////////////////////
+
+    // 根据 entity中的path字段建taxonomy的节点,并新增entity
+    public GTool createEntityByPath(String coll, GTool entity) throws Throwable {
+        String collId = getStrOfCollId(coll);
+        //Object o = entity.get(GEntityConfig.PATH);
+        String o = entity.getPath();
+        if (o != null) {
+            List<String> ps = toPathList(o);
+            TaxonomyNode node = buildPath(collId, ps);
+            String path = "/" + String.join("/", ps);
+            //entity.put(GEntityConfig.PATH, path);
+            entity.setPath(path);
+            GTool ret = dkDocsService.save(coll, entity);
+            return ret;
+        } else {
+            GTool ret = dkDocsService.save(coll, entity);
+            return ret;
+        }
+    }
+
+    public TaxonomyNode createNodeByPath(String coll, Map<String, Object> data) throws Exception {
+        String collId = getStrOfCollId(coll);
+        Object o = data.get(GEntityConfig.PATH);
+        if (o != null && o instanceof String s) {
+            List<String> ps = toPathList(s);
+            TaxonomyNode node = buildPath(collId, ps);
+            //String path = "/" + String.join("/", ps);
+            //entity.put(GEntityConfig.PATH, path);
+            int parentId = dkTaxonomyRepository.getRoot();
+            if (node != null) {
+                parentId = node.getId();
+            }
+            Map<String, Object> kv = new HashMap<String, Object>();
+            kv.put(GEntityConfig.NAME, data.get(GEntityConfig.NAME));
+            kv.put("parentId", parentId);
+            Object o1 = data.get(GEntityConfig.DESCRIPTION);
+            if (o1 != null) {
+                kv.put(GEntityConfig.DESCRIPTION, o1);
+            }
+            o1 = data.get(GEntityConfig.ATTRIBUTES);
+            if (o1 != null) {
+                kv.put(GEntityConfig.ATTRIBUTES, o1);
+            }
+            node = dkTaxonomyRepository.createNode(collId, kv);
+            return node;
+        }
+        return null;
+    }
+
+    public TaxonomyNode updateNodeByPath(String coll, Map<String, Object> data) throws Exception {
+        String collId = getStrOfCollId(coll);
+        Object o = data.remove(GEntityConfig.PATH);
+        if (o != null && o instanceof String s) {
+            List<String> ps = toPathList(s);
+            TaxonomyNode node = getPath(collId, ps);
+            if (node != null) {
+                //Map<String, Object> attr = new HashMap<>();
+                //attr.put(GEntityConfig.ATTRIBUTES, data);
+                TaxonomyNode ret = dkTaxonomyRepository.updateFields(collId, node.getId(), data);
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    public TaxonomyNode findNodeByPath(String coll, String path) throws Exception {
+        String collId = getStrOfCollId(coll);
+        if (path != null) {
+            List<String> ps = toPathList(path);
+            TaxonomyNode node = getPath(collId, ps);
+            return node;
+        }
+        return null;
+    }
+
+    public int deleteFolderByPath(String coll, String path) throws Exception {
+        String collId = getStrOfCollId(coll);
+        if (path != null) {
+            List<String> ps = toPathList(path);
+            TaxonomyNode node = getPath(collId, ps);
+            if (node != null) {
+                //int count = dkDocRepository.deletePathAndDescendants(collId, path);
+                int ret = dkDocsService.deletePathAndDescendants(coll, path);
+                dkTaxonomyRepository.deleteSubtree(collId, node.getId());
+                return ret;
+            }
+        }
+        return 0;
+    }
+
+}
+
+

+ 5 - 0
server/src/main/java/com/giantan/data/dk/service/IDkExamplesService.java

@@ -0,0 +1,5 @@
+package com.giantan.data.dk.service;
+
+public interface IDkExamplesService {
+    long deleteAll(String collId);
+}

+ 27 - 24
server/src/main/java/com/giantan/data/index/HybridSearch.java

@@ -1,7 +1,7 @@
 package com.giantan.data.index;
 
 
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.data.index.dto.DocReq;
 import com.giantan.data.index.dto.DocResp;
 import com.giantan.data.index.dto.DocSearchResp;
@@ -40,7 +40,7 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public List<DocResp> add(String coll, List<DocReq> docs) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(docs);
+        String body = JsonUtils.toJsonString(docs);
 
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents"))
@@ -50,7 +50,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocResp> ls = toDocResps(o);
         return ls;
@@ -59,7 +59,7 @@ public class HybridSearch implements IHybridSearch {
     @Override
     // 已经有了embedding,直接增加
     public List<DocResp> addDirect(String coll, List<DocReq> docs) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(docs);
+        String body = JsonUtils.toJsonString(docs);
 
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/insert"))
@@ -69,7 +69,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocResp> ls = toDocResps(o);
         return ls;
@@ -78,7 +78,7 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public int delete(String coll, List<String> ids) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(ids);
+        String body = JsonUtils.toJsonString(ids);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents"))
                 .header("Content-Type", "application/json")
@@ -87,7 +87,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof Integer) {
             return (Integer) o;
@@ -98,7 +98,7 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public List<DocResp> getDocumentsByIds(String coll, List<String> ids) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(ids);
+        String body = JsonUtils.toJsonString(ids);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/batch"))
                 .header("Content-Type", "application/json")
@@ -109,7 +109,7 @@ public class HybridSearch implements IHybridSearch {
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         String body1 = response.body();
         //System.out.println(body1);
-        Map<String, Object> ret = JsonUtil.fromJsonString(body1);
+        Map<String, Object> ret = JsonUtils.fromJsonString(body1);
         Object o = ret.get("data");
         List<DocResp> ls = toDocResps(o);
         return ls;
@@ -125,7 +125,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
 
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof List) {
             return (List<String>) o;
@@ -142,7 +142,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         return IndexUtils.toBoolean(o);
     }
@@ -156,7 +156,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         return IndexUtils.toBoolean(o);
     }
@@ -178,7 +178,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocResp> ls = toDocResps(o);
         return ls;
@@ -200,7 +200,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof Integer) {
             return (Integer) o;
@@ -210,7 +210,7 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public int deleteDocumentsByFilter(String coll, Map<String, Object> query) throws IOException, InterruptedException {
-        String js = JsonUtil.toJson(query);
+        String js = JsonUtils.toJson(query);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/by-filter"))
                 .header("Content-Type", "application/json")
@@ -219,7 +219,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof Integer) {
             return (Integer) o;
@@ -233,7 +233,8 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public List<DocSearchResp> fulltextSearch(String coll, Map<String, Object> query) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(query);
+        Map<String, Object> query2 = MilvusSearchRequestBuilder.from(query);
+        String body = JsonUtils.toJsonString(query2);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/fulltextSearch"))
                 .header("Content-Type", "application/json")
@@ -241,7 +242,7 @@ public class HybridSearch implements IHybridSearch {
                 .method("POST", HttpRequest.BodyPublishers.ofString(body))
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocSearchResp> rets = new ArrayList<>();
         if (o != null && o instanceof List ls) {
@@ -256,7 +257,8 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public List<DocSearchResp> similaritySearch(String coll, Map<String, Object> query) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(query);
+        Map<String, Object> query2 = MilvusSearchRequestBuilder.from(query);
+        String body = JsonUtils.toJsonString(query2);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/similaritySearch"))
                 .header("Content-Type", "application/json")
@@ -264,7 +266,7 @@ public class HybridSearch implements IHybridSearch {
                 .method("POST", HttpRequest.BodyPublishers.ofString(body))
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocSearchResp> rets = new ArrayList<>();
         if (o != null && o instanceof List ls) {
@@ -279,7 +281,8 @@ public class HybridSearch implements IHybridSearch {
 
     @Override
     public List<DocSearchResp> hybridSearch(String coll, Map<String, Object> query) throws IOException, InterruptedException {
-        String body = JsonUtil.toJsonString(query);
+        Map<String, Object> query2 = MilvusSearchRequestBuilder.from(query);
+        String body = JsonUtils.toJsonString(query2);
         HttpRequest request = HttpRequest.newBuilder()
                 .uri(URI.create(url + coll + "/documents/hybridSearch"))
                 .header("Content-Type", "application/json")
@@ -287,7 +290,7 @@ public class HybridSearch implements IHybridSearch {
                 .method("POST", HttpRequest.BodyPublishers.ofString(body))
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         List<DocSearchResp> rets = new ArrayList<>();
         if (o != null && o instanceof List ls) {
@@ -309,7 +312,7 @@ public class HybridSearch implements IHybridSearch {
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
         //System.out.println(response.body());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof Boolean) {
             return 1;
@@ -325,7 +328,7 @@ public class HybridSearch implements IHybridSearch {
                 .method("GET", HttpRequest.BodyPublishers.noBody())
                 .build();
         HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
-        Map<String, Object> ret = JsonUtil.fromJsonString(response.body());
+        Map<String, Object> ret = JsonUtils.fromJsonString(response.body());
         Object o = ret.get("data");
         if (o != null && o instanceof List) {
             return (List<String>) o;

+ 14 - 0
server/src/main/java/com/giantan/data/index/IndexUtils.java

@@ -7,6 +7,20 @@ import java.util.Map;
 
 public class IndexUtils {
 
+    public static String normalize(String path) {
+        if (path == null || path.isBlank()) {
+            return "/";
+        }
+        String result = path.trim();
+
+        // 替换所有 '\' 为 '/'
+        String normalized = path.trim().replace('\\', '/');
+        if (!normalized.endsWith("/")) {
+            normalized += "/";
+        }
+        return normalized;
+    }
+
     public static Object toIntOrLong(Double d){
         if (d > Integer.MAX_VALUE || d < Integer.MIN_VALUE){
             return d.intValue();

+ 187 - 0
server/src/main/java/com/giantan/data/index/MilvusSearchRequestBuilder.java

@@ -0,0 +1,187 @@
+package com.giantan.data.index;
+
+
+import com.giantan.ai.util.JsonUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// 构建 milvus 的 filterExpression
+/*
+	public Builder from(Map<String, Object> request) {
+			String q = (String) request.get("query");
+			this.searchRequest.query = q;
+
+			Object o = request.get("topK");
+			if (o != null) {
+				this.searchRequest.topK = Integer.parseInt(o.toString());
+			}
+
+			o = request.get("similarityThreshold");
+			if (o != null) {
+				this.searchRequest.similarityThreshold = Double.parseDouble(o.toString());
+			}
+			o = request.get("filterExpression");
+			if (o != null) {
+				this.searchRequest.nativeExpression = (String)o;
+			}else{
+				o = request.get("tags");
+				String filter = null;
+				if (o != null){
+					if (o instanceof String){
+						filter = String.format("array_contains_any(tags, [\"%s\"])", (String)o);
+					} else if (o instanceof List<?>) {
+						List<?> ls = (List<?>)o;
+						if (ls.size() > 0) {
+							//Gson gson = new Gson();
+							//String json1 = gson.toJson(ls);
+							String json1 = GsonUtils.toJsonString(ls);
+
+							boolean cotainsAll = false;
+							o = request.get("tagsAll");
+							if (o != null && o instanceof Boolean) {
+								if (((Boolean) o).booleanValue()) {
+									cotainsAll = true;
+								} else {
+								}
+							}
+							if (cotainsAll) {
+								filter = String.format("array_contains_all(tags, %s)", json1);
+							} else {
+								filter = String.format("array_contains_any(tags, %s)", json1);
+							}
+						}
+					}
+				}
+
+				//fromTime / toTime
+				Object o1 = request.get("fromTime");
+				Object o2 = request.get("toTime");
+				String ts = null;
+				if (o1 != null && o2 != null) {
+					ts = String.format("(%s >= %s && %s < %s)",MilvusVectorStore3.TIME_FIELD_NAME , o1,
+							MilvusVectorStore3.TIME_FIELD_NAME,o2);
+				}else if (o1 != null) {
+					ts = String.format("(%s >= %s)",MilvusVectorStore3.TIME_FIELD_NAME , o1);
+				}else if (o2 != null){
+					ts = String.format("(%s < %s)",MilvusVectorStore3.TIME_FIELD_NAME , o2);
+				}
+				if (filter != null && filter.length() > 0) {
+					if (ts != null) {
+						filter = String.format("%s && %s", filter,ts);
+					}
+				}else{
+					filter = ts;
+				}
+
+				if (filter != null){
+					this.searchRequest.nativeExpression = filter;
+				}
+			}
+
+			return this;
+		}
+	}
+ */
+
+//query
+//topK
+//similarityThreshold
+//filterExpression
+
+// metadata["path"] like "/文本分类/影评2/%" && doc_id > "0"
+public class MilvusSearchRequestBuilder {
+
+    public static final String TIME_FIELD_NAME = "create_time";
+    public static final String FILTER_FIELD_NAME = "filterExpression";
+    // tags tagsAll
+    // path
+
+    public static Map<String, Object> from(Map<String, Object> request) {
+        String filter = null;
+        Map<String, Object> ret = new HashMap<String, Object>(request);
+
+        Object o = ret.remove("tags");
+
+        if (o != null) {
+            if (o instanceof String) {
+                filter = String.format("array_contains_any(tags, [\"%s\"])", (String) o);
+            } else if (o instanceof List<?>) {
+                List<?> ls = (List<?>) o;
+                if (ls.size() > 0) {
+                    String json1 = JsonUtils.toJson(ls);
+
+                    boolean cotainsAll = false;
+                    o = ret.remove("tagsAll");
+                    if (o != null && o instanceof Boolean) {
+                        if (((Boolean) o).booleanValue()) {
+                            cotainsAll = true;
+                        } else {
+                        }
+                    }
+                    if (cotainsAll) {
+                        filter = String.format("array_contains_all(tags, %s)", json1);
+                    } else {
+                        filter = String.format("array_contains_any(tags, %s)", json1);
+                    }
+                }
+            }
+        }
+
+        //fromTime / toTime
+        Object o1 = ret.remove("fromTime");
+        Object o2 = ret.remove("toTime");
+        String ts = null;
+        if (o1 != null && o2 != null) {
+            ts = String.format("(%s >= %s && %s < %s)", TIME_FIELD_NAME, o1,
+                    TIME_FIELD_NAME, o2);
+        } else if (o1 != null) {
+            ts = String.format("(%s >= %s)", TIME_FIELD_NAME, o1);
+        } else if (o2 != null) {
+            ts = String.format("(%s < %s)", TIME_FIELD_NAME, o2);
+        }
+        if (filter != null && filter.length() > 0) {
+            if (ts != null) {
+                filter = String.format("%s && %s", filter, ts);
+            }
+        } else {
+            filter = ts;
+        }
+
+        Object o3 = ret.remove("path");
+        String ps = null;
+        if (o3 != null) {
+            String path = (String) o3;
+            path = IndexUtils.normalize(path) + "%";
+            ps = String.format("metadata[\"path\"] like \"%s\"", path);
+
+            if (filter != null && filter.length() > 0) {
+                if (ps != null) {
+                    filter = String.format("%s && %s", filter, ps);
+                }
+            } else {
+                filter = ps;
+            }
+        }
+
+        Object o4 = ret.remove(FILTER_FIELD_NAME);
+        if (o4 != null) {
+            String fs = (String)o4;
+            if (filter != null && filter.length() > 0) {
+                if (fs != null) {
+                    filter = String.format("%s && %s", filter, fs);
+                }
+            } else {
+                filter = fs;
+            }
+        }
+
+        if (filter != null) {
+            ret.put(FILTER_FIELD_NAME, filter);
+        }
+        return ret;
+    }
+
+
+}

+ 4 - 4
server/src/main/java/com/giantan/data/index/Vectorization.java

@@ -1,6 +1,6 @@
 package com.giantan.data.index;
 
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
@@ -106,9 +106,9 @@ public class Vectorization implements IVectorization {
                 }
                 """;
         Object o = inputs.get("inputs");
-        String json = JsonUtil.toJsonString(o);
+        String json = JsonUtils.toJsonString(o);
 
-        String formatted = String.format(requestBody, JsonUtil.cleanControlChars(json));
+        String formatted = String.format(requestBody, JsonUtils.cleanControlChars(json));
         return getEmdedding(formatted);
     }
 
@@ -124,7 +124,7 @@ public class Vectorization implements IVectorization {
 
         String data = response.body();
         if (data != null) {
-            List<List<Float>> lss = JsonUtil.toListFloatList(data);
+            List<List<Float>> lss = JsonUtils.toListFloatList(data);
 
             List<float[]> lf = new ArrayList<float[]>();
             for (List<Float> m : lss) {

+ 2 - 2
server/src/main/java/com/giantan/data/kvs/repository/GDynamicRepository.java

@@ -3,7 +3,7 @@ package com.giantan.data.kvs.repository;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.ai.util.PathUtils;
 import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import com.giantan.data.kvs.kvstore.IGDynamicRepository;
@@ -601,7 +601,7 @@ public class GDynamicRepository implements IGDynamicRepository {
             throw new IllegalArgumentException("ID cannot be null");
         }
         if (attributes != null && !attributes.isEmpty()) {
-            String js = JsonUtil.toJsonString(attributes);
+            String js = JsonUtils.toJsonString(attributes);
 
             //String sql = String.format("UPDATE %s.%s SET attributes = COALESCE(attributes, '{}'::jsonb) || ?::jsonb  WHERE id = ?;", schema, table);// RETURNING *
             String sql = String.format("UPDATE %s.%s SET attributes = attributes || ?::jsonb  WHERE id = ?;", schema, tableName(collId));// RETURNING *

+ 2 - 2
server/src/main/java/com/giantan/data/kvs/repository/GRepository.java

@@ -32,7 +32,7 @@ package com.giantan.data.kvs.repository;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import com.giantan.data.kvs.kvstore.IGkvRepository;
 import com.giantan.ai.util.id.IdGenerator;
@@ -480,7 +480,7 @@ public class GRepository implements IGkvRepository {
 //        }
 //        GBaseKeyValue ret = find(id);
 //        return ret;
-        String js = JsonUtil.toJsonString(attributes);
+        String js = JsonUtils.toJsonString(attributes);
 
         String sql = String.format(
                 "UPDATE %s.%s " +

+ 3 - 3
server/src/main/java/com/giantan/data/kvs/repository/index/GIndexedRepository.java

@@ -4,7 +4,7 @@ package com.giantan.data.kvs.repository.index;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.ai.util.PathUtils;
 import com.giantan.ai.util.id.IdGenerator;
 import com.giantan.ai.util.id.UlidGenerator;
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
 public class GIndexedRepository implements IGDynamicRepository {
 
     private static final org.slf4j.Logger log
-            = org.slf4j.LoggerFactory.getLogger(GDynamicRepository.class);
+            = org.slf4j.LoggerFactory.getLogger(GIndexedRepository.class);
 
     private final ObjectMapper mapper = new ObjectMapper();
 
@@ -577,7 +577,7 @@ public class GIndexedRepository implements IGDynamicRepository {
             throw new IllegalArgumentException("ID cannot be null");
         }
         if (attributes != null && !attributes.isEmpty()) {
-            String js = JsonUtil.toJsonString(attributes);
+            String js = JsonUtils.toJsonString(attributes);
 
             //String sql = String.format("UPDATE %s.%s SET attributes = COALESCE(attributes, '{}'::jsonb) || ?::jsonb  WHERE id = ?;", schema, table);// RETURNING *
             String sql = String.format("UPDATE %s.%s SET attributes = attributes || ?::jsonb  WHERE id = ? RETURNING *;", schema, tableName(collId));

+ 6 - 2
server/src/main/java/com/giantan/data/kvs/repository/index/IIndexer.java

@@ -1,13 +1,17 @@
 package com.giantan.data.kvs.repository.index;
 
 import com.giantan.data.kvs.repository.GEntity;
-import com.giantan.data.qa.service.IQaCollectionService;
+import com.giantan.data.common.ICollectionMetaService;
 
 import java.io.IOException;
 import java.util.List;
 
 public interface IIndexer {
 
+    String getMappedIndexName(String collId);
+
+    String getMappedIndexNameByName(String collName);
+
     int onAdd(String collection, GEntity entity) throws IOException, InterruptedException;
 
     int onAdd(String collection, List<GEntity> entities) throws IOException, InterruptedException;
@@ -30,7 +34,7 @@ public interface IIndexer {
 
 
     // 用于获取collection的attributes 信息
-    void init(IQaCollectionService collectionService);
+    void init(ICollectionMetaService collectionService);
 
     int clearCollection(String collection) throws IOException, InterruptedException;
 }

+ 1 - 1
server/src/main/java/com/giantan/data/mds/MdsApplication.java

@@ -16,7 +16,7 @@ public class MdsApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(MdsApplication.class, args);
-        log.info("Mds server started. Version 2.5.0");
+        log.info("Mds server started. Version 3.0.1");
     }
 
 }

+ 2 - 2
server/src/main/java/com/giantan/data/mds/bot/GChatClient.java

@@ -1,6 +1,6 @@
 package com.giantan.data.mds.bot;
 
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.DefaultResourceLoader;
@@ -107,7 +107,7 @@ public class GChatClient {
             } else {
                 //System.out.println(ret);
                 try {
-                    Map<String, Object> map = JsonUtil.fromJsonString(ret);
+                    Map<String, Object> map = JsonUtils.fromJsonString(ret);
                     if (map != null) {
                         return map;
                     }

+ 4 - 4
server/src/main/java/com/giantan/data/mds/service/impl/MdCollectionsService.java

@@ -317,10 +317,6 @@ public class MdCollectionsService {   //extends KvCollectionService
         Integer intId = kv.getIntId();
         String id = kv.getId();
 
-        long deleted = collections.delete(intId);
-
-        removeFromStore(name);
-
         try {
             indexer.deleteCollection(id);
         } catch (Exception e) {
@@ -358,6 +354,10 @@ public class MdCollectionsService {   //extends KvCollectionService
         } catch (Exception e) {
             log.error(e.getMessage());
         }
+
+        removeFromStore(name);
+        long deleted = collections.delete(intId);
+
         return deleted;
     }
 

+ 2 - 2
server/src/main/java/com/giantan/data/mds/task/impl/ChunksTaskHandler.java

@@ -1,7 +1,7 @@
 package com.giantan.data.mds.task.impl;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.data.mds.bot.GChatClient;
 import com.giantan.data.mds.chunk.MdChunk;
 import com.giantan.data.mds.repository.MdIndexer;
@@ -296,7 +296,7 @@ public class ChunksTaskHandler extends BaseTaskHandler {
         sb.append(plainText);
         float[] fs = vectorizationService.singleVectorization(sb.toString());
         if (fs != null) {
-            String js = JsonUtil.toJsonString(fs);
+            String js = JsonUtils.toJsonString(fs);
             int r = mdChunksService.setEmbedding(coll, chunkId, js);
             return r;
         }

+ 3 - 3
server/src/main/java/com/giantan/data/mds/task/impl/MdsTaskHandler.java

@@ -1,6 +1,6 @@
 package com.giantan.data.mds.task.impl;
 
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import com.giantan.data.mds.chunk.MdChunk;
 import com.giantan.data.mds.service.IMdChunksService;
@@ -113,14 +113,14 @@ public class MdsTaskHandler extends BaseTaskHandler {
         MdChunk chunk = new MdChunk();
         chunk.setMdId(mdId);
         chunk.setChunkIndex(idx);
-        chunk.setPlainText(JsonUtil.cleanControlChars(doc.getText()));
+        chunk.setPlainText(JsonUtils.cleanControlChars(doc.getText()));
         chunk.setSectionPath(doc.getId());
         chunk.setCreatedAt(new Date().toInstant());
 
         Map<String, Object> metadata = doc.getMetadata();
         Object o = metadata.remove(GDocConstants.RAW_CONTENT);
         if (o != null) {
-            chunk.setContent(JsonUtil.cleanControlChars(o.toString()));
+            chunk.setContent(JsonUtils.cleanControlChars(o.toString()));
         }
 
         o = metadata.remove(GDocConstants.START_OFFSET);

+ 4 - 9
server/src/main/java/com/giantan/data/qa/controller/QaSearchContoller.java

@@ -1,10 +1,6 @@
 package com.giantan.data.qa.controller;
 
-import com.giantan.ai.common.reponse.R;
-import com.giantan.data.index.IHybridSearch;
-import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import com.giantan.data.qa.constant.QaConstants;
-import com.giantan.data.qa.repository.QaDocRepository;
 import com.giantan.data.qa.repository.QaIndexer;
 import com.giantan.data.qa.service.QaDocsService;
 import jakarta.servlet.http.HttpServletRequest;
@@ -19,8 +15,6 @@ import org.springframework.web.client.RestTemplate;
 
 import java.lang.invoke.MethodHandles;
 import java.util.Collections;
-import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping(QaConstants.API_PREFIX + "/collections/{coll}")
@@ -32,8 +26,8 @@ public class QaSearchContoller {
     String url = "http://120.78.4.46:7387/v1/collections/";
 
     @Autowired
-    //QaDocRepository qaDocRepository;
-    QaDocsService qaDocsService;
+    //QaDocsService qaDocsService;
+    QaIndexer qaIndexer;
 
     private final RestTemplate restTemplate = new RestTemplate();
 
@@ -60,7 +54,8 @@ public class QaSearchContoller {
 
         //TARGET_PREFIX = url.replaceFirst("/collections", "");
         // 拼接目标 URL
-        String collName = qaDocsService.getIndexName(coll);
+        //String collName = qaDocsService.getIndexName(coll);
+        String collName = qaIndexer.getMappedIndexNameByName(coll);
 
         String targetUrl = url + collName + targetPath + (query != null ? "?" + query : "");
 

+ 24 - 5
server/src/main/java/com/giantan/data/qa/repository/QaIndexer.java

@@ -3,12 +3,13 @@ package com.giantan.data.qa.repository;
 import com.giantan.data.index.HybridIndexer;
 import com.giantan.data.index.IHybridSearch;
 import com.giantan.data.index.IndexConfig;
+import com.giantan.data.index.IndexUtils;
 import com.giantan.data.index.dto.DocReq;
 import com.giantan.data.index.dto.DocResp;
 import com.giantan.data.kvs.repository.GEntity;
 import com.giantan.data.kvs.repository.GEntityConfig;
 import com.giantan.data.kvs.repository.index.IIndexer;
-import com.giantan.data.qa.service.IQaCollectionService;
+import com.giantan.data.common.ICollectionMetaService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
@@ -28,6 +29,7 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
     private static final org.slf4j.Logger log
             = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+    public static final String FIELD_PATH = "path";
 
     String cellectionPrefix = "qas_";
 
@@ -38,7 +40,7 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
 
 
     //    ICollectionMapper collMapper;
-    IQaCollectionService collectionService;
+    ICollectionMetaService collectionService;
 
     public QaIndexer() {
         //collMapper = new DefaultCollectionMapper();
@@ -46,16 +48,16 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
     }
 
     @Override
-    public void init(IQaCollectionService collectionService) {
+    public void init(ICollectionMetaService collectionService) {
         // 根据 params的attributes
         setCollectionService(collectionService);
     }
 
-    public IQaCollectionService getCollectionService() {
+    public ICollectionMetaService getCollectionService() {
         return collectionService;
     }
 
-    public void setCollectionService(IQaCollectionService collectionService) {
+    public void setCollectionService(ICollectionMetaService collectionService) {
         this.collectionService = collectionService;
     }
 
@@ -75,6 +77,9 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
         }
         metadata.put(DOC_ID, entity.getId().toString());
         metadata.put(COLL_ID, collection);
+        if (entity.getPath() != null) {
+            metadata.put(FIELD_PATH, IndexUtils.normalize(entity.getPath()));
+        }
     }
 
     public String indexName(String coll) {
@@ -85,6 +90,7 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
         return prefix + coll;
     }
 
+    @Override
     public String getMappedIndexName(String collId) {
         String collName = collectionService.getCollectionName(Integer.parseInt(collId));
         String mapped = indexName(collName);
@@ -96,6 +102,18 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
         return mapped;
     }
 
+    @Override
+    public String getMappedIndexNameByName(String collName) {
+        String collId = collectionService.getCollectionId(collName) + "";
+        String mapped = indexName(collName);
+        Map<String, Object> attibutes = collectionService.getAttibutes(collId);
+        String configIndexName = getConfigIndexName(collId, attibutes);
+        if (configIndexName != null) {
+            mapped = configIndexName;
+        }
+        return mapped;
+    }
+
     public boolean isOne2One(String collId) {
         boolean isOne2One = true;
         Map<String, Object> attibutes = collectionService.getAttibutes(collId);
@@ -409,4 +427,5 @@ public class QaIndexer extends HybridIndexer implements IIndexer {
         return r;
     }
 
+
 }

+ 20 - 4
server/src/main/java/com/giantan/data/qa/service/QaCollectionService.java

@@ -1,5 +1,6 @@
 package com.giantan.data.qa.service;
 
+import com.giantan.data.common.ICollectionMetaService;
 import com.giantan.data.kvs.kvstore.GBaseKeyValue;
 import com.giantan.data.kvs.repository.GEntityConfig;
 import com.giantan.data.kvs.repository.GRepository;
@@ -17,7 +18,7 @@ import java.util.List;
 import java.util.Map;
 
 @Service
-public class QaCollectionService implements IQaCollectionService {
+public class QaCollectionService implements ICollectionMetaService {
     private static final org.slf4j.Logger log
             = org.slf4j.LoggerFactory.getLogger(QaCollectionService.class);
 
@@ -108,8 +109,24 @@ public class QaCollectionService implements IQaCollectionService {
         CollectionInstance instance = mdCores.getById(id);
         if (instance != null) {
             return instance.getName();
+        }else{
+            try {
+                GBaseKeyValue kv = collections.find(id);
+                if (kv != null){
+                    instance = CollectionInstance.build(kv.getIntId(), kv.getGid(), kv.getName(), kv.get(GEntityConfig.ATTRIBUTES));
+                    mdCores.put(kv.getName(), instance);
+                    return instance.getName();
+                }else{
+                    log.warn("Collection with id '" + id + "' not exist.");
+                    throw new ResponseStatusException(HttpStatus.NOT_FOUND,
+                            "Collection with id '" + id + "' does not exist.");
+                }
+            } catch (Throwable e) {
+                log.error("Failed to get collection by id={}", id, e);
+                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
+                        "Error occurred while retrieving collection with id '" + id + "'", e);
+            }
         }
-        return null;
     }
 
     public int getCollectionOrNew(String name) {
@@ -221,8 +238,6 @@ public class QaCollectionService implements IQaCollectionService {
         Integer intId = kv.getIntId();
         String id = kv.getId();
 
-        removeFromStore(name);
-
         try {
             qaRepository.deleteTable(id);
         } catch (Exception e) {
@@ -252,6 +267,7 @@ public class QaCollectionService implements IQaCollectionService {
             log.error(e.getMessage());
         }
 
+        removeFromStore(name);
         long deleted = collections.delete(intId);
 
         return deleted;

+ 2 - 0
server/src/main/java/com/giantan/data/qa/service/QaTaxonomyService.java

@@ -255,6 +255,8 @@ public class QaTaxonomyService {
 
                 node = qaTaxonomyRepository.createNode(collId, kv);
                 parentId = node.getId();
+            }else{
+                parentId = node.getId();
             }
         }
         return node;

+ 2 - 0
server/src/main/resources/application.yml

@@ -65,5 +65,7 @@ task:
 
 m3:
   url: "http://120.78.4.46:7387/v1/embeddings/embed"
+#  url: "http://192.168.101.88:18205/v1/embeddings/embed"
 qas:
   url: "http://120.78.4.46:7387/v1/collections/"
+#  url: "http://192.168.101.88:18205/v1/collections/"

+ 3 - 3
server/src/test/java/com/giantan/data/mds/MapDoubleToInt.java

@@ -1,7 +1,7 @@
 package com.giantan.data.mds;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.giantan.ai.util.JsonUtil;
+import com.giantan.ai.util.JsonUtils;
 import com.giantan.data.tasks.TaskObjectStatus;
 import com.giantan.data.tasks.TaskOperationsStatus;
 import com.giantan.data.tasks.TaskState;
@@ -82,9 +82,9 @@ public static void test3(){
     to.addState(new TaskObjectStatus("slice", TaskState.SUCCESS));
     map.put("201",to);
 
-    String json = JsonUtil.toJson(map);
+    String json = JsonUtils.toJson(map);
     System.out.println(json);
-    Map<String, TaskOperationsStatus> temp = JsonUtil.fromJson(json, new TypeReference<Map<String, TaskOperationsStatus>>() {
+    Map<String, TaskOperationsStatus> temp = JsonUtils.fromJson(json, new TypeReference<Map<String, TaskOperationsStatus>>() {
     });
     System.out.println(temp);
 }

+ 42 - 0
server/src/test/java/com/giantan/data/mds/dk/DkExampleRepositoryTest.java

@@ -0,0 +1,42 @@
+package com.giantan.data.mds.dk;
+
+import com.giantan.data.dk.constant.DkExampleConfig;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.List;
+
+@SpringBootTest
+public class DkExampleRepositoryTest {
+    @Autowired
+    JdbcTemplate jdbcTemplate;
+
+    @Test
+    void createTable() throws Throwable {
+        //List<String> sqls = GEntityConfig.createGEntityTable(GKG_CATALOG, EntryConfig.GKG_ENTRY_COLLECTIONS);
+        //List<String> sqls = GEntityConfig.createGEntityTable(KvConfig.GKG_CATALOG, KvConfig.GKG_ENTRY_COLLECTIONS);
+        List<String> sqls = DkExampleConfig.createTable("dkdb", "example_3","dk_3");
+        String result = String.join("\n", sqls);
+        System.out.println(result);
+        jdbcTemplate.execute(result);
+    }
+
+    @Test
+    void dropTable() throws Throwable {
+        List<String> sqls = DkExampleConfig.dropTable("dkdb", "demo11");
+        String result = String.join("\n", sqls);
+        System.out.println(result);
+        jdbcTemplate.execute(result);
+    }
+
+
+    @Test
+    void clearTable() throws Throwable {
+        List<String> sqls = DkExampleConfig.clearAll("dkdb", "demo11");
+        String result = String.join("\n", sqls);
+        System.out.println(result);
+        jdbcTemplate.execute(result);
+    }
+}

+ 1 - 1
server/src/test/java/com/giantan/data/mds/DkRepositoryTest.java → server/src/test/java/com/giantan/data/mds/dk/DkRepositoryTest.java

@@ -1,4 +1,4 @@
-package com.giantan.data.mds;
+package com.giantan.data.mds.dk;
 
 import com.giantan.data.dk.constant.DkConfig;
 import org.junit.jupiter.api.Test;

+ 14 - 0
server/src/test/java/com/giantan/data/mds/index/MilvusSearchRequestUtilsTest.java

@@ -0,0 +1,14 @@
+package com.giantan.data.mds.index;
+
+import com.giantan.data.index.MilvusSearchRequestBuilder;
+
+import java.util.List;
+import java.util.Map;
+
+public class MilvusSearchRequestUtilsTest {
+    public static void main(String[] args) {
+        Map<String, Object> m = Map.of("tags", List.of("test","demo"),"path","/aa/b","filterExpression","docId > 0");
+        Map<String, Object> m1 = MilvusSearchRequestBuilder.from(m);
+        System.out.println(m1);
+    }
+}