|
@@ -91,6 +91,7 @@ public class DanKnowledgeBaseService extends ServiceImpl<DanKnowledgeBaseMapper,
|
|
|
}
|
|
}
|
|
|
wrapper.orderByAsc(DanKnowledgeBase::getSort).orderByAsc(DanKnowledgeBase::getId);
|
|
wrapper.orderByAsc(DanKnowledgeBase::getSort).orderByAsc(DanKnowledgeBase::getId);
|
|
|
this.page(p, wrapper);
|
|
this.page(p, wrapper);
|
|
|
|
|
+ fillAssociations(p.getRecords());
|
|
|
|
|
|
|
|
SortUtil.applySort(wrapper);
|
|
SortUtil.applySort(wrapper);
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
Map<String, Object> data = new HashMap<>();
|
|
@@ -131,17 +132,57 @@ public class DanKnowledgeBaseService extends ServiceImpl<DanKnowledgeBaseMapper,
|
|
|
public DanKnowledgeBase saveWithAssociations(DanKnowledgeBase knowledge,
|
|
public DanKnowledgeBase saveWithAssociations(DanKnowledgeBase knowledge,
|
|
|
List<Long> dimensionIds,
|
|
List<Long> dimensionIds,
|
|
|
List<Long> tagIds) {
|
|
List<Long> tagIds) {
|
|
|
|
|
+ DanKnowledgeBase existing = knowledge.getId() != null ? this.getById(knowledge.getId()) : null;
|
|
|
if (knowledge.getId() != null) {
|
|
if (knowledge.getId() != null) {
|
|
|
this.updateById(knowledge);
|
|
this.updateById(knowledge);
|
|
|
} else {
|
|
} else {
|
|
|
this.save(knowledge);
|
|
this.save(knowledge);
|
|
|
}
|
|
}
|
|
|
- if (dimensionIds != null) {
|
|
|
|
|
- bindDimensions(knowledge.getId(), dimensionIds);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ List<Long> resolvedDimensionIds = normalizeIds(dimensionIds);
|
|
|
|
|
+ List<Long> resolvedTagIds = normalizeIds(tagIds);
|
|
|
|
|
+ String matchTitle = knowledge.getTitle() != null
|
|
|
|
|
+ ? knowledge.getTitle() : (existing != null ? existing.getTitle() : null);
|
|
|
|
|
+ String matchContent = knowledge.getContent() != null
|
|
|
|
|
+ ? knowledge.getContent() : (existing != null ? existing.getContent() : null);
|
|
|
|
|
+
|
|
|
|
|
+ // 未显式选择维度时按标题/正文关键词自动推断;显式传入空数组同样视为“未选择”。
|
|
|
|
|
+ if (resolvedDimensionIds.isEmpty()) {
|
|
|
|
|
+ Set<String> codes = matchDimensionCodes(matchTitle, matchContent);
|
|
|
|
|
+ resolvedDimensionIds = codes.stream()
|
|
|
|
|
+ .map(this::dimensionIdByCode)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (!resolvedDimensionIds.isEmpty()) {
|
|
|
|
|
+ bindDimensions(knowledge.getId(), resolvedDimensionIds);
|
|
|
|
|
+ log.info("知识[{}]自动绑定维度: {}", knowledge.getId(), resolvedDimensionIds);
|
|
|
|
|
+ } else if (dimensionIds != null) {
|
|
|
|
|
+ // 编辑页显式清空且无关键词命中时,尊重清空动作
|
|
|
|
|
+ bindDimensions(knowledge.getId(), Collections.emptyList());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ bindDimensions(knowledge.getId(), resolvedDimensionIds);
|
|
|
}
|
|
}
|
|
|
- if (tagIds != null) {
|
|
|
|
|
- bindTags(knowledge.getId(), tagIds);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 未显式选择标签时按标题/正文关键词自动匹配;显式传入空数组同样视为“未选择”。
|
|
|
|
|
+ if (resolvedTagIds.isEmpty()) {
|
|
|
|
|
+ List<DanKnowledgeTag> allTags = danKnowledgeTagMapper.selectList(null);
|
|
|
|
|
+ resolvedTagIds = new ArrayList<>(matchTagIds(matchTitle, matchContent, allTags));
|
|
|
|
|
+ if (!resolvedTagIds.isEmpty()) {
|
|
|
|
|
+ bindTags(knowledge.getId(), resolvedTagIds);
|
|
|
|
|
+ log.info("知识[{}]自动绑定标签: {}", knowledge.getId(), resolvedTagIds);
|
|
|
|
|
+ } else if (tagIds != null) {
|
|
|
|
|
+ // 编辑页显式清空且无关键词命中时,尊重清空动作
|
|
|
|
|
+ bindTags(knowledge.getId(), Collections.emptyList());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ bindTags(knowledge.getId(), resolvedTagIds);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ knowledge.setDimensionIds(resolvedDimensionIds);
|
|
|
|
|
+ knowledge.setTagIds(resolvedTagIds);
|
|
|
|
|
+ fillAssociationNames(knowledge);
|
|
|
return knowledge;
|
|
return knowledge;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -150,23 +191,153 @@ public class DanKnowledgeBaseService extends ServiceImpl<DanKnowledgeBaseMapper,
|
|
|
if (knowledge == null) {
|
|
if (knowledge == null) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- List<Long> dimensionIds = danKnowledgeBaseDimensionMapper.selectList(
|
|
|
|
|
- new LambdaQueryWrapper<DanKnowledgeBaseDimension>()
|
|
|
|
|
- .eq(DanKnowledgeBaseDimension::getKnowledgeId, id))
|
|
|
|
|
- .stream().map(DanKnowledgeBaseDimension::getDimensionId)
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
- List<Long> tagIds = danKnowledgeBaseTagMapper.selectList(
|
|
|
|
|
- new LambdaQueryWrapper<DanKnowledgeBaseTag>()
|
|
|
|
|
- .eq(DanKnowledgeBaseTag::getKnowledgeId, id))
|
|
|
|
|
- .stream().map(DanKnowledgeBaseTag::getTagId)
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
- Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
|
+ fillAssociations(Collections.singletonList(knowledge));
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
result.put("knowledge", knowledge);
|
|
result.put("knowledge", knowledge);
|
|
|
- result.put("dimensionIds", dimensionIds);
|
|
|
|
|
- result.put("tagIds", tagIds);
|
|
|
|
|
|
|
+ result.put("dimensionIds", knowledge.getDimensionIds());
|
|
|
|
|
+ result.put("tagIds", knowledge.getTagIds());
|
|
|
|
|
+ // 编辑弹窗直接读取 res.data,因此同时提供平铺字段;保留 knowledge 嵌套兼容旧调用方。
|
|
|
|
|
+ result.put("id", knowledge.getId());
|
|
|
|
|
+ result.put("title", knowledge.getTitle());
|
|
|
|
|
+ result.put("content", knowledge.getContent());
|
|
|
|
|
+ result.put("sort", knowledge.getSort());
|
|
|
|
|
+ result.put("status", knowledge.getStatus());
|
|
|
|
|
+ result.put("remark", knowledge.getRemark());
|
|
|
|
|
+ result.put("fileUrl", knowledge.getFileUrl());
|
|
|
|
|
+ result.put("fileName", knowledge.getFileName());
|
|
|
|
|
+ result.put("sourceUrl", knowledge.getSourceUrl());
|
|
|
|
|
+ result.put("sourceType", knowledge.getSourceType());
|
|
|
|
|
+ result.put("createdAt", knowledge.getCreatedAt());
|
|
|
|
|
+ result.put("updatedAt", knowledge.getUpdatedAt());
|
|
|
|
|
+ result.put("dimensionCodes", knowledge.getDimensionCodes());
|
|
|
|
|
+ result.put("tagNames", knowledge.getTagNames());
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private List<Long> normalizeIds(List<Long> ids) {
|
|
|
|
|
+ if (ids == null) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ return ids.stream()
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量补齐知识条目的标签/维度,供管理端列表、langgraph 同步复用。
|
|
|
|
|
+ * 使用批量查询避免列表/导出时的 N+1。
|
|
|
|
|
+ */
|
|
|
|
|
+ private void fillAssociations(List<DanKnowledgeBase> records) {
|
|
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Long> knowledgeIds = records.stream()
|
|
|
|
|
+ .map(DanKnowledgeBase::getId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (knowledgeIds.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<DanKnowledgeBaseTag> tagRelations = danKnowledgeBaseTagMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<DanKnowledgeBaseTag>()
|
|
|
|
|
+ .in(DanKnowledgeBaseTag::getKnowledgeId, knowledgeIds));
|
|
|
|
|
+ List<DanKnowledgeBaseDimension> dimRelations = danKnowledgeBaseDimensionMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<DanKnowledgeBaseDimension>()
|
|
|
|
|
+ .in(DanKnowledgeBaseDimension::getKnowledgeId, knowledgeIds));
|
|
|
|
|
+ if (tagRelations == null) tagRelations = Collections.emptyList();
|
|
|
|
|
+ if (dimRelations == null) dimRelations = Collections.emptyList();
|
|
|
|
|
+
|
|
|
|
|
+ Map<Long, List<Long>> tagIdsByKnowledge = new HashMap<>();
|
|
|
|
|
+ Set<Long> allTagIds = new LinkedHashSet<>();
|
|
|
|
|
+ for (DanKnowledgeBaseTag rel : tagRelations) {
|
|
|
|
|
+ tagIdsByKnowledge.computeIfAbsent(rel.getKnowledgeId(), k -> new ArrayList<>())
|
|
|
|
|
+ .add(rel.getTagId());
|
|
|
|
|
+ allTagIds.add(rel.getTagId());
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Long, List<Long>> dimIdsByKnowledge = new HashMap<>();
|
|
|
|
|
+ Set<Long> allDimIds = new LinkedHashSet<>();
|
|
|
|
|
+ for (DanKnowledgeBaseDimension rel : dimRelations) {
|
|
|
|
|
+ dimIdsByKnowledge.computeIfAbsent(rel.getKnowledgeId(), k -> new ArrayList<>())
|
|
|
|
|
+ .add(rel.getDimensionId());
|
|
|
|
|
+ allDimIds.add(rel.getDimensionId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<Long, String> tagNamesById = new HashMap<>();
|
|
|
|
|
+ if (!allTagIds.isEmpty()) {
|
|
|
|
|
+ List<DanKnowledgeTag> tags = danKnowledgeTagMapper.selectBatchIds(allTagIds);
|
|
|
|
|
+ if (tags != null) {
|
|
|
|
|
+ for (DanKnowledgeTag tag : tags) {
|
|
|
|
|
+ if (tag.getId() != null && tag.getTagName() != null) {
|
|
|
|
|
+ tagNamesById.put(tag.getId(), tag.getTagName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Long, String> dimCodesById = new HashMap<>();
|
|
|
|
|
+ if (!allDimIds.isEmpty()) {
|
|
|
|
|
+ List<ProductDimensionConfig> configs = productDimensionConfigMapper.selectBatchIds(allDimIds);
|
|
|
|
|
+ if (configs != null) {
|
|
|
|
|
+ for (ProductDimensionConfig config : configs) {
|
|
|
|
|
+ if (config.getId() != null && config.getDimensionCode() != null) {
|
|
|
|
|
+ dimCodesById.put(config.getId(), config.getDimensionCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (DanKnowledgeBase record : records) {
|
|
|
|
|
+ List<Long> tids = tagIdsByKnowledge.getOrDefault(record.getId(), Collections.emptyList());
|
|
|
|
|
+ List<Long> dids = dimIdsByKnowledge.getOrDefault(record.getId(), Collections.emptyList());
|
|
|
|
|
+ record.setTagIds(tids);
|
|
|
|
|
+ record.setDimensionIds(dids);
|
|
|
|
|
+ record.setTagNames(tids.stream().map(tagNamesById::get)
|
|
|
|
|
+ .filter(Objects::nonNull).collect(Collectors.toList()));
|
|
|
|
|
+ record.setDimensionCodes(dids.stream().map(dimCodesById::get)
|
|
|
|
|
+ .filter(Objects::nonNull).collect(Collectors.toList()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void fillAssociationNames(DanKnowledgeBase knowledge) {
|
|
|
|
|
+ if (knowledge == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Long> tagIds = knowledge.getTagIds();
|
|
|
|
|
+ if (tagIds != null && !tagIds.isEmpty()) {
|
|
|
|
|
+ List<DanKnowledgeTag> tags = danKnowledgeTagMapper.selectBatchIds(tagIds);
|
|
|
|
|
+ Map<Long, String> names = new HashMap<>();
|
|
|
|
|
+ if (tags != null) {
|
|
|
|
|
+ for (DanKnowledgeTag tag : tags) {
|
|
|
|
|
+ if (tag.getId() != null && tag.getTagName() != null) {
|
|
|
|
|
+ names.put(tag.getId(), tag.getTagName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ knowledge.setTagNames(tagIds.stream().map(names::get)
|
|
|
|
|
+ .filter(Objects::nonNull).collect(Collectors.toList()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ knowledge.setTagNames(Collections.emptyList());
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Long> dimIds = knowledge.getDimensionIds();
|
|
|
|
|
+ if (dimIds != null && !dimIds.isEmpty()) {
|
|
|
|
|
+ List<ProductDimensionConfig> configs = productDimensionConfigMapper.selectBatchIds(dimIds);
|
|
|
|
|
+ Map<Long, String> codes = new HashMap<>();
|
|
|
|
|
+ if (configs != null) {
|
|
|
|
|
+ for (ProductDimensionConfig config : configs) {
|
|
|
|
|
+ if (config.getId() != null && config.getDimensionCode() != null) {
|
|
|
|
|
+ codes.put(config.getId(), config.getDimensionCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ knowledge.setDimensionCodes(dimIds.stream().map(codes::get)
|
|
|
|
|
+ .filter(Objects::nonNull).collect(Collectors.toList()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ knowledge.setDimensionCodes(Collections.emptyList());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public boolean deleteKnowledge(Long id) {
|
|
public boolean deleteKnowledge(Long id) {
|
|
|
// 关联表可能未建/已缺失,删除主记录不允许被关联表异常阻断
|
|
// 关联表可能未建/已缺失,删除主记录不允许被关联表异常阻断
|
|
@@ -273,6 +444,9 @@ public class DanKnowledgeBaseService extends ServiceImpl<DanKnowledgeBaseMapper,
|
|
|
/** 标签关键词匹配:2 字词仅标题命中;>=3 字词标题/正文均可;组合标签按分隔符拆分 */
|
|
/** 标签关键词匹配:2 字词仅标题命中;>=3 字词标题/正文均可;组合标签按分隔符拆分 */
|
|
|
public static Set<Long> matchTagIds(String title, String content, List<DanKnowledgeTag> tags) {
|
|
public static Set<Long> matchTagIds(String title, String content, List<DanKnowledgeTag> tags) {
|
|
|
Set<Long> hit = new LinkedHashSet<>();
|
|
Set<Long> hit = new LinkedHashSet<>();
|
|
|
|
|
+ if (tags == null || tags.isEmpty()) {
|
|
|
|
|
+ return hit;
|
|
|
|
|
+ }
|
|
|
String t = title == null ? "" : title;
|
|
String t = title == null ? "" : title;
|
|
|
String c = content == null ? "" : content;
|
|
String c = content == null ? "" : content;
|
|
|
List<DanKnowledgeTag> sorted = new ArrayList<>(tags);
|
|
List<DanKnowledgeTag> sorted = new ArrayList<>(tags);
|