xiatian před 1 rokem
rodič
revize
5d59ce8e83

+ 8 - 3
db/am_db.sql

@@ -24,6 +24,7 @@ CREATE TABLE `am_student` (
   `exam_id` bigint NOT NULL,
   `exam_id` bigint NOT NULL,
   `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+  `err_msg` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL,
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
   UNIQUE KEY `IDX_STUDENT_01` (`exam_id`, `subject_code`, `student_code`)
   UNIQUE KEY `IDX_STUDENT_01` (`exam_id`, `subject_code`, `student_code`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -34,16 +35,20 @@ CREATE TABLE `am_student_score` (
   `create_time` datetime(6) DEFAULT NULL,
   `create_time` datetime(6) DEFAULT NULL,
   `update_time` datetime(6) DEFAULT NULL,
   `update_time` datetime(6) DEFAULT NULL,
   `ai_score` double DEFAULT NULL,
   `ai_score` double DEFAULT NULL,
-  `data_status` varchar(255) int NOT NULL,
-  `err_msg` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
+  `answer_status` varchar(255) int NOT NULL,
+  `score_status` varchar(255) int NOT NULL,
+  `answer` longtext COLLATE utf8mb4_bin DEFAULT NULL,
+  `err_msg` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL,
   `main_number` int NOT NULL,
   `main_number` int NOT NULL,
   `marking_score` double DEFAULT NULL,
   `marking_score` double DEFAULT NULL,
   `score_ratio` double DEFAULT NULL,
   `score_ratio` double DEFAULT NULL,
   `student_id` bigint NOT NULL,
   `student_id` bigint NOT NULL,
+  `question_id` bigint NOT NULL,
   `exam_id` bigint NOT NULL,
   `exam_id` bigint NOT NULL,
   `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
   `sub_number` int NOT NULL,
   `sub_number` int NOT NULL,
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
-  UNIQUE KEY `IDX_STUDENT_SCORE_01` (`exam_id`, `subject_code`, `student_code`,`main_number`, `sub_number`)
+  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`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

+ 5 - 0
pom.xml

@@ -30,6 +30,11 @@
             <groupId>com.qmth.boot</groupId>
             <groupId>com.qmth.boot</groupId>
             <artifactId>starter-api</artifactId>
             <artifactId>starter-api</artifactId>
             <version>${qmth-boot-version}</version>
             <version>${qmth-boot-version}</version>
+        </dependency>
+        		<dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-ai</artifactId>
+            <version>${qmth-boot-version}</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
             <groupId>com.qmth.boot</groupId>

+ 26 - 0
src/main/java/cn/com/qmth/am/bean/AnswerImageDto.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.am.bean;
+
+public class AnswerImageDto {
+	private Integer pageIndex;
+	private byte[] image;
+	private String suff;
+	public Integer getPageIndex() {
+		return pageIndex;
+	}
+	public void setPageIndex(Integer pageIndex) {
+		this.pageIndex = pageIndex;
+	}
+	public byte[] getImage() {
+		return image;
+	}
+	public void setImage(byte[] image) {
+		this.image = image;
+	}
+	public String getSuff() {
+		return suff;
+	}
+	public void setSuff(String suff) {
+		this.suff = suff;
+	}
+	
+}

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

@@ -0,0 +1,41 @@
+package cn.com.qmth.am.bean;
+
+public class StudentScoreImageDto {
+	private Long studentScoreId;
+	private Long studentId;
+	private byte[] image;
+	private Integer retry=0;
+
+	public Long getStudentScoreId() {
+		return studentScoreId;
+	}
+
+	public void setStudentScoreId(Long studentScoreId) {
+		this.studentScoreId = studentScoreId;
+	}
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public byte[] getImage() {
+		return image;
+	}
+
+	public void setImage(byte[] image) {
+		this.image = image;
+	}
+
+	public Integer getRetry() {
+		return retry;
+	}
+
+	public void setRetry(Integer retry) {
+		this.retry = retry;
+	}
+
+}

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

@@ -0,0 +1,25 @@
+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);
+		}
+	}
+	
+
+}

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

@@ -0,0 +1,47 @@
+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;
+	}
+
+
+}

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

@@ -17,6 +17,8 @@ public class StudentEntity extends IdEntity {
 
 
 
 
 	private DataStatus dataStatus;
 	private DataStatus dataStatus;
+	
+	private String errMsg;
 
 
 
 
 	public Long getExamId() {
 	public Long getExamId() {
@@ -59,5 +61,15 @@ public class StudentEntity extends IdEntity {
 	}
 	}
 
 
 
 
+	public String getErrMsg() {
+		return errMsg;
+	}
+
+
+	public void setErrMsg(String errMsg) {
+		this.errMsg = errMsg;
+	}
+
+
 
 
 }
 }

+ 42 - 15
src/main/java/cn/com/qmth/am/entity/StudentScoreEntity.java

@@ -11,22 +11,27 @@ public class StudentScoreEntity extends IdEntity {
 	private static final long serialVersionUID = -6261302618070108336L;
 	private static final long serialVersionUID = -6261302618070108336L;
 
 
 	private Long studentId;
 	private Long studentId;
+	private Long questionId;
 	private Long examId;
 	private Long examId;
 
 
 	private String subjectCode;
 	private String subjectCode;
 	private String studentCode;
 	private String studentCode;
 	private Integer mainNumber;
 	private Integer mainNumber;
 	private String subNumber;
 	private String subNumber;
-	
-	//机评分
+
+	// 机评分
 	private Double aiScore;
 	private Double aiScore;
-	//人评分
+	// 人评分
 	private Double markingScore;
 	private Double markingScore;
-	//机评得分率
+	// 机评得分率
 	private Double scoreRatio;
 	private Double scoreRatio;
-	
-	private DataStatus dataStatus;
-	
+	//ocr状态
+	private DataStatus answerStatus;
+	//评分状态
+	private DataStatus scoreStatus;
+	//ocr结果
+	private String answer;
+
 	//错误信息
 	//错误信息
 	private String errMsg;
 	private String errMsg;
 
 
@@ -70,7 +75,6 @@ public class StudentScoreEntity extends IdEntity {
 		this.markingScore = markingScore;
 		this.markingScore = markingScore;
 	}
 	}
 
 
-
 	public Double getScoreRatio() {
 	public Double getScoreRatio() {
 		return scoreRatio;
 		return scoreRatio;
 	}
 	}
@@ -79,13 +83,6 @@ public class StudentScoreEntity extends IdEntity {
 		this.scoreRatio = scoreRatio;
 		this.scoreRatio = scoreRatio;
 	}
 	}
 
 
-	public DataStatus getDataStatus() {
-		return dataStatus;
-	}
-
-	public void setDataStatus(DataStatus dataStatus) {
-		this.dataStatus = dataStatus;
-	}
 
 
 	public String getErrMsg() {
 	public String getErrMsg() {
 		return errMsg;
 		return errMsg;
@@ -119,6 +116,36 @@ public class StudentScoreEntity extends IdEntity {
 		this.studentCode = studentCode;
 		this.studentCode = studentCode;
 	}
 	}
 
 
+	public DataStatus getAnswerStatus() {
+		return answerStatus;
+	}
+
+	public void setAnswerStatus(DataStatus answerStatus) {
+		this.answerStatus = answerStatus;
+	}
+
+	public DataStatus getScoreStatus() {
+		return scoreStatus;
+	}
+
+	public void setScoreStatus(DataStatus scoreStatus) {
+		this.scoreStatus = scoreStatus;
+	}
+
+	public String getAnswer() {
+		return answer;
+	}
 
 
+	public void setAnswer(String answer) {
+		this.answer = answer;
+	}
+
+	public Long getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(Long questionId) {
+		this.questionId = questionId;
+	}
 
 
 }
 }

+ 1 - 0
src/main/java/cn/com/qmth/am/enums/LockType.java

@@ -4,6 +4,7 @@ public enum LockType {
 	STUDENT_IMPORT("student_import"),
 	STUDENT_IMPORT("student_import"),
 	QUESTION_IMPORT("question_import"),
 	QUESTION_IMPORT("question_import"),
 	MARKING_SCORE_IMPORT("marking_score_import"),
 	MARKING_SCORE_IMPORT("marking_score_import"),
+	OCR("ocr"),
     AI_MARKING("ai_marking"),
     AI_MARKING("ai_marking"),
     ;
     ;
 
 

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

@@ -1,7 +1,14 @@
 package cn.com.qmth.am.service;
 package cn.com.qmth.am.service;
 
 
+import java.util.List;
+import java.util.Map;
+
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 
+import cn.com.qmth.am.bean.AnswerImageDto;
+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.entity.StudentScoreEntity;
 
 
 /**
 /**
@@ -11,4 +18,16 @@ public interface StudentScoreService  extends IService<StudentScoreEntity> {
 
 
 	void importScore();
 	void importScore();
 
 
+	List<StudentScoreEntity> getByStudentId(Long id);
+
+	void add(StudentEntity student, Map<Long,QuestionEntity> quetions);
+
+	void updateAnswerErr(Long id,String string);
+
+	void createSlice(StudentScoreEntity score, QuestionEntity q,Map<Integer,AnswerImageDto> answerImages);
+
+	StudentScoreImageDto pollStudentScoreImage();
+
+	void ocr(StudentScoreImageDto dto);
+
 }
 }

+ 16 - 0
src/main/java/cn/com/qmth/am/service/StudentService.java

@@ -1,11 +1,17 @@
 package cn.com.qmth.am.service;
 package cn.com.qmth.am.service;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
 
 
 import com.baomidou.mybatisplus.extension.service.IService;
 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.bean.ImportResult;
+import cn.com.qmth.am.entity.QuestionEntity;
 import cn.com.qmth.am.entity.StudentEntity;
 import cn.com.qmth.am.entity.StudentEntity;
+import cn.com.qmth.am.entity.StudentScoreEntity;
+import cn.com.qmth.am.enums.DataStatus;
 
 
 /**
 /**
  * 类注释
  * 类注释
@@ -16,5 +22,15 @@ public interface StudentService  extends IService<StudentEntity> {
 
 
 	ImportResult disposeFile(InputStream inputStream);
 	ImportResult disposeFile(InputStream inputStream);
 
 
+	List<StudentEntity> findToDispose();
+
+	void buildImage(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 from, DataStatus to);
+
 
 
 }
 }

+ 15 - 0
src/main/java/cn/com/qmth/am/service/impl/QuestionServiceImpl.java

@@ -7,6 +7,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
@@ -287,6 +288,20 @@ public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity
 				}
 				}
 				list.add(ret);
 				list.add(ret);
 			}
 			}
+			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;
 			return list;
 		} catch (Exception e) {
 		} catch (Exception e) {
 			return null;
 			return null;

+ 194 - 13
src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java

@@ -8,33 +8,70 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 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.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.ai.client.OcrApiClient;
+import com.qmth.boot.core.ai.model.ocr.OcrType;
 import com.qmth.boot.core.exception.StatusException;
 import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.core.retrofit.exception.RetrofitResponseError;
+import com.qmth.boot.core.retrofit.utils.SignatureInfo;
+import com.qmth.boot.core.retrofit.utils.UploadFile;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import com.qmth.boot.core.solar.service.SolarService;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.excel.model.DataMap;
 import com.qmth.boot.tools.excel.model.DataMap;
+import com.qmth.boot.tools.models.ByteArray;
 
 
+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.ImportResult;
 import cn.com.qmth.am.bean.StudentScoreDto;
 import cn.com.qmth.am.bean.StudentScoreDto;
+import cn.com.qmth.am.bean.StudentScoreImageDto;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.dao.StudentScoreDao;
 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.entity.StudentScoreEntity;
+import cn.com.qmth.am.enums.DataStatus;
 import cn.com.qmth.am.service.StudentScoreService;
 import cn.com.qmth.am.service.StudentScoreService;
+import cn.com.qmth.am.service.StudentService;
+import cn.com.qmth.am.utils.ImageUtil;
 
 
 @Service
 @Service
-public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, StudentScoreEntity> implements StudentScoreService {
-	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码","考生编号", "大题号", "小题号", "评分"};
+public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, StudentScoreEntity>
+		implements StudentScoreService {
+	private static final Logger log = LoggerFactory.getLogger(StudentScoreService.class);
+	private static BlockingQueue<StudentScoreImageDto> queue;
+	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "考生编号", "大题号", "小题号", "评分" };
 	@Autowired
 	@Autowired
 	private SysProperty sysProperty;
 	private SysProperty sysProperty;
+	@Autowired
+	private SolarService solarService;
+	@Autowired
+	private StudentService studentService;
+	@Autowired
+	private OcrApiClient ocrApiClient;
+	static {
+		int threadCount = Runtime.getRuntime().availableProcessors();
+		queue = new ArrayBlockingQueue<>(threadCount * 2);
+	}
 
 
 	@Override
 	@Override
 	public void importScore() {
 	public void importScore() {
@@ -175,7 +212,7 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 				msg.append("  科目代码不能超过100个字符");
 				msg.append("  科目代码不能超过100个字符");
 			}
 			}
 			imp.setSubjectCode(subjectCode);
 			imp.setSubjectCode(subjectCode);
-			
+
 			String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
 			String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
 			if (StringUtils.isBlank(studentCode)) {
 			if (StringUtils.isBlank(studentCode)) {
 				msg.append("  考生编号不能为空");
 				msg.append("  考生编号不能为空");
@@ -206,7 +243,7 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 				msg.append("  小题号不能为空");
 				msg.append("  小题号不能为空");
 			} else if (subNum.length() > 10) {
 			} else if (subNum.length() > 10) {
 				msg.append("  小题号不能超过10个字符");
 				msg.append("  小题号不能超过10个字符");
-			} 
+			}
 			imp.setSubNumber(subNum);
 			imp.setSubNumber(subNum);
 
 
 			String score = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
 			String score = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
@@ -223,7 +260,6 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 				}
 				}
 			}
 			}
 
 
-
 			if (msg.length() > 0) {
 			if (msg.length() > 0) {
 				failRecords.add(errorMsg(i + 1, msg.toString()));
 				failRecords.add(errorMsg(i + 1, msg.toString()));
 			} else {
 			} else {
@@ -236,26 +272,25 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 			return ret;
 			return ret;
 		}
 		}
 		try {
 		try {
-			updateScoreBatch(ret,ss);
+			updateScoreBatch(ret, ss);
 		} catch (Exception e) {
 		} catch (Exception e) {
 			failRecords.add("系统错误:" + e.getMessage());
 			failRecords.add("系统错误:" + e.getMessage());
 		}
 		}
 		return ret;
 		return ret;
 	}
 	}
 
 
-
-	private void updateScoreBatch(ImportResult ret,List<StudentScoreDto> ss) {
+	private void updateScoreBatch(ImportResult ret, List<StudentScoreDto> ss) {
 		if (CollectionUtils.isEmpty(ss)) {
 		if (CollectionUtils.isEmpty(ss)) {
 			ret.setCountInfo("更新数量:0");
 			ret.setCountInfo("更新数量:0");
 			return;
 			return;
 		}
 		}
-		int count=0;
+		int count = 0;
 		for (StudentScoreDto s : ss) {
 		for (StudentScoreDto s : ss) {
-			count=count+updateScore(s);
+			count = count + updateScore(s);
 		}
 		}
-		ret.setCountInfo("更新数量:"+count);
+		ret.setCountInfo("更新数量:" + count);
 	}
 	}
-	
+
 	private int updateScore(StudentScoreDto dto) {
 	private int updateScore(StudentScoreDto dto) {
 		UpdateWrapper<StudentScoreEntity> wrapper = new UpdateWrapper<>();
 		UpdateWrapper<StudentScoreEntity> wrapper = new UpdateWrapper<>();
 		LambdaUpdateWrapper<StudentScoreEntity> lw = wrapper.lambda();
 		LambdaUpdateWrapper<StudentScoreEntity> lw = wrapper.lambda();
@@ -265,6 +300,152 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 		lw.eq(StudentScoreEntity::getStudentCode, dto.getStudentCode());
 		lw.eq(StudentScoreEntity::getStudentCode, dto.getStudentCode());
 		lw.eq(StudentScoreEntity::getMainNumber, dto.getMainNumber());
 		lw.eq(StudentScoreEntity::getMainNumber, dto.getMainNumber());
 		lw.eq(StudentScoreEntity::getSubNumber, dto.getSubNumber());
 		lw.eq(StudentScoreEntity::getSubNumber, dto.getSubNumber());
-		return this.update(wrapper)?1:0;
+		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> adds = new ArrayList<>();
+		for (QuestionEntity q : quetions.values()) {
+			StudentScoreEntity s = new StudentScoreEntity();
+			adds.add(s);
+			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());
+		}
+		this.saveBatch(adds);
+	}
+
+	@Transactional
+	@Override
+	public void updateAnswerErr(Long id, String err) {
+		UpdateWrapper<StudentScoreEntity> wrapper = new UpdateWrapper<>();
+		LambdaUpdateWrapper<StudentScoreEntity> lw = wrapper.lambda();
+		lw.set(StudentScoreEntity::getAnswerStatus, DataStatus.FAILED);
+		lw.set(StudentScoreEntity::getErrMsg, err);
+		lw.eq(StudentScoreEntity::getId, id);
+		this.update(wrapper);
+	}
+
+	@Override
+	public void createSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages) {
+		StudentScoreImageDto dto=new StudentScoreImageDto();
+		dto.setStudentId(score.getStudentId());
+		dto.setStudentScoreId(score.getId());
+		dto.setImage(getSlice(score, q, answerImages));
+		queue.offer(dto);
+	}
+
+	private byte[] getSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages) {
+		List<byte[]> ret = new ArrayList<>();
+		String suff = null;
+		for (ImageSlice s : q.getImageSlice()) {
+			AnswerImageDto sheet = getSheet(score, q, s.getI(), answerImages);
+			suff=sheet.getSuff();
+			ret.add(ImageUtil.cutImage(sheet.getImage(), sheet.getSuff(), s.getX().intValue(), s.getY().intValue(),
+					s.getW().intValue(), s.getH().intValue()));
+		}
+		if(ret.size()>1) {
+			return ImageUtil.joinImages(ret, suff);
+		}else {
+			return ret.get(0);
+		}
+	}
+
+	private AnswerImageDto getSheet(StudentScoreEntity score, QuestionEntity q, Integer pageIndex,
+			Map<Integer, AnswerImageDto> answerImages) {
+		AnswerImageDto ret = answerImages.get(pageIndex);
+		if (ret != null) {
+			return ret;
+		}
+		try {
+			ret = new AnswerImageDto();
+			String url = getImageUrl(score, q, pageIndex);
+			String tem = url.split("\\?")[0];
+			String suff = tem.substring(tem.lastIndexOf(".") + 1).toLowerCase();
+			ret.setImage(ByteArray.fromUrl(url).value());
+			ret.setPageIndex(pageIndex);
+			ret.setSuff(suff);
+			answerImages.put(pageIndex, ret);
+			return ret;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private String getImageUrl(StudentScoreEntity score, QuestionEntity q, Integer pageIndex) {
+		return null;
+	}
+	
+	@Override
+	public StudentScoreImageDto pollStudentScoreImage() {
+		return queue.poll();
+	}
+
+	@Transactional
+	@Override
+	public void ocr(StudentScoreImageDto dto) {
+		OrgInfo org=solarService.getOrgList().get(0);
+		String ret=ocrDispose(dto, org);
+		if(ret!=null) {
+			updateAnswer(dto.getStudentScoreId(), ret);
+		}
+	}
+	public void updateAnswer(Long id, String answer) {
+		UpdateWrapper<StudentScoreEntity> wrapper = new UpdateWrapper<>();
+		LambdaUpdateWrapper<StudentScoreEntity> lw = wrapper.lambda();
+		lw.set(StudentScoreEntity::getAnswerStatus, DataStatus.SUCCESS);
+		lw.set(StudentScoreEntity::getAnswer, answer);
+		lw.eq(StudentScoreEntity::getId, id);
+		this.update(wrapper);
+	}
+	private String ocrDispose(StudentScoreImageDto dto,OrgInfo org) {
+		SignatureInfo signature = SignatureInfo.secret(org.getAccessKey(), org.getAccessSecret());
+		try {
+			return ocrApiClient.forImage(signature, OcrType.HANDWRITING, UploadFile.build("image", "", dto.getImage()));
+		} catch (Exception e) {
+			log.error("ocr异常",e);
+			if(e instanceof RetrofitResponseError) {
+				RetrofitResponseError tem=(RetrofitResponseError)e;
+				if(tem.getCode()==503) {
+					if(dto.getRetry()<=3) {
+						try {
+							Thread.sleep(3000);
+						} catch (InterruptedException e1) {
+						}
+						dto.setRetry(dto.getRetry()+1);
+						return ocrDispose(dto, org);
+					}else {
+						ocrErr(dto);
+						return null;
+					}
+				}else {
+					ocrErr(dto);
+					return null;
+				}
+			}else {
+				ocrErr(dto);
+				return null;
+			}
+		}
+	}
+	
+	private void ocrErr(StudentScoreImageDto dto) {
+		updateAnswerErr(dto.getStudentScoreId(), "ocr异常");
+		studentService.updateStatus(dto.getStudentId(), DataStatus.PROCESSING, DataStatus.FAILED);
 	}
 	}
 }
 }

+ 79 - 0
src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java

@@ -7,36 +7,52 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.boot.core.exception.StatusException;
 import com.qmth.boot.core.exception.StatusException;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.excel.model.DataMap;
 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.ImportResult;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.dao.StudentDao;
 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.StudentEntity;
+import cn.com.qmth.am.entity.StudentScoreEntity;
+import cn.com.qmth.am.enums.DataStatus;
+import cn.com.qmth.am.service.StudentScoreService;
 import cn.com.qmth.am.service.StudentService;
 import cn.com.qmth.am.service.StudentService;
 
 
 @Service
 @Service
 public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
 public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
+	private static final Logger log = LoggerFactory.getLogger(StudentService.class);
 	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码","考生编号" };
 	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码","考生编号" };
 	@Autowired
 	@Autowired
 	private SysProperty sysProperty;
 	private SysProperty sysProperty;
 	@Autowired
 	@Autowired
 	private StudentService studentService;
 	private StudentService studentService;
+	@Autowired
+	private StudentScoreService studentScoreService;
 
 
 	@Override
 	@Override
 	public void importStudent() {
 	public void importStudent() {
@@ -236,4 +252,67 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
 		ret.setCountInfo("新增数量:"+adds.size());
 		ret.setCountInfo("新增数量:"+adds.size());
 	}
 	}
 
 
+	@Override
+	public List<StudentEntity> findToDispose() {
+		QueryWrapper<StudentEntity> wrapper = new QueryWrapper<>();
+		LambdaQueryWrapper<StudentEntity> lw = wrapper.lambda();
+		lw.eq(StudentEntity::getDataStatus, DataStatus.WAITING);
+		return this.list(wrapper);
+	}
+
+	@Override
+	public void buildImage(StudentEntity student, Map<Long,QuestionEntity> quetions) {
+		List<StudentScoreEntity> scores=studentService.getOrCreateScores(student, quetions);
+		Map<Integer,AnswerImageDto> answerImages=new HashMap<>();
+		for(StudentScoreEntity score:scores) {
+			if(DataStatus.WAITING.equals(score.getAnswerStatus())) {
+				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.PROCESSING,DataStatus.FAILED);
+		}
+		try {
+			studentScoreService.createSlice(score,q,answerImages);
+		} catch (Exception e) {
+			if(e instanceof StatusException) {
+				studentScoreService.updateAnswerErr(score.getId(),e.getMessage());
+				updateStatus(score.getStudentId(),DataStatus.PROCESSING,DataStatus.FAILED);
+			}else {
+				log.error("系统异常",e);
+				studentScoreService.updateAnswerErr(score.getId(),"系统异常");
+				updateStatus(score.getStudentId(),DataStatus.PROCESSING,DataStatus.FAILED);
+			}
+		}
+	}
+
+	@Transactional
+	@Override
+	public List<StudentScoreEntity> getOrCreateScores(StudentEntity student, Map<Long,QuestionEntity> quetions) {
+		List<StudentScoreEntity> scores=studentScoreService.getByStudentId(student.getId());
+		if(CollectionUtils.isEmpty(scores)) {
+			studentScoreService.add(student,quetions);
+		}
+		updateStatus(student.getId(),DataStatus.WAITING,DataStatus.PROCESSING);
+		return scores;
+	}
+	
+	@Transactional
+	@Override
+	public void updateStatus(Long id,DataStatus from,DataStatus to) {
+		UpdateWrapper<StudentEntity> wrapper = new UpdateWrapper<>();
+		LambdaUpdateWrapper<StudentEntity> lw = wrapper.lambda();
+		lw.set(StudentEntity::getDataStatus, to);
+		lw.eq(StudentEntity::getId, id);
+		lw.eq(StudentEntity::getDataStatus, from);
+		this.update(wrapper);
+	}
+
 }
 }

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

@@ -15,7 +15,7 @@ public class AiMarkingJob {
     @Autowired
     @Autowired
     private ConcurrentService concurrentService;
     private ConcurrentService concurrentService;
 
 
-    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 20 * 1000)
     public void doJob() {
     public void doJob() {
         boolean lock = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock();
         boolean lock = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock();
         try {
         try {

+ 101 - 0
src/main/java/cn/com/qmth/am/task/BiuldImageJob.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.am.task;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+
+import cn.com.qmth.am.consumer.BuildImageConsumer;
+import cn.com.qmth.am.entity.QuestionEntity;
+import cn.com.qmth.am.entity.StudentEntity;
+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.utils.SpringContextHolder;
+
+@Service
+public class BiuldImageJob {
+	@Autowired
+	private StudentService studentService;
+	@Autowired
+	private QuestionService questionService;
+	@Autowired
+	private ConcurrentService concurrentService;
+
+	private static ExecutorService executor;
+
+	static {
+		int threadCount = Runtime.getRuntime().availableProcessors();
+		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 = 20 * 1000)
+	public void doJob() {
+		boolean lock = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock();
+		try {
+			if (!lock) {
+				return;
+			}
+			this.dispose();
+		} finally {
+			if (lock) {
+				concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock();
+			}
+		}
+	}
+
+	private void dispose() {
+		List<StudentEntity> stus = studentService.findToDispose();
+		if (CollectionUtils.isEmpty(stus)) {
+			return;
+		}
+		List<QuestionEntity> qs=questionService.list();
+		if (CollectionUtils.isEmpty(qs)) {
+			return;
+		}
+		Map<String,Map<Long,QuestionEntity>> qmap=new HashMap<>();
+		for(QuestionEntity q:qs) {
+			String key=q.getExamId()+"-"+q.getSubjectCode();
+			Map<Long,QuestionEntity> tem=qmap.get(key);
+			if(tem==null) {
+				tem=new HashMap<>();
+				qmap.put(key, tem);
+			}
+			tem.put(q.getId(),q);
+		}
+		for (StudentEntity stu : stus) {
+			String key=stu.getExamId()+"-"+stu.getSubjectCode();
+			Map<Long,QuestionEntity> tem=qmap.get(key);
+			if(tem!=null) {
+				BuildImageConsumer com = SpringContextHolder.getBean(BuildImageConsumer.class);
+				com.setStudent(stu);
+				com.setQuetions(tem);
+				executor.execute(com);
+			}
+		}
+	}
+
+}

+ 406 - 0
src/main/java/cn/com/qmth/am/utils/ImageUtil.java

@@ -0,0 +1,406 @@
+package cn.com.qmth.am.utils;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+
+public final class ImageUtil {
+
+	/**
+	 * 图片水印
+	 * 
+	 * @param pressImg  水印图片
+	 * @param targetImg 目标图片
+	 * @param x         修正值 默认在中间
+	 * @param y         修正值 默认在中间
+	 * @param alpha     透明度
+	 */
+	public final static void pressImage(String pressImg, String targetImg, int x, int y, float alpha) {
+		try {
+			File img = new File(targetImg);
+			Image src = ImageIO.read(img);
+			int wideth = src.getWidth(null);
+			int height = src.getHeight(null);
+			BufferedImage image = new BufferedImage(wideth, height, BufferedImage.TYPE_INT_RGB);
+			Graphics2D g = image.createGraphics();
+			g.drawImage(src, 0, 0, wideth, height, null);
+			// 水印文件
+			Image src_biao = ImageIO.read(new File(pressImg));
+			int wideth_biao = src_biao.getWidth(null);
+			int height_biao = src_biao.getHeight(null);
+			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+			g.drawImage(src_biao, (wideth - wideth_biao) / 2, (height - height_biao) / 2, wideth_biao, height_biao,
+					null);
+			// 水印文件结束
+			g.dispose();
+			ImageIO.write((BufferedImage) image, "jpg", img);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * 文字水印
+	 * 
+	 * @param pressText 水印文字
+	 * @param targetImg 目标图片
+	 * @param fontName  字体名称
+	 * @param fontStyle 字体样式
+	 * @param color     字体颜色
+	 * @param fontSize  字体大小
+	 * @param x         修正值
+	 * @param y         修正值
+	 * @param alpha     透明度
+	 */
+	public static void pressText(String pressText, String targetImg, String fontName, int fontStyle, Color color,
+			int fontSize, int x, int y, float alpha) {
+		try {
+			File img = new File(targetImg);
+			Image src = ImageIO.read(img);
+			int width = src.getWidth(null);
+			int height = src.getHeight(null);
+			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+			Graphics2D g = image.createGraphics();
+			g.drawImage(src, 0, 0, width, height, null);
+			g.setColor(color);
+			g.setFont(new Font(fontName, fontStyle, fontSize));
+			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+			g.drawString(pressText, (width - (getLength(pressText) * fontSize)) / 2 + x, (height - fontSize) / 2 + y);
+			g.dispose();
+			ImageIO.write((BufferedImage) image, "jpg", img);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * 缩放
+	 * 
+	 * @param filePath    图片路径
+	 * @param newFileName 缩放完比图片路径
+	 * @param height      高度
+	 * @param width       宽度
+	 * @param bb          比例不对时是否需要补白
+	 */
+	@SuppressWarnings("static-access")
+	public static void resize(String filePath, String newFileName, int height, int width, String formatName,
+			boolean bb) {
+		try {
+			double ratio = 0.0; // 缩放比例
+			File f = new File(filePath);
+			BufferedImage bi = ImageIO.read(f);
+			Image itemp = bi.getScaledInstance(width, height, bi.SCALE_SMOOTH);
+			// 计算比例
+			if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
+				if (bi.getHeight() > bi.getWidth()) {
+					ratio = (new Integer(height)).doubleValue() / bi.getHeight();
+				} else {
+					ratio = (new Integer(width)).doubleValue() / bi.getWidth();
+				}
+				AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
+				itemp = op.filter(bi, null);
+			}
+			if (bb) {
+				BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+				Graphics2D g = image.createGraphics();
+				g.setColor(Color.white);
+				g.fillRect(0, 0, width, height);
+				if (width == itemp.getWidth(null))
+					g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2, itemp.getWidth(null),
+							itemp.getHeight(null), Color.white, null);
+				else
+					g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0, itemp.getWidth(null),
+							itemp.getHeight(null), Color.white, null);
+				g.dispose();
+				itemp = image;
+			}
+			BufferedImage outbi = null;
+			if (itemp instanceof BufferedImage)
+				outbi = (BufferedImage) itemp;
+			else
+				outbi = convertImageToBuffer(itemp);
+
+			File newFile = null;
+			if (newFileName == null || newFileName.length() == 0) {
+				newFile = f;
+			} else {
+				newFile = new File(newFileName);
+				if (!newFile.getParentFile().exists())
+					newFile.getParentFile().mkdirs();
+			}
+			ImageIO.write(outbi, formatName, newFile);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * 对流缩放
+	 * 
+	 * @param input      图片流
+	 * @param formatName 图片格式
+	 * @param height     高度
+	 * @param width      宽度
+	 * @param bb         比例不对时是否需要补白
+	 */
+	@SuppressWarnings("static-access")
+	public static InputStream MemoryResize(InputStream input, String formatName, int height, int width, boolean bb) {
+		try {
+			double ratio = 0.0; // 缩放比例
+			BufferedImage bi = ImageIO.read(input);
+			Image itemp = bi.getScaledInstance(width, height, bi.SCALE_SMOOTH);
+			// 计算比例
+			if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
+				if (bi.getHeight() > bi.getWidth()) {
+					ratio = (new Integer(height)).doubleValue() / bi.getHeight();
+				} else {
+					ratio = (new Integer(width)).doubleValue() / bi.getWidth();
+				}
+				AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
+				itemp = op.filter(bi, null);
+			}
+			if (bb) {
+				BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+				Graphics2D g = image.createGraphics();
+				g.setColor(Color.white);
+				g.fillRect(0, 0, width, height);
+				if (width == itemp.getWidth(null))
+					g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2, itemp.getWidth(null),
+							itemp.getHeight(null), Color.white, null);
+				else
+					g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0, itemp.getWidth(null),
+							itemp.getHeight(null), Color.white, null);
+				g.dispose();
+				itemp = image;
+			}
+			ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+			BufferedImage outbi = null;
+			if (itemp instanceof BufferedImage)
+				outbi = (BufferedImage) itemp;
+			else
+				outbi = convertImageToBuffer(itemp);
+
+			ImageIO.write(outbi, formatName, output);
+			byte[] buff = output.toByteArray();
+			return new ByteArrayInputStream(buff);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	/**
+	 * 裁剪图片
+	 * 
+	 * @param img
+	 * @param dest
+	 * @param top
+	 * @param left
+	 * @param width
+	 * @param height
+	 * @return
+	 * @throws IOException
+	 */
+	public static boolean cutImage(File img, String dest, int top, int left, int width, int height) throws IOException {
+		File fileDest = new File(dest);
+		if (!fileDest.getParentFile().exists())
+			fileDest.getParentFile().mkdirs();
+		String ext = getExtension(dest).toLowerCase();
+		BufferedImage bi = (BufferedImage) ImageIO.read(img);
+		height = Math.min(height, bi.getHeight());
+		width = Math.min(width, bi.getWidth());
+		if (height <= 0)
+			height = bi.getHeight();
+		if (width <= 0)
+			width = bi.getWidth();
+		top = Math.min(Math.max(0, top), bi.getHeight() - height);
+		left = Math.min(Math.max(0, left), bi.getWidth() - width);
+
+		BufferedImage bi_cropper = bi.getSubimage(left, top, width, height);
+		return ImageIO.write(bi_cropper, ext.equals("png") ? "png" : "jpg", fileDest);
+	}
+
+	public static byte[] cutImage(byte[] img, String suff, int top, int left, int width, int height) {
+		InputStream in = null;
+		BufferedImage bi = null;
+		ByteArrayOutputStream output = null;
+		try {
+			in = new ByteArrayInputStream(img);
+			bi = ImageIO.read(in);
+			height = Math.min(height, bi.getHeight());
+			width = Math.min(width, bi.getWidth());
+			if (height <= 0)
+				height = bi.getHeight();
+			if (width <= 0)
+				width = bi.getWidth();
+			top = Math.min(Math.max(0, top), bi.getHeight() - height);
+			left = Math.min(Math.max(0, left), bi.getWidth() - width);
+
+			BufferedImage bi_cropper = bi.getSubimage(left, top, width, height);
+			output = new ByteArrayOutputStream();
+			ImageIO.write(bi_cropper, suff, output);
+			byte[] ret = output.toByteArray();
+			return ret;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			try {
+				if (in != null) {
+					in.close();
+				}
+			} catch (IOException e) {
+			}
+			try {
+				if (output != null) {
+					output.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	public static int getLength(String text) {
+		int length = 0;
+		for (int i = 0; i < text.length(); i++) {
+			if (new String(text.charAt(i) + "").getBytes().length > 1) {
+				length += 2;
+			} else {
+				length += 1;
+			}
+		}
+		return length / 2;
+	}
+
+	public static String getExtension(File f) {
+		return (f != null) ? getExtension(f.getName()) : "";
+	}
+
+	public static String getExtension(String filename) {
+		return getExtension(filename, "");
+	}
+
+	public static String getExtension(String filename, String defExt) {
+		if ((filename != null) && (filename.length() > 0)) {
+			int i = filename.lastIndexOf('.');
+
+			if ((i > -1) && (i < (filename.length() - 1))) {
+				return filename.substring(i + 1);
+			}
+		}
+		return defExt;
+	}
+
+	public static String trimExtension(String filename) {
+		if ((filename != null) && (filename.length() > 0)) {
+			int i = filename.lastIndexOf('.');
+			if ((i > -1) && (i < (filename.length()))) {
+				return filename.substring(0, i);
+			}
+		}
+		return filename;
+	}
+
+	/**
+	 * 转化Image 为 BufferedImage
+	 * 
+	 * @param img
+	 * @return
+	 */
+	private static BufferedImage convertImageToBuffer(Image img) {
+		BufferedImage bufferedImage = new BufferedImage(img.getWidth(null), img.getHeight(null),
+				BufferedImage.TYPE_INT_RGB);
+		Graphics g = bufferedImage.createGraphics();
+		g.drawImage(img, 0, 0, null);
+		g.dispose();
+		return bufferedImage;
+	}
+
+	// 拼接图片
+	public static byte[] joinImages(List<byte[]> slices, String suff) {
+		boolean horizontal = false;
+		List<BufferedImage> imageList = new ArrayList<>();
+		for (byte[] slice : slices) {
+			InputStream in = null;
+			try {
+				in = new ByteArrayInputStream(slice);
+				BufferedImage image = ImageIO.read(in);
+				imageList.add(image);
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			} finally {
+				try {
+					if (in != null) {
+						in.close();
+					}
+				} catch (IOException e) {
+				}
+			}
+		}
+		int height = imageList.get(0).getHeight();
+		int width = imageList.get(0).getWidth();
+		if (horizontal) {
+			height = imageList.stream().mapToInt(BufferedImage::getHeight).max().getAsInt();
+			width = imageList.stream().mapToInt(BufferedImage::getWidth).sum();
+		} else {
+			width = imageList.stream().mapToInt(BufferedImage::getWidth).max().getAsInt();
+			height = imageList.stream().mapToInt(BufferedImage::getHeight).sum();
+		}
+		// 创建拼接后的图片画布,参数分别为宽,高,类型,这里我们使用RGB3元色类型
+		BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+		Graphics graphics = resultImage.getGraphics();
+		int previousWidth = 0;
+		int previousHeight = 0;
+		for (BufferedImage image : imageList) {
+			// 向画布上画图片
+			graphics.drawImage(image, previousWidth, previousHeight, null);
+			if (horizontal) {
+				previousWidth += image.getWidth();
+			} else {
+				previousHeight += image.getHeight();
+			}
+		}
+		ByteArrayOutputStream output = new ByteArrayOutputStream();
+		try {
+			Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(suff);
+			ImageWriter imageWriter = iter.next();
+			JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(Locale.getDefault());
+			jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+			jpegParams.setCompressionQuality(1.0F);
+			imageWriter.setOutput(ImageIO.createImageOutputStream(output));
+			imageWriter.write(null, new IIOImage(resultImage, null, null), jpegParams);
+			byte[] ret = output.toByteArray();
+			return ret;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			try {
+				if (output != null) {
+					output.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+}