word方式导入试题

beetlsql3-dev
Mlxa0324
parent 0d1ebd8f4a
commit d458f1b90c

@ -34,4 +34,6 @@ public interface ResourcesQuestionDao extends BaseMapper<ResourcesQuestion>{
* @return
*/
List<QuestionTypeCountVO> getGroupQuestionTypeCount(Long courseInfoId);
int checkUnique(ResourcesQuestion question);
}

@ -1,5 +1,7 @@
package com.ibeetl.jlw.entity;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.jlw.validate.ValidateConfig;
import com.ibeetl.admin.core.annotation.Dict;
import com.ibeetl.admin.core.annotation.DictEnum;
@ -8,7 +10,10 @@ import com.ibeetl.jlw.enums.AddTypeEnum;
import org.beetl.sql.annotation.entity.AutoID;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/*
* -
@ -285,4 +290,28 @@ public class ResourcesQuestion extends BaseEntity{
public void setAddType(AddTypeEnum addType) {
this.addType = addType;
}
/**,
*@return
*/
public String getQuestionStemRegexp(){
return ReUtil.escape(questionStem)
.replaceAll("<img.*?[^<]/>", "<img.*?[^<]/>")
.replace("(","").replace(")","");
}
public List<String> takeQuestionOptionConcatList() {
List<String> result = new ArrayList<>();
Field[] fields = ReflectUtil.getFields(this.getClass(), f -> f.getName().startsWith("questionOption"));
for (Field field : fields) {
Object fieldValue = ReflectUtil.getFieldValue(this, field);
if (fieldValue != null) {
result.add(fieldValue.toString());
}
}
return result;
}
}

@ -1,36 +1,38 @@
package com.ibeetl.jlw.service;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.*;
import cn.jlw.Interceptor.GetFile;
import cn.jlw.util.ToolUtils;
import com.ibeetl.admin.core.entity.CoreUser;
import com.ibeetl.admin.core.service.CoreBaseService;
import com.ibeetl.admin.core.service.CorePlatformService;
import com.ibeetl.admin.core.util.BeanCopyUtil;
import com.ibeetl.admin.core.util.TimeTool;
import com.ibeetl.admin.core.web.JsonResult;
import com.ibeetl.admin.core.web.JsonReturnCode;
import com.ibeetl.jlw.dao.CourseInfoDao;
import com.ibeetl.jlw.dao.ResourcesQuestionDao;
import com.ibeetl.jlw.entity.CourseInfo;
import com.ibeetl.jlw.entity.FileEntity;
import com.ibeetl.jlw.entity.ResourcesQuestion;
import com.ibeetl.jlw.entity.UniversitiesCollegesJurisdictionExperimentalSystem;
import com.ibeetl.jlw.entity.*;
import com.ibeetl.jlw.entity.dto.QuestionSettingDTO;
import com.ibeetl.jlw.entity.vo.QuestionTypeCountVO;
import com.ibeetl.jlw.enums.AddTypeEnum;
import com.ibeetl.jlw.enums.ResourcesQuestionTypeEnum;
import com.ibeetl.jlw.service.strategy.StrategyContext;
import com.ibeetl.jlw.validator.QuestionValidator;
import com.ibeetl.jlw.web.query.CourseInfoQuery;
import com.ibeetl.jlw.web.query.ResourcesQuestionQuery;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.assertj.core.util.Lists;
import org.beetl.sql.core.SqlId;
import org.beetl.sql.core.engine.PageQuery;
@ -43,17 +45,27 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.hutool.core.util.ArrayUtil.join;
import static com.ibeetl.jlw.entity.ResourcesQuestionOptionEntity.shuffleOrderOptions;
import static com.ibeetl.jlw.service.strategy.StrategyContext.QuestionParagraphTypeEnum.OTHER;
import static com.ibeetl.jlw.service.strategy.StrategyContext.QuestionTypeConcatEnum.DECIDE;
import static com.ibeetl.jlw.service.strategy.StrategyContext.QuestionTypeConcatEnum.MULTIPLE;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static org.apache.poi.util.LocaleID.SE;
/**
@ -75,6 +87,7 @@ public class ResourcesQuestionService extends CoreBaseService<ResourcesQuestion>
@Autowired private ResourcesQuestionService resourcesQuestionService;
@Autowired private StrategyContext strategyContext;
@Autowired private QuestionValidator questionValidator;
public PageQuery<ResourcesQuestion> queryByCondition(PageQuery query){
PageQuery ret = resourcesQuestionDao.queryByCondition(query);
@ -776,254 +789,278 @@ public class ResourcesQuestionService extends CoreBaseService<ResourcesQuestion>
return g;
}
// @SneakyThrows
// public JsonResult importQBankWordTemplate(final Integer isPublic, @NotEmpty List<FileEntity> fileEntityList, CoreUser coreUser, String referer) {
// // 公共库题目导入,用于下面的部分验证
// final String publicReferer = "/jlw/question/importExcel.do";
// /** 找出段落, 逐段落处理 */
// AtomicReference<FileInputStream> inputStream = new AtomicReference<>();
// AtomicReference<XWPFDocument> xDocument = new AtomicReference<>();
//
// //装结果 key卷子访问地址 value{标题题目List}
// Map<String,Object[]> resultMap = new HashMap<>();
// /** 仅用于存放标题 */
// final String[] title = { Strings.EMPTY };
// /** 标题暂存,可能是多个 */
// final StringBuilder titleStb = new StringBuilder();
//
// /** 循环文件集合 */
// Long finalCorSchoolId = corSchoolId;
// fileEntityList.forEach(fileEntity -> {
// /** 上传文件的临时路径 */
// String importPath = fileEntity.getAbsoluteUrl();
// /** 新建文件夹绝对路径 */
// String absoluteDirPath = String.format("%s%s%s%s%s%s", GetFile.p , SE , "filesystem" , SE , "temp" , SE );
// /** 新建文件的绝对路径 */
// String absolutePath = absoluteDirPath + fileEntity.getTempName();
// //各种流
// try {
// inputStream.set(new FileInputStream(importPath));
// xDocument.set(new XWPFDocument(inputStream.get()));
// /** 集合拷贝 */
// List<MyXWPFParagraph> myParagraphs = BeanCopyUtil.copyListProperties(xDocument.get().getParagraphs() , MyXWPFParagraph::new, (t, d) -> {
// d.setXwpfParagraph(t);
// });
//
// /** 需要存库的题目集合 */
// List<ResourcesQuestion> questionList = new CopyOnWriteArrayList<>();
// /** 题目类型 */
// AtomicReference<StrategyContext.QuestionTypeConcatEnum> typeConcatEnum = new AtomicReference<>();
// /** 段落类型 */
// AtomicReference<StrategyContext.QuestionParagraphTypeEnum> paragraphTypeEnum = new AtomicReference<>();
// /** 每题分数 */
// AtomicReference<BigDecimal> questionScore = new AtomicReference<>(BigDecimal.ZERO);
// /** 题干 */
// AtomicReference<String> questionStem = new AtomicReference<>(Strings.EMPTY);
// /** 答案 */
// AtomicReference<String> questionAnswer = new AtomicReference<>(Strings.EMPTY);
// /** 题目实体 */
// AtomicReference<ResourcesQuestion> question = new AtomicReference<>();
// /** 题干计数 */
// AtomicInteger recordsCount = new AtomicInteger();
// /** 遍历所有段落 */
// myParagraphs.forEach(e -> {
// /** 策略中的参数设置 */
// strategyContext.setText(e.getText(absoluteDirPath, fileEntity.getTempName())).start(c -> {
// /** 第一行 */
// if (recordsCount.get() == 1 && StringUtils.isBlank(title[0])) {
// /** 暴力取值 */
// titleStb.append(title[0] = myParagraphs.get(0).getText(absoluteDirPath, fileEntity.getTempName()));
// }
// /** k 策略枚举v 处理后的段落文字 */
// c.forEach((k, v) -> {
// /** 记录段落类型, 未查询类型之外的类型 */
// if (!k.equals(OTHER)) {paragraphTypeEnum.set(k);}
// switch(k) {
// /** 未匹配到的段落 */
// case OTHER: {
// /** 如果非标题字符,可能是前一个段落的副行。需要追加到相应的段落中 */
// insertCorrespondingParagraph(typeConcatEnum, paragraphTypeEnum.get() ,v, question, questionStem, questionAnswer);
// } break;
// /** 题目类型,分值,该类型的总分 */
// case TYPE: {
// /** 题目类型作用域 */
// String[] sp = v.split(",");
// typeConcatEnum.set(StrategyContext.QuestionTypeConcatEnum.matchText(sp[2]));
// questionScore.set(NumberUtil.toBigDecimal(sp[0]).setScale(1, RoundingMode.HALF_UP));
// } break;
// /** 题干 */
// case STEM: {
// final String numStartRegex = "^\\d+[\\.\\、\\]";
// if(ReUtil.contains(numStartRegex, v)) {
// /** 题干的编号需要移除掉 */
// questionStem.set(v.replaceFirst(numStartRegex, Strings.EMPTY).trim());
// /** 题数++ */
// recordsCount.incrementAndGet();
// /** 清空变量 */
// questionAnswer.set(Strings.EMPTY);
//
// /** 带有编号的段落,是主题干。需要创建新的题目对象 */
// Question qs = new Question();
// /** 题目类型,查询插入操作 */
// QuestionType questionType = selectOrInsert(typeConcatEnum.get(), coreUser);
// qs.setQuestionTypeId(questionType.getQuestionTypeId());
// qs.putQuestionTypeConcat(questionType.getQuestionTypeConcat());
// qs.putQuestionTypeName(questionType.getQuestionTypeName());
// qs.setSourceCorSchoolId(finalCorSchoolId);
// qs.setQuestionScore(questionScore.get());
// qs.setQuestionAddTime(new Date());
//
// if (StringUtils.isNotBlank(questionStem.get())){
// qs.setQuestionStem(questionStem.get().replace("<","&lt;").replace(">","&gt;"));
// }else {
// qs.setQuestionStem(questionStem.get());
// }
//
// qs.setQuestionStatus(1);
// qs.set("sign", 0);
// qs.setUserId(coreUser.getId());
// qs.setOrgId(coreUser.getOrgId());
//
// question.set(qs);
// questionList.add(question.get());
// }
// } break;
// /** 选项 */
// case OPTION: {
// putQuestionOption(v, question.get());
// } break;
// /** 答案 questionAnswer不包含答案 字样 */
// case ANSWER: {
// questionAnswer.set(questionAnswer.get().concat(v));
// // questionAnswer.get() 默认不包含 【答案】 字样
// questionAnswer.set(secondFormat(questionAnswer.get(), typeConcatEnum.get()));
// question.get().setQuestionAnswer(questionAnswer.get());
// } break;
// }
// });
// });
// });
//
// if (referer.contains(publicReferer)) {
// if(questionList.size() == 0){
// titleStb.append("</br><font size=\"0.5\" color=\"red\">没有解析到题目</font>");
// }else if(ObjectUtil.isEmpty(typeConcatEnum.get())) {
// titleStb.append("</br><font size=\"0.5\" color=\"red\">没有解析到题型</font>");
// }else {
// titleStb.append("</br><font size=\"0.5\" color=\"blue\">解析结果:");
// titleStb.append(recordsCount.get());
// titleStb.append(" 道题目</font>");
// }
// }
//
// Object ids[] = getCourseIdsNew(title[0], groupByCourseId.get(), groupByCourseName.get());
// final Long courseId = (Long)ids[0];
// final Long chapterId = (Long)ids[1];
// String finalTitle = !referer.contains(publicReferer)? titleStb.toString(): titleStb.append(ids[2] + Strings.EMPTY).toString();
//
// /** 最后检测属性, 并行处理,没有向外部非线程安全的变量写入东西,无需担心丢数据 */
// questionList.parallelStream().forEach(item -> {
// item.set("chapterId", ObjectUtil.defaultIfNull(chapterId, courseId));
// questionValidator.checkQuestion(item);
// if (referer.contains(publicReferer)) {
// checkUnique(isPublic, item);
// }
// });
//
// /** 本次上传的题目判断唯一性 */
// checkLocalQuestionsUnique(questionList);
//
// /** 拼成前端需要的数据 */
// resultMap.put(url2HttpUrl(fileEntity.getUrl()),new Object[]{finalTitle,questionList});
// } catch (Exception ex) { ex.printStackTrace(); }
// finally {
// try {inputStream.get().close();xDocument.get().close();}
// catch (Exception ioe) {ioe.printStackTrace();}
// }
// });
//
// return JsonResult.success(resultMap);
// }
//
// /**
// * 验证本地待上传的题库 是否存在重复,
// * 本次上传的题目判断唯一性
// * @param questionList
// */
// private void checkLocalQuestionsUnique(List<Question> questionList) {
// if (ObjectUtil.isEmpty(questionList)) { return; }
// // 默认给50道题的容量
// final Set<String> hashMap = new LinkedHashSet<>(50);
// final List<String> strings = new ArrayList<>(50);
// for (Question question : questionList) {
// String questionToString = question.toString();
// // 如果添加失败,则代表题目已经存在
// if (!hashMap.add(questionToString)) {
// // 告知前端 错误异常
// question.set("errMsg", String.format("可能与上面的第 【%s】 题重复",strings.indexOf(questionToString) + 1));
// }
// else { strings.add(questionToString); }
// }
//
// hashMap.clear();
// strings.clear();
// }
//
// /**
// * 检测题目唯一
// * @param question
// */
// public void checkUnique(Integer isPublic, @NotNull Question question) {
// /** 正则去尽可能的精准匹配, 模糊查询img标签和() 符号 */
// question.set("questionStemRegexp", null);
// try {
// question.set("questionStemRegexp", "^" + question.getQuestionStemRegexp() + "$");
// if (publicSqlManager.getMapper(QuestionDao.class).checkUnique(question) > 0) {
// question.set("errMsg", ObjectUtil.defaultIfNull(question.get("errMsg"), "") + "该题目可能存在重复!");
// }
// }catch (Exception e){
// log.error("报错了:"+question.getQuestionStemRegexp());
// e.printStackTrace();
// }
// }
//
// /**
// * 格式化答案
// * <ol>
// * <p>情况一:</p>
// * <li><p>ABC</p> 只取 ABC</li>
// * <li>转大写</li>
// * <li>ABC => 转换成A,B,C</li>
// * </ol>
// * <ol>
// * <p>情况二:</p>
// * <li><p>对</p> 只取 对</li>
// * <li>转大写</li>
// *
// * @param QuestionAnswer 答案
// * @param questionTypeConcatEnum
// * @return
// */
// public String secondFormat(String questionAnswer, StrategyContext.QuestionTypeConcatEnum typeConcatEnum) {
// if (StringUtils.isBlank(questionAnswer)) { return questionAnswer; }
// // 去除各种空格
// questionAnswer = org.springframework.util.StringUtils.trimAllWhitespace(questionAnswer);
// // <p>ABC</p> 只获取ABC 删除所有</>标签元素。
// String replaced = questionAnswer.replaceAll(" |</?[a-zA-Z\\u4e00-\\u9fa5]*?[^<]>", "");
// // 取中文或者字母, 也有移除空格等符号的操作
// String res = ObjectUtil.defaultIfNull(ReUtil.getGroup0("[A-Za-z\\u4e00-\\u9fa5]+", replaced), replaced).toUpperCase();
// // 如果只有字母,则拆分规则: ABC => A,B,C
// if (typeConcatEnum.equals(MULTIPLE) && ReUtil.isMatch("^[A-Za-z]+$",res)) {
// // ABC => A,B,C
// String[] split = res.split("");
// Arrays.sort(split);
// res = ArrayUtil.join(split, ",");
// }
// // 判断题逻辑
// if (typeConcatEnum.equals(DECIDE) && ReUtil.isMatch("(正确)|(错误)|A|B",res)) {
// // ABC => A,B,C
// res = res.replaceAll("(正确)|A", "对").replaceAll("(错误)|B", "错");
// }
// return res;
// }
@SneakyThrows
public JsonResult importQBankWordTemplate(@NotEmpty List<FileEntity> fileEntityList, CoreUser coreUser, String referer) {
/** 找出段落, 逐段落处理 */
AtomicReference<FileInputStream> inputStream = new AtomicReference<>();
AtomicReference<XWPFDocument> xDocument = new AtomicReference<>();
//装结果 key卷子访问地址 value{标题题目List}
Map<String,Object[]> resultMap = new HashMap<>();
/** 仅用于存放标题 */
final String[] title = { Strings.EMPTY };
/** 标题暂存,可能是多个 */
final StringBuilder titleStb = new StringBuilder();
/** 循环文件集合 */
fileEntityList.forEach(fileEntity -> {
/** 上传文件的临时路径 */
String importPath = fileEntity.getAbsoluteUrl();
/** 新建文件夹绝对路径 */
String absoluteDirPath = String.format("%s%s%s%s%s%s", GetFile.p , SE , "filesystem" , SE , "temp" , SE );
/** 新建文件的绝对路径 */
String absolutePath = absoluteDirPath + fileEntity.getTempName();
//各种流
try {
inputStream.set(new FileInputStream(importPath));
xDocument.set(new XWPFDocument(inputStream.get()));
/** 集合拷贝 */
List<MyXWPFParagraph> myParagraphs = BeanCopyUtil.copyListProperties(xDocument.get().getParagraphs() , MyXWPFParagraph::new, (t, d) -> {
d.setXwpfParagraph(t);
});
/** 需要存库的题目集合 */
List<ResourcesQuestion> questionList = new CopyOnWriteArrayList<>();
/** 题目类型 */
AtomicReference<StrategyContext.QuestionTypeConcatEnum> typeConcatEnum = new AtomicReference<>();
/** 段落类型 */
AtomicReference<StrategyContext.QuestionParagraphTypeEnum> paragraphTypeEnum = new AtomicReference<>();
/** 每题分数 */
AtomicReference<BigDecimal> questionScore = new AtomicReference<>(BigDecimal.ZERO);
/** 题干 */
AtomicReference<String> questionStem = new AtomicReference<>(Strings.EMPTY);
/** 答案 */
AtomicReference<String> questionAnswer = new AtomicReference<>(Strings.EMPTY);
/** 题目实体 */
AtomicReference<ResourcesQuestion> question = new AtomicReference<>();
/** 题干计数 */
AtomicInteger recordsCount = new AtomicInteger();
/** 遍历所有段落 */
myParagraphs.forEach(e -> {
/** 策略中的参数设置 */
strategyContext.setText(e.getText(absoluteDirPath, fileEntity.getTempName())).start(c -> {
/** 第一行 */
if (recordsCount.get() == 1 && StringUtils.isBlank(title[0])) {
/** 暴力取值 */
titleStb.append(title[0] = myParagraphs.get(0).getText(absoluteDirPath, fileEntity.getTempName()));
}
/** k 策略枚举v 处理后的段落文字 */
c.forEach((k, v) -> {
/** 记录段落类型, 未查询类型之外的类型 */
if (!k.equals(OTHER)) {paragraphTypeEnum.set(k);}
switch(k) {
/** 未匹配到的段落 */
case OTHER: {
/** 如果非标题字符,可能是前一个段落的副行。需要追加到相应的段落中 */
insertCorrespondingParagraph(typeConcatEnum, paragraphTypeEnum.get() ,v, question, questionStem, questionAnswer);
} break;
/** 题目类型,分值,该类型的总分 */
case TYPE: {
/** 题目类型作用域 */
String[] sp = v.split(",");
typeConcatEnum.set(StrategyContext.QuestionTypeConcatEnum.matchText(sp[2]));
questionScore.set(NumberUtil.toBigDecimal(sp[0]).setScale(1, RoundingMode.HALF_UP));
} break;
/** 题干 */
case STEM: {
final String numStartRegex = "^\\d+[\\.\\、\\]";
if(ReUtil.contains(numStartRegex, v)) {
/** 题干的编号需要移除掉 */
questionStem.set(v.replaceFirst(numStartRegex, Strings.EMPTY).trim());
/** 题数++ */
recordsCount.incrementAndGet();
/** 清空变量 */
questionAnswer.set(Strings.EMPTY);
/** 带有编号的段落,是主题干。需要创建新的题目对象 */
ResourcesQuestion qs = new ResourcesQuestion();
/** 题目类型,查询插入操作 */
QuestionType questionType = selectOrInsert(typeConcatEnum.get(), coreUser);
qs.setQuestionTypeId(questionType.getQuestionTypeId());
qs.putQuestionTypeConcat(questionType.getQuestionTypeConcat());
qs.putQuestionTypeName(questionType.getQuestionTypeName());
qs.setQuestionScore(questionScore.get());
if (StringUtils.isNotBlank(questionStem.get())){
qs.setQuestionStem(questionStem.get().replace("<","&lt;").replace(">","&gt;"));
}else {
qs.setQuestionStem(questionStem.get());
}
qs.setQuestionStatus(1);
qs.setUserId(coreUser.getId());
qs.setOrgId(coreUser.getOrgId());
question.set(qs);
questionList.add(question.get());
}
} break;
/** 选项 */
case OPTION: {
putQuestionOption(v, question.get());
} break;
/** 答案 questionAnswer不包含答案 字样 */
case ANSWER: {
questionAnswer.set(questionAnswer.get().concat(v));
// questionAnswer.get() 默认不包含 【答案】 字样
questionAnswer.set(secondFormat(questionAnswer.get(), typeConcatEnum.get()));
question.get().setQuestionAnswer(questionAnswer.get());
} break;
}
});
});
});
titleStb.append("</br><font size=\"0.5\" color=\"blue\">解析结果:");
titleStb.append(recordsCount.get());
titleStb.append(" 道题目</font>");
Object ids[] = getCourseIdsNew(title[0], groupByCourseId.get(), groupByCourseName.get());
final Long courseId = (Long)ids[0];
final Long chapterId = (Long)ids[1];
String finalTitle = !referer.contains(publicReferer)? titleStb.toString(): titleStb.append(ids[2] + Strings.EMPTY).toString();
/** 最后检测属性, 并行处理,没有向外部非线程安全的变量写入东西,无需担心丢数据 */
questionList.parallelStream().forEach(item -> {
item.set("chapterId", ObjectUtil.defaultIfNull(chapterId, courseId));
questionValidator.checkQuestion(item);
checkUnique(item);
});
/** 本次上传的题目判断唯一性 */
checkLocalQuestionsUnique(questionList);
/** 拼成前端需要的数据 */
resultMap.put(url2HttpUrl(fileEntity.getUrl()),new Object[]{finalTitle,questionList});
} catch (Exception ex) { ex.printStackTrace(); }
finally {
try {inputStream.get().close();xDocument.get().close();}
catch (Exception ioe) {ioe.printStackTrace();}
}
});
return JsonResult.success(resultMap);
}
/** 取选项的正则 */
private Pattern optionPattern = Pattern.compile("^[A-Za-z][\\.\\、\\  ]");
/**
*
* @param text
* @param question
*/
public void putQuestionOption(String text, ResourcesQuestion question){
//text为选项
if (StringUtils.isNotBlank(text)){
text = text.replace("<","&lt;").replace(">","&gt;");
}
Matcher matcher = optionPattern.matcher(text);
if(matcher.find()){
QuestionOption questionOption = new QuestionOption();
questionOption.setQuestionOptionContent(text.substring(2));
if(null != question.takeQuestionOptionList()){
List<QuestionOption> questionOptionList = question.takeQuestionOptionList();
questionOptionList.add(questionOption);
question.putQuestionOptionList(questionOptionList);
}else {
List<QuestionOption> questionOptionList = new ArrayList<>();
questionOptionList.add(questionOption);
question.putQuestionOptionList(questionOptionList);
}
if(null != question.takeQuestionOptionConcatList()){
List<String> questionOptionConcatList = question.takeQuestionOptionConcatList();
questionOptionConcatList.add(text.substring(2));
question.putQuestionOptionConcatList(questionOptionConcatList);
}else {
List<String> questionOptionConcatList = new ArrayList<>();
questionOptionConcatList.add(text.substring(2));
question.putQuestionOptionConcatList(questionOptionConcatList);
}
}
}
/**
* ,
*
* @param questionList
*/
private void checkLocalQuestionsUnique(List<ResourcesQuestion> questionList) {
if (ObjectUtil.isEmpty(questionList)) { return; }
// 默认给50道题的容量
final Set<String> hashSet = new LinkedHashSet<>(50);
final List<String> strings = new ArrayList<>(50);
for (ResourcesQuestion question : questionList) {
String questionToString = question.toString();
// 如果添加失败,则代表题目已经存在
if (!hashSet.add(questionToString)) {
// 告知前端 错误异常
question.set("errMsg", String.format("可能与上面的第 【%s】 题重复",strings.indexOf(questionToString) + 1));
}
else { strings.add(questionToString); }
}
hashSet.clear();
strings.clear();
}
/**
*
* @param question
*/
public void checkUnique(@NotNull ResourcesQuestion question) {
/** 正则去尽可能的精准匹配, 模糊查询img标签和() 符号 */
question.set("questionStemRegexp", null);
try {
question.set("questionStemRegexp", "^" + question.getQuestionStemRegexp() + "$");
if (resourcesQuestionDao.checkUnique(question) > 0) {
question.set("errMsg", ObjectUtil.defaultIfNull(question.get("errMsg"), "") + "该题目可能存在重复!");
}
}catch (Exception e){
log.error("报错了:"+question.getQuestionStemRegexp());
e.printStackTrace();
}
}
/**
*
* <ol>
* <p></p>
* <li><p>ABC</p> ABC</li>
* <li></li>
* <li>ABC => A,B,C</li>
* </ol>
* <ol>
* <p></p>
* <li><p></p> </li>
* <li></li>
*
* @param questionAnswer
* @param typeConcatEnum
* @return
*/
public String secondFormat(String questionAnswer, StrategyContext.QuestionTypeConcatEnum typeConcatEnum) {
if (StringUtils.isBlank(questionAnswer)) { return questionAnswer; }
// 去除各种空格
questionAnswer = org.springframework.util.StringUtils.trimAllWhitespace(questionAnswer);
// <p>ABC</p> 只获取ABC 删除所有</>标签元素。
String replaced = questionAnswer.replaceAll(" |</?[a-zA-Z\\u4e00-\\u9fa5]*?[^<]>", "");
// 取中文或者字母, 也有移除空格等符号的操作
String res = ObjectUtil.defaultIfNull(ReUtil.getGroup0("[A-Za-z\\u4e00-\\u9fa5]+", replaced), replaced).toUpperCase();
// 如果只有字母,则拆分规则: ABC => A,B,C
if (typeConcatEnum.equals(MULTIPLE) && ReUtil.isMatch("^[A-Za-z]+$",res)) {
// ABC => A,B,C
String[] split = res.split("");
Arrays.sort(split);
res = ArrayUtil.join(split, ",");
}
// 判断题逻辑
if (typeConcatEnum.equals(DECIDE) && ReUtil.isMatch("(正确)|(错误)|A|B",res)) {
// ABC => A,B,C
res = res.replaceAll("(正确)|A", "对").replaceAll("(错误)|B", "错");
}
return res;
}
}

@ -99,7 +99,8 @@ public class StrategyContext {
SINGLE("单选题", 1, "单.+题"),
MULTIPLE("多选题",2, "多.+题"),
DECIDE("判断题",3, "[判断]{1,2}.+题"),
SHORT("简答题",4, "简.+题");
FILL_QUESTION("填空题",4, "填.+题"),
ANALYSIS_QUESTION("分析题",5, "分析题");
private String typeName;
private Integer typeConcat;

@ -0,0 +1,126 @@
package com.ibeetl.jlw.validator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.jlw.util.ToolUtils;
import com.ibeetl.jlw.entity.ResourcesQuestion;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import static com.ibeetl.jlw.service.strategy.StrategyContext.QuestionTypeConcatEnum.*;
/**
*
*
* @author mlx
* @date 2022/6/1
**/
@Component
public class QuestionValidator {
/**
*
*/
private Range<String> options = Range.between("A", "Z");
/**
*
* @param question
*/
public String checkQuestion(ResourcesQuestion question) {
try {
checkQuestionThrow(question);
}catch (IllegalArgumentException e) {
/** 动态对象中传入一份errMsg */
question.set("errMsg", e.getMessage());
return e.getMessage();
}
return Strings.EMPTY;
}
/**
*
* @param question
* @return
*/
public void checkQuestionThrow(ResourcesQuestion question) {
Assert.notNull(question, "题目为空");
List<String> questionOptionConcatList = question.takeQuestionOptionConcatList();
String error = optionIncludeAnswer(question, questionOptionConcatList,question.getQuestionAnswer());
Assert.isTrue(StringUtils.isBlank(error), error);
Assert.notBlank(question.getQuestionStem(), "题干为空");
Assert.notBlank(question.getQuestionAnswer(), "答案为空");
// 有空值,返回异常
Assert.isFalse(optionIsBlank(questionOptionConcatList), "选项有空值");
// 判断题
if(MULTIPLE.getTypeConcat().equals(question.getQuestionType())){
Assert.isFalse(null == questionOptionConcatList || questionOptionConcatList.size()<2, "至少要有两个选项");
}
}
/**
*
* @param questionOptionConcatList
* @return
*/
private Boolean optionIsBlank(List<String> questionOptionConcatList) {
if(ObjectUtil.isEmpty(questionOptionConcatList)){
return true;
}
List<String> tempList = ToolUtils.deepCopyList(questionOptionConcatList);
List<String> nullList = new ArrayList<>();
nullList.add(null);
List<String> blackList = new ArrayList<>();
blackList.add("");
tempList.remove(nullList);
tempList.remove(blackList);
if(tempList.size() != questionOptionConcatList.size()){
return true;
}
return false;
}
/**
*
*
* @param question
* @param questionOptionConcatList
* @param questionAnswer
* @return
*/
private String optionIncludeAnswer(ResourcesQuestion question, List<String> questionOptionConcatList, String questionAnswer) {
if(ObjectUtil.isEmpty(questionOptionConcatList)) {
return "选项为空";
}
if(StringUtils.isBlank(questionAnswer)){
return "答案为空";
}
// 是判断题
if (DECIDE.getTypeConcat().equals(question.getQuestionType())) {
// 答案只能是对或错,前后不能有空格
return !questionAnswer.matches("^[对错]$") ? "判断题答案有误": "";
}
// 是单选题
if (SINGLE.getTypeConcat().equals(question.getQuestionType())) {
// 单选题只能有一个答案
return questionAnswer.length() != 1 ? "单选题只能有一个答案": "";
}
for (String s : questionAnswer.split(",")) {
if (!options.contains(s.toUpperCase())) {
return "答案不在选项内";
}
}
return "";
}
}

@ -374,4 +374,18 @@ getGroupQuestionTypeCount
GROUP BY
t.question_type
ORDER BY
t.question_type ASC
t.question_type ASC
checkUnique
===
* 验证题干是否可能存在重复
SELECT
COUNT(0) num
FROM
question t
WHERE
1 = 1
AND replace(replace(t.question_stem,'(',''),')','') REGEXP #questionStemRegexp#
AND t.question_status = 1
Loading…
Cancel
Save