Преглед изворни кода

添加内容解构服务及视频搜索集成

wangyunpeng пре 3 дана
родитељ
комит
d90348c98b

+ 34 - 40
core/src/main/java/com/tzld/videoVector/job/VideoVectorJob.java

@@ -80,16 +80,16 @@ public class VideoVectorJob {
                     // 4. 逐个处理新的 videoId
                     for (Long videoId : newVideoIds) {
                         try {
-                            // 4.1 查询视频详情
-                            JSONObject videoDetail = queryVideoDetail(videoId);
-                            if (videoDetail == null) {
+                            // 4.1 查询视频选题
+                            String videoTopic = queryVideoTopic(videoId);
+                            if (videoTopic == null) {
                                 log.warn("videoId={} 详情查询为空,跳过", videoId);
                                 totalFailCount++;
                                 continue;
                             }
 
                             // 4.2 提取字段并向量化
-                            List<Float> vector = extractAndVectorize(videoDetail);
+                            List<Float> vector = extractAndVectorize(videoTopic);
                             if (vector == null || vector.isEmpty()) {
                                 log.warn("videoId={} 向量化失败,跳过", videoId);
                                 totalFailCount++;
@@ -97,7 +97,7 @@ public class VideoVectorJob {
                             }
 
                             // 4.3 存储到 Milvus
-                            insertToMilvus(videoId, vector, videoDetail);
+                            insertToMilvus(videoId, vector, videoTopic);
                             totalSuccessCount++;
                             log.debug("videoId={} 处理成功", videoId);
 
@@ -135,7 +135,11 @@ public class VideoVectorJob {
     private List<Long> queryVideoIdsByPage(int pageNum, int pageSize) {
         int offset = pageNum * pageSize;
         String sql = String.format(
-                "SELECT video_id FROM your_table WHERE status = 1 ORDER BY video_id LIMIT %d, %d",
+                "SELECT content_id " +
+                        "FROM videoods.content_profile " +
+                        "WHERE status = 3 and is_deleted = 0 " +
+                        "ORDER BY video_id " +
+                        "LIMIT %d, %d",
                 offset, pageSize);
         List<Record> records = OdpsUtil.getOdpsData(sql);
         if (records == null || records.isEmpty()) {
@@ -148,59 +152,49 @@ public class VideoVectorJob {
     }
 
     /**
-     * 查询视频详情
+     * 查询视频选题
      */
-    private JSONObject queryVideoDetail(Long videoId) {
+    private String queryVideoTopic(Long videoId) {
         String sql = String.format(
-                "SELECT video_id, title, description, tags, category FROM your_detail_table WHERE video_id = %d",
+                "SELECT video_id, raw_result " +
+                        "FROM videoods.content_profile " +
+                        "WHERE status = 3 and is_deleted = 0 and content_id = %d",
                 videoId);
         List<Record> records = OdpsUtil.getOdpsData(sql);
         if (records == null || records.isEmpty()) {
             return null;
         }
         Record record = records.get(0);
-        JSONObject result = new JSONObject();
-        result.put("video_id", record.getBigint("video_id"));
-        result.put("title", record.getString("title"));
-        result.put("description", record.getString("description"));
-        result.put("tags", record.getString("tags"));
-        result.put("category", record.getString("category"));
-        return result;
-    }
-
-    /**
-     * 提取字段并向量化
-     */
-    private List<Float> extractAndVectorize(JSONObject videoDetail) {
-        // 提取用于向量化的文本字段
-        String title = videoDetail.getString("title");
-        String description = videoDetail.getString("description");
-        String tags = videoDetail.getString("tags");
-
-        // 拼接文本
-        StringBuilder textBuilder = new StringBuilder();
-        if (title != null && !title.isEmpty()) {
-            textBuilder.append(title).append(" ");
+        JSONObject videoDeconstructContent =  JSONObject.parseObject(record.getString("raw_result"));
+        if (videoDeconstructContent == null) {
+            return null;
         }
-        if (description != null && !description.isEmpty()) {
-            textBuilder.append(description).append(" ");
+        JSONObject finalNormalizationRebuild = videoDeconstructContent.getJSONObject("final_normalization_rebuild");
+        if (finalNormalizationRebuild == null) {
+            return null;
         }
-        if (tags != null && !tags.isEmpty()) {
-            textBuilder.append(tags);
+        JSONObject topicFusionResult = finalNormalizationRebuild.getJSONObject("topic_fusion_result");
+        if (topicFusionResult == null) {
+            return null;
         }
-        String text = textBuilder.toString().trim();
-        if (text.isEmpty()) {
+        JSONObject finalTopic = topicFusionResult.getJSONObject("最终选题");
+        if (finalTopic == null) {
             return null;
         }
+        return finalTopic.getString("选题");
+    }
 
-        // 使用 EmbeddingService 进行向量化
-        return embeddingService.embed(text);
+    /**
+     * 提取字段并向量化
+     */
+    private List<Float> extractAndVectorize(String videoTopic) {
+        return embeddingService.embed(videoTopic);
     }
 
     /**
      * 将向量数据存储到 Milvus
      */
-    private void insertToMilvus(Long videoId, List<Float> vector, JSONObject videoDetail) {
+    private void insertToMilvus(Long videoId, List<Float> vector, String videoTopic) {
         // 使用 MilvusUtil 进行插入操作
         // 注意:需要确保 Milvus 集合已创建,且包含 video_id 和 vector 字段
         List<List<Float>> vectors = new ArrayList<>();

+ 70 - 0
core/src/main/java/com/tzld/videoVector/model/entity/DeconstructResult.java

@@ -0,0 +1,70 @@
+package com.tzld.videoVector.model.entity;
+
+import lombok.Data;
+
+/**
+ * 内容解构结果实体类
+ */
+@Data
+public class DeconstructResult {
+
+    /** 任务ID */
+    private String taskId;
+
+    /** 任务状态:0 PENDING, 1 RUNNING, 2 SUCCESS, 3 FAILED */
+    private Integer status;
+
+    /** 解构结果 JSON 字符串 */
+    private String result;
+
+    /** 失败原因 */
+    private String reason;
+
+    /** 选题点结构结果页地址 */
+    private String pointUrl;
+
+    /** 权重页地址 */
+    private String weightUrl;
+
+    /** 选题点聚类结果页地址 */
+    private String patternUrl;
+
+    /** 是否成功 */
+    private boolean success;
+
+    /**
+     * 检查任务是否完成(成功或失败)
+     */
+    public boolean isFinished() {
+        return status != null && (status == 2 || status == 3);
+    }
+
+    /**
+     * 获取状态描述
+     */
+    public String getStatusDesc() {
+        if (status == null) return "UNKNOWN";
+        switch (status) {
+            case 0:
+                return "PENDING";
+            case 1:
+                return "RUNNING";
+            case 2:
+                return "SUCCESS";
+            case 3:
+                return "FAILED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DeconstructResult{" +
+                "taskId='" + taskId + '\'' +
+                ", status=" + status +
+                ", success=" + success +
+                ", statusDesc='" + getStatusDesc() + '\'' +
+                '}';
+    }
+}

+ 55 - 0
core/src/main/java/com/tzld/videoVector/service/DeconstructService.java

@@ -0,0 +1,55 @@
+package com.tzld.videoVector.service;
+
+import com.tzld.videoVector.model.entity.DeconstructResult;
+
+import java.util.List;
+
+/**
+ * 内容解构服务接口
+ */
+public interface DeconstructService {
+
+    /**
+     * 提交内容解构任务
+     * @param scene 业务场景:0选题 1创作 2制作
+     * @param contentType 内容类型:1长文 2图文 3视频
+     * @param channelContentId 帖子id/视频id
+     * @param videoUrl 视频地址
+     * @param images 图片列表
+     * @param bodyText 正文内容
+     * @param title 标题
+     * @param channelAccountId 作者id
+     * @param channelAccountName 作者名称
+     * @return taskId 任务ID
+     */
+    String deconstruct(int scene, int contentType, String channelContentId, 
+                       String videoUrl, List<String> images,
+                       String bodyText, String title, 
+                       String channelAccountId, String channelAccountName);
+
+    /**
+     * 提交视频解构任务(简化版)
+     * @param videoId 视频ID
+     * @param videoUrl 视频地址
+     * @param title 标题
+     * @return taskId 任务ID
+     */
+    String deconstructVideo(String videoId, String videoUrl, String title);
+
+    /**
+     * 提交图文解构任务(简化版)
+     * @param contentId 内容ID
+     * @param title 标题
+     * @param bodyText 正文内容
+     * @param images 图片列表
+     * @return taskId 任务ID
+     */
+    String deconstructArticle(String contentId, String title, String bodyText, java.util.List<String> images);
+
+    /**
+     * 根据 taskId 查询解构结果
+     * @param taskId 任务ID
+     * @return 解构结果,包含 taskId、status、result 等信息
+     */
+    DeconstructResult getDeconstructResult(String taskId);
+}

+ 222 - 0
core/src/main/java/com/tzld/videoVector/service/impl/DeconstructServiceImpl.java

@@ -0,0 +1,222 @@
+package com.tzld.videoVector.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.model.entity.DeconstructResult;
+import com.tzld.videoVector.service.DeconstructService;
+import com.tzld.videoVector.util.http.HttpClientUtils;
+import com.tzld.videoVector.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 内容解构服务实现类
+ */
+@Slf4j
+@Service
+public class DeconstructServiceImpl implements DeconstructService {
+
+    /**
+     * 内容解构 API Host
+     */
+    @Value("${deconstruct.api.host:http://supply-content-deconstruction-api.piaoquantv.com}")
+    private String apiHost;
+
+    /**
+     * 创建任务接口路径
+     */
+    private static final String CREATE_TASK_PATH = "/api/v1/content/tasks/decode";
+
+    /**
+     * 查询结果接口路径模板
+     */
+    private static final String QUERY_RESULT_PATH_TEMPLATE = "/api/v1/content/tasks/%s";
+
+    @Override
+    public String deconstruct(int scene, int contentType, String channelContentId,
+                              String videoUrl, List<String> images,
+                              String bodyText, String title,
+                              String channelAccountId, String channelAccountName) {
+        // 构建 content 对象
+        JSONObject content = new JSONObject();
+        content.put("channel_content_id", channelContentId);
+        content.put("video_url", videoUrl != null ? videoUrl : "");
+        content.put("images", images != null ? images : new java.util.ArrayList<>());
+        content.put("body_text", bodyText != null ? bodyText : "");
+        content.put("title", title != null ? title : "");
+        content.put("channel_account_id", channelAccountId != null ? channelAccountId : "");
+        content.put("channel_account_name", channelAccountName != null ? channelAccountName : "");
+
+        // 构建请求体
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("scene", scene);
+        requestBody.put("content_type", contentType);
+        requestBody.put("content", content);
+
+        // 构建完整 URL
+        String url = apiHost + CREATE_TASK_PATH;
+        log.info("调用内容解构 API,url: {}, 请求参数: {}", url, requestBody.toJSONString());
+
+        try {
+            // 发送 POST 请求
+            HttpResponseContent response = HttpClientUtils.postRequestBody(
+                    url, 
+                    requestBody, 
+                    null
+            );
+
+            if (response == null) {
+                log.error("调用内容解构 API 失败,响应为空");
+                return null;
+            }
+
+            // 解析响应
+            String responseBody = response.getBodyContent();
+            log.info("内容解构 API 响应: {}", responseBody);
+
+            JSONObject jsonResponse = JSONObject.parseObject(responseBody);
+            
+            // 检查响应码
+            Integer code = jsonResponse.getInteger("code");
+            if (code == null || code != 0) {
+                String msg = jsonResponse.getString("msg");
+                log.error("内容解构 API 调用失败,code: {}, msg: {}", code, msg);
+                return null;
+            }
+
+            // 提取 taskId
+            JSONObject data = jsonResponse.getJSONObject("data");
+            if (data == null) {
+                log.error("内容解构 API 响应中 data 为空");
+                return null;
+            }
+
+            String taskId = data.getString("taskId");
+            log.info("内容解构任务创建成功,taskId: {}", taskId);
+            return taskId;
+
+        } catch (Exception e) {
+            log.error("调用内容解构 API 异常: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    @Override
+    public String deconstructVideo(String videoId, String videoUrl, String title) {
+        return deconstruct(
+                0,              // scene: 0选题
+                3,              // content_type: 3视频
+                videoId,        // channel_content_id
+                videoUrl,       // video_url
+                null,           // images
+                null,           // body_text
+                title,          // title
+                null,           // channel_account_id
+                null            // channel_account_name
+        );
+    }
+
+    @Override
+    public String deconstructArticle(String contentId, String title, String bodyText, List<String> images) {
+        return deconstruct(
+                0,              // scene: 0选题
+                2,              // content_type: 2图文
+                contentId,      // channel_content_id
+                null,           // video_url
+                images,         // images
+                bodyText,       // body_text
+                title,          // title
+                null,           // channel_account_id
+                null            // channel_account_name
+        );
+    }
+
+    @Override
+    public DeconstructResult getDeconstructResult(String taskId) {
+        if (taskId == null || taskId.trim().isEmpty()) {
+            log.error("taskId 不能为空");
+            return null;
+        }
+
+        // 构建完整 URL
+        String url = apiHost + String.format(QUERY_RESULT_PATH_TEMPLATE, taskId);
+        log.info("查询解构结果,taskId: {}, url: {}", taskId, url);
+
+        try {
+            // 发送 GET 请求
+            HttpResponseContent response = HttpClientUtils.get(url);
+
+            if (response == null) {
+                log.error("查询解构结果失败,响应为空,taskId: {}", taskId);
+                return null;
+            }
+
+            // 解析响应
+            String responseBody = response.getBodyContent();
+            log.info("查询解构结果响应: {}", responseBody);
+
+            JSONObject jsonResponse = JSONObject.parseObject(responseBody);
+
+            DeconstructResult result = new DeconstructResult();
+            result.setTaskId(taskId);
+
+            // 检查响应码
+            Integer code = jsonResponse.getInteger("code");
+            if (code == null) {
+                log.error("查询解构结果响应中 code 为空,taskId: {}", taskId);
+                return result;
+            }
+
+            // 404 表示任务不存在
+            if (code == 404) {
+                log.warn("任务不存在,taskId: {}", taskId);
+                result.setStatus(3); // FAILED
+                result.setSuccess(false);
+                result.setReason("任务不存在");
+                return result;
+            }
+
+            // 其他非成功状态
+            if (code != 0) {
+                String msg = jsonResponse.getString("msg");
+                log.error("查询解构结果失败,code: {}, msg: {}, taskId: {}", code, msg, taskId);
+                result.setSuccess(false);
+                result.setReason(msg);
+                return result;
+            }
+
+            // 解析 data
+            JSONObject data = jsonResponse.getJSONObject("data");
+            if (data == null) {
+                log.error("查询解构结果响应中 data 为空,taskId: {}", taskId);
+                result.setSuccess(false);
+                return result;
+            }
+
+            // 填充结果
+            result.setTaskId(data.getString("taskId"));
+            result.setStatus(data.getInteger("status"));
+            result.setResult(data.getString("result"));
+            result.setReason(data.getString("reason"));
+            result.setSuccess(result.getStatus() != null && result.getStatus() == 2);
+
+            // 解析 url 对象
+            JSONObject urlObj = data.getJSONObject("url");
+            if (urlObj != null) {
+                result.setPointUrl(urlObj.getString("pointUrl"));
+                result.setWeightUrl(urlObj.getString("weightUrl"));
+                result.setPatternUrl(urlObj.getString("patternUrl"));
+            }
+
+            log.info("查询解构结果成功,taskId: {}, status: {}, success: {}", 
+                    result.getTaskId(), result.getStatusDesc(), result.isSuccess());
+            return result;
+
+        } catch (Exception e) {
+            log.error("查询解构结果异常,taskId: {}, error: {}", taskId, e.getMessage(), e);
+            return null;
+        }
+    }
+}

+ 77 - 2
core/src/main/java/com/tzld/videoVector/service/impl/VideoSearchServiceImpl.java

@@ -1,26 +1,101 @@
 package com.tzld.videoVector.service.impl;
 
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.model.entity.DeconstructResult;
 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.service.DeconstructService;
 import com.tzld.videoVector.service.VideoSearchService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
 import java.util.Collections;
 import java.util.List;
 
+@Slf4j
 @Service
 public class VideoSearchServiceImpl implements VideoSearchService {
 
+    @Resource
+    private DeconstructService deconstructService;
 
     @Override
     public String deconstruct(DeconstructParam param) {
-        return "";
+        if (param == null) {
+            log.error("deconstruct 参数为空");
+            return null;
+        }
+
+        log.info("调用内容解构服务,title: {}", param.getTitle());
+
+        // 调用 DeconstructService 提交图文解构任务
+        // scene=0(选题), contentType=2(图文)
+        String taskId = deconstructService.deconstructArticle(
+                null,                       // contentId 为空,由服务端生成
+                param.getTitle(),           // 标题
+                null,                       // bodyText 为空
+                param.getImageList()        // 图片列表
+        );
+
+        log.info("内容解构任务提交成功,taskId: {}", taskId);
+        return taskId;
     }
 
     @Override
     public String getDeconstructResult(GetDeconstructParam param) {
-        return "";
+        if (param == null || param.getTaskId() == null || param.getTaskId().trim().isEmpty()) {
+            log.error("getDeconstructResult 参数或 taskId 为空");
+            return null;
+        }
+
+        String taskId = param.getTaskId();
+        log.info("查询内容解构结果,taskId: {}", taskId);
+
+        // 调用 DeconstructService 查询解构结果
+        DeconstructResult result = deconstructService.getDeconstructResult(taskId);
+
+        if (result == null) {
+            log.error("查询解构结果失败,taskId: {}", taskId);
+            return null;
+        }
+
+        // 将结果转换为 JSON 字符串返回
+        JSONObject jsonResult = new JSONObject();
+        jsonResult.put("taskId", result.getTaskId());
+        jsonResult.put("status", result.getStatus());
+        jsonResult.put("statusDesc", result.getStatusDesc());
+        jsonResult.put("success", result.isSuccess());
+        jsonResult.put("finished", result.isFinished());
+
+        if (result.getResult() != null) {
+            jsonResult.put("result", result.getResult());
+        }
+
+        if (result.getReason() != null) {
+            jsonResult.put("reason", result.getReason());
+        }
+
+        // 添加 URL 信息
+        JSONObject urlObj = new JSONObject();
+        if (result.getPointUrl() != null) {
+            urlObj.put("pointUrl", result.getPointUrl());
+        }
+        if (result.getWeightUrl() != null) {
+            urlObj.put("weightUrl", result.getWeightUrl());
+        }
+        if (result.getPatternUrl() != null) {
+            urlObj.put("patternUrl", result.getPatternUrl());
+        }
+        if (!urlObj.isEmpty()) {
+            jsonResult.put("url", urlObj);
+        }
+
+        log.info("查询解构结果成功,taskId: {}, status: {}, success: {}",
+                taskId, result.getStatusDesc(), result.isSuccess());
+
+        return jsonResult.toJSONString();
     }
 
     @Override