xiatian 3 meses atrás
pai
commit
c845573776
23 arquivos alterados com 1331 adições e 173 exclusões
  1. 50 36
      src/main/java/cn/com/qmth/am/bean/StudentScoreImageDto.java
  2. 20 0
      src/main/java/cn/com/qmth/am/bean/ds/ChatReq.java
  3. 26 0
      src/main/java/cn/com/qmth/am/bean/ds/ImageContent.java
  4. 3 16
      src/main/java/cn/com/qmth/am/bean/ds/MarkingReq.java
  5. 4 0
      src/main/java/cn/com/qmth/am/bean/ds/OcrContent.java
  6. 30 0
      src/main/java/cn/com/qmth/am/bean/ds/OcrMessage.java
  7. 28 0
      src/main/java/cn/com/qmth/am/bean/ds/OcrReq.java
  8. 17 0
      src/main/java/cn/com/qmth/am/bean/ds/TextContent.java
  9. 50 7
      src/main/java/cn/com/qmth/am/config/SysProperty.java
  10. 11 0
      src/main/java/cn/com/qmth/am/entity/StudentScoreEntity.java
  11. 4 3
      src/main/java/cn/com/qmth/am/enums/ModelTypeBak.java
  12. 2 0
      src/main/java/cn/com/qmth/am/service/DsMarkingService.java
  13. 1 1
      src/main/java/cn/com/qmth/am/service/StudentService.java
  14. 39 6
      src/main/java/cn/com/qmth/am/service/impl/DsMarkingServiceImpl.java
  15. 38 11
      src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java
  16. 4 1
      src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java
  17. 1 1
      src/main/java/cn/com/qmth/am/task/AiMarkingJob.java
  18. 88 79
      src/main/java/cn/com/qmth/am/task/BuildImageJob.java
  19. 2 2
      src/main/java/cn/com/qmth/am/task/QuestionImportJob.java
  20. 2 2
      src/main/java/cn/com/qmth/am/task/StudentImportJob.java
  21. 2 2
      src/main/java/cn/com/qmth/am/task/StudentScoreImportJob.java
  22. 899 0
      src/main/java/cn/com/qmth/am/utils/FileUtil.java
  23. 10 6
      src/main/resources/application.properties

+ 50 - 36
src/main/java/cn/com/qmth/am/bean/StudentScoreImageDto.java

@@ -1,41 +1,55 @@
 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;
-	}
+
+    private Long studentScoreId;
+
+    private Long studentId;
+
+    private byte[] image;
+
+    private String suff;
+
+    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;
+    }
+
+    public String getSuff() {
+        return suff;
+    }
+
+    public void setSuff(String suff) {
+        this.suff = suff;
+    }
 
 }

+ 20 - 0
src/main/java/cn/com/qmth/am/bean/ds/ChatReq.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.am.bean.ds;
+
+public class ChatReq {
+
+    private String model;
+
+    public ChatReq(String modelType) {
+        super();
+        this.model = modelType;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+}

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

@@ -0,0 +1,26 @@
+package cn.com.qmth.am.bean.ds;
+
+public class ImageContent extends OcrContent {
+
+    private String type = "image_url";
+
+    private String image_url;
+
+    public ImageContent(String image_url) {
+        super();
+        this.image_url = image_url;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getImage_url() {
+        return image_url;
+    }
+
+    public void setImage_url(String image_url) {
+        this.image_url = image_url;
+    }
+
+}

+ 3 - 16
src/main/java/cn/com/qmth/am/bean/ds/ReqMessages.java → src/main/java/cn/com/qmth/am/bean/ds/MarkingReq.java

@@ -5,17 +5,12 @@ import java.util.List;
 
 import com.qmth.boot.core.ai.model.llm.ChatRole;
 
-import cn.com.qmth.am.enums.ModelType;
-
-public class ReqMessages {
+public class MarkingReq extends ChatReq {
 
     private List<ChatContent> messages;
 
-    private String model;
-
-    public ReqMessages(ModelType modelType) {
-        super();
-        this.model = modelType.getCode();
+    public MarkingReq(String modelType) {
+        super(modelType);
     }
 
     public List<ChatContent> getMessages() {
@@ -26,14 +21,6 @@ public class ReqMessages {
         this.messages = messages;
     }
 
-    public String getModel() {
-        return model;
-    }
-
-    public void setModel(String model) {
-        this.model = model;
-    }
-
     public void addMsg(ChatRole role, String msg) {
         if (messages == null) {
             messages = new ArrayList<>();

+ 4 - 0
src/main/java/cn/com/qmth/am/bean/ds/OcrContent.java

@@ -0,0 +1,4 @@
+package cn.com.qmth.am.bean.ds;
+
+public class OcrContent {
+}

+ 30 - 0
src/main/java/cn/com/qmth/am/bean/ds/OcrMessage.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.am.bean.ds;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.qmth.boot.core.ai.model.llm.ChatRole;
+
+public class OcrMessage {
+
+    private ChatRole role;
+
+    private List<OcrContent> content;
+
+    public OcrMessage(String base64) {
+        super();
+        this.role = ChatRole.user;
+        content = new ArrayList<>();
+        content.add(new ImageContent(base64));
+        content.add(new TextContent());
+    }
+
+    public ChatRole getRole() {
+        return role;
+    }
+
+    public List<OcrContent> getContent() {
+        return content;
+    }
+
+}

+ 28 - 0
src/main/java/cn/com/qmth/am/bean/ds/OcrReq.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.am.bean.ds;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OcrReq extends ChatReq {
+
+    private List<OcrMessage> messages;
+
+    public OcrReq(String modelType) {
+        super(modelType);
+    }
+
+    public List<OcrMessage> getMessages() {
+        return messages;
+    }
+
+    public void setMessages(List<OcrMessage> messages) {
+        this.messages = messages;
+    }
+
+    public void addMsg(OcrMessage cc) {
+        if (messages == null) {
+            messages = new ArrayList<>();
+        }
+        messages.add(cc);
+    }
+}

+ 17 - 0
src/main/java/cn/com/qmth/am/bean/ds/TextContent.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.am.bean.ds;
+
+public class TextContent extends OcrContent {
+
+    private String type = "text";
+
+    private String text = "只输出图片中的内容";
+
+    public String getType() {
+        return type;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+}

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

@@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import cn.com.qmth.am.enums.DataType;
-import cn.com.qmth.am.enums.ModelType;
 
 @Component
 public class SysProperty {
@@ -27,15 +26,27 @@ public class SysProperty {
     @Value("${am.data-type}")
     private DataType dataType;
 
-    @Value("${am.marking-model}")
-    private ModelType markingModel;
+    @Value("${am.marking-ocr-thread-count:2}")
+    private Integer ocrThreadCount;
 
-    @Value("${am.marking-key:none}")
+    @Value("${am.marking-marking-model:solar}")
+    private String markingModel;
+
+    @Value("${am.marking-marking-key:none}")
     private String markingKey;
 
-    @Value("${am.marking-server:none}")
+    @Value("${am.marking-marking-server:none}")
     private String markingServer;
 
+    @Value("${am.marking-ocr-model:solar}")
+    private String ocrModel;
+
+    @Value("${am.marking-ocr-key:none}")
+    private String ocrKey;
+
+    @Value("${am.marking-ocr-server:none}")
+    private String ocrServer;
+
     public String getVersion() {
         return version;
     }
@@ -84,11 +95,11 @@ public class SysProperty {
         this.markingTaskEnable = markingTaskEnable;
     }
 
-    public ModelType getMarkingModel() {
+    public String getMarkingModel() {
         return markingModel;
     }
 
-    public void setMarkingModel(ModelType markingModel) {
+    public void setMarkingModel(String markingModel) {
         this.markingModel = markingModel;
     }
 
@@ -108,4 +119,36 @@ public class SysProperty {
         this.markingServer = markingServer;
     }
 
+    public String getOcrModel() {
+        return ocrModel;
+    }
+
+    public void setOcrModel(String ocrModel) {
+        this.ocrModel = ocrModel;
+    }
+
+    public String getOcrKey() {
+        return ocrKey;
+    }
+
+    public void setOcrKey(String ocrKey) {
+        this.ocrKey = ocrKey;
+    }
+
+    public String getOcrServer() {
+        return ocrServer;
+    }
+
+    public void setOcrServer(String ocrServer) {
+        this.ocrServer = ocrServer;
+    }
+
+    public Integer getOcrThreadCount() {
+        return ocrThreadCount;
+    }
+
+    public void setOcrThreadCount(Integer ocrThreadCount) {
+        this.ocrThreadCount = ocrThreadCount;
+    }
+
 }

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

@@ -47,6 +47,9 @@ public class StudentScoreEntity extends IdEntity {
     // ocr结果
     private String answer;
 
+    // ocr图片
+    private String image;
+
     // 错误信息
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private String errMsg;
@@ -185,4 +188,12 @@ public class StudentScoreEntity extends IdEntity {
         this.stepScore = stepScore;
     }
 
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
 }

+ 4 - 3
src/main/java/cn/com/qmth/am/enums/ModelType.java → src/main/java/cn/com/qmth/am/enums/ModelTypeBak.java

@@ -1,11 +1,12 @@
 package cn.com.qmth.am.enums;
 
-public enum ModelType {
+public enum ModelTypeBak {
 
     solar("solar"), qwen2_5("Qwen2.5-32B-Instruct-GPTQ-Int4"), ds_r1("deepseek-r1-distill-qwen-32b-awq"), glm4_9(
-            "glm-4-9b-chat"), ds_v3("deepseek-v3"),;
+            "glm-4-9b-chat"), ds_v3(
+                    "deepseek-v3"), qwen72_ocr("qwen2.5-vl-72b-instruct"), qwen7_ocr("qwen2.5-vl-7b-instruct");
 
-    private ModelType(String code) {
+    private ModelTypeBak(String code) {
         this.code = code;
     }
 

+ 2 - 0
src/main/java/cn/com/qmth/am/service/DsMarkingService.java

@@ -10,4 +10,6 @@ public interface DsMarkingService {
 
     AutoScoreResult autoScore(AutoScoreRequest req);
 
+    String ocr(String base64);
+
 }

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

@@ -22,7 +22,7 @@ public interface StudentService extends IService<StudentEntity> {
 
     ImportResult disposeFile(InputStream inputStream);
 
-    List<StudentEntity> findToDispose();
+    List<StudentEntity> findToDispose(Long examId);
 
     void buildImage(StudentEntity student, Map<Long, QuestionEntity> quetions);
 

+ 39 - 6
src/main/java/cn/com/qmth/am/service/impl/DsMarkingServiceImpl.java

@@ -21,8 +21,11 @@ import com.qmth.boot.core.ai.model.llm.AutoScoreResult;
 import com.qmth.boot.core.ai.model.llm.ChatRole;
 import com.qmth.boot.core.retrofit.exception.RetrofitResponseError;
 
+import cn.com.qmth.am.bean.ds.ChatReq;
 import cn.com.qmth.am.bean.ds.ChatResult;
-import cn.com.qmth.am.bean.ds.ReqMessages;
+import cn.com.qmth.am.bean.ds.MarkingReq;
+import cn.com.qmth.am.bean.ds.OcrMessage;
+import cn.com.qmth.am.bean.ds.OcrReq;
 import cn.com.qmth.am.config.SysProperty;
 import cn.com.qmth.am.service.DsMarkingService;
 import cn.com.qmth.am.utils.FreeMarkerUtil;
@@ -59,13 +62,23 @@ public class DsMarkingServiceImpl implements DsMarkingService {
     // IOUtils.closeQuietly(resp);
     // }
     // }
+    @Override
+    public String ocr(String base64) {
+        OcrReq dreq = new OcrReq(sysProperty.getOcrModel());
+        dreq.addMsg(new OcrMessage(base64));
+        String res = ocr(dreq);
+        ChatResult result = JSONObject.parseObject(res, ChatResult.class);
+        String text = result.getChoices().stream().filter(choice -> choice.getMessage().getRole() == ChatRole.assistant)
+                .map(choice -> choice.getMessage().getContent()).findFirst().orElse("");
+        return text;
+    }
 
     @Override
     public AutoScoreResult autoScore(AutoScoreRequest request) {
         String question = FreeMarkerUtil.getDsMarkingReq(request);
-        ReqMessages dreq = new ReqMessages(sysProperty.getMarkingModel());
+        MarkingReq dreq = new MarkingReq(sysProperty.getMarkingModel());
         dreq.addMsg(ChatRole.user, question);
-        String res = chat(dreq);
+        String res = marking(dreq);
         ChatResult result = JSONObject.parseObject(res, ChatResult.class);
         String text = result.getChoices().stream().filter(choice -> choice.getMessage().getRole() == ChatRole.assistant)
                 .map(choice -> choice.getMessage().getContent()).findFirst().orElse("");
@@ -130,7 +143,7 @@ public class DsMarkingServiceImpl implements DsMarkingService {
     public static void main(String[] args) {
         String scoreStr = "</think>。\\n\\n\\n70,70,60\\n\\n评分结果2个3,29,110 \\n\\n考生的回答完全覆盖了所有的关键内容,逻辑清晰,术语使用准确";
         scoreStr = scoreStr.substring(scoreStr.lastIndexOf("</think>") + 1).trim();
-        System.out.println(scoreStr);
+        System.out.println(Runtime.getRuntime().availableProcessors());
         String ret = scoreStr.replaceAll(",", ",").replaceAll("。", "").replaceAll(":", ":").replaceAll("[0-9]\\.", "");
         Pattern pattern = Pattern.compile("(\\d{1,3}\\s*,\\s*)+\\d{1,3}");
         Matcher matcher = pattern.matcher(ret);
@@ -140,7 +153,7 @@ public class DsMarkingServiceImpl implements DsMarkingService {
     }
 
     @SuppressWarnings("deprecation")
-    private String chat(ReqMessages dreq) {
+    private String marking(ChatReq dreq) {
 
         Map<String, String> headers = new HashMap<>();
         headers.put("Authorization", "Bearer " + sysProperty.getMarkingKey());
@@ -154,7 +167,27 @@ public class DsMarkingServiceImpl implements DsMarkingService {
                 return resp.body().string();
             }
         } catch (Exception e) {
-            throw new RetrofitResponseError(resp.code(), e.getMessage(), e);
+            throw new RetrofitResponseError(500, e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private String ocr(ChatReq dreq) {
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer " + sysProperty.getOcrKey());
+        Response resp = null;
+        try {
+            resp = OKHttpUtil.call(HttpMethod.POST, sysProperty.getOcrServer(), headers, JSONObject.toJSONString(dreq));
+            if (resp.code() != 200) {
+                throw new RetrofitResponseError(resp.code(), resp.body().string());
+            } else {
+                return resp.body().string();
+            }
+        } catch (Exception e) {
+            throw new RetrofitResponseError(500, e.getMessage(), e);
         } finally {
             IOUtils.closeQuietly(resp);
         }

+ 38 - 11
src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java

@@ -60,11 +60,12 @@ import cn.com.qmth.am.entity.StudentScoreEntity;
 import cn.com.qmth.am.enums.DataStatus;
 import cn.com.qmth.am.enums.DataType;
 import cn.com.qmth.am.enums.ImportFileName;
-import cn.com.qmth.am.enums.ModelType;
+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.FileUtil;
 import cn.com.qmth.am.utils.ImageUtil;
 
 @Service
@@ -393,7 +394,7 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         StudentScoreImageDto dto = new StudentScoreImageDto();
         dto.setStudentId(score.getStudentId());
         dto.setStudentScoreId(score.getId());
-        dto.setImage(getSlice(score, q, answerImages));
+        getSlice(score, q, answerImages, dto);
         // saveSliceImage(score, dto.getImage());
         try {
             queue.put(dto);
@@ -402,7 +403,8 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         }
     }
 
-    private byte[] getSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages) {
+    private void getSlice(StudentScoreEntity score, QuestionEntity q, Map<Integer, AnswerImageDto> answerImages,
+            StudentScoreImageDto dto) {
         List<byte[]> ret = new ArrayList<>();
         String suff = null;
         for (ImageSlice s : q.getImageSlice()) {
@@ -411,10 +413,11 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
             ret.add(ImageUtil.cutImage(sheet.getImage(), sheet.getSuff(), s.getX().intValue(), s.getY().intValue(),
                     s.getW().intValue(), s.getH().intValue()));
         }
+        dto.setSuff(suff);
         if (ret.size() > 1) {
-            return ImageUtil.joinImages(ret, suff);
+            dto.setImage(ImageUtil.joinImages(ret, suff));
         } else {
-            return ret.get(0);
+            dto.setImage(ret.get(0));
         }
     }
 
@@ -424,9 +427,10 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         if (ret != null) {
             return ret;
         }
+        ret = new AnswerImageDto();
+        String url = getImageUrl(score, q, pageIndex);
+        url = url.replace("https", "http");
         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());
@@ -590,10 +594,33 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
         this.update(wrapper);
     }
 
+    private void updateImage(Long id, String base64) {
+        UpdateWrapper<StudentScoreEntity> wrapper = new UpdateWrapper<>();
+        LambdaUpdateWrapper<StudentScoreEntity> lw = wrapper.lambda();
+        lw.set(StudentScoreEntity::getImage, base64);
+        lw.eq(StudentScoreEntity::getId, id);
+        this.update(wrapper);
+    }
+
     private String ocrDispose(StudentScoreImageDto dto, OrgInfo org) {
-        SignatureInfo signature = SignatureInfo.secret(org.getAccessKey(), org.getAccessSecret());
+        SignatureInfo signature = null;
+        if (ModelTypeBak.solar.getCode().equals(sysProperty.getOcrModel())) {
+            signature = SignatureInfo.secret(org.getAccessKey(), org.getAccessSecret());
+        }
         try {
-            return ocrApiClient.forImage(signature, OcrType.HANDWRITING, UploadFile.build("image", "", dto.getImage()));
+            if (ModelTypeBak.solar.getCode().equals(sysProperty.getOcrModel())) {
+                String base64 = FileUtil.byteToBase64(dto.getImage(), dto.getSuff());
+                String ret = ocrApiClient.forImage(signature, OcrType.HANDWRITING,
+                        UploadFile.build("image", "", dto.getImage()));
+                updateImage(dto.getStudentScoreId(), base64);
+                return ret;
+            } else {
+                String base64 = FileUtil.byteToBase64(dto.getImage(), dto.getSuff());
+                String ret = dsMarkingService.ocr(base64);
+                updateImage(dto.getStudentScoreId(), base64);
+                return ret;
+
+            }
         } catch (Exception e) {
             log.error("ocr异常", e);
             if (e instanceof RetrofitResponseError) {
@@ -698,11 +725,11 @@ public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, Studen
 
     private AutoScoreResult aiMarkingDispose(AiMarkingDto dto, OrgInfo org, AutoScoreRequest req) {
         SignatureInfo signature = null;
-        if (ModelType.solar.equals(sysProperty.getMarkingModel())) {
+        if (ModelTypeBak.solar.getCode().equals(sysProperty.getMarkingModel())) {
             signature = SignatureInfo.secret(org.getAccessKey(), org.getAccessSecret());
         }
         try {
-            if (ModelType.solar.equals(sysProperty.getMarkingModel())) {
+            if (ModelTypeBak.solar.getCode().equals(sysProperty.getMarkingModel())) {
                 return aiService.autoScore(req, signature);
             } else {
                 return dsMarkingService.autoScore(req);

+ 4 - 1
src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java

@@ -264,9 +264,12 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
     }
 
     @Override
-    public List<StudentEntity> findToDispose() {
+    public List<StudentEntity> findToDispose(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);
         return this.list(wrapper);
     }

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

@@ -36,7 +36,7 @@ public class AiMarkingJob {
         if (!sysProperty.getMarkingTaskEnable()) {
             return;
         }
-        Long examId = null;
+        Long examId = 1342L;
         List<StudentEntity> students = studentService.findToMarking(examId);
         if (CollectionUtils.isEmpty(students)) {
             return;

+ 88 - 79
src/main/java/cn/com/qmth/am/task/BuildImageJob.java

@@ -8,6 +8,8 @@ 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;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -26,84 +28,91 @@ import cn.com.qmth.am.utils.SpringContextHolder;
 
 @Service
 public class BuildImageJob {
-	@Autowired
-	private StudentService studentService;
-	@Autowired
-	private QuestionService questionService;
-	@Autowired
-	private ConcurrentService concurrentService;
-	@Autowired
-	private SysProperty sysProperty;
-	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() {
-		if(!sysProperty.getOcrTaskEnable()) {
-			return;
-		}
-		List<StudentEntity> stus = studentService.findToDispose();
-		if (CollectionUtils.isEmpty(stus)) {
-			return;
-		}
-		List<QuestionEntity> qs=questionService.list();
-		if (CollectionUtils.isEmpty(qs)) {
-			return;
-		}
-		boolean lock = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock();
-		try {
-			if (!lock) {
-				return;
-			}
-			this.dispose(stus,qs);
-		} finally {
-			if (lock) {
-				concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock();
-			}
-		}
-	}
-
-	private void dispose(List<StudentEntity> stus,List<QuestionEntity> qs) {
-		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) {
-			if(!sysProperty.getOcrTaskEnable()) {
-				return;
-			}
-			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);
-			}
-		}
-	}
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private QuestionService questionService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    private ExecutorService executor;
+
+    @PostConstruct
+    public void initExecutor() {
+        int threadCount = sysProperty.getOcrThreadCount();
+        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.getOcrTaskEnable()) {
+            return;
+        }
+
+        List<QuestionEntity> qs = questionService.list();
+        if (CollectionUtils.isEmpty(qs)) {
+            return;
+        }
+        Long examId = 1342L;
+        List<StudentEntity> stus = studentService.findToDispose(examId);
+        if (CollectionUtils.isEmpty(stus)) {
+            return;
+        }
+        boolean lock = concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().tryLock();
+        try {
+            if (!lock) {
+                return;
+            }
+            this.dispose(stus, qs);
+        } finally {
+            if (lock) {
+                concurrentService.getReadWriteLock(LockType.OCR.name()).writeLock().unlock();
+            }
+        }
+    }
+
+    private void dispose(List<StudentEntity> stus, List<QuestionEntity> qs) {
+        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) {
+            if (!sysProperty.getOcrTaskEnable()) {
+                return;
+            }
+            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);
+            }
+        }
+    }
 
 }

+ 2 - 2
src/main/java/cn/com/qmth/am/task/QuestionImportJob.java

@@ -14,11 +14,11 @@ public class QuestionImportJob {
 
     @Autowired
     private ConcurrentService concurrentService;
-    
+
     @Autowired
     private QuestionService questionService;
 
-    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 5 * 1000)
     public void doJob() {
         boolean lock = concurrentService.getReadWriteLock(LockType.QUESTION_IMPORT.name()).writeLock().tryLock();
         try {

+ 2 - 2
src/main/java/cn/com/qmth/am/task/StudentImportJob.java

@@ -14,11 +14,11 @@ public class StudentImportJob {
 
     @Autowired
     private ConcurrentService concurrentService;
-    
+
     @Autowired
     private StudentService studentService;
 
-    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 5 * 1000)
     public void doJob() {
         boolean lock = concurrentService.getReadWriteLock(LockType.STUDENT_IMPORT.name()).writeLock().tryLock();
         try {

+ 2 - 2
src/main/java/cn/com/qmth/am/task/StudentScoreImportJob.java

@@ -14,11 +14,11 @@ public class StudentScoreImportJob {
 
     @Autowired
     private ConcurrentService concurrentService;
-    
+
     @Autowired
     private StudentScoreService studentScoreService;
 
-    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 5 * 1000)
     public void doJob() {
         boolean lock = concurrentService.getReadWriteLock(LockType.MARKING_SCORE_IMPORT.name()).writeLock().tryLock();
         try {

+ 899 - 0
src/main/java/cn/com/qmth/am/utils/FileUtil.java

@@ -0,0 +1,899 @@
+package cn.com.qmth.am.utils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import sun.misc.BASE64Decoder;
+
+@SuppressWarnings("restriction")
+public class FileUtil {
+
+    public static void deleteDirectory(File dirFile) {
+        if (!dirFile.exists()) {
+            return;
+        }
+        if (dirFile.isFile()) {
+            dirFile.delete();
+        } else {
+            File[] files = dirFile.listFiles();
+            if (files != null) {
+                for (int i = 0; i < files.length; i++) {
+                    deleteDirectory(files[i]);
+                }
+            }
+            dirFile.delete();
+        }
+    }
+
+    public static boolean saveFileByUrl(String fileUrl, File file) {
+        URL url;
+        try {
+            url = new URL(fileUrl);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("文件链接错误:" + fileUrl, e);
+        }
+
+        HttpURLConnection connection;
+        try {
+            connection = (HttpURLConnection) url.openConnection();
+        } catch (IOException e) {
+            throw new RuntimeException("下载出错", e);
+        }
+
+        try (DataInputStream dataInputStream = new DataInputStream(connection.getInputStream());
+                FileOutputStream fileOutputStream = new FileOutputStream(file);
+                DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) {
+
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = dataInputStream.read(buffer)) > 0) {
+                dataOutputStream.write(buffer, 0, count);
+            }
+            fileOutputStream.flush();
+            dataOutputStream.flush();
+            return true;
+        } catch (Exception e) {
+            throw new RuntimeException("下载出错", e);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    public static byte[] base64ToByte(String base64Str) {
+        if (base64Str.contains("data:image")) {// base64图片
+            base64Str = base64Str.substring(base64Str.indexOf(",") + 1).replaceAll("%0A", "");
+            BASE64Decoder decoder = new BASE64Decoder();
+            try {
+                return decoder.decodeBuffer(base64Str);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            throw new RuntimeException("not base64 string");
+        }
+    }
+
+    public static void base64ToFile(File file, String base64Str) {
+        if (base64Str.contains("data:image")) {// base64图片
+            base64Str = base64Str.substring(base64Str.indexOf(",") + 1).replaceAll("%0A", "");
+            BASE64Decoder decoder = new BASE64Decoder();
+            try {
+                byte[] bytes = decoder.decodeBuffer(base64Str);
+                FileUtils.writeByteArrayToFile(file, bytes);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            throw new RuntimeException("not base64 string");
+        }
+    }
+
+    public static String fileToBase64(InputStream is, String suff) {
+        byte[] base64Byte;
+        try {
+            base64Byte = new byte[0];
+            byte[] imgByte;
+            imgByte = IOUtils.toByteArray(is);
+            base64Byte = Base64.encodeBase64(imgByte);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return "data:image/" + suff + ";base64," + new String(base64Byte);
+    }
+
+    public static String byteToBase64(byte[] imgByte, String suff) {
+        InputStream is = null;
+        byte[] base64Byte = new byte[0];
+        try {
+            base64Byte = Base64.encodeBase64(imgByte);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return "data:image/" + suff + ";base64," + new String(base64Byte);
+    }
+
+    public static String fileToBase64(File imgFile) {
+        String fileName = imgFile.getName();
+        String suff = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
+        InputStream is = null;
+        byte[] base64Byte;
+        try {
+            base64Byte = new byte[0];
+            byte[] imgByte;
+            is = new FileInputStream(imgFile);
+            imgByte = IOUtils.toByteArray(is);
+            base64Byte = Base64.encodeBase64(imgByte);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return "data:image/" + suff + ";base64," + new String(base64Byte);
+    }
+
+    /**
+     * 下载服务器上的文件
+     *
+     * @param filename
+     *            文件名称
+     * @param fullFilePath
+     *            文件全路径
+     * @param response
+     */
+    public static void downloadFile(String filename, String fullFilePath, HttpServletResponse response) {
+        try (InputStream in = new FileInputStream(fullFilePath); OutputStream out = response.getOutputStream();) {
+
+            // 设置编码
+            response.setCharacterEncoding("UTF-8");
+
+            // 设置Content-Disposition,名称强制为UTF-8
+            response.setHeader("Content-Disposition", "attachment;filename="
+                    + URLEncoder.encode(filename, "UTF-8").replace("%28", "(").replace("%29", ")"));
+            response.setHeader("Accept-Length", String.valueOf(in.available()));
+
+            // 设置强制下载不打开
+            response.setContentType("application/octet-stream;charset=utf-8");
+
+            // 读取目标文件,通过response将目标文件写到客户端
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = in.read(buffer)) > 0) {
+                out.write(buffer, 0, count);
+            }
+
+            response.flushBuffer();
+        } catch (IOException e) {
+            throw new RuntimeException("下载出错:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 获得文件MIME类型
+     *
+     * @param filename
+     * @return
+     */
+    public static String getContentType(String filename) {
+        String type = null;
+        Path path = Paths.get(filename);
+        try {
+            type = Files.probeContentType(path);
+        } catch (IOException e) {
+            throw new RuntimeException("出错:" + e.getMessage(), e);
+        }
+        return type;
+    }
+
+    /**
+     * 将存放在sourceFilePath目录下的源文件,打包成fileName名称的zip文件,并存放到zipFilePath路径下
+     *
+     * @param sourceFilePath
+     *            :待压缩的文件夹路径
+     * @param zipFilePath
+     *            :压缩后zip文件的存放路径
+     * @param fileName
+     *            :zip文件的名称
+     * @return
+     */
+    public static void fileToZip(String sourceFilePath, String zipFilePath, String fileName) {
+
+        File sourceFile = new File(sourceFilePath);
+        if (!sourceFile.exists()) {
+            throw new RuntimeException("待压缩的文件目录:" + sourceFilePath + "不存在.");
+        }
+
+        File zipFile = new File(zipFilePath + File.separator + fileName + ".zip");
+        if (zipFile.exists()) {
+            throw new RuntimeException(zipFilePath + "目录下存在名字为:" + fileName + ".zip" + "打包文件.");
+        }
+
+        File[] sourceFiles = sourceFile.listFiles();
+        if (null == sourceFiles || sourceFiles.length < 1) {
+            throw new RuntimeException("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(zipFile);
+                BufferedOutputStream bos = new BufferedOutputStream(fos);
+                ZipOutputStream zos = new ZipOutputStream(bos);) {
+
+            byte[] bytes = new byte[1024 * 10];
+            for (int i = 0; i < sourceFiles.length; i++) {
+                File file = sourceFiles[i];
+                if (!file.isFile()) {
+                    continue;
+                }
+
+                try (FileInputStream fis = new FileInputStream(file);
+                        BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);) {
+                    // 创建ZIP实体,并添加进压缩包
+                    String fileEncode = System.getProperty("file.encoding");
+                    String name = new String(file.getName().getBytes(), fileEncode);
+
+                    ZipEntry zipEntry = new ZipEntry(name);
+                    zos.putNextEntry(zipEntry);
+
+                    // 读取待压缩的文件并写进压缩包里
+                    int read;
+                    while ((read = bis.read(bytes, 0, 1024 * 10)) != -1) {
+                        zos.write(bytes, 0, read);
+                    }
+
+                    zos.flush();
+                } catch (Exception e) {
+                    throw new RuntimeException("出错:" + e.getMessage(), e);
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("出错:" + e.getMessage(), e);
+        }
+    }
+
+    public static void createDirectory(String downloadDirectory) {
+        File directory = new File(downloadDirectory);
+        if (!directory.exists()) {
+            directory.mkdirs();
+        } else {
+            FileUtils.deleteQuietly(directory);
+            directory.mkdirs();
+        }
+    }
+
+    public static File createZip(String sourceFilePath, String targetFilePath) throws IOException {
+        OutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            File zipfile = new File(targetFilePath);
+            zipfile.deleteOnExit();
+            fos = new FileOutputStream(zipfile);
+            zos = new ZipOutputStream(fos);
+            // zos.setEncoding("utf-8"); // Solve linxu's mess
+            writeZip(new File(sourceFilePath), null, zos);
+            return zipfile;
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    private static void writeZip(File file, String parentPath, ZipOutputStream zos) throws IOException {
+        if (file.exists()) {
+            ZipEntry ze = null;
+            if (file.isDirectory()) {// Processing folder
+                if (parentPath == null) {
+                    parentPath = "";
+                } else {
+                    parentPath += file.getName() + "/";
+                }
+                File[] files = file.listFiles();
+                if (files != null) {
+                    for (File f : files) {
+                        writeZip(f, parentPath, zos);
+                    }
+                } else { // An empty directory creates the current directory
+                    try {
+                        ze = new ZipEntry(parentPath);
+                        // ze.setUnixMode(755);// Solve Linux mess file Settings
+                        // 644 directory Settings 755
+                        zos.putNextEntry(ze);
+
+                        zos.flush();
+                    } finally {
+                        if (zos != null) {
+                            zos.closeEntry();
+                        }
+                    }
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ze = new ZipEntry(parentPath + file.getName());
+                    // ze.setUnixMode(644);// Solve Linux mess file Settings 644
+                    // directory Settings 755
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                    if (zos != null) {
+                        zos.closeEntry();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 文件压缩
+     *
+     * @param target
+     *            目录或文件
+     * @param zipFile
+     *            压缩后的ZIP文件
+     */
+    public static boolean doZip(File target, File zipFile) {
+        if (target == null || !target.exists()) {
+            throw new RuntimeException("目录或文件不能为空!");
+        }
+
+        if (zipFile == null) {
+            throw new RuntimeException("待压缩的文件不能为空!");
+        }
+
+        try (OutputStream outStream = new FileOutputStream(zipFile);
+                ZipOutputStream zipOutStream = new ZipOutputStream(outStream, Charset.forName("UTF-8"));) {
+            if (!zipFile.exists()) {
+                boolean ok = zipFile.createNewFile();
+                if (!ok) {
+                    throw new RuntimeException("压缩的文件创建失败!");
+                }
+            }
+
+            if (target.isDirectory()) {
+                File[] files = target.listFiles();
+                if (files.length == 0) {
+                    throw new RuntimeException("文件夹内未找到任何文件!");
+                }
+
+                for (File file : files) {
+                    doZip(zipOutStream, file, null);
+                }
+            } else {
+                doZip(zipOutStream, target, null);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return true;
+    }
+
+    private static void doZip(ZipOutputStream zipOutStream, File target, String parentDir) throws IOException {
+        // log.info("Zip:" + parentDir);
+        if (parentDir == null) {
+            parentDir = "";
+        }
+
+        if (!"".equals(parentDir) && !parentDir.endsWith(File.separator)) {
+            parentDir += File.separator;
+        }
+
+        if (target.isDirectory()) {
+            File[] files = target.listFiles();
+            if (files.length > 0) {
+                for (File file : files) {
+                    doZip(zipOutStream, file, parentDir + target.getName());
+                }
+            } else {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                zipOutStream.closeEntry();
+            }
+        } else {
+            try (InputStream is = new FileInputStream(target);) {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                int len;
+                byte[] bytes = new byte[1024];
+                while ((len = is.read(bytes)) > 0) {
+                    zipOutStream.write(bytes, 0, len);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            zipOutStream.closeEntry();
+        }
+    }
+
+    /**
+     * 解压文件
+     *
+     * @param targetDir
+     *            解压目录
+     * @param zipFile
+     *            待解压的ZIP文件
+     */
+    public static List<File> unZip(File targetDir, File zipFile) {
+        if (targetDir == null) {
+            throw new RuntimeException("解压目录不能为空!");
+        }
+
+        if (zipFile == null) {
+            throw new RuntimeException("待解压的文件不能为空!");
+        }
+
+        if (!zipFile.exists()) {
+            throw new RuntimeException("待解压的文件不存在!" + zipFile.getAbsolutePath());
+        }
+
+        String zipName = zipFile.getName().toLowerCase();
+        if (zipFile.isDirectory() || zipName.indexOf(".zip") < 0) {
+            throw new RuntimeException("待解压的文件格式错误!");
+        }
+
+        if (!targetDir.exists()) {
+            targetDir.mkdir();
+        }
+
+        List<File> result = new LinkedList<>();
+
+        try (ZipFile zip = new ZipFile(zipFile, Charset.forName("GBK"));) {
+
+            @SuppressWarnings("rawtypes")
+            Enumeration entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+
+                // Linux中需要替换掉路径的反斜杠
+                String entryName = (File.separator + entry.getName()).replaceAll("\\\\", "/");
+
+                String filePath = targetDir.getAbsolutePath() + entryName;
+                File target = new File(filePath);
+                if (entry.isDirectory()) {
+                    target.mkdirs();
+                } else {
+                    File dir = target.getParentFile();
+                    if (!dir.exists()) {
+                        dir.mkdirs();
+                    }
+
+                    try (OutputStream os = new FileOutputStream(target); InputStream is = zip.getInputStream(entry);) {
+                        IOUtils.copy(is, os);
+                        os.flush();
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                    result.add(target);
+                }
+            }
+
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return result;
+    }
+
+    public static void unZipFiles(String zipFileName, String targetDirName) throws IOException {
+        if (!targetDirName.endsWith(File.separator)) {
+            targetDirName = targetDirName + File.separator;
+        }
+        ZipFile zipFile = null;
+        try {
+            // Create the ZipFile object from the ZIP file
+            zipFile = new ZipFile(zipFileName);
+            ZipEntry entry = null;
+            String entryName = null;
+            String descFileDir = null;
+            byte[] buf = new byte[4096];
+            int readByte = 0;
+            // Gets all entry in the ZIP file
+            @SuppressWarnings("rawtypes")
+            Enumeration enums = zipFile.entries();
+            // Go through all entry
+            while (enums.hasMoreElements()) {
+                entry = (ZipEntry) enums.nextElement();
+                // Get the name entry
+                entryName = entry.getName();
+                descFileDir = targetDirName + entryName;
+                if (entry.isDirectory()) {
+                    // If entry is a directory, create the directory
+                    // entry.setUnixMode(755);// Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).mkdirs();
+                    continue;
+                } else {
+                    // If entry is a file, the parent directory is created
+                    // entry.setUnixMode(644);//Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).getParentFile().mkdirs();
+                }
+                File file = new File(descFileDir);
+                // Open the file output stream
+                OutputStream os = null;
+                // Open the entry input stream from the ZipFile object
+                InputStream is = null;
+                try {
+                    os = new FileOutputStream(file);
+                    is = zipFile.getInputStream(entry);
+                    while ((readByte = is.read(buf)) != -1) {
+                        os.write(buf, 0, readByte);
+                    }
+                } finally {
+                    if (os != null)
+                        os.close();
+                    if (is != null)
+                        is.close();
+                }
+            }
+        } finally {
+            if (zipFile != null)
+                zipFile.close();
+        }
+    }
+
+    public static void deleteFolder(String path) {
+
+        File file = new File(path);
+        if (file.exists()) {
+            if (file.isFile()) {
+                deleteFile(path);
+            } else {
+                deleteDirectory(path);
+            }
+        }
+    }
+
+    public static void deleteFile(String path) {
+        File file = new File(path);
+        if (file.isFile() && file.exists()) {
+            file.delete();
+        }
+    }
+
+    public static void deleteDirectory(String path) {
+        if (!path.endsWith(File.separator)) {
+            path = path + File.separator;
+        }
+        File dirFile = new File(path);
+        if (!dirFile.exists() || !dirFile.isDirectory()) {
+            return;
+        }
+        File[] files = dirFile.listFiles();
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
+                if (files[i].isFile()) {
+                    deleteFile(files[i].getAbsolutePath());
+                } else {
+                    deleteDirectory(files[i].getAbsolutePath());
+                }
+            }
+        }
+
+        dirFile.delete();
+    }
+
+    public static File cutFile(String sourcePath, String targetPath, int n) {
+        File file = new File(sourcePath);
+        File newFile = new File(targetPath);
+
+        try (FileInputStream fis = new FileInputStream(file);
+                InputStream is = new BufferedInputStream(fis);
+                OutputStream os = new FileOutputStream(newFile);) {
+
+            // 从n个字节开始读,注意中文是两个字节
+            fis.skip(n);
+
+            // 指定文件位置读取的文件流,存入新文件
+            byte buffer[] = new byte[4 * 1024];
+            int len;
+            while ((len = is.read(buffer)) != -1) {
+                os.write(buffer, 0, len);
+            }
+
+            os.flush();
+            return newFile;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 读取文件前面部分N个字节
+     *
+     * @param path
+     *            文件路径
+     * @param headerSize
+     *            头信息字节数(必须2的倍数)
+     * @param signSize
+     *            签名信息字节数
+     * @return
+     */
+    public static String[] readFileHeader(String path, int headerSize, int signSize) {
+        int n = headerSize / 2;
+        String[] codes = new String[n + 1];
+
+        File file = new File(path);
+        try (FileInputStream fis = new FileInputStream(file); DataInputStream ois = new DataInputStream(fis);) {
+            // 分n次读取文件(n * 2)个字节
+            for (int i = 0; i < n; i++) {
+                codes[i] = String.valueOf(ois.readShort());
+            }
+
+            if (signSize > 0) {
+                StringBuilder ss = new StringBuilder();
+                for (int i = 0; i < signSize; i++) {
+                    ss.append((char) ois.readByte());
+                }
+                codes[2] = ss.toString();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return codes;
+    }
+
+    /**
+     * 读取文件内容
+     *
+     * @param file
+     * @return
+     */
+    @SuppressWarnings("deprecation")
+    public static String readFileContent(File file) {
+        StringBuilder content = new StringBuilder();
+        InputStreamReader streamReader = null;
+        BufferedReader bufferedReader = null;
+        try {
+            String encoding = "UTF-8";
+            if (file.exists() && file.isFile()) {
+                streamReader = new InputStreamReader(new FileInputStream(file), encoding);
+                bufferedReader = new BufferedReader(streamReader);
+                String line;
+                while ((line = bufferedReader.readLine()) != null) {
+                    content.append(line);
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(streamReader);
+            IOUtils.closeQuietly(bufferedReader);
+        }
+        return content.toString();
+    }
+
+    @SuppressWarnings("deprecation")
+    public static String readFileContent(InputStream in) {
+        StringBuilder content = new StringBuilder();
+        InputStreamReader streamReader = null;
+        BufferedReader bufferedReader = null;
+        try {
+            String encoding = "UTF-8";
+            streamReader = new InputStreamReader(in, encoding);
+            bufferedReader = new BufferedReader(streamReader);
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                content.append(line);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(in);
+            IOUtils.closeQuietly(streamReader);
+            IOUtils.closeQuietly(bufferedReader);
+        }
+        return content.toString();
+    }
+
+    /**
+     * 生成日期目录路径
+     */
+    public static String generateDateDir() {
+        return "/" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "/";
+    }
+
+    public static String generateFileName() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String generateDateName() {
+        return new SimpleDateFormat("yyMMddHHmmss").format(new Date());
+    }
+
+    /**
+     * 获取文件后缀名(包含".")
+     */
+    public static String getFileSuffix(String fileName) {
+        if (fileName == null) {
+            return "";
+        }
+        int index = fileName.lastIndexOf(".");
+        if (index > -1) {
+            return fileName.substring(index).toLowerCase();
+        }
+        return "";
+    }
+
+    /**
+     * 获取无后缀的文件名
+     *
+     * @param fileName
+     *            示例:../xxx/abc.xx
+     * @return 示例:../xxx/abc
+     */
+    public static String getFilePathName(String fileName) {
+        if (fileName != null && fileName.length() > 0) {
+            int index = fileName.lastIndexOf(".");
+            if (index != -1) {
+                return fileName.substring(0, index);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 创建文件目录
+     */
+    public static boolean makeDirs(String path) {
+        if (path == null || "".equals(path)) {
+            return false;
+        }
+        File folder = new File(path);
+        if (!folder.exists()) {
+            return folder.mkdirs();
+        }
+        return true;
+    }
+
+    public static void dozip(File target, File[] files) {
+        // 压缩后的 zip 文件名
+        if (target == null || !target.exists()) {
+            throw new RuntimeException("目录或文件不能为空!");
+        }
+        try {
+            // 创建 ZipOutputStream 对象
+            FileOutputStream fos = new FileOutputStream(target);
+            ZipOutputStream zipOut = new ZipOutputStream(fos, Charset.forName("UTF-8"));
+
+            // 循环每个文件并将其添加到压缩包
+            for (File vo : files) {
+                FileInputStream in = new FileInputStream(vo);
+                try {
+                    ZipEntry zipEntry = new ZipEntry(vo.getName());
+                    zipOut.putNextEntry(zipEntry);
+                    // 将文件内容写入 ZipOutputStream
+                    byte[] bytes = new byte[1024];
+                    int length;
+                    while ((length = in.read(bytes)) >= 0) {
+                        zipOut.write(bytes, 0, length);
+                    }
+                } finally {
+                    // 关闭当前文件的输入流
+                    in.close();
+                }
+            }
+
+            // 关闭 ZipOutputStream
+            zipOut.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void dozip(File target, List<File> files) {
+        // 压缩后的 zip 文件名
+        if (target == null || !target.exists()) {
+            throw new RuntimeException("目录或文件不能为空!");
+        }
+        try {
+            // 创建 ZipOutputStream 对象
+            FileOutputStream fos = new FileOutputStream(target);
+            ZipOutputStream zipOut = new ZipOutputStream(fos, Charset.forName("UTF-8"));
+
+            // 循环每个文件并将其添加到压缩包
+            for (File vo : files) {
+                FileInputStream in = new FileInputStream(vo);
+                try {
+                    ZipEntry zipEntry = new ZipEntry(vo.getName());
+                    zipOut.putNextEntry(zipEntry);
+                    // 将文件内容写入 ZipOutputStream
+                    byte[] bytes = new byte[1024];
+                    int length;
+                    while ((length = in.read(bytes)) >= 0) {
+                        zipOut.write(bytes, 0, length);
+                    }
+                } finally {
+                    // 关闭当前文件的输入流
+                    in.close();
+                }
+            }
+
+            // 关闭 ZipOutputStream
+            zipOut.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    public static void donwLoadFile(HttpServletResponse response, String fileName, InputStream in) {
+        OutputStream out = null;
+        try {
+            fileName = URLEncoder.encode(fileName, "UTF-8");
+            response.reset();
+            response.setHeader("Content-Disposition", "inline; filename=" + fileName);
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            out = new BufferedOutputStream(response.getOutputStream());
+            IOUtils.copy(in, out);
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(in);
+        }
+    }
+}

+ 10 - 6
src/main/resources/application.properties

@@ -13,11 +13,12 @@ com.qmth.mybatis.block-attack=false
 #
 db.host=localhost
 db.port=3306
-db.database=ai_marking_ds_v3
+db.database=ai_marking_ocr
 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
-
+com.qmth.datasource.max-pool-size=200
+com.qmth.datasource.min-idle=10
 #
 # ********** sys config **********
 #
@@ -37,12 +38,15 @@ com.qmth.ocr.server=https://solar.qmth.com.cn
 com.qmth.solar.access-key=7bbdc11570bc474dbf50e0d4a5dff328
 com.qmth.solar.access-secret=IOodRvbp2LspJTHOScgB7Yx8MRloMpyl
 
-am.ocr-task.enable=false
+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-model=ds_v3
-am.marking-server=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
-am.marking-key=sk-75087dd712b84173a984b19393627ac6
+am.marking-ocr-model=qwen2.5-vl-7b-instruct
+am.marking-ocr-server=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
+am.marking-ocr-key=sk-75087dd712b84173a984b19393627ac6
+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
 ##################################setting##########################################