|
@@ -479,6 +479,8 @@ public class VideoSearchServiceImpl implements VideoSearchService {
|
|
|
|
|
|
|
|
// 从 Redis 获取视频基础信息并填充到结果中
|
|
// 从 Redis 获取视频基础信息并填充到结果中
|
|
|
enrichVideoDetail(result);
|
|
enrichVideoDetail(result);
|
|
|
|
|
+ // 从 Redis 获取视频解构(选题 + 高价值实质点)并填充
|
|
|
|
|
+ enrichDeconstruct(result);
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
@@ -501,6 +503,8 @@ public class VideoSearchServiceImpl implements VideoSearchService {
|
|
|
}
|
|
}
|
|
|
// 填充视频详情
|
|
// 填充视频详情
|
|
|
enrichVideoDetail(result);
|
|
enrichVideoDetail(result);
|
|
|
|
|
+ // 填充视频解构
|
|
|
|
|
+ enrichDeconstruct(result);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -814,4 +818,116 @@ public class VideoSearchServiceImpl implements VideoSearchService {
|
|
|
log.error("批量获取视频详情失败: {}", e.getMessage(), e);
|
|
log.error("批量获取视频详情失败: {}", e.getMessage(), e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 Redis 批量获取视频解构,以扁平 key 形式塞进 videoDetail.deconstruct 子对象
|
|
|
|
|
+ *
|
|
|
|
|
+ * 输出结构 (videoDetail.deconstruct):
|
|
|
|
|
+ * {
|
|
|
|
|
+ * "topic": "...",
|
|
|
|
|
+ * "灵感点": ["name1", ...],
|
|
|
|
|
+ * "灵感点-实质": [{"word":"...","score":0.9}, ...],
|
|
|
|
|
+ * "关键点": [...], "关键点-实质": [...],
|
|
|
|
|
+ * "目的点": [...], "目的点-实质": [...]
|
|
|
|
|
+ * }
|
|
|
|
|
+ *
|
|
|
|
|
+ * 数据来源:script/sync_decode_to_redis.py 同步,Key: recall:vid_decode:{videoId}
|
|
|
|
|
+ */
|
|
|
|
|
+ private void enrichDeconstruct(List<VideoMatchResult> results) {
|
|
|
|
|
+ if (results == null || results.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<String> keys = results.stream()
|
|
|
|
|
+ .map(r -> VectorConstants.VID_DECODE_KEY_PREFIX + r.getVideoId())
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ List<String> values = redisUtils.mGet(keys);
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < results.size(); i++) {
|
|
|
|
|
+ String json = values.get(i);
|
|
|
|
|
+ if (json == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Object> deconstructFlat = buildFlatDeconstruct(json);
|
|
|
|
|
+ if (deconstructFlat == null || deconstructFlat.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ VideoMatchResult r = results.get(i);
|
|
|
|
|
+ Map<String, Object> detail = r.getVideoDetail();
|
|
|
|
|
+ if (detail == null) {
|
|
|
|
|
+ detail = new HashMap<>();
|
|
|
|
|
+ r.setVideoDetail(detail);
|
|
|
|
|
+ }
|
|
|
|
|
+ detail.put("deconstruct", deconstructFlat);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("解析视频解构失败,videoId={}: {}", results.get(i).getVideoId(), e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("批量获取视频解构失败: {}", e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 把 Redis 原始解构 JSON 转成扁平结构 (topic + 3 组 类型/类型-实质 key)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Object> buildFlatDeconstruct(String json) {
|
|
|
|
|
+ JSONObject src = JSONObject.parseObject(json);
|
|
|
|
|
+ if (src == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> out = new LinkedHashMap<>();
|
|
|
|
|
+ String topic = src.getString("topic");
|
|
|
|
|
+ if (topic != null) {
|
|
|
|
|
+ out.put("topic", topic);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 按 type 分桶: name 列表 + essences flatten
|
|
|
|
|
+ Map<String, List<String>> nameByType = new LinkedHashMap<>();
|
|
|
|
|
+ Map<String, List<Map<String, Object>>> essenceByType = new LinkedHashMap<>();
|
|
|
|
|
+ for (String t : new String[]{"灵感点", "关键点", "目的点"}) {
|
|
|
|
|
+ nameByType.put(t, new ArrayList<>());
|
|
|
|
|
+ essenceByType.put(t, new ArrayList<>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ com.alibaba.fastjson.JSONArray points = src.getJSONArray("highValuePoints");
|
|
|
|
|
+ if (points != null) {
|
|
|
|
|
+ for (int j = 0; j < points.size(); j++) {
|
|
|
|
|
+ JSONObject p = points.getJSONObject(j);
|
|
|
|
|
+ if (p == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String type = p.getString("type");
|
|
|
|
|
+ if (type == null || !nameByType.containsKey(type)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String name = p.getString("name");
|
|
|
|
|
+ if (name != null && !name.isEmpty()) {
|
|
|
|
|
+ nameByType.get(type).add(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ com.alibaba.fastjson.JSONArray essences = p.getJSONArray("essences");
|
|
|
|
|
+ if (essences != null) {
|
|
|
|
|
+ for (int k = 0; k < essences.size(); k++) {
|
|
|
|
|
+ JSONObject e = essences.getJSONObject(k);
|
|
|
|
|
+ if (e == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("word", e.getString("word"));
|
|
|
|
|
+ item.put("score", e.getDouble("score"));
|
|
|
|
|
+ essenceByType.get(type).add(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (String t : new String[]{"灵感点", "关键点", "目的点"}) {
|
|
|
|
|
+ out.put(t, nameByType.get(t));
|
|
|
|
|
+ out.put(t + "-实质", essenceByType.get(t));
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|