feat(sj_1.0.0): 新增导入、导出 复制的接口功能

This commit is contained in:
opensnail 2024-05-26 23:27:06 +08:00
parent 08cf9e9909
commit c37c1ae685
5 changed files with 240 additions and 41 deletions

View File

@ -5,6 +5,7 @@ import com.aizuda.snailjob.server.web.model.base.PageResult;
import com.aizuda.snailjob.server.web.model.request.JobBatchQueryVO;
import com.aizuda.snailjob.server.web.model.response.JobBatchResponseVO;
import com.aizuda.snailjob.server.web.service.JobBatchService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -17,10 +18,9 @@ import java.util.List;
*/
@RestController
@RequestMapping("/job/batch")
@RequiredArgsConstructor
public class JobBatchController {
@Autowired
private JobBatchService jobBatchService;
private final JobBatchService jobBatchService;
@GetMapping("/list")
@LoginRequired

View File

@ -1,16 +1,38 @@
package com.aizuda.snailjob.server.web.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.server.HttpServerResponse;
import com.aizuda.snailjob.client.model.request.DispatchJobRequest;
import com.aizuda.snailjob.common.core.annotation.OriginalControllerReturnValue;
import com.aizuda.snailjob.common.core.exception.SnailJobCommonException;
import com.aizuda.snailjob.common.core.model.Result;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.server.common.util.DateUtils;
import com.aizuda.snailjob.server.web.annotation.LoginRequired;
import com.aizuda.snailjob.server.web.model.base.PageResult;
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.response.SceneConfigResponseVO;
import com.aizuda.snailjob.server.web.service.SceneConfigService;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* 重试场景接口
@ -20,10 +42,11 @@ import java.util.List;
*/
@RestController
@RequestMapping("/scene-config")
@RequiredArgsConstructor
public class SceneConfigController {
@Autowired
private SceneConfigService sceneConfigService;
private final List<String> FILE_EXTENSIONS = List.of("json");
private final SceneConfigService sceneConfigService;
@LoginRequired
@GetMapping("page/list")
@ -60,4 +83,61 @@ public class SceneConfigController {
public Boolean updateSceneConfig(@RequestBody @Validated SceneConfigRequestVO requestVO) {
return sceneConfigService.updateSceneConfig(requestVO);
}
@PostMapping("/import")
@LoginRequired
public void importScene(final MultipartFile file)
throws IOException {
if (file.isEmpty()) {
throw new SnailJobCommonException("Please select a file to upload");
}
// 保存文件到服务器
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
if (!FILE_EXTENSIONS.contains(suffix)) {
throw new SnailJobCommonException("文件类型错误");
}
JsonNode node = JsonUtil.toJson(file.getBytes());
List<SceneConfigRequestVO> requestList = JsonUtil.parseList(JsonUtil.toJsonString(node),
SceneConfigRequestVO.class);
// 校验参数是否合法
for (final SceneConfigRequestVO sceneConfigRequestVO : requestList) {
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Set<ConstraintViolation<SceneConfigRequestVO>> set = validator.validate(sceneConfigRequestVO);
for (final ConstraintViolation<SceneConfigRequestVO> violation : set) {
throw new SnailJobCommonException(violation.getMessage());
}
}
// 写入数据
sceneConfigService.importSceneConfig(requestList);
}
@LoginRequired
@PostMapping("/export")
@OriginalControllerReturnValue
public ResponseEntity<String> export(@RequestBody Set<Long> sceneIds) {
String configs = sceneConfigService.exportSceneConfig(sceneIds);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 设置下载时的文件名称
String fileName = String.format("%s.json", DateUtils.toNowFormat(DateUtils.PURE_DATETIME_MS_PATTERN));
String disposition = "attachment; filename=" +
new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
headers.add(HttpHeaders.CONTENT_DISPOSITION, disposition);
return ResponseEntity.ok()
.headers(headers)
.body(configs);
}
@LoginRequired
@PostMapping("/{targetNamespaceId}/batch/copy")
public void batchCopy(@PathVariable("targetNamespaceId") Long targetNamespaceId, @RequestBody Set<Long> sceneIds) {
sceneConfigService.batchCopy(targetNamespaceId, sceneIds);
}
}

View File

@ -6,6 +6,7 @@ import com.aizuda.snailjob.server.web.model.request.SceneConfigRequestVO;
import com.aizuda.snailjob.server.web.model.response.SceneConfigResponseVO;
import java.util.List;
import java.util.Set;
/**
* @author: opensnail
@ -24,4 +25,10 @@ public interface SceneConfigService {
SceneConfigResponseVO getSceneConfigDetail(Long id);
boolean updateStatus(Long id, final Integer status);
void importSceneConfig(List<SceneConfigRequestVO> requests);
String exportSceneConfig(Set<Long> sceneIds);
void batchCopy(Long targetNamespaceId, Set<Long> sceneIds);
}

View File

@ -5,6 +5,8 @@ import com.aizuda.snailjob.template.datasource.persistence.po.RetrySceneConfig;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author: opensnail
* @date : 2021-11-26 13:49
@ -14,6 +16,11 @@ public interface SceneConfigConverter {
SceneConfigConverter INSTANCE = Mappers.getMapper(SceneConfigConverter.class);
RetrySceneConfig convert(SceneConfigRequestVO requestVO);
RetrySceneConfig toRetrySceneConfig(SceneConfigRequestVO requestVO);
List<RetrySceneConfig> toRetrySceneConfigs(List<SceneConfigRequestVO> requestVOs);
List<SceneConfigRequestVO> toSceneConfigRequestVOs(List<RetrySceneConfig> requestVOs);
}

View File

@ -1,8 +1,10 @@
package com.aizuda.snailjob.server.web.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.server.common.exception.SnailJobServerException;
import com.aizuda.snailjob.server.common.strategy.WaitStrategies;
import com.aizuda.snailjob.server.common.util.CronUtils;
@ -18,17 +20,24 @@ import com.aizuda.snailjob.server.web.service.handler.SyncConfigHandler;
import com.aizuda.snailjob.server.web.util.UserSessionUtils;
import com.aizuda.snailjob.template.datasource.access.AccessTemplate;
import com.aizuda.snailjob.template.datasource.access.ConfigAccess;
import com.aizuda.snailjob.template.datasource.persistence.mapper.NamespaceMapper;
import com.aizuda.snailjob.template.datasource.persistence.po.GroupConfig;
import com.aizuda.snailjob.template.datasource.persistence.po.Namespace;
import com.aizuda.snailjob.template.datasource.persistence.po.RetrySceneConfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* @author: opensnail
@ -37,12 +46,14 @@ import java.util.Optional;
@Service
@RequiredArgsConstructor
public class SceneConfigServiceImpl implements SceneConfigService {
private final AccessTemplate accessTemplate;
private final NamespaceMapper namespaceMapper;
private static void checkExecuteInterval(SceneConfigRequestVO requestVO) {
if (Lists.newArrayList(WaitStrategies.WaitStrategyEnum.FIXED.getType(),
WaitStrategies.WaitStrategyEnum.RANDOM.getType())
.contains(requestVO.getBackOff())) {
WaitStrategies.WaitStrategyEnum.RANDOM.getType())
.contains(requestVO.getBackOff())) {
if (Integer.parseInt(requestVO.getTriggerInterval()) < 10) {
throw new SnailJobServerException("间隔时间不得小于10");
}
@ -60,14 +71,14 @@ public class SceneConfigServiceImpl implements SceneConfigService {
UserSessionVO userSessionVO = UserSessionUtils.currentUserSession();
String namespaceId = userSessionVO.getNamespaceId();
pageDTO = accessTemplate.getSceneConfigAccess().listPage(pageDTO,
new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.in(userSessionVO.isUser(), RetrySceneConfig::getGroupName, userSessionVO.getGroupNames())
.eq(StrUtil.isNotBlank(queryVO.getGroupName()),
RetrySceneConfig::getGroupName, StrUtil.trim(queryVO.getGroupName()))
.eq(StrUtil.isNotBlank(queryVO.getSceneName()),
RetrySceneConfig::getSceneName, StrUtil.trim(queryVO.getSceneName()))
.orderByDesc(RetrySceneConfig::getCreateDt));
new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.in(userSessionVO.isUser(), RetrySceneConfig::getGroupName, userSessionVO.getGroupNames())
.eq(StrUtil.isNotBlank(queryVO.getGroupName()),
RetrySceneConfig::getGroupName, StrUtil.trim(queryVO.getGroupName()))
.eq(StrUtil.isNotBlank(queryVO.getSceneName()),
RetrySceneConfig::getSceneName, StrUtil.trim(queryVO.getSceneName()))
.orderByDesc(RetrySceneConfig::getCreateDt));
return new PageResult<>(pageDTO, SceneConfigResponseVOConverter.INSTANCE.convertList(pageDTO.getRecords()));
@ -79,12 +90,12 @@ public class SceneConfigServiceImpl implements SceneConfigService {
String namespaceId = UserSessionUtils.currentUserSession().getNamespaceId();
List<RetrySceneConfig> retrySceneConfigs = accessTemplate.getSceneConfigAccess()
.list(new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, groupName)
.select(RetrySceneConfig::getSceneName,
RetrySceneConfig::getDescription, RetrySceneConfig::getMaxRetryCount)
.orderByDesc(RetrySceneConfig::getCreateDt));
.list(new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, groupName)
.select(RetrySceneConfig::getSceneName,
RetrySceneConfig::getDescription, RetrySceneConfig::getMaxRetryCount)
.orderByDesc(RetrySceneConfig::getCreateDt));
return SceneConfigResponseVOConverter.INSTANCE.convertList(retrySceneConfigs);
}
@ -96,14 +107,14 @@ public class SceneConfigServiceImpl implements SceneConfigService {
String namespaceId = UserSessionUtils.currentUserSession().getNamespaceId();
ConfigAccess<RetrySceneConfig> sceneConfigAccess = accessTemplate.getSceneConfigAccess();
Assert.isTrue(0 == sceneConfigAccess.count(
new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, requestVO.getGroupName())
.eq(RetrySceneConfig::getSceneName, requestVO.getSceneName())
new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, requestVO.getGroupName())
.eq(RetrySceneConfig::getSceneName, requestVO.getSceneName())
), () -> new SnailJobServerException("场景名称重复. {}", requestVO.getSceneName()));
RetrySceneConfig retrySceneConfig = SceneConfigConverter.INSTANCE.convert(requestVO);
RetrySceneConfig retrySceneConfig = SceneConfigConverter.INSTANCE.toRetrySceneConfig(requestVO);
retrySceneConfig.setCreateDt(LocalDateTime.now());
retrySceneConfig.setNamespaceId(namespaceId);
if (requestVO.getBackOff() == WaitStrategies.WaitStrategyEnum.DELAY_LEVEL.getType()) {
@ -111,8 +122,8 @@ public class SceneConfigServiceImpl implements SceneConfigService {
}
Assert.isTrue(1 == sceneConfigAccess.insert(retrySceneConfig),
() -> new SnailJobServerException("failed to insert scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig)));
() -> new SnailJobServerException("failed to insert scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig)));
// 同步配置到客户端
SyncConfigHandler.addSyncTask(requestVO.getGroupName(), namespaceId);
@ -123,7 +134,7 @@ public class SceneConfigServiceImpl implements SceneConfigService {
@Override
public Boolean updateSceneConfig(SceneConfigRequestVO requestVO) {
checkExecuteInterval(requestVO);
RetrySceneConfig retrySceneConfig = SceneConfigConverter.INSTANCE.convert(requestVO);
RetrySceneConfig retrySceneConfig = SceneConfigConverter.INSTANCE.toRetrySceneConfig(requestVO);
// 防止更新
retrySceneConfig.setSceneName(null);
retrySceneConfig.setGroupName(null);
@ -131,14 +142,15 @@ public class SceneConfigServiceImpl implements SceneConfigService {
String namespaceId = UserSessionUtils.currentUserSession().getNamespaceId();
retrySceneConfig.setTriggerInterval(Optional.ofNullable(retrySceneConfig.getTriggerInterval()).orElse(StrUtil.EMPTY));
retrySceneConfig.setTriggerInterval(
Optional.ofNullable(retrySceneConfig.getTriggerInterval()).orElse(StrUtil.EMPTY));
Assert.isTrue(1 == accessTemplate.getSceneConfigAccess().update(retrySceneConfig,
new LambdaUpdateWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, requestVO.getGroupName())
.eq(RetrySceneConfig::getSceneName, requestVO.getSceneName())),
() -> new SnailJobServerException("failed to update scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig)));
new LambdaUpdateWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.eq(RetrySceneConfig::getGroupName, requestVO.getGroupName())
.eq(RetrySceneConfig::getSceneName, requestVO.getSceneName())),
() -> new SnailJobServerException("failed to update scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig)));
// 同步配置到客户端
SyncConfigHandler.addSyncTask(requestVO.getGroupName(), namespaceId);
@ -147,7 +159,8 @@ public class SceneConfigServiceImpl implements SceneConfigService {
@Override
public SceneConfigResponseVO getSceneConfigDetail(Long id) {
RetrySceneConfig retrySceneConfig = accessTemplate.getSceneConfigAccess().one(new LambdaQueryWrapper<RetrySceneConfig>()
RetrySceneConfig retrySceneConfig = accessTemplate.getSceneConfigAccess()
.one(new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getId, id));
return SceneConfigResponseVOConverter.INSTANCE.convert(retrySceneConfig);
}
@ -161,8 +174,100 @@ public class SceneConfigServiceImpl implements SceneConfigService {
config.setSceneStatus(status);
return 1 == accessTemplate.getSceneConfigAccess().update(config,
new LambdaUpdateWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getId, id)
.eq(RetrySceneConfig::getNamespaceId, namespaceId));
new LambdaUpdateWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getId, id)
.eq(RetrySceneConfig::getNamespaceId, namespaceId));
}
@Override
@Transactional
public void importSceneConfig(final List<SceneConfigRequestVO> requests) {
batchSaveSceneConfig(requests, UserSessionUtils.currentUserSession().getNamespaceId());
}
@Override
public String exportSceneConfig(final Set<Long> sceneIds) {
List<RetrySceneConfig> sceneConfigs = accessTemplate.getSceneConfigAccess()
.list(new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, UserSessionUtils.currentUserSession().getNamespaceId())
.in(RetrySceneConfig::getId, sceneIds)
);
List<SceneConfigRequestVO> sceneConfigRequestVOs = SceneConfigConverter.INSTANCE.toSceneConfigRequestVOs(
sceneConfigs);
return JsonUtil.toJsonString(sceneConfigRequestVOs);
}
@Override
@Transactional
public void batchCopy(final Long targetNamespaceId, final Set<Long> sceneIds) {
Assert.notEmpty(sceneIds, () -> new SnailJobServerException("参数错误"));
Namespace namespace = namespaceMapper.selectById(targetNamespaceId);
Assert.notNull(namespace, () -> new SnailJobServerException("空间不存在"));
List<RetrySceneConfig> sceneConfigs = accessTemplate.getSceneConfigAccess()
.list(new LambdaQueryWrapper<RetrySceneConfig>()
.eq(RetrySceneConfig::getNamespaceId, UserSessionUtils.currentUserSession().getNamespaceId())
.in(RetrySceneConfig::getId, sceneIds)
);
Assert.isTrue(sceneIds.size() == sceneConfigs.size(), () -> new SnailJobServerException("存在部分场景配置不存在"));
List<SceneConfigRequestVO> sceneConfigRequestVOs = SceneConfigConverter.INSTANCE.toSceneConfigRequestVOs(
sceneConfigs);
// 批量写入
batchSaveSceneConfig(sceneConfigRequestVOs, namespace.getUniqueId());
}
private void batchSaveSceneConfig(final List<SceneConfigRequestVO> requests, final String namespaceId) {
Set<String> groupNameSet = Sets.newHashSet();
Set<String> sceneNameSet = Sets.newHashSet();
for (final SceneConfigRequestVO request : requests) {
checkExecuteInterval(request);
groupNameSet.add(request.getGroupName());
sceneNameSet.add(request.getSceneName());
}
List<GroupConfig> groupConfigs = accessTemplate.getGroupConfigAccess()
.list(new LambdaQueryWrapper<GroupConfig>()
.select(GroupConfig::getGroupName)
.eq(GroupConfig::getNamespaceId, namespaceId)
.in(GroupConfig::getGroupName, groupNameSet)
);
SetView<String> notExistedGroupNameSet = Sets.difference(groupNameSet,
StreamUtils.toSet(groupConfigs, GroupConfig::getGroupName));
Assert.notEmpty(notExistedGroupNameSet,
() -> new SnailJobServerException("组:{}不存在", notExistedGroupNameSet));
ConfigAccess<RetrySceneConfig> sceneConfigAccess = accessTemplate.getSceneConfigAccess();
List<RetrySceneConfig> sceneConfigs = sceneConfigAccess.list(
new LambdaQueryWrapper<RetrySceneConfig>()
.select(RetrySceneConfig::getSceneName)
.eq(RetrySceneConfig::getNamespaceId, namespaceId)
.in(RetrySceneConfig::getGroupName, groupNameSet)
.eq(RetrySceneConfig::getSceneName, sceneNameSet));
Assert.isTrue(CollUtil.isEmpty(sceneConfigs), () -> new SnailJobServerException("场景:{}已存在",
StreamUtils.toSet(sceneConfigs, RetrySceneConfig::getSceneName)));
LocalDateTime now = LocalDateTime.now();
List<RetrySceneConfig> retrySceneConfigs = SceneConfigConverter.INSTANCE.toRetrySceneConfigs(requests);
for (final RetrySceneConfig retrySceneConfig : retrySceneConfigs) {
retrySceneConfig.setCreateDt(now);
retrySceneConfig.setNamespaceId(namespaceId);
if (retrySceneConfig.getBackOff() == WaitStrategies.WaitStrategyEnum.DELAY_LEVEL.getType()) {
retrySceneConfig.setTriggerInterval(StrUtil.EMPTY);
}
// TODO 优化成批量插入
Assert.isTrue(1 == sceneConfigAccess.insert(retrySceneConfig),
() -> new SnailJobServerException("failed to insert scene. retrySceneConfig:[{}]",
JsonUtil.toJsonString(retrySceneConfig)));
}
}
}