Sfoglia il codice sorgente

创建/修改题卡卡格式

xiatian 1 anno fa
parent
commit
6312416cff
20 ha cambiato i file con 844 aggiunte e 26 eliminazioni
  1. 47 6
      distributed-print/src/main/java/com/qmth/distributed/print/api/mark/ScanAnswerCardController.java
  2. 29 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerArea.java
  3. 101 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerCardSaveDomain.java
  4. 28 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerCardSaveVo.java
  5. 110 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardFile.java
  6. 86 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardPage.java
  7. 14 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardPageWrapper.java
  8. 59 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/FillArea.java
  9. 40 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/FillItem.java
  10. 27 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/Locator.java
  11. 41 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/SliceConfig.java
  12. 20 9
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/ScanAnswerCard.java
  13. 51 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/FileType.java
  14. 25 11
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/LockType.java
  15. 11 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/FileService.java
  16. 2 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkPaperService.java
  17. 3 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/ScanAnswerCardService.java
  18. 47 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/FileServiceImpl.java
  19. 8 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java
  20. 95 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/ScanAnswerCardServiceImpl.java

+ 47 - 6
distributed-print/src/main/java/com/qmth/distributed/print/api/mark/ScanAnswerCardController.java

@@ -1,6 +1,5 @@
 package com.qmth.distributed.print.api.mark;
 
-
 import java.util.List;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -8,10 +7,20 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
 
+import com.alibaba.fastjson.JSONObject;
 import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.ReentrantException;
+import com.qmth.distributed.print.business.service.BasicExamService;
 import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.BasicExam;
+import com.qmth.teachcloud.mark.bean.answercard.AnswerCardSaveDomain;
+import com.qmth.teachcloud.mark.bean.answercard.AnswerCardSaveVo;
 import com.qmth.teachcloud.mark.bean.answercard.AnswerCardVo;
+import com.qmth.teachcloud.mark.enums.LockType;
 import com.qmth.teachcloud.mark.service.ScanAnswerCardService;
 
 import io.swagger.annotations.Api;
@@ -29,13 +38,45 @@ import io.swagger.annotations.ApiOperation;
 @RestController
 @RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + SystemConstant.PREFIX_URL_SCAN + "/answer/card")
 public class ScanAnswerCardController {
-	
+
 	@Autowired
 	private ScanAnswerCardService scanAnswerCardService;
+
+	@Autowired
+	private ConcurrentService concurrentService;
 	
+	@Autowired
+	private BasicExamService basicExamService;
+
 	@ApiOperation(value = "答题卡卡格式列表")
-    @RequestMapping(value = "list", method = RequestMethod.POST)
-    public List<AnswerCardVo> cardList(@RequestParam Long examId,@RequestParam(required = false) String coursePaperId) {
-        return scanAnswerCardService.cardList(examId,coursePaperId);
-    }
+	@RequestMapping(value = "list", method = RequestMethod.POST)
+	public List<AnswerCardVo> cardList(@RequestParam Long examId,
+			@RequestParam(required = false) String coursePaperId) {
+		return scanAnswerCardService.cardList(examId, coursePaperId);
+	}
+
+	@ApiOperation(value = "创建/修改题卡卡格式")
+	@RequestMapping(value = "save", method = RequestMethod.POST)
+	public AnswerCardSaveVo cardSave(@RequestParam Integer dpi, @RequestParam Long examId,
+			@RequestParam String coursePaperId, @RequestParam String md5,
+			@RequestParam(required = false) Integer number, @RequestParam(required = false) String remark,
+			@RequestParam Integer paperCount, @RequestParam MultipartFile file) {
+		BasicExam exam=basicExamService.getById(examId);
+		if(exam==null) {
+			throw new ParameterException("未找到考试信息");
+		}
+		AnswerCardSaveDomain domain = new AnswerCardSaveDomain(number, examId, coursePaperId, remark, paperCount, file,
+				md5, dpi);
+		if (concurrentService.getLock(LockType.CARD_SAVE + "-" + examId).tryLock()) {
+			try {
+				scanAnswerCardService.cardSave(domain);
+				AnswerCardSaveVo vo = new AnswerCardSaveVo(domain.getNumber(), System.currentTimeMillis());
+				return vo;
+			} finally {
+				concurrentService.getLock(LockType.CARD_SAVE + "-" + examId).unlock();
+			}
+		} else {
+			throw new ReentrantException("正在保存卡格式,请稍后再试");
+		}
+	}
 }

+ 29 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerArea.java

@@ -0,0 +1,29 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AnswerArea {
+
+    @JsonProperty("main_number")
+    private Integer mainNumber;
+
+    private double[] area;
+
+    public Integer getMainNumber() {
+        return mainNumber;
+    }
+
+    public void setMainNumber(Integer mainNumber) {
+        this.mainNumber = mainNumber;
+    }
+
+    public double[] getArea() {
+        return area;
+    }
+
+    public void setArea(double[] area) {
+        this.area = area;
+    }
+}

+ 101 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerCardSaveDomain.java

@@ -0,0 +1,101 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import org.springframework.web.multipart.MultipartFile;
+
+
+public class AnswerCardSaveDomain {
+
+    private Integer number;
+    private Long examId;
+    private String coursePaperId;
+    private String remark;
+
+    private Integer paperCount;
+    private MultipartFile file;
+
+    private String md5;
+    
+    private Integer dpi;
+    
+    
+
+	public AnswerCardSaveDomain(Integer number, Long examId, String coursePaperId, String remark, Integer paperCount,
+			MultipartFile file, String md5, Integer dpi) {
+		super();
+		this.number = number;
+		this.examId = examId;
+		this.coursePaperId = coursePaperId;
+		this.remark = remark;
+		this.paperCount = paperCount;
+		this.file = file;
+		this.md5 = md5;
+		this.dpi = dpi;
+	}
+
+	public Integer getNumber() {
+		return number;
+	}
+
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public String getCoursePaperId() {
+		return coursePaperId;
+	}
+
+	public void setCoursePaperId(String coursePaperId) {
+		this.coursePaperId = coursePaperId;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Integer getPaperCount() {
+		return paperCount;
+	}
+
+	public void setPaperCount(Integer paperCount) {
+		this.paperCount = paperCount;
+	}
+
+	public MultipartFile getFile() {
+		return file;
+	}
+
+	public void setFile(MultipartFile file) {
+		this.file = file;
+	}
+
+	public String getMd5() {
+		return md5;
+	}
+
+	public void setMd5(String md5) {
+		this.md5 = md5;
+	}
+
+	public Integer getDpi() {
+		return dpi;
+	}
+
+	public void setDpi(Integer dpi) {
+		this.dpi = dpi;
+	}
+    
+
+
+}

+ 28 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/AnswerCardSaveVo.java

@@ -0,0 +1,28 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+public class AnswerCardSaveVo {
+
+    private Integer number;
+    private Long updateTime;
+    
+    
+	public AnswerCardSaveVo(Integer number, Long updateTime) {
+		super();
+		this.number = number;
+		this.updateTime = updateTime;
+	}
+	public Integer getNumber() {
+		return number;
+	}
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+	public Long getUpdateTime() {
+		return updateTime;
+	}
+	public void setUpdateTime(Long updateTime) {
+		this.updateTime = updateTime;
+	}
+
+
+}

+ 110 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardFile.java

@@ -0,0 +1,110 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import java.awt.Image;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.qmth.boot.core.exception.ParameterException;
+
+/**
+ * 卡格式文件内容结构
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CardFile {
+
+    private String version;
+
+    private boolean adapted;
+
+    private List<CardPageWrapper> pages;
+
+    @JsonIgnore
+    private SliceConfig sliceConfig;
+    
+    @JsonIgnore
+    private List<String> sliceName;
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public boolean isAdapted() {
+        return adapted;
+    }
+
+    public void setAdapted(boolean adapted) {
+        this.adapted = adapted;
+    }
+
+    public List<CardPageWrapper> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<CardPageWrapper> pages) {
+        this.pages = pages;
+    }
+
+    /**
+     * 解析卡格式文件中的裁切图坐标,用于云阅卷同步
+     *
+     * @return
+     */
+    public SliceConfig getSliceConfig() throws IOException {
+        if (sliceConfig == null) {
+            sliceConfig = new SliceConfig();
+            if (pages != null) {
+                int pageNumber = 0;
+                for (CardPageWrapper pageWrapper : pages) {
+                    pageNumber++;
+                    CardPage page = pageWrapper.getExchange();
+                    //遮盖模式
+                    if (page.getInfoArea() != null && page.getInfoArea().length > 0) {
+                        sliceConfig.addConfig(pageNumber, 0, 0, 0, 0);
+                    }
+                    //裁切模式
+                    else if (page.getAnswerArea() != null && !page.getAnswerArea().isEmpty()) {
+                        //获取卡格式图片原始尺寸
+                        //此处图片base64编码特殊,需要采用Mime标准
+                        Image image = ImageIO
+                                .read(new ByteArrayInputStream(Base64.getMimeDecoder().decode(page.getPageImage())));
+                        int width = image.getWidth(null);
+                        int height = image.getHeight(null);
+                        for (AnswerArea area : page.getAnswerArea()) {
+                            sliceConfig.addConfig(pageNumber, (int) (area.getArea()[0] * width),
+                                    (int) (area.getArea()[1] * height), (int) (area.getArea()[2] * width),
+                                    (int) (area.getArea()[3] * height));
+                        }
+                    }
+                }
+            }
+        }
+        return sliceConfig;
+    }
+    
+    public List<String> getSliceName() throws IOException {
+        List<String> sliceName=new LinkedList<String>();
+        if (pages != null) {
+            for (CardPageWrapper pageWrapper : pages) {
+                CardPage page = pageWrapper.getExchange();
+                for (AnswerArea area : page.getAnswerArea()) {
+                    if(area.getMainNumber()==null) {
+                        throw new ParameterException("CET模式下裁切图必须指定大题号");
+                    }
+                    sliceName.add(area.getMainNumber().toString());
+                }
+            }
+        }
+        return sliceName;
+    }
+}

+ 86 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardPage.java

@@ -0,0 +1,86 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CardPage {
+
+    @JsonProperty("card_type")
+    private int cardType;
+
+    @JsonProperty("page_size")
+    private String pageSize;
+
+    @JsonProperty("page_image")
+    private String pageImage;
+
+    private Locator locator;
+
+    @JsonProperty("info_area")
+    private double[][] infoArea;
+
+    @JsonProperty("answer_area")
+    private List<AnswerArea> answerArea;
+
+    @JsonProperty("fill_area")
+    private List<FillArea> fillArea;
+
+    public int getCardType() {
+        return cardType;
+    }
+
+    public void setCardType(int cardType) {
+        this.cardType = cardType;
+    }
+
+    public String getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(String pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public String getPageImage() {
+        return pageImage;
+    }
+
+    public void setPageImage(String pageImage) {
+        this.pageImage = pageImage;
+    }
+
+    public Locator getLocator() {
+        return locator;
+    }
+
+    public void setLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    public double[][] getInfoArea() {
+        return infoArea;
+    }
+
+    public void setInfoArea(double[][] infoArea) {
+        this.infoArea = infoArea;
+    }
+
+    public List<AnswerArea> getAnswerArea() {
+        return answerArea;
+    }
+
+    public void setAnswerArea(List<AnswerArea> answerArea) {
+        this.answerArea = answerArea;
+    }
+
+    public List<FillArea> getFillArea() {
+        return fillArea;
+    }
+
+    public void setFillArea(List<FillArea> fillArea) {
+        this.fillArea = fillArea;
+    }
+}

+ 14 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/CardPageWrapper.java

@@ -0,0 +1,14 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+public class CardPageWrapper {
+
+    private CardPage exchange;
+
+    public CardPage getExchange() {
+        return exchange;
+    }
+
+    public void setExchange(CardPage exchange) {
+        this.exchange = exchange;
+    }
+}

+ 59 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/FillArea.java

@@ -0,0 +1,59 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class FillArea {
+
+    private String field;
+
+    private int index;
+
+    private boolean single;
+
+    private boolean horizontal;
+
+    private List<FillItem> items;
+
+    public String getField() {
+        return field;
+    }
+
+    public void setField(String field) {
+        this.field = field;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public boolean isSingle() {
+        return single;
+    }
+
+    public void setSingle(boolean single) {
+        this.single = single;
+    }
+
+    public boolean isHorizontal() {
+        return horizontal;
+    }
+
+    public void setHorizontal(boolean horizontal) {
+        this.horizontal = horizontal;
+    }
+
+    public List<FillItem> getItems() {
+        return items;
+    }
+
+    public void setItems(List<FillItem> items) {
+        this.items = items;
+    }
+}

+ 40 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/FillItem.java

@@ -0,0 +1,40 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class FillItem {
+
+    @JsonProperty("main_number")
+    private Integer mainNumber;
+
+    @JsonProperty("sub_number")
+    private Integer subNumber;
+
+    private double[][] options;
+
+    public Integer getMainNumber() {
+        return mainNumber;
+    }
+
+    public void setMainNumber(Integer mainNumber) {
+        this.mainNumber = mainNumber;
+    }
+
+    public Integer getSubNumber() {
+        return subNumber;
+    }
+
+    public void setSubNumber(Integer subNumber) {
+        this.subNumber = subNumber;
+    }
+
+    public double[][] getOptions() {
+        return options;
+    }
+
+    public void setOptions(double[][] options) {
+        this.options = options;
+    }
+}

+ 27 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/Locator.java

@@ -0,0 +1,27 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Locator {
+
+    private double[][] top;
+
+    private double[][] bottom;
+
+    public double[][] getTop() {
+        return top;
+    }
+
+    public void setTop(double[][] top) {
+        this.top = top;
+    }
+
+    public double[][] getBottom() {
+        return bottom;
+    }
+
+    public void setBottom(double[][] bottom) {
+        this.bottom = bottom;
+    }
+}

+ 41 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/bean/answercard/SliceConfig.java

@@ -0,0 +1,41 @@
+package com.qmth.teachcloud.mark.bean.answercard;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SliceConfig {
+
+    private List<ConfigItem> items;
+
+    public SliceConfig() {
+        this.items = new LinkedList<>();
+    }
+
+    public void addConfig(int number, int left, int top, int width, int height) {
+        ConfigItem item = new ConfigItem();
+        item.number = number;
+        item.left = left;
+        item.top = top;
+        item.width = width;
+        item.height = height;
+        this.items.add(item);
+    }
+
+    @Override
+    public String toString() {
+        return StringUtils.join(items, ",");
+    }
+
+    static class ConfigItem {
+
+        int number, left, top, width, height;
+
+        @Override
+        public String toString() {
+            return StringUtils.join(Arrays.asList(number, left, top, width, height), ":");
+        }
+    }
+}

+ 20 - 9
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/ScanAnswerCard.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.teachcloud.mark.enums.CardSource;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -37,9 +38,10 @@ public class ScanAnswerCard implements Serializable {
 
     @ApiModelProperty(value = "序号")
     private Integer number;
-
+    @ApiModelProperty("卡格式文件存储路径")
+    private String uri;
     @ApiModelProperty(value = "来源")
-    private String source;
+    private CardSource source;
 
     @ApiModelProperty(value = "文件md5")
     private String md5;
@@ -54,7 +56,7 @@ public class ScanAnswerCard implements Serializable {
     private Long updateTime;
 
     @ApiModelProperty(value = "是否需要适配")
-    private Integer needAdapte;
+    private Boolean needAdapte;
 
     private Integer paperCount;
 
@@ -102,11 +104,11 @@ public class ScanAnswerCard implements Serializable {
     public void setNumber(Integer number) {
         this.number = number;
     }
-    public String getSource() {
+    public CardSource getSource() {
         return source;
     }
 
-    public void setSource(String source) {
+    public void setSource(CardSource source) {
         this.source = source;
     }
     public String getMd5() {
@@ -137,11 +139,11 @@ public class ScanAnswerCard implements Serializable {
     public void setUpdateTime(Long updateTime) {
         this.updateTime = updateTime;
     }
-    public Integer getNeedAdapte() {
+    public Boolean getNeedAdapte() {
         return needAdapte;
     }
 
-    public void setNeedAdapte(Integer needAdapte) {
+    public void setNeedAdapte(Boolean needAdapte) {
         this.needAdapte = needAdapte;
     }
     public Integer getPaperCount() {
@@ -194,14 +196,23 @@ public class ScanAnswerCard implements Serializable {
         this.adapteUri = adapteUri;
     }
 
-    @Override
+
+	public String getUri() {
+		return uri;
+	}
+
+	public void setUri(String uri) {
+		this.uri = uri;
+	}
+
+	@Override
     public String toString() {
         return "ScanAnswerCard{" +
             "examId=" + examId +
             ", paperNumber=" + paperNumber +
             ", coursePaperId=" + coursePaperId +
             ", number=" + number +
-            ", source=" + source +
+            ", source=" + source.name() +
             ", md5=" + md5 +
             ", parameter=" + parameter +
             ", remark=" + remark +

+ 51 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/FileType.java

@@ -0,0 +1,51 @@
+package com.qmth.teachcloud.mark.enums;
+
+/**
+ * 所有管理的文件类型
+ */
+public enum FileType {
+
+//    SHEET("原图", "%d/answer/%d/%s/%d-%d.%s"),
+//    SLICE("裁切图", "%d/answer/%d/%s/%d-%d-%d.%s"),
+//    PACKAGE("签到表", "%d/card/package.%s"),
+    CARD("题卡", "%d/card/answer/%d.%s"),
+//    ADMIN_SLICE("管理员上传裁切图", "%d/upload/%s/%d-%d-%d/%s.%s"),
+//    ADMIN_SHEET("管理员上传裁原图", "%d/upload/%s/%d-%d/%s.%s"),
+//    ADAPTE_FILE("适配题卡", "%d/card/answer/%d/%s.%s"),
+    ;
+
+    private String name;
+
+    private String pattern;
+
+    private FileType(String name, String pattern) {
+        this.name = name;
+        this.pattern = pattern;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public boolean equals(String type) {
+        return toString().equalsIgnoreCase(type);
+    }
+
+    public static FileType findByText(String text) {
+        for (FileType type : values()) {
+            if (type.equals(text)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    public String getPath(Object... param) {
+        return String.format(pattern, param);
+    }
+
+}

+ 25 - 11
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/LockType.java

@@ -1,19 +1,33 @@
 package com.qmth.teachcloud.mark.enums;
 
 public enum LockType {
-    EXAM_SUBJECT("exam_subject"), GROUP("group"), GROUP_DELETE("group_delete"), STUDENT("student"), MARK_USER_GROUP("mark_user_group"), MARKER_RESET(
-            "marker_reset"), USER("user"), FORMAL_LIBRARY("formal_library"), TRIAL_LIBRARY("trial_library"), ARBITRATE(
-            "arbitrate"), BATCH_QUALITY("batch_quality"), SCORE_CALCULATE("score_calculate"), GROUP_LIBRARY(
-            "group_library"), DATA_SYNC("data_sync"), SCHOOL("school"), QUESTION_DELETE("question_delete");
+	EXAM_SUBJECT("exam_subject"), 
+	GROUP("group"), 
+	GROUP_DELETE("group_delete"), 
+	STUDENT("student"),
+	MARK_USER_GROUP("mark_user_group"), 
+	MARKER_RESET("marker_reset"), 
+	USER("user"), 
+	FORMAL_LIBRARY("formal_library"),
+	TRIAL_LIBRARY("trial_library"), 
+	ARBITRATE("arbitrate"), 
+	BATCH_QUALITY("batch_quality"),
+	SCORE_CALCULATE("score_calculate"), 
+	GROUP_LIBRARY("group_library"), 
+	DATA_SYNC("data_sync"), 
+	SCHOOL("school"),
+	QUESTION_DELETE("question_delete"),
+	CARD_SAVE("card_save"),
+	;
 
-    private String name;
+	private String name;
 
-    private LockType(String name) {
-        this.name = name;
-    }
+	private LockType(String name) {
+		this.name = name;
+	}
 
-    public String getName() {
-        return name;
-    }
+	public String getName() {
+		return name;
+	}
 
 }

+ 11 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/FileService.java

@@ -0,0 +1,11 @@
+package com.qmth.teachcloud.mark.service;
+
+import java.io.InputStream;
+
+public interface FileService {
+
+	String getAnswerCardUri(Long examId, Integer number);
+
+	String uploadAnswerCard(InputStream in, String md5, Long examId, Integer number);
+
+}

+ 2 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkPaperService.java

@@ -39,4 +39,6 @@ public interface MarkPaperService extends IService<MarkPaper> {
     void updateUploadCount(Long examId, String paperNumber, int countUploaded);
 
     IPage<MarkPaperPackageDto> listPackage(Long examId, String paperNumber, String packageCode, Integer pageNumber, Integer pageSize);
+
+	MarkPaper getByExamIdAndCoursePaperId(Long examId, String coursePaperId);
 }

+ 3 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/ScanAnswerCardService.java

@@ -1,5 +1,6 @@
 package com.qmth.teachcloud.mark.service;
 
+import com.qmth.teachcloud.mark.bean.answercard.AnswerCardSaveDomain;
 import com.qmth.teachcloud.mark.bean.answercard.AnswerCardVo;
 import com.qmth.teachcloud.mark.entity.ScanAnswerCard;
 
@@ -21,4 +22,6 @@ public interface ScanAnswerCardService extends IService<ScanAnswerCard> {
 
 	List<AnswerCardVo> cardList(Long examId, String coursePaperId);
 
+	void cardSave(AnswerCardSaveDomain domain);
+
 }

+ 47 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/FileServiceImpl.java

@@ -0,0 +1,47 @@
+package com.qmth.teachcloud.mark.service.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.aliyuncs.http.FormatType;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.teachcloud.mark.enums.FileType;
+import com.qmth.teachcloud.mark.service.FileService;
+
+@Service
+public class FileServiceImpl implements FileService {
+
+    @Autowired
+    private FileStore fileStore;
+
+    @Override
+    public String getAnswerCardUri(Long examId, Integer number) {
+        return FileType.CARD.getPath(examId, number, FormatType.JSON.name().toLowerCase());
+    }
+
+
+    @Override
+    public String uploadAnswerCard(InputStream in, String md5, Long examId, Integer number) {
+    	String path = getAnswerCardUri(examId, number);
+        try {
+            fileStore.write(path, in, md5);
+            return path;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("文件上传出错:"+path, e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+}

+ 8 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java

@@ -141,4 +141,12 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
         return markPaperPackageDtoIPage;
     }
 
+    @Override
+    public MarkPaper getByExamIdAndCoursePaperId(Long examId, String coursePaperId) {
+        QueryWrapper<MarkPaper> wrapper = new QueryWrapper<>();
+        wrapper.lambda().eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getCoursePaperId, coursePaperId);
+        wrapper.last("LIMIT 1");
+        return this.getOne(wrapper);
+    }
 }

+ 95 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/ScanAnswerCardServiceImpl.java

@@ -1,16 +1,27 @@
 package com.qmth.teachcloud.mark.service.impl;
 
+import java.io.IOException;
 import java.util.List;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
+import com.qmth.teachcloud.mark.bean.answercard.AnswerCardSaveDomain;
 import com.qmth.teachcloud.mark.bean.answercard.AnswerCardVo;
+import com.qmth.teachcloud.mark.bean.answercard.CardFile;
+import com.qmth.teachcloud.mark.entity.MarkPaper;
 import com.qmth.teachcloud.mark.entity.ScanAnswerCard;
+import com.qmth.teachcloud.mark.enums.CardSource;
 import com.qmth.teachcloud.mark.mapper.ScanAnswerCardMapper;
+import com.qmth.teachcloud.mark.service.FileService;
+import com.qmth.teachcloud.mark.service.MarkPaperService;
 import com.qmth.teachcloud.mark.service.ScanAnswerCardService;
 
 /**
@@ -25,6 +36,13 @@ import com.qmth.teachcloud.mark.service.ScanAnswerCardService;
 public class ScanAnswerCardServiceImpl extends ServiceImpl<ScanAnswerCardMapper, ScanAnswerCard>
 		implements ScanAnswerCardService {
 
+	@Autowired
+	private MarkPaperService markPaperService;
+
+    @Autowired
+    private FileService fileService;
+
+	
 	@Override
 	public ScanAnswerCard findByExamAndNumber(Long examId, Integer cardNumber) {
 		if (examId == null) {
@@ -45,4 +63,81 @@ public class ScanAnswerCardServiceImpl extends ServiceImpl<ScanAnswerCardMapper,
 		return baseMapper.cardList(examId,coursePaperId);
 	}
 
+	@Transactional
+	@Override
+	public void cardSave(AnswerCardSaveDomain domain) {
+		boolean singlePage=false;
+		MarkPaper mp=markPaperService.getByExamIdAndCoursePaperId(domain.getExamId(), domain.getCoursePaperId());
+		if(MarkPaperStatus.FINISH.equals(mp.getStatus())) {
+			throw new ParameterException("阅卷已结束");
+		}
+		ScanAnswerCard card=null;
+		if(domain.getNumber()!=null) {
+			card=findByExamAndNumber(domain.getExamId(), domain.getNumber());
+			if(card==null) {
+				throw new ParameterException("未找到卡格式信息");
+			}
+		}else {
+			card=new ScanAnswerCard();
+			card.setExamId(domain.getExamId());
+			card.setCoursePaperId(domain.getCoursePaperId());
+			card.setPaperCount(domain.getPaperCount());
+			card.setSinglePage(singlePage);
+			card.setRemark(domain.getRemark());
+			card.setDpi(domain.getDpi());
+			card.setSource(CardSource.CLIENT);
+			card.setNumber(findMaxCardNumberByExamId(domain.getExamId())+1);
+			card.setNeedAdapte(false);
+		}
+		CardFile cardFile;
+        byte[] fileData;
+        String sliceConfig;
+        try {
+            fileData = domain.getFile().getBytes();
+            //解析卡格式文件
+            cardFile = parseCardFile(fileData);
+            if (singlePage && domain.getPaperCount()!=cardFile.getPages().size()) {
+                throw new ParameterException("卡格式数量不一致");
+            }
+            if (!singlePage && domain.getPaperCount()*2!=cardFile.getPages().size()) {
+                throw new ParameterException("卡格式数量不一致");
+            }
+            //提取裁切坐标
+            sliceConfig = cardFile.getSliceConfig().toString();
+        } catch (IOException e) {
+            throw new ParameterException("文件解析失败", e);
+        }
+		String filePath=null;
+        try {
+        	fileService.uploadAnswerCard(domain.getFile().getInputStream(), domain.getMd5(), domain.getExamId(), domain.getNumber());
+        } catch (IOException e) {
+            throw new ParameterException("文件上传失败", e);
+        }
+        card.setSliceConfig(sliceConfig);
+        card.setMd5(domain.getMd5());
+        card.setUri(filePath);
+        this.saveOrUpdate(card);
+	}
+    /**
+     * 解析卡格式文件中的裁切图坐标,用于云阅卷同步
+     *
+     * @param data
+     */
+    private CardFile parseCardFile(byte[] data) throws IOException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        return objectMapper.readValue(data, CardFile.class);
+    }
+    private Integer findMaxCardNumberByExamId(Long examId) {
+        int number = 0;
+        QueryWrapper<ScanAnswerCard> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(ScanAnswerCard::getExamId, examId);
+        List<ScanAnswerCard> list = this.list(queryWrapper);
+        for (ScanAnswerCard card : list) {
+            if (number < card.getNumber()) {
+                number = card.getNumber();
+            }
+        }
+        return number;
+    }
+
 }