PaperService.java 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  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. if (!chooseTypes.contains(qt)) {
  263. qs.setOptions(Lists.newArrayList("会", "不会"));
  264. } else {
  265. qs.setOptions(StringUtils.getOptions(lqs.getOptionA(), lqs.getOptionB(), lqs.getOptionC(), lqs.getOptionD(), lqs.getOptionE(), lqs.getOptionF(), lqs.getOptionG()));
  266. }
  267. if(StringUtils.isNotBlank(lqs.getTitle0())) { // 大题
  268. PaperVO.QuestionSeq qg = gropuMap.get(lqs.getTitle0());
  269. if(qg == null) {
  270. qg = new PaperVO.QuestionSeq();
  271. qg.setTypeId(99);
  272. qg.setSubQuestions(Lists.newArrayList());
  273. qg.setTitle(lqs.getTitle0());
  274. gropuMap.put(lqs.getTitle0(), qg);
  275. paperQuestionList.add(qg);
  276. }
  277. qg.getSubQuestions().add(qs);
  278. } else {
  279. paperQuestionList.add(qs);
  280. }
  281. }
  282. return paperQuestionList;
  283. }
  284. public List<PaperVO.QuestionAnswer> loadPaperQuestionAnswers(Long userId, Long paperId, Map<Long, LearnAnswer> answerMap, boolean withParse) {
  285. List<LearnQuestions> questions = questionsMapper.selectQuestionByPaperId(paperId);
  286. learnQuestionsService.fillCollectInfo(userId, questions);
  287. Map<String, PaperVO.QuestionAnswer> gropuMap = Maps.newHashMap();
  288. List<PaperVO.QuestionAnswer> paperQuestionList = Lists.newArrayList();
  289. LearnAnswer answer;
  290. for(LearnQuestions lqs : questions) {
  291. PaperVO.QuestionAnswer qs = new PaperVO.QuestionAnswer();
  292. QuestionType qt = QuestionType.of(lqs.getQtpye());
  293. if(withParse || !chooseTypes.contains(qt)) {
  294. BeanUtils.copyProperties(lqs, qs, "title", "options");
  295. } else {
  296. BeanUtils.copyProperties(lqs, qs, "title", "options", "parse", "answer1", "answer2");
  297. }
  298. qs.setTotalScore(lqs.getScore());
  299. qs.setScore(null);
  300. qs.setTypeId(qt.getVal());
  301. qs.setIsFavorite(lqs.isCollect());
  302. if(null != answerMap && null != (answer = answerMap.get(lqs.getId()))) {
  303. qs.setAnswerId(answer.getAnswerId());
  304. qs.setAnswers(Arrays.asList(answer.getAnswer().split(",")));
  305. qs.setState(answer.getState());
  306. qs.setIsMark(answer.getMark());
  307. qs.setIsNotKnow(answer.getNotKnow());
  308. qs.setScore(answer.getScore());
  309. }
  310. if(StringUtils.isNotBlank(lqs.getTitle0())) { // 大题
  311. PaperVO.QuestionAnswer qg = gropuMap.get(lqs.getTitle0());
  312. if(qg == null) {
  313. qg = new PaperVO.QuestionAnswer();
  314. qg.setTypeId(99);
  315. qg.setSubQuestions(Lists.newArrayList());
  316. qg.setTitle(lqs.getTitle0());
  317. gropuMap.put(lqs.getTitle0(), qg);
  318. paperQuestionList.add(qg);
  319. }
  320. qg.getSubQuestions().add(qs);
  321. } else {
  322. paperQuestionList.add(qs);
  323. }
  324. }
  325. return paperQuestionList;
  326. }
  327. public int buildSimulatedPaperForUniversity(Long universityId, Long subjectId, Long directedId, Integer seq) {
  328. LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
  329. dkCond.setUniversityId(universityId);
  330. List<LearnDirectedKnowledge> directedKnowledgeList = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond);
  331. BBusiWishUniversities universities = wishUniversitiesService.selectBBusiWishUniversitiesById(universityId);
  332. for(LearnDirectedKnowledge dk : directedKnowledgeList) {
  333. if(null == directedId || directedId.equals(dk.getId())) {
  334. buildSimulatedPaperForKnowledge(subjectId, seq, universities, dk);
  335. }
  336. }
  337. return 0;
  338. }
  339. /**
  340. * 根据院校专业要求生成模拟试卷
  341. * @return
  342. */
  343. public int buildSimulatedPaperForKnowledge(Long subjectId, Integer seq, BBusiWishUniversities universities, LearnDirectedKnowledge dk) {
  344. if(StringUtils.isBlank(dk.getConditions())) {
  345. return 0;
  346. }
  347. TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2(dk.getKnowledges(), dk.getConditions());
  348. paperDef.setFillExclude(false);
  349. LearnPaper paper = new LearnPaper();
  350. paper.setSubjectId(subjectId);
  351. paper.setPaperType(PaperType.Simulated.name());
  352. paper.setRelateId(dk.getId()); // 定向ID
  353. paper.setYear(dk.getYear());
  354. paper.setPaperSource(seq);
  355. paper.setStatus(1);
  356. if(CollectionUtils.isNotEmpty(paperMapper.selectLearnPaperList(paper))) {
  357. log.warn("已经生成: {}:{}", dk.getId(), seq);
  358. return 0;
  359. }
  360. paper.setPaperName(StringUtils.isNotBlank(dk.getDirectKey()) ? universities.getName() + "(" + dk.getDirectKey() + ")" : universities.getName());
  361. paper.setDirectKey(universities.getId() + "_" + dk.getExamineeTypes() + "_" + dk.getDirectKey());
  362. paper.setStatus(PaperStatus.Valid.getVal());
  363. paper.setNumber(paperDef.getTotal());
  364. paper.setFenshu(paperDef.getScore().intValue());
  365. AnswerSheet.PaperCond info = new AnswerSheet.PaperCond();
  366. info.setScore(paper.getFenshu());
  367. info.setTime(dk.getTime() * 60);
  368. info.setTypes(paperDef.getTypes().stream().map(t -> new AnswerSheet.PaperCondType(t.getType().getTitle(), t.getCount(), t.getScore())).collect(Collectors.toList()));
  369. paper.setPaperInfo(JSONObject.toJSONString(info));
  370. try {
  371. Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = buildPaper2(null, paper, paperDef);
  372. savePaper(paperResult.getKey(), paperResult.getValue());
  373. return 0;
  374. } catch(Exception e) {
  375. log.error(e.getMessage());
  376. }
  377. return 0;
  378. }
  379. /**
  380. * 原版本,新的是2
  381. * @param directedKnowledge
  382. * @return
  383. */
  384. public Pair<LearnPaper, List<LearnPaperQuestion>> buildSimulatedPaper(LearnDirectedKnowledge directedKnowledge) {
  385. LearnPaper paper = new LearnPaper();
  386. paper.setPaperType(PaperType.Simulated.name());
  387. paper.setRelateId(directedKnowledge.getId()); // 定向ID
  388. paper.setYear(directedKnowledge.getYear());
  389. paper.setStatus(PaperStatus.Valid.getVal());
  390. paper.setDirectKey(directedKnowledge.getDirectKey());
  391. TestPaperVO.PaperDef paperDef = JSONObject.parseObject(directedKnowledge.getConditions(), TestPaperVO.PaperDef.class);
  392. paperDef.setKnowIds(Stream.of(directedKnowledge.getKnowledges().split(",")).map(Long::valueOf).collect(Collectors.toList()));
  393. paperDef.setTypes(JSONArray.parseArray(directedKnowledge.getQuestionTypes(), TestPaperVO.TypeDef.class));
  394. return buildPaper(null, paper, paperDef);
  395. }
  396. public Pair<LearnPaper, List<LearnPaperQuestion>> buildPaper2(Long studentId, LearnPaper paper, TestPaperVO.PaperDef2 paperDef) {
  397. paperDef.setFillExclude(null != studentId);
  398. List<LearnPaperQuestion> pqList = getQuestions2(studentId, paperDef);
  399. return Pair.of(paper, pqList);
  400. }
  401. /**
  402. * 根据试卷定义生成试卷
  403. * @param studentId
  404. * @param paper
  405. * @param paperDef
  406. * @return
  407. */
  408. public Pair<LearnPaper, List<LearnPaperQuestion>> buildPaper(Long studentId, LearnPaper paper, TestPaperVO.PaperDef paperDef) {
  409. if(null == studentId){
  410. paperDef.setFillExclude(false);
  411. }
  412. List<LearnPaperQuestion> pqList = getQuestions(studentId, paperDef);
  413. return Pair.of(paper, pqList);
  414. }
  415. /**
  416. * 保存试卷
  417. * @param paper
  418. * @param pqList
  419. * @return
  420. */
  421. public LearnPaper savePaper(LearnPaper paper, List<LearnPaperQuestion> pqList) {
  422. paper.setNumber(pqList.size());
  423. paperMapper.insertLearnPaper(paper);
  424. Long paperId = paper.getId();
  425. pqList.stream().forEach(t -> t.setPaperId(paperId));
  426. paperQuestionMapper.batchInsert(pqList);
  427. return paper;
  428. }
  429. /**
  430. * 按类型,知识点平均分配组卷
  431. * @param studentId
  432. * @param paperDef
  433. * @return
  434. */
  435. public List<LearnPaperQuestion> getQuestionsByType(Long studentId, TestPaperVO.PaperDef paperDef) {
  436. // 统计知识点+类型的有效数量 TODO 总量可以缓存
  437. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, paperDef);
  438. assignTypeFirstWithCount(paperDef, knowTypeAssignMap); // 知识优先,类型可变
  439. return getQuestions(studentId, paperDef, knowTypeAssignMap);
  440. }
  441. public List<LearnPaperQuestion> getQuestions2(Long studentId, TestPaperVO.PaperDef2 paperDef) {
  442. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  443. Set<Long> existQuestionIdSet = Sets.newHashSet();
  444. for(TestPaperVO.KnowledgeTypeDef2 ktd : paperDef.getKnowTypes()) {
  445. Map<Long, List<Long>> ktSubMap = Maps.newHashMap();
  446. Integer maxSubCount = 6;
  447. List<Long> newKnownList = Lists.newArrayList();
  448. List<Long> tailKnownList = Lists.newArrayList();
  449. List<LearnKnowledgeTree> ktList = learnKnowledgeTreeMapper.selectLearnKnowledgeTreeByParentIds(ktd.getKnowledges());
  450. for(LearnKnowledgeTree kt : ktList) {
  451. List<Long> subIdList = ktSubMap.computeIfAbsent(kt.getPid(), k -> Lists.newArrayList());
  452. subIdList.add(kt.getId());
  453. }
  454. for(Long knownId : ktd.getKnowledges()) {
  455. List<Long> subList = ktSubMap.get(knownId);
  456. if(null == subList) {
  457. newKnownList.add(knownId);
  458. } else if(subList.size() > maxSubCount) {
  459. newKnownList.addAll(subList.subList(0, maxSubCount));
  460. tailKnownList.addAll(subList.subList(maxSubCount, subList.size()));
  461. } else {
  462. newKnownList.addAll(subList);
  463. }
  464. }
  465. newKnownList.addAll(tailKnownList);
  466. ktd.setKnowledges(newKnownList);
  467. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, "0", ktd.getTypes().stream().map(t -> t.getType().getTitle()).collect(Collectors.toList()), newKnownList, paperDef.getFillExclude());
  468. assignTypeFirst(paperDef.getFillExclude(), ktd, knowTypeAssignMap);
  469. pqList.addAll(getQuestions2(studentId, ktd.getCount(), pqList.size(), newKnownList, ktd.getTypes(), knowTypeAssignMap, existQuestionIdSet));
  470. }
  471. reSort(pqList);
  472. return pqList;
  473. }
  474. public List<LearnPaperQuestion> getQuestionsByRandom(Long studentId, Integer total, Collection<Long> knowledgeIds, List<String> types) {
  475. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, "1", types, knowledgeIds, false);
  476. List<KnowTypeAssign> knowTypeAssignList = Lists.newArrayList(knowTypeAssignMap.values());
  477. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  478. Set<Long> existQuestionIdSet = Sets.newHashSet();
  479. Random random = new Random();
  480. Map<String, List<LearnQuestions>> typeQuestionMap = Maps.newHashMap();
  481. LearnQuestions qCond = new LearnQuestions();
  482. do {
  483. if(knowTypeAssignList.isEmpty()) {
  484. break;
  485. }
  486. int idx = random.nextInt(knowTypeAssignList.size());
  487. KnowTypeAssign knowTypeAssign = knowTypeAssignList.get(idx);
  488. if(knowTypeAssign.getExclCount() > knowTypeAssign.getExclAssign()) {
  489. List<LearnQuestions> questions = typeQuestionMap.get(knowTypeAssign.getType());
  490. if(null == questions) {
  491. qCond.setKnowledgeId(knowTypeAssign.getKnowId());
  492. qCond.setQtpye(knowTypeAssign.getType());
  493. qCond.setId(studentId);
  494. qCond.setNumber(knowTypeAssign.exclCount > 500 ? (long) random.nextInt(knowTypeAssign.exclCount.intValue() - 500) : 0L);
  495. qCond.setIsSubType("1");
  496. questions = questionsMapper.selectQuestionsForPaper(qCond);
  497. typeQuestionMap.put(knowTypeAssign.getType(), questions);
  498. }
  499. if(!questions.isEmpty()) {
  500. int oldSize = pqList.size();
  501. addRandomList(knowTypeAssign.getKnowId(), QuestionType.of(knowTypeAssign.getType()), questions, random, total.longValue(), 1L, 1.0, existQuestionIdSet, 1, pqList);
  502. if(oldSize != pqList.size()) {
  503. knowTypeAssign.exclAssign++;
  504. }
  505. }
  506. } else {
  507. knowTypeAssignList.remove(idx);
  508. }
  509. } while(pqList.size() < total);
  510. reSort(pqList);
  511. return pqList;
  512. }
  513. /**
  514. * // diff(type), paperId(parentId), seq(type), questionId(id)
  515. * @param pqList
  516. */
  517. private void reSort(List<LearnPaperQuestion> pqList) {
  518. Collections.sort(pqList, new Comparator<LearnPaperQuestion>() {
  519. @Override
  520. public int compare(LearnPaperQuestion o1, LearnPaperQuestion o2) {
  521. int iRet;
  522. if(0 != (iRet = o1.getDiff().compareTo(o2.getDiff()))) {
  523. return iRet;
  524. }
  525. if(0 != (iRet = o1.getPaperId().compareTo(o2.getPaperId()))) {
  526. return iRet;
  527. }
  528. if(0 != (iRet = o1.getSeq().compareTo(o2.getSeq()))) {
  529. return iRet;
  530. }
  531. return o1.getQuestionId().compareTo(o2.getQuestionId());
  532. }
  533. });
  534. Integer[] idx = {1};
  535. pqList.forEach(t -> {
  536. t.setSeq(idx[0]++);
  537. t.setPaperId(null);
  538. t.setDiff(null);
  539. });
  540. }
  541. /**
  542. * 按知识点,题型平均分配组卷
  543. * @param studentId
  544. * @param paperDef
  545. * @return
  546. */
  547. public List<LearnPaperQuestion> getQuestions(Long studentId, TestPaperVO.PaperDef paperDef) {
  548. // 题型分布定义, 知识点列表, 分值定义
  549. // 统计知识点+类型的有效数量 TODO 总量可以缓存
  550. Map<String, KnowTypeAssign> knowTypeAssignMap = buildKnowTypeAssignMap(studentId, paperDef);
  551. assignKnowFirst(paperDef, knowTypeAssignMap); // 知识优先,类型可变
  552. return getQuestions(studentId, paperDef, knowTypeAssignMap);
  553. }
  554. private void assignTypeFirstWithCount(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  555. AtomicLong assignCount = new AtomicLong(0);
  556. Map<Long, Long> knownAdjMap = Maps.newHashMap();
  557. Map<String, Set<Long>> typeKnowIdMap = Maps.newHashMap();
  558. Map<String, Long> typeFillCntMap = Maps.newHashMap();
  559. Map<String, Long> typeMinCntMap = Maps.newHashMap();
  560. // 所有知识点,正常补全一次,哪个知识点差多少先记录下
  561. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  562. Long avgKnowTypeCount = typeDef.getCount().longValue() / paperDef.getKnowIds().size();
  563. if(avgKnowTypeCount.equals(0L)) {
  564. avgKnowTypeCount = 1L;
  565. }
  566. assignCount.set(0);
  567. for(Long knowId : paperDef.getKnowIds()) {
  568. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, avgKnowTypeCount, paperDef.getFillExclude(), assignCount);
  569. if (tmpMinKnowTypeCount > 0) {
  570. // 记录最小数
  571. Long minKnowTypeCount = typeMinCntMap.get(typeDef.getType());
  572. typeMinCntMap.put(typeDef.getType(), null == minKnowTypeCount ? tmpMinKnowTypeCount : Math.min(minKnowTypeCount, tmpMinKnowTypeCount));
  573. // 记录有效知识点
  574. Set<Long> knownIdSet = typeKnowIdMap.get(typeDef.getType());
  575. if(null == knownIdSet) {
  576. typeKnowIdMap.put(typeDef.getType(), Sets.newHashSet(knowId));
  577. } else {
  578. knownIdSet.add(knowId);
  579. }
  580. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  581. // 记录知识点差额
  582. Long lackCount = knownAdjMap.get(knowId);
  583. knownAdjMap.put(knowId, (null != lackCount ? lackCount : 0L) - tmpMinKnowTypeCount);
  584. }
  585. }
  586. typeFillCntMap.put(typeDef.getType(), assignCount.get());
  587. }
  588. knownAdjMap.clear();
  589. // 优先补充差的知识点
  590. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  591. assignCount.set(typeFillCntMap.get(typeDef.getType()));
  592. Long lackTotal = typeDef.getCount().longValue();
  593. Long minCount = typeMinCntMap.get(typeDef.getType());
  594. Set<Long> knownIdSet = typeKnowIdMap.get(typeDef.getType());
  595. do {
  596. if(lackTotal <= assignCount.get()) {
  597. break;
  598. }
  599. Long needCount = lackTotal - assignCount.get();
  600. Long avgKnowTypeCount = knownIdSet.size() > 0 ? Math.min(minCount, needCount / knownIdSet.size()) : 1;
  601. if (avgKnowTypeCount <= 0L) {
  602. avgKnowTypeCount = 1L;
  603. }
  604. minCount = Long.MAX_VALUE;
  605. Set<Long> validIdSet = Sets.newHashSet(knownIdSet);
  606. knownIdSet.clear();
  607. for(Long knowId : paperDef.getKnowIds()) {
  608. if(knownIdSet.size() > 0 && !validIdSet.contains(knowId)) {
  609. continue;
  610. }
  611. // Long lastLack = null; knownAdjMap.remove(knowId); null != lastLack ? lastLack + avgKnowTypeCount :
  612. Long knowTypeCount = avgKnowTypeCount;
  613. if (knowTypeCount > 0) {
  614. // Long oldCnt = assignCount.get();
  615. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  616. // Long fillCnt = oldCnt - assignCount.get();
  617. // if(fillCnt < 0) {
  618. // Long oldFill = knownAdjMap.get(knowId);
  619. // knownAdjMap.put(knowId, null == oldFill ? fillCnt : oldFill - fillCnt);
  620. // }
  621. if (tmpMinKnowTypeCount > 0) {
  622. minCount = Math.min(minCount, tmpMinKnowTypeCount);
  623. knownIdSet.add(knowId);
  624. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  625. // knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  626. }
  627. } else if(knowTypeCount < 0) {
  628. // knownAdjMap.put(knowId, knowTypeCount);
  629. }
  630. if(lackTotal <= assignCount.get()) {
  631. break;
  632. }
  633. }
  634. } while(true);
  635. }
  636. }
  637. private void assignTypeFirstWithCount2(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  638. Map<String, Set<Long>> typeKnownIdsMap = knowTypeAssignMap.values().stream().collect(Collectors.groupingBy(KnowTypeAssign::getType, Collectors.mapping(KnowTypeAssign::getKnowId, Collectors.toSet())));
  639. Map<Long, Long> knownAdjMap = Maps.newHashMap();
  640. AtomicLong assignCount = new AtomicLong(0);
  641. Integer needCount = paperDef.getTypes().size();
  642. Set<String> doneSet = Sets.newHashSet();
  643. do {
  644. for(TestPaperVO.TypeDef typeDef : paperDef.getTypes()) { // 每个类型,让所有知识先平均补充,补充的由后面填充
  645. Long minKnowTypeCount = Long.MAX_VALUE;
  646. // 首先按平均数量填充
  647. assignCount.set(0L);
  648. Long lackTotal = typeDef.getCount().longValue();
  649. Set<Long> knownIdSet;
  650. if(knownAdjMap.size() > 0) { // 先补充之前的同类型
  651. knownIdSet = Sets.newHashSet();
  652. for(Long knowId : paperDef.getKnowIds()) {
  653. Long lastLack = knownAdjMap.remove(knowId);
  654. Long knowTypeCount = null != lastLack ? lastLack : 0;
  655. if(knowTypeCount >= 0) {
  656. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  657. if (tmpMinKnowTypeCount > 0) {
  658. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  659. knownIdSet.add(knowId);
  660. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  661. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  662. }
  663. } else if(knowTypeCount < 0) {
  664. knownAdjMap.put(knowId, knowTypeCount);
  665. }
  666. }
  667. } else {
  668. knownIdSet = typeKnownIdsMap.get(typeDef.getType());
  669. }
  670. if(lackTotal <= assignCount.get()) {
  671. doneSet.add(typeDef.getType());
  672. continue;
  673. }
  674. Long avgKnowTypeCount = (lackTotal - assignCount.get()) / paperDef.getKnowIds().size();
  675. if (avgKnowTypeCount <= 0L) {
  676. avgKnowTypeCount = 1L;
  677. }
  678. for(Long knowId : paperDef.getKnowIds()) {
  679. Long lastLack = knownAdjMap.remove(knowId);
  680. Long knowTypeCount = null != lastLack ? lastLack + avgKnowTypeCount : avgKnowTypeCount;
  681. if(knowTypeCount > 0) {
  682. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  683. if (tmpMinKnowTypeCount > 0) {
  684. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  685. knownIdSet.add(knowId);
  686. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  687. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  688. }
  689. } else if(knowTypeCount < 0) {
  690. knownAdjMap.put(knowId, knowTypeCount);
  691. }
  692. }
  693. // 然后进行多级补充,多退少补
  694. do {
  695. if(lackTotal <= assignCount.get()) {
  696. doneSet.add(typeDef.getType());
  697. break;
  698. }
  699. avgKnowTypeCount = Math.min(minKnowTypeCount, (lackTotal - assignCount.get()) / knownIdSet.size());
  700. if (avgKnowTypeCount <= 0L) {
  701. avgKnowTypeCount = 1L;
  702. }
  703. minKnowTypeCount = Long.MAX_VALUE;
  704. Set<Long> validIdSet = Sets.newHashSet(knownIdSet);
  705. knownIdSet.clear();
  706. for(Long knowId : paperDef.getKnowIds()) {
  707. if(!validIdSet.contains(knowId)) {
  708. continue;
  709. }
  710. Long lastLack = knownAdjMap.remove(knowId);
  711. Long knowTypeCount = null != lastLack ? lastLack + avgKnowTypeCount : avgKnowTypeCount;
  712. if (knowTypeCount > 0) {
  713. Long oldCnt = assignCount.get();
  714. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, knowTypeCount, paperDef.getFillExclude(), assignCount);
  715. Long fillCnt = oldCnt - assignCount.get();
  716. if(fillCnt < 0) {
  717. Long oldFill = knownAdjMap.get(knowId);
  718. knownAdjMap.put(knowId, null == oldFill ? fillCnt : oldFill - fillCnt);
  719. }
  720. if (tmpMinKnowTypeCount > 0) {
  721. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  722. knownIdSet.add(knowId);
  723. } else if(tmpMinKnowTypeCount < 0) { // 差的个数移到下一类型,补充的类型也移到下一类型
  724. knownAdjMap.put(knowId, -tmpMinKnowTypeCount);
  725. }
  726. } else if(knowTypeCount < 0) {
  727. knownAdjMap.put(knowId, knowTypeCount);
  728. }
  729. }
  730. } while(true);
  731. }
  732. } while(doneSet.size() < needCount);
  733. }
  734. private void assignTypeFirst(Boolean fillExclude, TestPaperVO.KnowledgeTypeDef2 knowledgeTypeDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  735. Map<String, Set<Long>> typeKnownIdsMap = knowTypeAssignMap.values().stream().collect(Collectors.groupingBy(KnowTypeAssign::getType, Collectors.mapping(KnowTypeAssign::getKnowId, Collectors.toSet())));
  736. AtomicLong assignCount = new AtomicLong(0);
  737. for(TestPaperVO.TypeDef2 typeDef : knowledgeTypeDef.getTypes()) { // 每个类型,让所有知识先平均补充,补充的由后面填充
  738. Long minKnowTypeCount = Long.MAX_VALUE;
  739. Long lackTotal = typeDef.getCount().longValue();
  740. String typeTitle = typeDef.getType().getTitle();
  741. Set<Long> knownIdSet = typeKnownIdsMap.get(typeTitle);
  742. if(CollectionUtils.isEmpty(knownIdSet)) {
  743. log.error("Invalid knowledge type: " + typeTitle);
  744. return;
  745. }
  746. assignCount.set(0L);
  747. do {
  748. Long avgKnowTypeCount = (lackTotal - assignCount.get()) / knownIdSet.size();
  749. if (avgKnowTypeCount <= 0L) {
  750. avgKnowTypeCount = 1L;
  751. }
  752. knownIdSet.clear();
  753. for(Long knowId : knowledgeTypeDef.getKnowledges()) {
  754. if(avgKnowTypeCount > 0) {
  755. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeTitle, knowTypeAssignMap, avgKnowTypeCount, fillExclude, assignCount);
  756. if (tmpMinKnowTypeCount > 0) {
  757. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  758. knownIdSet.add(knowId);
  759. }
  760. if(assignCount.get() >= lackTotal) {
  761. break;
  762. }
  763. }
  764. }
  765. } while(lackTotal != assignCount.get() && knownIdSet.size() > 0);
  766. if(lackTotal < assignCount.get()) {
  767. throw new RuntimeException("题数不足: " + typeTitle + "差" + (lackTotal - assignCount.get()));
  768. }
  769. }
  770. }
  771. private void assignKnowFirst(TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  772. // 循环补充未做+已做,如果知识点总数不够时才填充其他知识点的
  773. Long lackTotal = paperDef.getTotal();
  774. AtomicLong assignCount = new AtomicLong(0);
  775. Map<Long, Integer> knowTypesMap = Maps.newHashMap();
  776. int typeCount = paperDef.getTypes().size();
  777. Set<Long> knowSet = knowTypeAssignMap.values().stream().map(KnowTypeAssign::getKnowId).collect(Collectors.toSet());
  778. for (Long knowId : paperDef.getKnowIds()) {
  779. knowTypesMap.put(knowId, knowSet.contains(knowId) ? typeCount : 0);
  780. }
  781. Long minKnowTypeCount = Long.MAX_VALUE;
  782. do {
  783. Integer knowCount = knowSet.size();
  784. knowSet.clear();
  785. for (Long knowId : paperDef.getKnowIds()) {
  786. typeCount = knowTypesMap.get(knowId);
  787. if (0 == typeCount) {
  788. continue;
  789. }
  790. Long avgKnowTypeCount = Math.min(minKnowTypeCount, lackTotal / knowCount / typeCount);
  791. if(avgKnowTypeCount == 0 && lackTotal > 0) {
  792. avgKnowTypeCount = 1L;
  793. }
  794. typeCount = 0;
  795. for (TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  796. Long tmpMinKnowTypeCount = assignKnownCount(knowId, typeDef.getType(), knowTypeAssignMap, avgKnowTypeCount, paperDef.getFillExclude(), assignCount);
  797. if (tmpMinKnowTypeCount > 0) {
  798. minKnowTypeCount = Math.min(minKnowTypeCount, tmpMinKnowTypeCount);
  799. knowSet.add(knowId);
  800. typeCount++;
  801. }
  802. }
  803. knowTypesMap.put(knowId, typeCount);
  804. }
  805. lackTotal = paperDef.getTotal() - assignCount.get();
  806. if (lackTotal <= 0 || knowSet.isEmpty()) {
  807. break;
  808. }
  809. } while (true);
  810. }
  811. /**
  812. * 根据计划的分配数生成题关系
  813. * @param studentId
  814. * @param paperDef
  815. * @param knowTypeAssignMap
  816. * @return
  817. */
  818. public List<LearnPaperQuestion> getQuestions(Long studentId, TestPaperVO.PaperDef paperDef, Map<String, KnowTypeAssign> knowTypeAssignMap) {
  819. // 知识点已经分配,准备题型分配
  820. LearnQuestions qCond = new LearnQuestions();
  821. Random random = new Random();
  822. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  823. Set<Long> existQuestionIdSet = Sets.newHashSet();
  824. int total = paperDef.getTotal().intValue();
  825. for (TestPaperVO.TypeDef typeDef : paperDef.getTypes()) {
  826. for (Long knowId : paperDef.getKnowIds()) {
  827. String key = knowId + "_" + typeDef.getType();
  828. KnowTypeAssign ktc = knowTypeAssignMap.get(key);
  829. if(null == ktc) {
  830. continue;
  831. }
  832. qCond.setKnowledgeId(ktc.getKnowId());
  833. qCond.setQtpye(ktc.getType());
  834. qCond.setIsSubType("0");
  835. QuestionType qt = QuestionType.of(ktc.getType());
  836. if(ktc.exclAssign > 0){
  837. qCond.setId(studentId);
  838. qCond.setNumber(ktc.exclAssign > 500 ? (long) random.nextInt(ktc.exclAssign.intValue() - 500) : 0L);
  839. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  840. ktc.exclAssign = addRandomList(knowId, qt, questions, random, paperDef.getTotal(), ktc.exclAssign, typeDef.getScore().doubleValue(), existQuestionIdSet, 1, pqList);
  841. if(pqList.size() == total) {
  842. break;
  843. }
  844. }
  845. if(ktc.assign > 0L) {
  846. qCond.setId(null);
  847. qCond.setNumber(ktc.assign > 500 ? (long) random.nextInt(ktc.assign.intValue() - 500) : 0L);
  848. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  849. ktc.assign = addRandomList(knowId, qt, questions, random, paperDef.getTotal(), ktc.assign, typeDef.getScore().doubleValue(), existQuestionIdSet, 1, pqList);
  850. if(pqList.size() == total) {
  851. break;
  852. }
  853. }
  854. }
  855. if(pqList.size() == total) {
  856. break;
  857. }
  858. }
  859. if(CollectionUtils.isEmpty(pqList)) {
  860. throw new RuntimeException("题数不足");
  861. }
  862. reSort(pqList);
  863. return pqList;
  864. }
  865. public List<LearnPaperQuestion> getQuestions2(Long studentId, Integer total, Integer seqId, Collection<Long> knownIds, List<TestPaperVO.TypeDef2> types, Map<String, KnowTypeAssign> knowTypeAssignMap, Set<Long> existQuestionIdSet) {
  866. // 知识点已经分配,准备题型分配
  867. LearnQuestions qCond = new LearnQuestions();
  868. Random random = new Random();
  869. List<LearnPaperQuestion> pqList = Lists.newArrayList();
  870. for (TestPaperVO.TypeDef2 typeDef : types) {
  871. String typeTitle = typeDef.getType().getTitle();
  872. for (Long knowId : knownIds) {
  873. String key = knowId + "_" + typeTitle;
  874. KnowTypeAssign ktc = knowTypeAssignMap.get(key);
  875. if(null == ktc) {
  876. continue;
  877. }
  878. qCond.setKnowledgeId(ktc.getKnowId());
  879. qCond.setQtpye(typeTitle);
  880. qCond.setIsSubType("0");
  881. if(ktc.exclAssign > 0){
  882. qCond.setId(studentId);
  883. qCond.setNumber(ktc.exclAssign > 500 ? (long) random.nextInt(ktc.exclAssign.intValue() - 500) : 0L);
  884. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  885. ktc.exclAssign = addRandomList(knowId, typeDef.getType(), questions, random, total.longValue(), ktc.exclAssign, typeDef.getScore(), existQuestionIdSet, seqId, pqList);
  886. if(pqList.size() == total) {
  887. break;
  888. }
  889. }
  890. if(ktc.assign > 0L) {
  891. qCond.setId(null);
  892. qCond.setNumber(ktc.assign > 500 ? (long) random.nextInt(ktc.assign.intValue() - 500) : 0L);
  893. List<LearnQuestions> questions = questionsMapper.selectQuestionsForPaper(qCond);
  894. ktc.assign = addRandomList(knowId, typeDef.getType(), questions, random, total.longValue(), ktc.assign, typeDef.getScore(), existQuestionIdSet, seqId, pqList);
  895. if(pqList.size() == total) {
  896. break;
  897. }
  898. }
  899. }
  900. if(pqList.size() == total) {
  901. break;
  902. }
  903. }
  904. if(pqList.size() < total) {
  905. throw new RuntimeException("题数不足 " + types.stream().map( t -> t.getType().getTitle()).collect(Collectors.joining(",")) + ":" + StringUtils.join(knownIds, ","));
  906. }
  907. return pqList;
  908. }
  909. /**
  910. * 初始化当前用户卷情况
  911. * @param studentId
  912. * @param paperDef
  913. * @return
  914. */
  915. private Map<String, KnowTypeAssign> buildKnowTypeAssignMap(Long studentId, TestPaperVO.PaperDef paperDef) {
  916. return buildKnowTypeAssignMap(studentId, "0", paperDef.getTypes().stream().map(TestPaperVO.TypeDef::getType).collect(Collectors.toList()), paperDef.getKnowIds(), paperDef.getFillExclude());
  917. }
  918. /**
  919. * 初始化当前用户知识点的情况
  920. * @param studentId
  921. * @param types
  922. * @param knownIds
  923. * @param fillExclude
  924. * @return
  925. */
  926. private Map<String, KnowTypeAssign> buildKnowTypeAssignMap(Long studentId, String isSubType, List<String> types, Collection<Long> knownIds, Boolean fillExclude) {
  927. Map<String, KnowTypeAssign> knowTypeAssignMap = Maps.newHashMap();
  928. Map cond = Maps.newHashMap();
  929. cond.put("studentId", studentId);
  930. cond.put("knowIds", knownIds);
  931. cond.put("types", types);
  932. cond.put("isSubType", isSubType);
  933. setValue(knowTypeAssignMap, cond, 1); // 填充排除后总量
  934. if (null != studentId && fillExclude) {
  935. cond.remove("studentId");
  936. setValue(knowTypeAssignMap, cond, 2); // 按需填充已做总量
  937. }
  938. return knowTypeAssignMap;
  939. }
  940. /**
  941. * 随机从 knowId 题池中提取需要个数的题
  942. * @param knowId 知识点
  943. * @param type 类型
  944. * @param questions 题池
  945. * @param random 随机数
  946. * @param totalCount 总题数
  947. * @param count 本池分配数
  948. * @param score 题分
  949. * @param existQuestionIdSet 不能使用的题
  950. * @param pqList 卷题关系 diff(type),paperId(parentId),seq,questionId(id)
  951. */
  952. 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) {
  953. while(count > 0L && !questions.isEmpty()) {
  954. LearnQuestions q = questions.size() > 1 ? questions.remove(random.nextInt(questions.size() - 1)) : questions.remove(0);
  955. if(existQuestionIdSet.add(q.getId())) {
  956. if("1".equals(q.getIsSubType())) {
  957. LearnQuestions subCond = new LearnQuestions();
  958. subCond.setKnowId(q.getId());
  959. for(LearnQuestions sq : questionsMapper.selectLearnQuestionsList(subCond)) {
  960. LearnPaperQuestion pq = new LearnPaperQuestion();
  961. pq.setSeq(baseSeq + pqList.size());
  962. pq.setKnowledgeId(knowId);
  963. pq.setScore(score);
  964. pq.setQuestionId(sq.getId());
  965. pq.setType(type.getTitle());
  966. pq.setDiff(type.getVal());
  967. pq.setPaperId(q.getId());
  968. pqList.add(pq);
  969. count--;
  970. }
  971. } else {
  972. LearnPaperQuestion pq = new LearnPaperQuestion();
  973. pq.setSeq(baseSeq + pqList.size());
  974. pq.setKnowledgeId(knowId);
  975. pq.setScore(score);
  976. pq.setQuestionId(q.getId());
  977. pq.setType(type.getTitle());
  978. pq.setDiff(type.getVal());
  979. pq.setPaperId(0L);
  980. pqList.add(pq);
  981. count--;
  982. }
  983. if(pqList.size() >= totalCount) {
  984. break;
  985. }
  986. }
  987. }
  988. return count;
  989. }
  990. /**
  991. * 给指定类型分配
  992. * @param knowId
  993. * @param qtype
  994. * @param knowTypeAssignMap
  995. * @param knowTypeCount
  996. * @param fillExclude
  997. * @param assignCount
  998. */
  999. private Long assignKnownCount(Long knowId, String qtype, Map<String, KnowTypeAssign> knowTypeAssignMap, Long knowTypeCount, Boolean fillExclude,
  1000. AtomicLong assignCount) {
  1001. String key = knowId + "_" + qtype;
  1002. KnowTypeAssign knowTypeAssign = knowTypeAssignMap.get(key);
  1003. long lackCount;
  1004. if (null != knowTypeAssign && knowTypeAssign.exclCount > 0) {
  1005. lackCount = knowTypeCount - knowTypeAssign.exclCount;
  1006. if (lackCount <= 0) { // 足量
  1007. assignCount.getAndAdd(knowTypeCount);
  1008. knowTypeAssign.exclCount -= knowTypeCount;
  1009. knowTypeAssign.exclAssign += knowTypeCount;
  1010. } else { // 不足时且还有时全转
  1011. assignCount.getAndAdd(knowTypeAssign.exclCount);
  1012. knowTypeAssign.exclAssign += knowTypeAssign.exclCount;
  1013. knowTypeAssign.exclCount = 0L;
  1014. }
  1015. } else {
  1016. lackCount = knowTypeCount;
  1017. }
  1018. long lack = 0;
  1019. if (lackCount > 0) { // 差额优先补充已做过的
  1020. if(fillExclude && null != knowTypeAssign && knowTypeAssign.total > 0) {
  1021. lack = lackCount - knowTypeAssign.total;
  1022. if (lack <= 0) { // 足量
  1023. assignCount.getAndAdd(lackCount);
  1024. knowTypeAssign.total -= lackCount;
  1025. knowTypeAssign.assign += lackCount;
  1026. } else { // 不足时全转
  1027. assignCount.getAndAdd(knowTypeAssign.total);
  1028. knowTypeAssign.assign += knowTypeAssign.total;
  1029. knowTypeAssign.total = 0L;
  1030. }
  1031. } else {
  1032. lack = lackCount;
  1033. }
  1034. }
  1035. if(lackCount < 0) {
  1036. return -lackCount;
  1037. } else if(lack < 0) {
  1038. return -lack;
  1039. }
  1040. return -lack;
  1041. }
  1042. /**
  1043. * 合并 未用总量和已有总量, 分配数置0
  1044. * @param knowTypeAssignMap key=knowId +"_" + qtype
  1045. * @param cond
  1046. * @param index 1 free 2 used
  1047. */
  1048. private void setValue(Map<String, KnowTypeAssign> knowTypeAssignMap, Map cond, Integer index) {
  1049. for (LearnQuestions q : questionsMapper.statByKnowledgeType(cond)) {
  1050. String key = q.getKnowledgeId() + "_" + q.getQtpye();
  1051. KnowTypeAssign knowTypeAssign = knowTypeAssignMap.get(key);
  1052. if (null == knowTypeAssign) {
  1053. knowTypeAssign = new KnowTypeAssign();
  1054. knowTypeAssign.setKnowId(q.getKnowledgeId());
  1055. knowTypeAssign.setType(q.getQtpye());
  1056. knowTypeAssign.total = 0L;
  1057. knowTypeAssign.exclAssign = 0L;
  1058. knowTypeAssign.assign = 0L;
  1059. knowTypeAssign.exclCount = 0L;
  1060. knowTypeAssignMap.put(key, knowTypeAssign);
  1061. }
  1062. if (1 == index) {
  1063. knowTypeAssign.exclCount = q.getNumber();
  1064. } else {
  1065. knowTypeAssign.total = q.getNumber() - knowTypeAssign.exclCount;
  1066. }
  1067. }
  1068. }
  1069. @Data
  1070. public static class KnowTypeAssign {
  1071. Long knowId; // 知识点
  1072. String type; // 题类型
  1073. Long exclAssign; // 未用分配数
  1074. Long assign; // 已用分配数
  1075. Long exclCount; // 未用总量
  1076. Long total; // 已用总量
  1077. }
  1078. }