diff --git a/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java b/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java index 8c58c01094..49fe510c41 100644 --- a/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java +++ b/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java @@ -1,17 +1,18 @@ package io.metersphere.base.domain; -import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import java.io.Serializable; + @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class TestCaseWithBLOBs extends TestCase implements Serializable { private String remark; - private String steps; + private String steps; //与TestCaseExcelData里的属性名不一致,BeanUtils.copyBean()复制不了值,需要手动赋值 private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java b/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java index d4cb6ce786..b47c168904 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java +++ b/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java @@ -9,5 +9,7 @@ public class ExcelResponse { private Boolean success; private List> errList; + private Boolean isUpdated; //是否有更新过用例 + } diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java index 161486e0fc..099c9b462f 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java @@ -8,6 +8,8 @@ import lombok.Setter; @Setter public class TestCaseExcelData { + @ExcelIgnore + private Integer num; @ExcelIgnore private String name; @ExcelIgnore diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java index 218a59e01e..6cc4b5fca4 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java @@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern; @ColumnWidth(15) public class TestCaseExcelDataCn extends TestCaseExcelData { + @ExcelProperty("ID") + @NotRequired + private Integer num; + @NotBlank(message = "{cannot_be_null}") @Length(max = 255) @ExcelProperty("用例名称") diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java index d60c24fa2a..aeea86ca0f 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java @@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern; @ColumnWidth(15) public class TestCaseExcelDataTw extends TestCaseExcelData { + @ExcelProperty("ID") + @NotRequired + private Integer num; + @NotBlank(message = "{cannot_be_null}") @Length(max = 255) @ExcelProperty("用例名稱") diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java index 6916c3a5ec..c56260b523 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java @@ -14,6 +14,10 @@ import javax.validation.constraints.Pattern; @ColumnWidth(15) public class TestCaseExcelDataUs extends TestCaseExcelData { + @ExcelProperty("ID") + @NotRequired + private Integer num; + @NotBlank(message = "{cannot_be_null}") @Length(max = 255) @ExcelProperty("Name") diff --git a/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java b/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java index 9397a20ef9..ad588dc8cd 100644 --- a/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java +++ b/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java @@ -1,12 +1,16 @@ package io.metersphere.excel.listener; +import com.alibaba.excel.context.AnalysisContext; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.TestCaseWithBLOBs; import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.excel.domain.ExcelErrData; import io.metersphere.excel.domain.TestCaseExcelData; +import io.metersphere.excel.utils.ExcelValidateHelper; import io.metersphere.i18n.Translator; import io.metersphere.track.service.TestCaseService; import org.apache.commons.lang3.StringUtils; @@ -22,10 +26,18 @@ public class TestCaseDataListener extends EasyExcelListener { private String projectId; + protected List updateList = new ArrayList<>(); //存储待更新用例的集合 + + protected boolean isUpdated = false; //判断是否更新过用例,将会传给前端 + Set testCaseNames; Set userIds; + public boolean isUpdated() { + return isUpdated; + } + public TestCaseDataListener(Class clazz, String projectId, Set testCaseNames, Set userIds) { this.clazz = clazz; this.testCaseService = (TestCaseService) CommonBeanFactory.getBean("testCaseService"); @@ -39,12 +51,15 @@ public class TestCaseDataListener extends EasyExcelListener { String nodePath = data.getNodePath(); StringBuilder stringBuilder = new StringBuilder(errMsg); + //校验”所属模块" if (nodePath != null) { String[] nodes = nodePath.split("/"); + //校验模块深度 if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) { stringBuilder.append(Translator.get("test_case_node_level_tip") + TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level") + "; "); } + //模块名不能为空 for (int i = 0; i < nodes.length; i++) { if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) { stringBuilder.append(Translator.get("module_not_null") + "; "); @@ -57,10 +72,39 @@ public class TestCaseDataListener extends EasyExcelListener { // stringBuilder.append(Translator.get("functional_method_tip") + "; "); // } + + //校验维护人 if (!userIds.contains(data.getMaintainer())) { stringBuilder.append(Translator.get("user_not_exists") + ":" + data.getMaintainer() + "; "); } + /* + 校验Excel中是否有ID + 有的话校验ID是否已在当前项目中存在,存在则更新用例, + 不存在则继续校验看是否重复,不重复则新建用例。 + */ + if (null != data.getNum()) { //当前读取的数据有ID + + if (null != testCaseService.checkIdExist(data.getNum(), projectId)) { //该ID在当前项目中存在 + //如果前面所经过的校验都没报错 + if (StringUtils.isEmpty(stringBuilder)) { + updateList.add(data); //将当前数据存入更新列表 + stringBuilder.append("update_testcase"); //该信息用于在invoke方法中判断是否该更新用例 + } + return stringBuilder.toString(); + } else { + /* + 该ID在当前数据库中不存在,应当继续校验用例是否重复, + 在下面的校验过程中,num的值会被用于判断是否重复,所以应当先设置为null + */ + data.setNum(null); + } + } + + + /* + 校验用例 + */ if (testCaseNames.contains(data.getName())) { TestCaseWithBLOBs testCase = new TestCaseWithBLOBs(); BeanUtils.copyBean(testCase, data); @@ -96,18 +140,27 @@ public class TestCaseDataListener extends EasyExcelListener { @Override public void saveData() { - //无错误数据才插入数据 - if (!errList.isEmpty()) { + //excel中用例都有错误时就返回,只要有用例可用于更新或者插入就不返回 + if (!errList.isEmpty() && list.size() == 0 && updateList.size() == 0) { return; } - Collections.reverse(list); + if (!(list.size() == 0)){ + Collections.reverse(list); //因为saveImportData里面是先分配最大的ID,这个ID应该先发给list中最后的数据,所以要reverse + List result = list.stream() + .map(item -> this.convert2TestCase(item)) + .collect(Collectors.toList()); + testCaseService.saveImportData(result, projectId); + } - List result = list.stream() - .map(item -> this.convert2TestCase(item)) - .collect(Collectors.toList()); - - testCaseService.saveImportData(result, projectId); + if (!(updateList.size() == 0)) { + List result2 = updateList.stream() + .map(item -> this.convert2TestCaseForUpdate(item)) + .collect(Collectors.toList()); + testCaseService.updateImportDataCarryId(result2, projectId); + this.isUpdated = true; + updateList.clear(); + } } @@ -131,6 +184,32 @@ public class TestCaseDataListener extends EasyExcelListener { testCase.setNodePath(nodePath); + String steps = getSteps(data); + testCase.setSteps(steps); + + return testCase; +} + + /** + * 将Excel中的数据对象转换为用于更新操作的用例数据对象, + * @param data + * @return + */ + private TestCaseWithBLOBs convert2TestCaseForUpdate(TestCaseExcelData data) { + TestCaseWithBLOBs testCase = new TestCaseWithBLOBs(); + BeanUtils.copyBean(testCase, data); + testCase.setProjectId(this.projectId); + testCase.setUpdateTime(System.currentTimeMillis()); + + String nodePath = data.getNodePath(); + if (!nodePath.startsWith("/")) { + nodePath = "/" + nodePath; + } + if (nodePath.endsWith("/")) { + nodePath = nodePath.substring(0, nodePath.length() - 1); + } + testCase.setNodePath(nodePath); + String steps = getSteps(data); testCase.setSteps(steps); @@ -189,4 +268,38 @@ public class TestCaseDataListener extends EasyExcelListener { return jsonArray.toJSONString(); } + @Override + public void invoke(TestCaseExcelData testCaseExcelData, AnalysisContext analysisContext) { + String errMsg; + Integer rowIndex = analysisContext.readRowHolder().getRowIndex(); + String updateMsg = "update_testcase"; + try { + //根据excel数据实体中的javax.validation + 正则表达式来校验excel数据 + errMsg = ExcelValidateHelper.validateEntity(testCaseExcelData); + //自定义校验规则 + errMsg = validate(testCaseExcelData, errMsg); + } catch (NoSuchFieldException e) { + errMsg = Translator.get("parse_data_error"); + LogUtil.error(e.getMessage(), e); + } + + if (!StringUtils.isEmpty(errMsg)) { + + //如果errMsg只有"update testcase",说明用例待更新 + if (!errMsg.equals(updateMsg)){ + ExcelErrData excelErrData = new ExcelErrData(testCaseExcelData, rowIndex, + Translator.get("number") + " " + rowIndex + " " + Translator.get("row") + Translator.get("error") + + ":" + errMsg); + + errList.add(excelErrData); + } + } else { + list.add(testCaseExcelData); + } + + if (list.size() > BATCH_COUNT) { + saveData(); + list.clear(); + } + } } diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index 313901669b..1ed153236f 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -19,7 +19,6 @@ import io.metersphere.excel.domain.ExcelErrData; import io.metersphere.excel.domain.ExcelResponse; import io.metersphere.excel.domain.TestCaseExcelData; import io.metersphere.excel.domain.TestCaseExcelDataFactory; -import io.metersphere.excel.listener.EasyExcelListener; import io.metersphere.excel.listener.TestCaseDataListener; import io.metersphere.excel.utils.EasyExcelExporter; import io.metersphere.i18n.Translator; @@ -126,11 +125,26 @@ public class TestCaseService { // 全部字段值相同才判断为用例存在 if (testCase != null) { + + /* + 例如对于“/模块5”,用户的输入可能为“模块5”或者“/模块5/”或者“模块5/”。 + 不这样处理的话,下面进行判断时就会用用户输入的错误格式进行判断,而模块名为“/模块5”、 + “模块5”、“/模块5/”、“模块5/”时,它们应该被认为是同一个模块。 + 数据库存储的node_path都是“/模块5”这种格式的 + */ + String nodePath = testCase.getNodePath(); + if (!nodePath.startsWith("/")) { + nodePath = "/" + nodePath; + } + if (nodePath.endsWith("/")) { + nodePath = nodePath.substring(0, nodePath.length() - 1); + } + TestCaseExample example = new TestCaseExample(); TestCaseExample.Criteria criteria = example.createCriteria(); criteria.andNameEqualTo(testCase.getName()) .andProjectIdEqualTo(testCase.getProjectId()) - .andNodePathEqualTo(testCase.getNodePath()) + .andNodePathEqualTo(nodePath) .andTypeEqualTo(testCase.getType()) .andMaintainerEqualTo(testCase.getMaintainer()) .andPriorityEqualTo(testCase.getPriority()); @@ -177,6 +191,26 @@ public class TestCaseService { return null; } + /** + * 根据id和pojectId查询id是否在数据库中存在。 + * 在数据库中单id的话是可重复的,id与projectId的组合是唯一的 + */ + public Integer checkIdExist(Integer id, String projectId){ + TestCaseExample example = new TestCaseExample(); + TestCaseExample.Criteria criteria = example.createCriteria(); + if (null != id) { + criteria.andNumEqualTo(id); + criteria.andProjectIdEqualTo(projectId); + long count = testCaseMapper.countByExample(example); //查询是否有包含此ID的数据 + if(count == 0){ //如果ID不存在 + return null; + }else { //有对应ID的数据 + return id; + } + } + return null; + } + public int deleteTestCase(String testCaseId) { TestPlanTestCaseExample example = new TestPlanTestCaseExample(); example.createCriteria().andCaseIdEqualTo(testCaseId); @@ -286,6 +320,7 @@ public class TestCaseService { public ExcelResponse testCaseImport(MultipartFile multipartFile, String projectId, String userId) { ExcelResponse excelResponse = new ExcelResponse(); + boolean isUpdated = false; //判断是否更新了用例 String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest(); queryTestCaseRequest.setProjectId(projectId); @@ -338,10 +373,15 @@ public class TestCaseService { Set userIds = userRoleMapper.selectByExample(userRoleExample).stream().map(UserRole::getUserId).collect(Collectors.toSet()); try { + //根据本地语言环境选择用哪种数据对象进行存放读取的数据 Class clazz = new TestCaseExcelDataFactory().getExcelDataByLocal(); - EasyExcelListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds); + + TestCaseDataListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds); + //读取excel数据 EasyExcelFactory.read(multipartFile.getInputStream(), clazz, easyExcelListener).sheet().doRead(); + errList = easyExcelListener.getErrList(); + isUpdated = easyExcelListener.isUpdated(); } catch (Exception e) { LogUtil.error(e.getMessage(), e); MSException.throwException(e.getMessage()); @@ -352,6 +392,7 @@ public class TestCaseService { if (!errList.isEmpty()) { excelResponse.setSuccess(false); excelResponse.setErrList(errList); + excelResponse.setIsUpdated(isUpdated); } else { excelResponse.setSuccess(true); } @@ -374,7 +415,7 @@ public class TestCaseService { testcase.setSort(sort.getAndIncrement()); testcase.setNum(num.decrementAndGet()); testcase.setReviewStatus(TestCaseReviewStatus.Prepare.name()); - mapper.insert(testcase); + mapper.insert(testcase); }); } sqlSession.flushStatements(); @@ -400,6 +441,43 @@ public class TestCaseService { sqlSession.flushStatements(); } + /** + * 把Excel中带ID的数据更新到数据库 + * @param testCases + * @param projectId + */ + public void updateImportDataCarryId(List testCases, String projectId) { + Map nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId); + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class); + + /* + 获取用例的“网页上所显示id”与“数据库ID”映射。 + */ + List nums = testCases.stream() + .map(TestCase::getNum) + .collect(Collectors.toList()); + TestCaseExample example = new TestCaseExample(); + example.createCriteria().andNumIn(nums) + .andProjectIdEqualTo(projectId); + List testCasesList = testCaseMapper.selectByExample(example); + Map numIdMap = testCasesList.stream() + .collect(Collectors.toMap(TestCase::getNum, TestCase::getId)); + + + if (!testCases.isEmpty()) { + AtomicInteger sort = new AtomicInteger(); + testCases.forEach(testcase -> { + testcase.setUpdateTime(System.currentTimeMillis()); + testcase.setNodeId(nodePathMap.get(testcase.getNodePath())); + testcase.setSort(sort.getAndIncrement()); + testcase.setId(numIdMap.get(testcase.getNum())); + mapper.updateByPrimaryKeySelective(testcase); + }); + } + sqlSession.flushStatements(); + } + public void testCaseTemplateExport(HttpServletResponse response) { try { @@ -510,6 +588,7 @@ public class TestCaseService { StringBuilder result = new StringBuilder(""); TestCaseList.forEach(t -> { TestCaseExcelData data = new TestCaseExcelData(); + data.setNum(t.getNum()); data.setName(t.getName()); data.setNodePath(t.getNodePath()); data.setPriority(t.getPriority()); diff --git a/frontend/src/business/components/track/case/components/TestCaseImport.vue b/frontend/src/business/components/track/case/components/TestCaseImport.vue index a82195348f..1410afc46f 100644 --- a/frontend/src/business/components/track/case/components/TestCaseImport.vue +++ b/frontend/src/business/components/track/case/components/TestCaseImport.vue @@ -126,7 +126,8 @@ fileList: [], errList: [], xmindErrList: [], - isLoading: false + isLoading: false, + isUpdated: false } }, methods: { @@ -180,6 +181,12 @@ this.fileList = []; this.errList = []; this.xmindErrList = []; + + //通过excel导入更新过数据的话就刷新页面 + if (this.isUpdated === true){ + this.$emit("refreshAll"); + this.isUpdated = false; + } }, downloadTemplate() { this.$fileDownload('/test/case/export/template'); @@ -207,6 +214,7 @@ this.$emit("refreshAll"); } else { this.errList = res.errList; + this.isUpdated = res.isUpdated; } this.fileList = []; }, erro => {