又来活了,近日在做考试题目管理模块时,涉及到了题目基础数据的导入。需要支持word模板和excel模板两种方式。常规的题目类型包括,单选题、多选题、判断题、填空题、简答题、案例题。其中,案例题较为特殊,相当于阅读理解题目,根据一段材料,出若干道相关的题目,题目类型可以是其他任意类型的题目。在程序角度理解,案例题可以看做父子结构的题目。在这样的前提下,要实现导入的需求,就需要一步一步慢慢来。借着这个机会,分享一下,我的思路。
一、表结构设计
题目分类支持多种类型,为了支持案例题设计了父子结构。
二、技术选型
1、Excel模板导入:EasyPoi
easyPoi提供了一套完整的解决方案,用于处理excel表格的导入导出,直接使用easypoi即可
2、Word模板导入:JavaPoi
关于word的模板导入,没有成体系的解决方案,所以只需要javaPoi提供的文档解析功能即可,核心逻辑自行实现
三、模板设计
1、word模板
a.每道题目之间使用空行隔开
b.标注“题目类型:”“题目内容:”“答案:”“解析”
c.每个选项占一段落,选项与选项内容使用、分隔
d.简答题答案可以标注(关键字)用于区分两种计分模式
2、excel模板
a.案例题的子项题目,是否子项题目列为“是”
b.横向展开A-Z共计26项
c.全部题型均以选项形式设置答案
四、整体思路
1、word模板解析逻辑
2、excel模板解析逻辑
3、自动替换规则
a.选项中存在&的直接替换为空格
b.选项、答案中存在英文逗号的直接替换为中文逗号
4、数据校验逻辑
5、数据插入逻辑
五、代码实现
1、引入easypoi的pom依赖
<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-annotation</artifactId> <version>4.3.0</version> </dependency>
2、引入javapoi的pom依赖
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>4.1.1</version> </dependency> <!--解析docx文档的XWPFDocument对象在这个包里--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.1</version> </dependency>
3、创建关键DTO对象
4、控制层代码
/** * 导入考试题目数据 * * @param file * @return */ @PostMapping(value = "/importExamSubjectData") @ApiOperation(value = "导入考试题目数据", notes = "导入考试题目数据") public R<ExamSubjectImportResultDTO> importExamSubjectData(@RequestPart("file") MultipartFile file, @RequestParam("productId") Integer productId, @RequestParam("subjectCategory") Integer subjectCategory) { try { String fileType = StrUtil.split(file.getOriginalFilename(), StrPool.DOT)[1]; List<String> supportList = new ArrayList<String>(); supportList.add("xlsx"); supportList.add("docx"); if (!supportList.contains(fileType)) { return fail(ExceptionCode.SERVICE_EX.getCode(), "文件格式错误!"); } ExamSubjectImportResultDTO resultDTO = examSubjectService.importExamSubjectData(file, productId, subjectCategory); return success(resultDTO); } catch (BizException e) { return fail(ExceptionCode.SERVICE_EX.getCode(), e.getMessage()); } catch (Exception ex) { LogPrintUtil.formatOutput(ex, "[ExamSubjectController.importExamSubjectData]导入考试题目数据错误,联系系统管理员处理!"); return fail(ExceptionCode.OPERATION_EX.getCode(), "导入考试题目数据错误,联系系统管理员处理!"); } }
5、服务层代码
public ExamSubjectImportResultDTO importExamSubjectData(MultipartFile file, Integer productId, Integer subjectCategory) throws Exception { ExamSubjectImportResultDTO resultDTO = new ExamSubjectImportResultDTO(); String fileType = StrUtil.split(file.getOriginalFilename(), StrPool.DOT)[1]; // 解析数据列表 List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<>(); // excel导入 if ("xlsx".equals(fileType)) { saveDTOS = analysisImportExamSubjectExcel(resultDTO, file, productId, subjectCategory); // word导入 } else if ("docx".equals(fileType)) { saveDTOS = analysisImportExamSubjectWord(resultDTO, file, productId, subjectCategory); } // 校验数据列表 validateExamSubjectData(resultDTO, saveDTOS); // 数据校验成功后 if (resultDTO.getDataValidateFlag()) { // 执行数据插入操作 insertDBExamSubjectData(resultDTO, saveDTOS, productId, subjectCategory); } return resultDTO; }
6、excel解析代码
/** * 解析导入题目excel模板 * * @param resultDTO * @param file * @param productId * @param subjectCategory * @return */ private List<ExamSubjectSaveDTO> analysisImportExamSubjectExcel(ExamSubjectImportResultDTO resultDTO, MultipartFile file, Integer productId, Integer subjectCategory) throws Exception { List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<>(); //导入参数 ImportParams params = new ImportParams(); //开启校验 params.setNeedVerify(true); // 解析excel模板 ExcelImportResult<ExamSubjectExcelImportDTO> subjectExcelResult = ExcelImportUtil.importExcelMore(file.getInputStream(), ExamSubjectExcelImportDTO.class, params); // excel导入数据 List<ExamSubjectExcelImportDTO> excelImportDTOS = new ArrayList<ExamSubjectExcelImportDTO>(); excelImportDTOS.addAll(subjectExcelResult.getFailList()); excelImportDTOS.addAll(subjectExcelResult.getList()); // 非法字符校验 答案中不能有,(英文逗号) 选项中不能有&和,(英文逗号) if (CollectionUtils.isNotEmpty(excelImportDTOS)) { // 获取导入bean对象 BeanInfo beanInfo = Introspector.getBeanInfo(ExamSubjectExcelImportDTO.class); // 获取匹配名称的属性对象 List<PropertyDescriptor> descriptors = Arrays.stream(beanInfo.getPropertyDescriptors()).collect(Collectors.toList()); // 转换map Map<String, PropertyDescriptor> nameMap = descriptors.stream().collect(Collectors.toMap(FeatureDescriptor::getName, Function.identity())); int line = 0; for (ExamSubjectExcelImportDTO excelImportDTO : excelImportDTOS) { line++; String subjectAnswer = StrHelper.getObjectValue(excelImportDTO.getSubjectAnswer()); if (subjectAnswer.contains(",")) { // throw new BizException("第" + line + "道题目,答案中不可使用,(英文逗号)"); excelImportDTO.setSubjectAnswer(subjectAnswer.replaceAll(",", ",")); // 直接替换为中文逗号 } // 按顺序遍历字母 for (String letter : LetterUtils.LETTER_LIST) { // 获取name值 String name = StrHelper.getObjectValue(letter).toLowerCase(); // 获取属性对象 PropertyDescriptor property = nameMap.getOrDefault(name, null); if (Objects.isNull(property)) { break; } Method method = property.getReadMethod(); Method writeMethod = property.getWriteMethod(); // 调用对应get方法获取值 String option = StrHelper.getObjectValue(method.invoke(excelImportDTO)).trim(); if (StringUtils.isBlank(option)) { break; } if (option.contains("&")) { // throw new BizException("第" + line + "道题目,选项" + letter + "中不可使用&符号"); writeMethod.invoke(excelImportDTO, option.replaceAll("&", " "));// &替换成空格 } if (option.contains(",")) { // throw new BizException("第" + line + "道题目,选项" + letter + "中不可使用,(英文逗号)"); writeMethod.invoke(excelImportDTO, option.replaceAll(",", ","));// 英文逗号替换成中文逗号 } } } } // 解析excel对象 拆分题目列表 一道大题一个元素 List<List<ExamSubjectExcelImportDTO>> excelImportDTOGroupList = generateSubjectListByExcelImportDTOList(excelImportDTOS); // 根据excel导入对象解析成saveDTO对象列表 saveDTOS = analysisExamSubjectByGroupExcelImportList(excelImportDTOGroupList, productId, subjectCategory); return saveDTOS; } /** * 解析excel对象 拆分题目列表 一道大题一个元素 * * @param excelImportDTOS * @return */ private List<List<ExamSubjectExcelImportDTO>> generateSubjectListByExcelImportDTOList(List<ExamSubjectExcelImportDTO> excelImportDTOS) { List<List<ExamSubjectExcelImportDTO>> resultList = new ArrayList<List<ExamSubjectExcelImportDTO>>(); if (CollectionUtils.isEmpty(excelImportDTOS)) { return resultList; } for (ExamSubjectExcelImportDTO excelImportDTO : excelImportDTOS) { // 是否子项目 String isChildSubject = excelImportDTO.getIsChildSubject(); ExamSubjectTypeEnum typeEnum = ExamSubjectTypeEnum.getByName(excelImportDTO.getSubjectType().trim()); // 获取题目类型 String subjectType = Objects.isNull(typeEnum) ? "" : typeEnum.getCode(); excelImportDTO.setSubjectType(subjectType); // 不是子项目 if ("否".equals(isChildSubject)) { // 直接添加一条记录 List<ExamSubjectExcelImportDTO> tempList = new ArrayList<>(); tempList.add(excelImportDTO); resultList.add(tempList); // 是子项目 } else if ("是".equals(isChildSubject)) { // 获取集合中最后一项 List<ExamSubjectExcelImportDTO> tempList = resultList.get(resultList.size() - 1); // 追加题目 tempList.add(excelImportDTO); // 重设 resultList.set(resultList.size() - 1, tempList); } } return resultList; } /** * 根据excel分组导入对象解析成saveDTO对象列表 * * @param excelImportDTOGroupList * @return */ private List<ExamSubjectSaveDTO> analysisExamSubjectByGroupExcelImportList(List<List<ExamSubjectExcelImportDTO>> excelImportDTOGroupList, Integer productId, Integer subjectCategory) throws IllegalAccessException, IntrospectionException, InvocationTargetException { List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<>(); if (CollectionUtils.isEmpty(excelImportDTOGroupList)) { return saveDTOS; } for (List<ExamSubjectExcelImportDTO> excelImportDTOS : excelImportDTOGroupList) { if (CollectionUtils.isEmpty(excelImportDTOS)) { continue; } // 获取第一个对象 ExamSubjectExcelImportDTO firstDTO = excelImportDTOS.get(0); // 设置父级对象 ExamSubjectSaveDTO parentExamSubjectSaveDTO = new ExamSubjectSaveDTO(); parentExamSubjectSaveDTO.setSubjectType(firstDTO.getSubjectType()); parentExamSubjectSaveDTO.setSubjectContent(firstDTO.getSubjectContent()); parentExamSubjectSaveDTO.setProductId(productId); parentExamSubjectSaveDTO.setSubjectCategory(subjectCategory); // 一道题目时 if (excelImportDTOS.size() == 1) { // 调用解析方法 List<ExamSubjectSaveDTO> tempList = analysisExamSubjectByExcelImportList(excelImportDTOS, productId, subjectCategory); if (CollectionUtils.isNotEmpty(tempList)) { parentExamSubjectSaveDTO = tempList.get(0); } // 多道题目时 表示为案例题 } else if (excelImportDTOS.size() > 1) { // 截取题目 排除父级题目 List<ExamSubjectExcelImportDTO> subList = excelImportDTOS.subList(1, excelImportDTOS.size()); // 调用解析方法 List<ExamSubjectSaveDTO> tempList = analysisExamSubjectByExcelImportList(subList, productId, subjectCategory); parentExamSubjectSaveDTO.setChildren(tempList); } saveDTOS.add(parentExamSubjectSaveDTO); } return saveDTOS; } /** * 根据excel导入对象解析成saveDTO对象列表 * * @param excelImportDTOS * @return */ private List<ExamSubjectSaveDTO> analysisExamSubjectByExcelImportList(List<ExamSubjectExcelImportDTO> excelImportDTOS, Integer productId, Integer subjectCategory) throws IntrospectionException, InvocationTargetException, IllegalAccessException { List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<>(); if (CollectionUtils.isEmpty(excelImportDTOS)) { return saveDTOS; } // 获取导入bean对象 BeanInfo beanInfo = Introspector.getBeanInfo(ExamSubjectExcelImportDTO.class); // 获取匹配名称的属性对象 List<PropertyDescriptor> descriptors = Arrays.stream(beanInfo.getPropertyDescriptors()).collect(Collectors.toList()); // 转换map Map<String, PropertyDescriptor> nameMap = descriptors.stream().collect(Collectors.toMap(FeatureDescriptor::getName, Function.identity())); for (ExamSubjectExcelImportDTO excelImportDTO : excelImportDTOS) { ExamSubjectSaveDTO saveDTO = new ExamSubjectSaveDTO(); String subjectType = excelImportDTO.getSubjectType(); saveDTO.setSubjectType(subjectType); saveDTO.setSubjectContent(excelImportDTO.getSubjectContent()); saveDTO.setProductId(productId); saveDTO.setSubjectCategory(subjectCategory); // 如果是判断题 if (ExamSubjectTypeEnum.JUDGE.getCode().equals(subjectType)) { // 默认设置A、B选项 excelImportDTO.setA("正确"); excelImportDTO.setB("错误"); } // 设置解析值 saveDTO.setSubjectAnswerAnalysis(excelImportDTO.getSubjectAnswerAnalysis()); // 获取答案 String subjectAnswer = StrHelper.getObjectValue(excelImportDTO.getSubjectAnswer()); saveDTO.setSubjectScoreRule(subjectAnswer.contains("关键字") ? "like" : "eq"); subjectAnswer = subjectAnswer.replaceFirst("(关键字)", ""); // 按照、分割答案值 List<String> answers = StringUtils.isBlank(subjectAnswer) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(subjectAnswer.split("、"))); // 单选、多选 if (new ArrayList<String>(Arrays.asList(ExamSubjectTypeEnum.RADIO.getCode(), ExamSubjectTypeEnum.MULTIPLE.getCode())).contains(subjectType)) { // 直接设置答案 saveDTO.setSubjectAnswer(Strings.join(answers, ',')); // 其他类型 根据选项获取具体答案内容 } else { // 答案值列表 List<String> resultAnswers = new ArrayList<>(); // 遍历答案选项 for (String answer : answers) { // 获取匹配名称的属性对象 List<PropertyDescriptor> matchingDescriptors = descriptors.stream().filter(propertyDescriptor -> StrHelper.getObjectValue(answer).toLowerCase().equals(StrHelper.getObjectValue(propertyDescriptor.getName()).toLowerCase())).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(matchingDescriptors)) { // 获取读取方法 Method method = matchingDescriptors.get(0).getReadMethod(); // 调用对应get方法获取值 String resultAnswer = StrHelper.getObjectValue(method.invoke(excelImportDTO)); resultAnswers.add(resultAnswer); } } // 设置最终答案值 saveDTO.setSubjectAnswer(Strings.join(resultAnswers, ',')); } // 选项列表 List<String> options = new ArrayList<String>(); // 按顺序遍历字母 for (String letter : LetterUtils.LETTER_LIST) { // 获取name值 String name = StrHelper.getObjectValue(letter).toLowerCase(); // 获取属性对象 PropertyDescriptor property = nameMap.getOrDefault(name, null); if (Objects.isNull(property)) { break; } Method method = property.getReadMethod(); // 调用对应get方法获取值 String option = StrHelper.getObjectValue(method.invoke(excelImportDTO)).trim(); if (StringUtils.isBlank(option)) { break; } // 单选、多选 if (new ArrayList<String>(Arrays.asList(ExamSubjectTypeEnum.RADIO.getCode(), ExamSubjectTypeEnum.MULTIPLE.getCode())).contains(subjectType)) { // 拼接选项 option = StrHelper.getObjectValue(letter).toUpperCase() + "&" + option; } options.add(option); } // 设置选项 saveDTO.setSubjectOptions(Strings.join(options, ',')); saveDTOS.add(saveDTO); } return saveDTOS; }
7、word解析代码
/** * 解析导入题目word模板 * * @param resultDTO * @param file * @param productId * @param subjectCategory */ private List<ExamSubjectSaveDTO> analysisImportExamSubjectWord(ExamSubjectImportResultDTO resultDTO, MultipartFile file, Integer productId, Integer subjectCategory) throws IOException { List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<ExamSubjectSaveDTO>(); // 获取文件流 InputStream fileInputStream = file.getInputStream(); // 解析文档 XWPFDocument xd = new XWPFDocument(fileInputStream); // 获取全部的文本段落 List<XWPFParagraph> xwPfParagraphList = xd.getParagraphs(); // 过滤掉 以题目序号 开头的段落 xwPfParagraphList = xwPfParagraphList.stream().filter(xwpfParagraph -> !StrHelper.getObjectValue(xwpfParagraph.getText()).startsWith("题目序号")).collect(Collectors.toList()); // 题目段落列表 List<List<XWPFParagraph>> subjectParagraphList = generateSubjectParagraphList(xwPfParagraphList); if (CollectionUtils.isEmpty(subjectParagraphList)) { return saveDTOS; } for (List<XWPFParagraph> xwpfParagraphs : subjectParagraphList) { // 解析考试题目保存对象根据段落列表 ExamSubjectSaveDTO examSubjectSaveDTO = analysisExamSubjectByParagraph(xwpfParagraphs, productId, subjectCategory); saveDTOS.add(examSubjectSaveDTO); } return saveDTOS; } /** * 按照题目生成 段落列表 * 每个题目一个段落列表 * * @param xwPfParagraphLis * @return */ private List<List<XWPFParagraph>> generateSubjectParagraphList(List<XWPFParagraph> xwPfParagraphLis) { List<List<XWPFParagraph>> list = new ArrayList<>(); if (CollectionUtils.isEmpty(xwPfParagraphLis)) { return list; } boolean isStart = false; list.add(new ArrayList<XWPFParagraph>()); for (int i = 0; i < xwPfParagraphLis.size(); i++) { // 当前项 XWPFParagraph xwPfParagraphLi = xwPfParagraphLis.get(i); // 获取文本内容 String text = xwPfParagraphLi.getText(); // 以题目类型开头 算做起始段落 if (text.startsWith("题目类型:")) { isStart = true; } // 开始标志 且 当前是空行 if (isStart && StringUtils.isBlank(text)) { // 获取最后一个题目段落列表 List<XWPFParagraph> last = list.get(list.size() - 1); // 段落列表不为空 且 倒数第一个段落是以解析:开头的 if (last.size() > 0 && last.get(last.size() - 1).getText().startsWith("解析:")) { // 算做结束段落 isStart = false; // 空行不是最后一行时 if (i < xwPfParagraphLis.size() - 1) { // 继续添加段落列表 list.add(new ArrayList<XWPFParagraph>()); } } } // 处于开始标志时 if (isStart) { // 获取最后一个题目段落列表 List<XWPFParagraph> last = list.get(list.size() - 1); // 为空处理 last = CollectionUtils.isEmpty(last) ? new ArrayList<>() : last; // 将当前段落添加到列表中 last.add(xwPfParagraphLi); list.set(list.size() - 1, last); } } if (CollectionUtils.isNotEmpty(list)) { // 如果最后一项是空的,就删除最后一项 List<XWPFParagraph> tempList = list.get(list.size() - 1); if (tempList.size() == 0) { list.remove(list.size() - 1); } } return list; } /** * 解析考试题目保存对象根据段落列表 * * @param xwpfParagraphs * @param productId * @param subjectCategory * @return */ private ExamSubjectSaveDTO analysisExamSubjectByParagraph(List<XWPFParagraph> xwpfParagraphs, Integer productId, Integer subjectCategory) { // 创建题目保存对象 ExamSubjectSaveDTO parentExamSubjectSaveDTO = new ExamSubjectSaveDTO(); parentExamSubjectSaveDTO.setSubjectCategory(subjectCategory); parentExamSubjectSaveDTO.setProductId(productId); // 获取父级题目类型 String parentSubjectType = xwpfParagraphs.get(0).getText(); // 获取父级题目内容 String parentSubjectContent = xwpfParagraphs.size() > 1 ? StrHelper.getObjectValue(xwpfParagraphs.get(1).getText()).replaceAll("题目内容:", "") : ""; // 案例题单独处理 if (parentSubjectType.contains("案例题")) { parentExamSubjectSaveDTO.setSubjectType(ExamSubjectTypeEnum.CASE.getCode());// 设置父级题目类型 parentExamSubjectSaveDTO.setSubjectContent(parentSubjectContent);// 设置父级题目内容 // 截取除主题目题干部分 的其他段落 List<XWPFParagraph> childrenParagraphs = xwpfParagraphs.subList(2, xwpfParagraphs.size()); // 调用方法获取子项题目的解析对象列表 List<ExamSubjectSaveDTO> saveDTOS = generateExamSubjectSaveDTOList(childrenParagraphs, productId, subjectCategory); // 设置子项题目 parentExamSubjectSaveDTO.setChildren(saveDTOS); // 其他类型统一处理 } else { // 调用方法获取解析对象 List<ExamSubjectSaveDTO> saveDTOS = generateExamSubjectSaveDTOList(xwpfParagraphs, productId, subjectCategory); if (CollectionUtils.isNotEmpty(saveDTOS)) { parentExamSubjectSaveDTO = saveDTOS.get(0); } } return parentExamSubjectSaveDTO; } /** * 生成题目保存对象列表 * * @return */ private List<ExamSubjectSaveDTO> generateExamSubjectSaveDTOList(List<XWPFParagraph> xwpfParagraphs, Integer productId, Integer subjectCategory) { List<ExamSubjectSaveDTO> saveDTOS = new ArrayList<>(); if (CollectionUtils.isEmpty(xwpfParagraphs)) { return saveDTOS; } boolean typeFlag = false; boolean contentFlag = false; boolean optionsFlag = false; int line = 0; for (XWPFParagraph xwpfParagraph : xwpfParagraphs) { line++; if (ObjectUtil.isNull(xwpfParagraph)) { continue; } // 读取当前段落内容 String text = StrHelper.getObjectValue(xwpfParagraph.getText()).trim(); if (text.startsWith("题目类型:")) { ExamSubjectTypeEnum typeEnum = ExamSubjectTypeEnum.getByName(text.replaceFirst("题目类型:", "")); String subjectType = Objects.isNull(typeEnum) ? "" : typeEnum.getCode(); ExamSubjectSaveDTO saveDTO = new ExamSubjectSaveDTO(); saveDTO.setProductId(productId); saveDTO.setSubjectCategory(subjectCategory); saveDTO.setSubjectType(subjectType); saveDTO.setSubjectScoreRule("eq");// 得分规则 默认完全匹配 if (ExamSubjectTypeEnum.JUDGE.getCode().equals(subjectType)) {// 判断题 默认添加选项 saveDTO.setSubjectOptions("正确,错误"); } saveDTOS.add(saveDTO); typeFlag = true; } else if (text.startsWith("题目内容:")) { ExamSubjectSaveDTO saveDTO = saveDTOS.get(saveDTOS.size() - 1); saveDTO.setSubjectContent(text.replaceFirst("题目内容:", "")); saveDTOS.set(saveDTOS.size() - 1, saveDTO); contentFlag = true; // 处理答案 } else if (text.startsWith("答案:")) { if (text.contains(",")) { // throw new BizException("第" + line + "段落,答案中不可使用,(英文逗号)"); text = text.replaceAll(",", ",");// 英文逗号替换为中文逗号 } ExamSubjectSaveDTO saveDTO = saveDTOS.get(saveDTOS.size() - 1); // 包含 if (text.contains("关键字")) { saveDTO.setSubjectScoreRule("like"); } // 获取回答部分 String answer = StrHelper.getObjectValue(text.replaceFirst("答案:", "").replaceFirst("(关键字)", "")).trim(); if (StringUtils.isNotBlank(answer)) { // 按照、分割 List<String> answers = StringUtils.isBlank(answer) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(answer.split("、"))); // 重新设置答案 saveDTO.setSubjectAnswer(Strings.join(answers, ',')); } saveDTOS.set(saveDTOS.size() - 1, saveDTO); optionsFlag = true; // 处理解析 } else if (text.startsWith("解析:")) { ExamSubjectSaveDTO saveDTO = saveDTOS.get(saveDTOS.size() - 1); saveDTO.setSubjectAnswerAnalysis(text.replaceFirst("解析:", "")); saveDTOS.set(saveDTOS.size() - 1, saveDTO); // 恢复完成标志 typeFlag = false; contentFlag = false; optionsFlag = false; // 处理选项 } else if (typeFlag && contentFlag && !optionsFlag) { if (StringUtils.isBlank(text)) { // 表示题目的图片部分 } else { if (text.contains("&")) { // throw new BizException("第" + line + "段落,选项中不可使用&符号"); text = text.replaceAll("&", " ");// 英文逗号替换为中文逗号 } if (text.contains(",")) { // throw new BizException("第" + line + "段落,选项中不可使用,(英文逗号)"); text = text.replaceAll(",", ",");// 英文逗号替换为中文逗号 } ExamSubjectSaveDTO saveDTO = saveDTOS.get(saveDTOS.size() - 1); // 获取全部选项 String subjectOptions = StrHelper.getObjectValue(saveDTO.getSubjectOptions()); // 按照英文逗号分割 List<String> options = StringUtils.isBlank(subjectOptions) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(subjectOptions.split(","))); // 获取当前选项部分 String curOptions = text.replaceFirst("、", "&"); options.add(curOptions); // 重新设置选项 saveDTO.setSubjectOptions(Strings.join(options, ',')); saveDTOS.set(saveDTOS.size() - 1, saveDTO); } // 处理答案(如果答案具体部分换行了) } else if (typeFlag && contentFlag) { if (text.contains(",")) { // throw new BizException("第" + line + "段落,答案中不可使用,(英文逗号)"); text = text.replaceAll(",", ",");// 英文逗号替换为中文逗号 } ExamSubjectSaveDTO saveDTO = saveDTOS.get(saveDTOS.size() - 1); // 获取全部答案 String subjectAnswer = StrHelper.getObjectValue(saveDTO.getSubjectAnswer()); // 按照英文逗号分割 List<String> answers = StringUtils.isBlank(subjectAnswer) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(subjectAnswer.split(","))); answers.add(text); // 重新设置答案 saveDTO.setSubjectAnswer(Strings.join(answers, ',')); saveDTOS.set(saveDTOS.size() - 1, saveDTO); } } return saveDTOS; }
8、校验数据方法
/** * 校验解析数据 * * @param resultDTO * @param saveDTOS */ private void validateExamSubjectData(ExamSubjectImportResultDTO resultDTO, List<ExamSubjectSaveDTO> saveDTOS) { // 解析题目数量 resultDTO.setAnalysisCount(saveDTOS.size()); // 错误记录条数 Integer errorCount = 0; // 错误信息列表 List<Map<String, String>> errorMsgMapList = new ArrayList<Map<String, String>>(); boolean dataValidateFlag = true; for (int i = 0; i < saveDTOS.size(); i++) { Map<String, String> errorMsgMap = new HashMap<String, String>(); // 校验数据 boolean validateFlag = validateExamSubjectSaveDTO(errorMsgMap, saveDTOS.get(i), i); // 校验未通过 if (!validateFlag) { // 有一次未通过就更改全部状态 dataValidateFlag = false; // 记录错误次数 errorCount++; // 添加错误信息 errorMsgMapList.add(errorMsgMap); } } resultDTO.setDataValidateFlag(dataValidateFlag); resultDTO.setErrorCount(errorCount); resultDTO.setErrorMsgMapList(errorMsgMapList); } /** * 校验数据方法 * * @param errorMsgMap * @param saveDTO * @param index * @return */ private boolean validateExamSubjectSaveDTO(Map<String, String> errorMsgMap, ExamSubjectSaveDTO saveDTO, Integer index) { List<String> codes = ExamSubjectTypeEnum.getAllObj().stream().map(ExamSubjectTypeEnum::getCode).collect(Collectors.toList()); // 校验成功标志 boolean validateFlag = true; // 错误信息列表 List<String> errorMsgList = new ArrayList<String>(); String subjectType = saveDTO.getSubjectType();// 题目类型 if (!codes.contains(subjectType)) { validateFlag = false; errorMsgList.add("题目类型不合法"); } String subjectContent = saveDTO.getSubjectContent();// 题目内容 if (StringUtils.isBlank(subjectContent)) { validateFlag = false; errorMsgList.add("题目内容不能为空"); } // 当前是案例题 if (ExamSubjectTypeEnum.CASE.getCode().equals(subjectType)) { // 校验子项题目数据 List<ExamSubjectSaveDTO> children = saveDTO.getChildren(); if (CollectionUtils.isNotEmpty(children)) { for (int j = 0; j < children.size(); j++) { ExamSubjectSaveDTO child = children.get(j); Map<String, String> childErrorMsgMap = new HashMap<String, String>(); boolean childValidateFlag = validateExamSubjectSaveDTO(childErrorMsgMap, child, j); // 子项题目校验失败 if (!childValidateFlag) { // 更改标志 validateFlag = false; // 获取子项错误信息 String errorMsgString = childErrorMsgMap.get("errorMsg"); // 子项错误信息列表 List<String> childrenErrorMsgList = StringUtils.isBlank(errorMsgString) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(errorMsgString.split("&"))); for (int m = 0; m < childrenErrorMsgList.size(); m++) { String s = childrenErrorMsgList.get(m); childrenErrorMsgList.set(m, "第" + (j + 1) + "题" + s); } // 添加到父级错误信息列表中 errorMsgList.addAll(childrenErrorMsgList); } } } // 题目序号 errorMsgMap.put("subjectNo", StrHelper.getObjectValue(index + 1)); // 题目内容 errorMsgMap.put("subjectContent", subjectContent); // 错误信息 errorMsgMap.put("errorMsg", Strings.join(errorMsgList, '&')); return validateFlag; } // 获取选项 String subjectOptions = saveDTO.getSubjectOptions(); // 拆分集合 List<String> subjectOptionsArr = StringUtils.isBlank(subjectOptions) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(subjectOptions.split(","))); // 选项key列表 List<String> optionKeyList = new ArrayList<String>(); // 单选、多选 if (ExamSubjectTypeEnum.RADIO.getCode().equals(subjectType) || ExamSubjectTypeEnum.MULTIPLE.getCode().equals(subjectType)) { // 至少要有两个选项 if (subjectOptionsArr.size() <= 2 && ExamSubjectTypeEnum.RADIO.getCode().equals(subjectType)) { validateFlag = false; errorMsgList.add("单选题至少需要两个选项"); } // 至少要有三个选项 if (subjectOptionsArr.size() <= 3 && ExamSubjectTypeEnum.MULTIPLE.getCode().equals(subjectType)) { validateFlag = false; errorMsgList.add("多选题至少需要三个选项"); } for (String option : subjectOptionsArr) { // 选项列表 List<String> optionList = StringUtils.isBlank(option) ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(option.split("&"))); // 选项值 String optionValue = optionList.size() == 2 ? optionList.get(1) : ""; String optionKey = optionList.size() == 2 ? optionList.get(0) : ""; if (StringUtils.isBlank(optionValue)) { validateFlag = false; errorMsgList.add("选项值不能为空"); } optionKeyList.add(optionKey); } // 是否自然顺序 boolean isContinuity = LetterUtils.letterIsContinuitySort(optionKeyList); if (CollectionUtils.isNotEmpty(optionKeyList) && !isContinuity) { validateFlag = false; errorMsgList.add("选项不是自然顺序"); } } // 题目答案值 String subjectAnswer = saveDTO.getSubjectAnswer(); if (StringUtils.isBlank(subjectAnswer)) { validateFlag = false; errorMsgList.add("答案不能为空"); } else { // 解析答案列表 List<String> answerList = new ArrayList<String>(Arrays.asList(subjectAnswer.split(","))); // 创建不重复集合 Set<String> answerSet = new HashSet<String>(answerList); if (answerList.size() > answerSet.size()) { validateFlag = false; errorMsgList.add("答案不能有重复项"); } // 单选、多选 if (ExamSubjectTypeEnum.RADIO.getCode().equals(subjectType) || ExamSubjectTypeEnum.MULTIPLE.getCode().equals(subjectType)) { if (ExamSubjectTypeEnum.RADIO.getCode().equals(subjectType) && answerList.size() > 1) { validateFlag = false; errorMsgList.add("单选题只能有一个答案"); } if (ExamSubjectTypeEnum.MULTIPLE.getCode().equals(subjectType) && answerList.size() < 2) { validateFlag = false; errorMsgList.add("多选题至少要有两个答案"); } for (String answer : answerList) { // 判断答案是否在选项中 if (!optionKeyList.contains(answer)) { validateFlag = false; errorMsgList.add("答案" + answer + "未在选项中"); } } } } // 题目序号 errorMsgMap.put("subjectNo", StrHelper.getObjectValue(index + 1)); // 题目内容 errorMsgMap.put("subjectContent", subjectContent); // 错误信息 errorMsgMap.put("errorMsg", Strings.join(errorMsgList, '&')); return validateFlag; }
9、数据插入操作
/** * 数据插入操作 * * @param resultDTO * @param saveDTOS */ private void insertDBExamSubjectData(ExamSubjectImportResultDTO resultDTO, List<ExamSubjectSaveDTO> saveDTOS, Integer productId, Integer subjectCategory) { if (CollectionUtils.isEmpty(saveDTOS)) { return; } // 跳过数量 Integer skipCount = NumberHelper.getOrDef(resultDTO.getSkipCount(), 0); // 错误数量 Integer errorCount = NumberHelper.getOrDef(resultDTO.getErrorCount(), 0); // 成功数量 Integer successCount = NumberHelper.getOrDef(resultDTO.getSuccessCount(), 0); // 查询产品id+分类下的全部题目 List<ExamSubject> examSubjects = this.list(Wraps.<ExamSubject>lbQ().eq(ExamSubject::getProductId, productId).eq(ExamSubject::getSubjectCategory, subjectCategory)); // 题目Map Map<String, ExamSubject> examSubjectMap = Optional.ofNullable(examSubjects).orElse(new ArrayList<ExamSubject>()).stream().collect(Collectors.toMap(examSubject -> StrHelper.getObjectValue(examSubject.getProductId() + "&" + examSubject.getSubjectCategory() + "&" + examSubject.getSubjectType() + "&" + examSubject.getSubjectContent()), Function.identity(), (o1, o2) -> o1)); // 错误信息列表 List<Map<String, String>> errorMsgMapList = new ArrayList<Map<String, String>>(); for (int i = 0; i < saveDTOS.size(); i++) { ExamSubjectSaveDTO saveDTO = saveDTOS.get(i); // key值 String key = StrHelper.getObjectValue(saveDTO.getProductId() + "&" + saveDTO.getSubjectCategory() + "&" + saveDTO.getSubjectType() + "&" + saveDTO.getSubjectContent()); // 题目已存在 if (examSubjectMap.containsKey(key)) { // 跳过 skipCount++; continue; } try { // 调用保存接口 this.saveExamSubject(saveDTO); successCount++; } catch (Exception e) { // 错误数量自增 errorCount++; Map<String, String> errorMsgMap = new HashMap<String, String>(); // 题目序号 errorMsgMap.put("subjectNo", StrHelper.getObjectValue(i + 1)); // 题目内容 errorMsgMap.put("subjectContent", saveDTO.getSubjectContent()); // 错误信息 errorMsgMap.put("errorMsg", StrHelper.getObjectValue(e)); LogPrintUtil.formatOutput(e,"保存题目信息报错",saveDTO); errorMsgMapList.add(errorMsgMap); } } resultDTO.setSuccessCount(successCount); resultDTO.setSkipCount(skipCount); resultDTO.setErrorCount(errorCount); resultDTO.setErrorMsgMapList(errorMsgMapList); }
六、接口测试
发表评论