瀏覽代碼

数据同步云阅卷

xiaof 4 年之前
父節點
當前提交
46e5b54f3f
共有 18 個文件被更改,包括 892 次插入2 次删除
  1. 12 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/config/DictionaryConfig.java
  2. 57 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/domain/SyncDataDomain.java
  3. 21 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/ExamPrintPlan.java
  4. 10 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/ExamStudent.java
  5. 175 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/TBSyncTask.java
  6. 2 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/mapper/ExamStudentMapper.java
  7. 12 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/mapper/TBSyncTaskMapper.java
  8. 9 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/DataSyncService.java
  9. 2 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/ExamStudentService.java
  10. 15 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/TBSyncTaskService.java
  11. 299 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/DataSyncServiceImpl.java
  12. 5 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ExamStudentServiceImpl.java
  13. 36 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/TBSyncTaskServiceImpl.java
  14. 198 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/util/HttpKit.java
  15. 15 0
      distributed-print-business/src/main/resources/mapper/ExamStudentMapper.xml
  16. 4 0
      distributed-print-business/src/main/resources/mapper/TBSyncTaskMapper.xml
  17. 6 0
      distributed-print-task/src/main/java/com/qmth/distributed/print/task/job/service/impl/JobServiceImpl.java
  18. 14 2
      distributed-print/src/main/resources/application.properties

+ 12 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/config/DictionaryConfig.java

@@ -69,4 +69,16 @@ public class DictionaryConfig {
     public OrgCenterDomain orgCenterDomain() {
         return new OrgCenterDomain();
     }
+
+
+    /**
+     * 机构用户中心配置
+     *
+     * @return
+     */
+    @Bean
+    @ConfigurationProperties(prefix = "sync.config", ignoreUnknownFields = false)
+    public SyncDataDomain syncDataDomain() {
+        return new SyncDataDomain();
+    }
 }

+ 57 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/domain/SyncDataDomain.java

@@ -0,0 +1,57 @@
+package com.qmth.distributed.print.business.domain;
+
+/**
+ * @Description: 同步云阅卷参数
+ */
+public class SyncDataDomain {
+
+    boolean sync;
+
+    String hostUrl;
+
+    String examSaveUrl;
+
+    String studentSaveUrl;
+
+    String cardUploadUrl;
+
+    public boolean isSync() {
+        return sync;
+    }
+
+    public void setSync(boolean sync) {
+        this.sync = sync;
+    }
+
+    public String getHostUrl() {
+        return hostUrl;
+    }
+
+    public void setHostUrl(String hostUrl) {
+        this.hostUrl = hostUrl;
+    }
+
+    public String getExamSaveUrl() {
+        return examSaveUrl;
+    }
+
+    public void setExamSaveUrl(String examSaveUrl) {
+        this.examSaveUrl = examSaveUrl;
+    }
+
+    public String getStudentSaveUrl() {
+        return studentSaveUrl;
+    }
+
+    public void setStudentSaveUrl(String studentSaveUrl) {
+        this.studentSaveUrl = studentSaveUrl;
+    }
+
+    public String getCardUploadUrl() {
+        return cardUploadUrl;
+    }
+
+    public void setCardUploadUrl(String cardUploadUrl) {
+        this.cardUploadUrl = cardUploadUrl;
+    }
+}

+ 21 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/ExamPrintPlan.java

@@ -90,6 +90,12 @@ public class ExamPrintPlan extends BaseEntity implements Serializable {
     private String ordinaryContent;
     private PrintPlanStatusEnum status;
 
+    @TableField("third_relate_id")
+    private Long thirdRelateId;
+
+    @TableField("sync_status")
+    private Boolean syncStatus;
+
     public Long getSchoolId() {
         return schoolId;
     }
@@ -178,4 +184,19 @@ public class ExamPrintPlan extends BaseEntity implements Serializable {
         this.status = status;
     }
 
+    public Long getThirdRelateId() {
+        return thirdRelateId;
+    }
+
+    public void setThirdRelateId(Long thirdRelateId) {
+        this.thirdRelateId = thirdRelateId;
+    }
+
+    public Boolean getSyncStatus() {
+        return syncStatus;
+    }
+
+    public void setSyncStatus(Boolean syncStatus) {
+        this.syncStatus = syncStatus;
+    }
 }

+ 10 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/ExamStudent.java

@@ -50,6 +50,8 @@ public class ExamStudent extends BaseEntity implements Serializable {
     @ApiModelProperty(value = "附件id")
     @TableField(value = "attachment_id")
     private Long attachmentId;
+    @TableField(value = "sync_status")
+    private Boolean syncStatus;
 
     public static long getSerialVersionUID() {
         return serialVersionUID;
@@ -142,4 +144,12 @@ public class ExamStudent extends BaseEntity implements Serializable {
     public void setPaperType(String paperType) {
         this.paperType = paperType;
     }
+
+    public Boolean getSyncStatus() {
+        return syncStatus;
+    }
+
+    public void setSyncStatus(Boolean syncStatus) {
+        this.syncStatus = syncStatus;
+    }
 }

+ 175 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/entity/TBSyncTask.java

@@ -0,0 +1,175 @@
+package com.qmth.distributed.print.business.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.distributed.print.business.enums.TaskResultEnum;
+import com.qmth.distributed.print.business.enums.TaskStatusEnum;
+import com.qmth.distributed.print.business.enums.TaskTypeEnum;
+import com.qmth.distributed.print.common.contant.SystemConstant;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * 同步任务表
+ * </p>
+ */
+@ApiModel(value = "TBSyncTask对象", description = "同步任务表")
+public class TBSyncTask implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id")
+    private Long id;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "学校id")
+    @TableField(value = "school_id")
+    private Long schoolId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "印刷计划id")
+    @TableField(value = "print_plan_id")
+    private Long printPlanId;
+
+    @ApiModelProperty(value = "印刷计划名称")
+    @TableField(value = "print_plan_name")
+    private String printPlanName;
+
+    @ApiModelProperty(value = "任务状态,INIT:未开始,RUNNING:进行中,FINISH:已完成")
+    @TableField(value = "status")
+    private TaskStatusEnum status;
+
+    @ApiModelProperty(value = "实时摘要信息")
+    @TableField(value = "summary")
+    private String summary;
+
+    @ApiModelProperty(value = "数据结果,SUCCESS:成功,ERROR:失败")
+    @TableField(value = "result")
+    private TaskResultEnum result;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)//新增执行
+    private Long createTime;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "创建人id")
+    @TableField("create_id")
+    private Long createId;
+
+    @ApiModelProperty(value = "备注")
+    @TableField(value = "remark")
+    private String remark;
+
+    @ApiModelProperty(value = "重试次数")
+    @TableField(value = "reset_count")
+    private int resetCount;
+
+    @ApiModelProperty(value = "错误原因")
+    @TableField(value = "error_message", updateStrategy = FieldStrategy.IGNORED)
+    private String errorMessage;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+
+    public Long getPrintPlanId() {
+        return printPlanId;
+    }
+
+    public void setPrintPlanId(Long printPlanId) {
+        this.printPlanId = printPlanId;
+    }
+
+    public String getPrintPlanName() {
+        return printPlanName;
+    }
+
+    public void setPrintPlanName(String printPlanName) {
+        this.printPlanName = printPlanName;
+    }
+
+    public TaskStatusEnum getStatus() {
+        return status;
+    }
+
+    public void setStatus(TaskStatusEnum status) {
+        this.status = status;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public TaskResultEnum getResult() {
+        return result;
+    }
+
+    public void setResult(TaskResultEnum result) {
+        this.result = result;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getCreateId() {
+        return createId;
+    }
+
+    public void setCreateId(Long createId) {
+        this.createId = createId;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public int getResetCount() {
+        return resetCount;
+    }
+
+    public void setResetCount(int resetCount) {
+        this.resetCount = resetCount;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+}

+ 2 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/mapper/ExamStudentMapper.java

@@ -28,4 +28,6 @@ public interface ExamStudentMapper extends BaseMapper<ExamStudent> {
      * @return
      */
     List<ExamStudentCourseDto> queryBySchoolIdAndExamDetailCourseIds(@Param("schoolId") Long schoolId, @Param("examDetailCourseIds") List<Long> examDetailCourseIds);
+
+    List<ExamStudent> listStudentByPrintPlanIdAndSyncStatus(Long printPlanId);
 }

+ 12 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/mapper/TBSyncTaskMapper.java

@@ -0,0 +1,12 @@
+package com.qmth.distributed.print.business.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.distributed.print.business.entity.TBSyncTask;
+
+/**
+ * <p>
+ * 同步数据 Mapper 接口
+ * </p>
+ */
+public interface TBSyncTaskMapper extends BaseMapper<TBSyncTask> {
+}

+ 9 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/DataSyncService.java

@@ -0,0 +1,9 @@
+package com.qmth.distributed.print.business.service;
+
+/**
+ * 同步数据到云阅卷 服务类
+ * @Date: 2021/5/20.
+ */
+public interface DataSyncService {
+    void syncToCloudReview();
+}

+ 2 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/ExamStudentService.java

@@ -32,4 +32,6 @@ public interface ExamStudentService extends IService<ExamStudent> {
      * @return
      */
     List<ExamStudentCourseDto> queryBySchoolIdAndExamDetailCourseIds(Long schoolId, List<Long> examDetailCourseIds);
+
+    List<ExamStudent> listStudentByPrintPlanIdAndSyncStatus(Long printPlanId);
 }

+ 15 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/TBSyncTaskService.java

@@ -0,0 +1,15 @@
+package com.qmth.distributed.print.business.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.distributed.print.business.entity.ExamPrintPlan;
+import com.qmth.distributed.print.business.entity.TBSyncTask;
+
+/**
+ * <p>
+ * 同步数据 服务类
+ * </p>
+ */
+public interface TBSyncTaskService extends IService<TBSyncTask> {
+
+    TBSyncTask saveTask(ExamPrintPlan examPrintPlan);
+}

+ 299 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/DataSyncServiceImpl.java

@@ -0,0 +1,299 @@
+package com.qmth.distributed.print.business.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.qmth.boot.tools.signature.SignatureType;
+import com.qmth.distributed.print.business.config.DictionaryConfig;
+import com.qmth.distributed.print.business.entity.BasicSchool;
+import com.qmth.distributed.print.business.entity.ExamPrintPlan;
+import com.qmth.distributed.print.business.entity.ExamStudent;
+import com.qmth.distributed.print.business.entity.TBSyncTask;
+import com.qmth.distributed.print.business.enums.PrintPlanStatusEnum;
+import com.qmth.distributed.print.business.enums.TaskResultEnum;
+import com.qmth.distributed.print.business.enums.TaskStatusEnum;
+import com.qmth.distributed.print.business.service.*;
+import com.qmth.distributed.print.business.util.HttpKit;
+import com.qmth.distributed.print.common.SignatureEntityTest;
+import com.qmth.distributed.print.common.contant.SystemConstant;
+import com.qmth.distributed.print.common.enums.ExceptionResultEnum;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @Date: 2021/5/20.
+ */
+@Service
+public class DataSyncServiceImpl implements DataSyncService {
+
+    @Autowired
+    private ExamPrintPlanService examPrintPlanService;
+
+    @Autowired
+    private TBSyncTaskService tbSyncTaskService;
+
+    @Autowired
+    private DictionaryConfig dictionaryConfig;
+
+    @Autowired
+    private CacheService cacheService;
+
+    @Autowired
+    private ExamStudentService examStudentService;
+
+    private ExecutorService executors = Executors.newFixedThreadPool(5);
+
+    private static final String SAVE_EXAM_TYPE = "MULTI_MEDIA";
+    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    private static final String POST_METHOD = "POST";
+
+    @Override
+    public void syncToCloudReview() {
+        // 查询可同步计划(同步状态为空:未同步,false:同步失败)
+        QueryWrapper<ExamPrintPlan> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(ExamPrintPlan::getStatus, PrintPlanStatusEnum.END)
+                .and(q -> q.isNull(ExamPrintPlan::getSyncStatus).or().eq(ExamPrintPlan::getSyncStatus, false));
+        List<ExamPrintPlan> examPrintPlans = examPrintPlanService.list(queryWrapper);
+        if (!CollectionUtils.isEmpty(examPrintPlans)) {
+            for (ExamPrintPlan examPrintPlan : examPrintPlans) {
+                executors.execute(syncData(examPrintPlan));
+            }
+        }
+    }
+
+    private TimerTask syncData(ExamPrintPlan examPrintPlan) {
+        return new TimerTask() {
+            @Override
+            public void run() {
+                TBSyncTask syncTask = tbSyncTaskService.saveTask(examPrintPlan);
+                doSyncCore(examPrintPlan, syncTask);
+            }
+        };
+    }
+
+    /**
+     * 同步核心方法
+     *
+     * @param syncTask
+     */
+    @Transactional
+    public void doSyncCore(ExamPrintPlan examPrintPlan, TBSyncTask syncTask) {
+        try {
+            // 校验同步url
+            validatUrl();
+            // 同步计划 -> 对应云阅卷考试
+            Long thirdRelateId = saveExam(examPrintPlan);
+            // 考试同步成功,才能同步考生和题卡
+            if (Objects.nonNull(thirdRelateId)) {
+
+            }
+
+        } catch (Exception e) {
+            syncTask.setStatus(TaskStatusEnum.FINISH);
+            syncTask.setResult(TaskResultEnum.ERROR);
+            syncTask.setErrorMessage(e.getMessage());
+        } finally {
+            tbSyncTaskService.saveOrUpdate(syncTask);
+        }
+
+    }
+
+    /**
+     * 校验url是否非空
+     */
+    private void validatUrl() {
+        String hostUrl = dictionaryConfig.syncDataDomain().getHostUrl();
+        String examSaveUrl = dictionaryConfig.syncDataDomain().getExamSaveUrl();
+        String studentSaveUrl = dictionaryConfig.syncDataDomain().getStudentSaveUrl();
+        String cardUploadUrl = dictionaryConfig.syncDataDomain().getCardUploadUrl();
+
+        if (StringUtils.isAnyBlank(hostUrl, examSaveUrl, studentSaveUrl, cardUploadUrl)) {
+            throw ExceptionResultEnum.ERROR.exception("云阅卷同步接口URL未正确配置");
+        }
+    }
+
+    /**
+     * 创建考试
+     *
+     * @param examPrintPlan
+     * @return
+     */
+    public Long saveExam(ExamPrintPlan examPrintPlan) {
+        String hostUrl = dictionaryConfig.syncDataDomain().getHostUrl();
+        String examSaveUrl = dictionaryConfig.syncDataDomain().getExamSaveUrl();
+        String postUrl = hostUrl.concat(examSaveUrl);
+        try {
+            //参数
+            Map<String, String> map = new HashMap<>();
+            map.put("code", String.valueOf(examPrintPlan.getId()));
+            map.put("name", examPrintPlan.getName());
+            map.put("examTime", DateUtil.format(new Date(examPrintPlan.getExamEndTime()), DATE_FORMAT));
+            map.put("type", SAVE_EXAM_TYPE);
+
+            String result = HttpKit.sendPost(postUrl, getHeaders(examPrintPlan.getSchoolId(), examSaveUrl), map, null, null, null);
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            if (jsonObject.containsKey("id")) {
+                Long id = Long.valueOf(jsonObject.get("id").toString());
+                UpdateWrapper<ExamPrintPlan> updateWrapper = new UpdateWrapper<>();
+                updateWrapper.lambda().set(ExamPrintPlan::getThirdRelateId, id).set(ExamPrintPlan::getSyncStatus, true);
+                examPrintPlanService.update(updateWrapper);
+                return id;
+            }
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        }
+
+        return null;
+    }
+
+    /**
+     * 新增考生
+     *
+     * @return
+     */
+    public void saveStudent(ExamPrintPlan examPrintPlan) {
+        String hostUrl = dictionaryConfig.syncDataDomain().getHostUrl();
+        String studentSaveUrl = dictionaryConfig.syncDataDomain().getStudentSaveUrl();
+        String postUrl = hostUrl.concat(studentSaveUrl);
+
+        List<ExamStudent> examStudents = examStudentService.listStudentByPrintPlanIdAndSyncStatus(examPrintPlan.getId());
+        for (ExamStudent examStudent : examStudents) {
+            Map<String, Object> resultMap = new HashMap<>();
+            try {
+                //参数
+                Map<String, String> map = new HashMap<>();
+                map.put("examId", String.valueOf(examPrintPlan.getThirdRelateId()));
+                map.put("examNumber", examStudent.getTicketNumber());
+                map.put("studentCode", examStudent.getStudentCode());
+                map.put("name", examStudent.getStudentName());
+                map.put("college", "无");
+//                map.put("className", examStudent.get());
+                map.put("teacher", "无");
+//                map.put("subjectCode", examStudent.get());
+//                map.put("subjectName", tcPExamStudent.getCourseName());
+
+                String result = HttpKit.sendPost(postUrl, getHeaders(examPrintPlan.getSchoolId(), studentSaveUrl), map, null, null, null);
+                JSONObject jsonObject = JSONObject.parseObject(result);
+                if (jsonObject.containsKey("updateTime")) {
+                    UpdateWrapper<ExamStudent> updateWrapper = new UpdateWrapper<>();
+                    updateWrapper.lambda().set(ExamStudent::getSyncStatus, true).eq(ExamStudent::getId, examStudent.getId());
+                    examStudentService.update(updateWrapper);
+                }
+            } catch (Exception e) {
+                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 推送题卡
+     *
+     * @return
+     */
+    /*public void saveCard(ExamPrintPlan examPrintPlan) {
+        String hostUrl = dictionaryConfig.syncDataDomain().getHostUrl();
+        String studentSaveUrl = dictionaryConfig.syncDataDomain().getStudentSaveUrl();
+        String postUrl = hostUrl.concat(studentSaveUrl);
+        List<TcPCard> cards = tcPCardMapper.selectListNotSyncSuccess(tcPExam.getId());
+        cards.forEach(tcPCard -> {
+            //题卡卡内容
+            QueryWrapper<TcPCardDetail> tcPCardDetailQueryWrapper = new QueryWrapper<>();
+            tcPCardDetailQueryWrapper.lambda().eq(TcPCardDetail::getCardId, tcPCard.getId());
+            TcPCardDetail tcPCardDetail = tcPCardDetailMapper.selectOne(tcPCardDetailQueryWrapper);
+
+            Map<String, Object> resultMap = new HashMap<>();
+            if (StringUtils.isNotEmpty(tcPCardDetail.getContent())) {
+                //生成json文件
+                File file = null;
+                try {
+                    String filePath = makeDirs();
+                    file = FileHelper.createJsonFile(filePath);
+                    createFile(file, tcPCardDetail.getContent());
+                    Map<String, String> files = new HashMap<>();
+                    if (file.exists()) {
+                        files.put(tcPCard.getTitle(), file.getPath());
+                    }
+
+                    log.info("同步题卡:考试id:{},题卡id:{},开始同步", tcPExam.getId(), tcPCard.getId());
+                    //表单数据
+                    Map<String, String> formText = new HashMap<>();
+                    formText.put("examId", String.valueOf(tcPExam.getExternalId()));
+                    formText.put("subjectCode", tcPCard.getCourseCode());
+                    formText.put("format", "json");
+                    formText.put("md5", DigestUtils.md5Hex(new FileInputStream(file)));
+
+
+                    log.info("同步题卡:考试id:{},题卡id:{},请求url:{}, 表单参数:{},表头参数:{},发送请求", tcPExam.getId(), tcPCard.getId(), url, JSONObject.toJSONString(formText), JSONObject.toJSONString(getHeaders(cardUrl)));
+                    String result = HttpKit.sendPost(url, getHeaders(cardUrl), formText, files, null, null);
+                    JSONObject jsonObject = JSONObject.parseObject(result);
+                    if (jsonObject.containsKey("success")) {
+                        log.info("同步题卡:考试id:{},题卡id:{},请求成功,返回数据:{}", tcPExam.getId(), tcPCard.getId(), result);
+                        resultMap.put("success", true);
+                        resultMap.put("msg", jsonObject.get("success"));
+                    }
+                } catch (Exception e) {
+                    resultMap.put("success", false);
+                    resultMap.put("msg", e.getMessage());
+                } finally {
+                    if (file != null && file.exists()) {
+                        file.delete();
+                    }
+                }
+            } else {
+                resultMap.put("success", false);
+                resultMap.put("msg", "题卡内容为空");
+            }
+            Boolean success = Boolean.valueOf(resultMap.get("success").toString());
+            String msg = resultMap.get("msg").toString();
+            log.info("同步题卡:考试id:{},题卡id:{},请求状态:{},返回数据:{}", tcPExam.getId(), tcPCard.getId(), success, msg);
+
+            String relaName = tcPCard.getCourseName().concat("(").concat(tcPCard.getCourseCode()).concat(")");
+            TcPExamSyncRecord tcPExamSyncRecord = new TcPExamSyncRecord(tcPExam.getId(), "card", tcPCard.getId(), relaName, success, msg);
+            saveSyncRecord(tcPExamSyncRecord, "card", cards.size());
+            log.info("同步题卡:考试id:{},题卡id:{},保存同步记录成功,保存参数:{}", tcPExam.getId(), tcPCard.getId(), JSONObject.toJSONString(tcPExamSyncRecord));
+        });
+    }*/
+
+    /**
+     * http请求头
+     *
+     * @param url
+     * @return
+     */
+    private Map<String, String> getHeaders(Long schoolId, String url) {
+        long time = System.currentTimeMillis();
+        Map<String, String> header = new HashMap<>();
+        header.put(SystemConstant.HEADER_AUTHORIZATION, createSign(schoolId, time, url));
+        header.put(SystemConstant.HEADER_TIME, String.valueOf(time));
+        return header;
+    }
+
+    /**
+     * 签名
+     *
+     * @param schoolId
+     * @param time
+     * @param url
+     * @return
+     */
+    private String createSign(Long schoolId, long time, String url) {
+        BasicSchool basicSchool = cacheService.schoolCache(schoolId);
+        if (basicSchool == null) {
+            throw ExceptionResultEnum.ERROR.exception("学校不存在");
+        } else {
+            if (!basicSchool.getEnable()) {
+                throw ExceptionResultEnum.ERROR.exception("学校已禁用");
+            }
+        }
+        String signature = SignatureEntityTest.build(SignatureType.SECRET, POST_METHOD, url, time, basicSchool.getAccessKey(), basicSchool.getAccessSecret());
+        return signature;
+    }
+}

+ 5 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ExamStudentServiceImpl.java

@@ -73,4 +73,9 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamS
     public List<ExamStudentCourseDto> queryBySchoolIdAndExamDetailCourseIds(Long schoolId, List<Long> examDetailCourseIds) {
         return examStudentMapper.queryBySchoolIdAndExamDetailCourseIds(schoolId, examDetailCourseIds);
     }
+
+    @Override
+    public List<ExamStudent> listStudentByPrintPlanIdAndSyncStatus(Long printPlanId) {
+        return this.baseMapper.listStudentByPrintPlanIdAndSyncStatus(printPlanId);
+    }
 }

+ 36 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/TBSyncTaskServiceImpl.java

@@ -0,0 +1,36 @@
+package com.qmth.distributed.print.business.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.distributed.print.business.entity.ExamPrintPlan;
+import com.qmth.distributed.print.business.entity.TBSyncTask;
+import com.qmth.distributed.print.business.enums.TaskStatusEnum;
+import com.qmth.distributed.print.business.mapper.TBSyncTaskMapper;
+import com.qmth.distributed.print.business.service.TBSyncTaskService;
+import com.qmth.distributed.print.common.contant.SystemConstant;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Date: 2021/5/20.
+ */
+@Service
+public class TBSyncTaskServiceImpl extends ServiceImpl<TBSyncTaskMapper, TBSyncTask> implements TBSyncTaskService {
+    @Override
+    public TBSyncTask saveTask(ExamPrintPlan examPrintPlan) {
+        QueryWrapper<TBSyncTask> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(TBSyncTask::getSchoolId, examPrintPlan.getSchoolId())
+                .eq(TBSyncTask::getPrintPlanId, examPrintPlan.getId());
+        TBSyncTask tbSyncTask = this.getOne(queryWrapper);
+        if (tbSyncTask == null) {
+            tbSyncTask = new TBSyncTask();
+            tbSyncTask.setId(SystemConstant.getDbUuid());
+            tbSyncTask.setSchoolId(examPrintPlan.getSchoolId());
+            tbSyncTask.setPrintPlanId(examPrintPlan.getId());
+            tbSyncTask.setPrintPlanName(examPrintPlan.getName());
+            tbSyncTask.setStatus(TaskStatusEnum.INIT);
+            tbSyncTask.setCreateTime(System.currentTimeMillis());
+        }
+        this.save(tbSyncTask);
+        return tbSyncTask;
+    }
+}

+ 198 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/util/HttpKit.java

@@ -0,0 +1,198 @@
+package com.qmth.distributed.print.business.util;
+
+import com.qmth.distributed.print.common.enums.ExceptionResultEnum;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+
+public class HttpKit {
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url    发送请求的 URL
+     * @param params 请求的参数集合
+     * @return 远程资源的响应结果
+     */
+    @SuppressWarnings("unused")
+    public static String sendPost(String url, Map<String, String> params, Map<String, String> requestHeader) {
+        OutputStreamWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        try {
+            URL realUrl = new URL(url);
+            HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
+            // 发送POST请求必须设置如下两行
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            // POST方法
+            conn.setRequestMethod("POST");
+            // 设置通用的请求属性
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent",
+                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            if (requestHeader != null && requestHeader.size() > 0) {
+                for (Map.Entry<String, String> entry : requestHeader.entrySet()) {
+                    conn.setRequestProperty(entry.getKey(), entry.getValue());
+                }
+            }
+//            conn.setRequestProperty("Authorization", authorization);
+            conn.connect();
+            // 获取URLConnection对象对应的输出流
+            out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
+            // 发送请求参数
+            if (params != null) {
+                StringBuilder param = new StringBuilder();
+                for (Map.Entry<String, String> entry : params.entrySet()) {
+                    if (param.length() > 0) {
+                        param.append("&");
+                    }
+                    param.append(entry.getKey());
+                    param.append("=");
+                    param.append(entry.getValue());
+                }
+                out.write(param.toString());
+            }
+            // flush输出流的缓冲
+            out.flush();
+            // 定义BufferedReader输入流来读取URL的响应
+            in = new BufferedReader(
+                    new InputStreamReader(conn.getInputStream(), "UTF-8"));
+            String line;
+            while ((line = in.readLine()) != null) {
+                result.append(line);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        //使用finally块来关闭输出流、输入流
+        finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * 发送post请求
+     *
+     * @param requestUrl       请求url
+     * @param requestHeader    请求头
+     * @param formTexts        表单数据
+     * @param files            上传文件
+     * @param requestEncoding  请求编码
+     * @param responseEncoding 响应编码
+     * @return 页面响应html
+     */
+    public static String sendPost(String requestUrl, Map<String, String> requestHeader, Map<String, String> formTexts, Map<String, String> files, String requestEncoding, String responseEncoding) {
+        OutputStream out = null;
+        BufferedReader reader = null;
+        String result = "";
+        try {
+            if (requestUrl == null || requestUrl.isEmpty()) {
+                return result;
+            }
+            URL realUrl = new URL(requestUrl);
+            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
+            connection.setRequestProperty("accept", "text/html, application/xhtml+xml, image/jxr, */*");
+            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0");
+            if (requestHeader != null && requestHeader.size() > 0) {
+                for (Map.Entry<String, String> entry : requestHeader.entrySet()) {
+                    connection.setRequestProperty(entry.getKey(), entry.getValue());
+                }
+            }
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setRequestMethod("POST");
+            if (requestEncoding == null || requestEncoding.isEmpty()) {
+                requestEncoding = "UTF-8";
+            }
+            if (responseEncoding == null || responseEncoding.isEmpty()) {
+                responseEncoding = "UTF-8";
+            }
+            if (files == null || files.size() == 0) {
+                connection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
+                out = new DataOutputStream(connection.getOutputStream());
+                if (formTexts != null && formTexts.size() > 0) {
+                    String formData = "";
+                    for (Map.Entry<String, String> entry : formTexts.entrySet()) {
+                        formData += entry.getKey() + "=" + entry.getValue() + "&";
+                    }
+                    formData = formData.substring(0, formData.length() - 1);
+                    out.write(formData.getBytes(requestEncoding));
+                }
+            } else {
+                String boundary = "-----------------------------" + new Date().getTime();
+                connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary);
+                out = new DataOutputStream(connection.getOutputStream());
+                if (formTexts != null && formTexts.size() > 0) {
+                    StringBuilder sbFormData = new StringBuilder();
+                    for (Map.Entry<String, String> entry : formTexts.entrySet()) {
+                        sbFormData.append("--" + boundary + "\r\n");
+                        sbFormData.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");
+                        sbFormData.append(entry.getValue() + "\r\n");
+                    }
+                    out.write(sbFormData.toString().getBytes(requestEncoding));
+                }
+                for (Map.Entry<String, String> entry : files.entrySet()) {
+                    String fileName = entry.getKey();
+                    String filePath = entry.getValue();
+                    if (fileName == null || fileName.isEmpty() || filePath == null || filePath.isEmpty()) {
+                        continue;
+                    }
+                    File file = new File(filePath);
+                    if (!file.exists()) {
+                        continue;
+                    }
+                    out.write(("--" + boundary + "\r\n").getBytes(requestEncoding));
+                    out.write(("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"\r\n").getBytes(requestEncoding));
+                    out.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes(requestEncoding));
+                    DataInputStream in = new DataInputStream(new FileInputStream(file));
+                    int bytes = 0;
+                    byte[] bufferOut = new byte[1024];
+                    while ((bytes = in.read(bufferOut)) != -1) {
+                        out.write(bufferOut, 0, bytes);
+                    }
+                    in.close();
+                    out.write(("\r\n").getBytes(requestEncoding));
+                }
+                out.write(("--" + boundary + "--").getBytes(requestEncoding));
+            }
+            out.flush();
+            out.close();
+//            out = null;
+            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), responseEncoding));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                result += line;
+            }
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("发送POST请求出现异常:" + e.getMessage());
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+        return result;
+    }
+}

+ 15 - 0
distributed-print-business/src/main/resources/mapper/ExamStudentMapper.xml

@@ -85,5 +85,20 @@
             </where>
             order by edc.course_code
     </select>
+    <select id="listStudentByPrintPlanIdAndSyncStatus"
+            resultType="com.qmth.distributed.print.business.entity.ExamStudent">
+        SELECT
+            *
+        FROM
+            exam_student a
+                LEFT JOIN
+            exam_detail_course b ON a.exam_detail_course_id = b.id
+                LEFT JOIN
+            exam_detail c ON b.exam_detail_id = c.id
+        WHERE
+            c.print_plan_id = #{printPlanId}
+                AND (a.sync_status IS NULL
+                OR a.sync_status = FALSE)
+    </select>
 
 </mapper>

+ 4 - 0
distributed-print-business/src/main/resources/mapper/TBSyncTaskMapper.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.qmth.distributed.print.business.mapper.TBSyncTaskMapper">
+</mapper>

+ 6 - 0
distributed-print-task/src/main/java/com/qmth/distributed/print/task/job/service/impl/JobServiceImpl.java

@@ -46,6 +46,9 @@ public class JobServiceImpl implements JobService {
     @Autowired
     BasicMessageService basicMessageService;
 
+    @Autowired
+    DataSyncService dataSyncService;
+
     @Override
     public void updateSchoolInfo() throws IOException {
         orgCenterDataDisposeService.updateSchoolInfo();
@@ -74,6 +77,9 @@ public class JobServiceImpl implements JobService {
             }
         }
         examPrintPlanService.saveOrUpdateBatch(examPrintPlanList);
+
+        // 2021-05-20 同步数据到云阅卷
+        dataSyncService.syncToCloudReview();
     }
 
     /**

+ 14 - 2
distributed-print/src/main/resources/application.properties

@@ -12,9 +12,9 @@ spring.application.name=distributed-print
 #\u6570\u636E\u6E90\u914D\u7F6E
 db.host=localhost
 db.port=3306
-db.name=distributed-36
+db.name=distributed-print-v2.0.1
 db.username=root
-db.password=123456789
+db.password=root
 
 #redis\u6570\u636E\u6E90\u914D\u7F6E
 com.qmth.redis.host=${db.host}
@@ -95,6 +95,18 @@ com.qmth.logging.file-path=/Users/king/Downloads/distributed-print.log
 #\u5F15\u5165task\u914D\u7F6E\u6587\u4EF6
 spring.profiles.include=task
 
+#云阅卷相关url
+#是否同步
+sync.config.sync=false
+sync.config.hostUrl=https://www.markingcloud.com
+#同步考试
+sync.config.examSaveUrl=/api/exam/save
+#同步考生
+sync.config.studentSaveUrl=/api/exam/student/save
+#同步题卡
+sync.config.cardUploadUrl=/api/file/card/upload
+
+
 sms.config.smsNormalCode=qmth
 sms.config.codeExpiredTime=2
 sms.config.codeSendInterval=60