PaperService.java 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. package com.ruoyi.web.service;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson2.JSONObject;
  4. import com.github.pagehelper.PageHelper;
  5. import com.google.common.collect.Lists;
  6. import com.google.common.collect.Maps;
  7. import com.google.common.collect.Sets;
  8. import com.ruoyi.common.utils.StringUtils;
  9. import com.ruoyi.common.utils.bean.BeanUtils;
  10. import com.ruoyi.enums.PaperStatus;
  11. import com.ruoyi.enums.PaperType;
  12. import com.ruoyi.enums.QuestionType;
  13. import com.ruoyi.learn.domain.*;
  14. import com.ruoyi.learn.mapper.*;
  15. import com.ruoyi.learn.service.ILearnQuestionsService;
  16. import com.ruoyi.syzy.domain.BBusiWishUniversities;
  17. import com.ruoyi.syzy.mapper.BBusiWishUniversitiesMapper;
  18. import com.ruoyi.syzy.service.IBBusiWishUniversitiesService;
  19. import lombok.Data;
  20. import lombok.extern.slf4j.Slf4j;
  21. import org.apache.commons.collections4.CollectionUtils;
  22. import org.apache.commons.lang3.tuple.Pair;
  23. import org.springframework.stereotype.Service;
  24. import java.util.*;
  25. import java.util.concurrent.atomic.AtomicLong;
  26. import java.util.function.Function;
  27. import java.util.regex.Matcher;
  28. import java.util.regex.Pattern;
  29. import java.util.stream.Collectors;
  30. import java.util.stream.Stream;
  31. /**
  32. * 考卷服务
  33. */
  34. @Service
  35. @Slf4j
  36. public class PaperService {
  37. Set<QuestionType> chooseTypes = Sets.newHashSet(QuestionType.Judgment, QuestionType.Multiple, QuestionType.Single);
  38. private final LearnPaperMapper paperMapper;
  39. private final LearnPaperQuestionMapper paperQuestionMapper;
  40. private final LearnQuestionsMapper questionsMapper;
  41. private final ILearnQuestionsService learnQuestionsService;
  42. private final LearnDirectedKnowledgeMapper learnDirectedKnowledgeMapper;
  43. private final IBBusiWishUniversitiesService wishUniversitiesService;
  44. private final BBusiWishUniversitiesMapper bBusiWishUniversitiesMapper;
  45. private final LearnKnowledgeTreeMapper learnKnowledgeTreeMapper;
  46. private final LearnKnowledgeCourseMapper learnKnowledgeCourseMapper;
  47. PaperService(LearnPaperMapper paperMapper, LearnPaperQuestionMapper paperQuestionMapper, LearnQuestionsMapper questionsMapper, ILearnQuestionsService learnQuestionsService, LearnDirectedKnowledgeMapper learnDirectedKnowledgeMapper, IBBusiWishUniversitiesService wishUniversitiesService, BBusiWishUniversitiesMapper bBusiWishUniversitiesMapper, LearnKnowledgeTreeMapper learnKnowledgeTreeMapper, LearnKnowledgeCourseMapper learnKnowledgeCourseMapper) {
  48. this.paperMapper = paperMapper;
  49. this.paperQuestionMapper = paperQuestionMapper;
  50. this.questionsMapper = questionsMapper;
  51. this.learnQuestionsService = learnQuestionsService;
  52. this.learnDirectedKnowledgeMapper = learnDirectedKnowledgeMapper;
  53. this.wishUniversitiesService = wishUniversitiesService;
  54. this.bBusiWishUniversitiesMapper = bBusiWishUniversitiesMapper;
  55. this.learnKnowledgeTreeMapper = learnKnowledgeTreeMapper;
  56. this.learnKnowledgeCourseMapper = learnKnowledgeCourseMapper;
  57. // buildAllPapers(2);
  58. // buildSimulatedPaperForUniversity(20950L, 11L, 78L, 2);
  59. // buildSimulatedPaperForUniversity(20962L, 11L, 156L, 2);
  60. // test2();
  61. // testCulture();
  62. // corrQuestions();
  63. }
  64. public static String convertImg2Html(String text) {
  65. String urlPattern = "!\\[\\]\\((https?://[\\w.-]+\\.[a-zA-Z]{2,}[/\\w.-]*\\??[/\\w.=&%-]*)\\)";
  66. Pattern pattern = Pattern.compile(urlPattern);
  67. Matcher matcher = pattern.matcher(text);
  68. StringBuffer result = new StringBuffer();
  69. while (matcher.find()) {
  70. String url = matcher.group(1);
  71. String replacement = String.format("<img src=\"%s\" />", url);
  72. matcher.appendReplacement(result, replacement);
  73. }
  74. matcher.appendTail(result);
  75. return result.toString();
  76. }
  77. public void corrQuestions() {
  78. LearnQuestions cond = new LearnQuestions();
  79. Long id = 0L;
  80. Integer pageSize = 0;
  81. List<LearnQuestions> questionsList;
  82. String newValue;
  83. do {
  84. cond.setId(id);
  85. PageHelper.startPage(1, pageSize, null);
  86. questionsList = questionsMapper.selectLearnQuestionsList(cond);
  87. for(LearnQuestions q : questionsList) {
  88. boolean updated = false;
  89. LearnQuestions up = new LearnQuestions();
  90. up.setId(q.getId());
  91. if(!q.getTitle().equals((newValue = convertImg2Html(q.getTitle())))) {
  92. up.setTitle(newValue);
  93. updated = true;
  94. }
  95. if(StringUtils.isNotBlank(q.getTitle0())) {
  96. if(!q.getTitle0().equals((newValue = convertImg2Html(q.getTitle0())))) {
  97. up.setTitle0(newValue);
  98. updated = true;
  99. }
  100. }
  101. if(StringUtils.isNotBlank(q.getAnswer2())) {
  102. if(!q.getAnswer2().equals(newValue = convertImg2Html(q.getAnswer2()))) {
  103. up.setAnswer2(newValue);
  104. updated = true;
  105. }
  106. }
  107. if(StringUtils.isNotBlank(q.getParse())) {
  108. if(!q.getParse().equals(newValue = convertImg2Html(q.getParse()))) {
  109. up.setParse(newValue);
  110. updated = true;
  111. }
  112. }
  113. if(StringUtils.isNotBlank(q.getOptionA())) {
  114. if(!q.getOptionA().equals(newValue = convertImg2Html(q.getOptionA()))) {
  115. up.setOptionA(newValue);
  116. updated = true;
  117. }
  118. }
  119. if(StringUtils.isNotBlank(q.getOptionB())) {
  120. if(!q.getOptionB().equals(newValue = convertImg2Html(q.getOptionB()))) {
  121. up.setOptionB(newValue);
  122. updated = true;
  123. }
  124. }
  125. if(StringUtils.isNotBlank(q.getOptionC())) {
  126. if(!q.getOptionC().equals(newValue = convertImg2Html(q.getOptionC()))) {
  127. up.setOptionC(newValue);
  128. updated = true;
  129. }
  130. }
  131. if(StringUtils.isNotBlank(q.getOptionD())) {
  132. if(!q.getOptionD().equals(newValue = convertImg2Html(q.getOptionD()))) {
  133. up.setOptionD(newValue);
  134. updated = true;
  135. }
  136. }
  137. if(StringUtils.isNotBlank(q.getOptionE())) {
  138. if(!q.getOptionE().equals(newValue = convertImg2Html(q.getOptionE()))) {
  139. up.setOptionE(newValue);
  140. updated = true;
  141. }
  142. }
  143. if(StringUtils.isNotBlank(q.getOptionF())) {
  144. if(!q.getOptionF().equals(newValue = convertImg2Html(q.getOptionF()))) {
  145. up.setOptionF(newValue);
  146. updated = true;
  147. }
  148. }
  149. if(StringUtils.isNotBlank(q.getOptionG())) {
  150. if(!q.getOptionG().equals(newValue = convertImg2Html(q.getOptionG()))) {
  151. up.setOptionG(newValue);
  152. updated = true;
  153. }
  154. }
  155. if(updated) {
  156. up.setIsUpdate(1L);
  157. questionsMapper.updateLearnQuestions(up);
  158. }
  159. id = q.getId();
  160. }
  161. } while(questionsList.size() != pageSize);
  162. }
  163. public void buildAllPapers(Integer seq) {
  164. LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
  165. dkCond.setYear(2025);
  166. Map<Long, List<LearnDirectedKnowledge>> universityDirectedMap = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond).stream().collect(Collectors.groupingBy(LearnDirectedKnowledge::getUniversityId));
  167. Map uCond = new HashMap();
  168. uCond.put("ids", universityDirectedMap.keySet());
  169. Map<Long, BBusiWishUniversities> universityMap = bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesListSimpleByIds(uCond).stream().collect(Collectors.toMap(BBusiWishUniversities::getId, Function.identity()));
  170. for(Long universityId : universityDirectedMap.keySet()) {
  171. BBusiWishUniversities universities = universityMap.get(universityId);
  172. if(null == universities) {
  173. continue;
  174. }
  175. for(LearnDirectedKnowledge dk : universityDirectedMap.get(universityId)) {
  176. buildSimulatedPaperForKnowledge(11L, seq, universities, dk);
  177. }
  178. }
  179. }
  180. public void testCulture() {
  181. LearnPaper paper = new LearnPaper();
  182. // TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2("", "[{\"score\":40,\"knowledges\":[2001,2002,2003,2004,2005,2006,2007,2010,2013],\"types\":{\"judgment\":\"0\",\"single\":\"10/4\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":20,\"knowledges\":[2012],\"types\":{\"judgment\":\"0\",\"single\":\"5/4\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":40,\"knowledges\":[2011],\"types\":{\"judgment\":\"0\",\"single\":\"8/5\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}}]");
  183. // paperDef.setFillExclude(false);
  184. // paper.setSubjectId(1010L);
  185. // paper.setRelateId(1010L); // 定向ID
  186. // paper.setPaperName("语文");
  187. // TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2("", "[{\"score\":80,\"knowledges\":[1005,1006,1007,1008,1009,1010,1011,1013,1015,2044,2043],\"types\":{\"judgment\":\"10/2\",\"single\":\"15/4\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":10,\"knowledges\":[2043],\"types\":{\"judgment\":\"0\",\"single\":\"1/10\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":10,\"knowledges\":[2043],\"types\":{\"judgment\":\"0\",\"single\":\"1/10\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}}]");
  188. // paperDef.setFillExclude(false);
  189. // paper.setSubjectId(1011L);
  190. // paper.setRelateId(1011L);
  191. // paper.setPaperName("数学");
  192. TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2("", "[{\"score\":60,\"knowledges\":[1022,1193,1023],\"types\":{\"judgment\":\"0\",\"single\":\"15/4\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":20,\"knowledges\":[1022,1193,1023],\"types\":{\"judgment\":\"0\",\"single\":\"5/4\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}},{\"score\":20,\"knowledges\":[1022,1193,1023],\"types\":{\"judgment\":\"0\",\"single\":\"10/2\",\"multiple\":\"0\",\"subjective\":\"0\",\"fill\":\"0\",\"essay\":\"0\",\"short\":\"0\"}}]");
  193. paperDef.setFillExclude(false);
  194. paper.setSubjectId(1012L);
  195. paper.setRelateId(1012L);
  196. paper.setPaperName("英语");
  197. paper.setPaperType(PaperType.Simulated.name());
  198. paper.setYear(2025);
  199. paper.setPaperSource(1);
  200. paper.setDirectKey("");
  201. paper.setStatus(PaperStatus.Valid.getVal());
  202. paper.setNumber(paperDef.getTotal());
  203. paper.setFenshu(paperDef.getScore().intValue());
  204. AnswerSheet.PaperCond info = new AnswerSheet.PaperCond();
  205. info.setScore(paper.getFenshu());
  206. info.setTime(60 * 60);
  207. info.setTypes(paperDef.getTypes().stream().map(t -> new AnswerSheet.PaperCondType(t.getType().getTitle(), t.getCount(), t.getScore())).collect(Collectors.toList()));
  208. paper.setPaperInfo(JSONObject.toJSONString(info));
  209. try {
  210. Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = buildPaper2(null, paper, paperDef);
  211. savePaper(paperResult.getKey(), paperResult.getValue());
  212. return;
  213. } catch(Exception e) {
  214. log.error(e.getMessage());
  215. }
  216. }
  217. public void test2() {
  218. TestPaperVO.PaperDef paperDef = new TestPaperVO.PaperDef();
  219. paperDef.setFillExclude(true);
  220. paperDef.setKnowIds(Lists.newArrayList(1016L,1101L,1091L, 1103L));
  221. List<TestPaperVO.TypeDef> typeDefList= Lists.newArrayList();
  222. typeDefList.add(new TestPaperVO.TypeDef("判断题", "判断题", 20, 2));
  223. typeDefList.add(new TestPaperVO.TypeDef("单选题", "单选题", 10, 1));
  224. typeDefList.add(new TestPaperVO.TypeDef("多选题", "多选题", 10, 2));
  225. paperDef.setTypes(typeDefList);
  226. paperDef.setTotal(40L);
  227. List<LearnPaperQuestion> questionList = getQuestionsByType(null, paperDef);
  228. return;
  229. }
  230. public void test() {
  231. // PaperDef paperDef = new PaperDef();
  232. // paperDef.setFillExclude(true);
  233. // paperDef.setKnowIds(Lists.newArrayList(133614L,130166L,130187L));
  234. // List<TypeDef> typeDefList= Lists.newArrayList();
  235. // typeDefList.add(new TypeDef("单选题", "单选题", 80, 1));
  236. // typeDefList.add(new TypeDef("判断题", "判断题", 10, 2));
  237. // paperDef.setTypes(typeDefList);
  238. // getQuestions(1L, 100L, paperDef);
  239. }
  240. public PaperVO loadPaper(Long paperId) {
  241. LearnPaper learnPaper = paperMapper.selectLearnPaperById(paperId);
  242. PaperVO result = new PaperVO();
  243. BeanUtils.copyProperties(learnPaper, result);
  244. result.setQuestions(loadPaperQuestions(paperId));
  245. return result;
  246. }
  247. /**
  248. * 加载试卷
  249. * @param paperId
  250. * @return
  251. */
  252. public List<PaperVO.QuestionSeq> loadPaperQuestions(Long paperId) {
  253. List<LearnQuestions> questions = questionsMapper.selectQuestionByPaperId(paperId);
  254. Map<String, PaperVO.QuestionSeq> gropuMap = Maps.newHashMap();
  255. List<PaperVO.QuestionSeq> paperQuestionList = Lists.newArrayList();
  256. for(LearnQuestions lqs : questions) {
  257. PaperVO.QuestionSeq qs = new PaperVO.QuestionSeq();
  258. BeanUtils.copyProperties(lqs, qs, "options", "parse", "answer1", "answer2");
  259. QuestionType qt = QuestionType.of(lqs.getQtpye());
  260. qs.setTypeId(qt.getVal());
  261. qs.setType(qt.getTitle());
  262. qs.setQtpye(qt.getTitle()); // TODO 兼容
  263. if (!chooseTypes.contains(qt)) {
  264. qs.setOptions(Lists.newArrayList("会", "不会"));
  265. } else {
  266. qs.setOptions(StringUtils.getOptions(lqs.getOptionA(), lqs.getOptionB(), lqs.getOptionC(), lqs.getOptionD(), lqs.getOptionE(), lqs.getOptionF(), lqs.getOptionG()));
  267. }
  268. if(StringUtils.isNotBlank(lqs.getTitle0())) { // 大题
  269. PaperVO.QuestionSeq qg = gropuMap.get(lqs.getTitle0());
  270. if(qg == null) {
  271. qg = new PaperVO.QuestionSeq();
  272. qg.setTypeId(99);
  273. qg.setSubQuestions(Lists.newArrayList());
  274. qg.setTitle(lqs.getTitle0());
  275. gropuMap.put(lqs.getTitle0(), qg);
  276. paperQuestionList.add(qg);
  277. }
  278. qg.getSubQuestions().add(qs);
  279. } else {
  280. paperQuestionList.add(qs);
  281. }
  282. }
  283. return paperQuestionList;
  284. }
  285. public List<PaperVO.QuestionAnswer> loadPaperQuestionAnswers(Long userId, Long paperId, Map<Long, LearnAnswer> answerMap, boolean withParse) {
  286. List<LearnQuestions> questions = questionsMapper.selectQuestionByPaperId(paperId);
  287. learnQuestionsService.fillCollectInfo(userId, questions);
  288. Map<String, PaperVO.QuestionAnswer> gropuMap = Maps.newHashMap();
  289. List<PaperVO.QuestionAnswer> paperQuestionList = Lists.newArrayList();
  290. LearnAnswer answer;
  291. for(LearnQuestions lqs : questions) {
  292. PaperVO.QuestionAnswer qs = new PaperVO.QuestionAnswer();
  293. QuestionType qt = QuestionType.of(lqs.getQtpye());
  294. if(withParse || !chooseTypes.contains(qt)) {
  295. BeanUtils.copyProperties(lqs, qs, "title", "options");
  296. } else {
  297. BeanUtils.copyProperties(lqs, qs, "title", "options", "parse", "answer1", "answer2");
  298. }
  299. qs.setTotalScore(lqs.getScore());
  300. qs.setScore(null);
  301. qs.setTypeId(qt.getVal());
  302. qs.setIsFavorite(lqs.isCollect());
  303. if(null != answerMap && null != (answer = answerMap.get(lqs.getId()))) {
  304. qs.setAnswerId(answer.getAnswerId());
  305. qs.setAnswers(Arrays.asList(answer.getAnswer().split(",")));
  306. qs.setState(answer.getState());
  307. qs.setIsMark(answer.getMark());
  308. qs.setIsNotKnow(answer.getNotKnow());
  309. qs.setScore(answer.getScore());
  310. }
  311. if(StringUtils.isNotBlank(lqs.getTitle0())) { // 大题
  312. PaperVO.QuestionAnswer qg = gropuMap.get(lqs.getTitle0());
  313. if(qg == null) {
  314. qg = new PaperVO.QuestionAnswer();
  315. qg.setTypeId(99);
  316. qg.setSubQuestions(Lists.newArrayList());
  317. qg.setTitle(lqs.getTitle0());
  318. gropuMap.put(lqs.getTitle0(), qg);
  319. paperQuestionList.add(qg);
  320. }
  321. qg.getSubQuestions().add(qs);
  322. } else {
  323. paperQuestionList.add(qs);
  324. }
  325. }
  326. return paperQuestionList;
  327. }
  328. public int buildSimulatedPaperForUniversity(Long universityId, Long subjectId, Long directedId, Integer seq) {
  329. LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
  330. dkCond.setUniversityId(universityId);
  331. List<LearnDirectedKnowledge> directedKnowledgeList = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond);
  332. BBusiWishUniversities universities = wishUniversitiesService.selectBBusiWishUniversitiesById(universityId);
  333. for(LearnDirectedKnowledge dk : directedKnowledgeList) {
  334. if(null == directedId || directedId.equals(dk.getId())) {
  335. buildSimulatedPaperForKnowledge(subjectId, seq, universities, dk);
  336. }
  337. }
  338. return 0;
  339. }
  340. /**
  341. * 根据院校专业要求生成模拟试卷
  342. * @return
  343. */
  344. public int buildSimulatedPaperForKnowledge(Long subjectId, Integer seq, BBusiWishUniversities universities, LearnDirectedKnowledge dk) {
  345. if(StringUtils.isBlank(dk.getConditions())) {
  346. return 0;
  347. }
  348. TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2(dk.getKnowledges(), dk.getConditions());
  349. paperDef.setFillExclude(false);
  350. LearnPaper paper = new LearnPaper();
  351. paper.setSubjectId(subjectId);
  352. paper.setPaperType(PaperType.Simulated.name());
  353. paper.setRelateId(dk.getId()); // 定向ID
  354. paper.setYear(dk.getYear());
  355. paper.setPaperSource(seq);
  356. paper.setStatus(1);
  357. if(CollectionUtils.isNotEmpty(paperMapper.selectLearnPaperList(paper))) {
  358. log.warn("已经生成: {}:{}", dk.getId(), seq);
  359. return 0;
  360. }
  361. paper.setPaperName(StringUtils.isNotBlank(dk.getDirectKey()) ? universities.getName() + "(" + dk.getDirectKey() + ")" : universities.getName());
  362. paper.setDirectKey(universities.getId() + "_" + dk.getExamineeTypes() + "_" + dk.getDirectKey());
  363. paper.setStatus(PaperStatus.Valid.getVal());
  364. paper.setNumber(paperDef.getTotal());
  365. paper.setFenshu(paperDef.getScore().intValue());
  366. AnswerSheet.PaperCond info = new AnswerSheet.PaperCond();
  367. info.setScore(paper.getFenshu());
  368. info.setTime(dk.getTime() * 60);
  369. info.setTypes(paperDef.getTypes().stream().map(t -> new AnswerSheet.PaperCondType(t.getType().getTitle(), t.getCount(), t.getScore())).collect(Collectors.toList()));
  370. paper.setPaperInfo(JSONObject.toJSONString(info));
  371. try {
  372. Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = buildPaper2(null, paper, paperDef);
  373. savePaper(paperResult.getKey(), paperResult.getValue());
  374. return 0;
  375. } catch(Exception e) {
  376. log.error(e.getMessage());
  377. }
  378. return 0;
  379. }
  380. public Pair<LearnPaper, List<LearnPaperQuestion>> buildPaper2(Long studentId, LearnPaper paper, TestPaperVO.PaperDef2 paperDef) {
  381. paperDef.setFillExclude(null != studentId);
  382. List<LearnPaperQuestion> pqList = getQuestions2(studentId, paperDef);
  383. return Pair.of(paper, pqList);
  384. }
  385. /**
  386. * 根据试卷定义生成试卷
  387. * @param studentId
  388. * @param paper
  389. * @param paperDef
  390. * @return
  391. */
  392. public Pair<LearnPaper, List<LearnPaperQuestion>> buildPaper(Long studentId, LearnPaper paper, TestPaperVO.PaperDef paperDef) {
  393. if(null == studentId){
  394. paperDef.setFillExclude(false);
  395. }
  396. List<LearnPaperQuestion> pqList = getQuestions(studentId, paperDef);
  397. return Pair.of(paper, pqList);
  398. }
  399. /**
  400. * 保存试卷
  401. * @param paper
  402. * @param pqList
  403. * @return
  404. */
  405. public LearnPaper savePaper(LearnPaper paper, List<LearnPaperQuestion> pqList) {
  406. paper.setNumber(pqList.size());
  407. paper.setFenshu((int) Math.round(pqList.stream().mapToDouble(LearnPaperQuestion::getScore).sum()));
  408. paperMapper.insertLearnPaper(paper);
  409. Long paperId = paper.getId();
  410. pqList.stream().forEach(t -> t.setPaperId(paperId));
  411. paperQuestionMapper.batchInsert(pqList);
  412. return paper;
  413. }
  414. /**
  415. * 按类型,知识点平均分配组卷
  416. * @param studentId
  417. * @param paperDef
  418. * @return
  419. */
  420. public List<LearnPaperQuestion> getQuestionsByType(Long studentId, TestPaperVO.PaperDef paperDef) {
  421. // 统计知识点+类型的有效数量 TODO 总量可以缓存
  422. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, paperDef);
  423. assignTypeFirstWithCount(paperDef, knowTypeAssignMap); // 知识优先,类型可变
  424. return getQuestions(studentId, paperDef, knowTypeAssignMap);
  425. }
  426. public List<LearnPaperQuestion> getQuestions2(Long studentId, TestPaperVO.PaperDef2 paperDef) {
  427. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  428. Set<Long> existQuestionIdSet = Sets.newHashSet();
  429. for(TestPaperVO.KnowledgeTypeDef2 ktd : paperDef.getKnowTypes()) {
  430. Map<Long, List<Long>> ktSubMap = Maps.newHashMap();
  431. Integer maxSubCount = 6;
  432. List<Long> newKnownList = Lists.newArrayList();
  433. List<Long> tailKnownList = Lists.newArrayList();
  434. List<LearnKnowledgeTree> ktList = learnKnowledgeTreeMapper.selectLearnKnowledgeTreeByParentIds(ktd.getKnowledges());
  435. for(LearnKnowledgeTree kt : ktList) {
  436. List<Long> subIdList = ktSubMap.computeIfAbsent(kt.getPid(), k -> Lists.newArrayList());
  437. subIdList.add(kt.getId());
  438. }
  439. for(Long knownId : ktd.getKnowledges()) {
  440. List<Long> subList = ktSubMap.get(knownId);
  441. if(null == subList) {
  442. newKnownList.add(knownId);
  443. } else if(subList.size() > maxSubCount) {
  444. newKnownList.addAll(subList.subList(0, maxSubCount));
  445. tailKnownList.addAll(subList.subList(maxSubCount, subList.size()));
  446. } else {
  447. newKnownList.addAll(subList);
  448. }
  449. }
  450. newKnownList.addAll(tailKnownList);
  451. ktd.setKnowledges(newKnownList);
  452. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, "0", ktd.getTypes().stream().map(t -> t.getType().getTitle()).collect(Collectors.toList()), newKnownList, paperDef.getFillExclude());
  453. assignTypeFirst(paperDef.getFillExclude(), ktd, knowTypeAssignMap);
  454. pqList.addAll(getQuestions2(studentId, ktd.getCount(), pqList.size(), newKnownList, ktd.getTypes(), knowTypeAssignMap, existQuestionIdSet));
  455. }
  456. reSort(pqList);
  457. return pqList;
  458. }
  459. public List<LearnPaperQuestion> getQuestionsByRandom(Long studentId, Integer total, Collection<Long> knowledgeIds, List<String> types) {
  460. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, "1", types, knowledgeIds, false);
  461. List<KnowTypeAssign> knowTypeAssignList = Lists.newArrayList(knowTypeAssignMap.values());
  462. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  463. Set<Long> existQuestionIdSet = Sets.newHashSet();
  464. Random random = new Random();
  465. Map<String, List<LearnQuestions>> typeQuestionMap = Maps.newHashMap();
  466. LearnQuestions qCond = new LearnQuestions();
  467. do {
  468. if(knowTypeAssignList.isEmpty()) {
  469. break;
  470. }
  471. int idx = random.nextInt(knowTypeAssignList.size());
  472. KnowTypeAssign knowTypeAssign = knowTypeAssignList.get(idx);
  473. if(knowTypeAssign.getExclCount() > knowTypeAssign.getExclAssign()) {
  474. List<LearnQuestions> questions = typeQuestionMap.get(knowTypeAssign.getType());
  475. if(null == questions) {
  476. qCond.setKnowledgeId(knowTypeAssign.getKnowId());
  477. qCond.setQtpye(knowTypeAssign.getType());
  478. qCond.setId(studentId);
  479. qCond.setNumber(knowTypeAssign.exclCount > 500 ? (long) random.nextInt(knowTypeAssign.exclCount.intValue() - 500) : 0L);
  480. qCond.setIsSubType("1");
  481. questions = questionsMapper.selectQuestionsForPaper(qCond);
  482. typeQuestionMap.put(knowTypeAssign.getType(), questions);
  483. }
  484. if(!questions.isEmpty()) {
  485. int oldSize = pqList.size();
  486. addRandomList(knowTypeAssign.getKnowId(), QuestionType.of(knowTypeAssign.getType()), questions, random, total.longValue(), 1L, 1.0, existQuestionIdSet, 1, pqList);
  487. if(oldSize != pqList.size()) {
  488. knowTypeAssign.exclAssign++;
  489. }
  490. }
  491. } else {
  492. knowTypeAssignList.remove(idx);
  493. }
  494. } while(pqList.size() < total);
  495. reSort(pqList);
  496. return pqList;
  497. }
  498. /**
  499. * // diff(type), paperId(parentId), seq(type), questionId(id)
  500. * @param pqList
  501. */
  502. private void reSort(List<LearnPaperQuestion> pqList) {
  503. Collections.sort(pqList, new Comparator<LearnPaperQuestion>() {
  504. @Override
  505. public int compare(LearnPaperQuestion o1, LearnPaperQuestion o2) {
  506. int iRet;
  507. if(0 != (iRet = o1.getDiff().compareTo(o2.getDiff()))) {
  508. return iRet;
  509. }
  510. if(0 != (iRet = o1.getPaperId().compareTo(o2.getPaperId()))) {
  511. return iRet;
  512. }
  513. if(0 != (iRet = o1.getSeq().compareTo(o2.getSeq()))) {
  514. return iRet;
  515. }
  516. return o1.getQuestionId().compareTo(o2.getQuestionId());
  517. }
  518. });
  519. Integer[] idx = {1};
  520. pqList.forEach(t -> {
  521. t.setSeq(idx[0]++);
  522. t.setPaperId(null);
  523. t.setDiff(null);
  524. });
  525. }
  526. /**
  527. * 按知识点,题型平均分配组卷
  528. * @param studentId
  529. * @param paperDef
  530. * @return
  531. */
  532. public List<LearnPaperQuestion> getQuestions(Long studentId, TestPaperVO.PaperDef paperDef) {
  533. // 题型分布定义, 知识点列表, 分值定义
  534. // 统计知识点+类型的有效数量 TODO 总量可以缓存
  535. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, paperDef);
  536. assignKnowFirst(paperDef, knowTypeAssignMap); // 知识优先,类型可变
  537. return getQuestions(studentId, paperDef, knowTypeAssignMap);
  538. }
  539. private void assignTypeFirstWithCount(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  540. AtomicLong assignCount = new AtomicLong(0);
  541. Map<Long, Long> knownAdjMap = Maps.newHashMap();
  542. Map<String, Set<Long>> typeKnowIdMap = Maps.newHashMap();
  543. Map<String, Long> typeFillCntMap = Maps.newHashMap();
  544. Map<String, Long> typeMinCntMap = Maps.newHashMap();
  545. // 所有知识点,正常补全一次,哪个知识点差多少先记录下
  546. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  547. Long avgKnowTypeCount = typeDef.getCount().longValue() / paperDef.getKnowIds().size();
  548. if(avgKnowTypeCount.equals(0L)) {
  549. avgKnowTypeCount = 1L;
  550. }
  551. assignCount.set(0);
  552. for(Long knowId : paperDef.getKnowIds()) {
  553. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, avgKnowTypeCount, paperDef.getFillExclude(), assignCount);
  554. if (tmpMinKnowTypeCount > 0) {
  555. // 记录最小数
  556. Long minKnowTypeCount = typeMinCntMap.get(typeDef.getType());
  557. typeMinCntMap.put(typeDef.getType(), null == minKnowTypeCount ? tmpMinKnowTypeCount : Math.min(minKnowTypeCount, tmpMinKnowTypeCount));
  558. // 记录有效知识点
  559. Set<Long> knownIdSet = typeKnowIdMap.get(typeDef.getType());
  560. if(null == knownIdSet) {
  561. typeKnowIdMap.put(typeDef.getType(), Sets.newHashSet(knowId));
  562. } else {
  563. knownIdSet.add(knowId);
  564. }
  565. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  566. // 记录知识点差额
  567. Long lackCount = knownAdjMap.get(knowId);
  568. knownAdjMap.put(knowId, (null != lackCount ? lackCount : 0L) - tmpMinKnowTypeCount);
  569. }
  570. }
  571. typeFillCntMap.put(typeDef.getType(), assignCount.get());
  572. }
  573. knownAdjMap.clear();
  574. // 优先补充差的知识点
  575. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  576. assignCount.set(typeFillCntMap.get(typeDef.getType()));
  577. Long lackTotal = typeDef.getCount().longValue();
  578. Long minCount = typeMinCntMap.get(typeDef.getType());
  579. Set<Long> knownIdSet = typeKnowIdMap.get(typeDef.getType());
  580. do {
  581. if(lackTotal <= assignCount.get()) {
  582. break;
  583. }
  584. Long needCount = lackTotal - assignCount.get();
  585. Long avgKnowTypeCount = knownIdSet.size() > 0 ? Math.min(minCount, needCount / knownIdSet.size()) : 1;
  586. if (avgKnowTypeCount <= 0L) {
  587. avgKnowTypeCount = 1L;
  588. }
  589. minCount = Long.MAX_VALUE;
  590. Set<Long> validIdSet = Sets.newHashSet(knownIdSet);
  591. knownIdSet.clear();
  592. for(Long knowId : paperDef.getKnowIds()) {
  593. if(knownIdSet.size() > 0 && !validIdSet.contains(knowId)) {
  594. continue;
  595. }
  596. // Long lastLack = null; knownAdjMap.remove(knowId); null != lastLack ? lastLack + avgKnowTypeCount :
  597. Long knowTypeCount = avgKnowTypeCount;
  598. if (knowTypeCount > 0) {
  599. // Long oldCnt = assignCount.get();
  600. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  601. // Long fillCnt = oldCnt - assignCount.get();
  602. // if(fillCnt < 0) {
  603. // Long oldFill = knownAdjMap.get(knowId);
  604. // knownAdjMap.put(knowId, null == oldFill ? fillCnt : oldFill - fillCnt);
  605. // }
  606. if (tmpMinKnowTypeCount > 0) {
  607. minCount = Math.min(minCount, tmpMinKnowTypeCount);
  608. knownIdSet.add(knowId);
  609. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  610. // knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  611. }
  612. } else if(knowTypeCount < 0) {
  613. // knownAdjMap.put(knowId, knowTypeCount);
  614. }
  615. if(lackTotal <= assignCount.get()) {
  616. break;
  617. }
  618. }
  619. } while(true);
  620. }
  621. }
  622. private void assignTypeFirstWithCount2(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  623. Map<String, Set<Long>> typeKnownIdsMap = knowTypeAssignMap.values().stream().collect(Collectors.groupingBy(KnowTypeAssign::getType, Collectors.mapping(KnowTypeAssign::getKnowId, Collectors.toSet())));
  624. Map<Long, Long> knownAdjMap = Maps.newHashMap();
  625. AtomicLong assignCount = new AtomicLong(0);
  626. Integer needCount = paperDef.getTypes().size();
  627. Set<String> doneSet = Sets.newHashSet();
  628. do {
  629. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) { // 每个类型,让所有知识先平均补充,补充的由后面填充
  630. Long minKnowTypeCount = Long.MAX_VALUE;
  631. // 首先按平均数量填充
  632. assignCount.set(0L);
  633. Long lackTotal = typeDef.getCount().longValue();
  634. Set<Long> knownIdSet;
  635. if(knownAdjMap.size() > 0) { // 先补充之前的同类型
  636. knownIdSet = Sets.newHashSet();
  637. for(Long knowId : paperDef.getKnowIds()) {
  638. Long lastLack = knownAdjMap.remove(knowId);
  639. Long knowTypeCount = null != lastLack ? lastLack : 0;
  640. if(knowTypeCount >= 0) {
  641. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  642. if (tmpMinKnowTypeCount > 0) {
  643. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  644. knownIdSet.add(knowId);
  645. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  646. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  647. }
  648. } else if(knowTypeCount < 0) {
  649. knownAdjMap.put(knowId, knowTypeCount);
  650. }
  651. }
  652. } else {
  653. knownIdSet = typeKnownIdsMap.get(typeDef.getType());
  654. }
  655. if(lackTotal <= assignCount.get()) {
  656. doneSet.add(typeDef.getType());
  657. continue;
  658. }
  659. Long avgKnowTypeCount = (lackTotal - assignCount.get()) / paperDef.getKnowIds().size();
  660. if (avgKnowTypeCount <= 0L) {
  661. avgKnowTypeCount = 1L;
  662. }
  663. for(Long knowId : paperDef.getKnowIds()) {
  664. Long lastLack = knownAdjMap.remove(knowId);
  665. Long knowTypeCount = null != lastLack ? lastLack + avgKnowTypeCount : avgKnowTypeCount;
  666. if(knowTypeCount > 0) {
  667. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  668. if (tmpMinKnowTypeCount > 0) {
  669. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  670. knownIdSet.add(knowId);
  671. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  672. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  673. }
  674. } else if(knowTypeCount < 0) {
  675. knownAdjMap.put(knowId, knowTypeCount);
  676. }
  677. }
  678. // 然后进行多级补充,多退少补
  679. do {
  680. if(lackTotal <= assignCount.get()) {
  681. doneSet.add(typeDef.getType());
  682. break;
  683. }
  684. avgKnowTypeCount = Math.min(minKnowTypeCount, (lackTotal - assignCount.get()) / knownIdSet.size());
  685. if (avgKnowTypeCount <= 0L) {
  686. avgKnowTypeCount = 1L;
  687. }
  688. minKnowTypeCount = Long.MAX_VALUE;
  689. Set<Long> validIdSet = Sets.newHashSet(knownIdSet);
  690. knownIdSet.clear();
  691. for(Long knowId : paperDef.getKnowIds()) {
  692. if(!validIdSet.contains(knowId)) {
  693. continue;
  694. }
  695. Long lastLack = knownAdjMap.remove(knowId);
  696. Long knowTypeCount = null != lastLack ? lastLack + avgKnowTypeCount : avgKnowTypeCount;
  697. if (knowTypeCount > 0) {
  698. Long oldCnt = assignCount.get();
  699. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  700. Long fillCnt = oldCnt - assignCount.get();
  701. if(fillCnt < 0) {
  702. Long oldFill = knownAdjMap.get(knowId);
  703. knownAdjMap.put(knowId, null == oldFill ? fillCnt : oldFill - fillCnt);
  704. }
  705. if (tmpMinKnowTypeCount > 0) {
  706. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  707. knownIdSet.add(knowId);
  708. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  709. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  710. }
  711. } else if(knowTypeCount < 0) {
  712. knownAdjMap.put(knowId, knowTypeCount);
  713. }
  714. }
  715. } while(true);
  716. }
  717. } while(doneSet.size() < needCount);
  718. }
  719. private void assignTypeFirst(Boolean fillExclude, TestPaperVO.KnowledgeTypeDef2 knowledgeTypeDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  720. Map<String, Set<Long>> typeKnownIdsMap = knowTypeAssignMap.values().stream().collect(Collectors.groupingBy(KnowTypeAssign::getType, Collectors.mapping(KnowTypeAssign::getKnowId, Collectors.toSet())));
  721. AtomicLong assignCount = new AtomicLong(0);
  722. for(TestPaperVO.TypeDef2 typeDef : knowledgeTypeDef.getTypes()) { // 每个类型,让所有知识先平均补充,补充的由后面填充
  723. Long minKnowTypeCount = Long.MAX_VALUE;
  724. Long lackTotal = typeDef.getCount().longValue();
  725. String typeTitle = typeDef.getType().getTitle();
  726. Set<Long> knownIdSet = typeKnownIdsMap.get(typeTitle);
  727. if(CollectionUtils.isEmpty(knownIdSet)) {
  728. log.error("Invalid knowledge type: " + typeTitle);
  729. return;
  730. }
  731. assignCount.set(0L);
  732. do {
  733. Long avgKnowTypeCount = (lackTotal - assignCount.get()) / knownIdSet.size();
  734. if (avgKnowTypeCount <= 0L) {
  735. avgKnowTypeCount = 1L;
  736. }
  737. knownIdSet.clear();
  738. for(Long knowId : knowledgeTypeDef.getKnowledges()) {
  739. if(avgKnowTypeCount > 0) {
  740. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeTitle, knowTypeAssignMap, avgKnowTypeCount, fillExclude, assignCount);
  741. if (tmpMinKnowTypeCount > 0) {
  742. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  743. knownIdSet.add(knowId);
  744. }
  745. if(assignCount.get() >= lackTotal) {
  746. break;
  747. }
  748. }
  749. }
  750. } while(lackTotal != assignCount.get() && knownIdSet.size() > 0);
  751. if(lackTotal < assignCount.get()) {
  752. throw new RuntimeException("题数不足: " + typeTitle + "差" + (lackTotal - assignCount.get()));
  753. }
  754. }
  755. }
  756. private void assignKnowFirst(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  757. // 循环补充未做+已做,如果知识点总数不够时才填充其他知识点的
  758. Long lackTotal = paperDef.getTotal();
  759. AtomicLong assignCount = new AtomicLong(0);
  760. Map<Long, Integer> knowTypesMap = Maps.newHashMap();
  761. int typeCount = paperDef.getTypes().size();
  762. Set<Long> knowSet = knowTypeAssignMap.values().stream().map(KnowTypeAssign::getKnowId).collect(Collectors.toSet());
  763. for (Long knowId : paperDef.getKnowIds()) {
  764. knowTypesMap.put(knowId, knowSet.contains(knowId) ? typeCount : 0);
  765. }
  766. Long minKnowTypeCount = Long.MAX_VALUE;
  767. do {
  768. Integer knowCount = knowSet.size();
  769. knowSet.clear();
  770. for (Long knowId : paperDef.getKnowIds()) {
  771. typeCount = knowTypesMap.get(knowId);
  772. if (0 == typeCount) {
  773. continue;
  774. }
  775. Long avgKnowTypeCount = Math.min(minKnowTypeCount, lackTotal / knowCount / typeCount);
  776. if(avgKnowTypeCount == 0L && lackTotal > 0L) {
  777. avgKnowTypeCount = 1L;
  778. }
  779. typeCount = 0;
  780. for (TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  781. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, avgKnowTypeCount, paperDef.getFillExclude(), assignCount);
  782. if (tmpMinKnowTypeCount > 0L) {
  783. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  784. knowSet.add(knowId);
  785. typeCount++;
  786. }
  787. }
  788. knowTypesMap.put(knowId, typeCount);
  789. }
  790. lackTotal = paperDef.getTotal() - assignCount.get();
  791. if (lackTotal <= 0L || knowSet.isEmpty()) {
  792. break;
  793. }
  794. } while (true);
  795. }
  796. /**
  797. * 根据计划的分配数生成题关系
  798. * @param studentId
  799. * @param paperDef
  800. * @param knowTypeAssignMap
  801. * @return
  802. */
  803. public List<LearnPaperQuestion> getQuestions(Long studentId, TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  804. // 知识点已经分配,准备题型分配
  805. LearnQuestions qCond = new LearnQuestions();
  806. Random random = new Random();
  807. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  808. Set<Long> existQuestionIdSet = Sets.newHashSet();
  809. int total = paperDef.getTotal().intValue();
  810. for (TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  811. for (Long knowId : paperDef.getKnowIds()) {
  812. String key = knowId + "_" + typeDef.getType();
  813. KnowTypeAssign ktc = knowTypeAssignMap.get(key);
  814. if(null == ktc) {
  815. continue;
  816. }
  817. qCond.setKnowledgeId(ktc.getKnowId());
  818. qCond.setQtpye(ktc.getType());
  819. qCond.setIsSubType("0");
  820. QuestionType qt = QuestionType.of(ktc.getType());
  821. if(ktc.exclAssign > 0){
  822. qCond.setId(studentId);
  823. qCond.setNumber(ktc.exclAssign > 500 ? (long) random.nextInt(ktc.exclAssign.intValue() - 500) : 0L);
  824. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  825. ktc.exclAssign = addRandomList(knowId, qt, questions, random, paperDef.getTotal(), ktc.exclAssign, typeDef.getScore().doubleValue(), existQuestionIdSet, 1, pqList);
  826. if(pqList.size() == total) {
  827. break;
  828. }
  829. }
  830. if(ktc.assign > 0L) {
  831. qCond.setId(null);
  832. qCond.setNumber(ktc.assign > 500 ? (long) random.nextInt(ktc.assign.intValue() - 500) : 0L);
  833. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  834. ktc.assign = addRandomList(knowId, qt, questions, random, paperDef.getTotal(), ktc.assign, typeDef.getScore().doubleValue(), existQuestionIdSet, 1, pqList);
  835. if(pqList.size() == total) {
  836. break;
  837. }
  838. }
  839. }
  840. if(pqList.size() == total) {
  841. break;
  842. }
  843. }
  844. if(CollectionUtils.isEmpty(pqList)) {
  845. throw new RuntimeException("题数不足");
  846. }
  847. reSort(pqList);
  848. return pqList;
  849. }
  850. public List<LearnPaperQuestion> getQuestions2(Long studentId, Integer total, Integer seqId, Collection<Long> knownIds, List<TestPaperVO.TypeDef2> types, Map<String, KnowTypeAssign> knowTypeAssignMap, Set<Long> existQuestionIdSet) {
  851. // 知识点已经分配,准备题型分配
  852. LearnQuestions qCond = new LearnQuestions();
  853. Random random = new Random();
  854. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  855. for (TestPaperVO.TypeDef2 typeDef : types) {
  856. String typeTitle = typeDef.getType().getTitle();
  857. for (Long knowId : knownIds) {
  858. String key = knowId + "_" + typeTitle;
  859. KnowTypeAssign ktc = knowTypeAssignMap.get(key);
  860. if(null == ktc) {
  861. continue;
  862. }
  863. qCond.setKnowledgeId(ktc.getKnowId());
  864. qCond.setQtpye(typeTitle);
  865. qCond.setIsSubType("0");
  866. if(ktc.exclAssign > 0){
  867. qCond.setId(studentId);
  868. qCond.setNumber(ktc.exclAssign > 500 ? (long) random.nextInt(ktc.exclAssign.intValue() - 500) : 0L);
  869. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  870. ktc.exclAssign = addRandomList(knowId, typeDef.getType(), questions, random, total.longValue(), ktc.exclAssign, typeDef.getScore(), existQuestionIdSet, seqId, pqList);
  871. if(pqList.size() == total) {
  872. break;
  873. }
  874. }
  875. if(ktc.assign > 0L) {
  876. qCond.setId(null);
  877. qCond.setNumber(ktc.assign > 500 ? (long) random.nextInt(ktc.assign.intValue() - 500) : 0L);
  878. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  879. ktc.assign = addRandomList(knowId, typeDef.getType(), questions, random, total.longValue(), ktc.assign, typeDef.getScore(), existQuestionIdSet, seqId, pqList);
  880. if(pqList.size() == total) {
  881. break;
  882. }
  883. }
  884. }
  885. if(pqList.size() == total) {
  886. break;
  887. }
  888. }
  889. if(pqList.size() < total) {
  890. throw new RuntimeException("题数不足 " + types.stream().map( t -> t.getType().getTitle()).collect(Collectors.joining(",")) + ":" + StringUtils.join(knownIds, ","));
  891. }
  892. return pqList;
  893. }
  894. /**
  895. * 初始化当前用户卷情况
  896. * @param studentId
  897. * @param paperDef
  898. * @return
  899. */
  900. private Map<String, KnowTypeAssign> buildKnowTypeAssignMap(Long studentId, TestPaperVO.PaperDef paperDef) {
  901. return buildKnowTypeAssignMap(studentId, "0", paperDef.getTypes().stream().map(TestPaperVO.TypeDef::getType).collect(Collectors.toList()), paperDef.getKnowIds(), paperDef.getFillExclude());
  902. }
  903. /**
  904. * 初始化当前用户知识点的情况
  905. * @param studentId
  906. * @param types
  907. * @param knownIds
  908. * @param fillExclude
  909. * @return
  910. */
  911. private Map<String, KnowTypeAssign> buildKnowTypeAssignMap(Long studentId, String isSubType, List<String> types, Collection<Long> knownIds, Boolean fillExclude) {
  912. Map<String, KnowTypeAssign> knowTypeAssignMap = Maps.newHashMap();
  913. Map cond = Maps.newHashMap();
  914. cond.put("studentId", studentId);
  915. cond.put("knowIds", knownIds);
  916. cond.put("types", types);
  917. cond.put("isSubType", isSubType);
  918. setValue(knowTypeAssignMap, cond, 1); // 填充排除后总量
  919. if (null != studentId && fillExclude) {
  920. cond.remove("studentId");
  921. setValue(knowTypeAssignMap, cond, 2); // 按需填充已做总量
  922. }
  923. return knowTypeAssignMap;
  924. }
  925. /**
  926. * 随机从 knowId 题池中提取需要个数的题
  927. * @param knowId 知识点
  928. * @param type 类型
  929. * @param questions 题池
  930. * @param random 随机数
  931. * @param totalCount 总题数
  932. * @param count 本池分配数
  933. * @param score 题分
  934. * @param existQuestionIdSet 不能使用的题
  935. * @param pqList 卷题关系 diff(type),paperId(parentId),seq,questionId(id)
  936. */
  937. private Long addRandomList(Long knowId, QuestionType type, List<LearnQuestions> questions, Random random, Long totalCount, Long count, Double score, Set<Long> existQuestionIdSet, Integer baseSeq, List<LearnPaperQuestion> pqList) {
  938. while(count > 0L && !questions.isEmpty()) {
  939. LearnQuestions q = questions.size() > 1 ? questions.remove(random.nextInt(questions.size() - 1)) : questions.remove(0);
  940. if(existQuestionIdSet.add(q.getId())) {
  941. if("1".equals(q.getIsSubType())) {
  942. LearnQuestions subCond = new LearnQuestions();
  943. subCond.setKnowId(q.getId());
  944. for(LearnQuestions sq : questionsMapper.selectLearnQuestionsList(subCond)) {
  945. LearnPaperQuestion pq = new LearnPaperQuestion();
  946. pq.setSeq(baseSeq + pqList.size());
  947. pq.setKnowledgeId(knowId);
  948. pq.setScore(score);
  949. pq.setQuestionId(sq.getId());
  950. pq.setType(type.getTitle());
  951. pq.setDiff(type.getVal());
  952. pq.setPaperId(q.getId());
  953. pqList.add(pq);
  954. count--;
  955. }
  956. } else {
  957. LearnPaperQuestion pq = new LearnPaperQuestion();
  958. pq.setSeq(baseSeq + pqList.size());
  959. pq.setKnowledgeId(knowId);
  960. pq.setScore(score);
  961. pq.setQuestionId(q.getId());
  962. pq.setType(type.getTitle());
  963. pq.setDiff(type.getVal());
  964. pq.setPaperId(0L);
  965. pqList.add(pq);
  966. count--;
  967. }
  968. if(pqList.size() >= totalCount) {
  969. break;
  970. }
  971. }
  972. }
  973. return count;
  974. }
  975. /**
  976. * 给指定类型分配
  977. * @param knowId
  978. * @param qtype
  979. * @param knowTypeAssignMap
  980. * @param knowTypeCount
  981. * @param fillExclude
  982. * @param assignCount
  983. */
  984. private Long assignKnownCount(Long knowId, String qtype, Map<String, KnowTypeAssign> knowTypeAssignMap, Long knowTypeCount, Boolean fillExclude,
  985. AtomicLong assignCount) {
  986. String key = knowId + "_" + qtype;
  987. KnowTypeAssign knowTypeAssign = knowTypeAssignMap.get(key);
  988. long lackCount;
  989. if (null != knowTypeAssign && knowTypeAssign.exclCount > 0) {
  990. lackCount = knowTypeCount - knowTypeAssign.exclCount;
  991. if (lackCount <= 0) { // 足量
  992. assignCount.getAndAdd(knowTypeCount);
  993. knowTypeAssign.exclCount -= knowTypeCount;
  994. knowTypeAssign.exclAssign += knowTypeCount;
  995. } else { // 不足时且还有时全转
  996. assignCount.getAndAdd(knowTypeAssign.exclCount);
  997. knowTypeAssign.exclAssign += knowTypeAssign.exclCount;
  998. knowTypeAssign.exclCount = 0L;
  999. }
  1000. } else {
  1001. lackCount = knowTypeCount;
  1002. }
  1003. long lack = 0;
  1004. if (lackCount > 0) { // 差额优先补充已做过的
  1005. if(fillExclude && null != knowTypeAssign && knowTypeAssign.total > 0) {
  1006. lack = lackCount - knowTypeAssign.total;
  1007. if (lack <= 0) { // 足量
  1008. assignCount.getAndAdd(lackCount);
  1009. knowTypeAssign.total -= lackCount;
  1010. knowTypeAssign.assign += lackCount;
  1011. } else { // 不足时全转
  1012. assignCount.getAndAdd(knowTypeAssign.total);
  1013. knowTypeAssign.assign += knowTypeAssign.total;
  1014. knowTypeAssign.total = 0L;
  1015. }
  1016. } else {
  1017. lack = lackCount;
  1018. }
  1019. }
  1020. if(lackCount < 0) {
  1021. return -lackCount;
  1022. } else if(lack < 0) {
  1023. return -lack;
  1024. }
  1025. return -lack;
  1026. }
  1027. /**
  1028. * 合并 未用总量和已有总量, 分配数置0
  1029. * @param knowTypeAssignMap key=knowId +"_" + qtype
  1030. * @param cond
  1031. * @param index 1 free 2 used
  1032. */
  1033. private void setValue(Map<String, KnowTypeAssign> knowTypeAssignMap, Map cond, Integer index) {
  1034. for (LearnQuestions q : questionsMapper.statByKnowledgeType(cond)) {
  1035. String key = q.getKnowledgeId() + "_" + q.getQtpye();
  1036. KnowTypeAssign knowTypeAssign = knowTypeAssignMap.get(key);
  1037. if (null == knowTypeAssign) {
  1038. knowTypeAssign = new KnowTypeAssign();
  1039. knowTypeAssign.setKnowId(q.getKnowledgeId());
  1040. knowTypeAssign.setType(q.getQtpye());
  1041. knowTypeAssign.total = 0L;
  1042. knowTypeAssign.exclAssign = 0L;
  1043. knowTypeAssign.assign = 0L;
  1044. knowTypeAssign.exclCount = 0L;
  1045. knowTypeAssignMap.put(key, knowTypeAssign);
  1046. }
  1047. if (1 == index) {
  1048. knowTypeAssign.exclCount = q.getNumber();
  1049. } else {
  1050. knowTypeAssign.total = q.getNumber() - knowTypeAssign.exclCount;
  1051. }
  1052. }
  1053. }
  1054. @Data
  1055. public static class KnowTypeAssign {
  1056. Long knowId; // 知识点
  1057. String type; // 题类型
  1058. Long exclAssign; // 未用分配数
  1059. Long assign; // 已用分配数
  1060. Long exclCount; // 未用总量
  1061. Long total; // 已用总量
  1062. }
  1063. }