Przeglądaj źródła

merge: master 接入 pgvector + matchTopNVideo 强类型化

- Application.java: 取并集 — 移除 @MapperScan(已由各 DBConfig 分包配置), 移除 WebMvcConfigurer/CrosDomainAllowInterceptor(K8s/网关统一处理 CORS)
- VectorRecallTestServiceImpl.java:
  * DeconstructContent* → MysqlDeconstructContent* (类被 master 重命名)
  * matchTopNVideo 返回值改为 List<VideoMatchResult> 强类型(替代原 List<Object>+JSONObject 解析)
  * 移除多余的 toJSONObject 辅助方法
- 业务逻辑零改动,仅适配 master 重构后的类型签名
刘立冬 13 godzin temu
rodzic
commit
047fe4b6c1
76 zmienionych plików z 13613 dodań i 808 usunięć
  1. 31 0
      core/src/main/java/com/tzld/videoVector/common/constant/VectorConstants.java
  2. 62 0
      core/src/main/java/com/tzld/videoVector/config/db/PgVectorDBConfig.java
  3. 8 8
      core/src/main/java/com/tzld/videoVector/config/db/VideoVectorDBConfig.java
  4. 19 0
      core/src/main/java/com/tzld/videoVector/config/mybatis/PgVectorMybatisConfig.java
  5. 44 0
      core/src/main/java/com/tzld/videoVector/dao/generator/PgMybatisGeneratorMain.java
  6. 97 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ContentVectorMapper.java
  7. 96 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/DeconstructContentMapper.java
  8. 97 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/DeconstructVectorConfigMapper.java
  9. 96 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/VideoVectorMapper.java
  10. 61 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ext/ContentVectorMapperExt.java
  11. 20 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ext/DeconstructVectorConfigMapperExt.java
  12. 96 0
      core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ext/VideoVectorMapperExt.java
  13. 18 17
      core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructContentMapper.java
  14. 18 17
      core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructContentVectorMapper.java
  15. 14 14
      core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructVectorConfigMapper.java
  16. 418 0
      core/src/main/java/com/tzld/videoVector/job/DataMigrationJob.java
  17. 157 0
      core/src/main/java/com/tzld/videoVector/job/MaterialDeconstructCheckJob.java
  18. 96 0
      core/src/main/java/com/tzld/videoVector/job/MaterialVectorJob.java
  19. 719 298
      core/src/main/java/com/tzld/videoVector/job/VideoVectorJob.java
  20. 487 0
      core/src/main/java/com/tzld/videoVector/job/VideoVectorTextBackfillJob.java
  21. 31 1
      core/src/main/java/com/tzld/videoVector/model/entity/VideoMatch.java
  22. 27 0
      core/src/main/java/com/tzld/videoVector/model/param/MaterialMatchParam.java
  23. 36 0
      core/src/main/java/com/tzld/videoVector/model/param/MaterialSubmitParam.java
  24. 495 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/ContentVector.java
  25. 1153 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/ContentVectorExample.java
  26. 712 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructContent.java
  27. 1573 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructContentExample.java
  28. 640 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructVectorConfig.java
  29. 1383 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructVectorConfigExample.java
  30. 316 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoVector.java
  31. 803 0
      core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoVectorExample.java
  32. 1 1
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContent.java
  33. 2 2
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentExample.java
  34. 1 1
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentVector.java
  35. 2 2
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentVectorExample.java
  36. 1 1
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructVectorConfig.java
  37. 2 2
      core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructVectorConfigExample.java
  38. 41 0
      core/src/main/java/com/tzld/videoVector/model/vo/MaterialMatchResult.java
  39. 29 0
      core/src/main/java/com/tzld/videoVector/model/vo/VideoMatchResult.java
  40. 47 0
      core/src/main/java/com/tzld/videoVector/model/vo/VideoVectorSearchResult.java
  41. 1 1
      core/src/main/java/com/tzld/videoVector/service/EmbeddingService.java
  42. 32 0
      core/src/main/java/com/tzld/videoVector/service/MaterialSearchService.java
  43. 18 5
      core/src/main/java/com/tzld/videoVector/service/VectorStoreService.java
  44. 9 9
      core/src/main/java/com/tzld/videoVector/service/VectorizeService.java
  45. 10 1
      core/src/main/java/com/tzld/videoVector/service/VideoSearchService.java
  46. 1 1
      core/src/main/java/com/tzld/videoVector/service/impl/EmbeddingServiceImpl.java
  47. 470 0
      core/src/main/java/com/tzld/videoVector/service/impl/MaterialSearchServiceImpl.java
  48. 255 0
      core/src/main/java/com/tzld/videoVector/service/impl/PgVectorStoreServiceImpl.java
  49. 17 13
      core/src/main/java/com/tzld/videoVector/service/impl/RedisVectorStoreServiceImpl.java
  50. 57 182
      core/src/main/java/com/tzld/videoVector/service/impl/VectorizeServiceImpl.java
  51. 244 142
      core/src/main/java/com/tzld/videoVector/service/impl/VideoSearchServiceImpl.java
  52. 23 43
      core/src/main/java/com/tzld/videoVector/service/recall/impl/VectorRecallTestServiceImpl.java
  53. 246 0
      core/src/main/java/com/tzld/videoVector/util/VectorUtils.java
  54. 82 0
      core/src/main/resources/generator/mybatis-pgvector-generator-config.xml
  55. 2 2
      core/src/main/resources/generator/mybatis-vector-generator-config.xml
  56. 408 0
      core/src/main/resources/mapper/pgVector/ContentVectorMapper.xml
  57. 502 0
      core/src/main/resources/mapper/pgVector/DeconstructContentMapper.xml
  58. 471 0
      core/src/main/resources/mapper/pgVector/DeconstructVectorConfigMapper.xml
  59. 312 0
      core/src/main/resources/mapper/pgVector/VideoVectorMapper.xml
  60. 109 0
      core/src/main/resources/mapper/pgVector/ext/ContentVectorMapperExt.xml
  61. 27 0
      core/src/main/resources/mapper/pgVector/ext/DeconstructVectorConfigMapperExt.xml
  62. 112 0
      core/src/main/resources/mapper/pgVector/ext/VideoVectorMapperExt.xml
  63. 12 12
      core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructContentMapper.xml
  64. 12 12
      core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructContentVectorMapper.xml
  65. 9 9
      core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructVectorConfigMapper.xml
  66. 14 0
      pom.xml
  67. 3 2
      server/src/main/java/com/tzld/videoVector/Application.java
  68. 41 0
      server/src/main/java/com/tzld/videoVector/controller/MaterialController.java
  69. 9 5
      server/src/main/java/com/tzld/videoVector/controller/VideoSearchController.java
  70. 44 0
      server/src/main/java/com/tzld/videoVector/controller/XxlJobController.java
  71. 11 0
      server/src/main/resources/application-dev.yml
  72. 11 0
      server/src/main/resources/application-prod.yml
  73. 11 0
      server/src/main/resources/application-test.yml
  74. 2 5
      server/src/main/resources/application.yml
  75. 4 0
      server/src/main/resources/logback-spring.xml
  76. 73 0
      server/src/test/java/DataMigrationTest.java

+ 31 - 0
core/src/main/java/com/tzld/videoVector/common/constant/VectorConstants.java

@@ -0,0 +1,31 @@
+package com.tzld.videoVector.common.constant;
+
+/**
+ * 向量化相关常量
+ */
+public interface VectorConstants {
+
+    // ========================== 配置编码 ==========================
+
+    /** 默认配置编码(选题) */
+    String DEFAULT_CONFIG_CODE = "VIDEO_TOPIC";
+
+    /** configCode 传 "ALL" 表示搜索所有启用的向量化配置 */
+    String ALL_CONFIG_CODE = "ALL";
+
+    // ========================== Redis Key ==========================
+
+    /** 向量存储 Redis Key 前缀 */
+    String VECTOR_KEY_PREFIX = "video:vector:";
+
+    // ========================== 批处理参数 ==========================
+
+    /** 每页查询数量 */
+    int PAGE_SIZE = 1000;
+
+    /** 审核状态检查批次大小 */
+    int AUDIT_CHECK_BATCH_SIZE = 20;
+
+    /** 超时时间:1小时(毫秒) */
+    long TIMEOUT_MS = 60 * 60 * 1000L;
+}

+ 62 - 0
core/src/main/java/com/tzld/videoVector/config/db/PgVectorDBConfig.java

@@ -0,0 +1,62 @@
+package com.tzld.videoVector.config.db;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableTransactionManagement
+public class PgVectorDBConfig {
+
+    // 1. 配置 pgVector 数据源
+    @Bean(name = "pgVectorDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.pg-vector")
+    public DataSource pgVectorDataSource() {
+        return new HikariDataSource();
+    }
+
+    // 2. 配置 pgVector 专属 SqlSessionFactory(独立 Configuration,避免与 MySQL 数据源互相污染)
+    @Bean(name = "pgVectorSqlSessionFactory")
+    public SqlSessionFactory pgVectorSqlSessionFactory(
+            @Qualifier("pgVectorDataSource") DataSource pgVectorDataSource) throws Exception {
+        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+        sessionFactory.setDataSource(pgVectorDataSource);
+
+        // 指定 pgVector 模块的 mapper 文件路径
+        try {
+            org.springframework.core.io.Resource[] mapperResources =
+                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/pgVector/**/*.xml");
+            if (mapperResources != null && mapperResources.length > 0) {
+                sessionFactory.setMapperLocations(mapperResources);
+            }
+        } catch (Exception e) {
+            // 如果目录不存在或为空,不设置 mapper locations,应用仍可正常启动
+        }
+
+        // 每个数据源必须独立创建 Configuration,不能共享 MybatisProperties.getConfiguration()
+        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
+        configuration.setMapUnderscoreToCamelCase(true);
+        configuration.setUseGeneratedKeys(true);
+        sessionFactory.setConfiguration(configuration);
+        sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
+        return sessionFactory.getObject();
+    }
+
+    // 3. 配置 pgVector 事务管理器
+    @Bean(name = "pgVectorTransactionManager")
+    public PlatformTransactionManager pgVectorTransactionManager(
+            @Qualifier("pgVectorDataSource") DataSource pgVectorDataSource) {
+        return new DataSourceTransactionManager(pgVectorDataSource);
+    }
+
+}

+ 8 - 8
core/src/main/java/com/tzld/videoVector/config/db/VideoVectorDBConfig.java

@@ -3,7 +3,6 @@ package com.tzld.videoVector.config.db;
 import com.zaxxer.hikari.HikariDataSource;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.SqlSessionFactoryBean;
-import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
@@ -28,17 +27,15 @@ public class VideoVectorDBConfig {
         return new HikariDataSource(); // 使用 HikariCP 连接池
     }
 
-    // 2. 配置 videoVector 专属 SqlSessionFactory
+    // 2. 配置 videoVector 专属 SqlSessionFactory(独立 Configuration,避免与 PG 数据源互相污染)
     @Primary
     @Bean(name = "videoVectorSqlSessionFactory")
     public SqlSessionFactory videoVectorSqlSessionFactory(
-            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource,
-            MybatisProperties properties) throws Exception {
+            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource) throws Exception {
         SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
         sessionFactory.setDataSource(videoVectorDataSource); // 关联 videoVector 数据源
         
         // 关键:指定 videoVector 模块的 mapper 文件路径(隔离其他数据源)
-        // 使用 try-catch 避免空目录报错
         try {
             org.springframework.core.io.Resource[] mapperResources = 
                 new PathMatchingResourcePatternResolver().getResources("classpath:mapper/videoVector/**/*.xml");
@@ -46,12 +43,15 @@ public class VideoVectorDBConfig {
                 sessionFactory.setMapperLocations(mapperResources);
             }
         } catch (Exception e) {
-            // 如果目录不存在或为空,不设置 mapper  locations,应用仍可正常启动
-            // 后续添加 mapper 文件后重启即可生效
+            // 如果目录不存在或为空,不设置 mapper locations,应用仍可正常启动
         }
         
+        // 每个数据源必须独立创建 Configuration,不能共享 MybatisProperties.getConfiguration()
+        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
+        configuration.setMapUnderscoreToCamelCase(true);
+        configuration.setUseGeneratedKeys(true);
+        sessionFactory.setConfiguration(configuration);
         sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
-        sessionFactory.setConfiguration(properties.getConfiguration());
         return sessionFactory.getObject();
     }
 

+ 19 - 0
core/src/main/java/com/tzld/videoVector/config/mybatis/PgVectorMybatisConfig.java

@@ -0,0 +1,19 @@
+package com.tzld.videoVector.config.mybatis;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@MapperScan(basePackages = "com.tzld.videoVector.dao.mapper.pgVector",
+        sqlSessionFactoryRef = "pgVectorSqlSessionFactory")
+public class PgVectorMybatisConfig {
+
+    @Bean(name = "pgVectorSqlSessionTemplate")
+    public SqlSessionTemplate pgVectorSqlSessionTemplate(@Qualifier("pgVectorSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+}

+ 44 - 0
core/src/main/java/com/tzld/videoVector/dao/generator/PgMybatisGeneratorMain.java

@@ -0,0 +1,44 @@
+package com.tzld.videoVector.dao.generator;
+
+import org.mybatis.generator.api.MyBatisGenerator;
+import org.mybatis.generator.config.Configuration;
+import org.mybatis.generator.config.xml.ConfigurationParser;
+import org.mybatis.generator.exception.InvalidConfigurationException;
+import org.mybatis.generator.exception.XMLParserException;
+import org.mybatis.generator.internal.DefaultShellCallback;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * pgVector 数据源 MyBatis Generator 入口
+ * 基于 PostgreSQL 17 的 video_vectors 表生成实体类和 Mapper
+ *
+ * 注意:embedding 列(vector 类型)需在生成后手动补充到实体类和 Mapper XML 中
+ */
+public class PgMybatisGeneratorMain {
+
+    public static void main(String[] args)
+            throws SQLException, IOException, InterruptedException, InvalidConfigurationException, XMLParserException {
+        List<String> warnings = new ArrayList<String>();
+        boolean overwrite = true;
+        File configFile = new File(PgMybatisGeneratorMain.class.getResource("/generator/mybatis-pgvector-generator-config.xml").getFile());
+
+        ConfigurationParser cp = new ConfigurationParser(warnings);
+        Configuration config = cp.parseConfiguration(configFile);
+        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
+        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
+        myBatisGenerator.generate(null);
+
+        if (!warnings.isEmpty()) {
+            System.out.println("Warnings:");
+            for (String warning : warnings) {
+                System.out.println("  " + warning);
+            }
+        }
+        System.out.println("pgVector generate finish");
+    }
+}

+ 97 - 0
core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/ContentVectorMapper.java

@@ -0,0 +1,97 @@
+package com.tzld.videoVector.dao.mapper.pgVector;
+
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+import com.tzld.videoVector.model.po.pgVector.ContentVectorExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface ContentVectorMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    long countByExample(ContentVectorExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int deleteByExample(ContentVectorExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int insert(ContentVector record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int insertSelective(ContentVector record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    List<ContentVector> selectByExample(ContentVectorExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    ContentVector selectByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int updateByExampleSelective(@Param("record") ContentVector record, @Param("example") ContentVectorExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int updateByExample(@Param("record") ContentVector record, @Param("example") ContentVectorExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int updateByPrimaryKeySelective(ContentVector record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    int updateByPrimaryKey(ContentVector record);
+
+}

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

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

+ 97 - 0
core/src/main/java/com/tzld/videoVector/dao/mapper/pgVector/DeconstructVectorConfigMapper.java

@@ -0,0 +1,97 @@
+package com.tzld.videoVector.dao.mapper.pgVector;
+
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface DeconstructVectorConfigMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    long countByExample(DeconstructVectorConfigExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int deleteByExample(DeconstructVectorConfigExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int insert(DeconstructVectorConfig record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int insertSelective(DeconstructVectorConfig record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    List<DeconstructVectorConfig> selectByExample(DeconstructVectorConfigExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    DeconstructVectorConfig selectByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int updateByExampleSelective(@Param("record") DeconstructVectorConfig record, @Param("example") DeconstructVectorConfigExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int updateByExample(@Param("record") DeconstructVectorConfig record, @Param("example") DeconstructVectorConfigExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int updateByPrimaryKeySelective(DeconstructVectorConfig record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    int updateByPrimaryKey(DeconstructVectorConfig record);
+
+}

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

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

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

@@ -0,0 +1,61 @@
+package com.tzld.videoVector.dao.mapper.pgVector.ext;
+
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * ContentVector 自定义向量操作 Mapper(与 MBG 生成的基础 Mapper 分离)
+ */
+public interface ContentVectorMapperExt {
+
+    /**
+     * 插入向量(含 embedding,通过 ON CONFLICT 去重)
+     */
+    int upsertWithEmbedding(@Param("contentId") Long contentId,
+                            @Param("taskId") String taskId,
+                            @Param("configCode") String configCode,
+                            @Param("sourceField") String sourceField,
+                            @Param("sourcePath") String sourcePath,
+                            @Param("textHash") String textHash,
+                            @Param("embeddingModel") String embeddingModel,
+                            @Param("segmentIndex") Integer segmentIndex,
+                            @Param("segmentTotal") Integer segmentTotal,
+                            @Param("sourceText") String sourceText,
+                            @Param("embedding") String embedding);
+
+    /**
+     * 根据 contentId + configCode 查询向量列表(含 embedding)
+     */
+    List<ContentVector> selectByContentIdAndConfigCode(@Param("contentId") Long contentId,
+                                                       @Param("configCode") String configCode);
+
+    /**
+     * 根据 contentId 查询向量列表(含 embedding)
+     */
+    List<ContentVector> selectByContentId(@Param("contentId") Long contentId);
+
+    /**
+     * 根据 contentId + sourceField 查询向量
+     */
+    List<ContentVector> selectByContentIdAndField(@Param("contentId") Long contentId,
+                                                  @Param("sourceField") String sourceField);
+
+    /**
+     * 根据 textHash + configCode 查询已缓存的向量
+     */
+    ContentVector selectByTextHashAndConfigCode(@Param("textHash") String textHash,
+                                                @Param("configCode") String configCode);
+
+    /**
+     * 根据 textHash 查询向量(不限 configCode)
+     */
+    ContentVector selectByTextHash(@Param("textHash") String textHash);
+
+    /**
+     * 余弦相似度搜索 Top-N
+     */
+    List<ContentVector> searchTopNByCosine(@Param("configCode") String configCode,
+                                           @Param("queryVector") String queryVector,
+                                           @Param("topN") int topN);
+}

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

@@ -0,0 +1,20 @@
+package com.tzld.videoVector.dao.mapper.pgVector.ext;
+
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * DeconstructVectorConfig 自定义查询 Mapper(与 MBG 生成的基础 Mapper 分离)
+ */
+public interface DeconstructVectorConfigMapperExt {
+
+    /**
+     * 按业务类型和内容类型查询启用的向量配置,支持 null 表示通配所有类型
+     */
+    List<DeconstructVectorConfig> selectMatchingConfigs(
+            @Param("bizType") Short bizType,
+            @Param("contentType") Short contentType,
+            @Param("sourceField") String sourceField);
+}

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

@@ -0,0 +1,96 @@
+package com.tzld.videoVector.dao.mapper.pgVector.ext;
+
+import com.tzld.videoVector.model.po.pgVector.VideoVector;
+import com.tzld.videoVector.model.vo.VideoVectorSearchResult;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * VideoVector 自定义向量操作 Mapper(与 MBG 生成的基础 Mapper 分离)
+ */
+public interface VideoVectorMapperExt {
+
+    /**
+     * 插入或更新向量(ON CONFLICT 语义,支持多点模式)
+     * @param videoId    视频ID
+     * @param configCode 配置编码
+     * @param pointIndex 向量点索引(单点模式传0)
+     * @param embedding  向量字符串,格式: "[0.1,0.2,...]"
+     * @param text       向量化的原始文本内容
+     */
+    int upsertVector(@Param("videoId") Long videoId,
+                     @Param("configCode") String configCode,
+                     @Param("pointIndex") int pointIndex,
+                     @Param("embedding") String embedding,
+                     @Param("text") String text);
+
+    /**
+     * 判断指定 videoId + configCode 的向量是否存在(任意 pointIndex)
+     */
+    int existsByVideoIdAndConfigCode(@Param("videoId") Long videoId,
+                                     @Param("configCode") String configCode);
+
+    /**
+     * 批量判断 videoIds 在指定 configCode 下是否存在,返回已存在的 videoId 列表(去重)
+     */
+    List<Long> selectExistingVideoIds(@Param("videoIds") List<Long> videoIds,
+                                      @Param("configCode") String configCode);
+
+    /**
+     * 获取指定 videoId + configCode 的向量(以字符串形式返回,取 pointIndex=0)
+     */
+    String selectEmbeddingByVideoIdAndConfigCode(@Param("videoId") Long videoId,
+                                                 @Param("configCode") String configCode);
+
+    /**
+     * 批量获取向量
+     */
+    List<VideoVector> selectVectorsByVideoIds(@Param("videoIds") List<Long> videoIds,
+                                             @Param("configCode") String configCode);
+
+    /**
+     * 获取指定 configCode 下所有 videoId(去重)
+     */
+    List<Long> selectAllVideoIdsByConfigCode(@Param("configCode") String configCode);
+
+    /**
+     * 删除指定 videoId + configCode 的所有向量点
+     */
+    int deleteByVideoIdAndConfigCode(@Param("videoId") Long videoId,
+                                     @Param("configCode") String configCode);
+
+    /**
+     * 批量删除指定 videoIds + configCode 的所有向量点
+     */
+    int deleteBatchByVideoIdsAndConfigCode(@Param("videoIds") List<Long> videoIds,
+                                           @Param("configCode") String configCode);
+
+    /**
+     * 余弦相似度搜索 Top-N
+     * @param configCode  配置编码
+     * @param queryVector 查询向量字符串
+     * @param topN        返回数量
+     * @return VideoVector 列表(包含 videoId、pointIndex 和相似度得分)
+     */
+    List<VideoVectorSearchResult> searchTopNByCosine(@Param("configCode") String configCode,
+                                        @Param("queryVector") String queryVector,
+                                        @Param("topN") int topN);
+
+    /**
+     * 分页查询指定 configCode 下的记录(不过滤 text 是否为空,只要有数据就返回)
+     * @param configCode 配置编码
+     * @param offset     偏移量
+     * @param limit      每页数量
+     * @return VideoVector 列表(包含 videoId、pointIndex)
+     */
+    List<VideoVector> selectByConfigCodePaged(@Param("configCode") String configCode,
+                                              @Param("offset") int offset,
+                                              @Param("limit") int limit);
+
+    /**
+     * 批量更新 text 字段
+     * @param id   主键ID
+     * @param text 文本内容
+     */
+    int updateTextById(@Param("id") Long id, @Param("text") String text);
+}

+ 18 - 17
core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/DeconstructContentMapper.java → core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructContentMapper.java

@@ -1,18 +1,19 @@
 package com.tzld.videoVector.dao.mapper.videoVector.deconstruct;
 
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample;
-import java.util.List;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample;
 import org.apache.ibatis.annotations.Param;
 
-public interface DeconstructContentMapper {
+import java.util.List;
+
+public interface MysqlDeconstructContentMapper {
     /**
      * This method was generated by MyBatis Generator.
      * This method corresponds to the database table deconstruct_content
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    long countByExample(DeconstructContentExample example);
+    long countByExample(MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -20,7 +21,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int deleteByExample(DeconstructContentExample example);
+    int deleteByExample(MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -36,7 +37,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int insert(DeconstructContent record);
+    int insert(MysqlDeconstructContent record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -44,7 +45,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int insertSelective(DeconstructContent record);
+    int insertSelective(MysqlDeconstructContent record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -52,7 +53,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    List<DeconstructContent> selectByExampleWithBLOBs(DeconstructContentExample example);
+    List<MysqlDeconstructContent> selectByExampleWithBLOBs(MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -60,7 +61,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    List<DeconstructContent> selectByExample(DeconstructContentExample example);
+    List<MysqlDeconstructContent> selectByExample(MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -68,7 +69,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    DeconstructContent selectByPrimaryKey(Long id);
+    MysqlDeconstructContent selectByPrimaryKey(Long id);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -76,7 +77,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByExampleSelective(@Param("record") DeconstructContent record, @Param("example") DeconstructContentExample example);
+    int updateByExampleSelective(@Param("record") MysqlDeconstructContent record, @Param("example") MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -84,7 +85,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByExampleWithBLOBs(@Param("record") DeconstructContent record, @Param("example") DeconstructContentExample example);
+    int updateByExampleWithBLOBs(@Param("record") MysqlDeconstructContent record, @Param("example") MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -92,7 +93,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByExample(@Param("record") DeconstructContent record, @Param("example") DeconstructContentExample example);
+    int updateByExample(@Param("record") MysqlDeconstructContent record, @Param("example") MysqlDeconstructContentExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -100,7 +101,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByPrimaryKeySelective(DeconstructContent record);
+    int updateByPrimaryKeySelective(MysqlDeconstructContent record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -108,7 +109,7 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByPrimaryKeyWithBLOBs(DeconstructContent record);
+    int updateByPrimaryKeyWithBLOBs(MysqlDeconstructContent record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -116,5 +117,5 @@ public interface DeconstructContentMapper {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    int updateByPrimaryKey(DeconstructContent record);
+    int updateByPrimaryKey(MysqlDeconstructContent record);
 }

+ 18 - 17
core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/DeconstructContentVectorMapper.java → core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructContentVectorMapper.java

@@ -1,18 +1,19 @@
 package com.tzld.videoVector.dao.mapper.videoVector.deconstruct;
 
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample;
-import java.util.List;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVectorExample;
 import org.apache.ibatis.annotations.Param;
 
-public interface DeconstructContentVectorMapper {
+import java.util.List;
+
+public interface MysqlDeconstructContentVectorMapper {
     /**
      * This method was generated by MyBatis Generator.
      * This method corresponds to the database table deconstruct_content_vector
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    long countByExample(DeconstructContentVectorExample example);
+    long countByExample(MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -20,7 +21,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int deleteByExample(DeconstructContentVectorExample example);
+    int deleteByExample(MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -36,7 +37,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int insert(DeconstructContentVector record);
+    int insert(MysqlDeconstructContentVector record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -44,7 +45,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int insertSelective(DeconstructContentVector record);
+    int insertSelective(MysqlDeconstructContentVector record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -52,7 +53,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    List<DeconstructContentVector> selectByExampleWithBLOBs(DeconstructContentVectorExample example);
+    List<MysqlDeconstructContentVector> selectByExampleWithBLOBs(MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -60,7 +61,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    List<DeconstructContentVector> selectByExample(DeconstructContentVectorExample example);
+    List<MysqlDeconstructContentVector> selectByExample(MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -68,7 +69,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    DeconstructContentVector selectByPrimaryKey(Long id);
+    MysqlDeconstructContentVector selectByPrimaryKey(Long id);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -76,7 +77,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByExampleSelective(@Param("record") DeconstructContentVector record, @Param("example") DeconstructContentVectorExample example);
+    int updateByExampleSelective(@Param("record") MysqlDeconstructContentVector record, @Param("example") MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -84,7 +85,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByExampleWithBLOBs(@Param("record") DeconstructContentVector record, @Param("example") DeconstructContentVectorExample example);
+    int updateByExampleWithBLOBs(@Param("record") MysqlDeconstructContentVector record, @Param("example") MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -92,7 +93,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByExample(@Param("record") DeconstructContentVector record, @Param("example") DeconstructContentVectorExample example);
+    int updateByExample(@Param("record") MysqlDeconstructContentVector record, @Param("example") MysqlDeconstructContentVectorExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -100,7 +101,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByPrimaryKeySelective(DeconstructContentVector record);
+    int updateByPrimaryKeySelective(MysqlDeconstructContentVector record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -108,7 +109,7 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByPrimaryKeyWithBLOBs(DeconstructContentVector record);
+    int updateByPrimaryKeyWithBLOBs(MysqlDeconstructContentVector record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -116,5 +117,5 @@ public interface DeconstructContentVectorMapper {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    int updateByPrimaryKey(DeconstructContentVector record);
+    int updateByPrimaryKey(MysqlDeconstructContentVector record);
 }

+ 14 - 14
core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/DeconstructVectorConfigMapper.java → core/src/main/java/com/tzld/videoVector/dao/mapper/videoVector/deconstruct/MysqlDeconstructVectorConfigMapper.java

@@ -1,18 +1,18 @@
 package com.tzld.videoVector.dao.mapper.videoVector.deconstruct;
 
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfigExample;
 import java.util.List;
 import org.apache.ibatis.annotations.Param;
 
-public interface DeconstructVectorConfigMapper {
+public interface MysqlDeconstructVectorConfigMapper {
     /**
      * This method was generated by MyBatis Generator.
      * This method corresponds to the database table deconstruct_vector_config
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    long countByExample(DeconstructVectorConfigExample example);
+    long countByExample(MysqlDeconstructVectorConfigExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -20,7 +20,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int deleteByExample(DeconstructVectorConfigExample example);
+    int deleteByExample(MysqlDeconstructVectorConfigExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -36,7 +36,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int insert(DeconstructVectorConfig record);
+    int insert(MysqlDeconstructVectorConfig record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -44,7 +44,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int insertSelective(DeconstructVectorConfig record);
+    int insertSelective(MysqlDeconstructVectorConfig record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -52,7 +52,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    List<DeconstructVectorConfig> selectByExample(DeconstructVectorConfigExample example);
+    List<MysqlDeconstructVectorConfig> selectByExample(MysqlDeconstructVectorConfigExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -60,7 +60,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    DeconstructVectorConfig selectByPrimaryKey(Long id);
+    MysqlDeconstructVectorConfig selectByPrimaryKey(Long id);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -68,7 +68,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int updateByExampleSelective(@Param("record") DeconstructVectorConfig record, @Param("example") DeconstructVectorConfigExample example);
+    int updateByExampleSelective(@Param("record") MysqlDeconstructVectorConfig record, @Param("example") MysqlDeconstructVectorConfigExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -76,7 +76,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int updateByExample(@Param("record") DeconstructVectorConfig record, @Param("example") DeconstructVectorConfigExample example);
+    int updateByExample(@Param("record") MysqlDeconstructVectorConfig record, @Param("example") MysqlDeconstructVectorConfigExample example);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -84,7 +84,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int updateByPrimaryKeySelective(DeconstructVectorConfig record);
+    int updateByPrimaryKeySelective(MysqlDeconstructVectorConfig record);
 
     /**
      * This method was generated by MyBatis Generator.
@@ -92,7 +92,7 @@ public interface DeconstructVectorConfigMapper {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    int updateByPrimaryKey(DeconstructVectorConfig record);
+    int updateByPrimaryKey(MysqlDeconstructVectorConfig record);
 
     /**
      * 按业务类型和内容类型查询启用的向量配置,支持 null 表示通配所有类型
@@ -102,7 +102,7 @@ public interface DeconstructVectorConfigMapper {
      * @param contentType 内容类型,为 null 时不过滤
      * @return 匹配的向量配置列表
      */
-    List<DeconstructVectorConfig> selectMatchingConfigs(
+    List<MysqlDeconstructVectorConfig> selectMatchingConfigs(
             @Param("bizType") Byte bizType,
             @Param("contentType") Byte contentType);
 }

+ 418 - 0
core/src/main/java/com/tzld/videoVector/job/DataMigrationJob.java

@@ -0,0 +1,418 @@
+package com.tzld.videoVector.job;
+
+import com.tzld.videoVector.common.constant.VectorConstants;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.ContentVectorMapperExt;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructContentMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoVectorMapperExt;
+import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructContentVectorMapper;
+import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructContentMapper;
+import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructVectorConfigMapper;
+import com.tzld.videoVector.model.po.pgVector.DeconstructContent;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.*;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 数据迁移任务
+ * 1. MySQL deconstruct_content / deconstruct_vector_config → PG
+ * 2. MySQL deconstruct_content_vector → PG content_vectors
+ * 3. Redis 视频向量缓存 → PG video_vectors
+ */
+@Slf4j
+@Component
+public class DataMigrationJob {
+
+    // ==================== MySQL 数据源 Mapper ====================
+    @Resource
+    private MysqlDeconstructContentMapper mysqlContentMapper;
+
+    @Resource
+    private MysqlDeconstructVectorConfigMapper mysqlConfigMapper;
+
+    @Resource
+    private MysqlDeconstructContentVectorMapper mysqlContentVectorMapper;
+
+    // ==================== PG 数据源 Mapper ====================
+    @Resource
+    private DeconstructContentMapper deconstructContentMapper;
+
+    @Resource
+    private DeconstructVectorConfigMapper deconstructVectorConfigMapper;
+
+    @Resource
+    private ContentVectorMapperExt contentVectorMapperExt;
+
+    @Resource
+    private VideoVectorMapperExt videoVectorMapperExt;
+
+    // ==================== Redis ====================
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    // 向量值是纯 JSON 字符串,没有 Jackson 类型信息,需用 StringRedisTemplate 读取
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    // ==================== 迁移批次大小 ====================
+    private static final int BATCH_SIZE = 200;
+
+    /**
+     * MySQL deconstruct_content 表迁移到 PG
+     */
+    @XxlJob("migrateContentToPgJob")
+    public ReturnT<String> migrateContentToPgJob(String param) {
+        log.info("开始迁移 deconstruct_content 数据到 PG, param: {}", param);
+
+        try {
+            // 从 MySQL 分页读取
+            MysqlDeconstructContentExample example =
+                    new MysqlDeconstructContentExample();
+            example.setOrderByClause("id ASC");
+            List<MysqlDeconstructContent> allRecords =
+                    mysqlContentMapper.selectByExampleWithBLOBs(example);
+
+            if (CollectionUtils.isEmpty(allRecords)) {
+                log.info("MySQL deconstruct_content 无数据,跳过");
+                return ReturnT.SUCCESS;
+            }
+
+            log.info("查询到 {} 条 deconstruct_content 记录待迁移", allRecords.size());
+
+            int successCount = 0;
+            int skipCount = 0;
+            int failCount = 0;
+
+            for (MysqlDeconstructContent mysql : allRecords) {
+                try {
+                    // 转换为 PG 实体
+                    DeconstructContent pg = convertContent(mysql);
+                    // 插入 PG(使用 insertSelective 避免主键冲突时可手动处理)
+                    deconstructContentMapper.insertSelective(pg);
+                    successCount++;
+                } catch (Exception e) {
+                    if (e.getMessage() != null && e.getMessage().contains("duplicate key")) {
+                        skipCount++;
+                    } else {
+                        failCount++;
+                        log.error("迁移 content id={} 失败: {}", mysql.getId(), e.getMessage());
+                    }
+                }
+            }
+
+            log.info("deconstruct_content 迁移完成,成功: {}, 跳过(已存在): {}, 失败: {}",
+                    successCount, skipCount, failCount);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("deconstruct_content 迁移失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "迁移失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * MySQL deconstruct_vector_config 表迁移到 PG
+     */
+    @XxlJob("migrateVectorConfigToPgJob")
+    public ReturnT<String> migrateVectorConfigToPgJob(String param) {
+        log.info("开始迁移 deconstruct_vector_config 数据到 PG, param: {}", param);
+
+        try {
+            MysqlDeconstructVectorConfigExample example =
+                    new MysqlDeconstructVectorConfigExample();
+            example.setOrderByClause("id ASC");
+            List<MysqlDeconstructVectorConfig> allRecords =
+                    mysqlConfigMapper.selectByExample(example);
+
+            if (CollectionUtils.isEmpty(allRecords)) {
+                log.info("MySQL deconstruct_vector_config 无数据,跳过");
+                return ReturnT.SUCCESS;
+            }
+
+            log.info("查询到 {} 条 deconstruct_vector_config 记录待迁移", allRecords.size());
+
+            int successCount = 0;
+            int skipCount = 0;
+            int failCount = 0;
+
+            for (MysqlDeconstructVectorConfig mysql : allRecords) {
+                try {
+                    DeconstructVectorConfig pg = convertVectorConfig(mysql);
+                    deconstructVectorConfigMapper.insertSelective(pg);
+                    successCount++;
+                } catch (Exception e) {
+                    if (e.getMessage() != null && e.getMessage().contains("duplicate key")) {
+                        skipCount++;
+                    } else {
+                        failCount++;
+                        log.error("迁移 vector_config id={} 失败: {}", mysql.getId(), e.getMessage());
+                    }
+                }
+            }
+
+            log.info("deconstruct_vector_config 迁移完成,成功: {}, 跳过(已存在): {}, 失败: {}",
+                    successCount, skipCount, failCount);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("deconstruct_vector_config 迁移失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "迁移失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * MySQL deconstruct_content_vector 表迁移到 PG content_vectors
+     * 将 vector_data(JSON 数组)转为 pgvector embedding
+     */
+    @XxlJob("migrateContentVectorToPgJob")
+    public ReturnT<String> migrateContentVectorToPgJob(String param) {
+        log.info("开始迁移 deconstruct_content_vector 数据到 PG content_vectors, param: {}", param);
+
+        try {
+            MysqlDeconstructContentVectorExample example = new MysqlDeconstructContentVectorExample();
+            example.setOrderByClause("id ASC");
+            List<MysqlDeconstructContentVector> allRecords = mysqlContentVectorMapper.selectByExampleWithBLOBs(example);
+
+            if (CollectionUtils.isEmpty(allRecords)) {
+                log.info("MySQL deconstruct_content_vector 无数据,跳过");
+                return ReturnT.SUCCESS;
+            }
+
+            log.info("查询到 {} 条 deconstruct_content_vector 记录待迁移", allRecords.size());
+
+            int successCount = 0;
+            int skipCount = 0;
+            int failCount = 0;
+
+            for (MysqlDeconstructContentVector mysql : allRecords) {
+                try {
+                    String vectorData = mysql.getVectorData();
+                    if (!StringUtils.hasText(vectorData)) {
+                        log.debug("content_vector id={} vectorData 为空,跳过", mysql.getId());
+                        skipCount++;
+                        continue;
+                    }
+
+                    // vectorData 是 JSON 数组格式 "[0.1,0.2,...]",pgvector 接受此格式
+                    contentVectorMapperExt.upsertWithEmbedding(
+                            mysql.getContentId(),
+                            mysql.getTaskId(),
+                            mysql.getConfigCode(),
+                            mysql.getSourceField(),
+                            mysql.getSourcePath(),
+                            mysql.getTextHash(),
+                            mysql.getEmbeddingModel(),
+                            mysql.getSegmentIndex(),
+                            mysql.getSegmentTotal(),
+                            mysql.getSourceText(),
+                            vectorData
+                    );
+                    successCount++;
+                } catch (Exception e) {
+                    if (e.getMessage() != null && e.getMessage().contains("duplicate key")) {
+                        skipCount++;
+                    } else {
+                        failCount++;
+                        log.error("迁移 content_vector id={} 失败: {}", mysql.getId(), e.getMessage());
+                    }
+                }
+            }
+
+            log.info("deconstruct_content_vector → content_vectors 迁移完成,成功: {}, 跳过: {}, 失败: {}",
+                    successCount, skipCount, failCount);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("content_vector 迁移失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "迁移失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Redis 视频向量缓存迁移到 PG video_vectors
+     * Redis 存储结构:
+     *   Key: video:vector:{configCode}:{videoId}  Value: JSON 数组字符串
+     *   Key: video:vector:{configCode}:ids        类型: Set(存储所有 videoId)
+     */
+    @XxlJob("migrateRedisVectorToPgJob")
+    public ReturnT<String> migrateRedisVectorToPgJob(String param) {
+        log.info("开始迁移 Redis 视频向量缓存到 PG video_vectors, param: {}", param);
+
+        try {
+            // 1. 获取所有启用的配置编码
+            List<String> configCodes = getAllConfigCodes();
+            if (configCodes.isEmpty()) {
+                log.warn("未获取到任何配置编码");
+                return ReturnT.SUCCESS;
+            }
+            log.info("待迁移的配置编码: {}", configCodes);
+
+            int totalSuccess = 0;
+            int totalSkip = 0;
+            int totalFail = 0;
+
+            // 2. 逐个配置迁移
+            for (String configCode : configCodes) {
+                log.info("开始迁移配置 {} 的向量数据", configCode);
+
+                // 从 Redis Set 获取所有 videoId
+                String idsKey = VectorConstants.VECTOR_KEY_PREFIX + configCode + ":ids";
+                Set<Object> idMembers = redisTemplate.opsForSet().members(idsKey);
+
+                if (idMembers == null || idMembers.isEmpty()) {
+                    log.info("配置 {} 在 Redis 中无 videoId 索引,跳过", configCode);
+                    continue;
+                }
+
+                log.info("配置 {} 共有 {} 个 videoId 待迁移", configCode, idMembers.size());
+
+                // Redis value 可能是 Integer/Long/String,统一转为 String
+                List<String> idList = new ArrayList<>();
+                for (Object member : idMembers) {
+                    idList.add(String.valueOf(member));
+                }
+
+                // 分批处理
+                for (int i = 0; i < idList.size(); i += BATCH_SIZE) {
+                    int end = Math.min(i + BATCH_SIZE, idList.size());
+                    List<String> batch = idList.subList(i, end);
+
+                    for (String idStr : batch) {
+                        try {
+                            Long videoId = Long.parseLong(idStr);
+                            String vectorKey = VectorConstants.VECTOR_KEY_PREFIX + configCode + ":" + videoId;
+                            String vectorJson = stringRedisTemplate.opsForValue().get(vectorKey);
+
+                            if (!StringUtils.hasText(vectorJson)) {
+                                totalSkip++;
+                                continue;
+                            }
+
+                            // Redis 中存储的是归一化后的 JSON 数组 "[0.1,0.2,...]"
+                            // pgvector 接受此格式
+                            videoVectorMapperExt.upsertVector(videoId, configCode, 0, vectorJson, null);
+                            totalSuccess++;
+                        } catch (NumberFormatException e) {
+                            log.warn("非法 videoId: {}", idStr);
+                            totalSkip++;
+                        } catch (Exception e) {
+                            if (e.getMessage() != null && e.getMessage().contains("duplicate key")) {
+                                totalSkip++;
+                            } else {
+                                totalFail++;
+                                log.error("迁移 Redis 向量失败,configCode={}, videoId={}, error={}",
+                                        configCode, idStr, e.getMessage());
+                            }
+                        }
+                    }
+
+                    log.info("配置 {} 进度: {}/{}", configCode, Math.min(end, idList.size()), idList.size());
+                }
+            }
+
+            log.info("Redis 视频向量迁移完成,总成功: {}, 总跳过: {}, 总失败: {}",
+                    totalSuccess, totalSkip, totalFail);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("Redis 向量迁移失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "迁移失败: " + e.getMessage());
+        }
+    }
+
+    // ==================== 工具方法 ====================
+
+    /**
+     * 获取所有非多点模式的配置编码(从 PG 查询 + 默认编码)
+     * 多点模式(extract_rule 非空)的数据不迁移,由 Job 重新生成
+     */
+    private List<String> getAllConfigCodes() {
+        Set<String> codes = new LinkedHashSet<>();
+        codes.add(VectorConstants.DEFAULT_CONFIG_CODE);
+
+        try {
+            DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+            example.createCriteria().andEnabledEqualTo((short) 1);
+            List<DeconstructVectorConfig> configs = deconstructVectorConfigMapper.selectByExample(example);
+            if (configs != null) {
+                for (DeconstructVectorConfig config : configs) {
+                    if (StringUtils.hasText(config.getConfigCode())
+                            && !StringUtils.hasText(config.getExtractRule())) {
+                        // 仅迁移非多点模式(extract_rule 为空)的配置
+                        codes.add(config.getConfigCode());
+                    } else if (StringUtils.hasText(config.getExtractRule())) {
+                        log.info("跳过多点模式配置: {}(extract_rule 非空,由 Job 重新生成)", config.getConfigCode());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取配置编码失败: {}", e.getMessage());
+        }
+
+        return new ArrayList<>(codes);
+    }
+
+    /**
+     * MySQL DeconstructContent → PG DeconstructContent 实体转换
+     * 主要差异:Byte → Short
+     */
+    private DeconstructContent convertContent(
+            MysqlDeconstructContent mysql) {
+        DeconstructContent pg = new DeconstructContent();
+        // 不设置 id,让 PG 自增生成
+        pg.setTaskId(mysql.getTaskId());
+        pg.setBizType(mysql.getBizType() != null ? mysql.getBizType().shortValue() : null);
+        pg.setContentType(mysql.getContentType() != null ? mysql.getContentType().shortValue() : null);
+        pg.setChannelContentId(mysql.getChannelContentId());
+        pg.setTitle(mysql.getTitle());
+        pg.setBodyText(mysql.getBodyText());
+        pg.setVideoUrl(mysql.getVideoUrl());
+        pg.setImages(mysql.getImages());
+        pg.setChannelAccountId(mysql.getChannelAccountId());
+        pg.setChannelAccountName(mysql.getChannelAccountName());
+        pg.setStatus(mysql.getStatus() != null ? mysql.getStatus().shortValue() : null);
+        pg.setResultJson(mysql.getResultJson());
+        pg.setFailureReason(mysql.getFailureReason());
+        pg.setPointUrl(mysql.getPointUrl());
+        pg.setWeightUrl(mysql.getWeightUrl());
+        pg.setPatternUrl(mysql.getPatternUrl());
+        pg.setCreateTime(mysql.getCreateTime());
+        pg.setUpdateTime(mysql.getUpdateTime());
+        return pg;
+    }
+
+    /**
+     * MySQL DeconstructVectorConfig → PG DeconstructVectorConfig 实体转换
+     */
+    private DeconstructVectorConfig convertVectorConfig(
+            MysqlDeconstructVectorConfig mysql) {
+        DeconstructVectorConfig pg = new DeconstructVectorConfig();
+        pg.setConfigCode(mysql.getConfigCode());
+        pg.setConfigName(mysql.getConfigName());
+        pg.setBizType(mysql.getBizType() != null ? mysql.getBizType().shortValue() : null);
+        pg.setContentType(mysql.getContentType() != null ? mysql.getContentType().shortValue() : null);
+        pg.setSourceField(mysql.getSourceField());
+        pg.setSourcePath(mysql.getSourcePath());
+        pg.setExtractRule(mysql.getExtractRule());
+        pg.setEmbeddingModel(mysql.getEmbeddingModel());
+        pg.setDimension(mysql.getDimension());
+        pg.setMaxLength(mysql.getMaxLength());
+        pg.setEnableSegment(mysql.getEnableSegment() != null ? mysql.getEnableSegment().shortValue() : null);
+        pg.setSegmentSize(mysql.getSegmentSize());
+        pg.setPriority(mysql.getPriority());
+        pg.setEnabled(mysql.getEnabled() != null ? mysql.getEnabled().shortValue() : null);
+        pg.setCreateTime(mysql.getCreateTime());
+        pg.setUpdateTime(mysql.getUpdateTime());
+        return pg;
+    }
+}

+ 157 - 0
core/src/main/java/com/tzld/videoVector/job/MaterialDeconstructCheckJob.java

@@ -0,0 +1,157 @@
+package com.tzld.videoVector.job;
+
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructContentMapper;
+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.service.DeconstructService;
+import com.tzld.videoVector.service.VectorizeService;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 素材解构结果轮询定时任务
+ * <p>
+ * 扫描 deconstruct_content 中未完成(status=0 PENDING 或 status=1 RUNNING)的记录,
+ * 调用解构 API 查询结果,更新状态。
+ * 若解构成功(status=2),自动触发 VectorizeService 向量化。
+ */
+@Slf4j
+@Component
+public class MaterialDeconstructCheckJob {
+
+    @Resource
+    private DeconstructContentMapper deconstructContentMapper;
+
+    @Resource
+    private DeconstructService deconstructService;
+
+    @Resource
+    private VectorizeService vectorizeService;
+
+    /**
+     * 素材解构结果轮询
+     * 扫描未完成的解构记录,查询 API 更新状态,完成后自动向量化
+     */
+    @XxlJob("checkMaterialDeconstructJob")
+    public ReturnT<String> checkMaterialDeconstructJob(String param) {
+        log.info("开始执行素材解构结果轮询任务, param: {}", param);
+
+        try {
+            int totalChecked = 0;
+            int totalCompleted = 0;
+            int totalVectorized = 0;
+            int totalFailed = 0;
+            int pageNum = 0;
+            int pageSize = 50;
+
+            while (true) {
+                // 分页查询未完成的解构记录(status=0 或 status=1)
+                List<DeconstructContent> contents = queryPendingContents(pageNum, pageSize);
+                if (CollectionUtils.isEmpty(contents)) {
+                    log.info("第 {} 页无待查询数据,轮询结束", pageNum);
+                    break;
+                }
+                log.info("第 {} 页查询到 {} 条未完成的解构记录", pageNum, contents.size());
+
+                for (DeconstructContent content : contents) {
+                    totalChecked++;
+                    try {
+                        String taskId = content.getTaskId();
+                        if (!StringUtils.hasText(taskId)) {
+                            log.warn("contentId={} 的 taskId 为空,跳过", content.getId());
+                            continue;
+                        }
+
+                        // 调用解构 API 查询结果
+                        DeconstructResult result = deconstructService.getDeconstructResult(taskId);
+                        if (result == null) {
+                            log.warn("查询解构结果返回空,taskId={}", taskId);
+                            continue;
+                        }
+
+                        // 更新状态
+                        Short newStatus = result.getStatus() != null
+                                ? result.getStatus().shortValue() : content.getStatus();
+
+                        // 状态未变化则跳过更新
+                        if (newStatus.equals(content.getStatus())) {
+                            log.debug("解构状态未变化,taskId={}, status={}", taskId, newStatus);
+                            continue;
+                        }
+
+                        content.setStatus(newStatus);
+                        content.setResultJson(result.getResult());
+                        content.setFailureReason(result.getReason());
+                        content.setPointUrl(result.getPointUrl());
+                        content.setWeightUrl(result.getWeightUrl());
+                        content.setPatternUrl(result.getPatternUrl());
+                        content.setUpdateTime(new Date());
+                        deconstructContentMapper.updateByPrimaryKeySelective(content);
+
+                        log.info("更新解构状态,contentId={}, taskId={}, status={} -> {}",
+                                content.getId(), taskId, content.getStatus(), newStatus);
+
+                        // 解构成功,触发向量化
+                        if (newStatus == 2) {
+                            totalCompleted++;
+                            try {
+                                vectorizeService.vectorizeContent(content);
+                                totalVectorized++;
+                                log.info("解构完成并向量化成功,contentId={}, taskId={}",
+                                        content.getId(), taskId);
+                            } catch (Exception e) {
+                                log.error("向量化失败,contentId={}, taskId={}, error={}",
+                                        content.getId(), taskId, e.getMessage(), e);
+                            }
+                        } else if (newStatus == 3) {
+                            totalFailed++;
+                            log.warn("解构失败,contentId={}, taskId={}, reason={}",
+                                    content.getId(), taskId, result.getReason());
+                        }
+
+                    } catch (Exception e) {
+                        log.error("处理解构结果失败,contentId={}, error={}",
+                                content.getId(), e.getMessage(), e);
+                    }
+                }
+
+                if (contents.size() < pageSize) {
+                    log.info("第 {} 页数据量 {} 小于 pageSize {},轮询结束",
+                            pageNum, contents.size(), pageSize);
+                    break;
+                }
+                pageNum++;
+            }
+
+            log.info("素材解构结果轮询完成,检查: {}, 完成: {}, 向量化: {}, 失败: {}",
+                    totalChecked, totalCompleted, totalVectorized, totalFailed);
+            return ReturnT.SUCCESS;
+
+        } catch (Exception e) {
+            log.error("素材解构结果轮询任务异常: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 分页查询未完成的解构内容(status=0 PENDING 或 status=1 RUNNING)
+     */
+    private List<DeconstructContent> queryPendingContents(int pageNum, int pageSize) {
+        DeconstructContentExample example = new DeconstructContentExample();
+        DeconstructContentExample.Criteria criteria = example.createCriteria();
+        // status IN (0, 1)
+        List<Short> pendingStatuses = java.util.Arrays.asList((short) 0, (short) 1);
+        criteria.andStatusIn(pendingStatuses);
+        example.setOrderByClause("id ASC LIMIT " + pageSize + " OFFSET " + (pageNum * pageSize));
+        return deconstructContentMapper.selectByExample(example);
+    }
+}

+ 96 - 0
core/src/main/java/com/tzld/videoVector/job/MaterialVectorJob.java

@@ -0,0 +1,96 @@
+package com.tzld.videoVector.job;
+
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructContentMapper;
+import com.tzld.videoVector.model.po.pgVector.DeconstructContent;
+import com.tzld.videoVector.model.po.pgVector.DeconstructContentExample;
+import com.tzld.videoVector.service.VectorizeService;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 素材批量向量化定时任务
+ * <p>
+ * 扫描 deconstruct_content 中解构完成(status=2)的记录,
+ * 调用 VectorizeService.vectorizeContent() 进行向量化(内部自动幂等)。
+ * <p>
+ * 复用 deconstruct_vector_config 中 biz_type=NULL 的通用配置,与视频解构配置共享。
+ */
+@Slf4j
+@Component
+public class MaterialVectorJob {
+
+    @Resource
+    private DeconstructContentMapper deconstructContentMapper;
+
+    @Resource
+    private VectorizeService vectorizeService;
+
+    /**
+     * 素材批量向量化
+     * 扫描解构完成的素材,逐条调用 VectorizeService 进行向量化
+     */
+    @XxlJob("vectorMaterialJob")
+    public ReturnT<String> vectorMaterialJob(String param) {
+        log.info("开始执行素材批量向量化任务, param: {}", param);
+
+        try {
+            int totalSuccess = 0;
+            int totalFail = 0;
+            int pageNum = 0;
+            int pageSize = 100;
+
+            while (true) {
+                // 分页查询解构完成的内容(status=2)
+                List<DeconstructContent> contents = queryDeconstructedContents(pageNum, pageSize);
+                if (CollectionUtils.isEmpty(contents)) {
+                    log.info("第 {} 页无数据,分页结束", pageNum);
+                    break;
+                }
+                log.info("第 {} 页查询到 {} 条解构完成的内容", pageNum, contents.size());
+
+                for (DeconstructContent content : contents) {
+                    try {
+                        // VectorizeService 内部自动幂等:已有向量的 configCode 会跳过
+                        vectorizeService.vectorizeContent(content);
+                        totalSuccess++;
+                    } catch (Exception e) {
+                        log.error("素材向量化失败,contentId={}, error={}",
+                                content.getId(), e.getMessage(), e);
+                        totalFail++;
+                    }
+                }
+
+                if (contents.size() < pageSize) {
+                    log.info("第 {} 页数据量 {} 小于 pageSize {},分页结束",
+                            pageNum, contents.size(), pageSize);
+                    break;
+                }
+                pageNum++;
+            }
+
+            log.info("素材批量向量化完成,成功: {}, 失败: {}, 总页数: {}",
+                    totalSuccess, totalFail, pageNum + 1);
+            return ReturnT.SUCCESS;
+
+        } catch (Exception e) {
+            log.error("素材批量向量化任务异常: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 分页查询解构完成的内容
+     */
+    private List<DeconstructContent> queryDeconstructedContents(int pageNum, int pageSize) {
+        DeconstructContentExample example = new DeconstructContentExample();
+        example.createCriteria().andStatusEqualTo((short) 2);
+        example.setOrderByClause("id ASC LIMIT " + pageSize + " OFFSET " + (pageNum * pageSize));
+        return deconstructContentMapper.selectByExample(example);
+    }
+}

+ 719 - 298
core/src/main/java/com/tzld/videoVector/job/VideoVectorJob.java

@@ -1,23 +1,24 @@
 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.google.common.collect.Lists;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentMapper;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructVectorConfigMapper;
-import com.tzld.videoVector.model.entity.DeconstructResult;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample;
 import com.tzld.videoVector.api.AigcApiService;
 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.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.service.DeconstructService;
 import com.tzld.videoVector.service.EmbeddingService;
 import com.tzld.videoVector.service.VectorStoreService;
 import com.tzld.videoVector.util.OdpsUtil;
+import com.tzld.videoVector.util.VectorUtils;
 import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.handler.annotation.XxlJob;
 import lombok.extern.slf4j.Slf4j;
@@ -27,6 +28,8 @@ import org.springframework.util.StringUtils;
 
 import javax.annotation.Resource;
 import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 
@@ -55,27 +58,6 @@ public class VideoVectorJob {
     @Resource
     private AigcApiService aigcApiService;
 
-    /**
-     * 每页查询数量
-     */
-    private static final int PAGE_SIZE = 1000;
-
-    /**
-     * 审核状态检查批次大小
-     */
-    private static final int AUDIT_CHECK_BATCH_SIZE = 20;
-
-    /**
-     * 超时时间:1小时(毫秒)
-     */
-    private static final long TIMEOUT_MS = 60 * 60 * 1000L;
-
-    /**
-     * 内容类型:长文
-     * @deprecated 已不再按内容类型过滤,加载所有启用的配置
-     */
-    @Deprecated
-    private static final byte CONTENT_TYPE_LONG_ARTICLE = 1;
 
     /**
      * 视频向量化
@@ -89,8 +71,8 @@ public class VideoVectorJob {
         log.info("开始执行视频向量化任务, param: {}", param);
 
         try {
-            // 1. 获取所有启用的向量化配置(不限制内容类型)
-            List<DeconstructVectorConfig> configs = getEnabledConfigs();
+            // 1. 获取 result_json 来源的向量化配置
+            List<DeconstructVectorConfig> configs = getEnabledConfigsBySourceField("result_json");
             if (CollectionUtils.isEmpty(configs)) {
                 log.warn("未找到启用的向量化配置");
                 return ReturnT.SUCCESS;
@@ -103,94 +85,114 @@ public class VideoVectorJob {
             }
             log.info("审核清理完成,开始分页向量化处理");
 
-            int totalSuccessCount = 0;
-            int totalFailCount = 0;
+            AtomicInteger totalSuccessCount = new AtomicInteger(0);
+            AtomicInteger totalFailCount = new AtomicInteger(0);
             int pageNum = 0;
 
             while (true) {
                 // 2. 分页查询 videoId 列表
-                List<Long> videoIds = queryVideoIdsByPage(pageNum, PAGE_SIZE);
+                List<Long> videoIds = queryVideoIdsByPage(pageNum, VectorConstants.PAGE_SIZE);
                 if (CollectionUtils.isEmpty(videoIds)) {
                     log.info("第 {} 页没有查询到数据,分页查询结束", pageNum);
                     break;
                 }
                 log.info("第 {} 页查询到 {} 个 videoId", pageNum, videoIds.size());
 
-                // 3. 对每个配置进行处理
-                for (DeconstructVectorConfig config : configs) {
-                    String configCode = config.getConfigCode();
-
-                    // 3.0 审核清理已移至分页外,此处仅进行向量存在性检查
-                    // 3.1 查询哪些 videoId 在该配置下已有向量
-                    Set<Long> existingIds = vectorStoreService.existsByIds(configCode, videoIds);
-                    // 3.2 过滤出需要处理的 videoId(排除已有向量的)
-                    List<Long> needProcessIds = videoIds.stream()
-                            .filter(id -> !existingIds.contains(id))
-                            .collect(Collectors.toList());
-                    
-                    if (needProcessIds.isEmpty()) {
-                        log.debug("配置 {} 下所有视频已有向量,跳过", configCode);
-                        continue;
+                // 3. 先进行审核过滤(每页只过滤一次,避免在 config 循环内重复调用)
+                List<Long> auditPassedIds = filterAuditPassedIds(videoIds);
+                if (auditPassedIds.isEmpty()) {
+                    log.info("第 {} 页所有视频均未通过审核,跳过", pageNum);
+                    if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                        break;
                     }
-                    log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
+                    pageNum++;
+                    continue;
+                }
+                log.info("第 {} 页审核通过 {} 个视频", pageNum, auditPassedIds.size());
 
-                    // 3.3 审核状态过滤:排除审核未通过的视频
-                    needProcessIds = filterAuditPassedIds(needProcessIds);
-                    if (needProcessIds.isEmpty()) {
-                        log.info("配置 {} 待处理视频均未通过审核,跳过", configCode);
-                        continue;
-                    }
-                    log.info("配置 {} 审核通过后需处理 {} 个视频", configCode, needProcessIds.size());
-
-                    // 3.4 批量查询需要处理的视频 raw_result
-                    for (List<Long> partition : Lists.partition(needProcessIds, 50)) {
-                        Map<Long, String> videoRawResults = batchQueryVideoRawResults(partition);
-                        if (videoRawResults.isEmpty()) {
-                            log.warn("配置 {} 未查询到任何 raw_result", configCode);
-                            continue;
-                        }
+                // 4. 对每个配置并发处理
+                ExecutorService configExecutor = Executors.newFixedThreadPool(configs.size());
+                List<Future<?>> configFutures = new ArrayList<>();
+                for (DeconstructVectorConfig config : configs) {
+                    configFutures.add(configExecutor.submit(() -> {
+                        String configCode = config.getConfigCode();
+                        try {
+                            // 4.1 查询哪些 videoId 在该配置下已有向量(数据库层已做 DISTINCT video_id)
+                            Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
+                            // 4.2 过滤出需要处理的 videoId(排除已有向量的)
+                            List<Long> needProcessIds = auditPassedIds.stream()
+                                    .filter(id -> !existingVideoIds.contains(id))
+                                    .collect(Collectors.toList());
+
+                            if (needProcessIds.isEmpty()) {
+                                log.debug("配置 {} 下所有视频已有向量,跳过", configCode);
+                                return;
+                            }
+                            log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
 
-                        // 3.5 逐个处理
-                        for (Long videoId : partition) {
-                            try {
-                                String rawResult = videoRawResults.get(videoId);
-                                if (!StringUtils.hasText(rawResult)) {
-                                    log.debug("videoId={} raw_result 为空,跳过", videoId);
-                                    totalFailCount++;
+                            // 4.3 批量查询需要处理的视频 raw_result
+                            for (List<Long> partition : Lists.partition(needProcessIds, 50)) {
+                                Map<Long, String> videoRawResults = batchQueryVideoRawResults(partition);
+                                if (videoRawResults.isEmpty()) {
+                                    log.warn("配置 {} 未查询到任何 raw_result", configCode);
                                     continue;
                                 }
 
-                                // 根据配置提取文本
-                                List<String> texts = extractTextsFromRawResult(rawResult, config);
-                                if (CollectionUtils.isEmpty(texts)) {
-                                    log.debug("videoId={} 配置 {} 未提取到文本,跳过", videoId, configCode);
-                                    totalFailCount++;
-                                    continue;
-                                }
+                                // 4.4 逐个处理
+                                for (Long videoId : partition) {
+                                    try {
+                                        String rawResult = videoRawResults.get(videoId);
+                                        if (!StringUtils.hasText(rawResult)) {
+                                            log.debug("videoId={} raw_result 为空,跳过", videoId);
+                                            totalFailCount.incrementAndGet();
+                                            continue;
+                                        }
 
-                                // 向量化并存储
-                                boolean success = vectorizeAndStore(config, videoId, texts);
-                                if (success) {
-                                    totalSuccessCount++;
-                                } else {
-                                    totalFailCount++;
-                                }
+                                        // 根据配置提取文本(支持置信度过滤)
+                                        List<String> texts = extractTextsFromRawResult(rawResult, config);
+                                        if (CollectionUtils.isEmpty(texts)) {
+                                            log.debug("videoId={} 配置 {} 未提取到文本,跳过", videoId, configCode);
+                                            totalFailCount.incrementAndGet();
+                                            continue;
+                                        }
 
-                            } catch (Exception e) {
-                                log.error("处理 videoId={} 配置 {} 时发生异常: {}", videoId, configCode, e.getMessage(), e);
-                                totalFailCount++;
+                                        // 向量化并存储(多点模式返回成功数>0即为成功)
+                                        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();
+                                    }
+                                }
                             }
+                        } catch (Exception e) {
+                            log.error("配置 {} 处理异常: {}", configCode, e.getMessage(), e);
                         }
+                    }));
+                }
+                // 等待所有配置处理完成
+                for (Future<?> future : configFutures) {
+                    try {
+                        future.get(10, TimeUnit.MINUTES);
+                    } catch (Exception e) {
+                        log.error("配置并发任务等待异常: {}", e.getMessage(), e);
                     }
                 }
+                configExecutor.shutdown();
+
                 // 如果查询到的数据少于 PAGE_SIZE,说明已经是最后一页
-                if (videoIds.size() < PAGE_SIZE) {
-                    log.info("第 {} 页数据量 {} 小于 PAGE_SIZE {},分页查询结束", pageNum, videoIds.size(), PAGE_SIZE);
+                if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                    log.info("第 {} 页数据量 {} 小于 PAGE_SIZE {},分页查询结束", pageNum, videoIds.size(), VectorConstants.PAGE_SIZE);
                     break;
                 }
                 pageNum++;
             }
-            log.info("视频向量化任务完成,总成功: {}, 总失败: {}, 总页数: {}", totalSuccessCount, totalFailCount, pageNum + 1);
+            log.info("视频向量化任务完成,总成功: {}, 总失败: {}, 总页数: {}", totalSuccessCount.get(), totalFailCount.get(), pageNum + 1);
             return ReturnT.SUCCESS;
         } catch (Exception e) {
             log.error("视频向量化任务执行失败: {}", e.getMessage(), e);
@@ -198,15 +200,6 @@ public class VideoVectorJob {
         }
     }
 
-    /**
-     * 获取启用的向量化配置(不限制内容类型)
-     */
-    private List<DeconstructVectorConfig> getEnabledConfigs() {
-        DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
-        example.createCriteria().andEnabledEqualTo((byte) 1);
-        example.setOrderByClause("priority ASC");
-        return vectorConfigMapper.selectByExample(example);
-    }
 
     /**
      * 批量查询视频的 raw_result
@@ -245,168 +238,210 @@ public class VideoVectorJob {
 
     /**
      * 根据配置从 raw_result 中提取文本
+     * 当配置了 extract_rule 时,启用置信度过滤逻辑
      */
     private List<String> extractTextsFromRawResult(String rawResult, DeconstructVectorConfig config) {
         List<String> texts = new ArrayList<>();
-        
+
         try {
             JSONObject json = JSON.parseObject(rawResult);
             if (json == null) {
                 return texts;
             }
-            
+
             String sourcePath = config.getSourcePath();
             if (!StringUtils.hasText(sourcePath)) {
                 return texts;
             }
-            
-            // 解析路径并提取
-            texts.addAll(extractFromJson(json, sourcePath));
-            
+
+            String extractRule = config.getExtractRule();
+            if (StringUtils.hasText(extractRule)) {
+                // 多点模式:从数组中提取对象,按置信度过滤后取文本字段
+                texts.addAll(extractTextsWithConfidence(json, sourcePath, extractRule));
+            } else {
+                // 单点模式:直接提取文本值(向后兼容)
+                texts.addAll(VectorUtils.extractFromJson(json, sourcePath));
+            }
+
         } catch (Exception e) {
             log.error("解析 raw_result 失败: {}", e.getMessage());
         }
-        
+
         return texts;
     }
 
     /**
-     * 从JSON中提取文本
-     * 支持路径格式:$.final_normalization_rebuild.topic_fusion_result.最终选题.选题
+     * 带置信度过滤的文本提取
+     * 支持两种路径模式:
+     * - 数组路径(以 [*] 结尾):从数组中提取满足置信度条件的文本
+     * - 单对象路径(不以 [*] 结尾):对单个对象进行置信度检查后提取文本
+     *
+     * @param json       原始JSON
+     * @param sourcePath 路径(如 $.keypoint_final.最终关键点列表[*] 或 $.最终选题)
+     * @param extractRule 提取规则JSON(如 {"text_field":"关键点","confidence_field":"置信度","confidence_threshold":0.8})
+     * @return 满足置信度条件的文本列表
      */
-    private List<String> extractFromJson(JSONObject json, String path) {
-        List<String> results = new ArrayList<>();
-        
-        if (json == null || !StringUtils.hasText(path)) {
-            return results;
-        }
-        
+    private List<String> extractTextsWithConfidence(JSONObject json, String sourcePath, String extractRule) {
+        List<String> texts = new ArrayList<>();
+
         try {
-            // 路径处理
-            if (path.startsWith("$.")) {
-                String pathContent = path.substring(2);
-                List<String> parts = parseJsonPath(pathContent);
-                Object current = json;
-
-                for (int i = 0; i < parts.size(); i++) {
-                    String part = parts.get(i);
-                    if (current == null) {
-                        break;
-                    }
+            JSONObject rule = JSON.parseObject(extractRule);
+            String textField = rule.getString("text_field");
+            String confidenceField = rule.getString("confidence_field");
+            double confidenceThreshold = rule.getDoubleValue("confidence_threshold");
 
-                    // 处理数组路径(如 topics[*])
-                    if (part.endsWith("[*]")) {
-                        String arrayKey = part.substring(0, part.length() - 3);
-                        if (current instanceof JSONObject) {
-                            JSONArray array = ((JSONObject) current).getJSONArray(arrayKey);
-                            if (array != null) {
-                                List<String> remainingParts = parts.subList(i + 1, parts.size());
-                                String remainingPath = String.join(".", remainingParts);
-                                for (int j = 0; j < array.size(); j++) {
-                                    Object item = array.get(j);
-                                    if (remainingParts.isEmpty()) {
-                                        if (item instanceof String) {
-                                            results.add((String) item);
-                                        }
-                                    } else {
-                                        results.addAll(extractFromJson(
-                                                JSON.parseObject(JSON.toJSONString(item)), "$." + remainingPath));
-                                    }
-                                }
-                            }
-                        }
-                        return results;
-                    } else {
-                        if (current instanceof JSONObject) {
-                            current = ((JSONObject) current).get(part);
-                        } else {
-                            break;
+            if (!StringUtils.hasText(textField) || !StringUtils.hasText(confidenceField)) {
+                log.warn("extract_rule 缺少必要字段: text_field={}, confidence_field={}", textField, confidenceField);
+                return texts;
+            }
+
+            if (sourcePath.endsWith("[*]")) {
+                // 数组模式:提取数组项,逐个检查置信度
+                List<JSONObject> items = VectorUtils.extractArrayItemsFromJson(json, sourcePath);
+                for (JSONObject item : items) {
+                    if (isConfidenceQualified(item, confidenceField, confidenceThreshold)) {
+                        String text = item.getString(textField);
+                        if (StringUtils.hasText(text)) {
+                            texts.add(text);
                         }
                     }
                 }
-
-                if (current instanceof String) {
-                    results.add((String) current);
-                } else if (current instanceof JSONArray) {
-                    JSONArray array = (JSONArray) current;
-                    for (int i = 0; i < array.size(); i++) {
-                        Object item = array.get(i);
-                        if (item instanceof String) {
-                            results.add((String) item);
+                log.debug("置信度过滤(数组):路径={}, 总数={}, 满足条件={}", sourcePath, items.size(), texts.size());
+            } else {
+                // 单对象模式:导航到目标对象,检查置信度后提取文本
+                List<String> pathValues = VectorUtils.extractFromJson(json, sourcePath);
+                if (!pathValues.isEmpty()) {
+                    // sourcePath 指向父对象,需要获取父对象进行置信度检查
+                    JSONObject targetObj = navigateToObject(json, sourcePath);
+                    if (targetObj != null && isConfidenceQualified(targetObj, confidenceField, confidenceThreshold)) {
+                        String text = targetObj.getString(textField);
+                        if (StringUtils.hasText(text)) {
+                            texts.add(text);
                         }
                     }
+                    log.debug("置信度过滤(单对象):路径={}, 通过={}", sourcePath, !texts.isEmpty());
                 }
             }
+
         } catch (Exception e) {
-            log.error("JSON提取失败,path={}, error={}", path, e.getMessage());
+            log.error("置信度过滤提取失败: path={}, error={}", sourcePath, e.getMessage());
         }
 
-        return results;
+        return texts;
     }
 
     /**
-     * 解析 JSONPath 路径
+     * 根据 JSON 路径导航到目标对象
+     * 支持 $.key1.key2 格式的嵌套路径
+     *
+     * @param json 原始JSON
+     * @param path 路径(如 $.最终选题)
+     * @return 目标 JSONObject,找不到返回 null
      */
-    private List<String> parseJsonPath(String pathContent) {
-        List<String> parts = new ArrayList<>();
-        StringBuilder current = new StringBuilder();
-        
-        for (int i = 0; i < pathContent.length(); i++) {
-            char c = pathContent.charAt(i);
-            if (c == '.') {
-                if (current.length() > 0) {
-                    parts.add(current.toString());
-                    current = new StringBuilder();
+    private JSONObject navigateToObject(JSONObject json, String path) {
+        if (json == null || !StringUtils.hasText(path) || !path.startsWith("$.")) {
+            return null;
+        }
+        try {
+            String pathContent = path.substring(2);
+            String[] parts = pathContent.split("\\.");
+            Object current = json;
+            for (String part : parts) {
+                if (current instanceof JSONObject) {
+                    current = ((JSONObject) current).get(part);
+                } else {
+                    return null;
                 }
-            } else {
-                current.append(c);
             }
+            return current instanceof JSONObject ? (JSONObject) current : null;
+        } catch (Exception e) {
+            return null;
         }
-        if (current.length() > 0) {
-            parts.add(current.toString());
-        }
-        
-        return parts;
     }
 
     /**
-     * 向量化并存储
-     * 取第一段有效文本进行向量化,以 configCode 为命名空间存储
-     * 避免多分段时拼接 configCode:i 导致存储键与查询键不一致
+     * 判断置信度是否满足条件
+     * 规则:置信度 == "high"(字符串)或 置信度 > threshold(数值)
      */
-    private boolean vectorizeAndStore(DeconstructVectorConfig config, Long videoId, List<String> texts) {
-        String configCode = config.getConfigCode();
-        Integer maxLength = config.getMaxLength();
-
-        // 取第一段有效文本(VIDEO_TOPIC 等配置通常只有一段)
-        String text = null;
-        for (String t : texts) {
-            if (StringUtils.hasText(t)) {
-                text = t;
-                break;
-            }
-        }
-        if (text == null) {
-            log.debug("videoId={} 配置 {} 无有效文本,跳过", videoId, configCode);
+    private boolean isConfidenceQualified(JSONObject item, String confidenceField, double threshold) {
+        Object value = item.get(confidenceField);
+        if (value == null) {
             return false;
         }
 
-        // 文本截断
-        if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
-            text = text.substring(0, maxLength);
+        if (value instanceof String) {
+            return "high".equalsIgnoreCase((String) value);
         }
-
-        // 向量化
-        List<Float> vector = embeddingService.embed(text, config);
-        if (vector == null || vector.isEmpty()) {
-            log.warn("videoId={} 配置 {} 文本向量化失败", videoId, configCode);
-            return false;
+        if (value instanceof Number) {
+            return ((Number) value).doubleValue() >= threshold;
         }
+        return false;
+    }
 
-        // 以 configCode 为命名空间存储
-        vectorStoreService.save(configCode, videoId, vector);
-        log.debug("videoId={} 配置 {} 向量化存储成功", videoId, configCode);
-        return true;
+    /**
+     * 向量化并存储视频向量
+     * <p>
+     * 多点模式(extract_rule 非空):对 texts 中每个有效文本分别向量化,
+     * 以真实 videoId + pointIndex 存入同一 configCode 命名空间,
+     * 后续搜索时每个点都可被独立匹配。
+     * <p>
+     * 单点模式(extract_rule 为空):仅取第一段有效文本向量化,
+     * 以 pointIndex=0 存储(向后兼容)。
+     *
+     * @return 成功向量化的数量(单点模式返回0或1)
+     */
+    private int vectorizeAndStore(DeconstructVectorConfig config, Long videoId, List<String> texts) {
+        String configCode = config.getConfigCode();
+        Integer maxLength = config.getMaxLength();
+        boolean multiPoint = VectorUtils.isMultiPointConfig(config);
+
+        if (multiPoint) {
+            // ---- 多点模式:每个文本独立向量化存储 ----
+            int limit = texts.size();
+            int successCount = 0;
+            for (int i = 0; i < limit; i++) {
+                String text = texts.get(i);
+                if (!StringUtils.hasText(text)) {
+                    continue;
+                }
+                if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
+                    text = text.substring(0, maxLength);
+                }
+                List<Float> vector = embeddingService.embed(text, config);
+                if (vector == null || vector.isEmpty()) {
+                    log.warn("videoId={} 配置 {} 第{}个文本向量化失败", videoId, configCode, i);
+                    continue;
+                }
+                vectorStoreService.save(configCode, videoId, i, vector, text);
+                log.debug("videoId={} 配置 {} 第{}个点向量化存储成功", videoId, configCode, i);
+                successCount++;
+            }
+            return successCount;
+        } else {
+            // ---- 单点模式:仅取第一段有效文本(向后兼容) ----
+            String text = null;
+            for (String t : texts) {
+                if (StringUtils.hasText(t)) {
+                    text = t;
+                    break;
+                }
+            }
+            if (text == null) {
+                log.debug("videoId={} 配置 {} 无有效文本,跳过", videoId, configCode);
+                return 0;
+            }
+            if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
+                text = text.substring(0, maxLength);
+            }
+            List<Float> vector = embeddingService.embed(text, config);
+            if (vector == null || vector.isEmpty()) {
+                log.warn("videoId={} 配置 {} 文本向量化失败", videoId, configCode);
+                return 0;
+            }
+            vectorStoreService.save(configCode, videoId, vector, text);
+            log.debug("videoId={} 配置 {} 向量化存储成功", videoId, configCode);
+            return 1;
+        }
     }
 
     /**
@@ -450,10 +485,10 @@ public class VideoVectorJob {
     public ReturnT<String> aigcVideoVectorJob(String param) {
         log.info("开始执行 AIGC 来源视频向量化任务, param: {}", param);
         try {
-            // 1. 获取所有启用的向量化配置
-            List<DeconstructVectorConfig> configs = getEnabledConfigs();
+            // 1. 获取 aigc_deconstruct 专用的向量化配置
+            List<DeconstructVectorConfig> configs = getEnabledConfigsBySourceField("aigc_deconstruct");
             if (CollectionUtils.isEmpty(configs)) {
-                log.warn("未找到启用的向量化配置");
+                log.warn("未找到 aigc_deconstruct 来源的向量化配置");
                 return ReturnT.SUCCESS;
             }
 
@@ -482,73 +517,89 @@ public class VideoVectorJob {
             List<Long> allVideoIds = new ArrayList<>(videoIdToTaskInstanceId.keySet());
             log.info("共 {} 个有效 videoId", allVideoIds.size());
 
-            int totalSuccessCount = 0;
-            int totalFailCount = 0;
-
-            // 4. 对每个配置进行处理
-            for (DeconstructVectorConfig config : configs) {
-                String configCode = config.getConfigCode();
-
-                // 4.1 查询该配置下已有向量的 videoId,排除已处理过的
-                Set<Long> existingIds = vectorStoreService.existsByIds(configCode, allVideoIds);
-                List<Long> needProcessIds = allVideoIds.stream()
-                        .filter(id -> !existingIds.contains(id))
-                        .collect(Collectors.toList());
-                if (needProcessIds.isEmpty()) {
-                    log.debug("配置 {} 下所有视频已有向量,跳过", configCode);
-                    continue;
-                }
-                log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
+            // 4. 先进行审核过滤(只过滤一次,避免在 config 循环内重复调用)
+            List<Long> auditPassedIds = filterAuditPassedIds(allVideoIds);
+            if (auditPassedIds.isEmpty()) {
+                log.info("所有视频均未通过审核,任务结束");
+                return ReturnT.SUCCESS;
+            }
+            log.info("审核通过 {} 个视频", auditPassedIds.size());
 
-                // 4.2 审核状态过滤:排除审核未通过的视频
-                needProcessIds = filterAuditPassedIds(needProcessIds);
-                if (needProcessIds.isEmpty()) {
-                    log.info("配置 {} 待处理视频均未通过审核,跳过", configCode);
-                    continue;
-                }
-                log.info("配置 {} 审核通过后需处理 {} 个视频", configCode, needProcessIds.size());
+            AtomicInteger totalSuccessCount = new AtomicInteger(0);
+            AtomicInteger totalFailCount = new AtomicInteger(0);
 
-                // 4.3 逐个调用 detail 接口,提取选题并向量化存储
-                for (Long videoId : needProcessIds) {
+            // 5. 对每个配置并发处理
+            ExecutorService configExecutor = Executors.newFixedThreadPool(configs.size());
+            List<Future<?>> configFutures = new ArrayList<>();
+            for (DeconstructVectorConfig config : configs) {
+                configFutures.add(configExecutor.submit(() -> {
+                    String configCode = config.getConfigCode();
                     try {
-                        Long taskInstanceId = videoIdToTaskInstanceId.get(videoId);
-                        if (taskInstanceId == null) {
-                            log.warn("videoId={} 无对应 taskInstanceId,跳过", videoId);
-                            totalFailCount++;
-                            continue;
+                        // 5.1 查询该配置下已有向量的 videoId,排除已处理过的(数据库层已做 DISTINCT video_id)
+                        Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
+                        List<Long> needProcessIds = auditPassedIds.stream()
+                                .filter(id -> !existingVideoIds.contains(id))
+                                .collect(Collectors.toList());
+                        if (needProcessIds.isEmpty()) {
+                            log.debug("配置 {} 下所有视频已有向量,跳过", configCode);
+                            return;
                         }
+                        log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
 
-                        // 调用 detail 接口获取 dataContent(解构详情)
-                        JSONObject dataContent = aigcApiService.getTaskCallbackDetail(taskInstanceId);
-                        if (dataContent == null) {
-                            log.warn("videoId={} taskInstanceId={} 获取 dataContent 失败,跳过", videoId, taskInstanceId);
-                            totalFailCount++;
-                            continue;
-                        }
+                        // 5.2 逐个调用 detail 接口,提取选题并向量化存储
+                        for (Long videoId : needProcessIds) {
+                            try {
+                                Long taskInstanceId = videoIdToTaskInstanceId.get(videoId);
+                                if (taskInstanceId == null) {
+                                    log.warn("videoId={} 无对应 taskInstanceId,跳过", videoId);
+                                    totalFailCount.incrementAndGet();
+                                    continue;
+                                }
 
-                        // 从 dataContent 中提取选题文本
-                        List<String> texts = extractTopicFromDataContent(dataContent);
-                        if (CollectionUtils.isEmpty(texts)) {
-                            log.debug("videoId={} 配置 {} 未提取到选题文本,跳过", videoId, configCode);
-                            totalFailCount++;
-                            continue;
-                        }
+                                // 调用 detail 接口获取 dataContent(解构详情)
+                                JSONObject dataContent = aigcApiService.getTaskCallbackDetail(taskInstanceId);
+                                if (dataContent == null) {
+                                    log.warn("videoId={} taskInstanceId={} 获取 dataContent 失败,跳过", videoId, taskInstanceId);
+                                    totalFailCount.incrementAndGet();
+                                    continue;
+                                }
 
-                        // 向量化并写入 Redis
-                        boolean success = vectorizeAndStore(config, videoId, texts);
-                        if (success) {
-                            totalSuccessCount++;
-                        } else {
-                            totalFailCount++;
+                                // 从 dataContent 中提取文本(支持置信度过滤)
+                                List<String> texts = extractTextsFromDataContent(dataContent, config);
+                                if (CollectionUtils.isEmpty(texts)) {
+                                    log.debug("videoId={} 配置 {} 未提取到选题文本,跳过", videoId, configCode);
+                                    totalFailCount.incrementAndGet();
+                                    continue;
+                                }
+
+                                // 向量化并存储(多点模式返回成功数>0即为成功)
+                                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();
+                            }
                         }
                     } catch (Exception e) {
-                        log.error("处理 videoId={} 配置 {} 时发生异常: {}", videoId, configCode, e.getMessage(), e);
-                        totalFailCount++;
+                        log.error("配置 {} 处理异常: {}", configCode, e.getMessage(), e);
                     }
+                }));
+            }
+            // 等待所有配置处理完成
+            for (Future<?> future : configFutures) {
+                try {
+                    future.get(30, TimeUnit.MINUTES);
+                } catch (Exception e) {
+                    log.error("配置并发任务等待异常: {}", e.getMessage(), e);
                 }
             }
+            configExecutor.shutdown();
 
-            log.info("AIGC 来源视频向量化任务完成,总成功: {}, 总失败: {}", totalSuccessCount, totalFailCount);
+            log.info("AIGC 来源视频向量化任务完成,总成功: {}, 总失败: {}", totalSuccessCount.get(), totalFailCount.get());
             return ReturnT.SUCCESS;
         } catch (Exception e) {
             log.error("AIGC 来源视频向量化任务执行失败: {}", e.getMessage(), e);
@@ -557,18 +608,40 @@ public class VideoVectorJob {
     }
 
     /**
-     * 从 dataContent 中提取选题文本
-     * 默认复用配置的 sourcePath 提取逻辑
+     * 从 dataContent 中提取文本
+     * 根据配置的 extract_rule 决定是否进行置信度过滤
      *
      * @param dataContent dataContent 解析后的 JSONObject
+     * @param config      向量化配置
      * @return 提取的文本列表
      */
+    private List<String> extractTextsFromDataContent(JSONObject dataContent, DeconstructVectorConfig config) {
+        if (dataContent == null) {
+            return Collections.emptyList();
+        }
+
+        String extractRule = config.getExtractRule();
+        if (StringUtils.hasText(extractRule)) {
+            // 多点模式:使用配置的 sourcePath 中相对路径 + 置信度过滤
+            // AIGC dataContent 的结构与 raw_result 中 final_normalization_rebuild 下的子结构一致
+            return extractTextsWithConfidence(dataContent, config.getSourcePath(), extractRule);
+        } else {
+            // 单点模式:直接提取
+            return VectorUtils.extractFromJson(dataContent, config.getSourcePath());
+        }
+    }
+
+    /**
+     * 从 dataContent 中提取选题文本(向后兼容)
+     * @deprecated 请使用 extractTextsFromDataContent(dataContent, config)
+     */
+    @Deprecated
     private List<String> extractTopicFromDataContent(JSONObject dataContent) {
         if (dataContent == null) {
             return Collections.emptyList();
         }
         String sourcePath = "$.最终选题.选题";
-        return extractFromJson(dataContent, sourcePath);
+        return VectorUtils.extractFromJson(dataContent, sourcePath);
     }
 
     /**
@@ -588,10 +661,10 @@ public class VideoVectorJob {
 
         try {
             // 1. 查询超时的任务(创建时间超过1小时,状态为PENDING或RUNNING)
-            Date timeoutThreshold = new Date(System.currentTimeMillis() - TIMEOUT_MS);
+            Date timeoutThreshold = new Date(System.currentTimeMillis() - VectorConstants.TIMEOUT_MS);
 
             DeconstructContentExample example = new DeconstructContentExample();
-            example.createCriteria().andStatusIn(Arrays.asList((byte) 0, (byte) 1))  // PENDING=0, RUNNING=1
+            example.createCriteria().andStatusIn(Arrays.asList((short) 0, (short) 1))  // PENDING=0, RUNNING=1
                     .andCreateTimeLessThanOrEqualTo(timeoutThreshold);
             List<DeconstructContent> timeoutTasks = deconstructContentMapper.selectByExample(example);
 
@@ -625,7 +698,7 @@ public class VideoVectorJob {
                     if (result.isFinished()) {
                         if (result.isSuccess()) {
                             // 成功
-                            content.setStatus((byte) 2);
+                            content.setStatus((short) 2);
                             content.setResultJson(result.getResult());
                             content.setPointUrl(result.getPointUrl());
                             content.setWeightUrl(result.getWeightUrl());
@@ -636,13 +709,13 @@ public class VideoVectorJob {
                             log.info("重试解构任务成功,taskId={}", taskId);
                         } else {
                             // 失败
-                            updateContentStatus(content, (byte) 3, result.getReason());
+                            updateContentStatus(content, (short) 3, result.getReason());
                             failCount++;
                             log.warn("重试解构任务失败,taskId={}, reason={}", taskId, result.getReason());
                         }
                     } else {
                         // 仍在处理中,更新状态
-                        content.setStatus(result.getStatus().byteValue());
+                        content.setStatus(result.getStatus().shortValue());
                         content.setUpdateTime(new Date());
                         deconstructContentMapper.updateByPrimaryKeySelective(content);
                         log.info("解构任务仍在处理中,taskId={}, status={}", taskId, result.getStatusDesc());
@@ -666,41 +739,39 @@ public class VideoVectorJob {
 
     /**
      * 检查并移除审核状态不通过的视频向量
-     * 每批次最多检查20个videoId
      *
      * @param configCode 配置编码
      */
     private void checkAndRemoveNotAuditPassedVideos(String configCode) {
         try {
-            // 获取该配置下所有已有的视频ID
-            Set<Long> allVideoIds = vectorStoreService.getAllVideoIds(configCode);
-            if (allVideoIds == null || allVideoIds.isEmpty()) {
+            // 获取该配置下所有已有的存储ID
+            Set<Long> allStoredIds = vectorStoreService.getAllVideoIds(configCode);
+            if (allStoredIds == null || allStoredIds.isEmpty()) {
                 log.debug("配置 {} 下没有已存储的向量,跳过审核检查", configCode);
                 return;
             }
 
-            log.info("配置 {} 开始检查审核状态,共 {} 个视频", configCode, allVideoIds.size());
+            log.info("配置 {} 开始检查审核状态,共 {} 个视频", configCode, allStoredIds.size());
 
-            // 分批检查审核状态
-            List<Long> videoIdList = new ArrayList<>(allVideoIds);
+            // 分批检查审核状态(数据库层 selectAllVideoIdsByConfigCode 已返回 DISTINCT video_id)
+            List<Long> videoIdList = new ArrayList<>(allStoredIds);
             int totalRemoved = 0;
 
-            for (int i = 0; i < videoIdList.size(); i += AUDIT_CHECK_BATCH_SIZE) {
-                int end = Math.min(i + AUDIT_CHECK_BATCH_SIZE, videoIdList.size());
+            for (int i = 0; i < videoIdList.size(); i += VectorConstants.AUDIT_CHECK_BATCH_SIZE) {
+                int end = Math.min(i + VectorConstants.AUDIT_CHECK_BATCH_SIZE, videoIdList.size());
                 Set<Long> batchIds = new HashSet<>(videoIdList.subList(i, end));
 
                 // 获取审核未通过的视频ID
                 Set<Long> notPassedIds = videoApiService.getNotAuditPassedVideoIds(batchIds);
 
                 if (!notPassedIds.isEmpty()) {
-                    // 批量删除审核不通过的视频向量
                     vectorStoreService.deleteBatch(configCode, notPassedIds);
                     totalRemoved += notPassedIds.size();
                     log.info("配置 {} 移除审核不通过的视频 {} 个: {}", configCode, notPassedIds.size(), notPassedIds);
                 }
             }
 
-            log.info("配置 {} 审核检查完成,共移除 {} 个视频向量", configCode, totalRemoved);
+            log.info("配置 {} 审核检查完成,共移除 {} 个向量条目", configCode, totalRemoved);
 
         } catch (Exception e) {
             log.error("配置 {} 检查审核状态失败: {}", configCode, e.getMessage(), e);
@@ -718,15 +789,37 @@ public class VideoVectorJob {
         if (CollectionUtils.isEmpty(videoIds)) {
             return Collections.emptyList();
         }
-        Set<Long> notPassedIds = new HashSet<>();
-        for (List<Long> batch : Lists.partition(videoIds, AUDIT_CHECK_BATCH_SIZE)) {
-            try {
-                Set<Long> batchNotPassed = videoApiService.getNotAuditPassedVideoIds(new HashSet<>(batch));
-                notPassedIds.addAll(batchNotPassed);
-            } catch (Exception e) {
-                log.error("审核状态查询失败,batch={}, error={}", batch, e.getMessage(), e);
+
+        List<List<Long>> batches = Lists.partition(videoIds, VectorConstants.AUDIT_CHECK_BATCH_SIZE);
+        // 并发提交所有批次的审核查询(API 限制每批 20 条,需用更多线程提升吞吐)
+        int parallelism = Math.min(batches.size(), 10);
+        ExecutorService executor = Executors.newFixedThreadPool(parallelism);
+        Set<Long> notPassedIds = ConcurrentHashMap.newKeySet();
+
+        try {
+            List<Future<?>> futures = new ArrayList<>();
+            for (List<Long> batch : batches) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        Set<Long> batchNotPassed = videoApiService.getNotAuditPassedVideoIds(new HashSet<>(batch));
+                        notPassedIds.addAll(batchNotPassed);
+                    } catch (Exception e) {
+                        log.error("审核状态查询失败,batch={}, error={}", batch, e.getMessage(), e);
+                    }
+                }));
             }
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get(30, TimeUnit.SECONDS);
+                } catch (Exception e) {
+                    log.error("审核查询任务等待异常: {}", e.getMessage(), e);
+                }
+            }
+        } finally {
+            executor.shutdown();
         }
+
         if (notPassedIds.isEmpty()) {
             return videoIds;
         }
@@ -738,13 +831,341 @@ public class VideoVectorJob {
         return passed;
     }
 
+    /**
+     * result_log 来源视频向量化任务
+     * 从 ODPS 查询播放量>10000 且有 result_log 的视频,
+     * 经过审核过滤、已向量化过滤后,查询解构内容并提取文本进行向量化
+     *
+     * @param param 参数
+     * @return 执行结果
+     */
+    @XxlJob("resultLogVideoVectorJob")
+    public ReturnT<String> resultLogVideoVectorJob(String param) {
+        log.info("开始执行 result_log 来源视频向量化任务, param: {}", param);
+
+        try {
+            // 1. 获取 source_field = 'result_log' 的启用配置
+            List<DeconstructVectorConfig> configs = getEnabledConfigsBySourceField("result_log");
+            if (CollectionUtils.isEmpty(configs)) {
+                log.warn("未找到 result_log 来源的向量化配置");
+                return ReturnT.SUCCESS;
+            }
+            log.info("加载 {} 个 result_log 向量化配置", configs.size());
+
+            // 2. 审核清理:每次 Job 执行只做一次
+            for (DeconstructVectorConfig config : configs) {
+                checkAndRemoveNotAuditPassedVideos(config.getConfigCode());
+            }
+            log.info("审核清理完成,开始分页向量化处理");
+
+            AtomicInteger totalSuccessCount = new AtomicInteger(0);
+            AtomicInteger totalFailCount = new AtomicInteger(0);
+            int pageNum = 0;
+
+            while (true) {
+                // 3. 分页查询 result_log 视频池中的 videoId
+                List<Long> videoIds = queryResultLogVideoIdsByPage(pageNum, VectorConstants.PAGE_SIZE);
+                if (CollectionUtils.isEmpty(videoIds)) {
+                    log.info("第 {} 页没有查询到数据,分页查询结束", pageNum);
+                    break;
+                }
+                log.info("第 {} 页查询到 {} 个 videoId", pageNum, videoIds.size());
+
+                // 4. 先进行审核过滤(每页只过滤一次,避免在 config 循环内重复调用)
+                List<Long> auditPassedIds = filterAuditPassedIds(videoIds);
+                if (auditPassedIds.isEmpty()) {
+                    log.info("第 {} 页所有视频均未通过审核,跳过", pageNum);
+                    if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                        break;
+                    }
+                    pageNum++;
+                    continue;
+                }
+                log.info("第 {} 页审核通过 {} 个视频", pageNum, auditPassedIds.size());
+
+                // 5. 对每个配置并发处理
+                ExecutorService configExecutor = Executors.newFixedThreadPool(configs.size());
+                List<Future<?>> configFutures = new ArrayList<>();
+                for (DeconstructVectorConfig config : configs) {
+                    configFutures.add(configExecutor.submit(() -> {
+                        String configCode = config.getConfigCode();
+                        try {
+                            // 5.1 已向量化过滤
+                            Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
+                            List<Long> needProcessIds = auditPassedIds.stream()
+                                    .filter(id -> !existingVideoIds.contains(id))
+                                    .collect(Collectors.toList());
+                            if (needProcessIds.isEmpty()) {
+                                log.debug("配置 {} 下所有视频已有向量,跳过", configCode);
+                                return;
+                            }
+                            log.info("配置 {} 需要处理 {} 个视频", configCode, needProcessIds.size());
+
+                            // 5.2 分批查询 result_log 的 data 字段并向量化
+                            for (List<Long> partition : Lists.partition(needProcessIds, 50)) {
+                                Map<Long, String> videoDataMap = batchQueryResultLogData(partition);
+                                if (videoDataMap.isEmpty()) {
+                                    log.warn("配置 {} 未查询到任何 result_log data", configCode);
+                                    continue;
+                                }
+
+                                for (Long videoId : partition) {
+                                    try {
+                                        String data = videoDataMap.get(videoId);
+                                        if (!StringUtils.hasText(data)) {
+                                            log.debug("videoId={} result_log data 为空,跳过", videoId);
+                                            totalFailCount.incrementAndGet();
+                                            continue;
+                                        }
+
+                                        // 从 data JSON 中根据配置提取文本
+                                        List<String> texts = extractTextsFromResultLogData(data, config);
+                                        if (CollectionUtils.isEmpty(texts)) {
+                                            log.debug("videoId={} 配置 {} 未提取到文本,跳过", videoId, configCode);
+                                            totalFailCount.incrementAndGet();
+                                            continue;
+                                        }
+
+                                        // 向量化并存储
+                                        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();
+                                    }
+                                }
+                            }
+                        } catch (Exception e) {
+                            log.error("配置 {} 处理异常: {}", configCode, e.getMessage(), e);
+                        }
+                    }));
+                }
+                // 等待所有配置处理完成
+                for (Future<?> future : configFutures) {
+                    try {
+                        future.get(10, TimeUnit.MINUTES);
+                    } catch (Exception e) {
+                        log.error("配置并发任务等待异常: {}", e.getMessage(), e);
+                    }
+                }
+                configExecutor.shutdown();
+
+                if (videoIds.size() < VectorConstants.PAGE_SIZE) {
+                    log.info("第 {} 页数据量 {} 小于 PAGE_SIZE {},分页查询结束", pageNum, videoIds.size(), VectorConstants.PAGE_SIZE);
+                    break;
+                }
+                pageNum++;
+            }
+
+            log.info("result_log 来源视频向量化任务完成,总成功: {}, 总失败: {}, 总页数: {}", totalSuccessCount.get(), totalFailCount.get(), pageNum + 1);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("result_log 来源视频向量化任务执行失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务执行失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取指定 source_field 的启用向量化配置
+     * 用于区分不同内容池的配置
+     *
+     * @param sourceField 来源字段标识(如 result_log)
+     * @return 匹配的启用配置列表
+     */
+    private List<DeconstructVectorConfig> getEnabledConfigsBySourceField(String sourceField) {
+        DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+        example.createCriteria()
+                .andEnabledEqualTo((short) 1)
+                .andSourceFieldEqualTo(sourceField);
+        example.setOrderByClause("priority ASC");
+        return vectorConfigMapper.selectByExample(example);
+    }
+
+    /**
+     * 分页查询 result_log 视频池中的 videoId
+     * 条件:播放量>10000 且有 result_log 记录(dt > 20240001)
+     *
+     * @param pageNum  页码(从0开始)
+     * @param pageSize 每页数量
+     * @return videoId 列表
+     */
+    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;
+    }
+
+    /**
+     * 批量查询 result_log 的 data 字段
+     *
+     * @param videoIds 视频ID列表
+     * @return videoId -> data JSON 字符串
+     */
+    private Map<Long, String> batchQueryResultLogData(List<Long> videoIds) {
+        if (CollectionUtils.isEmpty(videoIds)) {
+            return Collections.emptyMap();
+        }
+
+        String idsStr = videoIds.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<Record> records = OdpsUtil.getOdpsData(sql);
+        if (records == null || records.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        Map<Long, String> result = new HashMap<>();
+        for (Record record : records) {
+            Long videoId = Long.valueOf(record.getString(0));
+            String data = record.getString(1);
+            if (videoId != null && data != null) {
+                result.put(videoId, data);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 从 result_log 的 data 字段中提取文本
+     * data 结构参考 r.json,直接按 sourcePath 提取单点文本值
+     * 与 content_profile 的 raw_result 提取逻辑不同,此处数据结构是扁平的中文 key 嵌套
+     *
+     * @param data   result_log 的 data JSON 字符串
+     * @param config 向量化配置
+     * @return 提取的文本列表
+     */
+    private List<String> extractTextsFromResultLogData(String data, DeconstructVectorConfig config) {
+        List<String> texts = new ArrayList<>();
+
+        try {
+            JSONObject json = JSON.parseObject(data);
+            if (json == null) {
+                return texts;
+            }
+
+            String sourcePath = config.getSourcePath();
+            if (!StringUtils.hasText(sourcePath)) {
+                return texts;
+            }
+
+            // result_log 的 data 结构为扁平中文 key 嵌套(如 $.一、基础信息.内容选题)
+            // 直接使用单点模式提取
+            texts.addAll(VectorUtils.extractFromJson(json, sourcePath));
+
+        } catch (Exception e) {
+            log.error("解析 result_log data 失败: {}", e.getMessage());
+        }
+
+        return texts;
+    }
+
     /**
      * 更新内容状态为失败
      */
-    private void updateContentStatus(DeconstructContent content, byte status, String reason) {
+    private void updateContentStatus(DeconstructContent content, short status, String reason) {
         content.setStatus(status);
         content.setFailureReason(reason);
         content.setUpdateTime(new Date());
         deconstructContentMapper.updateByPrimaryKeySelective(content);
     }
+
+    /**
+     * 聚合视频向量化任务
+     * 依次执行三个视频向量化子任务:vectorVideoJob、aigcVideoVectorJob、resultLogVideoVectorJob
+     * 任一子任务失败不影响后续子任务执行
+     *
+     * @param param 参数
+     * @return 执行结果
+     */
+    @XxlJob("allVideoVectorJob")
+    public ReturnT<String> allVideoVectorJob(String param) {
+        log.info("开始执行聚合视频向量化任务, param: {}", param);
+
+        boolean hasFailure = false;
+        StringBuilder failMessages = new StringBuilder();
+
+        // 1. 执行 vectorVideoJob
+        try {
+            log.info("===== 开始执行子任务: vectorVideoJob =====");
+            ReturnT<String> result = vectorVideoJob(param);
+            if (result.getCode() != ReturnT.SUCCESS_CODE) {
+                hasFailure = true;
+                failMessages.append("vectorVideoJob失败: ").append(result.getMsg()).append("; ");
+            }
+            log.info("===== 子任务 vectorVideoJob 执行完成, code={} =====", result.getCode());
+        } catch (Exception e) {
+            hasFailure = true;
+            failMessages.append("vectorVideoJob异常: ").append(e.getMessage()).append("; ");
+            log.error("子任务 vectorVideoJob 执行异常: {}", e.getMessage(), e);
+        }
+
+        // 2. 执行 aigcVideoVectorJob
+        try {
+            log.info("===== 开始执行子任务: aigcVideoVectorJob =====");
+            ReturnT<String> result = aigcVideoVectorJob(param);
+            if (result.getCode() != ReturnT.SUCCESS_CODE) {
+                hasFailure = true;
+                failMessages.append("aigcVideoVectorJob失败: ").append(result.getMsg()).append("; ");
+            }
+            log.info("===== 子任务 aigcVideoVectorJob 执行完成, code={} =====", result.getCode());
+        } catch (Exception e) {
+            hasFailure = true;
+            failMessages.append("aigcVideoVectorJob异常: ").append(e.getMessage()).append("; ");
+            log.error("子任务 aigcVideoVectorJob 执行异常: {}", e.getMessage(), e);
+        }
+
+        // 3. 执行 resultLogVideoVectorJob
+        try {
+            log.info("===== 开始执行子任务: resultLogVideoVectorJob =====");
+            ReturnT<String> result = resultLogVideoVectorJob(param);
+            if (result.getCode() != ReturnT.SUCCESS_CODE) {
+                hasFailure = true;
+                failMessages.append("resultLogVideoVectorJob失败: ").append(result.getMsg()).append("; ");
+            }
+            log.info("===== 子任务 resultLogVideoVectorJob 执行完成, code={} =====", result.getCode());
+        } catch (Exception e) {
+            hasFailure = true;
+            failMessages.append("resultLogVideoVectorJob异常: ").append(e.getMessage()).append("; ");
+            log.error("子任务 resultLogVideoVectorJob 执行异常: {}", e.getMessage(), e);
+        }
+
+        if (hasFailure) {
+            log.warn("聚合视频向量化任务完成,部分子任务失败: {}", failMessages);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "部分子任务失败: " + failMessages);
+        }
+
+        log.info("聚合视频向量化任务全部完成");
+        return ReturnT.SUCCESS;
+    }
 }

+ 487 - 0
core/src/main/java/com/tzld/videoVector/job/VideoVectorTextBackfillJob.java

@@ -0,0 +1,487 @@
+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.google.common.collect.Lists;
+import com.tzld.videoVector.api.AigcApiService;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoVectorMapperExt;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.pgVector.VideoVector;
+import com.tzld.videoVector.util.OdpsUtil;
+import com.tzld.videoVector.util.VectorUtils;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+ * 历史数据 text 字段补充任务
+ * <p>
+ * 扫描 video_vectors 中的现有数据,根据其 config_code 对应的配置,
+ * 从原始数据源(content_profile / result_log / aigc_deconstruct)中提取文本,
+ * 回填到 video_vectors.text 字段。
+ * <p>
+ * 不检查数据状态,只要有数据就根据 config 提取对应数据。
+ */
+@Slf4j
+@Component
+public class VideoVectorTextBackfillJob {
+
+    @Resource
+    private VideoVectorMapperExt videoVectorMapperExt;
+
+    @Resource
+    private DeconstructVectorConfigMapper vectorConfigMapper;
+
+    @Resource
+    private AigcApiService aigcApiService;
+
+    /** 每批处理数量 */
+    private static final int BATCH_SIZE = 500;
+
+    /**
+     * 历史数据 text 字段补充
+     * 遍历所有启用的配置,对 video_vectors 中已有记录补充 text 字段
+     *
+     * @param param 参数(可选:指定 configCode 只处理特定配置)
+     * @return 执行结果
+     */
+    @XxlJob("videoVectorTextBackfillJob")
+    public ReturnT<String> videoVectorTextBackfillJob(String param) {
+        log.info("开始执行历史数据 text 字段补充任务, param: {}", param);
+
+        try {
+            // 获取所有启用的配置
+            List<DeconstructVectorConfig> configs = getAllEnabledConfigs();
+            if (CollectionUtils.isEmpty(configs)) {
+                log.warn("未找到启用的向量化配置");
+                return ReturnT.SUCCESS;
+            }
+
+            // 如果指定了 configCode,只处理特定配置
+            if (StringUtils.hasText(param)) {
+                configs = configs.stream()
+                        .filter(c -> param.equals(c.getConfigCode()))
+                        .collect(Collectors.toList());
+                if (configs.isEmpty()) {
+                    log.warn("未找到 configCode={} 的启用配置", param);
+                    return ReturnT.SUCCESS;
+                }
+            }
+
+            log.info("共加载 {} 个向量化配置", configs.size());
+
+            int totalUpdateCount = 0;
+            int totalSkipCount = 0;
+            int totalFailCount = 0;
+
+            // 逐个配置处理
+            for (DeconstructVectorConfig config : configs) {
+                String configCode = config.getConfigCode();
+                String sourceField = config.getSourceField();
+                log.info("开始处理配置: configCode={}, sourceField={}", configCode, sourceField);
+
+                int configUpdateCount = 0;
+                int configSkipCount = 0;
+                int configFailCount = 0;
+                int offset = 0;
+
+                while (true) {
+                    // 分页查询该配置下的记录
+                    List<VideoVector> vectors = videoVectorMapperExt.selectByConfigCodePaged(configCode, offset, BATCH_SIZE);
+                    if (CollectionUtils.isEmpty(vectors)) {
+                        log.info("配置 {} 第 {} 偏移无数据,处理结束", configCode, offset);
+                        break;
+                    }
+
+                    // 按 sourceField 类型分发处理
+                    int[] counts = processVectorBatch(vectors, config);
+                    configUpdateCount += counts[0];
+                    configSkipCount += counts[1];
+                    configFailCount += counts[2];
+
+                    if (vectors.size() < BATCH_SIZE) {
+                        break;
+                    }
+                    offset += BATCH_SIZE;
+                }
+
+                log.info("配置 {} 处理完成,更新: {}, 跳过: {}, 失败: {}",
+                        configCode, configUpdateCount, configSkipCount, configFailCount);
+                totalUpdateCount += configUpdateCount;
+                totalSkipCount += configSkipCount;
+                totalFailCount += configFailCount;
+            }
+
+            log.info("历史数据 text 字段补充任务完成,总更新: {}, 总跳过: {}, 总失败: {}",
+                    totalUpdateCount, totalSkipCount, totalFailCount);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("历史数据 text 字段补充任务执行失败: {}", e.getMessage(), e);
+            return new ReturnT<>(ReturnT.FAIL_CODE, "任务执行失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理一批 VideoVector 记录
+     * <p>
+     * 多点模式处理策略:
+     * 原始向量化时 texts[i] 对应 point_index=i,回填时保持相同映射。
+     * 由于置信度过滤可能导致重新提取的文本列表与原始不同,
+     * 当 point_index 超出当前提取的 texts 范围时直接跳过,避免错误匹配。
+     * <p>
+     * 单点模式(point_index=0):直接取第一个有效文本。
+     *
+     * @return int[]{更新数, 跳过数, 失败数}
+     */
+    private int[] processVectorBatch(List<VideoVector> vectors, DeconstructVectorConfig config) {
+        String sourceField = config.getSourceField();
+        boolean multiPoint = VectorUtils.isMultiPointConfig(config);
+        AtomicInteger updateCount = new AtomicInteger(0);
+        AtomicInteger skipCount = new AtomicInteger(0);
+        AtomicInteger failCount = new AtomicInteger(0);
+
+        // 收集需要处理的 videoId(去重)
+        List<Long> videoIds = vectors.stream()
+                .map(VideoVector::getVideoId)
+                .distinct()
+                .collect(Collectors.toList());
+
+        // 根据 sourceField 获取原始数据
+        Map<Long, String> rawDataMap;
+        switch (sourceField) {
+            case "result_json":
+                rawDataMap = batchQueryVideoRawResults(videoIds);
+                break;
+            case "result_log":
+                rawDataMap = batchQueryResultLogData(videoIds);
+                break;
+            case "aigc_deconstruct":
+                rawDataMap = batchQueryAigcData(videoIds);
+                break;
+            default:
+                log.warn("不支持的 sourceField: {}", sourceField);
+                return new int[]{0, vectors.size(), 0};
+        }
+
+        // 缓存每个 videoId 提取的文本列表,避免对同一 videoId 重复解析
+        Map<Long, List<String>> textsCache = new HashMap<>();
+
+        // 遍历每条记录,提取文本并更新
+        for (VideoVector vector : vectors) {
+            try {
+                Long videoId = vector.getVideoId();
+                String rawData = rawDataMap.get(videoId);
+                if (!StringUtils.hasText(rawData)) {
+                    skipCount.incrementAndGet();
+                    continue;
+                }
+
+                // 根据配置提取文本(相同 videoId 复用缓存)
+                List<String> texts = textsCache.computeIfAbsent(videoId,
+                        id -> extractTexts(rawData, config));
+                if (CollectionUtils.isEmpty(texts)) {
+                    skipCount.incrementAndGet();
+                    continue;
+                }
+
+                // 根据 pointIndex 取对应的文本
+                int pointIndex = vector.getPointIndex() != null ? vector.getPointIndex() : 0;
+                String text;
+
+                if (multiPoint) {
+                    // 多点模式:严格按 point_index 映射,超出范围则跳过
+                    if (pointIndex < texts.size()) {
+                        text = texts.get(pointIndex);
+                    } else {
+                        log.debug("videoId={} point_index={} 超出提取文本范围(size={}),跳过",
+                                videoId, pointIndex, texts.size());
+                        skipCount.incrementAndGet();
+                        continue;
+                    }
+                } else {
+                    // 单点模式:取第一个有效文本
+                    text = texts.stream()
+                            .filter(StringUtils::hasText)
+                            .findFirst()
+                            .orElse(null);
+                }
+
+                if (!StringUtils.hasText(text)) {
+                    skipCount.incrementAndGet();
+                    continue;
+                }
+
+                // 截断文本
+                Integer maxLength = config.getMaxLength();
+                if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
+                    text = text.substring(0, maxLength);
+                }
+
+                // 更新 text 字段
+                videoVectorMapperExt.updateTextById(vector.getId(), text);
+                updateCount.incrementAndGet();
+            } catch (Exception e) {
+                log.error("处理 videoVector id={} videoId={} 时异常: {}",
+                        vector.getId(), vector.getVideoId(), e.getMessage());
+                failCount.incrementAndGet();
+            }
+        }
+
+        return new int[]{updateCount.get(), skipCount.get(), failCount.get()};
+    }
+
+    /**
+     * 根据配置从原始数据中提取文本
+     */
+    private List<String> extractTexts(String rawData, DeconstructVectorConfig config) {
+        List<String> texts = new ArrayList<>();
+        try {
+            JSONObject json = JSON.parseObject(rawData);
+            if (json == null) {
+                return texts;
+            }
+
+            String sourcePath = config.getSourcePath();
+            if (!StringUtils.hasText(sourcePath)) {
+                return texts;
+            }
+
+            String extractRule = config.getExtractRule();
+            if (StringUtils.hasText(extractRule)) {
+                // 多点模式:带置信度过滤
+                texts.addAll(extractTextsWithConfidence(json, sourcePath, extractRule));
+            } else {
+                // 单点模式:直接提取
+                texts.addAll(VectorUtils.extractFromJson(json, sourcePath));
+            }
+        } catch (Exception e) {
+            log.error("解析原始数据失败: {}", e.getMessage());
+        }
+        return texts;
+    }
+
+    /**
+     * 带置信度过滤的文本提取
+     */
+    private List<String> extractTextsWithConfidence(JSONObject json, String sourcePath, String extractRule) {
+        List<String> texts = new ArrayList<>();
+        try {
+            JSONObject rule = JSON.parseObject(extractRule);
+            String textField = rule.getString("text_field");
+            String confidenceField = rule.getString("confidence_field");
+            double confidenceThreshold = rule.getDoubleValue("confidence_threshold");
+
+            if (!StringUtils.hasText(textField) || !StringUtils.hasText(confidenceField)) {
+                return texts;
+            }
+
+            if (sourcePath.endsWith("[*]")) {
+                // 数组路径模式
+                String arrayPath = sourcePath.substring(0, sourcePath.length() - 3);
+                Object arrayObj = navigateToValue(json, arrayPath);
+                if (arrayObj instanceof JSONArray) {
+                    JSONArray array = (JSONArray) arrayObj;
+                    for (int i = 0; i < array.size(); i++) {
+                        JSONObject item = array.getJSONObject(i);
+                        if (item != null && isConfidenceQualified(item, confidenceField, confidenceThreshold)) {
+                            String text = item.getString(textField);
+                            if (StringUtils.hasText(text)) {
+                                texts.add(text);
+                            }
+                        }
+                    }
+                }
+            } else {
+                // 单对象路径模式
+                Object obj = navigateToValue(json, sourcePath);
+                if (obj instanceof JSONObject) {
+                    JSONObject item = (JSONObject) obj;
+                    if (isConfidenceQualified(item, confidenceField, confidenceThreshold)) {
+                        String text = item.getString(textField);
+                        if (StringUtils.hasText(text)) {
+                            texts.add(text);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("置信度提取失败: {}", e.getMessage());
+        }
+        return texts;
+    }
+
+    /**
+     * 根据 JSON 路径导航到目标值
+     */
+    private Object navigateToValue(JSONObject json, String path) {
+        if (json == null || !StringUtils.hasText(path) || !path.startsWith("$.")) {
+            return null;
+        }
+        try {
+            String pathContent = path.substring(2);
+            String[] parts = pathContent.split("\\.");
+            Object current = json;
+            for (String part : parts) {
+                if (current instanceof JSONObject) {
+                    current = ((JSONObject) current).get(part);
+                } else {
+                    return null;
+                }
+            }
+            return current;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 判断置信度是否满足条件
+     */
+    private boolean isConfidenceQualified(JSONObject item, String confidenceField, double threshold) {
+        Object value = item.get(confidenceField);
+        if (value == null) {
+            return false;
+        }
+        if (value instanceof String) {
+            return "high".equalsIgnoreCase((String) value);
+        }
+        if (value instanceof Number) {
+            return ((Number) value).doubleValue() >= threshold;
+        }
+        return false;
+    }
+
+    // ========================== 数据源查询方法 ==========================
+
+    /**
+     * 批量查询 content_profile 的 raw_result
+     */
+    private Map<Long, String> batchQueryVideoRawResults(List<Long> videoIds) {
+        Map<Long, String> result = new HashMap<>();
+        for (List<Long> partition : Lists.partition(videoIds, 200)) {
+            String idsStr = partition.stream()
+                    .map(String::valueOf)
+                    .collect(Collectors.joining(","));
+            String sql = String.format(
+                    "SELECT content_id, raw_result " +
+                            "FROM videoods.content_profile " +
+                            "WHERE content_id IN (%s);",
+                    idsStr);
+            try {
+                List<Record> records = OdpsUtil.getOdpsData(sql);
+                if (records != null) {
+                    for (Record record : records) {
+                        Long videoId = Long.valueOf(record.getString(0));
+                        String rawResult = record.getString(1);
+                        if (videoId != null && rawResult != null) {
+                            result.put(videoId, rawResult);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("查询 content_profile raw_result 失败: {}", e.getMessage());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 批量查询 result_log 的 data 字段
+     */
+    private Map<Long, String> batchQueryResultLogData(List<Long> videoIds) {
+        Map<Long, String> result = new HashMap<>();
+        for (List<Long> partition : Lists.partition(videoIds, 200)) {
+            String idsStr = partition.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);
+            try {
+                List<Record> records = OdpsUtil.getOdpsData(sql);
+                if (records != null) {
+                    for (Record record : records) {
+                        Long videoId = Long.valueOf(record.getString(0));
+                        String data = record.getString(1);
+                        if (videoId != null && data != null) {
+                            result.put(videoId, data);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("查询 result_log data 失败: {}", e.getMessage());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 批量查询 AIGC 解构数据
+     * 通过 AIGC API 获取 dataContent
+     */
+    private Map<Long, String> batchQueryAigcData(List<Long> videoIds) {
+        Map<Long, String> result = new HashMap<>();
+        try {
+            // 获取 AIGC 任务输入列表
+            List<AigcApiService.AigcTaskInput> taskInputList = aigcApiService.getTaskInputList(46);
+            if (CollectionUtils.isEmpty(taskInputList)) {
+                return result;
+            }
+
+            // 构建 videoId -> taskInstanceId 映射
+            Map<Long, Long> videoIdToTaskInstanceId = new HashMap<>();
+            for (AigcApiService.AigcTaskInput input : taskInputList) {
+                try {
+                    Long videoId = Long.parseLong(input.getBizUniqueId());
+                    videoIdToTaskInstanceId.put(videoId, input.getTaskInstanceId());
+                } catch (NumberFormatException e) {
+                    // 忽略格式非法的
+                }
+            }
+
+            // 对需要的 videoId 查询 dataContent
+            Set<Long> targetIds = new HashSet<>(videoIds);
+            for (Map.Entry<Long, Long> entry : videoIdToTaskInstanceId.entrySet()) {
+                Long videoId = entry.getKey();
+                if (!targetIds.contains(videoId)) {
+                    continue;
+                }
+                try {
+                    JSONObject dataContent = aigcApiService.getTaskCallbackDetail(entry.getValue());
+                    if (dataContent != null) {
+                        result.put(videoId, dataContent.toJSONString());
+                    }
+                } catch (Exception e) {
+                    log.error("查询 AIGC dataContent 失败, videoId={}: {}", videoId, e.getMessage());
+                }
+            }
+        } catch (Exception e) {
+            log.error("批量查询 AIGC 数据失败: {}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 获取所有启用的向量化配置
+     */
+    private List<DeconstructVectorConfig> getAllEnabledConfigs() {
+        DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+        example.createCriteria().andEnabledEqualTo((short) 1);
+        example.setOrderByClause("priority ASC");
+        return vectorConfigMapper.selectByExample(example);
+    }
+}

+ 31 - 1
core/src/main/java/com/tzld/videoVector/model/entity/VideoMatch.java

@@ -11,11 +11,23 @@ public class VideoMatch {
     /** 余弦相似度分值(-1 ~ 1,越大越相似) */
     private double score;
 
+    /** 命中的配置编码(用于区分来源) */
+    private String configCode;
+
+    /** 向量点索引(多点模式下区分同一视频的不同向量点) */
+    private Integer pointIndex;
+
     public VideoMatch(Long videoId, double score) {
         this.videoId = videoId;
         this.score = score;
     }
 
+    public VideoMatch(Long videoId, double score, String configCode) {
+        this.videoId = videoId;
+        this.score = score;
+        this.configCode = configCode;
+    }
+
     public Long getVideoId() {
         return videoId;
     }
@@ -32,8 +44,26 @@ public class VideoMatch {
         this.score = score;
     }
 
+    public String getConfigCode() {
+        return configCode;
+    }
+
+    public void setConfigCode(String configCode) {
+        this.configCode = configCode;
+    }
+
     @Override
     public String toString() {
-        return "VideoMatch{videoId=" + videoId + ", score=" + score + '}';
+        return "VideoMatch{videoId=" + videoId + ", score=" + score +
+                ", configCode='" + configCode + "'" +
+                ", pointIndex=" + pointIndex + "}";
+    }
+
+    public Integer getPointIndex() {
+        return pointIndex;
+    }
+
+    public void setPointIndex(Integer pointIndex) {
+        this.pointIndex = pointIndex;
     }
 }

+ 27 - 0
core/src/main/java/com/tzld/videoVector/model/param/MaterialMatchParam.java

@@ -0,0 +1,27 @@
+package com.tzld.videoVector.model.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 素材相似搜索请求参数
+ */
+@Data
+public class MaterialMatchParam {
+
+    /** 查询文本,将被向量化后进行检索 */
+    private String queryText;
+
+    /** 直接传入查询向量(与 queryText 二选一,优先使用此字段) */
+    private List<Float> queryVector;
+
+    /** 业务内容ID,用于复用历史向量(可选) */
+    private String channelContentId;
+
+    /** 配置编码(默认复用 VIDEO_TOPIC 等通用配置,传 ALL 搜所有启用配置) */
+    private String configCode;
+
+    /** 返回 Top-N 结果数量,默认 10 */
+    private Integer topN;
+}

+ 36 - 0
core/src/main/java/com/tzld/videoVector/model/param/MaterialSubmitParam.java

@@ -0,0 +1,36 @@
+package com.tzld.videoVector.model.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 素材入库请求参数
+ */
+@Data
+public class MaterialSubmitParam {
+
+    /** 素材业务ID(复用 deconstruct_content.channel_content_id) */
+    private String channelContentId;
+
+    /** 内容类型:1长文 2图文 3视频 */
+    private Integer contentType;
+
+    /** 素材标题 */
+    private String title;
+
+    /** 素材正文/描述 */
+    private String bodyText;
+
+    /** 视频地址(可选) */
+    private String videoUrl;
+
+    /** 图片列表(可选) */
+    private List<String> imageList;
+
+    /** 作者ID(可选) */
+    private String channelAccountId;
+
+    /** 作者名称(可选) */
+    private String channelAccountName;
+}

+ 495 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/ContentVector.java

@@ -0,0 +1,495 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.Date;
+
+/**
+ *
+ * This class was generated by MyBatis Generator.
+ * This class corresponds to the database table content_vectors
+ */
+public class ContentVector {
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private Long id;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.content_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private Long contentId;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String taskId;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String configCode;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String sourceField;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String sourcePath;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.text_hash
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String textHash;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String embeddingModel;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.segment_index
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private Integer segmentIndex;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.segment_total
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private Integer segmentTotal;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.source_text
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    private String sourceText;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.created_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    private Date createdAt;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column content_vectors.updated_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    private Date updatedAt;
+
+    /**
+     * embedding 向量字段(vector(1024)类型,以字符串形式传递)
+     * 格式示例: "[0.1,0.2,...]"
+     */
+    private String embedding;
+
+    public String getEmbedding() {
+        return embedding;
+    }
+
+    public void setEmbedding(String embedding) {
+        this.embedding = embedding;
+    }
+
+    /**
+     * 余弦相似度得分(仅搜索时使用,非持久化字段)
+     */
+    private Double score;
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.id
+     *
+     * @return the value of content_vectors.id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.id
+     *
+     * @param id the value for content_vectors.id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.content_id
+     *
+     * @return the value of content_vectors.content_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public Long getContentId() {
+        return contentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.content_id
+     *
+     * @param contentId the value for content_vectors.content_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setContentId(Long contentId) {
+        this.contentId = contentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.task_id
+     *
+     * @return the value of content_vectors.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getTaskId() {
+        return taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.task_id
+     *
+     * @param taskId the value for content_vectors.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setTaskId(String taskId) {
+        this.taskId = taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.config_code
+     *
+     * @return the value of content_vectors.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getConfigCode() {
+        return configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.config_code
+     *
+     * @param configCode the value for content_vectors.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setConfigCode(String configCode) {
+        this.configCode = configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.source_field
+     *
+     * @return the value of content_vectors.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getSourceField() {
+        return sourceField;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.source_field
+     *
+     * @param sourceField the value for content_vectors.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setSourceField(String sourceField) {
+        this.sourceField = sourceField;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.source_path
+     *
+     * @return the value of content_vectors.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getSourcePath() {
+        return sourcePath;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.source_path
+     *
+     * @param sourcePath the value for content_vectors.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setSourcePath(String sourcePath) {
+        this.sourcePath = sourcePath;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.text_hash
+     *
+     * @return the value of content_vectors.text_hash
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getTextHash() {
+        return textHash;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.text_hash
+     *
+     * @param textHash the value for content_vectors.text_hash
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setTextHash(String textHash) {
+        this.textHash = textHash;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.embedding_model
+     *
+     * @return the value of content_vectors.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getEmbeddingModel() {
+        return embeddingModel;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.embedding_model
+     *
+     * @param embeddingModel the value for content_vectors.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setEmbeddingModel(String embeddingModel) {
+        this.embeddingModel = embeddingModel;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.segment_index
+     *
+     * @return the value of content_vectors.segment_index
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public Integer getSegmentIndex() {
+        return segmentIndex;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.segment_index
+     *
+     * @param segmentIndex the value for content_vectors.segment_index
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setSegmentIndex(Integer segmentIndex) {
+        this.segmentIndex = segmentIndex;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.segment_total
+     *
+     * @return the value of content_vectors.segment_total
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public Integer getSegmentTotal() {
+        return segmentTotal;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.segment_total
+     *
+     * @param segmentTotal the value for content_vectors.segment_total
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setSegmentTotal(Integer segmentTotal) {
+        this.segmentTotal = segmentTotal;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.source_text
+     *
+     * @return the value of content_vectors.source_text
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public String getSourceText() {
+        return sourceText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.source_text
+     *
+     * @param sourceText the value for content_vectors.source_text
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public void setSourceText(String sourceText) {
+        this.sourceText = sourceText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.created_at
+     *
+     * @return the value of content_vectors.created_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.created_at
+     *
+     * @param createdAt the value for content_vectors.created_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column content_vectors.updated_at
+     *
+     * @return the value of content_vectors.updated_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column content_vectors.updated_at
+     *
+     * @param updatedAt the value for content_vectors.updated_at
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:37 CST 2026
+     */
+    @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(", contentId=").append(contentId);
+        sb.append(", taskId=").append(taskId);
+        sb.append(", configCode=").append(configCode);
+        sb.append(", sourceField=").append(sourceField);
+        sb.append(", sourcePath=").append(sourcePath);
+        sb.append(", textHash=").append(textHash);
+        sb.append(", embeddingModel=").append(embeddingModel);
+        sb.append(", segmentIndex=").append(segmentIndex);
+        sb.append(", segmentTotal=").append(segmentTotal);
+        sb.append(", sourceText=").append(sourceText);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", updatedAt=").append(updatedAt);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1153 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/ContentVectorExample.java

@@ -0,0 +1,1153 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class ContentVectorExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public ContentVectorExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    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 content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    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 content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    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 andContentIdIsNull() {
+            addCriterion("content_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdIsNotNull() {
+            addCriterion("content_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdEqualTo(Long value) {
+            addCriterion("content_id =", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotEqualTo(Long value) {
+            addCriterion("content_id <>", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdGreaterThan(Long value) {
+            addCriterion("content_id >", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("content_id >=", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdLessThan(Long value) {
+            addCriterion("content_id <", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdLessThanOrEqualTo(Long value) {
+            addCriterion("content_id <=", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdIn(List<Long> values) {
+            addCriterion("content_id in", values, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotIn(List<Long> values) {
+            addCriterion("content_id not in", values, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdBetween(Long value1, Long value2) {
+            addCriterion("content_id between", value1, value2, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotBetween(Long value1, Long value2) {
+            addCriterion("content_id not between", value1, value2, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIsNull() {
+            addCriterion("task_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIsNotNull() {
+            addCriterion("task_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdEqualTo(String value) {
+            addCriterion("task_id =", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotEqualTo(String value) {
+            addCriterion("task_id <>", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThan(String value) {
+            addCriterion("task_id >", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThanOrEqualTo(String value) {
+            addCriterion("task_id >=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThan(String value) {
+            addCriterion("task_id <", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThanOrEqualTo(String value) {
+            addCriterion("task_id <=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLike(String value) {
+            addCriterion("task_id like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotLike(String value) {
+            addCriterion("task_id not like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIn(List<String> values) {
+            addCriterion("task_id in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotIn(List<String> values) {
+            addCriterion("task_id not in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdBetween(String value1, String value2) {
+            addCriterion("task_id between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotBetween(String value1, String value2) {
+            addCriterion("task_id not between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIsNull() {
+            addCriterion("config_code is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIsNotNull() {
+            addCriterion("config_code is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeEqualTo(String value) {
+            addCriterion("config_code =", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotEqualTo(String value) {
+            addCriterion("config_code <>", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThan(String value) {
+            addCriterion("config_code >", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThanOrEqualTo(String value) {
+            addCriterion("config_code >=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThan(String value) {
+            addCriterion("config_code <", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThanOrEqualTo(String value) {
+            addCriterion("config_code <=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLike(String value) {
+            addCriterion("config_code like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotLike(String value) {
+            addCriterion("config_code not like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIn(List<String> values) {
+            addCriterion("config_code in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotIn(List<String> values) {
+            addCriterion("config_code not in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeBetween(String value1, String value2) {
+            addCriterion("config_code between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotBetween(String value1, String value2) {
+            addCriterion("config_code not between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIsNull() {
+            addCriterion("source_field is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIsNotNull() {
+            addCriterion("source_field is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldEqualTo(String value) {
+            addCriterion("source_field =", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotEqualTo(String value) {
+            addCriterion("source_field <>", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldGreaterThan(String value) {
+            addCriterion("source_field >", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldGreaterThanOrEqualTo(String value) {
+            addCriterion("source_field >=", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLessThan(String value) {
+            addCriterion("source_field <", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLessThanOrEqualTo(String value) {
+            addCriterion("source_field <=", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLike(String value) {
+            addCriterion("source_field like", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotLike(String value) {
+            addCriterion("source_field not like", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIn(List<String> values) {
+            addCriterion("source_field in", values, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotIn(List<String> values) {
+            addCriterion("source_field not in", values, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldBetween(String value1, String value2) {
+            addCriterion("source_field between", value1, value2, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotBetween(String value1, String value2) {
+            addCriterion("source_field not between", value1, value2, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIsNull() {
+            addCriterion("source_path is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIsNotNull() {
+            addCriterion("source_path is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathEqualTo(String value) {
+            addCriterion("source_path =", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotEqualTo(String value) {
+            addCriterion("source_path <>", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathGreaterThan(String value) {
+            addCriterion("source_path >", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathGreaterThanOrEqualTo(String value) {
+            addCriterion("source_path >=", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLessThan(String value) {
+            addCriterion("source_path <", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLessThanOrEqualTo(String value) {
+            addCriterion("source_path <=", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLike(String value) {
+            addCriterion("source_path like", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotLike(String value) {
+            addCriterion("source_path not like", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIn(List<String> values) {
+            addCriterion("source_path in", values, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotIn(List<String> values) {
+            addCriterion("source_path not in", values, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathBetween(String value1, String value2) {
+            addCriterion("source_path between", value1, value2, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotBetween(String value1, String value2) {
+            addCriterion("source_path not between", value1, value2, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashIsNull() {
+            addCriterion("text_hash is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashIsNotNull() {
+            addCriterion("text_hash is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashEqualTo(String value) {
+            addCriterion("text_hash =", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashNotEqualTo(String value) {
+            addCriterion("text_hash <>", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashGreaterThan(String value) {
+            addCriterion("text_hash >", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashGreaterThanOrEqualTo(String value) {
+            addCriterion("text_hash >=", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashLessThan(String value) {
+            addCriterion("text_hash <", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashLessThanOrEqualTo(String value) {
+            addCriterion("text_hash <=", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashLike(String value) {
+            addCriterion("text_hash like", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashNotLike(String value) {
+            addCriterion("text_hash not like", value, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashIn(List<String> values) {
+            addCriterion("text_hash in", values, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashNotIn(List<String> values) {
+            addCriterion("text_hash not in", values, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashBetween(String value1, String value2) {
+            addCriterion("text_hash between", value1, value2, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextHashNotBetween(String value1, String value2) {
+            addCriterion("text_hash not between", value1, value2, "textHash");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIsNull() {
+            addCriterion("embedding_model is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIsNotNull() {
+            addCriterion("embedding_model is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelEqualTo(String value) {
+            addCriterion("embedding_model =", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotEqualTo(String value) {
+            addCriterion("embedding_model <>", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelGreaterThan(String value) {
+            addCriterion("embedding_model >", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelGreaterThanOrEqualTo(String value) {
+            addCriterion("embedding_model >=", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLessThan(String value) {
+            addCriterion("embedding_model <", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLessThanOrEqualTo(String value) {
+            addCriterion("embedding_model <=", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLike(String value) {
+            addCriterion("embedding_model like", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotLike(String value) {
+            addCriterion("embedding_model not like", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIn(List<String> values) {
+            addCriterion("embedding_model in", values, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotIn(List<String> values) {
+            addCriterion("embedding_model not in", values, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelBetween(String value1, String value2) {
+            addCriterion("embedding_model between", value1, value2, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotBetween(String value1, String value2) {
+            addCriterion("embedding_model not between", value1, value2, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexIsNull() {
+            addCriterion("segment_index is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexIsNotNull() {
+            addCriterion("segment_index is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexEqualTo(Integer value) {
+            addCriterion("segment_index =", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexNotEqualTo(Integer value) {
+            addCriterion("segment_index <>", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexGreaterThan(Integer value) {
+            addCriterion("segment_index >", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexGreaterThanOrEqualTo(Integer value) {
+            addCriterion("segment_index >=", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexLessThan(Integer value) {
+            addCriterion("segment_index <", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexLessThanOrEqualTo(Integer value) {
+            addCriterion("segment_index <=", value, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexIn(List<Integer> values) {
+            addCriterion("segment_index in", values, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexNotIn(List<Integer> values) {
+            addCriterion("segment_index not in", values, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexBetween(Integer value1, Integer value2) {
+            addCriterion("segment_index between", value1, value2, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentIndexNotBetween(Integer value1, Integer value2) {
+            addCriterion("segment_index not between", value1, value2, "segmentIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalIsNull() {
+            addCriterion("segment_total is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalIsNotNull() {
+            addCriterion("segment_total is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalEqualTo(Integer value) {
+            addCriterion("segment_total =", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalNotEqualTo(Integer value) {
+            addCriterion("segment_total <>", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalGreaterThan(Integer value) {
+            addCriterion("segment_total >", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalGreaterThanOrEqualTo(Integer value) {
+            addCriterion("segment_total >=", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalLessThan(Integer value) {
+            addCriterion("segment_total <", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalLessThanOrEqualTo(Integer value) {
+            addCriterion("segment_total <=", value, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalIn(List<Integer> values) {
+            addCriterion("segment_total in", values, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalNotIn(List<Integer> values) {
+            addCriterion("segment_total not in", values, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalBetween(Integer value1, Integer value2) {
+            addCriterion("segment_total between", value1, value2, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentTotalNotBetween(Integer value1, Integer value2) {
+            addCriterion("segment_total not between", value1, value2, "segmentTotal");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextIsNull() {
+            addCriterion("source_text is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextIsNotNull() {
+            addCriterion("source_text is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextEqualTo(String value) {
+            addCriterion("source_text =", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextNotEqualTo(String value) {
+            addCriterion("source_text <>", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextGreaterThan(String value) {
+            addCriterion("source_text >", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextGreaterThanOrEqualTo(String value) {
+            addCriterion("source_text >=", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextLessThan(String value) {
+            addCriterion("source_text <", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextLessThanOrEqualTo(String value) {
+            addCriterion("source_text <=", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextLike(String value) {
+            addCriterion("source_text like", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextNotLike(String value) {
+            addCriterion("source_text not like", value, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextIn(List<String> values) {
+            addCriterion("source_text in", values, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextNotIn(List<String> values) {
+            addCriterion("source_text not in", values, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextBetween(String value1, String value2) {
+            addCriterion("source_text between", value1, value2, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceTextNotBetween(String value1, String value2) {
+            addCriterion("source_text not between", value1, value2, "sourceText");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIsNull() {
+            addCriterion("created_at is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIsNotNull() {
+            addCriterion("created_at is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtEqualTo(Date value) {
+            addCriterion("created_at =", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotEqualTo(Date value) {
+            addCriterion("created_at <>", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtGreaterThan(Date value) {
+            addCriterion("created_at >", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtGreaterThanOrEqualTo(Date value) {
+            addCriterion("created_at >=", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtLessThan(Date value) {
+            addCriterion("created_at <", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtLessThanOrEqualTo(Date value) {
+            addCriterion("created_at <=", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIn(List<Date> values) {
+            addCriterion("created_at in", values, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotIn(List<Date> values) {
+            addCriterion("created_at not in", values, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtBetween(Date value1, Date value2) {
+            addCriterion("created_at between", value1, value2, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotBetween(Date value1, Date value2) {
+            addCriterion("created_at not between", value1, value2, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIsNull() {
+            addCriterion("updated_at is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIsNotNull() {
+            addCriterion("updated_at is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtEqualTo(Date value) {
+            addCriterion("updated_at =", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotEqualTo(Date value) {
+            addCriterion("updated_at <>", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtGreaterThan(Date value) {
+            addCriterion("updated_at >", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtGreaterThanOrEqualTo(Date value) {
+            addCriterion("updated_at >=", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtLessThan(Date value) {
+            addCriterion("updated_at <", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtLessThanOrEqualTo(Date value) {
+            addCriterion("updated_at <=", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIn(List<Date> values) {
+            addCriterion("updated_at in", values, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotIn(List<Date> values) {
+            addCriterion("updated_at not in", values, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtBetween(Date value1, Date value2) {
+            addCriterion("updated_at between", value1, value2, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotBetween(Date value1, Date value2) {
+            addCriterion("updated_at not between", value1, value2, "updatedAt");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table content_vectors
+     *
+     * @mbg.generated do_not_delete_during_merge Wed Apr 29 15:27:36 CST 2026
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table content_vectors
+     *
+     * @mbg.generated Wed Apr 29 15:27:36 CST 2026
+     */
+    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);
+        }
+    }
+}

+ 712 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructContent.java

@@ -0,0 +1,712 @@
+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 deconstruct_content
+ */
+public class DeconstructContent {
+    /**
+     * Database Column Remarks:
+     *   主键ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   解构任务ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String taskId;
+
+    /**
+     * Database Column Remarks:
+     *   业务类型:0选题 1创作 2制作
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short bizType;
+
+    /**
+     * Database Column Remarks:
+     *   内容类型:1长文 2图文 3视频
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short contentType;
+
+    /**
+     * Database Column Remarks:
+     *   业务内容ID(帖子ID/视频ID)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.channel_content_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String channelContentId;
+
+    /**
+     * Database Column Remarks:
+     *   标题
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.title
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String title;
+
+    /**
+     * Database Column Remarks:
+     *   正文内容
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.body_text
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String bodyText;
+
+    /**
+     * Database Column Remarks:
+     *   视频地址
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.video_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String videoUrl;
+
+    /**
+     * Database Column Remarks:
+     *   图片列表(JSON数组)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.images
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String images;
+
+    /**
+     * Database Column Remarks:
+     *   作者ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.channel_account_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String channelAccountId;
+
+    /**
+     * Database Column Remarks:
+     *   作者名称
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.channel_account_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String channelAccountName;
+
+    /**
+     * Database Column Remarks:
+     *   任务状态:0待处理 1处理中 2成功 3失败
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.status
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short status;
+
+    /**
+     * Database Column Remarks:
+     *   解构结果JSON字符串
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.result_json
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String resultJson;
+
+    /**
+     * Database Column Remarks:
+     *   失败原因
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.failure_reason
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String failureReason;
+
+    /**
+     * Database Column Remarks:
+     *   选题点结构结果页地址
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.point_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String pointUrl;
+
+    /**
+     * Database Column Remarks:
+     *   权重页地址
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.weight_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String weightUrl;
+
+    /**
+     * Database Column Remarks:
+     *   选题点聚类结果页地址
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.pattern_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String patternUrl;
+
+    /**
+     * Database Column Remarks:
+     *   创建时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Date createTime;
+
+    /**
+     * Database Column Remarks:
+     *   更新时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_content.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Date updateTime;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.id
+     *
+     * @return the value of deconstruct_content.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.id
+     *
+     * @param id the value for deconstruct_content.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.task_id
+     *
+     * @return the value of deconstruct_content.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getTaskId() {
+        return taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.task_id
+     *
+     * @param taskId the value for deconstruct_content.task_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setTaskId(String taskId) {
+        this.taskId = taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.biz_type
+     *
+     * @return the value of deconstruct_content.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getBizType() {
+        return bizType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.biz_type
+     *
+     * @param bizType the value for deconstruct_content.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setBizType(Short bizType) {
+        this.bizType = bizType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.content_type
+     *
+     * @return the value of deconstruct_content.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getContentType() {
+        return contentType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.content_type
+     *
+     * @param contentType the value for deconstruct_content.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setContentType(Short contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.channel_content_id
+     *
+     * @return the value of deconstruct_content.channel_content_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getChannelContentId() {
+        return channelContentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.channel_content_id
+     *
+     * @param channelContentId the value for deconstruct_content.channel_content_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setChannelContentId(String channelContentId) {
+        this.channelContentId = channelContentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.title
+     *
+     * @return the value of deconstruct_content.title
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.title
+     *
+     * @param title the value for deconstruct_content.title
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.body_text
+     *
+     * @return the value of deconstruct_content.body_text
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getBodyText() {
+        return bodyText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.body_text
+     *
+     * @param bodyText the value for deconstruct_content.body_text
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setBodyText(String bodyText) {
+        this.bodyText = bodyText;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.video_url
+     *
+     * @return the value of deconstruct_content.video_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.video_url
+     *
+     * @param videoUrl the value for deconstruct_content.video_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.images
+     *
+     * @return the value of deconstruct_content.images
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getImages() {
+        return images;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.images
+     *
+     * @param images the value for deconstruct_content.images
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setImages(String images) {
+        this.images = images;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.channel_account_id
+     *
+     * @return the value of deconstruct_content.channel_account_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getChannelAccountId() {
+        return channelAccountId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.channel_account_id
+     *
+     * @param channelAccountId the value for deconstruct_content.channel_account_id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setChannelAccountId(String channelAccountId) {
+        this.channelAccountId = channelAccountId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.channel_account_name
+     *
+     * @return the value of deconstruct_content.channel_account_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getChannelAccountName() {
+        return channelAccountName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.channel_account_name
+     *
+     * @param channelAccountName the value for deconstruct_content.channel_account_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setChannelAccountName(String channelAccountName) {
+        this.channelAccountName = channelAccountName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.status
+     *
+     * @return the value of deconstruct_content.status
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getStatus() {
+        return status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.status
+     *
+     * @param status the value for deconstruct_content.status
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setStatus(Short status) {
+        this.status = status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.result_json
+     *
+     * @return the value of deconstruct_content.result_json
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getResultJson() {
+        return resultJson;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.result_json
+     *
+     * @param resultJson the value for deconstruct_content.result_json
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setResultJson(String resultJson) {
+        this.resultJson = resultJson;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.failure_reason
+     *
+     * @return the value of deconstruct_content.failure_reason
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getFailureReason() {
+        return failureReason;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.failure_reason
+     *
+     * @param failureReason the value for deconstruct_content.failure_reason
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setFailureReason(String failureReason) {
+        this.failureReason = failureReason;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.point_url
+     *
+     * @return the value of deconstruct_content.point_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getPointUrl() {
+        return pointUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.point_url
+     *
+     * @param pointUrl the value for deconstruct_content.point_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setPointUrl(String pointUrl) {
+        this.pointUrl = pointUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.weight_url
+     *
+     * @return the value of deconstruct_content.weight_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getWeightUrl() {
+        return weightUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.weight_url
+     *
+     * @param weightUrl the value for deconstruct_content.weight_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setWeightUrl(String weightUrl) {
+        this.weightUrl = weightUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.pattern_url
+     *
+     * @return the value of deconstruct_content.pattern_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getPatternUrl() {
+        return patternUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.pattern_url
+     *
+     * @param patternUrl the value for deconstruct_content.pattern_url
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setPatternUrl(String patternUrl) {
+        this.patternUrl = patternUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.create_time
+     *
+     * @return the value of deconstruct_content.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.create_time
+     *
+     * @param createTime the value for deconstruct_content.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_content.update_time
+     *
+     * @return the value of deconstruct_content.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_content.update_time
+     *
+     * @param updateTime the value for deconstruct_content.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    @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(", taskId=").append(taskId);
+        sb.append(", bizType=").append(bizType);
+        sb.append(", contentType=").append(contentType);
+        sb.append(", channelContentId=").append(channelContentId);
+        sb.append(", title=").append(title);
+        sb.append(", bodyText=").append(bodyText);
+        sb.append(", videoUrl=").append(videoUrl);
+        sb.append(", images=").append(images);
+        sb.append(", channelAccountId=").append(channelAccountId);
+        sb.append(", channelAccountName=").append(channelAccountName);
+        sb.append(", status=").append(status);
+        sb.append(", resultJson=").append(resultJson);
+        sb.append(", failureReason=").append(failureReason);
+        sb.append(", pointUrl=").append(pointUrl);
+        sb.append(", weightUrl=").append(weightUrl);
+        sb.append(", patternUrl=").append(patternUrl);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1573 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructContentExample.java

@@ -0,0 +1,1573 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class DeconstructContentExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public DeconstructContentExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 andTaskIdIsNull() {
+            addCriterion("task_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIsNotNull() {
+            addCriterion("task_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdEqualTo(String value) {
+            addCriterion("task_id =", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotEqualTo(String value) {
+            addCriterion("task_id <>", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThan(String value) {
+            addCriterion("task_id >", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThanOrEqualTo(String value) {
+            addCriterion("task_id >=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThan(String value) {
+            addCriterion("task_id <", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThanOrEqualTo(String value) {
+            addCriterion("task_id <=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLike(String value) {
+            addCriterion("task_id like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotLike(String value) {
+            addCriterion("task_id not like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIn(List<String> values) {
+            addCriterion("task_id in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotIn(List<String> values) {
+            addCriterion("task_id not in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdBetween(String value1, String value2) {
+            addCriterion("task_id between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotBetween(String value1, String value2) {
+            addCriterion("task_id not between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIsNull() {
+            addCriterion("biz_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIsNotNull() {
+            addCriterion("biz_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeEqualTo(Short value) {
+            addCriterion("biz_type =", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotEqualTo(Short value) {
+            addCriterion("biz_type <>", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeGreaterThan(Short value) {
+            addCriterion("biz_type >", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeGreaterThanOrEqualTo(Short value) {
+            addCriterion("biz_type >=", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeLessThan(Short value) {
+            addCriterion("biz_type <", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeLessThanOrEqualTo(Short value) {
+            addCriterion("biz_type <=", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIn(List<Short> values) {
+            addCriterion("biz_type in", values, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotIn(List<Short> values) {
+            addCriterion("biz_type not in", values, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeBetween(Short value1, Short value2) {
+            addCriterion("biz_type between", value1, value2, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotBetween(Short value1, Short value2) {
+            addCriterion("biz_type not between", value1, value2, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIsNull() {
+            addCriterion("content_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIsNotNull() {
+            addCriterion("content_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeEqualTo(Short value) {
+            addCriterion("content_type =", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotEqualTo(Short value) {
+            addCriterion("content_type <>", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeGreaterThan(Short value) {
+            addCriterion("content_type >", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeGreaterThanOrEqualTo(Short value) {
+            addCriterion("content_type >=", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeLessThan(Short value) {
+            addCriterion("content_type <", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeLessThanOrEqualTo(Short value) {
+            addCriterion("content_type <=", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIn(List<Short> values) {
+            addCriterion("content_type in", values, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotIn(List<Short> values) {
+            addCriterion("content_type not in", values, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeBetween(Short value1, Short value2) {
+            addCriterion("content_type between", value1, value2, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotBetween(Short value1, Short value2) {
+            addCriterion("content_type not between", value1, value2, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdIsNull() {
+            addCriterion("channel_content_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdIsNotNull() {
+            addCriterion("channel_content_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdEqualTo(String value) {
+            addCriterion("channel_content_id =", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdNotEqualTo(String value) {
+            addCriterion("channel_content_id <>", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdGreaterThan(String value) {
+            addCriterion("channel_content_id >", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdGreaterThanOrEqualTo(String value) {
+            addCriterion("channel_content_id >=", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdLessThan(String value) {
+            addCriterion("channel_content_id <", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdLessThanOrEqualTo(String value) {
+            addCriterion("channel_content_id <=", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdLike(String value) {
+            addCriterion("channel_content_id like", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdNotLike(String value) {
+            addCriterion("channel_content_id not like", value, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdIn(List<String> values) {
+            addCriterion("channel_content_id in", values, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdNotIn(List<String> values) {
+            addCriterion("channel_content_id not in", values, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdBetween(String value1, String value2) {
+            addCriterion("channel_content_id between", value1, value2, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelContentIdNotBetween(String value1, String value2) {
+            addCriterion("channel_content_id not between", value1, value2, "channelContentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIsNull() {
+            addCriterion("title is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIsNotNull() {
+            addCriterion("title is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleEqualTo(String value) {
+            addCriterion("title =", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotEqualTo(String value) {
+            addCriterion("title <>", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleGreaterThan(String value) {
+            addCriterion("title >", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleGreaterThanOrEqualTo(String value) {
+            addCriterion("title >=", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLessThan(String value) {
+            addCriterion("title <", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLessThanOrEqualTo(String value) {
+            addCriterion("title <=", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLike(String value) {
+            addCriterion("title like", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotLike(String value) {
+            addCriterion("title not like", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIn(List<String> values) {
+            addCriterion("title in", values, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotIn(List<String> values) {
+            addCriterion("title not in", values, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleBetween(String value1, String value2) {
+            addCriterion("title between", value1, value2, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotBetween(String value1, String value2) {
+            addCriterion("title not between", value1, value2, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextIsNull() {
+            addCriterion("body_text is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextIsNotNull() {
+            addCriterion("body_text is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextEqualTo(String value) {
+            addCriterion("body_text =", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextNotEqualTo(String value) {
+            addCriterion("body_text <>", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextGreaterThan(String value) {
+            addCriterion("body_text >", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextGreaterThanOrEqualTo(String value) {
+            addCriterion("body_text >=", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextLessThan(String value) {
+            addCriterion("body_text <", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextLessThanOrEqualTo(String value) {
+            addCriterion("body_text <=", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextLike(String value) {
+            addCriterion("body_text like", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextNotLike(String value) {
+            addCriterion("body_text not like", value, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextIn(List<String> values) {
+            addCriterion("body_text in", values, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextNotIn(List<String> values) {
+            addCriterion("body_text not in", values, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextBetween(String value1, String value2) {
+            addCriterion("body_text between", value1, value2, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andBodyTextNotBetween(String value1, String value2) {
+            addCriterion("body_text not between", value1, value2, "bodyText");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlIsNull() {
+            addCriterion("video_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlIsNotNull() {
+            addCriterion("video_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlEqualTo(String value) {
+            addCriterion("video_url =", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlNotEqualTo(String value) {
+            addCriterion("video_url <>", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlGreaterThan(String value) {
+            addCriterion("video_url >", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("video_url >=", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlLessThan(String value) {
+            addCriterion("video_url <", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlLessThanOrEqualTo(String value) {
+            addCriterion("video_url <=", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlLike(String value) {
+            addCriterion("video_url like", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlNotLike(String value) {
+            addCriterion("video_url not like", value, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlIn(List<String> values) {
+            addCriterion("video_url in", values, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlNotIn(List<String> values) {
+            addCriterion("video_url not in", values, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlBetween(String value1, String value2) {
+            addCriterion("video_url between", value1, value2, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andVideoUrlNotBetween(String value1, String value2) {
+            addCriterion("video_url not between", value1, value2, "videoUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesIsNull() {
+            addCriterion("images is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesIsNotNull() {
+            addCriterion("images is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesEqualTo(String value) {
+            addCriterion("images =", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesNotEqualTo(String value) {
+            addCriterion("images <>", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesGreaterThan(String value) {
+            addCriterion("images >", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesGreaterThanOrEqualTo(String value) {
+            addCriterion("images >=", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesLessThan(String value) {
+            addCriterion("images <", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesLessThanOrEqualTo(String value) {
+            addCriterion("images <=", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesLike(String value) {
+            addCriterion("images like", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesNotLike(String value) {
+            addCriterion("images not like", value, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesIn(List<String> values) {
+            addCriterion("images in", values, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesNotIn(List<String> values) {
+            addCriterion("images not in", values, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesBetween(String value1, String value2) {
+            addCriterion("images between", value1, value2, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andImagesNotBetween(String value1, String value2) {
+            addCriterion("images not between", value1, value2, "images");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdIsNull() {
+            addCriterion("channel_account_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdIsNotNull() {
+            addCriterion("channel_account_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdEqualTo(String value) {
+            addCriterion("channel_account_id =", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdNotEqualTo(String value) {
+            addCriterion("channel_account_id <>", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdGreaterThan(String value) {
+            addCriterion("channel_account_id >", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdGreaterThanOrEqualTo(String value) {
+            addCriterion("channel_account_id >=", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdLessThan(String value) {
+            addCriterion("channel_account_id <", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdLessThanOrEqualTo(String value) {
+            addCriterion("channel_account_id <=", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdLike(String value) {
+            addCriterion("channel_account_id like", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdNotLike(String value) {
+            addCriterion("channel_account_id not like", value, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdIn(List<String> values) {
+            addCriterion("channel_account_id in", values, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdNotIn(List<String> values) {
+            addCriterion("channel_account_id not in", values, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdBetween(String value1, String value2) {
+            addCriterion("channel_account_id between", value1, value2, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountIdNotBetween(String value1, String value2) {
+            addCriterion("channel_account_id not between", value1, value2, "channelAccountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameIsNull() {
+            addCriterion("channel_account_name is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameIsNotNull() {
+            addCriterion("channel_account_name is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameEqualTo(String value) {
+            addCriterion("channel_account_name =", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameNotEqualTo(String value) {
+            addCriterion("channel_account_name <>", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameGreaterThan(String value) {
+            addCriterion("channel_account_name >", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameGreaterThanOrEqualTo(String value) {
+            addCriterion("channel_account_name >=", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameLessThan(String value) {
+            addCriterion("channel_account_name <", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameLessThanOrEqualTo(String value) {
+            addCriterion("channel_account_name <=", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameLike(String value) {
+            addCriterion("channel_account_name like", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameNotLike(String value) {
+            addCriterion("channel_account_name not like", value, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameIn(List<String> values) {
+            addCriterion("channel_account_name in", values, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameNotIn(List<String> values) {
+            addCriterion("channel_account_name not in", values, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameBetween(String value1, String value2) {
+            addCriterion("channel_account_name between", value1, value2, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelAccountNameNotBetween(String value1, String value2) {
+            addCriterion("channel_account_name not between", value1, value2, "channelAccountName");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNull() {
+            addCriterion("\"status\" is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNotNull() {
+            addCriterion("\"status\" is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusEqualTo(Short value) {
+            addCriterion("\"status\" =", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotEqualTo(Short value) {
+            addCriterion("\"status\" <>", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThan(Short value) {
+            addCriterion("\"status\" >", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThanOrEqualTo(Short value) {
+            addCriterion("\"status\" >=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThan(Short value) {
+            addCriterion("\"status\" <", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThanOrEqualTo(Short value) {
+            addCriterion("\"status\" <=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIn(List<Short> values) {
+            addCriterion("\"status\" in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotIn(List<Short> values) {
+            addCriterion("\"status\" not in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusBetween(Short value1, Short value2) {
+            addCriterion("\"status\" between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotBetween(Short value1, Short value2) {
+            addCriterion("\"status\" not between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonIsNull() {
+            addCriterion("result_json is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonIsNotNull() {
+            addCriterion("result_json is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonEqualTo(String value) {
+            addCriterion("result_json =", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonNotEqualTo(String value) {
+            addCriterion("result_json <>", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonGreaterThan(String value) {
+            addCriterion("result_json >", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonGreaterThanOrEqualTo(String value) {
+            addCriterion("result_json >=", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonLessThan(String value) {
+            addCriterion("result_json <", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonLessThanOrEqualTo(String value) {
+            addCriterion("result_json <=", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonLike(String value) {
+            addCriterion("result_json like", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonNotLike(String value) {
+            addCriterion("result_json not like", value, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonIn(List<String> values) {
+            addCriterion("result_json in", values, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonNotIn(List<String> values) {
+            addCriterion("result_json not in", values, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonBetween(String value1, String value2) {
+            addCriterion("result_json between", value1, value2, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andResultJsonNotBetween(String value1, String value2) {
+            addCriterion("result_json not between", value1, value2, "resultJson");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonIsNull() {
+            addCriterion("failure_reason is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonIsNotNull() {
+            addCriterion("failure_reason is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonEqualTo(String value) {
+            addCriterion("failure_reason =", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonNotEqualTo(String value) {
+            addCriterion("failure_reason <>", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonGreaterThan(String value) {
+            addCriterion("failure_reason >", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonGreaterThanOrEqualTo(String value) {
+            addCriterion("failure_reason >=", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonLessThan(String value) {
+            addCriterion("failure_reason <", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonLessThanOrEqualTo(String value) {
+            addCriterion("failure_reason <=", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonLike(String value) {
+            addCriterion("failure_reason like", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonNotLike(String value) {
+            addCriterion("failure_reason not like", value, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonIn(List<String> values) {
+            addCriterion("failure_reason in", values, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonNotIn(List<String> values) {
+            addCriterion("failure_reason not in", values, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonBetween(String value1, String value2) {
+            addCriterion("failure_reason between", value1, value2, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andFailureReasonNotBetween(String value1, String value2) {
+            addCriterion("failure_reason not between", value1, value2, "failureReason");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlIsNull() {
+            addCriterion("point_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlIsNotNull() {
+            addCriterion("point_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlEqualTo(String value) {
+            addCriterion("point_url =", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlNotEqualTo(String value) {
+            addCriterion("point_url <>", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlGreaterThan(String value) {
+            addCriterion("point_url >", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("point_url >=", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlLessThan(String value) {
+            addCriterion("point_url <", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlLessThanOrEqualTo(String value) {
+            addCriterion("point_url <=", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlLike(String value) {
+            addCriterion("point_url like", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlNotLike(String value) {
+            addCriterion("point_url not like", value, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlIn(List<String> values) {
+            addCriterion("point_url in", values, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlNotIn(List<String> values) {
+            addCriterion("point_url not in", values, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlBetween(String value1, String value2) {
+            addCriterion("point_url between", value1, value2, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointUrlNotBetween(String value1, String value2) {
+            addCriterion("point_url not between", value1, value2, "pointUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlIsNull() {
+            addCriterion("weight_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlIsNotNull() {
+            addCriterion("weight_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlEqualTo(String value) {
+            addCriterion("weight_url =", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlNotEqualTo(String value) {
+            addCriterion("weight_url <>", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlGreaterThan(String value) {
+            addCriterion("weight_url >", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("weight_url >=", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlLessThan(String value) {
+            addCriterion("weight_url <", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlLessThanOrEqualTo(String value) {
+            addCriterion("weight_url <=", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlLike(String value) {
+            addCriterion("weight_url like", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlNotLike(String value) {
+            addCriterion("weight_url not like", value, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlIn(List<String> values) {
+            addCriterion("weight_url in", values, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlNotIn(List<String> values) {
+            addCriterion("weight_url not in", values, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlBetween(String value1, String value2) {
+            addCriterion("weight_url between", value1, value2, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andWeightUrlNotBetween(String value1, String value2) {
+            addCriterion("weight_url not between", value1, value2, "weightUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlIsNull() {
+            addCriterion("pattern_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlIsNotNull() {
+            addCriterion("pattern_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlEqualTo(String value) {
+            addCriterion("pattern_url =", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlNotEqualTo(String value) {
+            addCriterion("pattern_url <>", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlGreaterThan(String value) {
+            addCriterion("pattern_url >", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("pattern_url >=", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlLessThan(String value) {
+            addCriterion("pattern_url <", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlLessThanOrEqualTo(String value) {
+            addCriterion("pattern_url <=", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlLike(String value) {
+            addCriterion("pattern_url like", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlNotLike(String value) {
+            addCriterion("pattern_url not like", value, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlIn(List<String> values) {
+            addCriterion("pattern_url in", values, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlNotIn(List<String> values) {
+            addCriterion("pattern_url not in", values, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlBetween(String value1, String value2) {
+            addCriterion("pattern_url between", value1, value2, "patternUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andPatternUrlNotBetween(String value1, String value2) {
+            addCriterion("pattern_url not between", value1, value2, "patternUrl");
+            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 deconstruct_content
+     *
+     * @mbg.generated do_not_delete_during_merge Wed Apr 29 15:35:34 CST 2026
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table deconstruct_content
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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);
+        }
+    }
+}

+ 640 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructVectorConfig.java

@@ -0,0 +1,640 @@
+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 deconstruct_vector_config
+ */
+public class DeconstructVectorConfig {
+    /**
+     * Database Column Remarks:
+     *   主键ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   配置编码
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String configCode;
+
+    /**
+     * Database Column Remarks:
+     *   配置名称
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.config_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String configName;
+
+    /**
+     * Database Column Remarks:
+     *   业务类型:0选题 1创作 2制作(null表示全部)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short bizType;
+
+    /**
+     * Database Column Remarks:
+     *   内容类型:1长文 2图文 3视频(null表示全部)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short contentType;
+
+    /**
+     * Database Column Remarks:
+     *   来源字段名
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String sourceField;
+
+    /**
+     * Database Column Remarks:
+     *   JSON提取路径
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String sourcePath;
+
+    /**
+     * Database Column Remarks:
+     *   提取规则(正则/JSONPath等)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.extract_rule
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String extractRule;
+
+    /**
+     * Database Column Remarks:
+     *   默认嵌入模型
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private String embeddingModel;
+
+    /**
+     * Database Column Remarks:
+     *   维度
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.dimension
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Integer dimension;
+
+    /**
+     * Database Column Remarks:
+     *   最大文本长度
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.max_length
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Integer maxLength;
+
+    /**
+     * Database Column Remarks:
+     *   是否分段:0否 1是
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.enable_segment
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short enableSegment;
+
+    /**
+     * Database Column Remarks:
+     *   分段大小
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.segment_size
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Integer segmentSize;
+
+    /**
+     * Database Column Remarks:
+     *   优先级(数字越小越优先)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.priority
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Integer priority;
+
+    /**
+     * Database Column Remarks:
+     *   是否启用:0禁用 1启用
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.enabled
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Short enabled;
+
+    /**
+     * Database Column Remarks:
+     *   创建时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Date createTime;
+
+    /**
+     * Database Column Remarks:
+     *   更新时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column deconstruct_vector_config.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    private Date updateTime;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.id
+     *
+     * @return the value of deconstruct_vector_config.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.id
+     *
+     * @param id the value for deconstruct_vector_config.id
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.config_code
+     *
+     * @return the value of deconstruct_vector_config.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getConfigCode() {
+        return configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.config_code
+     *
+     * @param configCode the value for deconstruct_vector_config.config_code
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setConfigCode(String configCode) {
+        this.configCode = configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.config_name
+     *
+     * @return the value of deconstruct_vector_config.config_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getConfigName() {
+        return configName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.config_name
+     *
+     * @param configName the value for deconstruct_vector_config.config_name
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setConfigName(String configName) {
+        this.configName = configName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.biz_type
+     *
+     * @return the value of deconstruct_vector_config.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getBizType() {
+        return bizType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.biz_type
+     *
+     * @param bizType the value for deconstruct_vector_config.biz_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setBizType(Short bizType) {
+        this.bizType = bizType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.content_type
+     *
+     * @return the value of deconstruct_vector_config.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getContentType() {
+        return contentType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.content_type
+     *
+     * @param contentType the value for deconstruct_vector_config.content_type
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setContentType(Short contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.source_field
+     *
+     * @return the value of deconstruct_vector_config.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getSourceField() {
+        return sourceField;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.source_field
+     *
+     * @param sourceField the value for deconstruct_vector_config.source_field
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setSourceField(String sourceField) {
+        this.sourceField = sourceField;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.source_path
+     *
+     * @return the value of deconstruct_vector_config.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getSourcePath() {
+        return sourcePath;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.source_path
+     *
+     * @param sourcePath the value for deconstruct_vector_config.source_path
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setSourcePath(String sourcePath) {
+        this.sourcePath = sourcePath;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.extract_rule
+     *
+     * @return the value of deconstruct_vector_config.extract_rule
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getExtractRule() {
+        return extractRule;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.extract_rule
+     *
+     * @param extractRule the value for deconstruct_vector_config.extract_rule
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setExtractRule(String extractRule) {
+        this.extractRule = extractRule;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.embedding_model
+     *
+     * @return the value of deconstruct_vector_config.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getEmbeddingModel() {
+        return embeddingModel;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.embedding_model
+     *
+     * @param embeddingModel the value for deconstruct_vector_config.embedding_model
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setEmbeddingModel(String embeddingModel) {
+        this.embeddingModel = embeddingModel;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.dimension
+     *
+     * @return the value of deconstruct_vector_config.dimension
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Integer getDimension() {
+        return dimension;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.dimension
+     *
+     * @param dimension the value for deconstruct_vector_config.dimension
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setDimension(Integer dimension) {
+        this.dimension = dimension;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.max_length
+     *
+     * @return the value of deconstruct_vector_config.max_length
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Integer getMaxLength() {
+        return maxLength;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.max_length
+     *
+     * @param maxLength the value for deconstruct_vector_config.max_length
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setMaxLength(Integer maxLength) {
+        this.maxLength = maxLength;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.enable_segment
+     *
+     * @return the value of deconstruct_vector_config.enable_segment
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getEnableSegment() {
+        return enableSegment;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.enable_segment
+     *
+     * @param enableSegment the value for deconstruct_vector_config.enable_segment
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setEnableSegment(Short enableSegment) {
+        this.enableSegment = enableSegment;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.segment_size
+     *
+     * @return the value of deconstruct_vector_config.segment_size
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Integer getSegmentSize() {
+        return segmentSize;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.segment_size
+     *
+     * @param segmentSize the value for deconstruct_vector_config.segment_size
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setSegmentSize(Integer segmentSize) {
+        this.segmentSize = segmentSize;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.priority
+     *
+     * @return the value of deconstruct_vector_config.priority
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Integer getPriority() {
+        return priority;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.priority
+     *
+     * @param priority the value for deconstruct_vector_config.priority
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.enabled
+     *
+     * @return the value of deconstruct_vector_config.enabled
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Short getEnabled() {
+        return enabled;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.enabled
+     *
+     * @param enabled the value for deconstruct_vector_config.enabled
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setEnabled(Short enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.create_time
+     *
+     * @return the value of deconstruct_vector_config.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.create_time
+     *
+     * @param createTime the value for deconstruct_vector_config.create_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column deconstruct_vector_config.update_time
+     *
+     * @return the value of deconstruct_vector_config.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column deconstruct_vector_config.update_time
+     *
+     * @param updateTime the value for deconstruct_vector_config.update_time
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    @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(", configCode=").append(configCode);
+        sb.append(", configName=").append(configName);
+        sb.append(", bizType=").append(bizType);
+        sb.append(", contentType=").append(contentType);
+        sb.append(", sourceField=").append(sourceField);
+        sb.append(", sourcePath=").append(sourcePath);
+        sb.append(", extractRule=").append(extractRule);
+        sb.append(", embeddingModel=").append(embeddingModel);
+        sb.append(", dimension=").append(dimension);
+        sb.append(", maxLength=").append(maxLength);
+        sb.append(", enableSegment=").append(enableSegment);
+        sb.append(", segmentSize=").append(segmentSize);
+        sb.append(", priority=").append(priority);
+        sb.append(", enabled=").append(enabled);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1383 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/DeconstructVectorConfigExample.java

@@ -0,0 +1,1383 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class DeconstructVectorConfigExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public DeconstructVectorConfigExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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 andConfigCodeIsNull() {
+            addCriterion("config_code is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIsNotNull() {
+            addCriterion("config_code is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeEqualTo(String value) {
+            addCriterion("config_code =", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotEqualTo(String value) {
+            addCriterion("config_code <>", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThan(String value) {
+            addCriterion("config_code >", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThanOrEqualTo(String value) {
+            addCriterion("config_code >=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThan(String value) {
+            addCriterion("config_code <", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThanOrEqualTo(String value) {
+            addCriterion("config_code <=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLike(String value) {
+            addCriterion("config_code like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotLike(String value) {
+            addCriterion("config_code not like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIn(List<String> values) {
+            addCriterion("config_code in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotIn(List<String> values) {
+            addCriterion("config_code not in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeBetween(String value1, String value2) {
+            addCriterion("config_code between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotBetween(String value1, String value2) {
+            addCriterion("config_code not between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameIsNull() {
+            addCriterion("config_name is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameIsNotNull() {
+            addCriterion("config_name is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameEqualTo(String value) {
+            addCriterion("config_name =", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameNotEqualTo(String value) {
+            addCriterion("config_name <>", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameGreaterThan(String value) {
+            addCriterion("config_name >", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameGreaterThanOrEqualTo(String value) {
+            addCriterion("config_name >=", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameLessThan(String value) {
+            addCriterion("config_name <", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameLessThanOrEqualTo(String value) {
+            addCriterion("config_name <=", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameLike(String value) {
+            addCriterion("config_name like", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameNotLike(String value) {
+            addCriterion("config_name not like", value, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameIn(List<String> values) {
+            addCriterion("config_name in", values, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameNotIn(List<String> values) {
+            addCriterion("config_name not in", values, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameBetween(String value1, String value2) {
+            addCriterion("config_name between", value1, value2, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigNameNotBetween(String value1, String value2) {
+            addCriterion("config_name not between", value1, value2, "configName");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIsNull() {
+            addCriterion("biz_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIsNotNull() {
+            addCriterion("biz_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeEqualTo(Short value) {
+            addCriterion("biz_type =", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotEqualTo(Short value) {
+            addCriterion("biz_type <>", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeGreaterThan(Short value) {
+            addCriterion("biz_type >", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeGreaterThanOrEqualTo(Short value) {
+            addCriterion("biz_type >=", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeLessThan(Short value) {
+            addCriterion("biz_type <", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeLessThanOrEqualTo(Short value) {
+            addCriterion("biz_type <=", value, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeIn(List<Short> values) {
+            addCriterion("biz_type in", values, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotIn(List<Short> values) {
+            addCriterion("biz_type not in", values, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeBetween(Short value1, Short value2) {
+            addCriterion("biz_type between", value1, value2, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andBizTypeNotBetween(Short value1, Short value2) {
+            addCriterion("biz_type not between", value1, value2, "bizType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIsNull() {
+            addCriterion("content_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIsNotNull() {
+            addCriterion("content_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeEqualTo(Short value) {
+            addCriterion("content_type =", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotEqualTo(Short value) {
+            addCriterion("content_type <>", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeGreaterThan(Short value) {
+            addCriterion("content_type >", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeGreaterThanOrEqualTo(Short value) {
+            addCriterion("content_type >=", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeLessThan(Short value) {
+            addCriterion("content_type <", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeLessThanOrEqualTo(Short value) {
+            addCriterion("content_type <=", value, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeIn(List<Short> values) {
+            addCriterion("content_type in", values, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotIn(List<Short> values) {
+            addCriterion("content_type not in", values, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeBetween(Short value1, Short value2) {
+            addCriterion("content_type between", value1, value2, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentTypeNotBetween(Short value1, Short value2) {
+            addCriterion("content_type not between", value1, value2, "contentType");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIsNull() {
+            addCriterion("source_field is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIsNotNull() {
+            addCriterion("source_field is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldEqualTo(String value) {
+            addCriterion("source_field =", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotEqualTo(String value) {
+            addCriterion("source_field <>", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldGreaterThan(String value) {
+            addCriterion("source_field >", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldGreaterThanOrEqualTo(String value) {
+            addCriterion("source_field >=", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLessThan(String value) {
+            addCriterion("source_field <", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLessThanOrEqualTo(String value) {
+            addCriterion("source_field <=", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldLike(String value) {
+            addCriterion("source_field like", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotLike(String value) {
+            addCriterion("source_field not like", value, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldIn(List<String> values) {
+            addCriterion("source_field in", values, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotIn(List<String> values) {
+            addCriterion("source_field not in", values, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldBetween(String value1, String value2) {
+            addCriterion("source_field between", value1, value2, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceFieldNotBetween(String value1, String value2) {
+            addCriterion("source_field not between", value1, value2, "sourceField");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIsNull() {
+            addCriterion("source_path is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIsNotNull() {
+            addCriterion("source_path is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathEqualTo(String value) {
+            addCriterion("source_path =", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotEqualTo(String value) {
+            addCriterion("source_path <>", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathGreaterThan(String value) {
+            addCriterion("source_path >", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathGreaterThanOrEqualTo(String value) {
+            addCriterion("source_path >=", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLessThan(String value) {
+            addCriterion("source_path <", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLessThanOrEqualTo(String value) {
+            addCriterion("source_path <=", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathLike(String value) {
+            addCriterion("source_path like", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotLike(String value) {
+            addCriterion("source_path not like", value, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathIn(List<String> values) {
+            addCriterion("source_path in", values, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotIn(List<String> values) {
+            addCriterion("source_path not in", values, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathBetween(String value1, String value2) {
+            addCriterion("source_path between", value1, value2, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourcePathNotBetween(String value1, String value2) {
+            addCriterion("source_path not between", value1, value2, "sourcePath");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleIsNull() {
+            addCriterion("extract_rule is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleIsNotNull() {
+            addCriterion("extract_rule is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleEqualTo(String value) {
+            addCriterion("extract_rule =", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleNotEqualTo(String value) {
+            addCriterion("extract_rule <>", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleGreaterThan(String value) {
+            addCriterion("extract_rule >", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleGreaterThanOrEqualTo(String value) {
+            addCriterion("extract_rule >=", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleLessThan(String value) {
+            addCriterion("extract_rule <", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleLessThanOrEqualTo(String value) {
+            addCriterion("extract_rule <=", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleLike(String value) {
+            addCriterion("extract_rule like", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleNotLike(String value) {
+            addCriterion("extract_rule not like", value, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleIn(List<String> values) {
+            addCriterion("extract_rule in", values, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleNotIn(List<String> values) {
+            addCriterion("extract_rule not in", values, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleBetween(String value1, String value2) {
+            addCriterion("extract_rule between", value1, value2, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andExtractRuleNotBetween(String value1, String value2) {
+            addCriterion("extract_rule not between", value1, value2, "extractRule");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIsNull() {
+            addCriterion("embedding_model is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIsNotNull() {
+            addCriterion("embedding_model is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelEqualTo(String value) {
+            addCriterion("embedding_model =", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotEqualTo(String value) {
+            addCriterion("embedding_model <>", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelGreaterThan(String value) {
+            addCriterion("embedding_model >", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelGreaterThanOrEqualTo(String value) {
+            addCriterion("embedding_model >=", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLessThan(String value) {
+            addCriterion("embedding_model <", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLessThanOrEqualTo(String value) {
+            addCriterion("embedding_model <=", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelLike(String value) {
+            addCriterion("embedding_model like", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotLike(String value) {
+            addCriterion("embedding_model not like", value, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelIn(List<String> values) {
+            addCriterion("embedding_model in", values, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotIn(List<String> values) {
+            addCriterion("embedding_model not in", values, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelBetween(String value1, String value2) {
+            addCriterion("embedding_model between", value1, value2, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingModelNotBetween(String value1, String value2) {
+            addCriterion("embedding_model not between", value1, value2, "embeddingModel");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionIsNull() {
+            addCriterion("dimension is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionIsNotNull() {
+            addCriterion("dimension is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionEqualTo(Integer value) {
+            addCriterion("dimension =", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionNotEqualTo(Integer value) {
+            addCriterion("dimension <>", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionGreaterThan(Integer value) {
+            addCriterion("dimension >", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionGreaterThanOrEqualTo(Integer value) {
+            addCriterion("dimension >=", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionLessThan(Integer value) {
+            addCriterion("dimension <", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionLessThanOrEqualTo(Integer value) {
+            addCriterion("dimension <=", value, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionIn(List<Integer> values) {
+            addCriterion("dimension in", values, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionNotIn(List<Integer> values) {
+            addCriterion("dimension not in", values, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionBetween(Integer value1, Integer value2) {
+            addCriterion("dimension between", value1, value2, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andDimensionNotBetween(Integer value1, Integer value2) {
+            addCriterion("dimension not between", value1, value2, "dimension");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthIsNull() {
+            addCriterion("max_length is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthIsNotNull() {
+            addCriterion("max_length is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthEqualTo(Integer value) {
+            addCriterion("max_length =", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthNotEqualTo(Integer value) {
+            addCriterion("max_length <>", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthGreaterThan(Integer value) {
+            addCriterion("max_length >", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthGreaterThanOrEqualTo(Integer value) {
+            addCriterion("max_length >=", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthLessThan(Integer value) {
+            addCriterion("max_length <", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthLessThanOrEqualTo(Integer value) {
+            addCriterion("max_length <=", value, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthIn(List<Integer> values) {
+            addCriterion("max_length in", values, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthNotIn(List<Integer> values) {
+            addCriterion("max_length not in", values, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthBetween(Integer value1, Integer value2) {
+            addCriterion("max_length between", value1, value2, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andMaxLengthNotBetween(Integer value1, Integer value2) {
+            addCriterion("max_length not between", value1, value2, "maxLength");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentIsNull() {
+            addCriterion("enable_segment is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentIsNotNull() {
+            addCriterion("enable_segment is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentEqualTo(Short value) {
+            addCriterion("enable_segment =", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentNotEqualTo(Short value) {
+            addCriterion("enable_segment <>", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentGreaterThan(Short value) {
+            addCriterion("enable_segment >", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentGreaterThanOrEqualTo(Short value) {
+            addCriterion("enable_segment >=", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentLessThan(Short value) {
+            addCriterion("enable_segment <", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentLessThanOrEqualTo(Short value) {
+            addCriterion("enable_segment <=", value, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentIn(List<Short> values) {
+            addCriterion("enable_segment in", values, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentNotIn(List<Short> values) {
+            addCriterion("enable_segment not in", values, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentBetween(Short value1, Short value2) {
+            addCriterion("enable_segment between", value1, value2, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnableSegmentNotBetween(Short value1, Short value2) {
+            addCriterion("enable_segment not between", value1, value2, "enableSegment");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeIsNull() {
+            addCriterion("segment_size is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeIsNotNull() {
+            addCriterion("segment_size is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeEqualTo(Integer value) {
+            addCriterion("segment_size =", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeNotEqualTo(Integer value) {
+            addCriterion("segment_size <>", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeGreaterThan(Integer value) {
+            addCriterion("segment_size >", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeGreaterThanOrEqualTo(Integer value) {
+            addCriterion("segment_size >=", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeLessThan(Integer value) {
+            addCriterion("segment_size <", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeLessThanOrEqualTo(Integer value) {
+            addCriterion("segment_size <=", value, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeIn(List<Integer> values) {
+            addCriterion("segment_size in", values, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeNotIn(List<Integer> values) {
+            addCriterion("segment_size not in", values, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeBetween(Integer value1, Integer value2) {
+            addCriterion("segment_size between", value1, value2, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andSegmentSizeNotBetween(Integer value1, Integer value2) {
+            addCriterion("segment_size not between", value1, value2, "segmentSize");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityIsNull() {
+            addCriterion("priority is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityIsNotNull() {
+            addCriterion("priority is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityEqualTo(Integer value) {
+            addCriterion("priority =", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityNotEqualTo(Integer value) {
+            addCriterion("priority <>", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityGreaterThan(Integer value) {
+            addCriterion("priority >", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityGreaterThanOrEqualTo(Integer value) {
+            addCriterion("priority >=", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityLessThan(Integer value) {
+            addCriterion("priority <", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityLessThanOrEqualTo(Integer value) {
+            addCriterion("priority <=", value, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityIn(List<Integer> values) {
+            addCriterion("priority in", values, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityNotIn(List<Integer> values) {
+            addCriterion("priority not in", values, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityBetween(Integer value1, Integer value2) {
+            addCriterion("priority between", value1, value2, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andPriorityNotBetween(Integer value1, Integer value2) {
+            addCriterion("priority not between", value1, value2, "priority");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledIsNull() {
+            addCriterion("enabled is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledIsNotNull() {
+            addCriterion("enabled is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledEqualTo(Short value) {
+            addCriterion("enabled =", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledNotEqualTo(Short value) {
+            addCriterion("enabled <>", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledGreaterThan(Short value) {
+            addCriterion("enabled >", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledGreaterThanOrEqualTo(Short value) {
+            addCriterion("enabled >=", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledLessThan(Short value) {
+            addCriterion("enabled <", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledLessThanOrEqualTo(Short value) {
+            addCriterion("enabled <=", value, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledIn(List<Short> values) {
+            addCriterion("enabled in", values, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledNotIn(List<Short> values) {
+            addCriterion("enabled not in", values, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledBetween(Short value1, Short value2) {
+            addCriterion("enabled between", value1, value2, "enabled");
+            return (Criteria) this;
+        }
+
+        public Criteria andEnabledNotBetween(Short value1, Short value2) {
+            addCriterion("enabled not between", value1, value2, "enabled");
+            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 deconstruct_vector_config
+     *
+     * @mbg.generated do_not_delete_during_merge Wed Apr 29 15:35:34 CST 2026
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table deconstruct_vector_config
+     *
+     * @mbg.generated Wed Apr 29 15:35:34 CST 2026
+     */
+    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);
+        }
+    }
+}

+ 316 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoVector.java

@@ -0,0 +1,316 @@
+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_vectors
+ */
+public class VideoVector {
+    /**
+     * Database Column Remarks:
+     *   自增主键
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.id
+     *
+     * @mbg.generated
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   视频ID(关联业务侧content_id)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.video_id
+     *
+     * @mbg.generated
+     */
+    private Long videoId;
+
+    /**
+     * Database Column Remarks:
+     *   向量化配置编码(如VIDEO_TOPIC、VIDEO_KEYPOINT等)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.config_code
+     *
+     * @mbg.generated
+     */
+    private String configCode;
+
+    /**
+     * Database Column Remarks:
+     *   向量数据(pgvector vector(1024)类型,余弦距离检索)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.embedding
+     *
+     * @mbg.generated
+     */
+    private String embedding;
+
+    /**
+     * Database Column Remarks:
+     *   创建时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.created_at
+     *
+     * @mbg.generated
+     */
+    private Date createdAt;
+
+    /**
+     * Database Column Remarks:
+     *   更新时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.updated_at
+     *
+     * @mbg.generated
+     */
+    private Date updatedAt;
+
+    /**
+     * Database Column Remarks:
+     *   向量点索引:单点模式=0,多点模式=0,1,2,...
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.point_index
+     *
+     * @mbg.generated
+     */
+    private Integer pointIndex;
+
+    /**
+     * Database Column Remarks:
+     *   向量化的原始文本内容
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column video_vectors.text
+     *
+     * @mbg.generated
+     */
+    private String text;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.id
+     *
+     * @return the value of video_vectors.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_vectors.id
+     *
+     * @param id the value for video_vectors.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_vectors.video_id
+     *
+     * @return the value of video_vectors.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_vectors.video_id
+     *
+     * @param videoId the value for video_vectors.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_vectors.config_code
+     *
+     * @return the value of video_vectors.config_code
+     *
+     * @mbg.generated
+     */
+    public String getConfigCode() {
+        return configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.config_code
+     *
+     * @param configCode the value for video_vectors.config_code
+     *
+     * @mbg.generated
+     */
+    public void setConfigCode(String configCode) {
+        this.configCode = configCode;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.embedding
+     *
+     * @return the value of video_vectors.embedding
+     *
+     * @mbg.generated
+     */
+    public String getEmbedding() {
+        return embedding;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.embedding
+     *
+     * @param embedding the value for video_vectors.embedding
+     *
+     * @mbg.generated
+     */
+    public void setEmbedding(String embedding) {
+        this.embedding = embedding;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.created_at
+     *
+     * @return the value of video_vectors.created_at
+     *
+     * @mbg.generated
+     */
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.created_at
+     *
+     * @param createdAt the value for video_vectors.created_at
+     *
+     * @mbg.generated
+     */
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.updated_at
+     *
+     * @return the value of video_vectors.updated_at
+     *
+     * @mbg.generated
+     */
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.updated_at
+     *
+     * @param updatedAt the value for video_vectors.updated_at
+     *
+     * @mbg.generated
+     */
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.point_index
+     *
+     * @return the value of video_vectors.point_index
+     *
+     * @mbg.generated
+     */
+    public Integer getPointIndex() {
+        return pointIndex;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.point_index
+     *
+     * @param pointIndex the value for video_vectors.point_index
+     *
+     * @mbg.generated
+     */
+    public void setPointIndex(Integer pointIndex) {
+        this.pointIndex = pointIndex;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column video_vectors.text
+     *
+     * @return the value of video_vectors.text
+     *
+     * @mbg.generated
+     */
+    public String getText() {
+        return text;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column video_vectors.text
+     *
+     * @param text the value for video_vectors.text
+     *
+     * @mbg.generated
+     */
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @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(", configCode=").append(configCode);
+        sb.append(", embedding=").append(embedding);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", updatedAt=").append(updatedAt);
+        sb.append(", pointIndex=").append(pointIndex);
+        sb.append(", text=").append(text);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 803 - 0
core/src/main/java/com/tzld/videoVector/model/po/pgVector/VideoVectorExample.java

@@ -0,0 +1,803 @@
+package com.tzld.videoVector.model.po.pgVector;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class VideoVectorExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_vectors
+     *
+     * @mbg.generated
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_vectors
+     *
+     * @mbg.generated
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table video_vectors
+     *
+     * @mbg.generated
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @mbg.generated
+     */
+    public VideoVectorExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @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_vectors
+     *
+     * @mbg.generated
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @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_vectors
+     *
+     * @mbg.generated
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @mbg.generated
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table video_vectors
+     *
+     * @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_vectors
+     *
+     * @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_vectors
+     *
+     * @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_vectors
+     *
+     * @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_vectors
+     *
+     * @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_vectors
+     *
+     * @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 andConfigCodeIsNull() {
+            addCriterion("config_code is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIsNotNull() {
+            addCriterion("config_code is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeEqualTo(String value) {
+            addCriterion("config_code =", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotEqualTo(String value) {
+            addCriterion("config_code <>", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThan(String value) {
+            addCriterion("config_code >", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeGreaterThanOrEqualTo(String value) {
+            addCriterion("config_code >=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThan(String value) {
+            addCriterion("config_code <", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLessThanOrEqualTo(String value) {
+            addCriterion("config_code <=", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeLike(String value) {
+            addCriterion("config_code like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotLike(String value) {
+            addCriterion("config_code not like", value, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeIn(List<String> values) {
+            addCriterion("config_code in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotIn(List<String> values) {
+            addCriterion("config_code not in", values, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeBetween(String value1, String value2) {
+            addCriterion("config_code between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andConfigCodeNotBetween(String value1, String value2) {
+            addCriterion("config_code not between", value1, value2, "configCode");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingIsNull() {
+            addCriterion("embedding is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingIsNotNull() {
+            addCriterion("embedding is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingEqualTo(String value) {
+            addCriterion("embedding =", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingNotEqualTo(String value) {
+            addCriterion("embedding <>", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingGreaterThan(String value) {
+            addCriterion("embedding >", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingGreaterThanOrEqualTo(String value) {
+            addCriterion("embedding >=", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingLessThan(String value) {
+            addCriterion("embedding <", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingLessThanOrEqualTo(String value) {
+            addCriterion("embedding <=", value, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingIn(List<String> values) {
+            addCriterion("embedding in", values, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingNotIn(List<String> values) {
+            addCriterion("embedding not in", values, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingBetween(String value1, String value2) {
+            addCriterion("embedding between", value1, value2, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andEmbeddingNotBetween(String value1, String value2) {
+            addCriterion("embedding not between", value1, value2, "embedding");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIsNull() {
+            addCriterion("created_at is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIsNotNull() {
+            addCriterion("created_at is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtEqualTo(Date value) {
+            addCriterion("created_at =", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotEqualTo(Date value) {
+            addCriterion("created_at <>", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtGreaterThan(Date value) {
+            addCriterion("created_at >", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtGreaterThanOrEqualTo(Date value) {
+            addCriterion("created_at >=", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtLessThan(Date value) {
+            addCriterion("created_at <", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtLessThanOrEqualTo(Date value) {
+            addCriterion("created_at <=", value, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtIn(List<Date> values) {
+            addCriterion("created_at in", values, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotIn(List<Date> values) {
+            addCriterion("created_at not in", values, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtBetween(Date value1, Date value2) {
+            addCriterion("created_at between", value1, value2, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreatedAtNotBetween(Date value1, Date value2) {
+            addCriterion("created_at not between", value1, value2, "createdAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIsNull() {
+            addCriterion("updated_at is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIsNotNull() {
+            addCriterion("updated_at is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtEqualTo(Date value) {
+            addCriterion("updated_at =", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotEqualTo(Date value) {
+            addCriterion("updated_at <>", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtGreaterThan(Date value) {
+            addCriterion("updated_at >", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtGreaterThanOrEqualTo(Date value) {
+            addCriterion("updated_at >=", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtLessThan(Date value) {
+            addCriterion("updated_at <", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtLessThanOrEqualTo(Date value) {
+            addCriterion("updated_at <=", value, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtIn(List<Date> values) {
+            addCriterion("updated_at in", values, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotIn(List<Date> values) {
+            addCriterion("updated_at not in", values, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtBetween(Date value1, Date value2) {
+            addCriterion("updated_at between", value1, value2, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdatedAtNotBetween(Date value1, Date value2) {
+            addCriterion("updated_at not between", value1, value2, "updatedAt");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexIsNull() {
+            addCriterion("point_index is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexIsNotNull() {
+            addCriterion("point_index is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexEqualTo(Integer value) {
+            addCriterion("point_index =", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexNotEqualTo(Integer value) {
+            addCriterion("point_index <>", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexGreaterThan(Integer value) {
+            addCriterion("point_index >", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexGreaterThanOrEqualTo(Integer value) {
+            addCriterion("point_index >=", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexLessThan(Integer value) {
+            addCriterion("point_index <", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexLessThanOrEqualTo(Integer value) {
+            addCriterion("point_index <=", value, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexIn(List<Integer> values) {
+            addCriterion("point_index in", values, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexNotIn(List<Integer> values) {
+            addCriterion("point_index not in", values, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexBetween(Integer value1, Integer value2) {
+            addCriterion("point_index between", value1, value2, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andPointIndexNotBetween(Integer value1, Integer value2) {
+            addCriterion("point_index not between", value1, value2, "pointIndex");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextIsNull() {
+            addCriterion("\"text\" is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextIsNotNull() {
+            addCriterion("\"text\" is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextEqualTo(String value) {
+            addCriterion("\"text\" =", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextNotEqualTo(String value) {
+            addCriterion("\"text\" <>", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextGreaterThan(String value) {
+            addCriterion("\"text\" >", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextGreaterThanOrEqualTo(String value) {
+            addCriterion("\"text\" >=", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextLessThan(String value) {
+            addCriterion("\"text\" <", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextLessThanOrEqualTo(String value) {
+            addCriterion("\"text\" <=", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextLike(String value) {
+            addCriterion("\"text\" like", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextNotLike(String value) {
+            addCriterion("\"text\" not like", value, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextIn(List<String> values) {
+            addCriterion("\"text\" in", values, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextNotIn(List<String> values) {
+            addCriterion("\"text\" not in", values, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextBetween(String value1, String value2) {
+            addCriterion("\"text\" between", value1, value2, "text");
+            return (Criteria) this;
+        }
+
+        public Criteria andTextNotBetween(String value1, String value2) {
+            addCriterion("\"text\" not between", value1, value2, "text");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table video_vectors
+     *
+     * @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_vectors
+     *
+     * @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);
+        }
+    }
+}

+ 1 - 1
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructContent.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContent.java

@@ -7,7 +7,7 @@ import java.util.Date;
  * This class was generated by MyBatis Generator.
  * This class corresponds to the database table deconstruct_content
  */
-public class DeconstructContent {
+public class MysqlDeconstructContent {
     /**
      * Database Column Remarks:
      *   主键ID

+ 2 - 2
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructContentExample.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentExample.java

@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-public class DeconstructContentExample {
+public class MysqlDeconstructContentExample {
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database table deconstruct_content
@@ -35,7 +35,7 @@ public class DeconstructContentExample {
      *
      * @mbg.generated Mon Mar 09 10:39:51 CST 2026
      */
-    public DeconstructContentExample() {
+    public MysqlDeconstructContentExample() {
         oredCriteria = new ArrayList<Criteria>();
     }
 

+ 1 - 1
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructContentVector.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentVector.java

@@ -7,7 +7,7 @@ import java.util.Date;
  * This class was generated by MyBatis Generator.
  * This class corresponds to the database table deconstruct_content_vector
  */
-public class DeconstructContentVector {
+public class MysqlDeconstructContentVector {
     /**
      * Database Column Remarks:
      *   主键ID

+ 2 - 2
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructContentVectorExample.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructContentVectorExample.java

@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-public class DeconstructContentVectorExample {
+public class MysqlDeconstructContentVectorExample {
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database table deconstruct_content_vector
@@ -35,7 +35,7 @@ public class DeconstructContentVectorExample {
      *
      * @mbg.generated Thu Apr 23 15:03:47 CST 2026
      */
-    public DeconstructContentVectorExample() {
+    public MysqlDeconstructContentVectorExample() {
         oredCriteria = new ArrayList<Criteria>();
     }
 

+ 1 - 1
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructVectorConfig.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructVectorConfig.java

@@ -7,7 +7,7 @@ import java.util.Date;
  * This class was generated by MyBatis Generator.
  * This class corresponds to the database table deconstruct_vector_config
  */
-public class DeconstructVectorConfig {
+public class MysqlDeconstructVectorConfig {
     /**
      * Database Column Remarks:
      *   主键ID

+ 2 - 2
core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/DeconstructVectorConfigExample.java → core/src/main/java/com/tzld/videoVector/model/po/videoVector/deconstruct/MysqlDeconstructVectorConfigExample.java

@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-public class DeconstructVectorConfigExample {
+public class MysqlDeconstructVectorConfigExample {
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database table deconstruct_vector_config
@@ -35,7 +35,7 @@ public class DeconstructVectorConfigExample {
      *
      * @mbg.generated Thu Apr 23 14:55:06 CST 2026
      */
-    public DeconstructVectorConfigExample() {
+    public MysqlDeconstructVectorConfigExample() {
         oredCriteria = new ArrayList<Criteria>();
     }
 

+ 41 - 0
core/src/main/java/com/tzld/videoVector/model/vo/MaterialMatchResult.java

@@ -0,0 +1,41 @@
+package com.tzld.videoVector.model.vo;
+
+import lombok.Data;
+
+/**
+ * 素材匹配结果
+ */
+@Data
+public class MaterialMatchResult {
+
+    /** 命中的配置编码 */
+    private String configCode;
+
+    /** deconstruct_content.id */
+    private Long contentId;
+
+    /** 素材业务ID(deconstruct_content.channel_content_id) */
+    private String channelContentId;
+
+    /** 余弦相似度分值 */
+    private Double score;
+
+    /** 素材标题 */
+    private String title;
+
+    /** 命中的向量原文 */
+    private String sourceText;
+
+    public MaterialMatchResult() {
+    }
+
+    public MaterialMatchResult(String configCode, Long contentId, String channelContentId,
+                               Double score, String title, String sourceText) {
+        this.configCode = configCode;
+        this.contentId = contentId;
+        this.channelContentId = channelContentId;
+        this.score = score;
+        this.title = title;
+        this.sourceText = sourceText;
+    }
+}

+ 29 - 0
core/src/main/java/com/tzld/videoVector/model/vo/VideoMatchResult.java

@@ -0,0 +1,29 @@
+package com.tzld.videoVector.model.vo;
+
+import lombok.Data;
+
+/**
+ * 视频匹配结果包装类
+ * 替代原来手动拼接 JSONObject 的返回方式,提供类型安全
+ */
+@Data
+public class VideoMatchResult {
+
+    /** 命中的配置编码(如 VIDEO_TOPIC、VIDEO_KEYPOINT 等) */
+    private String configCode;
+
+    /** 匹配到的视频ID */
+    private Long videoId;
+
+    /** 余弦相似度分值 */
+    private Double score;
+
+    public VideoMatchResult() {
+    }
+
+    public VideoMatchResult(String configCode, Long videoId, Double score) {
+        this.configCode = configCode;
+        this.videoId = videoId;
+        this.score = score;
+    }
+}

+ 47 - 0
core/src/main/java/com/tzld/videoVector/model/vo/VideoVectorSearchResult.java

@@ -0,0 +1,47 @@
+package com.tzld.videoVector.model.vo;
+
+/**
+ * 视频向量搜索结果 DTO(余弦相似度搜索返回)
+ */
+public class VideoVectorSearchResult {
+
+    /** 视频ID */
+    private Long videoId;
+
+    /** 向量点索引 */
+    private Integer pointIndex;
+
+    /** 余弦相似度得分 */
+    private Double score;
+
+    public Long getVideoId() {
+        return videoId;
+    }
+
+    public void setVideoId(Long videoId) {
+        this.videoId = videoId;
+    }
+
+    public Integer getPointIndex() {
+        return pointIndex;
+    }
+
+    public void setPointIndex(Integer pointIndex) {
+        this.pointIndex = pointIndex;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    @Override
+    public String toString() {
+        return "VideoVectorSearchResult{videoId=" + videoId +
+                ", pointIndex=" + pointIndex +
+                ", score=" + score + "}";
+    }
+}

+ 1 - 1
core/src/main/java/com/tzld/videoVector/service/EmbeddingService.java

@@ -1,6 +1,6 @@
 package com.tzld.videoVector.service;
 
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
 
 import java.util.List;
 

+ 32 - 0
core/src/main/java/com/tzld/videoVector/service/MaterialSearchService.java

@@ -0,0 +1,32 @@
+package com.tzld.videoVector.service;
+
+import com.tzld.videoVector.model.param.MaterialMatchParam;
+import com.tzld.videoVector.model.param.MaterialSubmitParam;
+import com.tzld.videoVector.model.vo.MaterialMatchResult;
+
+import java.util.List;
+
+/**
+ * 素材搜索服务接口
+ * <p>
+ * 复用现有 deconstruct_content / content_vectors / deconstruct_vector_config 表,
+ * 通过 biz_type 区分素材与视频,向量化配置完全复用视频解构配置(biz_type=NULL 通配)。
+ */
+public interface MaterialSearchService {
+
+    /**
+     * 提交素材入库(解构 + 异步向量化)
+     *
+     * @param param 素材入库参数
+     * @return taskId 解构任务ID
+     */
+    String submitMaterial(MaterialSubmitParam param);
+
+    /**
+     * 素材相似搜索 Top-N
+     *
+     * @param param 搜索参数
+     * @return 匹配结果列表
+     */
+    List<MaterialMatchResult> matchTopNMaterial(MaterialMatchParam param);
+}

+ 18 - 5
core/src/main/java/com/tzld/videoVector/service/VectorStoreService.java

@@ -1,5 +1,6 @@
 package com.tzld.videoVector.service;
 
+import com.tzld.videoVector.common.constant.VectorConstants;
 import com.tzld.videoVector.model.entity.VideoMatch;
 
 import java.util.Collection;
@@ -8,28 +9,40 @@ import java.util.Map;
 import java.util.Set;
 
 /**
- * 向量存储服务接口(Redis 实现)
+ * 向量存储服务接口(pgvector 实现)
  * 支持按配置编码(configCode)存储多类型向量
  */
 public interface VectorStoreService {
 
     /** 默认配置 */
-    String DEFAULT_CONFIG_CODE = "VIDEO_TOPIC";
+    String DEFAULT_CONFIG_CODE = VectorConstants.DEFAULT_CONFIG_CODE;
 
     /**
      * 保存视频向量(默认配置)
      * @param videoId 视频ID
      * @param vector  向量数据
+     * @param text    向量化的原始文本内容
      */
-    void save(Long videoId, List<Float> vector);
+    void save(Long videoId, List<Float> vector, String text);
 
     /**
-     * 保存视频向量(指定配置)
+     * 保存视频向量(指定配置,单点模式 pointIndex=0
      * @param configCode 配置编码
      * @param videoId    视频ID
      * @param vector     向量数据
+     * @param text       向量化的原始文本内容
      */
-    void save(String configCode, Long videoId, List<Float> vector);
+    void save(String configCode, Long videoId, List<Float> vector, String text);
+
+    /**
+     * 保存视频向量(指定配置 + 点索引,支持多点模式)
+     * @param configCode 配置编码
+     * @param videoId    视频ID
+     * @param pointIndex 向量点索引(单点模式传0,多点模式传 0,1,2,...)
+     * @param vector     向量数据
+     * @param text       向量化的原始文本内容
+     */
+    void save(String configCode, Long videoId, int pointIndex, List<Float> vector, String text);
 
     /**
      * 判断某个 videoId 的向量是否已存在(默认配置)

+ 9 - 9
core/src/main/java/com/tzld/videoVector/service/VectorizeService.java

@@ -1,8 +1,8 @@
 package com.tzld.videoVector.service;
 
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+import com.tzld.videoVector.model.po.pgVector.DeconstructContent;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
 
 import java.util.List;
 
@@ -27,7 +27,7 @@ public interface VectorizeService {
      * @param content 解构内容
      * @return 生成的向量列表
      */
-    List<DeconstructContentVector> vectorizeContent(DeconstructContent content);
+    List<ContentVector> vectorizeContent(DeconstructContent content);
 
     /**
      * 根据配置提取文本并进行向量化
@@ -36,7 +36,7 @@ public interface VectorizeService {
      * @param config  向量化配置
      * @return 向量列表(支持分段)
      */
-    List<DeconstructContentVector> vectorizeByConfig(DeconstructContent content, DeconstructVectorConfig config);
+    List<ContentVector> vectorizeByConfig(DeconstructContent content, DeconstructVectorConfig config);
 
     /**
      * 批量保存向量数据
@@ -44,7 +44,7 @@ public interface VectorizeService {
      * @param vectors 向量列表
      * @return 保存数量
      */
-    int batchSaveVectors(List<DeconstructContentVector> vectors);
+    int batchSaveVectors(List<ContentVector> vectors);
 
     /**
      * 根据内容ID查询向量列表
@@ -52,7 +52,7 @@ public interface VectorizeService {
      * @param contentId 内容ID
      * @return 向量列表
      */
-    List<DeconstructContentVector> getVectorsByContentId(Long contentId);
+    List<ContentVector> getVectorsByContentId(Long contentId);
 
     /**
      * 根据内容ID和配置编码查询向量列表
@@ -61,7 +61,7 @@ public interface VectorizeService {
      * @param configCode 配置编码,为空时不过滤
      * @return 向量列表
      */
-    List<DeconstructContentVector> getVectorsByContentId(Long contentId, String configCode);
+    List<ContentVector> getVectorsByContentId(Long contentId, String configCode);
 
     /**
      * 根据内容ID和字段标识查询向量
@@ -70,5 +70,5 @@ public interface VectorizeService {
      * @param sourceField 来源字段
      * @return 向量列表
      */
-    List<DeconstructContentVector> getVectorsByField(Long contentId, String sourceField);
+    List<ContentVector> getVectorsByField(Long contentId, String sourceField);
 }

+ 10 - 1
core/src/main/java/com/tzld/videoVector/service/VideoSearchService.java

@@ -5,7 +5,10 @@ import com.tzld.videoVector.model.param.DeconstructParam;
 import com.tzld.videoVector.model.param.GetDeconstructParam;
 import com.tzld.videoVector.model.param.MatchTopNVideoParam;
 
+import com.tzld.videoVector.model.vo.VideoMatchResult;
+
 import java.util.List;
+import java.util.Map;
 
 public interface VideoSearchService {
 
@@ -28,5 +31,11 @@ public interface VideoSearchService {
      * @param param 匹配参数
      * @return 匹配结果列表
      */
-    List<Object> matchTopNVideo(MatchTopNVideoParam param);
+    List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param);
+
+    /**
+     * 获取所有启用的 configCode 列表
+     * @return configCode -> configName 的映射
+     */
+    Map<String, String> getAllConfigCodes();
 }

+ 1 - 1
core/src/main/java/com/tzld/videoVector/service/impl/EmbeddingServiceImpl.java

@@ -1,7 +1,7 @@
 package com.tzld.videoVector.service.impl;
 
 import com.tzld.videoVector.api.DashScopeEmbeddingApiService;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
 import com.tzld.videoVector.service.EmbeddingService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;

+ 470 - 0
core/src/main/java/com/tzld/videoVector/service/impl/MaterialSearchServiceImpl.java

@@ -0,0 +1,470 @@
+package com.tzld.videoVector.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+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.ContentVectorMapperExt;
+import com.tzld.videoVector.model.param.MaterialMatchParam;
+import com.tzld.videoVector.model.param.MaterialSubmitParam;
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+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.vo.MaterialMatchResult;
+import com.tzld.videoVector.service.DeconstructService;
+import com.tzld.videoVector.service.EmbeddingService;
+import com.tzld.videoVector.service.MaterialSearchService;
+import com.tzld.videoVector.service.VectorizeService;
+import com.tzld.videoVector.util.Md5Util;
+import com.tzld.videoVector.util.VectorUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.tzld.videoVector.common.constant.VectorConstants.*;
+
+/**
+ * 素材搜索服务实现
+ * <p>
+ * 完全复用 deconstruct_content / content_vectors / deconstruct_vector_config 表,
+ * 配置复用视频解构配置(biz_type=NULL 通配所有业务类型)。
+ */
+@Slf4j
+@Service
+public class MaterialSearchServiceImpl implements MaterialSearchService {
+
+    @Resource
+    private DeconstructService deconstructService;
+
+    @Resource
+    private DeconstructContentMapper deconstructContentMapper;
+
+    @Resource
+    private DeconstructVectorConfigMapper deconstructVectorConfigMapper;
+
+    @Resource
+    private VectorizeService vectorizeService;
+
+    @Resource
+    private EmbeddingService embeddingService;
+
+    @Resource
+    private ContentVectorMapperExt contentVectorMapperExt;
+
+    // ================================================================ 入库
+    @Override
+    public String submitMaterial(MaterialSubmitParam param) {
+        if (param == null) {
+            log.error("submitMaterial 参数为空");
+            return null;
+        }
+
+        log.info("素材入库,channelContentId={}, title={}, contentType={}",
+                param.getChannelContentId(), param.getTitle(), param.getContentType());
+
+        // 幂等检查:channelContentId 是否已存在
+        String channelContentId = param.getChannelContentId();
+        DeconstructContent failedContent = null;
+        if (StringUtils.hasText(channelContentId)) {
+            DeconstructContent existing = getDeconstructContentByChannelContentId(channelContentId);
+            if (existing != null) {
+                Short status = existing.getStatus();
+                if (status != null && status != 3) {
+                    log.info("素材已存在,channelContentId={}, taskId={}, status={}, 不重复提交",
+                            channelContentId, existing.getTaskId(), status);
+                    return existing.getTaskId();
+                }
+                log.info("素材已存在但失败,channelContentId={}, 允许重新提交", channelContentId);
+                failedContent = existing;
+            }
+        }
+
+        Integer contentType = param.getContentType() != null ? param.getContentType() : 2;
+
+        // 调用解构服务
+        String taskId = deconstructService.deconstruct(
+                0,
+                contentType,
+                param.getChannelContentId(),
+                param.getVideoUrl(),
+                param.getImageList(),
+                param.getBodyText(),
+                param.getTitle(),
+                param.getChannelAccountId(),
+                param.getChannelAccountName()
+        );
+
+        if (!StringUtils.hasText(taskId)) {
+            log.error("素材解构任务提交失败");
+            return null;
+        }
+
+        log.info("素材解构任务提交成功,taskId={}", taskId);
+
+        // 保存到 deconstruct_content
+        try {
+            if (failedContent != null) {
+                failedContent.setTaskId(taskId);
+                failedContent.setBizType((short) 0);
+                failedContent.setContentType(contentType.shortValue());
+                failedContent.setTitle(param.getTitle());
+                failedContent.setBodyText(param.getBodyText());
+                failedContent.setVideoUrl(param.getVideoUrl());
+                failedContent.setChannelAccountId(param.getChannelAccountId());
+                failedContent.setChannelAccountName(param.getChannelAccountName());
+                failedContent.setStatus((short) 0);
+                failedContent.setResultJson("");
+                failedContent.setFailureReason("");
+                failedContent.setPointUrl("");
+                failedContent.setWeightUrl("");
+                failedContent.setPatternUrl("");
+                failedContent.setUpdateTime(new Date());
+                if (param.getImageList() != null && !param.getImageList().isEmpty()) {
+                    failedContent.setImages(JSONObject.toJSONString(param.getImageList()));
+                }
+                deconstructContentMapper.updateByPrimaryKeySelective(failedContent);
+                log.info("素材失败记录已更新,contentId={}, taskId={}", failedContent.getId(), taskId);
+            } else {
+                DeconstructContent content = new DeconstructContent();
+                content.setTaskId(taskId);
+                content.setBizType((short) 0);
+                content.setContentType(contentType.shortValue());
+                content.setChannelContentId(param.getChannelContentId());
+                content.setTitle(param.getTitle());
+                content.setBodyText(param.getBodyText());
+                content.setVideoUrl(param.getVideoUrl());
+                content.setChannelAccountId(param.getChannelAccountId());
+                content.setChannelAccountName(param.getChannelAccountName());
+                content.setStatus((short) 0);
+                content.setCreateTime(new Date());
+                content.setUpdateTime(new Date());
+                if (param.getImageList() != null && !param.getImageList().isEmpty()) {
+                    content.setImages(JSONObject.toJSONString(param.getImageList()));
+                }
+                deconstructContentMapper.insertSelective(content);
+                log.info("素材记录已保存,contentId={}, taskId={}", content.getId(), taskId);
+            }
+        } catch (Exception e) {
+            log.error("保存素材记录失败,taskId={}, error={}", taskId, e.getMessage(), e);
+        }
+
+        return taskId;
+    }
+
+    // ================================================================ 搜索
+    @Override
+    public List<MaterialMatchResult> matchTopNMaterial(MaterialMatchParam param) {
+        if (param == null) {
+            log.error("matchTopNMaterial 参数为空");
+            return Collections.emptyList();
+        }
+
+        int topN = param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10;
+        String configCode = param.getConfigCode();
+        if (!StringUtils.hasText(configCode)) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        // 若提供 channelContentId,检查是否需要触发向量化
+        if (StringUtils.hasText(param.getChannelContentId())
+                && !ALL_CONFIG_CODE.equalsIgnoreCase(configCode)) {
+            triggerVectorizeIfNeeded(param.getChannelContentId(), configCode);
+        }
+
+        // 确定要搜索的配置列表
+        List<DeconstructVectorConfig> searchConfigs;
+        if (ALL_CONFIG_CODE.equalsIgnoreCase(configCode)) {
+            searchConfigs = getEnabledConfigs();
+            if (searchConfigs.isEmpty()) {
+                log.warn("素材搜索:未找到任何启用的向量化配置");
+                return Collections.emptyList();
+            }
+            log.info("素材搜索 ALL 模式,加载 {} 个配置", searchConfigs.size());
+        } else {
+            DeconstructVectorConfig singleConfig = getVectorConfigByCode(configCode);
+            if (singleConfig == null) {
+                log.warn("素材搜索:未找到 configCode={} 的配置", configCode);
+                return Collections.emptyList();
+            }
+            searchConfigs = Collections.singletonList(singleConfig);
+        }
+
+        List<MaterialMatchResult> result = new ArrayList<>();
+        int candidateSize = topN * 3;
+        Map<String, List<Float>> embeddingCache = new HashMap<>();
+
+        for (DeconstructVectorConfig config : searchConfigs) {
+            String cfgCode = config.getConfigCode();
+            try {
+                // 解析查询向量
+                List<Float> queryVector = resolveQueryVector(param, config, embeddingCache);
+                if (queryVector == null || queryVector.isEmpty()) {
+                    log.warn("配置 {} 无法获取查询向量,跳过", cfgCode);
+                    continue;
+                }
+
+                log.info("素材搜索 配置 {} 开始 Top-{},向量维度={}", cfgCode, candidateSize, queryVector.size());
+
+                // 搜索 content_vectors
+                String queryVectorStr = queryVector.toString();
+                List<ContentVector> matches = contentVectorMapperExt.searchTopNByCosine(
+                        cfgCode, queryVectorStr, candidateSize);
+
+                if (CollectionUtils.isEmpty(matches)) {
+                    log.debug("配置 {} 无匹配结果", cfgCode);
+                    continue;
+                }
+
+                // 按 contentId 去重保留最高分
+                Map<Long, ContentVector> deduped = new LinkedHashMap<>();
+                for (ContentVector cv : matches) {
+                    Long contentId = cv.getContentId();
+                    ContentVector existing = deduped.get(contentId);
+                    if (existing == null || (cv.getScore() != null && cv.getScore() > existing.getScore())) {
+                        deduped.put(contentId, cv);
+                    }
+                }
+
+                // 取 topN 并填充素材详情
+                List<ContentVector> topMatches = deduped.values().stream()
+                        .limit(topN)
+                        .collect(Collectors.toList());
+
+                // 批量查询 deconstruct_content 填充 channelContentId / title
+                Set<Long> contentIds = topMatches.stream()
+                        .map(ContentVector::getContentId)
+                        .collect(Collectors.toSet());
+                Map<Long, DeconstructContent> contentMap = batchGetDeconstructContent(contentIds);
+
+                for (ContentVector cv : topMatches) {
+                    DeconstructContent dc = contentMap.get(cv.getContentId());
+                    result.add(new MaterialMatchResult(
+                            cfgCode,
+                            cv.getContentId(),
+                            dc != null ? dc.getChannelContentId() : null,
+                            cv.getScore(),
+                            dc != null ? dc.getTitle() : null,
+                            cv.getSourceText()
+                    ));
+                }
+
+                log.info("配置 {} 搜索完成,返回 {} 条", cfgCode, topMatches.size());
+
+            } catch (Exception e) {
+                log.error("配置 {} 搜索失败: {}", cfgCode, e.getMessage(), e);
+            }
+        }
+
+        log.info("素材搜索完成,共返回 {} 条结果", result.size());
+        return result;
+    }
+
+    // ================================================================ 私有方法
+
+    /**
+     * 解析查询向量
+     * 优先级:queryVector > channelContentId 历史向量 > queryText embedding
+     */
+    private List<Float> resolveQueryVector(MaterialMatchParam param,
+                                           DeconstructVectorConfig config,
+                                           Map<String, List<Float>> embeddingCache) {
+        // 1. 直接传入的 queryVector
+        if (param.getQueryVector() != null && !param.getQueryVector().isEmpty()) {
+            return param.getQueryVector();
+        }
+
+        // 2. channelContentId 历史向量
+        if (StringUtils.hasText(param.getChannelContentId())) {
+            List<Float> cached = getVectorByChannelContentId(
+                    param.getChannelContentId(), config.getConfigCode());
+            if (cached != null && !cached.isEmpty()) {
+                log.info("配置 {} 命中 channelContentId 历史向量", config.getConfigCode());
+                return cached;
+            }
+            log.info("配置 {} channelContentId={} 未命中,降级到 queryText",
+                    config.getConfigCode(), param.getChannelContentId());
+        }
+
+        // 3. queryText embedding(按 embeddingModel 缓存)
+        if (StringUtils.hasText(param.getQueryText())) {
+            String embeddingModel = config.getEmbeddingModel();
+            String cacheKey = embeddingModel != null ? embeddingModel : "__default__";
+
+            if (embeddingCache.containsKey(cacheKey)) {
+                return embeddingCache.get(cacheKey);
+            }
+
+            // text_hash 缓存查询
+            String textHash = Md5Util.encoderByMd5(param.getQueryText());
+            if (StringUtils.hasText(textHash)) {
+                List<Float> hashCached = getVectorByTextHash(textHash, config.getConfigCode());
+                if (hashCached != null && !hashCached.isEmpty()) {
+                    embeddingCache.put(cacheKey, hashCached);
+                    return hashCached;
+                }
+            }
+
+            // 调用 embedding API
+            List<Float> vector = embeddingService.embed(param.getQueryText(), config);
+            if (vector != null && !vector.isEmpty()) {
+                embeddingCache.put(cacheKey, vector);
+                return vector;
+            }
+            log.warn("配置 {} embedding 失败", config.getConfigCode());
+        }
+
+        return null;
+    }
+
+    /**
+     * 通过 channelContentId 查询历史向量
+     */
+    private List<Float> getVectorByChannelContentId(String channelContentId, String configCode) {
+        try {
+            DeconstructContent content = getDeconstructContentByChannelContentId(channelContentId);
+            if (content == null || content.getId() == null) {
+                return null;
+            }
+            List<ContentVector> vectors;
+            if (StringUtils.hasText(configCode)) {
+                vectors = contentVectorMapperExt.selectByContentIdAndConfigCode(content.getId(), configCode);
+            } else {
+                vectors = contentVectorMapperExt.selectByContentId(content.getId());
+            }
+            if (vectors == null || vectors.isEmpty()) {
+                return null;
+            }
+            ContentVector vector = vectors.get(0);
+            if (!StringUtils.hasText(vector.getEmbedding())) {
+                return null;
+            }
+            List<Float> vectorData = VectorUtils.parseVectorString(vector.getEmbedding());
+            log.info("复用历史向量,channelContentId={}, contentId={}, configCode={}, 维度={}",
+                    channelContentId, content.getId(), configCode,
+                    vectorData != null ? vectorData.size() : 0);
+            return vectorData;
+        } catch (Exception e) {
+            log.error("获取历史向量失败,channelContentId={}, error={}", channelContentId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 通过 text_hash 查询缓存向量
+     */
+    private List<Float> getVectorByTextHash(String textHash, String configCode) {
+        try {
+            ContentVector cached;
+            if (StringUtils.hasText(configCode)) {
+                cached = contentVectorMapperExt.selectByTextHashAndConfigCode(textHash, configCode);
+            } else {
+                cached = contentVectorMapperExt.selectByTextHash(textHash);
+            }
+            if (cached != null && StringUtils.hasText(cached.getEmbedding())) {
+                return VectorUtils.parseVectorString(cached.getEmbedding());
+            }
+        } catch (Exception e) {
+            log.error("text_hash 查询向量失败,hash={}, error={}", textHash, e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 若内容已解构完成但向量表缺少对应 configCode 的向量,则触发向量化
+     */
+    private void triggerVectorizeIfNeeded(String channelContentId, String configCode) {
+        try {
+            DeconstructContent content = getDeconstructContentByChannelContentId(channelContentId);
+            if (content == null || content.getStatus() == null || content.getStatus() != 2) {
+                return;
+            }
+            List<ContentVector> vectors =
+                    vectorizeService.getVectorsByContentId(content.getId(), configCode);
+            if (vectors != null && !vectors.isEmpty()) {
+                return;
+            }
+            log.info("素材已解构但缺少向量,触发向量化,channelContentId={}, contentId={}, configCode={}",
+                    channelContentId, content.getId(), configCode);
+            vectorizeService.vectorizeContent(content);
+        } catch (Exception e) {
+            log.error("触发向量化失败,channelContentId={}, error={}", channelContentId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据 channelContentId 查询解构内容
+     */
+    private DeconstructContent getDeconstructContentByChannelContentId(String channelContentId) {
+        DeconstructContentExample example = new DeconstructContentExample();
+        example.createCriteria().andChannelContentIdEqualTo(channelContentId);
+        example.setOrderByClause("id DESC");
+        List<DeconstructContent> list = deconstructContentMapper.selectByExample(example);
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    /**
+     * 批量查询 deconstruct_content
+     */
+    private Map<Long, DeconstructContent> batchGetDeconstructContent(Set<Long> contentIds) {
+        Map<Long, DeconstructContent> map = new HashMap<>();
+        if (CollectionUtils.isEmpty(contentIds)) {
+            return map;
+        }
+        try {
+            DeconstructContentExample example = new DeconstructContentExample();
+            example.createCriteria().andIdIn(new ArrayList<>(contentIds));
+            List<DeconstructContent> list = deconstructContentMapper.selectByExample(example);
+            if (list != null) {
+                for (DeconstructContent dc : list) {
+                    map.put(dc.getId(), dc);
+                }
+            }
+        } catch (Exception e) {
+            log.error("批量查询 deconstruct_content 失败,ids={}, error={}",
+                    contentIds, e.getMessage(), e);
+        }
+        return map;
+    }
+
+    /**
+     * 获取所有启用的向量化配置
+     * 仅返回 source_field = 'result_json' 的素材/视频配置
+     */
+    private List<DeconstructVectorConfig> getEnabledConfigs() {
+        try {
+            DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+            example.createCriteria().andEnabledEqualTo((short) 1).andSourceFieldEqualTo("result_json");
+            example.setOrderByClause("priority ASC");
+            List<DeconstructVectorConfig> configs = deconstructVectorConfigMapper.selectByExample(example);
+            return configs != null ? configs : Collections.emptyList();
+        } catch (Exception e) {
+            log.error("查询启用配置失败: {}", e.getMessage(), e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 根据 configCode 查询向量配置
+     */
+    private DeconstructVectorConfig getVectorConfigByCode(String configCode) {
+        if (!StringUtils.hasText(configCode)) {
+            return null;
+        }
+        try {
+            DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+            example.createCriteria().andConfigCodeEqualTo(configCode);
+            List<DeconstructVectorConfig> configs = deconstructVectorConfigMapper.selectByExample(example);
+            return (configs != null && !configs.isEmpty()) ? configs.get(0) : null;
+        } catch (Exception e) {
+            log.error("查询向量配置失败,configCode={}, error={}", configCode, e.getMessage(), e);
+            return null;
+        }
+    }
+}

+ 255 - 0
core/src/main/java/com/tzld/videoVector/service/impl/PgVectorStoreServiceImpl.java

@@ -0,0 +1,255 @@
+package com.tzld.videoVector.service.impl;
+
+import com.tzld.videoVector.dao.mapper.pgVector.ext.VideoVectorMapperExt;
+import com.tzld.videoVector.model.entity.VideoMatch;
+import com.tzld.videoVector.model.po.pgVector.VideoVector;
+import com.tzld.videoVector.model.vo.VideoVectorSearchResult;
+import com.tzld.videoVector.service.VectorStoreService;
+import com.tzld.videoVector.util.VectorUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 基于 PostgreSQL pgvector 的向量存储服务实现
+ *
+ * <p>利用 pgvector 的 HNSW 索引实现高效的余弦相似度搜索,
+ * 替代原有 Redis 暴力搜索方案,解决内存爆炸和延迟问题。
+ *
+ * <p>存储结构:
+ * <ul>
+ *   <li>表:video_vectors (id, video_id, config_code, point_index, embedding vector(1024), created_at, updated_at)</li>
+ *   <li>唯一索引:(config_code, video_id, point_index)</li>
+ *   <li>HNSW 索引:embedding 列,基于余弦距离</li>
+ * </ul>
+ */
+@Slf4j
+@Service
+public class PgVectorStoreServiceImpl implements VectorStoreService {
+
+    @Autowired
+    private VideoVectorMapperExt videoVectorMapperExt;
+
+    // ---------------------------------------------------------------- CRUD
+
+    @Override
+    public void save(Long videoId, List<Float> vector, String text) {
+        save(DEFAULT_CONFIG_CODE, videoId, vector, text);
+    }
+
+    @Override
+    public void save(String configCode, Long videoId, List<Float> vector, String text) {
+        save(configCode, videoId, 0, vector, text);
+    }
+
+    @Override
+    public void save(String configCode, Long videoId, int pointIndex, List<Float> vector, String text) {
+        if (videoId == null || vector == null || vector.isEmpty()) {
+            log.warn("save 参数非法,configCode={}, videoId={}", configCode, videoId);
+            return;
+        }
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        String embedding = vectorToString(vector);
+        videoVectorMapperExt.upsertVector(videoId, configCode, pointIndex, embedding, text);
+        log.debug("保存向量成功,configCode={}, videoId={}, pointIndex={}, 维度={}", configCode, videoId, pointIndex, vector.size());
+    }
+
+    @Override
+    public boolean exists(Long videoId) {
+        return exists(DEFAULT_CONFIG_CODE, videoId);
+    }
+
+    @Override
+    public boolean exists(String configCode, Long videoId) {
+        if (videoId == null) return false;
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+        return videoVectorMapperExt.existsByVideoIdAndConfigCode(videoId, configCode) > 0;
+    }
+
+    @Override
+    public Set<Long> existsByIds(Collection<Long> videoIds) {
+        return existsByIds(DEFAULT_CONFIG_CODE, videoIds);
+    }
+
+    @Override
+    public Set<Long> existsByIds(String configCode, Collection<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) return Collections.emptySet();
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        List<Long> idList = new ArrayList<>(videoIds);
+        // 分批查询,避免 IN 子句过长
+        Set<Long> existing = new HashSet<>();
+        int batchSize = 1000;
+        for (int i = 0; i < idList.size(); i += batchSize) {
+            int end = Math.min(i + batchSize, idList.size());
+            List<Long> batch = idList.subList(i, end);
+            List<Long> found = videoVectorMapperExt.selectExistingVideoIds(batch, configCode);
+            if (found != null) {
+                existing.addAll(found);
+            }
+        }
+        return existing;
+    }
+
+    @Override
+    public List<Float> getVector(Long videoId) {
+        return getVector(DEFAULT_CONFIG_CODE, videoId);
+    }
+
+    @Override
+    public List<Float> getVector(String configCode, Long videoId) {
+        if (videoId == null) return null;
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        String embedding = videoVectorMapperExt.selectEmbeddingByVideoIdAndConfigCode(videoId, configCode);
+        return VectorUtils.parseVectorString(embedding);
+    }
+
+    @Override
+    public Map<Long, List<Float>> getVectors(Collection<Long> videoIds) {
+        return getVectors(DEFAULT_CONFIG_CODE, videoIds);
+    }
+
+    @Override
+    public Map<Long, List<Float>> getVectors(String configCode, Collection<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) return Collections.emptyMap();
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        List<Long> idList = new ArrayList<>(videoIds);
+        Map<Long, List<Float>> result = new HashMap<>();
+        int batchSize = 1000;
+
+        for (int i = 0; i < idList.size(); i += batchSize) {
+            int end = Math.min(i + batchSize, idList.size());
+            List<Long> batch = idList.subList(i, end);
+            List<VideoVector> vectors = videoVectorMapperExt.selectVectorsByVideoIds(batch, configCode);
+            if (vectors != null) {
+                for (VideoVector vv : vectors) {
+                    List<Float> parsed = VectorUtils.parseVectorString(vv.getEmbedding());
+                    if (parsed != null) {
+                        result.put(vv.getVideoId(), parsed);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<Long> getAllVideoIds() {
+        return getAllVideoIds(DEFAULT_CONFIG_CODE);
+    }
+
+    @Override
+    public Set<Long> getAllVideoIds(String configCode) {
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+        List<Long> ids = videoVectorMapperExt.selectAllVideoIdsByConfigCode(configCode);
+        if (ids == null) return Collections.emptySet();
+        return new HashSet<>(ids);
+    }
+
+    @Override
+    public void delete(Long videoId) {
+        delete(DEFAULT_CONFIG_CODE, videoId);
+    }
+
+    @Override
+    public void delete(String configCode, Long videoId) {
+        if (videoId == null) return;
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+        videoVectorMapperExt.deleteByVideoIdAndConfigCode(videoId, configCode);
+        log.debug("删除向量成功,configCode={}, videoId={}", configCode, videoId);
+    }
+
+    @Override
+    public void deleteBatch(Collection<Long> videoIds) {
+        deleteBatch(DEFAULT_CONFIG_CODE, videoIds);
+    }
+
+    @Override
+    public void deleteBatch(String configCode, Collection<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) return;
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        List<Long> idList = new ArrayList<>(videoIds);
+        int batchSize = 1000;
+        for (int i = 0; i < idList.size(); i += batchSize) {
+            int end = Math.min(i + batchSize, idList.size());
+            List<Long> batch = idList.subList(i, end);
+            videoVectorMapperExt.deleteBatchByVideoIdsAndConfigCode(batch, configCode);
+        }
+        log.info("批量删除向量成功,configCode={}, 数量={}", configCode, videoIds.size());
+    }
+
+    // ---------------------------------------------------------------- 搜索
+
+    @Override
+    public List<VideoMatch> searchTopN(List<Float> queryVector, int topN) {
+        return searchTopN(DEFAULT_CONFIG_CODE, queryVector, topN);
+    }
+
+    @Override
+    public List<VideoMatch> searchTopN(String configCode, List<Float> queryVector, int topN) {
+        if (queryVector == null || queryVector.isEmpty() || topN <= 0) {
+            return Collections.emptyList();
+        }
+        if (configCode == null || configCode.isEmpty()) {
+            configCode = DEFAULT_CONFIG_CODE;
+        }
+
+        String queryVectorStr = vectorToString(queryVector);
+        log.info("开始pgvector搜索,configCode={},topN={}", configCode, topN);
+
+        List<VideoVectorSearchResult> results = videoVectorMapperExt.searchTopNByCosine(configCode, queryVectorStr, topN);
+        if (results == null || results.isEmpty()) {
+            log.info("向量库为空或无匹配结果,configCode={}", configCode);
+            return Collections.emptyList();
+        }
+
+        List<VideoMatch> matches = results.stream()
+                .map(vv -> {
+                    VideoMatch m = new VideoMatch(vv.getVideoId(), vv.getScore() != null ? vv.getScore() : 0.0);
+                    m.setPointIndex(vv.getPointIndex());
+                    return m;
+                })
+                .collect(Collectors.toList());
+
+        log.info("pgvector搜索完成,configCode={},返回 {} 条结果", configCode, matches.size());
+        return matches;
+    }
+
+    // ---------------------------------------------------------------- 工具方法
+
+    /**
+     * 将 List<Float> 转换为 pgvector 格式字符串: "[0.1,0.2,...]"
+     */
+    private String vectorToString(List<Float> vector) {
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < vector.size(); i++) {
+            if (i > 0) sb.append(",");
+            sb.append(vector.get(i));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 17 - 13
core/src/main/java/com/tzld/videoVector/service/impl/RedisVectorStoreServiceImpl.java

@@ -3,10 +3,11 @@ package com.tzld.videoVector.service.impl;
 import com.alibaba.fastjson.JSONArray;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructVectorConfigMapper;
+import com.tzld.videoVector.common.constant.VectorConstants;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
 import com.tzld.videoVector.model.entity.VideoMatch;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
 import com.tzld.videoVector.service.VectorStoreService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,11 +39,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
  * </ul>
  */
 @Slf4j
-@Service
+//@Service  // 已替换为 PgVectorStoreServiceImpl
 public class RedisVectorStoreServiceImpl implements VectorStoreService {
 
-    /** 向量 Key 前缀 */
-    private static final String VECTOR_KEY_PREFIX = "video:vector:";
 
     /** 本地缓存:configCode -> (videoId -> 归一化向量) */
     private final Cache<String, Map<Long, float[]>> vectorCache = CacheBuilder.newBuilder()
@@ -62,12 +61,17 @@ public class RedisVectorStoreServiceImpl implements VectorStoreService {
     // ---------------------------------------------------------------- CRUD
 
     @Override
-    public void save(Long videoId, List<Float> vector) {
-        save(DEFAULT_CONFIG_CODE, videoId, vector);
+    public void save(Long videoId, List<Float> vector, String text) {
+        save(DEFAULT_CONFIG_CODE, videoId, vector, text);
     }
 
     @Override
-    public void save(String configCode, Long videoId, List<Float> vector) {
+    public void save(String configCode, Long videoId, List<Float> vector, String text) {
+        save(configCode, videoId, 0, vector, text);
+    }
+
+    @Override
+    public void save(String configCode, Long videoId, int pointIndex, List<Float> vector, String text) {
         if (videoId == null || vector == null || vector.isEmpty()) {
             log.warn("save 参数非法,configCode={}, videoId={}", configCode, videoId);
             return;
@@ -513,9 +517,9 @@ public class RedisVectorStoreServiceImpl implements VectorStoreService {
     private Set<String> getAllConfigCodes() {
         Set<String> configCodes = new HashSet<>();
         try {
-            // 从数据库查询所有启用的配置
+            // 从数据库查询启用的 result_json 配置
             DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
-            example.createCriteria().andEnabledEqualTo((byte) 1);
+            example.createCriteria().andEnabledEqualTo((short) 1).andSourceFieldEqualTo("result_json");
             List<DeconstructVectorConfig> configs = vectorConfigMapper.selectByExample(example);
 
             for (DeconstructVectorConfig config : configs) {
@@ -585,7 +589,7 @@ public class RedisVectorStoreServiceImpl implements VectorStoreService {
      * 格式: video:vector:{configCode}:{videoId}
      */
     private String buildKey(String configCode, Long videoId) {
-        return VECTOR_KEY_PREFIX + configCode + ":" + videoId;
+        return VectorConstants.VECTOR_KEY_PREFIX + configCode + ":" + videoId;
     }
 
     /**
@@ -593,7 +597,7 @@ public class RedisVectorStoreServiceImpl implements VectorStoreService {
      * 格式: video:vector:{configCode}:ids
      */
     private String buildIdsKey(String configCode) {
-        return VECTOR_KEY_PREFIX + configCode + ":ids";
+        return VectorConstants.VECTOR_KEY_PREFIX + configCode + ":ids";
     }
 
     /**

+ 57 - 182
core/src/main/java/com/tzld/videoVector/service/impl/VectorizeServiceImpl.java

@@ -1,14 +1,15 @@
 package com.tzld.videoVector.service.impl;
 
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentVectorMapper;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructVectorConfigMapper;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.*;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.ContentVectorMapperExt;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.DeconstructVectorConfigMapperExt;
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+import com.tzld.videoVector.model.po.pgVector.DeconstructContent;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
 import com.tzld.videoVector.service.EmbeddingService;
 import com.tzld.videoVector.service.VectorizeService;
 import com.tzld.videoVector.util.Md5Util;
+import com.tzld.videoVector.util.VectorUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -27,10 +28,10 @@ import java.util.List;
 public class VectorizeServiceImpl implements VectorizeService {
 
     @Resource
-    private DeconstructVectorConfigMapper vectorConfigMapper;
+    private DeconstructVectorConfigMapperExt vectorConfigMapperExt;
 
     @Resource
-    private DeconstructContentVectorMapper contentVectorMapper;
+    private ContentVectorMapperExt contentVectorMapperExt;
 
     @Resource
     private EmbeddingService embeddingService;
@@ -38,14 +39,15 @@ public class VectorizeServiceImpl implements VectorizeService {
     @Override
     public List<DeconstructVectorConfig> getVectorConfigs(Integer bizType, Integer contentType) {
         // 使用 null-aware 查询:字段为 null(通用配置)和字段等于指定値的配置均可匹配
-        return vectorConfigMapper.selectMatchingConfigs(
-                bizType != null ? bizType.byteValue() : null,
-                contentType != null ? contentType.byteValue() : null
+        return vectorConfigMapperExt.selectMatchingConfigs(
+                bizType != null ? bizType.shortValue() : null,
+                contentType != null ? contentType.shortValue() : null,
+                "result_json"
         );
     }
 
     @Override
-    public List<DeconstructContentVector> vectorizeContent(DeconstructContent content) {
+    public List<ContentVector> vectorizeContent(DeconstructContent content) {
         if (content == null || content.getId() == null) {
             log.error("向量化内容为空或ID为空");
             return new ArrayList<>();
@@ -63,19 +65,19 @@ public class VectorizeServiceImpl implements VectorizeService {
             return new ArrayList<>();
         }
 
-        List<DeconstructContentVector> allVectors = new ArrayList<>();
+        List<ContentVector> allVectors = new ArrayList<>();
 
         // 按配置逐个向量化
         for (DeconstructVectorConfig config : configs) {
             try {
                 // 幂等检查:若该 configCode 已有向量则跳过,防止重复向量化
-                List<DeconstructContentVector> existing = getVectorsByContentId(content.getId(), config.getConfigCode());
+                List<ContentVector> existing = getVectorsByContentId(content.getId(), config.getConfigCode());
                 if (!CollectionUtils.isEmpty(existing)) {
                     log.debug("contentId={} 已有 configCode={} 的向量,跳过向量化",
                             content.getId(), config.getConfigCode());
                     continue;
                 }
-                List<DeconstructContentVector> vectors = vectorizeByConfig(content, config);
+                List<ContentVector> vectors = vectorizeByConfig(content, config);
                 if (!CollectionUtils.isEmpty(vectors)) {
                     allVectors.addAll(vectors);
                 }
@@ -96,9 +98,9 @@ public class VectorizeServiceImpl implements VectorizeService {
     }
 
     @Override
-    public List<DeconstructContentVector> vectorizeByConfig(DeconstructContent content,
-                                                            DeconstructVectorConfig config) {
-        List<DeconstructContentVector> vectors = new ArrayList<>();
+    public List<ContentVector> vectorizeByConfig(DeconstructContent content,
+                                                 DeconstructVectorConfig config) {
+        List<ContentVector> vectors = new ArrayList<>();
 
         // 提取文本内容
         List<String> texts = extractTexts(content, config);
@@ -133,22 +135,21 @@ public class VectorizeServiceImpl implements VectorizeService {
                 }
             }
 
-            // 构建向量实体
-            DeconstructContentVector vector = new DeconstructContentVector();
+            // 构建向量实体(直接使用 PG ContentVector)
+            ContentVector vector = new ContentVector();
             vector.setContentId(content.getId());
             vector.setTaskId(content.getTaskId());
             vector.setConfigCode(config.getConfigCode());
             vector.setSourceField(config.getSourceField());
             vector.setSourcePath(config.getSourcePath());
-            vector.setVectorDimension(vectorData.size());
-            vector.setVectorData(JSON.toJSONString(vectorData));
+            vector.setEmbedding(JSON.toJSONString(vectorData));
             vector.setSourceText(truncatedText);
             vector.setTextHash(textHash);
             vector.setEmbeddingModel(embeddingModel);
             vector.setSegmentIndex(segmentIndex++);
             vector.setSegmentTotal(texts.size());
-            vector.setCreateTime(new Date());
-            vector.setUpdateTime(new Date());
+            vector.setCreatedAt(new Date());
+            vector.setUpdatedAt(new Date());
 
             vectors.add(vector);
         }
@@ -179,13 +180,13 @@ public class VectorizeServiceImpl implements VectorizeService {
             case "result_json":
                 // 从解构结果JSON中提取
                 if (StringUtils.hasText(content.getResultJson()) && StringUtils.hasText(sourcePath)) {
-                    texts.addAll(extractFromJson(content.getResultJson(), sourcePath));
+                    texts.addAll(VectorUtils.extractFromJson(content.getResultJson(), sourcePath));
                 }
                 break;
             default:
                 // 尝试从result_json中提取自定义字段
                 if (StringUtils.hasText(content.getResultJson()) && StringUtils.hasText(sourcePath)) {
-                    texts.addAll(extractFromJson(content.getResultJson(), sourcePath));
+                    texts.addAll(VectorUtils.extractFromJson(content.getResultJson(), sourcePath));
                 }
                 break;
         }
@@ -193,120 +194,6 @@ public class VectorizeServiceImpl implements VectorizeService {
         return texts;
     }
 
-    /**
-     * 从JSON中提取文本
-     * 支持路径格式:$.final_normalization_rebuild.topic_fusion_result.最终选题.选题
-     * 支持中文 key 和数组路径如 topics[*]
-     */
-    private List<String> extractFromJson(String json, String path) {
-        List<String> results = new ArrayList<>();
-
-        if (!StringUtils.hasText(json) || !StringUtils.hasText(path)) {
-            return results;
-        }
-
-        try {
-            JSONObject jsonObject = JSON.parseObject(json);
-            if (jsonObject == null) {
-                return results;
-            }
-
-            // 路径处理(如 $.final_normalization_rebuild.topic_fusion_result.最终选题.选题)
-            if (path.startsWith("$.")) {
-                String pathContent = path.substring(2);
-                // 使用正则分割,支持中文 key
-                List<String> parts = parseJsonPath(pathContent);
-                Object current = jsonObject;
-
-                for (int i = 0; i < parts.size(); i++) {
-                    String part = parts.get(i);
-                    if (current == null) {
-                        break;
-                    }
-
-                    // 处理数组路径(如 topics[*])
-                    if (part.endsWith("[*]")) {
-                        String arrayKey = part.substring(0, part.length() - 3);
-                        if (current instanceof JSONObject) {
-                            JSONArray array = ((JSONObject) current).getJSONArray(arrayKey);
-                            if (array != null) {
-                                // 递归处理数组元素
-                                List<String> remainingParts = parts.subList(i + 1, parts.size());
-                                String remainingPath = String.join(".", remainingParts);
-                                for (int j = 0; j < array.size(); j++) {
-                                    Object item = array.get(j);
-                                    if (remainingParts.isEmpty()) {
-                                        // 直接取数组元素值
-                                        if (item instanceof String) {
-                                            results.add((String) item);
-                                        }
-                                    } else {
-                                        // 继续深入
-                                        results.addAll(extractFromJson(
-                                                JSON.toJSONString(item), "$." + remainingPath));
-                                    }
-                                }
-                            }
-                        }
-                        return results;
-                    } else {
-                        // 普通对象路径(支持中文 key)
-                        if (current instanceof JSONObject) {
-                            current = ((JSONObject) current).get(part);
-                        } else {
-                            // 当前节点不是对象,无法继续
-                            break;
-                        }
-                    }
-                }
-
-                // 提取最终值
-                if (current instanceof String) {
-                    results.add((String) current);
-                } else if (current instanceof JSONArray) {
-                    JSONArray array = (JSONArray) current;
-                    for (int i = 0; i < array.size(); i++) {
-                        Object item = array.get(i);
-                        if (item instanceof String) {
-                            results.add((String) item);
-                        }
-                    }
-                }
-            }
-        } catch (Exception e) {
-            log.error("JSON提取失败,path={}, error={}", path, e.getMessage());
-        }
-
-        return results;
-    }
-
-    /**
-     * 解析 JSONPath 路径,支持中文 key
-     * 例如:final_normalization_rebuild.topic_fusion_result.最终选题.选题
-     */
-    private List<String> parseJsonPath(String pathContent) {
-        List<String> parts = new ArrayList<>();
-        StringBuilder current = new StringBuilder();
-        
-        for (int i = 0; i < pathContent.length(); i++) {
-            char c = pathContent.charAt(i);
-            if (c == '.') {
-                if (current.length() > 0) {
-                    parts.add(current.toString());
-                    current = new StringBuilder();
-                }
-            } else {
-                current.append(c);
-            }
-        }
-        // 添加最后一个部分
-        if (current.length() > 0) {
-            parts.add(current.toString());
-        }
-        
-        return parts;
-    }
-
     /**
      * 文本截断
      * maxLength 为 null 或 <= 0 时不进行截断
@@ -327,47 +214,52 @@ public class VectorizeServiceImpl implements VectorizeService {
 
     /**
      * 查询已缓存的向量数据(text_hash + config_code 联合匹配)
-     * 命中则返回向量浮点数列表,否则返回 null
+     * 优先查 pgvector,命中则返回向量浮点数列表,否则返回 null
      */
     private List<Float> findCachedVectorData(String textHash, String configCode) {
         if (!StringUtils.hasText(textHash) || !StringUtils.hasText(configCode)) {
             return null;
         }
         try {
-            DeconstructContentVectorExample example = new DeconstructContentVectorExample();
-            example.createCriteria()
-                    .andTextHashEqualTo(textHash)
-                    .andConfigCodeEqualTo(configCode);
-            example.setOrderByClause("id DESC");
-            List<DeconstructContentVector> cached = contentVectorMapper.selectByExampleWithBLOBs(example);
-            if (CollectionUtils.isEmpty(cached)) {
-                return null;
+            // 从 pgvector 查询
+            ContentVector cached = contentVectorMapperExt.selectByTextHashAndConfigCode(textHash, configCode);
+            if (cached != null && StringUtils.hasText(cached.getEmbedding())) {
+                return VectorUtils.parseVectorString(cached.getEmbedding());
             }
-            String vectorDataJson = cached.get(0).getVectorData();
-            if (!StringUtils.hasText(vectorDataJson)) {
-                return null;
-            }
-            return JSON.parseArray(vectorDataJson, Float.class);
         } catch (Exception e) {
             log.error("查询 text_hash 向量缓存失败,hash={}, configCode={}, error={}",
                     textHash, configCode, e.getMessage());
-            return null;
         }
+        return null;
     }
 
     @Override
-    public int batchSaveVectors(List<DeconstructContentVector> vectors) {
+    public int batchSaveVectors(List<ContentVector> vectors) {
         if (CollectionUtils.isEmpty(vectors)) {
             return 0;
         }
 
         int count = 0;
-        for (DeconstructContentVector vector : vectors) {
+        for (ContentVector vector : vectors) {
             try {
-                contentVectorMapper.insertSelective(vector);
+                // 存储到 pgvector,embedding 是 JSON 数组格式 "[0.1,0.2,...]"
+                String embedding = vector.getEmbedding();
+                contentVectorMapperExt.upsertWithEmbedding(
+                        vector.getContentId(),
+                        vector.getTaskId(),
+                        vector.getConfigCode(),
+                        vector.getSourceField(),
+                        vector.getSourcePath(),
+                        vector.getTextHash(),
+                        vector.getEmbeddingModel(),
+                        vector.getSegmentIndex(),
+                        vector.getSegmentTotal(),
+                        vector.getSourceText(),
+                        embedding
+                );
                 count++;
             } catch (Exception e) {
-                log.error("保存向量失败,contentId={}, sourceField={}, error={}",
+                log.error("保存向量到pgvector失败,contentId={}, sourceField={}, error={}",
                         vector.getContentId(), vector.getSourceField(), e.getMessage());
             }
         }
@@ -375,36 +267,19 @@ public class VectorizeServiceImpl implements VectorizeService {
         return count;
     }
 
-    /**
-     * 根据 contentId 查询向量列表
-     */
     @Override
-    public List<DeconstructContentVector> getVectorsByContentId(Long contentId) {
-        DeconstructContentVectorExample example = new DeconstructContentVectorExample();
-        example.createCriteria().andContentIdEqualTo(contentId);
-        example.setOrderByClause("source_field ASC, segment_index ASC");
-        return contentVectorMapper.selectByExampleWithBLOBs(example);
+    public List<ContentVector> getVectorsByContentId(Long contentId) {
+        return contentVectorMapperExt.selectByContentId(contentId);
     }
 
     @Override
-    public List<DeconstructContentVector> getVectorsByContentId(Long contentId, String configCode) {
-        DeconstructContentVectorExample example = new DeconstructContentVectorExample();
-        DeconstructContentVectorExample.Criteria criteria = example.createCriteria()
-                .andContentIdEqualTo(contentId);
-        if (StringUtils.hasText(configCode)) {
-            criteria.andConfigCodeEqualTo(configCode);
-        }
-        example.setOrderByClause("source_field ASC, segment_index ASC");
-        return contentVectorMapper.selectByExampleWithBLOBs(example);
+    public List<ContentVector> getVectorsByContentId(Long contentId, String configCode) {
+        return contentVectorMapperExt.selectByContentIdAndConfigCode(contentId, configCode);
     }
 
     @Override
-    public List<DeconstructContentVector> getVectorsByField(Long contentId, String sourceField) {
-        DeconstructContentVectorExample example = new DeconstructContentVectorExample();
-        example.createCriteria()
-                .andContentIdEqualTo(contentId)
-                .andSourceFieldEqualTo(sourceField);
-        example.setOrderByClause("segment_index ASC");
-        return contentVectorMapper.selectByExampleWithBLOBs(example);
+    public List<ContentVector> getVectorsByField(Long contentId, String sourceField) {
+        return contentVectorMapperExt.selectByContentIdAndField(contentId, sourceField);
     }
+
 }

+ 244 - 142
core/src/main/java/com/tzld/videoVector/service/impl/VideoSearchServiceImpl.java

@@ -2,33 +2,34 @@ package com.tzld.videoVector.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.videoVector.api.VideoApiService;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentMapper;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentVectorMapper;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructVectorConfigMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.ext.ContentVectorMapperExt;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructContentMapper;
+import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
 import com.tzld.videoVector.model.entity.DeconstructResult;
 import com.tzld.videoVector.model.entity.VideoDetail;
 import com.tzld.videoVector.model.entity.VideoMatch;
 import com.tzld.videoVector.model.param.DeconstructParam;
 import com.tzld.videoVector.model.param.GetDeconstructParam;
 import com.tzld.videoVector.model.param.MatchTopNVideoParam;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample;
+import com.tzld.videoVector.model.po.pgVector.ContentVector;
+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.vo.VideoMatchResult;
+import com.tzld.videoVector.common.constant.VectorConstants;
 import com.tzld.videoVector.service.*;
 import com.tzld.videoVector.util.Md5Util;
+import com.tzld.videoVector.util.VectorUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
 import javax.annotation.Resource;
 import java.util.*;
-import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
-import static com.tzld.videoVector.service.VectorStoreService.DEFAULT_CONFIG_CODE;
+import static com.tzld.videoVector.common.constant.VectorConstants.*;
 
 @Slf4j
 @Service
@@ -47,10 +48,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     private DeconstructContentMapper deconstructContentMapper;
 
     @Resource
-    private DeconstructContentVectorMapper deconstructContentVectorMapper;
-
-    @Resource
-    private VectorizeService vectorizeService;
+    private ContentVectorMapperExt pgContentVectorMapper;
 
     @Resource
     private VideoApiService videoApiService;
@@ -58,19 +56,6 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     @Resource
     private DeconstructVectorConfigMapper deconstructVectorConfigMapper;
 
-    // 异步向量化线程池,避免裸创建 Thread
-    private static final ExecutorService VECTORIZE_EXECUTOR = new ThreadPoolExecutor(
-            2, 10, 60L, TimeUnit.SECONDS,
-            new LinkedBlockingQueue<>(100),
-            r -> {
-                Thread t = new Thread(r);
-                t.setName("vectorize-async-" + t.getId());
-                t.setDaemon(true);
-                return t;
-            },
-            new ThreadPoolExecutor.CallerRunsPolicy()
-    );
-
     @Override
     public String deconstruct(DeconstructParam param) {
         if (param == null) {
@@ -87,7 +72,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
         if (StringUtils.hasText(channelContentId)) {
             DeconstructContent existingContent = getDeconstructContentByChannelContentId(channelContentId);
             if (existingContent != null) {
-                Byte status = existingContent.getStatus();
+                Short status = existingContent.getStatus();
                 // 状态: 0-PENDING, 1-RUNNING, 2-SUCCESS, 3-FAILED
                 // 如果状态为 PENDING、RUNNING 或 SUCCESS,不重复提交,直接返回已有 taskId
                 if (status != null && status != 3) {
@@ -131,14 +116,14 @@ public class VideoSearchServiceImpl implements VideoSearchService {
             if (failedContent != null) {
                 // 历史存在失败记录,更新该记录,不新增
                 failedContent.setTaskId(taskId);
-                failedContent.setBizType(bizType.byteValue());
-                failedContent.setContentType(contentType.byteValue());
+                failedContent.setBizType(bizType.shortValue());
+                failedContent.setContentType(contentType.shortValue());
                 failedContent.setTitle(param.getTitle());
                 failedContent.setBodyText(param.getBodyText());
                 failedContent.setVideoUrl(param.getVideoUrl());
                 failedContent.setChannelAccountId(param.getChannelAccountId());
                 failedContent.setChannelAccountName(param.getChannelAccountName());
-                failedContent.setStatus((byte) 0); // PENDING
+                failedContent.setStatus((short) 0); // PENDING
                 // 清除历史失败信息(使用空字符串确保 selective update 生效)
                 failedContent.setResultJson("");
                 failedContent.setFailureReason("");
@@ -155,15 +140,15 @@ public class VideoSearchServiceImpl implements VideoSearchService {
             } else {
                 DeconstructContent content = new DeconstructContent();
                 content.setTaskId(taskId);
-                content.setBizType(bizType.byteValue());
-                content.setContentType(contentType.byteValue());
+                content.setBizType(bizType.shortValue());
+                content.setContentType(contentType.shortValue());
                 content.setChannelContentId(param.getChannelContentId());
                 content.setTitle(param.getTitle());
                 content.setBodyText(param.getBodyText());
                 content.setVideoUrl(param.getVideoUrl());
                 content.setChannelAccountId(param.getChannelAccountId());
                 content.setChannelAccountName(param.getChannelAccountName());
-                content.setStatus((byte) 0); // PENDING
+                content.setStatus((short) 0); // PENDING
                 content.setCreateTime(new Date());
                 content.setUpdateTime(new Date());
                 // 处理图片列表
@@ -224,13 +209,6 @@ public class VideoSearchServiceImpl implements VideoSearchService {
             content = updateContentFromResult(content, result, taskId, param);
         }
 
-        // 如果任务完成且成功,触发向量化处理
-        // vectorizeContent 内部已按 configCode 做幂等检查,新增配置也会被自动补充向量化
-        if (content != null && content.getStatus() != null && content.getStatus() == 2) {
-            vectorizeContentAsync(content);
-            log.info("触发向量化处理,contentId={}, taskId={}", content.getId(), taskId);
-        }
-
         // 构建返回结果
         if (result != null) {
             return buildResultFromApiResult(result);
@@ -247,7 +225,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     private DeconstructContent getDeconstructContentByTaskId(String taskId) {
         DeconstructContentExample example = new DeconstructContentExample();
         example.createCriteria().andTaskIdEqualTo(taskId);
-        List<DeconstructContent> list = deconstructContentMapper.selectByExampleWithBLOBs(example);
+        List<DeconstructContent> list = deconstructContentMapper.selectByExample(example);
         return list.isEmpty() ? null : list.get(0);
     }
 
@@ -274,14 +252,14 @@ public class VideoSearchServiceImpl implements VideoSearchService {
                 // 创建新记录
                 content = new DeconstructContent();
                 content.setTaskId(taskId);
-                content.setBizType(param.getBizType() != null ? param.getBizType().byteValue() : 0);
-                content.setContentType(param.getContentType() != null ? param.getContentType().byteValue() : 2);
+                content.setBizType(param.getBizType() != null ? param.getBizType().shortValue() : 0);
+                content.setContentType(param.getContentType() != null ? param.getContentType().shortValue() : 2);
                 content.setChannelContentId(param.getChannelContentId());
                 content.setCreateTime(new Date());
             }
 
             // 更新状态和结果
-            content.setStatus(result.getStatus() != null ? result.getStatus().byteValue() : (byte) 3);
+            content.setStatus(result.getStatus() != null ? result.getStatus().shortValue() : (short) 3);
             content.setResultJson(result.getResult());
             content.setFailureReason(result.getReason());
             content.setPointUrl(result.getPointUrl());
@@ -383,7 +361,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     /**
      * 获取状态描述
      */
-    private String getStatusDesc(Byte status) {
+    private String getStatusDesc(Short status) {
         if (status == null) return "UNKNOWN";
         switch (status) {
             case 0: return "PENDING";
@@ -395,61 +373,10 @@ public class VideoSearchServiceImpl implements VideoSearchService {
     }
 
     /**
-     * 若内容已解构完成(status==2)但向量表缺少对应 configCode 的向量,则同步触发向量化
-     */
-    private void triggerVectorizeIfNeeded(String channelContentId, String configCode) {
-        try {
-            // 查询解构内容(含 BLOB 字段,vectorizeContent 需要 resultJson/bodyText 等)
-            DeconstructContentExample example = new DeconstructContentExample();
-            example.createCriteria().andChannelContentIdEqualTo(channelContentId);
-            example.setOrderByClause("id DESC");
-            List<DeconstructContent> list = deconstructContentMapper.selectByExampleWithBLOBs(example);
-            if (list == null || list.isEmpty()) {
-                log.debug("triggerVectorizeIfNeeded: 未找到 channelContentId={} 的解构记录", channelContentId);
-                return;
-            }
-            DeconstructContent content = list.get(0);
-            // 仅对已解构完成(status == 2)的内容触发向量化
-            if (content.getStatus() == null || content.getStatus() != 2) {
-                log.debug("triggerVectorizeIfNeeded: channelContentId={} 解构未完成,status={}",
-                        channelContentId, content.getStatus());
-                return;
-            }
-            // 检查向量表是否已有该 configCode 的记录
-            List<DeconstructContentVector> vectors =
-                    vectorizeService.getVectorsByContentId(content.getId(), configCode);
-            if (vectors != null && !vectors.isEmpty()) {
-                log.debug("triggerVectorizeIfNeeded: channelContentId={} 已有 configCode={} 向量,无需触发",
-                        channelContentId, configCode);
-                return;
-            }
-            log.info("内容已解构完成但向量表无对应 configCode 向量,触发向量化,channelContentId={}, contentId={}, configCode={}",
-                    channelContentId, content.getId(), configCode);
-            vectorizeService.vectorizeContent(content);
-        } catch (Exception e) {
-            log.error("检查并触发向量化失败,channelContentId={}, configCode={}, error={}",
-                    channelContentId, configCode, e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 异步向量化内容,使用线程池执行,避免阻塞接口响应
+     * 匹配TopN视频
      */
-    private void vectorizeContentAsync(DeconstructContent content) {
-        VECTORIZE_EXECUTOR.submit(() -> {
-            try {
-                log.info("开始异步向量化处理,contentId={}", content.getId());
-                vectorizeService.vectorizeContent(content);
-                log.info("异步向量化处理完成,contentId={}", content.getId());
-            } catch (Exception e) {
-                log.error("异步向量化处理失败,contentId={}, error={}",
-                        content.getId(), e.getMessage(), e);
-            }
-        });
-    }
-
     @Override
-    public List<Object> matchTopNVideo(MatchTopNVideoParam param) {
+    public List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param) {
         if (param == null) {
             log.error("matchTopNVideo 参数为空");
             return Collections.emptyList();
@@ -461,40 +388,195 @@ public class VideoSearchServiceImpl implements VideoSearchService {
             configCode = DEFAULT_CONFIG_CODE;
         }
 
-        // 若提供了 channelContentId,且内容已解构完成但向量表缺少对应 configCode 的向量,则异步触发向量化
-        if (StringUtils.hasText(param.getChannelContentId())) {
-            triggerVectorizeIfNeeded(param.getChannelContentId(), configCode);
+        // 确定要搜索的配置列表
+        List<DeconstructVectorConfig> searchConfigs;
+        if (VectorConstants.ALL_CONFIG_CODE.equalsIgnoreCase(configCode)) {
+            searchConfigs = getEnabledConfigs();
+            if (searchConfigs.isEmpty()) {
+                log.warn("未找到任何启用的向量化配置");
+                return Collections.emptyList();
+            }
+            log.info("all 模式,加载 {} 个启用的向量化配置", searchConfigs.size());
+        } else {
+            DeconstructVectorConfig singleConfig = getVectorConfigByCode(configCode);
+            searchConfigs = singleConfig != null
+                    ? Collections.singletonList(singleConfig)
+                    : Collections.emptyList();
+            // 兼容:如果查不到配置记录,仍按原始逻辑用 configCode 搜索(不做多点解码)
+            if (searchConfigs.isEmpty()) {
+                log.warn("未找到 configCode={} 的配置记录,降级为原始搜索", configCode);
+                return matchTopNVideoLegacy(param, configCode, topN);
+            }
         }
 
-        // 确定查询向量:直接传入 > channelContentId历史向量 > text_hash历史embedding > 文本向量化
+        // 对每个配置分别搜索,各自取 topN 后汇总返回
+        List<VideoMatchResult> result = new ArrayList<>();
+        int candidateSize = topN * 3;
+
+        // 缓存 embeddingModel -> queryVector,避免相同模型重复 embedding
+        Map<String, List<Float>> embeddingCache = new HashMap<>();
+
+        for (DeconstructVectorConfig config : searchConfigs) {
+            String cfgCode = config.getConfigCode();
+            try {
+                // 解析查询向量(按配置的 embeddingModel 分组缓存)
+                List<Float> queryVector = resolveQueryVectorForConfig(param, config, embeddingCache);
+                if (queryVector == null || queryVector.isEmpty()) {
+                    log.warn("配置 {} 无法获取查询向量,跳过", cfgCode);
+                    continue;
+                }
+
+                log.info("配置 {} 开始搜索 Top-{},向量维度: {}", cfgCode, candidateSize, queryVector.size());
+                List<VideoMatch> matches = vectorStoreService.searchTopN(cfgCode, queryVector, candidateSize);
+
+                if (matches == null || matches.isEmpty()) {
+                    log.debug("配置 {} 无匹配结果", cfgCode);
+                    continue;
+                }
+
+                // 多点模式:同一videoId去重保留最高分
+                boolean multiPoint = VectorUtils.isMultiPointConfig(config);
+                if (multiPoint) {
+                    matches = deduplicateMultiPointMatches(matches, cfgCode);
+                } else {
+                    // 单点模式:直接设置 configCode
+                    for (VideoMatch m : matches) {
+                        m.setConfigCode(cfgCode);
+                    }
+                }
+
+                // 每个配置独立进行审核过滤并取各自的 topN
+                List<VideoMatch> filteredMatches = filterByAuditStatus(matches, topN);
+
+                // 转化为强类型返回格式
+                for (VideoMatch match : filteredMatches) {
+                    result.add(new VideoMatchResult(cfgCode, match.getVideoId(), match.getScore()));
+                }
+
+                log.info("配置 {} 搜索完成,返回 {} 条结果", cfgCode, filteredMatches.size());
+
+            } catch (Exception e) {
+                log.error("配置 {} 搜索失败: {}", cfgCode, e.getMessage(), e);
+            }
+        }
+
+        log.info("匹配完成,configCode: {},共返回 {} 条结果", param.getConfigCode(), result.size());
+        return result;
+    }
+
+    /**
+     * 降级的原始匹配逻辑(configCode 在数据库中无记录时使用)
+     */
+    private List<VideoMatchResult> matchTopNVideoLegacy(MatchTopNVideoParam param, String configCode, int topN) {
         List<Float> queryVector = resolveQueryVector(param);
         if (queryVector == null || queryVector.isEmpty()) {
             log.error("matchTopNVideo 无法获取查询向量,param={}", param);
             return Collections.emptyList();
         }
-
-        log.info("开始匹配 Top-{} 视频,configCode: {},向量维度: {}", topN, configCode, queryVector.size());
-
-        // configCode 已在上方确保非空,直接使用它搜索
         int candidateSize = topN * 3;
         List<VideoMatch> matches = vectorStoreService.searchTopN(configCode, queryVector, candidateSize);
-
-        // 过滤审核状态不通过的视频
         List<VideoMatch> filteredMatches = filterByAuditStatus(matches, topN);
-
-        // 转化为返回格式
-        List<Object> result = new ArrayList<>(filteredMatches.size());
+        List<VideoMatchResult> result = new ArrayList<>(filteredMatches.size());
         for (VideoMatch match : filteredMatches) {
-            JSONObject item = new JSONObject();
-            item.put("videoId", match.getVideoId());
-            item.put("score", match.getScore());
-            result.add(item);
+            result.add(new VideoMatchResult(configCode, match.getVideoId(), match.getScore()));
         }
-
-        log.info("匹配完成,configCode: {},返回 {} 条结果", configCode, result.size());
         return result;
     }
 
+    /**
+     * 根据具体配置解析查询向量(含降级逻辑)
+     * 优先级:直接传入 queryVector > channelContentId 历史向量 > queryText 向量化
+     * 任意一级命中则直接返回;channelContentId 查不到时自动降级到 queryText
+     * 相同 embeddingModel 的配置共享同一个查询向量,避免重复 embedding
+     */
+    private List<Float> resolveQueryVectorForConfig(MatchTopNVideoParam param,
+                                                    DeconstructVectorConfig config,
+                                                    Map<String, List<Float>> embeddingCache) {
+        // 1. 直接传入的 queryVector 优先(不依赖 embeddingModel)
+        if (param.getQueryVector() != null && !param.getQueryVector().isEmpty()) {
+            return param.getQueryVector();
+        }
+
+        // 2. channelContentId 历史向量(查不到时降级到 queryText)
+        if (StringUtils.hasText(param.getChannelContentId())) {
+            List<Float> cached = getVectorByChannelContentId(param.getChannelContentId(), config.getConfigCode());
+            if (cached != null && !cached.isEmpty()) {
+                log.info("配置 {} 命中 channelContentId 历史向量,channelContentId={}",
+                        config.getConfigCode(), param.getChannelContentId());
+                return cached;
+            }
+            log.info("配置 {} channelContentId={} 未命中历史向量,降级到 queryText",
+                    config.getConfigCode(), param.getChannelContentId());
+        }
+
+        // 3. queryText 向量化:按 embeddingModel 缓存
+        if (StringUtils.hasText(param.getQueryText())) {
+            String embeddingModel = config.getEmbeddingModel();
+            String cacheKey = embeddingModel != null ? embeddingModel : "__default__";
+
+            if (embeddingCache.containsKey(cacheKey)) {
+                return embeddingCache.get(cacheKey);
+            }
+
+            // 先查 text_hash 缓存
+            String textHash = Md5Util.encoderByMd5(param.getQueryText());
+            if (StringUtils.hasText(textHash)) {
+                List<Float> cached = getVectorByTextHash(textHash, config.getConfigCode());
+                if (cached != null && !cached.isEmpty()) {
+                    embeddingCache.put(cacheKey, cached);
+                    return cached;
+                }
+            }
+
+            // 调用 embedding API
+            List<Float> vector = embeddingService.embed(param.getQueryText(), config);
+            if (vector != null && !vector.isEmpty()) {
+                embeddingCache.put(cacheKey, vector);
+                return vector;
+            }
+            log.warn("配置 {} embedding 失败", config.getConfigCode());
+            return null;
+        }
+
+        return null;
+    }
+
+    /**
+     * 多点模式下对同一videoId去重(保留最高分)
+     * 由于 video_vectors 表已有独立的 point_index 列,搜索结果中的 videoId 即为真实值,
+     * 无需再从复合ID解码。
+     */
+    private List<VideoMatch> deduplicateMultiPointMatches(List<VideoMatch> matches, String configCode) {
+        Map<Long, VideoMatch> deduped = new LinkedHashMap<>();
+
+        for (VideoMatch match : matches) {
+            Long videoId = match.getVideoId();
+            VideoMatch existing = deduped.get(videoId);
+            if (existing == null || match.getScore() > existing.getScore()) {
+                deduped.put(videoId, new VideoMatch(videoId, match.getScore(), configCode));
+            }
+        }
+
+        return new ArrayList<>(deduped.values());
+    }
+
+    /**
+     * 获取所有启用的向量化配置
+     * 仅返回 source_field = 'result_json' 的素材/视频配置
+     */
+    private List<DeconstructVectorConfig> getEnabledConfigs() {
+        try {
+            DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+            example.createCriteria().andEnabledEqualTo((short) 1).andSourceFieldEqualTo("result_json");
+            example.setOrderByClause("priority ASC");
+            List<DeconstructVectorConfig> configs = deconstructVectorConfigMapper.selectByExample(example);
+            return configs != null ? configs : Collections.emptyList();
+        } catch (Exception e) {
+            log.error("查询启用配置失败: {}", e.getMessage(), e);
+            return Collections.emptyList();
+        }
+    }
+
     /**
      * 解析查询向量,优先级:直接传入 > channelContentId历史向量 > text_hash历史embedding > 文本向量化
      * 任意一级命中则直接返回,不再继续降级
@@ -564,57 +646,56 @@ public class VideoSearchServiceImpl implements VideoSearchService {
 
     /**
      * 通过 text_hash 查询历史 embedding 结果,命中则跳过 embedding API 调用
+     * 优先从 pgvector 查询
      */
     private List<Float> getVectorByTextHash(String textHash, String configCode) {
         try {
-            DeconstructContentVectorExample example = new DeconstructContentVectorExample();
-            DeconstructContentVectorExample.Criteria criteria = example.createCriteria()
-                    .andTextHashEqualTo(textHash);
+            ContentVector cached;
             if (StringUtils.hasText(configCode)) {
-                criteria.andConfigCodeEqualTo(configCode);
-            }
-            example.setOrderByClause("id DESC");
-            List<DeconstructContentVector> vectors = deconstructContentVectorMapper.selectByExampleWithBLOBs(example);
-            if (vectors == null || vectors.isEmpty()) {
-                return null;
+                cached = pgContentVectorMapper.selectByTextHashAndConfigCode(textHash, configCode);
+            } else {
+                cached = pgContentVectorMapper.selectByTextHash(textHash);
             }
-            String vectorDataJson = vectors.get(0).getVectorData();
-            if (!StringUtils.hasText(vectorDataJson)) {
-                return null;
+            if (cached != null && StringUtils.hasText(cached.getEmbedding())) {
+                return VectorUtils.parseVectorString(cached.getEmbedding());
             }
-            return com.alibaba.fastjson.JSON.parseArray(vectorDataJson, Float.class);
         } catch (Exception e) {
             log.error("按 text_hash 查询向量失败,hash={}, configCode={}, error={}", textHash, configCode, e.getMessage(), e);
-            return null;
         }
+        return null;
     }
 
     /**
      * 通过 channelContentId 查询历史向量化结果,有则直接复用,避免重复调用 embedding
+     * 直接从 PG content_vectors 表查询,不再依赖 MySQL
      */
     private List<Float> getVectorByChannelContentId(String channelContentId, String configCode) {
         try {
-            // 查询 deconstruct_content
+            // 查询 deconstruct_content(PG)
             DeconstructContent content = getDeconstructContentByChannelContentId(channelContentId);
             if (content == null || content.getId() == null) {
                 log.info("未找到 channelContentId={} 对应的解构内容", channelContentId);
                 return null;
             }
-            // 查询 deconstruct_content_vector(按 configCode 过滤)
-            List<DeconstructContentVector> vectors = vectorizeService.getVectorsByContentId(content.getId(), configCode);
-            if (vectors == null || vectors.isEmpty()) {
+            // 直接从 PG content_vectors 查询向量(按 configCode 过滤)
+            List<ContentVector> pgVectors;
+            if (StringUtils.hasText(configCode)) {
+                pgVectors = pgContentVectorMapper.selectByContentIdAndConfigCode(content.getId(), configCode);
+            } else {
+                pgVectors = pgContentVectorMapper.selectByContentId(content.getId());
+            }
+            if (pgVectors == null || pgVectors.isEmpty()) {
                 log.info("channelContentId={} 尚无历史向量化结果,contentId={}, configCode={}",
                         channelContentId, content.getId(), configCode);
                 return null;
             }
             // 使用第一条向量数据
-            DeconstructContentVector vector = vectors.get(0);
-            String vectorDataJson = vector.getVectorData();
-            if (!StringUtils.hasText(vectorDataJson)) {
+            ContentVector vector = pgVectors.get(0);
+            if (!StringUtils.hasText(vector.getEmbedding())) {
                 return null;
             }
-            List<Float> vectorData = com.alibaba.fastjson.JSON.parseArray(vectorDataJson, Float.class);
-            log.info("复用历史向量化结果,channelContentId={}, contentId={}, configCode={}, sourceField={}, 向量维度={}",
+            List<Float> vectorData = VectorUtils.parseVectorString(vector.getEmbedding());
+            log.info("复用历史向量化结果(PG),channelContentId={}, contentId={}, configCode={}, sourceField={}, 向量维度={}",
                     channelContentId, content.getId(), configCode, vector.getSourceField(),
                     vectorData != null ? vectorData.size() : 0);
             return vectorData;
@@ -658,4 +739,25 @@ public class VideoSearchServiceImpl implements VideoSearchService {
 
         return filteredMatches;
     }
+
+    @Override
+    public Map<String, String> getAllConfigCodes() {
+        try {
+            DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
+            example.createCriteria().andEnabledEqualTo((short) 1);
+            example.setOrderByClause("priority ASC");
+            List<DeconstructVectorConfig> configs = deconstructVectorConfigMapper.selectByExample(example);
+            if (configs == null || configs.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            Map<String, String> result = new LinkedHashMap<>();
+            for (DeconstructVectorConfig config : configs) {
+                result.putIfAbsent(config.getConfigCode(), config.getConfigName());
+            }
+            return result;
+        } catch (Exception e) {
+            log.error("查询所有 configCode 失败: {}", e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
 }

+ 23 - 43
core/src/main/java/com/tzld/videoVector/service/recall/impl/VectorRecallTestServiceImpl.java

@@ -2,18 +2,18 @@ package com.tzld.videoVector.service.recall.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
 import com.tzld.videoVector.api.VideoApiService;
 import com.tzld.videoVector.common.enums.Modality;
 import com.tzld.videoVector.dao.mapper.videoVector.VideoAiUnderstandingMapper;
-import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.DeconstructContentMapper;
+import com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructContentMapper;
 import com.tzld.videoVector.model.entity.VideoDetail;
 import com.tzld.videoVector.model.param.MatchTopNVideoParam;
 import com.tzld.videoVector.model.param.recall.MatchByTextParam;
 import com.tzld.videoVector.model.param.recall.MatchByVideoIdParam;
 import com.tzld.videoVector.model.po.videoVector.VideoAiUnderstanding;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent;
-import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent;
+import com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample;
+import com.tzld.videoVector.model.vo.VideoMatchResult;
 import com.tzld.videoVector.model.vo.recall.AIUnderstandingVO;
 import com.tzld.videoVector.model.vo.recall.DeconstructPointsVO;
 import com.tzld.videoVector.model.vo.recall.RecallResultVO;
@@ -54,7 +54,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
     private VideoApiService videoApiService;
 
     @Autowired
-    private DeconstructContentMapper deconstructContentMapper;
+    private MysqlDeconstructContentMapper mysqlDeconstructContentMapper;
 
     @Autowired(required = false)
     private VideoAiUnderstandingMapper videoAiUnderstandingMapper;
@@ -105,7 +105,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         matchParam.setConfigCode(param.getConfigCode());
         matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
 
-        List<Object> rawMatches = videoSearchService.matchTopNVideo(matchParam);
+        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam);
         if (CollectionUtils.isEmpty(rawMatches)) {
             return empty;
         }
@@ -127,7 +127,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         matchParam.setConfigCode(param.getConfigCode());
         matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
 
-        List<Object> rawMatches = videoSearchService.matchTopNVideo(matchParam);
+        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam);
         if (CollectionUtils.isEmpty(rawMatches)) {
             return empty;
         }
@@ -216,22 +216,17 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
      *  - 素材/长文用 deconstruct_content 数据
      *  - 默认按视频处理(用户确认 content_type 缺省语义)
      */
-    private RecallResultVO enrich(List<Object> rawMatches) {
-        // 解析原始 matches: [{configCode, videoId, score}, ...]
+    private RecallResultVO enrich(List<VideoMatchResult> rawMatches) {
+        // 转成内部 MatchItem(过滤 null/无效 id)
         List<MatchItem> matches = new ArrayList<>(rawMatches.size());
-        for (Object obj : rawMatches) {
-            JSONObject jo = toJSONObject(obj);
-            if (jo == null) {
-                continue;
-            }
-            Long id = jo.getLong("videoId");
-            if (id == null) {
+        for (VideoMatchResult r : rawMatches) {
+            if (r == null || r.getVideoId() == null) {
                 continue;
             }
             MatchItem mi = new MatchItem();
-            mi.id = id;
-            mi.configCode = jo.getString("configCode");
-            mi.score = jo.getDouble("score");
+            mi.id = r.getVideoId();
+            mi.configCode = r.getConfigCode();
+            mi.score = r.getScore();
             matches.add(mi);
         }
 
@@ -244,14 +239,14 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         List<String> idStrings = allIds.stream().map(String::valueOf).collect(Collectors.toList());
 
         // 查 deconstruct_content
-        Map<String, DeconstructContent> contentByCcid = queryDeconstructContent(idStrings);
+        Map<String, MysqlDeconstructContent> contentByCcid = queryDeconstructContent(idStrings);
 
         // 收集需要走 VideoApiService 的视频id
         Set<Long> videoIds = new HashSet<>();
         Map<Long, Modality> modalityMap = new HashMap<>();
 
         for (MatchItem m : matches) {
-            DeconstructContent c = contentByCcid.get(String.valueOf(m.id));
+            MysqlDeconstructContent c = contentByCcid.get(String.valueOf(m.id));
             Modality modality = (c == null) ? Modality.VIDEO : Modality.fromContentType(c.getContentType());
             modalityMap.put(m.id, modality);
             if (modality == Modality.VIDEO) {
@@ -283,7 +278,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
             vo.setReadCount(PLACEHOLDER);
             vo.setRov(PLACEHOLDER);
 
-            DeconstructContent content = contentByCcid.get(String.valueOf(m.id));
+            MysqlDeconstructContent content = contentByCcid.get(String.valueOf(m.id));
 
             switch (modality) {
                 case VIDEO:
@@ -336,22 +331,22 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
     /**
      * 按 channelContentId 批量查 deconstruct_content
      */
-    private Map<String, DeconstructContent> queryDeconstructContent(List<String> channelContentIds) {
+    private Map<String, MysqlDeconstructContent> queryDeconstructContent(List<String> channelContentIds) {
         if (CollectionUtils.isEmpty(channelContentIds)) {
             return Collections.emptyMap();
         }
         try {
-            DeconstructContentExample example = new DeconstructContentExample();
+            MysqlDeconstructContentExample example = new MysqlDeconstructContentExample();
             example.createCriteria().andChannelContentIdIn(channelContentIds);
-            List<DeconstructContent> list = deconstructContentMapper.selectByExample(example);
+            List<MysqlDeconstructContent> list = mysqlDeconstructContentMapper.selectByExample(example);
             // channel_content_id 可能重复(同一内容多次解构),保留最新一条
-            Map<String, DeconstructContent> map = new HashMap<>();
-            for (DeconstructContent c : list) {
+            Map<String, MysqlDeconstructContent> map = new HashMap<>();
+            for (MysqlDeconstructContent c : list) {
                 String ccid = c.getChannelContentId();
                 if (ccid == null) {
                     continue;
                 }
-                DeconstructContent prev = map.get(ccid);
+                MysqlDeconstructContent prev = map.get(ccid);
                 if (prev == null || (c.getId() != null && (prev.getId() == null || c.getId() > prev.getId()))) {
                     map.put(ccid, c);
                 }
@@ -363,21 +358,6 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         }
     }
 
-    private JSONObject toJSONObject(Object obj) {
-        if (obj == null) {
-            return null;
-        }
-        if (obj instanceof JSONObject) {
-            return (JSONObject) obj;
-        }
-        try {
-            return JSON.parseObject(JSON.toJSONString(obj));
-        } catch (Exception e) {
-            log.warn("toJSONObject parse fail: {}", e.getMessage());
-            return null;
-        }
-    }
-
     private List<String> parseImages(String imagesJson) {
         if (!StringUtils.hasText(imagesJson)) {
             return Collections.emptyList();

+ 246 - 0
core/src/main/java/com/tzld/videoVector/util/VectorUtils.java

@@ -0,0 +1,246 @@
+package com.tzld.videoVector.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 向量化公共工具方法
+ * 集中管理 parseVectorString、extractFromJson、parseJsonPath、isMultiPointConfig 等通用逻辑
+ */
+public final class VectorUtils {
+
+    private VectorUtils() {
+    }
+
+    // ========================== 向量字符串解析 ==========================
+
+    /**
+     * 解析 pgvector 字符串为 List<Float>
+     * 格式: "[0.1,0.2,...]"
+     */
+    public static List<Float> parseVectorString(String vectorStr) {
+        if (vectorStr == null || vectorStr.isEmpty()) return null;
+        try {
+            String trimmed = vectorStr.trim();
+            if (trimmed.startsWith("[")) {
+                trimmed = trimmed.substring(1);
+            }
+            if (trimmed.endsWith("]")) {
+                trimmed = trimmed.substring(0, trimmed.length() - 1);
+            }
+            String[] parts = trimmed.split(",");
+            List<Float> result = new ArrayList<>(parts.length);
+            for (String part : parts) {
+                result.add(Float.parseFloat(part.trim()));
+            }
+            return result;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    // ========================== 多点模式判断 ==========================
+
+    /**
+     * 判断配置是否为多点模式(有 extract_rule 配置)
+     */
+    public static boolean isMultiPointConfig(DeconstructVectorConfig config) {
+        return config != null && StringUtils.hasText(config.getExtractRule());
+    }
+
+    // ========================== JSON 数组项提取 ==========================
+
+    /**
+     * 从JSON中提取数组项对象列表(用于多点模式置信度过滤)
+     * 路径格式:$.a.b.c[*],以 [*] 结尾表示提取数组中的 JSONObject 列表
+     *
+     * @param json       原始JSONObject
+     * @param sourcePath JSONPath,如 $.final_normalization_rebuild.keypoint_final.最终关键点列表[*]
+     * @return 提取的JSONObject列表
+     */
+    public static List<JSONObject> extractArrayItemsFromJson(JSONObject json, String sourcePath) {
+        List<JSONObject> items = new ArrayList<>();
+
+        if (json == null || !StringUtils.hasText(sourcePath)) {
+            return items;
+        }
+
+        try {
+            if (!sourcePath.startsWith("$.")) {
+                return items;
+            }
+
+            String pathContent = sourcePath.substring(2);
+            // 路径应以 [*] 结尾
+            if (!pathContent.endsWith("[*]")) {
+                return items;
+            }
+
+            // 去掉末尾的 [*],找到数组所在的父路径和数组字段名
+            String pathWithoutWildcard = pathContent.substring(0, pathContent.length() - 3);
+            List<String> parts = parseJsonPath(pathWithoutWildcard);
+
+            // 逐层访问到数组字段的父对象
+            Object current = json;
+            for (int i = 0; i < parts.size() - 1; i++) {
+                if (current instanceof JSONObject) {
+                    current = ((JSONObject) current).get(parts.get(i));
+                } else {
+                    return items;
+                }
+            }
+
+            // 获取数组
+            if (current instanceof JSONObject && !parts.isEmpty()) {
+                String arrayKey = parts.get(parts.size() - 1);
+                JSONArray array = ((JSONObject) current).getJSONArray(arrayKey);
+                if (array != null) {
+                    for (int i = 0; i < array.size(); i++) {
+                        JSONObject item = array.getJSONObject(i);
+                        if (item != null) {
+                            items.add(item);
+                        }
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            // silently ignore, consistent with other utility methods
+        }
+
+        return items;
+    }
+
+    // ========================== JSON 路径提取 ==========================
+
+    /**
+     * 从JSON中提取文本
+     * 支持路径格式:$.final_normalization_rebuild.topic_fusion_result.最终选题.选题
+     * 支持中文 key 和数组路径如 topics[*]
+     */
+    public static List<String> extractFromJson(String json, String path) {
+        List<String> results = new ArrayList<>();
+
+        if (!StringUtils.hasText(json) || !StringUtils.hasText(path)) {
+            return results;
+        }
+
+        try {
+            JSONObject jsonObject = JSON.parseObject(json);
+            if (jsonObject == null) {
+                return results;
+            }
+            results.addAll(extractFromJson(jsonObject, path));
+        } catch (Exception e) {
+            // silently ignore
+        }
+
+        return results;
+    }
+
+    /**
+     * 从JSONObject中提取文本(重载)
+     */
+    public static List<String> extractFromJson(JSONObject json, String path) {
+        List<String> results = new ArrayList<>();
+
+        if (json == null || !StringUtils.hasText(path)) {
+            return results;
+        }
+
+        try {
+            if (path.startsWith("$.")) {
+                String pathContent = path.substring(2);
+                List<String> parts = parseJsonPath(pathContent);
+                Object current = json;
+
+                for (int i = 0; i < parts.size(); i++) {
+                    String part = parts.get(i);
+                    if (current == null) {
+                        break;
+                    }
+
+                    // 处理数组路径(如 topics[*])
+                    if (part.endsWith("[*]")) {
+                        String arrayKey = part.substring(0, part.length() - 3);
+                        if (current instanceof JSONObject) {
+                            JSONArray array = ((JSONObject) current).getJSONArray(arrayKey);
+                            if (array != null) {
+                                List<String> remainingParts = parts.subList(i + 1, parts.size());
+                                String remainingPath = String.join(".", remainingParts);
+                                for (int j = 0; j < array.size(); j++) {
+                                    Object item = array.get(j);
+                                    if (remainingParts.isEmpty()) {
+                                        if (item instanceof String) {
+                                            results.add((String) item);
+                                        }
+                                    } else {
+                                        results.addAll(extractFromJson(
+                                                JSON.toJSONString(item), "$." + remainingPath));
+                                    }
+                                }
+                            }
+                        }
+                        return results;
+                    } else {
+                        // 普通对象路径(支持中文 key)
+                        if (current instanceof JSONObject) {
+                            current = ((JSONObject) current).get(part);
+                        } else {
+                            break;
+                        }
+                    }
+                }
+
+                // 提取最终值
+                if (current instanceof String) {
+                    results.add((String) current);
+                } else if (current instanceof JSONArray) {
+                    JSONArray array = (JSONArray) current;
+                    for (int i = 0; i < array.size(); i++) {
+                        Object item = array.get(i);
+                        if (item instanceof String) {
+                            results.add((String) item);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // silently ignore
+        }
+
+        return results;
+    }
+
+    /**
+     * 解析 JSONPath 路径,支持中文 key
+     * 例如:final_normalization_rebuild.topic_fusion_result.最终选题.选题
+     */
+    public static List<String> parseJsonPath(String pathContent) {
+        List<String> parts = new ArrayList<>();
+        StringBuilder current = new StringBuilder();
+
+        for (int i = 0; i < pathContent.length(); i++) {
+            char c = pathContent.charAt(i);
+            if (c == '.') {
+                if (current.length() > 0) {
+                    parts.add(current.toString());
+                    current = new StringBuilder();
+                }
+            } else {
+                current.append(c);
+            }
+        }
+        if (current.length() > 0) {
+            parts.add(current.toString());
+        }
+
+        return parts;
+    }
+}

+ 82 - 0
core/src/main/resources/generator/mybatis-pgvector-generator-config.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE generatorConfiguration
+        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
+        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
+<!-- pgVector 数据源生成器配置 -->
+<generatorConfiguration>
+    <context id="pgVector" defaultModelType="flat" targetRuntime="MyBatis3">
+        <property name="autoDelimitKeywords" value="true"/>
+        <!-- 生成的Java文件的编码 -->
+        <property name="javaFileEncoding" value="UTF-8"/>
+        <!-- 格式化java代码 -->
+        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
+        <!-- 格式化XML代码 -->
+        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
+        <!-- PostgreSQL 使用双引号作为标识符分隔符 -->
+        <property name="beginningDelimiter" value="&quot;"/>
+        <property name="endingDelimiter" value="&quot;"/>
+
+        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
+        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
+
+        <commentGenerator>
+            <property name="addRemarkComments" value="true"/>
+            <property name="suppressDate" value="true"/>
+            <property name="suppressAllComments" value="false"/>
+        </commentGenerator>
+
+        <!-- PostgreSQL 17 连接配置 -->
+        <jdbcConnection driverClass="org.postgresql.Driver"
+                        connectionURL="jdbc:postgresql://pgm-bp1p0qr1gim87wnibo.pg.rds.aliyuncs.com/vector?currentSchema=public"
+                        userId="vector" password="vector123456@">
+            <!-- PostgreSQL 需要指定 schema -->
+            <property name="nullCatalogMeansCurrent" value="true"/>
+        </jdbcConnection>
+
+        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
+            <property name="forceBigDecimals" value="false"/>
+        </javaTypeResolver>
+
+        <!-- PO 实体类输出路径 -->
+        <javaModelGenerator targetPackage="com.tzld.videoVector.model.po.pgVector" targetProject="core/src/main/java">
+            <property name="constructorBased" value="false"/>
+            <property name="enableSubPackages" value="true"/>
+            <property name="immutable" value="false"/>
+        </javaModelGenerator>
+
+        <!-- Mapper XML 输出路径 -->
+        <sqlMapGenerator targetPackage="mapper.pgVector" targetProject="core/src/main/resources">
+            <property name="enableSubPackages" value="true"/>
+        </sqlMapGenerator>
+
+        <!-- Mapper 接口输出路径 -->
+        <javaClientGenerator targetPackage="com.tzld.videoVector.dao.mapper.pgVector" type="XMLMAPPER" targetProject="core/src/main/java">
+            <property name="enableSubPackages" value="true"/>
+        </javaClientGenerator>
+
+        <!-- 视频向量主表 -->
+        <table tableName="video_vectors" domainObjectName="VideoVector" alias="">
+            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>
+            <!-- pgvector 的 vector 类型强制映射为 String -->
+            <columnOverride column="embedding" javaType="java.lang.String" jdbcType="VARCHAR"/>
+        </table>
+
+        <!-- 素材向量表(embedding 列类型 MBG 不识别,生成后需手动补充) -->
+<!--        <table tableName="content_vectors" domainObjectName="ContentVector" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--            &lt;!&ndash; pgvector 的 vector 类型 MBG 无法自动映射,忽略后手动补充 &ndash;&gt;-->
+<!--            <columnOverride column="embedding" javaType="java.lang.String" jdbcType="VARCHAR"/>-->
+<!--        </table>-->
+
+        <!-- 内容解构主表 -->
+<!--        <table tableName="deconstruct_content" domainObjectName="DeconstructContent" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--        </table>-->
+
+        <!-- 向量化字段配置表 -->
+<!--        <table tableName="deconstruct_vector_config" domainObjectName="DeconstructVectorConfig" alias="">-->
+<!--            <generatedKey column="id" sqlStatement="JDBC" identity="true"/>-->
+<!--        </table>-->
+    </context>
+
+</generatorConfiguration>

+ 2 - 2
core/src/main/resources/generator/mybatis-vector-generator-config.xml

@@ -46,9 +46,9 @@
             <property name="enableSubPackages" value="true"/>
         </javaClientGenerator>
 
-<!--        <table tableName="deconstruct_content" domainObjectName="" alias=""/>-->
+<!--        <table tableName="deconstruct_content" domainObjectName="MysqlDeconstructContent" alias=""/>-->
         <table tableName="deconstruct_content_vector" domainObjectName="" alias=""/>
-<!--        <table tableName="deconstruct_vector_config" domainObjectName="" alias=""/>-->
+<!--        <table tableName="deconstruct_vector_config" domainObjectName="MysqlDeconstructVectorConfig" alias=""/>-->
     </context>
 
 </generatorConfiguration>

+ 408 - 0
core/src/main/resources/mapper/pgVector/ContentVectorMapper.xml

@@ -0,0 +1,408 @@
+<?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.ContentVectorMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.ContentVector">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="content_id" jdbcType="BIGINT" property="contentId" />
+    <result column="task_id" jdbcType="VARCHAR" property="taskId" />
+    <result column="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="source_field" jdbcType="VARCHAR" property="sourceField" />
+    <result column="source_path" jdbcType="VARCHAR" property="sourcePath" />
+    <result column="text_hash" jdbcType="VARCHAR" property="textHash" />
+    <result column="embedding_model" jdbcType="VARCHAR" property="embeddingModel" />
+    <result column="segment_index" jdbcType="INTEGER" property="segmentIndex" />
+    <result column="segment_total" jdbcType="INTEGER" property="segmentTotal" />
+    <result column="source_text" jdbcType="VARCHAR" property="sourceText" />
+    <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
+    <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
+  </resultMap>
+  <sql id="Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    id, content_id, task_id, config_code, source_field, source_path, text_hash, embedding_model, 
+    segment_index, segment_total, source_text, created_at, updated_at
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.ContentVectorExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from content_vectors
+    <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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    from content_vectors
+    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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    delete from content_vectors
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.ContentVectorExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    delete from content_vectors
+    <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.ContentVector" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    insert into content_vectors (content_id, task_id, config_code, 
+      source_field, source_path, text_hash, 
+      embedding_model, segment_index, segment_total, 
+      source_text, created_at, updated_at
+      )
+    values (#{contentId,jdbcType=BIGINT}, #{taskId,jdbcType=VARCHAR}, #{configCode,jdbcType=VARCHAR}, 
+      #{sourceField,jdbcType=VARCHAR}, #{sourcePath,jdbcType=VARCHAR}, #{textHash,jdbcType=VARCHAR}, 
+      #{embeddingModel,jdbcType=VARCHAR}, #{segmentIndex,jdbcType=INTEGER}, #{segmentTotal,jdbcType=INTEGER}, 
+      #{sourceText,jdbcType=VARCHAR}, #{createdAt,jdbcType=TIMESTAMP}, #{updatedAt,jdbcType=TIMESTAMP}
+      )
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.ContentVector" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    insert into content_vectors
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="contentId != null">
+        content_id,
+      </if>
+      <if test="taskId != null">
+        task_id,
+      </if>
+      <if test="configCode != null">
+        config_code,
+      </if>
+      <if test="sourceField != null">
+        source_field,
+      </if>
+      <if test="sourcePath != null">
+        source_path,
+      </if>
+      <if test="textHash != null">
+        text_hash,
+      </if>
+      <if test="embeddingModel != null">
+        embedding_model,
+      </if>
+      <if test="segmentIndex != null">
+        segment_index,
+      </if>
+      <if test="segmentTotal != null">
+        segment_total,
+      </if>
+      <if test="sourceText != null">
+        source_text,
+      </if>
+      <if test="createdAt != null">
+        created_at,
+      </if>
+      <if test="updatedAt != null">
+        updated_at,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="contentId != null">
+        #{contentId,jdbcType=BIGINT},
+      </if>
+      <if test="taskId != null">
+        #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="configCode != null">
+        #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="sourceField != null">
+        #{sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="sourcePath != null">
+        #{sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="textHash != null">
+        #{textHash,jdbcType=VARCHAR},
+      </if>
+      <if test="embeddingModel != null">
+        #{embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="segmentIndex != null">
+        #{segmentIndex,jdbcType=INTEGER},
+      </if>
+      <if test="segmentTotal != null">
+        #{segmentTotal,jdbcType=INTEGER},
+      </if>
+      <if test="sourceText != null">
+        #{sourceText,jdbcType=VARCHAR},
+      </if>
+      <if test="createdAt != null">
+        #{createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updatedAt != null">
+        #{updatedAt,jdbcType=TIMESTAMP},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.pgVector.ContentVectorExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    select count(*) from content_vectors
+    <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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    update content_vectors
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.contentId != null">
+        content_id = #{record.contentId,jdbcType=BIGINT},
+      </if>
+      <if test="record.taskId != null">
+        task_id = #{record.taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.configCode != null">
+        config_code = #{record.configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="record.sourceField != null">
+        source_field = #{record.sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="record.sourcePath != null">
+        source_path = #{record.sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="record.textHash != null">
+        text_hash = #{record.textHash,jdbcType=VARCHAR},
+      </if>
+      <if test="record.embeddingModel != null">
+        embedding_model = #{record.embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="record.segmentIndex != null">
+        segment_index = #{record.segmentIndex,jdbcType=INTEGER},
+      </if>
+      <if test="record.segmentTotal != null">
+        segment_total = #{record.segmentTotal,jdbcType=INTEGER},
+      </if>
+      <if test="record.sourceText != null">
+        source_text = #{record.sourceText,jdbcType=VARCHAR},
+      </if>
+      <if test="record.createdAt != null">
+        created_at = #{record.createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.updatedAt != null">
+        updated_at = #{record.updatedAt,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.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    update content_vectors
+    set id = #{record.id,jdbcType=BIGINT},
+      content_id = #{record.contentId,jdbcType=BIGINT},
+      task_id = #{record.taskId,jdbcType=VARCHAR},
+      config_code = #{record.configCode,jdbcType=VARCHAR},
+      source_field = #{record.sourceField,jdbcType=VARCHAR},
+      source_path = #{record.sourcePath,jdbcType=VARCHAR},
+      text_hash = #{record.textHash,jdbcType=VARCHAR},
+      embedding_model = #{record.embeddingModel,jdbcType=VARCHAR},
+      segment_index = #{record.segmentIndex,jdbcType=INTEGER},
+      segment_total = #{record.segmentTotal,jdbcType=INTEGER},
+      source_text = #{record.sourceText,jdbcType=VARCHAR},
+      created_at = #{record.createdAt,jdbcType=TIMESTAMP},
+      updated_at = #{record.updatedAt,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.ContentVector">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    update content_vectors
+    <set>
+      <if test="contentId != null">
+        content_id = #{contentId,jdbcType=BIGINT},
+      </if>
+      <if test="taskId != null">
+        task_id = #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="configCode != null">
+        config_code = #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="sourceField != null">
+        source_field = #{sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="sourcePath != null">
+        source_path = #{sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="textHash != null">
+        text_hash = #{textHash,jdbcType=VARCHAR},
+      </if>
+      <if test="embeddingModel != null">
+        embedding_model = #{embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="segmentIndex != null">
+        segment_index = #{segmentIndex,jdbcType=INTEGER},
+      </if>
+      <if test="segmentTotal != null">
+        segment_total = #{segmentTotal,jdbcType=INTEGER},
+      </if>
+      <if test="sourceText != null">
+        source_text = #{sourceText,jdbcType=VARCHAR},
+      </if>
+      <if test="createdAt != null">
+        created_at = #{createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updatedAt != null">
+        updated_at = #{updatedAt,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.pgVector.ContentVector">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:27:37 CST 2026.
+    -->
+    update content_vectors
+    set content_id = #{contentId,jdbcType=BIGINT},
+      task_id = #{taskId,jdbcType=VARCHAR},
+      config_code = #{configCode,jdbcType=VARCHAR},
+      source_field = #{sourceField,jdbcType=VARCHAR},
+      source_path = #{sourcePath,jdbcType=VARCHAR},
+      text_hash = #{textHash,jdbcType=VARCHAR},
+      embedding_model = #{embeddingModel,jdbcType=VARCHAR},
+      segment_index = #{segmentIndex,jdbcType=INTEGER},
+      segment_total = #{segmentTotal,jdbcType=INTEGER},
+      source_text = #{sourceText,jdbcType=VARCHAR},
+      created_at = #{createdAt,jdbcType=TIMESTAMP},
+      updated_at = #{updatedAt,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+
+</mapper>

+ 502 - 0
core/src/main/resources/mapper/pgVector/DeconstructContentMapper.xml

@@ -0,0 +1,502 @@
+<?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.DeconstructContentMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.DeconstructContent">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="task_id" jdbcType="VARCHAR" property="taskId" />
+    <result column="biz_type" jdbcType="SMALLINT" property="bizType" />
+    <result column="content_type" jdbcType="SMALLINT" property="contentType" />
+    <result column="channel_content_id" jdbcType="VARCHAR" property="channelContentId" />
+    <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="body_text" jdbcType="VARCHAR" property="bodyText" />
+    <result column="video_url" jdbcType="VARCHAR" property="videoUrl" />
+    <result column="images" jdbcType="VARCHAR" property="images" />
+    <result column="channel_account_id" jdbcType="VARCHAR" property="channelAccountId" />
+    <result column="channel_account_name" jdbcType="VARCHAR" property="channelAccountName" />
+    <result column="status" jdbcType="SMALLINT" property="status" />
+    <result column="result_json" jdbcType="VARCHAR" property="resultJson" />
+    <result column="failure_reason" jdbcType="VARCHAR" property="failureReason" />
+    <result column="point_url" jdbcType="VARCHAR" property="pointUrl" />
+    <result column="weight_url" jdbcType="VARCHAR" property="weightUrl" />
+    <result column="pattern_url" jdbcType="VARCHAR" property="patternUrl" />
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    id, task_id, biz_type, content_type, channel_content_id, title, body_text, video_url, 
+    images, channel_account_id, channel_account_name, "status", result_json, failure_reason, 
+    point_url, weight_url, pattern_url, create_time, update_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructContentExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from deconstruct_content
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    from deconstruct_content
+    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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    delete from deconstruct_content
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructContentExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    delete from deconstruct_content
+    <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.DeconstructContent" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    insert into deconstruct_content (task_id, biz_type, content_type, 
+      channel_content_id, title, body_text, 
+      video_url, images, channel_account_id, 
+      channel_account_name, "status", result_json, 
+      failure_reason, point_url, weight_url, 
+      pattern_url, create_time, update_time
+      )
+    values (#{taskId,jdbcType=VARCHAR}, #{bizType,jdbcType=SMALLINT}, #{contentType,jdbcType=SMALLINT}, 
+      #{channelContentId,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{bodyText,jdbcType=VARCHAR}, 
+      #{videoUrl,jdbcType=VARCHAR}, #{images,jdbcType=VARCHAR}, #{channelAccountId,jdbcType=VARCHAR}, 
+      #{channelAccountName,jdbcType=VARCHAR}, #{status,jdbcType=SMALLINT}, #{resultJson,jdbcType=VARCHAR}, 
+      #{failureReason,jdbcType=VARCHAR}, #{pointUrl,jdbcType=VARCHAR}, #{weightUrl,jdbcType=VARCHAR}, 
+      #{patternUrl,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}
+      )
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructContent" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    insert into deconstruct_content
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="taskId != null">
+        task_id,
+      </if>
+      <if test="bizType != null">
+        biz_type,
+      </if>
+      <if test="contentType != null">
+        content_type,
+      </if>
+      <if test="channelContentId != null">
+        channel_content_id,
+      </if>
+      <if test="title != null">
+        title,
+      </if>
+      <if test="bodyText != null">
+        body_text,
+      </if>
+      <if test="videoUrl != null">
+        video_url,
+      </if>
+      <if test="images != null">
+        images,
+      </if>
+      <if test="channelAccountId != null">
+        channel_account_id,
+      </if>
+      <if test="channelAccountName != null">
+        channel_account_name,
+      </if>
+      <if test="status != null">
+        "status",
+      </if>
+      <if test="resultJson != null">
+        result_json,
+      </if>
+      <if test="failureReason != null">
+        failure_reason,
+      </if>
+      <if test="pointUrl != null">
+        point_url,
+      </if>
+      <if test="weightUrl != null">
+        weight_url,
+      </if>
+      <if test="patternUrl != null">
+        pattern_url,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="taskId != null">
+        #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="bizType != null">
+        #{bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="contentType != null">
+        #{contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="channelContentId != null">
+        #{channelContentId,jdbcType=VARCHAR},
+      </if>
+      <if test="title != null">
+        #{title,jdbcType=VARCHAR},
+      </if>
+      <if test="bodyText != null">
+        #{bodyText,jdbcType=VARCHAR},
+      </if>
+      <if test="videoUrl != null">
+        #{videoUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="images != null">
+        #{images,jdbcType=VARCHAR},
+      </if>
+      <if test="channelAccountId != null">
+        #{channelAccountId,jdbcType=VARCHAR},
+      </if>
+      <if test="channelAccountName != null">
+        #{channelAccountName,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        #{status,jdbcType=SMALLINT},
+      </if>
+      <if test="resultJson != null">
+        #{resultJson,jdbcType=VARCHAR},
+      </if>
+      <if test="failureReason != null">
+        #{failureReason,jdbcType=VARCHAR},
+      </if>
+      <if test="pointUrl != null">
+        #{pointUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="weightUrl != null">
+        #{weightUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="patternUrl != null">
+        #{patternUrl,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.DeconstructContentExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select count(*) from deconstruct_content
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_content
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.taskId != null">
+        task_id = #{record.taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.bizType != null">
+        biz_type = #{record.bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="record.contentType != null">
+        content_type = #{record.contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="record.channelContentId != null">
+        channel_content_id = #{record.channelContentId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.title != null">
+        title = #{record.title,jdbcType=VARCHAR},
+      </if>
+      <if test="record.bodyText != null">
+        body_text = #{record.bodyText,jdbcType=VARCHAR},
+      </if>
+      <if test="record.videoUrl != null">
+        video_url = #{record.videoUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="record.images != null">
+        images = #{record.images,jdbcType=VARCHAR},
+      </if>
+      <if test="record.channelAccountId != null">
+        channel_account_id = #{record.channelAccountId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.channelAccountName != null">
+        channel_account_name = #{record.channelAccountName,jdbcType=VARCHAR},
+      </if>
+      <if test="record.status != null">
+        "status" = #{record.status,jdbcType=SMALLINT},
+      </if>
+      <if test="record.resultJson != null">
+        result_json = #{record.resultJson,jdbcType=VARCHAR},
+      </if>
+      <if test="record.failureReason != null">
+        failure_reason = #{record.failureReason,jdbcType=VARCHAR},
+      </if>
+      <if test="record.pointUrl != null">
+        point_url = #{record.pointUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="record.weightUrl != null">
+        weight_url = #{record.weightUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="record.patternUrl != null">
+        pattern_url = #{record.patternUrl,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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_content
+    set id = #{record.id,jdbcType=BIGINT},
+      task_id = #{record.taskId,jdbcType=VARCHAR},
+      biz_type = #{record.bizType,jdbcType=SMALLINT},
+      content_type = #{record.contentType,jdbcType=SMALLINT},
+      channel_content_id = #{record.channelContentId,jdbcType=VARCHAR},
+      title = #{record.title,jdbcType=VARCHAR},
+      body_text = #{record.bodyText,jdbcType=VARCHAR},
+      video_url = #{record.videoUrl,jdbcType=VARCHAR},
+      images = #{record.images,jdbcType=VARCHAR},
+      channel_account_id = #{record.channelAccountId,jdbcType=VARCHAR},
+      channel_account_name = #{record.channelAccountName,jdbcType=VARCHAR},
+      "status" = #{record.status,jdbcType=SMALLINT},
+      result_json = #{record.resultJson,jdbcType=VARCHAR},
+      failure_reason = #{record.failureReason,jdbcType=VARCHAR},
+      point_url = #{record.pointUrl,jdbcType=VARCHAR},
+      weight_url = #{record.weightUrl,jdbcType=VARCHAR},
+      pattern_url = #{record.patternUrl,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.DeconstructContent">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_content
+    <set>
+      <if test="taskId != null">
+        task_id = #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="bizType != null">
+        biz_type = #{bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="contentType != null">
+        content_type = #{contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="channelContentId != null">
+        channel_content_id = #{channelContentId,jdbcType=VARCHAR},
+      </if>
+      <if test="title != null">
+        title = #{title,jdbcType=VARCHAR},
+      </if>
+      <if test="bodyText != null">
+        body_text = #{bodyText,jdbcType=VARCHAR},
+      </if>
+      <if test="videoUrl != null">
+        video_url = #{videoUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="images != null">
+        images = #{images,jdbcType=VARCHAR},
+      </if>
+      <if test="channelAccountId != null">
+        channel_account_id = #{channelAccountId,jdbcType=VARCHAR},
+      </if>
+      <if test="channelAccountName != null">
+        channel_account_name = #{channelAccountName,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        "status" = #{status,jdbcType=SMALLINT},
+      </if>
+      <if test="resultJson != null">
+        result_json = #{resultJson,jdbcType=VARCHAR},
+      </if>
+      <if test="failureReason != null">
+        failure_reason = #{failureReason,jdbcType=VARCHAR},
+      </if>
+      <if test="pointUrl != null">
+        point_url = #{pointUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="weightUrl != null">
+        weight_url = #{weightUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="patternUrl != null">
+        pattern_url = #{patternUrl,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.DeconstructContent">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_content
+    set task_id = #{taskId,jdbcType=VARCHAR},
+      biz_type = #{bizType,jdbcType=SMALLINT},
+      content_type = #{contentType,jdbcType=SMALLINT},
+      channel_content_id = #{channelContentId,jdbcType=VARCHAR},
+      title = #{title,jdbcType=VARCHAR},
+      body_text = #{bodyText,jdbcType=VARCHAR},
+      video_url = #{videoUrl,jdbcType=VARCHAR},
+      images = #{images,jdbcType=VARCHAR},
+      channel_account_id = #{channelAccountId,jdbcType=VARCHAR},
+      channel_account_name = #{channelAccountName,jdbcType=VARCHAR},
+      "status" = #{status,jdbcType=SMALLINT},
+      result_json = #{resultJson,jdbcType=VARCHAR},
+      failure_reason = #{failureReason,jdbcType=VARCHAR},
+      point_url = #{pointUrl,jdbcType=VARCHAR},
+      weight_url = #{weightUrl,jdbcType=VARCHAR},
+      pattern_url = #{patternUrl,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 471 - 0
core/src/main/resources/mapper/pgVector/DeconstructVectorConfigMapper.xml

@@ -0,0 +1,471 @@
+<?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.DeconstructVectorConfigMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="config_name" jdbcType="VARCHAR" property="configName" />
+    <result column="biz_type" jdbcType="SMALLINT" property="bizType" />
+    <result column="content_type" jdbcType="SMALLINT" property="contentType" />
+    <result column="source_field" jdbcType="VARCHAR" property="sourceField" />
+    <result column="source_path" jdbcType="VARCHAR" property="sourcePath" />
+    <result column="extract_rule" jdbcType="VARCHAR" property="extractRule" />
+    <result column="embedding_model" jdbcType="VARCHAR" property="embeddingModel" />
+    <result column="dimension" jdbcType="INTEGER" property="dimension" />
+    <result column="max_length" jdbcType="INTEGER" property="maxLength" />
+    <result column="enable_segment" jdbcType="SMALLINT" property="enableSegment" />
+    <result column="segment_size" jdbcType="INTEGER" property="segmentSize" />
+    <result column="priority" jdbcType="INTEGER" property="priority" />
+    <result column="enabled" jdbcType="SMALLINT" property="enabled" />
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    id, config_code, config_name, biz_type, content_type, source_field, source_path, 
+    extract_rule, embedding_model, dimension, max_length, enable_segment, segment_size, 
+    priority, enabled, create_time, update_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from deconstruct_vector_config
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    from deconstruct_vector_config
+    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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    delete from deconstruct_vector_config
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    delete from deconstruct_vector_config
+    <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.DeconstructVectorConfig" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    insert into deconstruct_vector_config (config_code, config_name, biz_type, 
+      content_type, source_field, source_path, 
+      extract_rule, embedding_model, dimension, 
+      max_length, enable_segment, segment_size, 
+      priority, enabled, create_time, 
+      update_time)
+    values (#{configCode,jdbcType=VARCHAR}, #{configName,jdbcType=VARCHAR}, #{bizType,jdbcType=SMALLINT}, 
+      #{contentType,jdbcType=SMALLINT}, #{sourceField,jdbcType=VARCHAR}, #{sourcePath,jdbcType=VARCHAR}, 
+      #{extractRule,jdbcType=VARCHAR}, #{embeddingModel,jdbcType=VARCHAR}, #{dimension,jdbcType=INTEGER}, 
+      #{maxLength,jdbcType=INTEGER}, #{enableSegment,jdbcType=SMALLINT}, #{segmentSize,jdbcType=INTEGER}, 
+      #{priority,jdbcType=INTEGER}, #{enabled,jdbcType=SMALLINT}, #{createTime,jdbcType=TIMESTAMP}, 
+      #{updateTime,jdbcType=TIMESTAMP})
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    insert into deconstruct_vector_config
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="configCode != null">
+        config_code,
+      </if>
+      <if test="configName != null">
+        config_name,
+      </if>
+      <if test="bizType != null">
+        biz_type,
+      </if>
+      <if test="contentType != null">
+        content_type,
+      </if>
+      <if test="sourceField != null">
+        source_field,
+      </if>
+      <if test="sourcePath != null">
+        source_path,
+      </if>
+      <if test="extractRule != null">
+        extract_rule,
+      </if>
+      <if test="embeddingModel != null">
+        embedding_model,
+      </if>
+      <if test="dimension != null">
+        dimension,
+      </if>
+      <if test="maxLength != null">
+        max_length,
+      </if>
+      <if test="enableSegment != null">
+        enable_segment,
+      </if>
+      <if test="segmentSize != null">
+        segment_size,
+      </if>
+      <if test="priority != null">
+        priority,
+      </if>
+      <if test="enabled != null">
+        enabled,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="configCode != null">
+        #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="configName != null">
+        #{configName,jdbcType=VARCHAR},
+      </if>
+      <if test="bizType != null">
+        #{bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="contentType != null">
+        #{contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="sourceField != null">
+        #{sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="sourcePath != null">
+        #{sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="extractRule != null">
+        #{extractRule,jdbcType=VARCHAR},
+      </if>
+      <if test="embeddingModel != null">
+        #{embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="dimension != null">
+        #{dimension,jdbcType=INTEGER},
+      </if>
+      <if test="maxLength != null">
+        #{maxLength,jdbcType=INTEGER},
+      </if>
+      <if test="enableSegment != null">
+        #{enableSegment,jdbcType=SMALLINT},
+      </if>
+      <if test="segmentSize != null">
+        #{segmentSize,jdbcType=INTEGER},
+      </if>
+      <if test="priority != null">
+        #{priority,jdbcType=INTEGER},
+      </if>
+      <if test="enabled != null">
+        #{enabled,jdbcType=SMALLINT},
+      </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.DeconstructVectorConfigExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    select count(*) from deconstruct_vector_config
+    <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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_vector_config
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.configCode != null">
+        config_code = #{record.configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="record.configName != null">
+        config_name = #{record.configName,jdbcType=VARCHAR},
+      </if>
+      <if test="record.bizType != null">
+        biz_type = #{record.bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="record.contentType != null">
+        content_type = #{record.contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="record.sourceField != null">
+        source_field = #{record.sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="record.sourcePath != null">
+        source_path = #{record.sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="record.extractRule != null">
+        extract_rule = #{record.extractRule,jdbcType=VARCHAR},
+      </if>
+      <if test="record.embeddingModel != null">
+        embedding_model = #{record.embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="record.dimension != null">
+        dimension = #{record.dimension,jdbcType=INTEGER},
+      </if>
+      <if test="record.maxLength != null">
+        max_length = #{record.maxLength,jdbcType=INTEGER},
+      </if>
+      <if test="record.enableSegment != null">
+        enable_segment = #{record.enableSegment,jdbcType=SMALLINT},
+      </if>
+      <if test="record.segmentSize != null">
+        segment_size = #{record.segmentSize,jdbcType=INTEGER},
+      </if>
+      <if test="record.priority != null">
+        priority = #{record.priority,jdbcType=INTEGER},
+      </if>
+      <if test="record.enabled != null">
+        enabled = #{record.enabled,jdbcType=SMALLINT},
+      </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.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_vector_config
+    set id = #{record.id,jdbcType=BIGINT},
+      config_code = #{record.configCode,jdbcType=VARCHAR},
+      config_name = #{record.configName,jdbcType=VARCHAR},
+      biz_type = #{record.bizType,jdbcType=SMALLINT},
+      content_type = #{record.contentType,jdbcType=SMALLINT},
+      source_field = #{record.sourceField,jdbcType=VARCHAR},
+      source_path = #{record.sourcePath,jdbcType=VARCHAR},
+      extract_rule = #{record.extractRule,jdbcType=VARCHAR},
+      embedding_model = #{record.embeddingModel,jdbcType=VARCHAR},
+      dimension = #{record.dimension,jdbcType=INTEGER},
+      max_length = #{record.maxLength,jdbcType=INTEGER},
+      enable_segment = #{record.enableSegment,jdbcType=SMALLINT},
+      segment_size = #{record.segmentSize,jdbcType=INTEGER},
+      priority = #{record.priority,jdbcType=INTEGER},
+      enabled = #{record.enabled,jdbcType=SMALLINT},
+      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.DeconstructVectorConfig">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_vector_config
+    <set>
+      <if test="configCode != null">
+        config_code = #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="configName != null">
+        config_name = #{configName,jdbcType=VARCHAR},
+      </if>
+      <if test="bizType != null">
+        biz_type = #{bizType,jdbcType=SMALLINT},
+      </if>
+      <if test="contentType != null">
+        content_type = #{contentType,jdbcType=SMALLINT},
+      </if>
+      <if test="sourceField != null">
+        source_field = #{sourceField,jdbcType=VARCHAR},
+      </if>
+      <if test="sourcePath != null">
+        source_path = #{sourcePath,jdbcType=VARCHAR},
+      </if>
+      <if test="extractRule != null">
+        extract_rule = #{extractRule,jdbcType=VARCHAR},
+      </if>
+      <if test="embeddingModel != null">
+        embedding_model = #{embeddingModel,jdbcType=VARCHAR},
+      </if>
+      <if test="dimension != null">
+        dimension = #{dimension,jdbcType=INTEGER},
+      </if>
+      <if test="maxLength != null">
+        max_length = #{maxLength,jdbcType=INTEGER},
+      </if>
+      <if test="enableSegment != null">
+        enable_segment = #{enableSegment,jdbcType=SMALLINT},
+      </if>
+      <if test="segmentSize != null">
+        segment_size = #{segmentSize,jdbcType=INTEGER},
+      </if>
+      <if test="priority != null">
+        priority = #{priority,jdbcType=INTEGER},
+      </if>
+      <if test="enabled != null">
+        enabled = #{enabled,jdbcType=SMALLINT},
+      </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.DeconstructVectorConfig">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Wed Apr 29 15:35:34 CST 2026.
+    -->
+    update deconstruct_vector_config
+    set config_code = #{configCode,jdbcType=VARCHAR},
+      config_name = #{configName,jdbcType=VARCHAR},
+      biz_type = #{bizType,jdbcType=SMALLINT},
+      content_type = #{contentType,jdbcType=SMALLINT},
+      source_field = #{sourceField,jdbcType=VARCHAR},
+      source_path = #{sourcePath,jdbcType=VARCHAR},
+      extract_rule = #{extractRule,jdbcType=VARCHAR},
+      embedding_model = #{embeddingModel,jdbcType=VARCHAR},
+      dimension = #{dimension,jdbcType=INTEGER},
+      max_length = #{maxLength,jdbcType=INTEGER},
+      enable_segment = #{enableSegment,jdbcType=SMALLINT},
+      segment_size = #{segmentSize,jdbcType=INTEGER},
+      priority = #{priority,jdbcType=INTEGER},
+      enabled = #{enabled,jdbcType=SMALLINT},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+
+</mapper>

+ 312 - 0
core/src/main/resources/mapper/pgVector/VideoVectorMapper.xml

@@ -0,0 +1,312 @@
+<?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.VideoVectorMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.pgVector.VideoVector">
+    <!--
+      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="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="embedding" jdbcType="VARCHAR" property="embedding" />
+    <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
+    <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
+    <result column="point_index" jdbcType="INTEGER" property="pointIndex" />
+    <result column="text" jdbcType="VARCHAR" property="text" />
+  </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, config_code, embedding, created_at, updated_at, point_index, "text"
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVectorExample" 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_vectors
+    <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_vectors
+    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_vectors
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVectorExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    delete from video_vectors
+    <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.VideoVector" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_vectors (video_id, config_code, embedding, 
+      created_at, updated_at, point_index, 
+      "text")
+    values (#{videoId,jdbcType=BIGINT}, #{configCode,jdbcType=VARCHAR}, #{embedding,jdbcType=VARCHAR}, 
+      #{createdAt,jdbcType=TIMESTAMP}, #{updatedAt,jdbcType=TIMESTAMP}, #{pointIndex,jdbcType=INTEGER}, 
+      #{text,jdbcType=VARCHAR})
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVector" useGeneratedKeys="true">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    insert into video_vectors
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="videoId != null">
+        video_id,
+      </if>
+      <if test="configCode != null">
+        config_code,
+      </if>
+      <if test="embedding != null">
+        embedding,
+      </if>
+      <if test="createdAt != null">
+        created_at,
+      </if>
+      <if test="updatedAt != null">
+        updated_at,
+      </if>
+      <if test="pointIndex != null">
+        point_index,
+      </if>
+      <if test="text != null">
+        "text",
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="videoId != null">
+        #{videoId,jdbcType=BIGINT},
+      </if>
+      <if test="configCode != null">
+        #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="embedding != null">
+        #{embedding,jdbcType=VARCHAR},
+      </if>
+      <if test="createdAt != null">
+        #{createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updatedAt != null">
+        #{updatedAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="pointIndex != null">
+        #{pointIndex,jdbcType=INTEGER},
+      </if>
+      <if test="text != null">
+        #{text,jdbcType=VARCHAR},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVectorExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    select count(*) from video_vectors
+    <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_vectors
+    <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.configCode != null">
+        config_code = #{record.configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="record.embedding != null">
+        embedding = #{record.embedding,jdbcType=VARCHAR},
+      </if>
+      <if test="record.createdAt != null">
+        created_at = #{record.createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.updatedAt != null">
+        updated_at = #{record.updatedAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.pointIndex != null">
+        point_index = #{record.pointIndex,jdbcType=INTEGER},
+      </if>
+      <if test="record.text != null">
+        "text" = #{record.text,jdbcType=VARCHAR},
+      </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_vectors
+    set id = #{record.id,jdbcType=BIGINT},
+      video_id = #{record.videoId,jdbcType=BIGINT},
+      config_code = #{record.configCode,jdbcType=VARCHAR},
+      embedding = #{record.embedding,jdbcType=VARCHAR},
+      created_at = #{record.createdAt,jdbcType=TIMESTAMP},
+      updated_at = #{record.updatedAt,jdbcType=TIMESTAMP},
+      point_index = #{record.pointIndex,jdbcType=INTEGER},
+      "text" = #{record.text,jdbcType=VARCHAR}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVector">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_vectors
+    <set>
+      <if test="videoId != null">
+        video_id = #{videoId,jdbcType=BIGINT},
+      </if>
+      <if test="configCode != null">
+        config_code = #{configCode,jdbcType=VARCHAR},
+      </if>
+      <if test="embedding != null">
+        embedding = #{embedding,jdbcType=VARCHAR},
+      </if>
+      <if test="createdAt != null">
+        created_at = #{createdAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updatedAt != null">
+        updated_at = #{updatedAt,jdbcType=TIMESTAMP},
+      </if>
+      <if test="pointIndex != null">
+        point_index = #{pointIndex,jdbcType=INTEGER},
+      </if>
+      <if test="text != null">
+        "text" = #{text,jdbcType=VARCHAR},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.pgVector.VideoVector">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+    -->
+    update video_vectors
+    set video_id = #{videoId,jdbcType=BIGINT},
+      config_code = #{configCode,jdbcType=VARCHAR},
+      embedding = #{embedding,jdbcType=VARCHAR},
+      created_at = #{createdAt,jdbcType=TIMESTAMP},
+      updated_at = #{updatedAt,jdbcType=TIMESTAMP},
+      point_index = #{pointIndex,jdbcType=INTEGER},
+      "text" = #{text,jdbcType=VARCHAR}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 109 - 0
core/src/main/resources/mapper/pgVector/ext/ContentVectorMapperExt.xml

@@ -0,0 +1,109 @@
+<?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.ContentVectorMapperExt">
+
+  <!-- ==================== 自定义 ResultMap ==================== -->
+
+  <resultMap id="VectorWithEmbeddingResultMap" type="com.tzld.videoVector.model.po.pgVector.ContentVector">
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="content_id" jdbcType="BIGINT" property="contentId" />
+    <result column="task_id" jdbcType="VARCHAR" property="taskId" />
+    <result column="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="source_field" jdbcType="VARCHAR" property="sourceField" />
+    <result column="source_path" jdbcType="VARCHAR" property="sourcePath" />
+    <result column="text_hash" jdbcType="VARCHAR" property="textHash" />
+    <result column="embedding_model" jdbcType="VARCHAR" property="embeddingModel" />
+    <result column="segment_index" jdbcType="INTEGER" property="segmentIndex" />
+    <result column="segment_total" jdbcType="INTEGER" property="segmentTotal" />
+    <result column="source_text" jdbcType="VARCHAR" property="sourceText" />
+    <result column="embedding" jdbcType="VARCHAR" property="embedding" />
+    <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
+    <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
+  </resultMap>
+
+  <resultMap id="SearchResultMap" type="com.tzld.videoVector.model.po.pgVector.ContentVector">
+    <result column="id" jdbcType="BIGINT" property="id" />
+    <result column="content_id" jdbcType="BIGINT" property="contentId" />
+    <result column="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="source_text" jdbcType="VARCHAR" property="sourceText" />
+    <result column="score" jdbcType="DOUBLE" property="score" />
+  </resultMap>
+
+  <!-- ==================== 自定义向量操作 SQL ==================== -->
+
+  <!-- Upsert: 插入或更新向量 -->
+  <insert id="upsertWithEmbedding">
+    INSERT INTO content_vectors (content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text, embedding, created_at, updated_at)
+    VALUES (#{contentId}, #{taskId}, #{configCode}, #{sourceField}, #{sourcePath},
+      #{textHash}, #{embeddingModel}, #{segmentIndex}, #{segmentTotal}, #{sourceText},
+      #{embedding}::vector, NOW(), NOW())
+    ON CONFLICT (content_id, config_code, text_hash, segment_index)
+    DO UPDATE SET embedding = EXCLUDED.embedding, source_text = EXCLUDED.source_text, updated_at = NOW()
+  </insert>
+
+  <!-- 根据 contentId + configCode 查询 -->
+  <select id="selectByContentIdAndConfigCode" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text,
+      embedding::text as embedding, created_at, updated_at
+    FROM content_vectors
+    WHERE content_id = #{contentId}
+    <if test="configCode != null and configCode != ''">
+      AND config_code = #{configCode}
+    </if>
+    ORDER BY source_field ASC, segment_index ASC
+  </select>
+
+  <!-- 根据 contentId 查询 -->
+  <select id="selectByContentId" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text,
+      embedding::text as embedding, created_at, updated_at
+    FROM content_vectors
+    WHERE content_id = #{contentId}
+    ORDER BY source_field ASC, segment_index ASC
+  </select>
+
+  <!-- 根据 contentId + sourceField 查询 -->
+  <select id="selectByContentIdAndField" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text,
+      embedding::text as embedding, created_at, updated_at
+    FROM content_vectors
+    WHERE content_id = #{contentId} AND source_field = #{sourceField}
+    ORDER BY segment_index ASC
+  </select>
+
+  <!-- 根据 textHash + configCode 查询缓存向量 -->
+  <select id="selectByTextHashAndConfigCode" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text,
+      embedding::text as embedding, created_at, updated_at
+    FROM content_vectors
+    WHERE text_hash = #{textHash} AND config_code = #{configCode}
+    ORDER BY id DESC
+    LIMIT 1
+  </select>
+
+  <!-- 根据 textHash 查询(不限 configCode) -->
+  <select id="selectByTextHash" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, content_id, task_id, config_code, source_field, source_path,
+      text_hash, embedding_model, segment_index, segment_total, source_text,
+      embedding::text as embedding, created_at, updated_at
+    FROM content_vectors
+    WHERE text_hash = #{textHash}
+    ORDER BY id DESC
+    LIMIT 1
+  </select>
+
+  <!-- 余弦相似度搜索 Top-N -->
+  <select id="searchTopNByCosine" resultMap="SearchResultMap">
+    SELECT id, content_id, config_code, source_text, 1 - (embedding &lt;=&gt; #{queryVector}::vector) AS score
+    FROM content_vectors
+    WHERE config_code = #{configCode}
+    ORDER BY embedding &lt;=&gt; #{queryVector}::vector
+    LIMIT #{topN}
+  </select>
+
+</mapper>

+ 27 - 0
core/src/main/resources/mapper/pgVector/ext/DeconstructVectorConfigMapperExt.xml

@@ -0,0 +1,27 @@
+<?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.DeconstructVectorConfigMapperExt">
+
+  <sql id="Base_Column_List">
+    id, config_code, config_name, biz_type, content_type, source_field, source_path, extract_rule,
+    embedding_model, dimension, max_length, enable_segment, segment_size, priority, enabled,
+    create_time, update_time
+  </sql>
+
+  <!-- 自定义:按业务类型和内容类型查询启用的向量配置(仅返回 source_field = 'result_json' 的配置) -->
+  <select id="selectMatchingConfigs" resultMap="com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper.BaseResultMap">
+    SELECT
+    <include refid="Base_Column_List" />
+    FROM deconstruct_vector_config
+    WHERE enabled = 1
+    AND source_field = #{sourceField}
+    <if test="bizType != null">
+      AND (biz_type IS NULL OR biz_type = #{bizType})
+    </if>
+    <if test="contentType != null">
+      AND (content_type IS NULL OR content_type = #{contentType})
+    </if>
+    ORDER BY priority ASC
+  </select>
+
+</mapper>

+ 112 - 0
core/src/main/resources/mapper/pgVector/ext/VideoVectorMapperExt.xml

@@ -0,0 +1,112 @@
+<?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.VideoVectorMapperExt">
+
+  <!-- ==================== 自定义 ResultMap ==================== -->
+
+  <!-- 批量获取向量 -->
+  <resultMap id="VectorWithEmbeddingResultMap" type="com.tzld.videoVector.model.po.pgVector.VideoVector">
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="video_id" jdbcType="BIGINT" property="videoId" />
+    <result column="config_code" jdbcType="VARCHAR" property="configCode" />
+    <result column="point_index" jdbcType="INTEGER" property="pointIndex" />
+    <result column="embedding" jdbcType="VARCHAR" property="embedding" />
+    <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
+    <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
+  </resultMap>
+
+  <!-- 余弦相似度搜索 Top-N(返回 videoId + pointIndex + score) -->
+  <resultMap id="SearchResultMap" type="com.tzld.videoVector.model.vo.VideoVectorSearchResult">
+    <result column="video_id" jdbcType="BIGINT" property="videoId" />
+    <result column="point_index" jdbcType="INTEGER" property="pointIndex" />
+    <result column="score" jdbcType="DOUBLE" property="score" />
+  </resultMap>
+
+  <!-- ==================== 自定义向量操作 SQL ==================== -->
+
+  <!-- Upsert: 插入或更新向量(支持多点模式) -->
+  <insert id="upsertVector">
+    INSERT INTO video_vectors (video_id, config_code, point_index, embedding, text, created_at, updated_at)
+    VALUES (#{videoId}, #{configCode}, #{pointIndex}, #{embedding}::vector, #{text}, NOW(), NOW())
+    ON CONFLICT (config_code, video_id, point_index)
+    DO UPDATE SET embedding = EXCLUDED.embedding, text = EXCLUDED.text, updated_at = NOW()
+  </insert>
+
+  <!-- 判断是否存在(任意 pointIndex) -->
+  <select id="existsByVideoIdAndConfigCode" resultType="int">
+    SELECT COUNT(1) FROM video_vectors
+    WHERE video_id = #{videoId} AND config_code = #{configCode}
+  </select>
+
+  <!-- 批量查询已存在的 videoId(去重) -->
+  <select id="selectExistingVideoIds" resultType="java.lang.Long">
+    SELECT DISTINCT video_id FROM video_vectors
+    WHERE config_code = #{configCode}
+    AND video_id IN
+    <foreach collection="videoIds" item="vid" open="(" separator="," close=")">
+      #{vid}
+    </foreach>
+  </select>
+
+  <!-- 获取单个向量(取 pointIndex=0) -->
+  <select id="selectEmbeddingByVideoIdAndConfigCode" resultType="java.lang.String">
+    SELECT embedding::text FROM video_vectors
+    WHERE video_id = #{videoId} AND config_code = #{configCode} AND point_index = 0
+  </select>
+
+  <select id="selectVectorsByVideoIds" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, video_id, config_code, point_index, embedding::text as embedding, created_at, updated_at
+    FROM video_vectors
+    WHERE config_code = #{configCode}
+    AND video_id IN
+    <foreach collection="videoIds" item="vid" open="(" separator="," close=")">
+      #{vid}
+    </foreach>
+  </select>
+
+  <!-- 获取指定 configCode 下所有 videoId(去重) -->
+  <select id="selectAllVideoIdsByConfigCode" resultType="java.lang.Long">
+    SELECT DISTINCT video_id FROM video_vectors
+    WHERE config_code = #{configCode}
+  </select>
+
+  <!-- 删除单个视频的所有向量点 -->
+  <delete id="deleteByVideoIdAndConfigCode">
+    DELETE FROM video_vectors
+    WHERE video_id = #{videoId} AND config_code = #{configCode}
+  </delete>
+
+  <!-- 批量删除向量 -->
+  <delete id="deleteBatchByVideoIdsAndConfigCode">
+    DELETE FROM video_vectors
+    WHERE config_code = #{configCode}
+    AND video_id IN
+    <foreach collection="videoIds" item="vid" open="(" separator="," close=")">
+      #{vid}
+    </foreach>
+  </delete>
+
+  <select id="searchTopNByCosine" resultMap="SearchResultMap">
+    SELECT video_id, point_index, 1 - (embedding &lt;=&gt; #{queryVector}::vector) AS score
+    FROM video_vectors
+    WHERE config_code = #{configCode}
+    ORDER BY embedding &lt;=&gt; #{queryVector}::vector
+    LIMIT #{topN}
+  </select>
+
+  <!-- 分页查询指定 configCode 下的记录 -->
+  <select id="selectByConfigCodePaged" resultMap="VectorWithEmbeddingResultMap">
+    SELECT id, video_id, config_code, point_index, created_at, updated_at
+    FROM video_vectors
+    WHERE config_code = #{configCode}
+    ORDER BY id
+    LIMIT #{limit} OFFSET #{offset}
+  </select>
+
+  <!-- 更新 text 字段 -->
+  <update id="updateTextById">
+    UPDATE video_vectors SET "text" = #{text}, updated_at = NOW()
+    WHERE id = #{id}
+  </update>
+
+</mapper>

+ 12 - 12
core/src/main/resources/mapper/videoVector/deconstruct/DeconstructContentMapper.xml → core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructContentMapper.xml

@@ -1,7 +1,7 @@
 <?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.videoVector.deconstruct.DeconstructContentMapper">
-  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+<mapper namespace="com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructContentMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -25,7 +25,7 @@
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
-  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -120,7 +120,7 @@
     -->
     body_text, result_json
   </sql>
-  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample" resultMap="ResultMapWithBLOBs">
+  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample" resultMap="ResultMapWithBLOBs">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -141,7 +141,7 @@
       order by ${orderByClause}
     </if>
   </select>
-  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample" resultMap="BaseResultMap">
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample" resultMap="BaseResultMap">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -182,7 +182,7 @@
     delete from deconstruct_content
     where id = #{id,jdbcType=BIGINT}
   </delete>
-  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample">
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -193,7 +193,7 @@
       <include refid="Example_Where_Clause" />
     </if>
   </delete>
-  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -214,7 +214,7 @@
       #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{bodyText,jdbcType=LONGVARCHAR}, 
       #{resultJson,jdbcType=LONGVARCHAR})
   </insert>
-  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -340,7 +340,7 @@
       </if>
     </trim>
   </insert>
-  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentExample" resultType="java.lang.Long">
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentExample" resultType="java.lang.Long">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -479,7 +479,7 @@
       <include refid="Update_By_Example_Where_Clause" />
     </if>
   </update>
-  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -544,7 +544,7 @@
     </set>
     where id = #{id,jdbcType=BIGINT}
   </update>
-  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -571,7 +571,7 @@
       result_json = #{resultJson,jdbcType=LONGVARCHAR}
     where id = #{id,jdbcType=BIGINT}
   </update>
-  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContent">
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContent">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.

+ 12 - 12
core/src/main/resources/mapper/videoVector/deconstruct/DeconstructContentVectorMapper.xml → core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructContentVectorMapper.xml

@@ -1,7 +1,7 @@
 <?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.videoVector.deconstruct.DeconstructContentVectorMapper">
-  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+<mapper namespace="com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructContentVectorMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -21,7 +21,7 @@
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
-  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -115,7 +115,7 @@
     -->
     vector_data, source_text
   </sql>
-  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample" resultMap="ResultMapWithBLOBs">
+  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVectorExample" resultMap="ResultMapWithBLOBs">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -136,7 +136,7 @@
       order by ${orderByClause}
     </if>
   </select>
-  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample" resultMap="BaseResultMap">
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVectorExample" resultMap="BaseResultMap">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -177,7 +177,7 @@
     delete from deconstruct_content_vector
     where id = #{id,jdbcType=BIGINT}
   </delete>
-  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample">
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVectorExample">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -188,7 +188,7 @@
       <include refid="Example_Where_Clause" />
     </if>
   </delete>
-  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -207,7 +207,7 @@
       #{updateTime,jdbcType=TIMESTAMP}, #{vectorData,jdbcType=LONGVARCHAR}, #{sourceText,jdbcType=LONGVARCHAR}
       )
   </insert>
-  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -309,7 +309,7 @@
       </if>
     </trim>
   </insert>
-  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVectorExample" resultType="java.lang.Long">
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVectorExample" resultType="java.lang.Long">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -428,7 +428,7 @@
       <include refid="Update_By_Example_Where_Clause" />
     </if>
   </update>
-  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -481,7 +481,7 @@
     </set>
     where id = #{id,jdbcType=BIGINT}
   </update>
-  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -504,7 +504,7 @@
       source_text = #{sourceText,jdbcType=LONGVARCHAR}
     where id = #{id,jdbcType=BIGINT}
   </update>
-  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructContentVector">
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructContentVector">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.

+ 9 - 9
core/src/main/resources/mapper/videoVector/deconstruct/DeconstructVectorConfigMapper.xml → core/src/main/resources/mapper/videoVector/deconstruct/MysqlDeconstructVectorConfigMapper.xml

@@ -1,7 +1,7 @@
 <?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.videoVector.deconstruct.DeconstructVectorConfigMapper">
-  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig">
+<mapper namespace="com.tzld.videoVector.dao.mapper.videoVector.deconstruct.MysqlDeconstructVectorConfigMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -103,7 +103,7 @@
     extract_rule, embedding_model, dimension, max_length, enable_segment, segment_size, 
     priority, enabled, create_time, update_time
   </sql>
-  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample" resultMap="BaseResultMap">
+  <select id="selectByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfigExample" resultMap="BaseResultMap">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -142,7 +142,7 @@
     delete from deconstruct_vector_config
     where id = #{id,jdbcType=BIGINT}
   </delete>
-  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample">
+  <delete id="deleteByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfigExample">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -153,7 +153,7 @@
       <include refid="Example_Where_Clause" />
     </if>
   </delete>
-  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig">
+  <insert id="insert" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -172,7 +172,7 @@
       #{segmentSize,jdbcType=INTEGER}, #{priority,jdbcType=INTEGER}, #{enabled,jdbcType=TINYINT}, 
       #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP})
   </insert>
-  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig">
+  <insert id="insertSelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -286,7 +286,7 @@
       </if>
     </trim>
   </insert>
-  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfigExample" resultType="java.lang.Long">
+  <select id="countByExample" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfigExample" resultType="java.lang.Long">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -389,7 +389,7 @@
       <include refid="Update_By_Example_Where_Clause" />
     </if>
   </update>
-  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig">
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.
@@ -448,7 +448,7 @@
     </set>
     where id = #{id,jdbcType=BIGINT}
   </update>
-  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig">
+  <update id="updateByPrimaryKey" parameterType="com.tzld.videoVector.model.po.videoVector.deconstruct.MysqlDeconstructVectorConfig">
     <!--
       WARNING - @mbg.generated
       This element is automatically generated by MyBatis Generator, do not modify.

+ 14 - 0
pom.xml

@@ -289,6 +289,20 @@
             <version>0.9.9</version>
         </dependency>
 
+        <!-- PostgreSQL JDBC 驱动 -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.7.3</version>
+        </dependency>
+
+        <!-- pgvector Java 客户端 -->
+        <dependency>
+            <groupId>com.pgvector</groupId>
+            <artifactId>pgvector</artifactId>
+            <version>0.1.6</version>
+        </dependency>
+
 
     </dependencies>
 

+ 3 - 2
server/src/main/java/com/tzld/videoVector/Application.java

@@ -1,6 +1,5 @@
 package com.tzld.videoVector;
 
-import org.mybatis.spring.annotation.MapperScan;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.SpringApplication;
@@ -12,9 +11,11 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
 /**
  * CORS 由 K8s Ingress / 阿里云 Tengine 网关统一处理,后端不再重复加 CORS 头,
  * 否则浏览器会因 Access-Control-Allow-Origin 出现多个值而拦截响应。
+ *
+ * @MapperScan 由各 DBConfig (PgVectorDBConfig / VideoVectorDBConfig) 分包配置,
+ * 此处不再放全局 scan。
  */
 @SpringBootApplication
-@MapperScan("com.tzld.videoVector.dao")
 @ServletComponentScan("com.tzld.videoVector.controller")
 @EnableDiscoveryClient
 @EnableFeignClients

+ 41 - 0
server/src/main/java/com/tzld/videoVector/controller/MaterialController.java

@@ -0,0 +1,41 @@
+package com.tzld.videoVector.controller;
+
+import com.tzld.videoVector.common.base.CommonResponse;
+import com.tzld.videoVector.model.param.MaterialMatchParam;
+import com.tzld.videoVector.model.param.MaterialSubmitParam;
+import com.tzld.videoVector.model.vo.MaterialMatchResult;
+import com.tzld.videoVector.service.MaterialSearchService;
+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;
+
+import java.util.List;
+
+/**
+ * 素材向量化搜索接口
+ */
+@RestController
+@RequestMapping("/material")
+public class MaterialController {
+
+    @Autowired
+    private MaterialSearchService materialSearchService;
+
+    /**
+     * 素材入库(解构 + 异步向量化)
+     */
+    @PostMapping("/submit")
+    public CommonResponse<String> submitMaterial(@RequestBody MaterialSubmitParam param) {
+        return CommonResponse.success(materialSearchService.submitMaterial(param));
+    }
+
+    /**
+     * 素材相似搜索 Top-N
+     */
+    @PostMapping("/matchTopN")
+    public CommonResponse<List<MaterialMatchResult>> matchTopN(@RequestBody MaterialMatchParam param) {
+        return CommonResponse.success(materialSearchService.matchTopNMaterial(param));
+    }
+}

+ 9 - 5
server/src/main/java/com/tzld/videoVector/controller/VideoSearchController.java

@@ -5,14 +5,13 @@ import com.tzld.videoVector.common.base.CommonResponse;
 import com.tzld.videoVector.model.param.DeconstructParam;
 import com.tzld.videoVector.model.param.GetDeconstructParam;
 import com.tzld.videoVector.model.param.MatchTopNVideoParam;
+import com.tzld.videoVector.model.vo.VideoMatchResult;
 import com.tzld.videoVector.service.VideoSearchService;
 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;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Map;
 
 @RestController
 @RequestMapping("/videoSearch")
@@ -32,7 +31,12 @@ public class VideoSearchController {
     }
 
     @PostMapping("/matchTopNVideo")
-    public CommonResponse<List<Object>> matchTopNVideo(@RequestBody MatchTopNVideoParam param) {
+    public CommonResponse<List<VideoMatchResult>> matchTopNVideo(@RequestBody MatchTopNVideoParam param) {
         return CommonResponse.success(videoSearchService.matchTopNVideo(param));
     }
+
+    @GetMapping("/getAllConfigCodes")
+    public CommonResponse<Map<String, String>> getAllConfigCodes() {
+        return CommonResponse.success(videoSearchService.getAllConfigCodes());
+    }
 }

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

@@ -14,6 +14,17 @@ public class XxlJobController {
     @Autowired
     private VideoVectorJob videoVectorJob;
 
+    @Autowired
+    private MaterialDeconstructCheckJob materialDeconstructCheckJob;
+
+    @Autowired
+    private MaterialVectorJob materialVectorJob;
+
+    @Autowired
+    private VideoVectorTextBackfillJob videoVectorTextBackfillJob;
+
+    // ==================== 视频向量化任务 ====================
+
     @GetMapping("/vectorVideoJob")
     public CommonResponse<Void> vectorVideoJob() {
         videoVectorJob.vectorVideoJob(null);
@@ -26,10 +37,43 @@ public class XxlJobController {
         return CommonResponse.success();
     }
 
+    @GetMapping("/resultLogVideoVectorJob")
+    public CommonResponse<Void> resultLogVideoVectorJob() {
+        videoVectorJob.resultLogVideoVectorJob(null);
+        return CommonResponse.success();
+    }
+
+    @GetMapping("/allVideoVectorJob")
+    public CommonResponse<Void> allVideoVectorJob() {
+        videoVectorJob.allVideoVectorJob(null);
+        return CommonResponse.success();
+    }
+
     @GetMapping("/retryDeconstructJob")
     public CommonResponse<Void> retryDeconstructJob() {
         videoVectorJob.retryDeconstructJob(null);
         return CommonResponse.success();
     }
 
+    // ==================== 素材相关任务 ====================
+
+    @GetMapping("/checkMaterialDeconstructJob")
+    public CommonResponse<Void> checkMaterialDeconstructJob() {
+        materialDeconstructCheckJob.checkMaterialDeconstructJob(null);
+        return CommonResponse.success();
+    }
+
+    @GetMapping("/vectorMaterialJob")
+    public CommonResponse<Void> vectorMaterialJob() {
+        materialVectorJob.vectorMaterialJob(null);
+        return CommonResponse.success();
+    }
+
+    @GetMapping("/videoVectorTextBackfillJob")
+    public CommonResponse<Void> videoVectorTextBackfillJob() {
+        videoVectorTextBackfillJob.videoVectorTextBackfillJob(null);
+        return CommonResponse.success();
+    }
+
+
 }

+ 11 - 0
server/src/main/resources/application-dev.yml

@@ -13,6 +13,17 @@ spring:
         minimum-idle: 10
         maximum-pool-size: 20
         connection-test-query: SELECT 1
+    pg-vector:
+      driver-class-name: org.postgresql.Driver
+      jdbc-url: jdbc:postgresql://pgm-bp1p0qr1gim87wnibo.pg.rds.aliyuncs.com/vector?currentSchema=public
+      username: vector
+      password: vector123456@
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 5
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+        connection-init-sql: SET hnsw.ef_search = 100
 
   redis:
     host: r-bp1zg8fw8db0vxdo2mpd.redis.rds.aliyuncs.com

+ 11 - 0
server/src/main/resources/application-prod.yml

@@ -13,6 +13,17 @@ spring:
         minimum-idle: 10
         maximum-pool-size: 20
         connection-test-query: SELECT 1
+    pg-vector:
+      driver-class-name: org.postgresql.Driver
+      jdbc-url: jdbc:postgresql://pgm-bp1p0qr1gim87wni.pg.rds.aliyuncs.com/vector?currentSchema=public
+      username: vector
+      password: vector123456@
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 5
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+        connection-init-sql: SET hnsw.ef_search = 100
 
   redis:
     host: r-bp1zg8fw8db0vxdo2mpd.redis.rds.aliyuncs.com

+ 11 - 0
server/src/main/resources/application-test.yml

@@ -13,6 +13,17 @@ spring:
         minimum-idle: 10
         maximum-pool-size: 20
         connection-test-query: SELECT 1
+    pg-vector:
+      driver-class-name: org.postgresql.Driver
+      jdbc-url: jdbc:postgresql://pgm-bp1p0qr1gim87wni.pg.rds.aliyuncs.com/vector?currentSchema=public
+      username: vector
+      password: vector123456@
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 5
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+        connection-init-sql: SET hnsw.ef_search = 100
 
   redis:
     host: r-bp1zg8fw8db0vxdo2mpd.redis.rds.aliyuncs.com

+ 2 - 5
server/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 spring:
   profiles:
-    active: test
+    active: dev
   application:
     name: video-vector-server
 
@@ -19,11 +19,8 @@ pagehelper:
   helper-dialect: mysql
 
 mybatis:
-  mapper-locations: classpath:mapper/**/*.xml
   configuration:
-    use-generated-keys: true
-    map-underscore-to-camel-case: true
-    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 直接输出到控制台(简单调试用)
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl  # 通过 SLF4J 输出,受 logback 级别控制
 
 app:
   id: video-vector-server  # 应用标识

+ 4 - 0
server/src/main/resources/logback-spring.xml

@@ -149,6 +149,10 @@
     <!--<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>-->
     <!-- 解决 aliyun loghub debug模式下循环发送的问题 -->
     <logger name="org.apache.http.impl.conn.Wire" level="WARN"/>
+    <!-- 屏蔽 MyBatis SqlSession 创建/关闭的 DEBUG 日志 -->
+    <logger name="org.mybatis.spring.SqlSessionUtils" level="WARN"/>
+    <!-- 屏蔽 JDBC Connection will not be managed by Spring 日志 -->
+    <logger name="org.mybatis.spring.transaction.SpringManagedTransaction" level="WARN"/>
     <!--aliyun loghub 为了防止进程退出时,内存中的数据丢失,请加上此选项-->
     <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
 

+ 73 - 0
server/src/test/java/DataMigrationTest.java

@@ -0,0 +1,73 @@
+import com.tzld.videoVector.Application;
+import com.tzld.videoVector.job.DataMigrationJob;
+import com.xxl.job.core.biz.model.ReturnT;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest(classes = Application.class)
+@Slf4j
+public class DataMigrationTest {
+
+    @Autowired
+    private DataMigrationJob dataMigrationJob;
+
+    /**
+     * 迁移 deconstruct_vector_config(建议最先执行)
+     */
+    @Test
+    public void testMigrateVectorConfig() {
+        ReturnT<String> result = dataMigrationJob.migrateVectorConfigToPgJob("");
+        log.info("migrateVectorConfigToPgJob 结果: code={}", result.getCode());
+    }
+
+    /**
+     * 迁移 deconstruct_content
+     */
+    @Test
+    public void testMigrateContent() {
+        ReturnT<String> result = dataMigrationJob.migrateContentToPgJob("");
+        log.info("migrateContentToPgJob 结果: code={}", result.getCode());
+    }
+
+    /**
+     * 迁移 deconstruct_content_vector → content_vectors
+     */
+    @Test
+    public void testMigrateContentVector() {
+        ReturnT<String> result = dataMigrationJob.migrateContentVectorToPgJob("");
+        log.info("migrateContentVectorToPgJob 结果: code={}", result.getCode());
+    }
+
+    /**
+     * 迁移 Redis 视频向量 → video_vectors(跳过多点模式)
+     */
+    @Test
+    public void testMigrateRedisVector() {
+        ReturnT<String> result = dataMigrationJob.migrateRedisVectorToPgJob("");
+        log.info("migrateRedisVectorToPgJob 结果: code={}", result.getCode());
+    }
+
+    /**
+     * 一键执行全部迁移(按依赖顺序)
+     */
+    @Test
+    public void testMigrateAll() {
+        log.info("===== 开始全量数据迁移 =====");
+
+        log.info("--- 1. 迁移向量化配置 ---");
+        dataMigrationJob.migrateVectorConfigToPgJob("");
+
+        log.info("--- 2. 迁移解构内容 ---");
+        dataMigrationJob.migrateContentToPgJob("");
+
+        log.info("--- 3. 迁移素材向量 ---");
+        dataMigrationJob.migrateContentVectorToPgJob("");
+
+        log.info("--- 4. 迁移Redis视频向量 ---");
+        dataMigrationJob.migrateRedisVectorToPgJob("");
+
+        log.info("===== 全量数据迁移完成 =====");
+    }
+}