feat(接口测试): 增加用例批量操作

This commit is contained in:
wxg0103 2023-11-20 15:01:29 +08:00 committed by wxg0103
parent 467942fc59
commit 58fb5d7357
18 changed files with 740 additions and 70 deletions

View File

@ -477,4 +477,11 @@ permission.api_definition.execute=执行
api_definition_not_exist=接口不存在
api_test_case_name_exist=接口用例名称已存在
file_upload_fail=文件上传失败
api_test_case_not_exist=接口用例不存在
please_recover_the_interface_data_first=请先恢复接口数据
batch_edit_type_error=批量编辑类型错误
environment_id_is_null=环境ID不能为空
environment_is_not_exist=环境不存在
status_is_null=状态不能为空
priority_is_null=用例等级不能为空

View File

@ -486,4 +486,11 @@ permission.api_definition.execute=Execute
#接口管理
api_definition_not_exist=API definition is not exist
api_test_case_name_exist=API test case name is exist
file_upload_fail=File upload fail
file_upload_fail=File upload fail
api_test_case_not_exist=API test case is not exist
please_recover_the_interface_data_first=Please recover the interface data first
batch_edit_type_error=Batch edit type error
environment_id_is_null=Environment ID is null
environment_is_not_exist=Environment is not exist
status_is_null=Status is null
priority_is_null=Priority is null

View File

@ -482,4 +482,11 @@ permission.api_definition.execute=执行
#接口管理
api_definition_not_exist=接口不存在
file_upload_fail=文件上传失败
api_test_case_name_exist=接口用例名称已存在
api_test_case_name_exist=接口用例名称已存在
api_test_case_not_exist=接口用例不存在
please_recover_the_interface_data_first=请先恢复接口数据
batch_edit_type_error=批量编辑类型错误
environment_id_is_null=环境ID不能为空
environment_is_not_exist=环境不存在
status_is_null=状态不能为空
priority_is_null=用例等级不能为空

View File

@ -482,4 +482,11 @@ permission.api_definition.execute=執行
#接口管理
api_definition_not_exist=接口不存在
file_upload_fail=文件上傳失敗
api_test_case_name_exist=接口用例名稱已存在
api_test_case_name_exist=接口用例名稱已存在
api_test_case_not_exist=接口用例不存在
please_recover_the_interface_data_first=請先恢復接口數據
batch_edit_type_error=批量編輯類型錯誤
environment_id_is_null=環境ID不能為空
environment_is_not_exist=環境不存在
status_is_null=狀態不能為空
priority_is_null=優先級不能為空

View File

@ -3,10 +3,7 @@ package io.metersphere.api.controller.definition;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.dto.definition.ApiTestCaseAddRequest;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseUpdateRequest;
import io.metersphere.api.dto.request.ApiTestCasePageRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.service.definition.ApiTestCaseLogService;
import io.metersphere.api.service.definition.ApiTestCaseNoticeService;
import io.metersphere.api.service.definition.ApiTestCaseService;
@ -90,7 +87,7 @@ public class ApiTestCaseController {
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = ApiTestCaseLogService.class)
public void delete(@PathVariable String id) {
apiTestCaseService.delete(id);
apiTestCaseService.delete(id, SessionUtils.getUserId());
}
@PostMapping(value = "/update")
@ -118,5 +115,23 @@ public class ApiTestCaseController {
return PageUtils.setPageInfo(page, apiTestCaseService.page(request));
}
@PostMapping("/batch/delete")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE)
public void deleteBatchByParam(@RequestBody ApiTestCaseBatchRequest request) {
apiTestCaseService.batchDelete(request, SessionUtils.getUserId());
}
@PostMapping("/batch/move-gc")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE)
public void deleteToGcByParam(@RequestBody ApiTestCaseBatchRequest request) {
apiTestCaseService.batchMoveGc(request, SessionUtils.getUserId());
}
@PostMapping("/batch/edit")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE)
public void batchUpdate(@Validated @RequestBody ApiCaseBatchEditRequest request) {
apiTestCaseService.batchEdit(request, SessionUtils.getUserId());
}
}

View File

@ -0,0 +1,33 @@
package io.metersphere.api.dto.definition;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.LinkedHashSet;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiCaseBatchEditRequest extends ApiTestCaseBatchRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "标签")
private LinkedHashSet<
@NotBlank
String> tags;
@Schema(description = "批量编辑的类型 用例等级: Priority,状态 :Status,标签: Tags,用例环境: Environment")
@NotBlank
private String type;
@Schema(description = "默认覆盖原标签")
private boolean appendTag = false;
@Schema(description = "环境id")
private String envId;
@Schema(description = "用例状态")
private String status;
@Schema(description = "用例等级")
private String priority;
}

View File

@ -6,7 +6,7 @@ import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.LinkedHashSet;
@Data
public class ApiTestCaseAddRequest implements Serializable {
@ -39,7 +39,7 @@ public class ApiTestCaseAddRequest implements Serializable {
private String apiDefinitionId;
@Schema(description = "标签")
private List<
private LinkedHashSet<
@NotBlank
String> tags;

View File

@ -1,11 +1,15 @@
package io.metersphere.api.dto.definition;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@ -13,16 +17,26 @@ public class ApiTestCaseBatchRequest extends TableBatchProcessDTO implements Ser
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "接口ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@Schema(description = "接口pk")
private String apiDefinitionId;
@Schema(description = "模块ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String moduleId;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}")
private String projectId;
@Schema(description = "协议", requiredMode = Schema.RequiredMode.REQUIRED)
private String protocol;
@Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition.protocol.not_blank}")
@Size(min = 1, max = 20, message = "{api_definition.protocol.length_range}")
private String protocol = ModuleConstants.NODE_PROTOCOL_HTTP;
@Schema(description = "模块ID")
private List<String> moduleIds;
@Schema(description = "版本fk")
private String versionId;
@Schema(description = "版本来源")
private String refId;
}

View File

@ -1,4 +1,4 @@
package io.metersphere.api.dto.request;
package io.metersphere.api.dto.definition;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.system.dto.sdk.BasePageRequest;

View File

@ -1,8 +1,10 @@
package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.dto.definition.ApiTestCaseBatchRequest;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.request.ApiTestCasePageRequest;
import io.metersphere.api.dto.definition.ApiTestCasePageRequest;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -18,4 +20,13 @@ public interface ExtApiTestCaseMapper {
Long getPos(@Param("projectId") String projectId);
List<ApiTestCaseDTO> listByRequest(@Param("request") ApiTestCasePageRequest request, @Param("deleted") boolean deleted);
List<String> getIds(@Param("request") ApiTestCaseBatchRequest request, @Param("deleted") boolean deleted);
void batchMoveGc(@Param("ids") List<String> ids, @Param("userId") String userId);
List<ApiTestCase> getCaseInfoByApiIds(@Param("ids") List<String> apiIds, @Param("deleted") boolean deleted);
List<ApiTestCase> getCaseInfoByIds(@Param("ids") List<String> caseIds, @Param("deleted") boolean deleted);
}

View File

@ -2,6 +2,16 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.api.mapper.ExtApiTestCaseMapper">
<update id="batchMoveGc">
UPDATE api_test_case
SET deleted = 1,
delete_time = UNIX_TIMESTAMP()*1000,
delete_user = #{userId}
WHERE id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<select id="getPos" resultType="java.lang.Long">
SELECT pos
@ -35,6 +45,65 @@
WHERE t1.deleted =#{deleted}
<include refid="queryWhereCondition"/>
</select>
<select id="getIds" resultType="java.lang.String">
SELECT
t1.id
FROM
api_test_case t1
LEFT JOIN api_report t3 ON t1.last_report_id = t3.id
INNER JOIN api_definition a ON t1.api_definition_id = a.id
WHERE t1.deleted =#{deleted}
<include refid="queryWhereConditionByBatch"/>
</select>
<select id="getCaseInfoByApiIds" resultType="io.metersphere.api.domain.ApiTestCase">
SELECT id,
name
FROM api_test_case
WHERE deleted = #{deleted} and api_definition_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="getCaseInfoByIds" resultType="io.metersphere.api.domain.ApiTestCase">
SELECT id,
name
FROM api_test_case
WHERE deleted = #{deleted} and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<sql id="queryWhereConditionByBatch">
<if test="request.protocol != null and request.protocol!=''">
and a.protocol = #{request.protocol}
</if>
<if test="request.apiDefinitionId != null and request.apiDefinitionId!=''">
and t1.api_definition_id = #{request.apiDefinitionId}
</if>
<if test="request.projectId != null and request.projectId!=''">
and t1.project_id = #{request.projectId}
</if>
<if test="request.condition.keyword != null">
and (
t1.name like concat('%', #{request.condition.keyword},'%')
or t1.num like concat('%', #{request.condition.keyword},'%')
or t1.tags like JSON_CONTAINS(t1.tags, concat('["',#{request.condition.keyword},'"]'))
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and a.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
<include refid="filters">
<property name="filter" value="request.condition.filter"/>
</include>
<include refid="queryVersionCondition">
<property name="versionTable" value="t1"/>
</include>
</sql>
<sql id="queryWhereCondition">
<if test="request.protocol != null and request.protocol!=''">
@ -68,8 +137,8 @@
</sql>
<sql id="filters">
<if test="request.filter != null and request.filter.size() > 0">
<foreach collection="request.filter.entrySet()" index="key" item="values">
<if test="${filter} != null and ${filter}.size() > 0">
<foreach collection="${filter}.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key == 'priority'">

View File

@ -2,6 +2,7 @@ package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.NodeSortDTO;
import io.metersphere.project.mapper.ProjectMapper;
@ -114,6 +115,27 @@ public class ApiDefinitionModuleLogService {
operationLogService.batchAdd(logs);
}
public void saveDeleteCaseLog(List<ApiTestCase> apiTestCases, String operator, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
List<LogDTO> logs = new ArrayList<>();
apiTestCases.forEach(item -> {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(OperationLogModule.API_DEFINITION)
.method(HttpMethodConstants.GET.name())
.path(DELETE + "/%s")
.sourceId(item.getId())
.content(item.getName())
.createUser(operator)
.build().getLogDTO();
logs.add(dto);
}
);
operationLogService.batchAdd(logs);
}
public void saveMoveLog(@Validated NodeSortDTO request, String operator) {
BaseModule moveNode = request.getNode();
BaseModule previousNode = request.getPreviousNode();

View File

@ -1,18 +1,12 @@
package io.metersphere.api.service.definition;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionExample;
import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.domain.ApiDefinitionModuleExample;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.debug.ApiTreeNode;
import io.metersphere.api.dto.debug.ModuleCreateRequest;
import io.metersphere.api.dto.debug.ModuleUpdateRequest;
import io.metersphere.api.dto.definition.ApiModuleRequest;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMapper;
import io.metersphere.api.mapper.ExtApiDefinitionModuleMapper;
import io.metersphere.api.mapper.*;
import io.metersphere.api.service.debug.ApiDebugModuleService;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.NodeSortDTO;
@ -56,6 +50,12 @@ public class ApiDefinitionModuleService extends ModuleTreeService {
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private ApiTestCaseLogService apiTestCaseLogService;
public List<BaseTreeNode> getTree(ApiModuleRequest request) {
//接口的树结构是 模块子模块+接口 接口为非delete状态的
@ -180,8 +180,12 @@ public class ApiDefinitionModuleService extends ModuleTreeService {
//删除接口
extApiDefinitionMapper.deleteApiToGc(refIds, userId, System.currentTimeMillis());
apiDefinitionModuleLogService.saveDeleteDataLog(apiDefinitions, userId, projectId);
//删除接口用例 TODO 这里有点问题 如果把接口下的用例放到回收站里了 把回收站的用例恢复 但是接口没有恢复那这些用例应该挂在哪个接口下面
//删除接口用例
List<String> apiIds = apiDefinitions.stream().map(ApiDefinition::getId).distinct().toList();
List<ApiTestCase> caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, false);
List<String> caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList();
apiTestCaseService.batchDeleteToGc(caseIds, userId, projectId, false);
apiDefinitionModuleLogService.saveDeleteCaseLog(caseLists, userId, projectId);
apiCount = apiDefinitionMapper.countByExample(example);
}
}

View File

@ -9,12 +9,17 @@ import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.builder.LogDTOBuilder;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ApiTestCaseLogService {
@ -22,6 +27,8 @@ public class ApiTestCaseLogService {
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private OperationLogService operationLogService;
/**
* 添加接口日志
@ -172,4 +179,66 @@ public class ApiTestCaseLogService {
return dto;
}
public void deleteBatchLog(List<ApiTestCase> apiTestCases, String operator, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
List<LogDTO> logs = new ArrayList<>();
apiTestCases.forEach(item -> {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(OperationLogModule.API_DEFINITION_CASE)
.method(HttpMethodConstants.POST.name())
.path("/api/testCase/batch/delete")
.sourceId(item.getId())
.content(item.getName())
.createUser(operator)
.build().getLogDTO();
logs.add(dto);
}
);
operationLogService.batchAdd(logs);
}
public void batchToGcLog(List<ApiTestCase> apiTestCases, String operator, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
List<LogDTO> logs = new ArrayList<>();
apiTestCases.forEach(item -> {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(OperationLogModule.API_DEFINITION_CASE)
.method(HttpMethodConstants.POST.name())
.path("/api/testCase/batch/move-gc")
.sourceId(item.getId())
.content(item.getName())
.createUser(operator)
.build().getLogDTO();
logs.add(dto);
}
);
operationLogService.batchAdd(logs);
}
public void batchEditLog(List<ApiTestCase> apiTestCases, String operator, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
List<LogDTO> logs = new ArrayList<>();
apiTestCases.forEach(item -> {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(OperationLogModule.API_DEFINITION_CASE)
.method(HttpMethodConstants.POST.name())
.path("/api/testCase/batch/move-gc")
.sourceId(item.getId())
.content(item.getName())
.createUser(operator)
.build().getLogDTO();
logs.add(dto);
}
);
operationLogService.batchAdd(logs);
}
}

View File

@ -1,10 +1,7 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.ApiTestCaseAddRequest;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseUpdateRequest;
import io.metersphere.api.dto.request.ApiTestCasePageRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.mapper.*;
import io.metersphere.api.util.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -30,19 +27,33 @@ import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiTestCaseService {
public static final Long ORDER_STEP = 5000L;
private static final String MAIN_FOLDER_PROJECT = "project";
private static final String APP_NAME_API_CASE = "apiCase";
public static final String PRIORITY = "Priority";
public static final String STATUS = "Status";
public static final String TAGS = "Tags";
public static final String ENVIRONMENT = "Environment";
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
@ -61,13 +72,17 @@ public class ApiTestCaseService {
private UserLoginService userLoginService;
@Resource
private EnvironmentMapper environmentMapper;
@Resource
private ApiTestCaseLogService apiTestCaseLogService;
@Resource
private SqlSessionFactory sqlSessionFactory;
public ApiTestCase addCase(ApiTestCaseAddRequest request, List<MultipartFile> files, String userId) {
ApiTestCase testCase = new ApiTestCase();
testCase.setId(IDGenerator.nextStr());
BeanUtils.copyBean(testCase, request);
ApiDefinition apiDefinition = getApiDefinition(request.getApiDefinitionId());
testCase.setNum(NumGenerator.nextNum(request.getProjectId()+ "_" + apiDefinition.getNum(), ApplicationNumScope.API_TEST_CASE));
testCase.setNum(NumGenerator.nextNum(request.getProjectId() + "_" + apiDefinition.getNum(), ApplicationNumScope.API_TEST_CASE));
testCase.setApiDefinitionId(request.getApiDefinitionId());
testCase.setName(request.getName());
testCase.setPos(getNextOrder(request.getProjectId()));
@ -184,14 +199,12 @@ public class ApiTestCaseService {
apiTestCaseMapper.updateByPrimaryKeySelective(apiTestCase);
}
public void recover(String id) {
checkResourceExist(id);
ApiTestCase apiTestCase = new ApiTestCase();
apiTestCase.setId(id);
apiTestCase.setDeleted(false);
apiTestCase.setDeleteUser(null);
apiTestCase.setDeleteTime(null);
apiTestCaseMapper.updateByPrimaryKeySelective(apiTestCase);
private void checkApiExist(String id) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdEqualTo(id).andDeletedEqualTo(false);
if (apiDefinitionMapper.countByExample(example) == 0) {
throw new MSException(Translator.get("please_recover_the_interface_data_first"));
}
}
public void follow(String id, String userId) {
@ -214,22 +227,6 @@ public class ApiTestCaseService {
apiTestCaseFollowerMapper.deleteByExample(example);
}
public void delete(String id) {
ApiTestCase apiCase = checkResourceExist(id);
apiTestCaseMapper.deleteByPrimaryKey(id);
apiTestCaseBlobMapper.deleteByPrimaryKey(id);
ApiTestCaseFollowerExample example = new ApiTestCaseFollowerExample();
example.createCriteria().andCaseIdEqualTo(id);
apiTestCaseFollowerMapper.deleteByExample(example);
try {
FileRequest request = new FileRequest();
request.setFolder(DefaultRepositoryDir.getApiCaseDir(apiCase.getProjectId(), id));
minioRepository.deleteFolder(request);
} catch (Exception e) {
LogUtils.info("删除body文件失败: 文件名称:" + id, e);
}
}
public ApiTestCase update(ApiTestCaseUpdateRequest request, List<MultipartFile> files, String userId) {
ApiTestCase testCase = checkResourceExist(request.getId());
BeanUtils.copyBean(testCase, request);
@ -290,4 +287,206 @@ public class ApiTestCaseService {
});
}
}
public void recover(String id) {
ApiTestCase testCase = checkResourceExist(id);
checkApiExist(testCase.getApiDefinitionId());
checkNameExist(testCase);
ApiTestCase apiTestCase = new ApiTestCase();
apiTestCase.setId(id);
apiTestCase.setDeleted(false);
apiTestCase.setDeleteUser(null);
apiTestCase.setDeleteTime(null);
apiTestCaseMapper.updateByPrimaryKeySelective(apiTestCase);
}
public void delete(String id, String userId) {
ApiTestCase apiCase = checkResourceExist(id);
deleteResourceByIds(List.of(id), apiCase.getProjectId(), userId);
}
public void batchDelete(ApiTestCaseBatchRequest request, String userId) {
List<String> ids = doSelectIds(request, true);
if (CollectionUtils.isEmpty(ids)) {
return;
}
while (!ids.isEmpty()) {
if (ids.size() <= 2000) {
deleteResourceByIds(ids, request.getProjectId(), userId);
break;
}
List<String> subList = ids.subList(0, 2000);
deleteResourceByIds(subList, request.getProjectId(), userId);
ids.removeAll(subList);
}
}
public void deleteResourceByIds(List<String> ids, String projectId, String userId) {
deleteFollows(ids);
List<ApiTestCase> caseLists = extApiTestCaseMapper.getCaseInfoByIds(ids, true);
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andIdIn(ids);
apiTestCaseMapper.deleteByExample(example);
ApiTestCaseBlobExample blobExample = new ApiTestCaseBlobExample();
blobExample.createCriteria().andIdIn(ids);
apiTestCaseBlobMapper.deleteByExample(blobExample);
//删除body文件
FileRequest request = new FileRequest();
ids.forEach(id -> {
try {
request.setFolder(DefaultRepositoryDir.getApiCaseDir(projectId, id));
minioRepository.deleteFolder(request);
} catch (Exception e) {
LogUtils.info("删除body文件失败: 文件名称:" + id, e);
}
});
//记录删除日志
apiTestCaseLogService.deleteBatchLog(caseLists, userId, projectId);
//TODO 需要删除测试计划与用例的中间表 功能用例的关联表等
//TODO 删除附件关系
//extFileAssociationService.deleteByResourceIds(ids);
}
private void deleteFollows(List<String> caseIds) {
ApiTestCaseFollowerExample example = new ApiTestCaseFollowerExample();
example.createCriteria().andCaseIdIn(caseIds);
apiTestCaseFollowerMapper.deleteByExample(example);
}
public List<String> doSelectIds(ApiTestCaseBatchRequest request, boolean deleted) {
if (request.isSelectAll()) {
List<String> ids = extApiTestCaseMapper.getIds(request, deleted);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds());
}
return ids;
} else {
return request.getSelectIds();
}
}
public void batchMoveGc(ApiTestCaseBatchRequest request, String userId) {
List<String> ids = doSelectIds(request, false);
if (CollectionUtils.isNotEmpty(ids)) {
batchDeleteToGc(ids, userId, request.getProjectId(), true);
}
}
public void batchDeleteToGc(List<String> ids, String userId, String projectId, boolean saveLog) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
while (!ids.isEmpty()) {
if (ids.size() <= 2000) {
batchMoveToGc(ids, userId, projectId, saveLog);
break;
}
List<String> subList = ids.subList(0, 2000);
batchMoveToGc(subList, userId, projectId, saveLog);
ids.removeAll(subList);
}
}
private void batchMoveToGc(List<String> ids, String userId, String projectId, boolean saveLog) {
extApiTestCaseMapper.batchMoveGc(ids, userId);
if (saveLog) {
List<ApiTestCase> apiTestCases = extApiTestCaseMapper.getCaseInfoByIds(ids, true);
apiTestCaseLogService.batchToGcLog(apiTestCases, userId, projectId);
}
}
public void batchEdit(ApiCaseBatchEditRequest request, String userId) {
List<String> ids = doSelectIds(request, false);
if (CollectionUtils.isEmpty(ids)) {
return;
}
while (!ids.isEmpty()) {
if (ids.size() <= 2000) {
batchEditByType(request, ids, userId, request.getProjectId());
break;
}
List<String> subList = ids.subList(0, 2000);
batchEditByType(request, subList, userId, request.getProjectId());
ids.removeAll(subList);
}
}
private void batchEditByType(ApiCaseBatchEditRequest request, List<String> ids, String userId, String projectId) {
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andIdIn(ids);
ApiTestCase updateCase = new ApiTestCase();
updateCase.setUpdateUser(userId);
updateCase.setUpdateTime(System.currentTimeMillis());
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiTestCaseMapper mapper = sqlSession.getMapper(ApiTestCaseMapper.class);
switch (request.getType()) {
case PRIORITY -> batchUpdatePriority(example, updateCase, request.getPriority());
case STATUS -> batchUpdateStatus(example, updateCase, request.getStatus());
case TAGS ->
batchUpdateTags(example, updateCase, request.getTags(), request.isAppendTag(), sqlSession, mapper);
case ENVIRONMENT -> batchUpdateEnvironment(example, updateCase, request.getEnvId());
default -> throw new MSException(Translator.get("batch_edit_type_error"));
}
List<ApiTestCase> caseInfoByIds = extApiTestCaseMapper.getCaseInfoByIds(ids, false);
apiTestCaseLogService.batchEditLog(caseInfoByIds, userId, projectId);
}
private void batchUpdateEnvironment(ApiTestCaseExample example, ApiTestCase updateCase, String envId) {
if (StringUtils.isBlank(envId)) {
throw new MSException(Translator.get("environment_id_is_null"));
}
Environment environment = environmentMapper.selectByPrimaryKey(envId);
if (environment == null) {
throw new MSException(Translator.get("environment_is_not_exist"));
}
updateCase.setEnvironmentId(envId);
apiTestCaseMapper.updateByExampleSelective(updateCase, example);
}
private void batchUpdateTags(ApiTestCaseExample example, ApiTestCase updateCase,
LinkedHashSet<String> tags, boolean appendTag,
SqlSession sqlSession, ApiTestCaseMapper mapper) {
if (CollectionUtils.isEmpty(tags)) {
throw new MSException(Translator.get("tags_is_null"));
}
if (appendTag) {
List<ApiTestCase> caseList = apiTestCaseMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(caseList)) {
caseList.forEach(apiTestCase -> {
if (StringUtils.isNotBlank(apiTestCase.getTags())) {
LinkedHashSet orgTags = ApiDataUtils.parseObject(apiTestCase.getTags(), LinkedHashSet.class);
orgTags.addAll(tags);
apiTestCase.setTags(JSON.toJSONString(orgTags));
} else {
apiTestCase.setTags(JSON.toJSONString(tags));
}
mapper.updateByPrimaryKey(apiTestCase);
});
sqlSession.flushStatements();
if (sqlSessionFactory != null) {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
} else {
updateCase.setTags(JSON.toJSONString(tags));
apiTestCaseMapper.updateByExampleSelective(updateCase, example);
}
}
private void batchUpdateStatus(ApiTestCaseExample example, ApiTestCase updateCase, String status) {
if (StringUtils.isBlank(status)) {
throw new MSException(Translator.get("status_is_null"));
}
updateCase.setStatus(status);
apiTestCaseMapper.updateByExampleSelective(updateCase, example);
}
private void batchUpdatePriority(ApiTestCaseExample example, ApiTestCase updateCase, String priority) {
if (StringUtils.isBlank(priority)) {
throw new MSException(Translator.get("priority_is_null"));
}
updateCase.setPriority(priority);
apiTestCaseMapper.updateByExampleSelective(updateCase, example);
}
}

View File

@ -8,6 +8,7 @@ import io.metersphere.api.dto.definition.ApiModuleRequest;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ApiTestCaseMapper;
import io.metersphere.api.service.definition.ApiDefinitionModuleService;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ProjectMapper;
@ -66,6 +67,8 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource
private ApiDefinitionModuleService apiDefinitionModuleService;
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Autowired
public ApiDefinitionModuleControllerTests(ProjectServiceInvoker serviceInvoker) {
@ -142,6 +145,27 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
apiDefinitionBlob.setRequest(new byte[0]);
apiDefinitionBlob.setResponse(new byte[0]);
apiDefinitionBlobMapper.insertSelective(apiDefinitionBlob);
List<ApiTestCase> apiTestCasesList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
ApiTestCase apiTestCase = new ApiTestCase();
apiTestCase.setId(IDGenerator.nextStr());
apiTestCase.setApiDefinitionId(apiDefinition.getId());
apiTestCase.setProjectId(DEFAULT_PROJECT_ID);
apiTestCase.setName(StringUtils.join("接口用例", apiTestCase.getId()));
apiTestCase.setPriority("P0");
apiTestCase.setStatus("Underway");
apiTestCase.setNum(NumGenerator.nextNum(DEFAULT_PROJECT_ID + "_" + apiDefinition.getNum(), ApplicationNumScope.API_TEST_CASE));
apiTestCase.setPos(0L);
apiTestCase.setCreateTime(System.currentTimeMillis());
apiTestCase.setUpdateTime(System.currentTimeMillis());
apiTestCase.setCreateUser("admin");
apiTestCase.setUpdateUser("admin");
apiTestCase.setVersionId("1.0");
apiTestCase.setDeleted(false);
apiTestCasesList.add(apiTestCase);
}
apiTestCaseMapper.batchInsert(apiTestCasesList);
}
@Test

View File

@ -2,10 +2,7 @@ package io.metersphere.api.controller;
import io.metersphere.api.controller.param.ApiTestCaseAddRequestDefinition;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.ApiTestCaseAddRequest;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseUpdateRequest;
import io.metersphere.api.dto.request.ApiTestCasePageRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.mapper.*;
import io.metersphere.api.util.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -44,6 +41,7 @@ import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@ -64,6 +62,10 @@ public class ApiTestCaseControllerTests extends BaseTest {
private static final String UPDATE = BASE_PATH + "update";
private static final String PAGE = BASE_PATH + "page";
private static final String UPDATE_STATUS = BASE_PATH + "update-status";
private static final String BATCH_EDIT = BASE_PATH + "batch/edit";
private static final String BATCH_DELETE = BASE_PATH + "batch/delete";
private static final String BATCH_MOVE_GC = BASE_PATH + "batch/move-gc";
private static final ResultMatcher ERROR_REQUEST_MATCHER = status().is5xxServerError();
private static ApiTestCase apiTestCase;
@ -108,7 +110,7 @@ public class ApiTestCaseControllerTests extends BaseTest {
apiDefinition.setId("apiDefinitionId");
apiDefinition.setProjectId(DEFAULT_PROJECT_ID);
apiDefinition.setName(StringUtils.join("接口定义", apiDefinition.getId()));
apiDefinition.setModuleId("moduleId");
apiDefinition.setModuleId("case-moduleId");
apiDefinition.setProtocol("HTTP");
apiDefinition.setMethod("GET");
apiDefinition.setStatus("未规划");
@ -131,9 +133,34 @@ public class ApiTestCaseControllerTests extends BaseTest {
apiDefinition.setId("apiDefinitionId1");
apiDefinition.setModuleId("moduleId1");
apiDefinitionMapper.insertSelective(apiDefinition);
}
public void initCaseData() {
//2000条数据
List<ApiTestCase> apiTestCasesList = new ArrayList<>();
for (int i = 0; i < 2100; i++) {
ApiTestCase apiTestCase = new ApiTestCase();
apiTestCase.setId("apiTestCaseId" + i);
apiTestCase.setApiDefinitionId("apiDefinitionId");
apiTestCase.setProjectId(DEFAULT_PROJECT_ID);
apiTestCase.setName(StringUtils.join("接口用例", apiTestCase.getId()));
apiTestCase.setPriority("P0");
apiTestCase.setStatus("Underway");
apiTestCase.setNum(NumGenerator.nextNum(DEFAULT_PROJECT_ID + "_" + "100001", ApplicationNumScope.API_TEST_CASE));
apiTestCase.setPos(0L);
apiTestCase.setCreateTime(System.currentTimeMillis());
apiTestCase.setUpdateTime(System.currentTimeMillis());
apiTestCase.setCreateUser("admin");
apiTestCase.setUpdateUser("admin");
apiTestCase.setVersionId("1.0");
apiTestCase.setDeleted(false);
apiTestCasesList.add(apiTestCase);
}
apiTestCaseMapper.batchInsert(apiTestCasesList);
}
@Test
@Order(1)
public void add() throws Exception {
@ -148,7 +175,7 @@ public class ApiTestCaseControllerTests extends BaseTest {
request.setProjectId(DEFAULT_PROJECT_ID);
request.setPriority("P0");
request.setStatus("Underway");
request.setTags(List.of("tag1", "tag2"));
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2")));
request.setEnvironmentId(environments.get(0).getId());
MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
@ -166,7 +193,7 @@ public class ApiTestCaseControllerTests extends BaseTest {
// 再插入一条数据便于修改时重名校验
request.setName("test1");
request.setTags(new ArrayList<>());
request.setTags(new LinkedHashSet<>());
paramMap.clear();
paramMap.add("request", JSON.toJSONString(request));
mvcResult = this.requestMultipartWithOkAndReturn(ADD, paramMap);
@ -275,6 +302,13 @@ public class ApiTestCaseControllerTests extends BaseTest {
// @@校验日志
checkLog(apiTestCase.getId(), OperationLogType.RECOVER);
this.requestGet(RECOVER + "111").andExpect(ERROR_REQUEST_MATCHER);
ApiTestCase updateCase = new ApiTestCase();
updateCase.setId(apiTestCase.getId());
updateCase.setApiDefinitionId("aaaa");
apiTestCaseMapper.updateByPrimaryKeySelective(updateCase);
this.requestGet(RECOVER + apiTestCase.getId()).andExpect(ERROR_REQUEST_MATCHER);
updateCase.setApiDefinitionId("apiDefinitionId");
apiTestCaseMapper.updateByPrimaryKeySelective(updateCase);
// @@校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_RECOVER, RECOVER + apiTestCase.getId());
}
@ -368,7 +402,7 @@ public class ApiTestCaseControllerTests extends BaseTest {
request.setProjectId(DEFAULT_PROJECT_ID);
request.setPriority("P0");
request.setStatus("Underway");
request.setTags(List.of("tag1", "tag2"));
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2")));
MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
@ -434,6 +468,131 @@ public class ApiTestCaseControllerTests extends BaseTest {
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE, UPDATE_STATUS + "/" + apiTestCase.getId() + "/Underway");
}
@Test
@Order(10)
public void batchEdit() throws Exception {
// 追加标签
ApiCaseBatchEditRequest request = new ApiCaseBatchEditRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
request.setType("Tags");
request.setAppendTag(true);
request.setSelectAll(true);
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag3", "tag4")));
responsePost(BATCH_EDIT, request);
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andDeletedEqualTo(false);
apiTestCaseMapper.selectByExample(example).forEach(apiTestCase -> {
Assertions.assertTrue(apiTestCase.getTags().contains("tag1"));
Assertions.assertTrue(apiTestCase.getTags().contains("tag3"));
Assertions.assertTrue(apiTestCase.getTags().contains("tag4"));
});
//覆盖标签
request.setTags(new LinkedHashSet<>(List.of("tag1")));
request.setAppendTag(false);
responsePost(BATCH_EDIT, request);
apiTestCaseMapper.selectByExample(example).forEach(apiTestCase -> {
Assertions.assertEquals(apiTestCase.getTags(), "[\"tag1\"]");
});
//标签为空 报错
request.setTags(new LinkedHashSet<>());
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//ids为空的时候
request.setTags(new LinkedHashSet<>(List.of("tag1")));
request.setSelectAll(false);
request.setSelectIds(List.of(apiTestCase.getId()));
request.setExcludeIds(List.of(apiTestCase.getId()));
responsePost(BATCH_EDIT, request);
initCaseData();
//优先级
request.setTags(new LinkedHashSet<>());
request.setSelectAll(true);
request.setType("Priority");
request.setModuleIds(List.of("case-moduleId"));
request.setPriority("P3");
request.setExcludeIds(new ArrayList<>());
responsePost(BATCH_EDIT, request);
//判断数据的优先级是不是P3
example.clear();
example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andApiDefinitionIdEqualTo("apiDefinitionId").andDeletedEqualTo(false);
List<ApiTestCase> caseList = apiTestCaseMapper.selectByExample(example);
caseList.forEach(apiTestCase -> Assertions.assertEquals(apiTestCase.getPriority(), "P3"));
//优先级数据为空
request.setPriority(null);
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//ids为空的时候
request.setPriority("P3");
request.setSelectAll(false);
request.setSelectIds(List.of(apiTestCase.getId()));
request.setExcludeIds(List.of(apiTestCase.getId()));
responsePost(BATCH_EDIT, request);
//状态
request.setPriority(null);
request.setType("Status");
request.setStatus("Completed");
request.setSelectAll(true);
request.setExcludeIds(new ArrayList<>());
responsePost(BATCH_EDIT, request);
//判断数据的状态是不是Completed
caseList = apiTestCaseMapper.selectByExample(example);
caseList.forEach(apiTestCase -> Assertions.assertEquals(apiTestCase.getStatus(), "Completed"));
//状态数据为空
request.setStatus(null);
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//环境
request.setStatus(null);
request.setType("Environment");
EnvironmentExample environmentExample = new EnvironmentExample();
environmentExample.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andMockEqualTo(true);
List<Environment> environments = environmentMapper.selectByExample(environmentExample);
request.setEnvId(environments.get(0).getId());
responsePost(BATCH_EDIT, request);
//判断数据的环境是不是environments.get(0).getId()
caseList = apiTestCaseMapper.selectByExample(example);
caseList.forEach(apiTestCase -> Assertions.assertEquals(apiTestCase.getEnvironmentId(), environments.get(0).getId()));
//环境数据为空
request.setEnvId(null);
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//环境不存在
request.setEnvId("111");
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//类型错误
request.setType("111");
this.requestPost(BATCH_EDIT, request, ERROR_REQUEST_MATCHER);
//校验日志
checkLog(apiTestCase.getId(), OperationLogType.UPDATE);
//校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE, BATCH_EDIT, request);
}
@Test
@Order(11)
public void batchMoveGc() throws Exception {
// @@请求成功
ApiTestCaseBatchRequest request = new ApiTestCaseBatchRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(false);
request.setSelectIds(List.of(apiTestCase.getId()));
request.setExcludeIds(List.of(apiTestCase.getId()));
responsePost(BATCH_MOVE_GC, request);
request.setSelectAll(true);
request.setExcludeIds(new ArrayList<>());
request.setModuleIds(List.of("case-moduleId"));
responsePost(BATCH_MOVE_GC, request);
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andApiDefinitionIdEqualTo("apiDefinitionId").andDeletedEqualTo(true);
List<ApiTestCase> caseList = apiTestCaseMapper.selectByExample(example);
caseList.forEach(apiTestCase -> Assertions.assertTrue(apiTestCase.getDeleted()));
//校验日志
checkLog(apiTestCase.getId(), OperationLogType.DELETE);
//校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE, BATCH_MOVE_GC, request);
}
@Test
@Order(20)
@ -463,5 +622,28 @@ public class ApiTestCaseControllerTests extends BaseTest {
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE, DELETE + apiTestCase.getId());
}
@Test
@Order(21)
public void batchDeleted() throws Exception {
// @@请求成功
ApiTestCaseBatchRequest request = new ApiTestCaseBatchRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(false);
request.setSelectIds(List.of(apiTestCase.getId()));
request.setExcludeIds(List.of(apiTestCase.getId()));
responsePost(BATCH_DELETE, request);
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
request.setModuleIds(List.of("case-moduleId"));
responsePost(BATCH_DELETE, request);
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andApiDefinitionIdEqualTo("apiDefinitionId");
//数据为空
Assertions.assertEquals(0, apiTestCaseMapper.selectByExample(example).size());
//校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_DELETE, BATCH_DELETE, request);
}
}

View File

@ -46,7 +46,7 @@ import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional
@Transactional(rollbackFor = Exception.class)
public class EnvironmentService {
@Resource
private EnvironmentMapper environmentMapper;