feat(sj_1.0.0): 优化重试场景的导出

This commit is contained in:
opensnail 2024-05-29 23:10:10 +08:00
parent fe4a69c66f
commit b80da2d616
9 changed files with 132 additions and 85 deletions

View File

@ -93,10 +93,6 @@ public class GroupConfigController {
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@LoginRequired(role = RoleEnum.ADMIN) @LoginRequired(role = RoleEnum.ADMIN)
public void importScene(@RequestPart("file") MultipartFile file) throws IOException { public void importScene(@RequestPart("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
throw new SnailJobCommonException("请选择一个文件上传");
}
groupConfigService.importGroup(ImportUtils.parseList(file, GroupConfigRequestVO.class)); groupConfigService.importGroup(ImportUtils.parseList(file, GroupConfigRequestVO.class));
} }

View File

@ -101,21 +101,16 @@ public class JobController {
} }
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@LoginRequired(role = RoleEnum.ADMIN) @LoginRequired
public void importScene(@RequestPart("file") MultipartFile file) throws IOException { public void importScene(@RequestPart("file") MultipartFile file) throws IOException {
if (file.isEmpty()) { jobService.importJobs(ImportUtils.parseList(file, JobRequestVO.class));
throw new SnailJobCommonException("请选择一个文件上传");
}
List<JobRequestVO> requestList = ImportUtils.parseList(file, JobRequestVO.class);
jobService.importJobs(requestList);
} }
@PostMapping("/export") @PostMapping("/export")
@LoginRequired(role = RoleEnum.ADMIN) @LoginRequired
@OriginalControllerReturnValue @OriginalControllerReturnValue
public ResponseEntity<String> exportGroup(@RequestBody Set<Long> jobIds) { public ResponseEntity<String> exportGroup(@RequestBody Set<Long> jobIds) {
String jobsJson = jobService.exportJobs(jobIds); return ExportUtils.doExport(jobService.exportJobs(jobIds));
return ExportUtils.doExport(jobsJson);
} }
} }

View File

@ -4,6 +4,7 @@ import com.aizuda.snailjob.common.core.annotation.OriginalControllerReturnValue;
import com.aizuda.snailjob.common.core.exception.SnailJobCommonException; import com.aizuda.snailjob.common.core.exception.SnailJobCommonException;
import com.aizuda.snailjob.server.web.annotation.LoginRequired; import com.aizuda.snailjob.server.web.annotation.LoginRequired;
import com.aizuda.snailjob.server.web.model.base.PageResult; import com.aizuda.snailjob.server.web.model.base.PageResult;
import com.aizuda.snailjob.server.web.model.request.ExportSceneVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO;
import com.aizuda.snailjob.server.web.model.response.SceneConfigResponseVO; import com.aizuda.snailjob.server.web.model.response.SceneConfigResponseVO;
@ -72,10 +73,6 @@ public class SceneConfigController {
@LoginRequired @LoginRequired
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void importScene(@RequestPart("file") MultipartFile file) throws IOException { public void importScene(@RequestPart("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
throw new SnailJobCommonException("请选择一个文件上传");
}
// 写入数据 // 写入数据
sceneConfigService.importSceneConfig(ImportUtils.parseList(file, SceneConfigRequestVO.class)); sceneConfigService.importSceneConfig(ImportUtils.parseList(file, SceneConfigRequestVO.class));
} }
@ -83,9 +80,8 @@ public class SceneConfigController {
@LoginRequired @LoginRequired
@PostMapping("/export") @PostMapping("/export")
@OriginalControllerReturnValue @OriginalControllerReturnValue
public ResponseEntity<String> export(@RequestBody Set<Long> sceneIds) { public ResponseEntity<String> export(@RequestBody ExportSceneVO exportSceneVO) {
return ExportUtils.doExport(sceneConfigService.exportSceneConfig(sceneIds)); return ExportUtils.doExport(sceneConfigService.exportSceneConfig(exportSceneVO));
} }
} }

View File

@ -0,0 +1,23 @@
package com.aizuda.snailjob.server.web.model.request;
import lombok.Data;
import java.util.Set;
/**
* @author: opensnail
* @date : 2024-05-29
* @since : sj_1.0.0
*/
@Data
public class ExportSceneVO {
private String groupName;
private Integer sceneStatus;
private String sceneName;
private Set<Long> sceneIds;
}

View File

@ -14,4 +14,5 @@ import lombok.EqualsAndHashCode;
public class SceneConfigQueryVO extends BaseQueryVO { public class SceneConfigQueryVO extends BaseQueryVO {
private String groupName; private String groupName;
private String sceneName; private String sceneName;
private Integer sceneStatus;
} }

View File

@ -1,6 +1,7 @@
package com.aizuda.snailjob.server.web.service; package com.aizuda.snailjob.server.web.service;
import com.aizuda.snailjob.server.web.model.base.PageResult; import com.aizuda.snailjob.server.web.model.base.PageResult;
import com.aizuda.snailjob.server.web.model.request.ExportSceneVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO;
import com.aizuda.snailjob.server.web.model.response.SceneConfigResponseVO; import com.aizuda.snailjob.server.web.model.response.SceneConfigResponseVO;
@ -8,7 +9,6 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* @author: opensnail * @author: opensnail
@ -30,6 +30,6 @@ public interface SceneConfigService {
void importSceneConfig(@Valid @NotEmpty(message = "导入数据不能为空") List<SceneConfigRequestVO> requests); void importSceneConfig(@Valid @NotEmpty(message = "导入数据不能为空") List<SceneConfigRequestVO> requests);
String exportSceneConfig(Set<Long> sceneIds); String exportSceneConfig(ExportSceneVO exportSceneVO);
} }

View File

@ -439,8 +439,7 @@ public class GroupConfigServiceImpl implements GroupConfigService {
return groupConfigs.stream().map(GroupConfigPartitionTask::new).toList(); return groupConfigs.stream().map(GroupConfigPartitionTask::new).toList();
}), partitionTasks -> { }), partitionTasks -> {
List<GroupConfigPartitionTask> configPartitionTasks = (List<GroupConfigPartitionTask>) partitionTasks; List<GroupConfigPartitionTask> configPartitionTasks = (List<GroupConfigPartitionTask>) partitionTasks;
List<GroupConfig> configs = configPartitionTasks.stream().map(GroupConfigPartitionTask::getConfig) List<GroupConfig> configs = StreamUtils.toList(configPartitionTasks, GroupConfigPartitionTask::getConfig);
.collect(Collectors.toList());
allRequestList.addAll(GroupConfigConverter.INSTANCE.toGroupConfigRequestVOs(configs)); allRequestList.addAll(GroupConfigConverter.INSTANCE.toGroupConfigRequestVOs(configs));
}, 0); }, 0);

View File

@ -5,10 +5,13 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.common.core.util.JsonUtil; import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.core.util.StreamUtils; import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.server.common.dto.PartitionTask;
import com.aizuda.snailjob.server.common.exception.SnailJobServerException; import com.aizuda.snailjob.server.common.exception.SnailJobServerException;
import com.aizuda.snailjob.server.common.strategy.WaitStrategies; import com.aizuda.snailjob.server.common.strategy.WaitStrategies;
import com.aizuda.snailjob.server.common.util.CronUtils; import com.aizuda.snailjob.server.common.util.CronUtils;
import com.aizuda.snailjob.server.common.util.PartitionTaskUtils;
import com.aizuda.snailjob.server.web.model.base.PageResult; import com.aizuda.snailjob.server.web.model.base.PageResult;
import com.aizuda.snailjob.server.web.model.request.ExportSceneVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigQueryVO;
import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO; import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO;
import com.aizuda.snailjob.server.web.model.request.UserSessionVO; import com.aizuda.snailjob.server.web.model.request.UserSessionVO;
@ -29,15 +32,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView; import com.google.common.collect.Sets.SetView;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.*;
import java.util.Optional;
import java.util.Set;
/** /**
* @author: opensnail * @author: opensnail
@ -75,9 +79,10 @@ public class SceneConfigServiceImpl implements SceneConfigService {
new LambdaQueryWrapper<RetrySceneConfig>() new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId) .eq(RetrySceneConfig::getNamespaceId, namespaceId)
.in(userSessionVO.isUser(), RetrySceneConfig::getGroupName, userSessionVO.getGroupNames()) .in(userSessionVO.isUser(), RetrySceneConfig::getGroupName, userSessionVO.getGroupNames())
.eq(Objects.nonNull(queryVO.getSceneStatus()), RetrySceneConfig::getSceneStatus, queryVO.getSceneStatus())
.eq(StrUtil.isNotBlank(queryVO.getGroupName()), .eq(StrUtil.isNotBlank(queryVO.getGroupName()),
RetrySceneConfig::getGroupName, StrUtil.trim(queryVO.getGroupName())) RetrySceneConfig::getGroupName, StrUtil.trim(queryVO.getGroupName()))
.eq(StrUtil.isNotBlank(queryVO.getSceneName()), .likeRight(StrUtil.isNotBlank(queryVO.getSceneName()),
RetrySceneConfig::getSceneName, StrUtil.trim(queryVO.getSceneName())) RetrySceneConfig::getSceneName, StrUtil.trim(queryVO.getSceneName()))
.orderByDesc(RetrySceneConfig::getCreateDt)); .orderByDesc(RetrySceneConfig::getCreateDt));
@ -187,18 +192,35 @@ public class SceneConfigServiceImpl implements SceneConfigService {
} }
@Override @Override
public String exportSceneConfig(final Set<Long> sceneIds) { public String exportSceneConfig(final ExportSceneVO exportSceneVO) {
List<SceneConfigRequestVO> requestList = new ArrayList<>();
PartitionTaskUtils.process(startId -> {
List<RetrySceneConfig> sceneConfigs = accessTemplate.getSceneConfigAccess() List<RetrySceneConfig> sceneConfigs = accessTemplate.getSceneConfigAccess()
.list(new LambdaQueryWrapper<RetrySceneConfig>() .listPage(new PageDTO<>(0, 500), new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, UserSessionUtils.currentUserSession().getNamespaceId()) .eq(RetrySceneConfig::getNamespaceId, UserSessionUtils.currentUserSession().getNamespaceId())
// TODO 若导出全部需要分页查询避免一次拉取太多数据 .eq(Objects.nonNull(exportSceneVO.getSceneStatus()), RetrySceneConfig::getSceneStatus, exportSceneVO.getSceneStatus())
.in(CollUtil.isNotEmpty(sceneIds), RetrySceneConfig::getId, sceneIds) .eq(StrUtil.isNotBlank(exportSceneVO.getGroupName()),
RetrySceneConfig::getGroupName, StrUtil.trim(exportSceneVO.getGroupName()))
.likeRight(StrUtil.isNotBlank(exportSceneVO.getSceneName()),
RetrySceneConfig::getSceneName, StrUtil.trim(exportSceneVO.getSceneName()))
.in(CollUtil.isNotEmpty(exportSceneVO.getSceneIds()), RetrySceneConfig::getId, exportSceneVO.getSceneIds())
.ge(RetrySceneConfig::getId, startId)
.orderByAsc(RetrySceneConfig::getId)
).getRecords();
return StreamUtils.toList(sceneConfigs, SceneConfigPartitionTask::new);
}, partitionTasks -> {
List<SceneConfigPartitionTask> partitionTaskList = (List<SceneConfigPartitionTask>) partitionTasks;
requestList.addAll(SceneConfigConverter.INSTANCE.toSceneConfigRequestVOs(
StreamUtils.toList(partitionTaskList, SceneConfigPartitionTask::getConfig))
); );
}, 0);
return JsonUtil.toJsonString(SceneConfigConverter.INSTANCE.toSceneConfigRequestVOs(sceneConfigs)); return JsonUtil.toJsonString(requestList);
} }
private void batchSaveSceneConfig(final List<SceneConfigRequestVO> requests, final String namespaceId) { private void batchSaveSceneConfig(final List<SceneConfigRequestVO> requests, final String namespaceId) {
Set<String> groupNameSet = Sets.newHashSet(); Set<String> groupNameSet = Sets.newHashSet();
@ -242,11 +264,22 @@ public class SceneConfigServiceImpl implements SceneConfigService {
retrySceneConfig.setTriggerInterval(StrUtil.EMPTY); retrySceneConfig.setTriggerInterval(StrUtil.EMPTY);
} }
// TODO 优化成批量插入
Assert.isTrue(1 == sceneConfigAccess.insert(retrySceneConfig), Assert.isTrue(1 == sceneConfigAccess.insert(retrySceneConfig),
() -> new SnailJobServerException("failed to insert scene. retrySceneConfig:[{}]", () -> new SnailJobServerException("failed to insert scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig))); JsonUtil.toJsonString(retrySceneConfig)));
} }
} }
@EqualsAndHashCode(callSuper = true)
@Getter
private static class SceneConfigPartitionTask extends PartitionTask {
// 这里就直接放RetrySceneConfig为了后面若加字段不需要再这里在调整了
private final RetrySceneConfig config;
public SceneConfigPartitionTask(@NotNull RetrySceneConfig config) {
this.config = config;
setId(config.getId());
}
}
} }

View File

@ -15,6 +15,10 @@ public class ImportUtils {
private static final List<String> FILE_EXTENSIONS = List.of("json"); private static final List<String> FILE_EXTENSIONS = List.of("json");
public static @NotNull <VO> List<VO> parseList(MultipartFile file, Class<VO> clazz) throws IOException { public static @NotNull <VO> List<VO> parseList(MultipartFile file, Class<VO> clazz) throws IOException {
if (file.isEmpty()) {
throw new SnailJobCommonException("请选择一个文件上传");
}
// 保存文件到服务器 // 保存文件到服务器
String suffix = FileUtil.getSuffix(file.getOriginalFilename()); String suffix = FileUtil.getSuffix(file.getOriginalFilename());
if (!FILE_EXTENSIONS.contains(suffix)) { if (!FILE_EXTENSIONS.contains(suffix)) {