feature: 增加算子详情页;优化算子上传更新逻辑 (#64)

* feature: 增加算子详情页;优化算子上传更新逻辑
This commit is contained in:
hhhhsc701
2025-11-07 16:54:00 +08:00
committed by GitHub
parent 78f50ea520
commit 2138ba23c7
24 changed files with 338 additions and 456 deletions

View File

@@ -1,8 +1,10 @@
package com.datamate.operator.application;
import com.datamate.operator.domain.contants.OperatorConstant;
import com.datamate.operator.domain.repository.CategoryRelationRepository;
import com.datamate.operator.domain.repository.CategoryRepository;
import com.datamate.operator.domain.repository.OperatorRepository;
import com.datamate.operator.interfaces.dto.CategoryDto;
import com.datamate.operator.interfaces.dto.CategoryRelationDto;
import com.datamate.operator.interfaces.dto.CategoryTreeResponse;
@@ -11,9 +13,7 @@ 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.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -21,6 +21,8 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CategoryService {
private final OperatorRepository operatorRepo;
private final CategoryRepository categoryRepo;
private final CategoryRelationRepository categoryRelationRepo;
@@ -40,7 +42,7 @@ public class CategoryService {
.filter(relation -> !StringUtils.equals(relation.getParentId(), "0"))
.collect(Collectors.groupingBy(CategoryDto::getParentId));
return groupedByParentId.entrySet().stream()
List<CategoryTreeResponse> categoryTreeResponses = groupedByParentId.entrySet().stream()
.sorted(categoryComparator(nameMap))
.map(entry -> {
String parentId = entry.getKey();
@@ -55,7 +57,11 @@ public class CategoryService {
}).sorted(Comparator.comparing(CategoryDto::getCreatedAt)).toList());
response.setCount(totalCount.get());
return response;
}).toList();
}).collect(Collectors.toCollection(ArrayList::new));
int stars = operatorRepo.countOperatorByStar(true);
categoryTreeResponses.add(buildStarCategoryTree(stars));
return categoryTreeResponses;
}
private Comparator<Map.Entry<String, List<CategoryDto>>> categoryComparator(Map<String, CategoryDto> categoryMap) {
@@ -65,4 +71,21 @@ public class CategoryService {
return index1.compareTo(index2);
};
}
private CategoryTreeResponse buildStarCategoryTree(int stars) {
CategoryTreeResponse starResponse = new CategoryTreeResponse();
starResponse.setName("收藏状态");
starResponse.setCount(stars);
starResponse.setId("257b27e0-bba9-11f0-89d7-00155d0a6153");
CategoryDto star = new CategoryDto();
star.setId(OperatorConstant.CATEGORY_STAR_ID);
star.setName("已收藏");
star.setValue("isStar");
star.setCount(stars);
star.setParentId("257b27e0-bba9-11f0-89d7-00155d0a6153");
star.setCreatedAt(LocalDateTime.now());
star.setType("predefined");
starResponse.setCategories(Collections.singletonList(star));
return starResponse;
}
}

View File

@@ -2,24 +2,35 @@ package com.datamate.operator.application;
import com.datamate.common.domain.model.ChunkUploadPreRequest;
import com.datamate.common.domain.service.FileService;
import com.datamate.common.infrastructure.exception.BusinessException;
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.exception.OperatorErrorCode;
import com.datamate.operator.infrastructure.parser.ParserHolder;
import com.datamate.operator.interfaces.dto.OperatorDto;
import com.datamate.operator.interfaces.dto.UploadOperatorRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@Slf4j
@RequiredArgsConstructor
public class OperatorService {
private final OperatorRepository operatorRepo;
@@ -32,6 +43,8 @@ public class OperatorService {
private final FileService fileService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Value("${operator.base.path:/operators}")
private String operatorBasePath;
@@ -53,19 +66,25 @@ public class OperatorService {
@Transactional
public OperatorDto createOperator(OperatorDto req) {
overrideSettings(req);
operatorRepo.insertOperator(req);
relationRepo.batchInsert(req.getId(), req.getCategories());
parserHolder.extractTo(getFileType(req.getFileName()), getUploadPath(req.getFileName()),
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
return getOperatorById(req.getId());
}
@Transactional
public OperatorDto updateOperator(String id, OperatorDto req) {
overrideSettings(req);
operatorRepo.updateOperator(req);
relationRepo.batchInsert(id, req.getCategories());
parserHolder.extractTo(getFileType(req.getFileName()), getUploadPath(req.getFileName()),
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
if (CollectionUtils.isNotEmpty(req.getCategories())) {
relationRepo.batchUpdate(id, req.getCategories());
}
if (StringUtils.isNotBlank(req.getFileName())) {
parserHolder.extractTo(getFileType(req.getFileName()), getUploadPath(req.getFileName()),
getExtractPath(getFileNameWithoutExtension(req.getFileName())));
}
return getOperatorById(id);
}
@@ -77,7 +96,7 @@ public class OperatorService {
public OperatorDto uploadOperator(String fileName) {
return parserHolder.parseYamlFromArchive(getFileType(fileName), new File(getUploadPath(fileName)),
OperatorConstant.YAML_PATH);
OperatorConstant.YAML_PATH);
}
public String preUpload() {
@@ -107,4 +126,76 @@ public class OperatorService {
private String getExtractPath(String fileName) {
return operatorBasePath + File.separator + "extract" + File.separator + fileName;
}
private void overrideSettings(OperatorDto operatorDto) {
if (StringUtils.isBlank(operatorDto.getSettings()) || MapUtils.isEmpty(operatorDto.getOverrides())) {
return;
}
try {
Map<String, Map<String, Object>> settings = objectMapper.readValue(operatorDto.getSettings(), Map.class);
for (Map.Entry<String, Object> entry : operatorDto.getOverrides().entrySet()) {
String key = entry.getKey();
if (!settings.containsKey(key)) {
continue;
}
Object value = entry.getValue();
Map<String, Object> setting = settings.get(key);
String type = setting.get("type").toString();
switch (type) {
case "slider":
case "switch":
case "select":
case "input":
case "radio":
setting.put("defaultVal", value);
break;
case "checkbox":
setting.put("defaultVal", convertObjectToListString(value));
break;
case "range":
updateProperties(setting, value);
default:
}
settings.put(key, setting);
}
operatorDto.setSettings(objectMapper.writeValueAsString(settings));
} catch (JsonProcessingException e) {
throw BusinessException.of(OperatorErrorCode.SETTINGS_PARSE_FAILED, e.getMessage());
}
}
private String convertObjectToListString(Object object) {
if (object == null) {
return null;
} else if (object instanceof List<?> list) {
List<String> result = new ArrayList<>();
for (Object item : list) {
result.add(String.valueOf(item));
}
return String.join(",", result);
} else {
return object.toString();
}
}
private void updateProperties(Map<String, Object> setting, Object value) {
List<Object> defaultValue = new ArrayList<>();
if (value instanceof List) {
defaultValue.addAll((List<?>) value);
}
Object properties = setting.get("properties");
if (properties instanceof List<?> list) {
if (defaultValue.size() != list.size()) {
return;
}
List<Map<String, Object>> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = objectMapper.convertValue(list.get(i), Map.class);
map.put("defaultVal", defaultValue.get(i));
result.add(map);
}
setting.put("properties", result);
}
}
}

View File

@@ -28,6 +28,8 @@ public class OperatorConstant {
public static String CATEGORY_ALL_ID = "4d7dbd77-0a92-44f3-9056-2cd62d4a71e4";
public static String CATEGORY_STAR_ID = "51847c24-bba9-11f0-888b-5b143cb738aa";
public static Map<String, String> CATEGORY_MAP = new HashMap<>();
static {

View File

@@ -12,5 +12,7 @@ public interface CategoryRelationRepository extends IRepository<CategoryRelation
void batchInsert(String operatorId, List<String> categories);
void batchUpdate(String operatorId, List<String> categories);
void deleteByOperatorId(String operatorId);
}

View File

@@ -14,4 +14,6 @@ public interface OperatorRepository extends IRepository<Operator> {
void insertOperator(OperatorDto operator);
void deleteOperator(String id);
int countOperatorByStar(boolean isStar);
}

View File

@@ -14,7 +14,9 @@ public enum OperatorErrorCode implements ErrorCode {
YAML_NOT_FOUND("op.0002", "算子中缺少元数据文件"),
FIELD_NOT_FOUND("op.0003", "缺少必要的字段");
FIELD_NOT_FOUND("op.0003", "缺少必要的字段"),
SETTINGS_PARSE_FAILED("op.0004", "settings字段解析失败");
private final String code;
private final String message;

View File

@@ -31,6 +31,17 @@ public class CategoryRelationRepositoryImpl extends CrudRepository<CategoryRelat
mapper.insert(categoryRelations);
}
@Override
public void batchUpdate(String operatorId, List<String> categories) {
List<CategoryRelation> categoryRelations = categories.stream()
.map(category -> new CategoryRelation(category, operatorId))
.toList();
LambdaQueryWrapper<CategoryRelation> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CategoryRelation::getOperatorId, operatorId);
mapper.delete(queryWrapper);
mapper.insert(categoryRelations);
}
@Override
public void deleteByOperatorId(String operatorId) {
LambdaQueryWrapper<CategoryRelation> queryWrapper = new LambdaQueryWrapper<>();

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.infrastructure.converter.OperatorConverter;
import com.datamate.operator.domain.model.Operator;
@@ -35,4 +36,11 @@ public class OperatorRepositoryImpl extends CrudRepository<OperatorMapper, Opera
public void deleteOperator(String id) {
mapper.deleteById(id);
}
@Override
public int countOperatorByStar(boolean isStar) {
LambdaQueryWrapper<Operator> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Operator::getIsStar, isStar);
return Math.toIntExact(mapper.selectCount(queryWrapper));
}
}

View File

@@ -27,7 +27,8 @@ public class OperatorViewRepositoryImpl extends CrudRepository<OperatorViewMappe
queryWrapper.in(CollectionUtils.isNotEmpty(categories), "category_id", categories)
.like(StringUtils.isNotBlank(operatorName), "operator_name", operatorName)
.eq(isStar != null, "is_star", isStar)
.groupBy("operator_id");
.groupBy("operator_id")
.orderByDesc("created_at");
Page<OperatorView> queryPage = null;
if (size != null && page != null) {
queryPage = new Page<>(page + 1, size);

View File

@@ -24,7 +24,7 @@ public interface OperatorViewMapper extends BaseMapper<OperatorView> {
@Select("SELECT operator_id AS id, operator_name AS name, description, version, inputs, outputs, runtime, " +
"settings, is_star, created_at, updated_at, " +
"GROUP_CONCAT(category_id ORDER BY created_at DESC SEPARATOR ',') AS categories " +
"GROUP_CONCAT(category_name ORDER BY created_at DESC SEPARATOR ',') AS categories " +
"FROM v_operator WHERE operator_id = #{id}")
OperatorView findOperatorById(@Param("id") String id);
}

View File

@@ -6,6 +6,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* OperatorDto
@@ -32,6 +33,8 @@ public class OperatorDto {
private String settings;
private Map<String, Object> overrides;
private String fileName;
private Boolean isStar;

View File

@@ -2,10 +2,12 @@ package com.datamate.operator.interfaces.rest;
import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.OperatorService;
import com.datamate.operator.domain.contants.OperatorConstant;
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.apache.commons.collections4.CollectionUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -19,10 +21,16 @@ public class OperatorController {
@PostMapping("/list")
public PagedResponse<OperatorDto> operatorsListPost(@RequestBody OperatorsListPostRequest request) {
Boolean isStar = null;
List<String> categories = request.getCategories();
if (CollectionUtils.isNotEmpty(request.getCategories()) &&
request.getCategories().contains(OperatorConstant.CATEGORY_STAR_ID)) {
isStar = true;
categories.remove(OperatorConstant.CATEGORY_STAR_ID);
}
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());
categories, request.getOperatorName(), isStar);
int count = operatorService.getOperatorsCount(categories, request.getOperatorName(), isStar);
int totalPages = (count + request.getSize() + 1) / request.getSize();
return PagedResponse.of(responses, request.getPage(), count, totalPages);
}