package cn.com.qmth.scancentral.service.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.github.jeffreyning.mybatisplus.service.MppServiceImpl; import com.linuxense.javadbf.DBFDataType; import com.linuxense.javadbf.DBFField; import com.linuxense.javadbf.DBFWriter; import com.qmth.boot.core.exception.ParameterException; import com.qmth.boot.core.exception.StatusException; import com.qmth.boot.core.fss.store.FileStore; import com.qmth.boot.tools.uuid.FastUUID; import cn.com.qmth.scancentral.bean.ImportSubjectDomain; import cn.com.qmth.scancentral.bean.SubjectConfigDomain; import cn.com.qmth.scancentral.bean.User; import cn.com.qmth.scancentral.consumer.AnswerDataExportConsumer; import cn.com.qmth.scancentral.consumer.SliceDataExportConsumer; import cn.com.qmth.scancentral.dao.SubjectDao; import cn.com.qmth.scancentral.entity.ExamEntity; import cn.com.qmth.scancentral.entity.MarkSiteEntity; import cn.com.qmth.scancentral.entity.SubjectEntity; import cn.com.qmth.scancentral.enums.ExamStatus; import cn.com.qmth.scancentral.service.AsyncTaskService; import cn.com.qmth.scancentral.service.BatchPaperService; import cn.com.qmth.scancentral.service.ExamService; import cn.com.qmth.scancentral.service.FileService; import cn.com.qmth.scancentral.service.MarkSiteService; import cn.com.qmth.scancentral.service.StudentService; import cn.com.qmth.scancentral.service.SubjectService; import cn.com.qmth.scancentral.support.SpringContextHolder; import cn.com.qmth.scancentral.util.Calculator; import cn.com.qmth.scancentral.util.DateUtil; import cn.com.qmth.scancentral.util.FileUtil; import cn.com.qmth.scancentral.vo.ExportCetMarkingQueryVo; import cn.com.qmth.scancentral.vo.ExportCetVo; import cn.com.qmth.scancentral.vo.SubjectConfigVo; import cn.com.qmth.scancentral.vo.examinfo.SubjectConfig; import cn.com.qmth.scancentral.vo.subject.DataExportTaskVo; import cn.com.qmth.scancentral.vo.subject.ScanProgressVo; import cn.com.qmth.scancentral.vo.subject.SubjectBreachVo; import cn.com.qmth.scancentral.vo.subject.SubjectCustStatusVo; import cn.com.qmth.scancentral.vo.subject.SubjectScanProgressVo; import cn.com.qmth.scancentral.vo.subject.TaskIdVo; @Service public class SubjectServiceImpl extends MppServiceImpl implements SubjectService { private static final Logger log = LoggerFactory.getLogger(SubjectService.class); private static ExecutorService exec; @Autowired private ExamService examService; @Autowired private StudentService studentService; @Autowired private FileService fileService; @Autowired private FileStore fileStore; @Autowired private BatchPaperService batchPaperService; @Autowired private AsyncTaskService asyncTaskService; @Autowired private MarkSiteService markSiteService; static { int threadCount = 5; exec = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(threadCount * 2), r -> { Thread t = new Thread(r); return t; }, (r, executor) -> { if (!executor.isShutdown()) { try { executor.getQueue().put(r); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); } @Transactional @Override public int cleanByExamId(Long examId, String subjectCode) { QueryWrapper wrapper = new QueryWrapper<>(); LambdaQueryWrapper lw = wrapper.lambda(); lw.eq(SubjectEntity::getExamId, examId); lw.eq(SubjectEntity::getCode, subjectCode); return this.baseMapper.delete(wrapper); } @Override public List listByExamId(Long examId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(SubjectEntity::getExamId, examId); queryWrapper.lambda().orderByDesc(SubjectEntity::getCode); return this.list(queryWrapper); } @Override public SubjectEntity findByExamIdAndCode(Long examId, String code) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(SubjectEntity::getExamId, examId); queryWrapper.lambda().eq(SubjectEntity::getCode, code); return this.getOne(queryWrapper); } @Override public int countByExamId(Long examId) { QueryWrapper wrapper = new QueryWrapper<>(); LambdaQueryWrapper lw = wrapper.lambda(); lw.eq(SubjectEntity::getExamId, examId); return this.count(wrapper); } @Transactional @Override public int importSubject(List subjects) { Map> map = new HashMap>(); List list = this.list(); for (SubjectEntity subjectEntity : list) { Set set = map.get(subjectEntity.getExamId()); if (set == null) { set = new HashSet(); } set.add(subjectEntity.getCode()); map.put(subjectEntity.getExamId(), set); } List saveList = new ArrayList(); for (ImportSubjectDomain importSubjectDomain : subjects) { Set set = map.get(importSubjectDomain.getExamId()); if (set != null) { if (!set.contains(importSubjectDomain.getSubjectCode())) { SubjectEntity subjectEntity = new SubjectEntity(importSubjectDomain.getExamId(), importSubjectDomain.getSubjectCode(), importSubjectDomain.getSubjectName()); set.add(importSubjectDomain.getSubjectCode()); saveList.add(subjectEntity); } } else { set = new HashSet(); SubjectEntity subjectEntity = new SubjectEntity(importSubjectDomain.getExamId(), importSubjectDomain.getSubjectCode(), importSubjectDomain.getSubjectName()); set.add(importSubjectDomain.getSubjectCode()); saveList.add(subjectEntity); } map.put(importSubjectDomain.getExamId(), set); } this.saveOrUpdateBatchByMultiId(saveList); return saveList.size(); } @Transactional @Override public SubjectConfigVo config(User user, SubjectConfigDomain domain) { SubjectEntity s = this.findByExamIdAndCode(domain.getExamId(), domain.getSubjectCode()); if (s == null) { throw new ParameterException("找不到对应的科目"); } s.setPaperTypeBarcodeContent(domain.getPaperTypeBarcodeContent()); this.saveOrUpdateByMultiId(s); return new SubjectConfigVo(s); } @Override public List listConfigByExamId(Long examId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(SubjectEntity::getExamId, examId); queryWrapper.lambda().isNotNull(SubjectEntity::getPaperTypeBarcodeContent); List list = this.list(queryWrapper); List result = new ArrayList(); for (SubjectEntity subjectEntity : list) { result.add(new SubjectConfig(subjectEntity)); } return result; } @Override public ScanProgressVo scanProgress(Long examId, String subjectCode) { ScanProgressVo ret = new ScanProgressVo(); List subjects = studentService.scanProgress(examId, subjectCode); ret.setSubjects(subjects); ret.setTotal(new SubjectScanProgressVo()); if (CollectionUtils.isNotEmpty(subjects)) { Calendar now = Calendar.getInstance(); long endTime = now.getTimeInMillis(); now.add(Calendar.MINUTE, -1); long startTime = now.getTimeInMillis(); for (SubjectScanProgressVo vo : subjects) { vo.setScannedCount(vo.getStudentCount() - vo.getUnexistCount()); vo.setProgress(Calculator.percentage(vo.getScannedCount(), vo.getStudentCount(), 2)); Integer count = batchPaperService.getScanStudentCount(examId, subjectCode, startTime, endTime); if (count == null || count == 0) { vo.setEstimation("-"); } else { vo.setEstimation(Calculator.divide( Calculator.multiply(Calculator.divide(count, endTime - startTime), vo.getUnexistCount()), 3600000) + "h"); } } int totalUnScanned = studentService.getUnscannedCountByExam(examId); int totalCount = studentService.getCountByExam(examId); ret.getTotal().setStudentCount(totalCount); ret.getTotal().setUnexistCount(totalUnScanned); ret.getTotal().setProgress( Calculator.percentage(ret.getTotal().getScannedCount(), ret.getTotal().getStudentCount(), 2)); Integer count = batchPaperService.getScanStudentCount(examId, null, startTime, endTime); if (count == null || count == 0) { ret.getTotal().setEstimation("-"); } else { ret.getTotal().setEstimation( Calculator.divide(Calculator.multiply(Calculator.divide(count, endTime - startTime), ret.getTotal().getUnexistCount()), 3600000) + "h"); } } return ret; } @Override public List breachList(Long examId) { List ret = new ArrayList<>(); List subjects = listByExamId(examId); if (CollectionUtils.isNotEmpty(subjects)) { for (SubjectEntity e : subjects) { SubjectBreachVo vo = new SubjectBreachVo(); ret.add(vo); vo.setSubjectCode(e.getCode()); vo.setSubjectName(e.getName()); String url = fileService.getBreachInfoUri(examId, e.getCode()); try { if (fileStore.exist(url)) { vo.setBreachCount(Integer.valueOf(FileUtil.readFileContent(fileStore.read(url)).trim())); } else { vo.setBreachCount(0); } } catch (Exception e1) { throw new RuntimeException("文件访问异常", e1); } } } return ret; } @Override public List custStatusList(Long examId) { List ret = new ArrayList<>(); List subjects = listByExamId(examId); if (CollectionUtils.isNotEmpty(subjects)) { for (SubjectEntity e : subjects) { SubjectCustStatusVo vo = new SubjectCustStatusVo(); ret.add(vo); vo.setSubjectCode(e.getCode()); vo.setSubjectName(e.getName()); String url = fileService.getCustStatusInfoUri(examId, e.getCode()); try { if (fileStore.exist(url)) { vo.setCustStatusCount(Integer.valueOf(FileUtil.readFileContent(fileStore.read(url)).trim())); } else { vo.setCustStatusCount(0); } } catch (Exception e1) { throw new RuntimeException("文件访问异常", e1); } } } return ret; } @Override public TaskIdVo answerDataExport(Long examId, String subjectCode) { ExportCetMarkingQueryVo req = new ExportCetMarkingQueryVo(); req.setExamId(examId); req.setSubjectCode(subjectCode); int scount = studentService.countCetMarking(req); if (scount == 0) { throw new StatusException("没有考生信息"); } DataExportTaskVo vo = new DataExportTaskVo(); vo.setTaskId(FastUUID.get()); vo.setTotalCount(scount); vo.setExamId(examId); vo.setSubjectCode(subjectCode); vo.setSuccess(true); vo.setProgress(0.0); AnswerDataExportConsumer com = SpringContextHolder.getBean(AnswerDataExportConsumer.class); com.setVo(vo); exec.execute(com); asyncTaskService.addTask(vo); return TaskIdVo.create(vo.getTaskId()); } @Override public TaskIdVo sliceDataExport(Long examId, String subjectCode) { ExportCetMarkingQueryVo req = new ExportCetMarkingQueryVo(); req.setExamId(examId); req.setSubjectCode(subjectCode); int scount = studentService.countCetMarking(req); if (scount == 0) { throw new StatusException("没有考生信息"); } DataExportTaskVo vo = new DataExportTaskVo(); vo.setTaskId(FastUUID.get()); vo.setTotalCount(scount); vo.setExamId(examId); vo.setSubjectCode(subjectCode); vo.setSuccess(true); vo.setProgress(0.0); SliceDataExportConsumer com = SpringContextHolder.getBean(SliceDataExportConsumer.class); com.setVo(vo); exec.execute(com); asyncTaskService.addTask(vo); return TaskIdVo.create(vo.getTaskId()); } @Override public void answerDataExportDispose(DataExportTaskVo vo) { try { File temDir = new File("temp/" + FastUUID.get() + "/"); temDir.mkdirs(); File dbf = new File(temDir.getAbsolutePath() + "/answer-data.dbf"); dbf.createNewFile(); ExportCetMarkingQueryVo req = new ExportCetMarkingQueryVo(); req.setExamId(vo.getExamId()); req.setSubjectCode(vo.getSubjectCode()); req.setPageSize(100000); int pageNumber = 0; req.setPageNumber(pageNumber); Set examNumbers = new HashSet<>(); Map cms = readCetMarking(vo.getExamId(), vo.getSubjectCode()); Map breachMap = breachMap(vo.getExamId(), vo.getSubjectCode()); Map custStatusMap = custStatusMap(vo.getExamId(), vo.getSubjectCode()); while (true) { req.setPageNumber(++pageNumber); List list = studentService.exportCetData(req); if (CollectionUtils.isEmpty(list)) { break; } exportAnswer(examNumbers, breachMap, custStatusMap, dbf, list, cms, vo); } ExamEntity exam = examService.getById(vo.getExamId()); SubjectEntity subejct = this.findByExamIdAndCode(vo.getExamId(), vo.getSubjectCode()); vo.setFileName(subejct.getName() + "_" + exam.getScanSite() + "_OMR.dbf"); vo.setFilePath(dbf.getAbsolutePath()); } catch (StatusException e) { vo.setSuccess(false); vo.setErrMsg(e.getMessage()); } catch (Exception e) { vo.setSuccess(false); vo.setErrMsg("系统异常"); log.error("导出扫描答案DBF异常", e); } } private void exportAnswer(Set examNumbers, Map breachMap, Map custStatusMap, File dbf, List list, Map cms, DataExportTaskVo vo) throws FileNotFoundException { FileOutputStream fos = null; DBFWriter writer = null; try { DBFField fields[] = new DBFField[11]; fields[0] = new DBFField(); fields[0].setName("Zkzh"); fields[0].setType(DBFDataType.CHARACTER); fields[0].setLength(15); fields[1] = new DBFField(); fields[1].setName("Xm"); fields[1].setType(DBFDataType.CHARACTER); fields[1].setLength(30); fields[2] = new DBFField(); fields[2].setName("Qk"); fields[2].setType(DBFDataType.NUMERIC); fields[2].setLength(4); fields[3] = new DBFField(); fields[3].setName("Km_h"); fields[3].setType(DBFDataType.NUMERIC); fields[3].setLength(4); fields[4] = new DBFField(); fields[4].setName("Smda"); fields[4].setType(DBFDataType.CHARACTER); fields[4].setLength(100); fields[5] = new DBFField(); fields[5].setName("Yc1"); fields[5].setType(DBFDataType.NUMERIC); fields[5].setLength(4); fields[6] = new DBFField(); fields[6].setName("Yc2"); fields[6].setType(DBFDataType.NUMERIC); fields[6].setLength(4); fields[7] = new DBFField(); fields[7].setName("Wi"); fields[7].setType(DBFDataType.NUMERIC); fields[7].setLength(2); fields[8] = new DBFField(); fields[8].setName("Barcode"); fields[8].setType(DBFDataType.CHARACTER); fields[8].setLength(6); fields[9] = new DBFField(); fields[9].setName("Pjd"); fields[9].setType(DBFDataType.CHARACTER); fields[9].setLength(2); fields[10] = new DBFField(); fields[10].setName("Kszt"); fields[10].setType(DBFDataType.CHARACTER); fields[10].setLength(2); fos = new FileOutputStream(dbf); writer = new DBFWriter(fos, Charset.forName("gbk")); writer.setFields(fields); for (ExportCetVo data : list) { if (examNumbers.contains(data.getExamNumber())) { continue; } vo.setProgressCount(vo.getProgressCount() + 1); examNumbers.add(data.getExamNumber()); Object[] rowData = new Object[11]; rowData[0] = data.getExamNumber(); rowData[1] = data.getName(); if (ExamStatus.ABSENT.equals(data.getExamStatus())) { rowData[2] = 1; } else { rowData[2] = 0; } rowData[3] = getIntFromString(data.getSubjectCode()); rowData[4] = data.getAnswer(); rowData[5] = data.getCardFirst(); rowData[6] = data.getCardSecond(); String breach = breachMap.get(data.getExamNumber()); rowData[7] = StringUtils.isBlank(breach) ? 0 : getIntFromString(breach); rowData[8] = data.getPaperType(); int subjectCode = Integer.valueOf(data.getExamNumber().substring(9, 10)); String markingCode; if (subjectCode >= 3 && subjectCode <= 9) { markingCode = "88"; } else { if (ExamStatus.ABSENT.equals(data.getExamStatus()) || "000000".equals(data.getPaperType()) || "999999".equals(data.getPaperType())) { markingCode = "00"; } else { int site = Integer.valueOf(data.getExamNumber().substring(10, 13)); MarkSiteEntity cm = cms.get(data.getSubjectCode() + "-" + data.getPaperType()); if (cm == null) { throw new StatusException("未找到评卷点数据,SubjectCode:" + data.getSubjectCode() + " PaperType:" + data.getPaperType()); } if (site % 2 == 0) { markingCode = cm.getEvenNumber(); } else { markingCode = cm.getOddNumber(); } } } rowData[9] = markingCode; String custStatus = custStatusMap.get(data.getExamNumber()); rowData[10] = StringUtils.isBlank(custStatus) ? "0" : custStatus; writer.addRecord(rowData); } } finally { if (writer != null) { writer.close(); } try { if (fos != null) { fos.close(); } } catch (IOException e) { } } } private Map breachMap(Long examId, String subjectCode) throws Exception { Map ret = new HashMap<>(); String path = fileService.getBreachUri(examId, subjectCode); if (fileStore.exist(path)) { List list = IOUtils.readLines(fileStore.read(path), "utf-8"); if (CollectionUtils.isNotEmpty(list)) { for (int i = 1; i < list.size(); i++) { String[] ss = list.get(i).split(","); ret.put(ss[0].trim(), ss[2].trim()); } } } return ret; } private Map custStatusMap(Long examId, String subjectCode) throws Exception { Map ret = new HashMap<>(); String path = fileService.getCustStatusUri(examId, subjectCode); if (fileStore.exist(path)) { List list = IOUtils.readLines(fileStore.read(path), "utf-8"); if (CollectionUtils.isNotEmpty(list)) { for (int i = 1; i < list.size(); i++) { String[] ss = list.get(i).split(","); ret.put(ss[0].trim(), ss[2].trim()); } } } return ret; } private Integer getIntFromString(String v) { if (v == null) { return null; } return Integer.valueOf(v); } private Map readCetMarking(Long examId, String subejctCode) { Map ret = new HashMap<>(); List list = markSiteService.findByExamAndSubject(examId, subejctCode); if (CollectionUtils.isNotEmpty(list)) { for (MarkSiteEntity e : list) { ret.put(e.getPaperType(), e); } } return ret; } @Override public void sliceDataExportDispose(DataExportTaskVo vo) { try { File temDir = new File("temp/" + FastUUID.get() + "/"); temDir.mkdirs(); File dbf = new File(temDir.getAbsolutePath() + "/slice-data.dbf"); dbf.createNewFile(); ExportCetMarkingQueryVo req = new ExportCetMarkingQueryVo(); req.setExamId(vo.getExamId()); req.setSubjectCode(vo.getSubjectCode()); req.setPageSize(100000); int pageNumber = 0; req.setPageNumber(pageNumber); Set examNumbers = new HashSet<>(); Map cms = readCetMarking(vo.getExamId(), vo.getSubjectCode()); while (true) { req.setPageNumber(++pageNumber); List list = studentService.exportCetData(req); if (CollectionUtils.isEmpty(list)) { break; } exportSlice(examNumbers, dbf, list, cms, vo); } ExamEntity exam = examService.getById(vo.getExamId()); vo.setFileName(exam.getScanSite() + "_" + vo.getTotalCount() + "_" + DateUtil.format(new Date(), DateUtil.DatePatterns.YYYYMMDDHH) + ".dbf"); vo.setFilePath(dbf.getAbsolutePath()); } catch (StatusException e) { vo.setSuccess(false); vo.setErrMsg(e.getMessage()); } catch (Exception e) { vo.setSuccess(false); vo.setErrMsg("系统异常"); log.error("导出打包DBF异常", e); } } private void exportSlice(Set examNumbers, File dbf, List list, Map cms, DataExportTaskVo vo) throws FileNotFoundException { FileOutputStream fos = null; DBFWriter writer = null; try { DBFField fields[] = new DBFField[3]; fields[0] = new DBFField(); fields[0].setName("Zkzh"); fields[0].setType(DBFDataType.CHARACTER); fields[0].setLength(15); fields[1] = new DBFField(); fields[1].setName("Yjd"); fields[1].setType(DBFDataType.CHARACTER); fields[1].setLength(2); fields[2] = new DBFField(); fields[2].setName("Wjdx"); fields[2].setType(DBFDataType.CHARACTER); fields[2].setLength(254); fos = new FileOutputStream(dbf); writer = new DBFWriter(fos, Charset.forName("gbk")); writer.setFields(fields); for (ExportCetVo data : list) { if (examNumbers.contains(data.getExamNumber())) { continue; } vo.setProgressCount(vo.getProgressCount() + 1); examNumbers.add(data.getExamNumber()); String markingCode; if ("888888".equals(data.getPaperType())) { markingCode = "88"; } else if (ExamStatus.ABSENT.equals(data.getExamStatus()) || "000000".equals(data.getPaperType()) || "999999".equals(data.getPaperType())) { markingCode = "00"; } else { int site = Integer.valueOf(data.getExamNumber().substring(10, 13)); MarkSiteEntity cm = cms.get(data.getPaperType()); if (cm == null) { throw new StatusException("未找到考点数据,PaperType:" + data.getPaperType()); } if (site % 2 == 0) { markingCode = cm.getEvenNumber(); } else { markingCode = cm.getOddNumber(); } } Object[] rowData = new Object[3]; rowData[0] = data.getExamNumber(); rowData[1] = markingCode; rowData[2] = StringUtils.join(data.getSliceImageInfo(), ";") + ";"; writer.addRecord(rowData); } } finally { if (writer != null) { writer.close(); } try { if (fos != null) { fos.close(); } } catch (IOException e) { } } } }