|
|
@@ -0,0 +1,469 @@
|
|
|
+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 contentVectorMapper;
|
|
|
+
|
|
|
+ // ================================================================ 入库
|
|
|
+ @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 = contentVectorMapper.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 = contentVectorMapper.selectByContentIdAndConfigCode(content.getId(), configCode);
|
|
|
+ } else {
|
|
|
+ vectors = contentVectorMapper.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 = contentVectorMapper.selectByTextHashAndConfigCode(textHash, configCode);
|
|
|
+ } else {
|
|
|
+ cached = contentVectorMapper.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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有启用的向量化配置
|
|
|
+ */
|
|
|
+ private List<DeconstructVectorConfig> getEnabledConfigs() {
|
|
|
+ try {
|
|
|
+ DeconstructVectorConfigExample example = new DeconstructVectorConfigExample();
|
|
|
+ example.createCriteria().andEnabledEqualTo((short) 1);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|