Jelajahi Sumber

移除 Milvus 相关代码并切换到 Redis 实现

wangyunpeng 3 hari lalu
induk
melakukan
9ba2502ec3

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

@@ -1,78 +0,0 @@
-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();
-    }
-}

+ 6 - 25
core/src/main/java/com/tzld/videoVector/job/VideoVectorJob.java

@@ -3,8 +3,7 @@ package com.tzld.videoVector.job;
 import com.alibaba.fastjson.JSONObject;
 import com.aliyun.odps.data.Record;
 import com.tzld.videoVector.service.EmbeddingService;
-import com.tzld.videoVector.service.MilvusService;
-import com.tzld.videoVector.util.MilvusUtil;
+import com.tzld.videoVector.service.VectorStoreService;
 import com.tzld.videoVector.util.OdpsUtil;
 import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.handler.annotation.XxlJob;
@@ -24,19 +23,11 @@ import java.util.stream.Collectors;
 public class VideoVectorJob {
 
     @Resource
-    private MilvusService milvusService;
-
-    @Resource
-    private MilvusUtil milvusUtil;
+    private VectorStoreService vectorStoreService;
 
     @Resource
     private EmbeddingService embeddingService;
 
-    /**
-     * 集合名称
-     */
-    private static final String COLLECTION_NAME = "video_vector";
-
     /**
      * 每页查询数量
      */
@@ -65,8 +56,8 @@ public class VideoVectorJob {
                 }
                 log.info("第 {} 页查询到 {} 个 videoId", pageNum, videoIds.size());
 
-                // 2. 查询哪些 videoId 在 Milvus 中已存在
-                Set<Long> existingIds = milvusService.existsByIds(COLLECTION_NAME, videoIds);
+                // 2. 查询哪些 videoId 在 Redis 中已存在
+                Set<Long> existingIds = vectorStoreService.existsByIds(videoIds);
                 log.info("已存在 {} 个 videoId,将跳过", existingIds.size());
 
                 // 3. 过滤出不存在的 videoId
@@ -96,8 +87,8 @@ public class VideoVectorJob {
                                 continue;
                             }
 
-                            // 4.3 存储到 Milvus
-                            insertToMilvus(videoId, vector, videoTopic);
+                            // 4.3 存储到 Redis
+                            vectorStoreService.save(videoId, vector);
                             totalSuccessCount++;
                             log.debug("videoId={} 处理成功", videoId);
 
@@ -191,15 +182,5 @@ public class VideoVectorJob {
         return embeddingService.embed(videoTopic);
     }
 
-    /**
-     * 将向量数据存储到 Milvus
-     */
-    private void insertToMilvus(Long videoId, List<Float> vector, String videoTopic) {
-        // 使用 MilvusUtil 进行插入操作
-        // 注意:需要确保 Milvus 集合已创建,且包含 video_id 和 vector 字段
-        List<List<Float>> vectors = new ArrayList<>();
-        vectors.add(vector);
-        milvusUtil.insertVectors(COLLECTION_NAME, vectors);
-    }
 
 }

+ 39 - 0
core/src/main/java/com/tzld/videoVector/model/entity/VideoMatch.java

@@ -0,0 +1,39 @@
+package com.tzld.videoVector.model.entity;
+
+/**
+ * 向量匹配结果实体
+ */
+public class VideoMatch {
+
+    /** 视频ID */
+    private Long videoId;
+
+    /** 余弦相似度分值(-1 ~ 1,越大越相似) */
+    private double score;
+
+    public VideoMatch(Long videoId, double score) {
+        this.videoId = videoId;
+        this.score = score;
+    }
+
+    public Long getVideoId() {
+        return videoId;
+    }
+
+    public void setVideoId(Long videoId) {
+        this.videoId = videoId;
+    }
+
+    public double getScore() {
+        return score;
+    }
+
+    public void setScore(double score) {
+        this.score = score;
+    }
+
+    @Override
+    public String toString() {
+        return "VideoMatch{videoId=" + videoId + ", score=" + score + '}';
+    }
+}

+ 11 - 0
core/src/main/java/com/tzld/videoVector/model/param/MatchTopNVideoParam.java

@@ -2,6 +2,17 @@ package com.tzld.videoVector.model.param;
 
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 public class MatchTopNVideoParam {
+
+    /** 查询文本,将被向量化后进行检索 */
+    private String queryText;
+
+    /** 直接传入查询向量(与 queryText 二选一,优先使用此字段) */
+    private List<Float> queryVector;
+
+    /** 返回 Top-N 结果数量,默认 10 */
+    private Integer topN = 10;
 }

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

@@ -1,122 +0,0 @@
-package com.tzld.videoVector.service;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * 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);
-
-    /**
-     * 根据 videoId 列表查询在 Milvus 中已存在的 ID
-     * @param collectionName 集合名称
-     * @param videoIds 要查询的 videoId 列表
-     * @return 存在的 videoId 集合
-     */
-    Set<Long> existsByIds(String collectionName, List<Long> videoIds);
-
-    /**
-     * 搜索结果实体类
-     */
-    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;
-        }
-    }
-}

+ 70 - 0
core/src/main/java/com/tzld/videoVector/service/VectorStoreService.java

@@ -0,0 +1,70 @@
+package com.tzld.videoVector.service;
+
+import com.tzld.videoVector.model.entity.VideoMatch;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 向量存储服务接口(Redis 实现)
+ */
+public interface VectorStoreService {
+
+    /**
+     * 保存视频向量
+     * @param videoId 视频ID
+     * @param vector  向量数据
+     */
+    void save(Long videoId, List<Float> vector);
+
+    /**
+     * 判断某个 videoId 的向量是否已存在
+     * @param videoId 视频ID
+     * @return 是否存在
+     */
+    boolean exists(Long videoId);
+
+    /**
+     * 批量判断 videoId 是否已存在
+     * @param videoIds 视频ID列表
+     * @return 已存在的 videoId 集合
+     */
+    Set<Long> existsByIds(Collection<Long> videoIds);
+
+    /**
+     * 获取指定 videoId 的向量
+     * @param videoId 视频ID
+     * @return 向量数据,不存在返回 null
+     */
+    List<Float> getVector(Long videoId);
+
+    /**
+     * 批量获取向量
+     * @param videoIds 视频ID列表
+     * @return videoId -> vector 映射
+     */
+    Map<Long, List<Float>> getVectors(Collection<Long> videoIds);
+
+    /**
+     * 获取所有已存储的 videoId
+     * @return videoId 集合
+     */
+    Set<Long> getAllVideoIds();
+
+    /**
+     * 删除指定视频向量
+     * @param videoId 视频ID
+     */
+    void delete(Long videoId);
+
+    /**
+     * 在所有向量中搜索 Top-N 最相似的视频
+     * @param queryVector 查询向量
+     * @param topN        返回数量
+     * @return 按相似度降序排列的结果列表
+     */
+    List<VideoMatch> searchTopN(List<Float> queryVector, int topN);
+
+}

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

@@ -1,92 +0,0 @@
-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;
-import java.util.Set;
-
-/**
- * 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);
-    }
-
-    @Override
-    public Set<Long> existsByIds(String collectionName, List<Long> videoIds) {
-        log.info("查询 {} 个 videoId 是否在集合 {} 中存在", videoIds.size(), collectionName);
-        return milvusUtil.existsByIds(collectionName, videoIds);
-    }
-}

+ 219 - 0
core/src/main/java/com/tzld/videoVector/service/impl/RedisVectorStoreServiceImpl.java

@@ -0,0 +1,219 @@
+package com.tzld.videoVector.service.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.model.entity.VideoMatch;
+import com.tzld.videoVector.service.VectorStoreService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 基于 Redis 的向量存储服务实现
+ *
+ * <p>存储结构:
+ * <ul>
+ *   <li>向量数据:Key = {@code video:vector:{videoId}},Value = JSON 数组字符串</li>
+ *   <li>ID 索引:Key = {@code video:vector:ids},类型 = Redis Set</li>
+ * </ul>
+ */
+@Slf4j
+@Service
+public class RedisVectorStoreServiceImpl implements VectorStoreService {
+
+    /** 单条向量 Key 前缀 */
+    private static final String VECTOR_KEY_PREFIX = "video:vector:";
+
+    /** 所有 videoId 的索引集合 Key */
+    private static final String IDS_SET_KEY = "video:vector:ids";
+
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    // ---------------------------------------------------------------- CRUD
+
+    @Override
+    public void save(Long videoId, List<Float> vector) {
+        if (videoId == null || vector == null || vector.isEmpty()) {
+            log.warn("save 参数非法,videoId={}", videoId);
+            return;
+        }
+        String key = buildKey(videoId);
+        String value = JSONArray.toJSONString(vector);
+        redisTemplate.opsForValue().set(key, value);
+        redisTemplate.opsForSet().add(IDS_SET_KEY, videoId.toString());
+        log.debug("保存向量成功,videoId={}, 维度={}", videoId, vector.size());
+    }
+
+    @Override
+    public boolean exists(Long videoId) {
+        if (videoId == null) return false;
+        return Boolean.TRUE.equals(redisTemplate.hasKey(buildKey(videoId)));
+    }
+
+    @Override
+    public Set<Long> existsByIds(Collection<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) return Collections.emptySet();
+
+        // Pipeline 批量判断 Key 是否存在
+        List<String> keys = videoIds.stream()
+                .map(this::buildKey)
+                .collect(Collectors.toList());
+
+        List<Boolean> results = redisTemplate.executePipelined(
+                (org.springframework.data.redis.core.RedisCallback<Object>) conn -> {
+                    for (String key : keys) {
+                        conn.exists(key.getBytes());
+                    }
+                    return null;
+                }
+        ).stream()
+                .map(r -> r instanceof Boolean ? (Boolean) r : Boolean.FALSE)
+                .collect(Collectors.toList());
+
+        Set<Long> existing = new HashSet<>();
+        List<Long> idList = new ArrayList<>(videoIds);
+        for (int i = 0; i < idList.size(); i++) {
+            if (Boolean.TRUE.equals(results.get(i))) {
+                existing.add(idList.get(i));
+            }
+        }
+        return existing;
+    }
+
+    @Override
+    public List<Float> getVector(Long videoId) {
+        if (videoId == null) return null;
+        String value = redisTemplate.opsForValue().get(buildKey(videoId));
+        return parseVector(value);
+    }
+
+    @Override
+    public Map<Long, List<Float>> getVectors(Collection<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) return Collections.emptyMap();
+        List<Long> idList = new ArrayList<>(videoIds);
+        List<String> keys = idList.stream().map(this::buildKey).collect(Collectors.toList());
+        List<String> values = redisTemplate.opsForValue().multiGet(keys);
+
+        Map<Long, List<Float>> result = new HashMap<>();
+        if (values == null) return result;
+        for (int i = 0; i < idList.size(); i++) {
+            List<Float> vector = parseVector(values.get(i));
+            if (vector != null) {
+                result.put(idList.get(i), vector);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<Long> getAllVideoIds() {
+        Set<String> members = redisTemplate.opsForSet().members(IDS_SET_KEY);
+        if (members == null) return Collections.emptySet();
+        return members.stream()
+                .map(Long::parseLong)
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public void delete(Long videoId) {
+        if (videoId == null) return;
+        redisTemplate.delete(buildKey(videoId));
+        redisTemplate.opsForSet().remove(IDS_SET_KEY, videoId.toString());
+        log.debug("删除向量成功,videoId={}", videoId);
+    }
+
+    // ---------------------------------------------------------------- 搜索
+
+    @Override
+    public List<VideoMatch> searchTopN(List<Float> queryVector, int topN) {
+        if (queryVector == null || queryVector.isEmpty() || topN <= 0) {
+            return Collections.emptyList();
+        }
+
+        Set<Long> allIds = getAllVideoIds();
+        if (allIds.isEmpty()) {
+            log.info("向量库为空,无法搜索");
+            return Collections.emptyList();
+        }
+
+        log.info("开始向量搜索,库中共 {} 条记录,topN={}", allIds.size(), topN);
+
+        // 批量获取所有向量
+        Map<Long, List<Float>> allVectors = getVectors(allIds);
+
+        // 对查询向量做 L2 归一化,加速余弦相似度计算
+        float[] qNorm = l2Normalize(queryVector);
+
+        // 计算每条记录的余弦相似度
+        List<VideoMatch> matches = new ArrayList<>(allVectors.size());
+        for (Map.Entry<Long, List<Float>> entry : allVectors.entrySet()) {
+            float[] storedNorm = l2Normalize(entry.getValue());
+            double score = dotProduct(qNorm, storedNorm);
+            matches.add(new VideoMatch(entry.getKey(), score));
+        }
+
+        // 按相似度降序,取前 topN
+        matches.sort((a, b) -> Double.compare(b.getScore(), a.getScore()));
+        List<VideoMatch> topMatches = matches.subList(0, Math.min(topN, matches.size()));
+
+        log.info("向量搜索完成,返回 {} 条结果", topMatches.size());
+        return topMatches;
+    }
+
+    // ---------------------------------------------------------------- 工具方法
+
+    private String buildKey(Long videoId) {
+        return VECTOR_KEY_PREFIX + videoId;
+    }
+
+    private List<Float> parseVector(String value) {
+        if (value == null || value.isEmpty()) return null;
+        try {
+            JSONArray array = JSONArray.parseArray(value);
+            List<Float> vector = new ArrayList<>(array.size());
+            for (int i = 0; i < array.size(); i++) {
+                vector.add(array.getFloat(i));
+            }
+            return vector;
+        } catch (Exception e) {
+            log.error("向量反序列化失败: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * L2 归一化:将向量转换为单位向量
+     */
+    private float[] l2Normalize(List<Float> vector) {
+        float[] arr = new float[vector.size()];
+        float norm = 0f;
+        for (int i = 0; i < vector.size(); i++) {
+            arr[i] = vector.get(i);
+            norm += arr[i] * arr[i];
+        }
+        norm = (float) Math.sqrt(norm);
+        if (norm > 0f) {
+            for (int i = 0; i < arr.length; i++) {
+                arr[i] /= norm;
+            }
+        }
+        return arr;
+    }
+
+    /**
+     * 两个已归一化向量的点积 = 余弦相似度
+     */
+    private double dotProduct(float[] a, float[] b) {
+        int len = Math.min(a.length, b.length);
+        double sum = 0.0;
+        for (int i = 0; i < len; i++) {
+            sum += (double) a[i] * b[i];
+        }
+        return sum;
+    }
+}

+ 48 - 1
core/src/main/java/com/tzld/videoVector/service/impl/VideoSearchServiceImpl.java

@@ -2,15 +2,19 @@ package com.tzld.videoVector.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.videoVector.model.entity.DeconstructResult;
+import com.tzld.videoVector.model.entity.VideoMatch;
 import com.tzld.videoVector.model.param.DeconstructParam;
 import com.tzld.videoVector.model.param.GetDeconstructParam;
 import com.tzld.videoVector.model.param.MatchTopNVideoParam;
 import com.tzld.videoVector.service.DeconstructService;
+import com.tzld.videoVector.service.EmbeddingService;
+import com.tzld.videoVector.service.VectorStoreService;
 import com.tzld.videoVector.service.VideoSearchService;
 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;
 
@@ -21,6 +25,12 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     @Resource
     private DeconstructService deconstructService;
 
+    @Resource
+    private VectorStoreService vectorStoreService;
+
+    @Resource
+    private EmbeddingService embeddingService;
+
     @Override
     public String deconstruct(DeconstructParam param) {
         if (param == null) {
@@ -100,6 +110,43 @@ public class VideoSearchServiceImpl implements VideoSearchService {
 
     @Override
     public List<Object> matchTopNVideo(MatchTopNVideoParam param) {
-        return Collections.emptyList();
+        if (param == null) {
+            log.error("matchTopNVideo 参数为空");
+            return Collections.emptyList();
+        }
+
+        int topN = param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10;
+
+        // 确定查询向量:直接传入 or 文本向量化
+        List<Float> queryVector = param.getQueryVector();
+        if (queryVector == null || queryVector.isEmpty()) {
+            if (param.getQueryText() == null || param.getQueryText().trim().isEmpty()) {
+                log.error("matchTopNVideo 缺少 queryVector 和 queryText");
+                return Collections.emptyList();
+            }
+            log.info("对文本进行向量化,文本: {}", param.getQueryText());
+            queryVector = embeddingService.embed(param.getQueryText());
+            if (queryVector == null || queryVector.isEmpty()) {
+                log.error("文本向量化失败");
+                return Collections.emptyList();
+            }
+        }
+
+        log.info("开始匹配 Top-{} 视频,向量维度: {}", topN, queryVector.size());
+
+        // 在 Redis 中搜索
+        List<VideoMatch> matches = vectorStoreService.searchTopN(queryVector, topN);
+
+        // 转化为返回格式
+        List<Object> result = new ArrayList<>(matches.size());
+        for (VideoMatch match : matches) {
+            JSONObject item = new JSONObject();
+            item.put("videoId", match.getVideoId());
+            item.put("score", match.getScore());
+            result.add(item);
+        }
+
+        log.info("匹配完成,返回 {} 条结果", result.size());
+        return result;
     }
 }

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

@@ -1,279 +0,0 @@
-package com.tzld.videoVector.util;
-
-import com.tzld.videoVector.config.MilvusConfig;
-import io.milvus.client.MilvusClient;
-import io.milvus.client.MilvusServiceClient;
-import io.milvus.grpc.DataType;
-import io.milvus.grpc.MutationResult;
-import io.milvus.grpc.QueryResults;
-import io.milvus.grpc.SearchResults;
-import io.milvus.param.ConnectParam;
-import io.milvus.param.IndexType;
-import io.milvus.param.MetricType;
-import io.milvus.param.R;
-import io.milvus.param.collection.*;
-import io.milvus.param.dml.InsertParam;
-import io.milvus.param.dml.QueryParam;
-import io.milvus.param.dml.SearchParam;
-import io.milvus.param.index.CreateIndexParam;
-import io.milvus.response.MutationResultWrapper;
-import io.milvus.response.QueryResultsWrapper;
-import io.milvus.response.SearchResultsWrapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-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);
-        }
-    }
-
-    /**
-     * 根据 videoId 列表查询在 Milvus 中存在的 ID
-     * @param collectionName 集合名称
-     * @param videoIds 要查询的 videoId 列表
-     * @return 存在的 videoId 集合
-     */
-    public java.util.Set<Long> existsByIds(String collectionName, List<Long> videoIds) {
-        java.util.Set<Long> existingIds = new java.util.HashSet<>();
-        if (videoIds == null || videoIds.isEmpty()) {
-            return existingIds;
-        }
-        try {
-            // 构建 IN 查询表达式
-            String expr = "video_id in [" + videoIds.stream()
-                    .map(String::valueOf)
-                    .collect(java.util.stream.Collectors.joining(",")) + "]";
-            
-            R<QueryResults> response = milvusClient.query(QueryParam.newBuilder()
-                    .withCollectionName(collectionName)
-                    .withExpr(expr)
-                    .withOutFields(Collections.singletonList("video_id"))
-                    .build());
-            
-            if (response.getData() != null) {
-                QueryResultsWrapper wrapper = new QueryResultsWrapper(response.getData());
-                List<?> fieldData = wrapper.getFieldWrapper("video_id").getFieldData();
-                for (Object id : fieldData) {
-                    if (id instanceof Long) {
-                        existingIds.add((Long) id);
-                    }
-                }
-            }
-            log.info("查询完成,找到 {} 条已存在的记录", existingIds.size());
-            return existingIds;
-        } catch (Exception e) {
-            log.error("查询 videoId 是否存在失败:{}", e.getMessage(), e);
-            return existingIds;
-        }
-    }
-
-    /**
-     * 获取客户端实例
-     */
-    public MilvusClient getClient() {
-        return milvusClient;
-    }
-}

+ 0 - 6
pom.xml

@@ -289,12 +289,6 @@
             <version>0.9.9</version>
         </dependency>
 
-        <!-- Milvus Java SDK -->
-        <dependency>
-            <groupId>io.milvus</groupId>
-            <artifactId>milvus-sdk-java</artifactId>
-            <version>2.3.8</version>
-        </dependency>
 
     </dependencies>
 

+ 0 - 10
server/src/main/resources/application-dev.yml

@@ -56,13 +56,3 @@ danger:
     recognize:
       host: http://47.111.230.160:5000
 
-
-milvus:
-  host: localhost
-  port: 19530
-  username: 
-  password: 
-  databaseName: default
-  enableTls: false
-  connectTimeout: 10000
-  keepAliveTime: 10000

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

@@ -50,15 +50,3 @@ eureka:
   client:
     service-url:  # Eureka 服务注册中心地址
       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
-

+ 0 - 11
server/src/main/resources/application-test.yml

@@ -50,14 +50,3 @@ eureka:
   client:
     service-url:  # Eureka 服务注册中心地址
       defaultZone: http://testeureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
-
-# Milvus 向量数据库配置
-milvus:
-  host: localhost
-  port: 19530
-  username: 
-  password: 
-  databaseName: default
-  enableTls: false
-  connectTimeout: 10000
-  keepAliveTime: 10000

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

@@ -1,135 +0,0 @@
-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;
-    }
-}