wangyunpeng 4 дней назад
Родитель
Сommit
991a0108a2

+ 78 - 0
core/src/main/java/com/tzld/videoVector/config/MilvusConfig.java

@@ -0,0 +1,78 @@
+package com.tzld.videoVector.config;
+
+import io.milvus.param.ConnectParam;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Milvus 配置类
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "milvus")
+public class MilvusConfig {
+
+    /**
+     * Milvus 服务地址
+     */
+    private String host;
+
+    /**
+     * Milvus 服务端口
+     */
+    private Integer port;
+
+    /**
+     * 用户名(可选)
+     */
+    private String username;
+
+    /**
+     * 密码(可选)
+     */
+    private String password;
+
+    /**
+     * 数据库名称(可选,默认 default)
+     */
+    private String databaseName;
+
+    /**
+     * 是否启用 TLS
+     */
+    private Boolean enableTls = false;
+
+    /**
+     * 连接超时时间(毫秒)
+     */
+    private Long connectTimeout = 10000L;
+
+    /**
+     * 保持活跃时间(毫秒)
+     */
+    private Long keepAliveTime = 10000L;
+
+    /**
+     * 构建 ConnectParam 对象
+     */
+    public ConnectParam buildConnectParam() {
+        ConnectParam.Builder builder = ConnectParam.newBuilder()
+                .withHost(host)
+                .withPort(port)
+                .withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+                .withKeepAliveTime(keepAliveTime, TimeUnit.MILLISECONDS);
+
+        if (username != null && !username.isEmpty()) {
+            builder.withAuthorization(username, password);
+        }
+
+        if (enableTls) {
+            builder.withSecure(true);
+        }
+
+        return builder.build();
+    }
+}

+ 14 - 3
core/src/main/java/com/tzld/videoVector/config/db/VideoVectorDBConfig.java

@@ -23,7 +23,7 @@ public class VideoVectorDBConfig {
     // 1. 配置 videoVector 数据源
     // 1. 配置 videoVector 数据源
     @Primary
     @Primary
     @Bean(name = "videoVectorDataSource")
     @Bean(name = "videoVectorDataSource")
-    @ConfigurationProperties(prefix = "spring.datasource.videoVector")
+    @ConfigurationProperties(prefix = "spring.datasource.video-vector")
     public DataSource videoVectorDataSource() {
     public DataSource videoVectorDataSource() {
         return new HikariDataSource(); // 使用 HikariCP 连接池
         return new HikariDataSource(); // 使用 HikariCP 连接池
     }
     }
@@ -36,9 +36,20 @@ public class VideoVectorDBConfig {
             MybatisProperties properties) throws Exception {
             MybatisProperties properties) throws Exception {
         SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
         SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
         sessionFactory.setDataSource(videoVectorDataSource); // 关联 videoVector 数据源
         sessionFactory.setDataSource(videoVectorDataSource); // 关联 videoVector 数据源
+        
         // 关键:指定 videoVector 模块的 mapper 文件路径(隔离其他数据源)
         // 关键:指定 videoVector 模块的 mapper 文件路径(隔离其他数据源)
-        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
-                .getResources("classpath:mapper/videoVector/**/*.xml"));
+        // 使用 try-catch 避免空目录报错
+        try {
+            org.springframework.core.io.Resource[] mapperResources = 
+                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/videoVector/**/*.xml");
+            if (mapperResources != null && mapperResources.length > 0) {
+                sessionFactory.setMapperLocations(mapperResources);
+            }
+        } catch (Exception e) {
+            // 如果目录不存在或为空,不设置 mapper  locations,应用仍可正常启动
+            // 后续添加 mapper 文件后重启即可生效
+        }
+        
         sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
         sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
         sessionFactory.setConfiguration(properties.getConfiguration());
         sessionFactory.setConfiguration(properties.getConfiguration());
         return sessionFactory.getObject();
         return sessionFactory.getObject();

+ 58 - 0
core/src/main/java/com/tzld/videoVector/model/entity/VectorData.java

@@ -0,0 +1,58 @@
+package com.tzld.videoVector.model.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 向量数据实体类
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class VectorData {
+
+    /**
+     * 主键 ID
+     */
+    private Long id;
+
+    /**
+     * 向量数据
+     */
+    private List<Float> vector;
+
+    /**
+     * 元数据 - 标题(可选)
+     */
+    private String title;
+
+    /**
+     * 元数据 - 描述(可选)
+     */
+    private String description;
+
+    /**
+     * 元数据 - 分类(可选)
+     */
+    private String category;
+
+    /**
+     * 元数据 - 标签(可选)
+     */
+    private List<String> tags;
+
+    /**
+     * 创建时间戳
+     */
+    private Long createTime;
+
+    /**
+     * 更新时间戳
+     */
+    private Long updateTime;
+}

+ 113 - 0
core/src/main/java/com/tzld/videoVector/service/MilvusService.java

@@ -0,0 +1,113 @@
+package com.tzld.videoVector.service;
+
+import java.util.List;
+
+/**
+ * Milvus 向量服务接口
+ */
+public interface MilvusService {
+
+    /**
+     * 创建集合
+     * @param collectionName 集合名称
+     * @param dimension 向量维度
+     * @param indexType 索引类型(如:IVF_FLAT, HNSW 等)
+     */
+    void createCollection(String collectionName, int dimension, String indexType);
+
+    /**
+     * 检查集合是否存在
+     * @param collectionName 集合名称
+     * @return 是否存在
+     */
+    boolean hasCollection(String collectionName);
+
+    /**
+     * 加载集合到内存
+     * @param collectionName 集合名称
+     */
+    void loadCollection(String collectionName);
+
+    /**
+     * 插入单条向量数据
+     * @param collectionName 集合名称
+     * @param vector 向量数据
+     * @return 插入数据的 ID
+     */
+    Long insertVector(String collectionName, List<Float> vector);
+
+    /**
+     * 批量插入向量数据
+     * @param collectionName 集合名称
+     * @param vectors 向量列表
+     * @return 插入数据的 ID 列表
+     */
+    List<Long> batchInsertVectors(String collectionName, List<List<Float>> vectors);
+
+    /**
+     * 向量相似度搜索
+     * @param collectionName 集合名称
+     * @param queryVector 查询向量
+     * @param topK 返回最相似的 K 个结果
+     * @return 搜索结果
+     */
+    List<SearchResult> searchVectors(String collectionName, List<Float> queryVector, int topK);
+
+    /**
+     * 删除集合
+     * @param collectionName 集合名称
+     */
+    void deleteCollection(String collectionName);
+
+    /**
+     * 搜索结果实体类
+     */
+    class SearchResult {
+        private Long id;
+        private Float score;
+        private List<Float> vector;
+        private String metadata;
+
+        public SearchResult() {
+        }
+
+        public SearchResult(Long id, Float score, List<Float> vector, String metadata) {
+            this.id = id;
+            this.score = score;
+            this.vector = vector;
+            this.metadata = metadata;
+        }
+
+        public Long getId() {
+            return id;
+        }
+
+        public void setId(Long id) {
+            this.id = id;
+        }
+
+        public Float getScore() {
+            return score;
+        }
+
+        public void setScore(Float score) {
+            this.score = score;
+        }
+
+        public List<Float> getVector() {
+            return vector;
+        }
+
+        public void setVector(List<Float> vector) {
+            this.vector = vector;
+        }
+
+        public String getMetadata() {
+            return metadata;
+        }
+
+        public void setMetadata(String metadata) {
+            this.metadata = metadata;
+        }
+    }
+}

+ 85 - 0
core/src/main/java/com/tzld/videoVector/service/impl/MilvusServiceImpl.java

@@ -0,0 +1,85 @@
+package com.tzld.videoVector.service.impl;
+
+import com.tzld.videoVector.service.MilvusService;
+import com.tzld.videoVector.util.MilvusUtil;
+import io.milvus.response.SearchResultsWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Milvus 向量服务实现类
+ */
+@Slf4j
+@Service
+public class MilvusServiceImpl implements MilvusService {
+
+    @Resource
+    private MilvusUtil milvusUtil;
+
+    @Override
+    public void createCollection(String collectionName, int dimension, String indexType) {
+        log.info("开始创建集合:{}, 维度:{}, 索引类型:{}", collectionName, dimension, indexType);
+        milvusUtil.createCollection(collectionName, dimension, indexType);
+        log.info("集合 {} 创建完成", collectionName);
+    }
+
+    @Override
+    public boolean hasCollection(String collectionName) {
+        return milvusUtil.hasCollection(collectionName);
+    }
+
+    @Override
+    public void loadCollection(String collectionName) {
+        log.info("加载集合到内存:{}", collectionName);
+        milvusUtil.loadCollection(collectionName);
+    }
+
+    @Override
+    public Long insertVector(String collectionName, List<Float> vector) {
+        log.info("插入单条向量数据到集合:{}", collectionName);
+        List<Long> ids = batchInsertVectors(collectionName, Collections.singletonList(vector));
+        return ids != null && !ids.isEmpty() ? ids.get(0) : null;
+    }
+
+    @Override
+    public List<Long> batchInsertVectors(String collectionName, List<List<Float>> vectors) {
+        log.info("批量插入 {} 条向量数据到集合:{}", vectors.size(), collectionName);
+        return milvusUtil.insertVectors(collectionName, vectors);
+    }
+
+    @Override
+    public List<SearchResult> searchVectors(String collectionName, List<Float> queryVector, int topK) {
+        log.info("搜索集合 {} 中的相似向量,查询向量维度:{}, TopK: {}", collectionName, queryVector.size(), topK);
+        
+        try {
+            // 执行搜索
+            List<SearchResultsWrapper.IDScore> records = milvusUtil.searchVectors(collectionName, queryVector, topK);
+
+            List<SearchResult> results = new ArrayList<>();
+            for (SearchResultsWrapper.IDScore record : records) {
+                SearchResult result = new SearchResult();
+                result.setId(record.getLongID());
+                result.setScore(record.getScore());
+                results.add(result);
+            }
+
+            log.info("搜索完成,找到 {} 条相似向量", results.size());
+            return results;
+
+        } catch (Exception e) {
+            log.error("搜索向量失败:{}", e.getMessage(), e);
+            throw new RuntimeException("搜索向量失败:" + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public void deleteCollection(String collectionName) {
+        log.info("删除集合:{}", collectionName);
+        milvusUtil.dropCollection(collectionName);
+    }
+}

+ 236 - 0
core/src/main/java/com/tzld/videoVector/util/MilvusUtil.java

@@ -0,0 +1,236 @@
+package com.tzld.videoVector.util;
+
+import io.milvus.client.MilvusClient;
+import io.milvus.client.MilvusServiceClient;
+import io.milvus.grpc.DataType;
+import io.milvus.grpc.MutationResult;
+import io.milvus.grpc.SearchResults;
+import io.milvus.param.ConnectParam;
+import io.milvus.param.MetricType;
+import io.milvus.param.IndexType;
+import io.milvus.param.R;
+import io.milvus.param.collection.*;
+import io.milvus.param.dml.InsertParam;
+import io.milvus.param.dml.SearchParam;
+import io.milvus.param.index.CreateIndexParam;
+import io.milvus.response.SearchResultsWrapper;
+import io.milvus.response.MutationResultWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import com.tzld.videoVector.config.MilvusConfig;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Milvus 工具类 - 封装常用操作(使用 MilvusClient V1 版本)
+ */
+@Slf4j
+@Component
+public class MilvusUtil {
+
+    private final MilvusConfig milvusConfig;
+
+    private MilvusClient milvusClient;
+
+    public MilvusUtil(MilvusConfig milvusConfig) {
+        this.milvusConfig = milvusConfig;
+    }
+
+    @PostConstruct
+    public void init() {
+        try {
+            // 初始化 Milvus 客户端 V1
+            ConnectParam connectParam = milvusConfig.buildConnectParam();
+            this.milvusClient = new MilvusServiceClient(connectParam);
+            log.info("Milvus 客户端初始化成功");
+        } catch (Exception e) {
+            log.error("Milvus 客户端初始化失败:{}", e.getMessage(), e);
+        }
+    }
+
+    @PreDestroy
+    public void destroy() {
+        if (milvusClient != null) {
+            try {
+                milvusClient.close();
+                log.info("Milvus 客户端已关闭");
+            } catch (Exception e) {
+                log.error("关闭 Milvus 客户端时出错:{}", e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * 检查集合是否存在
+     */
+    public boolean hasCollection(String collectionName) {
+        try {
+            R<Boolean> response = milvusClient.hasCollection(HasCollectionParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .build());
+            return response.getData();
+        } catch (Exception e) {
+            log.error("检查集合 {} 是否存在失败:{}", collectionName, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 创建集合
+     */
+    public void createCollection(String collectionName, int dimension, String indexType) {
+        try {
+            if (hasCollection(collectionName)) {
+                log.warn("集合 {} 已存在", collectionName);
+                return;
+            }
+
+            // 定义字段
+            List<FieldType> fieldsSchema = new ArrayList<>();
+            
+            // ID 字段
+            fieldsSchema.add(FieldType.newBuilder()
+                    .withName("id")
+                    .withDataType(DataType.Int64)
+                    .withPrimaryKey(true)
+                    .withAutoID(true)
+                    .build());
+
+            // 向量字段
+            fieldsSchema.add(FieldType.newBuilder()
+                    .withName("vector")
+                    .withDataType(DataType.FloatVector)
+                    .withDimension(dimension)
+                    .build());
+
+            // 创建集合
+            milvusClient.createCollection(CreateCollectionParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .withFieldTypes(fieldsSchema)
+                    .build());
+
+            log.info("集合 {} 创建成功", collectionName);
+
+            // 创建索引
+            createIndex(collectionName, "vector", indexType, dimension);
+
+        } catch (Exception e) {
+            log.error("创建集合 {} 失败:{}", collectionName, e.getMessage(), e);
+            throw new RuntimeException("创建集合失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 创建索引
+     */
+    public void createIndex(String collectionName, String fieldName, String indexType, int dimension) {
+        try {
+            CreateIndexParam indexParam = CreateIndexParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .withFieldName(fieldName)
+                    .withIndexType(IndexType.valueOf(indexType))
+                    .withMetricType(MetricType.IP) // 内积相似度
+                    .withExtraParam("{\"nlist\":1024}")
+                    .build();
+
+            milvusClient.createIndex(indexParam);
+
+            log.info("集合 {} 的字段 {} 索引创建成功,类型:{}", collectionName, fieldName, indexType);
+        } catch (Exception e) {
+            log.error("创建索引失败:{}", e.getMessage(), e);
+            throw new RuntimeException("创建索引失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 加载集合到内存
+     */
+    public void loadCollection(String collectionName) {
+        try {
+            milvusClient.loadCollection(LoadCollectionParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .build());
+            log.info("集合 {} 加载到内存成功", collectionName);
+        } catch (Exception e) {
+            log.error("加载集合 {} 失败:{}", collectionName, e.getMessage(), e);
+            throw new RuntimeException("加载集合失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 插入向量数据
+     */
+    public List<Long> insertVectors(String collectionName, List<List<Float>> vectors) {
+        try {
+            // 构建插入数据
+            List<InsertParam.Field> fields = new ArrayList<>();
+            fields.add(new InsertParam.Field("vector", vectors));
+
+            R<MutationResult> response = milvusClient.insert(InsertParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .withFields(fields)
+                    .build());
+
+            MutationResultWrapper wrapper = new MutationResultWrapper(response.getData());
+            List<Long> ids = wrapper.getLongIDs();
+            log.info("成功插入 {} 条向量数据到集合 {}", vectors.size(), collectionName);
+            return ids;
+        } catch (Exception e) {
+            log.error("插入向量数据失败:{}", e.getMessage(), e);
+            throw new RuntimeException("插入向量数据失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 向量搜索
+     */
+    public List<SearchResultsWrapper.IDScore> searchVectors(String collectionName, List<Float> queryVector, int topK) {
+        try {
+            // 执行搜索
+            R<SearchResults> response = milvusClient.search(SearchParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .withVectors(Collections.singletonList(queryVector))
+                    .withVectorFieldName("vector")
+                    .withTopK(topK)
+                    .withMetricType(MetricType.IP)
+                    .withOutFields(Collections.singletonList("id"))
+                    .withParams("{\"nprobe\":10}")
+                    .build());
+
+            SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
+            List<SearchResultsWrapper.IDScore> records = wrapper.getIDScore(0);
+            
+            log.info("搜索完成,找到 {} 条相似向量", records.size());
+            return records;
+        } catch (Exception e) {
+            log.error("搜索向量失败:{}", e.getMessage(), e);
+            throw new RuntimeException("搜索向量失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 删除集合
+     */
+    public void dropCollection(String collectionName) {
+        try {
+            milvusClient.dropCollection(DropCollectionParam.newBuilder()
+                    .withCollectionName(collectionName)
+                    .build());
+            log.info("集合 {} 删除成功", collectionName);
+        } catch (Exception e) {
+            log.error("删除集合 {} 失败:{}", collectionName, e.getMessage(), e);
+            throw new RuntimeException("删除集合失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 获取客户端实例
+     */
+    public MilvusClient getClient() {
+        return milvusClient;
+    }
+}

+ 19 - 0
pom.xml

@@ -254,6 +254,18 @@
             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
         </dependency>
         </dependency>
 
 
+        <!-- Jersey 客户端(Eureka 依赖) -->
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>1.19.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey.contribs</groupId>
+            <artifactId>jersey-apache-client4</artifactId>
+            <version>1.19.4</version>
+        </dependency>
+
         <!-- OpenFeign -->
         <!-- OpenFeign -->
         <dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <groupId>org.springframework.cloud</groupId>
@@ -277,6 +289,13 @@
             <version>0.9.9</version>
             <version>0.9.9</version>
         </dependency>
         </dependency>
 
 
+        <!-- Milvus Java SDK -->
+        <dependency>
+            <groupId>io.milvus</groupId>
+            <artifactId>milvus-sdk-java</artifactId>
+            <version>2.3.8</version>
+        </dependency>
+
     </dependencies>
     </dependencies>
 
 
 </project>
 </project>

+ 13 - 2
server/src/main/resources/application-dev.yml

@@ -3,7 +3,7 @@ server:
 
 
 spring:
 spring:
   datasource:
   datasource:
-    videoVector:
+    video-vector:
       driver-class-name: com.mysql.jdbc.Driver
       driver-class-name: com.mysql.jdbc.Driver
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       username: wqsd
       username: wqsd
@@ -54,4 +54,15 @@ eureka:
 danger:
 danger:
   face:
   face:
     recognize:
     recognize:
-      host: http://47.111.230.160:5000
+      host: http://47.111.230.160:5000
+
+
+milvus:
+  host: localhost
+  port: 19530
+  username: 
+  password: 
+  databaseName: default
+  enableTls: false
+  connectTimeout: 10000
+  keepAliveTime: 10000

+ 12 - 1
server/src/main/resources/application-prod.yml

@@ -3,7 +3,7 @@ server:
 
 
 spring:
 spring:
   datasource:
   datasource:
-    videoVector:
+    video-vector:
       driver-class-name: com.mysql.jdbc.Driver
       driver-class-name: com.mysql.jdbc.Driver
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       username: wqsd
       username: wqsd
@@ -51,3 +51,14 @@ eureka:
     service-url:  # Eureka 服务注册中心地址
     service-url:  # Eureka 服务注册中心地址
       defaultZone: http://eureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
       defaultZone: http://eureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
 
 
+# Milvus 向量数据库配置
+milvus:
+  host: milvus.piaoquantv.com
+  port: 19530
+  username: 
+  password: 
+  databaseName: default
+  enableTls: false
+  connectTimeout: 10000
+  keepAliveTime: 10000
+

+ 12 - 1
server/src/main/resources/application-test.yml

@@ -3,7 +3,7 @@ server:
 
 
 spring:
 spring:
   datasource:
   datasource:
-    videoVector:
+    video-vector:
       driver-class-name: com.mysql.jdbc.Driver
       driver-class-name: com.mysql.jdbc.Driver
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
       username: wqsd
       username: wqsd
@@ -50,3 +50,14 @@ eureka:
   client:
   client:
     service-url:  # Eureka 服务注册中心地址
     service-url:  # Eureka 服务注册中心地址
       defaultZone: http://testeureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
       defaultZone: http://testeureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
+
+# Milvus 向量数据库配置
+milvus:
+  host: localhost
+  port: 19530
+  username: 
+  password: 
+  databaseName: default
+  enableTls: false
+  connectTimeout: 10000
+  keepAliveTime: 10000

+ 1 - 1
server/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 spring:
 spring:
   profiles:
   profiles:
-    active: dev
+    active: test
   application:
   application:
     name: video-vector-server
     name: video-vector-server
 
 

+ 135 - 0
server/src/test/java/MilvusTest.java

@@ -0,0 +1,135 @@
+package com.tzld.videoVector;
+
+import com.tzld.videoVector.service.MilvusService;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Milvus 集成测试
+ */
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MilvusTest {
+
+    @Resource
+    private MilvusService milvusService;
+
+    /**
+     * 测试创建集合
+     */
+    @Test
+    public void testCreateCollection() {
+        String collectionName = "test_collection";
+        int dimension = 128;
+        String indexType = "IVF_FLAT";
+
+        try {
+            milvusService.createCollection(collectionName, dimension, indexType);
+            log.info("集合 {} 创建成功", collectionName);
+        } catch (Exception e) {
+            log.error("创建集合失败:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 测试检查集合是否存在
+     */
+    @Test
+    public void testHasCollection() {
+        String collectionName = "test_collection";
+        boolean exists = milvusService.hasCollection(collectionName);
+        log.info("集合 {} 是否存在:{}", collectionName, exists);
+    }
+
+    /**
+     * 测试插入单条向量
+     */
+    @Test
+    public void testInsertVector() {
+        String collectionName = "test_collection";
+        List<Float> vector = generateRandomVector(128);
+
+        try {
+            Long id = milvusService.insertVector(collectionName, vector);
+            log.info("插入向量成功,ID: {}", id);
+        } catch (Exception e) {
+            log.error("插入向量失败:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 测试批量插入向量
+     */
+    @Test
+    public void testBatchInsertVectors() {
+        String collectionName = "test_collection";
+        List<List<Float>> vectors = new ArrayList<>();
+        
+        // 生成 10 个随机向量
+        for (int i = 0; i < 10; i++) {
+            vectors.add(generateRandomVector(128));
+        }
+
+        try {
+            List<Long> ids = milvusService.batchInsertVectors(collectionName, vectors);
+            log.info("批量插入向量成功,插入数量:{}, ID 列表:{}", vectors.size(), ids);
+        } catch (Exception e) {
+            log.error("批量插入向量失败:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 测试向量搜索
+     */
+    @Test
+    public void testSearchVectors() {
+        String collectionName = "test_collection";
+        List<Float> queryVector = generateRandomVector(128);
+        int topK = 5;
+
+        try {
+            List<MilvusService.SearchResult> results = milvusService.searchVectors(collectionName, queryVector, topK);
+            log.info("搜索完成,找到 {} 条相似向量", results.size());
+            for (MilvusService.SearchResult result : results) {
+                log.info("ID: {}, Score: {}", result.getId(), result.getScore());
+            }
+        } catch (Exception e) {
+            log.error("搜索向量失败:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 测试加载集合
+     */
+    @Test
+    public void testLoadCollection() {
+        String collectionName = "test_collection";
+        try {
+            milvusService.loadCollection(collectionName);
+            log.info("集合 {} 加载到内存成功", collectionName);
+        } catch (Exception e) {
+            log.error("加载集合失败:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 生成随机向量
+     */
+    private List<Float> generateRandomVector(int dimension) {
+        Random random = new Random();
+        List<Float> vector = new ArrayList<>();
+        for (int i = 0; i < dimension; i++) {
+            vector.add(random.nextFloat());
+        }
+        return vector;
+    }
+}