又来活了,近日在做考试题目管理模块时,涉及到了题目基础数据的导入。需要支持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);
} 六、接口测试














发表评论