diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java new file mode 100644 index 0000000..4e41020 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java @@ -0,0 +1,340 @@ +package com.datamate.datamanagement.application; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datamate.common.infrastructure.exception.BusinessAssert; +import com.datamate.common.infrastructure.exception.BusinessException; +import com.datamate.common.infrastructure.exception.CommonErrorCode; +import com.datamate.common.infrastructure.exception.SystemErrorCode; +import com.datamate.common.interfaces.PagedResponse; +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import com.datamate.datamanagement.domain.model.dataset.Dataset; +import com.datamate.datamanagement.domain.model.dataset.DatasetFile; +import com.datamate.datamanagement.domain.model.dataset.Tag; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode; +import com.datamate.datamanagement.infrastructure.persistence.mapper.TagMapper; +import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository; +import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetRepository; +import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository; +import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeSetRepository; +import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeItemRequest; +import com.datamate.datamanagement.interfaces.dto.ImportKnowledgeItemsRequest; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse; +import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeItemRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * 知识条目应用服务 + */ +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class KnowledgeItemApplicationService { + private static final Set SUPPORTED_TEXT_EXTENSIONS = Set.of("txt", "md", "markdown"); + + private final KnowledgeItemRepository knowledgeItemRepository; + private final KnowledgeSetRepository knowledgeSetRepository; + private final DatasetRepository datasetRepository; + private final DatasetFileRepository datasetFileRepository; + private final TagMapper tagMapper; + + public KnowledgeItem createKnowledgeItem(String setId, CreateKnowledgeItemRequest request) { + KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), + DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); + + KnowledgeItem knowledgeItem = KnowledgeConverter.INSTANCE.convertToKnowledgeItem(request); + knowledgeItem.setId(UUID.randomUUID().toString()); + knowledgeItem.setSetId(setId); + knowledgeItem.setSourceType(KnowledgeSourceType.MANUAL); + if (knowledgeItem.getStatus() == null) { + knowledgeItem.setStatus(KnowledgeStatusType.DRAFT); + } + + applyDefaultsFromSet(knowledgeItem, knowledgeSet, request.getTags(), request.getDomain(), + request.getBusinessLine(), request.getOwner(), request.getSensitivity(), + request.getValidFrom(), request.getValidTo(), request.getMetadata()); + + validateEffectivePeriod(knowledgeItem.getValidFrom(), knowledgeItem.getValidTo()); + validatePublishable(knowledgeItem); + + knowledgeItemRepository.save(knowledgeItem); + return knowledgeItem; + } + + public KnowledgeItem updateKnowledgeItem(String setId, String itemId, UpdateKnowledgeItemRequest request) { + KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); + KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId); + BusinessAssert.notNull(knowledgeItem, DataManagementErrorCode.KNOWLEDGE_ITEM_NOT_FOUND); + BusinessAssert.isTrue(Objects.equals(knowledgeItem.getSetId(), setId), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeItem.getStatus()), + DataManagementErrorCode.KNOWLEDGE_ITEM_STATUS_ERROR); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), + DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); + + if (request.getTitle() != null) { + BusinessAssert.isTrue(StringUtils.isNotBlank(request.getTitle()), CommonErrorCode.PARAM_ERROR); + knowledgeItem.setTitle(request.getTitle()); + } + if (request.getContent() != null) { + BusinessAssert.isTrue(StringUtils.isNotBlank(request.getContent()), CommonErrorCode.PARAM_ERROR); + knowledgeItem.setContent(request.getContent()); + } + if (request.getContentType() != null) { + knowledgeItem.setContentType(request.getContentType()); + } + if (request.getStatus() != null) { + knowledgeItem.setStatus(request.getStatus()); + } + if (request.getTags() != null) { + knowledgeItem.setTags(processTagNames(request.getTags())); + } + if (request.getDomain() != null) { + knowledgeItem.setDomain(request.getDomain()); + } + if (request.getBusinessLine() != null) { + knowledgeItem.setBusinessLine(request.getBusinessLine()); + } + if (request.getOwner() != null) { + knowledgeItem.setOwner(request.getOwner()); + } + if (request.getValidFrom() != null || request.getValidTo() != null) { + knowledgeItem.setValidFrom(request.getValidFrom()); + knowledgeItem.setValidTo(request.getValidTo()); + } + if (request.getSensitivity() != null) { + knowledgeItem.setSensitivity(request.getSensitivity()); + } + if (request.getMetadata() != null) { + knowledgeItem.setMetadata(request.getMetadata()); + } + + validateEffectivePeriod(knowledgeItem.getValidFrom(), knowledgeItem.getValidTo()); + validatePublishable(knowledgeItem); + + knowledgeItemRepository.updateById(knowledgeItem); + return knowledgeItem; + } + + public void deleteKnowledgeItem(String setId, String itemId) { + KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId); + BusinessAssert.notNull(knowledgeItem, DataManagementErrorCode.KNOWLEDGE_ITEM_NOT_FOUND); + BusinessAssert.isTrue(Objects.equals(knowledgeItem.getSetId(), setId), CommonErrorCode.PARAM_ERROR); + knowledgeItemRepository.removeById(itemId); + } + + @Transactional(readOnly = true) + public KnowledgeItem getKnowledgeItem(String setId, String itemId) { + KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId); + BusinessAssert.notNull(knowledgeItem, DataManagementErrorCode.KNOWLEDGE_ITEM_NOT_FOUND); + BusinessAssert.isTrue(Objects.equals(knowledgeItem.getSetId(), setId), CommonErrorCode.PARAM_ERROR); + return knowledgeItem; + } + + @Transactional(readOnly = true) + public PagedResponse getKnowledgeItems(String setId, KnowledgeItemPagingQuery query) { + query.setSetId(setId); + IPage page = new Page<>(query.getPage(), query.getSize()); + page = knowledgeItemRepository.findByCriteria(page, query); + List responses = KnowledgeConverter.INSTANCE.convertItemResponses(page.getRecords()); + return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages()); + } + + public List importKnowledgeItems(String setId, ImportKnowledgeItemsRequest request) { + KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), + DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); + Dataset dataset = datasetRepository.getById(request.getDatasetId()); + BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND); + + List items = new ArrayList<>(); + for (String fileId : request.getFileIds()) { + DatasetFile datasetFile = datasetFileRepository.getById(fileId); + BusinessAssert.notNull(datasetFile, DataManagementErrorCode.DATASET_FILE_NOT_FOUND); + BusinessAssert.isTrue(Objects.equals(datasetFile.getDatasetId(), dataset.getId()), CommonErrorCode.PARAM_ERROR); + + KnowledgeContentType contentType = resolveContentType(datasetFile); + String content = readTextContent(datasetFile, dataset); + + KnowledgeItem knowledgeItem = new KnowledgeItem(); + knowledgeItem.setId(UUID.randomUUID().toString()); + knowledgeItem.setSetId(setId); + knowledgeItem.setTitle(stripExtension(datasetFile.getFileName())); + knowledgeItem.setContent(content); + knowledgeItem.setContentType(contentType); + knowledgeItem.setSourceType(KnowledgeSourceType.DATASET_FILE); + knowledgeItem.setSourceDatasetId(dataset.getId()); + knowledgeItem.setSourceFileId(datasetFile.getId()); + knowledgeItem.setStatus(request.getStatus() == null ? KnowledgeStatusType.DRAFT : request.getStatus()); + + applyDefaultsFromSet(knowledgeItem, knowledgeSet, request.getTags(), request.getDomain(), + request.getBusinessLine(), request.getOwner(), request.getSensitivity(), + request.getValidFrom(), request.getValidTo(), request.getMetadata()); + + validateEffectivePeriod(knowledgeItem.getValidFrom(), knowledgeItem.getValidTo()); + validatePublishable(knowledgeItem); + + items.add(knowledgeItem); + } + + if (CollectionUtils.isNotEmpty(items)) { + knowledgeItemRepository.saveBatch(items, items.size()); + } + return items; + } + + private KnowledgeSet requireKnowledgeSet(String setId) { + KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); + BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + return knowledgeSet; + } + + private KnowledgeContentType resolveContentType(DatasetFile datasetFile) { + String extension = getFileExtension(datasetFile); + if (StringUtils.isBlank(extension)) { + throw BusinessException.of(DataManagementErrorCode.KNOWLEDGE_ITEM_SOURCE_NOT_SUPPORTED); + } + String normalized = extension.toLowerCase(); + if (!SUPPORTED_TEXT_EXTENSIONS.contains(normalized)) { + throw BusinessException.of(DataManagementErrorCode.KNOWLEDGE_ITEM_SOURCE_NOT_SUPPORTED); + } + return normalized.equals("md") || normalized.equals("markdown") + ? KnowledgeContentType.MARKDOWN + : KnowledgeContentType.TEXT; + } + + private String getFileExtension(DatasetFile datasetFile) { + String fileName = datasetFile.getFileName(); + if (StringUtils.isNotBlank(fileName) && fileName.contains(".")) { + return fileName.substring(fileName.lastIndexOf('.') + 1); + } + String fileType = datasetFile.getFileType(); + if (StringUtils.isNotBlank(fileType) && fileType.startsWith(".")) { + return fileType.substring(1); + } + return fileType; + } + + private String readTextContent(DatasetFile datasetFile, Dataset dataset) { + Path filePath = Paths.get(datasetFile.getFilePath()).normalize(); + Path datasetPath = Paths.get(dataset.getPath()).normalize(); + BusinessAssert.isTrue(filePath.startsWith(datasetPath), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(Files.exists(filePath) && Files.isRegularFile(filePath), CommonErrorCode.PARAM_ERROR); + try { + return Files.readString(filePath, StandardCharsets.UTF_8); + } catch (IOException e) { + log.error("read knowledge content error, fileId: {}", datasetFile.getId(), e); + throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); + } + } + + private String stripExtension(String fileName) { + if (StringUtils.isBlank(fileName)) { + return "未命名"; + } + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex <= 0) { + return fileName; + } + return fileName.substring(0, dotIndex); + } + + private void applyDefaultsFromSet(KnowledgeItem knowledgeItem, + KnowledgeSet knowledgeSet, + List tags, + String domain, + String businessLine, + String owner, + String sensitivity, + LocalDate validFrom, + LocalDate validTo, + String metadata) { + if (tags != null) { + knowledgeItem.setTags(processTagNames(tags)); + } else if (knowledgeSet.getTags() != null) { + knowledgeItem.setTags(new HashSet<>(knowledgeSet.getTags())); + } else { + knowledgeItem.setTags(new HashSet<>()); + } + knowledgeItem.setDomain(StringUtils.isNotBlank(domain) ? domain : knowledgeSet.getDomain()); + knowledgeItem.setBusinessLine(StringUtils.isNotBlank(businessLine) ? businessLine : knowledgeSet.getBusinessLine()); + knowledgeItem.setOwner(StringUtils.isNotBlank(owner) ? owner : knowledgeSet.getOwner()); + knowledgeItem.setSensitivity(StringUtils.isNotBlank(sensitivity) ? sensitivity : knowledgeSet.getSensitivity()); + knowledgeItem.setValidFrom(validFrom != null ? validFrom : knowledgeSet.getValidFrom()); + knowledgeItem.setValidTo(validTo != null ? validTo : knowledgeSet.getValidTo()); + knowledgeItem.setMetadata(metadata != null ? metadata : knowledgeSet.getMetadata()); + } + + private boolean isReadOnlyStatus(KnowledgeStatusType status) { + return status == KnowledgeStatusType.ARCHIVED || status == KnowledgeStatusType.DEPRECATED; + } + + private void validatePublishable(KnowledgeItem knowledgeItem) { + if (knowledgeItem.getStatus() != KnowledgeStatusType.PUBLISHED) { + return; + } + BusinessAssert.isTrue(StringUtils.isNotBlank(knowledgeItem.getDomain()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.isNotBlank(knowledgeItem.getBusinessLine()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.isNotBlank(knowledgeItem.getOwner()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.isNotBlank(knowledgeItem.getSensitivity()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeItem.getSourceType(), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeItem.getValidFrom(), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeItem.getValidTo(), CommonErrorCode.PARAM_ERROR); + } + + private void validateEffectivePeriod(LocalDate validFrom, LocalDate validTo) { + if (validFrom == null || validTo == null) { + return; + } + BusinessAssert.isTrue(!validFrom.isAfter(validTo), CommonErrorCode.PARAM_ERROR); + } + + private Set processTagNames(List tagNames) { + if (CollectionUtils.isEmpty(tagNames)) { + return new HashSet<>(); + } + Set tags = new HashSet<>(); + for (String tagName : tagNames) { + if (StringUtils.isBlank(tagName)) { + continue; + } + Tag tag = tagMapper.findByName(tagName); + if (tag == null) { + Tag newTag = new Tag(tagName, null, null, "#007bff"); + newTag.setUsageCount(0L); + newTag.setId(UUID.randomUUID().toString()); + tagMapper.insert(newTag); + tag = newTag; + } + tag.setUsageCount(tag.getUsageCount() == null ? 1L : tag.getUsageCount() + 1); + tagMapper.updateUsageCount(tag.getId(), tag.getUsageCount()); + tags.add(tag); + } + return tags; + } +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java new file mode 100644 index 0000000..259e765 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java @@ -0,0 +1,187 @@ +package com.datamate.datamanagement.application; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datamate.common.infrastructure.exception.BusinessAssert; +import com.datamate.common.infrastructure.exception.CommonErrorCode; +import com.datamate.common.interfaces.PagedResponse; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import com.datamate.datamanagement.domain.model.dataset.Tag; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode; +import com.datamate.datamanagement.infrastructure.persistence.mapper.TagMapper; +import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeSetRepository; +import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeSetRequest; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetResponse; +import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeSetRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * 知识集应用服务 + */ +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class KnowledgeSetApplicationService { + private final KnowledgeSetRepository knowledgeSetRepository; + private final TagMapper tagMapper; + + public KnowledgeSet createKnowledgeSet(CreateKnowledgeSetRequest request) { + BusinessAssert.isTrue(knowledgeSetRepository.findByName(request.getName()) == null, + DataManagementErrorCode.KNOWLEDGE_SET_ALREADY_EXISTS); + + KnowledgeSet knowledgeSet = KnowledgeConverter.INSTANCE.convertToKnowledgeSet(request); + knowledgeSet.setId(UUID.randomUUID().toString()); + if (knowledgeSet.getStatus() == null) { + knowledgeSet.setStatus(KnowledgeStatusType.DRAFT); + } + + Set tags = processTagNames(request.getTags()); + knowledgeSet.setTags(tags); + + validateEffectivePeriod(knowledgeSet.getValidFrom(), knowledgeSet.getValidTo()); + validatePublishable(knowledgeSet); + + knowledgeSetRepository.save(knowledgeSet); + return knowledgeSet; + } + + public KnowledgeSet updateKnowledgeSet(String setId, UpdateKnowledgeSetRequest request) { + KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); + BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), + DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); + + if (StringUtils.hasText(request.getName()) && !Objects.equals(request.getName(), knowledgeSet.getName())) { + KnowledgeSet exist = knowledgeSetRepository.findByName(request.getName()); + BusinessAssert.isTrue(exist == null || Objects.equals(exist.getId(), setId), + DataManagementErrorCode.KNOWLEDGE_SET_ALREADY_EXISTS); + knowledgeSet.setName(request.getName()); + } + + if (request.getDescription() != null) { + knowledgeSet.setDescription(request.getDescription()); + } + + if (request.getTags() != null) { + knowledgeSet.setTags(processTagNames(request.getTags())); + } + + if (request.getStatus() != null) { + knowledgeSet.setStatus(request.getStatus()); + } + + if (request.getDomain() != null) { + knowledgeSet.setDomain(request.getDomain()); + } + if (request.getBusinessLine() != null) { + knowledgeSet.setBusinessLine(request.getBusinessLine()); + } + if (request.getOwner() != null) { + knowledgeSet.setOwner(request.getOwner()); + } + if (request.getValidFrom() != null || request.getValidTo() != null) { + knowledgeSet.setValidFrom(request.getValidFrom()); + knowledgeSet.setValidTo(request.getValidTo()); + } + if (request.getSourceType() != null) { + knowledgeSet.setSourceType(request.getSourceType()); + } + if (request.getSensitivity() != null) { + knowledgeSet.setSensitivity(request.getSensitivity()); + } + if (request.getMetadata() != null) { + knowledgeSet.setMetadata(request.getMetadata()); + } + + validateEffectivePeriod(knowledgeSet.getValidFrom(), knowledgeSet.getValidTo()); + validatePublishable(knowledgeSet); + + knowledgeSetRepository.updateById(knowledgeSet); + return knowledgeSet; + } + + public void deleteKnowledgeSet(String setId) { + KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); + BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + knowledgeSetRepository.removeById(setId); + } + + @Transactional(readOnly = true) + public KnowledgeSet getKnowledgeSet(String setId) { + KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); + BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + return knowledgeSet; + } + + @Transactional(readOnly = true) + public PagedResponse getKnowledgeSets(KnowledgeSetPagingQuery query) { + IPage page = new Page<>(query.getPage(), query.getSize()); + page = knowledgeSetRepository.findByCriteria(page, query); + List responses = KnowledgeConverter.INSTANCE.convertSetResponses(page.getRecords()); + return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages()); + } + + private boolean isReadOnlyStatus(KnowledgeStatusType status) { + return status == KnowledgeStatusType.ARCHIVED || status == KnowledgeStatusType.DEPRECATED; + } + + private void validatePublishable(KnowledgeSet knowledgeSet) { + if (knowledgeSet.getStatus() != KnowledgeStatusType.PUBLISHED) { + return; + } + BusinessAssert.isTrue(StringUtils.hasText(knowledgeSet.getDomain()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.hasText(knowledgeSet.getBusinessLine()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.hasText(knowledgeSet.getOwner()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(StringUtils.hasText(knowledgeSet.getSensitivity()), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeSet.getSourceType(), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeSet.getValidFrom(), CommonErrorCode.PARAM_ERROR); + BusinessAssert.notNull(knowledgeSet.getValidTo(), CommonErrorCode.PARAM_ERROR); + } + + private void validateEffectivePeriod(LocalDate validFrom, LocalDate validTo) { + if (validFrom == null || validTo == null) { + return; + } + BusinessAssert.isTrue(!validFrom.isAfter(validTo), CommonErrorCode.PARAM_ERROR); + } + + private Set processTagNames(List tagNames) { + if (CollectionUtils.isEmpty(tagNames)) { + return new HashSet<>(); + } + Set tags = new HashSet<>(); + for (String tagName : tagNames) { + if (!StringUtils.hasText(tagName)) { + continue; + } + Tag tag = tagMapper.findByName(tagName); + if (tag == null) { + Tag newTag = new Tag(tagName, null, null, "#007bff"); + newTag.setUsageCount(0L); + newTag.setId(UUID.randomUUID().toString()); + tagMapper.insert(newTag); + tag = newTag; + } + tag.setUsageCount(tag.getUsageCount() == null ? 1L : tag.getUsageCount() + 1); + tagMapper.updateUsageCount(tag.getId(), tag.getUsageCount()); + tags.add(tag); + } + return tags; + } +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java new file mode 100644 index 0000000..bf19b3b --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java @@ -0,0 +1,15 @@ +package com.datamate.datamanagement.common.enums; + +/** + * 知识内容类型 + */ +public enum KnowledgeContentType { + /** + * 纯文本 + */ + TEXT, + /** + * Markdown + */ + MARKDOWN +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java new file mode 100644 index 0000000..68408bf --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java @@ -0,0 +1,15 @@ +package com.datamate.datamanagement.common.enums; + +/** + * 知识来源类型 + */ +public enum KnowledgeSourceType { + /** + * 手工录入 + */ + MANUAL, + /** + * 数据集文件导入 + */ + DATASET_FILE +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeStatusType.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeStatusType.java new file mode 100644 index 0000000..09677ae --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeStatusType.java @@ -0,0 +1,23 @@ +package com.datamate.datamanagement.common.enums; + +/** + * 知识状态类型 + */ +public enum KnowledgeStatusType { + /** + * 草稿状态 + */ + DRAFT, + /** + * 已发布状态 + */ + PUBLISHED, + /** + * 已归档状态 + */ + ARCHIVED, + /** + * 已废弃状态 + */ + DEPRECATED +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeItem.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeItem.java new file mode 100644 index 0000000..eccddbe --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeItem.java @@ -0,0 +1,90 @@ +package com.datamate.datamanagement.domain.model.knowledge; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.datamate.common.domain.model.base.BaseEntity; +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import com.datamate.datamanagement.domain.model.dataset.Tag; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.HashSet; + +/** + * 知识条目实体(与数据库表 t_dm_knowledge_items 对齐) + */ +@Getter +@Setter +@TableName(value = "t_dm_knowledge_items", autoResultMap = true) +public class KnowledgeItem extends BaseEntity { + /** + * 所属知识集ID + */ + private String setId; + /** + * 标题 + */ + private String title; + /** + * 内容 + */ + private String content; + /** + * 内容类型 + */ + private KnowledgeContentType contentType; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 领域 + */ + private String domain; + /** + * 业务线 + */ + private String businessLine; + /** + * 负责人 + */ + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 敏感级别 + */ + private String sensitivity; + /** + * 来源数据集ID + */ + private String sourceDatasetId; + /** + * 来源文件ID + */ + private String sourceFileId; + /** + * 标签列表 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Collection tags = new HashSet<>(); + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeSet.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeSet.java new file mode 100644 index 0000000..5b195d4 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/domain/model/knowledge/KnowledgeSet.java @@ -0,0 +1,73 @@ +package com.datamate.datamanagement.domain.model.knowledge; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.datamate.common.domain.model.base.BaseEntity; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import com.datamate.datamanagement.domain.model.dataset.Tag; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.HashSet; + +/** + * 知识集实体(与数据库表 t_dm_knowledge_sets 对齐) + */ +@Getter +@Setter +@TableName(value = "t_dm_knowledge_sets", autoResultMap = true) +public class KnowledgeSet extends BaseEntity { + /** + * 知识集名称 + */ + private String name; + /** + * 知识集描述 + */ + private String description; + /** + * 知识集状态 + */ + private KnowledgeStatusType status; + /** + * 领域 + */ + private String domain; + /** + * 业务线 + */ + private String businessLine; + /** + * 负责人 + */ + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 敏感级别 + */ + private String sensitivity; + /** + * 标签列表 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Collection tags = new HashSet<>(); + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/exception/DataManagementErrorCode.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/exception/DataManagementErrorCode.java index 63c7afb..8312975 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/exception/DataManagementErrorCode.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/exception/DataManagementErrorCode.java @@ -44,7 +44,35 @@ public enum DataManagementErrorCode implements ErrorCode { /** * 存在子数据集 */ - DATASET_HAS_CHILDREN("data_management.0008", "存在子数据集,禁止删除或移动"); + DATASET_HAS_CHILDREN("data_management.0008", "存在子数据集,禁止删除或移动"), + /** + * 数据集文件不存在 + */ + DATASET_FILE_NOT_FOUND("data_management.0009", "数据集文件不存在"), + /** + * 知识集不存在 + */ + KNOWLEDGE_SET_NOT_FOUND("data_management.0101", "知识集不存在"), + /** + * 知识集已存在 + */ + KNOWLEDGE_SET_ALREADY_EXISTS("data_management.0102", "知识集已存在"), + /** + * 知识集状态不可修改 + */ + KNOWLEDGE_SET_STATUS_ERROR("data_management.0103", "知识集状态不可修改"), + /** + * 知识条目不存在 + */ + KNOWLEDGE_ITEM_NOT_FOUND("data_management.0104", "知识条目不存在"), + /** + * 知识条目状态不可修改 + */ + KNOWLEDGE_ITEM_STATUS_ERROR("data_management.0105", "知识条目状态不可修改"), + /** + * 知识条目来源不支持 + */ + KNOWLEDGE_ITEM_SOURCE_NOT_SUPPORTED("data_management.0106", "知识条目来源不支持"); private final String code; private final String message; diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeItemMapper.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeItemMapper.java new file mode 100644 index 0000000..c354ebd --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeItemMapper.java @@ -0,0 +1,9 @@ +package com.datamate.datamanagement.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KnowledgeItemMapper extends BaseMapper { +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeSetMapper.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeSetMapper.java new file mode 100644 index 0000000..82c580c --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/KnowledgeSetMapper.java @@ -0,0 +1,9 @@ +package com.datamate.datamanagement.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KnowledgeSetMapper extends BaseMapper { +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeItemRepository.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeItemRepository.java new file mode 100644 index 0000000..244eab8 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeItemRepository.java @@ -0,0 +1,15 @@ +package com.datamate.datamanagement.infrastructure.persistence.repository; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.repository.IRepository; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery; + +/** + * 知识条目仓储接口 + */ +public interface KnowledgeItemRepository extends IRepository { + IPage findByCriteria(IPage page, KnowledgeItemPagingQuery query); + + long countBySetId(String setId); +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java new file mode 100644 index 0000000..7d36db4 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java @@ -0,0 +1,15 @@ +package com.datamate.datamanagement.infrastructure.persistence.repository; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.repository.IRepository; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery; + +/** + * 知识集仓储接口 + */ +public interface KnowledgeSetRepository extends IRepository { + KnowledgeSet findByName(String name); + + IPage findByCriteria(IPage page, KnowledgeSetPagingQuery query); +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeItemRepositoryImpl.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeItemRepositoryImpl.java new file mode 100644 index 0000000..68312a4 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeItemRepositoryImpl.java @@ -0,0 +1,60 @@ +package com.datamate.datamanagement.infrastructure.persistence.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.repository.CrudRepository; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import com.datamate.datamanagement.infrastructure.persistence.mapper.KnowledgeItemMapper; +import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Repository; + +/** + * 知识条目仓储实现类 + */ +@Repository +@RequiredArgsConstructor +public class KnowledgeItemRepositoryImpl extends CrudRepository implements KnowledgeItemRepository { + private final KnowledgeItemMapper knowledgeItemMapper; + + @Override + public IPage findByCriteria(IPage page, KnowledgeItemPagingQuery query) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(query.getSetId()), KnowledgeItem::getSetId, query.getSetId()) + .eq(query.getStatus() != null, KnowledgeItem::getStatus, query.getStatus()) + .eq(query.getContentType() != null, KnowledgeItem::getContentType, query.getContentType()) + .eq(StringUtils.isNotBlank(query.getDomain()), KnowledgeItem::getDomain, query.getDomain()) + .eq(StringUtils.isNotBlank(query.getBusinessLine()), KnowledgeItem::getBusinessLine, query.getBusinessLine()) + .eq(StringUtils.isNotBlank(query.getOwner()), KnowledgeItem::getOwner, query.getOwner()) + .eq(StringUtils.isNotBlank(query.getSensitivity()), KnowledgeItem::getSensitivity, query.getSensitivity()) + .eq(query.getSourceType() != null, KnowledgeItem::getSourceType, query.getSourceType()) + .ge(query.getValidFrom() != null, KnowledgeItem::getValidFrom, query.getValidFrom()) + .le(query.getValidTo() != null, KnowledgeItem::getValidTo, query.getValidTo()); + + if (StringUtils.isNotBlank(query.getKeyword())) { + wrapper.and(w -> w.like(KnowledgeItem::getTitle, query.getKeyword()) + .or() + .like(KnowledgeItem::getContent, query.getKeyword())); + } + + for (String tagName : query.getTags()) { + wrapper.and(w -> + w.apply("tags IS NOT NULL " + + "AND JSON_VALID(tags) = 1 " + + "AND JSON_LENGTH(tags) > 0 " + + "AND JSON_SEARCH(tags, 'one', {0}, NULL, '$[*].name') IS NOT NULL", tagName) + ); + } + + wrapper.orderByDesc(KnowledgeItem::getCreatedAt); + return knowledgeItemMapper.selectPage(page, wrapper); + } + + @Override + public long countBySetId(String setId) { + return knowledgeItemMapper.selectCount(new LambdaQueryWrapper() + .eq(KnowledgeItem::getSetId, setId)); + } +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java new file mode 100644 index 0000000..896e61c --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java @@ -0,0 +1,57 @@ +package com.datamate.datamanagement.infrastructure.persistence.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.repository.CrudRepository; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.infrastructure.persistence.mapper.KnowledgeSetMapper; +import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeSetRepository; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Repository; + +/** + * 知识集仓储实现类 + */ +@Repository +@RequiredArgsConstructor +public class KnowledgeSetRepositoryImpl extends CrudRepository implements KnowledgeSetRepository { + private final KnowledgeSetMapper knowledgeSetMapper; + + @Override + public KnowledgeSet findByName(String name) { + return knowledgeSetMapper.selectOne(new LambdaQueryWrapper().eq(KnowledgeSet::getName, name)); + } + + @Override + public IPage findByCriteria(IPage page, KnowledgeSetPagingQuery query) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(query.getStatus() != null, KnowledgeSet::getStatus, query.getStatus()) + .eq(StringUtils.isNotBlank(query.getDomain()), KnowledgeSet::getDomain, query.getDomain()) + .eq(StringUtils.isNotBlank(query.getBusinessLine()), KnowledgeSet::getBusinessLine, query.getBusinessLine()) + .eq(StringUtils.isNotBlank(query.getOwner()), KnowledgeSet::getOwner, query.getOwner()) + .eq(StringUtils.isNotBlank(query.getSensitivity()), KnowledgeSet::getSensitivity, query.getSensitivity()) + .eq(query.getSourceType() != null, KnowledgeSet::getSourceType, query.getSourceType()) + .ge(query.getValidFrom() != null, KnowledgeSet::getValidFrom, query.getValidFrom()) + .le(query.getValidTo() != null, KnowledgeSet::getValidTo, query.getValidTo()); + + if (StringUtils.isNotBlank(query.getKeyword())) { + wrapper.and(w -> w.like(KnowledgeSet::getName, query.getKeyword()) + .or() + .like(KnowledgeSet::getDescription, query.getKeyword())); + } + + for (String tagName : query.getTags()) { + wrapper.and(w -> + w.apply("tags IS NOT NULL " + + "AND JSON_VALID(tags) = 1 " + + "AND JSON_LENGTH(tags) > 0 " + + "AND JSON_SEARCH(tags, 'one', {0}, NULL, '$[*].name') IS NOT NULL", tagName) + ); + } + + wrapper.orderByDesc(KnowledgeSet::getCreatedAt); + return knowledgeSetMapper.selectPage(page, wrapper); + } +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/converter/KnowledgeConverter.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/converter/KnowledgeConverter.java new file mode 100644 index 0000000..26a4a86 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/converter/KnowledgeConverter.java @@ -0,0 +1,35 @@ +package com.datamate.datamanagement.interfaces.converter; + +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeItemRequest; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeSetRequest; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 知识管理转换器 + */ +@Mapper +public interface KnowledgeConverter { + KnowledgeConverter INSTANCE = Mappers.getMapper(KnowledgeConverter.class); + + @Mapping(target = "tags", ignore = true) + KnowledgeSet convertToKnowledgeSet(CreateKnowledgeSetRequest request); + + @Mapping(target = "tags", ignore = true) + KnowledgeItem convertToKnowledgeItem(CreateKnowledgeItemRequest request); + + KnowledgeSetResponse convertToResponse(KnowledgeSet knowledgeSet); + + List convertSetResponses(List sets); + + KnowledgeItemResponse convertToResponse(KnowledgeItem knowledgeItem); + + List convertItemResponses(List items); +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeItemRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeItemRequest.java new file mode 100644 index 0000000..0c31be9 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeItemRequest.java @@ -0,0 +1,80 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +/** + * 创建知识条目请求DTO + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class CreateKnowledgeItemRequest { + /** + * 标题 + */ + @Size(min = 1, max = 200) + @NotBlank(message = "标题不能为空") + private String title; + /** + * 内容 + */ + @NotBlank(message = "内容不能为空") + private String content; + /** + * 内容类型 + */ + @NotNull(message = "内容类型不能为空") + private KnowledgeContentType contentType; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 标签列表 + */ + private List tags; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeSetRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeSetRequest.java new file mode 100644 index 0000000..afe4443 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/CreateKnowledgeSetRequest.java @@ -0,0 +1,78 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +/** + * 创建知识集请求DTO + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class CreateKnowledgeSetRequest { + /** + * 知识集名称 + */ + @Size(min = 1, max = 100) + @NotBlank(message = "知识集名称不能为空") + private String name; + /** + * 知识集描述 + */ + @Size(max = 1000) + private String description; + /** + * 标签列表 + */ + private List tags; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/ImportKnowledgeItemsRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/ImportKnowledgeItemsRequest.java new file mode 100644 index 0000000..06d8131 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/ImportKnowledgeItemsRequest.java @@ -0,0 +1,69 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +/** + * 导入知识条目请求DTO(基于数据集文件) + */ +@Getter +@Setter +public class ImportKnowledgeItemsRequest { + /** + * 数据集ID + */ + @NotBlank(message = "数据集ID不能为空") + private String datasetId; + /** + * 文件ID列表 + */ + @NotEmpty(message = "文件列表不能为空") + private List fileIds; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 标签列表 + */ + private List tags; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemPagingQuery.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemPagingQuery.java new file mode 100644 index 0000000..db236c4 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemPagingQuery.java @@ -0,0 +1,72 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.common.interfaces.PagingQuery; +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * 知识条目分页查询请求 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeItemPagingQuery extends PagingQuery { + /** + * 所属知识集ID + */ + private String setId; + /** + * 标签名过滤 + */ + private List tags = new ArrayList<>(); + /** + * 关键词搜索(标题或内容) + */ + private String keyword; + /** + * 状态过滤 + */ + private KnowledgeStatusType status; + /** + * 内容类型过滤 + */ + private KnowledgeContentType contentType; + /** + * 领域 + */ + private String domain; + /** + * 业务线 + */ + private String businessLine; + /** + * 负责人 + */ + private String owner; + /** + * 敏感级别 + */ + private String sensitivity; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemResponse.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemResponse.java new file mode 100644 index 0000000..0c79bd9 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeItemResponse.java @@ -0,0 +1,40 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 知识条目响应DTO + */ +@Getter +@Setter +public class KnowledgeItemResponse { + private String id; + private String setId; + private String title; + private String content; + private KnowledgeContentType contentType; + private KnowledgeStatusType status; + private List tags; + private String domain; + private String businessLine; + private String owner; + private LocalDate validFrom; + private LocalDate validTo; + private KnowledgeSourceType sourceType; + private String sensitivity; + private String sourceDatasetId; + private String sourceFileId; + private String metadata; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String createdBy; + private String updatedBy; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetPagingQuery.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetPagingQuery.java new file mode 100644 index 0000000..9cffd67 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetPagingQuery.java @@ -0,0 +1,63 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.common.interfaces.PagingQuery; +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * 知识集分页查询请求 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeSetPagingQuery extends PagingQuery { + /** + * 标签名过滤 + */ + private List tags = new ArrayList<>(); + /** + * 关键词搜索(名称或描述) + */ + private String keyword; + /** + * 状态过滤 + */ + private KnowledgeStatusType status; + /** + * 领域 + */ + private String domain; + /** + * 业务线 + */ + private String businessLine; + /** + * 负责人 + */ + private String owner; + /** + * 敏感级别 + */ + private String sensitivity; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetResponse.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetResponse.java new file mode 100644 index 0000000..7953e0e --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/KnowledgeSetResponse.java @@ -0,0 +1,35 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 知识集响应DTO + */ +@Getter +@Setter +public class KnowledgeSetResponse { + private String id; + private String name; + private String description; + private KnowledgeStatusType status; + private List tags; + private String domain; + private String businessLine; + private String owner; + private LocalDate validFrom; + private LocalDate validTo; + private KnowledgeSourceType sourceType; + private String sensitivity; + private String metadata; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String createdBy; + private String updatedBy; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeItemRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeItemRequest.java new file mode 100644 index 0000000..002658d --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeItemRequest.java @@ -0,0 +1,71 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeContentType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +/** + * 更新知识条目请求DTO + */ +@Getter +@Setter +public class UpdateKnowledgeItemRequest { + /** + * 标题 + */ + @Size(min = 1, max = 200) + private String title; + /** + * 内容 + */ + private String content; + /** + * 内容类型 + */ + private KnowledgeContentType contentType; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 标签列表 + */ + private List tags; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeSetRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeSetRequest.java new file mode 100644 index 0000000..2053e22 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UpdateKnowledgeSetRequest.java @@ -0,0 +1,74 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeSourceType; +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +/** + * 更新知识集请求DTO + */ +@Getter +@Setter +public class UpdateKnowledgeSetRequest { + /** + * 知识集名称 + */ + @Size(min = 1, max = 100) + @NotBlank(message = "知识集名称不能为空") + private String name; + /** + * 知识集描述 + */ + @Size(max = 1000) + private String description; + /** + * 标签列表 + */ + private List tags; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + private LocalDate validFrom; + /** + * 有效期结束 + */ + private LocalDate validTo; + /** + * 来源类型 + */ + private KnowledgeSourceType sourceType; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java new file mode 100644 index 0000000..353d153 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java @@ -0,0 +1,69 @@ +package com.datamate.datamanagement.interfaces.rest; + +import com.datamate.common.interfaces.PagedResponse; +import com.datamate.datamanagement.application.KnowledgeItemApplicationService; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; +import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeItemRequest; +import com.datamate.datamanagement.interfaces.dto.ImportKnowledgeItemsRequest; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery; +import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse; +import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeItemRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 知识条目 REST 控制器 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/data-management/knowledge-sets/{setId}/items") +public class KnowledgeItemController { + private final KnowledgeItemApplicationService knowledgeItemApplicationService; + + @GetMapping + public PagedResponse getKnowledgeItems(@PathVariable("setId") String setId, + KnowledgeItemPagingQuery query) { + return knowledgeItemApplicationService.getKnowledgeItems(setId, query); + } + + @PostMapping + public KnowledgeItemResponse createKnowledgeItem(@PathVariable("setId") String setId, + @RequestBody @Valid CreateKnowledgeItemRequest request) { + KnowledgeItem knowledgeItem = knowledgeItemApplicationService.createKnowledgeItem(setId, request); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeItem); + } + + @PostMapping("/import") + public List importKnowledgeItems(@PathVariable("setId") String setId, + @RequestBody @Valid ImportKnowledgeItemsRequest request) { + List items = knowledgeItemApplicationService.importKnowledgeItems(setId, request); + return KnowledgeConverter.INSTANCE.convertItemResponses(items); + } + + @GetMapping("/{itemId}") + public KnowledgeItemResponse getKnowledgeItemById(@PathVariable("setId") String setId, + @PathVariable("itemId") String itemId) { + KnowledgeItem knowledgeItem = knowledgeItemApplicationService.getKnowledgeItem(setId, itemId); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeItem); + } + + @PutMapping("/{itemId}") + public KnowledgeItemResponse updateKnowledgeItem(@PathVariable("setId") String setId, + @PathVariable("itemId") String itemId, + @RequestBody @Valid UpdateKnowledgeItemRequest request) { + KnowledgeItem knowledgeItem = knowledgeItemApplicationService.updateKnowledgeItem(setId, itemId, request); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeItem); + } + + @DeleteMapping("/{itemId}") + public void deleteKnowledgeItem(@PathVariable("setId") String setId, + @PathVariable("itemId") String itemId) { + knowledgeItemApplicationService.deleteKnowledgeItem(setId, itemId); + } +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeSetController.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeSetController.java new file mode 100644 index 0000000..1f55960 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeSetController.java @@ -0,0 +1,54 @@ +package com.datamate.datamanagement.interfaces.rest; + +import com.datamate.common.interfaces.PagedResponse; +import com.datamate.datamanagement.application.KnowledgeSetApplicationService; +import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter; +import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeSetRequest; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery; +import com.datamate.datamanagement.interfaces.dto.KnowledgeSetResponse; +import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeSetRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 知识集 REST 控制器 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/data-management/knowledge-sets") +public class KnowledgeSetController { + private final KnowledgeSetApplicationService knowledgeSetApplicationService; + + @GetMapping + public PagedResponse getKnowledgeSets(KnowledgeSetPagingQuery query) { + return knowledgeSetApplicationService.getKnowledgeSets(query); + } + + @PostMapping + public KnowledgeSetResponse createKnowledgeSet(@RequestBody @Valid CreateKnowledgeSetRequest request) { + KnowledgeSet knowledgeSet = knowledgeSetApplicationService.createKnowledgeSet(request); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeSet); + } + + @GetMapping("/{setId}") + public KnowledgeSetResponse getKnowledgeSetById(@PathVariable("setId") String setId) { + KnowledgeSet knowledgeSet = knowledgeSetApplicationService.getKnowledgeSet(setId); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeSet); + } + + @PutMapping("/{setId}") + public KnowledgeSetResponse updateKnowledgeSet(@PathVariable("setId") String setId, + @RequestBody @Valid UpdateKnowledgeSetRequest request) { + KnowledgeSet knowledgeSet = knowledgeSetApplicationService.updateKnowledgeSet(setId, request); + return KnowledgeConverter.INSTANCE.convertToResponse(knowledgeSet); + } + + @DeleteMapping("/{setId}") + public void deleteKnowledgeSet(@PathVariable("setId") String setId) { + knowledgeSetApplicationService.deleteKnowledgeSet(setId); + } +} diff --git a/scripts/db/data-management-init.sql b/scripts/db/data-management-init.sql index 8839a61..054e120 100644 --- a/scripts/db/data-management-init.sql +++ b/scripts/db/data-management-init.sql @@ -115,6 +115,69 @@ CREATE TABLE IF NOT EXISTS t_dm_dataset_tags ( FOREIGN KEY (tag_id) REFERENCES t_dm_tags(id) ON DELETE CASCADE ) COMMENT='数据集标签关联表(UUID 外键)'; +-- 知识集表 +CREATE TABLE IF NOT EXISTS t_dm_knowledge_sets ( + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID', + name VARCHAR(255) NOT NULL COMMENT '知识集名称', + description TEXT COMMENT '知识集描述', + status VARCHAR(50) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/ARCHIVED/DEPRECATED', + domain VARCHAR(100) COMMENT '领域', + business_line VARCHAR(100) COMMENT '业务线', + owner VARCHAR(100) COMMENT '负责人', + valid_from DATE COMMENT '有效期开始', + valid_to DATE COMMENT '有效期结束', + source_type VARCHAR(50) COMMENT '来源类型:MANUAL/DATASET_FILE', + sensitivity VARCHAR(50) COMMENT '敏感级别', + tags JSON COMMENT '标签列表', + metadata JSON COMMENT '扩展元数据', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + created_by VARCHAR(255) COMMENT '创建者', + updated_by VARCHAR(255) COMMENT '更新者', + INDEX idx_dm_ks_status (status), + INDEX idx_dm_ks_domain (domain), + INDEX idx_dm_ks_business_line (business_line), + INDEX idx_dm_ks_owner (owner), + INDEX idx_dm_ks_valid_from (valid_from), + INDEX idx_dm_ks_valid_to (valid_to) +) COMMENT='知识集表(UUID 主键)'; + +-- 知识条目表 +CREATE TABLE IF NOT EXISTS t_dm_knowledge_items ( + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID', + set_id VARCHAR(36) NOT NULL COMMENT '所属知识集ID(UUID)', + title VARCHAR(255) NOT NULL COMMENT '标题', + content LONGTEXT NOT NULL COMMENT '内容', + content_type VARCHAR(20) NOT NULL COMMENT '内容类型:TEXT/MARKDOWN', + status VARCHAR(50) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/ARCHIVED/DEPRECATED', + domain VARCHAR(100) COMMENT '领域', + business_line VARCHAR(100) COMMENT '业务线', + owner VARCHAR(100) COMMENT '负责人', + valid_from DATE COMMENT '有效期开始', + valid_to DATE COMMENT '有效期结束', + source_type VARCHAR(50) COMMENT '来源类型:MANUAL/DATASET_FILE', + sensitivity VARCHAR(50) COMMENT '敏感级别', + source_dataset_id VARCHAR(36) COMMENT '来源数据集ID', + source_file_id VARCHAR(36) COMMENT '来源文件ID', + tags JSON COMMENT '标签列表', + metadata JSON COMMENT '扩展元数据', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + created_by VARCHAR(255) COMMENT '创建者', + updated_by VARCHAR(255) COMMENT '更新者', + FOREIGN KEY (set_id) REFERENCES t_dm_knowledge_sets(id) ON DELETE CASCADE, + INDEX idx_dm_ki_set_id (set_id), + INDEX idx_dm_ki_status (status), + INDEX idx_dm_ki_content_type (content_type), + INDEX idx_dm_ki_domain (domain), + INDEX idx_dm_ki_business_line (business_line), + INDEX idx_dm_ki_owner (owner), + INDEX idx_dm_ki_valid_from (valid_from), + INDEX idx_dm_ki_valid_to (valid_to), + INDEX idx_dm_ki_source_dataset (source_dataset_id), + INDEX idx_dm_ki_source_file (source_file_id) +) COMMENT='知识条目表(UUID 主键)'; + -- =========================================== -- 非数据管理表(如 users、t_data_sources)保持不变 -- ===========================================