|
@@ -12,6 +12,7 @@ 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.DeconstructContentExample;
|
|
|
import com.tzld.videoVector.model.po.videoVector.deconstruct.DeconstructVectorConfig;
|
|
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.DeconstructVectorConfigExample;
|
|
|
|
|
+import com.tzld.videoVector.api.AigcApiService;
|
|
|
import com.tzld.videoVector.api.VideoApiService;
|
|
import com.tzld.videoVector.api.VideoApiService;
|
|
|
import com.tzld.videoVector.service.DeconstructService;
|
|
import com.tzld.videoVector.service.DeconstructService;
|
|
|
import com.tzld.videoVector.service.EmbeddingService;
|
|
import com.tzld.videoVector.service.EmbeddingService;
|
|
@@ -51,6 +52,9 @@ public class VideoVectorJob {
|
|
|
@Resource
|
|
@Resource
|
|
|
private VideoApiService videoApiService;
|
|
private VideoApiService videoApiService;
|
|
|
|
|
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private AigcApiService aigcApiService;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 每页查询数量
|
|
* 每页查询数量
|
|
|
*/
|
|
*/
|
|
@@ -434,6 +438,139 @@ public class VideoVectorJob {
|
|
|
return videoIds;
|
|
return videoIds;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * AIGC 来源视频向量化任务
|
|
|
|
|
+ * 从 AIGC API(id=46)拉取视频列表,经过向量存在性检查和审核过滤后,
|
|
|
|
|
+ * 调用 detail 接口获取解构详情(dataContent),提取选题文本并向量化写入 Redis
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param param 参数
|
|
|
|
|
+ * @return 执行结果
|
|
|
|
|
+ */
|
|
|
|
|
+ @XxlJob("aigcVideoVectorJob")
|
|
|
|
|
+ public ReturnT<String> aigcVideoVectorJob(String param) {
|
|
|
|
|
+ log.info("开始执行 AIGC 来源视频向量化任务, param: {}", param);
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取所有启用的向量化配置
|
|
|
|
|
+ List<DeconstructVectorConfig> configs = getEnabledConfigs();
|
|
|
|
|
+ if (CollectionUtils.isEmpty(configs)) {
|
|
|
|
|
+ log.warn("未找到启用的向量化配置");
|
|
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 从 AIGC API 获取任务输入列表(bizUniqueId 为视频 ID)
|
|
|
|
|
+ List<AigcApiService.AigcTaskInput> taskInputList = aigcApiService.getTaskInputList(46);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(taskInputList)) {
|
|
|
|
|
+ log.info("AIGC API 未返回任务输入数据");
|
|
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("获取到 {} 条 AIGC 任务输入数据", taskInputList.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 构建 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) {
|
|
|
|
|
+ log.warn("bizUniqueId 格式非法,跳过: {}", input.getBizUniqueId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (videoIdToTaskInstanceId.isEmpty()) {
|
|
|
|
|
+ log.info("无有效 videoId,任务结束");
|
|
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+ 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.2 审核状态过滤:排除审核未通过的视频
|
|
|
|
|
+ needProcessIds = filterAuditPassedIds(needProcessIds);
|
|
|
|
|
+ if (needProcessIds.isEmpty()) {
|
|
|
|
|
+ log.info("配置 {} 待处理视频均未通过审核,跳过", configCode);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("配置 {} 审核通过后需处理 {} 个视频", configCode, needProcessIds.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 4.3 逐个调用 detail 接口,提取选题并向量化存储
|
|
|
|
|
+ for (Long videoId : needProcessIds) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Long taskInstanceId = videoIdToTaskInstanceId.get(videoId);
|
|
|
|
|
+ if (taskInstanceId == null) {
|
|
|
|
|
+ log.warn("videoId={} 无对应 taskInstanceId,跳过", videoId);
|
|
|
|
|
+ totalFailCount++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 调用 detail 接口获取 dataContent(解构详情)
|
|
|
|
|
+ JSONObject dataContent = aigcApiService.getTaskCallbackDetail(taskInstanceId);
|
|
|
|
|
+ if (dataContent == null) {
|
|
|
|
|
+ log.warn("videoId={} taskInstanceId={} 获取 dataContent 失败,跳过", videoId, taskInstanceId);
|
|
|
|
|
+ totalFailCount++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 从 dataContent 中提取选题文本
|
|
|
|
|
+ List<String> texts = extractTopicFromDataContent(dataContent);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(texts)) {
|
|
|
|
|
+ log.debug("videoId={} 配置 {} 未提取到选题文本,跳过", videoId, configCode);
|
|
|
|
|
+ totalFailCount++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 向量化并写入 Redis
|
|
|
|
|
+ boolean success = vectorizeAndStore(config, videoId, texts);
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ totalSuccessCount++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ totalFailCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("处理 videoId={} 配置 {} 时发生异常: {}", videoId, configCode, e.getMessage(), e);
|
|
|
|
|
+ totalFailCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("AIGC 来源视频向量化任务完成,总成功: {}, 总失败: {}", totalSuccessCount, totalFailCount);
|
|
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("AIGC 来源视频向量化任务执行失败: {}", e.getMessage(), e);
|
|
|
|
|
+ return new ReturnT<>(ReturnT.FAIL_CODE, "任务执行失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 dataContent 中提取选题文本
|
|
|
|
|
+ * 默认复用配置的 sourcePath 提取逻辑
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param dataContent dataContent 解析后的 JSONObject
|
|
|
|
|
+ * @return 提取的文本列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> extractTopicFromDataContent(JSONObject dataContent) {
|
|
|
|
|
+ if (dataContent == null) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ String sourcePath = "$.最终选题.选题";
|
|
|
|
|
+ return extractFromJson(dataContent, sourcePath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 重试超时的解构任务
|
|
* 重试超时的解构任务
|
|
|
* 检查创建超过一小时,状态不是成功或失败的内容重新查询解构结果
|
|
* 检查创建超过一小时,状态不是成功或失败的内容重新查询解构结果
|