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

Merge branch 'cooperation_video_candidate_pool_improved_lld_0509' of Server/growth-manager into master

liulidong 1 час назад
Родитель
Сommit
9cd816b916

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

@@ -694,10 +694,11 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         }
         List<VideoContentItemVO> list;
         if (SOURCE_PRIOR.equals(source)) {
-            // 粉丝喜欢 = 人群需求-场景 与 人群需求 严格 1:1 穿插,场景先出,prior 用完顺位补齐
+            // 粉丝喜欢 = priorScene(场景已看视频) 与 prior(人群需求·票圈推荐库) 池间交替,块大小 1~2 随机,起始池由 seed 决定;
+            // K=2 保证两池 top-2 必在前 4 条,池间交替避免连续同源,seed=userId^date 同用户同日稳定
             List<VideoContentItemVO> scene = fetchPriorSceneCandidates(param, user, DEMAND_CANDIDATE_LIMIT);
             List<VideoContentItemVO> prior = fetchPriorCandidates(param, user, DEMAND_CANDIDATE_LIMIT);
-            list = interleavePriorWithScene(scene, prior);
+            list = interleavePriorWithScene(scene, prior, user);
         } else {
             list = fetchPosteriorCandidates(param, user, DEMAND_CANDIDATE_LIMIT);
         }
@@ -708,23 +709,45 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     }
 
     /**
-     * priorScene 与 prior 严格 1:1 穿插 + 跨池 video_id / 标题 去重(priorScene 优先到达)。
-     * 一侧用完后,另一侧剩余按原顺序追加。
+     * priorScene 与 prior 池间交替混合输出:
+     *  - 块大小 1~2 随机(K=2),池间交替;起始池由 seed 决定
+     *  - seed = userId XOR LocalDate.toString().hashCode():同一用户同一天刷新顺序稳定,跨用户/跨日不同
+     *  - K=2 保证两池 top-2 必落在前 4 条(只是顺序按 seed 微调,不再机械 1:1)
+     *  - 跨池 video_id / 标题去重;一侧用完后,剩余按原顺序追加输出,不丢数据
      */
-    private List<VideoContentItemVO> interleavePriorWithScene(List<VideoContentItemVO> scene, List<VideoContentItemVO> prior) {
+    private List<VideoContentItemVO> interleavePriorWithScene(List<VideoContentItemVO> scene,
+                                                              List<VideoContentItemVO> prior,
+                                                              ContentPlatformAccount user) {
         Set<Long> seenIds = new HashSet<>();
         Set<String> seenTitles = new HashSet<>();
         List<VideoContentItemVO> out = new ArrayList<>();
         int si = 0, pi = 0;
+
+        long userSeed = (user == null || user.getId() == null) ? 0L : user.getId();
+        long seed = userSeed ^ LocalDate.now().toString().hashCode();
+        Random rng = new Random(seed);
+
+        boolean fromScene = rng.nextBoolean();
+
         while (si < scene.size() || pi < prior.size()) {
-            while (si < scene.size()) {
-                VideoContentItemVO v = scene.get(si++);
-                if (tryEmit(v, seenIds, seenTitles, out)) break;
-            }
-            while (pi < prior.size()) {
-                VideoContentItemVO v = prior.get(pi++);
-                if (tryEmit(v, seenIds, seenTitles, out)) break;
+            // 当前选中的池已空 → 强制切到另一池
+            if (fromScene && si >= scene.size()) fromScene = false;
+            else if (!fromScene && pi >= prior.size()) fromScene = true;
+
+            int blockSize = 1 + rng.nextInt(2); // 1 or 2
+            int emitted = 0;
+            if (fromScene) {
+                while (si < scene.size() && emitted < blockSize) {
+                    VideoContentItemVO v = scene.get(si++);
+                    if (tryEmit(v, seenIds, seenTitles, out)) emitted++;
+                }
+            } else {
+                while (pi < prior.size() && emitted < blockSize) {
+                    VideoContentItemVO v = prior.get(pi++);
+                    if (tryEmit(v, seenIds, seenTitles, out)) emitted++;
+                }
             }
+            fromScene = !fromScene;
         }
         return out;
     }