BatchServiceImpl.java 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  1. package cn.com.qmth.scancentral.service.impl;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.HashSet;
  5. import java.util.LinkedHashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;
  9. import cn.com.qmth.scancentral.service.*;
  10. import org.apache.commons.collections4.CollectionUtils;
  11. import org.apache.commons.lang.math.RandomUtils;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Service;
  15. import org.springframework.transaction.annotation.Transactional;
  16. import org.springframework.web.multipart.MultipartFile;
  17. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  18. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  19. import com.baomidou.mybatisplus.core.metadata.IPage;
  20. import com.baomidou.mybatisplus.core.metadata.OrderItem;
  21. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  22. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  23. import com.qmth.boot.core.collection.PageResult;
  24. import com.qmth.boot.core.concurrent.service.ConcurrentService;
  25. import com.qmth.boot.core.exception.ParameterException;
  26. import com.qmth.boot.core.exception.StatusException;
  27. import cn.com.qmth.scancentral.bean.BatchCreateDomain;
  28. import cn.com.qmth.scancentral.bean.BatchQueryDomain;
  29. import cn.com.qmth.scancentral.bean.User;
  30. import cn.com.qmth.scancentral.bean.WorkloadDomain;
  31. import cn.com.qmth.scancentral.bean.answersave.AnswerDomain;
  32. import cn.com.qmth.scancentral.bean.answersave.AnswerPaper;
  33. import cn.com.qmth.scancentral.dao.BatchDao;
  34. import cn.com.qmth.scancentral.entity.AnswerCardEntity;
  35. import cn.com.qmth.scancentral.entity.BatchEntity;
  36. import cn.com.qmth.scancentral.entity.BatchPaperEntity;
  37. import cn.com.qmth.scancentral.entity.ExamEntity;
  38. import cn.com.qmth.scancentral.entity.PaperEntity;
  39. import cn.com.qmth.scancentral.entity.PaperPageEntity;
  40. import cn.com.qmth.scancentral.entity.ScannerEntity;
  41. import cn.com.qmth.scancentral.entity.StudentEntity;
  42. import cn.com.qmth.scancentral.entity.StudentPaperEntity;
  43. import cn.com.qmth.scancentral.entity.SubjectEntity;
  44. import cn.com.qmth.scancentral.enums.BatchStatus;
  45. import cn.com.qmth.scancentral.enums.CheckStatus;
  46. import cn.com.qmth.scancentral.enums.LockType;
  47. import cn.com.qmth.scancentral.enums.Role;
  48. import cn.com.qmth.scancentral.enums.VerifyStatus;
  49. import cn.com.qmth.scancentral.exception.NotFoundExceptions;
  50. import cn.com.qmth.scancentral.exception.ParameterExceptions;
  51. import cn.com.qmth.scancentral.support.TaskLock;
  52. import cn.com.qmth.scancentral.support.TaskLockUtil;
  53. import cn.com.qmth.scancentral.util.PageUtil;
  54. import cn.com.qmth.scancentral.vo.BatchFinishVo;
  55. import cn.com.qmth.scancentral.vo.BatchQueryVo;
  56. import cn.com.qmth.scancentral.vo.BatchVerifyVo;
  57. import cn.com.qmth.scancentral.vo.ScannerWorkloadVo;
  58. import cn.com.qmth.scancentral.vo.SheetUploadVo;
  59. import cn.com.qmth.scancentral.vo.SliceUploadVo;
  60. import cn.com.qmth.scancentral.vo.SubjectVo;
  61. import cn.com.qmth.scancentral.vo.batch.AnswerSaveVo;
  62. import cn.com.qmth.scancentral.vo.batch.BatchCreateVo;
  63. import cn.com.qmth.scancentral.vo.batch.BatchDeviceQuery;
  64. import cn.com.qmth.scancentral.vo.batch.BatchQuery;
  65. import cn.com.qmth.scancentral.vo.batch.BatchStudentSimpleVo;
  66. import cn.com.qmth.scancentral.vo.batch.BatchSubjectQuery;
  67. import cn.com.qmth.scancentral.vo.batch.BatchVo;
  68. import cn.com.qmth.scancentral.vo.batch.Paper;
  69. import cn.com.qmth.scancentral.vo.batch.Rescan;
  70. import cn.com.qmth.scancentral.vo.batchdetail.BatchDetailDO;
  71. import cn.com.qmth.scancentral.vo.batchdetail.BatchDetailVo;
  72. import cn.com.qmth.scancentral.vo.batchdetail.BatchStudentVo;
  73. import cn.com.qmth.scancentral.vo.checkimage.CheckImageSubmitVo;
  74. import cn.com.qmth.scancentral.vo.device.DeviceVo;
  75. import cn.com.qmth.scancentral.vo.examroom.ExamRoomScannedQuery;
  76. import cn.com.qmth.scancentral.vo.paper.PaperVo;
  77. import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
  78. import cn.com.qmth.scancentral.vo.verify.VerifyStudentVo;
  79. import cn.com.qmth.scancentral.vo.verify.VerifyTaskVo;
  80. @Service
  81. public class BatchServiceImpl extends ServiceImpl<BatchDao, BatchEntity> implements BatchService {
  82. @Autowired
  83. private ExamRoomService examRoomService;
  84. @Autowired
  85. private ExamService examService;
  86. @Autowired
  87. private StudentService studentService;
  88. @Autowired
  89. private PaperService paperService;
  90. @Autowired
  91. private PaperPageService paperPageService;
  92. @Autowired
  93. private BatchPaperService batchPaperService;
  94. @Autowired
  95. private AnswerCardService answerCardService;
  96. @Autowired
  97. private AnswerCardSubjectService answerCardSubjectService;
  98. @Autowired
  99. private ConcurrentService concurrentService;
  100. @Autowired
  101. private FileService fileService;
  102. @Autowired
  103. private FilePropertyService filePropertyService;
  104. @Autowired
  105. private SubjectService subjectService;
  106. @Autowired
  107. private AssignedCheckHistoryService assignedCheckHistoryService;
  108. @Autowired
  109. private ScannerService scannerService;
  110. @Override
  111. public BatchEntity checkBatchStatus(BatchEntity b) {
  112. if (b == null) {
  113. throw new ParameterException("批次未找到");
  114. }
  115. if (BatchStatus.FINISH.equals(b.getStatus())) {
  116. throw new ParameterException("批次已经完成");
  117. }
  118. if (VerifyStatus.WAITING.equals(b.getVerifyStatus())) {
  119. throw new ParameterException("批次正在审核中");
  120. }
  121. if (VerifyStatus.REJECT.equals(b.getVerifyStatus())) {
  122. throw new ParameterException("批次审核不通过");
  123. }
  124. return b;
  125. }
  126. @Transactional
  127. @Override
  128. public BatchCreateVo batchCreate(BatchCreateDomain domain, User user) {
  129. ExamEntity exam = examService.getById(domain.getExamId());
  130. if (exam == null) {
  131. throw ParameterExceptions.EXAM_NOT_FOUND;
  132. }
  133. List<Rescan> rescan = new ArrayList<>();
  134. String packageCode = null;
  135. for (String examNumber : domain.getExamNumbers()) {
  136. StudentEntity student = studentService.findByExamAndSubjectCodeAndExamNumber(domain.getExamId(),
  137. domain.getSubjectCode(), examNumber);
  138. if (student == null) {
  139. throw new ParameterException("未找到考生信息:" + examNumber);
  140. }
  141. if (exam.getScanByPackage() != null && exam.getScanByPackage()) {
  142. if (packageCode == null) {
  143. packageCode = student.getPackageCode();
  144. } else {
  145. if (!packageCode.equals(student.getPackageCode())) {
  146. throw new ParameterException("考生[" + examNumber + "]卷袋编号与其它考生不一致");
  147. }
  148. }
  149. if (domain.getSubjectCode() != null && !domain.getSubjectCode().equals(student.getSubjectCode())) {
  150. throw new ParameterException("考生[" + examNumber + "]科目代码与[" + domain.getSubjectCode() + "]不一致");
  151. }
  152. }
  153. Rescan rc = getRescan(student);
  154. if (rc != null) {
  155. rescan.add(rc);
  156. }
  157. }
  158. if (exam.getScanByPackage() != null && exam.getScanByPackage()) {
  159. if (StringUtils.isBlank(domain.getSubjectCode())) {
  160. throw new ParameterException("整袋模式下科目代码不能为空");
  161. }
  162. List<StudentEntity> ss = studentService.findByExamAndPackage(domain.getExamId(), packageCode,
  163. domain.getSubjectCode());
  164. if (CollectionUtils.isEmpty(ss) || ss.size() != domain.getExamNumbers().size()) {
  165. throw new ParameterException("该批次考生数量与卷袋考生数量不一致");
  166. }
  167. }
  168. BatchEntity b = new BatchEntity();
  169. b.setExamId(domain.getExamId());
  170. b.setDevice(user.getAccount());
  171. b.setPackageCode(packageCode);
  172. b.setSubjectCode(domain.getSubjectCode());
  173. b.setAssignedCount(0);
  174. b.setScanCount(0);
  175. b.setStatus(BatchStatus.INIT);
  176. this.save(b);
  177. BatchCreateVo vo = new BatchCreateVo();
  178. vo.setRescan(rescan);
  179. vo.setId(b.getId());
  180. vo.setCreateTime(b.getCreateTime());
  181. vo.setEnableSyncVerify(exam.getEnableSyncVerify());
  182. return vo;
  183. }
  184. private Rescan getRescan(StudentEntity student) {
  185. Rescan res = new Rescan();
  186. res.setExamNumber(student.getExamNumber());
  187. List<PaperVo> papers = paperService.findStudentPaper(student.getId());
  188. List<Paper> resPapers = new ArrayList<>();
  189. res.setPapers(resPapers);
  190. if (CollectionUtils.isEmpty(papers)) {
  191. return null;
  192. }
  193. PaperVo first = papers.get(0);
  194. res.setBatchId(first.getBatchId());
  195. res.setDevice(first.getDevice());
  196. res.setCreateTime(first.getCreateTime());
  197. for (PaperVo paper : papers) {
  198. Paper p = new Paper();
  199. p.setId(paper.getId());
  200. p.setNumber(paper.getNumber());
  201. p.setMismatch(paper.getMismatch());
  202. List<String> pages = new ArrayList<>();
  203. p.setPages(pages);
  204. List<PaperPageEntity> paperPages = paperPageService.listByPaperId(paper.getId());
  205. if (CollectionUtils.isNotEmpty(paperPages)) {
  206. for (PaperPageEntity pp : paperPages) {
  207. pages.add(pp.getSheetPath());
  208. }
  209. }
  210. resPapers.add(p);
  211. }
  212. return res;
  213. }
  214. @Override
  215. public int getCountByExam(Long examId) {
  216. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  217. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  218. lw.eq(BatchEntity::getExamId, examId);
  219. return this.count(wrapper);
  220. }
  221. @Override
  222. public int getValidCountByExam(Long examId) {
  223. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  224. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  225. lw.and(param -> {
  226. param.or().isNull(BatchEntity::getVerifyStatus);
  227. param.or().ne(BatchEntity::getVerifyStatus, VerifyStatus.REJECT);
  228. });
  229. lw.eq(BatchEntity::getExamId, examId);
  230. lw.gt(BatchEntity::getScanCount, 0);
  231. return this.count(wrapper);
  232. }
  233. @Override
  234. public int getVerifyCount(Long examId) {
  235. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  236. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  237. lw.eq(BatchEntity::getExamId, examId);
  238. lw.eq(BatchEntity::getVerifyStatus, VerifyStatus.WAITING);
  239. return this.count(wrapper);
  240. }
  241. @Transactional
  242. @Override
  243. public SheetUploadVo sheetUpload(Long batchId, String examNumber, Integer paperNumber, Integer pageIndex,
  244. MultipartFile multipartFile, String md5) {
  245. BatchEntity batch = checkBatchStatus(getById(batchId));
  246. try {
  247. String path = fileService.uploadSheet(multipartFile.getInputStream(), md5, batch.getExamId(), batchId,
  248. examNumber, paperNumber, pageIndex);
  249. filePropertyService.save(batch.getExamId(), path, md5, multipartFile.getSize());
  250. return SheetUploadVo.create(path);
  251. } catch (Exception e) {
  252. log.error("原图上传失败,batchId=" + batch + ", examNumber=" + examNumber + ", paperNumber=" + paperNumber
  253. + ", pageIndex=" + pageIndex, e);
  254. throw new StatusException("原图上传失败", e);
  255. }
  256. }
  257. @Transactional
  258. @Override
  259. public SliceUploadVo sliceUpload(Long batchId, String examNumber, Integer paperNumber, Integer pageIndex,
  260. Integer index, MultipartFile multipartFile, String md5) {
  261. BatchEntity batch = checkBatchStatus(getById(batchId));
  262. try {
  263. String path = fileService.uploadSlice(multipartFile.getInputStream(), md5, batch.getExamId(), batchId,
  264. examNumber, paperNumber, pageIndex, index);
  265. filePropertyService.save(batch.getExamId(), path, md5, multipartFile.getSize());
  266. return SliceUploadVo.create(path);
  267. } catch (Exception e) {
  268. log.error("裁切图上传失败,batchId=" + batch + ", examNumber=" + examNumber + ", paperNumber=" + paperNumber
  269. + ", pageIndex=" + pageIndex + ", index=" + index, e);
  270. throw new StatusException("裁切图上传失败", e);
  271. }
  272. }
  273. @Transactional
  274. @Override
  275. public AnswerSaveVo batchSave(AnswerDomain domain, User user) {
  276. BatchEntity batch = checkBatchStatus(getById(domain.getBatchId()));
  277. ExamEntity exam = examService.getById(batch.getExamId());
  278. if (exam == null) {
  279. throw ParameterExceptions.EXAM_NOT_FOUND;
  280. }
  281. String examNumber = domain.getExamNumber();
  282. StudentEntity student = studentService.findByExamAndSubjectCodeAndExamNumber(batch.getExamId(),
  283. domain.getSubjectCode(), examNumber);
  284. if (student == null) {
  285. throw new ParameterException("考生信息未找到");
  286. }
  287. AnswerCardEntity answerCard = answerCardService.findByExamAndNumber(batch.getExamId(), domain.getCardNumber());
  288. if (answerCard == null) {
  289. throw new ParameterException("卡格式信息未找到");
  290. }
  291. // 整袋扫描验证packageCode
  292. if (exam.getScanByPackage() != null && exam.getScanByPackage()) {
  293. if (!StringUtils.equals(batch.getPackageCode(), student.getPackageCode())) {
  294. throw new ParameterException("与批次卷袋号不一致");
  295. }
  296. }
  297. // 验证卡格式所属科目
  298. boolean allowSubject = answerCardSubjectService.checkSubject(answerCard.getExamId(), answerCard.getNumber(),
  299. student.getSubjectCode());
  300. if (!allowSubject) {
  301. throw new ParameterException("卡格式与考生科目不一致");
  302. }
  303. // 不允许缺页时,保存张数需要与卡格式定义一致
  304. if (!exam.getAllowUnexistPaper() && domain.getPapers().size() != answerCard.getPaperCount()) {
  305. throw new ParameterException("卡格式张数不一致");
  306. }
  307. // List<String> paperTypeBarcodeContents = markSiteService.findPaperTypeBarcodeContentByExamAndSubjectCode(student.getExamId(), student.getSubjectCode());
  308. // if (paperTypeBarcodeContents == null || paperTypeBarcodeContents.isEmpty()) {
  309. // paperTypeBarcodeContents = exam.getPaperTypeBarcodeContent();
  310. // }
  311. boolean studentAssigned = false;
  312. List<StudentPaperEntity> studentPaperList = new ArrayList<>();
  313. for (AnswerPaper answerPaper : domain.getPapers()) {
  314. // 验证page数量
  315. answerPaper.answerCardValidate(answerCard);
  316. Integer paperNumber = answerPaper.getNumber();
  317. PaperEntity paper = findOrCreatePaper(batch, student, paperNumber, user);
  318. // 设置paper属性
  319. paper.setExamId(batch.getExamId());
  320. paper.setCardNumber(answerCard.getNumber());
  321. paper.setNumber(paperNumber);
  322. paper.setMismatch(false);
  323. paper.setQuestionFilled(false);
  324. paper.setSubjectiveFilled(answerPaper.getSubjectiveFilled());
  325. paper.setOmrExamNumber(answerPaper.getOmrExamNumber());
  326. paper.setAssigned(answerPaper.getAssigned());
  327. // 保存paper与page到数据库
  328. paperService.savePaperAndPages(paper, answerPaper.buildPageList(null));
  329. // 记录paper与batch关联关系
  330. batchPaperService.update(batch, paper, student.getId(), paperNumber, false);
  331. // 创建student与paper的关联关系
  332. studentPaperList.add(new StudentPaperEntity(student.getId(), paperNumber, paper.getId()));
  333. studentAssigned = studentAssigned || paper.getAssigned();
  334. }
  335. // 更新批次统计数量,改到finish的时候更新
  336. // updateAssignedCount(batch.getId());
  337. // updateScanCount(batch.getId());
  338. // 不开启实时审核,或者没有人工绑定的情况下,或者当前批次无需审核,直接更新考生扫描状态
  339. if (VerifyStatus.CANCEL.equals(batch.getVerifyStatus()) || exam.getEnableSyncVerify() == null
  340. || !exam.getEnableSyncVerify() || !studentAssigned) {
  341. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().lock();
  342. try {
  343. studentService.updateStudentAndPaper(user, student.getId(), studentPaperList);
  344. studentService.updateAssignedCheckCount(student.getId(), true);
  345. } finally {
  346. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().unlock();
  347. }
  348. }
  349. // 更新考场扫描状态
  350. ExamRoomScannedQuery req = new ExamRoomScannedQuery();
  351. req.setExamId(student.getExamId());
  352. req.setSubjectCode(student.getSubjectCode());
  353. req.setProvince(student.getProvince());
  354. req.setCampusCode(student.getCampusCode());
  355. req.setExamSite(student.getExamSite());
  356. req.setExamRoom(student.getExamRoom());
  357. examRoomService.modifyByStudentScan(req);
  358. return AnswerSaveVo.create(findStudentCountByBatch(batch.getId()));
  359. }
  360. private PaperEntity findOrCreatePaper(BatchEntity batch, StudentEntity student, Integer paperNumber, User user) {
  361. PaperEntity paper = null;
  362. // 尝试重用已有paper对象
  363. BatchPaperEntity batchPaper = batchPaperService.findByBatchIdAndStudentIdAndPaperNumber(batch.getId(),
  364. student.getId(), paperNumber);
  365. if (batchPaper != null) {
  366. paper = paperService.getById(batchPaper.getPaperId());
  367. }
  368. // 构造全新paper对象
  369. if (paper == null) {
  370. paper = new PaperEntity();
  371. paper.setCreateTime(System.currentTimeMillis());
  372. paper.setCreatorId(user.getId());
  373. }
  374. return paper;
  375. }
  376. private void updateBatchAssignedStudentData(User user, Long batchId, Long examId) {
  377. List<BatchPaperEntity> batchPaperList = batchPaperService.findByBatchId(batchId);
  378. Map<Long, List<StudentPaperEntity>> studentMap = new HashMap<>();
  379. for (BatchPaperEntity batchPaper : batchPaperList) {
  380. studentMap.computeIfAbsent(batchPaper.getStudentId(), key -> new ArrayList<>()).add(new StudentPaperEntity(
  381. batchPaper.getStudentId(), batchPaper.getPaperNumber(), batchPaper.getPaperId()));
  382. }
  383. for (Map.Entry<Long, List<StudentPaperEntity>> entry : studentMap.entrySet()) {
  384. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + entry.getKey()).writeLock().lock();
  385. try {
  386. studentService.updateStudentAndPaper(user, entry.getKey(), entry.getValue());
  387. assignedCheckHistoryService.save(user.getId(), entry.getKey(), examId);
  388. studentService.updateAssignedCheckCount(entry.getKey(), false);
  389. } finally {
  390. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + entry.getKey()).writeLock().unlock();
  391. }
  392. }
  393. }
  394. @Transactional
  395. @Override
  396. public void verify(Long batchId, Boolean comfirm, User user) {
  397. BatchEntity entity = this.getById(batchId);
  398. if (entity == null) {
  399. throw new ParameterException("批次不存在");
  400. }
  401. if (!VerifyStatus.WAITING.equals(entity.getVerifyStatus())) {
  402. throw new ParameterException("批次已被处理");
  403. }
  404. if (comfirm) {
  405. updateBatchAssignedStudentData(user, batchId, entity.getExamId());
  406. entity.setVerifyStatus(VerifyStatus.CONFIRM);
  407. } else {
  408. entity.setVerifyStatus(VerifyStatus.REJECT);
  409. }
  410. entity.setUpdateTime(System.currentTimeMillis());
  411. this.saveOrUpdate(entity);
  412. }
  413. @Transactional
  414. @Override
  415. public BatchVerifyVo batchVerify(Long id) {
  416. BatchEntity entity = this.getById(id);
  417. if (entity == null) {
  418. throw new ParameterException("批次不存在");
  419. }
  420. Integer count = findStudentCountByBatch(id);
  421. if (count == null || count == 0) {
  422. throw new ParameterException("该批次未上传图片");
  423. }
  424. if (entity.getVerifyStatus() == null) {
  425. ExamEntity exam = examService.getById(entity.getExamId());
  426. if (exam.getEnableSyncVerify() == null || !exam.getEnableSyncVerify()) {
  427. throw new ParameterException("考试未启用实时审核");
  428. }
  429. entity.setVerifyStatus(VerifyStatus.WAITING);
  430. this.saveOrUpdate(entity);
  431. }
  432. BatchVerifyVo vo = new BatchVerifyVo();
  433. vo.setStatus(entity.getVerifyStatus());
  434. vo.setUpdateTime(entity.getUpdateTime());
  435. return vo;
  436. }
  437. @Transactional
  438. @Override
  439. public BatchFinishVo batchFinish(Long id) {
  440. BatchEntity entity = this.getById(id);
  441. if (entity == null) {
  442. throw new ParameterException("批次不存在");
  443. }
  444. checkBatchStatus(entity);
  445. ExamEntity exam = examService.getById(entity.getExamId());
  446. if (exam.getScanByPackage() != null && exam.getScanByPackage()) {
  447. checkStudentCount(entity);
  448. }
  449. List<BatchPaperEntity> saveList = new ArrayList<BatchPaperEntity>();
  450. if (exam.getImageCheckRatio() == 1) {
  451. List<BatchPaperEntity> papers = batchPaperService.findByBatchId(id);
  452. for (BatchPaperEntity batchPaperEntity : papers) {
  453. batchPaperEntity.setNeedCheck(true);
  454. saveList.add(batchPaperEntity);
  455. }
  456. } else {
  457. List<Long> studentIds = batchPaperService.findStudentIdByBatchId(id);
  458. int checkCount = (int) Math.round(studentIds.size() * exam.getImageCheckRatio());
  459. if (checkCount > 0) {
  460. Set<Long> checkIds = new HashSet<Long>();
  461. while (checkIds.size() < checkCount) {
  462. Long studentId = studentIds.get(RandomUtils.nextInt(studentIds.size()));
  463. checkIds.add(studentId);
  464. }
  465. List<BatchPaperEntity> papers = batchPaperService.findByBatchIdAndStudentIds(id, checkIds);
  466. for (BatchPaperEntity batchPaperEntity : papers) {
  467. batchPaperEntity.setNeedCheck(true);
  468. saveList.add(batchPaperEntity);
  469. }
  470. }
  471. }
  472. if (!saveList.isEmpty()) {
  473. batchPaperService.saveOrUpdateBatchByMultiId(saveList);
  474. entity.setCheckStatus(CheckStatus.WAITING);
  475. }
  476. entity.setStatus(BatchStatus.FINISH);
  477. this.saveOrUpdate(entity);
  478. updateAssignedCount(entity.getId());
  479. updateScanCount(entity.getId());
  480. BatchFinishVo vo = new BatchFinishVo();
  481. vo.setStatus(entity.getStatus());
  482. vo.setUpdateTime(System.currentTimeMillis());
  483. return vo;
  484. }
  485. private void checkStudentCount(BatchEntity entity) {
  486. List<BatchPaperEntity> papers = batchPaperService.findByBatchId(entity.getId());
  487. List<StudentEntity> students = studentService.findByExamAndPackage(entity.getExamId(), entity.getPackageCode(),
  488. entity.getSubjectCode());
  489. if (CollectionUtils.isEmpty(papers) && CollectionUtils.isEmpty(students)) {
  490. return;
  491. }
  492. if (CollectionUtils.isEmpty(papers) || CollectionUtils.isEmpty(students)) {
  493. throw new ParameterException("卷袋考生有未扫描");
  494. }
  495. Set<Long> ps = new HashSet<>();
  496. Set<Long> ss = new HashSet<>();
  497. for (BatchPaperEntity p : papers) {
  498. ps.add(p.getStudentId());
  499. }
  500. for (StudentEntity s : students) {
  501. ss.add(s.getId());
  502. }
  503. if (ps.size() != ss.size()) {
  504. throw new ParameterException("卷袋考生有未扫描");
  505. }
  506. for (Long p : ps) {
  507. if (!ss.contains(p)) {
  508. throw new ParameterException("卷袋考生有未扫描");
  509. }
  510. }
  511. }
  512. @Transactional
  513. @Override
  514. public void updateScanCount(Long id) {
  515. baseMapper.updateScanCount(id);
  516. }
  517. @Transactional
  518. @Override
  519. public void updateAssignedCount(Long id) {
  520. baseMapper.updateAssignedCount(id);
  521. }
  522. @Override
  523. public PageResult<BatchQueryVo> batchQuery(BatchQueryDomain query) {
  524. IPage<BatchQueryVo> iPage = baseMapper.batchQueryPage(new Page<>(query.getPageNumber(), query.getPageSize()),
  525. query);
  526. // TODO 需要后续补充incomplete逻辑
  527. return PageUtil.of(iPage);
  528. }
  529. @Override
  530. public List<Long> batchSummary(BatchQueryDomain query) {
  531. // TODO 需要后续补充incomplete逻辑
  532. return baseMapper.batchSummary(query);
  533. }
  534. @Override
  535. public BatchDetailVo batchDetail(Long id) {
  536. BatchEntity batchEntity = this.getById(id);
  537. BatchDetailVo vo = new BatchDetailVo();
  538. vo.update(batchEntity);
  539. Map<Long, BatchStudentVo> map = new LinkedHashMap<>();
  540. List<BatchDetailDO> list = baseMapper.batchDetailList(id);
  541. for (BatchDetailDO item : list) {
  542. BatchStudentVo detail = map.computeIfAbsent(item.getId(), key -> BatchStudentVo.build(item));
  543. // 判断是否人工绑定
  544. detail.setAssigned(detail.getAssigned() || item.getAssigned());
  545. // 判断是否识别缺考
  546. if (item.getAbsent() != null && item.getAbsent().getResult()) {
  547. detail.setOmrAbsent(true);
  548. }
  549. // 创建paper与page对象
  550. detail.findOrCreatePaper(item).addPage(item);
  551. }
  552. vo.setStudents(new ArrayList<>(map.values()));
  553. return vo;
  554. }
  555. @Override
  556. public VerifyTaskVo getVerifyTask(Long examId, User user) {
  557. int retry = 1;
  558. VerifyTaskVo task = null;
  559. while (task == null) {
  560. List<BatchEntity> list = this.findUnVerify(examId, retry, 20, VerifyStatus.WAITING).getRecords();
  561. if (list.isEmpty()) {
  562. break;
  563. }
  564. for (BatchEntity t : list) {
  565. if (this.applyVerifyTask(t, user.getAccount())) {
  566. task = toVerifyTaskVo(t);
  567. break;
  568. }
  569. }
  570. if (task == null) {
  571. retry++;
  572. }
  573. }
  574. if (task == null) {
  575. throw NotFoundExceptions.NO_SYNC_VERIFY_TASK;
  576. }
  577. return task;
  578. }
  579. private VerifyTaskVo toVerifyTaskVo(BatchEntity batch) {
  580. VerifyTaskVo vo = new VerifyTaskVo();
  581. vo.setBatchId(batch.getId());
  582. vo.setCreateTime(batch.getCreateTime());
  583. vo.setDevice(batch.getDevice());
  584. ScannerEntity scanner = scannerService.findByDevice(batch.getDevice());
  585. vo.setDeviceName(scanner.getDeviceName());
  586. vo.setPackageCode(batch.getPackageCode());
  587. Map<Long, VerifyStudentVo> studentMap = new LinkedHashMap<>();
  588. List<BatchDetailDO> list = baseMapper.batchDetailList(batch.getId());
  589. for (BatchDetailDO item : list) {
  590. VerifyStudentVo student = studentMap.computeIfAbsent(item.getId(), key -> VerifyStudentVo.build(item));
  591. student.findOrCreatePaper(item).getPages().add(item.getSheetPath());
  592. }
  593. vo.setStudents(new ArrayList<>(studentMap.values()));
  594. return vo;
  595. }
  596. @Override
  597. public boolean applyVerifyTask(BatchEntity t, String user) {
  598. TaskLock taskLock = TaskLockUtil.getVerifyTask(t.getExamId().toString());
  599. boolean lock = taskLock.add(t.getId(), user);
  600. // 上锁失败直接返回
  601. if (!lock) {
  602. return false;
  603. }
  604. // 重复校验任务状态
  605. if (VerifyStatus.WAITING.equals(t.getVerifyStatus())) {
  606. return true;
  607. } else {
  608. taskLock.remove(t.getId().toString());
  609. return false;
  610. }
  611. }
  612. @Override
  613. public void releaseVerifyTask(Long examId, String account) {
  614. TaskLock taskLock = TaskLockUtil.getVerifyTask(examId.toString());
  615. taskLock.clear(account);
  616. }
  617. @Override
  618. public void releaseVerifyTask(BatchEntity t) {
  619. TaskLock taskLock = TaskLockUtil.getVerifyTask(t.getExamId().toString());
  620. taskLock.remove(t.getId().toString());
  621. }
  622. @Override
  623. public boolean hasAppliedVerifyTask(BatchEntity t, String user) {
  624. TaskLock taskLock = TaskLockUtil.getVerifyTask(t.getExamId().toString());
  625. return taskLock.exist(t.getId(), user);
  626. }
  627. @Override
  628. public IPage<BatchEntity> findUnVerify(Long examId, int pageNumber, int pageSize, VerifyStatus status) {
  629. return this.baseMapper.findUnVerify(new Page<>(pageNumber, pageSize), examId, status);
  630. }
  631. @Override
  632. public List<String> batchScanner(Long examId) {
  633. return baseMapper.batchScanner(examId);
  634. }
  635. @Override
  636. public List<ScannerWorkloadVo> workload(WorkloadDomain domain) {
  637. Long examId = domain.getExamId();
  638. if (examId == null) {
  639. throw new ParameterException("examId不能为空");
  640. }
  641. return baseMapper.workload(domain);
  642. }
  643. @Override
  644. public Integer findStudentCountByBatch(Long batchId) {
  645. return baseMapper.findStudentCountByBatch(batchId);
  646. }
  647. @Override
  648. public BatchEntity findByPaperId(Long paperId) {
  649. return baseMapper.selectById(batchPaperService.findByPaperId(paperId).getBatchId());
  650. }
  651. @Override
  652. public TaskStatusVo getCheckImageStatus(Long examId, User user) {
  653. TaskStatusVo vo = new TaskStatusVo();
  654. vo.setFinishCount(this.getCheckCountByExamId(examId, CheckStatus.FINISH, user));
  655. vo.setTodoCount(this.getCheckCountByExamId(examId, CheckStatus.WAITING, user));
  656. return vo;
  657. }
  658. @Override
  659. public VerifyTaskVo getCheckImageTask(Long examId, User user) {
  660. ExamEntity exam = examService.getById(examId);
  661. int retry = 1;
  662. VerifyTaskVo task = null;
  663. while (task == null) {
  664. List<BatchEntity> list = this
  665. .findUnCheck(examId, retry, 20, BatchStatus.FINISH, CheckStatus.WAITING, exam.getImageCheckOrder())
  666. .getRecords();
  667. if (list.isEmpty()) {
  668. break;
  669. }
  670. for (BatchEntity t : list) {
  671. if (this.applyCheckImageTask(t, user.getAccount())) {
  672. task = toCheckTaskVo(t);
  673. break;
  674. }
  675. }
  676. if (task == null) {
  677. retry++;
  678. }
  679. }
  680. if (task == null) {
  681. throw NotFoundExceptions.NO_CHECK_IMAGE_TASK;
  682. }
  683. return task;
  684. }
  685. private VerifyTaskVo toCheckTaskVo(BatchEntity batch) {
  686. VerifyTaskVo vo = new VerifyTaskVo();
  687. vo.setBatchId(batch.getId());
  688. vo.setCreateTime(batch.getCreateTime());
  689. vo.setDevice(batch.getDevice());
  690. ScannerEntity scanner = scannerService.findByDevice(batch.getDevice());
  691. vo.setDeviceName(scanner.getDeviceName());
  692. vo.setPackageCode(batch.getPackageCode());
  693. Map<Long, VerifyStudentVo> studentMap = new LinkedHashMap<>();
  694. List<BatchDetailDO> list = baseMapper.batchDetailListToCheck(batch.getId(), true);
  695. for (BatchDetailDO item : list) {
  696. VerifyStudentVo student = studentMap.computeIfAbsent(item.getId(), key -> VerifyStudentVo.build(item));
  697. student.findOrCreatePaper(item).getPages().add(item.getSheetPath());
  698. }
  699. vo.setStudents(new ArrayList<>(studentMap.values()));
  700. return vo;
  701. }
  702. @Override
  703. public VerifyTaskVo getHistoryCheckImageTask(Long examId, Long batchId, User user) {
  704. if (examId == null) {
  705. throw new ParameterException("examId不能为空");
  706. }
  707. if (user.getId() == null) {
  708. throw new ParameterException("userId不能为空");
  709. }
  710. BatchEntity batchEntity = this.baseMapper.getHistory(examId, batchId, user.getId(), CheckStatus.FINISH);
  711. if (batchEntity == null) {
  712. throw NotFoundExceptions.NO_CHECK_IMAGE_TASK;
  713. }
  714. VerifyTaskVo task = toCheckTaskVo(batchEntity);
  715. return task;
  716. }
  717. @Transactional
  718. @Override
  719. public CheckImageSubmitVo submitCheckImageTask(Long examId, Long batchId, User user) {
  720. BatchEntity entity = this.getById(batchId);
  721. if (entity == null) {
  722. throw new ParameterException("批次不存在");
  723. }
  724. if (entity.getCheckStatus() == CheckStatus.FINISH) {
  725. throw new ParameterException("该批次已经被审核");
  726. }
  727. entity.setCheckStatus(CheckStatus.FINISH);
  728. entity.setCheckImageTime(System.currentTimeMillis());
  729. entity.setCheckImageUserId(user.getId());
  730. this.saveOrUpdate(entity);
  731. this.releaseCheckImageTask(entity);
  732. CheckImageSubmitVo vo = new CheckImageSubmitVo();
  733. vo.setBatchId(batchId);
  734. vo.setUpdateTime(entity.getUpdateTime());
  735. vo.getStatus().setFinishCount(this.getCheckCountByExamId(examId, CheckStatus.FINISH, user));
  736. vo.getStatus().setTodoCount(this.getCheckCountByExamId(examId, CheckStatus.WAITING, user));
  737. return vo;
  738. }
  739. @Override
  740. public void releaseCheckImageTask(Long examId, User user) {
  741. TaskLock taskLock = TaskLockUtil.getImageCheckTask(examId.toString());
  742. taskLock.clear(user.getAccount());
  743. }
  744. @Override
  745. public int getCheckCountByExamId(Long examId, CheckStatus status, User user) {
  746. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  747. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  748. lw.eq(BatchEntity::getExamId, examId);
  749. lw.eq(BatchEntity::getCheckStatus, status);
  750. lw.eq(BatchEntity::getStatus, BatchStatus.FINISH);
  751. // 审核员只统计自身已完成的数量
  752. if (status == CheckStatus.FINISH && user.getRole() == Role.AUDITOR) {
  753. lw.eq(BatchEntity::getCheckImageUserId, user.getId());
  754. }
  755. return this.count(wrapper);
  756. }
  757. @Override
  758. public void releaseCheckImageTask(BatchEntity t) {
  759. TaskLock taskLock = TaskLockUtil.getImageCheckTask(t.getExamId().toString());
  760. taskLock.remove(t.getId().toString());
  761. }
  762. @Override
  763. public IPage<BatchEntity> findUnCheck(Long examId, int pageNumber, int pageSize, BatchStatus batchStatus,
  764. CheckStatus checkStatus, String order) {
  765. Page<BatchEntity> page = new Page<>(pageNumber, pageSize);
  766. if ("ASC".equalsIgnoreCase(order)) {
  767. page.addOrder(OrderItem.asc("id"));
  768. } else {
  769. page.addOrder(OrderItem.desc("id"));
  770. }
  771. return this.baseMapper.findUnCheck(page, examId, batchStatus, checkStatus);
  772. }
  773. @Override
  774. public boolean applyCheckImageTask(BatchEntity t, String user) {
  775. TaskLock taskLock = TaskLockUtil.getImageCheckTask(t.getExamId().toString());
  776. boolean lock = taskLock.add(t.getId(), user);
  777. // 上锁失败直接返回
  778. if (!lock) {
  779. return false;
  780. }
  781. // 重复校验任务状态
  782. if (CheckStatus.WAITING.equals(t.getCheckStatus())) {
  783. return true;
  784. } else {
  785. taskLock.remove(t.getId().toString());
  786. return false;
  787. }
  788. }
  789. @Override
  790. @Transactional
  791. public void batchVerifyCancel(User user, Long examId) {
  792. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  793. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  794. lw.eq(BatchEntity::getExamId, examId);
  795. lw.eq(BatchEntity::getVerifyStatus, VerifyStatus.WAITING);
  796. List<BatchEntity> list = this.list(lw);
  797. for (BatchEntity batch : list) {
  798. List<BatchPaperEntity> batchPaperList = batchPaperService.findByBatchId(batch.getId());
  799. Map<Long, List<StudentPaperEntity>> studentMap = new HashMap<>();
  800. for (BatchPaperEntity batchPaper : batchPaperList) {
  801. studentMap.computeIfAbsent(batchPaper.getStudentId(), key -> new ArrayList<>())
  802. .add(new StudentPaperEntity(batchPaper.getStudentId(), batchPaper.getPaperNumber(),
  803. batchPaper.getPaperId()));
  804. }
  805. for (Map.Entry<Long, List<StudentPaperEntity>> entry : studentMap.entrySet()) {
  806. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + entry.getKey()).writeLock().lock();
  807. try {
  808. studentService.updateStudentAndPaper(user, entry.getKey(), entry.getValue());
  809. studentService.updateAssignedCheckCount(entry.getKey(), true);
  810. } finally {
  811. concurrentService.getReadWriteLock(LockType.STUDENT + "-" + entry.getKey()).writeLock().unlock();
  812. }
  813. }
  814. batch.setVerifyStatus(VerifyStatus.CANCEL);
  815. }
  816. this.updateBatchById(list);
  817. }
  818. @Override
  819. @Transactional
  820. public void updateBatchCount() {
  821. QueryWrapper<BatchEntity> wrapper = new QueryWrapper<>();
  822. LambdaQueryWrapper<BatchEntity> lw = wrapper.lambda();
  823. lw.eq(BatchEntity::getStatus, BatchStatus.INIT);
  824. List<BatchEntity> list = this.list(lw);
  825. for (BatchEntity batch : list) {
  826. updateAssignedCount(batch.getId());
  827. updateScanCount(batch.getId());
  828. }
  829. }
  830. @Override
  831. public List<SubjectVo> subjectList(BatchSubjectQuery req) {
  832. if (req.getExamId() == null) {
  833. throw new ParameterException("考试id不能为空");
  834. }
  835. return this.baseMapper.subjectList(req);
  836. }
  837. @Override
  838. public List<DeviceVo> deviceList(BatchDeviceQuery req) {
  839. if (req.getExamId() == null) {
  840. throw new ParameterException("考试id不能为空");
  841. }
  842. if (StringUtils.isBlank(req.getSubjectCode())) {
  843. throw new ParameterException("科目代码不能为空");
  844. }
  845. return this.baseMapper.deviceList(req);
  846. }
  847. @Override
  848. public List<BatchVo> batchList(BatchQuery req) {
  849. if (req.getExamId() == null) {
  850. throw new ParameterException("考试id不能为空");
  851. }
  852. if (StringUtils.isBlank(req.getSubjectCode())) {
  853. throw new ParameterException("科目代码不能为空");
  854. }
  855. if (StringUtils.isBlank(req.getDevice())) {
  856. throw new ParameterException("设备不能为空");
  857. }
  858. return this.baseMapper.batchList(req);
  859. }
  860. @Override
  861. public List<BatchStudentSimpleVo> studentList(Long batchId) {
  862. if (batchId == null) {
  863. throw new ParameterException("批次id不能为空");
  864. }
  865. return this.baseMapper.studentList(batchId);
  866. }
  867. }