commit 38d59631088f2839d75e2d3d34be3a2581890200 Author: xgc Date: Thu May 9 12:24:05 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b56f1dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +README.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/README.cn.md b/README.cn.md new file mode 100644 index 0000000..422fb3c --- /dev/null +++ b/README.cn.md @@ -0,0 +1,110 @@ +# MilvusPlus:向量数据库增强操作库 + +MilvusPlus 是一个功能强大的 Java 库,旨在简化与 Milvus 向量数据库的交互,为开发者提供类似 MyBatis-Plus 注解和方法调用风格的直观 API。 + +## 目录 + +1. [特性](#特性) +2. [快速开始](#快速开始) +3. [应用场景](#应用场景) +4. [注解说明](#注解说明) +5. [贡献](#贡献) +6. [许可证](#许可证) + +## 特性 + +- **注解式配置**:采用与 MyBatis-Plus 类似的注解方式来配置你的实体模型。 +- **直观的 API**:直接的 API 设计,让向量数据库操作变得自然而然。 +- **易于扩展**:以可扩展性为核心设计,方便新增功能。 +- **类型安全**:利用 Java 的类型安全特性,最大程度减少错误。 + +## 快速开始 + +将 MilvusPlus 添加到你的项目中: + +**Maven:** +```xml + + io.github.javpower + MilvusPlus + 0.0.1-SNAPSHOT + +``` + +## 应用场景 + +向量数据库在以下场景中特别有用: + +- **相似性搜索**:快速检索与给定向量最相似的项。 +- **推荐系统**:根据用户行为和偏好推荐相关内容。 +- **图像检索**:在大规模图像库中找到与查询图像最相似的图像。 +- **自然语言处理**:将文本转换为向量并执行语义搜索。 +- **生物信息学**:分析和比较生物序列,如蛋白质和基因组数据。 + +## 注解说明 + +MilvusPlus 引入了几个注解,用于将你的 Java 实体映射到 Milvus 集合: + +- `@MilvusCollection`:表示一个 Java 类是一个 Milvus 集合。 +- `@MilvusField`:将 Java 字段映射到 Milvus 字段,并提供数据类型、维度等选项。 +- `@MilvusIndex`:在 Milvus 字段上定义索引。 + +示例用法: + +```java + +@Data +@MilvusCollection(name = "face_collection") // 指定Milvus集合的名称 +public class Face { + @MilvusField( + name = "person_id", // 字段名称 + dataType = DataType.Int64, // 数据类型为64位整数 + isPrimaryKey = true, // 标记为主键 + autoID = true // 假设这个ID是自动生成的 + ) + private Long personId; // 人员的唯一标识符 + + @MilvusField( + name = "face_vector", // 字段名称 + dataType = DataType.FloatVector, // 数据类型为浮点型向量 + dimension = 128, // 向量维度,假设人脸特征向量的维度是128 + isPartitionKey = false // 假设这个字段不是分区键 + ) + @MilvusIndex( + indexType = IndexParam.IndexType.IVF_FLAT, // 使用IVF_FLAT索引类型 + metricType = IndexParam.MetricType.L2, // 使用L2距离度量类型 + indexName = "face_index", // 索引名称 + extraParams = { // 指定额外的索引参数 + @ExtraParam(key = "nlist", value = "100") // 例如,IVF的nlist参数 + } + ) + private List faceVector; // 存储人脸特征的向量 +} +``` +``` +public static void main(String[] args) { + MilvusWrapper wrapper=new MilvusWrapper(); + List vector = Lists.newArrayList(0.1f,0.2f,0.3f); + MilvusResp resp = wrapper.lambda() + .eq(Face::getPersonId,1l) + .addVector(vector) + .query(); + } + + +``` + +## 贡献 + +欢迎贡献! + +- 报告问题或建议功能,[创建一个 issue](https://github.com/yourusername/MilvusPlus/issues/new)。 +- 提交更改,[创建一个 pull request](https://github.com/yourusername/MilvusPlus/compare)。 + +## 许可证 + +MilvusPlus 是开源的,遵循 [MIT 许可证](https://github.com/yourusername/MilvusPlus/blob/master/LICENSE)。 + +## 联系 + +如有问题或需要支持,请联系 [javpower@163.com](mailto:javpower@163.com) 。 diff --git a/milvus-demo/pom.xml b/milvus-demo/pom.xml new file mode 100644 index 0000000..73e2d5e --- /dev/null +++ b/milvus-demo/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + io.github.javpower + milvus-demo + 0.0.1-SNAPSHOT + milvus-demo + milvus-demo + + 1.8 + UTF-8 + UTF-8 + 2.6.13 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.github.javpower + milvus-plus-boot-starter + 2.4.0 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + io.github.javpower.milvus.demo.MilvusDemoApplication + true + + + + repackage + + repackage + + + + + + + + diff --git a/milvus-demo/src/main/java/io/github/javpower/milvus/demo/MilvusDemoApplication.java b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/MilvusDemoApplication.java new file mode 100644 index 0000000..a10ac2d --- /dev/null +++ b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/MilvusDemoApplication.java @@ -0,0 +1,13 @@ +package io.github.javpower.milvus.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MilvusDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MilvusDemoApplication.class, args); + } + +} diff --git a/milvus-demo/src/main/java/io/github/javpower/milvus/demo/model/Face.java b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/model/Face.java new file mode 100644 index 0000000..43382c2 --- /dev/null +++ b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/model/Face.java @@ -0,0 +1,39 @@ +package io.github.javpower.milvus.demo.model; + +import io.github.javpower.milvus.plus.annotation.ExtraParam; +import io.github.javpower.milvus.plus.annotation.MilvusCollection; +import io.github.javpower.milvus.plus.annotation.MilvusField; +import io.github.javpower.milvus.plus.annotation.MilvusIndex; +import io.milvus.v2.common.DataType; +import io.milvus.v2.common.IndexParam; +import lombok.Data; + +import java.util.List; + +@Data +@MilvusCollection(name = "face_collection") // 指定Milvus集合的名称 +public class Face { + @MilvusField( + name = "person_id", // 字段名称 + dataType = DataType.Int64, // 数据类型为64位整数 + isPrimaryKey = true, // 标记为主键 + autoID = true // 假设这个ID是自动生成的 + ) + private Long personId; // 人员的唯一标识符 + + @MilvusField( + name = "face_vector", // 字段名称 + dataType = DataType.FloatVector, // 数据类型为浮点型向量 + dimension = 128, // 向量维度,假设人脸特征向量的维度是128 + isPartitionKey = false // 假设这个字段不是分区键 + ) + @MilvusIndex( + indexType = IndexParam.IndexType.IVF_FLAT, // 使用IVF_FLAT索引类型 + metricType = IndexParam.MetricType.L2, // 使用L2距离度量类型 + indexName = "face_index", // 索引名称 + extraParams = { // 指定额外的索引参数 + @ExtraParam(key = "nlist", value = "100") // 例如,IVF的nlist参数 + } + ) + private List faceVector; // 存储人脸特征的向量 +} \ No newline at end of file diff --git a/milvus-demo/src/main/java/io/github/javpower/milvus/demo/test/TestWrapper.java b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/test/TestWrapper.java new file mode 100644 index 0000000..6c931ef --- /dev/null +++ b/milvus-demo/src/main/java/io/github/javpower/milvus/demo/test/TestWrapper.java @@ -0,0 +1,21 @@ +//package io.github.javpower.milvus.demo.test; +// +//import com.google.common.collect.Lists; +//import io.github.javpower.milvus.plus.core.conditions.MilvusWrapper; +//import io.github.javpower.milvus.plus.model.MilvusResp; +// +//import java.util.List; +// +//public class TestWrapper { +// public static void main(String[] args) { +// MilvusWrapper wrapper=new MilvusWrapper(); +// List vector = Lists.newArrayList(0.1f,0.2f,0.3f); +// MilvusResp resp = wrapper.lambda() +// .eq(Face::getPersonId,1l) +// .addVector(vector) +// .query(); +// } +// +// +// +//} diff --git a/milvus-demo/src/main/resources/application.yml b/milvus-demo/src/main/resources/application.yml new file mode 100644 index 0000000..3d53b32 --- /dev/null +++ b/milvus-demo/src/main/resources/application.yml @@ -0,0 +1,5 @@ +server: + port: 8131 +milvus: + uri: localhost:8999 + token: sss \ No newline at end of file diff --git a/milvus-plus-boot-starter/pom.xml b/milvus-plus-boot-starter/pom.xml new file mode 100644 index 0000000..968b599 --- /dev/null +++ b/milvus-plus-boot-starter/pom.xml @@ -0,0 +1,201 @@ + + + 4.0.0 + io.github.javpower + milvus-plus-boot-starter + 2.4.0 + milvus-plus-boot-starter + a tool about milvus-plus + https://github.com/javpower/milvus-plus + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + https://github.com/javpower/milvus-plus-boot-starter + scm:git@github.com/javpower/milvus-plus.git + scm:git@github.com/javpower/milvus-plus.git + + + + gc.x + javpower@163.com + https://github.com/javpower + +8 + + + + 1.8 + UTF-8 + UTF-8 + 2.7.13 + 2.3.2 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + com.google.protobuf + protobuf-java + 3.24.0 + + + org.projectlombok + lombok + 1.18.22 + + + io.milvus + milvus-sdk-java + 2.4.0 + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.hadoop + hadoop-client + + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + + + com.azure + azure-storage-blob + + + com.azure + azure-identity + + + + + net.dreamlu + mica-auto + ${mica-auto.vaersion} + provided + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + private + true + UTF-8 + UTF-8 + UTF-8 + -Xdoclint:none + + + + package + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + + + + + diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/ExtraParam.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/ExtraParam.java new file mode 100644 index 0000000..14bed0e --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/ExtraParam.java @@ -0,0 +1,15 @@ +package io.github.javpower.milvus.plus.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * @author xgc + **/ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExtraParam { + String key(); + String value(); +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusCollection.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusCollection.java new file mode 100644 index 0000000..eb6d249 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusCollection.java @@ -0,0 +1,14 @@ +package io.github.javpower.milvus.plus.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * @author xgc + **/ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MilvusCollection { + String name(); // 集合的名称 +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusField.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusField.java new file mode 100644 index 0000000..3e2a7d2 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusField.java @@ -0,0 +1,26 @@ +package io.github.javpower.milvus.plus.annotation; + +import io.milvus.v2.common.DataType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * @author xgc + **/ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MilvusField { + + String name() default ""; // 字段名称,默认使用 Java 字段名 + DataType dataType() default DataType.FloatVector; // 数据类型,默认为 FLOAT_VECTOR + int dimension() default -1; // 向量维度,仅对向量类型有效 + boolean isPrimaryKey() default false; // 是否为主键 + boolean autoID() default false; // 是否自动生成 + String description() default ""; // 字段描述 + DataType elementType() default DataType.None; // 数组或集合中元素的类型,默认为 INVALID + int maxLength() default -1; // 数组或字符串类型的最大长度,默认为 -1(不指定) + int maxCapacity() default -1; // 集合类型的最大容量,默认为 -1(不指定) + boolean isPartitionKey() default false; // 是否为分区键 +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusIndex.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusIndex.java new file mode 100644 index 0000000..343beba --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/annotation/MilvusIndex.java @@ -0,0 +1,20 @@ +package io.github.javpower.milvus.plus.annotation; + + +import io.milvus.v2.common.IndexParam; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * @author xgc + **/ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MilvusIndex { + IndexParam.IndexType indexType() default IndexParam.IndexType.FLAT; // 索引类型 + IndexParam.MetricType metricType() default IndexParam.MetricType.L2; // 度量类型 + String indexName() default ""; // 索引名称 + ExtraParam[] extraParams() default {}; // 指定额外的参数 +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/builder/CollectionSchemaBuilder.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/builder/CollectionSchemaBuilder.java new file mode 100644 index 0000000..a02eafe --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/builder/CollectionSchemaBuilder.java @@ -0,0 +1,46 @@ +package io.github.javpower.milvus.plus.builder; + +import io.github.javpower.milvus.plus.service.MilvusClient; +import io.milvus.exception.MilvusException; +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AddFieldReq; +import io.milvus.v2.service.collection.request.CreateCollectionReq; +import io.milvus.v2.service.index.request.CreateIndexReq; +/** + * @author xgc + **/ +public class CollectionSchemaBuilder { + + private final String collectionName; + private final MilvusClient wrapper; + private final CreateCollectionReq.CollectionSchema schema; + + public CollectionSchemaBuilder(String collectionName, MilvusClient wrapper) { + this.collectionName = collectionName; + this.wrapper = wrapper; + this.schema = wrapper.client.createSchema(); + } + + public CollectionSchemaBuilder addField(AddFieldReq field) { + schema.addField(field); + return this; + } + public CollectionSchemaBuilder addField(AddFieldReq ... fields) { + for (AddFieldReq field : fields) { + schema.addField(field); + } + return this; + } + + public void createSchema() throws MilvusException { + CreateCollectionReq req=CreateCollectionReq.builder().collectionName(this.collectionName).collectionSchema(this.schema).build(); + wrapper.client.createCollection(req); + } + public void createIndex(java.util.List indexParams) throws MilvusException { + CreateIndexReq req = CreateIndexReq.builder() + .collectionName(collectionName) + .indexParams(indexParams) + .build(); + wrapper.client.createIndex(req); + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/ConversionCache.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/ConversionCache.java new file mode 100644 index 0000000..cbc2722 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/ConversionCache.java @@ -0,0 +1,15 @@ +package io.github.javpower.milvus.plus.cache; + +import io.github.javpower.milvus.plus.model.MilvusEntity; +import lombok.Data; +/** + * @author xgc + **/ +@Data +public class ConversionCache { + private String collectionName; + private FieldFunctionCache fieldFunctionCache; + private PropertyCache propertyCache; + private MilvusEntity milvusEntity; + +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/FieldFunctionCache.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/FieldFunctionCache.java new file mode 100644 index 0000000..af5bc15 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/FieldFunctionCache.java @@ -0,0 +1,44 @@ +package io.github.javpower.milvus.plus.cache; + +import io.github.javpower.milvus.plus.core.FieldFunction; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +/** + * @author xgc + **/ +public class FieldFunctionCache { + + public Map> propertyToFunctionMap = new HashMap<>();//属性名称获取对应的函数 + + public String getMethodName(Field field,String fieldName){ + String capitalizedFieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); + String getMethodName = "get" + capitalizedFieldName; // 构建get方法名称 + + // 检查字段类型是否为boolean,如果是,get方法名称应以is开头 + if (field.getType() == boolean.class) { + getMethodName = "is" + capitalizedFieldName; + } + return getMethodName; + } + public String getFieldName(FieldFunction fieldFunction) { + for (Map.Entry> entry : propertyToFunctionMap.entrySet()) { + if (entry.getValue().equals(fieldFunction)) { + return entry.getKey(); // 返回匹配的属性名称 + } + } + return null; // 如果没有找到匹配项,返回null + } + public FieldFunction createFunction(Method method) { + return (instance) -> { + try { + return method.invoke(instance); + } catch (Exception e) { + // 异常处理 + return null; + } + }; + } +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/MilvusCache.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/MilvusCache.java new file mode 100644 index 0000000..c518ff8 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/MilvusCache.java @@ -0,0 +1,14 @@ +package io.github.javpower.milvus.plus.cache; + +import lombok.Data; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +/** + * @author xgc + **/ +@Data +public class MilvusCache { + public static final Map,ConversionCache> milvusCache=new ConcurrentHashMap<>(); + +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/PropertyCache.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/PropertyCache.java new file mode 100644 index 0000000..cf00224 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/cache/PropertyCache.java @@ -0,0 +1,12 @@ +package io.github.javpower.milvus.plus.cache; + +import java.util.HashMap; +import java.util.Map; +/** + * @author xgc + **/ +public class PropertyCache { + + public Map functionToPropertyMap = new HashMap<>(); //属性名称->表名称 + +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusCollectionConfig.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusCollectionConfig.java new file mode 100644 index 0000000..c06ede3 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusCollectionConfig.java @@ -0,0 +1,38 @@ +package io.github.javpower.milvus.plus.config; + +import io.github.javpower.milvus.plus.annotation.MilvusCollection; +import io.github.javpower.milvus.plus.service.MilvusCollectionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * @author xgc + **/ +@Component +public class MilvusCollectionConfig implements ApplicationRunner { + + private final ApplicationContext applicationContext; + private final MilvusCollectionService milvusCollectionService; + + @Autowired + public MilvusCollectionConfig(ApplicationContext applicationContext, MilvusCollectionService milvusCollectionService) { + this.applicationContext = applicationContext; + this.milvusCollectionService = milvusCollectionService; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + // 获取所有带有@MilvusCollection注解的类 + String[] beanNames = applicationContext.getBeanNamesForAnnotation(MilvusCollection.class); + Class[] annotatedClasses = Arrays.stream(beanNames) + .map(applicationContext::getType) + .toArray(Class[]::new); + // 调用业务处理服务 + milvusCollectionService.performBusinessLogic(Arrays.asList(annotatedClasses)); + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusConfig.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusConfig.java new file mode 100644 index 0000000..e5671d4 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusConfig.java @@ -0,0 +1,23 @@ +package io.github.javpower.milvus.plus.config; + +import io.milvus.v2.client.ConnectConfig; +import io.milvus.v2.client.MilvusClientV2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +/** + * @author xgc + **/ +@Configuration +public class MilvusConfig { + @Autowired + private MilvusProperties properties; + @Bean + public MilvusClientV2 milvusClientV2() { + ConnectConfig connectConfig = ConnectConfig.builder() + .uri(properties.getUri()) + .token(properties.getToken()) + .build(); + return new MilvusClientV2(connectConfig); + } +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusProperties.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusProperties.java new file mode 100644 index 0000000..991a75b --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/config/MilvusProperties.java @@ -0,0 +1,15 @@ +package io.github.javpower.milvus.plus.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +/** + * @author xgc + **/ +@Data +@ConfigurationProperties(prefix = "milvus") +@Component +public class MilvusProperties { + private String uri; + private String token; +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/MilvusEntityConverter.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/MilvusEntityConverter.java new file mode 100644 index 0000000..a901c17 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/MilvusEntityConverter.java @@ -0,0 +1,97 @@ +package io.github.javpower.milvus.plus.converter; + + +import io.github.javpower.milvus.plus.annotation.MilvusCollection; +import io.github.javpower.milvus.plus.annotation.MilvusField; +import io.github.javpower.milvus.plus.annotation.MilvusIndex; +import io.github.javpower.milvus.plus.cache.ConversionCache; +import io.github.javpower.milvus.plus.cache.FieldFunctionCache; +import io.github.javpower.milvus.plus.cache.MilvusCache; +import io.github.javpower.milvus.plus.cache.PropertyCache; +import io.github.javpower.milvus.plus.core.FieldFunction; +import io.github.javpower.milvus.plus.model.MilvusEntity; +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AddFieldReq; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +/** + * @author xgc + **/ +public class MilvusEntityConverter { + + public static MilvusEntity convert(Class entityClass) { + MilvusEntity milvus=new MilvusEntity(); + MilvusCollection collectionAnnotation = entityClass.getAnnotation(MilvusCollection.class); + if (collectionAnnotation == null) { + throw new IllegalArgumentException("Entity must be annotated with @MilvusCollection"); + } + String collectionName = collectionAnnotation.name(); + milvus.setCollectionName(collectionName); + List milvusFields = new ArrayList<>(); + List indexParams=new ArrayList<>(); + PropertyCache propertyCache=new PropertyCache(); + FieldFunctionCache fieldFunctionCache=new FieldFunctionCache(); + for (Field field : entityClass.getDeclaredFields()) { + MilvusField fieldAnnotation = field.getAnnotation(MilvusField.class); + if (fieldAnnotation != null) { + String fieldName = fieldAnnotation.name().isEmpty() ? field.getName() : fieldAnnotation.name(); + propertyCache.functionToPropertyMap.put(field.getName(),fieldName); + String methodName = fieldFunctionCache.getMethodName(field, field.getName()); + try { + Method method = entityClass.getMethod(methodName); + FieldFunction function = fieldFunctionCache.createFunction(method); + fieldFunctionCache.propertyToFunctionMap.put(field.getName(),function); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + AddFieldReq milvusField = AddFieldReq.builder() + .fieldName(fieldName) + .dataType(fieldAnnotation.dataType()) + .isPrimaryKey(fieldAnnotation.isPrimaryKey()) + .autoID(fieldAnnotation.autoID()) + .description(StringUtils.isNotEmpty(fieldAnnotation.description()) ? fieldAnnotation.description() : null) + .dimension(fieldAnnotation.dimension()) + .maxLength(fieldAnnotation.maxLength() > 0 ? fieldAnnotation.maxLength() : null) + .isPartitionKey(fieldAnnotation.isPartitionKey()) + .elementType(fieldAnnotation.elementType()) + .maxCapacity(fieldAnnotation.maxCapacity() > 0 ? fieldAnnotation.maxCapacity() : null) + .build(); + milvusFields.add(milvusField); + // 构建IndexParam对象 + IndexParam indexParam = createIndexParam(field,fieldName); + if(indexParam!=null){ + indexParams.add(indexParam); + } + } + } + milvus.setMilvusFields(milvusFields); + milvus.setIndexParams(indexParams); + //缓存 + ConversionCache conversionCache=new ConversionCache<>(); + conversionCache.setMilvusEntity(milvus); + conversionCache.setCollectionName(collectionName); + conversionCache.setPropertyCache(propertyCache); + conversionCache.setFieldFunctionCache(fieldFunctionCache); + MilvusCache.milvusCache.put(entityClass,conversionCache); + return milvus; + } + + + private static IndexParam createIndexParam(Field field,String fieldName) { + MilvusIndex fieldAnnotation = field.getAnnotation(MilvusIndex.class); + if (fieldAnnotation == null) { + return null; + } + return IndexParam.builder() + .fieldName(fieldAnnotation.indexName().isEmpty() ? fieldName : fieldAnnotation.indexName()) + .indexType(fieldAnnotation.indexType()) + .metricType(fieldAnnotation.metricType()) // 默认使用L2距离,根据需要调整 + .build(); + } + + +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/SearchRespConverter.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/SearchRespConverter.java new file mode 100644 index 0000000..861b5ef --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/converter/SearchRespConverter.java @@ -0,0 +1,37 @@ +package io.github.javpower.milvus.plus.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.javpower.milvus.plus.model.MilvusResp; +import io.github.javpower.milvus.plus.model.MilvusResult; +import io.milvus.v2.service.vector.response.SearchResp; + +import java.util.List; +/** + * @author xgc + **/ +public class SearchRespConverter { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static MilvusResp convertSearchRespToMilvusResp(SearchResp searchResp, Class entityType){ + List> searchResults = searchResp.getSearchResults(); + + // 反序列化JSON字符串到具体的MilvusResult对象列表 + TypeReference>>> typeRef = new TypeReference>>>() {}; + List>> milvusResults = null; + try { + // 将searchResults转换为JSON字符串 + String jsonResults = objectMapper.writeValueAsString(searchResults); + milvusResults = objectMapper.readValue(jsonResults, typeRef); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + // 创建MilvusResp对象并设置结果 + MilvusResp milvusResp = new MilvusResp<>(); + milvusResp.setRes(milvusResults); + return milvusResp; + } + +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/FieldFunction.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/FieldFunction.java new file mode 100644 index 0000000..a2f64c9 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/FieldFunction.java @@ -0,0 +1,8 @@ +package io.github.javpower.milvus.plus.core; +/** + * @author xgc + **/ +@FunctionalInterface +public interface FieldFunction { + R apply(T entity); +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/conditions/MilvusWrapper.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/conditions/MilvusWrapper.java new file mode 100644 index 0000000..179717e --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/core/conditions/MilvusWrapper.java @@ -0,0 +1,380 @@ +package io.github.javpower.milvus.plus.core.conditions; + +import io.github.javpower.milvus.plus.annotation.MilvusCollection; +import io.github.javpower.milvus.plus.cache.ConversionCache; +import io.github.javpower.milvus.plus.cache.FieldFunctionCache; +import io.github.javpower.milvus.plus.cache.MilvusCache; +import io.github.javpower.milvus.plus.cache.PropertyCache; +import io.github.javpower.milvus.plus.converter.SearchRespConverter; +import io.github.javpower.milvus.plus.core.FieldFunction; +import io.github.javpower.milvus.plus.model.MilvusResp; +import io.github.javpower.milvus.plus.service.MilvusClient; +import io.github.javpower.milvus.plus.util.SpringUtils; +import io.milvus.exception.MilvusException; +import io.milvus.v2.common.ConsistencyLevel; +import io.milvus.v2.service.vector.request.SearchReq; +import io.milvus.v2.service.vector.response.SearchResp; +import lombok.Data; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +/** + * @author xgc + **/ +public class MilvusWrapper { + + + /** + * 创建搜索构建器实例 + * @param collectionName 集合名称 + * @return 返回搜索构建器 + */ + public LambdaSearchWrapper lambda() { + // 获取实例化的类的类型参数T + Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + Class entityType = (Class) type; + // 从实体类上获取@MilvusCollection注解 + MilvusCollection collectionAnnotation = entityType.getAnnotation(MilvusCollection.class); + if (collectionAnnotation == null) { + throw new IllegalStateException("Entity type " + entityType.getName() + " is not annotated with @MilvusCollection."); + } + ConversionCache conversionCache = MilvusCache.milvusCache.get(entityType); + String collectionName = conversionCache.getCollectionName(); + // 使用SpringUtil获取MilvusClient实例 + MilvusClient client = SpringUtils.getBean(MilvusClient.class); + // 使用注解中的集合名称创建LambdaSearchWrapper实例 + return new LambdaSearchWrapper<>(collectionName, client,conversionCache,entityType); + } + + /** + * 搜索构建器内部类,用于构建搜索请求 + */ + @Data + public static class LambdaSearchWrapper { + private ConversionCache conversionCache; + private Class entityType; + private String collectionName; + private String annsField; + private int topK; + private List filters = new ArrayList<>(); + private List> vectors = new ArrayList<>(); + private long offset; + private long limit; + private int roundDecimal; + private String searchParams; + private long guaranteeTimestamp; + private ConsistencyLevel consistencyLevel; + private boolean ignoreGrowing; + private MilvusClient client; + + public LambdaSearchWrapper(String collectionName, MilvusClient client,ConversionCache conversionCache,Class entityType) { + this.collectionName = collectionName; + this.client = client; + this.conversionCache=conversionCache; + this.entityType=entityType; + } + + public LambdaSearchWrapper() { + + } + + // addVector + + public LambdaSearchWrapper addVector(List vector) { + vectors.add(vector); + return this; + } + + // Common comparison operations + public LambdaSearchWrapper eq(String fieldName, Object value) { + return addFilter(fieldName, "==", value); + } + + public LambdaSearchWrapper ne(String fieldName, Object value) { + return addFilter(fieldName, "!=", value); + } + + public LambdaSearchWrapper gt(String fieldName, Object value) { + return addFilter(fieldName, ">", value); + } + + public LambdaSearchWrapper ge(String fieldName, Object value) { + return addFilter(fieldName, ">=", value); + } + + public LambdaSearchWrapper lt(String fieldName, Object value) { + return addFilter(fieldName, "<", value); + } + + public LambdaSearchWrapper le(String fieldName, Object value) { + return addFilter(fieldName, "<=", value); + } + + // Range operation + public LambdaSearchWrapper between(String fieldName, Object start, Object end) { + String filter = String.format("%s >= %s && %s <= %s", fieldName, convertValue(start), fieldName, convertValue(end)); + filters.add(filter); + return this; + } + + // Null check + public LambdaSearchWrapper isNull(String fieldName) { + filters.add(fieldName + " == null"); + return this; + } + + public LambdaSearchWrapper isNotNull(String fieldName) { + filters.add(fieldName + " != null"); + return this; + } + + // In operator + public LambdaSearchWrapper in(String fieldName, List values) { + String valueList = values.stream() + .map(this::convertValue) + .collect(Collectors.joining(", ", "[", "]")); + filters.add(fieldName + " in " + valueList); + return this; + } + + // Like operator + public LambdaSearchWrapper like(String fieldName, String value) { + filters.add(fieldName + " like '%" + value + "%'"); + return this; + } + + // JSON array operations + public LambdaSearchWrapper jsonContains(String fieldName, Object value) { + filters.add("JSON_CONTAINS(" + fieldName + ", " + convertValue(value) + ")"); + return this; + } + + public LambdaSearchWrapper jsonContainsAll(String fieldName, List values) { + String valueList = convertValues(values); + filters.add("JSON_CONTAINS_ALL(" + fieldName + ", " + valueList + ")"); + return this; + } + + public LambdaSearchWrapper jsonContainsAny(String fieldName, List values) { + String valueList = convertValues(values); + filters.add("JSON_CONTAINS_ANY(" + fieldName + ", " + valueList + ")"); + return this; + } + + // Array operations + public LambdaSearchWrapper arrayContains(String fieldName, Object value) { + filters.add("ARRAY_CONTAINS(" + fieldName + ", " + convertValue(value) + ")"); + return this; + } + + public LambdaSearchWrapper arrayContainsAll(String fieldName, List values) { + String valueList = convertValues(values); + filters.add("ARRAY_CONTAINS_ALL(" + fieldName + ", " + valueList + ")"); + return this; + } + + public LambdaSearchWrapper arrayContainsAny(String fieldName, List values) { + String valueList = convertValues(values); + filters.add("ARRAY_CONTAINS_ANY(" + fieldName + ", " + valueList + ")"); + return this; + } + + public LambdaSearchWrapper arrayLength(String fieldName, int length) { + filters.add(fieldName + ".length() == " + length); + return this; + } + + + + public LambdaSearchWrapper eq(FieldFunction fieldName, Object value) { + return addFilter(fieldName, "==", value); + } + + public LambdaSearchWrapper ne(FieldFunction fieldName, Object value) { + return addFilter(fieldName, "!=", value); + } + + public LambdaSearchWrapper gt(FieldFunction fieldName, Object value) { + return addFilter(fieldName, ">", value); + } + + public LambdaSearchWrapper ge(FieldFunction fieldName, Object value) { + return addFilter(fieldName, ">=", value); + } + + public LambdaSearchWrapper lt(FieldFunction fieldName, Object value) { + return addFilter(fieldName, "<", value); + } + + public LambdaSearchWrapper le(FieldFunction fieldName, Object value) { + return addFilter(fieldName, "<=", value); + } + + // Range operation + public LambdaSearchWrapper between(FieldFunction fieldName, Object start, Object end) { + String fn = getFieldName(fieldName); + String filter = String.format("%s >= %s && %s <= %s", fn, convertValue(start), fn, convertValue(end)); + filters.add(filter); + return this; + } + + // Null check + public LambdaSearchWrapper isNull(FieldFunction fieldName) { + String fn = getFieldName(fieldName); + filters.add(fn + " == null"); + return this; + } + + public LambdaSearchWrapper isNotNull(FieldFunction fieldName) { + String fn = getFieldName(fieldName); + filters.add(fn + " != null"); + return this; + } + + // In operator + public LambdaSearchWrapper in(FieldFunction fieldName, List values) { + String fn = getFieldName(fieldName); + String valueList = values.stream() + .map(this::convertValue) + .collect(Collectors.joining(", ", "[", "]")); + filters.add(fn + " in " + valueList); + return this; + } + + // Like operator + public LambdaSearchWrapper like(FieldFunction fieldName, String value) { + String fn = getFieldName(fieldName); + filters.add(fn + " like '%" + value + "%'"); + return this; + } + + // JSON array operations + public LambdaSearchWrapper jsonContains(FieldFunction fieldName, Object value) { + String fn = getFieldName(fieldName); + filters.add("JSON_CONTAINS(" + fn + ", " + convertValue(value) + ")"); + return this; + } + + public LambdaSearchWrapper jsonContainsAll(FieldFunction fieldName, List values) { + String fn = getFieldName(fieldName); + String valueList = convertValues(values); + filters.add("JSON_CONTAINS_ALL(" + fn + ", " + valueList + ")"); + return this; + } + + public LambdaSearchWrapper jsonContainsAny(FieldFunction fieldName, List values) { + String fn = getFieldName(fieldName); + String valueList = convertValues(values); + filters.add("JSON_CONTAINS_ANY(" + fn + ", " + valueList + ")"); + return this; + } + + // Array operations + public LambdaSearchWrapper arrayContains(FieldFunction fieldName, Object value) { + String fn = getFieldName(fieldName); + filters.add("ARRAY_CONTAINS(" + fn + ", " + convertValue(value) + ")"); + return this; + } + + public LambdaSearchWrapper arrayContainsAll(FieldFunction fieldName, List values) { + String fn = getFieldName(fieldName); + String valueList = convertValues(values); + filters.add("ARRAY_CONTAINS_ALL(" + fn + ", " + valueList + ")"); + return this; + } + public LambdaSearchWrapper arrayContainsAny(FieldFunction fieldName, List values) { + String fn = getFieldName(fieldName); + String valueList = convertValues(values); + filters.add("ARRAY_CONTAINS_ANY(" + fn + ", " + valueList + ")"); + return this; + } + + public LambdaSearchWrapper arrayLength(FieldFunction fieldName, int length) { + String fn = getFieldName(fieldName); + filters.add(fn + ".length() == " + length); + return this; + } + + // Logic operations + public LambdaSearchWrapper and(LambdaSearchWrapper other) { + filters.add("(" + String.join(" && ", other.filters) + ")"); + return this; + } + + public LambdaSearchWrapper or(LambdaSearchWrapper other) { + filters.add("(" + String.join(" || ", other.filters) + ")"); + return this; + } + + public LambdaSearchWrapper not() { + filters.add("not (" + String.join(" && ", filters) + ")"); + return this; + } + + // Helper methods + private String convertValue(Object value) { + if (value instanceof String) { + return "'" + value.toString().replace("'", "\\'") + "'"; + } + return value.toString(); + } + + private String convertValues(List values) { + return values.stream() + .map(this::convertValue) + .collect(Collectors.joining(", ", "[", "]")); + } + + private LambdaSearchWrapper addFilter(String fieldName, String op, Object value) { + filters.add(fieldName + " " + op + " " + convertValue(value)); + return this; + } + private LambdaSearchWrapper addFilter(FieldFunction fieldFunction, String op, Object value) { + String fieldName = getFieldName(fieldFunction); + filters.add(fieldName + " " + op + " " + convertValue(value)); + return this; + } + private String getFieldName(FieldFunction fieldFunction) { + FieldFunctionCache fieldFunctionCache = conversionCache.getFieldFunctionCache(); + String fn = fieldFunctionCache.getFieldName(fieldFunction); + PropertyCache propertyCache = conversionCache.getPropertyCache(); + String fieldName = propertyCache.functionToPropertyMap.get(fn); + return fieldName; + } + + /** + * 构建完整的搜索请求 + * @return 搜索请求对象 + */ + private SearchReq build() { + SearchReq.SearchReqBuilder builder = SearchReq.builder() + .collectionName(collectionName) + .annsField(annsField) + .topK(topK); + if (!vectors.isEmpty()) { + builder.data(vectors); + } + String filterStr = filters.stream().collect(Collectors.joining(" && ")); + if (filterStr != null && !filterStr.isEmpty()) { + builder.filter(filterStr); + } + // Set other parameters as needed + return builder.build(); + } + + /** + * 执行搜索 + * @return 搜索响应对象 + * @throws MilvusException 如果搜索执行失败 + */ + public MilvusResp query() throws MilvusException { + SearchReq searchReq = build(); + SearchResp search = client.client.search(searchReq); + MilvusResp tMilvusResp = SearchRespConverter.convertSearchRespToMilvusResp(search, entityType); + return tMilvusResp; + } + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusEntity.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusEntity.java new file mode 100644 index 0000000..25d3fb8 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusEntity.java @@ -0,0 +1,17 @@ +package io.github.javpower.milvus.plus.model; + +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AddFieldReq; +import lombok.Data; + +import java.util.List; +/** + * @author xgc + **/ +@Data +public class MilvusEntity { + private String collectionName; + private List indexParams; + private List milvusFields; + +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusField.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusField.java new file mode 100644 index 0000000..fbdce61 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusField.java @@ -0,0 +1,42 @@ +package io.github.javpower.milvus.plus.model; +import io.milvus.v2.common.DataType; +import io.milvus.v2.service.collection.request.AddFieldReq; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +/** + * @author xgc + **/ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MilvusField { + private String fieldName; + private DataType dataType; + private Boolean isPrimaryKey; + private Boolean autoId; + private String description; + private Integer dimension; + private Integer maxLength; + private Boolean isPartitionKey; + private DataType elementType; + private Integer maxCapacity; + + public AddFieldReq to() { + return AddFieldReq.builder() + .fieldName(fieldName) + .dataType(dataType) + .isPrimaryKey(isPrimaryKey) + .autoID(autoId) + .description(StringUtils.isNotEmpty(description) ? description : null) + .dimension(dimension) + .maxLength(maxLength != null && maxLength > 0 ? maxLength : null) + .isPartitionKey(isPartitionKey) + .elementType(elementType) + .maxCapacity(maxCapacity != null && maxCapacity > 0 ? maxCapacity : null) + .build(); + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResp.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResp.java new file mode 100644 index 0000000..9fb6fe4 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResp.java @@ -0,0 +1,13 @@ +package io.github.javpower.milvus.plus.model; + +import lombok.Data; + +import java.util.List; +/** + * @author xgc + **/ +@Data +public class MilvusResp { + private List>> res ; + +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResult.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResult.java new file mode 100644 index 0000000..66c62d4 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/model/MilvusResult.java @@ -0,0 +1,12 @@ +package io.github.javpower.milvus.plus.model; + +import lombok.Data; +/** + * @author xgc + **/ +@Data +public class MilvusResult { + private T entity; + private Float distance; + private Object id; +} diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusClient.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusClient.java new file mode 100644 index 0000000..a326738 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusClient.java @@ -0,0 +1,22 @@ +package io.github.javpower.milvus.plus.service; + + +import io.milvus.v2.client.MilvusClientV2; +import org.springframework.stereotype.Service; +/** + * @author xgc + **/ +@Service +public class MilvusClient implements AutoCloseable { + + public final MilvusClientV2 client; + + public MilvusClient(MilvusClientV2 client) { + this.client = client; + } + + @Override + public void close() throws InterruptedException { + client.close(10); + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusCollectionService.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusCollectionService.java new file mode 100644 index 0000000..8562a6d --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/service/MilvusCollectionService.java @@ -0,0 +1,77 @@ +package io.github.javpower.milvus.plus.service; + + +import io.github.javpower.milvus.plus.builder.CollectionSchemaBuilder; +import io.github.javpower.milvus.plus.converter.MilvusEntityConverter; +import io.github.javpower.milvus.plus.model.MilvusEntity; +import io.milvus.exception.MilvusException; +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AddFieldReq; +import io.milvus.v2.service.collection.request.DescribeCollectionReq; +import io.milvus.v2.service.collection.request.DropCollectionReq; +import io.milvus.v2.service.collection.request.HasCollectionReq; +import io.milvus.v2.service.collection.response.DescribeCollectionResp; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +/** + * @author xgc + **/ +@Service +public class MilvusCollectionService { + private final MilvusClient milvusClient; + + public MilvusCollectionService(MilvusClient milvusClient) { + this.milvusClient = milvusClient; + } + + public void performBusinessLogic(List> annotatedClasses) { + for (Class milvusClass : annotatedClasses) { + MilvusEntity milvusEntity = MilvusEntityConverter.convert(milvusClass); + try { + String collectionName = milvusEntity.getCollectionName(); + // 检查集合是否存在 + boolean collectionExists = milvusClient.client.hasCollection( + HasCollectionReq.builder().collectionName(collectionName).build() + ); + if (collectionExists) { + // 获取集合的详细信息 + DescribeCollectionResp collectionInfo = milvusClient.client.describeCollection( + DescribeCollectionReq.builder().collectionName(collectionName).build() + ); + // 检查字段是否一致,这里需要实现字段比较逻辑 + List existingFieldNames = collectionInfo.getFieldNames(); + List requiredFields = milvusEntity.getMilvusFields(); + List requiredFieldNames = requiredFields.stream().map(AddFieldReq::getFieldName).collect(Collectors.toList()); + if (!new HashSet<>(existingFieldNames).containsAll(requiredFieldNames) || !new HashSet<>(requiredFieldNames).containsAll(existingFieldNames)) { + // 字段不一致,删除并重新创建集合 + milvusClient.client.dropCollection( + DropCollectionReq.builder().collectionName(collectionName).build() + ); + // 创建新集合 + create(milvusEntity); + } + } else { + // 创建新集合 + create(milvusEntity); + } + } catch (MilvusException e) { + throw new RuntimeException("Error handling Milvus collection", e); + } + } + } + private void create(MilvusEntity milvusEntity){ + // 创建新集合 + CollectionSchemaBuilder schemaBuilder = new CollectionSchemaBuilder( + milvusEntity.getCollectionName(), milvusClient + ); + schemaBuilder.addField(milvusEntity.getMilvusFields().toArray(new AddFieldReq[0])); + List indexParams = milvusEntity.getIndexParams(); + schemaBuilder.createSchema(); + if (indexParams != null && !indexParams.isEmpty()) { + schemaBuilder.createIndex(indexParams); + } + } +} \ No newline at end of file diff --git a/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/util/SpringUtils.java b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/util/SpringUtils.java new file mode 100644 index 0000000..f6ffe76 --- /dev/null +++ b/milvus-plus-boot-starter/src/main/java/io/github/javpower/milvus/plus/util/SpringUtils.java @@ -0,0 +1,46 @@ +package io.github.javpower.milvus.plus.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * SpringUtils 提供从Spring应用上下文中获取Bean的静态方法。 + * @author xgc + */ + +@Component +public class SpringUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取应用上下文中的Bean实例。 + * @param Bean类型 + * @param clazz Bean的Class + * @return Bean实例,如果没有找到返回null + */ + @SuppressWarnings("unchecked") + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + + // 如果项目中有多个相同类型的Bean,你可能需要一个更具体的获取方法,例如: + /** + * 获取应用上下文中指定名称的Bean实例。 + * @param Bean类型 + * @param clazz Bean的Class + * @param beanName Bean的名称 + * @return Bean实例,如果没有找到返回null + */ + @SuppressWarnings("unchecked") + public static T getBean(Class clazz, String beanName) { + return (T) applicationContext.getBean(beanName, clazz); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e03019a --- /dev/null +++ b/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + io.github.javpower + MilvusPlus + 0.0.1-SNAPSHOT + milvus-plus + milvus-plus + pom + + + milvus-demo + milvus-plus-boot-starter + + +