StudentApplyServiceImpl.java 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. package com.qmth.exam.reserve.service.impl;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.time.LocalDate;
  6. import java.time.ZoneId;
  7. import java.util.ArrayList;
  8. import java.util.Arrays;
  9. import java.util.Calendar;
  10. import java.util.Comparator;
  11. import java.util.Date;
  12. import java.util.HashMap;
  13. import java.util.Iterator;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.stream.Collectors;
  17. import org.apache.commons.lang3.StringUtils;
  18. import org.apache.commons.lang3.time.DateUtils;
  19. import org.redisson.api.RLock;
  20. import org.slf4j.Logger;
  21. import org.slf4j.LoggerFactory;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.stereotype.Service;
  24. import org.springframework.transaction.annotation.Transactional;
  25. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  26. import com.baomidou.mybatisplus.core.metadata.IPage;
  27. import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  28. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  29. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  30. import com.qmth.boot.core.collection.PageResult;
  31. import com.qmth.boot.core.concurrent.service.ConcurrentService;
  32. import com.qmth.boot.core.exception.StatusException;
  33. import com.qmth.boot.tools.excel.ExcelReader;
  34. import com.qmth.boot.tools.excel.enums.ExcelType;
  35. import com.qmth.boot.tools.excel.model.DataMap;
  36. import com.qmth.boot.tools.io.ZipWriter;
  37. import com.qmth.exam.reserve.bean.Constants;
  38. import com.qmth.exam.reserve.bean.apply.ApplyRecordCacheBean;
  39. import com.qmth.exam.reserve.bean.login.LoginUser;
  40. import com.qmth.exam.reserve.bean.stdapply.AgentAndTimeVO;
  41. import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
  42. import com.qmth.exam.reserve.bean.stdapply.MaterialTitleInfo;
  43. import com.qmth.exam.reserve.bean.stdapply.SignInVO;
  44. import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
  45. import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
  46. import com.qmth.exam.reserve.bean.stdapply.StudentImportVO;
  47. import com.qmth.exam.reserve.cache.CacheConstants;
  48. import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
  49. import com.qmth.exam.reserve.dao.StudentApplyDao;
  50. import com.qmth.exam.reserve.entity.ApplyTaskEntity;
  51. import com.qmth.exam.reserve.entity.CategoryEntity;
  52. import com.qmth.exam.reserve.entity.ExamRoomEntity;
  53. import com.qmth.exam.reserve.entity.ExamSiteEntity;
  54. import com.qmth.exam.reserve.entity.StudentApplyEntity;
  55. import com.qmth.exam.reserve.entity.StudentEntity;
  56. import com.qmth.exam.reserve.entity.TimePeriodEntity;
  57. import com.qmth.exam.reserve.enums.CategoryLevel;
  58. import com.qmth.exam.reserve.enums.EventType;
  59. import com.qmth.exam.reserve.service.ApplyTaskService;
  60. import com.qmth.exam.reserve.service.CategoryService;
  61. import com.qmth.exam.reserve.service.ExamRoomService;
  62. import com.qmth.exam.reserve.service.ExamSiteService;
  63. import com.qmth.exam.reserve.service.MaterialGenerateService;
  64. import com.qmth.exam.reserve.service.OperateLogService;
  65. import com.qmth.exam.reserve.service.StudentApplyService;
  66. import com.qmth.exam.reserve.service.StudentService;
  67. import com.qmth.exam.reserve.service.TimePeriodService;
  68. import com.qmth.exam.reserve.util.DateUtil;
  69. import com.qmth.exam.reserve.util.JsonHelper;
  70. import com.qmth.exam.reserve.util.PageUtil;
  71. @Service
  72. public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity>
  73. implements StudentApplyService {
  74. private final static Logger log = LoggerFactory.getLogger(StudentApplyServiceImpl.class);
  75. private static final String[] EXCEL_HEADER = new String[] { "学号", "姓名", "证件号", "所属教学点", "预约考点1", "预约时段1", "预约考点2",
  76. "预约时段2", "预约考点3", "预约时段3", "预约考点4", "预约时段4" };
  77. @Autowired
  78. private TimePeriodService timePeriodService;
  79. @Autowired
  80. private ApplyTaskService applyTaskService;
  81. @Autowired
  82. private CategoryService categoryService;
  83. @Autowired
  84. private StudentService studentService;
  85. @Autowired
  86. private ExamSiteService examSiteService;
  87. @Autowired
  88. private OperateLogService operateLogService;
  89. @Autowired
  90. private ExamRoomService examRoomService;
  91. @Autowired
  92. private MaterialGenerateService materialService;
  93. @Autowired
  94. private ConcurrentService concurrentService;
  95. @Autowired
  96. private ApplyTaskCacheService cacheService;
  97. @Override
  98. public PageResult<StudentApplyVO> page(StudentApplyReq req) {
  99. if (req.getTaskId() == null) {
  100. ApplyTaskEntity task = getApplyTask();
  101. req.setTaskId(task.getId());
  102. }
  103. if (req.getTeachingId() != null) {
  104. List<CategoryVO> listExamSite = examSiteService.listExamSite(req.getTeachingId());
  105. List<Long> examSiteIds = listExamSite.stream().map(CategoryVO::getId).collect(Collectors.toList());
  106. if (!examSiteIds.isEmpty()) {
  107. req.setExamSiteIds(examSiteIds);
  108. }
  109. }
  110. IPage<StudentApplyVO> iPage = this.baseMapper
  111. .page(new Page<StudentApplyVO>(req.getPageNumber(), req.getPageSize()), req);
  112. return PageUtil.of(iPage);
  113. }
  114. @Transactional
  115. @Override
  116. public void cancel(LoginUser user, Long id) {
  117. // 时间判断
  118. StudentApplyEntity studentApply = this.baseMapper.selectById(id);
  119. if (studentApply == null || studentApply.getTimePeriodId() == null) {
  120. throw new StatusException("考生没有预约,无法取消!");
  121. }
  122. TimePeriodEntity timePeriod = timePeriodService.getById(studentApply.getTimePeriodId());
  123. if (timePeriod == null) {
  124. throw new StatusException("考试时段不存在,请检查考试时段数据!");
  125. }
  126. ApplyTaskEntity task = getApplyTask();
  127. Date applyDate = DateUtils.truncate(new Date(timePeriod.getStartTime()), Calendar.DATE);
  128. Date canCancelDay = DateUtil.addValues(applyDate, Calendar.DAY_OF_MONTH, -task.getAllowApplyCancelDays());
  129. if (new Date().after(canCancelDay)) {
  130. throw new StatusException("可取消时间已过,无法取消!");
  131. }
  132. String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, studentApply.getStudentId());
  133. RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
  134. try {
  135. if (!studentApplyLock.tryLock()) {
  136. log.warn("获取锁失败,同一个考生不允许同时操作预约!lockKey:{}", studentApplyLockKey);
  137. throw new StatusException(Constants.SYSTEM_BUSY);
  138. } else {
  139. log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
  140. studentApply.setCancel(Boolean.TRUE);
  141. this.baseMapper.updateById(studentApply);
  142. ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
  143. bean.setStudentId(studentApply.getStudentId());
  144. bean.setExamSiteId(studentApply.getExamSiteId());
  145. bean.setTimePeriodId(studentApply.getTimePeriodId());
  146. bean.setCancel(Boolean.TRUE);
  147. bean.setOperateId(user.getId());
  148. bean.setOperateTime(System.currentTimeMillis());
  149. // 先推送至预约队列
  150. cacheService.pushStudentApplyRecordQueue(bean);
  151. cacheService.saveStudentApplyRecord(bean);
  152. cacheService.decreaseApplyFinishCount(studentApply.getExamSiteId(), studentApply.getTimePeriodId());
  153. }
  154. } catch (Exception e) {
  155. throw new StatusException("取消预约失败,请稍后再试!", e);
  156. } finally {
  157. try {
  158. // 解锁前检查当前线程是否持有该锁
  159. if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
  160. studentApplyLock.unlock();
  161. log.info("解锁成功!lockKey:{}", studentApplyLockKey);
  162. }
  163. } catch (Exception e) {
  164. log.warn(e.getMessage());
  165. }
  166. }
  167. operateLogService.insertOperateLog(user.getId(), EventType.CANCEL_APPLY, JsonHelper.toJson(studentApply));
  168. }
  169. @Transactional
  170. @Override
  171. public List<Map<String, Object>> importPreExam(Long userId, Long teachingId, Integer level,
  172. InputStream inputStream) {
  173. List<DataMap> lineList;
  174. ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
  175. try {
  176. lineList = reader.getDataMapList();
  177. } catch (Exception e) {
  178. throw new StatusException("Excel 解析失败");
  179. }
  180. if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
  181. throw new StatusException("Excel表头错误");
  182. }
  183. if (CollectionUtils.isEmpty(lineList)) {
  184. throw new StatusException("Excel无内容");
  185. }
  186. ApplyTaskEntity task = getApplyTask();
  187. Date openStartTime = new Date(task.getOpenApplyStartTime());
  188. Date now = new Date();
  189. List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
  190. Map<String, Long> teachingCache = getTeachingCache(level);
  191. Map<String, Long> agentCache = getAgentCache();
  192. Map<String, TimePeriodEntity> timeCache = getTimePeriodCache();
  193. Map<Long, Long> examSiteCategoryCache = getExamSiteCategoryCache();
  194. List<StudentImportVO> applyList = new ArrayList<>();
  195. AgentAndTimeVO agentTime;
  196. for (int i = 0; i < lineList.size(); i++) {
  197. List<AgentAndTimeVO> agentTimeList = new ArrayList<>();
  198. DataMap line = lineList.get(i);
  199. StudentImportVO apply = new StudentImportVO();
  200. StringBuilder msg = new StringBuilder();
  201. String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
  202. if (StringUtils.isBlank(studentCode)) {
  203. msg.append(" 学号不能为空");
  204. }
  205. String name = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
  206. if (StringUtils.isBlank(name)) {
  207. msg.append(" 姓名不能为空");
  208. }
  209. String identityNumber = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
  210. if (StringUtils.isBlank(identityNumber)) {
  211. msg.append(" 证件号不能为空");
  212. }
  213. StudentEntity student = null;
  214. try {
  215. student = checkStd(studentCode, name, identityNumber, task);
  216. apply.setStudentId(student.getId());
  217. apply.setApplyNumber(student.getApplyNumber());
  218. } catch (StatusException e) {
  219. msg.append(" " + e.getMessage());
  220. failRecords.add(newError(i + 1, msg.toString()));
  221. continue;
  222. }
  223. String teachingName = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
  224. if (StringUtils.isBlank(teachingName)) {
  225. msg.append(" 所属教学点不能为空");
  226. }
  227. Long categoryId = teachingCache.get(teachingName);
  228. if (categoryId == null) {
  229. msg.append(" 所属教学点不存在");
  230. }
  231. if (categoryId != null && !student.getCategoryId().equals(categoryId)) {
  232. msg.append(" 导入的考生所属教学点和系统中的考生教学点不匹配");
  233. }
  234. if (categoryId != null && !categoryId.equals(teachingId)) {
  235. msg.append(" 不是本教学点的考生");
  236. }
  237. String agentName1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
  238. if (StringUtils.isBlank(agentName1)) {
  239. msg.append(" 预约考点1不能为空");
  240. }
  241. agentTime = new AgentAndTimeVO();
  242. Long agentId = agentCache.get(agentName1);
  243. Long applyCategoryId = null;
  244. if (agentId == null) {
  245. msg.append(" 预约考点1不存在");
  246. } else {
  247. applyCategoryId = examSiteCategoryCache.get(agentId);
  248. if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
  249. msg.append(" 未到自由预约时间,不允许预约其他教学点的考点");
  250. }
  251. }
  252. String timePeriod1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
  253. if (StringUtils.isBlank(timePeriod1)) {
  254. msg.append(" 预约时段1不能为空");
  255. }
  256. TimePeriodEntity timePeriod = null;
  257. try {
  258. timePeriod = checkTimePeriod(timePeriod1, timeCache);
  259. agentTime.setAgentId(agentId);
  260. agentTime.setTimePeriodId(timePeriod.getId());
  261. agentTime.setStartTime(timePeriod.getStartTime());
  262. agentTimeList.add(agentTime);
  263. } catch (StatusException e) {
  264. msg.append(" " + e.getMessage());
  265. }
  266. String agentName2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
  267. String timePeriod2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
  268. if (StringUtils.isBlank(agentName2) && StringUtils.isBlank(timePeriod2)) {
  269. apply.setAgentTimeList(agentTimeList);
  270. applyList.add(apply);
  271. if (msg.length() > 0)
  272. failRecords.add(newError(i + 1, msg.toString()));
  273. continue;
  274. } else {
  275. agentId = agentCache.get(agentName2);
  276. if (agentId == null) {
  277. msg.append(" 预约考点2不存在");
  278. } else {
  279. applyCategoryId = examSiteCategoryCache.get(agentId);
  280. if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
  281. msg.append(" 未到自由预约时间,不允许预约其他教学点的考点");
  282. }
  283. }
  284. try {
  285. timePeriod = checkTimePeriod(timePeriod2, timeCache);
  286. agentTime = new AgentAndTimeVO();
  287. agentTime.setAgentId(agentId);
  288. agentTime.setTimePeriodId(timePeriod.getId());
  289. agentTime.setStartTime(timePeriod.getStartTime());
  290. agentTimeList.add(agentTime);
  291. } catch (StatusException e) {
  292. msg.append(" " + e.getMessage());
  293. }
  294. }
  295. String agentName3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
  296. String timePeriod3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[9]));
  297. if (StringUtils.isBlank(agentName3) && StringUtils.isBlank(timePeriod3)) {
  298. apply.setAgentTimeList(agentTimeList);
  299. applyList.add(apply);
  300. if (msg.length() > 0)
  301. failRecords.add(newError(i + 1, msg.toString()));
  302. continue;
  303. } else {
  304. agentId = agentCache.get(agentName3);
  305. if (agentId == null) {
  306. msg.append(" 预约考点3不存在");
  307. } else {
  308. applyCategoryId = examSiteCategoryCache.get(agentId);
  309. if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
  310. msg.append(" 未到自由预约时间,不允许预约其他教学点的考点");
  311. }
  312. }
  313. try {
  314. timePeriod = checkTimePeriod(timePeriod3, timeCache);
  315. agentTime = new AgentAndTimeVO();
  316. agentTime.setAgentId(agentId);
  317. agentTime.setTimePeriodId(timePeriod.getId());
  318. agentTime.setStartTime(timePeriod.getStartTime());
  319. agentTimeList.add(agentTime);
  320. } catch (StatusException e) {
  321. msg.append(" " + e.getMessage());
  322. }
  323. }
  324. String agentName4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[10]));
  325. String timePeriod4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[11]));
  326. if (StringUtils.isBlank(agentName4) && StringUtils.isBlank(timePeriod4)) {
  327. apply.setAgentTimeList(agentTimeList);
  328. applyList.add(apply);
  329. if (msg.length() > 0)
  330. failRecords.add(newError(i + 1, msg.toString()));
  331. continue;
  332. } else {
  333. agentId = agentCache.get(agentName4);
  334. if (agentId == null) {
  335. msg.append(" 预约考点4不存在");
  336. } else {
  337. applyCategoryId = examSiteCategoryCache.get(agentId);
  338. if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
  339. msg.append(" 未到自由预约时间,不允许预约其他教学点的考点");
  340. }
  341. }
  342. try {
  343. timePeriod = checkTimePeriod(timePeriod4, timeCache);
  344. agentTime = new AgentAndTimeVO();
  345. agentTime.setAgentId(agentId);
  346. agentTime.setTimePeriodId(timePeriod.getId());
  347. agentTime.setStartTime(timePeriod.getStartTime());
  348. agentTimeList.add(agentTime);
  349. apply.setAgentTimeList(agentTimeList);
  350. applyList.add(apply);
  351. } catch (StatusException e) {
  352. msg.append(" " + e.getMessage());
  353. }
  354. }
  355. }
  356. checkStudentApplyTime(applyList, getApplyTask().getAllowApplyCancelDays(), failRecords);
  357. if (CollectionUtils.isNotEmpty(failRecords)) {
  358. // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  359. return failRecords;
  360. }
  361. checkAvailableTimePeriod(applyList);
  362. for (int i = 0; i < applyList.size(); i++) {
  363. StudentImportVO vo = applyList.get(i);
  364. try {
  365. saveStdApply(i, vo, userId, failRecords);
  366. } catch (StatusException e) {
  367. failRecords.add(newError(i + 1, " 系统异常"));
  368. log.error("导入异常", e);
  369. }
  370. }
  371. if (CollectionUtils.isNotEmpty(failRecords)) {
  372. // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  373. return failRecords;
  374. }
  375. return failRecords;
  376. }
  377. private Map<Long, Long> getExamSiteCategoryCache() {
  378. Map<Long, Long> cache = new HashMap<>();
  379. LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
  380. lm.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
  381. List<ExamSiteEntity> examSiteList = examSiteService.list(lm);
  382. examSiteList.forEach(site -> {
  383. cache.put(site.getId(), site.getCategoryId());
  384. });
  385. return cache;
  386. }
  387. private void checkAvailableTimePeriod(List<StudentImportVO> applyList) {
  388. for (StudentImportVO vo : applyList) {
  389. List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
  390. List<StudentApplyEntity> haveApplyList = listStudentApply(vo.getStudentId(), Boolean.FALSE);
  391. int studentApplyFinishCount = cacheService.getStudentApplyFinishCount(vo.getStudentId());
  392. if (studentApplyFinishCount == 0) {
  393. studentApplyFinishCount = haveApplyList.size();
  394. }
  395. agentTimeList.sort(Comparator.comparing(AgentAndTimeVO::getStartTime));
  396. List<AgentAndTimeVO> tobeInsertTimeList = new ArrayList<>();
  397. // 考生已经完成预约-不做处理
  398. if (studentApplyFinishCount >= vo.getApplyNumber()) {
  399. vo.setAgentTimeList(tobeInsertTimeList);
  400. continue;
  401. }
  402. // 只预约了部分
  403. if (studentApplyFinishCount > 0 && studentApplyFinishCount < vo.getApplyNumber()) {
  404. List<AgentAndTimeVO> availableList = listAvailableTime(haveApplyList, agentTimeList);
  405. // 需要填充的次数
  406. tobeInsertTimeList.addAll(availableList);
  407. }
  408. // 未预约
  409. if (studentApplyFinishCount == 0) {
  410. tobeInsertTimeList = agentTimeList;
  411. }
  412. vo.setAgentTimeList(tobeInsertTimeList);
  413. }
  414. }
  415. private List<AgentAndTimeVO> listAvailableTime(List<StudentApplyEntity> haveApplyList,
  416. List<AgentAndTimeVO> agentTimeList) {
  417. List<AgentAndTimeVO> availableList = new ArrayList<>();
  418. for (AgentAndTimeVO time : agentTimeList) {
  419. boolean flag = false;
  420. for (StudentApplyEntity apply : haveApplyList) {
  421. if (time.getTimePeriodId().equals(apply.getTimePeriodId())) {
  422. flag = true;
  423. break;
  424. }
  425. }
  426. if (!flag) {
  427. availableList.add(time);
  428. }
  429. }
  430. return availableList;
  431. }
  432. private void checkStudentApplyTime(List<StudentImportVO> applyList, Integer days,
  433. List<Map<String, Object>> failRecords) {
  434. for (int i = 0; i < applyList.size(); i++) {
  435. StudentImportVO vo = applyList.get(i);
  436. List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
  437. if (agentTimeList.size() > vo.getApplyNumber()) {
  438. failRecords.add(newError(i + 1, " 导入考生预约数量大于考生可预约的数量"));
  439. }
  440. boolean isDuplicate = agentTimeList.stream()
  441. .collect(Collectors.groupingBy(AgentAndTimeVO::getTimePeriodId, Collectors.counting())).entrySet()
  442. .stream().anyMatch(entry -> entry.getValue() > 1);
  443. if (isDuplicate) {
  444. failRecords.add(newError(i + 1, " 预约的时间不能相同"));
  445. }
  446. LocalDate day = LocalDate.now().plusDays(days);
  447. long epochMilli = day.plusDays(1).atStartOfDay(ZoneId.systemDefault()).minusSeconds(1).toInstant()
  448. .toEpochMilli();
  449. for (AgentAndTimeVO time : agentTimeList) {
  450. if (time.getStartTime() <= epochMilli) {
  451. failRecords.add(newError(i + 1, " 预约的考试时间,必须在可取消的时间之后"));
  452. }
  453. }
  454. }
  455. }
  456. private ApplyTaskEntity getApplyTask() {
  457. LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
  458. .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
  459. ApplyTaskEntity task = applyTaskService.getOne(wrapper);
  460. if (task == null)
  461. throw new StatusException("未开启预约任务");
  462. return task;
  463. }
  464. private List<StudentApplyEntity> saveStdApply(int row, StudentImportVO vo, Long userId,
  465. List<Map<String, Object>> failRecords) {
  466. List<StudentApplyEntity> ApplyList = new ArrayList<>();
  467. List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
  468. String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, vo.getStudentId());
  469. RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
  470. try {
  471. if (!studentApplyLock.tryLock()) {
  472. log.warn("获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
  473. } else {
  474. log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
  475. for (AgentAndTimeVO agentTime : agentTimeList) {
  476. // 已经预约的容量和考点的容量从redis中获取
  477. int haveApplyCount = cacheService.getApplyFinishCount(agentTime.getAgentId(),
  478. agentTime.getTimePeriodId());
  479. int examSiteCount = cacheService.getApplyTotalCount(agentTime.getAgentId());
  480. if (haveApplyCount >= examSiteCount) {
  481. ExamSiteEntity examSite = examSiteService.getById(agentTime.getAgentId());
  482. failRecords.add(newError(row + 1, " 考点【" + examSite.getName() + "】的容量已满"));
  483. } else {
  484. StudentApplyEntity entity = new StudentApplyEntity();
  485. entity.setStudentId(vo.getStudentId());
  486. entity.setExamSiteId(agentTime.getAgentId());
  487. entity.setTimePeriodId(agentTime.getTimePeriodId());
  488. entity.setCancel(Boolean.FALSE);
  489. entity.setOperateId(userId);
  490. StudentApplyEntity existStudentApply = findStudentApply(entity);
  491. if (existStudentApply != null) {
  492. existStudentApply.setCancel(Boolean.FALSE);
  493. baseMapper.updateById(existStudentApply);
  494. } else {
  495. baseMapper.insert(entity);
  496. }
  497. ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
  498. bean.setStudentId(vo.getStudentId());
  499. bean.setExamSiteId(agentTime.getAgentId());
  500. bean.setTimePeriodId(agentTime.getTimePeriodId());
  501. bean.setCancel(Boolean.FALSE);
  502. bean.setOperateId(userId);
  503. bean.setOperateTime(System.currentTimeMillis());
  504. // 先推送至预约队列
  505. cacheService.pushStudentApplyRecordQueue(bean);
  506. cacheService.saveStudentApplyRecord(bean);
  507. cacheService.increaseApplyFinishCount(agentTime.getAgentId(), agentTime.getTimePeriodId());
  508. }
  509. }
  510. }
  511. } catch (Exception e) {
  512. log.error("导入预考失败,错误原因:", e.getMessage());
  513. throw new StatusException("导入预考失败,请稍后再试!", e);
  514. } finally {
  515. try {
  516. if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
  517. studentApplyLock.unlock();
  518. log.info("解锁成功!lockKey:{}", studentApplyLockKey);
  519. }
  520. } catch (Exception e) {
  521. log.warn(e.getMessage());
  522. }
  523. }
  524. return ApplyList;
  525. }
  526. private TimePeriodEntity checkTimePeriod(String timePeriod, Map<String, TimePeriodEntity> timeCache) {
  527. if (timePeriod == null || timePeriod.split(" ").length != 2) {
  528. throw new StatusException(" 预约时段格式不正确");
  529. }
  530. String[] arr = timePeriod.split("-");
  531. String startTime = arr[0] + ":00";
  532. String endTime = startTime.substring(0, startTime.indexOf("日") + 1) + " " + arr[1] + ":00";
  533. Long startTimeLong = DateUtil.getLongTimeByZHDate(startTime);
  534. Long endTimeLong = DateUtil.getLongTimeByZHDate(endTime);
  535. if (timeCache.get(startTimeLong + "-" + endTimeLong) == null) {
  536. throw new StatusException(" 预约时段不存在");
  537. }
  538. return timeCache.get(startTimeLong + "-" + endTimeLong);
  539. }
  540. private Map<String, TimePeriodEntity> getTimePeriodCache() {
  541. Map<String, TimePeriodEntity> map = new HashMap<>();
  542. LambdaQueryWrapper<TimePeriodEntity> lm = new LambdaQueryWrapper<>();
  543. lm.eq(TimePeriodEntity::getApplyTaskId, getApplyTask().getId());
  544. List<TimePeriodEntity> timeList = timePeriodService.list(lm);
  545. for (TimePeriodEntity time : timeList) {
  546. map.put(time.getStartTime() + "-" + time.getEndTime(), time);
  547. }
  548. return map;
  549. }
  550. private Map<String, Long> getTeachingCache(Integer level) {
  551. LambdaQueryWrapper<CategoryEntity> lm = new LambdaQueryWrapper<>();
  552. lm.eq(CategoryEntity::getEnable, Boolean.TRUE);
  553. lm.eq(CategoryEntity::getLevel, level == null ? CategoryLevel.TEACHING.getValue() : level);
  554. List<CategoryEntity> categoryList = categoryService.list(lm);
  555. return categoryList.stream().collect(Collectors.toMap(CategoryEntity::getName, CategoryEntity::getId));
  556. }
  557. private Map<String, Long> getAgentCache() {
  558. LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
  559. lm.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
  560. List<ExamSiteEntity> categoryList = examSiteService.list(lm);
  561. return categoryList.stream().collect(Collectors.toMap(ExamSiteEntity::getName, ExamSiteEntity::getId));
  562. }
  563. private StudentEntity checkStd(String studentCode, String name, String identityNumber, ApplyTaskEntity task) {
  564. LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
  565. lm.eq(StudentEntity::getStudentCode, studentCode);
  566. lm.eq(StudentEntity::getName, name);
  567. lm.eq(StudentEntity::getIdentityNumber, identityNumber);
  568. lm.eq(StudentEntity::getApplyTaskId, task.getId());
  569. StudentEntity student = studentService.getOne(lm);
  570. if (student == null) {
  571. throw new StatusException(" 考生信息填写错误");
  572. }
  573. return student;
  574. }
  575. private Map<String, Object> newError(int lineNum, String msg) {
  576. Map<String, Object> map = new HashMap<>();
  577. map.put("lineNum", lineNum);
  578. map.put("msg", msg);
  579. return map;
  580. }
  581. private String trimAndNullIfBlank(String s) {
  582. if (StringUtils.isBlank(s)) {
  583. return null;
  584. }
  585. return s.trim();
  586. }
  587. @Override
  588. public void autoAssign(Long taskId, Long userId) {
  589. checkAfterOpenTime();
  590. RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
  591. try {
  592. if (!lock.tryLock()) {
  593. log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
  594. throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
  595. }
  596. log.warn("获取锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
  597. // 1、未完成预约的考生
  598. List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
  599. Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream()
  600. .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
  601. // 2、考位是否充足
  602. List<TimePeriodEntity> timeList = listTimePeriod(taskId);
  603. timeList = listNoCancelExamTimePeriod(timeList, taskId);
  604. checkTeachingCapacity(noFinishApplyMap, timeList, taskId);
  605. // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
  606. for (Long key : noFinishApplyMap.keySet()) {
  607. List<ExamSiteEntity> siteList = listExamSite(key, null);
  608. List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
  609. for (ExamSiteEntity site : siteList) {
  610. for (TimePeriodEntity time : timeList) {
  611. // 该时段已预约的考生
  612. Integer haveApplyNum = cacheService.getApplyFinishCount(site.getId(), time.getId());
  613. if (haveApplyNum == 0) {
  614. haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
  615. }
  616. // 剩余的考位
  617. Integer remainNum = site.getCapacity() - haveApplyNum;
  618. assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
  619. }
  620. }
  621. // 4、判断是否还有剩余考生未完成预约,提醒考位不够
  622. if (!teachingStudentList.isEmpty())
  623. throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
  624. }
  625. } catch (Exception e) {
  626. log.error(e.getMessage());
  627. throw new StatusException(e.getMessage());
  628. } finally {
  629. try {
  630. // 解锁前检查当前线程是否持有该锁
  631. if (lock.isLocked() && lock.isHeldByCurrentThread()) {
  632. lock.unlock();
  633. log.info("解锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
  634. }
  635. } catch (Exception e) {
  636. log.warn(e.getMessage());
  637. }
  638. }
  639. }
  640. private List<TimePeriodEntity> listNoCancelExamTimePeriod(List<TimePeriodEntity> timeList, Long taskId) {
  641. ApplyTaskEntity task = applyTaskService.getById(taskId);
  642. Long longToday = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(new Date()) + " 00:00:00");
  643. Date today = new Date(longToday);
  644. Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, task.getAllowApplyCancelDays());
  645. Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59");
  646. return timeList.stream().filter(time -> time.getStartTime() > longOtherDay).collect(Collectors.toList());
  647. }
  648. private List<StudentApplyEntity> assignStudentApply(Long userId, Long siteId, Long timeId,
  649. List<StudentEntity> teachingStudentList, Integer remainNum) {
  650. List<StudentApplyEntity> insertApplyList = new ArrayList<>();
  651. int num = 0;
  652. String studentApplyLockKey = null;
  653. RLock studentApplyLock = null;
  654. for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
  655. StudentEntity student = iterator.next();
  656. if (num >= remainNum)
  657. break;
  658. studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
  659. studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
  660. try {
  661. if (!studentApplyLock.tryLock()) {
  662. log.warn("获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
  663. iterator.remove();
  664. } else {
  665. log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
  666. List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId(), Boolean.FALSE);
  667. int toApplyNum = student.getApplyNumber() - studentApplyList.size();
  668. if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
  669. StudentApplyEntity studentApply = new StudentApplyEntity();
  670. studentApply.setStudentId(student.getId());
  671. studentApply.setExamSiteId(siteId);
  672. studentApply.setCancel(Boolean.FALSE);
  673. studentApply.setTimePeriodId(timeId);
  674. studentApply.setOperateId(userId);
  675. StudentApplyEntity existStudentApply = findStudentApply(studentApply);
  676. if (existStudentApply != null) {
  677. existStudentApply.setCancel(Boolean.FALSE);
  678. baseMapper.updateById(existStudentApply);
  679. } else {
  680. baseMapper.insert(studentApply);
  681. }
  682. insertApplyList.add(studentApply);
  683. num++;
  684. if (student.getApplyNumber() - (studentApplyList.size() + 1) == 0) {
  685. iterator.remove();
  686. }
  687. ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
  688. bean.setStudentId(studentApply.getStudentId());
  689. bean.setExamSiteId(studentApply.getExamSiteId());
  690. bean.setTimePeriodId(studentApply.getTimePeriodId());
  691. bean.setCancel(Boolean.FALSE);
  692. bean.setOperateId(userId);
  693. bean.setOperateTime(System.currentTimeMillis());
  694. // 先推送至预约队列
  695. cacheService.pushStudentApplyRecordQueue(bean);
  696. cacheService.saveStudentApplyRecord(bean);
  697. cacheService.increaseApplyFinishCount(studentApply.getExamSiteId(),
  698. studentApply.getTimePeriodId());
  699. }
  700. }
  701. } catch (Exception e) {
  702. log.error("自动安排预约失败,错误信息:{}", e.getMessage());
  703. throw new StatusException("自动安排预约失败,请稍后再试!");
  704. } finally {
  705. try {
  706. // 解锁前检查当前线程是否持有该锁
  707. if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
  708. studentApplyLock.unlock();
  709. log.info("解锁成功!lockKey:{}", studentApplyLockKey);
  710. }
  711. } catch (Exception e) {
  712. log.warn(e.getMessage());
  713. }
  714. }
  715. }
  716. return insertApplyList;
  717. }
  718. private StudentApplyEntity findStudentApply(StudentApplyEntity studentApply) {
  719. LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
  720. lm.eq(StudentApplyEntity::getExamSiteId, studentApply.getExamSiteId());
  721. lm.eq(StudentApplyEntity::getTimePeriodId, studentApply.getTimePeriodId());
  722. lm.eq(StudentApplyEntity::getStudentId, studentApply.getStudentId());
  723. return baseMapper.selectOne(lm);
  724. }
  725. private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
  726. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  727. wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
  728. wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
  729. wrapper.eq(StudentApplyEntity::getStudentId, studentId);
  730. wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
  731. StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
  732. return studentApply != null;
  733. }
  734. private Integer getHaveApplyNum(Long siteId, Long timeId) {
  735. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  736. wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
  737. wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
  738. wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
  739. return baseMapper.selectCount(wrapper);
  740. }
  741. private void checkAfterOpenTime() {
  742. ApplyTaskEntity task = getApplyTask();
  743. Date selfEndTime = new Date(task.getSelfApplyEndTime());
  744. Date openStartTime = new Date(task.getOpenApplyStartTime());
  745. if (!DateUtil.isBetwwen(selfEndTime, openStartTime)) {
  746. throw new StatusException("自动分配,时间必须要在第一阶段结束之后,第三阶段开始之前");
  747. }
  748. }
  749. private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList,
  750. Long taskId) {
  751. for (Long key : map.keySet()) {
  752. List<ExamSiteEntity> siteList = listExamSite(key, null);
  753. // 总考位数量
  754. Integer total = siteList.stream().collect(Collectors.summingInt(ExamSiteEntity::getCapacity))
  755. * timeList.size();
  756. // 已经预约的数量
  757. Integer haveApplyNum = 0;
  758. for (ExamSiteEntity site : siteList) {
  759. haveApplyNum += cacheService.getApplyTotalCount(site.getId());
  760. }
  761. if (haveApplyNum == 0) {
  762. haveApplyNum = getBaseMapper().getHaveApplyCount(
  763. siteList.stream().map(site -> site.getId()).collect(Collectors.toList()), Boolean.FALSE);
  764. }
  765. // 未预约的数量
  766. Integer noApplyNum = getNoApplyNum(map.get(key));
  767. if (noApplyNum > total - haveApplyNum) {
  768. CategoryEntity category = categoryService.getById(key);
  769. throw new StatusException("【" + category.getName() + "】教学点考位不足!剩余的考位数量:【" + (total - haveApplyNum)
  770. + "】,实际需要的考位数量:【" + noApplyNum + "】");
  771. }
  772. }
  773. }
  774. private Integer getNoApplyNum(List<StudentEntity> list) {
  775. int noApplyNum = 0;
  776. for (StudentEntity student : list) {
  777. if (student.getApplyNumber() == 1) {
  778. noApplyNum++;
  779. } else if (student.getApplyNumber() > 1) {
  780. // listStudentApply(student.getId(), Boolean.FALSE).size()
  781. int haveApplyNum = cacheService.getStudentApplyFinishCount(student.getId());
  782. noApplyNum = noApplyNum + (student.getApplyNumber() - haveApplyNum);
  783. }
  784. }
  785. return noApplyNum;
  786. }
  787. private List<StudentApplyEntity> listStudentApply(Long stdId, Boolean cancel) {
  788. LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
  789. lm.eq(StudentApplyEntity::getStudentId, stdId);
  790. lm.eq(StudentApplyEntity::getCancel, cancel);
  791. return this.baseMapper.selectList(lm);
  792. }
  793. private List<TimePeriodEntity> listTimePeriod(Long taskId) {
  794. LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
  795. wrapper.eq(TimePeriodEntity::getApplyTaskId, taskId);
  796. wrapper.orderByAsc(TimePeriodEntity::getStartTime);
  797. List<TimePeriodEntity> timeList = timePeriodService.list(wrapper);
  798. if (timeList.isEmpty()) {
  799. throw new StatusException("考试时段未设置");
  800. }
  801. return timeList;
  802. }
  803. private List<ExamSiteEntity> listExamSite(Long categoryId, Long examSiteId) {
  804. LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
  805. wrapper.eq(ExamSiteEntity::getCategoryId, categoryId);
  806. wrapper.eq(examSiteId != null, ExamSiteEntity::getId, examSiteId);
  807. wrapper.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
  808. return examSiteService.list(wrapper);
  809. }
  810. @Transactional
  811. @Override
  812. public void autoLayout(Long teachingId) {
  813. ApplyTaskEntity applyTask = getApplyTask();
  814. String autoLayoutLockKey = String.format(CacheConstants.LOCK_ARRANGE_EXAM,
  815. DateUtil.formatShortDateString(new Date()));
  816. RLock autoLayoutLock = (RLock) concurrentService.getLock(autoLayoutLockKey);
  817. try {
  818. if (!autoLayoutLock.tryLock()) {
  819. log.warn("获取锁失败,已有线程在执行排考!lockKey:{}", autoLayoutLock);
  820. return;
  821. }
  822. log.warn("获取锁成功!lockKey:{}", autoLayoutLockKey);
  823. // 1.根据当前日期,查询不能取消的时段
  824. List<TimePeriodEntity> timePeriodList = listTimePeriod(applyTask.getId());
  825. List<TimePeriodEntity> noCancelTimePeroidList = listNoCancelApplyTimePeriod(timePeriodList,
  826. applyTask.getAllowApplyCancelDays());
  827. if (noCancelTimePeroidList.isEmpty()) {
  828. log.warn("当前时间不在取消预约范围内");
  829. return;
  830. }
  831. // 2.查询考试日期的待排考的考生
  832. List<StudentApplyEntity> toBeLayoutStudentList = this.baseMapper.listTimePeriod(
  833. noCancelTimePeroidList.stream().map(item -> item.getId()).collect(Collectors.toList()),
  834. Boolean.FALSE);
  835. if (toBeLayoutStudentList == null || toBeLayoutStudentList.isEmpty()) {
  836. log.warn("没有待排考的考生");
  837. return;
  838. }
  839. // 3.开始排考
  840. Map<Long, List<StudentApplyEntity>> toBeLayoutStudentMap = toBeLayoutStudentList.stream()
  841. .collect(Collectors.groupingBy(StudentApplyEntity::getExamSiteId));
  842. for (Long examSiteId : toBeLayoutStudentMap.keySet()) {
  843. Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap = toBeLayoutStudentMap.get(examSiteId).stream()
  844. .collect(Collectors.groupingBy(StudentApplyEntity::getTimePeriodId));
  845. List<ExamRoomEntity> roomList = listExamRoom(examSiteId);
  846. if (roomList.isEmpty()) {
  847. log.warn("{}:未设置考场", examSiteId);
  848. return;
  849. }
  850. ExamSiteEntity examSite = examSiteService.getById(examSiteId);
  851. layoutStudentByTimePeriod(applyTask.getId(), examSite, roomList, timeLayoutStudentMap);
  852. }
  853. } catch (StatusException e) {
  854. log.error(e.getMessage());
  855. e.printStackTrace();
  856. } finally {
  857. try {
  858. // 解锁前检查当前线程是否持有该锁
  859. if (autoLayoutLock.isLocked() && autoLayoutLock.isHeldByCurrentThread()) {
  860. autoLayoutLock.unlock();
  861. log.info("解锁成功!lockKey:{}", autoLayoutLockKey);
  862. }
  863. } catch (Exception e) {
  864. log.warn(e.getMessage());
  865. }
  866. }
  867. }
  868. private void layoutStudentByTimePeriod(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
  869. Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap) {
  870. for (Long timePeriodId : timeLayoutStudentMap.keySet()) {
  871. List<StudentApplyEntity> studentApplyList = timeLayoutStudentMap.get(timePeriodId);
  872. layoutStudentToRoom(taskId, examSite, roomList, studentApplyList, timePeriodService.getById(timePeriodId));
  873. }
  874. }
  875. private void layoutStudentToRoom(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
  876. List<StudentApplyEntity> studentApplyList, TimePeriodEntity timePeriod) {
  877. Integer timePeriodOrder = getTimePeriodOrder(taskId, timePeriod);
  878. for (ExamRoomEntity room : roomList) {
  879. int num = 0;
  880. for (Iterator<StudentApplyEntity> iterator = studentApplyList.iterator(); iterator.hasNext();) {
  881. StudentApplyEntity student = iterator.next();
  882. if (num >= room.getCapacity())
  883. break;
  884. String seatNumber = StringUtils.leftPad(String.valueOf(++num), 3, '0');
  885. student.setExamRoomId(room.getId());
  886. student.setSeatNumber(seatNumber);
  887. student.setTicketNumber(
  888. generateTicketNumber(timePeriodOrder, examSite.getCode(), room.getCode(), seatNumber));
  889. this.baseMapper.updateById(student);
  890. iterator.remove();
  891. }
  892. }
  893. }
  894. private Integer getTimePeriodOrder(Long taskId, TimePeriodEntity timePeriod) {
  895. List<TimePeriodEntity> timeList = listTimePeriod(taskId);
  896. List<TimePeriodEntity> sameDayTimeList = listSameDayTimePeriod(timeList, timePeriod.getStartTime());
  897. for (int i = 0; i < sameDayTimeList.size(); i++) {
  898. TimePeriodEntity time = sameDayTimeList.get(i);
  899. if (time.getStartTime().equals(timePeriod.getStartTime()))
  900. return i + 1;
  901. }
  902. return 0;
  903. }
  904. private List<TimePeriodEntity> listSameDayTimePeriod(List<TimePeriodEntity> timeList, Long startTime) {
  905. Date day = new Date(startTime);
  906. List<TimePeriodEntity> resultList = new ArrayList<>();
  907. for (TimePeriodEntity time : timeList) {
  908. if (DateUtils.isSameDay(day, new Date(time.getStartTime())))
  909. resultList.add(time);
  910. }
  911. return resultList.stream().sorted(Comparator.comparing(TimePeriodEntity::getStartTime))
  912. .collect(Collectors.toList());
  913. }
  914. private String generateTicketNumber(Integer timePeriodOrder, String examSiteCode, String roomCode,
  915. String seatNumber) {
  916. return DateUtil.formatShortDateString(new Date()) + timePeriodOrder + examSiteCode + roomCode + seatNumber;
  917. }
  918. public List<ExamRoomEntity> listExamRoom(Long examSiteId) {
  919. LambdaQueryWrapper<ExamRoomEntity> wrapper = new LambdaQueryWrapper<>();
  920. wrapper.eq(ExamRoomEntity::getExamSiteId, examSiteId);
  921. wrapper.orderByAsc(ExamRoomEntity::getCode);
  922. return examRoomService.list(wrapper);
  923. }
  924. private List<TimePeriodEntity> listNoCancelApplyTimePeriod(List<TimePeriodEntity> list,
  925. Integer allowApplyCancelDays) {
  926. Date noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
  927. List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
  928. for (TimePeriodEntity time : list) {
  929. if (DateUtils.isSameDay(noCancelDate, new Date(time.getStartTime())))
  930. noCancelTimePeroidList.add(time);
  931. }
  932. return noCancelTimePeroidList;
  933. }
  934. private Date getNoCancelApplyDate(Integer allowApplyCancelDays) {
  935. return DateUtil.addValues(Calendar.DAY_OF_MONTH, allowApplyCancelDays);
  936. }
  937. @Override
  938. public File downloadSignIn(Long teachingId, Long agentId, Long examDate) {
  939. ApplyTaskEntity applyTask = getApplyTask();
  940. List<TimePeriodEntity> timePeriodList = listTimePeriod(applyTask.getId());
  941. List<TimePeriodEntity> noCancelTimePeroidList = listSameDayTimePeriod(timePeriodList, examDate);
  942. if (noCancelTimePeroidList.isEmpty()) {
  943. throw new StatusException("当前时间没有可下载的签到表");
  944. }
  945. List<ExamSiteEntity> siteList = listExamSite(teachingId, agentId);
  946. if (siteList.isEmpty())
  947. throw new StatusException("当前教学点下没有可用的考点");
  948. CategoryEntity category = categoryService.getById(teachingId);
  949. File tempFolder = new File("temp");
  950. if (!tempFolder.exists()) {
  951. tempFolder.mkdirs();
  952. }
  953. ZipWriter writer = null;
  954. File zipFile = new File(tempFolder, category.getName() + "签到表.zip");
  955. try {
  956. writer = ZipWriter.create(zipFile);
  957. List<File> fileList = new ArrayList<>();
  958. for (ExamSiteEntity site : siteList) {
  959. fileList.addAll(downloadByExamSite(writer, applyTask.getName(), site, noCancelTimePeroidList));
  960. }
  961. if (fileList.isEmpty()) {
  962. throw new StatusException("暂未排考,请等待排考后下载");
  963. }
  964. } catch (IOException e) {
  965. e.printStackTrace();
  966. throw new StatusException("文件写入异常");
  967. }
  968. writer.close();
  969. return zipFile;
  970. }
  971. public List<File> downloadByExamSite(ZipWriter writer, String taskName, ExamSiteEntity site,
  972. List<TimePeriodEntity> timeList) throws IOException {
  973. List<File> fileList = new ArrayList<>();
  974. List<ExamRoomEntity> roomList = listExamRoom(site.getId());
  975. MaterialTitleInfo title = new MaterialTitleInfo();
  976. title.setTaskName(taskName);
  977. title.setSiteName(site.getName());
  978. for (TimePeriodEntity time : timeList) {
  979. title.setTimePeriod(DateUtil.getStartAndEndTime(time.getStartTime(), time.getEndTime()));
  980. for (ExamRoomEntity room : roomList) {
  981. title.setAddress(room.getAddress());
  982. title.setRoomCode(room.getCode());
  983. List<StudentApplyVO> studentList = baseMapper.listStudentApply(time.getId(), room.getId());
  984. if (!studentList.isEmpty()) {
  985. File file = materialService.generateSignInForm(title, studentList);
  986. fileList.add(file);
  987. writer.write(file, title.getSiteName(),
  988. title.getTimePeriod() + " 第【" + title.getRoomCode() + "】考场" + ".pdf");
  989. }
  990. }
  991. }
  992. return fileList;
  993. }
  994. /**
  995. * 获取某考点某时段的“已预约数量”
  996. */
  997. @Override
  998. public int countApplyFinishForExamSiteAndTimePeriod(Long examSiteId, Long timePeriodId) {
  999. Integer value = baseMapper.countApplyFinishForExamSiteAndTimePeriod(examSiteId, timePeriodId);
  1000. return value != null ? value : 0;
  1001. }
  1002. @Override
  1003. public List<SignInVO> listSignInDate(Long taskId) {
  1004. List<SignInVO> signInList = new ArrayList<>();
  1005. ApplyTaskEntity task = null;
  1006. if (taskId != null) {
  1007. task = applyTaskService.getById(taskId);
  1008. } else {
  1009. LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
  1010. .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
  1011. task = applyTaskService.getOne(wrapper);
  1012. }
  1013. if (task == null) {
  1014. log.warn("当前没有开启的任务");
  1015. return signInList;
  1016. }
  1017. List<TimePeriodEntity> timePeriodList = listTimePeriod(task.getId());
  1018. if (timePeriodList.isEmpty()) {
  1019. log.warn("未配置考试时段");
  1020. return signInList;
  1021. }
  1022. Long startTime = timePeriodList.get(0).getStartTime();
  1023. Long endTime = timePeriodList.get(timePeriodList.size() - 1).getEndTime();
  1024. String todayStr = DateUtil.formatShortSplitDateString(new Date());
  1025. Long longToday = DateUtil.getLongTimeByDate(todayStr + " 00:00:00");
  1026. Date today = new Date(longToday);
  1027. if (longToday >= startTime && longToday <= endTime) {
  1028. SignInVO vo = new SignInVO();
  1029. vo.setExamDate(longToday);
  1030. signInList.add(vo);
  1031. }
  1032. for (int i = 1; i <= task.getAllowApplyCancelDays(); i++) {
  1033. Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, i);
  1034. Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 00:00:00");
  1035. if (longOtherDay >= startTime && longOtherDay <= endTime) {
  1036. SignInVO vo = new SignInVO();
  1037. vo.setExamDate(longOtherDay);
  1038. signInList.add(vo);
  1039. }
  1040. }
  1041. signInList.sort(Comparator.comparing(SignInVO::getExamDate));
  1042. return signInList;
  1043. }
  1044. }