فهرست منبع

搜索简化:关闭向量召回,纯关键字 LIKE + rov DESC 排序

- selectSearchWhitelist rov 阈值统一 >=0.02(去掉场景已看视频 rov>0 旁路),对齐 prior/posterior 池 DEMAND_MIN_ROV
- SQL 加 ORDER BY rov DESC, id ASC,Java putIfAbsent 取 max rov 代表行
- selectSearchWhitelist 加 "泛人群" choose/when 特判(crowd_segment IN NULL/''/-/null),对齐 selectForRecommend,修复 crowdPackage=泛人群 时搜不到结果
- searchByTitleInDemandPool 删除 Pass2 向量召回 + SEARCH_VECTOR_TOP_N 常量
- 排序改为 rov DESC, total_rov DESC tiebreaker;score VO 字段清 null 避免前端歧义
- title.trim() 防空白搜索,pageNum<1 兜底防 subList 越界
刘立冬 14 ساعت پیش
والد
کامیت
0b24472606

+ 3 - 5
api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/contentplatform/ext/ContentPlatformDemandVideoMapperExt.java

@@ -63,11 +63,9 @@ public interface ContentPlatformDemandVideoMapperExt {
     List<String> selectDistinctCrowdPackages(@Param("dt") String dt, @Param("channelName") String channelName);
 
     /**
-     * 搜索候选白名单:dt 最新分区下,该入口(channel + 入口维度)demand_strategy IN ('人群需求', '优质相似') 的视频行。
-     * rov 过滤分两档,与各推荐池线上展示口径对齐:
-     *   - match_method='场景已看视频'(priorScene 池):rov > 0 即可
-     *   - 其它(传播头部/增长头部/优质相似):rov >= 0.02
-     * 不做组内 topK / 分位过滤;仅用于 title 搜索时与关键字/向量召回取交集。
+     * 搜索候选白名单:dt 最新分区下,该入口(channel + 入口维度)demand_strategy IN ('人群需求', '优质相似') 且 rov >= 0.02 的视频行。
+     * crowdSegment == "泛人群" 时翻译成 (NULL OR ''/'-'/'null'),与 selectForRecommend 一致。
+     * ORDER BY rov DESC, id ASC,便于上层 putIfAbsent 直接取 max rov 代表行。
      * 小程序投流:crowdSegment=#{crowdPackage},channelLevel3=null
      * 公众号投流-稳定:crowdSegment=null,channelLevel3=#{ghName}
      */

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

@@ -654,8 +654,6 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
             CHANNEL_NAME_XCX, CHANNEL_NAME_GZH_TOULIU));
     /** 搜索入口仅对 INTERNAL(2 自营)/ AGENT(3 代理) 类型账号放开。 */
     private static final Set<Integer> USER_TYPES_ALLOW_SEARCH = new HashSet<>(Arrays.asList(2, 3));
-    /** 搜索向量召回 topN:取较大值,确保与 demand 白名单交集后仍有足量结果。 */
-    private static final int SEARCH_VECTOR_TOP_N = 500;
     /**
      * 合作类渠道:demand.crowd_segment 语义=合作方代码(yy/szhx/cdjh/...),与登录账号 ContentPlatformAccount.channel 同义,可作为过滤条件。
      * 其余渠道(投流-稳定/服务号投流):demand.crowd_segment 语义=人群分组标签(R50*xx/回流xx),与登录账号合作方无关,绝不可用 user.channel 过滤。
@@ -2025,18 +2023,19 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     }
 
     /**
-     * 搜索:在当前入口的 demand 白名单(prior+posterior 池,rov>=0.02)内做 title LIKE 关键字命中 + 向量召回,并集去重后统一排序。
+     * 搜索:在当前入口的 demand 白名单(rov >= 0.02)内做 title LIKE 关键字命中,按 rov DESC 排序。
      * 门控:
      *   - 用户身份 type ∈ {2 自营, 3 代理},否则返回空
      *   - 渠道 ∈ {小程序投流-稳定, 公众号投流-稳定},否则返回空
      *   - 小程序投流必填 crowdPackage,公众号投流必填 ghName,否则返回空
      * 流程:
      *   1. demand 白名单(channel + crowdSegment/channelLevel3 + demand_strategy IN (人群需求,优质相似) + rov>=0.02)
-     *   2. 同 video_id 取 sceneSumRov 最大的代表行
-     *   3. Pass1: 池内 title.toLowerCase().contains(query) 关键字命中,score=null
-     *   4. Pass2: 向量召回 topN=500,池内交集;同 video_id 已被关键字命中则用向量 score 升级
-     *   5. 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
-     *   6. 一次性算好全量,按 pageNum/pageSize 切片返回(翻页不再调向量服务)
+     *      SQL 已 ORDER BY rov DESC, id ASC
+     *   2. 同 video_id 去重:putIfAbsent 取 max rov 代表行 —— 与搜索 rov DESC 排序对齐,
+     *      与 priorScene 池(取 max sceneSumRov 代表行)有意区别;同 dt 内稳定,跨 dt 重灌可能换代表行。
+     *   3. title.trim().toLowerCase().contains(query) 关键字命中
+     *   4. 排序:rov DESC,次级 total_rov DESC,score 字段清空避免前端歧义
+     *   5. 按 pageNum/pageSize 内存切片返回
      */
     private Page<VideoContentItemVO> searchByTitleInDemandPool(VideoContentListParam param, ContentPlatformAccount user) {
         Page<VideoContentItemVO> empty = new Page<>(param.getPageNum(), param.getPageSize());
@@ -2056,6 +2055,9 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         if (isXcx && crowdPackage == null) return empty;
         if (!isXcx && ghName == null) return empty;
 
+        String kw = param.getTitle() == null ? "" : param.getTitle().trim().toLowerCase();
+        if (kw.isEmpty()) return empty;
+
         String dt = demandVideoMapperExt.getMaxDt(channelName);
         if (!StringUtils.hasText(dt)) return empty;
 
@@ -2063,73 +2065,42 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                 dt, channelName, isXcx ? crowdPackage : null, isXcx ? null : ghName);
         if (CollectionUtils.isEmpty(whitelist)) return empty;
 
-        // video_id 去重:取 sceneSumRov 最大的代表行
-        Map<Long, ContentPlatformDemandVideo> bestPerVideo = new HashMap<>();
+        // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
+        LinkedHashMap<Long, ContentPlatformDemandVideo> bestPerVideo = new LinkedHashMap<>();
         for (ContentPlatformDemandVideo r : whitelist) {
             if (r.getVideoId() == null) continue;
-            ContentPlatformDemandVideo cur = bestPerVideo.get(r.getVideoId());
-            double rSum = r.getSceneSumRov() == null ? 0d : r.getSceneSumRov();
-            double curSum = cur == null ? -1d : (cur.getSceneSumRov() == null ? 0d : cur.getSceneSumRov());
-            if (cur == null || rSum > curSum) {
-                bestPerVideo.put(r.getVideoId(), r);
-            }
+            bestPerVideo.putIfAbsent(r.getVideoId(), r);
         }
 
-        // Pass 1: title LIKE 关键字命中(池内 substring,大小写不敏感),score=null 兜底 sceneSumRov
-        String kw = param.getTitle().toLowerCase();
-        Map<Long, VideoContentItemVO> byVid = new LinkedHashMap<>();
+        List<VideoContentItemVO> hits = new ArrayList<>();
         for (ContentPlatformDemandVideo demand : bestPerVideo.values()) {
             if (demand.getTitle() == null) continue;
             if (!demand.getTitle().toLowerCase().contains(kw)) continue;
             VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
             vo.setSearchSource("keyword");
             vo.setScore(null);
-            byVid.put(demand.getVideoId(), vo);
-        }
-
-        // Pass 2: 向量召回,池内交集;若已被 title 命中,用向量 score 升级以提升排序
-        JSONObject vectorData = managerApiService.recallVideoWithScore(param.getTitle(), SEARCH_VECTOR_TOP_N);
-        JSONArray items = vectorData == null ? null : vectorData.getJSONArray("items");
-        if (items != null) {
-            for (int i = 0; i < items.size(); i++) {
-                JSONObject item = items.getJSONObject(i);
-                Long videoId = item.getLong("videoId");
-                if (videoId == null || !bestPerVideo.containsKey(videoId)) continue;
-                Double vScore = item.getDouble("score");
-                VideoContentItemVO existing = byVid.get(videoId);
-                if (existing != null) {
-                    existing.setSearchSource("both");
-                    existing.setScore(vScore);
-                } else {
-                    ContentPlatformDemandVideo demand = bestPerVideo.get(videoId);
-                    VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
-                    vo.setSearchSource("vector");
-                    vo.setScore(vScore);
-                    byVid.put(videoId, vo);
-                }
-            }
+            hits.add(vo);
         }
 
-        if (byVid.isEmpty()) return empty;
-        List<VideoContentItemVO> hits = new ArrayList<>(byVid.values());
+        if (hits.isEmpty()) return empty;
 
-        // 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
+        // rov DESC,次级 total_rov DESC 作稳定兜底
         hits.sort((a, b) -> {
-            double sa = a.getScore() == null ? 0d : a.getScore();
-            double sb = b.getScore() == null ? 0d : b.getScore();
-            int c = Double.compare(sb, sa);
+            double ra = a.getRov() == null ? 0d : a.getRov();
+            double rb = b.getRov() == null ? 0d : b.getRov();
+            int c = Double.compare(rb, ra);
             if (c != 0) return c;
-            double ra = a.getSceneSumRov() == null ? 0d : a.getSceneSumRov();
-            double rb = b.getSceneSumRov() == null ? 0d : b.getSceneSumRov();
-            return Double.compare(rb, ra);
+            double ta = a.getTotalRov() == null ? 0d : a.getTotalRov();
+            double tb = b.getTotalRov() == null ? 0d : b.getTotalRov();
+            return Double.compare(tb, ta);
         });
 
-        Page<VideoContentItemVO> page = new Page<>(param.getPageNum(), param.getPageSize());
-        page.setTotalSize(hits.size());
-        // 全量在后端计算好,按 pageNum/pageSize 切片(单次召回,翻页不再调向量服务)
+        int pageNum = Math.max(1, param.getPageNum());
         int pageSize = param.getPageSize() > 0 ? param.getPageSize() : 10;
-        int from = Math.min((param.getPageNum() - 1) * pageSize, hits.size());
-        int to = Math.min(param.getPageNum() * pageSize, hits.size());
+        Page<VideoContentItemVO> page = new Page<>(pageNum, pageSize);
+        page.setTotalSize(hits.size());
+        int from = Math.min((pageNum - 1) * pageSize, hits.size());
+        int to = Math.min(pageNum * pageSize, hits.size());
         page.setObjs(new ArrayList<>(hits.subList(from, to)));
         return page;
     }

+ 10 - 5
api-module/src/main/resources/mapper/contentplatform/ext/ContentPlatformDemandVideoMapperExt.xml

@@ -175,16 +175,21 @@
           AND demand_strategy IN ('人群需求', '优质相似')
           AND video_id IS NOT NULL
           AND rov IS NOT NULL
-          AND (
-                (match_method = '场景已看视频' AND rov &gt; 0)
-                OR rov &gt;= 0.02
-              )
+          AND rov &gt;= 0.02
         <if test="crowdSegment != null and crowdSegment != ''">
-            AND crowd_segment = #{crowdSegment}
+            <choose>
+                <when test='crowdSegment == "泛人群"'>
+                    AND (crowd_segment IS NULL OR crowd_segment = '' OR crowd_segment = '-' OR crowd_segment = 'null')
+                </when>
+                <otherwise>
+                    AND crowd_segment = #{crowdSegment}
+                </otherwise>
+            </choose>
         </if>
         <if test="channelLevel3 != null and channelLevel3 != ''">
             AND channel_level3 = #{channelLevel3}
         </if>
+        ORDER BY rov DESC, id ASC
     </select>
 
     <select id="selectDistinctCrowdPackages" resultType="java.lang.String">