Ver código fonte

特征点泛化 精准匹配、向量匹配 同时存在

wangyunpeng 1 dia atrás
pai
commit
39ad633242

+ 96 - 0
core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/VideoArticleMatchResultMapper.java

@@ -0,0 +1,96 @@
+package com.tzld.videoVector.dao.mapper.pgVector;
+
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResultExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface VideoArticleMatchResultMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    long countByExample(VideoArticleMatchResultExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int deleteByExample(VideoArticleMatchResultExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int insert(VideoArticleMatchResult record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int insertSelective(VideoArticleMatchResult record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    List<VideoArticleMatchResult> selectByExample(VideoArticleMatchResultExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    VideoArticleMatchResult selectByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int updateByExampleSelective(@Param("record") VideoArticleMatchResult record, @Param("example") VideoArticleMatchResultExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int updateByExample(@Param("record") VideoArticleMatchResult record, @Param("example") VideoArticleMatchResultExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int updateByPrimaryKeySelective(VideoArticleMatchResult record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    int updateByPrimaryKey(VideoArticleMatchResult record);
+}

+ 27 - 0
core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ext/VideoArticleMatchResultMapperExt.java

@@ -0,0 +1,27 @@
+package com.tzld.videoVector.dao.mapper.pgVector.ext;
+
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 视频→长文匹配结果 扩展 Mapper
+ */
+public interface VideoArticleMatchResultMapperExt {
+
+    /**
+     * 批量插入匹配结果
+     */
+    int batchInsert(@Param("list") List<VideoArticleMatchResult> list);
+
+    /**
+     * 按日期删除匹配结果
+     */
+    int deleteByDt(@Param("dt") String dt);
+
+    /**
+     * 查询表中最大的 dt 值
+     */
+    String selectMaxDt();
+}

+ 12 - 1
core/src/main/java/com/tzld/videoVector/job/ChannelDemandMatchJob.java

@@ -99,6 +99,11 @@ public class ChannelDemandMatchJob {
      */
     private static final String MATCH_METHOD_RECOMMEND_LIB = "视频库_解构特征_向量相似匹配";
 
+    /**
+     * 匹配手段:视频库_解构特征点_精准匹配(Library API 精准匹配)
+     */
+    private static final String MATCH_METHOD_RECOMMEND_LIB_EXACT = "视频库_解构特征点_精准匹配";
+
     /**
      * 需求来源:优质相似(对应 demand_strategy 取值)
      */
@@ -558,7 +563,6 @@ public class ChannelDemandMatchJob {
             List<ChannelDemandMatchResult> rows = new ArrayList<>();
             for (String configCode : configCodes) {
                 rows.addAll(doRecall(demand, demand.getStandardElement(), configCode, topN / configCodes.size()));
-
             }
             allBatchRows.addAll(rows);
         }
@@ -566,8 +570,14 @@ public class ChannelDemandMatchJob {
         // 策略三-泛化:特征点泛化 → 使用 Library API 召回
         if ("特征点泛化".equals(demand.getDemandType()) && hasValidValue(demand.getMatchGeneralizedPointType())
                 && hasValidValue(demand.getMatchGeneralizedElement())) {
+            // 精准匹配
             List<ChannelDemandMatchResult> rows = doLibraryRecall(demand, topN);
             allBatchRows.addAll(rows);
+            // 向量匹配
+            List<String> configCodes = POINT_TYPE_CONFIG_CODE_MAP.getOrDefault(demand.getPointType(), Arrays.asList("VIDEO_TOPIC"));
+            for (String configCode : configCodes) {
+                allBatchRows.addAll(doRecall(demand, demand.getMatchGeneralizedElement(), configCode, topN / configCodes.size()));
+            }
         }
 
         if (allBatchRows.isEmpty()) {
@@ -775,6 +785,7 @@ public class ChannelDemandMatchJob {
             Long postId = Long.parseLong(cp.getPostId());
 
             ChannelDemandMatchResult row = copyDemandFields(demand);
+            row.setMatchMethod(MATCH_METHOD_RECOMMEND_LIB_EXACT);
             row.setMatchVideoId(postId);
             row.setMatchConfigCode("LIBRARY_TOPIC_ELEMENT");
             row.setMatchRov(cp.getRov());

+ 749 - 0
core/src/main/java/com/tzld/videoVector/job/VideoArticleMatchJob.java

@@ -0,0 +1,749 @@
+package com.tzld.videoVector.job;
+
+import com.alibaba.fastjson.JSON;
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.tzld.videoVector.api.VideoApiService;
+import com.tzld.videoVector.common.constant.VectorConstants;
+import com.tzld.videoVector.dao.mapper.pgVector.ChannelDemandMatchResultMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoArticleMatchResultMapperExt;
+import com.tzld.videoVector.model.entity.VideoDetail;
+import com.tzld.videoVector.model.param.recall.BatchByTextParam;
+import com.tzld.videoVector.model.param.recall.RankingSpec;
+import com.tzld.videoVector.model.po.pgVector.ChannelDemandMatchResult;
+import com.tzld.videoVector.model.po.pgVector.ChannelDemandMatchResultExample;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+import com.tzld.videoVector.model.vo.recall.RecallResultVO;
+import com.tzld.videoVector.service.recall.VectorRecallTestService;
+import com.tzld.videoVector.util.RedisUtils;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+ * 视频标题→长文匹配任务。
+ *
+ * <p>整体流程:
+ * <ol>
+ *   <li>解析参数 & 读取 Apollo 配置</li>
+ *   <li>查询 channel_demand_match_result 中指定渠道的匹配记录</li>
+ *   <li>通过 VideoApiService 批量获取视频标题</li>
+ *   <li>对每个视频标题调用 batchByText 进行长文向量召回</li>
+ *   <li>1v1 去重:每视频取最佳文章 → 每文章保留最佳视频</li>
+ *   <li>清理旧数据 + 批量写入 video_article_match_result 表</li>
+ *   <li>按 channelLevel3 分组统计并输出日志</li>
+ * </ol>
+ *
+ * <p>XXL-Job handler: videoArticleMatchJob
+ * <p>参数格式:dt=20260615 或 20260615(可选,不传默认取前一天)
+ */
+@Slf4j
+@Component
+public class VideoArticleMatchJob {
+
+    // =====================================================
+    // 依赖注入
+    // =====================================================
+
+    @Resource
+    private ChannelDemandMatchResultMapper channelDemandMatchResultMapper;
+
+    @Resource
+    private VideoArticleMatchResultMapperExt videoArticleMatchResultMapperExt;
+
+    @Resource
+    private VectorRecallTestService vectorRecallTestService;
+
+    @Resource
+    private VideoApiService videoApiService;
+
+    @Resource
+    private RedisUtils redisUtils;
+
+    // =====================================================
+    // Apollo 配置 & 常量
+    // =====================================================
+
+    /** 渠道名 */
+    @Value("${video.article.match.channel.name:公众号投流-稳定}")
+    private String channelName;
+
+    /**
+     * 目标账号列表(对应 channel_demand_match_result.channel_level3)。
+     * Apollo 值格式: ["趣味生活方式","美好时光阅读汇", ...]
+     */
+    @ApolloJsonValue("${video.article.match.accounts:[\"趣味生活方式\",\n" +
+            "\"美好时光阅读汇\",\n" +
+            "\"生活慢时光\",\n" +
+            "\"趣味生活漫谈\",\n" +
+            "\"趣味生活漫时光\",\n" +
+            "\"史趣探秘\",\n" +
+            "\"银发生活畅谈\",\n" +
+            "\"银发乐活驿站\",\n" +
+            "\"时光趣味生活\",\n" +
+            "\"历史长河流淌\",\n" +
+            "\"小阳看天下\",\n" +
+            "\"票圈精彩\",\n" +
+            "\"生活知识指南\",\n" +
+            "\"暖心一隅\",\n" +
+            "\"生活科学指南\",\n" +
+            "\"青史铁事漫谈\",\n" +
+            "\"奇观妙技有乾坤\",\n" +
+            "\"银发乐活享站\",\n" +
+            "\"怀念旧时光故事\",\n" +
+            "\"银发康健阁\"]}")
+    private List<String> targetAccounts;
+
+    /** 文章召回的向量维度 */
+    private static final List<String> ARTICLE_CONFIG_CODES = Arrays.asList(
+            VectorConstants.ARTICLE_TITLE_CONFIG_CODE,
+            VectorConstants.ARTICLE_SUMMARY_CONFIG_CODE
+    );
+
+    /** 文章匹配每次返回条数 */
+    private static final int ARTICLE_DISPLAY_K = 10;
+
+    /** 视频标题 Redis 缓存 key 前缀 */
+    private static final String VIDEO_TITLE_CACHE_PREFIX = "video_article_match:video_title:";
+
+    /** 视频标题缓存过期时间(秒),默认 7 天 */
+    private static final long VIDEO_TITLE_CACHE_EXPIRE = 7 * 24 * 60 * 60;
+
+    /** 长文匹配并发线程数 */
+    @Value("${video.article.match.thread-pool-size:10}")
+    private int matchThreadPoolSize;
+
+    private ExecutorService matchExecutor;
+
+    @PostConstruct
+    public void initMatchExecutor() {
+        this.matchExecutor = new ThreadPoolExecutor(
+                matchThreadPoolSize, matchThreadPoolSize,
+                60L, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(),
+                r -> {
+                    Thread t = new Thread(r, "video-article-match-");
+                    t.setDaemon(true);
+                    return t;
+                },
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+    }
+
+    // =====================================================
+    // XxlJob 入口
+    // =====================================================
+
+    /**
+     * 视频标题→长文匹配任务。
+     *
+     * @param param 可选 dt=20260615,不传默认取前一天
+     */
+    @XxlJob("videoArticleMatchJob")
+    public ReturnT<String> videoArticleMatchJob(String param) {
+        log.info("========== 开始视频→长文匹配任务 ==========");
+
+        try {
+            // 1. 解析日期参数
+            String dt = parseDt(param);
+            log.info("数据日期: dt={}, channelName={}, targetAccounts={}", dt, channelName, targetAccounts);
+
+            // 1.1 校验配置:targetAccounts 为空则跳过
+            if (CollectionUtils.isEmpty(targetAccounts)) {
+                log.warn("targetAccounts 为空(Apollo 配置 video.article.match.accounts 未设置或为空),跳过执行");
+                return ReturnT.SUCCESS;
+            }
+
+            // 2. 查询渠道需求匹配记录
+            List<ChannelDemandMatchResult> records = fetchDemandRecords(dt);
+            if (CollectionUtils.isEmpty(records)) {
+                log.info("无匹配记录,任务结束");
+                return ReturnT.SUCCESS;
+            }
+
+            // 3. 批量获取视频标题
+            Map<Long, String> videoTitleMap = fetchVideoTitles(records);
+
+            // 4. 视频标题 → 长文向量召回
+            Map<Long, List<ArticleMatchItem>> videoArticleMatches = matchArticlesByTitles(videoTitleMap);
+
+            // 5. 1v1 去重配对
+            Map<Long, ArticleMatchItem> finalPairs = dedupOneToOne(videoArticleMatches);
+
+            // 6. 构建结果对象 → 清理旧数据 → 批量写入
+            saveResults(dt, records, videoTitleMap, finalPairs);
+
+            // 7. 分组统计日志
+            logSummary(dt, records, videoTitleMap, finalPairs);
+
+            log.info("========== 视频→长文匹配任务 完成 ==========");
+            return ReturnT.SUCCESS;
+
+        } catch (Exception e) {
+            log.error("视频→长文匹配任务执行失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务执行失败: " + e.getMessage());
+        }
+    }
+
+    // =====================================================
+    // 步骤 2: 查询渠道需求匹配记录
+    // =====================================================
+
+    /**
+     * 查询 channel_demand_match_result 表中符合条件的需求匹配记录。
+     *
+     * <p>过滤条件:
+     * <ul>
+     *   <li>channel_name = Apollo 配置值</li>
+     *   <li>dt = 指定日期</li>
+     *   <li>channel_level3 IS NOT NULL & IN (Apollo 目标账号列表)</li>
+     *   <li>match_video_id IS NOT NULL</li>
+     * </ul>
+     *
+     * @param dt 数据日期 (yyyyMMdd)
+     * @return 有效匹配记录列表(已过滤 match_video_id 为空的记录)
+     */
+    private List<ChannelDemandMatchResult> fetchDemandRecords(String dt) {
+        ChannelDemandMatchResultExample example = new ChannelDemandMatchResultExample();
+        example.createCriteria()
+                .andChannelNameEqualTo(channelName)
+                .andDtEqualTo(dt)
+                .andChannelLevel3In(targetAccounts);
+
+        List<ChannelDemandMatchResult> records = channelDemandMatchResultMapper.selectByExample(example);
+
+        // 过滤 matchVideoId 为空的无效记录
+        records = records.stream()
+                .filter(r -> r.getMatchVideoId() != null)
+                .collect(Collectors.toList());
+
+        log.info("查询渠道需求匹配记录: 共计 {} 条(已过滤 matchVideoId 为空)", records.size());
+        return records;
+    }
+
+    // =====================================================
+    // 步骤 3: 批量获取视频标题
+    // =====================================================
+
+    /**
+     * 提取所有唯一视频 ID,优先从 Redis 缓存读取标题,未命中则调用视频详情 API。
+     *
+     * <p>缓存 key: video_article_match:video_title:{videoId}
+     * <p>缓存过期: 7 天
+     *
+     * @param records 需求匹配记录列表
+     * @return videoId → title 映射(LinkedHashMap 保持插入顺序)
+     */
+    private Map<Long, String> fetchVideoTitles(List<ChannelDemandMatchResult> records) {
+        // 提取唯一视频 ID
+        Set<Long> uniqueVideoIds = records.stream()
+                .map(ChannelDemandMatchResult::getMatchVideoId)
+                .collect(Collectors.toSet());
+        log.info("唯一视频 ID 共 {} 个", uniqueVideoIds.size());
+
+        Map<Long, String> videoTitleMap = new LinkedHashMap<>();
+
+        // =================================
+        // 第一阶段:尝试从 Redis 缓存读取
+        // =================================
+        List<String> cacheKeys = uniqueVideoIds.stream()
+                .map(id -> VIDEO_TITLE_CACHE_PREFIX + id)
+                .collect(Collectors.toList());
+        List<String> cachedValues = redisUtils.mGet(cacheKeys);
+
+        Set<Long> cacheMissIds = new LinkedHashSet<>();
+        int cacheHitCount = 0;
+        int i = 0;
+        for (Long videoId : uniqueVideoIds) {
+            String cached = (i < cachedValues.size()) ? cachedValues.get(i) : null;
+            if (StringUtils.hasText(cached)) {
+                videoTitleMap.put(videoId, cached);
+                cacheHitCount++;
+            } else {
+                cacheMissIds.add(videoId);
+            }
+            i++;
+        }
+        log.info("Redis 缓存: 命中 {} 个 / 未命中 {} 个", cacheHitCount, cacheMissIds.size());
+
+        // =================================
+        // 第二阶段:缓存未命中 → 调用 API
+        // =================================
+        if (!cacheMissIds.isEmpty()) {
+            log.info("开始调用视频详情 API,共 {} 个视频...", cacheMissIds.size());
+            Map<Long, VideoDetail> videoDetailMap = videoApiService.getVideoDetail(cacheMissIds);
+            log.info("API 返回 {} 个视频详情", videoDetailMap.size());
+
+            // 构建需要回写缓存的 key-value
+            Map<String, String> toCache = new LinkedHashMap<>();
+            for (Map.Entry<Long, VideoDetail> entry : videoDetailMap.entrySet()) {
+                Long videoId = entry.getKey();
+                String title = entry.getValue().getTitle();
+                if (StringUtils.hasText(title)) {
+                    String trimmedTitle = title.trim();
+                    videoTitleMap.put(videoId, trimmedTitle);
+                    toCache.put(VIDEO_TITLE_CACHE_PREFIX + videoId, trimmedTitle);
+                }
+            }
+
+            // 批量回写 Redis 缓存
+            if (!toCache.isEmpty()) {
+                redisUtils.batchSetWithExpire(toCache, VIDEO_TITLE_CACHE_EXPIRE);
+                log.info("Redis 回写 {} 条视频标题缓存", toCache.size());
+            }
+        }
+
+        log.info("有效标题的视频 {} 个 / 唯一视频 {} 个", videoTitleMap.size(), uniqueVideoIds.size());
+        return videoTitleMap;
+    }
+
+    // =====================================================
+    // 步骤 4: 视频标题 → 长文向量召回
+    // =====================================================
+
+    /**
+     * 对每个视频标题调用 batchByText 进行长文相似度召回。
+     *
+     * <p>使用线程池并发执行,单条失败不影响整体流程。
+     * 每个标题使用 configCodes=[ARTICLE_TITLE, ARTICLE_SUMMARY] 进行并行 ANN 查询,
+     * 结果只保留 modality=ARTICLE 的条目,按 score 降序排列。
+     *
+     * @param videoTitleMap videoId → title 映射
+     * @return videoId → 文章匹配列表 映射(按 score 降序)
+     */
+    private Map<Long, List<ArticleMatchItem>> matchArticlesByTitles(Map<Long, String> videoTitleMap) {
+        ConcurrentHashMap<Long, List<ArticleMatchItem>> resultMap = new ConcurrentHashMap<>();
+        RankingSpec ranking = buildRankingSpec();
+
+        int totalVideos = videoTitleMap.size();
+        AtomicInteger processed = new AtomicInteger(0);
+        AtomicInteger matchedCount = new AtomicInteger(0);
+
+        // 构建并发任务
+        List<CompletableFuture<Void>> futures = new ArrayList<>(totalVideos);
+        for (Map.Entry<Long, String> entry : videoTitleMap.entrySet()) {
+            Long videoId = entry.getKey();
+            String title = entry.getValue();
+
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    BatchByTextParam batchParam = buildBatchByTextParam(title, ranking);
+                    RecallResultVO recallResult = vectorRecallTestService.batchByText(batchParam);
+                    List<ArticleMatchItem> articles = extractArticleItems(recallResult);
+
+                    if (!articles.isEmpty()) {
+                        resultMap.put(videoId, articles);
+                        matchedCount.incrementAndGet();
+                    }
+                } catch (Exception e) {
+                    log.error("视频 {} (标题: {}) 长文匹配失败: {}", videoId, title, e.getMessage());
+                } finally {
+                    int done = processed.incrementAndGet();
+                    // 每 10 条或每 50 条倍数的进度输出一次
+                    if (done % 10 == 0 || done == totalVideos) {
+                        log.info("长文匹配进度: {}/{} 视频已处理, {} 个命中",
+                                done, totalVideos, matchedCount.get());
+                    }
+                }
+            }, matchExecutor);
+
+            futures.add(future);
+        }
+
+        // 等待所有任务完成(每个 future 内部已 catch 异常,不会失败)
+        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+
+        // 二次校验:确保所有任务都已执行完毕
+        int finalProcessed = processed.get();
+        if (finalProcessed != totalVideos) {
+            log.warn("长文匹配未完全完成: 预期 {} 个, 实际完成 {} 个", totalVideos, finalProcessed);
+        }
+
+        // 转换回 LinkedHashMap 保持顺序
+        Map<Long, List<ArticleMatchItem>> result = new LinkedHashMap<>(resultMap);
+        log.info("长文匹配完成: {}/{} 个视频命中长文", matchedCount.get(), totalVideos);
+        return result;
+    }
+
+    /**
+     * 从 batchByText 返回结果中提取 Article 模态的匹配条目。
+     *
+     * <p>过滤条件: modality=ARTICLE 且 articleId 非空。
+     * 结果按 score 降序排列。
+     */
+    private List<ArticleMatchItem> extractArticleItems(RecallResultVO result) {
+        if (result == null || CollectionUtils.isEmpty(result.getItems())) {
+            return Collections.emptyList();
+        }
+
+        return result.getItems().stream()
+                .filter(item -> item.getModality() != null
+                        && "ARTICLE".equals(item.getModality().name()))
+                .filter(item -> StringUtils.hasText(item.getArticleId()))
+                .map(item -> {
+                    ArticleMatchItem am = new ArticleMatchItem();
+                    am.articleId = item.getArticleId();
+                    am.articleTitle = item.getTitle();
+                    am.score = item.getScore() != null ? item.getScore() : 0.0;
+                    am.configCode = item.getConfigCode();
+                    return am;
+                })
+                .sorted(Comparator.comparingDouble((ArticleMatchItem a) -> a.score).reversed())
+                .collect(Collectors.toList());
+    }
+
+    // =====================================================
+    // 步骤 5: 1v1 去重配对
+    // =====================================================
+
+    /**
+     * 对视频→文章多对多匹配结果进行 1v1 去重。
+     *
+     * <p>两轮约束:
+     * <ol>
+     *   <li><b>每视频取最佳文章</b>:每个视频只保留 score 最高的文章(视频→文章 N→1)</li>
+     *   <li><b>每文章保留最佳视频</b>:每篇文章只保留 score 最高的视频(文章→视频 N→1)</li>
+     * </ol>
+     * 最终结果中每个视频最多对应一篇文章,每篇文章最多对应一个视频。
+     *
+     * @param videoArticleMatches videoId → 文章列表(已按 score 降序)
+     * @return videoId → 唯一最佳文章 的最终配对
+     */
+    private Map<Long, ArticleMatchItem> dedupOneToOne(
+            Map<Long, List<ArticleMatchItem>> videoArticleMatches) {
+
+        // 第一轮约束:每视频取最佳文章 (top 1 by score)
+        Map<Long, ArticleMatchItem> videoBestArticle = new LinkedHashMap<>();
+        for (Map.Entry<Long, List<ArticleMatchItem>> entry : videoArticleMatches.entrySet()) {
+            videoBestArticle.put(entry.getKey(), entry.getValue().get(0));
+        }
+
+        // 第二轮约束:每文章保留最佳视频 (最高 score)
+        Map<String, Long> articleBestVideo = new LinkedHashMap<>();
+        Map<String, Double> articleBestScore = new LinkedHashMap<>();
+        for (Map.Entry<Long, ArticleMatchItem> entry : videoBestArticle.entrySet()) {
+            Long videoId = entry.getKey();
+            ArticleMatchItem article = entry.getValue();
+            Double currentBest = articleBestScore.get(article.articleId);
+            if (currentBest == null || article.score > currentBest) {
+                articleBestVideo.put(article.articleId, videoId);
+                articleBestScore.put(article.articleId, article.score);
+            }
+        }
+
+        // 仅保留被文章选中的视频(视频一定是该文章的最佳匹配才保留)
+        Map<Long, ArticleMatchItem> finalPairs = new LinkedHashMap<>();
+        for (Map.Entry<Long, ArticleMatchItem> entry : videoBestArticle.entrySet()) {
+            Long videoId = entry.getKey();
+            ArticleMatchItem article = entry.getValue();
+            if (videoId.equals(articleBestVideo.get(article.articleId))) {
+                finalPairs.put(videoId, article);
+            }
+        }
+
+        log.info("1v1 去重完成: {} 个视频 → {} 篇唯一文章 → {} 对最终配对",
+                videoBestArticle.size(), articleBestVideo.size(), finalPairs.size());
+        return finalPairs;
+    }
+
+    // =====================================================
+    // 步骤 6: 构建结果对象 & 存储
+    // =====================================================
+
+    /**
+     * 将最终配对结果写入 video_article_match_result 表。
+     *
+     * <p>每对 (video, article) 只产生一条记录(不关联需求维度),
+     * channelLevel3 / account 取自原始匹配记录。
+     * 先清理同日 dt 旧数据(幂等重跑),再批量插入新结果。
+     */
+    private void saveResults(String dt,
+                             List<ChannelDemandMatchResult> records,
+                             Map<Long, String> videoTitleMap,
+                             Map<Long, ArticleMatchItem> finalPairs) {
+
+        // 构建 videoId → (channelLevel3, account) 映射,取第一条匹配记录的值
+        Map<Long, ChannelDemandMatchResult> videoRecordMap = new LinkedHashMap<>();
+        for (ChannelDemandMatchResult r : records) {
+            videoRecordMap.putIfAbsent(r.getMatchVideoId(), r);
+        }
+
+        // 序列化排名参数(所有行共享)
+        String rankingJson = JSON.toJSONString(buildRankingSpec());
+        String configCodesStr = String.join(",", ARTICLE_CONFIG_CODES);
+
+        // 每对 (video, article)
+        List<VideoArticleMatchResult> resultList = new ArrayList<>();
+        for (Map.Entry<Long, ArticleMatchItem> pair : finalPairs.entrySet()) {
+            Long videoId = pair.getKey();
+            ArticleMatchItem article = pair.getValue();
+            String videoTitle = videoTitleMap.get(videoId);
+            ChannelDemandMatchResult record = videoRecordMap.get(videoId);
+            if (record == null) {
+                continue;
+            }
+
+            resultList.add(buildResultRow(dt, videoId, videoTitle, article,
+                    record.getChannelLevel3(), record.getAccount(),
+                    rankingJson, configCodesStr));
+        }
+
+        log.info("构建完成: {} 条结果记录", resultList.size());
+
+        if (resultList.isEmpty()) {
+            return;
+        }
+
+        // 先清理同日旧数据(支持幂等重跑)
+        int deleted = videoArticleMatchResultMapperExt.deleteByDt(dt);
+        log.info("清理 dt={} 旧数据: {} 条", dt, deleted);
+
+        // 分批写入
+        int total = resultList.size();
+        int inserted = 0;
+        for (int i = 0; i < total; i += 1000) {
+            int end = Math.min(i + 1000, total);
+            List<VideoArticleMatchResult> batch = resultList.subList(i, end);
+            videoArticleMatchResultMapperExt.batchInsert(batch);
+            inserted += batch.size();
+            log.info("分批写入进度: {}/{} 条", inserted, total);
+        }
+        log.info("分批写入完成: {} 条", total);
+    }
+
+    /**
+     * 构建单条 VideoArticleMatchResult 记录(不绑定需求维度)。
+     */
+    private VideoArticleMatchResult buildResultRow(String dt,
+                                                   Long videoId,
+                                                   String videoTitle,
+                                                   ArticleMatchItem article,
+                                                   String channelLevel3,
+                                                   String account,
+                                                   String rankingJson,
+                                                   String configCodesStr) {
+        VideoArticleMatchResult row = new VideoArticleMatchResult();
+        row.setDt(dt);
+        row.setChannelName(channelName);
+        row.setChannelLevel3(channelLevel3);
+        row.setAccount(account);
+        row.setMatchVideoId(videoId);
+        row.setVideoTitle(videoTitle);
+        row.setMatchedArticleId(article.articleId);
+        row.setMatchedArticleTitle(article.articleTitle);
+        row.setMatchScore(article.score);
+        row.setMatchConfigCode(article.configCode);
+        row.setQueryText(videoTitle);
+        row.setConfigCodes(configCodesStr);
+        row.setRankingParams(rankingJson);
+        return row;
+    }
+
+    // =====================================================
+    // 步骤 7: 分组统计日志
+    // =====================================================
+
+    /**
+     * 按 channelLevel3 分组统计匹配覆盖率并输出格式化日志。
+     *
+     * <p>统计维度:需求特征点数量、向量匹配视频数、视频匹配到长文数、覆盖文章数。
+     */
+    private void logSummary(String dt,
+                            List<ChannelDemandMatchResult> records,
+                            Map<Long, String> videoTitleMap,
+                            Map<Long, ArticleMatchItem> finalPairs) {
+
+        // 按 channelLevel3 分组聚合原始记录
+        Map<String, ChannelLevel3Stats> statsMap = new LinkedHashMap<>();
+        for (ChannelDemandMatchResult r : records) {
+            String cl3 = r.getChannelLevel3() != null ? r.getChannelLevel3() : "未知";
+            ChannelLevel3Stats stats = statsMap.computeIfAbsent(cl3, k -> new ChannelLevel3Stats());
+            if (StringUtils.hasText(r.getDemandId())) {
+                stats.demandIds.add(r.getDemandId());
+            }
+            if (r.getMatchVideoId() != null) {
+                stats.videoIds.add(r.getMatchVideoId());
+            }
+        }
+
+        // 计算每组的匹配情况
+        Set<String> allMatchedArticles = new HashSet<>();
+        for (ChannelLevel3Stats stats : statsMap.values()) {
+            for (Long videoId : stats.videoIds) {
+                ArticleMatchItem matched = finalPairs.get(videoId);
+                if (matched != null) {
+                    stats.matchedVideoCount++;
+                    stats.matchedArticleIds.add(matched.articleId);
+                }
+            }
+            allMatchedArticles.addAll(stats.matchedArticleIds);
+        }
+
+        // 获取所有唯一视频 ID
+        Set<Long> allVideoIds = records.stream()
+                .map(ChannelDemandMatchResult::getMatchVideoId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 输出格式化表格
+        log.info("\n============================================================");
+        log.info("  视频→长文匹配 统计汇总 (dt={})", dt);
+        log.info("============================================================");
+        log.info(String.format("  %-25s %8s %8s %8s %8s",
+                "channelLevel3", "需求点", "视频数", "匹配视频", "覆盖文章"));
+        log.info("  " + String.join("", Collections.nCopies(60, "-")));
+
+        int totalDemands = 0, totalVideos = 0, totalMatched = 0;
+        for (Map.Entry<String, ChannelLevel3Stats> entry : statsMap.entrySet()) {
+            ChannelLevel3Stats s = entry.getValue();
+            totalDemands += s.demandIds.size();
+            totalVideos += s.videoIds.size();
+            totalMatched += s.matchedVideoCount;
+            log.info(String.format("  %-25s %8d %8d %8d %8d",
+                    truncate(entry.getKey(), 25),
+                    s.demandIds.size(), s.videoIds.size(),
+                    s.matchedVideoCount, s.matchedArticleIds.size()));
+        }
+
+        log.info("  " + String.join("", Collections.nCopies(60, "-")));
+        log.info(String.format("  %-25s %8d %8d %8d %8d",
+                "【合计】", totalDemands, totalVideos, totalMatched, allMatchedArticles.size()));
+        log.info("============================================================");
+        log.info("  查询记录: {} | 唯一视频: {} | 有效标题: {} | 1v1配对: {} | 结果行数: {}",
+                records.size(), allVideoIds.size(), videoTitleMap.size(),
+                finalPairs.size(),
+                finalPairs.size() * (records.size() / Math.max(allVideoIds.size(), 1)));
+        log.info("============================================================\n");
+    }
+
+    // =====================================================
+    // 参数构建
+    // =====================================================
+
+    /**
+     * 解析 dt 参数。
+     *
+     * <p>支持格式:
+     * <ul>
+     *   <li>dt=20260615 — key=value 格式</li>
+     *   <li>20260615 — 直接传入 8 位日期</li>
+     *   <li>空/不传 — 默认取前一天</li>
+     * </ul>
+     */
+    private String parseDt(String param) {
+        if (StringUtils.hasText(param)) {
+            // 支持 dt=20260615 格式
+            if (param.contains("=")) {
+                String[] parts = param.split("=");
+                if (parts.length == 2 && "dt".equals(parts[0].trim())) {
+                    return parts[1].trim();
+                }
+            }
+            // 支持直接传入 8 位日期
+            if (param.matches("\\d{8}")) {
+                return param;
+            }
+        }
+        return LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+    }
+
+    /**
+     * 构建固定的精排参数。
+     *
+     * <p>参数值与前端页面配置一致:
+     * <ul>
+     *   <li>simThreshold=0.7 — 相似度硬筛阈值</li>
+     *   <li>alpha=0.6 — 相关性 vs 质量权衡</li>
+     *   <li>文章维度 boost: ARTICLE_TITLE=0.4, ARTICLE_SUMMARY=0.4</li>
+     *   <li>文章质量子维度: 阅读=0.4, 打开率=0.3, 裂变率=0.3</li>
+     * </ul>
+     */
+    private RankingSpec buildRankingSpec() {
+        RankingSpec ranking = new RankingSpec();
+        ranking.setSimThreshold(0.7);
+        ranking.setRovClipLow(0.0);
+        ranking.setRovClipHigh(0.07);
+        ranking.setAlpha(0.6);
+        ranking.setDeconstructBoost(0.4);
+        ranking.setWCtr(0.3);
+        ranking.setWViral(0.2);
+        ranking.setWRoi(0.1);
+        ranking.setMaterialMissingStrategy("group");
+        ranking.setWRead(0.4);
+        ranking.setWOpen(0.3);
+        ranking.setWFission(0.3);
+
+        Map<String, Double> boostsByCode = new HashMap<>();
+        boostsByCode.put(VectorConstants.ARTICLE_TITLE_CONFIG_CODE, 0.4);
+        boostsByCode.put(VectorConstants.ARTICLE_SUMMARY_CONFIG_CODE, 0.4);
+        ranking.setBoostsByCode(boostsByCode);
+
+        return ranking;
+    }
+
+    /**
+     * 构建长文召回请求参数。
+     */
+    private BatchByTextParam buildBatchByTextParam(String queryText, RankingSpec ranking) {
+        BatchByTextParam param = new BatchByTextParam();
+        param.setQueryText(queryText);
+        param.setConfigCodes(new ArrayList<>(ARTICLE_CONFIG_CODES));
+        param.setModalities(Collections.singletonList("ARTICLE"));
+        param.setDisplayK(ARTICLE_DISPLAY_K);
+        param.setRanking(ranking);
+        return param;
+    }
+
+    // =====================================================
+    // 工具方法
+    // =====================================================
+
+    private String truncate(String s, int maxLen) {
+        if (s == null) return "";
+        return s.length() <= maxLen ? s : s.substring(0, maxLen - 1) + "…";
+    }
+
+    // =====================================================
+    // 内部类
+    // =====================================================
+
+    /**
+     * 单条文章匹配结果。
+     */
+    private static class ArticleMatchItem {
+        /** 文章 ID */
+        String articleId;
+        /** 文章标题 */
+        String articleTitle;
+        /** 匹配相似度得分 */
+        double score;
+        /** 命中维度 (ARTICLE_TITLE / ARTICLE_SUMMARY) */
+        String configCode;
+    }
+
+    /**
+     * channelLevel3 维度分组统计。
+     */
+    private static class ChannelLevel3Stats {
+        /** 去重需求 ID 集合 */
+        final Set<String> demandIds = new HashSet<>();
+        /** 去重视频 ID 集合 */
+        final Set<Long> videoIds = new HashSet<>();
+        /** 匹配到长文的视频数 */
+        int matchedVideoCount;
+        /** 覆盖的去重文章 ID 集合 */
+        final Set<String> matchedArticleIds = new HashSet<>();
+    }
+}

+ 22 - 0
core/src/main/java/com/tzld/videoVector/model/param/VideoArticleMatchQueryParam.java

@@ -0,0 +1,22 @@
+package com.tzld.videoVector.model.param;
+
+import lombok.Data;
+
+/**
+ * 视频→长文匹配结果查询参数
+ */
+@Data
+public class VideoArticleMatchQueryParam {
+
+    /** 数据日期(yyyyMMdd),不传则取表中最大 dt */
+    private String dt;
+
+    /** 三级渠道(账号),可选,支持多个 */
+    private String channelLevel3;
+
+    /** 页码,从1开始,默认1 */
+    private Integer pageNum = 1;
+
+    /** 每页大小,默认20 */
+    private Integer pageSize = 20;
+}

+ 564 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoArticleMatchResult.java

@@ -0,0 +1,564 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.Date;
+
+/**
+ * Database Table Remarks:
+ *   视频标题→长文匹配结果表
+ *
+ * This class was generated by MyBatis Generator.
+ * This class corresponds to the database table video_article_match_result
+ */
+public class VideoArticleMatchResult {
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.id
+     *
+     * @mbg.generated
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   数据日期(yyyyMMdd)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.dt
+     *
+     * @mbg.generated
+     */
+    private String dt;
+
+    /**
+     * Database Column Remarks:
+     *   渠道名
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.channel_name
+     *
+     * @mbg.generated
+     */
+    private String channelName;
+
+    /**
+     * Database Column Remarks:
+     *   三级渠道(账号)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.channel_level3
+     *
+     * @mbg.generated
+     */
+    private String channelLevel3;
+
+    /**
+     * Database Column Remarks:
+     *   账号
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.account
+     *
+     * @mbg.generated
+     */
+    private String account;
+
+    /**
+     * Database Column Remarks:
+     *   匹配视频ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.match_video_id
+     *
+     * @mbg.generated
+     */
+    private Long matchVideoId;
+
+    /**
+     * Database Column Remarks:
+     *   视频标题
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.video_title
+     *
+     * @mbg.generated
+     */
+    private String videoTitle;
+
+    /**
+     * Database Column Remarks:
+     *   匹配到的文章ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.matched_article_id
+     *
+     * @mbg.generated
+     */
+    private String matchedArticleId;
+
+    /**
+     * Database Column Remarks:
+     *   匹配到的文章标题
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.matched_article_title
+     *
+     * @mbg.generated
+     */
+    private String matchedArticleTitle;
+
+    /**
+     * Database Column Remarks:
+     *   匹配得分
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.match_score
+     *
+     * @mbg.generated
+     */
+    private Double matchScore;
+
+    /**
+     * Database Column Remarks:
+     *   匹配配置编码(ARTICLE_TITLE/ARTICLE_SUMMARY)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.match_config_code
+     *
+     * @mbg.generated
+     */
+    private String matchConfigCode;
+
+    /**
+     * Database Column Remarks:
+     *   查询文本(视频标题)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.query_text
+     *
+     * @mbg.generated
+     */
+    private String queryText;
+
+    /**
+     * Database Column Remarks:
+     *   查询使用的configCode列表
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.config_codes
+     *
+     * @mbg.generated
+     */
+    private String configCodes;
+
+    /**
+     * Database Column Remarks:
+     *   精排参数JSON快照
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.ranking_params
+     *
+     * @mbg.generated
+     */
+    private String rankingParams;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_article_match_result.create_time
+     *
+     * @mbg.generated
+     */
+    private Date createTime;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.id
+     *
+     * @return the value of video_article_match_result.id
+     *
+     * @mbg.generated
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.id
+     *
+     * @param id the value for video_article_match_result.id
+     *
+     * @mbg.generated
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.dt
+     *
+     * @return the value of video_article_match_result.dt
+     *
+     * @mbg.generated
+     */
+    public String getDt() {
+        return dt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.dt
+     *
+     * @param dt the value for video_article_match_result.dt
+     *
+     * @mbg.generated
+     */
+    public void setDt(String dt) {
+        this.dt = dt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.channel_name
+     *
+     * @return the value of video_article_match_result.channel_name
+     *
+     * @mbg.generated
+     */
+    public String getChannelName() {
+        return channelName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.channel_name
+     *
+     * @param channelName the value for video_article_match_result.channel_name
+     *
+     * @mbg.generated
+     */
+    public void setChannelName(String channelName) {
+        this.channelName = channelName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.channel_level3
+     *
+     * @return the value of video_article_match_result.channel_level3
+     *
+     * @mbg.generated
+     */
+    public String getChannelLevel3() {
+        return channelLevel3;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.channel_level3
+     *
+     * @param channelLevel3 the value for video_article_match_result.channel_level3
+     *
+     * @mbg.generated
+     */
+    public void setChannelLevel3(String channelLevel3) {
+        this.channelLevel3 = channelLevel3;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.account
+     *
+     * @return the value of video_article_match_result.account
+     *
+     * @mbg.generated
+     */
+    public String getAccount() {
+        return account;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.account
+     *
+     * @param account the value for video_article_match_result.account
+     *
+     * @mbg.generated
+     */
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.match_video_id
+     *
+     * @return the value of video_article_match_result.match_video_id
+     *
+     * @mbg.generated
+     */
+    public Long getMatchVideoId() {
+        return matchVideoId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.match_video_id
+     *
+     * @param matchVideoId the value for video_article_match_result.match_video_id
+     *
+     * @mbg.generated
+     */
+    public void setMatchVideoId(Long matchVideoId) {
+        this.matchVideoId = matchVideoId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.video_title
+     *
+     * @return the value of video_article_match_result.video_title
+     *
+     * @mbg.generated
+     */
+    public String getVideoTitle() {
+        return videoTitle;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.video_title
+     *
+     * @param videoTitle the value for video_article_match_result.video_title
+     *
+     * @mbg.generated
+     */
+    public void setVideoTitle(String videoTitle) {
+        this.videoTitle = videoTitle;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.matched_article_id
+     *
+     * @return the value of video_article_match_result.matched_article_id
+     *
+     * @mbg.generated
+     */
+    public String getMatchedArticleId() {
+        return matchedArticleId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.matched_article_id
+     *
+     * @param matchedArticleId the value for video_article_match_result.matched_article_id
+     *
+     * @mbg.generated
+     */
+    public void setMatchedArticleId(String matchedArticleId) {
+        this.matchedArticleId = matchedArticleId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.matched_article_title
+     *
+     * @return the value of video_article_match_result.matched_article_title
+     *
+     * @mbg.generated
+     */
+    public String getMatchedArticleTitle() {
+        return matchedArticleTitle;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.matched_article_title
+     *
+     * @param matchedArticleTitle the value for video_article_match_result.matched_article_title
+     *
+     * @mbg.generated
+     */
+    public void setMatchedArticleTitle(String matchedArticleTitle) {
+        this.matchedArticleTitle = matchedArticleTitle;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.match_score
+     *
+     * @return the value of video_article_match_result.match_score
+     *
+     * @mbg.generated
+     */
+    public Double getMatchScore() {
+        return matchScore;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.match_score
+     *
+     * @param matchScore the value for video_article_match_result.match_score
+     *
+     * @mbg.generated
+     */
+    public void setMatchScore(Double matchScore) {
+        this.matchScore = matchScore;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.match_config_code
+     *
+     * @return the value of video_article_match_result.match_config_code
+     *
+     * @mbg.generated
+     */
+    public String getMatchConfigCode() {
+        return matchConfigCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.match_config_code
+     *
+     * @param matchConfigCode the value for video_article_match_result.match_config_code
+     *
+     * @mbg.generated
+     */
+    public void setMatchConfigCode(String matchConfigCode) {
+        this.matchConfigCode = matchConfigCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.query_text
+     *
+     * @return the value of video_article_match_result.query_text
+     *
+     * @mbg.generated
+     */
+    public String getQueryText() {
+        return queryText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.query_text
+     *
+     * @param queryText the value for video_article_match_result.query_text
+     *
+     * @mbg.generated
+     */
+    public void setQueryText(String queryText) {
+        this.queryText = queryText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.config_codes
+     *
+     * @return the value of video_article_match_result.config_codes
+     *
+     * @mbg.generated
+     */
+    public String getConfigCodes() {
+        return configCodes;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.config_codes
+     *
+     * @param configCodes the value for video_article_match_result.config_codes
+     *
+     * @mbg.generated
+     */
+    public void setConfigCodes(String configCodes) {
+        this.configCodes = configCodes;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.ranking_params
+     *
+     * @return the value of video_article_match_result.ranking_params
+     *
+     * @mbg.generated
+     */
+    public String getRankingParams() {
+        return rankingParams;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.ranking_params
+     *
+     * @param rankingParams the value for video_article_match_result.ranking_params
+     *
+     * @mbg.generated
+     */
+    public void setRankingParams(String rankingParams) {
+        this.rankingParams = rankingParams;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_article_match_result.create_time
+     *
+     * @return the value of video_article_match_result.create_time
+     *
+     * @mbg.generated
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_article_match_result.create_time
+     *
+     * @param createTime the value for video_article_match_result.create_time
+     *
+     * @mbg.generated
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", dt=").append(dt);
+        sb.append(", channelName=").append(channelName);
+        sb.append(", channelLevel3=").append(channelLevel3);
+        sb.append(", account=").append(account);
+        sb.append(", matchVideoId=").append(matchVideoId);
+        sb.append(", videoTitle=").append(videoTitle);
+        sb.append(", matchedArticleId=").append(matchedArticleId);
+        sb.append(", matchedArticleTitle=").append(matchedArticleTitle);
+        sb.append(", matchScore=").append(matchScore);
+        sb.append(", matchConfigCode=").append(matchConfigCode);
+        sb.append(", queryText=").append(queryText);
+        sb.append(", configCodes=").append(configCodes);
+        sb.append(", rankingParams=").append(rankingParams);
+        sb.append(", createTime=").append(createTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1313 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoArticleMatchResultExample.java

@@ -0,0 +1,1313 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class VideoArticleMatchResultExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public VideoArticleMatchResultExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public Criteria or() {
+        Criteria criteria = createCriteriaInternal();
+        oredCriteria.add(criteria);
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public Criteria createCriteria() {
+        Criteria criteria = createCriteriaInternal();
+        if (oredCriteria.size() == 0) {
+            oredCriteria.add(criteria);
+        }
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    protected abstract static class GeneratedCriteria {
+        protected List<Criterion> criteria;
+
+        protected GeneratedCriteria() {
+            super();
+            criteria = new ArrayList<Criterion>();
+        }
+
+        public boolean isValid() {
+            return criteria.size() > 0;
+        }
+
+        public List<Criterion> getAllCriteria() {
+            return criteria;
+        }
+
+        public List<Criterion> getCriteria() {
+            return criteria;
+        }
+
+        protected void addCriterion(String condition) {
+            if (condition == null) {
+                throw new RuntimeException("Value for condition cannot be null");
+            }
+            criteria.add(new Criterion(condition));
+        }
+
+        protected void addCriterion(String condition, Object value, String property) {
+            if (value == null) {
+                throw new RuntimeException("Value for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value));
+        }
+
+        protected void addCriterion(String condition, Object value1, Object value2, String property) {
+            if (value1 == null || value2 == null) {
+                throw new RuntimeException("Between values for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value1, value2));
+        }
+
+        public Criteria andIdIsNull() {
+            addCriterion("id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIsNotNull() {
+            addCriterion("id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdEqualTo(Long value) {
+            addCriterion("id =", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotEqualTo(Long value) {
+            addCriterion("id <>", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThan(Long value) {
+            addCriterion("id >", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("id >=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThan(Long value) {
+            addCriterion("id <", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThanOrEqualTo(Long value) {
+            addCriterion("id <=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIn(List<Long> values) {
+            addCriterion("id in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotIn(List<Long> values) {
+            addCriterion("id not in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdBetween(Long value1, Long value2) {
+            addCriterion("id between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotBetween(Long value1, Long value2) {
+            addCriterion("id not between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtIsNull() {
+            addCriterion("dt is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtIsNotNull() {
+            addCriterion("dt is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtEqualTo(String value) {
+            addCriterion("dt =", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtNotEqualTo(String value) {
+            addCriterion("dt <>", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtGreaterThan(String value) {
+            addCriterion("dt >", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtGreaterThanOrEqualTo(String value) {
+            addCriterion("dt >=", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtLessThan(String value) {
+            addCriterion("dt <", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtLessThanOrEqualTo(String value) {
+            addCriterion("dt <=", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtLike(String value) {
+            addCriterion("dt like", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtNotLike(String value) {
+            addCriterion("dt not like", value, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtIn(List<String> values) {
+            addCriterion("dt in", values, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtNotIn(List<String> values) {
+            addCriterion("dt not in", values, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtBetween(String value1, String value2) {
+            addCriterion("dt between", value1, value2, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andDtNotBetween(String value1, String value2) {
+            addCriterion("dt not between", value1, value2, "dt");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameIsNull() {
+            addCriterion("channel_name is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameIsNotNull() {
+            addCriterion("channel_name is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameEqualTo(String value) {
+            addCriterion("channel_name =", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameNotEqualTo(String value) {
+            addCriterion("channel_name <>", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameGreaterThan(String value) {
+            addCriterion("channel_name >", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameGreaterThanOrEqualTo(String value) {
+            addCriterion("channel_name >=", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameLessThan(String value) {
+            addCriterion("channel_name <", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameLessThanOrEqualTo(String value) {
+            addCriterion("channel_name <=", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameLike(String value) {
+            addCriterion("channel_name like", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameNotLike(String value) {
+            addCriterion("channel_name not like", value, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameIn(List<String> values) {
+            addCriterion("channel_name in", values, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameNotIn(List<String> values) {
+            addCriterion("channel_name not in", values, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameBetween(String value1, String value2) {
+            addCriterion("channel_name between", value1, value2, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNameNotBetween(String value1, String value2) {
+            addCriterion("channel_name not between", value1, value2, "channelName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3IsNull() {
+            addCriterion("channel_level3 is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3IsNotNull() {
+            addCriterion("channel_level3 is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3EqualTo(String value) {
+            addCriterion("channel_level3 =", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3NotEqualTo(String value) {
+            addCriterion("channel_level3 <>", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3GreaterThan(String value) {
+            addCriterion("channel_level3 >", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3GreaterThanOrEqualTo(String value) {
+            addCriterion("channel_level3 >=", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3LessThan(String value) {
+            addCriterion("channel_level3 <", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3LessThanOrEqualTo(String value) {
+            addCriterion("channel_level3 <=", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3Like(String value) {
+            addCriterion("channel_level3 like", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3NotLike(String value) {
+            addCriterion("channel_level3 not like", value, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3In(List<String> values) {
+            addCriterion("channel_level3 in", values, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3NotIn(List<String> values) {
+            addCriterion("channel_level3 not in", values, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3Between(String value1, String value2) {
+            addCriterion("channel_level3 between", value1, value2, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLevel3NotBetween(String value1, String value2) {
+            addCriterion("channel_level3 not between", value1, value2, "channelLevel3");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIsNull() {
+            addCriterion("account is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIsNotNull() {
+            addCriterion("account is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountEqualTo(String value) {
+            addCriterion("account =", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountNotEqualTo(String value) {
+            addCriterion("account <>", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountGreaterThan(String value) {
+            addCriterion("account >", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountGreaterThanOrEqualTo(String value) {
+            addCriterion("account >=", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountLessThan(String value) {
+            addCriterion("account <", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountLessThanOrEqualTo(String value) {
+            addCriterion("account <=", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountLike(String value) {
+            addCriterion("account like", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountNotLike(String value) {
+            addCriterion("account not like", value, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIn(List<String> values) {
+            addCriterion("account in", values, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountNotIn(List<String> values) {
+            addCriterion("account not in", values, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountBetween(String value1, String value2) {
+            addCriterion("account between", value1, value2, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountNotBetween(String value1, String value2) {
+            addCriterion("account not between", value1, value2, "account");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdIsNull() {
+            addCriterion("match_video_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdIsNotNull() {
+            addCriterion("match_video_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdEqualTo(Long value) {
+            addCriterion("match_video_id =", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdNotEqualTo(Long value) {
+            addCriterion("match_video_id <>", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdGreaterThan(Long value) {
+            addCriterion("match_video_id >", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("match_video_id >=", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdLessThan(Long value) {
+            addCriterion("match_video_id <", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdLessThanOrEqualTo(Long value) {
+            addCriterion("match_video_id <=", value, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdIn(List<Long> values) {
+            addCriterion("match_video_id in", values, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdNotIn(List<Long> values) {
+            addCriterion("match_video_id not in", values, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdBetween(Long value1, Long value2) {
+            addCriterion("match_video_id between", value1, value2, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchVideoIdNotBetween(Long value1, Long value2) {
+            addCriterion("match_video_id not between", value1, value2, "matchVideoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleIsNull() {
+            addCriterion("video_title is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleIsNotNull() {
+            addCriterion("video_title is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleEqualTo(String value) {
+            addCriterion("video_title =", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleNotEqualTo(String value) {
+            addCriterion("video_title <>", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleGreaterThan(String value) {
+            addCriterion("video_title >", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleGreaterThanOrEqualTo(String value) {
+            addCriterion("video_title >=", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleLessThan(String value) {
+            addCriterion("video_title <", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleLessThanOrEqualTo(String value) {
+            addCriterion("video_title <=", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleLike(String value) {
+            addCriterion("video_title like", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleNotLike(String value) {
+            addCriterion("video_title not like", value, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleIn(List<String> values) {
+            addCriterion("video_title in", values, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleNotIn(List<String> values) {
+            addCriterion("video_title not in", values, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleBetween(String value1, String value2) {
+            addCriterion("video_title between", value1, value2, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoTitleNotBetween(String value1, String value2) {
+            addCriterion("video_title not between", value1, value2, "videoTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdIsNull() {
+            addCriterion("matched_article_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdIsNotNull() {
+            addCriterion("matched_article_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdEqualTo(String value) {
+            addCriterion("matched_article_id =", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdNotEqualTo(String value) {
+            addCriterion("matched_article_id <>", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdGreaterThan(String value) {
+            addCriterion("matched_article_id >", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdGreaterThanOrEqualTo(String value) {
+            addCriterion("matched_article_id >=", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdLessThan(String value) {
+            addCriterion("matched_article_id <", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdLessThanOrEqualTo(String value) {
+            addCriterion("matched_article_id <=", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdLike(String value) {
+            addCriterion("matched_article_id like", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdNotLike(String value) {
+            addCriterion("matched_article_id not like", value, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdIn(List<String> values) {
+            addCriterion("matched_article_id in", values, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdNotIn(List<String> values) {
+            addCriterion("matched_article_id not in", values, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdBetween(String value1, String value2) {
+            addCriterion("matched_article_id between", value1, value2, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleIdNotBetween(String value1, String value2) {
+            addCriterion("matched_article_id not between", value1, value2, "matchedArticleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleIsNull() {
+            addCriterion("matched_article_title is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleIsNotNull() {
+            addCriterion("matched_article_title is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleEqualTo(String value) {
+            addCriterion("matched_article_title =", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleNotEqualTo(String value) {
+            addCriterion("matched_article_title <>", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleGreaterThan(String value) {
+            addCriterion("matched_article_title >", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleGreaterThanOrEqualTo(String value) {
+            addCriterion("matched_article_title >=", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleLessThan(String value) {
+            addCriterion("matched_article_title <", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleLessThanOrEqualTo(String value) {
+            addCriterion("matched_article_title <=", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleLike(String value) {
+            addCriterion("matched_article_title like", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleNotLike(String value) {
+            addCriterion("matched_article_title not like", value, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleIn(List<String> values) {
+            addCriterion("matched_article_title in", values, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleNotIn(List<String> values) {
+            addCriterion("matched_article_title not in", values, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleBetween(String value1, String value2) {
+            addCriterion("matched_article_title between", value1, value2, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchedArticleTitleNotBetween(String value1, String value2) {
+            addCriterion("matched_article_title not between", value1, value2, "matchedArticleTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreIsNull() {
+            addCriterion("match_score is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreIsNotNull() {
+            addCriterion("match_score is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreEqualTo(Double value) {
+            addCriterion("match_score =", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreNotEqualTo(Double value) {
+            addCriterion("match_score <>", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreGreaterThan(Double value) {
+            addCriterion("match_score >", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreGreaterThanOrEqualTo(Double value) {
+            addCriterion("match_score >=", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreLessThan(Double value) {
+            addCriterion("match_score <", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreLessThanOrEqualTo(Double value) {
+            addCriterion("match_score <=", value, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreIn(List<Double> values) {
+            addCriterion("match_score in", values, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreNotIn(List<Double> values) {
+            addCriterion("match_score not in", values, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreBetween(Double value1, Double value2) {
+            addCriterion("match_score between", value1, value2, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchScoreNotBetween(Double value1, Double value2) {
+            addCriterion("match_score not between", value1, value2, "matchScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeIsNull() {
+            addCriterion("match_config_code is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeIsNotNull() {
+            addCriterion("match_config_code is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeEqualTo(String value) {
+            addCriterion("match_config_code =", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeNotEqualTo(String value) {
+            addCriterion("match_config_code <>", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeGreaterThan(String value) {
+            addCriterion("match_config_code >", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeGreaterThanOrEqualTo(String value) {
+            addCriterion("match_config_code >=", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeLessThan(String value) {
+            addCriterion("match_config_code <", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeLessThanOrEqualTo(String value) {
+            addCriterion("match_config_code <=", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeLike(String value) {
+            addCriterion("match_config_code like", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeNotLike(String value) {
+            addCriterion("match_config_code not like", value, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeIn(List<String> values) {
+            addCriterion("match_config_code in", values, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeNotIn(List<String> values) {
+            addCriterion("match_config_code not in", values, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeBetween(String value1, String value2) {
+            addCriterion("match_config_code between", value1, value2, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchConfigCodeNotBetween(String value1, String value2) {
+            addCriterion("match_config_code not between", value1, value2, "matchConfigCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextIsNull() {
+            addCriterion("query_text is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextIsNotNull() {
+            addCriterion("query_text is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextEqualTo(String value) {
+            addCriterion("query_text =", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextNotEqualTo(String value) {
+            addCriterion("query_text <>", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextGreaterThan(String value) {
+            addCriterion("query_text >", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextGreaterThanOrEqualTo(String value) {
+            addCriterion("query_text >=", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextLessThan(String value) {
+            addCriterion("query_text <", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextLessThanOrEqualTo(String value) {
+            addCriterion("query_text <=", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextLike(String value) {
+            addCriterion("query_text like", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextNotLike(String value) {
+            addCriterion("query_text not like", value, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextIn(List<String> values) {
+            addCriterion("query_text in", values, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextNotIn(List<String> values) {
+            addCriterion("query_text not in", values, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextBetween(String value1, String value2) {
+            addCriterion("query_text between", value1, value2, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andQueryTextNotBetween(String value1, String value2) {
+            addCriterion("query_text not between", value1, value2, "queryText");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesIsNull() {
+            addCriterion("config_codes is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesIsNotNull() {
+            addCriterion("config_codes is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesEqualTo(String value) {
+            addCriterion("config_codes =", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesNotEqualTo(String value) {
+            addCriterion("config_codes <>", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesGreaterThan(String value) {
+            addCriterion("config_codes >", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesGreaterThanOrEqualTo(String value) {
+            addCriterion("config_codes >=", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesLessThan(String value) {
+            addCriterion("config_codes <", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesLessThanOrEqualTo(String value) {
+            addCriterion("config_codes <=", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesLike(String value) {
+            addCriterion("config_codes like", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesNotLike(String value) {
+            addCriterion("config_codes not like", value, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesIn(List<String> values) {
+            addCriterion("config_codes in", values, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesNotIn(List<String> values) {
+            addCriterion("config_codes not in", values, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesBetween(String value1, String value2) {
+            addCriterion("config_codes between", value1, value2, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodesNotBetween(String value1, String value2) {
+            addCriterion("config_codes not between", value1, value2, "configCodes");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsIsNull() {
+            addCriterion("ranking_params is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsIsNotNull() {
+            addCriterion("ranking_params is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsEqualTo(String value) {
+            addCriterion("ranking_params =", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsNotEqualTo(String value) {
+            addCriterion("ranking_params <>", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsGreaterThan(String value) {
+            addCriterion("ranking_params >", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsGreaterThanOrEqualTo(String value) {
+            addCriterion("ranking_params >=", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsLessThan(String value) {
+            addCriterion("ranking_params <", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsLessThanOrEqualTo(String value) {
+            addCriterion("ranking_params <=", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsLike(String value) {
+            addCriterion("ranking_params like", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsNotLike(String value) {
+            addCriterion("ranking_params not like", value, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsIn(List<String> values) {
+            addCriterion("ranking_params in", values, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsNotIn(List<String> values) {
+            addCriterion("ranking_params not in", values, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsBetween(String value1, String value2) {
+            addCriterion("ranking_params between", value1, value2, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andRankingParamsNotBetween(String value1, String value2) {
+            addCriterion("ranking_params not between", value1, value2, "rankingParams");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNull() {
+            addCriterion("create_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNotNull() {
+            addCriterion("create_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeEqualTo(Date value) {
+            addCriterion("create_time =", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotEqualTo(Date value) {
+            addCriterion("create_time <>", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThan(Date value) {
+            addCriterion("create_time >", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("create_time >=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThan(Date value) {
+            addCriterion("create_time <", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("create_time <=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIn(List<Date> values) {
+            addCriterion("create_time in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotIn(List<Date> values) {
+            addCriterion("create_time not in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeBetween(Date value1, Date value2) {
+            addCriterion("create_time between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotBetween(Date value1, Date value2) {
+            addCriterion("create_time not between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated do_not_delete_during_merge
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table video_article_match_result
+     *
+     * @mbg.generated
+     */
+    public static class Criterion {
+        private String condition;
+
+        private Object value;
+
+        private Object secondValue;
+
+        private boolean noValue;
+
+        private boolean singleValue;
+
+        private boolean betweenValue;
+
+        private boolean listValue;
+
+        private String typeHandler;
+
+        public String getCondition() {
+            return condition;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public Object getSecondValue() {
+            return secondValue;
+        }
+
+        public boolean isNoValue() {
+            return noValue;
+        }
+
+        public boolean isSingleValue() {
+            return singleValue;
+        }
+
+        public boolean isBetweenValue() {
+            return betweenValue;
+        }
+
+        public boolean isListValue() {
+            return listValue;
+        }
+
+        public String getTypeHandler() {
+            return typeHandler;
+        }
+
+        protected Criterion(String condition) {
+            super();
+            this.condition = condition;
+            this.typeHandler = null;
+            this.noValue = true;
+        }
+
+        protected Criterion(String condition, Object value, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.typeHandler = typeHandler;
+            if (value instanceof List<?>) {
+                this.listValue = true;
+            } else {
+                this.singleValue = true;
+            }
+        }
+
+        protected Criterion(String condition, Object value) {
+            this(condition, value, null);
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.secondValue = secondValue;
+            this.typeHandler = typeHandler;
+            this.betweenValue = true;
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue) {
+            this(condition, value, secondValue, null);
+        }
+    }
+}

+ 19 - 0
core/src/main/java/com/tzld/videoVector/service/VideoArticleMatchService.java

@@ -0,0 +1,19 @@
+package com.tzld.videoVector.service;
+
+import com.tzld.videoVector.model.param.VideoArticleMatchQueryParam;
+import com.tzld.videoVector.model.vo.PageResult;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+
+/**
+ * 视频→长文匹配结果查询服务
+ */
+public interface VideoArticleMatchService {
+
+    /**
+     * 按条件分页查询匹配结果
+     *
+     * @param param 查询参数(dt 必填,channelLevel3 可选)
+     * @return 分页结果
+     */
+    PageResult<VideoArticleMatchResult> queryMatchResult(VideoArticleMatchQueryParam param);
+}

+ 69 - 0
core/src/main/java/com/tzld/videoVector/service/impl/VideoArticleMatchServiceImpl.java

@@ -0,0 +1,69 @@
+package com.tzld.videoVector.service.impl;
+
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.tzld.videoVector.dao.mapper.pgVector.VideoArticleMatchResultMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoArticleMatchResultMapperExt;
+import com.tzld.videoVector.model.param.VideoArticleMatchQueryParam;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResultExample;
+import com.tzld.videoVector.model.vo.PageResult;
+import com.tzld.videoVector.service.VideoArticleMatchService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 视频→长文匹配结果查询服务实现
+ */
+@Slf4j
+@Service
+public class VideoArticleMatchServiceImpl implements VideoArticleMatchService {
+
+    @Resource
+    private VideoArticleMatchResultMapper videoArticleMatchResultMapper;
+
+    @Resource
+    private VideoArticleMatchResultMapperExt videoArticleMatchResultMapperExt;
+
+    @Override
+    public PageResult<VideoArticleMatchResult> queryMatchResult(VideoArticleMatchQueryParam param) {
+        // dt 可选:不传则取表中最大 dt
+        String dt = param.getDt();
+        if (!StringUtils.hasText(dt)) {
+            dt = videoArticleMatchResultMapperExt.selectMaxDt();
+            if (!StringUtils.hasText(dt)) {
+                log.info("video_article_match_result 表为空,返回空结果");
+                return PageResult.of(
+                        java.util.Collections.emptyList(), 0,
+                        param.getPageNum() != null ? param.getPageNum() : 1,
+                        param.getPageSize() != null ? param.getPageSize() : 20);
+            }
+            log.info("dt 未传,自动取最大 dt={}", dt);
+        }
+
+        VideoArticleMatchResultExample example = new VideoArticleMatchResultExample();
+        VideoArticleMatchResultExample.Criteria criteria = example.createCriteria();
+        criteria.andDtEqualTo(dt);
+
+        // channelLevel3 可选过滤
+        if (StringUtils.hasText(param.getChannelLevel3())) {
+            criteria.andChannelLevel3EqualTo(param.getChannelLevel3());
+        }
+
+        // 按创建时间倒序
+        example.setOrderByClause("create_time DESC");
+
+        int pageNum = param.getPageNum() != null ? param.getPageNum() : 1;
+        int pageSize = param.getPageSize() != null ? param.getPageSize() : 20;
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<VideoArticleMatchResult> list = videoArticleMatchResultMapper.selectByExample(example);
+        PageInfo<VideoArticleMatchResult> pageInfo = new PageInfo<>(list);
+
+        return PageResult.of(list, pageInfo.getTotal(), pageNum, pageSize);
+    }
+}

+ 4 - 1
core/src/main/resources/generator/mybatis-pgvector-generator-config.xml

@@ -80,7 +80,10 @@
 <!--        <table tableName="channel_demand_match_config" domainObjectName="ChannelDemandMatchConfig" alias="">-->
 <!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
 <!--        </table>-->
-        <table tableName="channel_demand_match_result" domainObjectName="ChannelDemandMatchResult" alias="">
+<!--        <table tableName="channel_demand_match_result" domainObjectName="ChannelDemandMatchResult" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--        </table>-->
+        <table tableName="video_article_match_result" domainObjectName="VideoArticleMatchResult" alias="">
             <generatedKey column="id" sqlStatement="JDBC" identity="true"/>
         </table>
 <!--        <table tableName="video_deconstruct_result" domainObjectName="VideoDeconstructResult" alias="">-->

+ 425 - 0
core/src/main/resources/mapper/pgVector/VideoArticleMatchResultMapper.xml

@@ -0,0 +1,425 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.tzld.videoVector.dao.mapper.pgVector.VideoArticleMatchResultMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="dt" jdbcType="VARCHAR" property="dt" />
+    <result column="channel_name" jdbcType="VARCHAR" property="channelName" />
+    <result column="channel_level3" jdbcType="VARCHAR" property="channelLevel3" />
+    <result column="account" jdbcType="VARCHAR" property="account" />
+    <result column="match_video_id" jdbcType="BIGINT" property="matchVideoId" />
+    <result column="video_title" jdbcType="VARCHAR" property="videoTitle" />
+    <result column="matched_article_id" jdbcType="VARCHAR" property="matchedArticleId" />
+    <result column="matched_article_title" jdbcType="VARCHAR" property="matchedArticleTitle" />
+    <result column="match_score" jdbcType="DOUBLE" property="matchScore" />
+    <result column="match_config_code" jdbcType="VARCHAR" property="matchConfigCode" />
+    <result column="query_text" jdbcType="VARCHAR" property="queryText" />
+    <result column="config_codes" jdbcType="VARCHAR" property="configCodes" />
+    <result column="ranking_params" jdbcType="VARCHAR" property="rankingParams" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+  </resultMap>
+  <sql id="Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    <where>
+      <foreach collection="oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Update_By_Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    <where>
+      <foreach collection="example.oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    id, dt, channel_name, channel_level3, account, match_video_id, video_title, matched_article_id, 
+    matched_article_title, match_score, match_config_code, query_text, config_codes, 
+    ranking_params, create_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResultExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from video_article_match_result
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+    <if test="orderByClause != null">
+      order by ${orderByClause}
+    </if>
+  </select>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    from video_article_match_result
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    delete from video_article_match_result
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResultExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    delete from video_article_match_result
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </delete>
+  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_article_match_result (dt, channel_name, channel_level3, 
+      account, match_video_id, video_title, 
+      matched_article_id, matched_article_title, 
+      match_score, match_config_code, query_text, 
+      config_codes, ranking_params, create_time
+      )
+    values (#{dt,jdbcType=VARCHAR}, #{channelName,jdbcType=VARCHAR}, #{channelLevel3,jdbcType=VARCHAR}, 
+      #{account,jdbcType=VARCHAR}, #{matchVideoId,jdbcType=BIGINT}, #{videoTitle,jdbcType=VARCHAR}, 
+      #{matchedArticleId,jdbcType=VARCHAR}, #{matchedArticleTitle,jdbcType=VARCHAR}, 
+      #{matchScore,jdbcType=DOUBLE}, #{matchConfigCode,jdbcType=VARCHAR}, #{queryText,jdbcType=VARCHAR}, 
+      #{configCodes,jdbcType=VARCHAR}, #{rankingParams,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}
+      )
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_article_match_result
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="dt != null">
+        dt,
+      </if>
+      <if test="channelName != null">
+        channel_name,
+      </if>
+      <if test="channelLevel3 != null">
+        channel_level3,
+      </if>
+      <if test="account != null">
+        account,
+      </if>
+      <if test="matchVideoId != null">
+        match_video_id,
+      </if>
+      <if test="videoTitle != null">
+        video_title,
+      </if>
+      <if test="matchedArticleId != null">
+        matched_article_id,
+      </if>
+      <if test="matchedArticleTitle != null">
+        matched_article_title,
+      </if>
+      <if test="matchScore != null">
+        match_score,
+      </if>
+      <if test="matchConfigCode != null">
+        match_config_code,
+      </if>
+      <if test="queryText != null">
+        query_text,
+      </if>
+      <if test="configCodes != null">
+        config_codes,
+      </if>
+      <if test="rankingParams != null">
+        ranking_params,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="dt != null">
+        #{dt,jdbcType=VARCHAR},
+      </if>
+      <if test="channelName != null">
+        #{channelName,jdbcType=VARCHAR},
+      </if>
+      <if test="channelLevel3 != null">
+        #{channelLevel3,jdbcType=VARCHAR},
+      </if>
+      <if test="account != null">
+        #{account,jdbcType=VARCHAR},
+      </if>
+      <if test="matchVideoId != null">
+        #{matchVideoId,jdbcType=BIGINT},
+      </if>
+      <if test="videoTitle != null">
+        #{videoTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="matchedArticleId != null">
+        #{matchedArticleId,jdbcType=VARCHAR},
+      </if>
+      <if test="matchedArticleTitle != null">
+        #{matchedArticleTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="matchScore != null">
+        #{matchScore,jdbcType=DOUBLE},
+      </if>
+      <if test="matchConfigCode != null">
+        #{matchConfigCode,jdbcType=VARCHAR},
+      </if>
+      <if test="queryText != null">
+        #{queryText,jdbcType=VARCHAR},
+      </if>
+      <if test="configCodes != null">
+        #{configCodes,jdbcType=VARCHAR},
+      </if>
+      <if test="rankingParams != null">
+        #{rankingParams,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResultExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    select count(*) from video_article_match_result
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </select>
+  <update id="updateByExampleSelective" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_article_match_result
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.dt != null">
+        dt = #{record.dt,jdbcType=VARCHAR},
+      </if>
+      <if test="record.channelName != null">
+        channel_name = #{record.channelName,jdbcType=VARCHAR},
+      </if>
+      <if test="record.channelLevel3 != null">
+        channel_level3 = #{record.channelLevel3,jdbcType=VARCHAR},
+      </if>
+      <if test="record.account != null">
+        account = #{record.account,jdbcType=VARCHAR},
+      </if>
+      <if test="record.matchVideoId != null">
+        match_video_id = #{record.matchVideoId,jdbcType=BIGINT},
+      </if>
+      <if test="record.videoTitle != null">
+        video_title = #{record.videoTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="record.matchedArticleId != null">
+        matched_article_id = #{record.matchedArticleId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.matchedArticleTitle != null">
+        matched_article_title = #{record.matchedArticleTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="record.matchScore != null">
+        match_score = #{record.matchScore,jdbcType=DOUBLE},
+      </if>
+      <if test="record.matchConfigCode != null">
+        match_config_code = #{record.matchConfigCode,jdbcType=VARCHAR},
+      </if>
+      <if test="record.queryText != null">
+        query_text = #{record.queryText,jdbcType=VARCHAR},
+      </if>
+      <if test="record.configCodes != null">
+        config_codes = #{record.configCodes,jdbcType=VARCHAR},
+      </if>
+      <if test="record.rankingParams != null">
+        ranking_params = #{record.rankingParams,jdbcType=VARCHAR},
+      </if>
+      <if test="record.createTime != null">
+        create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByExample" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_article_match_result
+    set id = #{record.id,jdbcType=BIGINT},
+      dt = #{record.dt,jdbcType=VARCHAR},
+      channel_name = #{record.channelName,jdbcType=VARCHAR},
+      channel_level3 = #{record.channelLevel3,jdbcType=VARCHAR},
+      account = #{record.account,jdbcType=VARCHAR},
+      match_video_id = #{record.matchVideoId,jdbcType=BIGINT},
+      video_title = #{record.videoTitle,jdbcType=VARCHAR},
+      matched_article_id = #{record.matchedArticleId,jdbcType=VARCHAR},
+      matched_article_title = #{record.matchedArticleTitle,jdbcType=VARCHAR},
+      match_score = #{record.matchScore,jdbcType=DOUBLE},
+      match_config_code = #{record.matchConfigCode,jdbcType=VARCHAR},
+      query_text = #{record.queryText,jdbcType=VARCHAR},
+      config_codes = #{record.configCodes,jdbcType=VARCHAR},
+      ranking_params = #{record.rankingParams,jdbcType=VARCHAR},
+      create_time = #{record.createTime,jdbcType=TIMESTAMP}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_article_match_result
+    <set>
+      <if test="dt != null">
+        dt = #{dt,jdbcType=VARCHAR},
+      </if>
+      <if test="channelName != null">
+        channel_name = #{channelName,jdbcType=VARCHAR},
+      </if>
+      <if test="channelLevel3 != null">
+        channel_level3 = #{channelLevel3,jdbcType=VARCHAR},
+      </if>
+      <if test="account != null">
+        account = #{account,jdbcType=VARCHAR},
+      </if>
+      <if test="matchVideoId != null">
+        match_video_id = #{matchVideoId,jdbcType=BIGINT},
+      </if>
+      <if test="videoTitle != null">
+        video_title = #{videoTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="matchedArticleId != null">
+        matched_article_id = #{matchedArticleId,jdbcType=VARCHAR},
+      </if>
+      <if test="matchedArticleTitle != null">
+        matched_article_title = #{matchedArticleTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="matchScore != null">
+        match_score = #{matchScore,jdbcType=DOUBLE},
+      </if>
+      <if test="matchConfigCode != null">
+        match_config_code = #{matchConfigCode,jdbcType=VARCHAR},
+      </if>
+      <if test="queryText != null">
+        query_text = #{queryText,jdbcType=VARCHAR},
+      </if>
+      <if test="configCodes != null">
+        config_codes = #{configCodes,jdbcType=VARCHAR},
+      </if>
+      <if test="rankingParams != null">
+        ranking_params = #{rankingParams,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_article_match_result
+    set dt = #{dt,jdbcType=VARCHAR},
+      channel_name = #{channelName,jdbcType=VARCHAR},
+      channel_level3 = #{channelLevel3,jdbcType=VARCHAR},
+      account = #{account,jdbcType=VARCHAR},
+      match_video_id = #{matchVideoId,jdbcType=BIGINT},
+      video_title = #{videoTitle,jdbcType=VARCHAR},
+      matched_article_id = #{matchedArticleId,jdbcType=VARCHAR},
+      matched_article_title = #{matchedArticleTitle,jdbcType=VARCHAR},
+      match_score = #{matchScore,jdbcType=DOUBLE},
+      match_config_code = #{matchConfigCode,jdbcType=VARCHAR},
+      query_text = #{queryText,jdbcType=VARCHAR},
+      config_codes = #{configCodes,jdbcType=VARCHAR},
+      ranking_params = #{rankingParams,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 30 - 0
core/src/main/resources/mapper/pgVector/ext/VideoArticleMatchResultMapperExt.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.tzld.videoVector.dao.mapper.pgVector.ext.VideoArticleMatchResultMapperExt">
+
+  <insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
+    insert into video_article_match_result (dt, channel_name, channel_level3,
+      account, match_video_id, video_title,
+      matched_article_id, matched_article_title,
+      match_score, match_config_code,
+      query_text, config_codes, ranking_params)
+    values
+    <foreach collection="list" item="item" separator=",">
+      (#{item.dt,jdbcType=VARCHAR}, #{item.channelName,jdbcType=VARCHAR}, #{item.channelLevel3,jdbcType=VARCHAR},
+      #{item.account,jdbcType=VARCHAR}, #{item.matchVideoId,jdbcType=BIGINT}, #{item.videoTitle,jdbcType=VARCHAR},
+      #{item.matchedArticleId,jdbcType=VARCHAR}, #{item.matchedArticleTitle,jdbcType=VARCHAR},
+      #{item.matchScore,jdbcType=DOUBLE}, #{item.matchConfigCode,jdbcType=VARCHAR},
+      #{item.queryText,jdbcType=VARCHAR}, #{item.configCodes,jdbcType=VARCHAR}, #{item.rankingParams,jdbcType=VARCHAR})
+    </foreach>
+  </insert>
+
+  <delete id="deleteByDt">
+    delete from video_article_match_result
+    where dt = #{dt,jdbcType=VARCHAR}
+  </delete>
+
+  <select id="selectMaxDt" resultType="java.lang.String">
+    select max(dt) from video_article_match_result
+  </select>
+
+</mapper>

+ 39 - 0
server/src/main/java/com/tzld/videoVector/controller/VideoArticleMatchController.java

@@ -0,0 +1,39 @@
+package com.tzld.videoVector.controller;
+
+import com.tzld.videoVector.common.base.CommonResponse;
+import com.tzld.videoVector.model.param.VideoArticleMatchQueryParam;
+import com.tzld.videoVector.model.po.pgVector.VideoArticleMatchResult;
+import com.tzld.videoVector.model.vo.PageResult;
+import com.tzld.videoVector.service.VideoArticleMatchService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 视频→长文匹配结果查询接口
+ */
+@RestController
+@RequestMapping("/videoArticleMatch")
+public class VideoArticleMatchController {
+
+    @Autowired
+    private VideoArticleMatchService videoArticleMatchService;
+
+    /**
+     * 按账号查询匹配结果。
+     * <p>dt 不传时自动取表中最新日期的数据。
+     *
+     * <pre>
+     * POST /videoVector/videoArticleMatch/query
+     * body: { "channelLevel3": "趣味生活方式", "pageNum": 1, "pageSize": 20 }
+     *   or: { "dt": "20260615", "channelLevel3": "趣味生活方式" }
+     * </pre>
+     */
+    @PostMapping("/query")
+    public CommonResponse<PageResult<VideoArticleMatchResult>> queryMatchResult(
+            @RequestBody VideoArticleMatchQueryParam param) {
+        return CommonResponse.success(videoArticleMatchService.queryMatchResult(param));
+    }
+}

+ 17 - 0
server/src/main/java/com/tzld/videoVector/controller/XxlJobController.java

@@ -5,6 +5,7 @@ import com.tzld.videoVector.job.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
@@ -38,6 +39,9 @@ public class XxlJobController {
     @Autowired
     private ChannelDemandMatchCheckJob channelDemandMatchCheckJob;
 
+    @Autowired
+    private VideoArticleMatchJob videoArticleMatchJob;
+
     // ==================== 视频向量化任务 ====================
 
     @GetMapping("/vectorVideoJob")
@@ -158,4 +162,17 @@ public class XxlJobController {
         return CommonResponse.success();
     }
 
+    // ==================== 视频→长文匹配任务 ====================
+
+    /**
+     * 执行视频→长文匹配任务。
+     * dt 不传则默认取前一天。
+     * 示例: GET /videoVector/job/videoArticleMatchJob?dt=20260615
+     */
+    @GetMapping("/videoArticleMatchJob")
+    public CommonResponse<Void> videoArticleMatchJob(@RequestParam(value = "dt", required = false) String dt) {
+        videoArticleMatchJob.videoArticleMatchJob(dt);
+        return CommonResponse.success();
+    }
+
 }