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

Merge branch 'cooperation_video_candidate_pool_improved_lld_0509' into test

刘立冬 1 день назад
Родитель
Сommit
0b0d3b0016

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

@@ -62,6 +62,18 @@ public interface ContentPlatformDemandVideoMapperExt {
 
     List<String> selectDistinctCrowdPackages(@Param("dt") String dt, @Param("channelName") String channelName);
 
+    /**
+     * 搜索候选白名单: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}
+     */
+    List<ContentPlatformDemandVideo> selectSearchWhitelist(@Param("dt") String dt,
+                                                            @Param("channelName") String channelName,
+                                                            @Param("crowdSegment") String crowdSegment,
+                                                            @Param("channelLevel3") String channelLevel3);
+
     List<ContentPlatformDemandVideo> selectActiveVideos(@Param("dt") String dt);
 
     int updateStatusByVideoId(@Param("videoId") Long videoId, @Param("dt") String dt, @Param("status") Integer status, @Param("updateTimestamp") Long updateTimestamp);

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

@@ -646,8 +646,14 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     /** channel_name 映射:企微/小程序 type 直推,公众号入口按 ghName 反查 demand 表(见 resolveChannelName)。 */
     private static final String CHANNEL_NAME_QW           = "群/企微合作-稳定";
     private static final String CHANNEL_NAME_XCX          = "小程序投流-稳定";
+    private static final String CHANNEL_NAME_GZH_TOULIU   = "公众号投流-稳定";
     private static final String CHANNEL_NAME_GZH_JIZHUAN  = "公众号合作-即转-稳定";
     private static final String CHANNEL_NAME_GZH_DAILY    = "公众号合作-Daily-自选";
+    /** 搜索入口仅对这两个渠道放开:小程序投流(按人群包)+ 公众号投流-稳定(按公众号)。 */
+    private static final Set<String> CHANNELS_ALLOW_SEARCH = new HashSet<>(Arrays.asList(
+            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));
     /**
      * 合作类渠道:demand.crowd_segment 语义=合作方代码(yy/szhx/cdjh/...),与登录账号 ContentPlatformAccount.channel 同义,可作为过滤条件。
      * 其余渠道(投流-稳定/服务号投流):demand.crowd_segment 语义=人群分组标签(R50*xx/回流xx),与登录账号合作方无关,绝不可用 user.channel 过滤。
@@ -706,9 +712,9 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     @Override
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
         ContentPlatformAccount user = LoginUserContext.getUser();
-        // 如果 title 有内容,调用 manager 平台接口搜索
+        // title 非空 → 走 demand 池白名单 + 向量召回交集搜索(切掉管理平台关键词 fallback,只对 type∈{2,3} + 投流类渠道放开)
         if (StringUtils.hasText(param.getTitle())) {
-            return getVideoContentListByTitleV2(param);
+            return searchByTitleInDemandPool(param, user);
         }
         String source = param.getSource();
         if (SOURCE_PRIOR.equalsIgnoreCase(source)) {
@@ -2016,6 +2022,89 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         return result;
     }
 
+    /**
+     * 搜索:在当前入口的 demand 白名单(rov >= 0.02)内做 title LIKE 关键字命中,按 rov DESC 排序。
+     * 门控:
+     *   - 用户身份 type ∈ {2 自营, 3 代理},否则返回空
+     *   - 渠道 ∈ {小程序投流-稳定, 公众号投流-稳定},否则返回空
+     *   - 小程序投流必填 crowdPackage,公众号投流必填 ghName,否则返回空
+     * 流程:
+     *   1. demand 白名单(channel + crowdSegment/channelLevel3 + demand_strategy IN (人群需求,优质相似) + rov>=0.02)
+     *      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());
+        empty.setTotalSize(0);
+        empty.setObjs(new ArrayList<>());
+
+        if (user == null || !USER_TYPES_ALLOW_SEARCH.contains(user.getType())) {
+            return empty;
+        }
+        String channelName = resolveChannelName(param);
+        if (channelName == null || !CHANNELS_ALLOW_SEARCH.contains(channelName)) {
+            return empty;
+        }
+        boolean isXcx = CHANNEL_NAME_XCX.equals(channelName);
+        String crowdPackage = StringUtils.hasText(param.getCrowdPackage()) ? param.getCrowdPackage() : null;
+        String ghName = StringUtils.hasText(param.getGhName()) ? param.getGhName() : null;
+        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;
+
+        List<ContentPlatformDemandVideo> whitelist = demandVideoMapperExt.selectSearchWhitelist(
+                dt, channelName, isXcx ? crowdPackage : null, isXcx ? null : ghName);
+        if (CollectionUtils.isEmpty(whitelist)) return empty;
+
+        // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
+        LinkedHashMap<Long, ContentPlatformDemandVideo> bestPerVideo = new LinkedHashMap<>();
+        for (ContentPlatformDemandVideo r : whitelist) {
+            if (r.getVideoId() == null) continue;
+            bestPerVideo.putIfAbsent(r.getVideoId(), r);
+        }
+
+        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);
+            hits.add(vo);
+        }
+
+        if (hits.isEmpty()) return empty;
+
+        // rov DESC,次级 total_rov DESC 作稳定兜底
+        hits.sort((a, b) -> {
+            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 ta = a.getTotalRov() == null ? 0d : a.getTotalRov();
+            double tb = b.getTotalRov() == null ? 0d : b.getTotalRov();
+            return Double.compare(tb, ta);
+        });
+
+        int pageNum = Math.max(1, param.getPageNum());
+        int pageSize = param.getPageSize() > 0 ? param.getPageSize() : 10;
+        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;
+    }
+
     private List<VideoContentItemVO> buildDemandVideoContentItemVOList(List<ContentPlatformDemandVideo> videoList) {
         if (CollectionUtils.isEmpty(videoList)) {
             return new ArrayList<>();

+ 32 - 0
api-module/src/main/resources/mapper/contentplatform/ext/ContentPlatformDemandVideoMapperExt.xml

@@ -160,6 +160,38 @@
         LIMIT #{limit}
     </select>
 
+    <select id="selectSearchWhitelist" resultType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformDemandVideo">
+        SELECT id, dt, channel_name, channel_level3, crowd_segment, dimension, point_type, standard_element,
+               category_name, category, demand_id, crowd_package, conversion_target, partner, account, scene_value,
+               demand_strategy, drive_dimension_time, demand_filter_sort_strategy, demand_type,
+               demand_content_id, demand_content_title, demand_content_topic,
+               crowd_count, video_count, visit_uv, uv_ratio, total_rov, online_action, match_experiment_id,
+               match_method, match_video_filter, match_sort,
+               video_id, config_code, score, sim, rov,
+               match_text, title, cover, video, experiment_id, scene_sum_rov, status, create_timestamp, update_timestamp
+        FROM content_platform_demand_video
+        WHERE dt = #{dt} AND status = 1
+          AND channel_name = #{channelName}
+          AND demand_strategy IN ('人群需求', '优质相似')
+          AND video_id IS NOT NULL
+          AND rov IS NOT NULL
+          AND rov &gt;= 0.02
+        <if test="crowdSegment != null and 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">
         SELECT DISTINCT
             CASE