feat(项目设置): 应用设置-测试计划配置-功能代码

This commit is contained in:
WangXu10 2023-08-28 16:18:10 +08:00 committed by 刘瑞斌
parent 95edab784c
commit 47a70f2eeb
27 changed files with 390 additions and 74 deletions

View File

@ -0,0 +1,23 @@
package io.metersphere.sdk.config;
import io.metersphere.sdk.sechedule.BaseScheduleService;
import io.metersphere.sdk.sechedule.ScheduleManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ScheduleConfig {
@Bean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public ScheduleManager scheduleManager() {
return new ScheduleManager();
}
@Bean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public BaseScheduleService baseScheduleService() {
return new BaseScheduleService();
}
}

View File

@ -112,4 +112,7 @@ public class PermissionConstants {
public static final String PROJECT_MESSAGE_READ_UPDATE = "PROJECT_MESSAGE:READ+UPDATE";
public static final String PROJECT_MESSAGE_READ_ADD = "PROJECT_MESSAGE:READ+ADD";
public static final String PROJECT_MESSAGE_READ_DELETE = "PROJECT_MESSAGE:READ+DELETE";
public static final String PROJECT_APPLICATION_TEST_PLAN_READ = "PROJECT_APPLICATION_TEST_PLAN:READ";
public static final String PROJECT_APPLICATION_TEST_PLAN_UPDATE = "PROJECT_APPLICATION_TEST_PLAN:UPDATE";
}

View File

@ -0,0 +1,48 @@
package io.metersphere.sdk.constants;
/**
* 应用设置 -相关配置
*/
public enum ProjectApplicationType {
/**
* 工作台 我的待办
*/
APPLICATION_WORKSTATION,
//测试计划
/**
* 自定义测试计划报告保留范围 是否开启标识 type
*/
APPLICATION_CLEAN_TEST_PLAN_REPORT,
/**
* 自定义测试计划报告保留范围 value
*/
APPLICATION_CLEAN_TEST_PLAN_REPORT_VALUE,
/**
* 自定义测试报告有效期 是否开启标识 type
*/
APPLICATION_TEST_PLAN_SHARE_REPORT,
/**
* 自定义测试报告有效期 value
*/
APPLICATION_TEST_PLAN_SHARE_REPORT_VALUE,
//缺陷管理
/**
* 同步缺陷 标识
*/
APPLICATION_ISSUE,
/**
* 缺陷模板
*/
APPLICATION_ISSUE_TEMPLATE,
}

View File

@ -0,0 +1,5 @@
package io.metersphere.sdk.constants;
public enum ScheduleType {
CRON, SIMPLE
}

View File

@ -9,7 +9,6 @@ public class BaseSystemConfigDTO {
private String url;
private String concurrency;
private String prometheusHost;
private String seleniumDockerUrl;
private String runMode;
private String docUrl;
}

View File

@ -20,4 +20,10 @@ public class ProjectServiceInvoker {
service.deleteResources(projectId);
}
}
public void invokeReportServices(String projectId) {
for (CleanupProjectResourceService service : cleanupProjectResourceServices) {
service.cleanReportResources(projectId);
}
}
}

View File

@ -1,15 +1,20 @@
package io.metersphere.sdk.sechedule;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.system.domain.Schedule;
import io.metersphere.system.domain.ScheduleExample;
import io.metersphere.system.mapper.ScheduleMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Transactional(rollbackFor = Exception.class)
@ -77,4 +82,23 @@ public class BaseScheduleService {
private void removeJob(String key, String job) {
scheduleManager.removeJob(new JobKey(key, job), new TriggerKey(key, job));
}
public void addOrUpdateCronJob(Schedule request, JobKey jobKey, TriggerKey triggerKey, Class clazz) {
Boolean enable = request.getEnable();
String cronExpression = request.getValue();
if (Optional.ofNullable(enable).isPresent() && StringUtils.isNotBlank(cronExpression)) {
try {
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, clazz, cronExpression,
scheduleManager.getDefaultJobDataMap(request, cronExpression, SessionUtils.getUser().getId()));
} catch (SchedulerException e) {
throw new MSException("定时任务开启异常: " + e.getMessage());
}
} else {
try {
scheduleManager.removeJob(jobKey, triggerKey);
} catch (Exception e) {
throw new MSException("定时任务关闭异常: " + e.getMessage());
}
}
}
}

View File

@ -5,4 +5,11 @@ package io.metersphere.sdk.service;
*/
public interface CleanupProjectResourceService {
void deleteResources(String projectId);
/**
* 清理报告资源
* @param projectId
*/
void cleanReportResources(String projectId);
}

View File

@ -104,4 +104,7 @@ permission.project_file.name=File management
permission.project_template.name=Template management
permission.project_message.name=Message management
permission.project_fake_error.name=Fake error
permission.project_application.name=Project application
permission.project_application_test_plan.read=Test plan read
permission.project_application_test_plan.update=Test plan update

View File

@ -104,3 +104,6 @@ permission.project_file.name=文件管理
permission.project_template.name=模版管理
permission.project_message.name=消息管理
permission.project_fake_error.name=误报库
permission.project_application.name=应用管理
permission.project_application_test_plan.read=测试计划-查询
permission.project_application_test_plan.update=测试计划-编辑

View File

@ -103,4 +103,7 @@ permission.project_environment.name=環境配置
permission.project_file.name=文件管理
permission.project_template.name=模版管理
permission.project_message.name=消息管理
permission.project_fake_error.name=誤報庫
permission.project_fake_error.name=誤報庫
permission.project_application.name=應用管理
permission.project_application_test_plan.read=測試計劃-查詢
permission.project_application_test_plan.update=測試計劃-編輯

View File

@ -11,4 +11,9 @@ public class CleanupAPIResourceService implements CleanupProjectResourceService
public void deleteResources(String projectId) {
LogUtils.info("删除当前项目[" + projectId + "]相关接口测试资源");
}
@Override
public void cleanReportResources(String projectId) {
LogUtils.info("清理当前项目[" + projectId + "]相关接口测试报告资源");
}
}

View File

@ -23,4 +23,9 @@ public class CleanupResourceTests {
resourceService.deleteResources("test");
}
@Test
@Order(1)
public void testCleanupReportResource() throws Exception {
resourceService.cleanReportResources("test");
}
}

View File

@ -11,4 +11,9 @@ public class CleanupBugResourceService implements CleanupProjectResourceService
public void deleteResources(String projectId) {
LogUtils.info("删除当前项目[" + projectId + "]相关缺陷资源");
}
@Override
public void cleanReportResources(String projectId) {
LogUtils.info("清理当前项目[" + projectId + "]相关缺陷报告资源");
}
}

View File

@ -23,4 +23,9 @@ public class CleanupResourceTests {
resourceService.deleteResources("test");
}
@Test
@Order(2)
public void testCleanupReportResource() throws Exception {
resourceService.cleanReportResources("test");
}
}

View File

@ -11,4 +11,9 @@ public class CleanupCaseResourceService implements CleanupProjectResourceService
public void deleteResources(String projectId) {
LogUtils.info("删除当前项目[" + projectId + "]相关功能用例资源");
}
@Override
public void cleanReportResources(String projectId) {
LogUtils.info("清理当前项目[" + projectId + "]相关功能用例报告资源");
}
}

View File

@ -23,4 +23,9 @@ public class CleanupResourceTests {
resourceService.deleteResources("test");
}
@Test
@Order(2)
public void testCleanupReportResource() throws Exception {
resourceService.cleanReportResources("test");
}
}

View File

@ -2,35 +2,31 @@ package io.metersphere.project.controller;
import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.validation.groups.Created;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.log.annotation.Log;
import io.metersphere.sdk.log.constants.OperationLogType;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "项目管理-应用设置")
@RestController
@RequestMapping("project/application")
@RequestMapping("/project/application")
public class ProjectApplicationController {
@Resource
private ProjectApplicationService projectApplicationService;
@PostMapping("save")
public ProjectApplication save(@Validated({Created.class}) @RequestBody ProjectApplication application) {
return projectApplicationService.save(application);
}
/**
* 更新
*/
@PostMapping("update")
@PostMapping("/update")
@Operation(summary = "应用设置-测试计划-配置")
@RequiresPermissions(PermissionConstants.PROJECT_APPLICATION_TEST_PLAN_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.updateLog(#application)", msClass = ProjectApplicationService.class)
public ProjectApplication update(@Validated({Updated.class}) @RequestBody ProjectApplication application) {
return projectApplicationService.update(application);
}
@GetMapping("list/{projectId}")
public List<ProjectApplication> update(@PathVariable String projectId) {
return projectApplicationService.list(projectId);
}
}

View File

@ -0,0 +1,25 @@
package io.metersphere.project.job;
import io.metersphere.sdk.sechedule.BaseScheduleJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
public class CleanUpReportJob extends BaseScheduleJob {
@Override
protected void businessExecute(JobExecutionContext context) {
//TODO 定时任务执行 清除报告 invokerReportServices
//serviceInvoker.invokeReportServices(projectId);
}
public static JobKey getJobKey(String projectId) {
return new JobKey(projectId, CleanUpReportJob.class.getName());
}
public static TriggerKey getTriggerKey(String projectId) {
return new TriggerKey(projectId, CleanUpReportJob.class.getName());
}
}

View File

@ -2,12 +2,26 @@ package io.metersphere.project.service;
import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.project.domain.ProjectApplicationExample;
import io.metersphere.project.job.CleanUpReportJob;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.sdk.constants.OperationLogConstants;
import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.constants.ScheduleType;
import io.metersphere.sdk.dto.LogDTO;
import io.metersphere.sdk.log.constants.OperationLogModule;
import io.metersphere.sdk.log.constants.OperationLogType;
import io.metersphere.sdk.sechedule.BaseScheduleService;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.system.domain.Schedule;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
@Transactional
@ -15,19 +29,94 @@ public class ProjectApplicationService {
@Resource
private ProjectApplicationMapper projectApplicationMapper;
public ProjectApplication save(ProjectApplication application) {
projectApplicationMapper.insert(application);
return application;
}
@Resource
private BaseScheduleService baseScheduleService;
public ProjectApplication update(ProjectApplication application) {
projectApplicationMapper.updateByPrimaryKey(application);
//定时任务配置检查是否存在定时任务配置存在则更新不存在则新增
this.doBeforeUpdate(application);
//配置信息入库
this.createOrUpdateConfig(application);
return application;
}
public List<ProjectApplication> list(String projectId) {
private void createOrUpdateConfig(ProjectApplication application) {
String type = application.getType();
String projectId = application.getProjectId();
ProjectApplicationExample example = new ProjectApplicationExample();
example.createCriteria().andProjectIdEqualTo(projectId);
return projectApplicationMapper.selectByExample(example);
example.createCriteria().andProjectIdEqualTo(projectId).andTypeEqualTo(type);
if (projectApplicationMapper.countByExample(example) > 0) {
example.clear();
example.createCriteria().andProjectIdEqualTo(projectId).andTypeEqualTo(type);
projectApplicationMapper.updateByExample(application, example);
} else {
projectApplicationMapper.insertSelective(application);
}
}
private void doBeforeUpdate(ProjectApplication application) {
String type = application.getType();
//TODO 自定义id配置 &其他配置
if (StringUtils.equals(type, ProjectApplicationType.APPLICATION_CLEAN_TEST_PLAN_REPORT.name())) {
//处理测试计划报告 定时任务
this.doHandleSchedule(application);
}
}
private void doHandleSchedule(ProjectApplication application) {
String typeValue = application.getTypeValue();
String projectId = application.getProjectId();
Boolean enable = BooleanUtils.isTrue(Boolean.valueOf(typeValue));
Schedule schedule = baseScheduleService.getScheduleByResource(application.getProjectId(), CleanUpReportJob.class.getName());
Optional<Schedule> optional = Optional.ofNullable(schedule);
optional.ifPresentOrElse(s -> {
s.setEnable(enable);
baseScheduleService.editSchedule(s);
baseScheduleService.addOrUpdateCronJob(s,
CleanUpReportJob.getJobKey(projectId),
CleanUpReportJob.getTriggerKey(projectId),
CleanUpReportJob.class);
},() -> {
Schedule request = new Schedule();
request.setName("Clean Report Job");
request.setResourceId(projectId);
request.setKey(projectId);
request.setProjectId(projectId);
request.setEnable(enable);
request.setCreateUser(SessionUtils.getUserId());
request.setType(ScheduleType.CRON.name());
// 每天凌晨2点执行清理任务
request.setValue("0 0 2 * * ?");
request.setJob(CleanUpReportJob.class.getName());
baseScheduleService.addSchedule(request);
baseScheduleService.addOrUpdateCronJob(request,
CleanUpReportJob.getJobKey(projectId),
CleanUpReportJob.getTriggerKey(projectId),
CleanUpReportJob.class);
});
}
/**
* 更新接口日志
*
* @param application
* @return
*/
public LogDTO updateLog(ProjectApplication application) {
ProjectApplicationExample example = new ProjectApplicationExample();
example.createCriteria().andTypeEqualTo(application.getType()).andProjectIdEqualTo(application.getProjectId());
List<ProjectApplication> list = projectApplicationMapper.selectByExample(example);
LogDTO dto = new LogDTO(
OperationLogConstants.SYSTEM,
OperationLogConstants.SYSTEM,
OperationLogConstants.SYSTEM,
null,
OperationLogType.ADD.name(),
OperationLogModule.PROJECT_PROJECT_MANAGER,
"测试计划配置");
dto.setOriginalValue(JSON.toJSONBytes(list));
return dto;
}
}

View File

@ -113,6 +113,21 @@
"id": "PROJECT_FAKE_ERROR:READ+DELETE"
}
]
},
{
"id": "PROJECT_APPLICATION",
"name": "permission.project_application.name",
"license": true,
"permissions": [
{
"id": "PROJECT_APPLICATION_TEST_PLAN:READ",
"name": "permission.project_application_test_plan.read"
},
{
"id": "PROJECT_APPLICATION_TEST_PLAN:UPDATE",
"name": "permission.project_application_test_plan.update"
}
]
}
]
}

View File

@ -1,74 +1,74 @@
package io.metersphere.project.controller;
import io.metersphere.project.controller.param.ProjectApplicationDefinition;
import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.sdk.base.BaseTest;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class ProjectApplicationControllerTests extends BaseTest {
@Resource
private MockMvc mockMvc;
@Test
@Order(0)
public void testAddApp() throws Exception {
ProjectApplication projectApplication = new ProjectApplication();
projectApplication.setProjectId("1");
projectApplication.setType("1");
projectApplication.setTypeValue("1");
mockMvc.perform(MockMvcRequestBuilders.post("/project/application/save")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(projectApplication))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data.typeValue").value("1"));
}
// 测试计划
public static final String TEST_PLAN_CLEAN_REPORT_URL = "/project/application/update";
//清理报告配置
public static final String TEST_PLAN_CLEAN_REPORT = "APPLICATION_CLEAN_TEST_PLAN_REPORT";
//分享报告配置
public static final String TEST_PLAN_SHARE_REPORT = "APPLICATION_TEST_PLAN_SHARE_REPORT";
/**
* 应用配置 - 测试计划 清理报告配置
* @throws Exception
*/
@Test
@Order(1)
public void testUpdateApp() throws Exception {
ProjectApplication projectApplication = new ProjectApplication();
projectApplication.setProjectId("1");
projectApplication.setType("1");
projectApplication.setTypeValue("2");
mockMvc.perform(MockMvcRequestBuilders.post("/project/application/update")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(projectApplication))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data.typeValue").value("2"));
public void testTestPlanClean() throws Exception {
//新增
ProjectApplication request = creatRequest(TEST_PLAN_CLEAN_REPORT);
this.requestPost(TEST_PLAN_CLEAN_REPORT_URL, request);
//更新
request.setTypeValue("false");
this.requestPost(TEST_PLAN_CLEAN_REPORT_URL, request);
// @@异常参数校验
updatedGroupParamValidateTest(ProjectApplicationDefinition.class, TEST_PLAN_CLEAN_REPORT_URL);
}
/**
* 应用管理 测试计划 - 分享报告配置
* @throws Exception
*/
@Test
@Order(2)
public void testListApp() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/project/application/list/1")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data[0].typeValue").value("2"));
public void testUpdateApp() throws Exception {
//新增
ProjectApplication request = creatRequest(TEST_PLAN_SHARE_REPORT);
this.requestPost(TEST_PLAN_CLEAN_REPORT_URL, request);
//更新
request.setTypeValue("false");
this.requestPost(TEST_PLAN_CLEAN_REPORT_URL, request);
}
private ProjectApplication creatRequest(String type) {
ProjectApplication projectApplication = new ProjectApplication();
projectApplication.setProjectId("project_application_test_id");
projectApplication.setType(type);
projectApplication.setTypeValue("true");
return projectApplication;
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.project.controller.param;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
public class ProjectApplicationDefinition implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "{project_application.project_id.not_blank}")
private String projectId;
@NotBlank(message = "{project_application.type.not_blank}")
private String type;
private String typeValue;
}

View File

@ -6,7 +6,7 @@ server.compression.enabled=true
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css,text/javascript,image/jpeg
server.compression.min-response-size=2048
#
quartz.enabled=false
quartz.enabled=true
quartz.scheduler-name=msScheduler
quartz.thread-count=10
quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true

View File

@ -11,4 +11,9 @@ public class CleanupTestResourceService implements CleanupProjectResourceService
public void deleteResources(String projectId) {
LogUtils.info("删除当前项目[" + projectId + "]TEST资源");
}
@Override
public void cleanReportResources(String projectId) {
LogUtils.info("清理当前项目[" + projectId + "]TEST报告资源");
}
}

View File

@ -11,4 +11,9 @@ public class CleanupPlanResourceService implements CleanupProjectResourceService
public void deleteResources(String projectId) {
LogUtils.info("删除当前项目[" + projectId + "]相关测试计划资源");
}
@Override
public void cleanReportResources(String projectId) {
LogUtils.info("清理当前项目[" + projectId + "]相关测试计划报告资源");
}
}

View File

@ -23,4 +23,9 @@ public class CleanupResourceTests {
resourceService.deleteResources("test");
}
@Test
@Order(2)
public void testCleanupReportResource() throws Exception {
resourceService.cleanReportResources("test");
}
}