package cn.com.qmth.am.controller; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.alibaba.fastjson.util.IOUtils; import com.qmth.boot.api.annotation.Aac; import com.qmth.boot.api.constant.ApiConstant; import com.qmth.boot.core.concurrent.service.ConcurrentService; import cn.com.qmth.am.bean.DataKey; import cn.com.qmth.am.bean.ExportExcelDto; import cn.com.qmth.am.bean.ModelSpeed; import cn.com.qmth.am.bean.StudentScoreVo; import cn.com.qmth.am.config.SysProperty; import cn.com.qmth.am.entity.QuestionEntity; import cn.com.qmth.am.entity.StudentScoreEntity; import cn.com.qmth.am.enums.DataStatus; import cn.com.qmth.am.enums.ImportFileName; import cn.com.qmth.am.enums.LockType; import cn.com.qmth.am.service.QuestionService; import cn.com.qmth.am.service.StudentScoreService; import cn.com.qmth.am.service.StudentService; import cn.com.qmth.am.utils.Calculator; import cn.com.qmth.am.utils.ExportUtils; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin") @Aac(strict = false, auth = false) public class AdminController { @Autowired private ModelSpeed modelSpeed; @Autowired private QuestionService questionService; @Autowired private StudentService studentService; @Autowired private StudentScoreService studentScoreService; @Autowired private SysProperty sysProperty; @Autowired private ConcurrentService concurrentService; @ApiOperation(value = "导出数据") @RequestMapping(value = "export", method = RequestMethod.GET) public void exportExcel(HttpServletResponse response, @RequestParam Long examId, @RequestParam(required = false) String subjectCode, @RequestParam(required = false) Boolean exZero, @RequestParam(required = false) Integer count, @RequestParam(required = false) Integer score) { List ret = new ArrayList<>(); List qs = questionService.findByExamIdAndSubject(examId, subjectCode); if (CollectionUtils.isNotEmpty(qs)) { for (QuestionEntity q : qs) { List scores = studentScoreService.findBy(q.getId(), exZero, count, score); if (CollectionUtils.isNotEmpty(scores)) { ExportExcelDto dto = new ExportExcelDto(); ret.add(dto); double[] a = new double[scores.size()]; double[] b = new double[scores.size()]; int i = 0; for (StudentScoreEntity s : scores) { a[i] = s.getAiScore(); b[i] = s.getMarkingScore(); i++; } dto.setExamId(q.getExamId() + ""); dto.setXgxs(new BigDecimal(Calculator.correlation(a, b)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue() + ""); double avg1 = new BigDecimal(Calculator.mean(a)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue(); dto.setAiAvg(avg1 + ""); double avg2 = new BigDecimal(Calculator.mean(b)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue(); dto.setSubjectCode(q.getSubjectCode()); dto.setMarkingAvg(avg2 + ""); dto.setSubjectName(q.getSubjectName()); dto.setQuestionTitle(q.getTitle()); dto.setQuetionNum(q.getMainNumber() + "-" + q.getSubNumber()); dto.setFullScore(q.getFullScore().toString()); dto.setTotalCount(scores.size() + ""); dto.setValidCount(scores.size() + ""); double avgdiff = Calculator.subtract(avg1, avg2, 2); if (avgdiff < 0) { avgdiff = 0 - avgdiff; } dto.setAvgDiff(avgdiff + ""); fill(scores, dto, q.getFullScore(), score); } } } ExportUtils.exportExcel("机评数据", ExportExcelDto.class, ret, response); } @ApiOperation(value = "分析数据") @RequestMapping(value = "fenxi", method = RequestMethod.GET) public void fenxi(HttpServletResponse response, @RequestParam Long examId, @RequestParam(required = false) String subjectCode, @RequestParam(required = false) Boolean exZero, @RequestParam(required = false) Integer count, @RequestParam(required = false) Integer score) { StringBuilder sb = new StringBuilder(); List qs = questionService.findByExamIdAndSubject(examId, subjectCode); if (CollectionUtils.isEmpty(qs)) { sb.append("试题数:0"); returnJson(sb.toString(), response); return; } sb.append("试题数:" + qs.size() + "\r\n"); for (QuestionEntity q : qs) { sb.append( "---------------------------------------------------------------------------------------------------------------------------------\r\n"); List scores = studentScoreService.findBy(q.getId(), exZero, count, score); if (CollectionUtils.isEmpty(scores)) { sb.append(q.getSubjectCode() + "|" + q.getMainNumber() + "|" + q.getSubNumber() + "| 相关系数:- \r\n"); } else { double[] a = new double[scores.size()]; double[] b = new double[scores.size()]; int i = 0; for (StudentScoreEntity s : scores) { a[i] = s.getAiScore(); b[i] = s.getMarkingScore(); i++; } try { double ret = new BigDecimal(Calculator.correlation(a, b)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue(); double avg1 = new BigDecimal(Calculator.mean(a)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue(); double avg2 = new BigDecimal(Calculator.mean(b)).setScale(2, BigDecimal.ROUND_HALF_UP) .doubleValue(); double diff = Calculator.absoluteDiff(avg1, avg2, 2); sb.append(q.getSubjectCode() + "|" + q.getMainNumber() + "|" + q.getSubNumber() + "| 题目满分:" + q.getFullScore() + "| 相关系数:" + ret + "| 人评均分:" + avg2 + "| 机评均分:" + avg1 + " | 均分差值:" + diff + "| 题数:" + scores.size() + " \r\n"); fill(scores, sb, q.getFullScore(), score); fillMarkingCount(scores, sb); fillAiCount(scores, sb); } catch (Exception e) { sb.append( q.getSubjectCode() + "|" + q.getMainNumber() + "|" + q.getSubNumber() + "| 相关系数出错:- \r\n"); } } } returnJson(sb.toString(), response); } private void fill(List scores, ExportExcelDto dto, Double questionScore, Integer score) { int total = scores.size(); int st = 0; Map ret = new HashMap<>(); for (StudentScoreEntity s : scores) { DataKey k = getKey(s.getAiScore() - s.getMarkingScore()); Integer tem = ret.get(k); if (tem == null) { tem = 0; } ret.put(k, tem + 1); } List ks = new ArrayList<>(ret.keySet()); ks.sort(new Comparator() { @Override public int compare(DataKey o1, DataKey o2) { int c1 = o1.getIndex(); int c2 = o2.getIndex(); if (c1 < c2) { return -1; } else if (c1 > c2) { return 1; } else { return 0; } } }); double roundedValue = Math.round(questionScore * 0.3); if (score != null) { roundedValue = score; } dto.setSameScoreRange(roundedValue + ""); StringBuilder sb = new StringBuilder(); for (DataKey k : ks) { Integer c = ret.get(k); if (k.getIndex() < roundedValue) { st = st + c; } sb.append(" " + k.getKey() + " " + c + " " + Calculator.percentage(c, total, 2) + " \r\n"); } dto.setSame(Calculator.percentage(st, total, 2)); dto.setSameCount(st + ""); dto.setSameDesc(sb.toString()); } private void fill(List scores, StringBuilder sb, Double questionScore, Integer score) { int total = scores.size(); int st = 0; Map ret = new HashMap<>(); for (StudentScoreEntity s : scores) { DataKey k = getKey(s.getAiScore() - s.getMarkingScore()); Integer tem = ret.get(k); if (tem == null) { tem = 0; } ret.put(k, tem + 1); } List ks = new ArrayList<>(ret.keySet()); ks.sort(new Comparator() { @Override public int compare(DataKey o1, DataKey o2) { int c1 = o1.getIndex(); int c2 = o2.getIndex(); if (c1 < c2) { return -1; } else if (c1 > c2) { return 1; } else { return 0; } } }); double roundedValue = Math.round(questionScore * 0.3); if (score != null) { roundedValue = score; } sb.append(" 差值分档: \r\n"); for (DataKey k : ks) { Integer c = ret.get(k); if (k.getIndex() < roundedValue) { st = st + c; } sb.append(" " + k.getKey() + "\t" + c + "\t" + Calculator.percentage(c, total, 2) + " \r\n"); } sb.append(" 一致率(≤" + roundedValue + "分)\t" + st + "\t" + Calculator.percentage(st, total, 2) + " \r\n"); } private void fillMarkingCount(List scores, StringBuilder sb) { int total = scores.size(); Map ret = new HashMap<>(); for (StudentScoreEntity s : scores) { DataKey k = getKey(s.getMarkingScore()); Integer tem = ret.get(k); if (tem == null) { tem = 0; } ret.put(k, tem + 1); } List ks = new ArrayList<>(ret.keySet()); ks.sort(new Comparator() { @Override public int compare(DataKey o1, DataKey o2) { int c1 = o1.getIndex(); int c2 = o2.getIndex(); if (c1 < c2) { return -1; } else if (c1 > c2) { return 1; } else { return 0; } } }); sb.append(" 人评分档: \r\n"); for (DataKey k : ks) { Integer c = ret.get(k); sb.append(" " + k.getKey() + " " + c + " " + Calculator.percentage(c, total, 2) + " \r\n"); } } private void fillAiCount(List scores, StringBuilder sb) { int total = scores.size(); Map ret = new HashMap<>(); for (StudentScoreEntity s : scores) { DataKey k = getKey(s.getAiScore()); Integer tem = ret.get(k); if (tem == null) { tem = 0; } ret.put(k, tem + 1); } List ks = new ArrayList<>(ret.keySet()); ks.sort(new Comparator() { @Override public int compare(DataKey o1, DataKey o2) { int c1 = o1.getIndex(); int c2 = o2.getIndex(); if (c1 < c2) { return -1; } else if (c1 > c2) { return 1; } else { return 0; } } }); sb.append(" 机评分档: \r\n"); for (DataKey k : ks) { Integer c = ret.get(k); sb.append(" " + k.getKey() + " " + c + " " + Calculator.percentage(c, total, 2) + " \r\n"); } } private static DataKey getKey(double s) { if (s < 0) { s = 0 - s; } if (s == 0) { DataKey r = new DataKey(); r.setKey("[0,0]"); r.setIndex(-1); return r; } int i = 0; for (;;) { if (i < s && s <= i + 1) { DataKey r = new DataKey(); r.setKey("(" + i + "," + (i + 1) + "]"); r.setIndex(i); return r; } i++; } } @ApiOperation(value = "进度详情") @RequestMapping(value = "info", method = RequestMethod.GET) public void info(HttpServletResponse response, @RequestParam(required = false) Long examId) { StringBuilder sb = new StringBuilder(); List qs = questionService.list(); int qstotal = 0; int qsCourse = 0; if (CollectionUtils.isNotEmpty(qs)) { Set cset = new HashSet<>(); qstotal = qs.size(); for (QuestionEntity q : qs) { cset.add(q.getSubjectCode()); } qsCourse = cset.size(); } sb.append("ocr是否开启:" + (sysProperty.getOcrTaskEnable() ? "是" : "否")); sb.append(" | 评分是否开启:" + (sysProperty.getMarkingTaskEnable() ? "是" : "否")); sb.append(" | 科目总数:" + qsCourse); sb.append(" | 小题总数:" + qstotal); double ocrSpeed = modelSpeed.getOcrSpeed(); double markingSpeed = modelSpeed.getMarkingSpeed(); sb.append(" | ocr速度:" + Calculator.scale(ocrSpeed, 2) + "个/秒"); sb.append(" | 评分速度:" + Calculator.scale(markingSpeed, 2) + "个/秒"); List vos = studentScoreService.getInfoByExam(examId); if (CollectionUtils.isEmpty(vos)) { returnJson(sb.toString(), response); return; } int ocrTodoCount = 0; int markingTodoCount = 0; if (examId != null) { Map> map = new LinkedHashMap<>(); for (StudentScoreVo vo : vos) { List tem = map.get(vo.getSubjectCode()); if (tem == null) { tem = new ArrayList<>(); map.put(vo.getSubjectCode(), tem); } tem.add(vo); if (!DataStatus.SUCCESS.equals(vo.getAnswerStatus())) { ocrTodoCount++; } if (!DataStatus.SUCCESS.equals(vo.getScoreStatus())) { markingTodoCount++; } } sb.append(" | ocr剩余时间:" + getLeftTime(ocrSpeed, ocrTodoCount)); sb.append(" | 评分剩余时间:" + getLeftTime(markingSpeed, markingTodoCount)); sb.append("\r\n"); subjectInfo(sb, examId, map); } else { Map>> exams = new LinkedHashMap<>(); for (StudentScoreVo vo : vos) { Map> sujects = exams.get(vo.getExamId()); if (sujects == null) { sujects = new LinkedHashMap<>(); exams.put(vo.getExamId(), sujects); } List tem = sujects.get(vo.getSubjectCode()); if (tem == null) { tem = new ArrayList<>(); sujects.put(vo.getSubjectCode(), tem); } tem.add(vo); if (!DataStatus.SUCCESS.equals(vo.getAnswerStatus())) { ocrTodoCount++; } if (!DataStatus.SUCCESS.equals(vo.getScoreStatus())) { markingTodoCount++; } } sb.append(" | ocr剩余时间:" + getLeftTime(ocrSpeed, ocrTodoCount)); sb.append(" | 评分剩余时间:" + getLeftTime(markingSpeed, markingTodoCount)); sb.append("\r\n"); for (Long exam : exams.keySet()) { subjectInfo(sb, exam, exams.get(exam)); } } returnJson(sb.toString(), response); } private String getLeftTime(double speed, int count) { if (speed == 0) { return "-"; } if (count == 0) { return getText(0); } int totalSeconds = Double.valueOf(Calculator.divide(count, speed, 0) + "").intValue(); return getText(totalSeconds); } private String getText(int totalSeconds) { int hours = totalSeconds / 3600; // 计算剩余秒数 int remainingSecondsAfterHours = totalSeconds % 3600; // 计算分钟数 int minutes = remainingSecondsAfterHours / 60; // 计算剩余秒数 int seconds = remainingSecondsAfterHours % 60; return String.format("%d小时%d分%d秒", hours, minutes, seconds); } private void subjectInfo(StringBuilder sb, Long examId, Map> subjects) { sb.append("\r\n-------------------------------------------------------------------------------\r\n"); List qs = questionService.findByExamId(examId); sb.append("考试id:" + examId); sb.append(" | 科目总数:" + subjects.size()); sb.append(" | 小题总数:" + qs.size()); Map qmap = new HashMap<>(); for (QuestionEntity q : qs) { Integer tem = qmap.get(q.getSubjectCode()); if (tem == null) { tem = 0; } qmap.put(q.getSubjectCode(), tem + 1); } for (String subject : subjects.keySet()) { sb.append("\r\n"); sb.append("\r\n"); sb.append(" 科目代码:" + subject); sb.append(" | 小题数:" + qmap.get(subject)); List vos = subjects.get(subject); Set studentSet = new HashSet<>(); int qocrsuc = 0; int qocrfailed = 0; int qsuc = 0; int qfailed = 0; int qtotal = 0; int markingtotal = 0; if (CollectionUtils.isNotEmpty(vos)) { qtotal = vos.size(); for (StudentScoreVo vo : vos) { studentSet.add(vo.getExamNumber()); if (DataStatus.SUCCESS.equals(vo.getAnswerStatus())) { qocrsuc++; } if (DataStatus.FAILED.equals(vo.getAnswerStatus())) { qocrfailed++; } if (DataStatus.SUCCESS.equals(vo.getScoreStatus())) { qsuc++; } if (DataStatus.FAILED.equals(vo.getScoreStatus())) { qfailed++; } if (vo.getMarkingScore() != null) { markingtotal++; } } } if (studentSet.size() == 0) { sb.append(" | 考生总数:0"); return; } sb.append(" | 考生总数:" + studentSet.size()); sb.append(" | 试题总数:" + qtotal); sb.append(" | 已人评数:" + markingtotal); sb.append(" | OCR成功总数:" + qocrsuc); sb.append(" | OCR失败总数:" + qocrfailed); sb.append(" | 评分成功总数:" + qsuc); sb.append(" | 评分失败总数:" + qfailed); } } @ApiOperation(value = "上传导入文件") @RequestMapping(value = "upload", method = RequestMethod.POST) public String upload(@RequestParam MultipartFile file) { if (ImportFileName.getByName(file.getOriginalFilename()) == null) { return "上传失败,文件名错误"; } File old = new File(sysProperty.getDataDir() + "/" + file.getOriginalFilename()); if (old.exists()) { return "上传失败,有正在处理的文件"; } InputStream in = null; try { in = file.getInputStream(); FileUtils.copyInputStreamToFile(file.getInputStream(), old); } catch (IOException e) { return "上传失败," + e.getMessage(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } return "上传成功"; } @ApiOperation(value = "重置数据") @RequestMapping(value = "reset", method = RequestMethod.GET) public String reset(@RequestParam Long examId, @RequestParam(required = false) String subjectCode) { boolean lock1 = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock(); if (!lock1) { return "重置失败,有任务正在处理,请稍后 再试"; } boolean lock2 = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock(); if (!lock2) { return "重置失败,有任务正在处理,请稍后 再试"; } try { studentService.reset(examId, subjectCode); return "重置成功"; } finally { if (lock2) { concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock(); } if (lock1) { concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().unlock(); } } } @ApiOperation(value = "删除数据") @RequestMapping(value = "clear", method = RequestMethod.GET) public String clear(@RequestParam Long examId, @RequestParam(required = false) String subjectCode) { boolean lock1 = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock(); if (!lock1) { return "删除失败,有任务正在处理,请稍后 再试"; } boolean lock2 = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock(); if (!lock2) { return "删除失败,有任务正在处理,请稍后 再试"; } try { studentService.clear(examId, subjectCode); return "删除成功"; } finally { if (lock2) { concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock(); } if (lock1) { concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().unlock(); } } } @ApiOperation(value = "机评开关") @RequestMapping(value = "marking/status", method = RequestMethod.GET) public String markingStatus(@RequestParam Boolean enable) { sysProperty.setMarkingTaskEnable(enable); return "设置成功:" + enable; } @ApiOperation(value = "ocr开关") @RequestMapping(value = "ocr/status", method = RequestMethod.GET) public String ocrStatus(@RequestParam Boolean enable) { sysProperty.setOcrTaskEnable(enable); return "设置成功:" + enable; } private void returnJson(String body, HttpServletResponse response) { response.setContentType("application/json;charset=utf-8"); PrintWriter writer = null; try { writer = response.getWriter(); writer.write(body); } catch (IOException e) { } finally { IOUtils.close(writer); } } }