|
|
@@ -654,8 +654,6 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
|
|
|
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));
|
|
|
- /** 搜索向量召回 topN:取较大值,确保与 demand 白名单交集后仍有足量结果。 */
|
|
|
- private static final int SEARCH_VECTOR_TOP_N = 500;
|
|
|
/**
|
|
|
* 合作类渠道:demand.crowd_segment 语义=合作方代码(yy/szhx/cdjh/...),与登录账号 ContentPlatformAccount.channel 同义,可作为过滤条件。
|
|
|
* 其余渠道(投流-稳定/服务号投流):demand.crowd_segment 语义=人群分组标签(R50*xx/回流xx),与登录账号合作方无关,绝不可用 user.channel 过滤。
|
|
|
@@ -2025,18 +2023,19 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 搜索:在当前入口的 demand 白名单(prior+posterior 池,rov>=0.02)内做 title LIKE 关键字命中 + 向量召回,并集去重后统一排序。
|
|
|
+ * 搜索:在当前入口的 demand 白名单(rov >= 0.02)内做 title LIKE 关键字命中,按 rov DESC 排序。
|
|
|
* 门控:
|
|
|
* - 用户身份 type ∈ {2 自营, 3 代理},否则返回空
|
|
|
* - 渠道 ∈ {小程序投流-稳定, 公众号投流-稳定},否则返回空
|
|
|
* - 小程序投流必填 crowdPackage,公众号投流必填 ghName,否则返回空
|
|
|
* 流程:
|
|
|
* 1. demand 白名单(channel + crowdSegment/channelLevel3 + demand_strategy IN (人群需求,优质相似) + rov>=0.02)
|
|
|
- * 2. 同 video_id 取 sceneSumRov 最大的代表行
|
|
|
- * 3. Pass1: 池内 title.toLowerCase().contains(query) 关键字命中,score=null
|
|
|
- * 4. Pass2: 向量召回 topN=500,池内交集;同 video_id 已被关键字命中则用向量 score 升级
|
|
|
- * 5. 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
|
|
|
- * 6. 一次性算好全量,按 pageNum/pageSize 切片返回(翻页不再调向量服务)
|
|
|
+ * 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());
|
|
|
@@ -2056,6 +2055,9 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
|
|
|
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;
|
|
|
|
|
|
@@ -2063,73 +2065,42 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
|
|
|
dt, channelName, isXcx ? crowdPackage : null, isXcx ? null : ghName);
|
|
|
if (CollectionUtils.isEmpty(whitelist)) return empty;
|
|
|
|
|
|
- // video_id 去重:取 sceneSumRov 最大的代表行
|
|
|
- Map<Long, ContentPlatformDemandVideo> bestPerVideo = new HashMap<>();
|
|
|
+ // SQL 已 ORDER BY rov DESC,putIfAbsent 即拿到 max rov 代表行(设计意图:与排序键一致)
|
|
|
+ LinkedHashMap<Long, ContentPlatformDemandVideo> bestPerVideo = new LinkedHashMap<>();
|
|
|
for (ContentPlatformDemandVideo r : whitelist) {
|
|
|
if (r.getVideoId() == null) continue;
|
|
|
- ContentPlatformDemandVideo cur = bestPerVideo.get(r.getVideoId());
|
|
|
- double rSum = r.getSceneSumRov() == null ? 0d : r.getSceneSumRov();
|
|
|
- double curSum = cur == null ? -1d : (cur.getSceneSumRov() == null ? 0d : cur.getSceneSumRov());
|
|
|
- if (cur == null || rSum > curSum) {
|
|
|
- bestPerVideo.put(r.getVideoId(), r);
|
|
|
- }
|
|
|
+ bestPerVideo.putIfAbsent(r.getVideoId(), r);
|
|
|
}
|
|
|
|
|
|
- // Pass 1: title LIKE 关键字命中(池内 substring,大小写不敏感),score=null 兜底 sceneSumRov
|
|
|
- String kw = param.getTitle().toLowerCase();
|
|
|
- Map<Long, VideoContentItemVO> byVid = new LinkedHashMap<>();
|
|
|
+ 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);
|
|
|
- byVid.put(demand.getVideoId(), vo);
|
|
|
- }
|
|
|
-
|
|
|
- // Pass 2: 向量召回,池内交集;若已被 title 命中,用向量 score 升级以提升排序
|
|
|
- JSONObject vectorData = managerApiService.recallVideoWithScore(param.getTitle(), SEARCH_VECTOR_TOP_N);
|
|
|
- JSONArray items = vectorData == null ? null : vectorData.getJSONArray("items");
|
|
|
- if (items != null) {
|
|
|
- for (int i = 0; i < items.size(); i++) {
|
|
|
- JSONObject item = items.getJSONObject(i);
|
|
|
- Long videoId = item.getLong("videoId");
|
|
|
- if (videoId == null || !bestPerVideo.containsKey(videoId)) continue;
|
|
|
- Double vScore = item.getDouble("score");
|
|
|
- VideoContentItemVO existing = byVid.get(videoId);
|
|
|
- if (existing != null) {
|
|
|
- existing.setSearchSource("both");
|
|
|
- existing.setScore(vScore);
|
|
|
- } else {
|
|
|
- ContentPlatformDemandVideo demand = bestPerVideo.get(videoId);
|
|
|
- VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
|
|
|
- vo.setSearchSource("vector");
|
|
|
- vo.setScore(vScore);
|
|
|
- byVid.put(videoId, vo);
|
|
|
- }
|
|
|
- }
|
|
|
+ hits.add(vo);
|
|
|
}
|
|
|
|
|
|
- if (byVid.isEmpty()) return empty;
|
|
|
- List<VideoContentItemVO> hits = new ArrayList<>(byVid.values());
|
|
|
+ if (hits.isEmpty()) return empty;
|
|
|
|
|
|
- // 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
|
|
|
+ // rov DESC,次级 total_rov DESC 作稳定兜底
|
|
|
hits.sort((a, b) -> {
|
|
|
- double sa = a.getScore() == null ? 0d : a.getScore();
|
|
|
- double sb = b.getScore() == null ? 0d : b.getScore();
|
|
|
- int c = Double.compare(sb, sa);
|
|
|
+ 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 ra = a.getSceneSumRov() == null ? 0d : a.getSceneSumRov();
|
|
|
- double rb = b.getSceneSumRov() == null ? 0d : b.getSceneSumRov();
|
|
|
- return Double.compare(rb, ra);
|
|
|
+ double ta = a.getTotalRov() == null ? 0d : a.getTotalRov();
|
|
|
+ double tb = b.getTotalRov() == null ? 0d : b.getTotalRov();
|
|
|
+ return Double.compare(tb, ta);
|
|
|
});
|
|
|
|
|
|
- Page<VideoContentItemVO> page = new Page<>(param.getPageNum(), param.getPageSize());
|
|
|
- page.setTotalSize(hits.size());
|
|
|
- // 全量在后端计算好,按 pageNum/pageSize 切片(单次召回,翻页不再调向量服务)
|
|
|
+ int pageNum = Math.max(1, param.getPageNum());
|
|
|
int pageSize = param.getPageSize() > 0 ? param.getPageSize() : 10;
|
|
|
- int from = Math.min((param.getPageNum() - 1) * pageSize, hits.size());
|
|
|
- int to = Math.min(param.getPageNum() * pageSize, hits.size());
|
|
|
+ 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;
|
|
|
}
|