Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-02-04 16:07:13 +08:00
6 changed files with 259 additions and 166 deletions

View File

@@ -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;
}

View File

@@ -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<Long, FileDictTagsAggDTO> queryByFileIds(List<Long> fileIds);
}

View File

@@ -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<Long, FileDictTagsAggDTO> queryByFileIds(List<Long> fileIds) {
if (CollectionUtils.isEmpty(fileIds)) {
return Collections.emptyMap();
}
Long tenantId = ThreadLocalContext.getTenantId();
// 1) file_tag_rel: DISTINCT fileId, tagId因冗余祖先目录导致重复
List<FileTagRel> fileTagRels = fileTagRelService.getBaseMapper().selectList(
Wrappers.<FileTagRel>query()
.select("DISTINCT fileId, tagId")
.eq("tenantId", tenantId)
.in("fileId", fileIds)
);
if (CollectionUtils.isEmpty(fileTagRels)) {
return Collections.emptyMap();
}
// 2) 提取 tagId 并批量查字典
List<Integer> 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<List<DataDictionary>> 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<Integer, DataDictionary> 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<dictValue>)
Map<Long, Map<String, LinkedHashSet<String>>> 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<Long, FileDictTagsAggDTO> result = new HashMap<>();
for (Map.Entry<Long, Map<String, LinkedHashSet<String>>> e : agg.entrySet()) {
Long fileId = e.getKey();
Map<String, LinkedHashSet<String>> classMap = e.getValue();
if (classMap == null || classMap.isEmpty()) {
continue;
}
FileDictTagsAggDTO dto = new FileDictTagsAggDTO();
dto.setFileId(fileId);
for (Map.Entry<String, LinkedHashSet<String>> 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;
}
}

View File

@@ -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<FileMetadataInfoResp> page1 = new PageInfo<>(dtoList);
page1.setTotal(total);
return PageUtils.getJsonObjectSdmResponse(dtoList, page1);
@@ -2115,188 +2115,69 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
/**
* 批量填充文件标签信息到 FileMetadataInfoResp
* 从 file_tag_rel 和 simulation_data_dictionary 查询标签,按 dictClass 分组
* 格式disciplineTypeDictClass: DISCIPLINE_TYPE, disciplineDictValue: '流体,机器人,动画'
*
* @param dtoList 文件元数据响应列表
* 批量填充文件标签信息到 dtoList只处理 dataType=FILE 的 dto
*/
private void fillFileTags(List<FileMetadataInfoResp> dtoList) {
private void fillFileTagsToDtos(List<FileMetadataInfoResp> dtoList) {
if (CollectionUtils.isEmpty(dtoList)) {
return;
}
Long tenantId = ThreadLocalContext.getTenantId();
// 1. 提取所有文件ID只包含文件类型排除目录
List<Long> fileIds = dtoList.stream()
.filter(dto -> DataTypeEnum.FILE.getValue()==(dto.getDataType()))
List<Long> fileIdsForTagQuery = dtoList.stream()
.filter(dto -> dto != null && dto.getId() != null && DataTypeEnum.FILE.getValue() == dto.getDataType())
.map(FileMetadataInfoResp::getId)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(fileIds)) {
return;
}
// 2. 批量查询 file_tag_rel 获取文件对应的 tagId
List<FileTagRel> fileTagRels = fileTagRelService.lambdaQuery()
.eq(FileTagRel::getTenantId, tenantId)
.in(FileTagRel::getFileId, fileIds)
.select(FileTagRel::getFileId, FileTagRel::getTagId)
.list();
if (CollectionUtils.isEmpty(fileTagRels)) {
return;
}
// 3. 提取所有 tagId
List<Integer> tagIds = fileTagRels.stream()
.map(FileTagRel::getTagId)
.distinct()
.toList();
// 4. 批量查询 simulation_data_dictionary 获取标签详情
List<DictTagReq> 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<List<DataDictionary>> response = sysConfigFeignClient.batchQueryDictionaries(batchReq);
if (!response.isSuccess() || CollectionUtils.isEmpty(response.getData())) {
log.warn("Batch query dictionaries failed or returned empty");
Map<Long, FileDictTagsAggDTO> fileIdToTagsMap = fillFileTags(fileIdsForTagQuery);
if (MapUtils.isEmpty(fileIdToTagsMap)) {
return;
}
// 5. 构建 tagId -> 字典信息的映射
Map<Integer, DataDictionary> tagIdToDictMap = response.getData().stream()
.filter(dict -> dict.id != null)
.collect(Collectors.toMap(
dict -> dict.id,
dict -> dict,
(v1, v2) -> v1
));
// 6. 构建 fileId -> tagIds 的映射tagId 去重)
Map<Long, List<Integer>> fileToTagsMap = fileTagRels.stream()
.collect(Collectors.groupingBy(
FileTagRel::getFileId,
Collectors.mapping(FileTagRel::getTagId, Collectors.toSet())
))
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> new ArrayList<>(entry.getValue())
));
// 7. 为每个文件填充标签信息
for (FileMetadataInfoResp dto : dtoList) {
if (!(DataTypeEnum.FILE.getValue()==(dto.getDataType()))) {
if (dto == null || dto.getId() == null || DataTypeEnum.FILE.getValue() != dto.getDataType()) {
continue;
}
List<Integer> fileTags = fileToTagsMap.get(dto.getId());
if (CollectionUtils.isEmpty(fileTags)) {
FileDictTagsAggDTO tags = fileIdToTagsMap.get(dto.getId());
if (tags == null) {
continue;
}
// 按 dictClass 分组,聚合 dictValue只处理配置的 dictClass
Map<String, List<String>> 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<String, List<String>> 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);
}
}
dto.setFileTypeDictClass(tags.getFileTypeDictClass());
dto.setFileTypeDictValue(tags.getFileTypeDictValue());
dto.setDisciplineTypeDictClass(tags.getDisciplineTypeDictClass());
dto.setDisciplineDictValue(tags.getDisciplineDictValue());
}
}
/**
* 根据枚举配置设置字典标签字段(推荐使用)
* 直接使用枚举配置的完整字段名,无需拼接
*
* @param dto FileMetadataInfoResp 对象
* @param tagEnum 字典标签枚举
* @param dictValues 字典值(逗号分隔)
* 批量填充文件标签信息到 FileMetadataInfoResp
* 从 file_tag_rel 和 simulation_data_dictionary 查询标签,按 dictClass 分组
* 格式:
* disciplineTypeDictClass: DISCIPLINE_TYPE,
* disciplineDictValue: '流体,机器人,动画'
*
* @param fileIds 文件ID列表
* @return fileId -> 标签聚合结果
*/
private void setDictTagFieldsByEnum(FileMetadataInfoResp dto, FileDictTagEnum tagEnum, String dictValues) {
if (tagEnum == null || dictValues == null) {
return;
private Map<Long, FileDictTagsAggDTO> fillFileTags(List<Long> fileIds) {
if (CollectionUtils.isEmpty(fileIds)) {
return Collections.emptyMap();
}
List<Long> idList = fileIds.stream()
.filter(Objects::nonNull)
.distinct()
.toList();
if (CollectionUtils.isEmpty(idList)) {
return Collections.emptyMap();
}
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());
}
Map<Long, FileDictTagsAggDTO> map = fileDictTagQueryService.queryByFileIds(idList);
return MapUtils.isEmpty(map) ? Collections.emptyMap() : map;
}
/**
* 在类及其父类中查找字段
*/
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;
@@ -2554,6 +2435,22 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileMetadataInfo.setUpdateTime(LocalDateTime.now());
fileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
fileMetadataInfo.setFileType(req.getFileType());
//绑定文件和工况库的关系
if (CollectionUtils.isNotEmpty(req.getSimulationPoolInfoList())) {
// 先删除原先所有的文件绑定关系
fileSimulationMappingService.lambdaUpdate().eq(FileSimulationMapping::getFileId, fileMetadataInfo.getId()).remove();
for (SimulationPoolInfo simulationPoolInfo : req.getSimulationPoolInfoList()) {
for (String simulationPoolTaskId : simulationPoolInfo.getSimulationPoolTaskIds()) {
FileSimulationMapping fileSimulationMapping = new FileSimulationMapping();
fileSimulationMapping.setFileId(fileMetadataInfo.getId());
fileSimulationMapping.setSimulationPoolId(simulationPoolInfo.getSimulationPoolId());
fileSimulationMapping.setSimulationPoolVersion(simulationPoolInfo.getSimulationPoolVersion());
fileSimulationMapping.setSimulationPoolTaskId(simulationPoolTaskId);
fileSimulationMappingService.save(fileSimulationMapping);
}
}
}
// 更新标签(如果有)
if (CollectionUtils.isNotEmpty(req.getDictTags())) {

View File

@@ -173,15 +173,15 @@ public class SimulationJob implements Serializable {
@TableField("dirId")
private Long dirId;
@Schema(description = "输入主文件正则")
@Schema(description = "输入主文件正则,params里取的base64的字符串")
@TableField("inputFormat")
private String inputFormat;
@Schema(description = "输入从文件正则")
@Schema(description = "输入从文件正则的base64的字符串")
@TableField("slaveFormat")
private String slaveFormat;
@Schema(description = "输出文件正则")
@Schema(description = "输出文件正则的base64的字符串")
@TableField("outputFormat")
private String outputFormat;

View File

@@ -288,8 +288,6 @@ public class PbsServiceDecorator implements IPbsServiceDecorator {
simulationJob.setRunName(req.getRunName());
// 软件及文件关联
simulationJob.setSoftwareId(req.getSoftwareId());
// 下面的待定
// simulationJob.setInputFileId(null);
simulationJob.setJobId(jobId);
// 没必要要
// simulationJob.setJobDetailId("");
@@ -325,6 +323,13 @@ public class PbsServiceDecorator implements IPbsServiceDecorator {
simulationJob.setUpdateTime(LocalDateTime.now());
// 当前节点minio的文件夹id
simulationJob.setDirId(req.getDirId());
simulationJob.setInputFileId(req.getInputFileId());
simulationJob.setInputFormat(req.getInputFormat());
simulationJob.setSlaveFormat(req.getSlaveFormat());
simulationJob.setOutputFormat(req.getOutputFormat());
simulationJob.setProcessDefinitionId(req.getProcessDefinitionId());
simulationJob.setProcessInstanceId(req.getProcessInstanceId());
simulationJobService.save(simulationJob);
}
}