|
@@ -0,0 +1,419 @@
|
|
|
|
|
+package com.tzld.videoVector.service.recall.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.tzld.videoVector.api.VideoApiService;
|
|
|
|
|
+import com.tzld.videoVector.common.enums.Modality;
|
|
|
|
|
+import com.tzld.videoVector.dao.mapper.videoVector.VideoAiUnderstandingMapper;
|
|
|
|
|
+import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentMapper;
|
|
|
|
|
+import com.tzld.videoVector.model.entity.VideoDetail;
|
|
|
|
|
+import com.tzld.videoVector.model.param.MatchTopNVideoParam;
|
|
|
|
|
+import com.tzld.videoVector.model.param.recall.MatchByTextParam;
|
|
|
|
|
+import com.tzld.videoVector.model.param.recall.MatchByVideoIdParam;
|
|
|
|
|
+import com.tzld.videoVector.model.po.videoVector.VideoAiUnderstanding;
|
|
|
|
|
+import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
|
|
|
|
|
+import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample;
|
|
|
|
|
+import com.tzld.videoVector.model.vo.recall.AIUnderstandingVO;
|
|
|
|
|
+import com.tzld.videoVector.model.vo.recall.DeconstructPointsVO;
|
|
|
|
|
+import com.tzld.videoVector.model.vo.recall.RecallResultVO;
|
|
|
|
|
+import com.tzld.videoVector.model.vo.recall.VideoBasicVO;
|
|
|
|
|
+import com.tzld.videoVector.model.vo.recall.VideoMatchEnrichedVO;
|
|
|
|
|
+import com.tzld.videoVector.service.VideoSearchService;
|
|
|
|
|
+import com.tzld.videoVector.service.recall.VectorRecallTestService;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.HashSet;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 向量召回测试 Service 实现
|
|
|
|
|
+ * 核心职责: 调度现有 Service + 数据组装 + 模态感知 enrich
|
|
|
|
|
+ * 严禁 mock 任何业务数据。
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private VideoSearchService videoSearchService;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private VideoApiService videoApiService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DeconstructContentMapper deconstructContentMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired(required = false)
|
|
|
|
|
+ private VideoAiUnderstandingMapper videoAiUnderstandingMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private static final String PLACEHOLDER = "--";
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public VideoBasicVO getVideoDetail(Long videoId) {
|
|
|
|
|
+ if (videoId == null || videoId <= 0L) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Long, VideoDetail> map = videoApiService.getVideoDetail(Collections.singleton(videoId));
|
|
|
|
|
+ VideoDetail detail = map.get(videoId);
|
|
|
|
|
+ if (detail == null) {
|
|
|
|
|
+ log.info("getVideoDetail: video not found, videoId={}", videoId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ VideoBasicVO vo = new VideoBasicVO();
|
|
|
|
|
+ vo.setVideoId(videoId);
|
|
|
|
|
+ vo.setTitle(detail.getTitle());
|
|
|
|
|
+ vo.setVideoUrl(detail.getVideoPath());
|
|
|
|
|
+ vo.setCover(detail.getCover());
|
|
|
|
|
+ // playCount 长视频API无字段,真实占位 "--"
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public RecallResultVO matchByText(MatchByTextParam param) {
|
|
|
|
|
+ RecallResultVO empty = emptyResult();
|
|
|
|
|
+ if (param == null || !StringUtils.hasText(param.getQueryText())) {
|
|
|
|
|
+ log.warn("matchByText: queryText 为空");
|
|
|
|
|
+ return empty;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 调用现有召回 Service
|
|
|
|
|
+ MatchTopNVideoParam matchParam = new MatchTopNVideoParam();
|
|
|
|
|
+ matchParam.setQueryText(param.getQueryText());
|
|
|
|
|
+ matchParam.setConfigCode(param.getConfigCode());
|
|
|
|
|
+ matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
|
|
|
|
|
+
|
|
|
|
|
+ List<Object> rawMatches = videoSearchService.matchTopNVideo(matchParam);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(rawMatches)) {
|
|
|
|
|
+ return empty;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 解析并 enrich
|
|
|
|
|
+ return enrich(rawMatches);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public RecallResultVO matchByVideoId(MatchByVideoIdParam param) {
|
|
|
|
|
+ RecallResultVO empty = emptyResult();
|
|
|
|
|
+ if (param == null || param.getVideoId() == null || param.getVideoId() <= 0L) {
|
|
|
|
|
+ return empty;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转调现有 matchTopNVideo,以 channelContentId 字符串形式传入
|
|
|
|
|
+ MatchTopNVideoParam matchParam = new MatchTopNVideoParam();
|
|
|
|
|
+ matchParam.setChannelContentId(String.valueOf(param.getVideoId()));
|
|
|
|
|
+ matchParam.setConfigCode(param.getConfigCode());
|
|
|
|
|
+ matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
|
|
|
|
|
+
|
|
|
|
|
+ List<Object> rawMatches = videoSearchService.matchTopNVideo(matchParam);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(rawMatches)) {
|
|
|
|
|
+ return empty;
|
|
|
|
|
+ }
|
|
|
|
|
+ return enrich(rawMatches);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public DeconstructPointsVO getDeconstructPoints(Long videoId) {
|
|
|
|
|
+ if (videoId == null || videoId <= 0L) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 通过 channelContentId = String(videoId) 查 deconstruct_content
|
|
|
|
|
+ Map<String, DeconstructContent> map = queryDeconstructContent(
|
|
|
|
|
+ Collections.singletonList(String.valueOf(videoId))
|
|
|
|
|
+ );
|
|
|
|
|
+ DeconstructContent content = map.get(String.valueOf(videoId));
|
|
|
|
|
+ if (content == null) {
|
|
|
|
|
+ log.info("getDeconstructPoints: not found, videoId={}", videoId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DeconstructPointsVO vo = new DeconstructPointsVO();
|
|
|
|
|
+ vo.setId(videoId);
|
|
|
|
|
+ vo.setChannelContentId(content.getChannelContentId());
|
|
|
|
|
+ vo.setTaskId(content.getTaskId());
|
|
|
|
|
+ // 用 Number → intValue 兼容 Byte (a909594) / Short (master) PO 字段类型
|
|
|
|
|
+ vo.setContentType(content.getContentType() == null ? null : content.getContentType().intValue());
|
|
|
|
|
+ vo.setBizType(content.getBizType() == null ? null : content.getBizType().intValue());
|
|
|
|
|
+ vo.setStatus(content.getStatus() == null ? null : content.getStatus().intValue());
|
|
|
|
|
+ vo.setStatusDesc(getStatusDesc(content.getStatus()));
|
|
|
|
|
+ vo.setTitle(content.getTitle());
|
|
|
|
|
+ vo.setFailureReason(content.getFailureReason());
|
|
|
|
|
+
|
|
|
|
|
+ if (StringUtils.hasText(content.getResultJson())) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ vo.setRawResult(JSON.parseObject(content.getResultJson()));
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("getDeconstructPoints: parse resultJson fail, videoId={}, err={}",
|
|
|
|
|
+ videoId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public AIUnderstandingVO getAiUnderstanding(Long videoId) {
|
|
|
|
|
+ if (videoId == null || videoId <= 0L) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (videoAiUnderstandingMapper == null) {
|
|
|
|
|
+ // 表/Mapper 未就绪(同步Job尚未实施)
|
|
|
|
|
+ log.info("getAiUnderstanding: mapper not available, returning null. videoId={}", videoId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ VideoAiUnderstanding po = videoAiUnderstandingMapper.selectByVideoId(videoId);
|
|
|
|
|
+ if (po == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ AIUnderstandingVO vo = new AIUnderstandingVO();
|
|
|
|
|
+ vo.setVideoId(po.getVideoId());
|
|
|
|
|
+ vo.setContentTopic(po.getContentTopic());
|
|
|
|
|
+ vo.setVideoTheme(po.getVideoTheme());
|
|
|
|
|
+ vo.setVideoKeywords(po.getVideoKeywords());
|
|
|
|
|
+ vo.setVideoNarration(po.getVideoNarration());
|
|
|
|
|
+ vo.setDt(po.getDt());
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 表可能尚未创建(BadSqlGrammarException等),按真实"未就绪"返回 null
|
|
|
|
|
+ log.warn("getAiUnderstanding: query failed, table may not exist yet. videoId={}, err={}",
|
|
|
|
|
+ videoId, e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String getStatusDesc(Number status) {
|
|
|
|
|
+ if (status == null) return "未知";
|
|
|
|
|
+ switch (status.intValue()) {
|
|
|
|
|
+ case 0: return "待处理";
|
|
|
|
|
+ case 1: return "处理中";
|
|
|
|
|
+ case 2: return "成功";
|
|
|
|
|
+ case 3: return "失败";
|
|
|
|
|
+ default: return "未知";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 召回结果模态感知 enrich
|
|
|
|
|
+ *
|
|
|
|
|
+ * 流程:
|
|
|
|
|
+ * - 提取所有 id
|
|
|
|
|
+ * - 查 deconstruct_content WHERE channel_content_id IN (...) 拿 content_type
|
|
|
|
|
+ * - 视频走 VideoApiService 取权威详情
|
|
|
|
|
+ * - 素材/长文用 deconstruct_content 数据
|
|
|
|
|
+ * - 默认按视频处理(用户确认 content_type 缺省语义)
|
|
|
|
|
+ */
|
|
|
|
|
+ private RecallResultVO enrich(List<Object> rawMatches) {
|
|
|
|
|
+ // 解析原始 matches: [{configCode, videoId, score}, ...]
|
|
|
|
|
+ List<MatchItem> matches = new ArrayList<>(rawMatches.size());
|
|
|
|
|
+ for (Object obj : rawMatches) {
|
|
|
|
|
+ JSONObject jo = toJSONObject(obj);
|
|
|
|
|
+ if (jo == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ Long id = jo.getLong("videoId");
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ MatchItem mi = new MatchItem();
|
|
|
|
|
+ mi.id = id;
|
|
|
|
|
+ mi.configCode = jo.getString("configCode");
|
|
|
|
|
+ mi.score = jo.getDouble("score");
|
|
|
|
|
+ matches.add(mi);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (matches.isEmpty()) {
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取 id 列表(string形式,用于查 channel_content_id)
|
|
|
|
|
+ Set<Long> allIds = matches.stream().map(m -> m.id).collect(Collectors.toSet());
|
|
|
|
|
+ List<String> idStrings = allIds.stream().map(String::valueOf).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ // 查 deconstruct_content
|
|
|
|
|
+ Map<String, DeconstructContent> contentByCcid = queryDeconstructContent(idStrings);
|
|
|
|
|
+
|
|
|
|
|
+ // 收集需要走 VideoApiService 的视频id
|
|
|
|
|
+ Set<Long> videoIds = new HashSet<>();
|
|
|
|
|
+ Map<Long, Modality> modalityMap = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (MatchItem m : matches) {
|
|
|
|
|
+ DeconstructContent c = contentByCcid.get(String.valueOf(m.id));
|
|
|
|
|
+ Modality modality = (c == null) ? Modality.VIDEO : Modality.fromContentType(c.getContentType());
|
|
|
|
|
+ modalityMap.put(m.id, modality);
|
|
|
|
|
+ if (modality == Modality.VIDEO) {
|
|
|
|
|
+ videoIds.add(m.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量取视频详情
|
|
|
|
|
+ Map<Long, VideoDetail> videoDetails = videoIds.isEmpty()
|
|
|
|
|
+ ? Collections.emptyMap()
|
|
|
|
|
+ : videoApiService.getVideoDetail(videoIds);
|
|
|
|
|
+
|
|
|
|
|
+ // 组装 VO
|
|
|
|
|
+ List<VideoMatchEnrichedVO> items = new ArrayList<>(matches.size());
|
|
|
|
|
+ int videoCount = 0;
|
|
|
|
|
+ int materialCount = 0;
|
|
|
|
|
+ int articleCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (MatchItem m : matches) {
|
|
|
|
|
+ Modality modality = modalityMap.get(m.id);
|
|
|
|
|
+ VideoMatchEnrichedVO vo = new VideoMatchEnrichedVO();
|
|
|
|
|
+ vo.setId(m.id);
|
|
|
|
|
+ vo.setModality(modality);
|
|
|
|
|
+ vo.setConfigCode(m.configCode);
|
|
|
|
|
+ vo.setScore(m.score);
|
|
|
|
|
+ vo.setPlayCount(PLACEHOLDER);
|
|
|
|
|
+ vo.setExposure(PLACEHOLDER);
|
|
|
|
|
+ vo.setCtr(PLACEHOLDER);
|
|
|
|
|
+ vo.setReadCount(PLACEHOLDER);
|
|
|
|
|
+ vo.setRov(PLACEHOLDER);
|
|
|
|
|
+
|
|
|
|
|
+ DeconstructContent content = contentByCcid.get(String.valueOf(m.id));
|
|
|
|
|
+
|
|
|
|
|
+ switch (modality) {
|
|
|
|
|
+ case VIDEO:
|
|
|
|
|
+ VideoDetail vd = videoDetails.get(m.id);
|
|
|
|
|
+ if (vd != null) {
|
|
|
|
|
+ vo.setTitle(vd.getTitle());
|
|
|
|
|
+ vo.setVideoUrl(vd.getVideoPath());
|
|
|
|
|
+ vo.setCover(vd.getCover());
|
|
|
|
|
+ } else if (content != null) {
|
|
|
|
|
+ // 长视频API查不到,降级用本地 deconstruct_content
|
|
|
|
|
+ vo.setTitle(content.getTitle());
|
|
|
|
|
+ vo.setVideoUrl(content.getVideoUrl());
|
|
|
|
|
+ }
|
|
|
|
|
+ videoCount++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case MATERIAL:
|
|
|
|
|
+ if (content != null) {
|
|
|
|
|
+ vo.setTitle(content.getTitle());
|
|
|
|
|
+ vo.setImageList(parseImages(content.getImages()));
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(vo.getImageList())) {
|
|
|
|
|
+ vo.setCover(vo.getImageList().get(0));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ materialCount++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case ARTICLE:
|
|
|
|
|
+ if (content != null) {
|
|
|
|
|
+ vo.setTitle(content.getTitle());
|
|
|
|
|
+ vo.setBodyText(content.getBodyText());
|
|
|
|
|
+ }
|
|
|
|
|
+ articleCount++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ videoCount++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ items.add(vo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ RecallResultVO result = new RecallResultVO();
|
|
|
|
|
+ result.setItems(items);
|
|
|
|
|
+ result.setVideoCount(videoCount);
|
|
|
|
|
+ result.setMaterialCount(materialCount);
|
|
|
|
|
+ result.setArticleCount(articleCount);
|
|
|
|
|
+ result.setTotal(items.size());
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按 channelContentId 批量查 deconstruct_content
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, DeconstructContent> queryDeconstructContent(List<String> channelContentIds) {
|
|
|
|
|
+ if (CollectionUtils.isEmpty(channelContentIds)) {
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ DeconstructContentExample example = new DeconstructContentExample();
|
|
|
|
|
+ example.createCriteria().andChannelContentIdIn(channelContentIds);
|
|
|
|
|
+ List<DeconstructContent> list = deconstructContentMapper.selectByExample(example);
|
|
|
|
|
+ // channel_content_id 可能重复(同一内容多次解构),保留最新一条
|
|
|
|
|
+ Map<String, DeconstructContent> map = new HashMap<>();
|
|
|
|
|
+ for (DeconstructContent c : list) {
|
|
|
|
|
+ String ccid = c.getChannelContentId();
|
|
|
|
|
+ if (ccid == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ DeconstructContent prev = map.get(ccid);
|
|
|
|
|
+ if (prev == null || (c.getId() != null && (prev.getId() == null || c.getId() > prev.getId()))) {
|
|
|
|
|
+ map.put(ccid, c);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("queryDeconstructContent error: {}", e.getMessage(), e);
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JSONObject toJSONObject(Object obj) {
|
|
|
|
|
+ if (obj == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (obj instanceof JSONObject) {
|
|
|
|
|
+ return (JSONObject) obj;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parseObject(JSON.toJSONString(obj));
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("toJSONObject parse fail: {}", e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<String> parseImages(String imagesJson) {
|
|
|
|
|
+ if (!StringUtils.hasText(imagesJson)) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ JSONArray arr = JSON.parseArray(imagesJson);
|
|
|
|
|
+ if (arr == null) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> result = new ArrayList<>(arr.size());
|
|
|
|
|
+ for (int i = 0; i < arr.size(); i++) {
|
|
|
|
|
+ String s = arr.getString(i);
|
|
|
|
|
+ if (StringUtils.hasText(s)) {
|
|
|
|
|
+ result.add(s);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("parseImages fail, json={}, err={}", imagesJson, e.getMessage());
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private RecallResultVO emptyResult() {
|
|
|
|
|
+ RecallResultVO vo = new RecallResultVO();
|
|
|
|
|
+ vo.setItems(Collections.emptyList());
|
|
|
|
|
+ vo.setVideoCount(0);
|
|
|
|
|
+ vo.setMaterialCount(0);
|
|
|
|
|
+ vo.setArticleCount(0);
|
|
|
|
|
+ vo.setTotal(0);
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析后的单条 match
|
|
|
|
|
+ */
|
|
|
|
|
+ private static class MatchItem {
|
|
|
|
|
+ Long id;
|
|
|
|
|
+ String configCode;
|
|
|
|
|
+ Double score;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|