xiatian 3 mēneši atpakaļ
vecāks
revīzija
ec09936736
28 mainītis faili ar 1202 papildinājumiem un 1229 dzēšanām
  1. 1 18
      db/am_db.sql
  2. 35 0
      src/main/java/cn/com/qmth/am/bean/StudentInfo.java
  3. 0 10
      src/main/java/cn/com/qmth/am/bean/StudentScoreImageDto.java
  4. 25 0
      src/main/java/cn/com/qmth/am/bean/StudentScoreInfo.java
  5. 2 8
      src/main/java/cn/com/qmth/am/config/InitData.java
  6. 0 25
      src/main/java/cn/com/qmth/am/config/StartOcr.java
  7. 6 6
      src/main/java/cn/com/qmth/am/config/SysProperty.java
  8. 0 47
      src/main/java/cn/com/qmth/am/consumer/BuildImageConsumer.java
  9. 48 0
      src/main/java/cn/com/qmth/am/consumer/MarkingConsumer.java
  10. 60 0
      src/main/java/cn/com/qmth/am/consumer/OcrConsumer.java
  11. 332 326
      src/main/java/cn/com/qmth/am/controller/AdminController.java
  12. 0 9
      src/main/java/cn/com/qmth/am/dao/StudentDao.java
  13. 5 0
      src/main/java/cn/com/qmth/am/dao/StudentScoreDao.java
  14. 0 75
      src/main/java/cn/com/qmth/am/entity/StudentEntity.java
  15. 0 10
      src/main/java/cn/com/qmth/am/entity/StudentScoreEntity.java
  16. 2 2
      src/main/java/cn/com/qmth/am/multithread/consumer/LocalOcrConsumer.java
  17. 2 2
      src/main/java/cn/com/qmth/am/multithread/producer/LocalOcrProducer.java
  18. 17 17
      src/main/java/cn/com/qmth/am/service/StudentScoreService.java
  19. 4 18
      src/main/java/cn/com/qmth/am/service/StudentService.java
  20. 3 3
      src/main/java/cn/com/qmth/am/service/impl/OcrServiceImpl.java
  21. 422 415
      src/main/java/cn/com/qmth/am/service/impl/QuestionServiceImpl.java
  22. 93 84
      src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java
  23. 25 115
      src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java
  24. 46 20
      src/main/java/cn/com/qmth/am/task/AiMarkingJob.java
  25. 22 15
      src/main/java/cn/com/qmth/am/task/OcrJob.java
  26. 40 0
      src/main/java/cn/com/qmth/am/utils/BatchSetDataUtil.java
  27. 5 4
      src/main/resources/application.properties
  28. 7 0
      src/main/resources/mapper/StudentScoreMapper.xml

+ 1 - 18
db/am_db.sql

@@ -26,28 +26,12 @@ CREATE TABLE `am_question` (
   UNIQUE KEY `IDX_QUESTION_01` (`exam_id`, `subject_code`, `main_number`, `sub_number`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
 
-DROP TABLE IF EXISTS `am_student`;
-CREATE TABLE `am_student` (
-  `id` bigint NOT NULL AUTO_INCREMENT,
-  `create_time` datetime DEFAULT NULL,
-  `update_time` datetime DEFAULT NULL,
-  `exam_id` bigint NOT NULL,
-  `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
-  `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
-  `data_status` varchar(255) NOT NULL,
-  `err_msg` varchar(2000) COLLATE utf8mb4_bin DEFAULT NULL,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `IDX_STUDENT_01` (`exam_id`, `subject_code`, `student_code`),
-  KEY `IDX_STUDENT_02` (`student_code`),
-  KEY `IDX_STUDENT_03` (`data_status`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
 
 DROP TABLE IF EXISTS `am_student_score`;
 CREATE TABLE `am_student_score` (
   `id` bigint NOT NULL AUTO_INCREMENT,
   `create_time` datetime DEFAULT NULL,
   `update_time` datetime DEFAULT NULL,
-  `student_id` bigint NOT NULL,
   `question_id` bigint NOT NULL,
   `exam_id` bigint NOT NULL,
   `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
@@ -64,6 +48,5 @@ CREATE TABLE `am_student_score` (
   `score_none` bit(1) DEFAULT NULL,
   `step_score` varchar(500) COLLATE utf8mb4_bin DEFAULT NULL,
   PRIMARY KEY (`id`),
-  UNIQUE KEY `IDX_STUDENT_SCORE_01` (`student_id`, `question_id`),
-  UNIQUE KEY `IDX_STUDENT_SCORE_02` (`exam_id`, `subject_code`, `student_code`,`main_number`, `sub_number`)
+  UNIQUE KEY `IDX_STUDENT_SCORE_01` (`exam_id`, `subject_code`, `student_code`,`question_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

+ 35 - 0
src/main/java/cn/com/qmth/am/bean/StudentInfo.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.am.bean;
+
+public class StudentInfo {
+
+    private Long examId;
+
+    private String subjectCode;
+
+    private String studentCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+}

+ 0 - 10
src/main/java/cn/com/qmth/am/bean/StudentScoreImageDto.java

@@ -4,8 +4,6 @@ public class StudentScoreImageDto {
 
     private Long studentScoreId;
 
-    private Long studentId;
-
     private byte[] image;
 
     private String suff;
@@ -20,14 +18,6 @@ public class StudentScoreImageDto {
         this.studentScoreId = studentScoreId;
     }
 
-    public Long getStudentId() {
-        return studentId;
-    }
-
-    public void setStudentId(Long studentId) {
-        this.studentId = studentId;
-    }
-
     public byte[] getImage() {
         return image;
     }

+ 25 - 0
src/main/java/cn/com/qmth/am/bean/StudentScoreInfo.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.am.bean;
+
+public class StudentScoreInfo {
+
+    private Long questionId;
+
+    private String studentCode;
+
+    public Long getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Long questionId) {
+        this.questionId = questionId;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+}

+ 2 - 8
src/main/java/cn/com/qmth/am/config/InitData.java

@@ -13,9 +13,7 @@ import org.springframework.stereotype.Component;
 import com.qmth.boot.core.solar.model.OrgInfo;
 import com.qmth.boot.core.solar.service.SolarService;
 
-import cn.com.qmth.am.service.OcrService;
 import cn.com.qmth.am.service.StudentScoreService;
-import cn.com.qmth.am.service.StudentService;
 
 @Component
 public class InitData implements CommandLineRunner {
@@ -31,11 +29,8 @@ public class InitData implements CommandLineRunner {
     @Autowired
     private StudentScoreService studentScoreService;
 
-    @Autowired
-    private StudentService studentService;
-
-    @Autowired
-    private OcrService ocrService;
+    // @Autowired
+    // private OcrService ocrService;
 
     @Override
     public void run(String... args) throws Exception {
@@ -60,7 +55,6 @@ public class InitData implements CommandLineRunner {
 
     private void resetTaskStatus() {
         studentScoreService.resetStatus();
-        studentService.resetStatus();
     }
 
 }

+ 0 - 25
src/main/java/cn/com/qmth/am/config/StartOcr.java

@@ -1,25 +0,0 @@
-package cn.com.qmth.am.config;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.stereotype.Component;
-
-import cn.com.qmth.am.bean.StudentScoreImageDto;
-import cn.com.qmth.am.service.StudentScoreService;
-
-@Component
-public class StartOcr implements CommandLineRunner {
-
-	@Autowired
-	private StudentScoreService studentScoreService;
-
-	@Override
-	public void run(String... args) throws Exception {
-		for(;;) {
-			StudentScoreImageDto dto=studentScoreService.pollStudentScoreImage();
-			studentScoreService.ocr(dto);
-		}
-	}
-	
-
-}

+ 6 - 6
src/main/java/cn/com/qmth/am/config/SysProperty.java

@@ -26,8 +26,8 @@ public class SysProperty {
     @Value("${am.data-type}")
     private DataType dataType;
 
-    @Value("${am.marking-ocr-thread-count:2}")
-    private Integer ocrThreadCount;
+    @Value("${am.marking-thread-count:2}")
+    private Integer threadCount;
 
     @Value("${am.marking-marking-model:solar}")
     private String markingModel;
@@ -143,12 +143,12 @@ public class SysProperty {
         this.ocrServer = ocrServer;
     }
 
-    public Integer getOcrThreadCount() {
-        return ocrThreadCount;
+    public Integer getThreadCount() {
+        return threadCount;
     }
 
-    public void setOcrThreadCount(Integer ocrThreadCount) {
-        this.ocrThreadCount = ocrThreadCount;
+    public void setThreadCount(Integer threadCount) {
+        this.threadCount = threadCount;
     }
 
 }

+ 0 - 47
src/main/java/cn/com/qmth/am/consumer/BuildImageConsumer.java

@@ -1,47 +0,0 @@
-package cn.com.qmth.am.consumer;
-
-import java.util.Map;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Scope;
-import org.springframework.stereotype.Service;
-
-import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
-import cn.com.qmth.am.service.StudentService;
-
-@Scope("prototype")
-@Service
-public class BuildImageConsumer implements Runnable {
-    private StudentEntity student;
-    private Map<Long,QuestionEntity> quetions;
-    @Autowired
-    private StudentService studentService;
-
-    @Override
-    public void run() {
-		studentService.buildImage(student,quetions);
-    }
-
-
-	public StudentEntity getStudent() {
-		return student;
-	}
-
-
-	public void setStudent(StudentEntity student) {
-		this.student = student;
-	}
-
-
-	public Map<Long,QuestionEntity> getQuetions() {
-		return quetions;
-	}
-
-
-	public void setQuetions(Map<Long,QuestionEntity> quetions) {
-		this.quetions = quetions;
-	}
-
-
-}

+ 48 - 0
src/main/java/cn/com/qmth/am/consumer/MarkingConsumer.java

@@ -0,0 +1,48 @@
+package cn.com.qmth.am.consumer;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.am.entity.StudentScoreEntity;
+import cn.com.qmth.am.service.StudentScoreService;
+
+@Scope("prototype")
+@Service
+public class MarkingConsumer implements Runnable {
+
+    private CountDownLatch endGate;
+
+    private StudentScoreEntity score;
+
+    @Autowired
+    private StudentScoreService studentScoreService;
+
+    @Override
+    public void run() {
+        try {
+            studentScoreService.aiMarking(score);
+        } finally {
+            endGate.countDown();
+        }
+    }
+
+    public CountDownLatch getEndGate() {
+        return endGate;
+    }
+
+    public void setEndGate(CountDownLatch endGate) {
+        this.endGate = endGate;
+    }
+
+    public StudentScoreEntity getScore() {
+        return score;
+    }
+
+    public void setScore(StudentScoreEntity score) {
+        this.score = score;
+    }
+
+}

+ 60 - 0
src/main/java/cn/com/qmth/am/consumer/OcrConsumer.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.am.consumer;
+
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.am.entity.QuestionEntity;
+import cn.com.qmth.am.entity.StudentScoreEntity;
+import cn.com.qmth.am.service.StudentService;
+
+@Scope("prototype")
+@Service
+public class OcrConsumer implements Runnable {
+
+    private CountDownLatch endGate;
+
+    private StudentScoreEntity score;
+
+    private Map<Long, QuestionEntity> quetions;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Override
+    public void run() {
+        try {
+            studentService.buildImage(score, quetions);
+        } finally {
+            endGate.countDown();
+        }
+    }
+
+    public CountDownLatch getEndGate() {
+        return endGate;
+    }
+
+    public void setEndGate(CountDownLatch endGate) {
+        this.endGate = endGate;
+    }
+
+    public StudentScoreEntity getScore() {
+        return score;
+    }
+
+    public void setScore(StudentScoreEntity score) {
+        this.score = score;
+    }
+
+    public Map<Long, QuestionEntity> getQuetions() {
+        return quetions;
+    }
+
+    public void setQuetions(Map<Long, QuestionEntity> quetions) {
+        this.quetions = quetions;
+    }
+
+}

+ 332 - 326
src/main/java/cn/com/qmth/am/controller/AdminController.java

@@ -40,342 +40,348 @@ import io.swagger.annotations.ApiOperation;
 @RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin")
 @Aac(strict = false, auth = false)
 public class AdminController {
-	@Autowired
-	private QuestionService questionService;
-	@Autowired
-	private StudentService studentService;
-	@Autowired
-	private StudentScoreService studentScoreService;
-	@Autowired
-	private SysProperty sysProperty;
-	@Autowired
-	private ConcurrentService concurrentService;
 
-	@ApiOperation(value = "分析数据")
-	@RequestMapping(value = "fenxi", method = RequestMethod.GET)
-	public void fenxi(HttpServletResponse response, @RequestParam Long examId,
-			@RequestParam(required = false) Boolean exZero, @RequestParam(required = false) Integer count,
-			@RequestParam(required = false) Integer score) {
-		StringBuilder sb = new StringBuilder();
-		List<QuestionEntity> qs = questionService.findByExamId(examId);
-		if (CollectionUtils.isEmpty(qs)) {
-			sb.append("试题数:0");
-			returnJson(sb.toString(), response);
-			return;
-		}
-		sb.append("试题数:" + qs.size() + "\r\n");
-		for (QuestionEntity q : qs) {
-			List<StudentScoreEntity> scores = studentScoreService.findBy(examId, q.getSubjectCode(), q.getMainNumber(),
-					q.getSubNumber(), 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();
-					sb.append(q.getSubjectCode() + "|" + q.getMainNumber() + "|" + q.getSubNumber()+ "| 题目满分:" + q.getFullScore()
-							+ "| 相关系数:" + ret+ "| 人评均分:" + avg2 + "| 机评均分:" + avg1 + "| 题数:" + 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);
-	}
+    @Autowired
+    private QuestionService questionService;
 
-	private void fill(List<StudentScoreEntity> scores, StringBuilder sb,Double questionScore,Integer score) {
-		int total = scores.size();
-		int st = 0;
-		Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
-		ks.sort(new Comparator<DataKey>() {
-			@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;
-		}
-		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");
-		}
-		sb.append("      "+roundedValue+"分差值:" + st + "  " + Calculator.percentage(st, total, 2) + " \r\n");
-	}
-	
-	private void fillMarkingCount(List<StudentScoreEntity> scores, StringBuilder sb) {
-		int total = scores.size();
-		Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
-		ks.sort(new Comparator<DataKey>() {
-			@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<StudentScoreEntity> scores, StringBuilder sb) {
-		int total = scores.size();
-		Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
-		ks.sort(new Comparator<DataKey>() {
-			@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");
-		}
-	}
+    @Autowired
+    private StudentService studentService;
 
-	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++;
-		}
-	}
+    @Autowired
+    private StudentScoreService studentScoreService;
 
-	@ApiOperation(value = "进度详情")
-	@RequestMapping(value = "info", method = RequestMethod.GET)
-	public void info(HttpServletResponse response, @RequestParam Long examId) {
-		StringBuilder sb = new StringBuilder();
-		List<QuestionEntity> qs = questionService.findByExamId(examId);
-		int qstotal = 0;
-		int qsCourse = 0;
-		if (CollectionUtils.isNotEmpty(qs)) {
-			Set<String> cset = new HashSet<>();
-			qstotal = qs.size();
-			for (QuestionEntity q : qs) {
-				cset.add(q.getSubjectCode());
-			}
-			qsCourse = cset.size();
-		}
-		sb.append("ocr任务是否开启:" + (sysProperty.getOcrTaskEnable() ? "是" : "否") + "\r\n");
-		sb.append("评分任务是否开启:" + (sysProperty.getMarkingTaskEnable() ? "是" : "否") + "\r\n");
-		sb.append("试卷科目总数:" + qsCourse + "\r\n");
-		sb.append("试卷小题总数:" + qstotal + "\r\n");
-		int total = studentService.countBy(examId, null);
-		if (total == 0) {
-			sb.append("考生总数:0");
-			returnJson(sb.toString(), response);
-			return;
-		}
-		sb.append("考生总数:" + total + "\r\n");
-		int suc = studentService.countBy(examId, DataStatus.SUCCESS);
-		sb.append("考生处理成功总数:" + suc + "\r\n");
-		int failed = studentService.countBy(examId, DataStatus.FAILED);
-		sb.append("考生处理失败总数:" + failed + "\r\n");
-		int qtotal = studentScoreService.countBy(examId, null);
-		if (qtotal == 0) {
-			sb.append("试题总数:0");
-			returnJson(sb.toString(), response);
-			return;
-		}
-		sb.append("考生试题总数:" + qtotal + "\r\n");
+    @Autowired
+    private SysProperty sysProperty;
 
-		int qocrsuc = studentScoreService.countOcrBy(examId, DataStatus.SUCCESS);
-		sb.append("考生试题OCR成功总数:" + qocrsuc + "\r\n");
-		int qocrfailed = studentScoreService.countOcrBy(examId, DataStatus.FAILED);
-		sb.append("考生试题OCR失败总数:" + qocrfailed + "\r\n");
+    @Autowired
+    private ConcurrentService concurrentService;
 
-		int qsuc = studentScoreService.countBy(examId, DataStatus.SUCCESS);
-		sb.append("考生试题评分成功总数:" + qsuc + "\r\n");
-		int qfailed = studentScoreService.countBy(examId, DataStatus.FAILED);
-		sb.append("考生试题评分失败总数:" + qfailed + "\r\n");
-		returnJson(sb.toString(), response);
-	}
+    @ApiOperation(value = "分析数据")
+    @RequestMapping(value = "fenxi", method = RequestMethod.GET)
+    public void fenxi(HttpServletResponse response, @RequestParam Long examId,
+            @RequestParam(required = false) Boolean exZero, @RequestParam(required = false) Integer count,
+            @RequestParam(required = false) Integer score) {
+        StringBuilder sb = new StringBuilder();
+        List<QuestionEntity> qs = questionService.findByExamId(examId);
+        if (CollectionUtils.isEmpty(qs)) {
+            sb.append("试题数:0");
+            returnJson(sb.toString(), response);
+            return;
+        }
+        sb.append("试题数:" + qs.size() + "\r\n");
+        for (QuestionEntity q : qs) {
+            List<StudentScoreEntity> scores = studentScoreService.findBy(examId, q.getSubjectCode(), q.getMainNumber(),
+                    q.getSubNumber(), 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();
+                    sb.append(q.getSubjectCode() + "|" + q.getMainNumber() + "|" + q.getSubNumber() + "| 题目满分:"
+                            + q.getFullScore() + "| 相关系数:" + ret + "| 人评均分:" + avg2 + "| 机评均分:" + avg1 + "| 题数:"
+                            + 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);
+    }
 
-	@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 "上传成功";
-	}
+    private void fill(List<StudentScoreEntity> scores, StringBuilder sb, Double questionScore, Integer score) {
+        int total = scores.size();
+        int st = 0;
+        Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
+        ks.sort(new Comparator<DataKey>() {
 
-	@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();
-			}
-		}
-	}
+            @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() + "  " + c + "  " + Calculator.percentage(c, total, 2) + " \r\n");
+        }
+        sb.append("        " + roundedValue + "分差值:" + st + "  " + Calculator.percentage(st, total, 2) + " \r\n");
+    }
 
-	@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();
-			}
-		}
-	}
+    private void fillMarkingCount(List<StudentScoreEntity> scores, StringBuilder sb) {
+        int total = scores.size();
+        Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
+        ks.sort(new Comparator<DataKey>() {
 
-	@ApiOperation(value = "机评开关")
-	@RequestMapping(value = "marking/status", method = RequestMethod.GET)
-	public String markingStatus(@RequestParam Boolean enable) {
-		sysProperty.setMarkingTaskEnable(enable);
-		return "设置成功:" + enable;
-	}
+            @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");
+        }
+    }
 
-	@ApiOperation(value = "ocr开关")
-	@RequestMapping(value = "ocr/status", method = RequestMethod.GET)
-	public String ocrStatus(@RequestParam Boolean enable) {
-		sysProperty.setOcrTaskEnable(enable);
-		return "设置成功:" + enable;
-	}
+    private void fillAiCount(List<StudentScoreEntity> scores, StringBuilder sb) {
+        int total = scores.size();
+        Map<DataKey, Integer> 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<DataKey> ks = new ArrayList<>(ret.keySet());
+        ks.sort(new Comparator<DataKey>() {
 
-	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);
-		}
-	}
+            @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 Long examId) {
+        StringBuilder sb = new StringBuilder();
+        List<QuestionEntity> qs = questionService.findByExamId(examId);
+        int qstotal = 0;
+        int qsCourse = 0;
+        if (CollectionUtils.isNotEmpty(qs)) {
+            Set<String> cset = new HashSet<>();
+            qstotal = qs.size();
+            for (QuestionEntity q : qs) {
+                cset.add(q.getSubjectCode());
+            }
+            qsCourse = cset.size();
+        }
+        sb.append("ocr任务是否开启:" + (sysProperty.getOcrTaskEnable() ? "是" : "否") + "\r\n");
+        sb.append("评分任务是否开启:" + (sysProperty.getMarkingTaskEnable() ? "是" : "否") + "\r\n");
+        sb.append("试卷科目总数:" + qsCourse + "\r\n");
+        sb.append("试卷小题总数:" + qstotal + "\r\n");
+        // int total = studentService.countBy(examId);
+        // if (total == 0) {
+        // sb.append("考生总数:0");
+        // returnJson(sb.toString(), response);
+        // return;
+        // }
+        // sb.append("考生总数:" + total + "\r\n");
+        int qtotal = studentScoreService.countBy(examId, null);
+        if (qtotal == 0) {
+            sb.append("试题总数:0");
+            returnJson(sb.toString(), response);
+            return;
+        }
+        sb.append("考生试题总数:" + qtotal + "\r\n");
+
+        int qocrsuc = studentScoreService.countOcrBy(examId, DataStatus.SUCCESS);
+        sb.append("考生试题OCR成功总数:" + qocrsuc + "\r\n");
+        int qocrfailed = studentScoreService.countOcrBy(examId, DataStatus.FAILED);
+        sb.append("考生试题OCR失败总数:" + qocrfailed + "\r\n");
+
+        int qsuc = studentScoreService.countBy(examId, DataStatus.SUCCESS);
+        sb.append("考生试题评分成功总数:" + qsuc + "\r\n");
+        int qfailed = studentScoreService.countBy(examId, DataStatus.FAILED);
+        sb.append("考生试题评分失败总数:" + qfailed + "\r\n");
+        returnJson(sb.toString(), response);
+    }
+
+    @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);
+        }
+    }
 }

+ 0 - 9
src/main/java/cn/com/qmth/am/dao/StudentDao.java

@@ -1,9 +0,0 @@
-package cn.com.qmth.am.dao;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
-import cn.com.qmth.am.entity.StudentEntity;
-
-public interface StudentDao extends BaseMapper<StudentEntity> {
-
-}

+ 5 - 0
src/main/java/cn/com/qmth/am/dao/StudentScoreDao.java

@@ -1,9 +1,14 @@
 package cn.com.qmth.am.dao;
 
+import java.util.List;
+
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 
+import cn.com.qmth.am.bean.StudentScoreInfo;
 import cn.com.qmth.am.entity.StudentScoreEntity;
 
 public interface StudentScoreDao extends BaseMapper<StudentScoreEntity> {
 
+    List<StudentScoreInfo> getAllList();
+
 }

+ 0 - 75
src/main/java/cn/com/qmth/am/entity/StudentEntity.java

@@ -1,75 +0,0 @@
-package cn.com.qmth.am.entity;
-
-import com.baomidou.mybatisplus.annotation.TableName;
-
-import cn.com.qmth.am.entity.base.IdEntity;
-import cn.com.qmth.am.enums.DataStatus;
-
-@TableName("am_student")
-public class StudentEntity extends IdEntity {
-
-	private static final long serialVersionUID = -6261302618070108336L;
-
-	private Long examId;
-
-	private String subjectCode;
-	private String studentCode;
-
-
-	private DataStatus dataStatus;
-	
-	private String errMsg;
-
-
-	public Long getExamId() {
-		return examId;
-	}
-
-
-	public void setExamId(Long examId) {
-		this.examId = examId;
-	}
-
-
-	public String getSubjectCode() {
-		return subjectCode;
-	}
-
-
-	public void setSubjectCode(String subjectCode) {
-		this.subjectCode = subjectCode;
-	}
-
-
-	public String getStudentCode() {
-		return studentCode;
-	}
-
-
-	public void setStudentCode(String studentCode) {
-		this.studentCode = studentCode;
-	}
-
-
-	public DataStatus getDataStatus() {
-		return dataStatus;
-	}
-
-
-	public void setDataStatus(DataStatus dataStatus) {
-		this.dataStatus = dataStatus;
-	}
-
-
-	public String getErrMsg() {
-		return errMsg;
-	}
-
-
-	public void setErrMsg(String errMsg) {
-		this.errMsg = errMsg;
-	}
-
-
-
-}

+ 0 - 10
src/main/java/cn/com/qmth/am/entity/StudentScoreEntity.java

@@ -15,8 +15,6 @@ public class StudentScoreEntity extends IdEntity {
 
     private static final long serialVersionUID = -6261302618070108336L;
 
-    private Long studentId;
-
     private Long questionId;
 
     private Long examId;
@@ -57,14 +55,6 @@ public class StudentScoreEntity extends IdEntity {
     @TableField(updateStrategy = FieldStrategy.IGNORED, typeHandler = JacksonTypeHandler.class)
     private List<Double> stepScore;
 
-    public Long getStudentId() {
-        return studentId;
-    }
-
-    public void setStudentId(Long studentId) {
-        this.studentId = studentId;
-    }
-
     public Integer getMainNumber() {
         return mainNumber;
     }

+ 2 - 2
src/main/java/cn/com/qmth/am/multithread/consumer/OcrConsumer.java → src/main/java/cn/com/qmth/am/multithread/consumer/LocalOcrConsumer.java

@@ -28,9 +28,9 @@ import cn.com.qmth.am.multithread.Consumer;
 
 @Scope("prototype")
 @Service
-public class OcrConsumer extends Consumer<OcrDto> {
+public class LocalOcrConsumer extends Consumer<OcrDto> {
 
-    private static final Logger log = LoggerFactory.getLogger(OcrConsumer.class);
+    private static final Logger log = LoggerFactory.getLogger(LocalOcrConsumer.class);
 
     @Autowired
     private OcrApiClient ocrApiClient;

+ 2 - 2
src/main/java/cn/com/qmth/am/multithread/producer/OcrProducer.java → src/main/java/cn/com/qmth/am/multithread/producer/LocalOcrProducer.java

@@ -7,10 +7,10 @@ import org.springframework.stereotype.Service;
 
 import cn.com.qmth.am.bean.OcrDto;
 import cn.com.qmth.am.multithread.Producer;
-import cn.com.qmth.am.multithread.consumer.OcrConsumer;
+import cn.com.qmth.am.multithread.consumer.LocalOcrConsumer;
 
 @Service
-public class OcrProducer extends Producer<OcrDto, OcrConsumer> {
+public class LocalOcrProducer extends Producer<OcrDto, LocalOcrConsumer> {
 
     @SuppressWarnings("unchecked")
     @Override

+ 17 - 17
src/main/java/cn/com/qmth/am/service/StudentScoreService.java

@@ -6,9 +6,9 @@ import java.util.Map;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 import cn.com.qmth.am.bean.AnswerImageDto;
+import cn.com.qmth.am.bean.StudentInfo;
 import cn.com.qmth.am.bean.StudentScoreImageDto;
 import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
 import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.DataStatus;
 
@@ -17,35 +17,35 @@ import cn.com.qmth.am.enums.DataStatus;
  */
 public interface StudentScoreService extends IService<StudentScoreEntity> {
 
-	void importScore();
+    void importScore();
 
-	List<StudentScoreEntity> getByStudentId(Long id);
+    void updateAnswerErr(Long id, String string);
 
-	void add(StudentEntity student, Map<Long, QuestionEntity> quetions,List<StudentScoreEntity> scores);
+    void createSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages);
 
-	void updateAnswerErr(Long id, String string);
+    // StudentScoreImageDto pollStudentScoreImage();
 
-	void createSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages);
+    void ocr(StudentScoreImageDto dto);
 
-	StudentScoreImageDto pollStudentScoreImage();
+    void aiMarking(StudentScoreEntity score);
 
-	void ocr(StudentScoreImageDto dto);
+    void resetStatus();
 
-	void aiMarking(StudentScoreEntity score);
+    List<StudentScoreEntity> findAllToAiMarking();
 
-	void resetStatus();
+    int countBy(Long examId, DataStatus success);
 
-	List<StudentScoreEntity> findAllToAiMarking();
+    int countOcrBy(Long examId, DataStatus success);
 
-	int countBy(Long examId, DataStatus success);
+    void removeBy(Long examId, String subjectCode);
 
-	int countOcrBy(Long examId, DataStatus success);
+    List<StudentScoreEntity> findBy(Long examId, String subjectCode, Integer mainNumber, String subNumber,
+            Boolean exZero, Integer count, Integer score);
 
-	void removeBy(Long examId, String subjectCode);
+    // List<StudentScoreEntity> findToAiMarking(Long studentId);
 
-	List<StudentScoreEntity> findBy(Long examId, String subjectCode, Integer mainNumber, String subNumber,
-			Boolean exZero, Integer count,Integer score);
+    void addStudentScore(List<StudentInfo> students);
 
-	List<StudentScoreEntity> findToAiMarking(Long studentId);
+    List<StudentScoreEntity> findAllToOcr();
 
 }

+ 4 - 18
src/main/java/cn/com/qmth/am/service/StudentService.java

@@ -1,46 +1,32 @@
 package cn.com.qmth.am.service;
 
 import java.io.InputStream;
-import java.util.List;
 import java.util.Map;
 
-import com.baomidou.mybatisplus.extension.service.IService;
-
 import cn.com.qmth.am.bean.AnswerImageDto;
 import cn.com.qmth.am.bean.ImportResult;
 import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
 import cn.com.qmth.am.entity.StudentScoreEntity;
-import cn.com.qmth.am.enums.DataStatus;
 
 /**
  * 类注释
  */
-public interface StudentService extends IService<StudentEntity> {
+public interface StudentService {
 
     void importStudent();
 
     ImportResult disposeFile(InputStream inputStream);
 
-    List<StudentEntity> findToDispose(Long examId);
-
-    void buildImage(StudentEntity student, Map<Long, QuestionEntity> quetions);
+    void buildImage(StudentScoreEntity score, Map<Long, QuestionEntity> quetions);
 
-    List<StudentScoreEntity> getOrCreateScores(StudentEntity student, Map<Long, QuestionEntity> quetions);
+    // List<StudentScoreEntity> getOrCreateScores(StudentEntity student,
+    // Map<Long, QuestionEntity> quetions);
 
     void createSlice(StudentScoreEntity score, Map<Long, QuestionEntity> quetions,
             Map<Integer, AnswerImageDto> answerImages);
 
-    void updateStatus(Long id, DataStatus to);
-
-    void resetStatus();
-
-    int countBy(Long examId, DataStatus success);
-
     void reset(Long examId, String subjectCode);
 
     void clear(Long examId, String subjectCode);
 
-    List<StudentEntity> findToMarking(Long examId);
-
 }

+ 3 - 3
src/main/java/cn/com/qmth/am/service/impl/OcrServiceImpl.java

@@ -16,7 +16,7 @@ import com.qmth.boot.core.solar.model.OrgInfo;
 import com.qmth.boot.core.solar.service.SolarService;
 
 import cn.com.qmth.am.bean.OcrDto;
-import cn.com.qmth.am.multithread.producer.OcrProducer;
+import cn.com.qmth.am.multithread.producer.LocalOcrProducer;
 import cn.com.qmth.am.service.OcrService;
 
 @Service
@@ -34,7 +34,7 @@ public class OcrServiceImpl implements OcrService {
         OrgInfo org = solarService.getOrgList().get(0);
         List<OcrDto> files = new ArrayList<>();
         disposeFile(files, dir, org);
-        OcrProducer producer = new OcrProducer();
+        LocalOcrProducer producer = new LocalOcrProducer();
         Map<String, Object> param = new HashMap<>();
         param.put("files", files);
         producer.startDispose(4, param, files.size());
@@ -45,7 +45,7 @@ public class OcrServiceImpl implements OcrService {
             }
             param = new HashMap<>();
             param.put("files", failed);
-            producer = new OcrProducer();
+            producer = new LocalOcrProducer();
             producer.startDispose(4, param, failed.size());
         }
         log.warn("OcrService ocr finish*************");

+ 422 - 415
src/main/java/cn/com/qmth/am/service/impl/QuestionServiceImpl.java

@@ -41,421 +41,428 @@ import cn.com.qmth.am.service.QuestionService;
 
 @Service
 public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity> implements QuestionService {
-	private Pattern scoreRex = Pattern.compile("\\[\\[([0-9][0-9]*(.[0-9]+){0,1})分\\]\\]");
-	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "科目名称", "大题号", "小题号", "满分", "试题内容",
-			"试题答案", "作答坐标" };
-	@Autowired
-	private SysProperty sysProperty;
-	@Autowired
-	private QuestionService questionService;
-
-	@Override
-	public void importQuestion() {
-		File dir = new File(sysProperty.getDataDir());
-		File[] fs = dir.listFiles();
-		if (fs == null || fs.length == 0) {
-			return;
-		}
-		for (File file : fs) {
-			if (!file.isFile() || !file.getName().equals(ImportFileName.QUESTION_IMPORT.getName())) {
-				continue;
-			}
-			InputStream inputStream = null;
-			ImportResult ret = null;
-			try {
-				inputStream = new FileInputStream(file);
-				ret = questionService.disposeFile(inputStream);
-			} catch (Exception e) {
-				String errMsg;
-				if (e instanceof FileNotFoundException) {
-					errMsg = "未找到文件:" + file.getAbsolutePath();
-				} else {
-					errMsg = "系统错误:" + e.getMessage();
-				}
-				ret = new ImportResult(errMsg);
-			} finally {
-				if (inputStream != null) {
-					try {
-						inputStream.close();
-					} catch (IOException e) {
-					}
-				}
-			}
-			moveFile(dir, file, ret);
-		}
-	}
-
-	private void moveFile(File dir, File file, ImportResult ret) {
-		try {
-			boolean succss = CollectionUtils.isEmpty(ret.getErrMsg());
-			if (succss) {
-				File sucDir = new File(dir.getAbsoluteFile() + "/success/");
-				if (!sucDir.exists()) {
-					sucDir.mkdir();
-				}
-				File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
-				if (targetFile.exists()) {
-					targetFile.delete();
-				}
-				FileUtils.copyFile(file, targetFile);
-				file.delete();
-				String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
-				File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
-				if (msgFile.exists()) {
-					msgFile.delete();
-				}
-				FileUtils.write(msgFile, ret.getCountInfo(), "utf-8");
-			} else {
-				File sucDir = new File(dir.getAbsoluteFile() + "/failed/");
-				if (!sucDir.exists()) {
-					sucDir.mkdir();
-				}
-				File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
-				if (targetFile.exists()) {
-					targetFile.delete();
-				}
-				FileUtils.copyFile(file, targetFile);
-				file.delete();
-				String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
-				File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
-				if (msgFile.exists()) {
-					msgFile.delete();
-				}
-				FileUtils.writeLines(msgFile, StandardCharsets.UTF_8.name(), ret.getErrMsg());
-			}
-		} catch (IOException e) {
-			throw new StatusException("文件处理出错", e);
-		}
-
-	}
-
-	private String errorMsg(int lineNum, String msg) {
-		return "第" + lineNum + "行 " + msg;
-	}
-
-	private String trimAndNullIfBlank(String s) {
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		return s.trim();
-	}
-
-	@Transactional
-	@Override
-	public ImportResult disposeFile(InputStream inputStream) {
-		List<DataMap> lineList = null;
-		ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
-		try {
-			lineList = reader.getDataMapList();
-		} catch (Exception e) {
-			throw new StatusException("Excel 解析失败");
-		}
-		if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
-			throw new StatusException("Excel表头错误");
-		}
-		if (CollectionUtils.isEmpty(lineList)) {
-			throw new StatusException("Excel无内容");
-		}
-		if (100001 < lineList.size()) {
-			throw new StatusException("数据行数不能超过100000");
-		}
-		List<QuestionEntity> ss = new ArrayList<>();
-		ImportResult ret = new ImportResult();
-		List<String> failRecords = new ArrayList<>();
-		ret.setErrMsg(failRecords);
-		for (int i = 0; i < lineList.size(); i++) {
-			DataMap line = lineList.get(i);
-
-			StringBuilder msg = new StringBuilder();
-
-			QuestionEntity imp = new QuestionEntity();
-			String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
-			if (StringUtils.isBlank(examId)) {
-				msg.append("  考试ID不能为空");
-			} else if (examId.length() > 20) {
-				msg.append("  考试ID不能超过20个字符");
-			} else {
-				try {
-					Long examIdVal = Long.parseLong(examId);
-					imp.setExamId(examIdVal);
-				} catch (NumberFormatException e) {
-					msg.append("  考试ID只能是数字");
-				}
-			}
-
-			String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
-			if (StringUtils.isBlank(subjectCode)) {
-				msg.append("  科目代码不能为空");
-			} else if (subjectCode.length() > 100) {
-				msg.append("  科目代码不能超过100个字符");
-			}
-			imp.setSubjectCode(subjectCode);
-
-			String subjectName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
-			if (StringUtils.isBlank(subjectName)) {
-				msg.append("  科目名称不能为空");
-			} else if (subjectName.length() > 100) {
-				msg.append("  科目名称不能超过100个字符");
-			}
-			imp.setSubjectName(subjectName);
-
-			String mainNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
-			if (StringUtils.isBlank(mainNum)) {
-				msg.append("  大题号不能为空");
-			} else if (mainNum.length() > 10) {
-				msg.append("  大题号不能超过10个字符");
-			} else {
-				try {
-					Integer mainNumVal = Integer.parseInt(mainNum);
-					if (mainNumVal <= 0) {
-						msg.append("  大题号必须大于0");
-					}
-					imp.setMainNumber(mainNumVal);
-				} catch (NumberFormatException e) {
-					msg.append("  大题号格式错误");
-				}
-			}
-
-			String subNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
-			if (StringUtils.isBlank(subNum)) {
-				msg.append("  小题号不能为空");
-			} else if (subNum.length() > 10) {
-				msg.append("  小题号不能超过10个字符");
-			}
-			imp.setSubNumber(subNum);
-
-			String fullScore = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
-			if (StringUtils.isBlank(fullScore)) {
-				msg.append("  满分不能为空");
-			} else if (fullScore.length() > 10) {
-				msg.append("  满分不能超过10个字符");
-			} else {
-				try {
-					Double fullScoreVal = Double.parseDouble(fullScore);
-					if (fullScoreVal <= 0) {
-						msg.append("  满分必须大于0");
-					}
-					imp.setFullScore(fullScoreVal);
-				} catch (NumberFormatException e) {
-					msg.append("  满分格式错误");
-				}
-			}
-
-			String content = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
-			if (StringUtils.isBlank(content)) {
-				msg.append("  试题内容不能为空");
-			}
-			imp.setContent(content);
-
-			String answer = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
-//			if (StringUtils.isBlank(answer)) {
-//				msg.append("  试题答案不能为空");
-//			}
-			imp.setAnswer(getStandardAnswer(answer));
-
-			String imageSlice = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
-			if (StringUtils.isBlank(imageSlice)) {
-				msg.append("  作答坐标不能为空");
-			} else if (imageSlice.length() > 1000) {
-				msg.append("  作答坐标不能超过1000个字符");
-			} else {
-				List<ImageSlice> val = getImageSlice(imageSlice);
-				if (val == null) {
-					msg.append("  作答坐标格式有误");
-				} else {
-					imp.setImageSlice(val);
-				}
-			}
-
-			if (msg.length() > 0) {
-				failRecords.add(errorMsg(i + 2, msg.toString()));
-			} else {
-				ss.add(imp);
-			}
-
-		}
-
-		if (CollectionUtils.isNotEmpty(failRecords)) {
-			return ret;
-		}
-		try {
-			saveQuestionBatch(ret, ss);
-		} catch (Exception e) {
-			failRecords.add("系统错误:" + e.getMessage());
-		}
-		return ret;
-	}
-
-	private List<ImageSlice> getImageSlice(String s) {
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		s = s.trim();
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		try {
-			List<ImageSlice> list = new ArrayList<>();
-			String[] items = s.split(",");
-			for (int i = 0; i < items.length; i++) {
-				String item = items[i];
-				item = item.trim();
-				String[] config = item.split(":");
-				if (config.length != 5) {
-					return null;
-				}
-				int iVal = Integer.valueOf(config[0]);
-				int x = Integer.valueOf(config[1]);
-				int y = Integer.valueOf(config[2]);
-				int w = Integer.valueOf(config[3]);
-				int h = Integer.valueOf(config[4]);
-				ImageSlice ret = new ImageSlice();
-				ret.setH(h);
-				ret.setI(iVal);
-				ret.setW(w);
-				ret.setX(x);
-				ret.setY(y);
-				if (ret.getH() == null || ret.getI() == null || ret.getW() == null || ret.getX() == null
-						|| ret.getY() == null || ret.getI() < 0) {
-					return null;
-				}
-				list.add(ret);
-			}
-			if (list.size() == 0) {
-				return null;
-			}
-			list.sort(new Comparator<ImageSlice>() {
-				@Override
-				public int compare(ImageSlice o1, ImageSlice o2) {
-					long c1 = o1.getI();
-					long c2 = o2.getI();
-					if (c1 < c2) {
-						return -1;
-					} else if (c1 > c2) {
-						return 1;
-					} else {
-						return 0;
-					}
-				}
-			});
-			return list;
-		} catch (Exception e) {
-			return null;
-		}
-	}
-	private List<StandardAnswer> getStandardAnswer(String s) {
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		s = s.trim();
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		List<StandardAnswer> list = new ArrayList<>();
-		Matcher matcher = scoreRex.matcher(s);
-		int start=0;
-		Double score = null;
-		while (matcher.find()) {
-			if(start!=0) {
-				StandardAnswer a=new StandardAnswer();
-				list.add(a);
-				a.setScore(score);
-				a.setContent(s.substring(start,matcher.start()));
-			}
-			try {
-				score=Double.valueOf(matcher.group(1));
-			} catch (NumberFormatException e) {
-				throw new StatusException("分数格式有误");
-			}
-			checkScore(score);
-			start=matcher.end();
+
+    private Pattern scoreRex = Pattern.compile("\\[\\[([0-9][0-9]*(.[0-9]+){0,1})分\\]\\]");
+
+    private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "科目名称", "大题号", "小题号", "满分", "试题内容",
+            "试题答案", "作答坐标" };
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    @Autowired
+    private QuestionService questionService;
+
+    @Override
+    public void importQuestion() {
+        File dir = new File(sysProperty.getDataDir());
+        File[] fs = dir.listFiles();
+        if (fs == null || fs.length == 0) {
+            return;
+        }
+        for (File file : fs) {
+            if (!file.isFile() || !file.getName().equals(ImportFileName.QUESTION_IMPORT.getName())) {
+                continue;
+            }
+            InputStream inputStream = null;
+            ImportResult ret = null;
+            try {
+                inputStream = new FileInputStream(file);
+                ret = questionService.disposeFile(inputStream);
+            } catch (Exception e) {
+                String errMsg;
+                if (e instanceof FileNotFoundException) {
+                    errMsg = "未找到文件:" + file.getAbsolutePath();
+                } else {
+                    errMsg = "系统错误:" + e.getMessage();
+                }
+                ret = new ImportResult(errMsg);
+            } finally {
+                if (inputStream != null) {
+                    try {
+                        inputStream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            moveFile(dir, file, ret);
+        }
+    }
+
+    private void moveFile(File dir, File file, ImportResult ret) {
+        try {
+            boolean succss = CollectionUtils.isEmpty(ret.getErrMsg());
+            if (succss) {
+                File sucDir = new File(dir.getAbsoluteFile() + "/success/");
+                if (!sucDir.exists()) {
+                    sucDir.mkdir();
+                }
+                File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
+                if (targetFile.exists()) {
+                    targetFile.delete();
+                }
+                FileUtils.copyFile(file, targetFile);
+                file.delete();
+                String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
+                File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
+                if (msgFile.exists()) {
+                    msgFile.delete();
+                }
+                FileUtils.write(msgFile, ret.getCountInfo(), "utf-8");
+            } else {
+                File sucDir = new File(dir.getAbsoluteFile() + "/failed/");
+                if (!sucDir.exists()) {
+                    sucDir.mkdir();
+                }
+                File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
+                if (targetFile.exists()) {
+                    targetFile.delete();
+                }
+                FileUtils.copyFile(file, targetFile);
+                file.delete();
+                String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
+                File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
+                if (msgFile.exists()) {
+                    msgFile.delete();
+                }
+                FileUtils.writeLines(msgFile, StandardCharsets.UTF_8.name(), ret.getErrMsg());
+            }
+        } catch (IOException e) {
+            throw new StatusException("文件处理出错", e);
+        }
+
+    }
+
+    private String errorMsg(int lineNum, String msg) {
+        return "第" + lineNum + "行 " + msg;
+    }
+
+    private String trimAndNullIfBlank(String s) {
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        return s.trim();
+    }
+
+    @SuppressWarnings("deprecation")
+    @Transactional
+    @Override
+    public ImportResult disposeFile(InputStream inputStream) {
+        List<DataMap> lineList = null;
+        ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
+        try {
+            lineList = reader.getDataMapList();
+        } catch (Exception e) {
+            throw new StatusException("Excel 解析失败");
+        }
+        if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
+            throw new StatusException("Excel表头错误");
+        }
+        if (CollectionUtils.isEmpty(lineList)) {
+            throw new StatusException("Excel无内容");
+        }
+        if (100001 < lineList.size()) {
+            throw new StatusException("数据行数不能超过100000");
+        }
+        List<QuestionEntity> ss = new ArrayList<>();
+        ImportResult ret = new ImportResult();
+        List<String> failRecords = new ArrayList<>();
+        ret.setErrMsg(failRecords);
+        for (int i = 0; i < lineList.size(); i++) {
+            DataMap line = lineList.get(i);
+
+            StringBuilder msg = new StringBuilder();
+
+            QuestionEntity imp = new QuestionEntity();
+            String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
+            if (StringUtils.isBlank(examId)) {
+                msg.append("  考试ID不能为空");
+            } else if (examId.length() > 20) {
+                msg.append("  考试ID不能超过20个字符");
+            } else {
+                try {
+                    Long examIdVal = Long.parseLong(examId);
+                    imp.setExamId(examIdVal);
+                } catch (NumberFormatException e) {
+                    msg.append("  考试ID只能是数字");
+                }
+            }
+
+            String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
+            if (StringUtils.isBlank(subjectCode)) {
+                msg.append("  科目代码不能为空");
+            } else if (subjectCode.length() > 100) {
+                msg.append("  科目代码不能超过100个字符");
+            }
+            imp.setSubjectCode(subjectCode);
+
+            String subjectName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
+            if (StringUtils.isBlank(subjectName)) {
+                msg.append("  科目名称不能为空");
+            } else if (subjectName.length() > 100) {
+                msg.append("  科目名称不能超过100个字符");
+            }
+            imp.setSubjectName(subjectName);
+
+            String mainNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
+            if (StringUtils.isBlank(mainNum)) {
+                msg.append("  大题号不能为空");
+            } else if (mainNum.length() > 10) {
+                msg.append("  大题号不能超过10个字符");
+            } else {
+                try {
+                    Integer mainNumVal = Integer.parseInt(mainNum);
+                    if (mainNumVal <= 0) {
+                        msg.append("  大题号必须大于0");
+                    }
+                    imp.setMainNumber(mainNumVal);
+                } catch (NumberFormatException e) {
+                    msg.append("  大题号格式错误");
+                }
+            }
+
+            String subNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
+            if (StringUtils.isBlank(subNum)) {
+                msg.append("  小题号不能为空");
+            } else if (subNum.length() > 10) {
+                msg.append("  小题号不能超过10个字符");
+            }
+            imp.setSubNumber(subNum);
+
+            String fullScore = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
+            if (StringUtils.isBlank(fullScore)) {
+                msg.append("  满分不能为空");
+            } else if (fullScore.length() > 10) {
+                msg.append("  满分不能超过10个字符");
+            } else {
+                try {
+                    Double fullScoreVal = Double.parseDouble(fullScore);
+                    if (fullScoreVal <= 0) {
+                        msg.append("  满分必须大于0");
+                    }
+                    imp.setFullScore(fullScoreVal);
+                } catch (NumberFormatException e) {
+                    msg.append("  满分格式错误");
+                }
+            }
+
+            String content = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
+            if (StringUtils.isBlank(content)) {
+                msg.append("  试题内容不能为空");
+            }
+            imp.setContent(content);
+
+            String answer = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
+            // if (StringUtils.isBlank(answer)) {
+            // msg.append(" 试题答案不能为空");
+            // }
+            imp.setAnswer(getStandardAnswer(answer));
+
+            String imageSlice = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
+            if (StringUtils.isBlank(imageSlice)) {
+                msg.append("  作答坐标不能为空");
+            } else if (imageSlice.length() > 1000) {
+                msg.append("  作答坐标不能超过1000个字符");
+            } else {
+                List<ImageSlice> val = getImageSlice(imageSlice);
+                if (val == null) {
+                    msg.append("  作答坐标格式有误");
+                } else {
+                    imp.setImageSlice(val);
+                }
+            }
+
+            if (msg.length() > 0) {
+                failRecords.add(errorMsg(i + 2, msg.toString()));
+            } else {
+                ss.add(imp);
+            }
+
+        }
+
+        if (CollectionUtils.isNotEmpty(failRecords)) {
+            return ret;
+        }
+        try {
+            saveQuestionBatch(ret, ss);
+        } catch (Exception e) {
+            failRecords.add("系统错误:" + e.getMessage());
+        }
+        return ret;
+    }
+
+    private List<ImageSlice> getImageSlice(String s) {
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        s = s.trim();
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        try {
+            List<ImageSlice> list = new ArrayList<>();
+            String[] items = s.split(",");
+            for (int i = 0; i < items.length; i++) {
+                String item = items[i];
+                item = item.trim();
+                String[] config = item.split(":");
+                if (config.length != 5) {
+                    return null;
+                }
+                int iVal = Integer.valueOf(config[0]);
+                int x = Integer.valueOf(config[1]);
+                int y = Integer.valueOf(config[2]);
+                int w = Integer.valueOf(config[3]);
+                int h = Integer.valueOf(config[4]);
+                ImageSlice ret = new ImageSlice();
+                ret.setH(h);
+                ret.setI(iVal);
+                ret.setW(w);
+                ret.setX(x);
+                ret.setY(y);
+                if (ret.getH() == null || ret.getI() == null || ret.getW() == null || ret.getX() == null
+                        || ret.getY() == null || ret.getI() < 0) {
+                    return null;
+                }
+                list.add(ret);
+            }
+            if (list.size() == 0) {
+                return null;
+            }
+            list.sort(new Comparator<ImageSlice>() {
+
+                @Override
+                public int compare(ImageSlice o1, ImageSlice o2) {
+                    long c1 = o1.getI();
+                    long c2 = o2.getI();
+                    if (c1 < c2) {
+                        return -1;
+                    } else if (c1 > c2) {
+                        return 1;
+                    } else {
+                        return 0;
+                    }
+                }
+            });
+            return list;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private List<StandardAnswer> getStandardAnswer(String s) {
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        s = s.trim();
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        List<StandardAnswer> list = new ArrayList<>();
+        Matcher matcher = scoreRex.matcher(s);
+        int start = 0;
+        Double score = null;
+        while (matcher.find()) {
+            if (start != 0) {
+                StandardAnswer a = new StandardAnswer();
+                list.add(a);
+                a.setScore(score);
+                a.setContent(s.substring(start, matcher.start()));
+            }
+            try {
+                score = Double.valueOf(matcher.group(1));
+            } catch (NumberFormatException e) {
+                throw new StatusException("分数格式有误");
+            }
+            checkScore(score);
+            start = matcher.end();
+        }
+        if (start < s.length()) {
+            StandardAnswer a = new StandardAnswer();
+            list.add(a);
+            a.setScore(score);
+            checkScore(score);
+            a.setContent(s.substring(start, s.length()));
+        }
+        return list;
+    }
+
+    private void checkScore(Double score) {
+        if (score == null) {
+            throw new StatusException("分数不能为空");
+        }
+    }
+
+    private void saveQuestionBatch(ImportResult ret, List<QuestionEntity> ss) {
+        if (CollectionUtils.isEmpty(ss)) {
+            ret.setCountInfo("新增数量:0,更新数量:0");
+            return;
+        }
+        List<QuestionEntity> all = this.list();
+        Map<String, QuestionEntity> old = new HashMap<>();
+        Map<String, QuestionEntity> addMap = new HashMap<>();
+        if (CollectionUtils.isNotEmpty(all)) {
+            for (QuestionEntity s : all) {
+                String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-"
+                        + s.getSubNumber();
+                old.put(key, s);
+            }
+        }
+        List<QuestionEntity> adds = new ArrayList<>();
+        List<QuestionEntity> updates = new ArrayList<>();
+        for (QuestionEntity s : ss) {
+            String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-" + s.getSubNumber();
+            if (old.get(key) == null) {
+                QuestionEntity add = addMap.get(key);
+                if (add != null) {
+                    add.setSubjectName(s.getSubjectName());
+                    add.setFullScore(s.getFullScore());
+                    add.setImageSlice(s.getImageSlice());
+                    add.setContent(s.getContent());
+                    add.setAnswer(s.getAnswer());
+                } else {
+                    addMap.put(key, s);
+                    adds.add(s);
+                }
+            } else {
+                QuestionEntity up = old.get(key);
+                up.setSubjectName(s.getSubjectName());
+                up.setFullScore(s.getFullScore());
+                up.setImageSlice(s.getImageSlice());
+                up.setContent(s.getContent());
+                up.setAnswer(s.getAnswer());
+                updates.add(up);
+            }
+        }
+        if (CollectionUtils.isNotEmpty(adds)) {
+            saveBatch(adds);
+        }
+        if (CollectionUtils.isNotEmpty(updates)) {
+            updateBatchById(updates);
+        }
+        ret.setCountInfo("新增数量:" + adds.size() + ",更新数量:" + updates.size());
+    }
+
+    @Override
+    public List<QuestionEntity> findByExamId(Long examId) {
+        QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
+        lw.eq(QuestionEntity::getExamId, examId);
+        lw.orderByAsc(QuestionEntity::getSubjectCode).orderByAsc(QuestionEntity::getMainNumber)
+                .orderByAsc(QuestionEntity::getSubNumber);
+        return this.list(wrapper);
+    }
+
+    @Transactional
+    @Override
+    public void removeBy(Long examId, String subjectCode) {
+        QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
+        if (subjectCode != null) {
+            lw.eq(QuestionEntity::getSubjectCode, subjectCode);
         }
-		if(start<s.length()) {
-			StandardAnswer a=new StandardAnswer();
-			list.add(a);
-			a.setScore(score);
-			checkScore(score);
-			a.setContent(s.substring(start,s.length()));
-		}
-		return list;
-	}
-	
-	private void checkScore(Double score) {
-		if(score==null) {
-			throw new StatusException("分数不能为空");
-		}
-	}
-
-	private void saveQuestionBatch(ImportResult ret, List<QuestionEntity> ss) {
-		if (CollectionUtils.isEmpty(ss)) {
-			ret.setCountInfo("新增数量:0,更新数量:0");
-			return;
-		}
-		List<QuestionEntity> all = this.list();
-		Map<String, QuestionEntity> old = new HashMap<>();
-		Map<String, QuestionEntity> addMap = new HashMap<>();
-		if (CollectionUtils.isNotEmpty(all)) {
-			for (QuestionEntity s : all) {
-				String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-"
-						+ s.getSubNumber();
-				old.put(key, s);
-			}
-		}
-		List<QuestionEntity> adds = new ArrayList<>();
-		List<QuestionEntity> updates = new ArrayList<>();
-		for (QuestionEntity s : ss) {
-			String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-" + s.getSubNumber();
-			if (old.get(key) == null) {
-				QuestionEntity add = addMap.get(key);
-				if (add != null) {
-					add.setSubjectName(s.getSubjectName());
-					add.setFullScore(s.getFullScore());
-					add.setImageSlice(s.getImageSlice());
-					add.setContent(s.getContent());
-					add.setAnswer(s.getAnswer());
-				} else {
-					addMap.put(key, s);
-					adds.add(s);
-				}
-			} else {
-				QuestionEntity up = old.get(key);
-				up.setSubjectName(s.getSubjectName());
-				up.setFullScore(s.getFullScore());
-				up.setImageSlice(s.getImageSlice());
-				up.setContent(s.getContent());
-				up.setAnswer(s.getAnswer());
-				updates.add(up);
-			}
-		}
-		if (CollectionUtils.isNotEmpty(adds)) {
-			saveBatch(adds);
-		}
-		if (CollectionUtils.isNotEmpty(updates)) {
-			updateBatchById(updates);
-		}
-		ret.setCountInfo("新增数量:" + adds.size() + ",更新数量:" + updates.size());
-	}
-
-	@Override
-	public List<QuestionEntity> findByExamId(Long examId) {
-		QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
-		LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
-		lw.eq(QuestionEntity::getExamId, examId);
-		lw.orderByAsc(QuestionEntity::getSubjectCode).orderByAsc(QuestionEntity::getMainNumber)
-				.orderByAsc(QuestionEntity::getSubNumber);
-		return this.list(wrapper);
-	}
-
-	@Transactional
-	@Override
-	public void removeBy(Long examId, String subjectCode) {
-		QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
-		LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
-		if (subjectCode != null) {
-			lw.eq(QuestionEntity::getSubjectCode, subjectCode);
-		}
-		lw.eq(QuestionEntity::getExamId, examId);
-		this.remove(wrapper);
-	}
+        lw.eq(QuestionEntity::getExamId, examId);
+        this.remove(wrapper);
+    }
 
 }

+ 93 - 84
src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java

@@ -10,10 +10,11 @@ import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
+import java.util.Set;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.io.FileUtils;
@@ -50,12 +51,13 @@ import cn.com.qmth.am.bean.AiMarkingDto;
 import cn.com.qmth.am.bean.AnswerImageDto;
 import cn.com.qmth.am.bean.ImageSlice;
 import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.bean.StudentInfo;
 import cn.com.qmth.am.bean.StudentScoreDto;
 import cn.com.qmth.am.bean.StudentScoreImageDto;
+import cn.com.qmth.am.bean.StudentScoreInfo;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.dao.StudentScoreDao;
 import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
 import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.DataStatus;
 import cn.com.qmth.am.enums.DataType;
@@ -64,7 +66,7 @@ import cn.com.qmth.am.enums.ModelTypeBak;
 import cn.com.qmth.am.service.DsMarkingService;
 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.BatchSetDataUtil;
 import cn.com.qmth.am.utils.FileUtil;
 import cn.com.qmth.am.utils.ImageUtil;
 
@@ -74,7 +76,7 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 
     private static final Logger log = LoggerFactory.getLogger(StudentScoreService.class);
 
-    private static BlockingQueue<StudentScoreImageDto> queue;
+    // private static BlockingQueue<StudentScoreImageDto> queue;
 
     private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "考生编号", "大题号", "小题号", "评分" };
 
@@ -84,9 +86,6 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
     @Autowired
     private SolarService solarService;
 
-    @Autowired
-    private StudentService studentService;
-
     @Autowired
     private OcrApiClient ocrApiClient;
 
@@ -98,10 +97,10 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 
     @Autowired
     private QuestionService questionService;
-    static {
-        int threadCount = Runtime.getRuntime().availableProcessors();
-        queue = new ArrayBlockingQueue<>(threadCount * 2);
-    }
+    // static {
+    // int threadCount = Runtime.getRuntime().availableProcessors();
+    // queue = new ArrayBlockingQueue<>(threadCount * 2);
+    // }
 
     @Override
     public void importScore() {
@@ -194,6 +193,7 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         return s.trim();
     }
 
+    @SuppressWarnings("deprecation")
     private ImportResult disposeFile(InputStream inputStream) {
         List<DataMap> lineList = null;
         ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
@@ -333,49 +333,71 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         return this.update(wrapper) ? 1 : 0;
     }
 
-    @Override
-    public List<StudentScoreEntity> getByStudentId(Long studentId) {
-        QueryWrapper<StudentScoreEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentScoreEntity> lw = wrapper.lambda();
-        lw.eq(StudentScoreEntity::getStudentId, studentId);
-        return this.list(wrapper);
-    }
-
     @Transactional
     @Override
-    public void add(StudentEntity student, Map<Long, QuestionEntity> quetions, List<StudentScoreEntity> oldscores) {
-        List<StudentScoreEntity> adds = new ArrayList<>();
-        for (QuestionEntity q : quetions.values()) {
-            if (!exists(q, oldscores)) {
-                StudentScoreEntity s = new StudentScoreEntity();
-                adds.add(s);
-                s.setQuestionId(q.getId());
-                s.setExamId(student.getExamId());
-                s.setAnswerStatus(DataStatus.WAITING);
-                s.setMainNumber(q.getMainNumber());
-                s.setScoreStatus(DataStatus.WAITING);
-                s.setStudentCode(student.getStudentCode());
-                s.setStudentId(student.getId());
-                s.setSubjectCode(student.getSubjectCode());
-                s.setSubNumber(q.getSubNumber());
+    public void addStudentScore(List<StudentInfo> ss) {
+        List<QuestionEntity> qs = questionService.list();
+        if (CollectionUtils.isEmpty(qs)) {
+            throw new StatusException("试题信息为空");
+        }
+        Map<String, List<QuestionEntity>> qmap = new HashMap<>();
+        for (QuestionEntity q : qs) {
+            String key = q.getExamId() + "-" + q.getSubjectCode();
+            List<QuestionEntity> tem = qmap.get(key);
+            if (tem == null) {
+                tem = new ArrayList<>();
+                qmap.put(key, tem);
             }
+            tem.add(q);
         }
-        if (CollectionUtils.isNotEmpty(adds)) {
-            this.saveBatch(adds);
-        }
+        Set<String> allStudent = getAllStudent();
+
+        BatchSetDataUtil<StudentInfo> bs = new BatchSetDataUtil<StudentInfo>() {
+
+            @Override
+            protected void setData(List<StudentInfo> dataList) {
+                List<StudentScoreEntity> adds = new ArrayList<>();
+                for (StudentInfo stu : dataList) {
+                    String key = stu.getExamId() + "-" + stu.getSubjectCode();
+                    if (qmap.get(key) == null) {
+                        throw new StatusException("试题信息为空:" + key);
+                    }
+                    for (QuestionEntity q : qmap.get(key)) {
+                        String scorekey = q.getId() + "-" + stu.getStudentCode();
+                        if (!allStudent.contains(scorekey)) {
+                            StudentScoreEntity s = new StudentScoreEntity();
+                            adds.add(s);
+                            allStudent.add(scorekey);
+                            s.setQuestionId(q.getId());
+                            s.setExamId(stu.getExamId());
+                            s.setAnswerStatus(DataStatus.WAITING);
+                            s.setMainNumber(q.getMainNumber());
+                            s.setScoreStatus(DataStatus.WAITING);
+                            s.setStudentCode(stu.getStudentCode());
+                            s.setSubjectCode(stu.getSubjectCode());
+                            s.setSubNumber(q.getSubNumber());
+                        }
+                    }
+                }
+                if (CollectionUtils.isNotEmpty(adds)) {
+                    saveBatch(adds);
+                }
+            }
+        };
+        bs.setDataForBatch(ss, 1000);
+
     }
 
-    private boolean exists(QuestionEntity q, List<StudentScoreEntity> oldscores) {
-        if (CollectionUtils.isEmpty(oldscores)) {
-            return false;
+    private Set<String> getAllStudent() {
+        Set<String> ret = new HashSet<>();
+        List<StudentScoreInfo> list = this.baseMapper.getAllList();
+        if (CollectionUtils.isEmpty(list)) {
+            return ret;
         }
-        for (StudentScoreEntity s : oldscores) {
-            if (s.getExamId().equals(q.getExamId()) && s.getSubjectCode().equals(q.getSubjectCode())
-                    && s.getMainNumber().equals(q.getMainNumber()) && s.getSubNumber().equals(q.getSubNumber())) {
-                return true;
-            }
+        for (StudentScoreInfo s : list) {
+            ret.add(s.getQuestionId() + "-" + s.getStudentCode());
         }
-        return false;
+        return ret;
     }
 
     @Transactional
@@ -389,18 +411,14 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         this.update(wrapper);
     }
 
+    @Transactional
     @Override
     public void createSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages) {
         StudentScoreImageDto dto = new StudentScoreImageDto();
-        dto.setStudentId(score.getStudentId());
         dto.setStudentScoreId(score.getId());
         getSlice(score, q, answerImages, dto);
         // saveSliceImage(score, dto.getImage());
-        try {
-            queue.put(dto);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
+        ocr(dto);
     }
 
     private void getSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages,
@@ -559,14 +577,14 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         System.out.println(s);
     }
 
-    @Override
-    public StudentScoreImageDto pollStudentScoreImage() {
-        try {
-            return queue.take();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
+    // @Override
+    // public StudentScoreImageDto pollStudentScoreImage() {
+    // try {
+    // return queue.take();
+    // } catch (InterruptedException e) {
+    // throw new RuntimeException(e);
+    // }
+    // }
 
     @Transactional
     @Override
@@ -636,28 +654,36 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 
     private void ocrErr(StudentScoreImageDto dto, String err) {
         updateAnswerErr(dto.getStudentScoreId(), err);
-        studentService.updateStatus(dto.getStudentId(), DataStatus.FAILED);
     }
 
     @Override
-    public List<StudentScoreEntity> findAllToAiMarking() {
+    public List<StudentScoreEntity> findAllToOcr() {
         QueryWrapper<StudentScoreEntity> wrapper = new QueryWrapper<>();
         LambdaQueryWrapper<StudentScoreEntity> lw = wrapper.lambda();
-        lw.eq(StudentScoreEntity::getAnswerStatus, DataStatus.SUCCESS);
-        lw.in(StudentScoreEntity::getScoreStatus, DataStatus.WAITING, DataStatus.FAILED);
+        lw.in(StudentScoreEntity::getAnswerStatus, DataStatus.WAITING, DataStatus.FAILED);
         return this.list(wrapper);
     }
 
     @Override
-    public List<StudentScoreEntity> findToAiMarking(Long studentId) {
+    public List<StudentScoreEntity> findAllToAiMarking() {
         QueryWrapper<StudentScoreEntity> wrapper = new QueryWrapper<>();
         LambdaQueryWrapper<StudentScoreEntity> lw = wrapper.lambda();
-        lw.eq(StudentScoreEntity::getStudentId, studentId);
         lw.eq(StudentScoreEntity::getAnswerStatus, DataStatus.SUCCESS);
         lw.in(StudentScoreEntity::getScoreStatus, DataStatus.WAITING, DataStatus.FAILED);
         return this.list(wrapper);
     }
 
+    // @Override
+    // public List<StudentScoreEntity> findToAiMarking(Long studentId) {
+    // QueryWrapper<StudentScoreEntity> wrapper = new QueryWrapper<>();
+    // LambdaQueryWrapper<StudentScoreEntity> lw = wrapper.lambda();
+    // lw.eq(StudentScoreEntity::getStudentId, studentId);
+    // lw.eq(StudentScoreEntity::getAnswerStatus, DataStatus.SUCCESS);
+    // lw.in(StudentScoreEntity::getScoreStatus, DataStatus.WAITING,
+    // DataStatus.FAILED);
+    // return this.list(wrapper);
+    // }
+
     @Transactional
     @Override
     public void aiMarking(StudentScoreEntity score) {
@@ -691,27 +717,11 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
             } else {
                 updateScoreNone(score.getId(), 0.0);
             }
-            if (allSuccess(score.getStudentId())) {
-                studentService.updateStatus(score.getStudentId(), DataStatus.SUCCESS);
-            }
         } catch (Exception e) {
             aiScoreErr(dto, e.getMessage());
         }
     }
 
-    private boolean allSuccess(Long studentId) {
-        QueryWrapper<StudentScoreEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentScoreEntity> lw = wrapper.lambda();
-        lw.eq(StudentScoreEntity::getStudentId, studentId);
-        List<StudentScoreEntity> list = this.list(wrapper);
-        for (StudentScoreEntity s : list) {
-            if (!DataStatus.SUCCESS.equals(s.getScoreStatus())) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private AutoScoreResult aiMarkingDispose(AiMarkingDto dto, OrgInfo org, AutoScoreRequest req) {
         SignatureInfo signature = null;
         if (ModelTypeBak.solar.getCode().equals(sysProperty.getMarkingModel())) {
@@ -774,7 +784,6 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 
     private void aiScoreErr(AiMarkingDto dto, String err) {
         updateScoreErr(dto.getScoreInfo().getId(), err);
-        studentService.updateStatus(dto.getScoreInfo().getStudentId(), DataStatus.FAILED);
     }
 
     private void updateScoreErr(Long id, String err) {

+ 25 - 115
src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java

@@ -23,11 +23,6 @@ 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.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.boot.core.exception.StatusException;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.enums.ExcelType;
@@ -35,10 +30,9 @@ import com.qmth.boot.tools.excel.model.DataMap;
 
 import cn.com.qmth.am.bean.AnswerImageDto;
 import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.bean.StudentInfo;
 import cn.com.qmth.am.config.SysProperty;
-import cn.com.qmth.am.dao.StudentDao;
 import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
 import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.DataStatus;
 import cn.com.qmth.am.enums.ImportFileName;
@@ -47,7 +41,7 @@ import cn.com.qmth.am.service.StudentScoreService;
 import cn.com.qmth.am.service.StudentService;
 
 @Service
-public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
+public class StudentServiceImpl implements StudentService {
 
     private static final Logger log = LoggerFactory.getLogger(StudentService.class);
 
@@ -86,7 +80,7 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
                 if (e instanceof FileNotFoundException) {
                     errMsg = "未找到文件:" + file.getAbsolutePath();
                 } else {
-                    errMsg = "系统错误:" + e.getMessage();
+                    errMsg = "错误:" + e.getMessage();
                 }
                 ret = new ImportResult(errMsg);
             } finally {
@@ -156,6 +150,7 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         return s.trim();
     }
 
+    @SuppressWarnings("deprecation")
     @Transactional
     @Override
     public ImportResult disposeFile(InputStream inputStream) {
@@ -175,7 +170,7 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         if (100001 < lineList.size()) {
             throw new StatusException("数据行数不能超过100000");
         }
-        List<StudentEntity> ss = new ArrayList<>();
+        List<StudentInfo> ss = new ArrayList<>();
         ImportResult ret = new ImportResult();
         List<String> failRecords = new ArrayList<>();
         ret.setErrMsg(failRecords);
@@ -184,8 +179,7 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
 
             StringBuilder msg = new StringBuilder();
 
-            StudentEntity imp = new StudentEntity();
-            imp.setDataStatus(DataStatus.WAITING);
+            StudentInfo imp = new StudentInfo();
             String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
             if (StringUtils.isBlank(examId)) {
                 msg.append("  考试ID不能为空");
@@ -229,27 +223,28 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         }
         try {
             saveStudentBatch(ret, ss);
-        } catch (Exception e) {
-            failRecords.add("系统错误:" + e.getMessage());
+        } catch (StatusException e) {
+            failRecords.add("错误:" + e.getMessage());
         }
         return ret;
     }
 
-    private void saveStudentBatch(ImportResult ret, List<StudentEntity> ss) {
+    private void saveStudentBatch(ImportResult ret, List<StudentInfo> ss) {
         if (CollectionUtils.isEmpty(ss)) {
             ret.setCountInfo("新增数量:0");
             return;
         }
-        List<StudentEntity> all = this.list();
+        // List<StudentEntity> all = this.list();
         Set<String> set = new HashSet<>();
-        if (CollectionUtils.isNotEmpty(all)) {
-            for (StudentEntity s : all) {
-                String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getStudentCode();
-                set.add(key);
-            }
-        }
-        List<StudentEntity> adds = new ArrayList<>();
-        for (StudentEntity s : ss) {
+        // if (CollectionUtils.isNotEmpty(all)) {
+        // for (StudentEntity s : all) {
+        // String key = s.getExamId() + "-" + s.getSubjectCode() + "-" +
+        // s.getStudentCode();
+        // set.add(key);
+        // }
+        // }
+        List<StudentInfo> adds = new ArrayList<>();
+        for (StudentInfo s : ss) {
             String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getStudentCode();
             if (!set.contains(key)) {
                 adds.add(s);
@@ -258,53 +253,26 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
             }
         }
         if (CollectionUtils.isNotEmpty(adds)) {
-            saveBatch(adds);
-        }
-        ret.setCountInfo("新增数量:" + adds.size());
-    }
-
-    @Override
-    public List<StudentEntity> findToDispose(Long examId) {
-        QueryWrapper<StudentEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentEntity> lw = wrapper.lambda();
-        if (examId != null) {
-            lw.eq(StudentEntity::getExamId, examId);
+            // saveBatch(adds);
+            studentScoreService.addStudentScore(adds);
         }
-        lw.in(StudentEntity::getDataStatus, DataStatus.WAITING, DataStatus.FAILED);
-        return this.list(wrapper);
+        // ret.setCountInfo("新增数量:" + adds.size());
     }
 
     @Override
-    public List<StudentEntity> findToMarking(Long examId) {
-        QueryWrapper<StudentEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentEntity> lw = wrapper.lambda();
-        if (examId != null) {
-            lw.eq(StudentEntity::getExamId, examId);
-        }
-        lw.in(StudentEntity::getDataStatus, DataStatus.WAITING, DataStatus.FAILED, DataStatus.PROCESSING);
-        return this.list(wrapper);
-    }
-
-    @Override
-    public void buildImage(StudentEntity student, Map<Long, QuestionEntity> quetions) {
-        List<StudentScoreEntity> scores = studentService.getOrCreateScores(student, quetions);
+    public void buildImage(StudentScoreEntity score, Map<Long, QuestionEntity> quetions) {
         Map<Integer, AnswerImageDto> answerImages = new HashMap<>();
-        for (StudentScoreEntity score : scores) {
-            if (DataStatus.WAITING.equals(score.getAnswerStatus())
-                    || DataStatus.FAILED.equals(score.getAnswerStatus())) {
-                studentService.createSlice(score, quetions, answerImages);
-            }
+        if (DataStatus.WAITING.equals(score.getAnswerStatus()) || DataStatus.FAILED.equals(score.getAnswerStatus())) {
+            studentService.createSlice(score, quetions, answerImages);
         }
     }
 
-    @Transactional
     @Override
     public void createSlice(StudentScoreEntity score, Map<Long, QuestionEntity> quetions,
             Map<Integer, AnswerImageDto> answerImages) {
         QuestionEntity q = quetions.get(score.getQuestionId());
         if (q == null) {
             studentScoreService.updateAnswerErr(score.getId(), "未找到试题信息");
-            updateStatus(score.getStudentId(), DataStatus.FAILED);
             return;
         }
         try {
@@ -312,68 +280,17 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         } catch (Exception e) {
             if (e instanceof StatusException) {
                 studentScoreService.updateAnswerErr(score.getId(), e.getMessage());
-                updateStatus(score.getStudentId(), DataStatus.FAILED);
             } else {
                 log.error("系统异常", e);
                 studentScoreService.updateAnswerErr(score.getId(), "系统异常");
-                updateStatus(score.getStudentId(), DataStatus.FAILED);
             }
         }
     }
 
-    @Transactional
-    @Override
-    public List<StudentScoreEntity> getOrCreateScores(StudentEntity student, Map<Long, QuestionEntity> quetions) {
-        List<StudentScoreEntity> oldscores = studentScoreService.getByStudentId(student.getId());
-        studentScoreService.add(student, quetions, oldscores);
-        oldscores = studentScoreService.getByStudentId(student.getId());
-        updateStatus(student.getId(), DataStatus.PROCESSING);
-        return oldscores;
-    }
-
-    @Transactional
-    @Override
-    public void updateStatus(Long id, DataStatus to) {
-        UpdateWrapper<StudentEntity> wrapper = new UpdateWrapper<>();
-        LambdaUpdateWrapper<StudentEntity> lw = wrapper.lambda();
-        lw.set(StudentEntity::getDataStatus, to);
-        lw.eq(StudentEntity::getId, id);
-        this.update(wrapper);
-    }
-
-    @Transactional
-    @Override
-    public void resetStatus() {
-        UpdateWrapper<StudentEntity> wrapper = new UpdateWrapper<>();
-        LambdaUpdateWrapper<StudentEntity> lw = wrapper.lambda();
-        lw.set(StudentEntity::getDataStatus, DataStatus.WAITING);
-        lw.eq(StudentEntity::getDataStatus, DataStatus.PROCESSING);
-        this.update(wrapper);
-    }
-
-    @Override
-    public int countBy(Long examId, DataStatus status) {
-        QueryWrapper<StudentEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentEntity> lw = wrapper.lambda();
-        if (status != null) {
-            lw.eq(StudentEntity::getDataStatus, status);
-        }
-        lw.eq(StudentEntity::getExamId, examId);
-        return this.count(wrapper);
-    }
-
     @Transactional
     @Override
     public void reset(Long examId, String subjectCode) {
         studentScoreService.removeBy(examId, subjectCode);
-        UpdateWrapper<StudentEntity> wrapper = new UpdateWrapper<>();
-        LambdaUpdateWrapper<StudentEntity> lw = wrapper.lambda();
-        lw.set(StudentEntity::getDataStatus, DataStatus.WAITING);
-        if (subjectCode != null) {
-            lw.eq(StudentEntity::getSubjectCode, subjectCode);
-        }
-        lw.eq(StudentEntity::getExamId, examId);
-        this.update(wrapper);
     }
 
     @Transactional
@@ -381,12 +298,5 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
     public void clear(Long examId, String subjectCode) {
         studentScoreService.removeBy(examId, subjectCode);
         questionService.removeBy(examId, subjectCode);
-        QueryWrapper<StudentEntity> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<StudentEntity> lw = wrapper.lambda();
-        if (subjectCode != null) {
-            lw.eq(StudentEntity::getSubjectCode, subjectCode);
-        }
-        lw.eq(StudentEntity::getExamId, examId);
-        this.remove(wrapper);
     }
 }

+ 46 - 20
src/main/java/cn/com/qmth/am/task/AiMarkingJob.java

@@ -1,6 +1,13 @@
 package cn.com.qmth.am.task;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,11 +17,11 @@ import org.springframework.stereotype.Service;
 import com.qmth.boot.core.concurrent.service.ConcurrentService;
 
 import cn.com.qmth.am.config.SysProperty;
-import cn.com.qmth.am.entity.StudentEntity;
+import cn.com.qmth.am.consumer.MarkingConsumer;
 import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.LockType;
 import cn.com.qmth.am.service.StudentScoreService;
-import cn.com.qmth.am.service.StudentService;
+import cn.com.qmth.am.utils.SpringContextHolder;
 
 @Service
 public class AiMarkingJob {
@@ -22,31 +29,43 @@ public class AiMarkingJob {
     @Autowired
     private StudentScoreService studentScoreService;
 
-    @Autowired
-    private StudentService studentService;
-
     @Autowired
     private ConcurrentService concurrentService;
 
     @Autowired
     private SysProperty sysProperty;
 
+    private ExecutorService executor;
+
+    @PostConstruct
+    public void initExecutor() {
+        int threadCount = sysProperty.getThreadCount();
+        executor = 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);
+                        }
+                    }
+                });
+    }
+
     @Scheduled(fixedDelay = 5 * 1000, initialDelay = 5 * 1000)
     public void doJob() {
         if (!sysProperty.getMarkingTaskEnable()) {
             return;
         }
-        Long examId = null;
-        List<StudentEntity> students = studentService.findToMarking(examId);
-        if (CollectionUtils.isEmpty(students)) {
-            return;
-        }
         boolean lock = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock();
         try {
             if (!lock) {
                 return;
             }
-            this.dispose(students);
+            this.dispose();
         } finally {
             if (lock) {
                 concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().unlock();
@@ -54,16 +73,23 @@ public class AiMarkingJob {
         }
     }
 
-    private void dispose(List<StudentEntity> students) {
-        for (StudentEntity student : students) {
-            if (!sysProperty.getMarkingTaskEnable()) {
-                return;
-            }
-            List<StudentScoreEntity> scores = studentScoreService.findToAiMarking(student.getId());
-            if (CollectionUtils.isNotEmpty(scores)) {
-                for (StudentScoreEntity score : scores) {
-                    studentScoreService.aiMarking(score);
+    private void dispose() {
+        List<StudentScoreEntity> scores = studentScoreService.findAllToAiMarking();
+        if (CollectionUtils.isNotEmpty(scores)) {
+            CountDownLatch endGate = new CountDownLatch(scores.size());
+            for (StudentScoreEntity score : scores) {
+                if (!sysProperty.getMarkingTaskEnable()) {
+                    return;
                 }
+                MarkingConsumer com = SpringContextHolder.getBean(MarkingConsumer.class);
+                com.setScore(score);
+                com.setEndGate(endGate);
+                executor.execute(com);
+            }
+            try {
+                endGate.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
             }
         }
     }

+ 22 - 15
src/main/java/cn/com/qmth/am/task/BuildImageJob.java → src/main/java/cn/com/qmth/am/task/OcrJob.java

@@ -3,6 +3,7 @@ package cn.com.qmth.am.task;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -18,19 +19,19 @@ import org.springframework.stereotype.Service;
 import com.qmth.boot.core.concurrent.service.ConcurrentService;
 
 import cn.com.qmth.am.config.SysProperty;
-import cn.com.qmth.am.consumer.BuildImageConsumer;
+import cn.com.qmth.am.consumer.OcrConsumer;
 import cn.com.qmth.am.entity.QuestionEntity;
-import cn.com.qmth.am.entity.StudentEntity;
+import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.LockType;
 import cn.com.qmth.am.service.QuestionService;
-import cn.com.qmth.am.service.StudentService;
+import cn.com.qmth.am.service.StudentScoreService;
 import cn.com.qmth.am.utils.SpringContextHolder;
 
 @Service
-public class BuildImageJob {
+public class OcrJob {
 
     @Autowired
-    private StudentService studentService;
+    private StudentScoreService studentScoreService;
 
     @Autowired
     private QuestionService questionService;
@@ -45,7 +46,7 @@ public class BuildImageJob {
 
     @PostConstruct
     public void initExecutor() {
-        int threadCount = sysProperty.getOcrThreadCount();
+        int threadCount = sysProperty.getThreadCount();
         executor = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.SECONDS,
                 new LinkedBlockingQueue<>(threadCount * 2), r -> {
                     Thread t = new Thread(r);
@@ -71,9 +72,8 @@ public class BuildImageJob {
         if (CollectionUtils.isEmpty(qs)) {
             return;
         }
-        Long examId = null;
-        List<StudentEntity> stus = studentService.findToDispose(examId);
-        if (CollectionUtils.isEmpty(stus)) {
+        List<StudentScoreEntity> scores = studentScoreService.findAllToOcr();
+        if (CollectionUtils.isEmpty(scores)) {
             return;
         }
         boolean lock = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock();
@@ -81,7 +81,7 @@ public class BuildImageJob {
             if (!lock) {
                 return;
             }
-            this.dispose(stus, qs);
+            this.dispose(scores, qs);
         } finally {
             if (lock) {
                 concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock();
@@ -89,7 +89,7 @@ public class BuildImageJob {
         }
     }
 
-    private void dispose(List<StudentEntity> stus, List<QuestionEntity> qs) {
+    private void dispose(List<StudentScoreEntity> scores, List<QuestionEntity> qs) {
         Map<String, Map<Long, QuestionEntity>> qmap = new HashMap<>();
         for (QuestionEntity q : qs) {
             String key = q.getExamId() + "-" + q.getSubjectCode();
@@ -100,19 +100,26 @@ public class BuildImageJob {
             }
             tem.put(q.getId(), q);
         }
-        for (StudentEntity stu : stus) {
+        CountDownLatch endGate = new CountDownLatch(scores.size());
+        for (StudentScoreEntity score : scores) {
             if (!sysProperty.getOcrTaskEnable()) {
                 return;
             }
-            String key = stu.getExamId() + "-" + stu.getSubjectCode();
+            String key = score.getExamId() + "-" + score.getSubjectCode();
             Map<Long, QuestionEntity> tem = qmap.get(key);
             if (tem != null) {
-                BuildImageConsumer com = SpringContextHolder.getBean(BuildImageConsumer.class);
-                com.setStudent(stu);
+                OcrConsumer com = SpringContextHolder.getBean(OcrConsumer.class);
+                com.setScore(score);
                 com.setQuetions(tem);
+                com.setEndGate(endGate);
                 executor.execute(com);
             }
         }
+        try {
+            endGate.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
     }
 
 }

+ 40 - 0
src/main/java/cn/com/qmth/am/utils/BatchSetDataUtil.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.am.utils;
+
+import java.util.List;
+
+public abstract class BatchSetDataUtil<P> {
+
+    /**
+     * @param dataList
+     *            待填充的对象集合
+     * @param batchSize
+     *            每批数量
+     */
+    public final void setDataForBatch(List<P> dataList, int batchSize) {
+        if (dataList == null || dataList.size() == 0) {
+            return;
+        }
+        int batchIndex = 1;
+        if (dataList.size() <= batchSize) {
+            setData(dataList);
+            afterBatch(dataList, batchIndex, batchSize);
+        } else {
+            int size = dataList.size();
+            int len = batchSize;
+            int count = (size + len - 1) / len;
+
+            for (int i = 0; i < count; i++) {
+                List<P> subList = dataList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+                setData(subList);
+                afterBatch(subList, batchIndex, batchSize);
+                batchIndex++;
+            }
+        }
+    }
+
+    protected void afterBatch(List<P> dataList, int batchIndex, int batchSize) {
+
+    }
+
+    protected abstract void setData(List<P> dataList);
+}

+ 5 - 4
src/main/resources/application.properties

@@ -13,7 +13,7 @@ com.qmth.mybatis.block-attack=false
 #
 db.host=localhost
 db.port=3306
-db.database=ai_marking_ocr
+db.database=ai_marking_muti
 com.qmth.datasource.username=root
 com.qmth.datasource.password=123456
 com.qmth.datasource.url=jdbc:mysql://${db.host}:${db.port}/${db.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
@@ -38,14 +38,15 @@ com.qmth.ocr.server=https://solar.qmth.com.cn
 com.qmth.solar.access-key=7bbdc11570bc474dbf50e0d4a5dff328
 com.qmth.solar.access-secret=IOodRvbp2LspJTHOScgB7Yx8MRloMpyl
 
+am.marking-thread-count=4
 am.ocr-task.enable=true
 am.marking-task.enable=true
 am.data-type=MARKING_CLOUD
 am.image-server=https://file.markingcloud.com
 am.data-dir=./data
-am.marking-ocr-model=Qwen2.5-VL-7B-Instruct
-am.marking-ocr-server=http://192.168.103.14:31091/inference/qwen-qwen2-5-vl-7b-instruct-nvidia-4090.spiritx-model/v1/chat/completions
-am.marking-ocr-key=sk-loBBngbg1ymvUo6f647bF35d69684f1280E5D544F1F59f20
+am.marking-marking-model=Qwen2.5-32B-Instruct-GPTQ-Int4
+am.marking-marking-server=http://39.174.90.3:31091/spiritx-api/v1/chat/completions
+am.marking-marking-key=sk-loBBngbg1ymvUo6f647bF35d69684f1280E5D544F1F59f20
 am.marking-marking-model=Qwen2.5-32B-Instruct-GPTQ-Int4
 am.marking-marking-server=http://39.174.90.3:31091/spiritx-api/v1/chat/completions
 am.marking-marking-key=sk-loBBngbg1ymvUo6f647bF35d69684f1280E5D544F1F59f20

+ 7 - 0
src/main/resources/mapper/StudentScoreMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.qmth.am.dao.StudentScoreDao">
+	<select id="getAllList" resultType="cn.com.qmth.am.bean.StudentScoreInfo">
+		select t.question_id,t.student_code from am_student_score t
+	</select>
+</mapper>