refactor(测试跟踪): 大批量导出测试用例优化
--story=1011416 --user=宋昌昌 【Bug转需求】【测试跟踪】功能用例-批量导出用例7000+测试跟踪服务挂了重启 https://www.tapd.cn/55049933/s/1361573
This commit is contained in:
parent
69f886b230
commit
9864e364f1
|
@ -250,11 +250,13 @@ export default {
|
|||
xmind_export_tip: "Switch to Case List and export!",
|
||||
export_field_select_tips: "please select export fields",
|
||||
export_to_excel: "Export excel",
|
||||
export_to_excel_tips: "support xls",
|
||||
export_to_excel_tips: "support xlsx",
|
||||
export_to_excel_tips1: "support xls/xlsx",
|
||||
export_to_xmind: "Export xmind",
|
||||
export_to_xmind_tips: "support xmind",
|
||||
export_format: "Export Format",
|
||||
export_to_zip: "Export zip",
|
||||
export_to_zip_tips: "support zip",
|
||||
},
|
||||
case_desc: "Case Desc",
|
||||
passing_rate: "Case Pass Rate",
|
||||
|
|
|
@ -222,10 +222,12 @@ export default {
|
|||
xmind_export_tip: "请切换成用例列表导出!",
|
||||
export_field_select_tips: "选择导出范围",
|
||||
export_to_excel: "导出Excel表格",
|
||||
export_to_excel_tips: "支持xls文件",
|
||||
export_to_excel_tips: "支持xlsx文件",
|
||||
export_to_excel_tips1: "支持xls/xlsx文件",
|
||||
export_to_xmind: "导出思维导图",
|
||||
export_to_xmind_tips: "支持xmind文件",
|
||||
export_to_zip: "导出压缩包",
|
||||
export_to_zip_tips: "支持zip文件",
|
||||
},
|
||||
case_desc: "用例描述",
|
||||
passing_rate: "用例通过率",
|
||||
|
|
|
@ -221,10 +221,12 @@ export default {
|
|||
xmind_export_tip: "請切換成用例列錶導出!",
|
||||
export_field_select_tips: "選擇導出範圍",
|
||||
export_to_excel: "導出Excel表格",
|
||||
export_to_excel_tips: "支持xls文件",
|
||||
export_to_excel_tips: "支持xlsx文件",
|
||||
export_to_excel_tips1: "支持xls/xlsx文件",
|
||||
export_to_xmind: "導出思維導圖",
|
||||
export_to_xmind_tips: "支持xmind文件",
|
||||
export_to_zip: "導出壓縮包",
|
||||
export_to_zip_tips: "支持zip文件",
|
||||
},
|
||||
case_desc: "用例描述",
|
||||
passing_rate: "用例通過率",
|
||||
|
|
|
@ -128,6 +128,33 @@ public class CompressUtils {
|
|||
return zipFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多个文件压缩至指定路径
|
||||
*
|
||||
* @param fileList 待压缩的文件列表
|
||||
* @param zipFilePath 压缩文件路径
|
||||
* @return 返回压缩好的文件
|
||||
* @throws IOException
|
||||
*/
|
||||
public static File zipFilesToPath(String zipFilePath, List<File> fileList) throws IOException {
|
||||
File zipFile = new File(zipFilePath);
|
||||
// 文件输出流
|
||||
FileOutputStream outputStream = getFileStream(zipFile);
|
||||
// 压缩流
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
|
||||
|
||||
int size = fileList.size();
|
||||
// 压缩列表中的文件
|
||||
for (int i = 0; i < size; i++) {
|
||||
File file = fileList.get(i);
|
||||
zipFile(file, zipOutputStream);
|
||||
}
|
||||
// 关闭压缩流、文件流
|
||||
zipOutputStream.close();
|
||||
outputStream.close();
|
||||
return zipFile;
|
||||
}
|
||||
|
||||
public static void deleteFile(String delPath) {
|
||||
try {
|
||||
File file = new File(delPath);
|
||||
|
|
|
@ -375,8 +375,7 @@
|
|||
</select>
|
||||
|
||||
<select id="listByTestCaseIds" resultType="io.metersphere.dto.TestCaseDTO">
|
||||
select test_case.*, api_test.name as apiName, load_test.name AS performName from test_case left join api_test on
|
||||
test_case.test_id=api_test.id left join load_test on test_case.test_id=load_test.id
|
||||
select test_case.* from test_case
|
||||
<where>
|
||||
<if test="!request.exportAll and request.ids != null and request.ids.size() > 0">
|
||||
and test_case.id in
|
||||
|
@ -394,6 +393,9 @@
|
|||
test_case.`${order.name}` ${order.type}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="request.pageCount != 0">
|
||||
limit ${request.pageStart}, ${request.pageCount}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="getMaxNumByProjectId" resultType="io.metersphere.base.domain.TestCase">
|
||||
|
|
|
@ -277,7 +277,7 @@ public class TestCaseController {
|
|||
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_CASE_READ_EXPORT)
|
||||
@MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.EXPORT, sourceId = "#request.id", title = "#request.name", project = "#request.projectId")
|
||||
public void testCaseExport(HttpServletResponse response, @RequestBody TestCaseExportRequest request) {
|
||||
testCaseService.testCaseExport(response, request);
|
||||
testCaseService.exportTestCaseZip(response, request);
|
||||
}
|
||||
|
||||
@PostMapping("/export/testcase/xmind")
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.listener;
|
|||
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.plan.service.TestPlanReportService;
|
||||
import io.metersphere.service.TestCaseService;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -13,11 +14,16 @@ public class TrackAppStartListener implements ApplicationListener<ApplicationRea
|
|||
|
||||
@Resource
|
||||
private TestPlanReportService testPlanReportService;
|
||||
@Resource
|
||||
private TestCaseService testCaseService;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
LogUtil.info("Start checking for incomplete reports");
|
||||
testPlanReportService.exceptionHandling();
|
||||
LogUtil.info("Completion Check Not Executed Completed Report");
|
||||
LogUtil.info("Start clean the tmp dir of jar classpath");
|
||||
testCaseService.cleanUpTmpDirOfClassPath();
|
||||
LogUtil.info("The tmp dir of jar classpath is cleared");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,15 @@ public class TestCaseBatchRequest extends TestCaseWithBLOBs {
|
|||
*/
|
||||
private Boolean exportAll = false;
|
||||
|
||||
/**
|
||||
* v2.9 大批量导出分页参数
|
||||
* pageStart: 偏移量
|
||||
* pageCount: 数目
|
||||
*/
|
||||
private int pageStart = 0;
|
||||
|
||||
private int pageCount = 0;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class CustomFiledRequest {
|
||||
|
|
|
@ -4,6 +4,7 @@ package io.metersphere.service;
|
|||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.EasyExcelFactory;
|
||||
import com.alibaba.excel.enums.CellExtraTypeEnum;
|
||||
import com.alibaba.excel.support.ExcelTypeEnum;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.*;
|
||||
|
@ -67,14 +68,12 @@ import org.apache.ibatis.session.SqlSessionFactory;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mybatis.spring.SqlSessionUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
@ -207,6 +206,11 @@ public class TestCaseService {
|
|||
|
||||
private ThreadLocal<Integer> importCreateNum = new ThreadLocal<>();
|
||||
|
||||
// 导出CASE的最大值
|
||||
private static final int EXPORT_CASE_MAX_COUNT = 1000;
|
||||
|
||||
private static final String EXPORT_CASE_TMP_DIR = "tmp";
|
||||
|
||||
private void setNode(TestCaseWithBLOBs testCase) {
|
||||
if (StringUtils.isEmpty(testCase.getNodeId()) || "default-module".equals(testCase.getNodeId())) {
|
||||
TestCaseNodeExample example = new TestCaseNodeExample();
|
||||
|
@ -1502,26 +1506,110 @@ public class TestCaseService {
|
|||
return list;
|
||||
}
|
||||
|
||||
public void testCaseExport(HttpServletResponse response, TestCaseExportRequest request) {
|
||||
String projectId = request.getProjectId();
|
||||
public void exportTestCaseZip(HttpServletResponse response, TestCaseExportRequest request) {
|
||||
// zip, response stream
|
||||
BufferedInputStream bis = null;
|
||||
OutputStream os = null;
|
||||
File tmpDir = null;
|
||||
|
||||
try {
|
||||
tmpDir = new File(this.getClass().getClassLoader().getResource(StringUtils.EMPTY).getPath() +
|
||||
EXPORT_CASE_TMP_DIR + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + UUID.randomUUID().toString());
|
||||
// 生成tmp随机目录
|
||||
FileUtils.deleteDir(tmpDir.getPath());
|
||||
tmpDir.mkdirs();
|
||||
// 生成EXCEL
|
||||
List<File> batchExcels = generateCaseExportExcel(tmpDir.getPath(), request);
|
||||
if (batchExcels.size() > 1) {
|
||||
// EXCEL -> ZIP (EXCEL数目大于1)
|
||||
File zipFile = CompressUtils.zipFilesToPath(tmpDir.getPath() + File.separatorChar + "caseExport.zip", batchExcels);
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("caseExport.zip", StandardCharsets.UTF_8.name()));
|
||||
bis = new BufferedInputStream(new FileInputStream(zipFile.getPath()));
|
||||
os = response.getOutputStream();
|
||||
int len;
|
||||
byte[] bytes = new byte[1024 * 2];
|
||||
while ((len = bis.read(bytes)) != -1) {
|
||||
os.write(bytes, 0, len);
|
||||
}
|
||||
} else {
|
||||
// EXCEL (EXCEL数目等于1)
|
||||
File singeFile = batchExcels.get(0);
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("caseExport.xlsx", StandardCharsets.UTF_8.name()));
|
||||
bis = new BufferedInputStream(new FileInputStream(singeFile.getPath()));
|
||||
os = response.getOutputStream();
|
||||
int len;
|
||||
byte[] bytes = new byte[1024 * 2];
|
||||
while ((len = bis.read(bytes)) != -1) {
|
||||
os.write(bytes, 0, len);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
MSException.throwException("export case zip error");
|
||||
} finally {
|
||||
try {
|
||||
if (bis != null) {
|
||||
bis.close();
|
||||
}
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
FileUtils.deleteDir(tmpDir.getPath());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<File> generateCaseExportExcel(String tmpZipPath, TestCaseExportRequest request) {
|
||||
List<File> tmpExportExcelList = new ArrayList<>();
|
||||
// 初始化ExcelHead
|
||||
request.getCondition().setStatusIsNot(CommonConstants.TrashStatus);
|
||||
TestCaseBatchRequest batchRequest = new TestCaseBatchRequest();
|
||||
BeanUtils.copyBean(batchRequest, request);
|
||||
List<TestCaseDTO> testCases = getExportData(batchRequest);
|
||||
List<List<String>> headList = getTestcaseExportHeads(request);
|
||||
boolean isUseCustomId = trackProjectService.useCustomNum(request.getProjectId());
|
||||
// 设置导出参数
|
||||
TestCaseBatchRequest initParam = new TestCaseBatchRequest();
|
||||
BeanUtils.copyBean(initParam, request);
|
||||
TestCaseBatchRequest batchRequest = setTestCaseExportParamIds(initParam);
|
||||
// 1000条截取一次, 生成EXCEL
|
||||
AtomicInteger i = new AtomicInteger(0);
|
||||
SubListUtil.dealForSubList(batchRequest.getIds(), EXPORT_CASE_MAX_COUNT, (subIds) -> {
|
||||
i.getAndIncrement();
|
||||
batchRequest.setIds(subIds);
|
||||
// 生成writeHandler
|
||||
Map<Integer, Integer> rowMergeInfo = new HashMap<>();
|
||||
FunctionCaseMergeWriteHandler writeHandler = new FunctionCaseMergeWriteHandler(rowMergeInfo, headList);
|
||||
Map<String, List<String>> caseLevelAndStatusValueMap = trackTestCaseTemplateService.getCaseLevelAndStatusMapByProjectId(request.getProjectId());
|
||||
FunctionCaseTemplateWriteHandler handler = new FunctionCaseTemplateWriteHandler(true, headList, caseLevelAndStatusValueMap);
|
||||
List<TestCaseDTO> exportData = getExportData(batchRequest);
|
||||
List<TestCaseExcelData> excelData = parseCaseData2ExcelData(exportData, rowMergeInfo, isUseCustomId, request.getOtherHeaders());
|
||||
List<List<Object>> data = parseExcelData2List(headList, excelData);
|
||||
File createFile = new File(tmpZipPath + File.separatorChar + "caseExport_" + i.get() + ".xlsx");
|
||||
if (!createFile.exists()) {
|
||||
try {
|
||||
createFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
MSException.throwException(e);
|
||||
}
|
||||
}
|
||||
//生成临时EXCEL
|
||||
EasyExcel.write(createFile)
|
||||
.head(Optional.ofNullable(headList).orElse(new ArrayList<>()))
|
||||
.registerWriteHandler(handler)
|
||||
.registerWriteHandler(writeHandler)
|
||||
.excelType(ExcelTypeEnum.XLSX).sheet(Translator.get("test_case_import_template_sheet")).doWrite(data);
|
||||
tmpExportExcelList.add(createFile);
|
||||
});
|
||||
return tmpExportExcelList;
|
||||
}
|
||||
|
||||
Map<Integer, Integer> rowMergeInfo = new HashMap<>();
|
||||
FunctionCaseMergeWriteHandler writeHandler = new FunctionCaseMergeWriteHandler(rowMergeInfo, headList);
|
||||
boolean isUseCustomId = trackProjectService.useCustomNum(projectId);
|
||||
|
||||
Map<String, List<String>> caseLevelAndStatusValueMap = trackTestCaseTemplateService.getCaseLevelAndStatusMapByProjectId(projectId);
|
||||
FunctionCaseTemplateWriteHandler handler = new FunctionCaseTemplateWriteHandler(true, headList, caseLevelAndStatusValueMap);
|
||||
|
||||
List<TestCaseExcelData> excelData = parseCaseData2ExcelData(testCases, rowMergeInfo, isUseCustomId, request.getOtherHeaders());
|
||||
List<List<Object>> data = parseExcelData2List(headList, excelData);
|
||||
new EasyExcelExporter(new TestCaseExcelDataFactory().getTestCaseExcelDataLocal().getClass())
|
||||
.exportByCustomWriteHandler(response, headList, data, Translator.get("test_case_import_template_name"),
|
||||
Translator.get("test_case_import_template_sheet"), handler, writeHandler);
|
||||
public void cleanUpTmpDirOfClassPath() {
|
||||
File tmpDir = new File(this.getClass().getClassLoader().getResource(StringUtils.EMPTY).getPath() + EXPORT_CASE_TMP_DIR);
|
||||
if (tmpDir.exists()) {
|
||||
FileUtils.deleteDir(tmpDir.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -1678,16 +1766,22 @@ public class TestCaseService {
|
|||
}
|
||||
|
||||
public List<TestCaseDTO> getExportData(TestCaseBatchRequest request) {
|
||||
ServiceUtils.getSelectAllIds(request, request.getCondition(),
|
||||
(query) -> extTestCaseMapper.selectIds(query));
|
||||
this.initRequest(request.getCondition(), true);
|
||||
setDefaultOrder(request.getCondition());
|
||||
Map<String, List<String>> filters = request.getCondition().getFilters();
|
||||
return extTestCaseMapper.listByTestCaseIds(request);
|
||||
}
|
||||
|
||||
private TestCaseBatchRequest setTestCaseExportParamIds(TestCaseBatchRequest param) {
|
||||
boolean queryUi = DiscoveryUtil.hasService(MicroServiceName.UI_TEST);
|
||||
param.getCondition().setQueryUi(queryUi);
|
||||
this.initRequest(param.getCondition(), true);
|
||||
setDefaultOrder(param.getCondition());
|
||||
ServiceUtils.setBaseQueryRequestCustomMultipleFields(param.getCondition());
|
||||
Map<String, List<String>> filters = param.getCondition().getFilters();
|
||||
if (filters != null && !filters.containsKey("status")) {
|
||||
filters.put("status", new ArrayList<>(0));
|
||||
}
|
||||
List<TestCaseDTO> testCaseList = extTestCaseMapper.listByTestCaseIds(request);
|
||||
return testCaseList;
|
||||
ServiceUtils.getSelectAllIds(param, param.getCondition(),
|
||||
(query) -> extTestCaseMapper.selectIds(query));
|
||||
return param;
|
||||
}
|
||||
|
||||
private List<TestCaseExcelData> parseCaseData2ExcelData(List<TestCaseDTO> testCaseList, Map<Integer, Integer> rowMergeInfo,
|
||||
|
|
|
@ -1052,7 +1052,7 @@ export default {
|
|||
fileNameSuffix = ".xmind";
|
||||
} else {
|
||||
url = '/test/case/export/testcase'
|
||||
fileNameSuffix = ".xlsx";
|
||||
fileNameSuffix = this.selectCounts > 1000 ? ".zip" : ".xlsx";
|
||||
}
|
||||
this.loading = true;
|
||||
store.isTestCaseExporting = true;
|
||||
|
|
Loading…
Reference in New Issue