Explorar o código

单源走 SQL 真分页 + 后验过滤自标题

- 新增 selectForRecommendPaged / countForRecommend:单源 prior/posterior 不再走 fetch+subList(500 cap),改 SQL OFFSET/LIMIT 真分页,totalSize 取 COUNT(*)
- 单源 hot 改走原 planMapperExt.getVideoCount + getVideoList(offset, pageSize),沿用老链路真分页
- 三个 demand 查询新增 excludeSelfTitle 参数:后验需求场景过滤 title == demand_content_title 行(避免推种子视频自身或同标题重复视频);prior 不开启
- 穿插模式保持候选池模型不变(跨池去重需要 materialize),fetchDemandCandidates 也按 strategy 传 excludeSelfTitle
刘立冬 hai 1 día
pai
achega
3eadf3c9c9

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

@@ -29,12 +29,34 @@ public interface ContentPlatformDemandVideoMapperExt {
 
     /**
      * 推荐场景候选池查询:按 demand_strategy 取指定 crowd_segment 的候选视频,按 score DESC 取前 N 行。
-     * 不在 SQL 层做 video_id 去重,调用方在 Java 侧按 video_id 保留首条(即得分最高的一条)。
+     * 用于穿插模式(需要先 materialize 三池再跨池去重);不在 SQL 层做 video_id 去重,调用方在 Java 侧按 video_id 保留首条(即得分最高的一条)。
+     * excludeSelfTitle=true 时过滤掉 video.title == demand_content_title 的行(用于后验需求避免推种子视频自身或同标题重复视频)。
      */
     List<ContentPlatformDemandVideo> selectForRecommend(@Param("dt") String dt,
                                                        @Param("crowdSegment") String crowdSegment,
                                                        @Param("demandStrategy") String demandStrategy,
-                                                       @Param("limit") int limit);
+                                                       @Param("limit") int limit,
+                                                       @Param("excludeSelfTitle") boolean excludeSelfTitle);
+
+    /**
+     * 单源真分页:按 demand_strategy 取指定 crowd_segment 的候选视频,按 score DESC 分页。
+     * 信任离线侧 (crowd_segment, strategy) 内 video_id 已去重的约定,不做 Java 端去重。
+     * excludeSelfTitle=true 时过滤掉 video.title == demand_content_title 的行。
+     */
+    List<ContentPlatformDemandVideo> selectForRecommendPaged(@Param("dt") String dt,
+                                                             @Param("crowdSegment") String crowdSegment,
+                                                             @Param("demandStrategy") String demandStrategy,
+                                                             @Param("offset") int offset,
+                                                             @Param("pageSize") int pageSize,
+                                                             @Param("excludeSelfTitle") boolean excludeSelfTitle);
+
+    /**
+     * 单源真分页对应的总数查询,用于 Page.totalSize;过滤条件需与 selectForRecommendPaged 保持一致。
+     */
+    int countForRecommend(@Param("dt") String dt,
+                          @Param("crowdSegment") String crowdSegment,
+                          @Param("demandStrategy") String demandStrategy,
+                          @Param("excludeSelfTitle") boolean excludeSelfTitle);
 
     List<ContentPlatformDemandVideo> selectActiveVideos(@Param("dt") String dt);
 

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

@@ -637,18 +637,90 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
      * 单一来源分页:从对应候选池取最多 N 条,按候选池顺序分页。
      */
     private Page<VideoContentItemVO> getSingleSourcePage(VideoContentListParam param, ContentPlatformAccount user, String source) {
-        List<VideoContentItemVO> candidates;
-        if (SOURCE_PRIOR.equals(source)) {
-            candidates = fetchPriorCandidates(user, RECOMMEND_CANDIDATE_LIMIT);
-        } else if (SOURCE_POSTERIOR.equals(source)) {
-            candidates = fetchPosteriorCandidates(user, RECOMMEND_CANDIDATE_LIMIT);
-        } else {
-            candidates = fetchHotCandidates(param, user, RECOMMEND_CANDIDATE_LIMIT);
+        if (SOURCE_HOT.equals(source)) {
+            return getHotSourcePaged(param, user);
+        }
+        String demandStrategy = SOURCE_PRIOR.equals(source) ? DEMAND_STRATEGY_PRIOR : DEMAND_STRATEGY_POSTERIOR;
+        return getDemandSourcePaged(param, user, source, demandStrategy);
+    }
+
+    /**
+     * 单源 prior/posterior:SQL OFFSET/LIMIT 真分页,totalSize 来自 COUNT(*)。
+     * 信任离线侧 (crowd_segment, strategy) 内 video_id 已去重的约定。
+     */
+    private Page<VideoContentItemVO> getDemandSourcePaged(VideoContentListParam param, ContentPlatformAccount user,
+                                                          String source, String demandStrategy) {
+        int pageSize = param.getPageSize();
+        int pageNum = param.getPageNum();
+        Page<VideoContentItemVO> result = new Page<>(pageNum, pageSize);
+        String dt = demandVideoMapperExt.getMaxDt();
+        if (!StringUtils.hasText(dt)) {
+            result.setTotalSize(0);
+            result.setObjs(new ArrayList<>());
+            return result;
         }
-        for (VideoContentItemVO v : candidates) {
+        String crowdSegment = user.getChannel();
+        boolean excludeSelfTitle = DEMAND_STRATEGY_POSTERIOR.equals(demandStrategy);
+        int count = demandVideoMapperExt.countForRecommend(dt, crowdSegment, demandStrategy, excludeSelfTitle);
+        result.setTotalSize(count);
+        if (count == 0) {
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        int offset = (pageNum - 1) * pageSize;
+        if (offset >= count) {
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        List<ContentPlatformDemandVideo> rows = demandVideoMapperExt.selectForRecommendPaged(dt, crowdSegment, demandStrategy, offset, pageSize, excludeSelfTitle);
+        List<VideoContentItemVO> list = buildDemandVideoContentItemVOList(rows);
+        for (VideoContentItemVO v : list) {
             v.setSource(source);
         }
-        return paginateCandidates(param, candidates);
+        result.setObjs(list);
+        return result;
+    }
+
+    /**
+     * 单源 hot:复用原 planMapperExt.getVideoCount + getVideoList 真分页链路。
+     */
+    private Page<VideoContentItemVO> getHotSourcePaged(VideoContentListParam param, ContentPlatformAccount user) {
+        int pageSize = param.getPageSize();
+        int pageNum = param.getPageNum();
+        Page<VideoContentItemVO> result = new Page<>(pageNum, pageSize);
+        String dt = planMapperExt.getVideoMaxDt();
+        String datastatDt = planMapperExt.getVideoDatastatMaxDt();
+        if (!StringUtils.hasText(dt)) {
+            result.setTotalSize(0);
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        int count = planMapperExt.getVideoCount(param, dt, videoMinScore);
+        result.setTotalSize(count);
+        if (count == 0) {
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        int offset = (pageNum - 1) * pageSize;
+        if (offset >= count) {
+            result.setObjs(new ArrayList<>());
+            return result;
+        }
+        String sort = getVideoContentListSort(param.getSort());
+        String type = getVideoContentListType(param.getType());
+        String channel = getVideoContentListChannel(param.getSort(), user.getChannel());
+        String strategy = param.getSort() == 3 ? "recommend" : "normal";
+        List<ContentPlatformVideo> videoList = planMapperExt.getVideoList(param, dt, datastatDt, type, channel, strategy,
+                videoMinScore, offset, pageSize, sort);
+        List<VideoContentItemVO> list = buildVideoContentItemVOList(videoList, type, "sum", user.getChannel(), datastatDt);
+        if (list == null) {
+            list = new ArrayList<>();
+        }
+        for (VideoContentItemVO v : list) {
+            v.setSource(SOURCE_HOT);
+        }
+        result.setObjs(list);
+        return result;
     }
 
     /**
@@ -720,8 +792,9 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
             return new ArrayList<>();
         }
         String crowdSegment = user.getChannel();
+        boolean excludeSelfTitle = DEMAND_STRATEGY_POSTERIOR.equals(demandStrategy);
         // 超量拉取,再按 video_id 去重保留首条(即得分最高的一条)
-        List<ContentPlatformDemandVideo> rows = demandVideoMapperExt.selectForRecommend(dt, crowdSegment, demandStrategy, limit * 3);
+        List<ContentPlatformDemandVideo> rows = demandVideoMapperExt.selectForRecommend(dt, crowdSegment, demandStrategy, limit * 3, excludeSelfTitle);
         if (CollectionUtils.isEmpty(rows)) {
             return new ArrayList<>();
         }

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

@@ -90,6 +90,9 @@
         <if test="demandStrategy != null and demandStrategy != ''">
             AND demand_strategy = #{demandStrategy}
         </if>
+        <if test="excludeSelfTitle">
+            AND (title IS NULL OR demand_content_title IS NULL OR title &lt;&gt; demand_content_title)
+        </if>
         ORDER BY score DESC
         LIMIT #{limit}
     </select>
@@ -100,6 +103,44 @@
         WHERE dt = #{dt} AND status = 1
     </select>
 
+    <select id="selectForRecommendPaged" resultType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformDemandVideo">
+        SELECT id, dt, channel_name, crowd_segment, dimension, point_type, standard_element,
+               category_name, 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,
+               video_id, config_code, score, sim, rov,
+               match_text, title, cover, video, experiment_id, status, create_timestamp, update_timestamp
+        FROM content_platform_demand_video
+        WHERE dt = #{dt} AND status = 1
+        <if test="crowdSegment != null and crowdSegment != ''">
+            AND crowd_segment = #{crowdSegment}
+        </if>
+        <if test="demandStrategy != null and demandStrategy != ''">
+            AND demand_strategy = #{demandStrategy}
+        </if>
+        <if test="excludeSelfTitle">
+            AND (title IS NULL OR demand_content_title IS NULL OR title &lt;&gt; demand_content_title)
+        </if>
+        ORDER BY score DESC
+        LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <select id="countForRecommend" resultType="int">
+        SELECT COUNT(*)
+        FROM content_platform_demand_video
+        WHERE dt = #{dt} AND status = 1
+        <if test="crowdSegment != null and crowdSegment != ''">
+            AND crowd_segment = #{crowdSegment}
+        </if>
+        <if test="demandStrategy != null and demandStrategy != ''">
+            AND demand_strategy = #{demandStrategy}
+        </if>
+        <if test="excludeSelfTitle">
+            AND (title IS NULL OR demand_content_title IS NULL OR title &lt;&gt; demand_content_title)
+        </if>
+    </select>
+
     <update id="updateStatusByVideoId">
         UPDATE content_platform_demand_video
         SET status = #{status}, update_timestamp = #{updateTimestamp}