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

Merge branch 'master' into test

wangyunpeng 5 дней назад
Родитель
Сommit
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);
     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 一致。
      * crowdSegment == "泛人群" 时翻译成 (NULL OR ''/'-'/'null'),与 selectForRecommend 一致。
      * ORDER BY rov DESC, id ASC,便于上层 putIfAbsent 直接取 max rov 代表行。
      * ORDER BY rov DESC, id ASC,便于上层 putIfAbsent 直接取 max rov 代表行。
      * 小程序投流:crowdSegment=#{crowdPackage},channelLevel3=null
      * 小程序投流:crowdSegment=#{crowdPackage},channelLevel3=null
@@ -80,7 +81,9 @@ public interface ContentPlatformDemandVideoMapperExt {
     List<ContentPlatformDemandVideo> selectSearchWhitelist(@Param("dt") String dt,
     List<ContentPlatformDemandVideo> selectSearchWhitelist(@Param("dt") String dt,
                                                             @Param("channelName") String channelName,
                                                             @Param("channelName") String channelName,
                                                             @Param("crowdSegment") String crowdSegment,
                                                             @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);
     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.PREMIUM, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, 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.DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.SCENE, true),
                 // 策略2: 人群需求*特征点*向量匹配
                 // 策略2: 人群需求*特征点*向量匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, 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.DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.STANDARD, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 // 策略3: 人群需求*特征点泛化*向量匹配
                 // 策略3: 人群需求*特征点泛化*向量匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, 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.DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
+                new PriorPoolConfig(PriorDimensionEnum.GROWTH_DISTRIBUTION, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.VECTOR_SIMILARITY, false),
                 // 策略4: 人群需求*特征点泛化*精准匹配
                 // 策略4: 人群需求*特征点泛化*精准匹配
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false),
                 new PriorPoolConfig(PriorDimensionEnum.PREMIUM, DemandTypeEnum.GENERALIZED, DemandMatchMethodEnum.PRECISION, false),
                 new PriorPoolConfig(PriorDimensionEnum.GROWTH, 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;
         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
     @Override
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
         ContentPlatformAccount user = LoginUserContext.getUser();
         ContentPlatformAccount user = LoginUserContext.getUser();
@@ -772,12 +810,13 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         String dt = demandVideoMapperExt.getMaxDt(channel == null ? null : channel.getValue());
         String dt = demandVideoMapperExt.getMaxDt(channel == null ? null : channel.getValue());
         List<VideoContentItemVO> list;
         List<VideoContentItemVO> list;
         if (VideoContentSource.PRIOR == source) {
         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);
             ExecutorService executor = Executors.newFixedThreadPool(poolCount);
             try {
             try {
                 List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(poolCount);
                 List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(poolCount);
-                for (PriorPoolConfig cfg : PRIOR_POOL_CONFIGS) {
+                for (PriorPoolConfig cfg : configs) {
                     futures.add(executor.submit(cfg.scene
                     futures.add(executor.submit(cfg.scene
                             ? () -> fetchPriorSceneCandidates(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)
                             ? () -> 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)));
                             : () -> fetchPriorPool(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)));
@@ -921,16 +960,18 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
      * 用 (userId ^ 当天日期) 作为种子,保证同一用户当天翻页顺序一致、刷新一致。
      * 用 (userId ^ 当天日期) 作为种子,保证同一用户当天翻页顺序一致、刷新一致。
      */
      */
     private Page<VideoContentItemVO> getInterleavedPage(VideoContentListParam param, ContentPlatformAccount user) {
     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,不受影响)
         // 杠杆1: channel + dt 每请求只解析一次,传入各 demand 池(hot 池用自己的 dt,不受影响)
         DemandChannelEnum channel = resolveChannelName(param);
         DemandChannelEnum channel = resolveChannelName(param);
         String dt = demandVideoMapperExt.getMaxDt(channel == null ? null : channel.getValue());
         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);
         ExecutorService executor = Executors.newFixedThreadPool(totalCount);
         try {
         try {
             // prior 池并行拉取
             // prior 池并行拉取
             List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(totalCount);
             List<Future<List<VideoContentItemVO>>> futures = new ArrayList<>(totalCount);
-            for (PriorPoolConfig cfg : PRIOR_POOL_CONFIGS) {
+            for (PriorPoolConfig cfg : priorConfigs) {
                 futures.add(executor.submit(cfg.scene
                 futures.add(executor.submit(cfg.scene
                         ? () -> fetchPriorSceneCandidates(param, user, DEMAND_CANDIDATE_LIMIT, channel, dt, cfg.dimension, cfg.demandType, cfg.matchMethod)
                         ? () -> 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)));
                         : () -> 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;
         if (!StringUtils.hasText(dt)) return empty;
 
 
         List<ContentPlatformDemandVideo> whitelist = demandVideoMapperExt.selectSearchWhitelist(
         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;
         if (CollectionUtils.isEmpty(whitelist)) return empty;
 
 
         // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
         // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
@@ -2048,6 +2090,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         for (ContentPlatformDemandVideo demand : bestPerVideo.values()) {
         for (ContentPlatformDemandVideo demand : bestPerVideo.values()) {
             if (demand.getTitle() == null) continue;
             if (demand.getTitle() == null) continue;
             if (!demand.getTitle().toLowerCase().contains(kw)) 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);
             VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
             vo.setSearchSource("keyword");
             vo.setSearchSource("keyword");
             vo.setScore(null);
             vo.setScore(null);

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

@@ -194,7 +194,11 @@
           AND demand_strategy IN ('人群需求', '优质相似')
           AND demand_strategy IN ('人群需求', '优质相似')
           AND video_id IS NOT NULL
           AND video_id IS NOT NULL
           AND rov 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 != ''">
         <if test="crowdSegment != null and crowdSegment != ''">
             <choose>
             <choose>
                 <when test='crowdSegment == "泛人群"'>
                 <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;
 package com.tzld.piaoquan.api.service.contentplatform.impl;
 
 
+import com.tzld.piaoquan.api.common.enums.contentplatform.DemandMatchMethodEnum;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -41,4 +42,24 @@ public class ContentPlatformPlanServiceImplExposureFilterTest {
         assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(2001L, 2000L));
         assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(2001L, 2000L));
         assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(50000L, 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));
+    }
 }
 }