StudentApplyServiceImpl.java 53 KB

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