فهرست منبع

Merge branch 'master' into test

wangyunpeng 4 روز پیش
والد
کامیت
03ad89424e

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

@@ -71,7 +71,8 @@ public interface ContentPlatformDemandVideoMapperExt {
     List<String> selectDistinctCrowdPackages(@Param("dt") String dt, @Param("channelName") String channelName);
 
     /**
-     * 搜索候选白名单:dt 最新分区下,该入口(channel + 入口维度)demand_strategy IN ('人群需求', '优质相似') 且 rov >= 0.03 的视频行。
+     * 搜索候选白名单:dt 最新分区下,该入口(channel + 入口维度)demand_strategy IN ('人群需求', '优质相似') 的视频行。
+     * 非场景已看视频要求 rov >= demandMinRov;场景已看视频与列表下发一致,要求 rov > 0 且 scene_sum_rov >= priorSceneMinSumRov。
      * crowdSegment == "泛人群" 时翻译成 (NULL OR ''/'-'/'null'),与 selectForRecommend 一致。
      * ORDER BY rov DESC, id ASC,便于上层 putIfAbsent 直接取 max rov 代表行。
      * 小程序投流:crowdSegment=#{crowdPackage},channelLevel3=null
@@ -80,7 +81,9 @@ public interface ContentPlatformDemandVideoMapperExt {
     List<ContentPlatformDemandVideo> selectSearchWhitelist(@Param("dt") String dt,
                                                             @Param("channelName") String channelName,
                                                             @Param("crowdSegment") String crowdSegment,
-                                                            @Param("channelLevel3") String channelLevel3);
+                                                            @Param("channelLevel3") String channelLevel3,
+                                                            @Param("demandMinRov") double demandMinRov,
+                                                            @Param("priorSceneMinSumRov") double priorSceneMinSumRov);
 
     List<ContentPlatformDemandVideo> selectActiveVideos(@Param("dt") String dt);
 

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

@@ -644,21 +644,50 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
                 new PriorPoolConfig(PriorDimensionEnum.DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
                 // 策略2: 人群需求*特征点*向量匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 // 策略3: 人群需求*特征点泛化*向量匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 // 策略4: 人群需求*特征点泛化*精准匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false),
-                new PriorPoolConfig(PriorDimensionEnum.DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false)
+                new PriorPoolConfig(PriorDimensionEnum.DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false)
         );
     }
 
+    /**
+     * 按渠道返回人群需求池配置,不同渠道排除特定策略组合。
+     */
+    private static List<PriorPoolConfig> getPriorPoolConfigs(DemandChannelEnum channel) {
+        List<PriorPoolConfig> configs = new ArrayList<>(PRIOR_POOL_CONFIGS);
+        if (channel == DemandChannelEnum.GZH_JIZHUAN) {
+            // 公众号即转合作:干掉 增长头部*场景已看、传播头部*场景已看
+            configs.removeIf(cfg ->
+                    (cfg.dimension == PriorDimensionEnum.GROWTH && cfg.demandType == DemandTypeEnum.STANDARD && cfg.matchMethod == DemandMatchMethodEnum.SCENE)
+                            || (cfg.dimension == PriorDimensionEnum.PREMIUM && cfg.demandType == DemandTypeEnum.STANDARD && cfg.matchMethod == DemandMatchMethodEnum.SCENE));
+        } else if (channel == DemandChannelEnum.XCX) {
+            // 小程序投流:干掉 增长头部*向量匹配、增长头部*泛化*精准匹配、传播头部*场景已看
+            configs.removeIf(cfg ->
+                    (cfg.dimension == PriorDimensionEnum.GROWTH && cfg.demandType == DemandTypeEnum.STANDARD && cfg.matchMethod == DemandMatchMethodEnum.VECTOR_SIMILARITY)
+                            || (cfg.dimension == PriorDimensionEnum.GROWTH && cfg.demandType == DemandTypeEnum.GENERALIZED && cfg.matchMethod == DemandMatchMethodEnum.PRECISION)
+                            || (cfg.dimension == PriorDimensionEnum.PREMIUM && cfg.demandType == DemandTypeEnum.STANDARD && cfg.matchMethod == DemandMatchMethodEnum.SCENE));
+        } else if (channel == DemandChannelEnum.QW) {
+            // 企微合作:干掉 传播头部*泛化*精准匹配、传播分发*泛化*精准匹配
+            configs.removeIf(cfg ->
+                    (cfg.dimension == PriorDimensionEnum.PREMIUM && cfg.demandType == DemandTypeEnum.GENERALIZED && cfg.matchMethod == DemandMatchMethodEnum.PRECISION)
+                            || (cfg.dimension == PriorDimensionEnum.DISTRIBUTION && cfg.demandType == DemandTypeEnum.GENERALIZED && cfg.matchMethod == DemandMatchMethodEnum.PRECISION));
+        }
+        return configs;
+    }
+
     /**
      * 人群需求单池配置
      */
@@ -739,6 +768,15 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         return matchExposurePv == null || matchExposurePv > minExposurePv;
     }
 
+    static boolean passesSearchDemandRovFilter(String matchMethod, Double rov, Double sceneSumRov,
+                                               double demandMinRov, double priorSceneMinSumRov) {
+        if (DemandMatchMethodEnum.SCENE.getValue().equals(matchMethod)) {
+            return rov != null && rov > 0
+                    && sceneSumRov != null && sceneSumRov >= priorSceneMinSumRov;
+        }
+        return rov != null && rov >= demandMinRov;
+    }
+
     @Override
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
         ContentPlatformAccount user = LoginUserContext.getUser();
@@ -772,12 +810,13 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         String dt = demandVideoMapperExt.getMaxDt(channel == null ? null : channel.getValue());
         List<VideoContentItemVO> list;
         if (VideoContentSource.PRIOR == source) {
-            // 人群需求池并行拉取, 按 PRIOR_POOL_CONFIGS 配置
-            int poolCount = PRIOR_POOL_CONFIGS.size();
+            // 人群需求池并行拉取, 按渠道差异化配置
+            List<PriorPoolConfig> configs = getPriorPoolConfigs(channel);
+            int poolCount = configs.size();
             ExecutorService executor = Executors.newFixedThreadPool(poolCount);
             try {
                 List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(poolCount);
-                for (PriorPoolConfig cfg : PRIOR_POOL_CONFIGS) {
+                for (PriorPoolConfig cfg : configs) {
                     futures.add(executor.submit(cfg.scene
                             ? () -> fetchPriorSceneCandidates(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)
                             : () -> fetchPriorPool(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)));
@@ -921,16 +960,18 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
      * 用 (userId ^ 当天日期) 作为种子,保证同一用户当天翻页顺序一致、刷新一致。
      */
     private Page<VideoContentItemVO> getInterleavedPage(VideoContentListParam param, ContentPlatformAccount user) {
-        int priorCount = PRIOR_POOL_CONFIGS.size();
-        int totalCount = priorCount + 2; // + posterior + hot
         // 杠杆1: channel + dt 每请求只解析一次,传入各 demand 池(hot 池用自己的 dt,不受影响)
         DemandChannelEnum channel = resolveChannelName(param);
         String dt = demandVideoMapperExt.getMaxDt(channel == null ? null : channel.getValue());
+        // 人群需求池按渠道差异化配置
+        List<PriorPoolConfig> priorConfigs = getPriorPoolConfigs(channel);
+        int priorCount = priorConfigs.size();
+        int totalCount = priorCount + 2; // + posterior + hot
         ExecutorService executor = Executors.newFixedThreadPool(totalCount);
         try {
             // prior 池并行拉取
             List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(totalCount);
-            for (PriorPoolConfig cfg : PRIOR_POOL_CONFIGS) {
+            for (PriorPoolConfig cfg : priorConfigs) {
                 futures.add(executor.submit(cfg.scene
                         ? () -> fetchPriorSceneCandidates(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)
                         : () -> fetchPriorPool(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)));
@@ -2034,7 +2075,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         if (!StringUtils.hasText(dt)) return empty;
 
         List<ContentPlatformDemandVideo> whitelist = demandVideoMapperExt.selectSearchWhitelist(
-                dt, ch, isXcx ? crowdPackage : null, isXcx ? null : ghName);
+                dt, ch, isXcx ? crowdPackage : null, isXcx ? null : ghName,
+                demandMinRov, priorSceneMinSumRov);
         if (CollectionUtils.isEmpty(whitelist)) return empty;
 
         // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
@@ -2048,6 +2090,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         for (ContentPlatformDemandVideo demand : bestPerVideo.values()) {
             if (demand.getTitle() == null) continue;
             if (!demand.getTitle().toLowerCase().contains(kw)) continue;
+            if (!passesSearchDemandRovFilter(demand.getMatchMethod(), demand.getRov(), demand.getSceneSumRov(),
+                    demandMinRov, priorSceneMinSumRov)) continue;
             VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
             vo.setSearchSource("keyword");
             vo.setScore(null);

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

@@ -194,7 +194,11 @@
           AND demand_strategy IN ('人群需求', '优质相似')
           AND video_id IS NOT NULL
           AND rov IS NOT NULL
-          AND rov &gt;= 0.03
+          AND (
+              (match_method = '场景已看视频' AND rov &gt; 0 AND scene_sum_rov IS NOT NULL AND scene_sum_rov &gt;= #{priorSceneMinSumRov})
+              OR
+              ((match_method IS NULL OR match_method &lt;&gt; '场景已看视频') AND rov &gt;= #{demandMinRov})
+          )
         <if test="crowdSegment != null and crowdSegment != ''">
             <choose>
                 <when test='crowdSegment == "泛人群"'>

+ 21 - 0
api-module/src/test/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImplExposureFilterTest.java

@@ -1,5 +1,6 @@
 package com.tzld.piaoquan.api.service.contentplatform.impl;
 
+import com.tzld.piaoquan.api.common.enums.contentplatform.DemandMatchMethodEnum;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -41,4 +42,24 @@ public class ContentPlatformPlanServiceImplExposureFilterTest {
         assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(2001L, 2000L));
         assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(50000L, 2000L));
     }
+
+    /** 搜索命中场景已看视频时,与列表下发一致:不按 rov>=0.03,改用 sceneSumRov>=0.03。 */
+    @Test
+    public void searchSceneVideo_usesSceneSumRovThreshold() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesSearchDemandRovFilter(
+                DemandMatchMethodEnum.SCENE.getValue(), 0.01d, 0.03d, 0.03d, 0.03d));
+        assertFalse(ContentPlatformPlanServiceImpl.passesSearchDemandRovFilter(
+                DemandMatchMethodEnum.SCENE.getValue(), 0.01d, 0.029d, 0.03d, 0.03d));
+        assertFalse(ContentPlatformPlanServiceImpl.passesSearchDemandRovFilter(
+                DemandMatchMethodEnum.SCENE.getValue(), 0d, 0.05d, 0.03d, 0.03d));
+    }
+
+    /** 搜索命中非场景已看视频时,仍沿用 demandMinRov。 */
+    @Test
+    public void searchNonSceneVideo_usesDemandMinRovThreshold() {
+        assertFalse(ContentPlatformPlanServiceImpl.passesSearchDemandRovFilter(
+                DemandMatchMethodEnum.VECTOR_SIMILARITY.getValue(), 0.029d, 0.2d, 0.03d, 0.03d));
+        assertTrue(ContentPlatformPlanServiceImpl.passesSearchDemandRovFilter(
+                DemandMatchMethodEnum.VECTOR_SIMILARITY.getValue(), 0.03d, null, 0.03d, 0.03d));
+    }
 }