ExamReserveServiceImpl.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. package com.qmth.exam.reserve.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  4. import com.qmth.boot.core.concurrent.service.ConcurrentService;
  5. import com.qmth.boot.core.exception.StatusException;
  6. import com.qmth.exam.reserve.bean.Constants;
  7. import com.qmth.exam.reserve.bean.RichTextBean;
  8. import com.qmth.exam.reserve.bean.apply.*;
  9. import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO;
  10. import com.qmth.exam.reserve.bean.category.CategoryCacheBean;
  11. import com.qmth.exam.reserve.bean.course.CourseVO;
  12. import com.qmth.exam.reserve.bean.examsite.ExamSiteCacheBean;
  13. import com.qmth.exam.reserve.bean.login.LoginUser;
  14. import com.qmth.exam.reserve.cache.CacheConstants;
  15. import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
  16. import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
  17. import com.qmth.exam.reserve.cache.impl.ExamSiteCacheService;
  18. import com.qmth.exam.reserve.dao.StudentApplyDao;
  19. import com.qmth.exam.reserve.entity.ApplyTaskEntity;
  20. import com.qmth.exam.reserve.entity.ExamSiteEntity;
  21. import com.qmth.exam.reserve.entity.StudentApplyEntity;
  22. import com.qmth.exam.reserve.entity.TimePeriodEntity;
  23. import com.qmth.exam.reserve.enums.ApplyStatus;
  24. import com.qmth.exam.reserve.service.*;
  25. import com.qmth.exam.reserve.util.DateUtil;
  26. import org.apache.commons.collections4.CollectionUtils;
  27. import org.apache.commons.collections4.MapUtils;
  28. import org.apache.commons.lang3.StringUtils;
  29. import org.apache.commons.lang3.time.FastDateFormat;
  30. import org.redisson.api.RLock;
  31. import org.slf4j.Logger;
  32. import org.slf4j.LoggerFactory;
  33. import org.springframework.beans.factory.annotation.Autowired;
  34. import org.springframework.stereotype.Service;
  35. import java.text.ParseException;
  36. import java.util.*;
  37. import java.util.stream.Collectors;
  38. @Service
  39. public class ExamReserveServiceImpl implements ExamReserveService {
  40. private static final Logger log = LoggerFactory.getLogger(ExamReserveServiceImpl.class);
  41. @Autowired
  42. private ApplyTaskCacheService applyTaskCacheService;
  43. @Autowired
  44. private ApplyTaskService applyTaskService;
  45. @Autowired
  46. private ExamSiteService examSiteService;
  47. @Autowired
  48. private ExamSiteCacheService examSiteCacheService;
  49. @Autowired
  50. private TimePeriodService timePeriodService;
  51. @Autowired
  52. private TimePeriodExamSiteService timePeriodExamSiteService;
  53. @Autowired
  54. private StudentApplyService studentApplyService;
  55. @Autowired
  56. private StudentCourseService studentCourseService;
  57. @Autowired
  58. private CategoryCacheService categoryCacheService;
  59. @Autowired
  60. private ConcurrentService concurrentService;
  61. @Autowired
  62. private SystemPropertyService systemPropertyService;
  63. @Override
  64. public void saveStudentApply(LoginUser student, Long examSiteId, Long timePeriodId) {
  65. if (examSiteId == null) {
  66. throw new StatusException("考点ID不能为空");
  67. }
  68. if (timePeriodId == null) {
  69. throw new StatusException("预约时段ID不能为空");
  70. }
  71. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  72. if (curApplyTask == null) {
  73. log.warn("预约失败,尚未开启预约任务!stuApplyTaskId:{}", student.getApplyTaskId());
  74. throw new StatusException("尚未开启预约任务");
  75. }
  76. if (curApplyTask.getSelfApplyStartTime() > System.currentTimeMillis()) {
  77. log.warn("预约失败,未到预约时间!selfApplyStartTime:{}", curApplyTask.getSelfApplyStartTime());
  78. throw new StatusException("未到预约时间");
  79. }
  80. if (curApplyTask.getOpenApplyEndTime() < System.currentTimeMillis()) {
  81. log.warn("预约失败,预约时间已结束!openApplyEndTime:{}", curApplyTask.getOpenApplyEndTime());
  82. throw new StatusException("预约时间已结束");
  83. }
  84. if (!curApplyTask.getTaskId().equals(student.getApplyTaskId())) {
  85. log.warn("预约失败,当前预约任务与学生的不匹配!curApplyTaskId:{} stuApplyTaskId:{}", curApplyTask.getTaskId(), student.getApplyTaskId());
  86. throw new StatusException("当前预约任务与学生的不匹配");
  87. }
  88. // 自主预约全局开关
  89. boolean globalOpen = systemPropertyService.existPropValue(curApplyTask.getOrgId(), Constants.APPLY_SWITCH, Constants.APPLY_SWITCH_OPEN);
  90. if (!globalOpen) {
  91. throw new StatusException("当前无法自主预约,请联系教学点老师进行操作");
  92. }
  93. CategoryCacheBean category = categoryCacheService.getCategoryById(student.getCategoryId());
  94. if (category == null || !category.getSelfApplyEnable()) {
  95. log.warn("预约失败,当前教学点尚未开启考生自主预约!categoryId:{}", student.getCategoryId());
  96. throw new StatusException("当前教学点尚未开启考生自主预约");
  97. }
  98. ExamSiteTimePeriodInfo timePeriod = timePeriodExamSiteService.findOneExamSiteTimePeriod(curApplyTask.getTaskId(), examSiteId, timePeriodId);
  99. if (timePeriod == null || !timePeriod.getEnable()) {
  100. log.warn("预约失败,当前预约时段不存在或未启用!timePeriodId:{}", timePeriodId);
  101. throw new StatusException("当前预约时段不存在或未启用");
  102. }
  103. // 考前N天,禁止考生自主预约
  104. Date allowDate = DateUtil.changeDateAndTimeEnd(new Date(), curApplyTask.getAllowApplyDays());
  105. Date curDate = new Date(timePeriod.getStartTime());
  106. if (curDate.before(allowDate)) {
  107. // 跳过过期时段,“当前时段开始时间”在“允许预约时间”之前,则禁止预约
  108. log.warn("预约失败,当前预约时段已禁止预约!allowApplyDays:{} timePeriodStart:{}",
  109. curApplyTask.getAllowApplyDays(), timePeriod.getStartTime());
  110. throw new StatusException("当前预约时段已禁止预约");
  111. }
  112. if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
  113. // 系统自动预约“任务执行期间”不允许预约
  114. log.warn("系统自动预约中,不允许考生操作预约!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
  115. throw new StatusException(Constants.SYSTEM_BUSY);
  116. }
  117. String capacityLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, examSiteId);
  118. if (concurrentService.isLocked(capacityLockKey)) {
  119. log.warn("考点剩余可约数量更新中,不允许考生操作预约!lockKey:{}", capacityLockKey);
  120. throw new StatusException(Constants.SYSTEM_BUSY);
  121. }
  122. // 考生预约操作锁
  123. String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
  124. RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
  125. try {
  126. if (!studentApplyLock.tryLock()) {
  127. log.warn("获取锁失败,同一个考生不允许同时操作预约!lockKey:{}", studentApplyLockKey);
  128. throw new StatusException(Constants.SYSTEM_BUSY);
  129. }
  130. log.info("获取锁成功!lockKey:{}", studentApplyLockKey);
  131. // 考生剩余预约次数 = 允许预约次数 - 已完成预约次数
  132. int studentApplyNumber = applyTaskCacheService.getStudentApplyNumber(student.getId());
  133. int studentApplyFinishCount = applyTaskCacheService.getStudentApplyFinishCount(student.getId());
  134. int unApplyNumber = studentApplyNumber - studentApplyFinishCount;
  135. if (unApplyNumber < 1) {
  136. String msg = "当前学生无剩余可约时段次数,已完成预约" + studentApplyNumber + "次";
  137. log.warn(msg);
  138. throw new StatusException(msg);
  139. }
  140. ApplyRecordCacheBean existApply = applyTaskCacheService.getStudentApplyRecord(student.getId(), examSiteId, timePeriodId);
  141. if (existApply != null && !existApply.getCancel()) {
  142. log.warn("当前时段已预约,请勿重复预约!examSiteId:{} timePeriodId:{}", examSiteId, timePeriodId);
  143. throw new StatusException("当前时段已预约,请勿重复预约");
  144. }
  145. // 某考点某时段的“剩余可约数量” 累减1(抢占1个数量)
  146. boolean takeSuccess = applyTaskCacheService.decreaseApplyAvailableCount(examSiteId, timePeriodId);
  147. if (!takeSuccess) {
  148. log.warn("预约失败,当前预约时段已约满!examSiteId:{} timePeriodId:{}", examSiteId, timePeriodId);
  149. throw new StatusException("当前预约时段已约满");
  150. }
  151. try {
  152. if (existApply != null) {
  153. // 存在历史“已取消”的预约记录,则恢复预约
  154. existApply.setCancel(false);
  155. existApply.setOperateId(student.getId());
  156. existApply.setOperateTime(System.currentTimeMillis());
  157. existApply.setBizId(applyTaskCacheService.increaseBizId());
  158. // 推送至预约队列
  159. boolean pushSuccess = applyTaskCacheService.pushStudentApplyRecordQueue(existApply);
  160. if (!pushSuccess) {
  161. throw new StatusException("预约消息推送失败,请稍后再试!");
  162. }
  163. // 保存至预约缓存
  164. applyTaskCacheService.saveStudentApplyRecord(existApply);
  165. } else {
  166. // 不存在则新增预约记录
  167. ApplyRecordCacheBean newApply = new ApplyRecordCacheBean();
  168. newApply.setStudentId(student.getId());
  169. newApply.setExamSiteId(examSiteId);
  170. newApply.setTimePeriodId(timePeriodId);
  171. newApply.setCancel(false);
  172. newApply.setOperateId(student.getId());
  173. newApply.setOperateTime(System.currentTimeMillis());
  174. newApply.setBizId(applyTaskCacheService.increaseBizId());
  175. // 推送至预约队列
  176. boolean pushSuccess = applyTaskCacheService.pushStudentApplyRecordQueue(newApply);
  177. if (!pushSuccess) {
  178. throw new StatusException("预约消息推送失败,请稍后再试!");
  179. }
  180. // 保存至预约缓存
  181. applyTaskCacheService.saveStudentApplyRecord(newApply);
  182. }
  183. log.warn("预约成功!studentId:{} examSiteId:{} timePeriodId:{}", student.getId(), examSiteId, timePeriodId);
  184. } catch (Exception e) {
  185. // 异常时,归还1个被占数量
  186. applyTaskCacheService.increaseApplyAvailableCount(examSiteId, timePeriodId);
  187. log.error("预约异常!examSiteId:{} timePeriodId:{} err:{}", examSiteId, timePeriodId, e.getMessage());
  188. throw new StatusException("预约失败,请稍后再试!", e);
  189. }
  190. } finally {
  191. try {
  192. // 解锁前检查当前线程是否持有该锁
  193. if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
  194. studentApplyLock.unlock();
  195. log.info("解锁成功!lockKey:{}", studentApplyLockKey);
  196. }
  197. } catch (Exception e) {
  198. log.warn("解锁失败!lockKey:{} err:{}", studentApplyLockKey, e.getMessage());
  199. }
  200. }
  201. }
  202. @Override
  203. public void cancelStudentApply(LoginUser student, Long examSiteId, Long timePeriodId) {
  204. if (examSiteId == null) {
  205. throw new StatusException("考点ID不能为空");
  206. }
  207. if (timePeriodId == null) {
  208. throw new StatusException("预约时段ID不能为空");
  209. }
  210. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  211. if (curApplyTask == null) {
  212. log.warn("取消预约失败,尚未开启预约任务!stuApplyTaskId:{}", student.getApplyTaskId());
  213. return;
  214. }
  215. if (!curApplyTask.getTaskId().equals(student.getApplyTaskId())) {
  216. log.warn("取消预约失败,当前预约任务与学生的不匹配!curApplyTaskId:{} stuApplyTaskId:{}", curApplyTask.getTaskId(), student.getApplyTaskId());
  217. return;
  218. }
  219. // 自主预约全局开关
  220. boolean globalOpen = systemPropertyService.existPropValue(curApplyTask.getOrgId(), Constants.APPLY_SWITCH, Constants.APPLY_SWITCH_OPEN);
  221. if (!globalOpen) {
  222. throw new StatusException("当前无法自主取消,请联系教学点老师进行操作");
  223. }
  224. TimePeriodEntity timePeriod = timePeriodService.getById(timePeriodId);
  225. if (timePeriod == null) {
  226. log.warn("取消预约失败,预约时段不存在!examSiteId:{} timePeriodId:{}", examSiteId, timePeriodId);
  227. return;
  228. }
  229. // 考前N天,禁止考生自主取消预约
  230. Date allowDate = DateUtil.changeDateAndTimeEnd(new Date(), curApplyTask.getAllowApplyCancelDays());
  231. Date curDate = new Date(timePeriod.getStartTime());
  232. if (curDate.before(allowDate)) {
  233. // “当前时段开始时间”在“允许取消时间”之前,禁止取消预约
  234. log.warn("取消预约失败,当前预约记录已禁止取消预约!allowApplyCancelDays:{} timePeriodStart:{}",
  235. curApplyTask.getAllowApplyCancelDays(), timePeriod.getStartTime());
  236. throw new StatusException("当前预约记录已禁止取消预约");
  237. }
  238. if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
  239. // 系统自动预约“任务执行期间”不允许取消预约
  240. log.warn("系统自动预约中,不允许考生操作预约!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
  241. throw new StatusException(Constants.SYSTEM_BUSY);
  242. }
  243. String capacityLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, examSiteId);
  244. if (concurrentService.isLocked(capacityLockKey)) {
  245. log.warn("考点剩余可约数量更新中,不允许考生操作预约!lockKey:{}", capacityLockKey);
  246. throw new StatusException(Constants.SYSTEM_BUSY);
  247. }
  248. // 考生预约操作锁
  249. String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
  250. RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
  251. try {
  252. if (!studentApplyLock.tryLock()) {
  253. log.warn("获取锁失败,同一个考生不允许同时操作预约!lockKey:{}", studentApplyLockKey);
  254. throw new StatusException(Constants.SYSTEM_BUSY);
  255. }
  256. log.info("获取锁成功!lockKey:{}", studentApplyLockKey);
  257. // StudentApplyEntity existApply = applyTaskCacheService.getStudentApplyRecordFormDB(student.getId(), examSiteId, timePeriodId);
  258. ApplyRecordCacheBean existApply = applyTaskCacheService.getStudentApplyRecord(student.getId(), examSiteId, timePeriodId);
  259. if (existApply == null) {
  260. log.warn("取消预约失败,预约记录不存在!examSiteId:{} timePeriodId:{}", examSiteId, timePeriodId);
  261. return;
  262. }
  263. if (existApply.getCancel()) {
  264. log.warn("当前时段已取消,忽略重复取消!examSiteId:{} timePeriodId:{}", examSiteId, timePeriodId);
  265. return;
  266. }
  267. existApply.setCancel(true);
  268. existApply.setOperateId(student.getId());
  269. existApply.setOperateTime(System.currentTimeMillis());
  270. existApply.setBizId(applyTaskCacheService.increaseBizId());
  271. // 推送至预约队列
  272. boolean pushSuccess = applyTaskCacheService.pushStudentApplyRecordQueue(existApply);
  273. if (!pushSuccess) {
  274. throw new StatusException("预约消息推送失败,请稍后再试!");
  275. }
  276. // 保存至预约缓存
  277. applyTaskCacheService.saveStudentApplyRecord(existApply);
  278. // 某考点某时段的“剩余可约数量” 累加1(归还1个被占数量)
  279. applyTaskCacheService.increaseApplyAvailableCount(examSiteId, timePeriodId);
  280. log.warn("取消预约成功!studentId:{} examSiteId:{} timePeriodId:{}", student.getId(), examSiteId, timePeriodId);
  281. } finally {
  282. try {
  283. // 解锁前检查当前线程是否持有该锁
  284. if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
  285. studentApplyLock.unlock();
  286. log.info("解锁成功!lockKey:{}", studentApplyLockKey);
  287. }
  288. } catch (Exception e) {
  289. log.warn("解锁失败!lockKey:{} err:{}", studentApplyLockKey, e.getMessage());
  290. }
  291. }
  292. }
  293. public void updateStudentApplyForCancel(Long applyId, boolean cancel, Long operateId) {
  294. LambdaUpdateWrapper<StudentApplyEntity> updateWrapper = new LambdaUpdateWrapper<>();
  295. updateWrapper.set(StudentApplyEntity::getCancel, cancel);
  296. updateWrapper.set(StudentApplyEntity::getOperateId, operateId);
  297. updateWrapper.set(StudentApplyEntity::getUpdateTime, System.currentTimeMillis());
  298. updateWrapper.eq(StudentApplyEntity::getId, applyId);
  299. studentApplyService.update(updateWrapper);
  300. }
  301. public void addStudentApply(ApplyRecordCacheBean bean) {
  302. StudentApplyEntity newApply = new StudentApplyEntity();
  303. newApply.setStudentId(bean.getStudentId());
  304. newApply.setExamSiteId(bean.getExamSiteId());
  305. newApply.setTimePeriodId(bean.getTimePeriodId());
  306. newApply.setOperateId(bean.getStudentId());
  307. newApply.setCancel(bean.getCancel());
  308. studentApplyService.save(newApply);
  309. }
  310. private boolean hasTicketNumber(Long studentId, Long examSiteId, Long timePeriodId) {
  311. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  312. wrapper.select(StudentApplyEntity::getTicketNumber);//只查询ticketNumber等字段
  313. wrapper.eq(StudentApplyEntity::getExamSiteId, examSiteId);
  314. wrapper.eq(StudentApplyEntity::getTimePeriodId, timePeriodId);
  315. wrapper.eq(StudentApplyEntity::getStudentId, studentId);
  316. StudentApplyEntity e = studentApplyService.getOne(wrapper);
  317. return e != null && StringUtils.isNotEmpty(e.getTicketNumber());
  318. }
  319. private Map<Long, TimePeriodEntity> getTimePeriods(Set<Long> timePeriodIds) {
  320. LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
  321. wrapper.select(TimePeriodEntity::getId, TimePeriodEntity::getStartTime, TimePeriodEntity::getEndTime);
  322. wrapper.in(TimePeriodEntity::getId, timePeriodIds);
  323. List<TimePeriodEntity> timePeriods = timePeriodService.list(wrapper);
  324. Map<Long, TimePeriodEntity> maps = new HashMap<>();
  325. for (TimePeriodEntity timePeriod : timePeriods) {
  326. maps.put(timePeriod.getId(), timePeriod);
  327. }
  328. return maps;
  329. }
  330. @Override
  331. public List<ApplyVO> getStudentApplyList(LoginUser student, Boolean cancel) {
  332. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  333. if (curApplyTask == null) {
  334. return new ArrayList<>();
  335. }
  336. StudentApplyDao baseMapper = (StudentApplyDao) studentApplyService.getBaseMapper();
  337. List<ApplyVO> list = baseMapper.getStudentApplyList(student.getId(), cancel);
  338. if (CollectionUtils.isEmpty(list)) {
  339. return new ArrayList<>();
  340. }
  341. // 自主预约全局开关
  342. boolean globalOpen = systemPropertyService.existPropValue(curApplyTask.getOrgId(), Constants.APPLY_SWITCH, Constants.APPLY_SWITCH_OPEN);
  343. // 考前N天,禁止考生自主取消预约
  344. Date allowDate = DateUtil.changeDateAndTimeEnd(new Date(), curApplyTask.getAllowApplyCancelDays());
  345. for (ApplyVO vo : list) {
  346. vo.setShowTicket(false);
  347. vo.setAllowCancel(false);
  348. if (!vo.getCancel()) {
  349. if (StringUtils.isNotEmpty(vo.getTicketNumber())) {
  350. // 准考证号已生成,则可查看
  351. vo.setShowTicket(true);
  352. }
  353. }
  354. Date curDate = new Date(vo.getTimePeriodStart());
  355. if (curDate.after(allowDate) && globalOpen) {
  356. // “当前时段开始时间”在“允许取消时间”之后,可以取消预约
  357. vo.setAllowCancel(true);
  358. }
  359. }
  360. Collections.sort(list);
  361. return list;
  362. }
  363. @Override
  364. public List<ApplyVO> getStudentApplyListFromCache(LoginUser student, Boolean cancel) {
  365. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  366. if (curApplyTask == null) {
  367. return new ArrayList<>();
  368. }
  369. Map<String, ApplyRecordCacheBean> maps = applyTaskCacheService.getStudentApplyRecords(student.getId());
  370. if (MapUtils.isEmpty(maps)) {
  371. return new ArrayList<>();
  372. }
  373. // 自主预约全局开关
  374. boolean globalOpen = systemPropertyService.existPropValue(curApplyTask.getOrgId(), Constants.APPLY_SWITCH, Constants.APPLY_SWITCH_OPEN);
  375. Set<Long> timePeriodIds = maps.values().stream().map(ApplyRecordCacheBean::getTimePeriodId).collect(Collectors.toSet());
  376. Map<Long, TimePeriodEntity> timePeriods = this.getTimePeriods(timePeriodIds);
  377. // 考前N天,禁止考生自主取消预约
  378. Date allowDate = DateUtil.changeDateAndTimeEnd(new Date(), curApplyTask.getAllowApplyCancelDays());
  379. List<ApplyVO> list = new ArrayList<>();
  380. for (ApplyRecordCacheBean bean : maps.values()) {
  381. if (cancel != null && cancel != bean.getCancel()) {
  382. // 按“取消预约”状态过滤
  383. continue;
  384. }
  385. ApplyVO vo = new ApplyVO();
  386. vo.setShowTicket(false);
  387. vo.setAllowCancel(false);
  388. vo.setCancel(bean.getCancel());
  389. TimePeriodEntity timePeriod = timePeriods.get(bean.getTimePeriodId());
  390. vo.setTimePeriodId(bean.getTimePeriodId());
  391. if (timePeriod != null) {
  392. vo.setTimePeriodStart(timePeriod.getStartTime());
  393. vo.setTimePeriodEnd(timePeriod.getEndTime());
  394. Date curDate = new Date(vo.getTimePeriodStart());
  395. if (curDate.after(allowDate) && globalOpen) {
  396. // “当前时段开始时间”在“允许取消时间”之后,可以取消预约
  397. vo.setAllowCancel(true);
  398. }
  399. }
  400. ExamSiteCacheBean examSite = examSiteCacheService.getExamSiteById(bean.getExamSiteId());
  401. vo.setExamSiteId(bean.getExamSiteId());
  402. if (examSite != null) {
  403. vo.setExamSiteName(examSite.getExamSiteName());
  404. vo.setExamSiteAddress(examSite.getExamSiteAddress());
  405. vo.setCategoryId(examSite.getCategoryId());
  406. vo.setCategoryName(examSite.getCategoryName());
  407. }
  408. if (!vo.getCancel()) {
  409. // 准考证号已生成,则可查看
  410. vo.setShowTicket(this.hasTicketNumber(bean.getStudentId(), bean.getExamSiteId(), bean.getTimePeriodId()));
  411. }
  412. list.add(vo);
  413. }
  414. Collections.sort(list);
  415. return list;
  416. }
  417. @Override
  418. public List<ApplyVO> getStudentApplyListForCurrent(LoginUser student) {
  419. // 默认过滤掉“已取消预约”的记录
  420. // List<ApplyVO> list = this.getStudentApplyList(student, false);
  421. List<ApplyVO> list = this.getStudentApplyListFromCache(student, false);
  422. Date now = new Date();
  423. List<ApplyVO> newList = new ArrayList<>();
  424. for (ApplyVO vo : list) {
  425. Date curDate = new Date(vo.getTimePeriodEnd());
  426. if (curDate.before(now)) {
  427. // “当前时段结束时间”在“当前时间”之前,则过滤掉
  428. continue;
  429. }
  430. newList.add(vo);
  431. }
  432. return newList;
  433. }
  434. @Override
  435. public TicketInfo getApplyTicket(Long studentId, Long examSiteId, Long timePeriodId) {
  436. if (examSiteId == null) {
  437. throw new StatusException("考点ID不能为空");
  438. }
  439. if (timePeriodId == null) {
  440. throw new StatusException("预约时段ID不能为空");
  441. }
  442. StudentApplyDao baseMapper = (StudentApplyDao) studentApplyService.getBaseMapper();
  443. TicketInfo info = baseMapper.getStudentApplyTicket(studentId, examSiteId, timePeriodId);
  444. if (info == null || info.getCancel()) {
  445. throw new StatusException("准考证信息不存在");
  446. }
  447. if (StringUtils.isEmpty(info.getTicketNumber())) {
  448. throw new StatusException("准考证信息尚未生成");
  449. }
  450. List<CourseVO> courses = studentCourseService.getStudentCourses(studentId);
  451. info.setCourses(courses);
  452. return info;
  453. }
  454. @Override
  455. public List<String> getApplyDateList(LoginUser student, Long examSiteId) {
  456. if (examSiteId == null) {
  457. throw new StatusException("考点ID不能为空");
  458. }
  459. // 日期格式:yyyyMMdd
  460. List<String> dateList = new ArrayList<>();
  461. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  462. if (curApplyTask == null) {
  463. return dateList;
  464. }
  465. ExamSiteTimePeriodQuery periodQuery = new ExamSiteTimePeriodQuery();
  466. periodQuery.setApplyTaskId(curApplyTask.getTaskId());
  467. periodQuery.setExamSiteId(examSiteId);
  468. List<ExamSiteTimePeriodInfo> timePeriods = timePeriodExamSiteService.findExamSiteTimePeriods(periodQuery);
  469. if (CollectionUtils.isEmpty(timePeriods)) {
  470. return dateList;
  471. }
  472. // 考前N天,禁止考生自主预约
  473. Date allowDate = DateUtil.changeDateAndTimeEnd(new Date(), curApplyTask.getAllowApplyDays());
  474. Set<String> dates = new HashSet<>();
  475. FastDateFormat fdf = FastDateFormat.getInstance("yyyyMMdd");
  476. for (ExamSiteTimePeriodInfo timePeriod : timePeriods) {
  477. if (!timePeriod.getEnable()) {
  478. // 跳过未启用的时段
  479. continue;
  480. }
  481. Date curDate = new Date(timePeriod.getStartTime());
  482. if (curDate.before(allowDate)) {
  483. // 跳过过期时段,“当前时段开始时间”在“允许预约时间”之前,则禁止预约
  484. continue;
  485. }
  486. dates.add(fdf.format(curDate));
  487. }
  488. dateList.addAll(dates);
  489. Collections.sort(dateList);
  490. return dateList;
  491. }
  492. @Override
  493. public ApplyTimePeriodResult getApplyTimePeriodList(LoginUser student, Long examSiteId, String date) {
  494. if (examSiteId == null) {
  495. throw new StatusException("考点ID不能为空");
  496. }
  497. if (StringUtils.isEmpty(date)) {
  498. throw new StatusException("预约日期不能为空");
  499. }
  500. Date timeBegin, timeEnd;
  501. try {
  502. FastDateFormat fdf = FastDateFormat.getInstance("yyyyMMddHHmmss");
  503. timeBegin = fdf.parse(date + "000000");
  504. timeEnd = fdf.parse(date + "235959");
  505. } catch (ParseException e) {
  506. log.error(e.getMessage(), e);
  507. throw new StatusException("预约日期值的格式不正确");
  508. }
  509. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
  510. if (curApplyTask == null) {
  511. throw new StatusException("尚未开启预约任务");
  512. }
  513. if (curApplyTask.getSelfApplyStartTime() > System.currentTimeMillis()) {
  514. log.warn("未到预约时间!selfApplyStartTime:{}", curApplyTask.getSelfApplyStartTime());
  515. throw new StatusException("未到预约时间");
  516. }
  517. ExamSiteTimePeriodQuery periodQuery = new ExamSiteTimePeriodQuery();
  518. periodQuery.setApplyTaskId(curApplyTask.getTaskId());
  519. periodQuery.setExamSiteId(examSiteId);
  520. periodQuery.setTimeBegin(timeBegin.getTime());
  521. periodQuery.setTimeEnd(timeEnd.getTime());
  522. List<ExamSiteTimePeriodInfo> timePeriods = timePeriodExamSiteService.findExamSiteTimePeriods(periodQuery);
  523. // 获取某考生的 所有的“预约记录”
  524. // Map<String, ApplyRecordCacheBean> maps = applyTaskCacheService.getStudentApplyRecordsFromDB(student.getId());
  525. Map<String, ApplyRecordCacheBean> maps = applyTaskCacheService.getStudentApplyRecords(student.getId());
  526. int studentApplyFinishCount = 0;
  527. for (ApplyRecordCacheBean bean : maps.values()) {
  528. if (!bean.getCancel()) {
  529. studentApplyFinishCount++;
  530. }
  531. }
  532. // 考生剩余预约次数 = 允许预约次数 - 已完成预约次数
  533. int studentApplyNumber = applyTaskCacheService.getStudentApplyNumber(student.getId());
  534. int unApplyNumber = studentApplyNumber - studentApplyFinishCount;
  535. List<TimePeriodInfo> periodList = new ArrayList<>();
  536. if (CollectionUtils.isNotEmpty(timePeriods)) {
  537. for (ExamSiteTimePeriodInfo timePeriod : timePeriods) {
  538. TimePeriodInfo info = new TimePeriodInfo();
  539. info.setTimePeriodId(timePeriod.getTimePeriodId());
  540. info.setTimePeriodStart(timePeriod.getStartTime());
  541. info.setTimePeriodEnd(timePeriod.getEndTime());
  542. info.setEnable(timePeriod.getEnable());
  543. int availableCount = applyTaskCacheService.getApplyAvailableCount(examSiteId, timePeriod.getTimePeriodId());
  544. info.setAvailableCount(availableCount);
  545. if (availableCount < 1) {
  546. info.setStatus(ApplyStatus.FULL);
  547. } else {
  548. info.setStatus(ApplyStatus.AVAILABLE);
  549. }
  550. String hashKey = String.format("%s_%s", examSiteId, timePeriod.getTimePeriodId());
  551. ApplyRecordCacheBean existApply = maps.get(hashKey);
  552. if (existApply != null && !existApply.getCancel()) {
  553. info.setStatus(ApplyStatus.FINISHED);
  554. }
  555. periodList.add(info);
  556. }
  557. }
  558. ApplyTimePeriodResult result = new ApplyTimePeriodResult();
  559. result.setUnApplyNumber(Math.max(unApplyNumber, 0));
  560. result.setPeriodList(periodList);
  561. return result;
  562. }
  563. @Override
  564. public RichTextBean getExamNotice(Long applyTaskId) {
  565. if (applyTaskId == null) {
  566. throw new StatusException("预约任务ID不能为空");
  567. }
  568. ApplyTaskEntity applyTask = applyTaskService.getById(applyTaskId);
  569. if (applyTask == null) {
  570. throw new StatusException("考试须知信息不存在");
  571. }
  572. RichTextBean bean = new RichTextBean();
  573. bean.setContent(applyTask.getNotice());
  574. return bean;
  575. }
  576. @Override
  577. public RichTextBean getExamSiteGuide(Long examSiteId) {
  578. if (examSiteId == null) {
  579. throw new StatusException("考点ID不能为空");
  580. }
  581. ExamSiteEntity examSite = examSiteService.getById(examSiteId);
  582. if (examSite == null) {
  583. throw new StatusException("考场引导信息不存在");
  584. }
  585. RichTextBean bean = new RichTextBean();
  586. bean.setContent(examSite.getGuide());
  587. return bean;
  588. }
  589. }