StudentApplyServiceImpl.java 37 KB

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