StudentApplyServiceImpl.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. package com.qmth.exam.reserve.service.impl;
  2. import java.io.InputStream;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.Comparator;
  6. import java.util.Date;
  7. import java.util.HashMap;
  8. import java.util.Iterator;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.UUID;
  12. import java.util.concurrent.TimeUnit;
  13. import java.util.stream.Collectors;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.stereotype.Service;
  19. import org.springframework.transaction.annotation.Transactional;
  20. import org.springframework.transaction.interceptor.TransactionAspectSupport;
  21. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  22. import com.baomidou.mybatisplus.core.metadata.IPage;
  23. import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  24. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  25. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  26. import com.qmth.boot.core.collection.PageResult;
  27. import com.qmth.boot.core.exception.StatusException;
  28. import com.qmth.boot.tools.excel.ExcelReader;
  29. import com.qmth.boot.tools.excel.enums.ExcelType;
  30. import com.qmth.boot.tools.excel.model.DataMap;
  31. import com.qmth.exam.reserve.bean.login.LoginUser;
  32. import com.qmth.exam.reserve.bean.stdapply.AgentAndTimeVO;
  33. import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
  34. import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
  35. import com.qmth.exam.reserve.bean.stdapply.StudentImportVO;
  36. import com.qmth.exam.reserve.cache.CacheConstants;
  37. import com.qmth.exam.reserve.cache.RedisClient;
  38. import com.qmth.exam.reserve.dao.StudentApplyDao;
  39. import com.qmth.exam.reserve.entity.ApplyTaskEntity;
  40. import com.qmth.exam.reserve.entity.CategoryEntity;
  41. import com.qmth.exam.reserve.entity.ExamRoomEntity;
  42. import com.qmth.exam.reserve.entity.ExamSiteEntity;
  43. import com.qmth.exam.reserve.entity.StudentApplyEntity;
  44. import com.qmth.exam.reserve.entity.StudentEntity;
  45. import com.qmth.exam.reserve.entity.TimePeriodEntity;
  46. import com.qmth.exam.reserve.enums.CategoryLevel;
  47. import com.qmth.exam.reserve.enums.DateField;
  48. import com.qmth.exam.reserve.enums.EventType;
  49. import com.qmth.exam.reserve.service.ApplyTaskService;
  50. import com.qmth.exam.reserve.service.CategoryService;
  51. import com.qmth.exam.reserve.service.ExamRoomService;
  52. import com.qmth.exam.reserve.service.ExamSiteService;
  53. import com.qmth.exam.reserve.service.OperateLogService;
  54. import com.qmth.exam.reserve.service.StudentApplyService;
  55. import com.qmth.exam.reserve.service.StudentService;
  56. import com.qmth.exam.reserve.service.TimePeriodService;
  57. import com.qmth.exam.reserve.util.DateUtil;
  58. import com.qmth.exam.reserve.util.JsonHelper;
  59. import com.qmth.exam.reserve.util.PageUtil;
  60. @Service
  61. public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity>
  62. implements StudentApplyService {
  63. private final static Logger log = LoggerFactory.getLogger(StudentApplyServiceImpl.class);
  64. private static final String[] EXCEL_HEADER = new String[] { "学号", "姓名", "证件号", "所属教学点", "预约考点1", "预约时段1", "预约考点2",
  65. "预约时段2", "预约考点3", "预约时段3", "预约考点4", "预约时段4" };
  66. private final long TIMEOUT = 60;
  67. @Autowired
  68. private TimePeriodService timePeriodService;
  69. @Autowired
  70. private ApplyTaskService applyTaskService;
  71. @Autowired
  72. private CategoryService categoryService;
  73. @Autowired
  74. private StudentService studentService;
  75. @Autowired
  76. private ExamSiteService examSiteService;
  77. @Autowired
  78. private OperateLogService operateLogService;
  79. @Autowired
  80. private RedisClient redisClient;
  81. @Autowired
  82. private ExamRoomService examRoomService;
  83. @Override
  84. public PageResult<StudentApplyVO> page(StudentApplyReq req) {
  85. IPage<StudentApplyVO> iPage = this.baseMapper
  86. .page(new Page<StudentApplyVO>(req.getPageNumber(), req.getPageSize()), req);
  87. return PageUtil.of(iPage);
  88. }
  89. @Transactional
  90. @Override
  91. public void cancel(LoginUser user, Long id) {
  92. // 时间判断
  93. StudentApplyEntity studentApply = this.baseMapper.selectById(id);
  94. if (studentApply == null || studentApply.getTimePeriodId() == null)
  95. throw new StatusException("考生没有预约,无法取消!");
  96. TimePeriodEntity timePeroid = timePeriodService.getById(studentApply.getTimePeriodId());
  97. if (timePeroid == null)
  98. throw new StatusException("考试时段不存在,请检查考试时段数据!");
  99. ApplyTaskEntity task = getApplyTask();
  100. Date applyDate = DateUtil.parse(DateUtil.getShortDateByLongTime(timePeroid.getStartTime()), "yyyy-MM-dd");
  101. Date canCancelDay = DateUtil.addValues(applyDate, DateField.DAY.getValue(), -task.getAllowApplyCancelDays());
  102. if (new Date().after(canCancelDay))
  103. throw new StatusException("可取消时间已过,无法取消!");
  104. studentApply.setCancel(Boolean.TRUE);
  105. this.baseMapper.updateById(studentApply);
  106. // TODO redis更新:该时段redis已预约的数量减1
  107. operateLogService.insertOperateLog(user.getId(), EventType.CANCEL_APPLY, JsonHelper.toJson(studentApply));
  108. }
  109. @Transactional
  110. @Override
  111. public List<Map<String, Object>> importPreExam(LoginUser user, Long teachingId, InputStream inputStream) {
  112. checkInOpenTime();
  113. List<DataMap> lineList = null;
  114. ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
  115. try {
  116. lineList = reader.getDataMapList();
  117. } catch (Exception e) {
  118. throw new StatusException("Excel 解析失败");
  119. }
  120. if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
  121. throw new StatusException("Excel表头错误");
  122. }
  123. if (CollectionUtils.isEmpty(lineList)) {
  124. throw new StatusException("Excel无内容");
  125. }
  126. List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
  127. Map<String, Long> teachingCache = getTeachingCache();
  128. Map<String, Long> agentCache = getAgentCache(teachingId);
  129. Map<String, Long> timeCache = getTimePeriodCache();
  130. List<StudentImportVO> applyList = new ArrayList<>();
  131. AgentAndTimeVO agentTime = new AgentAndTimeVO();
  132. for (int i = 0; i < lineList.size(); i++) {
  133. List<AgentAndTimeVO> agentTimeList = new ArrayList<>();
  134. DataMap line = lineList.get(i);
  135. StudentImportVO apply = new StudentImportVO();
  136. StringBuilder msg = new StringBuilder();
  137. String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
  138. if (StringUtils.isBlank(studentCode)) {
  139. msg.append(" 学号不能为空");
  140. }
  141. String name = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
  142. if (StringUtils.isBlank(name)) {
  143. msg.append(" 姓名不能为空");
  144. }
  145. String identityNumber = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
  146. if (StringUtils.isBlank(identityNumber)) {
  147. msg.append(" 证件号不能为空");
  148. }
  149. StudentEntity student = null;
  150. try {
  151. student = checkStd(studentCode, name, identityNumber);
  152. apply.setStudentId(student.getId());
  153. } catch (StatusException e) {
  154. msg.append(" " + e.getMessage());
  155. failRecords.add(newError(i + 1, msg.toString()));
  156. continue;
  157. }
  158. String teachingName = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
  159. if (StringUtils.isBlank(teachingName)) {
  160. msg.append(" 所属教学点不能为空");
  161. }
  162. Long categoryId = teachingCache.get(teachingName);
  163. if (categoryId == null) {
  164. msg.append(" 所属教学点不存在");
  165. }
  166. if (!student.getCategoryId().equals(categoryId)) {
  167. msg.append(" 考生所属教学点和库中的考生教学点不匹配");
  168. }
  169. String agentName1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
  170. if (StringUtils.isBlank(agentName1)) {
  171. msg.append(" 预约考点1不能为空");
  172. }
  173. agentTime = new AgentAndTimeVO();
  174. Long agentId = agentCache.get(agentName1);
  175. if (agentId == null) {
  176. msg.append(" 预约考点1不存在");
  177. }
  178. String timePeriod1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
  179. if (StringUtils.isBlank(timePeriod1)) {
  180. msg.append(" 预约时段1不能为空");
  181. }
  182. Long timePeriodId = null;
  183. try {
  184. timePeriodId = checkTimePeriod(timePeriod1, timeCache);
  185. agentTime.setAgentId(agentId);
  186. agentTime.setTimePeriodId(timePeriodId);
  187. agentTimeList.add(agentTime);
  188. } catch (StatusException e) {
  189. msg.append(" " + e.getMessage());
  190. }
  191. String agentName2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
  192. String timePeriod2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
  193. if (StringUtils.isBlank(agentName2) && StringUtils.isBlank(timePeriod2)) {
  194. apply.setAgentTimeList(agentTimeList);
  195. applyList.add(apply);
  196. if (msg.length() > 0)
  197. failRecords.add(newError(i + 1, msg.toString()));
  198. continue;
  199. } else {
  200. agentId = agentCache.get(agentName2);
  201. if (agentId == null)
  202. msg.append(" 预约考点2不存在");
  203. try {
  204. timePeriodId = checkTimePeriod(timePeriod2, timeCache);
  205. agentTime = new AgentAndTimeVO();
  206. agentTime.setAgentId(agentId);
  207. agentTime.setTimePeriodId(timePeriodId);
  208. agentTimeList.add(agentTime);
  209. } catch (StatusException e) {
  210. msg.append(" " + e.getMessage());
  211. }
  212. }
  213. String agentName3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
  214. String timePeriod3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[9]));
  215. if (StringUtils.isBlank(agentName3) && StringUtils.isBlank(timePeriod3)) {
  216. apply.setAgentTimeList(agentTimeList);
  217. applyList.add(apply);
  218. if (msg.length() > 0)
  219. failRecords.add(newError(i + 1, msg.toString()));
  220. continue;
  221. } else {
  222. agentId = agentCache.get(agentName3);
  223. if (agentId == null)
  224. msg.append(" 预约考点3不存在");
  225. try {
  226. timePeriodId = checkTimePeriod(timePeriod3, timeCache);
  227. agentTime = new AgentAndTimeVO();
  228. agentTime.setAgentId(agentId);
  229. agentTime.setTimePeriodId(timePeriodId);
  230. agentTimeList.add(agentTime);
  231. } catch (StatusException e) {
  232. msg.append(" " + e.getMessage());
  233. }
  234. }
  235. String agentName4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[10]));
  236. String timePeriod4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[11]));
  237. if (StringUtils.isBlank(agentName4) && StringUtils.isBlank(timePeriod4)) {
  238. apply.setAgentTimeList(agentTimeList);
  239. applyList.add(apply);
  240. if (msg.length() > 0)
  241. failRecords.add(newError(i + 1, msg.toString()));
  242. continue;
  243. } else {
  244. agentId = agentCache.get(agentName4);
  245. if (agentId == null)
  246. msg.append(" 预约考点4不存在");
  247. try {
  248. timePeriodId = checkTimePeriod(timePeriod4, timeCache);
  249. agentTime = new AgentAndTimeVO();
  250. agentTime.setAgentId(agentId);
  251. agentTime.setTimePeriodId(timePeriodId);
  252. agentTimeList.add(agentTime);
  253. apply.setAgentTimeList(agentTimeList);
  254. applyList.add(apply);
  255. } catch (StatusException e) {
  256. msg.append(" " + e.getMessage());
  257. }
  258. }
  259. }
  260. if (CollectionUtils.isNotEmpty(failRecords)) {
  261. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  262. return failRecords;
  263. }
  264. for (int i = 0; i < applyList.size(); i++) {
  265. StudentImportVO vo = applyList.get(i);
  266. try {
  267. saveStdApply(user.getId(), vo);
  268. } catch (StatusException e) {
  269. failRecords.add(newError(i + 1, e.getMessage()));
  270. } catch (Exception e) {
  271. failRecords.add(newError(i + 1, " 系统异常"));
  272. log.error("导入异常", e);
  273. }
  274. }
  275. if (CollectionUtils.isNotEmpty(failRecords)) {
  276. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  277. }
  278. if (CollectionUtils.isEmpty(failRecords)) {
  279. new Thread(() -> {
  280. checkStudentApplyFinish(applyList);
  281. }).start();
  282. }
  283. // TODO 更新redis
  284. return failRecords;
  285. }
  286. private void checkStudentApplyFinish(List<StudentImportVO> applyList) {
  287. for (StudentImportVO importVO : applyList) {
  288. StudentEntity student = studentService.getById(importVO.getStudentId());
  289. List<StudentApplyEntity> haveApplyList = listStudentApply(importVO.getStudentId(), Boolean.TRUE);
  290. if (student.getApplyNumber().intValue() == haveApplyList.size()) {
  291. // 更新考生的完成状态
  292. student.setApplyFinished(Boolean.TRUE);
  293. studentService.updateById(student);
  294. }
  295. }
  296. }
  297. private void checkInOpenTime() {
  298. ApplyTaskEntity task = getApplyTask();
  299. Date start = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyStartTime()), null);
  300. Date end = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
  301. if (!DateUtil.isBetwwen(start, end)) {
  302. throw new StatusException("导入预考,必须要在第一阶段导入!");
  303. }
  304. }
  305. private ApplyTaskEntity getApplyTask() {
  306. LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
  307. .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
  308. return applyTaskService.getOne(wrapper);
  309. }
  310. private void saveStdApply(Long userId, StudentImportVO vo) {
  311. List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
  312. LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
  313. lm.eq(StudentApplyEntity::getStudentId, vo.getStudentId());
  314. LogStdApply(userId, this.baseMapper.selectList(lm));
  315. this.baseMapper.delete(lm);
  316. for (AgentAndTimeVO agentTime : agentTimeList) {
  317. StudentApplyEntity entity = new StudentApplyEntity();
  318. entity.setStudentId(vo.getStudentId());
  319. entity.setCreateTime(System.currentTimeMillis());
  320. entity.setUpdateTime(System.currentTimeMillis());
  321. entity.setExamSiteId(agentTime.getAgentId());
  322. entity.setTimePeriodId(agentTime.getTimePeriodId());
  323. entity.setCancel(Boolean.FALSE);
  324. this.baseMapper.insert(entity);
  325. }
  326. }
  327. private void LogStdApply(Long userId, List<StudentApplyEntity> existList) {
  328. if (!existList.isEmpty()) {
  329. for (StudentApplyEntity studentApply : existList) {
  330. this.operateLogService.insertOperateLog(userId, EventType.DELETE_APPLY,
  331. JsonHelper.toJson(studentApply));
  332. }
  333. }
  334. }
  335. private Long checkTimePeriod(String timePeriod, Map<String, Long> timeCache) {
  336. if (timePeriod.split(" ").length != 2) {
  337. throw new StatusException(" 预约时段格式不正确");
  338. }
  339. String[] arr = timePeriod.split("-");
  340. String startTime = arr[0] + ":00";
  341. String endTime = startTime.substring(0, startTime.indexOf("日") + 1) + " " + arr[1] + ":00";
  342. Long startTimeLong = DateUtil.getLongTimeByZHDate(startTime);
  343. Long endTimeLong = DateUtil.getLongTimeByZHDate(endTime);
  344. if (timeCache.get(startTimeLong + "-" + endTimeLong) == null) {
  345. throw new StatusException(" 预约时段不存在");
  346. }
  347. return timeCache.get(startTimeLong + "-" + endTimeLong);
  348. }
  349. private Map<String, Long> getTimePeriodCache() {
  350. Map<String, Long> map = new HashMap<>();
  351. LambdaQueryWrapper<TimePeriodEntity> lm = new LambdaQueryWrapper<>();
  352. List<TimePeriodEntity> timeList = timePeriodService.list(lm);
  353. for (TimePeriodEntity time : timeList) {
  354. map.put(time.getStartTime() + "-" + time.getEndTime(), time.getId());
  355. }
  356. return map;
  357. }
  358. private Map<String, Long> getTeachingCache() {
  359. LambdaQueryWrapper<CategoryEntity> lm = new LambdaQueryWrapper<>();
  360. lm.eq(CategoryEntity::getEnable, Boolean.TRUE);
  361. lm.eq(CategoryEntity::getLevel, CategoryLevel.TEACHING.getValue());
  362. List<CategoryEntity> categoryList = categoryService.list(lm);
  363. return categoryList.stream().collect(Collectors.toMap(CategoryEntity::getName, CategoryEntity::getId));
  364. }
  365. private Map<String, Long> getAgentCache(Long categoryId) {
  366. LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
  367. lm.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
  368. lm.eq(ExamSiteEntity::getCategoryId, categoryId);
  369. List<ExamSiteEntity> categoryList = examSiteService.list(lm);
  370. return categoryList.stream().collect(Collectors.toMap(ExamSiteEntity::getName, ExamSiteEntity::getId));
  371. }
  372. private StudentEntity checkStd(String studentCode, String name, String identityNumber) {
  373. LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
  374. lm.eq(StudentEntity::getStudentCode, studentCode);
  375. lm.eq(StudentEntity::getName, name);
  376. lm.eq(StudentEntity::getIdentityNumber, identityNumber);
  377. StudentEntity student = studentService.getOne(lm);
  378. if (student == null) {
  379. throw new StatusException(" 考生信息填写错误");
  380. }
  381. return student;
  382. }
  383. private Map<String, Object> newError(int lineNum, String msg) {
  384. Map<String, Object> map = new HashMap<>();
  385. map.put("lineNum", lineNum);
  386. map.put("msg", msg);
  387. return map;
  388. }
  389. private String trimAndNullIfBlank(String s) {
  390. if (StringUtils.isBlank(s)) {
  391. return null;
  392. }
  393. return s.trim();
  394. }
  395. @Transactional
  396. @Override
  397. public void autoAssign(Long taskId) {
  398. checkAfterOpenTime();
  399. try {
  400. // 1、未完成预约的考生
  401. LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
  402. lm.eq(StudentEntity::getApplyFinished, Boolean.FALSE);
  403. List<StudentEntity> studentList = studentService.list(lm);
  404. Map<Long, List<StudentEntity>> map = studentList.stream()
  405. .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
  406. // 2、考位是否充足
  407. List<TimePeriodEntity> timeList = listTimePeroid(taskId);
  408. checkTeachingCapacity(map, timeList, taskId);
  409. // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
  410. for (Long key : map.keySet()) {
  411. List<ExamSiteEntity> siteList = listExamSite(key);
  412. List<StudentEntity> teachingStudentList = map.get(key);
  413. for (TimePeriodEntity time : timeList) {
  414. for (ExamSiteEntity site : siteList) {
  415. // 该时段已预约的考生
  416. Integer haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
  417. // 剩余的考位
  418. Integer remainNum = site.getCapacity() - haveApplyNum;
  419. assignStudentApply(site.getId(), time.getId(), teachingStudentList, remainNum);
  420. }
  421. }
  422. // 4、判断是否还有剩余考生未完成预约,提醒考位不够
  423. if (teachingStudentList.size() > 0)
  424. throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
  425. }
  426. } catch (Exception e) {
  427. log.error(e.getMessage());
  428. throw new StatusException(e.getMessage());
  429. } finally {
  430. }
  431. }
  432. private void assignStudentApply(Long siteId, Long timeId, List<StudentEntity> teachingStudentList,
  433. Integer remainNum) {
  434. int num = 0;
  435. for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
  436. StudentEntity student = iterator.next();
  437. if (num >= remainNum)
  438. break;
  439. List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId(), Boolean.FALSE);
  440. int toApplyNum = student.getApplyNumber() - studentApplyList.size();
  441. if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
  442. StudentApplyEntity studentApply = new StudentApplyEntity();
  443. studentApply.setCreateTime(System.currentTimeMillis());
  444. studentApply.setUpdateTime(System.currentTimeMillis());
  445. studentApply.setStudentId(student.getId());
  446. studentApply.setExamSiteId(siteId);
  447. studentApply.setCancel(Boolean.FALSE);
  448. studentApply.setTimePeriodId(timeId);
  449. baseMapper.insert(studentApply);
  450. num++;
  451. if (toApplyNum - (studentApplyList.size() + 1) == 0) {
  452. iterator.remove();
  453. student.setApplyFinished(true);
  454. this.studentService.updateById(student);
  455. }
  456. }
  457. }
  458. }
  459. private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
  460. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  461. wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
  462. wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
  463. wrapper.eq(StudentApplyEntity::getStudentId, studentId);
  464. wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
  465. StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
  466. return studentApply == null ? false : true;
  467. }
  468. private Integer getHaveApplyNum(Long siteId, Long timeId) {
  469. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  470. wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
  471. wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
  472. wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
  473. List<StudentApplyEntity> haveAplyList = baseMapper.selectList(wrapper);
  474. return haveAplyList == null ? 0 : haveAplyList.size();
  475. }
  476. private void checkAfterOpenTime() {
  477. ApplyTaskEntity task = getApplyTask();
  478. Date openEndTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
  479. Date selfStartTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getSelfApplyStartTime()), null);
  480. if (!DateUtil.isBetwwen(openEndTime, selfStartTime)) {
  481. throw new StatusException("自动分配,时间必须要在第一阶段结束之后,第三阶段开始之前");
  482. }
  483. }
  484. private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList,
  485. Long taskId) {
  486. for (Long key : map.keySet()) {
  487. List<ExamSiteEntity> siteList = listExamSite(key);
  488. // 总考位数量
  489. Integer total = siteList.stream().collect(Collectors.summingInt(ExamSiteEntity::getCapacity))
  490. * timeList.size();
  491. // 已经预约的数量
  492. Integer haveApplyNum = this.getBaseMapper().getHaveApplyCount(
  493. siteList.stream().map(site -> site.getId()).collect(Collectors.toList()), Boolean.FALSE);
  494. // 未预约的数量
  495. Integer noApplyNum = getNoApplyNum(map.get(key));
  496. if (noApplyNum > total - haveApplyNum) {
  497. CategoryEntity category = categoryService.getById(key);
  498. throw new StatusException("【" + category.getName() + "】教学点考位不足!剩余的考位数量:【" + (total - haveApplyNum)
  499. + "】,实际需要的考位数量:【" + noApplyNum + "】");
  500. }
  501. }
  502. }
  503. private Integer getNoApplyNum(List<StudentEntity> list) {
  504. Integer noApplyNum = 0;
  505. for (StudentEntity student : list) {
  506. if (student.getApplyNumber().intValue() == 1) {
  507. noApplyNum++;
  508. } else if (student.getApplyNumber().intValue() > 1) {
  509. noApplyNum = noApplyNum
  510. + (student.getApplyNumber() - listStudentApply(student.getId(), Boolean.TRUE).size());
  511. }
  512. }
  513. return noApplyNum;
  514. }
  515. private List<StudentApplyEntity> listStudentApply(Long stdId, Boolean cancel) {
  516. LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
  517. lm.eq(StudentApplyEntity::getStudentId, stdId);
  518. lm.eq(StudentApplyEntity::getCancel, cancel);
  519. return this.baseMapper.selectList(lm);
  520. }
  521. private List<TimePeriodEntity> listTimePeroid(Long taskId) {
  522. LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
  523. wrapper.eq(TimePeriodEntity::getApplyTaskId, taskId);
  524. wrapper.orderByAsc(TimePeriodEntity::getStartTime);
  525. List<TimePeriodEntity> timeList = timePeriodService.list(wrapper);
  526. if (timeList.isEmpty()) {
  527. throw new StatusException("考试时段未设置");
  528. }
  529. return timeList;
  530. }
  531. private List<ExamSiteEntity> listExamSite(Long categoryId) {
  532. LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
  533. wrapper.eq(ExamSiteEntity::getCategoryId, categoryId);
  534. return examSiteService.list(wrapper);
  535. }
  536. @Override
  537. public void autoLayout(Long teachingId) {
  538. ApplyTaskEntity applyTask = getApplyTask();
  539. if (applyTask == null) {
  540. log.info("没有开启的预约任务");
  541. return;
  542. }
  543. boolean isSuccess = redisClient.tryLock(
  544. CacheConstants.LOCK_ARRANGE_EXAM + DateUtil.formatShortDateString(new Date()), UUID.randomUUID(),
  545. TIMEOUT, TimeUnit.MINUTES);
  546. try {
  547. if (isSuccess) {
  548. // 1.根据当前日期,查询不能取消的时段
  549. List<TimePeriodEntity> timePeriodList = listTimePeroid(applyTask.getId());
  550. List<TimePeriodEntity> noCancelTimePeroidList = listNoCancelApplyTimePeroid(timePeriodList,
  551. applyTask.getAllowApplyCancelDays());
  552. if (noCancelTimePeroidList.isEmpty()) {
  553. log.info("当前时间,没有取消的时段。");
  554. return;
  555. }
  556. // 2.查询考试日期的待排考的考生
  557. List<StudentApplyEntity> toBeLayoutStudentList = this.baseMapper.listTimePeriod(
  558. noCancelTimePeroidList.stream().map(item -> item.getId()).collect(Collectors.toList()),
  559. Boolean.FALSE);
  560. // 3.开始排考
  561. Map<Long, List<StudentApplyEntity>> toBeLayoutStudentMap = toBeLayoutStudentList.stream()
  562. .collect(Collectors.groupingBy(StudentApplyEntity::getExamSiteId));
  563. for (Long examSiteId : toBeLayoutStudentMap.keySet()) {
  564. Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap = toBeLayoutStudentMap.get(examSiteId)
  565. .stream().collect(Collectors.groupingBy(StudentApplyEntity::getTimePeriodId));
  566. List<ExamRoomEntity> roomList = listExamRoom(examSiteId);
  567. if (roomList.isEmpty()) {
  568. throw new StatusException(examSiteId + ":未设置考场");
  569. }
  570. ExamSiteEntity examSite = examSiteService.getById(examSiteId);
  571. layoutStudentByTimePeriod(applyTask.getId(), examSite, roomList, timeLayoutStudentMap);
  572. }
  573. }
  574. } catch (StatusException e) {
  575. log.error(e.getMessage());
  576. e.printStackTrace();
  577. } finally {
  578. redisClient.delete(CacheConstants.LOCK_ARRANGE_EXAM + DateUtil.formatShortDateString(new Date()));
  579. }
  580. }
  581. private void layoutStudentByTimePeriod(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
  582. Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap) {
  583. for (Long timePeriodId : timeLayoutStudentMap.keySet()) {
  584. List<StudentApplyEntity> studentApplyList = timeLayoutStudentMap.get(timePeriodId);
  585. layoutStudentToRoom(taskId, examSite, roomList, studentApplyList, timePeriodService.getById(timePeriodId));
  586. }
  587. }
  588. private void layoutStudentToRoom(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
  589. List<StudentApplyEntity> studentApplyList, TimePeriodEntity timePeriod) {
  590. Integer timePeriodOrder = getTimePeriodOrder(taskId, timePeriod);
  591. for (ExamRoomEntity room : roomList) {
  592. Integer num = 0;
  593. for (Iterator<StudentApplyEntity> iterator = studentApplyList.iterator(); iterator.hasNext();) {
  594. StudentApplyEntity student = iterator.next();
  595. if (num >= room.getCapacity())
  596. break;
  597. String seatNumber = StringUtils.leftPad(String.valueOf(++num), 3, '0');
  598. student.setExamRoomId(room.getId());
  599. student.setSeatNumber(seatNumber);
  600. student.setTicketNumber(
  601. generateTicketNumber(timePeriodOrder, examSite.getCode(), room.getCode(), seatNumber));
  602. this.baseMapper.updateById(student);
  603. iterator.remove();
  604. }
  605. }
  606. }
  607. private Integer getTimePeriodOrder(Long taskId, TimePeriodEntity timePeriod) {
  608. List<TimePeriodEntity> timeList = listTimePeroid(taskId);
  609. List<TimePeriodEntity> sameDayTimeList = listSameDayTimePeroid(timeList, timePeriod.getStartTime());
  610. for (int i = 0; i < sameDayTimeList.size(); i++) {
  611. TimePeriodEntity time = sameDayTimeList.get(i);
  612. if (time.getStartTime().equals(timePeriod.getStartTime()))
  613. return i + 1;
  614. }
  615. return 0;
  616. }
  617. private List<TimePeriodEntity> listSameDayTimePeroid(List<TimePeriodEntity> timeList, Long startTime) {
  618. String day = DateUtil.getShortDateWithoutSplitByLongTime(startTime);
  619. List<TimePeriodEntity> resultList = new ArrayList<>();
  620. for (TimePeriodEntity time : timeList) {
  621. if (DateUtil.getShortDateWithoutSplitByLongTime(startTime).equals(day))
  622. resultList.add(time);
  623. }
  624. return resultList.stream().sorted(Comparator.comparing(TimePeriodEntity::getStartTime))
  625. .collect(Collectors.toList());
  626. }
  627. private String generateTicketNumber(Integer timePeriodOrder, String examSiteCode, String roomCode,
  628. String seatNumber) {
  629. return DateUtil.formatShortDateString(new Date()) + timePeriodOrder + examSiteCode + roomCode + seatNumber;
  630. }
  631. public List<ExamRoomEntity> listExamRoom(Long examSiteId) {
  632. LambdaQueryWrapper<ExamRoomEntity> wrapper = new LambdaQueryWrapper<>();
  633. wrapper.eq(ExamRoomEntity::getExamSiteId, examSiteId);
  634. wrapper.orderByAsc(ExamRoomEntity::getCode);
  635. return examRoomService.list(wrapper);
  636. }
  637. private List<TimePeriodEntity> listNoCancelApplyTimePeroid(List<TimePeriodEntity> list,
  638. Integer allowApplyCancelDays) {
  639. String noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
  640. List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
  641. for (TimePeriodEntity time : list) {
  642. if (DateUtil.getShortDateWithoutSplitByLongTime(time.getStartTime()).equals(noCancelDate))
  643. noCancelTimePeroidList.add(time);
  644. }
  645. return noCancelTimePeroidList;
  646. }
  647. private String getNoCancelApplyDate(Integer allowApplyCancelDays) {
  648. return DateUtil.formatShortDateString(DateUtil.addValues(DateField.DAY.getValue(), allowApplyCancelDays));
  649. }
  650. }