PaperService.java 60 KB

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