Develop op (#35)

* refactor: enhance CleaningTaskService and related components with validation and repository updates
* feature: 支持算子上传创建
This commit is contained in:
hhhhsc701
2025-10-30 17:17:00 +08:00
committed by GitHub
parent 8d2b41ed94
commit b9b97c1ac2
63 changed files with 1190 additions and 1177 deletions

View File

@@ -6,13 +6,16 @@ import com.datamate.operator.domain.repository.CategoryRepository;
import com.datamate.operator.interfaces.dto.CategoryDto;
import com.datamate.operator.interfaces.dto.CategoryRelationDto;
import com.datamate.operator.interfaces.dto.CategoryTreeResponse;
import com.datamate.operator.interfaces.dto.SubCategory;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@@ -26,37 +29,40 @@ public class CategoryService {
List<CategoryDto> allCategories = categoryRepo.findAllCategories();
List<CategoryRelationDto> allRelations = categoryRelationRepo.findAllRelation();
Map<Integer, Integer> relationMap = allRelations.stream()
Map<String, Integer> relationMap = allRelations.stream()
.collect(Collectors.groupingBy(
CategoryRelationDto::getCategoryId,
Collectors.collectingAndThen(Collectors.counting(), Math::toIntExact)));
Map<Integer, String> nameMap = allCategories.stream()
.collect(Collectors.toMap(CategoryDto::getId, CategoryDto::getName));
Map<Integer, List<CategoryDto>> groupedByParentId = allCategories.stream()
.filter(relation -> relation.getParentId() > 0)
Map<String, CategoryDto> nameMap = allCategories.stream()
.collect(Collectors.toMap(CategoryDto::getId, Function.identity()));
Map<String, List<CategoryDto>> groupedByParentId = allCategories.stream()
.filter(relation -> !StringUtils.equals(relation.getParentId(), "0"))
.collect(Collectors.groupingBy(CategoryDto::getParentId));
return groupedByParentId.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.sorted(categoryComparator(nameMap))
.map(entry -> {
Integer parentId = entry.getKey();
String parentId = entry.getKey();
List<CategoryDto> group = entry.getValue();
CategoryTreeResponse response = new CategoryTreeResponse();
response.setId(parentId);
response.setName(nameMap.get(parentId));
response.setName(nameMap.get(parentId).getName());
AtomicInteger totalCount = new AtomicInteger();
response.setCategories(group.stream().map(category -> {
SubCategory subCategory = new SubCategory();
subCategory.setId(category.getId());
subCategory.setName(category.getName());
subCategory.setCount(relationMap.getOrDefault(category.getId(), 0));
response.setCategories(group.stream().peek(category -> {
category.setCount(relationMap.getOrDefault(category.getId(), 0));
totalCount.getAndAdd(relationMap.getOrDefault(category.getId(), 0));
subCategory.setParentId(parentId);
return subCategory;
}).toList());
}).sorted(Comparator.comparing(CategoryDto::getCreatedAt)).toList());
response.setCount(totalCount.get());
return response;
}).toList();
}
private Comparator<Map.Entry<String, List<CategoryDto>>> categoryComparator(Map<String, CategoryDto> categoryMap) {
return (entry1, entry2) -> {
LocalDateTime index1 = categoryMap.get(entry1.getKey()).getCreatedAt();
LocalDateTime index2 = categoryMap.get(entry2.getKey()).getCreatedAt();
return index1.compareTo(index2);
};
}
}

View File

@@ -1,21 +0,0 @@
package com.datamate.operator.application;
import com.datamate.operator.interfaces.dto.LabelDto;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Collections;
@Service
public class LabelService {
public List<LabelDto> getLabels(Integer page, Integer size, String keyword) {
// TODO: 查询标签列表
return Collections.emptyList();
}
public void updateLabel(String id, List<LabelDto> updateLabelDtoRequest) {
// TODO: 更新标签
}
public void createLabels(LabelDto labelsPostRequest) {
// TODO: 批量创建标签
}
}

View File

@@ -1,15 +1,22 @@
package com.datamate.operator.application;
import com.datamate.common.domain.model.ChunkUploadPreRequest;
import com.datamate.common.domain.service.FileService;
import com.datamate.operator.domain.contants.OperatorConstant;
import com.datamate.operator.infrastructure.converter.OperatorConverter;
import com.datamate.operator.domain.model.OperatorView;
import com.datamate.operator.domain.repository.CategoryRelationRepository;
import com.datamate.operator.domain.repository.OperatorRepository;
import com.datamate.operator.domain.repository.OperatorViewRepository;
import com.datamate.operator.infrastructure.parser.ParserHolder;
import com.datamate.operator.interfaces.dto.OperatorDto;
import com.datamate.operator.interfaces.dto.UploadOperatorRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.List;
@Service
@@ -21,14 +28,21 @@ public class OperatorService {
private final CategoryRelationRepository relationRepo;
public List<OperatorDto> getOperators(Integer page, Integer size, List<Integer> categories,
private final ParserHolder parserHolder;
private final FileService fileService;
@Value("${operator.base.path:/operator}")
private String operatorBasePath;
public List<OperatorDto> getOperators(Integer page, Integer size, List<String> categories,
String operatorName, Boolean isStar) {
List<OperatorView> filteredOperators = operatorViewRepo.findOperatorsByCriteria(page, size, operatorName,
categories, isStar);
return filteredOperators.stream().map(OperatorConverter.INSTANCE::fromEntityToDto).toList();
}
public int getOperatorsCount(List<Integer> categories, String operatorName, Boolean isStar) {
public int getOperatorsCount(List<String> categories, String operatorName, Boolean isStar) {
return operatorViewRepo.countOperatorsByCriteria(operatorName, categories, isStar);
}
@@ -37,20 +51,60 @@ public class OperatorService {
return OperatorConverter.INSTANCE.fromEntityToDto(operator);
}
@Transactional
public OperatorDto createOperator(OperatorDto req) {
operatorRepo.insertOperator(req);
relationRepo.batchInsert(req.getId(), req.getCategories());
parserHolder.extractTo(getFileType(req.getFileName()), getUploadPath(req.getFileName()),
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
return getOperatorById(req.getId());
}
@Transactional
public OperatorDto updateOperator(String id, OperatorDto req) {
operatorRepo.updateOperator(req);
relationRepo.batchInsert(id, req.getCategories());
parserHolder.extractTo(getFileType(req.getFileName()), getUploadPath(req.getFileName()),
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
return getOperatorById(id);
}
public OperatorDto uploadOperator(MultipartFile file, String description) {
// TODO: 文件上传与解析
return new OperatorDto();
@Transactional
public void deleteOperator(String id) {
operatorRepo.deleteOperator(id);
relationRepo.deleteByOperatorId(id);
}
public OperatorDto uploadOperator(String fileName) {
return parserHolder.parseYamlFromArchive(getFileType(fileName), new File(getUploadPath(fileName)),
OperatorConstant.YAML_PATH);
}
public String preUpload() {
ChunkUploadPreRequest request = ChunkUploadPreRequest.builder().build();
request.setUploadPath(operatorBasePath + File.separator + "upload");
request.setTotalFileNum(1);
request.setServiceId(OperatorConstant.SERVICE_ID);
return fileService.preUpload(request);
}
public void chunkUpload(UploadOperatorRequest request) {
fileService.chunkUpload(OperatorConverter.INSTANCE.toChunkRequest(request));
}
private String getFileType(String fileName) {
return fileName.substring(fileName.lastIndexOf('.') + 1);
}
private String getFileNameWithoutExtension(String fileName) {
return fileName.substring(0, fileName.lastIndexOf('.'));
}
private String getUploadPath(String fileName) {
return operatorBasePath + File.separator + "upload" + File.separator + fileName;
}
private String getExtractPath(String fileName) {
return operatorBasePath + File.separator + "extract" + File.separator + fileName;
}
}

View File

@@ -0,0 +1,42 @@
package com.datamate.operator.domain.contants;
import java.util.HashMap;
import java.util.Map;
public class OperatorConstant {
public static String SERVICE_ID = "operator";
public static String YAML_PATH = "metadata.yml";
public static String CATEGORY_PYTHON = "python";
public static String CATEGORY_PYTHON_ID = "9eda9d5d-072b-499b-916c-797a0a8750e1";
public static String CATEGORY_JAVA = "java";
public static String CATEGORY_JAVA_ID = "b5bfc548-8ef6-417c-b8a6-a4197c078249";
public static String CATEGORY_CUSTOMIZED_ID = "ec2cdd17-8b93-4a81-88c4-ac9e98d10757";
public static String CATEGORY_TEXT_ID = "d8a5df7a-52a9-42c2-83c4-01062e60f597";
public static String CATEGORY_IMAGE_ID = "de36b61c-9e8a-4422-8c31-d30585c7100f";
public static String CATEGORY_AUDIO_ID = "42dd9392-73e4-458c-81ff-41751ada47b5";
public static String CATEGORY_VIDEO_ID = "a233d584-73c8-4188-ad5d-8f7c8dda9c27";
public static String CATEGORY_ALL_ID = "4d7dbd77-0a92-44f3-9056-2cd62d4a71e4";
public static Map<String, String> CATEGORY_MAP = new HashMap<>();
static {
CATEGORY_MAP.put(CATEGORY_PYTHON, CATEGORY_PYTHON_ID);
CATEGORY_MAP.put(CATEGORY_JAVA, CATEGORY_JAVA_ID);
CATEGORY_MAP.put("text", CATEGORY_TEXT_ID);
CATEGORY_MAP.put("image", CATEGORY_IMAGE_ID);
CATEGORY_MAP.put("audio", CATEGORY_AUDIO_ID);
CATEGORY_MAP.put("video", CATEGORY_VIDEO_ID);
CATEGORY_MAP.put("all", CATEGORY_ALL_ID);
}
}

View File

@@ -4,15 +4,21 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Setter
@Getter
@TableName(value = "t_operator_category", autoResultMap = true)
public class Category {
private Integer id;
private String id;
private String name;
private String value;
private String type;
private Integer parentId;
private String parentId;
private LocalDateTime createdAt;
}

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
@AllArgsConstructor
@TableName(value = "t_operator_category_relation", autoResultMap = true)
public class CategoryRelation {
private Integer categoryId;
private String categoryId;
private String operatorId;
}

View File

@@ -3,7 +3,6 @@ package com.datamate.operator.domain.repository;
import com.baomidou.mybatisplus.extension.repository.IRepository;
import com.datamate.operator.domain.model.CategoryRelation;
import com.datamate.operator.interfaces.dto.CategoryRelationDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -11,5 +10,7 @@ public interface CategoryRelationRepository extends IRepository<CategoryRelation
List<CategoryRelationDto> findAllRelation();
void batchInsert(@Param("operatorId") String operatorId, @Param("categories") List<Integer> categories);
void batchInsert(String operatorId, List<String> categories);
void deleteByOperatorId(String operatorId);
}

View File

@@ -7,9 +7,11 @@ import com.datamate.operator.interfaces.dto.OperatorDto;
import java.util.List;
public interface OperatorRepository extends IRepository<Operator> {
List<Operator> findAllOperators();
List<OperatorDto> findAllOperators();
void updateOperator(OperatorDto operator);
void insertOperator(OperatorDto operator);
void deleteOperator(String id);
}

View File

@@ -7,9 +7,9 @@ import java.util.List;
public interface OperatorViewRepository extends IRepository<OperatorView> {
List<OperatorView> findOperatorsByCriteria(Integer page, Integer size, String operatorName,
List<Integer> categories, Boolean isStar);
List<String> categories, Boolean isStar);
Integer countOperatorsByCriteria(String operatorName, List<Integer> categories, Boolean isStar);
Integer countOperatorsByCriteria(String operatorName, List<String> categories, Boolean isStar);
OperatorView findOperatorById(String id);
}

View File

@@ -1,8 +1,10 @@
package com.datamate.operator.infrastructure.converter;
import com.datamate.common.domain.model.ChunkUploadRequest;
import com.datamate.operator.domain.model.Operator;
import com.datamate.operator.domain.model.OperatorView;
import com.datamate.operator.interfaces.dto.OperatorDto;
import com.datamate.operator.interfaces.dto.UploadOperatorRequest;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@@ -19,13 +21,17 @@ public interface OperatorConverter {
@Mapping(target = "categories", source = "categories", qualifiedByName = "stringToList")
OperatorDto fromEntityToDto(OperatorView operator);
List<OperatorDto> fromEntityToDto(List<Operator> operator);
@Named("stringToList")
static List<Integer> stringToList(String input) {
static List<String> stringToList(String input) {
if (input == null || input.isEmpty()) {
return Collections.emptyList();
}
return Arrays.stream(input.split(",")).map(Integer::valueOf).toList();
return Arrays.stream(input.split(",")).map(String::valueOf).toList();
}
Operator fromDtoToEntity(OperatorDto operator);
ChunkUploadRequest toChunkRequest(UploadOperatorRequest request);
}

View File

@@ -0,0 +1,21 @@
package com.datamate.operator.infrastructure.exception;
import com.datamate.common.infrastructure.exception.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum OperatorErrorCode implements ErrorCode {
/**
* 不支持的文件类型
*/
UNSUPPORTED_FILE_TYPE("op.0001", "不支持的文件类型"),
YAML_NOT_FOUND("op.0002", "算子中缺少元数据文件"),
FIELD_NOT_FOUND("op.0003", "缺少必要的字段");
private final String code;
private final String message;
}

View File

@@ -0,0 +1,80 @@
package com.datamate.operator.infrastructure.parser;
import com.datamate.common.infrastructure.exception.BusinessException;
import com.datamate.common.infrastructure.exception.SystemErrorCode;
import com.datamate.operator.domain.contants.OperatorConstant;
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
import com.datamate.operator.interfaces.dto.OperatorDto;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public abstract class AbstractParser {
protected ObjectMapper objectMapper = new ObjectMapper();
protected OperatorDto parseYaml(InputStream yamlContent) {
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
Map<String, Object> content = yaml.load(yamlContent);
OperatorDto operator = new OperatorDto();
operator.setId(toStringIfNotNull(content.get("raw_id")));
operator.setName(toStringIfNotNull(content.get("name")));
operator.setDescription(toStringIfNotNull(content.get("description")));
operator.setVersion(toStringIfNotNull(content.get("version")));
operator.setInputs(toStringIfNotNull(content.get("inputs")));
operator.setOutputs(toStringIfNotNull(content.get("outputs")));
operator.setRuntime(toJsonIfNotNull(content.get("runtime")));
operator.setSettings(toJsonIfNotNull(content.get("settings")));
List<String> categories = new ArrayList<>();
categories.add(OperatorConstant.CATEGORY_MAP.get(toLowerCaseIfNotNull(content.get("language"))));
categories.add(OperatorConstant.CATEGORY_MAP.get(toLowerCaseIfNotNull(content.get("modal"))));
categories.add(OperatorConstant.CATEGORY_CUSTOMIZED_ID);
operator.setCategories(categories);
return operator;
};
/**
* 从压缩包内读取指定路径的 yaml 文件并解析为指定类型
* @param archive 压缩包路径(zip 或 tar)
* @param entryPath 压缩包内部的文件路径,例如 "config/app.yaml" 或 "./config/app.yaml"
* @return 解析后的对象
*/
public abstract OperatorDto parseYamlFromArchive(File archive, String entryPath);
/**
* 将压缩包解压到目标目录(保持相对路径)
* @param archive 压缩包路径
* @param targetDir 目标目录
*/
public abstract void extractTo(File archive, String targetDir);
private String toStringIfNotNull(Object obj) {
if (obj == null) {
throw BusinessException.of(OperatorErrorCode.FIELD_NOT_FOUND);
}
return obj.toString();
}
private String toLowerCaseIfNotNull(Object obj) {
if (obj == null) {
throw BusinessException.of(OperatorErrorCode.FIELD_NOT_FOUND);
}
return obj.toString().toLowerCase(Locale.ROOT);
}
private String toJsonIfNotNull(Object obj) {
try {
return obj == null ? null : objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw BusinessException.of(SystemErrorCode.UNKNOWN_ERROR, e.getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
package com.datamate.operator.infrastructure.parser;
import com.datamate.common.infrastructure.exception.BusinessException;
import com.datamate.common.infrastructure.exception.SystemErrorCode;
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
import com.datamate.operator.interfaces.dto.OperatorDto;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class ParserHolder {
// 存放 parser:key 为 parser 类型标识(例如 "zip" 或 "tar"),value 为 parser 实例
private final Map<String, AbstractParser> parserMap = new ConcurrentHashMap<>();
// 注册 parser(可在启动时调用)
public void registerParser(String type, AbstractParser parser) {
if (type == null || parser == null) {
throw BusinessException.of(SystemErrorCode.UNKNOWN_ERROR);
}
parserMap.put(type, parser);
}
// 根据类型获取 parser(可能为 null)
public AbstractParser getParser(String type) {
return parserMap.get(type);
}
// 便捷代理:从指定类型的压缩包中读取 entry 并解析为 clazz
public OperatorDto parseYamlFromArchive(String type, File archive, String entryPath) {
AbstractParser parser = getParser(type);
if (parser == null) {
throw BusinessException.of(OperatorErrorCode.UNSUPPORTED_FILE_TYPE,
"No parser registered for type: " + type);
}
return parser.parseYamlFromArchive(archive, entryPath);
}
// 便捷代理:将指定类型的压缩包解压到目标目录
public void extractTo(String type, File archive, String targetDir) {
AbstractParser parser = getParser(type);
if (parser == null) {
throw BusinessException.of(OperatorErrorCode.UNSUPPORTED_FILE_TYPE,
"No parser registered for type: " + type);
}
parser.extractTo(archive, targetDir);
}
public void extractTo(String type, String sourceDir, String targetDir) {
extractTo(type, new File(sourceDir), targetDir);
}
@PostConstruct
public void init() {
// 注册 zip 和 tar parser,key 可根据需要调整(例如 "zip"/"tar")
registerParser("zip", new ZipParser());
registerParser("tar", new TarParser());
}
}

View File

@@ -0,0 +1,77 @@
package com.datamate.operator.infrastructure.parser;
import com.datamate.common.infrastructure.exception.BusinessException;
import com.datamate.common.infrastructure.exception.SystemErrorCode;
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
import com.datamate.operator.interfaces.dto.OperatorDto;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
public class TarParser extends AbstractParser {
@Override
public OperatorDto parseYamlFromArchive(File archive, String entryPath) {
// 允许带或不带前导 "./"
String normalized = entryPath.startsWith("./") ? entryPath.substring(2) : entryPath;
try (InputStream fis = Files.newInputStream(archive.toPath());
TarArchiveInputStream tis = new TarArchiveInputStream(fis)) {
TarArchiveEntry entry;
while ((entry = tis.getNextEntry()) != null) {
String name = entry.getName();
if (Objects.equals(name, entryPath) || Objects.equals(name, normalized)) {
// 使用 SnakeYAML 解析当前 entry 的内容到目标类型
return parseYaml(tis);
}
}
} catch (IOException e) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, e.getMessage());
}
throw BusinessException.of(OperatorErrorCode.YAML_NOT_FOUND, "Entry not found in tar: " + entryPath);
}
@Override
public void extractTo(File archive, String targetDir) {
Path targetPath = Paths.get(targetDir);
try (InputStream fis = Files.newInputStream(archive.toPath());
TarArchiveInputStream tis = new TarArchiveInputStream(fis)) {
Files.createDirectories(targetPath);
TarArchiveEntry entry;
while ((entry = tis.getNextEntry()) != null) {
String entryName = entry.getName();
// 去掉可能的前导 "./"
if (entryName.startsWith("./")) {
entryName = entryName.substring(2);
}
Path resolved = targetPath.resolve(entryName).toAbsolutePath().normalize();
if (!resolved.startsWith(targetPath.toAbsolutePath().normalize())) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, "Bad tar entry: " + entryName);
}
if (entry.isDirectory()) {
Files.createDirectories(resolved);
} else {
Files.createDirectories(resolved.getParent());
try (OutputStream os = Files.newOutputStream(resolved)) {
byte[] buffer = new byte[8192];
int len;
while ((len = tis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
}
}
} catch (IOException e) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, e.getMessage());
}
}
}

View File

@@ -0,0 +1,77 @@
package com.datamate.operator.infrastructure.parser;
import com.datamate.common.infrastructure.exception.BusinessException;
import com.datamate.common.infrastructure.exception.SystemErrorCode;
import com.datamate.operator.infrastructure.exception.OperatorErrorCode;
import com.datamate.operator.interfaces.dto.OperatorDto;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ZipParser extends AbstractParser {
@Override
public OperatorDto parseYamlFromArchive(File archive, String entryPath) {
try (ZipFile zipFile = new ZipFile(archive)) {
// 允许带或不带前导 "./"
String normalized = entryPath.startsWith("./") ? entryPath.substring(2) : entryPath;
ZipEntry entry = zipFile.getEntry(entryPath);
if (entry == null) {
entry = zipFile.getEntry(normalized);
}
if (entry == null) {
throw BusinessException.of(OperatorErrorCode.YAML_NOT_FOUND, "Entry not found in zip: " + entryPath);
}
try (InputStream is = zipFile.getInputStream(entry)) {
// 使用 SnakeYAML 解析为目标类型
return parseYaml(is);
}
} catch (IOException e) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, e.getMessage());
}
}
@Override
public void extractTo(File archive, String targetDir) {
Path targetPath = Paths.get(targetDir);
try (ZipFile zipFile = new ZipFile(archive)) {
Files.createDirectories(targetPath);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String entryName = entry.getName();
// 防止 Zip Slip:确保解压路径仍在 targetDir 下
Path resolved = targetPath.resolve(entryName).toAbsolutePath().normalize();
if (!resolved.startsWith(targetPath.toAbsolutePath().normalize())) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, "Bad zip entry: " + entryName);
}
if (entry.isDirectory()) {
Files.createDirectories(resolved);
} else {
Files.createDirectories(resolved.getParent());
try (InputStream is = zipFile.getInputStream(entry);
OutputStream os = Files.newOutputStream(resolved)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
}
}
} catch (IOException e) {
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR, e.getMessage());
}
}
}

View File

@@ -1,5 +1,6 @@
package com.datamate.operator.infrastructure.persistence.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
import com.datamate.operator.domain.model.CategoryRelation;
import com.datamate.operator.domain.repository.CategoryRelationRepository;
@@ -23,10 +24,17 @@ public class CategoryRelationRepositoryImpl extends CrudRepository<CategoryRelat
}
@Override
public void batchInsert(String operatorId, List<Integer> categories) {
public void batchInsert(String operatorId, List<String> categories) {
List<CategoryRelation> categoryRelations = categories.stream()
.map(category -> new CategoryRelation(category, operatorId))
.toList();
mapper.insert(categoryRelations);
}
@Override
public void deleteByOperatorId(String operatorId) {
LambdaQueryWrapper<CategoryRelation> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CategoryRelation::getOperatorId, operatorId);
mapper.delete(queryWrapper);
}
}

View File

@@ -17,8 +17,8 @@ public class OperatorRepositoryImpl extends CrudRepository<OperatorMapper, Opera
private final OperatorMapper mapper;
@Override
public List<Operator> findAllOperators() {
return mapper.selectList(null);
public List<OperatorDto> findAllOperators() {
return OperatorConverter.INSTANCE.fromEntityToDto(mapper.selectList(null));
}
@Override
@@ -30,4 +30,9 @@ public class OperatorRepositoryImpl extends CrudRepository<OperatorMapper, Opera
public void insertOperator(OperatorDto operator) {
mapper.insert(OperatorConverter.INSTANCE.fromDtoToEntity(operator));
}
@Override
public void deleteOperator(String id) {
mapper.deleteById(id);
}
}

View File

@@ -22,7 +22,7 @@ public class OperatorViewRepositoryImpl extends CrudRepository<OperatorViewMappe
@Override
public List<OperatorView> findOperatorsByCriteria(Integer page, Integer size, String operatorName,
List<Integer> categories, Boolean isStar) {
List<String> categories, Boolean isStar) {
QueryWrapper<OperatorView> queryWrapper = Wrappers.query();
queryWrapper.in(CollectionUtils.isNotEmpty(categories), "category_id", categories)
.like(StringUtils.isNotBlank(operatorName), "operator_name", operatorName)
@@ -37,7 +37,7 @@ public class OperatorViewRepositoryImpl extends CrudRepository<OperatorViewMappe
}
@Override
public Integer countOperatorsByCriteria(String operatorName, List<Integer> categories, Boolean isStar) {
public Integer countOperatorsByCriteria(String operatorName, List<String> categories, Boolean isStar) {
QueryWrapper<OperatorView> queryWrapper = Wrappers.query();
queryWrapper.in(CollectionUtils.isNotEmpty(categories),"category_id", categories)
.like(StringUtils.isNotBlank(operatorName), "operator_name", operatorName)

View File

@@ -3,14 +3,22 @@ package com.datamate.operator.interfaces.dto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Setter
@Getter
public class CategoryDto {
private Integer id;
private String id;
private String name;
private String value;
private long count;
private String type;
private Integer parentId;
private String parentId;
private LocalDateTime createdAt;
}

View File

@@ -6,7 +6,7 @@ import lombok.Setter;
@Setter
@Getter
public class CategoryRelationDto {
private Integer categoryId;
private String categoryId;
private String operatorId;
}

View File

@@ -12,11 +12,11 @@ import java.util.List;
@Setter
@NoArgsConstructor
public class CategoryTreeResponse {
private Integer id;
private String id;
private String name;
private Integer count;
private List<SubCategory> categories = new ArrayList<>();
private List<CategoryDto> categories = new ArrayList<>();
}

View File

@@ -14,32 +14,32 @@ import java.util.List;
@Getter
@Setter
public class OperatorDto {
private String id;
private String id;
private String name;
private String name;
private String description;
private String description;
private String version;
private String version;
private String inputs;
private String inputs;
private String outputs;
private String outputs;
private List<Integer> categories;
private List<String> categories;
private String runtime;
private String runtime;
private String settings;
private String settings;
private String fileName;
private String fileName;
private Boolean isStar;
private Boolean isStar;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime createdAt;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime createdAt;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime updatedAt;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime updatedAt;
}

View File

@@ -19,7 +19,7 @@ public class OperatorsListPostRequest {
private Integer size;
private List<Integer> categories = new ArrayList<>();
private List<String> categories = new ArrayList<>();
private String operatorName;

View File

@@ -1,18 +0,0 @@
package com.datamate.operator.interfaces.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SubCategory {
private long id;
private String name;
private long count;
private String type;
private long parentId;
}

View File

@@ -0,0 +1,34 @@
package com.datamate.operator.interfaces.dto;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;
/**
* 上传文件请求
* 用于分块上传文件时的请求参数封装,支持大文件分片上传功能
*/
@Getter
@Setter
public class UploadOperatorRequest {
/** 预上传返回的id,用来确认同一个任务 */
private String reqId;
/** 文件编号,用于标识批量上传中的第几个文件 */
private int fileNo;
/** 文件名称 */
private String fileName;
/** 文件总分块数量 */
private int totalChunkNum;
/** 当前分块编号,从1开始 */
private int chunkNo;
/** 上传的文件分块内容 */
private MultipartFile file;
/** 文件分块的校验和(十六进制字符串),用于验证文件完整性 */
private String checkSumHex;
}

View File

@@ -1,11 +1,9 @@
package com.datamate.operator.interfaces.rest;
import com.datamate.common.infrastructure.common.Response;
import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.CategoryService;
import com.datamate.operator.interfaces.dto.CategoryTreeResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -20,8 +18,8 @@ public class CategoryController {
private final CategoryService categoryService;
@GetMapping("/tree")
public ResponseEntity<Response<PagedResponse<CategoryTreeResponse>>> categoryTreeGet() {
public PagedResponse<CategoryTreeResponse> categoryTreeGet() {
List<CategoryTreeResponse> allCategories = categoryService.getAllCategories();
return ResponseEntity.ok(Response.ok(PagedResponse.of(allCategories)));
return PagedResponse.of(allCategories);
}
}

View File

@@ -1,40 +0,0 @@
package com.datamate.operator.interfaces.rest;
import com.datamate.common.infrastructure.common.Response;
import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.LabelService;
import com.datamate.operator.interfaces.dto.LabelDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/labels")
@RequiredArgsConstructor
public class LabelController {
private final LabelService labelService;
@GetMapping
public ResponseEntity<Response<PagedResponse<LabelDto>>> labelsGet(@RequestParam("page") Integer page,
@RequestParam("size") Integer size,
@RequestParam("keyword") String keyword) {
return ResponseEntity.ok(Response.ok(PagedResponse.of(labelService.getLabels(page, size, keyword))));
}
@PutMapping("/{id}")
public ResponseEntity<Response<Object>> labelsIdPut(@PathVariable("id") String id,
@RequestBody List<LabelDto> updateLabelDtoRequest) {
labelService.updateLabel(id, updateLabelDtoRequest);
return ResponseEntity.ok(Response.ok(null));
}
@PostMapping
public ResponseEntity<Response<Object>> labelsPost(@RequestBody LabelDto labelsPostRequest) {
labelService.createLabels(labelsPostRequest);
return ResponseEntity.ok(Response.ok(null));
}
}

View File

@@ -1,14 +1,13 @@
package com.datamate.operator.interfaces.rest;
import com.datamate.common.infrastructure.common.Response;
import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.OperatorService;
import com.datamate.operator.interfaces.dto.OperatorDto;
import com.datamate.operator.interfaces.dto.OperatorsListPostRequest;
import com.datamate.operator.interfaces.dto.UploadOperatorRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -19,34 +18,48 @@ public class OperatorController {
private final OperatorService operatorService;
@PostMapping("/list")
public ResponseEntity<Response<PagedResponse<OperatorDto>>> operatorsListPost(@RequestBody OperatorsListPostRequest request) {
public PagedResponse<OperatorDto> operatorsListPost(@RequestBody OperatorsListPostRequest request) {
List<OperatorDto> responses = operatorService.getOperators(request.getPage(), request.getSize(),
request.getCategories(), request.getOperatorName(), request.getIsStar());
int count = operatorService.getOperatorsCount(request.getCategories(), request.getOperatorName(),
request.getIsStar());
int totalPages = (count + request.getSize() + 1) / request.getSize();
return ResponseEntity.ok(Response.ok(PagedResponse.of(responses, request.getPage(), count, totalPages)));
return PagedResponse.of(responses, request.getPage(), count, totalPages);
}
@GetMapping("/{id}")
public ResponseEntity<Response<OperatorDto>> operatorsIdGet(@PathVariable("id") String id) {
return ResponseEntity.ok(Response.ok(operatorService.getOperatorById(id)));
public OperatorDto operatorsIdGet(@PathVariable("id") String id) {
return operatorService.getOperatorById(id);
}
@PutMapping("/{id}")
public ResponseEntity<Response<OperatorDto>> operatorsIdPut(@PathVariable("id") String id,
public OperatorDto operatorsIdPut(@PathVariable("id") String id,
@RequestBody OperatorDto updateOperatorRequest) {
return ResponseEntity.ok(Response.ok(operatorService.updateOperator(id, updateOperatorRequest)));
return operatorService.updateOperator(id, updateOperatorRequest);
}
@PostMapping("/create")
public ResponseEntity<Response<OperatorDto>> operatorsCreatePost(@RequestBody OperatorDto createOperatorRequest) {
return ResponseEntity.ok(Response.ok(operatorService.createOperator(createOperatorRequest)));
public OperatorDto operatorsCreatePost(@RequestBody OperatorDto createOperatorRequest) {
return operatorService.createOperator(createOperatorRequest);
}
@PostMapping("/upload")
public ResponseEntity<Response<OperatorDto>> operatorsUploadPost(@RequestPart(value = "file") MultipartFile file,
@RequestParam(value = "description") String description) {
return ResponseEntity.ok(Response.ok(operatorService.uploadOperator(file, description)));
public OperatorDto operatorsUploadPost(@RequestBody UploadOperatorRequest request) {
return operatorService.uploadOperator(request.getFileName());
}
@PostMapping(value = "/upload/pre-upload", produces = MediaType.APPLICATION_JSON_VALUE)
public String preUpload() {
return operatorService.preUpload();
}
@PostMapping("/upload/chunk")
public void chunkUpload(@ModelAttribute UploadOperatorRequest request) {
operatorService.chunkUpload(request);
}
@DeleteMapping("/{id}")
public void operatorDelete(@PathVariable("id") String id) {
operatorService.deleteOperator(id);
}
}