ExaminationService.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. package com.qxgmat.service.extend;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.github.pagehelper.Page;
  4. import com.nuliji.tools.AbstractService;
  5. import com.nuliji.tools.PageResult;
  6. import com.nuliji.tools.Transform;
  7. import com.nuliji.tools.exception.ParameterException;
  8. import com.nuliji.tools.exception.SystemException;
  9. import com.qxgmat.data.constants.enums.QuestionDifficult;
  10. import com.qxgmat.data.constants.enums.QuestionSubject;
  11. import com.qxgmat.data.constants.enums.QuestionType;
  12. import com.qxgmat.data.constants.enums.ServiceKey;
  13. import com.qxgmat.data.constants.enums.module.PaperOrigin;
  14. import com.qxgmat.data.constants.enums.status.DirectionStatus;
  15. import com.qxgmat.data.dao.entity.*;
  16. import com.qxgmat.data.relation.ExaminationPaperRelationMapper;
  17. import com.qxgmat.data.relation.QuestionNoRelationMapper;
  18. import com.qxgmat.data.relation.UserPaperRelationMapper;
  19. import com.qxgmat.data.relation.UserReportRelationMapper;
  20. import com.qxgmat.data.relation.entity.QuestionDifficultRelation;
  21. import com.qxgmat.data.relation.entity.QuestionNoRelation;
  22. import com.qxgmat.service.UserPaperService;
  23. import com.qxgmat.service.UsersService;
  24. import com.qxgmat.service.inline.*;
  25. import org.springframework.stereotype.Service;
  26. import org.springframework.transaction.annotation.Transactional;
  27. import javax.annotation.Resource;
  28. import java.util.*;
  29. @Service
  30. public class ExaminationService extends AbstractService {
  31. private static Random rand = new Random();
  32. @Resource
  33. private QuestionService questionService;
  34. @Resource
  35. private QuestionNoService questionNoService;
  36. @Resource
  37. private QuestionNoRelationMapper questionNoRelationMapper;
  38. @Resource
  39. private ExaminationStructService examinationStructService;
  40. @Resource
  41. private ExaminationPaperService examinationPaperService;
  42. @Resource
  43. private UserPaperRelationMapper userPaperRelationMapper;
  44. @Resource
  45. private UserPaperService userPaperService;
  46. @Resource
  47. private UserReportRelationMapper userReportRelationMapper;
  48. @Resource
  49. private UserReportService userReportService;
  50. @Resource
  51. private UsersService usersService;
  52. @Resource
  53. private ExaminationPaperRelationMapper examinationPaperRelationMapper;
  54. /**
  55. * verbal考试相关设置
  56. */
  57. final public Integer verbalMaxLevel = 27;
  58. final public Integer verbalMinLevel = 9;
  59. final public Integer verbalInitLevel = 13;
  60. final public Integer verbalPre = 9;
  61. final public Integer verbalSC = 14;
  62. final public Integer verbalCR = 9;
  63. final public Integer verbalRC = 13;
  64. // 出RC阅读题的题号
  65. final public Integer[] verbalRCPosition = new Integer[]{5, 15, 24, 33};
  66. /**
  67. * quant考试相关设置
  68. */
  69. final public Integer[] quantBaseLevel = new Integer[]{8, 8, 8, 7};
  70. // 阶段的最大题目数
  71. final public Integer[] quantNumber = new Integer[]{0, 8, 16, 24, 31};
  72. final public Integer quantMinRatio = 1;
  73. final public Integer quantMaxRatio = 3;
  74. final public Integer quantInitLevel = 11;
  75. final public Integer quantPS = 17;
  76. final public Integer quantDS = 14;
  77. /**
  78. * 根据第三层结构建立paper
  79. * @param entity
  80. * @return
  81. */
  82. @Transactional
  83. public ExaminationStruct addPaper(ExaminationStruct entity){
  84. entity = examinationStructService.add(entity);
  85. if (entity.getLevel() == 3){
  86. // 添加2份paper
  87. ExaminationPaper paper = examinationPaperService.add(ExaminationPaper.builder()
  88. .isAdapt(entity.getIsAdapt())
  89. .structTwo(entity.getParentId())
  90. .structThree(entity.getId())
  91. .title(entity.getTitleZh())
  92. .build()
  93. );
  94. }
  95. return entity;
  96. }
  97. @Transactional
  98. public ExaminationStruct editPaper(ExaminationStruct entity){
  99. entity = examinationStructService.add(entity);
  100. if (entity.getLevel() == 3){
  101. ExaminationPaper paper = examinationPaperService.getByThree(entity.getId());
  102. if(paper == null){
  103. paper = examinationPaperService.add(ExaminationPaper.builder()
  104. .isAdapt(entity.getIsAdapt())
  105. .structTwo(entity.getParentId())
  106. .structThree(entity.getId())
  107. .title(entity.getTitleZh())
  108. .build()
  109. );
  110. }else{
  111. examinationPaperService.edit(ExaminationPaper.builder()
  112. .id(paper.getId())
  113. .isAdapt(entity.getIsAdapt())
  114. .structTwo(entity.getParentId())
  115. .structThree(entity.getId())
  116. .title(entity.getTitleZh())
  117. .build()
  118. );
  119. }
  120. }
  121. return entity;
  122. }
  123. @Transactional
  124. public Boolean deletePaper(Integer id){
  125. Boolean result = examinationStructService.delete(id);
  126. ExaminationPaper paper = examinationPaperService.getByThree(id);
  127. if(paper != null){
  128. examinationPaperService.delete(paper.getId());
  129. }
  130. return result;
  131. }
  132. /**
  133. *
  134. * @param page
  135. * @param pageSize
  136. * @param questionType
  137. * @param structId
  138. * @param questionNo
  139. * @param paperId
  140. * @param place
  141. * @param difficult
  142. * @param order
  143. * @param direction
  144. * @return
  145. */
  146. public Page<QuestionNoRelation> listAdmin(int page, int pageSize, String questionType, Number structId, Number questionNo, Number paperId, String place, String difficult, String order, DirectionStatus direction){
  147. if(order == null || order.isEmpty()) order = "id";
  148. if (direction == null){
  149. direction = DirectionStatus.DESC;
  150. }
  151. String finalOrder = order;
  152. DirectionStatus finalDirection = direction;
  153. Page<QuestionNoRelation> p = page(() -> {
  154. questionNoRelationMapper.listExaminationAdmin(questionType, structId, questionNo, paperId, place, difficult, finalOrder, finalDirection.key);
  155. }, page, pageSize);
  156. Collection ids = Transform.getIds(p, QuestionNoRelation.class, "id");
  157. // 获取详细数据
  158. List<QuestionNoRelation> list = questionNoService.relation(questionNoService.select(ids));
  159. Transform.replace(p, list, QuestionNoRelation.class, "id");
  160. return p;
  161. }
  162. /**
  163. * 查找模考组卷
  164. * @param page
  165. * @param size
  166. * @param structId
  167. * @param userId
  168. * @param times
  169. * @return
  170. */
  171. public PageResult<ExaminationPaper> list(int page, int size, Number structId, Number userId, Integer qxCatNo, Integer times){
  172. Page<ExaminationPaper> p = page(()->{
  173. examinationPaperRelationMapper.listWithUser(structId, userId, qxCatNo, times);
  174. },page, size);
  175. Collection ids = Transform.getIds(p, ExaminationPaper.class, "id");
  176. // 获取详细数据
  177. List<ExaminationPaper> list = examinationPaperService.select(ids);
  178. return new PageResult<>(list, p.getTotal());
  179. }
  180. /**
  181. * cat模考是否已经完成
  182. * @param userId
  183. * @return
  184. */
  185. public boolean isFinishCat(Integer userId){
  186. ExaminationStruct struct = getCat();
  187. List<ExaminationPaper> paperList = examinationPaperService.listByTwo(struct.getId());
  188. Collection ids = Transform.getIds(paperList, ExaminationPaper.class, "id");
  189. List<UserPaper> userPaperList = userPaperService.listWithOrigin(userId, PaperOrigin.EXAMINATION, ids, null);
  190. if (paperList.size() != userPaperList.size()){
  191. return false;
  192. }
  193. Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
  194. List<UserReport> reportList = userReportService.listWithLater(paperIds);
  195. for(UserReport report: reportList){
  196. if(report.getIsFinish() == 0){
  197. return false;
  198. }
  199. }
  200. return true;
  201. }
  202. /**
  203. * 重置Cat模考系列
  204. * @param userId
  205. * @return
  206. */
  207. @Transactional
  208. public Boolean resetCat(Integer userId, boolean force){
  209. ExaminationStruct struct = getCat();
  210. List<ExaminationPaper> paperList = examinationPaperService.listByTwo(struct.getId());
  211. Collection ids = Transform.getIds(paperList, ExaminationPaper.class, "id");
  212. List<UserPaper> userPaperList = userPaperService.listWithOrigin(userId, PaperOrigin.EXAMINATION, ids, null);
  213. if (!force && paperList.size() != userPaperList.size()){
  214. throw new ParameterException("未完成所有");
  215. }
  216. Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
  217. if(!force){
  218. List<UserReport> reportList = userReportService.listWithLater(paperIds);
  219. for(UserReport report: reportList){
  220. if(report.getIsFinish() == 0){
  221. throw new ParameterException("未完成所有");
  222. }
  223. }
  224. }
  225. // 增加用户cat计数
  226. User user = usersService.get(userId);
  227. usersService.edit(User.builder().id(userId).qxCat(user.getQxCat() + 1).build());
  228. return userPaperService.reset(paperIds, userId);
  229. }
  230. public ExaminationStruct getCat(){
  231. List<ExaminationStruct> list = examinationStructService.main();
  232. for (ExaminationStruct struct : list){
  233. if (struct.getLevel() == 1) continue;
  234. if (struct.getExtend().equals(ServiceKey.QX_CAT.key)) return struct;
  235. }
  236. throw new SystemException("没有找到cat模考节点");
  237. }
  238. /**
  239. * 获取下一阶段难度分
  240. * @param currentLevel
  241. * @param number
  242. * @param correct
  243. * @return
  244. */
  245. public Integer verbalNextLevel(Integer currentLevel, Integer number, long correct){
  246. long correctRate = correct * 100 / number;
  247. if (correctRate < 60){
  248. currentLevel -= 5;
  249. if (currentLevel < verbalMinLevel) return verbalMinLevel;
  250. }else if(correctRate > 80){
  251. currentLevel += 5;
  252. if (currentLevel > verbalMaxLevel) return verbalMaxLevel;
  253. }
  254. return currentLevel;
  255. }
  256. /**
  257. * 获取下一阶段难度分
  258. * @param currentLevel
  259. * @param number
  260. * @param correct
  261. * @param step
  262. * @return
  263. */
  264. public Integer quantNextLevel(Integer currentLevel, Integer number, long correct, Integer step){
  265. long correctRate = correct * 100 / number;
  266. Integer max = quantMaxRatio * quantBaseLevel[step];
  267. Integer min = quantMinRatio * quantBaseLevel[step];
  268. if (correctRate < 60){
  269. currentLevel -= 4;
  270. if (currentLevel < min) return min;
  271. }else if(correctRate > 80){
  272. currentLevel += 4;
  273. if (currentLevel > max) return max;
  274. }
  275. return currentLevel;
  276. }
  277. /**
  278. * 分组统计不同题型的数量
  279. * @param userQuestionList
  280. * @return
  281. */
  282. public Map<String, Integer> statTypeNumber(List<UserQuestion> userQuestionList){
  283. Map<String, Integer> result = new HashMap<>();
  284. for(UserQuestion question : userQuestionList){
  285. String type = question.getQuestionType();
  286. if (!result.containsKey(type)){
  287. result.put(type, 0);
  288. }
  289. result.put(type, result.get(type) + 1);
  290. }
  291. return result;
  292. }
  293. /**
  294. * 返回还需的题目类型
  295. * @param typeNumber
  296. * @param questionTypes
  297. * @return
  298. */
  299. public List<String> needQuestionTypes(Map<String, Integer> typeNumber, List<String> questionTypes){
  300. List<String> targetTypes = new ArrayList<>();
  301. for(String type : questionTypes){
  302. int target = 0;
  303. switch(type){
  304. case "sc":
  305. target = verbalSC;
  306. break;
  307. case "cr":
  308. target = verbalCR;
  309. break;
  310. case "rc":
  311. target = verbalRC;
  312. break;
  313. case "ps":
  314. target = quantPS;
  315. break;
  316. case "ds":
  317. target = quantDS;
  318. break;
  319. }
  320. if (target == 0 || target > typeNumber.get(type)){
  321. targetTypes.add(type);
  322. }
  323. }
  324. return targetTypes;
  325. }
  326. /**
  327. * 通过当前完成的题目数,判断是否该抽取阅读题
  328. * @param number
  329. * @return
  330. */
  331. public boolean verbalRC(Integer number){
  332. for(Integer n : verbalRCPosition){
  333. if (n == number) return true;
  334. }
  335. return false;
  336. }
  337. /**
  338. * 通过当前完成的题目数,判断当前阶段
  339. * @param number
  340. * @return
  341. */
  342. public Integer quantStep(Integer number) {
  343. Integer step = 0;
  344. Integer all = 0;
  345. for(Integer n : quantBaseLevel){
  346. all += n;
  347. if (all >= number) return step;
  348. step += 1;
  349. }
  350. return step;
  351. }
  352. /**
  353. * 初始化第一阶段题目
  354. * 一篇4题的阅读压轴
  355. * { "ids": [], "level": 0 }
  356. * @param structId
  357. * @return
  358. */
  359. public JSONObject initVerbal(Integer structId){
  360. JSONObject info = new JSONObject();
  361. Integer[] rcQ = questionNoService.randomExaminationRc(structId, 4, null);
  362. List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
  363. Integer rcLevel = computeNoLevel(rcRelationList);
  364. Integer targetLevel = 0;
  365. Collection types = QuestionType.FromSubject(QuestionSubject.VERBAL);
  366. List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
  367. List<QuestionDifficultRelation> selectedList = null;
  368. do{
  369. selectedList = randomList(difficultList, verbalPre - 4);
  370. targetLevel = rcLevel + computeDifficultLevel(selectedList);
  371. }while(targetLevel >= verbalInitLevel);
  372. List<Integer> targetIds = new ArrayList<>(verbalPre);
  373. for(QuestionDifficultRelation relation : selectedList){
  374. targetIds.add(relation.getId());
  375. }
  376. targetIds.addAll(Arrays.asList(rcQ));
  377. info.put("level", targetLevel);
  378. info.put("ids", targetIds);
  379. return info;
  380. }
  381. /**
  382. * 生成下一阶段题目
  383. * 一篇3题的阅读压轴
  384. * { "ids": [], "level": 0 }
  385. * @param structId
  386. * @param level
  387. * @param typeNumbers
  388. * @param ids
  389. * @return
  390. */
  391. public JSONObject generateVerbal(Integer structId, Integer level, Map<String, Integer> typeNumbers, Collection ids){
  392. JSONObject info = new JSONObject();
  393. Integer[] rcQ = questionNoService.randomExaminationRc(structId, 3, ids);
  394. List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
  395. Integer rcLevel = computeNoLevel(rcRelationList);
  396. Collection types = QuestionType.FromSubject(QuestionSubject.VERBAL);
  397. List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
  398. List<QuestionDifficultRelation> selectedList = new ArrayList<>(verbalPre - 3);
  399. Integer[] levels = generateLevel(level - rcLevel,verbalPre - 3);
  400. do{
  401. QuestionDifficultRelation r = random(difficultList, ids, levels[selectedList.size()]);
  402. int typeNumber = typeNumbers.get(r.getQuestionType()) + 1;
  403. switch(r.getQuestionType()){
  404. case "sc":
  405. if (typeNumber > verbalSC) {
  406. continue;
  407. }
  408. break;
  409. case "cr":
  410. if (typeNumber > verbalCR) {
  411. continue;
  412. }
  413. break;
  414. }
  415. typeNumbers.put(r.getQuestionType(), typeNumber);
  416. selectedList.add(r);
  417. ids.add(r.getId());
  418. }while(selectedList.size() == verbalPre -3);
  419. List<Integer> targetIds = new ArrayList<>(verbalPre);
  420. for(QuestionDifficultRelation relation : selectedList){
  421. targetIds.add(relation.getId());
  422. }
  423. targetIds.addAll(Arrays.asList(rcQ));
  424. info.put("ids", targetIds);
  425. info.put("level", level);
  426. return info;
  427. }
  428. /**
  429. * 初始化第一阶段题目
  430. * 一篇4题的阅读压轴
  431. * { "ids": [], "level": 0 }
  432. * @param structId
  433. * @return
  434. */
  435. public JSONObject initQuant(Integer structId){
  436. JSONObject info = new JSONObject();
  437. Integer targetLevel = 0;
  438. Collection types = QuestionType.FromSubject(QuestionSubject.QUANT);
  439. Integer number = quantBaseLevel[0];
  440. List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
  441. List<QuestionDifficultRelation> selectedList = null;
  442. do{
  443. selectedList = randomList(difficultList, number);
  444. targetLevel = computeDifficultLevel(selectedList);
  445. }while(targetLevel >= quantInitLevel);
  446. List<Integer> targetIds = new ArrayList<>(number);
  447. for(QuestionDifficultRelation relation : selectedList){
  448. targetIds.add(relation.getId());
  449. }
  450. info.put("level", targetLevel);
  451. info.put("ids", targetIds);
  452. return info;
  453. }
  454. /**
  455. * 生成下一阶段题目
  456. * 一篇3题的阅读压轴
  457. * { "ids": [], "level": 0 }
  458. * @param structId
  459. * @param level
  460. * @param typeNumbers
  461. * @param ids
  462. * @return
  463. */
  464. public JSONObject generateQuant(Integer structId, Integer level, Map<String, Integer> typeNumbers, Collection ids, Integer step){
  465. JSONObject info = new JSONObject();
  466. Collection types = QuestionType.FromSubject(QuestionSubject.QUANT);
  467. Integer number = quantBaseLevel[step];
  468. List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
  469. List<QuestionDifficultRelation> selectedList = null;
  470. Integer[] levels = generateLevel(level,number);
  471. do{
  472. QuestionDifficultRelation r = random(difficultList, ids, levels[selectedList.size()]);
  473. int typeNumber = typeNumbers.get(r.getQuestionType()) + 1;
  474. switch(r.getQuestionType()){
  475. case "ps":
  476. if (typeNumber > quantPS) {
  477. continue;
  478. }
  479. break;
  480. case "ds":
  481. if (typeNumber > quantDS) {
  482. continue;
  483. }
  484. break;
  485. }
  486. typeNumbers.put(r.getQuestionType(), typeNumber);
  487. selectedList.add(r);
  488. ids.add(r.getId());
  489. }while(selectedList.size() == number);
  490. List<Integer> targetIds = new ArrayList<>(number);
  491. for(QuestionDifficultRelation relation : selectedList){
  492. targetIds.add(relation.getId());
  493. }
  494. info.put("ids", targetIds);
  495. info.put("level", level);
  496. return info;
  497. }
  498. /**
  499. * 随机生成对应的题目列表
  500. * @param all
  501. * @param size
  502. * @return
  503. */
  504. public List<QuestionDifficultRelation> randomList(List<QuestionDifficultRelation> all, Integer size){
  505. List<QuestionDifficultRelation> list = new ArrayList<>(size);
  506. do{
  507. int index = rand.nextInt(all.size());
  508. QuestionDifficultRelation target = all.get(index);
  509. list.add(target);
  510. }while(list.size() == size);
  511. return list;
  512. }
  513. /**
  514. * 随机生成对应的题目
  515. * @param all
  516. * @param ids
  517. * @param level
  518. * @return
  519. */
  520. public QuestionDifficultRelation random(List<QuestionDifficultRelation> all, Collection ids, Integer level){
  521. String difficult = levelToDifficult(level);
  522. do{
  523. int index = rand.nextInt(all.size());
  524. QuestionDifficultRelation target = all.get(index);
  525. if (ids == null || !ids.contains(target.getId())){
  526. if (difficult == null || target.getDifficult().equals(difficult)) return target;
  527. }
  528. }while(true);
  529. }
  530. /**
  531. * 计算考题对应的难度分
  532. * @param relationList
  533. * @return
  534. */
  535. public Integer computeNoLevel(List<QuestionNoRelation> relationList){
  536. int level = 0;
  537. for(QuestionNoRelation relation : relationList){
  538. level += difficultToLevel(relation.getQuestion().getDifficult());
  539. }
  540. return level;
  541. }
  542. public Integer computeDifficultLevel(List<QuestionDifficultRelation> relationList){
  543. int level = 0;
  544. for(QuestionDifficultRelation relation : relationList){
  545. level += difficultToLevel(relation.getDifficult());
  546. }
  547. return level;
  548. }
  549. public Integer difficultToLevel(String difficult){
  550. switch(difficult){
  551. case "easy":
  552. return 1;
  553. case "medium":
  554. return 2;
  555. case "hard":
  556. return 3;
  557. }
  558. return 0;
  559. }
  560. public String levelToDifficult(Integer level){
  561. switch(level){
  562. case 1:
  563. return "easy";
  564. case 2:
  565. return "medium";
  566. case 3:
  567. return "hard";
  568. }
  569. return null;
  570. }
  571. public Integer[] generateLevel(Integer level, Integer number){
  572. Integer[] levels = new Integer[number];
  573. int no = 0;
  574. // 至少有一个可以3分
  575. if (level > number + 1){
  576. for(int i = 0; i < number; i++){
  577. int n = rand.nextInt(2) + 2; // 随机2-3
  578. levels[i] = n;
  579. level -= n;
  580. no += 1;
  581. // 只能还有一个2分
  582. if (level == number - no + 1){
  583. break;
  584. }
  585. }
  586. number -= no;
  587. }
  588. if(level == number + 1){
  589. // 只有一个2分
  590. levels[no] = 2;
  591. no += 1;
  592. }
  593. // 剩余都是1分
  594. for (int i = 0; i < number; i++) {
  595. levels[i+no] = 1;
  596. }
  597. shuffle(levels);
  598. return levels;
  599. }
  600. public static <T> void swap(T[] a, int i, int j){
  601. T temp = a[i];
  602. a[i] = a[j];
  603. a[j] = temp;
  604. }
  605. public static <T> void shuffle(T[] arr) {
  606. int length = arr.length;
  607. for ( int i = length; i > 0; i-- ){
  608. int randInd = rand.nextInt(i);
  609. swap(arr, randInd, i - 1);
  610. }
  611. }
  612. }