PaperService.java 59 KB

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