|
|
|
@ -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("<","<").replace(">",">"));
|
|
|
|
|
// }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("<","<").replace(">",">"));
|
|
|
|
|
}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("<","<").replace(">",">");
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|