Browse Source

向量搜索视频 暂不切换

wangyunpeng 6 hours ago
parent
commit
194af78ae0

+ 33 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/ManagerApiService.java

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
 
 
 import java.net.URLEncoder;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Objects;
 import java.util.Objects;
 
 
 @Slf4j
 @Slf4j
@@ -24,6 +25,9 @@ public class ManagerApiService {
     @Value("${manager.api.host:https://testadmin.piaoquantv.com/manager}")
     @Value("${manager.api.host:https://testadmin.piaoquantv.com/manager}")
     private String managerApiHost;
     private String managerApiHost;
 
 
+    @Value("${video.vector.api.host:http://api-internal.piaoquantv.com/videoVector}")
+    private String videoVectorApiHost;
+
     public JSONObject videoMultiTitleSave(Long videoId, String title) {
     public JSONObject videoMultiTitleSave(Long videoId, String title) {
         String url = managerApiHost + "/video/multiTitleV2/saveNoAuth";
         String url = managerApiHost + "/video/multiTitleV2/saveNoAuth";
         JSONObject res = null;
         JSONObject res = null;
@@ -168,4 +172,33 @@ public class ManagerApiService {
         return null;
         return null;
     }
     }
 
 
+    /**
+     * 调用视频向量搜索接口,根据查询文本召回视频并计算综合评分
+     *
+     * @param queryText 查询文本
+     * @param topN      返回数量
+     * @return 接口返回的 data 对象(RecallVideoScoreVO)
+     */
+    public JSONObject recallVideoWithScore(String queryText, int topN) {
+        String url = videoVectorApiHost + "/videoSearch/recallWithScore";
+        try {
+            JSONObject param = new JSONObject();
+            param.put("queryText", queryText);
+            param.put("configCode", "ALL");
+            param.put("topN", topN);
+            param.put("simMin", 0.7);
+            String post = httpPoolClient.post(url, param.toJSONString());
+            JSONObject res = JSONObject.parseObject(post);
+            if (res != null && "0".equals(res.getString("code"))) {
+                return res.getJSONObject("data");
+            }
+            log.error("ManagerApiService recallVideoWithScore failed, queryText={} topN={} res={}",
+                    queryText, topN, res);
+        } catch (Exception e) {
+            log.error("ManagerApiService recallVideoWithScore error, queryText={} topN={}",
+                    queryText, topN, e);
+        }
+        return null;
+    }
+
 }
 }

+ 101 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java

@@ -47,6 +47,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 
 
 import java.util.*;
 import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
@@ -656,6 +660,103 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         return result;
         return result;
     }
     }
 
 
+    /**
+     * 按标题通过向量搜索接口查询视频列表,并发请求视频详情组装数据返回
+     */
+    private Page<VideoContentItemVO> getVideoContentListByTitleV2(VideoContentListParam param) {
+        Page<VideoContentItemVO> result = new Page<>(param.getPageNum(), param.getPageSize());
+        int pageSize = param.getPageSize() > 0 ? param.getPageSize() : 10;
+
+        // 调用向量搜索接口,topN 取 pageSize 的合理上限
+        int topN = Math.min(pageSize, 50);
+        JSONObject data = managerApiService.recallVideoWithScore(param.getTitle(), topN);
+        if (data == null) {
+            result.setTotalSize(0);
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+
+        JSONArray items = data.getJSONArray("items");
+        if (items == null || items.isEmpty()) {
+            result.setTotalSize(0);
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+
+        int total = data.getIntValue("total");
+        int effectiveTotalSize = Math.min(total, videoTitleSearchMaxCount);
+        result.setTotalSize(effectiveTotalSize);
+
+        // 分页截取
+        int offset = (param.getPageNum() - 1) * pageSize;
+        int end = Math.min(offset + pageSize, items.size());
+        if (offset >= items.size()) {
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        List<JSONObject> pageItems = new ArrayList<>();
+        for (int i = offset; i < end; i++) {
+            pageItems.add(items.getJSONObject(i));
+        }
+
+        // 提取 videoId 列表
+        List<Long> videoIds = pageItems.stream()
+                .map(item -> item.getLong("videoId"))
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        // 并发请求视频详情
+        Map<Long, VideoDetail> videoDetailMap = new HashMap<>();
+        if (CollectionUtils.isNotEmpty(videoIds)) {
+            ExecutorService executor = Executors.newFixedThreadPool(videoIds.size() / 20 + 1);
+            try {
+                List<Future<Map<Long, VideoDetail>>> futures = new ArrayList<>();
+                for (List<Long> partition : Lists.partition(videoIds, 20)) {
+                    futures.add(executor.submit(() -> {
+                        Set<Long> ids = new HashSet<>(partition);
+                        return messageAttachmentService.getVideoDetail(ids);
+                    }));
+                }
+                for (Future<Map<Long, VideoDetail>> future : futures) {
+                    try {
+                        Map<Long, VideoDetail> detailMap = future.get(10, TimeUnit.SECONDS);
+                        if (detailMap != null) {
+                            videoDetailMap.putAll(detailMap);
+                        }
+                    } catch (Exception e) {
+                        log.error("getVideoContentListByTitle get video detail error", e);
+                    }
+                }
+            } finally {
+                executor.shutdown();
+            }
+        }
+
+        // 组装返回数据
+        List<VideoContentItemVO> voList = new ArrayList<>();
+        for (JSONObject item : pageItems) {
+            Long videoId = item.getLong("videoId");
+            VideoContentItemVO vo = new VideoContentItemVO();
+            vo.setVideoId(videoId);
+            vo.setScore(item.getDouble("score"));
+            // 优先从视频详情中获取标题、封面、视频地址
+            VideoDetail detail = videoDetailMap.get(videoId);
+            if (detail != null) {
+                vo.setTitle(detail.getTitle());
+                String cover = detail.getCover();
+                if (cover != null && cover.contains("/watermark")) {
+                    cover = cover.substring(0, cover.indexOf("/watermark"));
+                }
+                vo.setCover(cover);
+                vo.setVideo(detail.getVideoPath());
+                voList.add(vo);
+            }
+        }
+        result.setObjs(voList);
+        return result;
+    }
+
     /**
     /**
      * 将 manager 接口返回的 objs 数据转换为 VideoContentItemVO 列表
      * 将 manager 接口返回的 objs 数据转换为 VideoContentItemVO 列表
      */
      */