diff --git a/data/src/main/java/com/sdm/data/model/dto/FileDictTagsAggDTO.java b/data/src/main/java/com/sdm/data/model/dto/FileDictTagsAggDTO.java new file mode 100644 index 00000000..21800907 --- /dev/null +++ b/data/src/main/java/com/sdm/data/model/dto/FileDictTagsAggDTO.java @@ -0,0 +1,31 @@ +package com.sdm.data.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 文件字典标签聚合结果(按 fileId 聚合) + */ +@Data +@Schema(name = "FileDictTagsAggDTO", description = "文件字典标签聚合结果(按 fileId 聚合)") +public class FileDictTagsAggDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文件ID") + private Long fileId; + + @Schema(description = "文件类型字典类") + private String fileTypeDictClass; + + @Schema(description = "文件类型字典值(逗号分隔)") + private String fileTypeDictValue; + + @Schema(description = "学科类型字典类") + private String disciplineTypeDictClass; + + @Schema(description = "学科类型字典值(逗号分隔)") + private String disciplineDictValue; +} + diff --git a/data/src/main/java/com/sdm/data/service/IFileDictTagQueryService.java b/data/src/main/java/com/sdm/data/service/IFileDictTagQueryService.java new file mode 100644 index 00000000..04379bde --- /dev/null +++ b/data/src/main/java/com/sdm/data/service/IFileDictTagQueryService.java @@ -0,0 +1,21 @@ +package com.sdm.data.service; + +import com.sdm.data.model.dto.FileDictTagsAggDTO; + +import java.util.List; +import java.util.Map; + +/** + * 文件字典标签聚合查询服务 + * 输入 fileIds,返回每个文件对应的 dictClass/dictValue 聚合结果 + */ +public interface IFileDictTagQueryService { + + /** + * 按文件ID批量查询并聚合字典标签 + * @param fileIds 文件ID列表 + * @return fileId -> 聚合结果 + */ + Map queryByFileIds(List fileIds); +} + diff --git a/data/src/main/java/com/sdm/data/service/impl/FileDictTagQueryServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/FileDictTagQueryServiceImpl.java new file mode 100644 index 00000000..5c7c147b --- /dev/null +++ b/data/src/main/java/com/sdm/data/service/impl/FileDictTagQueryServiceImpl.java @@ -0,0 +1,139 @@ +package com.sdm.data.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.sdm.common.common.SdmResponse; +import com.sdm.common.common.ThreadLocalContext; +import com.sdm.common.entity.bo.DataDictionary; +import com.sdm.common.entity.enums.FileDictTagEnum; +import com.sdm.common.entity.req.system.DictTagReq; +import com.sdm.common.feign.inter.system.ISysConfigFeignClient; +import com.sdm.data.model.dto.FileDictTagsAggDTO; +import com.sdm.data.model.entity.FileTagRel; +import com.sdm.data.service.IFileDictTagQueryService; +import com.sdm.data.service.IFileTagRelService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件字典标签聚合查询服务实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FileDictTagQueryServiceImpl implements IFileDictTagQueryService { + + private final IFileTagRelService fileTagRelService; + private final ISysConfigFeignClient sysConfigFeignClient; + + @Override + public Map queryByFileIds(List fileIds) { + if (CollectionUtils.isEmpty(fileIds)) { + return Collections.emptyMap(); + } + + Long tenantId = ThreadLocalContext.getTenantId(); + + // 1) file_tag_rel: DISTINCT fileId, tagId(因冗余祖先目录导致重复) + List fileTagRels = fileTagRelService.getBaseMapper().selectList( + Wrappers.query() + .select("DISTINCT fileId, tagId") + .eq("tenantId", tenantId) + .in("fileId", fileIds) + ); + if (CollectionUtils.isEmpty(fileTagRels)) { + return Collections.emptyMap(); + } + + // 2) 提取 tagId 并批量查字典 + List tagIds = fileTagRels.stream() + .map(FileTagRel::getTagId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (CollectionUtils.isEmpty(tagIds)) { + return Collections.emptyMap(); + } + + DictTagReq.BatchDictQueryReq batchReq = new DictTagReq.BatchDictQueryReq(); + batchReq.setItems(tagIds.stream().map(tagId -> { + DictTagReq req = new DictTagReq(); + req.setDictId(tagId); + return req; + }).collect(Collectors.toList())); + + SdmResponse> response = sysConfigFeignClient.batchQueryDictionaries(batchReq); + if (response == null || !response.isSuccess() || CollectionUtils.isEmpty(response.getData())) { + log.warn("Batch query dictionaries failed or returned empty, fileIds: {}", fileIds); + return Collections.emptyMap(); + } + + Map tagIdToDictMap = response.getData().stream() + .filter(d -> d != null && d.id != null) + .collect(Collectors.toMap( + d -> d.id, + Function.identity(), + (v1, v2) -> v1 + )); + + // 3) fileId -> (dictClass -> Set) + Map>> agg = new HashMap<>(); + for (FileTagRel rel : fileTagRels) { + if (rel == null || rel.getFileId() == null || rel.getTagId() == null) { + continue; + } + DataDictionary dict = tagIdToDictMap.get(rel.getTagId()); + if (dict == null || dict.dictClass == null || dict.dictValue == null) { + continue; + } + if (!FileDictTagEnum.isConfigured(dict.dictClass)) { + continue; + } + agg.computeIfAbsent(rel.getFileId(), k -> new HashMap<>()) + .computeIfAbsent(dict.dictClass, k -> new LinkedHashSet<>()) + .add(dict.dictValue); + } + + if (agg.isEmpty()) { + return Collections.emptyMap(); + } + + // 4) 转为 DTO(目前只输出枚举配置的两类字段) + Map result = new HashMap<>(); + for (Map.Entry>> e : agg.entrySet()) { + Long fileId = e.getKey(); + Map> classMap = e.getValue(); + if (classMap == null || classMap.isEmpty()) { + continue; + } + + FileDictTagsAggDTO dto = new FileDictTagsAggDTO(); + dto.setFileId(fileId); + + for (Map.Entry> classEntry : classMap.entrySet()) { + String dictClass = classEntry.getKey(); + String dictValues = String.join(",", classEntry.getValue()); + FileDictTagEnum tagEnum = FileDictTagEnum.getByDictClass(dictClass); + if (tagEnum == null) { + continue; + } + if (tagEnum == FileDictTagEnum.FILE_TYPE) { + dto.setFileTypeDictClass(tagEnum.getDictClass()); + dto.setFileTypeDictValue(dictValues); + } else if (tagEnum == FileDictTagEnum.DISCIPLINE_TYPE) { + dto.setDisciplineTypeDictClass(tagEnum.getDictClass()); + dto.setDisciplineDictValue(dictValues); + } + } + result.put(fileId, dto); + } + + return result; + } +} + diff --git a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java index 8cf9b7d8..88b4f132 100644 --- a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java @@ -12,14 +12,12 @@ import com.google.common.collect.Sets; import com.sdm.common.common.SdmResponse; import com.sdm.common.common.ThreadLocalContext; import com.sdm.common.entity.ExportExcelFormat; -import com.sdm.common.entity.bo.DataDictionary; import com.sdm.common.entity.constants.NumberConstants; import com.sdm.common.entity.constants.PermConstants; import com.sdm.common.entity.enums.*; import com.sdm.common.entity.pojo.task.TaskBaseInfo; import com.sdm.common.entity.req.data.*; import com.sdm.common.entity.req.project.SpdmNodeListReq; -import com.sdm.common.entity.req.system.DictTagReq; import com.sdm.common.entity.req.system.LaunchApproveReq; import com.sdm.common.entity.req.system.UserListReq; import com.sdm.common.entity.req.system.UserQueryReq; @@ -28,7 +26,6 @@ import com.sdm.common.entity.resp.data.*; import com.sdm.common.entity.resp.project.SimulationNodeResp; import com.sdm.common.entity.resp.system.CIDUserResp; import com.sdm.common.feign.impl.project.SimulationNodeFeignClientImpl; -import com.sdm.common.feign.impl.system.SysConfigFeignClientImpl; import com.sdm.common.feign.impl.system.SysUserFeignClientImpl; import com.sdm.common.feign.inter.project.ISimulationNodeFeignClient; import com.sdm.common.feign.inter.system.IApproveFeignClient; @@ -40,6 +37,7 @@ import com.sdm.common.utils.excel.ExcelUtil; import com.sdm.data.aop.PermissionCheckAspect; import com.sdm.data.model.bo.ApprovalFileDataContentsModel; import com.sdm.data.model.dto.ExportKnowledgeDto; +import com.sdm.data.model.dto.FileDictTagsAggDTO; import com.sdm.data.model.entity.*; import com.sdm.data.model.enums.ApproveFileActionENUM; import com.sdm.data.model.req.*; @@ -137,6 +135,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { @Autowired private IFileTagRelService fileTagRelService; + @Autowired + private IFileDictTagQueryService fileDictTagQueryService; + @Autowired ISimulationParameterLibraryCategoryObjectService paramObjectService; @@ -890,9 +891,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { dto.setKnowledgeBaseName(knowledgeBaseName); return dto; }).collect(Collectors.toList()); - - // 批量填充文件标签信息 - fillFileTags(dtoList); + + fillFileTagsToDtos(dtoList); PageInfo page1 = new PageInfo<>(dtoList); page1.setTotal(total); return PageUtils.getJsonObjectSdmResponse(dtoList, page1); @@ -2115,187 +2115,69 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { } } + + + /** + * 批量填充文件标签信息到 dtoList(只处理 dataType=FILE 的 dto) + */ + private void fillFileTagsToDtos(List dtoList) { + if (CollectionUtils.isEmpty(dtoList)) { + return; + } + + List fileIdsForTagQuery = dtoList.stream() + .filter(dto -> dto != null && dto.getId() != null && DataTypeEnum.FILE.getValue() == dto.getDataType()) + .map(FileMetadataInfoResp::getId) + .distinct() + .toList(); + + Map fileIdToTagsMap = fillFileTags(fileIdsForTagQuery); + if (MapUtils.isEmpty(fileIdToTagsMap)) { + return; + } + + for (FileMetadataInfoResp dto : dtoList) { + if (dto == null || dto.getId() == null || DataTypeEnum.FILE.getValue() != dto.getDataType()) { + continue; + } + FileDictTagsAggDTO tags = fileIdToTagsMap.get(dto.getId()); + if (tags == null) { + continue; + } + dto.setFileTypeDictClass(tags.getFileTypeDictClass()); + dto.setFileTypeDictValue(tags.getFileTypeDictValue()); + dto.setDisciplineTypeDictClass(tags.getDisciplineTypeDictClass()); + dto.setDisciplineDictValue(tags.getDisciplineDictValue()); + } + } + /** * 批量填充文件标签信息到 FileMetadataInfoResp * 从 file_tag_rel 和 simulation_data_dictionary 查询标签,按 dictClass 分组 * 格式: * disciplineTypeDictClass: DISCIPLINE_TYPE, * disciplineDictValue: '流体,机器人,动画' - * - * @param dtoList 文件元数据响应列表 + * + * @param fileIds 文件ID列表 + * @return fileId -> 标签聚合结果 */ - private void fillFileTags(List dtoList) { - if (CollectionUtils.isEmpty(dtoList)) { - return; - } - - Long tenantId = ThreadLocalContext.getTenantId(); - - // 1. 提取所有文件ID(只包含文件类型,排除目录) - List fileIds = dtoList.stream() - .filter(dto -> DataTypeEnum.FILE.getValue()==(dto.getDataType())) - .map(FileMetadataInfoResp::getId) - .collect(Collectors.toList()); - + private Map fillFileTags(List fileIds) { if (CollectionUtils.isEmpty(fileIds)) { - return; + return Collections.emptyMap(); } - - // 2. 批量查询 file_tag_rel 获取文件对应的 tagId(数据库层去重) - // 注意:因为冗余记录了祖先父目录id,会导致 (fileId, tagId) 组合重复,需要在查询时去重 - List fileTagRels = fileTagRelService.getBaseMapper().selectList( - Wrappers.query() - .select("DISTINCT fileId, tagId") - .eq("tenantId", tenantId) - .in("fileId", fileIds) - ); - - if (CollectionUtils.isEmpty(fileTagRels)) { - return; - } - - // 3. 提取所有 tagId - List tagIds = fileTagRels.stream() - .map(FileTagRel::getTagId) + List idList = fileIds.stream() + .filter(Objects::nonNull) .distinct() .toList(); - // 4. 批量查询 simulation_data_dictionary 获取标签详情 - List dictTagReqs = tagIds.stream() - .map(tagId -> { - DictTagReq req = new DictTagReq(); - req.setDictId(tagId); - return req; - }) - .collect(Collectors.toList()); - - DictTagReq.BatchDictQueryReq batchReq = new DictTagReq.BatchDictQueryReq(); - batchReq.setItems(dictTagReqs); - SdmResponse> response = sysConfigFeignClient.batchQueryDictionaries(batchReq); - - if (!response.isSuccess() || CollectionUtils.isEmpty(response.getData())) { - log.warn("Batch query dictionaries failed or returned empty"); - return; + if (CollectionUtils.isEmpty(idList)) { + return Collections.emptyMap(); } - // 5. 构建 tagId -> 字典信息的映射 - Map tagIdToDictMap = response.getData().stream() - .filter(dict -> dict.id != null) - .collect(Collectors.toMap( - dict -> dict.id, - dict -> dict, - (v1, v2) -> v1 - )); - - // 6. 构建 fileId -> tagIds 的映射(数据库已去重,直接使用 toList) - Map> fileToTagsMap = fileTagRels.stream() - .collect(Collectors.groupingBy( - FileTagRel::getFileId, - Collectors.mapping(FileTagRel::getTagId, Collectors.toList()) - )); - - // 7. 为每个文件填充标签信息 - for (FileMetadataInfoResp dto : dtoList) { - if (!(DataTypeEnum.FILE.getValue()==(dto.getDataType()))) { - continue; - } - - List fileTags = fileToTagsMap.get(dto.getId()); - if (CollectionUtils.isEmpty(fileTags)) { - continue; - } - - // 按 dictClass 分组,聚合 dictValue(只处理配置的 dictClass) - Map> classToDictValuesMap = new HashMap<>(); - - for (Integer tagId : fileTags) { - DataDictionary dictInfo = tagIdToDictMap.get(tagId); - if (dictInfo == null) { - continue; - } - - String dictClass = dictInfo.dictClass; - String dictValue = dictInfo.dictValue; - - // 只处理枚举中配置的 dictClass - if (dictClass != null && dictValue != null && FileDictTagEnum.isConfigured(dictClass)) { - classToDictValuesMap - .computeIfAbsent(dictClass, k -> new ArrayList<>()) - .add(dictValue); - } - } - - // 设置到 DTO 中(使用枚举配置) - for (Map.Entry> entry : classToDictValuesMap.entrySet()) { - String dictClass = entry.getKey(); - String dictValues = String.join(",", entry.getValue()); - - // 根据枚举配置设置对应的字段 - FileDictTagEnum tagEnum = FileDictTagEnum.getByDictClass(dictClass); - if (tagEnum != null) { - setDictTagFieldsByEnum(dto, tagEnum, dictValues); - } - } - } + Map map = fileDictTagQueryService.queryByFileIds(idList); + return MapUtils.isEmpty(map) ? Collections.emptyMap() : map; } - /** - * 根据枚举配置设置字典标签字段(推荐使用) - * 直接使用枚举配置的完整字段名,无需拼接 - * - * @param dto FileMetadataInfoResp 对象 - * @param tagEnum 字典标签枚举 - * @param dictValues 字典值(逗号分隔) - */ - private void setDictTagFieldsByEnum(FileMetadataInfoResp dto, FileDictTagEnum tagEnum, String dictValues) { - if (tagEnum == null || dictValues == null) { - return; - } - - String dictClass = tagEnum.getDictClass(); - String dictClassFieldName = tagEnum.getDictClassFieldName(); - String dictValueFieldName = tagEnum.getDictValueFieldName(); - - try { - // 设置 dictClass 字段 - Field dictClassField = findField(FileMetadataInfoResp.class, dictClassFieldName); - if (dictClassField != null) { - dictClassField.setAccessible(true); - dictClassField.set(dto, dictClass); - } else { - log.warn("Field '{}' not found in FileMetadataInfoResp, please add field for dictClass: {}", - dictClassFieldName, dictClass); - } - - // 设置 dictValue 字段 - Field dictValueField = findField(FileMetadataInfoResp.class, dictValueFieldName); - if (dictValueField != null) { - dictValueField.setAccessible(true); - dictValueField.set(dto, dictValues); - } else { - log.warn("Field '{}' not found in FileMetadataInfoResp, please add field for dictClass: {}", - dictValueFieldName, dictClass); - } - } catch (Exception e) { - log.warn("Failed to set dict tag fields for dictClass: {}, error: {}", dictClass, e.getMessage()); - } - } - - /** - * 在类及其父类中查找字段 - */ - private Field findField(Class clazz, String fieldName) { - while (clazz != null && clazz != Object.class) { - try { - return clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } - return null; - } - - private void bindSimulationPool(UploadFilesReq req, FileMetadataInfo fileInfo) { if (CollectionUtils.isEmpty(req.getSimulationPoolInfoList())) { return;