Просмотр исходного кода

向量匹配使用 选题、标题 召回
召回视频是用原视频id

wangyunpeng 1 неделя назад
Родитель
Сommit
5a83d78f45

+ 4 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/model/bo/VideoDetail.java

@@ -15,6 +15,8 @@ public class VideoDetail {
 
 
     private String kimiTitle;
     private String kimiTitle;
 
 
+    private String title;
+
     private String videoId;
     private String videoId;
 
 
     private String shareImgPath;
     private String shareImgPath;
@@ -27,6 +29,8 @@ public class VideoDetail {
 
 
     private String videoPath;
     private String videoPath;
 
 
+    private String ossVideoPath;
+
     private String videoOss;
     private String videoOss;
 
 
     private String coverOss;
     private String coverOss;

+ 3 - 3
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/ContentServiceImpl.java

@@ -470,11 +470,11 @@ public class ContentServiceImpl implements ContentService {
                     }
                     }
                 }
                 }
                 if (videoId != null) {
                 if (videoId != null) {
-                    JSONObject videoDetail = VideoUtils.getVideoDetail(videoId);
+                    VideoDetail videoDetail = VideoUtils.getVideoDetail(videoId);
                     if (videoDetail != null) {
                     if (videoDetail != null) {
                         crawlerVideoId = Math.toIntExact(videoId);
                         crawlerVideoId = Math.toIntExact(videoId);
-                        videoOssPath = videoDetail.getString("ossVideoPath");
-                        videoPath = videoDetail.getString("videoPath");
+                        videoOssPath = videoDetail.getOssVideoPath();
+                        videoPath = videoDetail.getVideoPath();
                         JSONObject logJson = new JSONObject();
                         JSONObject logJson = new JSONObject();
                         logJson.put("content_id", kimiText.getContentId());
                         logJson.put("content_id", kimiText.getContentId());
                         logJson.put("article_title", kimiText.getArticleTitle());
                         logJson.put("article_title", kimiText.getArticleTitle());

+ 22 - 27
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/CoreServiceImpl.java

@@ -3,7 +3,6 @@ package com.tzld.piaoquan.longarticle.service.local.impl;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
 import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Lists;
 import com.tzld.piaoquan.longarticle.common.constants.TimeConstant;
 import com.tzld.piaoquan.longarticle.common.constants.TimeConstant;
@@ -110,15 +109,15 @@ public class CoreServiceImpl implements CoreService {
     @ApolloJsonValue("${fwhGhIdList:[]}")
     @ApolloJsonValue("${fwhGhIdList:[]}")
     private List<String> fwhGhIdList;
     private List<String> fwhGhIdList;
 
 
-    @Value("${recall.video.config.code:VIDEO_TOPIC}")
-    private String recallVideoConfigCode;
-
     @Value("${content.mini.video.num:3}")
     @Value("${content.mini.video.num:3}")
     private Integer miniVideoNum;
     private Integer miniVideoNum;
 
 
     @ApolloJsonValue("${recall.publish.miniprogram.accounts:[]}")
     @ApolloJsonValue("${recall.publish.miniprogram.accounts:[]}")
     private List<String> recallPublishMiniprogramAccounts;
     private List<String> recallPublishMiniprogramAccounts;
 
 
+    @ApolloJsonValue("${recall.video.config.codes:[\"VIDEO_TOPIC\",\"VIDEO_TITLE\"]}")
+    private List<String> recallVideoConfigCodes;
+
     @Value("${recall.replace.title.cover.switch:true}")
     @Value("${recall.replace.title.cover.switch:true}")
     private Boolean replaceRecallTitleCoverSwitch;
     private Boolean replaceRecallTitleCoverSwitch;
 
 
@@ -547,10 +546,7 @@ public class CoreServiceImpl implements CoreService {
         param.setSimMin(0.6);
         param.setSimMin(0.6);
         param.setRovMin(0.0);
         param.setRovMin(0.0);
         param.setDays(30);
         param.setDays(30);
-        if (StringUtils.isNotEmpty(recallVideoConfigCode)) {
-            param.setConfigCode(recallVideoConfigCode);
-        }
-        RecallVideoScoreVO result = videoVectorService.recallWithScore(param);
+        RecallVideoScoreVO result = videoVectorService.recallWithScore(param, recallVideoConfigCodes);
         if (result == null || CollectionUtils.isEmpty(result.getItems())) {
         if (result == null || CollectionUtils.isEmpty(result.getItems())) {
             log.info("recallWithScore no results, contentId={}, title={}", matchContent.getSourceId(), matchContent.getTitle());
             log.info("recallWithScore no results, contentId={}, title={}", matchContent.getSourceId(), matchContent.getTitle());
             return;
             return;
@@ -566,14 +562,14 @@ public class CoreServiceImpl implements CoreService {
                 continue;
                 continue;
             }
             }
             // 通过视频ID获取OSS路径和标题
             // 通过视频ID获取OSS路径和标题
-            JSONObject videoDetailJson = VideoUtils.getVideoDetail(item.getVideoId());
-            if (videoDetailJson == null) {
+            VideoDetail videoDetail = VideoUtils.getVideoDetail(item.getVideoId());
+            if (videoDetail == null) {
                 log.warn("getVideoDetail null for videoId={}, contentId={}", item.getVideoId(), matchContent.getSourceId());
                 log.warn("getVideoDetail null for videoId={}, contentId={}", item.getVideoId(), matchContent.getSourceId());
                 continue;
                 continue;
             }
             }
-            String ossVideoPath = videoDetailJson.getString("ossVideoPath");
-            String title = videoDetailJson.getString("title");
-            String shareImgPath = videoDetailJson.getString("shareImgPath");
+            String ossVideoPath = videoDetail.getOssVideoPath();
+            String title = videoDetail.getTitle();
+            String shareImgPath = videoDetail.getShareImgPath();
             if (StringUtils.isEmpty(ossVideoPath)) {
             if (StringUtils.isEmpty(ossVideoPath)) {
                 log.warn("ossVideoPath empty for videoId={}, contentId={}", item.getVideoId(), matchContent.getSourceId());
                 log.warn("ossVideoPath empty for videoId={}, contentId={}", item.getVideoId(), matchContent.getSourceId());
                 continue;
                 continue;
@@ -965,21 +961,20 @@ public class CoreServiceImpl implements CoreService {
         log.info("getRecallPublishMiniprograms found {} recall records, contentId={}", recallList.size(), publishContent.getSourceId());
         log.info("getRecallPublishMiniprograms found {} recall records, contentId={}", recallList.size(), publishContent.getSourceId());
         List<VideoDetail> videoDetails = new ArrayList<>();
         List<VideoDetail> videoDetails = new ArrayList<>();
         for (VectorMatchVideo recall : recallList) {
         for (VectorMatchVideo recall : recallList) {
-            try {
-                VideoDetail videoDetail = videoService.publish(recall.getVideoOssPath(), "69637498", recall.getVideoTitle());
-                if (videoDetail == null || StringUtils.isEmpty(videoDetail.getVideoId())) {
-                    log.warn("videoService.publish failed, recallId={}, videoId={}", recall.getId(), recall.getVideoId());
-                    continue;
-                }
-                videoDetail.setUid("69637498");
-                videoDetail.setSource("piaoquan");
-                videoDetail.setKimiTitle(recall.getVideoTitle());
-                videoDetail.setVideoOss(recall.getVideoOssPath());
-                videoDetail.setCoverOss(recall.getCoverUrl());
-                videoDetails.add(videoDetail);
-            } catch (Exception e) {
-                log.error("publish recall video error, recallId={}, videoId={}", recall.getId(), recall.getVideoId(), e);
+            if (recall.getVideoId() == null) {
+                log.warn("recall videoId null, recallId={}", recall.getId());
+                continue;
+            }
+            VideoDetail videoDetail = VideoUtils.getVideoDetail(recall.getVideoId());
+            if (videoDetail == null) {
+                log.warn("getVideoDetail null for videoId={}, recallId={}", recall.getVideoId(), recall.getId());
+                continue;
             }
             }
+            videoDetail.setSource("piaoquan");
+            videoDetail.setKimiTitle(recall.getVideoTitle());
+            videoDetail.setVideoOss(videoDetail.getOssVideoPath());
+            videoDetail.setCoverOss(videoDetail.getShareImgPath());
+            videoDetails.add(videoDetail);
         }
         }
         if (CollectionUtils.isEmpty(videoDetails)) {
         if (CollectionUtils.isEmpty(videoDetails)) {
             return null;
             return null;

+ 7 - 7
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/CrawlerVideoServiceImpl.java

@@ -105,7 +105,7 @@ public class CrawlerVideoServiceImpl {
             }
             }
         }
         }
         if (Objects.nonNull(pqVideoId)) {
         if (Objects.nonNull(pqVideoId)) {
-            JSONObject videoDetail = VideoUtils.getVideoDetail(pqVideoId);;
+            VideoDetail videoDetail = VideoUtils.getVideoDetail(pqVideoId);
             if (Objects.nonNull(videoDetail)) {
             if (Objects.nonNull(videoDetail)) {
                 CrawlerVideo crawlerVideo = piaoquanVideoProduce(contentId, videoDetail);
                 CrawlerVideo crawlerVideo = piaoquanVideoProduce(contentId, videoDetail);
                 crawlerVideo.setCrawlerTime(new Date());
                 crawlerVideo.setCrawlerTime(new Date());
@@ -145,15 +145,15 @@ public class CrawlerVideoServiceImpl {
         return count >= MIN_NUM;
         return count >= MIN_NUM;
     }
     }
 
 
-    public CrawlerVideo piaoquanVideoProduce(String contentId, JSONObject videoDetail) {
+    public CrawlerVideo piaoquanVideoProduce(String contentId, VideoDetail videoDetail) {
         CrawlerVideo crawlerVideo = new CrawlerVideo();
         CrawlerVideo crawlerVideo = new CrawlerVideo();
         crawlerVideo.setPlatform("piaoquan");
         crawlerVideo.setPlatform("piaoquan");
         crawlerVideo.setDownloadStatus(2);
         crawlerVideo.setDownloadStatus(2);
-        crawlerVideo.setVideoTitle(videoDetail.getString("title"));
-        crawlerVideo.setOutVideoId(String.valueOf(videoDetail.getLong("id")));
-        crawlerVideo.setVideoUrl(videoDetail.getString("videoPath"));
-        crawlerVideo.setVideoOssPath(videoDetail.getString("ossVideoPath"));
-        String shareImgPath = videoDetail.getString("shareImgPath");
+        crawlerVideo.setVideoTitle(videoDetail.getTitle());
+        crawlerVideo.setOutVideoId(videoDetail.getVideoId());
+        crawlerVideo.setVideoUrl(videoDetail.getVideoPath());
+        crawlerVideo.setVideoOssPath(videoDetail.getOssVideoPath());
+        String shareImgPath = videoDetail.getShareImgPath();
         if (StringUtils.isNotEmpty(shareImgPath)) {
         if (StringUtils.isNotEmpty(shareImgPath)) {
             String coverUrl = shareImgPath.substring(0, shareImgPath.indexOf("?"));
             String coverUrl = shareImgPath.substring(0, shareImgPath.indexOf("?"));
             crawlerVideo.setCoverUrl(coverUrl);
             crawlerVideo.setCoverUrl(coverUrl);

+ 11 - 14
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/NewMatchVideoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.tzld.piaoquan.longarticle.service.local.impl;
 package com.tzld.piaoquan.longarticle.service.local.impl;
 
 
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.tzld.piaoquan.longarticle.common.enums.NewContentStatusEnum;
 import com.tzld.piaoquan.longarticle.common.enums.NewContentStatusEnum;
 import com.tzld.piaoquan.longarticle.dao.mapper.aigc.ProducePlanExeRecordMapper;
 import com.tzld.piaoquan.longarticle.dao.mapper.aigc.ProducePlanExeRecordMapper;
 import com.tzld.piaoquan.longarticle.dao.mapper.longarticle.LongArticlesTextMapper;
 import com.tzld.piaoquan.longarticle.dao.mapper.longarticle.LongArticlesTextMapper;
@@ -16,12 +17,11 @@ import com.tzld.piaoquan.longarticle.model.vo.MatchContentItem;
 import com.tzld.piaoquan.longarticle.service.local.KimiService;
 import com.tzld.piaoquan.longarticle.service.local.KimiService;
 import com.tzld.piaoquan.longarticle.service.local.NewMatchVideoService;
 import com.tzld.piaoquan.longarticle.service.local.NewMatchVideoService;
 import com.tzld.piaoquan.longarticle.service.remote.VideoVectorService;
 import com.tzld.piaoquan.longarticle.service.remote.VideoVectorService;
+import com.tzld.piaoquan.longarticle.model.bo.VideoDetail;
 import com.tzld.piaoquan.longarticle.utils.VideoUtils;
 import com.tzld.piaoquan.longarticle.utils.VideoUtils;
-import com.alibaba.fastjson.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.CollectionUtils;
@@ -65,8 +65,8 @@ public class NewMatchVideoServiceImpl implements NewMatchVideoService {
     @Autowired
     @Autowired
     private UnmatchedArticleMapper unmatchedArticleMapper;
     private UnmatchedArticleMapper unmatchedArticleMapper;
 
 
-    @Value("${recall.video.config.code:VIDEO_TOPIC}")
-    private String recallVideoConfigCode;
+    @ApolloJsonValue("${recall.video.config.codes:[\"VIDEO_TOPIC\",\"VIDEO_TITLE\"]}")
+    private List<String> recallVideoConfigCodes;
 
 
     private static final Integer retryCountMax = 3;
     private static final Integer retryCountMax = 3;
 
 
@@ -248,7 +248,7 @@ public class NewMatchVideoServiceImpl implements NewMatchVideoService {
         if (StringUtils.isEmpty(title)) {
         if (StringUtils.isEmpty(title)) {
             return;
             return;
         }
         }
-        // 调用向量召回
+        // 多路向量召回合并(configCodes 由 Apollo 配置)
         RecallVideoScoreParam param = new RecallVideoScoreParam();
         RecallVideoScoreParam param = new RecallVideoScoreParam();
         param.setQueryText(title);
         param.setQueryText(title);
         param.setTopN(10);
         param.setTopN(10);
@@ -256,10 +256,7 @@ public class NewMatchVideoServiceImpl implements NewMatchVideoService {
         param.setSimMin(0.6);
         param.setSimMin(0.6);
         param.setRovMin(0.0);
         param.setRovMin(0.0);
         param.setDays(30);
         param.setDays(30);
-        if (StringUtils.isNotEmpty(recallVideoConfigCode)) {
-            param.setConfigCode(recallVideoConfigCode);
-        }
-        RecallVideoScoreVO result = videoVectorService.recallWithScore(param);
+        RecallVideoScoreVO result = videoVectorService.recallWithScore(param, recallVideoConfigCodes);
         if (result == null || CollectionUtils.isEmpty(result.getItems())) {
         if (result == null || CollectionUtils.isEmpty(result.getItems())) {
             log.info("recallWithScore no results, contentId={}, title={}", article.getContentId(), title);
             log.info("recallWithScore no results, contentId={}, title={}", article.getContentId(), title);
             // 插入占位记录,避免下次重复尝试
             // 插入占位记录,避免下次重复尝试
@@ -281,14 +278,14 @@ public class NewMatchVideoServiceImpl implements NewMatchVideoService {
             if (item.getVideoId() == null) {
             if (item.getVideoId() == null) {
                 continue;
                 continue;
             }
             }
-            JSONObject videoDetailJson = VideoUtils.getVideoDetail(item.getVideoId());
-            if (videoDetailJson == null) {
+            VideoDetail videoDetail = VideoUtils.getVideoDetail(item.getVideoId());
+            if (videoDetail == null) {
                 log.warn("getVideoDetail null for videoId={}, contentId={}", item.getVideoId(), article.getContentId());
                 log.warn("getVideoDetail null for videoId={}, contentId={}", item.getVideoId(), article.getContentId());
                 continue;
                 continue;
             }
             }
-            String ossVideoPath = videoDetailJson.getString("ossVideoPath");
-            String videoTitle = videoDetailJson.getString("title");
-            String shareImgPath = videoDetailJson.getJSONObject("coverImg").getString("coverImgPath");
+            String ossVideoPath = videoDetail.getOssVideoPath();
+            String videoTitle = videoDetail.getTitle();
+            String shareImgPath = videoDetail.getCoverImgPath();
             if (StringUtils.isEmpty(ossVideoPath)) {
             if (StringUtils.isEmpty(ossVideoPath)) {
                 log.warn("ossVideoPath empty for videoId={}, contentId={}", item.getVideoId(), article.getContentId());
                 log.warn("ossVideoPath empty for videoId={}, contentId={}", item.getVideoId(), article.getContentId());
                 continue;
                 continue;

+ 12 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/remote/VideoVectorService.java

@@ -3,6 +3,8 @@ package com.tzld.piaoquan.longarticle.service.remote;
 import com.tzld.piaoquan.longarticle.model.dto.RecallVideoScoreParam;
 import com.tzld.piaoquan.longarticle.model.dto.RecallVideoScoreParam;
 import com.tzld.piaoquan.longarticle.model.dto.RecallVideoScoreVO;
 import com.tzld.piaoquan.longarticle.model.dto.RecallVideoScoreVO;
 
 
+import java.util.List;
+
 public interface VideoVectorService {
 public interface VideoVectorService {
 
 
     /**
     /**
@@ -13,4 +15,14 @@ public interface VideoVectorService {
      * @return 按综合分降序排列的结果,失败返回 null
      * @return 按综合分降序排列的结果,失败返回 null
      */
      */
     RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param);
     RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param);
+
+    /**
+     * 使用多个 configCode 进行召回,合并结果后按综合评分降序排列取 topN
+     * 相同 videoId 保留 score 更高的那条
+     *
+     * @param param       召回评分参数(configCode 字段将被忽略,由 configCodes 参数替代)
+     * @param configCodes 多个配置编码,如 "VIDEO_TOPIC", "VIDEO_TITLE"
+     * @return 合并去重后按综合分降序排列的结果,全部失败返回 null
+     */
+    RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param, List<String> configCodes);
 }
 }

+ 2 - 56
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/remote/impl/VideoServiceImpl.java

@@ -12,12 +12,12 @@ import com.tzld.piaoquan.longarticle.service.remote.VideoService;
 import com.tzld.piaoquan.longarticle.utils.HttpClientUtil;
 import com.tzld.piaoquan.longarticle.utils.HttpClientUtil;
 import com.tzld.piaoquan.longarticle.utils.HttpPoolClientUtil;
 import com.tzld.piaoquan.longarticle.utils.HttpPoolClientUtil;
 import com.tzld.piaoquan.longarticle.utils.LarkRobotUtil;
 import com.tzld.piaoquan.longarticle.utils.LarkRobotUtil;
+import com.tzld.piaoquan.longarticle.utils.VideoUtils;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
 
 
 import java.util.*;
 import java.util.*;
 
 
@@ -29,7 +29,6 @@ public class VideoServiceImpl implements VideoService {
 
 
     private static final HttpPoolClientUtil HTTP_POOL_CLIENT_UTIL_DEFAULT = HttpClientUtil.create(5000, 5000, 20, 100, 3, 3000);
     private static final HttpPoolClientUtil HTTP_POOL_CLIENT_UTIL_DEFAULT = HttpClientUtil.create(5000, 5000, 20, 100, 3, 3000);
     private static final String PUBLISH_URL = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send";
     private static final String PUBLISH_URL = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send";
-    private static final String VIDEO_DETAIL_URL = "https://longvideoapi.piaoquantv.com/longvideoapi/openapi/video/batchSelectVideoInfo";
 
 
     @Autowired
     @Autowired
     private OffVideoMapper offVideoMapper;
     private OffVideoMapper offVideoMapper;
@@ -43,7 +42,7 @@ public class VideoServiceImpl implements VideoService {
         if (videoId == null) {
         if (videoId == null) {
             return null;
             return null;
         }
         }
-        return getPQVideoDetail(videoId);
+        return VideoUtils.getVideoDetail(Long.parseLong(videoId));
     }
     }
 
 
     private String publishToPQ(String ossPath, String uid, String title) {
     private String publishToPQ(String ossPath, String uid, String title) {
@@ -90,59 +89,6 @@ public class VideoServiceImpl implements VideoService {
         return null;
         return null;
     }
     }
 
 
-
-    private VideoDetail getPQVideoDetail(String videoId) {
-        try {
-            Map<String, Object> data = new HashMap<>();
-            data.put("videoIdList", new String[]{videoId});
-
-            HttpResponse response = HttpRequest.post(VIDEO_DETAIL_URL)
-                    .header("Content-Type", "application/json")
-                    .body(JSONUtil.toJsonStr(data)) // 将数据转换为 JSON 字符串
-                    .execute();
-            VideoDetail videoDetail = new VideoDetail();
-            JSONObject jsonObject = JSONUtil.parseObj(response.body());
-            List<JSONObject> list = jsonObject.get("data", List.class);
-            if (!CollectionUtils.isEmpty(data)) {
-                String id = list.get(0).getStr("id");
-                String shareImgPath = list.get(0).getStr("shareImgPath");
-                String coverImgPath = getCoverImgPath(shareImgPath);
-                String smallPlayImgPath = getSmallPlayImgPath(coverImgPath);
-                String videoPath = list.get(0).getStr("videoPath");
-                if (StringUtils.isNotEmpty(id)) {
-                    videoDetail.setVideoId(id);
-                }
-                videoDetail.setShareImgPath(shareImgPath);
-                videoDetail.setCoverImgPath(coverImgPath);
-                videoDetail.setSmallPlayImgPath(smallPlayImgPath);
-                videoDetail.setVideoPath(videoPath);
-            }
-            return videoDetail;
-        } catch (Exception e) {
-            log.error("getPQVideoDetail error:{}", e.getMessage());
-        }
-        return null;
-    }
-
-    private String getCoverImgPath(String shareImgPath) {
-        String[] split = shareImgPath.split("/");
-        StringBuilder stringBuilder = new StringBuilder();
-        for (String s : split) {
-            if (!s.startsWith("watermark")) {
-                stringBuilder.append(s).append("/");
-            }
-        }
-        if (stringBuilder.length() > 0) {
-            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
-        }
-        return stringBuilder.toString();
-    }
-
-
-    private String getSmallPlayImgPath(String coverImgPath) {
-        return coverImgPath + "/watermark,image_eXNoL3BpYy93YXRlcm1hcmtlci9pY29uX3BsYXlfd2hpdGUucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLHdfNzI=,g_center";
-    }
-
     @Override
     @Override
     public void miniProgramVideoOff(String traceId) {
     public void miniProgramVideoOff(String traceId) {
         try {
         try {

+ 81 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/remote/impl/VideoVectorServiceImpl.java

@@ -11,6 +11,14 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
 @Slf4j
 @Slf4j
 @Service
 @Service
 public class VideoVectorServiceImpl implements VideoVectorService {
 public class VideoVectorServiceImpl implements VideoVectorService {
@@ -22,6 +30,79 @@ public class VideoVectorServiceImpl implements VideoVectorService {
 
 
     @Override
     @Override
     public RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param) {
     public RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param) {
+        return doSingleRecall(param);
+    }
+
+    @Override
+    public RecallVideoScoreVO recallWithScore(RecallVideoScoreParam param, List<String> configCodes) {
+        if (configCodes == null || configCodes.isEmpty()) {
+            return doSingleRecall(param);
+        }
+        // 并发执行每个 configCode 的召回请求
+        CompletableFuture<RecallVideoScoreVO>[] futures = new CompletableFuture[configCodes.size()];
+        for (int i = 0; i < configCodes.size(); i++) {
+            final String configCode = configCodes.get(i);
+            futures[i] = CompletableFuture.supplyAsync(() -> {
+                RecallVideoScoreParam cloneParam = new RecallVideoScoreParam();
+                cloneParam.setConfigCode(configCode);
+                cloneParam.setQueryText(param.getQueryText());
+                cloneParam.setTopN(param.getTopN());
+                cloneParam.setAlpha(param.getAlpha());
+                cloneParam.setRovP95(param.getRovP95());
+                cloneParam.setRovP5(param.getRovP5());
+                cloneParam.setSimMin(param.getSimMin());
+                cloneParam.setRovMin(param.getRovMin());
+                cloneParam.setDays(param.getDays());
+                return doSingleRecall(cloneParam);
+            });
+        }
+
+        CompletableFuture.allOf(futures).join();
+
+        List<RecallVideoScoreVO.ScoredVideoItem> allItems = new ArrayList<>();
+        RecallVideoScoreVO firstResult = null;
+        for (CompletableFuture<RecallVideoScoreVO> future : futures) {
+            RecallVideoScoreVO result = future.join();
+            if (result != null && result.getItems() != null) {
+                allItems.addAll(result.getItems());
+                if (firstResult == null) {
+                    firstResult = result;
+                }
+            }
+        }
+
+        if (allItems.isEmpty()) {
+            return null;
+        }
+
+        // 按 videoId 去重,保留 score 更高的
+        Map<Long, RecallVideoScoreVO.ScoredVideoItem> mergedMap = new LinkedHashMap<>();
+        for (RecallVideoScoreVO.ScoredVideoItem item : allItems) {
+            if (item.getVideoId() == null) {
+                continue;
+            }
+            RecallVideoScoreVO.ScoredVideoItem existing = mergedMap.get(item.getVideoId());
+            if (existing == null || item.getScore() > existing.getScore()) {
+                mergedMap.put(item.getVideoId(), item);
+            }
+        }
+
+        // 按 score 降序排序,取 topN
+        int topN = param.getTopN() != null ? param.getTopN() : 10;
+        List<RecallVideoScoreVO.ScoredVideoItem> sortedItems = mergedMap.values().stream()
+                .sorted(Comparator.comparing(RecallVideoScoreVO.ScoredVideoItem::getScore,
+                        Comparator.nullsLast(Comparator.reverseOrder())))
+                .limit(topN)
+                .collect(Collectors.toList());
+
+        RecallVideoScoreVO mergedResult = new RecallVideoScoreVO();
+        mergedResult.setItems(sortedItems);
+        mergedResult.setTotal(sortedItems.size());
+        mergedResult.setScoreParams(firstResult != null ? firstResult.getScoreParams() : null);
+        return mergedResult;
+    }
+
+    private RecallVideoScoreVO doSingleRecall(RecallVideoScoreParam param) {
         String apiUrl = videoVectorApiHost + "/videoSearch/recallWithScore";
         String apiUrl = videoVectorApiHost + "/videoSearch/recallWithScore";
         try {
         try {
             log.info("recallWithScore request={}", JSON.toJSONString(param));
             log.info("recallWithScore request={}", JSON.toJSONString(param));

+ 43 - 7
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/VideoUtils.java

@@ -2,11 +2,13 @@ package com.tzld.piaoquan.longarticle.utils;
 
 
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.longarticle.model.bo.VideoDetail;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 @Slf4j
 @Slf4j
@@ -15,25 +17,59 @@ public class VideoUtils {
     private static final String VIDEO_DETAIL_URL = "https://longvideoapi.piaoquantv.com/longvideoapi/openapi/video/batchSelectVideoInfo";
     private static final String VIDEO_DETAIL_URL = "https://longvideoapi.piaoquantv.com/longvideoapi/openapi/video/batchSelectVideoInfo";
 
 
 
 
-    public static JSONObject getVideoDetail(Long videoId) {
+    public static VideoDetail getVideoDetail(Long videoId) {
         try {
         try {
             Map<String, Object> data = new HashMap<>();
             Map<String, Object> data = new HashMap<>();
             data.put("videoIdList", new String[]{String.valueOf(videoId)});
             data.put("videoIdList", new String[]{String.valueOf(videoId)});
             HttpResponse response = HttpRequest.post(VIDEO_DETAIL_URL)
             HttpResponse response = HttpRequest.post(VIDEO_DETAIL_URL)
                     .header("Content-Type", "application/json")
                     .header("Content-Type", "application/json")
-                    .body(JSONUtil.toJsonStr(data)) // 将数据转换为 JSON 字符串
+                    .body(JSONUtil.toJsonStr(data))
                     .execute();
                     .execute();
-            JSONObject jsonObject = JSONObject.parseObject(response.body());
-            if (jsonObject.getJSONArray("data") != null) {
-                return jsonObject.getJSONArray("data").getJSONObject(0);
+            JSONObject jsonObject = JSONUtil.parseObj(response.body());
+            List<JSONObject> list = jsonObject.get("data", List.class);
+            if (list == null || list.isEmpty()) {
+                return null;
             }
             }
+            JSONObject item = list.get(0);
+            VideoDetail videoDetail = new VideoDetail();
+            videoDetail.setVideoId(item.getStr("id"));
+            videoDetail.setUid(item.getStr("uid"));
+            videoDetail.setTitle(item.getStr("title"));
+            videoDetail.setOssVideoPath(item.getStr("ossVideoPath"));
+            videoDetail.setVideoPath(item.getStr("videoPath"));
+            String shareImgPath = item.getStr("shareImgPath");
+            videoDetail.setShareImgPath(shareImgPath);
+            if (shareImgPath != null && !shareImgPath.isEmpty()) {
+                String coverImgPath = getCoverImgPath(shareImgPath);
+                videoDetail.setCoverImgPath(coverImgPath);
+                videoDetail.setSmallPlayImgPath(getSmallPlayImgPath(coverImgPath));
+            }
+            return videoDetail;
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("VideoUtils getVideoDetail error:{}", e.getMessage());
             log.error("VideoUtils getVideoDetail error:{}", e.getMessage());
         }
         }
         return null;
         return null;
     }
     }
 
 
+    private static String getCoverImgPath(String shareImgPath) {
+        String[] split = shareImgPath.split("/");
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String s : split) {
+            if (!s.startsWith("watermark")) {
+                stringBuilder.append(s).append("/");
+            }
+        }
+        if (stringBuilder.length() > 0) {
+            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
+        }
+        return stringBuilder.toString();
+    }
+
+    private static String getSmallPlayImgPath(String coverImgPath) {
+        return coverImgPath + "/watermark,image_eXNoL3BpYy93YXRlcm1hcmtlci9pY29uX3BsYXlfd2hpdGUucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLHdfNzI=,g_center";
+    }
+
     public static void main(String[] args) {
     public static void main(String[] args) {
-        System.out.println(getVideoDetail(11466300L).getString("ossVideoPath"));
+        System.out.println(getVideoDetail(11466300L).getOssVideoPath());
     }
     }
 }
 }