PaperService.java 61 KB

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