fix:文件打标签重构

This commit is contained in:
2026-02-03 21:54:48 +08:00
parent ef9ed39fb8
commit 1ce63b1c37
14 changed files with 516 additions and 7 deletions

View File

@@ -0,0 +1,72 @@
package com.sdm.common.entity.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文件字典标签枚举
* 固定配置文件元数据响应中需要填充的字典标签字段映射关系
*
* 映射关系:
* - disciplineTypeDictClass/disciplineDictValue → DISCIPLINE_TYPE
* - fileTypeDictClass/fileTypeDictValue → ALL_FILE_TYPE
*/
@Getter
@AllArgsConstructor
public enum FileDictTagEnum {
/**
* 学科类型标签
* 字段: disciplineTypeDictClass, disciplineDictValue
* dictClass: DISCIPLINE_TYPE
*/
DISCIPLINE_TYPE("DISCIPLINE_TYPE", "disciplineTypeDictClass", "disciplineDictValue"),
/**
* 文件类型标签
* 字段: fileTypeDictClass, fileTypeDictValue
* dictClass: ALL_FILE_TYPE
*/
FILE_TYPE("ALL_FILE_TYPE", "fileTypeDictClass", "fileTypeDictValue");
/**
* 字典分类(对应数据库中的 dictClass
*/
private final String dictClass;
/**
* dictClass 字段名(完整字段名,如 disciplineTypeDictClass
*/
private final String dictClassFieldName;
/**
* dictValue 字段名(完整字段名,如 disciplineDictValue
*/
private final String dictValueFieldName;
/**
* 根据 dictClass 获取对应的枚举
* @param dictClass 字典分类
* @return 对应的枚举,如果不存在则返回 null
*/
public static FileDictTagEnum getByDictClass(String dictClass) {
if (dictClass == null) {
return null;
}
for (FileDictTagEnum enumItem : values()) {
if (enumItem.getDictClass().equals(dictClass)) {
return enumItem;
}
}
return null;
}
/**
* 判断是否为配置的字典分类
* @param dictClass 字典分类
* @return 是否配置
*/
public static boolean isConfigured(String dictClass) {
return getByDictClass(dictClass) != null;
}
}

View File

@@ -9,6 +9,9 @@ import java.util.Map;
@Data
@Schema(description = "字典标签查询请求")
public class DictTagReq {
@Schema(description = "字典ID", example = "1234")
private Integer dictId;
@Schema(description = "字典类型", example = "file_type")
private String dictClass;
@@ -27,4 +30,11 @@ public class DictTagReq {
@Schema(description = "字典标签查询项列表")
private List<DictTagReq> items;
}
@Data
@Schema(description = "批量查询字典详情请求")
public static class BatchDictQueryReq {
@Schema(description = "字典标签查询项列表")
private List<DictTagReq> items;
}
}

View File

@@ -1,5 +1,6 @@
package com.sdm.common.entity.resp.data;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -11,6 +12,7 @@ import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 文件元数据传输对象(用于接口返回给前端)
@@ -61,6 +63,21 @@ public class FileMetadataInfoResp extends BaseResp implements Serializable {
@Schema(description= "文件业务类型1模型文件 2仿真报告、3计算文件、4曲线文件、5云图文件6网格文件7计算过程文件")
private Integer fileType;
// ----------------------------------------------------------------
// 很重要,用于设置标签
@Schema(description = "文件类型字典类")
private String fileTypeDictClass;
@Schema(description = "文件类型字典值")
private String fileTypeDictValue;
@Schema(description = "学科类型字典类")
private String disciplineTypeDictClass;
@Schema(description = "学科类型字典值")
private String disciplineDictValue;
// ----------------------------------------------------------------
@Schema(description = "数据类型1-文件2-文件夹")
private Integer dataType;

View File

@@ -49,4 +49,19 @@ public class SysConfigFeignClientImpl implements ISysConfigFeignClient {
return SdmResponse.failed("字典信息未查询");
}
}
@Override
public SdmResponse<List<DataDictionary>> batchQueryDictionaries(DictTagReq.BatchDictQueryReq req) {
SdmResponse<List<DataDictionary>> sdmResponse;
try {
sdmResponse = sysConfigFeignClient.batchQueryDictionaries(req);
if(!sdmResponse.isSuccess() ||ObjectUtils.isEmpty(sdmResponse.getData())){
return SdmResponse.failed("字典信息未查询");
}
return sdmResponse;
} catch (Exception e) {
log.error("字典信息失败", e);
return SdmResponse.failed("字典信息未查询");
}
}
}

View File

@@ -22,4 +22,12 @@ public interface ISysConfigFeignClient {
@PostMapping(value = "/systemData/multiDictionaryIds")
SdmResponse<Map<String, Map<String, Integer>>> multiQueryDictionaryIds(@RequestBody DictTagReq.BatchDictIdQueryReq req);
/**
* 批量查询字典详情(根据 dictId 列表)
* @param req 包含 dictId 列表的请求
* @return 字典详情列表
*/
@PostMapping(value = "/systemData/batchQueryDictionaries")
SdmResponse<List<DataDictionary>> batchQueryDictionaries(@RequestBody DictTagReq.BatchDictQueryReq req);
}

View File

@@ -314,6 +314,7 @@ public class DataFileController implements IDataFeignClient {
* @param req
* @return
*/
@AutoFillDictTags
@PostMapping(value = "/updateFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "编辑更新文件", description = "更新指定文件")
public SdmResponse updateFile(@Validated UpdateFileReq req) {

View File

@@ -52,6 +52,28 @@ public class UpdateFileReq {
@Schema(description = "备注信息")
private String remarks;
// ----------------------------------------------------------------
// 很重要,用于设置标签
@Schema(description = "字典标签查询结果缓存", hidden = true)
@JSONField(serialize = false)
private java.util.Map<String, java.util.Map<String, Integer>> dictTagIdsCache;
@Schema(description = "字典标签查询列表,格式:['fileTypeDictClass','fileTypeDictValue','disciplineTypeDictClass','disciplineDictValue']")
private List<String> dictTags;
@Schema(description = "文件类型字典类")
private String fileTypeDictClass;
@Schema(description = "文件类型字典值")
private String fileTypeDictValue;
@Schema(description = "学科类型字典类")
private String disciplineTypeDictClass;
@Schema(description = "学科类型字典值")
private String disciplineDictValue;
// ----------------------------------------------------------------
@Schema(description = "知识库文件审批模板id")
private String templateId;

View File

@@ -12,6 +12,7 @@ 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.*;
@@ -882,6 +883,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
dto.setKnowledgeBaseName(knowledgeBaseName);
return dto;
}).collect(Collectors.toList());
// 批量填充文件标签信息
fillFileTags(dtoList);
PageInfo<FileMetadataInfoResp> page1 = new PageInfo<>(dtoList);
page1.setTotal(total);
return PageUtils.getJsonObjectSdmResponse(dtoList, page1);
@@ -1978,6 +1982,76 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
/**
* 更新文件标签
* 删除旧标签,插入新标签
*
* @param req 更新文件请求
* @param fileMetadataInfo 文件元数据
* @param dirMetadataInfo 目录元数据
*/
private void updateFileTags(UpdateFileReq req, FileMetadataInfo fileMetadataInfo, FileMetadataInfo dirMetadataInfo) {
Long tenantId = ThreadLocalContext.getTenantId();
Long creatorId = ThreadLocalContext.getUserId();
Long fileId = fileMetadataInfo.getId();
Long dirId = dirMetadataInfo != null ? dirMetadataInfo.getId() : null;
// 1. 收集当前目录和所有祖先目录
List<Long> ancestorDirIds = dirId != null ? collectAncestorDirIds(dirId) : new ArrayList<>();
// 2. 删除文件在所有目录下的旧标签关系
if (!ancestorDirIds.isEmpty()) {
fileTagRelService.lambdaUpdate()
.eq(FileTagRel::getTenantId, tenantId)
.eq(FileTagRel::getFileId, fileId)
.in(FileTagRel::getDirId, ancestorDirIds)
.remove();
}
// 3. 从缓存获取字典标签ID已由AOP切面自动填充
Map<String, Map<String, Integer>> dictIdMap = req.getDictTagIdsCache();
if (dictIdMap == null || dictIdMap.isEmpty()) {
log.warn("Dict tags cache is empty for update, trying to query manually");
dictIdMap = dictTagHelper.queryAndCacheDictTagIds(req);
if (dictIdMap.isEmpty()) {
log.warn("No dictionary ids found for tags update");
return;
}
}
// 4. 构建新的标签关系
List<FileTagRel> newTagRelList = new ArrayList<>();
long fileSize = fileMetadataInfo.getFileSize() != null ? fileMetadataInfo.getFileSize() : 0L;
for (Map.Entry<String, Map<String, Integer>> classEntry : dictIdMap.entrySet()) {
Map<String, Integer> valueMap = classEntry.getValue();
for (Integer dictId : valueMap.values()) {
if (dictId == null) {
continue;
}
// 为所有目录(当前目录 + 祖先目录)创建标签关系
for (Long dirIdItem : ancestorDirIds) {
FileTagRel tagRel = new FileTagRel();
tagRel.setFileId(fileId);
tagRel.setTagId(dictId);
tagRel.setDirId(dirIdItem);
tagRel.setTenantId(tenantId);
tagRel.setCreatorId(creatorId);
tagRel.setFileSize(fileSize);
newTagRelList.add(tagRel);
}
}
}
// 5. 批量插入新标签关系
if (CollectionUtils.isNotEmpty(newTagRelList)) {
fileTagRelService.saveBatch(newTagRelList);
log.info("Updated file tags for fileId: {}, total: {} tags", fileId, newTagRelList.size());
}
}
private void saveFileTags(UploadFilesReq req, FileMetadataInfo fileInfo, FileMetadataInfo dirMetadataInfo,
List<Long> ancestorDirIds) {
if (CollectionUtils.isEmpty(req.getDictTags())) {
@@ -1987,7 +2061,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
Long tenantId = ThreadLocalContext.getTenantId();
Long creatorId = ThreadLocalContext.getUserId();
long fileSize = resolveFileSize(req);
Long currentDirId = dirMetadataInfo.getId();
// 从缓存获取字典标签ID已由AOP切面自动填充
Map<String, Map<String, Integer>> dictIdMap = req.getDictTagIdsCache();
@@ -2033,6 +2106,187 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
/**
* 批量填充文件标签信息到 FileMetadataInfoResp
* 从 file_tag_rel 和 simulation_data_dictionary 查询标签,按 dictClass 分组
* 格式disciplineTypeDictClass: DISCIPLINE_TYPE, disciplineDictValue: '流体,机器人,动画'
*
* @param dtoList 文件元数据响应列表
*/
private void fillFileTags(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()))
.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");
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()))) {
continue;
}
List<Integer> fileTags = fileToTagsMap.get(dto.getId());
if (CollectionUtils.isEmpty(fileTags)) {
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);
}
}
}
}
/**
* 根据枚举配置设置字典标签字段(推荐使用)
* 直接使用枚举配置的完整字段名,无需拼接
*
* @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())) {
@@ -2279,6 +2533,11 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileMetadataInfo.setUpdateTime(LocalDateTime.now());
fileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
fileMetadataInfo.setFileType(req.getFileType());
// 更新标签(如果有)
if (CollectionUtils.isNotEmpty(req.getDictTags())) {
updateFileTags(req, fileMetadataInfo, dirMetadataInfo);
}
}
fileMetadataInfoService.updateById(fileMetadataInfo);

View File

@@ -179,6 +179,15 @@ public class SimulationSystemConfigController implements ISysConfigFeignClient {
return service.multiQueryDictionaryIds(req);
}
/**
* 批量查询字典详情(根据 dictId 列表)
*/
@PostMapping(value = "/batchQueryDictionaries")
@ResponseBody
public SdmResponse<List<DataDictionary>> batchQueryDictionaries(@RequestBody DictTagReq.BatchDictQueryReq req) {
return service.batchQueryDictionaries(req);
}
/**
* 表单管理 展示所有自定义表头的表单表
*/

View File

@@ -79,10 +79,11 @@ public interface SimulationSystemMapper {
@Select("<script>" +
"SELECT id, dictClass, dictValue FROM simulation_data_dictionary " +
"WHERE tenantId = #{tenantId} AND " +
"WHERE tenantId = #{tenantId} AND (" +
"<foreach collection='items' item='item' separator=' OR '>" +
"(dictClass = #{item.dictClass} AND dictValue = #{item.dictValue})" +
"</foreach>" +
")" +
" ORDER BY dictClass, dictValue" +
"</script>")
List<DataDictionary> batchQueryDictionaryIds(@Param("tenantId") long tenantId, @Param("items") List<DictTagReq> items);

View File

@@ -26,7 +26,7 @@ public class SysDataDictionary implements Serializable {
@Schema(description = "表单ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Integer id;
@Schema(description = "字典ID")
private String uuid;

View File

@@ -0,0 +1,39 @@
package com.sdm.system.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 数据字典响应实体
*/
@Data
@Schema(description = "数据字典响应")
public class DataDictionaryResp {
@Schema(description = "字典ID")
private Integer id;
@Schema(description = "字典分类名称")
private String dictClass;
@Schema(description = "字典名称")
private String dictName;
@Schema(description = "字典值")
private String dictValue;
@Schema(description = "字典别名")
private String aliasName;
@Schema(description = "字典值类型")
private String valueType;
@Schema(description = "排序序号")
private Integer dictOrder;
@Schema(description = "字典分类类型")
private String classType;
@Schema(description = "描述信息")
private String comment;
}

View File

@@ -57,6 +57,13 @@ public interface ISimulationSystemConfigService {
SdmResponse<Map<String, Map<String, Integer>>> multiQueryDictionaryIds(DictTagReq.BatchDictIdQueryReq req);
/**
* 批量查询字典详情(根据 dictId 列表)
* @param req 包含 dictId 列表的请求
* @return 字典详情列表
*/
SdmResponse<List<DataDictionary>> batchQueryDictionaries(DictTagReq.BatchDictQueryReq req);
SdmResponse addSystemParamConfig(SystemParamConfigBean configBean);
SdmResponse updateSystemParamConfig(SystemParamConfigBean configBean);

View File

@@ -23,15 +23,13 @@ import com.sdm.common.utils.PageUtils;
import com.sdm.system.dao.SimulationSystemMapper;
import com.sdm.system.model.bo.DictionaryClass;
import com.sdm.system.model.bo.FormConfigure;
import com.sdm.system.model.entity.SysDataDictionary;
import com.sdm.system.model.entity.SysFormConfigure;
import com.sdm.system.model.entity.SysFormUserConfigure;
import com.sdm.system.model.entity.SystemParamConfigBean;
import com.sdm.system.model.req.system.FormConfigureReq;
import com.sdm.system.model.resp.SimuDictionaryResp;
import com.sdm.system.service.IFormConfigureService;
import com.sdm.system.service.IFormUserConfigureService;
import com.sdm.system.service.ISimulationSystemConfigService;
import com.sdm.system.service.ISysUserService;
import com.sdm.system.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
@@ -77,6 +75,9 @@ public class SimulationSystemConfigServiceImpl extends BaseService implements IS
@Autowired
private IFormUserConfigureService formUserConfigureService;
@Autowired
private IDataDictionaryService dataDictionaryService;
/**
* 添加数据字典
* @param dict
@@ -961,6 +962,54 @@ public class SimulationSystemConfigServiceImpl extends BaseService implements IS
return SdmResponse.success(result);
}
@Override
public SdmResponse<List<DataDictionary>> batchQueryDictionaries(DictTagReq.BatchDictQueryReq req) {
long tenantId = ThreadLocalContext.getTenantId();
if (req.getItems() == null || req.getItems().isEmpty()) {
return SdmResponse.success(new ArrayList<>());
}
// 提取所有 dictId
List<Integer> dictIds = req.getItems().stream()
.map(DictTagReq::getDictId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (dictIds.isEmpty()) {
return SdmResponse.success(new ArrayList<>());
}
// 批量查询字典
List<SysDataDictionary> dictionaries = dataDictionaryService.lambdaQuery()
.eq(SysDataDictionary::getTenantId, tenantId)
.in(SysDataDictionary::getId, dictIds)
.list();
// 转换为 DataDictionary
List<DataDictionary> result = dictionaries.stream()
.map(dict -> {
DataDictionary dataDictionary = new DataDictionary();
dataDictionary.id = dict.getId();
dataDictionary.uuid = dict.getUuid();
dataDictionary.dictClass = dict.getDictClass();
dataDictionary.dictName = dict.getDictName();
dataDictionary.dictValue = dict.getDictValue();
dataDictionary.aliasName = dict.getAliasName();
dataDictionary.valueType = dict.getValueType();
dataDictionary.dictOrder = dict.getDictOrder();
dataDictionary.classType = dict.getClassType();
dataDictionary.comment = dict.getComment();
dataDictionary.tenantId = dict.getTenantId();
dataDictionary.creator = dict.getCreator();
return dataDictionary;
})
.collect(Collectors.toList());
return SdmResponse.success(result);
}
}