PaperService.java 55 KB

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