Quellcode durchsuchen

视频解构结果存储

wangyunpeng vor 3 Tagen
Ursprung
Commit
77eebb3ea2

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

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

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

@@ -0,0 +1,53 @@
+package com.tzld.videoVector.dao.mapper.pgVector.ext;
+
+import com.tzld.videoVector.model.po.pgVector.VideoDeconstructResult;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * VideoDeconstructResult 自定义 Mapper(存储三种来源的解构结果)
+ */
+public interface VideoDeconstructResultMapperExt {
+
+    /**
+     * 查询指定来源下已存在的 videoId 列表
+     *
+     * @param source   解构来源
+     * @param videoIds 待检查的视频ID列表
+     * @return 已存在的 videoId 列表
+     */
+    List<Long> selectExistingVideoIds(@Param("source") String source,
+                                      @Param("videoIds") List<Long> videoIds);
+
+    /**
+     * 批量插入解构结果(忽略已存在的记录)
+     * 使用 ON CONFLICT (video_id, source) DO NOTHING
+     *
+     * @param list 待插入的记录列表
+     * @return 实际插入的行数
+     */
+    int batchInsertIgnore(@Param("list") List<VideoDeconstructResult> list);
+
+    /**
+     * 分页查询指定来源的 videoId 列表
+     *
+     * @param source 解构来源
+     * @param offset 偏移量
+     * @param limit  每页数量
+     * @return videoId 列表
+     */
+    List<Long> selectVideoIdsBySourcePaged(@Param("source") String source,
+                                           @Param("offset") int offset,
+                                           @Param("limit") int limit);
+
+    /**
+     * 批量查询指定来源和 videoId 列表的解构结果
+     *
+     * @param source   解构来源
+     * @param videoIds 视频ID列表
+     * @return 解构结果列表
+     */
+    List<VideoDeconstructResult> selectResultsByVideoIds(@Param("source") String source,
+                                                        @Param("videoIds") List<Long> videoIds);
+}

+ 397 - 175
core/src/main/java/com/tzld/videoVector/job/VideoVectorJob.java

@@ -3,7 +3,6 @@ package com.tzld.videoVector.job;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
-import com.aliyun.odps.data.Record;
 import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.google.common.collect.Lists;
 import com.tzld.videoVector.api.AigcApiService;
@@ -11,11 +10,9 @@ import com.tzld.videoVector.api.VideoApiService;
 import com.tzld.videoVector.common.constant.VectorConstants;
 import com.tzld.videoVector.dao.mapper.pgVector.DeconstructContentMapper;
 import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoDeconstructResultMapperExt;
 import com.tzld.videoVector.model.entity.DeconstructResult;
-import com.tzld.videoVector.model.po.pgVector.DeconstructContent;
-import com.tzld.videoVector.model.po.pgVector.DeconstructContentExample;
-import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
-import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.pgVector.*;
 import com.tzld.videoVector.service.DeconstructService;
 import com.tzld.videoVector.service.EmbeddingService;
 import com.tzld.videoVector.service.VectorStoreService;
@@ -76,6 +73,9 @@ public class VideoVectorJob {
     @Resource
     private RedisUtils redisUtils;
 
+    @Resource
+    private VideoDeconstructResultMapperExt videoDeconstructResultMapperExt;
+
     /** 本次 Job 执行中已缓存 decode 的 videoId,避免多配置下重复写入 */
     private final Set<Long> decodeCachedInThisRun = ConcurrentHashMap.newKeySet();
 
@@ -162,7 +162,7 @@ public class VideoVectorJob {
 
     /**
      * 处理单个配置下的 raw_result 视频向量化
-     * 包含:空text检查清理、过滤已有向量、流式查询ODPS并并发embedding
+     * 包含:空text检查清理、过滤已有向量、从本地DB查询解构结果并并发embedding
      */
     private void processConfigForRawResult(DeconstructVectorConfig config, List<Long> auditPassedIds,
                                            AtomicInteger totalSuccessCount, AtomicInteger totalFailCount) {
@@ -182,22 +182,23 @@ public class VideoVectorJob {
             }
             log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
 
-            // 3. 流式查询并并发embedding
+            // 3. 从本地DB批量查询解构结果并并发embedding
             ExecutorService embedExecutor = Executors.newFixedThreadPool(VectorConstants.EMBEDDING_PARALLELISM);
             Semaphore inFlightLimiter = new Semaphore(VectorConstants.MAX_EMBEDDING_IN_FLIGHT);
             List<Future<?>> futures = new ArrayList<>();
 
             try {
                 for (List<Long> batchIds : Lists.partition(needProcessIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
-                    String idsStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
-                    String sql = String.format(
-                            "SELECT content_id, raw_result FROM videoods.content_profile " +
-                                    "WHERE status = 3 AND is_deleted = 0 AND content_id IN (%s);", idsStr);
-
-                    OdpsUtil.getOdpsDataStream(sql, record ->
-                            submitEmbeddingTask(record, config, embedExecutor, inFlightLimiter, futures,
-                                    totalSuccessCount, totalFailCount, "raw_result")
-                    );
+                    List<VideoDeconstructResult> results = videoDeconstructResultMapperExt
+                            .selectResultsByVideoIds("result_json", batchIds);
+                    for (VideoDeconstructResult r : results) {
+                        if (!StringUtils.hasText(r.getResult())) {
+                            continue;
+                        }
+                        submitLocalEmbeddingTask(r.getVideoId(), r.getResult(), config,
+                                embedExecutor, inFlightLimiter, futures,
+                                totalSuccessCount, totalFailCount, "raw_result");
+                    }
                 }
             } finally {
                 awaitAndShutdown(futures, embedExecutor, 30, "embedding");
@@ -208,15 +209,13 @@ public class VideoVectorJob {
     }
 
     /**
-     * 提交单条记录的 embedding 任务到线程池
+     * 提交单条记录的 embedding 任务到线程池(基于本地DB数据)
      * 使用 Semaphore 控制在途并发数
      */
-    private void submitEmbeddingTask(com.aliyun.odps.data.Record record, DeconstructVectorConfig config,
-                                     ExecutorService executor, Semaphore inFlightLimiter,
-                                     List<Future<?>> futures, AtomicInteger successCount,
-                                     AtomicInteger failCount, String dataType) {
-        Long videoId = Long.valueOf(record.getString(0));
-        String rawData = record.getString(1);
+    private void submitLocalEmbeddingTask(Long videoId, String rawData, DeconstructVectorConfig config,
+                                          ExecutorService executor, Semaphore inFlightLimiter,
+                                          List<Future<?>> futures, AtomicInteger successCount,
+                                          AtomicInteger failCount, String dataType) {
         if (videoId == null || !StringUtils.hasText(rawData)) {
             return;
         }
@@ -516,38 +515,20 @@ public class VideoVectorJob {
     }
 
     /**
-     * 分页查询 videoId 列表
+     * 分页查询 videoId 列表(从本地解构结果表查询 result_json 来源)
      * @param pageNum 页码(从0开始)
      * @param pageSize 每页数量
      * @return videoId 列表
      */
     private List<Long> queryVideoIdsByPage(int pageNum, int pageSize) {
         int offset = pageNum * pageSize;
-        String sql = String.format(
-                "SELECT content_id " +
-                        "FROM videoods.content_profile " +
-                        "WHERE status = 3 AND is_deleted = 0 " +
-                        "ORDER BY content_id " +
-                        "LIMIT %d, %d;",
-                offset, pageSize);
-        List<Record> records = OdpsUtil.getOdpsData(sql);
-        if (records == null || records.isEmpty()) {
-            return new ArrayList<>();
-        }
-        List<Long> videoIds = new ArrayList<>();
-        for (Record record : records) {
-            Long contentId = Long.valueOf(record.getString(0));
-            if (contentId != null) {
-                videoIds.add(contentId);
-            }
-        }
-        return videoIds;
+        return videoDeconstructResultMapperExt.selectVideoIdsBySourcePaged("result_json", offset, pageSize);
     }
 
     /**
      * AIGC 来源视频向量化任务
-     * 从 AIGC API(id=46)拉取视频列表,经过向量存在性检查和审核过滤后
-     * 调用 detail 接口获取解构详情(dataContent),提取选题文本并向量化写入 Redis
+     * 从本地数据库读取 aigc_deconstruct 来源的解构结果,
+     * 经过向量存在性检查和审核过滤后,提取文本并向量化
      *
      * @param param 参数
      * @return 执行结果
@@ -563,54 +544,48 @@ public class VideoVectorJob {
                 return ReturnT.SUCCESS;
             }
 
-            // 2. 从 AIGC API 获取任务输入列表(bizUniqueId 为视频 ID),循环所有配置的任务ID
-            Map<Long, Long> videoIdToTaskInstanceId = new HashMap<>();
-            for (Integer taskId : aigcDeconstructTaskIds) {
-                List<AigcApiService.AigcTaskInput> taskInputList = aigcApiService.getTaskInputList(taskId);
-                if (CollectionUtils.isEmpty(taskInputList)) {
-                    log.info("AIGC API taskId={} 未返回任务输入数据", taskId);
-                    continue;
+            // 2. 从本地DB分页查询 aigc_deconstruct 来源的 videoId
+            AtomicInteger totalSuccessCount = new AtomicInteger(0);
+            AtomicInteger totalFailCount = new AtomicInteger(0);
+            int pageNum = 0;
+
+            while (true) {
+                int offset = pageNum * VectorConstants.PAGE_SIZE;
+                List<Long> videoIds = videoDeconstructResultMapperExt.selectVideoIdsBySourcePaged(
+                        "aigc_deconstruct", offset, VectorConstants.PAGE_SIZE);
+                if (CollectionUtils.isEmpty(videoIds)) {
+                    log.info("第 {} 页没有查询到数据,分页查询结束", pageNum);
+                    break;
                 }
-                log.info("taskId={} 获取到 {} 条 AIGC 任务输入数据", taskId, taskInputList.size());
+                log.info("第 {} 页查询到 {} 个 videoId", pageNum, videoIds.size());
 
-                // 构建 videoId -> taskInstanceId 映射
-                for (AigcApiService.AigcTaskInput input : taskInputList) {
-                    try {
-                        Long videoId = Long.parseLong(input.getBizUniqueId());
-                        videoIdToTaskInstanceId.put(videoId, input.getTaskInstanceId());
-                    } catch (NumberFormatException e) {
-                        log.warn("bizUniqueId 格式非法,跳过: {}", input.getBizUniqueId());
+                // 3. 审核过滤
+                List<Long> auditPassedIds = filterAuditPassedIds(videoIds);
+                if (auditPassedIds.isEmpty()) {
+                    log.info("第 {} 页所有视频均未通过审核,跳过", pageNum);
+                    if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                        break;
                     }
+                    pageNum++;
+                    continue;
                 }
-            }
-            if (videoIdToTaskInstanceId.isEmpty()) {
-                log.info("无有效 videoId,任务结束");
-                return ReturnT.SUCCESS;
-            }
-            List<Long> allVideoIds = new ArrayList<>(videoIdToTaskInstanceId.keySet());
-            log.info("共 {} 个有效 videoId(来自 {} 个任务)", allVideoIds.size(), aigcDeconstructTaskIds.size());
-
-            // 4. 先进行审核过滤(只过滤一次,避免在 config 循环内重复调用)
-            List<Long> auditPassedIds = filterAuditPassedIds(allVideoIds);
-            if (auditPassedIds.isEmpty()) {
-                log.info("所有视频均未通过审核,任务结束");
-                return ReturnT.SUCCESS;
-            }
-            log.info("审核通过 {} 个视频", auditPassedIds.size());
+                log.info("第 {} 页审核通过 {} 个视频", pageNum, auditPassedIds.size());
 
-            AtomicInteger totalSuccessCount = new AtomicInteger(0);
-            AtomicInteger totalFailCount = new AtomicInteger(0);
+                // 4. 对每个配置并发处理
+                ExecutorService configExecutor = Executors.newFixedThreadPool(configs.size());
+                List<Future<?>> configFutures = new ArrayList<>();
+                for (DeconstructVectorConfig config : configs) {
+                    configFutures.add(configExecutor.submit(() ->
+                            processConfigForAigc(config, auditPassedIds, totalSuccessCount, totalFailCount)
+                    ));
+                }
+                awaitAndShutdown(configFutures, configExecutor, 30, "配置并发");
 
-            // 5. 对每个配置并发处理
-            ExecutorService configExecutor = Executors.newFixedThreadPool(configs.size());
-            List<Future<?>> configFutures = new ArrayList<>();
-            for (DeconstructVectorConfig config : configs) {
-                configFutures.add(configExecutor.submit(() ->
-                        processConfigForAigc(config, auditPassedIds, videoIdToTaskInstanceId,
-                                totalSuccessCount, totalFailCount)
-                ));
+                if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                    break;
+                }
+                pageNum++;
             }
-            awaitAndShutdown(configFutures, configExecutor, 30, "配置并发");
 
             log.info("AIGC 来源视频向量化任务完成,总成功: {}, 总失败: {}", totalSuccessCount.get(), totalFailCount.get());
             return ReturnT.SUCCESS;
@@ -622,10 +597,9 @@ public class VideoVectorJob {
 
     /**
      * 处理单个配置下的 AIGC 视频向量化
-     * 包含:空text检查清理、过滤已有向量、并发调用detail接口并向量化
+     * 包含:空text检查清理、过滤已有向量、从本地DB查询解构结果并向量化
      */
     private void processConfigForAigc(DeconstructVectorConfig config, List<Long> auditPassedIds,
-                                      Map<Long, Long> videoIdToTaskInstanceId,
                                       AtomicInteger totalSuccessCount, AtomicInteger totalFailCount) {
         String configCode = config.getConfigCode();
         try {
@@ -643,62 +617,61 @@ public class VideoVectorJob {
             }
             log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
 
-            // 3. 并发调用 detail 接口,提取文本并向量化
-            ExecutorService detailExecutor = Executors.newFixedThreadPool(VectorConstants.AIGC_DETAIL_PARALLELISM);
-            List<Future<?>> detailFutures = new ArrayList<>();
-            for (Long videoId : needProcessIds) {
-                detailFutures.add(detailExecutor.submit(() ->
-                        processAigcSingleVideo(videoId, config, videoIdToTaskInstanceId,
-                                totalSuccessCount, totalFailCount)
-                ));
-            }
-            awaitAndShutdown(detailFutures, detailExecutor, 30, "AIGC detail");
-        } catch (Exception e) {
-            log.error("配置 {} 处理异常: {}", configCode, e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 处理单个 AIGC 视频的向量化
-     */
-    private void processAigcSingleVideo(Long videoId, DeconstructVectorConfig config,
-                                        Map<Long, Long> videoIdToTaskInstanceId,
-                                        AtomicInteger successCount, AtomicInteger failCount) {
-        String configCode = config.getConfigCode();
-        try {
-            Long taskInstanceId = videoIdToTaskInstanceId.get(videoId);
-            if (taskInstanceId == null) {
-                log.warn("videoId={} 无对应 taskInstanceId,跳过", videoId);
-                failCount.incrementAndGet();
-                return;
-            }
-
-            JSONObject dataContent = aigcApiService.getTaskCallbackDetail(taskInstanceId);
-            if (dataContent == null) {
-                log.warn("videoId={} taskInstanceId={} 获取 dataContent 失败,跳过", videoId, taskInstanceId);
-                failCount.incrementAndGet();
-                return;
-            }
-
-            // 尝试缓存 decode 结果
-            tryCacheDecodeResult(videoId, dataContent);
+            // 3. 从本地DB批量查询解构结果并并发embedding
+            ExecutorService embedExecutor = Executors.newFixedThreadPool(VectorConstants.EMBEDDING_PARALLELISM);
+            Semaphore inFlightLimiter = new Semaphore(VectorConstants.MAX_EMBEDDING_IN_FLIGHT);
+            List<Future<?>> futures = new ArrayList<>();
 
-            List<String> texts = extractTextsFromDataContent(dataContent, config);
-            if (CollectionUtils.isEmpty(texts)) {
-                log.info("videoId={} 配置 {} 未提取到选题文本,跳过", videoId, configCode);
-                failCount.incrementAndGet();
-                return;
-            }
+            try {
+                for (List<Long> batchIds : Lists.partition(needProcessIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
+                    List<VideoDeconstructResult> results = videoDeconstructResultMapperExt
+                            .selectResultsByVideoIds("aigc_deconstruct", batchIds);
+                    for (VideoDeconstructResult r : results) {
+                        if (!StringUtils.hasText(r.getResult())) {
+                            continue;
+                        }
+                        Long videoId = r.getVideoId();
+                        JSONObject dataContent = JSON.parseObject(r.getResult());
+                        if (dataContent == null) {
+                            continue;
+                        }
+                        // 尝试缓存 decode 结果
+                        tryCacheDecodeResult(videoId, dataContent);
 
-            int storeCount = vectorizeAndStore(config, videoId, texts);
-            if (storeCount > 0) {
-                successCount.incrementAndGet();
-            } else {
-                failCount.incrementAndGet();
+                        try {
+                            inFlightLimiter.acquire();
+                        } catch (InterruptedException e) {
+                            Thread.currentThread().interrupt();
+                            return;
+                        }
+                        futures.add(embedExecutor.submit(() -> {
+                            try {
+                                List<String> texts = extractTextsFromDataContent(dataContent, config);
+                                if (CollectionUtils.isEmpty(texts)) {
+                                    log.info("videoId={} 配置 {} 未提取到选题文本,跳过", videoId, configCode);
+                                    totalFailCount.incrementAndGet();
+                                    return;
+                                }
+                                int storeCount = vectorizeAndStore(config, videoId, texts);
+                                if (storeCount > 0) {
+                                    totalSuccessCount.incrementAndGet();
+                                } else {
+                                    totalFailCount.incrementAndGet();
+                                }
+                            } catch (Exception e) {
+                                log.error("处理 videoId={} 配置 {} 时发生异常: {}", videoId, configCode, e.getMessage(), e);
+                                totalFailCount.incrementAndGet();
+                            } finally {
+                                inFlightLimiter.release();
+                            }
+                        }));
+                    }
+                }
+            } finally {
+                awaitAndShutdown(futures, embedExecutor, 30, "AIGC embedding");
             }
         } catch (Exception e) {
-            log.error("处理 videoId={} 配置 {} 时发生异常: {}", videoId, configCode, e.getMessage(), e);
-            failCount.incrementAndGet();
+            log.error("配置 {} 处理异常: {}", configCode, e.getMessage(), e);
         }
     }
 
@@ -1013,8 +986,7 @@ public class VideoVectorJob {
     }
 
     /**
-     * 分页查询 result_log 视频池中的 videoId
-     * 条件:播放量>10000 且有 result_log 记录(dt > 20240001)
+     * 分页查询 result_log 视频池中的 videoId(从本地解构结果表查询)
      *
      * @param pageNum  页码(从0开始)
      * @param pageSize 每页数量
@@ -1022,33 +994,12 @@ public class VideoVectorJob {
      */
     private List<Long> queryResultLogVideoIdsByPage(int pageNum, int pageSize) {
         int offset = pageNum * pageSize;
-        String sql = String.format(
-                "SELECT v.id " +
-                        "FROM videoods.wx_video v " +
-                        "INNER JOIN loghubods.result_log r " +
-                        "ON v.id = r.video_id " +
-                        "WHERE v.play_count_total > 10000 " +
-                        "AND r.dt > 20240001 " +
-                        "ORDER BY v.id " +
-                        "LIMIT %d, %d;",
-                offset, pageSize);
-        List<Record> records = OdpsUtil.getOdpsData(sql);
-        if (records == null || records.isEmpty()) {
-            return new ArrayList<>();
-        }
-        List<Long> videoIds = new ArrayList<>();
-        for (Record record : records) {
-            Long videoId = Long.valueOf(record.getString(0));
-            if (videoId != null) {
-                videoIds.add(videoId);
-            }
-        }
-        return videoIds;
+        return videoDeconstructResultMapperExt.selectVideoIdsBySourcePaged("result_log", offset, pageSize);
     }
 
     /**
      * 处理单个配置下的 result_log 视频向量化
-     * 包含:空text检查清理、过滤已有向量、流式查询ODPS并并发embedding
+     * 包含:空text检查清理、过滤已有向量、从本地DB查询解构结果并并发embedding
      */
     private void processConfigForResultLog(DeconstructVectorConfig config, List<Long> auditPassedIds,
                                            AtomicInteger totalSuccessCount, AtomicInteger totalFailCount) {
@@ -1068,22 +1019,23 @@ public class VideoVectorJob {
             }
             log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
 
-            // 3. 流式查询并并发embedding
+            // 3. 从本地DB批量查询解构结果并并发embedding
             ExecutorService embedExecutor = Executors.newFixedThreadPool(VectorConstants.EMBEDDING_PARALLELISM);
             Semaphore inFlightLimiter = new Semaphore(VectorConstants.MAX_EMBEDDING_IN_FLIGHT);
             List<Future<?>> futures = new ArrayList<>();
 
             try {
                 for (List<Long> batchIds : Lists.partition(needProcessIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
-                    String idsStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
-                    String sql = String.format(
-                            "SELECT video_id, data FROM loghubods.result_log " +
-                                    "WHERE video_id IN (%s) AND dt > 20240001;", idsStr);
-
-                    OdpsUtil.getOdpsDataStream(sql, record ->
-                            submitEmbeddingTask(record, config, embedExecutor, inFlightLimiter, futures,
-                                    totalSuccessCount, totalFailCount, "result_log")
-                    );
+                    List<VideoDeconstructResult> results = videoDeconstructResultMapperExt
+                            .selectResultsByVideoIds("result_log", batchIds);
+                    for (VideoDeconstructResult r : results) {
+                        if (!StringUtils.hasText(r.getResult())) {
+                            continue;
+                        }
+                        submitLocalEmbeddingTask(r.getVideoId(), r.getResult(), config,
+                                embedExecutor, inFlightLimiter, futures,
+                                totalSuccessCount, totalFailCount, "result_log");
+                    }
                 }
             } finally {
                 awaitAndShutdown(futures, embedExecutor, 30, "embedding");
@@ -1197,6 +1149,276 @@ public class VideoVectorJob {
         return false;
     }
 
+    // ========================== 解构结果同步任务 ==========================
+
+    /**
+     * 同步视频解构结果到本地数据库
+     * 从三种来源(result_json / aigc_deconstruct / result_log)拉取解构数据,
+     * 检查该来源是否已有记录,无则插入
+     *
+     * @param param 参数
+     * @return 执行结果
+     */
+    @XxlJob("syncDeconstructResultJob")
+    public ReturnT<String> syncDeconstructResultJob(String param) {
+        log.info("开始执行解构结果同步任务, param: {}", param);
+
+        AtomicInteger totalInsertCount = new AtomicInteger(0);
+        AtomicInteger totalSkipCount = new AtomicInteger(0);
+
+        try {
+            // 三个来源并发同步
+            ExecutorService syncExecutor = Executors.newFixedThreadPool(3);
+            List<Future<?>> syncFutures = new ArrayList<>();
+
+            syncFutures.add(syncExecutor.submit(() -> syncResultJsonSource(totalInsertCount, totalSkipCount)));
+            syncFutures.add(syncExecutor.submit(() -> syncAigcDeconstructSource(totalInsertCount, totalSkipCount)));
+            syncFutures.add(syncExecutor.submit(() -> syncResultLogSource(totalInsertCount, totalSkipCount)));
+
+            awaitAndShutdown(syncFutures, syncExecutor, 60, "解构结果同步");
+
+            log.info("解构结果同步任务完成,总插入: {}, 总跳过: {}", totalInsertCount.get(), totalSkipCount.get());
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("解构结果同步任务执行失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务执行失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 同步 result_json 来源:从 ODPS content_profile 拉取 raw_result
+     */
+    private void syncResultJsonSource(AtomicInteger insertCount, AtomicInteger skipCount) {
+        log.info("开始同步 result_json 来源");
+        int pageNum = 0;
+
+        while (true) {
+            List<Long> videoIds = queryOdpsVideoIdsByPage(pageNum, VectorConstants.PAGE_SIZE);
+            if (CollectionUtils.isEmpty(videoIds)) {
+                break;
+            }
+
+            // 检查哪些已存在
+            Set<Long> existingIds = new HashSet<>(videoDeconstructResultMapperExt.selectExistingVideoIds("result_json", videoIds));
+            List<Long> needSyncIds = videoIds.stream()
+                    .filter(id -> !existingIds.contains(id))
+                    .collect(Collectors.toList());
+            skipCount.addAndGet(existingIds.size());
+
+            if (!needSyncIds.isEmpty()) {
+                // 分批查询 ODPS 并插入
+                for (List<Long> batchIds : Lists.partition(needSyncIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
+                    String idsStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
+                    String sql = String.format(
+                            "SELECT content_id, raw_result FROM videoods.content_profile " +
+                                    "WHERE status = 3 AND is_deleted = 0 AND content_id IN (%s);", idsStr);
+
+                    List<VideoDeconstructResult> batch = new ArrayList<>();
+                    OdpsUtil.getOdpsDataStream(sql, record -> {
+                        String videoIdStr = record.getString(0);
+                        String rawResult = record.getString(1);
+                        if (StringUtils.hasText(videoIdStr) && StringUtils.hasText(rawResult)
+                                && !"\\N".equals(rawResult)) {
+                            VideoDeconstructResult r = new VideoDeconstructResult();
+                            r.setVideoId(Long.valueOf(videoIdStr));
+                            r.setSource("result_json");
+                            r.setResult(rawResult);
+                            batch.add(r);
+                            if (batch.size() >= 200) {
+                                insertCount.addAndGet(videoDeconstructResultMapperExt.batchInsertIgnore(new ArrayList<>(batch)));
+                                batch.clear();
+                            }
+                        }
+                    });
+                    if (!batch.isEmpty()) {
+                        insertCount.addAndGet(videoDeconstructResultMapperExt.batchInsertIgnore(batch));
+                    }
+                }
+            }
+
+            if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                break;
+            }
+            pageNum++;
+        }
+        log.info("result_json 来源同步完成");
+    }
+
+    /**
+     * 同步 aigc_deconstruct 来源:从 AIGC API 拉取 dataContent
+     */
+    private void syncAigcDeconstructSource(AtomicInteger insertCount, AtomicInteger skipCount) {
+        log.info("开始同步 aigc_deconstruct 来源");
+
+        // 从 AIGC API 获取任务输入列表
+        Map<Long, Long> videoIdToTaskInstanceId = new HashMap<>();
+        for (Integer taskId : aigcDeconstructTaskIds) {
+            List<AigcApiService.AigcTaskInput> taskInputList = aigcApiService.getTaskInputList(taskId);
+            if (CollectionUtils.isEmpty(taskInputList)) {
+                continue;
+            }
+            for (AigcApiService.AigcTaskInput input : taskInputList) {
+                try {
+                    Long videoId = Long.parseLong(input.getBizUniqueId());
+                    videoIdToTaskInstanceId.put(videoId, input.getTaskInstanceId());
+                } catch (NumberFormatException e) {
+                    // 跳过非法格式
+                }
+            }
+        }
+
+        if (videoIdToTaskInstanceId.isEmpty()) {
+            log.info("aigc_deconstruct 来源无有效数据");
+            return;
+        }
+
+        List<Long> allVideoIds = new ArrayList<>(videoIdToTaskInstanceId.keySet());
+        // 分批检查已存在的
+        for (List<Long> batchIds : Lists.partition(allVideoIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
+            Set<Long> existingIds = new HashSet<>(videoDeconstructResultMapperExt.selectExistingVideoIds("aigc_deconstruct", batchIds));
+            skipCount.addAndGet(existingIds.size());
+
+            List<Long> needSyncIds = batchIds.stream()
+                    .filter(id -> !existingIds.contains(id))
+                    .collect(Collectors.toList());
+
+            // 并发调用 detail 接口
+            ExecutorService executor = Executors.newFixedThreadPool(VectorConstants.AIGC_DETAIL_PARALLELISM);
+            List<Future<?>> futures = new ArrayList<>();
+            List<VideoDeconstructResult> batch = Collections.synchronizedList(new ArrayList<>());
+
+            for (Long videoId : needSyncIds) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        Long taskInstanceId = videoIdToTaskInstanceId.get(videoId);
+                        if (taskInstanceId == null) return;
+                        JSONObject dataContent = aigcApiService.getTaskCallbackDetail(taskInstanceId);
+                        if (dataContent != null) {
+                            VideoDeconstructResult r = new VideoDeconstructResult();
+                            r.setVideoId(videoId);
+                            r.setSource("aigc_deconstruct");
+                            r.setResult(dataContent.toJSONString());
+                            batch.add(r);
+                        }
+                    } catch (Exception e) {
+                        log.warn("同步 aigc videoId={} 失败: {}", videoId, e.getMessage());
+                    }
+                }));
+            }
+            awaitAndShutdown(futures, executor, 30, "aigc同步");
+
+            // 批量插入
+            if (!batch.isEmpty()) {
+                for (List<VideoDeconstructResult> subBatch : Lists.partition(batch, 200)) {
+                    insertCount.addAndGet(videoDeconstructResultMapperExt.batchInsertIgnore(subBatch));
+                }
+            }
+        }
+        log.info("aigc_deconstruct 来源同步完成");
+    }
+
+    /**
+     * 同步 result_log 来源:从 ODPS result_log 拉取 data
+     */
+    private void syncResultLogSource(AtomicInteger insertCount, AtomicInteger skipCount) {
+        log.info("开始同步 result_log 来源");
+        int pageNum = 0;
+
+        while (true) {
+            List<Long> videoIds = queryOdpsResultLogVideoIdsByPage(pageNum, VectorConstants.PAGE_SIZE);
+            if (CollectionUtils.isEmpty(videoIds)) {
+                break;
+            }
+
+            // 检查哪些已存在
+            Set<Long> existingIds = new HashSet<>(videoDeconstructResultMapperExt.selectExistingVideoIds("result_log", videoIds));
+            List<Long> needSyncIds = videoIds.stream()
+                    .filter(id -> !existingIds.contains(id))
+                    .collect(Collectors.toList());
+            skipCount.addAndGet(existingIds.size());
+
+            if (!needSyncIds.isEmpty()) {
+                for (List<Long> batchIds : Lists.partition(needSyncIds, VectorConstants.ODPS_IN_BATCH_SIZE)) {
+                    String idsStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
+                    String sql = String.format(
+                            "SELECT video_id, data FROM loghubods.result_log " +
+                                    "WHERE video_id IN (%s) AND dt > 20240001;", idsStr);
+
+                    List<VideoDeconstructResult> batch = new ArrayList<>();
+                    OdpsUtil.getOdpsDataStream(sql, record -> {
+                        String videoIdStr = record.getString(0);
+                        String data = record.getString(1);
+                        if (StringUtils.hasText(videoIdStr) && StringUtils.hasText(data)
+                                && !"\\N".equals(data)) {
+                            VideoDeconstructResult r = new VideoDeconstructResult();
+                            r.setVideoId(Long.valueOf(videoIdStr));
+                            r.setSource("result_log");
+                            r.setResult(data);
+                            batch.add(r);
+                            if (batch.size() >= 200) {
+                                insertCount.addAndGet(videoDeconstructResultMapperExt.batchInsertIgnore(new ArrayList<>(batch)));
+                                batch.clear();
+                            }
+                        }
+                    });
+                    if (!batch.isEmpty()) {
+                        insertCount.addAndGet(videoDeconstructResultMapperExt.batchInsertIgnore(batch));
+                    }
+                }
+            }
+
+            if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                break;
+            }
+            pageNum++;
+        }
+        log.info("result_log 来源同步完成");
+    }
+
+    // ========================== ODPS 查询(仅同步任务使用) ==========================
+
+    /**
+     * 从 ODPS 分页查询 content_profile 的 videoId(仅同步任务使用)
+     */
+    private List<Long> queryOdpsVideoIdsByPage(int pageNum, int pageSize) {
+        int offset = pageNum * pageSize;
+        String sql = String.format(
+                "SELECT content_id FROM videoods.content_profile " +
+                        "WHERE status = 3 AND is_deleted = 0 ORDER BY content_id LIMIT %d, %d;",
+                offset, pageSize);
+        List<com.aliyun.odps.data.Record> records = OdpsUtil.getOdpsData(sql);
+        if (records == null || records.isEmpty()) {
+            return new ArrayList<>();
+        }
+        List<Long> videoIds = new ArrayList<>();
+        for (com.aliyun.odps.data.Record record : records) {
+            videoIds.add(Long.valueOf(record.getString(0)));
+        }
+        return videoIds;
+    }
+
+    /**
+     * 从 ODPS 分页查询 result_log 视频池的 videoId(仅同步任务使用)
+     */
+    private List<Long> queryOdpsResultLogVideoIdsByPage(int pageNum, int pageSize) {
+        int offset = pageNum * pageSize;
+        String sql = String.format(
+                "SELECT v.id FROM videoods.wx_video v " +
+                        "INNER JOIN loghubods.result_log r ON v.id = r.video_id " +
+                        "WHERE v.play_count_total > 10000 AND r.dt > 20240001 " +
+                        "ORDER BY v.id LIMIT %d, %d;",
+                offset, pageSize);
+        List<com.aliyun.odps.data.Record> records = OdpsUtil.getOdpsData(sql);
+        if (records == null || records.isEmpty()) {
+            return new ArrayList<>();
+        }
+        List<Long> videoIds = new ArrayList<>();
+        for (com.aliyun.odps.data.Record record : records) {
+            videoIds.add(Long.valueOf(record.getString(0)));
+        }
+        return videoIds;
+    }
+
     // ========================== Decode 缓存相关方法 ==========================
 
     /**

+ 244 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoDeconstructResult.java

@@ -0,0 +1,244 @@
+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_deconstruct_result
+ */
+public class VideoDeconstructResult {
+    /**
+     * Database Column Remarks:
+     *   主键ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.id
+     *
+     * @mbg.generated
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   视频ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.video_id
+     *
+     * @mbg.generated
+     */
+    private Long videoId;
+
+    /**
+     * Database Column Remarks:
+     *   解构来源: result_json / aigc_deconstruct / result_log
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.source
+     *
+     * @mbg.generated
+     */
+    private String source;
+
+    /**
+     * Database Column Remarks:
+     *   解构结果 JSON 完整内容
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.result
+     *
+     * @mbg.generated
+     */
+    private String result;
+
+    /**
+     * Database Column Remarks:
+     *   首次写入时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.create_time
+     *
+     * @mbg.generated
+     */
+    private Date createTime;
+
+    /**
+     * Database Column Remarks:
+     *   最近更新时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_deconstruct_result.update_time
+     *
+     * @mbg.generated
+     */
+    private Date updateTime;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_deconstruct_result.id
+     *
+     * @return the value of video_deconstruct_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_deconstruct_result.id
+     *
+     * @param id the value for video_deconstruct_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_deconstruct_result.video_id
+     *
+     * @return the value of video_deconstruct_result.video_id
+     *
+     * @mbg.generated
+     */
+    public Long getVideoId() {
+        return videoId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_deconstruct_result.video_id
+     *
+     * @param videoId the value for video_deconstruct_result.video_id
+     *
+     * @mbg.generated
+     */
+    public void setVideoId(Long videoId) {
+        this.videoId = videoId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_deconstruct_result.source
+     *
+     * @return the value of video_deconstruct_result.source
+     *
+     * @mbg.generated
+     */
+    public String getSource() {
+        return source;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_deconstruct_result.source
+     *
+     * @param source the value for video_deconstruct_result.source
+     *
+     * @mbg.generated
+     */
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_deconstruct_result.result
+     *
+     * @return the value of video_deconstruct_result.result
+     *
+     * @mbg.generated
+     */
+    public String getResult() {
+        return result;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_deconstruct_result.result
+     *
+     * @param result the value for video_deconstruct_result.result
+     *
+     * @mbg.generated
+     */
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_deconstruct_result.create_time
+     *
+     * @return the value of video_deconstruct_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_deconstruct_result.create_time
+     *
+     * @param createTime the value for video_deconstruct_result.create_time
+     *
+     * @mbg.generated
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_deconstruct_result.update_time
+     *
+     * @return the value of video_deconstruct_result.update_time
+     *
+     * @mbg.generated
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_deconstruct_result.update_time
+     *
+     * @param updateTime the value for video_deconstruct_result.update_time
+     *
+     * @mbg.generated
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_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(", videoId=").append(videoId);
+        sb.append(", source=").append(source);
+        sb.append(", result=").append(result);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 683 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoDeconstructResultExample.java

@@ -0,0 +1,683 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class VideoDeconstructResultExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    public VideoDeconstructResultExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_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_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_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_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_result
+     *
+     * @mbg.generated
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_deconstruct_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_deconstruct_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_deconstruct_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_deconstruct_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_deconstruct_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_deconstruct_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 andVideoIdIsNull() {
+            addCriterion("video_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdIsNotNull() {
+            addCriterion("video_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdEqualTo(Long value) {
+            addCriterion("video_id =", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdNotEqualTo(Long value) {
+            addCriterion("video_id <>", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdGreaterThan(Long value) {
+            addCriterion("video_id >", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("video_id >=", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdLessThan(Long value) {
+            addCriterion("video_id <", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdLessThanOrEqualTo(Long value) {
+            addCriterion("video_id <=", value, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdIn(List<Long> values) {
+            addCriterion("video_id in", values, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdNotIn(List<Long> values) {
+            addCriterion("video_id not in", values, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdBetween(Long value1, Long value2) {
+            addCriterion("video_id between", value1, value2, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoIdNotBetween(Long value1, Long value2) {
+            addCriterion("video_id not between", value1, value2, "videoId");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceIsNull() {
+            addCriterion("\"source\" is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceIsNotNull() {
+            addCriterion("\"source\" is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceEqualTo(String value) {
+            addCriterion("\"source\" =", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceNotEqualTo(String value) {
+            addCriterion("\"source\" <>", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceGreaterThan(String value) {
+            addCriterion("\"source\" >", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceGreaterThanOrEqualTo(String value) {
+            addCriterion("\"source\" >=", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceLessThan(String value) {
+            addCriterion("\"source\" <", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceLessThanOrEqualTo(String value) {
+            addCriterion("\"source\" <=", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceLike(String value) {
+            addCriterion("\"source\" like", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceNotLike(String value) {
+            addCriterion("\"source\" not like", value, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceIn(List<String> values) {
+            addCriterion("\"source\" in", values, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceNotIn(List<String> values) {
+            addCriterion("\"source\" not in", values, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceBetween(String value1, String value2) {
+            addCriterion("\"source\" between", value1, value2, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceNotBetween(String value1, String value2) {
+            addCriterion("\"source\" not between", value1, value2, "source");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultIsNull() {
+            addCriterion("\"result\" is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultIsNotNull() {
+            addCriterion("\"result\" is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultEqualTo(String value) {
+            addCriterion("\"result\" =", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultNotEqualTo(String value) {
+            addCriterion("\"result\" <>", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultGreaterThan(String value) {
+            addCriterion("\"result\" >", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultGreaterThanOrEqualTo(String value) {
+            addCriterion("\"result\" >=", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultLessThan(String value) {
+            addCriterion("\"result\" <", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultLessThanOrEqualTo(String value) {
+            addCriterion("\"result\" <=", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultLike(String value) {
+            addCriterion("\"result\" like", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultNotLike(String value) {
+            addCriterion("\"result\" not like", value, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultIn(List<String> values) {
+            addCriterion("\"result\" in", values, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultNotIn(List<String> values) {
+            addCriterion("\"result\" not in", values, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultBetween(String value1, String value2) {
+            addCriterion("\"result\" between", value1, value2, "result");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultNotBetween(String value1, String value2) {
+            addCriterion("\"result\" not between", value1, value2, "result");
+            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;
+        }
+
+        public Criteria andUpdateTimeIsNull() {
+            addCriterion("update_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIsNotNull() {
+            addCriterion("update_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeEqualTo(Date value) {
+            addCriterion("update_time =", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotEqualTo(Date value) {
+            addCriterion("update_time <>", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThan(Date value) {
+            addCriterion("update_time >", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("update_time >=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThan(Date value) {
+            addCriterion("update_time <", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("update_time <=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIn(List<Date> values) {
+            addCriterion("update_time in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotIn(List<Date> values) {
+            addCriterion("update_time not in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeBetween(Date value1, Date value2) {
+            addCriterion("update_time between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotBetween(Date value1, Date value2) {
+            addCriterion("update_time not between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table video_deconstruct_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_deconstruct_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);
+        }
+    }
+}

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

@@ -77,10 +77,13 @@
 <!--        <table tableName="deconstruct_vector_config" domainObjectName="DeconstructVectorConfig" alias="">-->
 <!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
 <!--        </table>-->
-        <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_config" domainObjectName="ChannelDemandMatchConfig" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--        </table>-->
+<!--        <table tableName="channel_demand_match_result" domainObjectName="ChannelDemandMatchResult" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--        </table>-->
+        <table tableName="video_deconstruct_result" domainObjectName="VideoDeconstructResult" alias="">
             <generatedKey column="id" sqlStatement="JDBC" identity="true"/>
         </table>
     </context>

+ 280 - 0
core/src/main/resources/mapper/pgVector/VideoDeconstructResultMapper.xml

@@ -0,0 +1,280 @@
+<?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.VideoDeconstructResultMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="video_id" jdbcType="BIGINT" property="videoId" />
+    <result column="source" jdbcType="VARCHAR" property="source" />
+    <result column="result" jdbcType="VARCHAR" property="result" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+  </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, video_id, "source", "result", create_time, update_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResultExample" 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_deconstruct_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_deconstruct_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_deconstruct_result
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResultExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    delete from video_deconstruct_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.VideoDeconstructResult" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_deconstruct_result (video_id, "source", "result", 
+      create_time, update_time)
+    values (#{videoId,jdbcType=BIGINT}, #{source,jdbcType=VARCHAR}, #{result,jdbcType=VARCHAR}, 
+      #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP})
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResult" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_deconstruct_result
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="videoId != null">
+        video_id,
+      </if>
+      <if test="source != null">
+        "source",
+      </if>
+      <if test="result != null">
+        "result",
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="videoId != null">
+        #{videoId,jdbcType=BIGINT},
+      </if>
+      <if test="source != null">
+        #{source,jdbcType=VARCHAR},
+      </if>
+      <if test="result != null">
+        #{result,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResultExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    select count(*) from video_deconstruct_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_deconstruct_result
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.videoId != null">
+        video_id = #{record.videoId,jdbcType=BIGINT},
+      </if>
+      <if test="record.source != null">
+        "source" = #{record.source,jdbcType=VARCHAR},
+      </if>
+      <if test="record.result != null">
+        "result" = #{record.result,jdbcType=VARCHAR},
+      </if>
+      <if test="record.createTime != null">
+        create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.updateTime != null">
+        update_time = #{record.updateTime,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_deconstruct_result
+    set id = #{record.id,jdbcType=BIGINT},
+      video_id = #{record.videoId,jdbcType=BIGINT},
+      "source" = #{record.source,jdbcType=VARCHAR},
+      "result" = #{record.result,jdbcType=VARCHAR},
+      create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      update_time = #{record.updateTime,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.VideoDeconstructResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_deconstruct_result
+    <set>
+      <if test="videoId != null">
+        video_id = #{videoId,jdbcType=BIGINT},
+      </if>
+      <if test="source != null">
+        "source" = #{source,jdbcType=VARCHAR},
+      </if>
+      <if test="result != null">
+        "result" = #{result,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResult">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_deconstruct_result
+    set video_id = #{videoId,jdbcType=BIGINT},
+      "source" = #{source,jdbcType=VARCHAR},
+      "result" = #{result,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 55 - 0
core/src/main/resources/mapper/pgVector/ext/VideoDeconstructResultMapperExt.xml

@@ -0,0 +1,55 @@
+<?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.VideoDeconstructResultMapperExt">
+
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.VideoDeconstructResult">
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="video_id" jdbcType="BIGINT" property="videoId" />
+    <result column="source" jdbcType="VARCHAR" property="source" />
+    <result column="result" jdbcType="VARCHAR" property="result" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+  </resultMap>
+
+  <!-- 查询指定来源下已存在的 videoId 列表 -->
+  <select id="selectExistingVideoIds" resultType="java.lang.Long">
+    SELECT video_id
+    FROM video_deconstruct_result
+    WHERE source = #{source}
+    AND video_id IN
+    <foreach collection="videoIds" item="id" open="(" separator="," close=")">
+      #{id}
+    </foreach>
+  </select>
+
+  <!-- 批量插入,冲突时忽略 -->
+  <insert id="batchInsertIgnore">
+    INSERT INTO video_deconstruct_result (video_id, source, result)
+    VALUES
+    <foreach collection="list" item="item" separator=",">
+      (#{item.videoId}, #{item.source}, #{item.result})
+    </foreach>
+    ON CONFLICT (video_id, source) DO NOTHING
+  </insert>
+
+  <!-- 分页查询指定来源的 videoId -->
+  <select id="selectVideoIdsBySourcePaged" resultType="java.lang.Long">
+    SELECT video_id
+    FROM video_deconstruct_result
+    WHERE source = #{source}
+    ORDER BY video_id
+    LIMIT #{limit} OFFSET #{offset}
+  </select>
+
+  <!-- 批量查询指定来源和 videoId 的解构结果 -->
+  <select id="selectResultsByVideoIds" resultMap="BaseResultMap">
+    SELECT video_id, source, result
+    FROM video_deconstruct_result
+    WHERE source = #{source}
+    AND video_id IN
+    <foreach collection="videoIds" item="id" open="(" separator="," close=")">
+      #{id}
+    </foreach>
+  </select>
+
+</mapper>