deason 5 năm trước cách đây
commit
be61322db0
100 tập tin đã thay đổi với 8372 bổ sung0 xóa
  1. 13 0
      .gitignore
  2. 91 0
      examcloud-core-print-common/pom.xml
  3. 55 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/Constants.java
  4. 37 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/IdEntity.java
  5. 54 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/PageQuery.java
  6. 92 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/Result.java
  7. 151 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/SysProperty.java
  8. 50 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/aspect/AspectController.java
  9. 31 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/aspect/AspectRestController.java
  10. 91 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/Model.java
  11. 20 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/Op.java
  12. 75 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/OrderBuilder.java
  13. 219 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SearchBuilder.java
  14. 213 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SpecUtils.java
  15. 314 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SqlWrapper.java
  16. 101 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/upyun/UpYunClient.java
  17. 102 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/Check.java
  18. 76 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/DateUtils.java
  19. 129 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ElectronUtils.java
  20. 81 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ExcelStyle.java
  21. 68 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ExcelUtils.java
  22. 250 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/FileUtils.java
  23. 227 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/JsonMapper.java
  24. 51 0
      examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/Pair.java
  25. 20 0
      examcloud-core-print-dao/pom.xml
  26. 188 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/CoursePaper.java
  27. 139 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/CourseStatistic.java
  28. 56 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ExamQuestionStructure.java
  29. 106 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ExamStructure.java
  30. 206 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ObjectiveQuestionStructure.java
  31. 210 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/PrintingProject.java
  32. 86 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/PrintingTemplate.java
  33. 153 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectBackupSetting.java
  34. 50 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectOtherSetting.java
  35. 132 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectStatistic.java
  36. 173 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/SubjectiveQuestionStructure.java
  37. 39 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/BackupGroupType.java
  38. 49 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/ExamType.java
  39. 55 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/ExportType.java
  40. 49 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/PaperStatus.java
  41. 58 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/QuesStructType.java
  42. 64 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/TemplateType.java
  43. 29 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/CoursePaperRepository.java
  44. 36 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/CourseStatisticRepository.java
  45. 34 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ExamStructureRepository.java
  46. 34 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ObjectiveQuestionStructureRepository.java
  47. 44 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/PrintingProjectRepository.java
  48. 18 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/PrintingTemplateRepository.java
  49. 29 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectBackupSettingRepository.java
  50. 18 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectOtherSettingRepository.java
  51. 20 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectStatisticRepository.java
  52. 32 0
      examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/SubjectiveQuestionStructureRepository.java
  53. 211 0
      examcloud-core-print-dao/src/main/resources/db-schema.sql
  54. 21 0
      examcloud-core-print-provider/pom.xml
  55. 127 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/bean/UserInfo.java
  56. 83 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CommonController.java
  57. 101 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CoursePaperController.java
  58. 60 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CourseStatisticController.java
  59. 62 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ExamStructureController.java
  60. 95 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingProjectController.java
  61. 57 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingProjectStatisticController.java
  62. 55 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingTemplateController.java
  63. 55 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ProjectBackupSettingController.java
  64. 63 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ProjectOtherSettingController.java
  65. 53 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/provider/CoursePaperCloudServiceProvider.java
  66. 123 0
      examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/provider/SyncCloudServiceProvider.java
  67. 21 0
      examcloud-core-print-service/pom.xml
  68. 75 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/CoursePaperService.java
  69. 51 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/CourseStatisticService.java
  70. 46 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ExamQuestionStructureService.java
  71. 62 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ExamStructureService.java
  72. 94 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingProjectService.java
  73. 39 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingProjectStatisticService.java
  74. 35 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingTemplateService.java
  75. 34 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ProjectBackupSettingService.java
  76. 41 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ProjectOtherSettingService.java
  77. 88 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/StatisticService.java
  78. 67 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/CourseInfo.java
  79. 117 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/ExamCourseInfo.java
  80. 80 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/ExamInfo.java
  81. 54 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/OrgInfo.java
  82. 26 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/RefreshInfo.java
  83. 38 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperConvert.java
  84. 66 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperLessInfo.java
  85. 55 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperQuery.java
  86. 67 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperTotalInfo.java
  87. 114 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportAllReq.java
  88. 104 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportBatchReq.java
  89. 44 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportFileInfo.java
  90. 60 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticConvert.java
  91. 193 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticInfo.java
  92. 63 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticLessInfo.java
  93. 81 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticQuery.java
  94. 104 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticRefreshReq.java
  95. 64 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examquestionstructure/ExamQuestionStructureInfo.java
  96. 51 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureConvert.java
  97. 155 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureInfo.java
  98. 40 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureQuery.java
  99. 49 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/printingproject/PrintingProjectConvert.java
  100. 210 0
      examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/printingproject/PrintingProjectInfo.java

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+*.class
+.project
+.classpath
+.settings
+.idea/
+*.iml
+*.log
+*.jar
+*.war
+*.ear
+log/
+logs/
+target/

+ 91 - 0
examcloud-core-print-common/pom.xml

@@ -0,0 +1,91 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>examcloud-core-print-common</artifactId>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-print</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-support</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-print-api</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-global-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-basic-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-examwork-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-questions-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.upyun</groupId>
+            <artifactId>java-sdk</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-base</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itextpdf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 55 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/Constants.java

@@ -0,0 +1,55 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common;
+
+/**
+ * 系统常量
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public interface Constants {
+
+    /**
+     * 操作成功
+     */
+    String SYS_CODE_200 = "PRT-000200";
+
+    /**
+     * 系统错误
+     */
+    String SYS_CODE_500 = "PRT-000500";
+
+    /**
+     * 参数错误
+     */
+    String SYS_CODE_400 = "PRT-000400";
+
+    /**
+     * 权限错误
+     */
+    String SYS_CODE_403 = "PRT-000403";
+
+    /* 导出文件默认名称 */
+    String PAPER_PDF_NAME = "试卷.pdf";
+    String ANSWER_PDF_NAME = "答案.pdf";
+    String STRUCT_ZIP_NAME = "试卷结构.zip";
+    String OBJECTIVE_EXCEL_NAME = "客观题.xlsx";
+    String SUBJECTIVE_EXCEL_NAME = "主观题.xlsx";
+    String OBJECTIVE_TITLE = "客观题数据";
+    String SUBJECTIVE_TITLE = "主观题数据";
+
+    String SUFFIX_ZIP = ".zip";
+    String SUFFIX_PDF = ".pdf";
+    String SUFFIX_EXCEL = ".xlsx";
+
+    static String rootFileDir() {
+        return Constants.class.getClassLoader().getResource("").getPath() + "files";
+    }
+
+}

+ 37 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/IdEntity.java

@@ -0,0 +1,37 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-24 14:43:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * ID Entity
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+@MappedSuperclass
+public abstract class IdEntity extends JpaEntity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    protected Long id;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+}

+ 54 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/PageQuery.java

@@ -0,0 +1,54 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-24 14:24:55.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common;
+
+import java.io.Serializable;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class PageQuery implements Serializable {
+    private static final long serialVersionUID = 1L;
+    protected Integer pageNo;
+    protected Integer pageSize;
+
+    public PageQuery(Integer pageNo, Integer pageSize) {
+        this.pageNo = pageNo;
+        this.pageSize = pageSize;
+    }
+
+    public PageQuery() {
+
+    }
+
+    public Integer getPageNo() {
+        if (pageNo == null || pageNo < 0) {
+            // default value
+            return 1;
+        }
+        return pageNo;
+    }
+
+    public Integer getPageSize() {
+        if (pageSize == null || pageSize < 1) {
+            // default value
+            return 10;
+        }
+        return pageSize;
+    }
+
+    public void setPageNo(Integer pageNo) {
+        this.pageNo = pageNo;
+    }
+
+    public void setPageSize(Integer pageSize) {
+        this.pageSize = pageSize;
+    }
+
+}

+ 92 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/Result.java

@@ -0,0 +1,92 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-25 11:18:18.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common;
+
+import cn.com.qmth.examcloud.core.print.common.utils.JsonMapper;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.io.Serializable;
+
+import static cn.com.qmth.examcloud.core.print.common.Constants.*;
+
+/**
+ * 响应结果类
+ */
+public class Result<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 状态码
+     */
+    private String code;
+    /**
+     * 描述信息
+     */
+    private String desc;
+    /**
+     * 结果数据
+     */
+    private T data;
+
+    public Result(String code, String desc, T data) {
+        this.code = code;
+        this.desc = desc;
+        this.data = data;
+    }
+
+    public Result(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public static Result success() {
+        return success(null);
+    }
+
+    public static <T> Result success(T data) {
+        return new Result(SYS_CODE_200, "操作成功!", data);
+    }
+
+    public static Result error() {
+        return error("操作失败!");
+    }
+
+    public static Result error(String desc) {
+        return new Result(SYS_CODE_500, desc);
+    }
+
+    public static Result noAuth() {
+        return new Result(SYS_CODE_403, "无效认证信息,请先登录!");
+    }
+
+    @JsonIgnore
+    @ApiIgnore
+    public boolean isSuccess() {
+        if (SYS_CODE_200.equals(getCode())) {
+            return true;
+        }
+        return false;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public String toJson() {
+        return new JsonMapper().toJson(this);
+    }
+
+}

+ 151 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/SysProperty.java

@@ -0,0 +1,151 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-30 15:17:49.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+
+/**
+ * 系统配置信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/30
+ */
+@Component
+public class SysProperty implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Value("${$upyun.site.1.domain}")
+    protected String domain;
+
+    @Value("${$upyun.site.1.bucketName}")
+    protected String bucketName;
+
+    @Value("${$upyun.site.1.userName}")
+    protected String userName;
+
+    @Value("${$upyun.site.1.password}")
+    protected String password;
+
+    @Value("${$upyun.site.1.uploadUrl}")
+    private String uploadUrl;
+
+    /**
+     * 配置文件环境
+     */
+    @Value("${spring.profiles.active}")
+    private String profile;
+
+    /**
+     * 文件存放目录
+     */
+    @Value("${examcloud.web.sys.dataDir}")
+    private String dir;
+
+    /**
+     * 文件存放临时目录
+     */
+    @Value("${examcloud.web.sys.tempDataDir}")
+    private String tempDir;
+
+    /**
+     * 题库访问地址前缀
+     */
+    @Value("${$question.url.prefix}")
+    private String questionUrlPrefix;
+
+    public String getDomain() {
+        if (domain != null) {
+            return domain.trim();
+        }
+        return "";
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    public String getBucketName() {
+        if (bucketName != null) {
+            return bucketName.trim();
+        }
+        return "";
+    }
+
+    public void setBucketName(String bucketName) {
+        this.bucketName = bucketName;
+    }
+
+    public String getUserName() {
+        if (userName != null) {
+            return userName.trim();
+        }
+        return "";
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        if (password != null) {
+            return password.trim();
+        }
+        return "";
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getUploadUrl() {
+        if (uploadUrl != null) {
+            return uploadUrl.trim();
+        }
+        return "";
+    }
+
+    public void setUploadUrl(String uploadUrl) {
+        this.uploadUrl = uploadUrl;
+    }
+
+    public String getProfile() {
+        return profile;
+    }
+
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
+    public String getDir() {
+        return dir;
+    }
+
+    public void setDir(String dir) {
+        this.dir = dir;
+    }
+
+    public String getTempDir() {
+        return tempDir;
+    }
+
+    public void setTempDir(String tempDir) {
+        this.tempDir = tempDir;
+    }
+
+    public String getQuestionUrlPrefix() {
+        return questionUrlPrefix;
+    }
+
+    public void setQuestionUrlPrefix(String questionUrlPrefix) {
+        this.questionUrlPrefix = questionUrlPrefix;
+    }
+
+}

+ 50 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/aspect/AspectController.java

@@ -0,0 +1,50 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-25 13:52:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.aspect;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+//@ControllerAdvice(annotations = Controller.class)
+public class AspectController {
+    private final static Logger log = LoggerFactory.getLogger(AspectController.class);
+    public final static HttpStatus ERROR_CODE = HttpStatus.INTERNAL_SERVER_ERROR;
+    public final static String ERROR_CONTENT = "系统繁忙!";
+
+    @ResponseBody
+    @ExceptionHandler(value = RuntimeException.class)
+    public ResponseEntity<String> handle(RuntimeException e) {
+        return doHandle(e);
+    }
+
+    @ResponseBody
+    @ExceptionHandler(value = Exception.class)
+    public ResponseEntity<String> handle(Exception e) {
+        return doHandle(e);
+    }
+
+    public static ResponseEntity<String> doHandle(Exception e) {
+        if (e instanceof StatusException) {
+            StatusException se = (StatusException) e;
+            log.error(se.toJson());
+            return new ResponseEntity<>(se.toJson(), ERROR_CODE);
+        }
+        log.error(e.getMessage(), e);
+        String errorJson = Result.error(ERROR_CONTENT).toJson();
+        return new ResponseEntity<>(errorJson, ERROR_CODE);
+    }
+
+}

+ 31 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/aspect/AspectRestController.java

@@ -0,0 +1,31 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-25 13:52:55.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.aspect;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+//@RestControllerAdvice(annotations = RestController.class)
+public class AspectRestController {
+
+    @ResponseBody
+    @ExceptionHandler(value = RuntimeException.class)
+    public ResponseEntity<String> handle(RuntimeException e) {
+        return AspectController.doHandle(e);
+    }
+
+    @ResponseBody
+    @ExceptionHandler(value = Exception.class)
+    public ResponseEntity<String> handle(Exception e) {
+        return AspectController.doHandle(e);
+    }
+
+}

+ 91 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/Model.java

@@ -0,0 +1,91 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class Model {
+
+    public static <T> T of(Optional<T> optional) {
+        try {
+            if (optional.isPresent()) {
+                return optional.get();
+            }
+        } catch (NoSuchElementException e) {
+            //ignore
+        }
+        return null;
+    }
+
+    public static String splitFieldName(String fieldName) {
+        if (fieldName == null) {
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+        char[] chars = fieldName.toCharArray();
+        final int A = 65, Z = 90, DOT = 46;
+        for (int i = 0; i < chars.length; i++) {
+            Character c = chars[i];
+            if (i == 0) {
+                result.append(c);
+                continue;
+            }
+            if (c == DOT) {
+                result.append("_");
+                continue;
+            }
+            if (c >= A && c <= Z) {
+                result.append("_");
+            }
+            result.append(c);
+        }
+        return result.toString().toUpperCase();
+    }
+
+    public static List<String> parseFields(final Class clazz) {
+        return parseFields(clazz, null);
+    }
+
+    public static List<String> parseFields(final Class clazz, final Class rootClazz) {
+        List<String> fieldNames = new ArrayList<>();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+            if (Modifier.isFinal(field.getModifiers())) {
+                continue;
+            }
+            Class<?> fieldType = field.getType();
+            String fieldName = field.getName();
+            if (rootClazz != null && rootClazz.isAssignableFrom(fieldType)) {
+                List<String> subFieldNames = parseFields(fieldType, rootClazz);
+                if (subFieldNames.size() == 0) {
+                    continue;
+                }
+                fieldNames.addAll(subFieldNames.stream()
+                        .map(subName -> subName = fieldName + "." + subName)
+                        .collect(Collectors.toList()));
+            } else {
+                fieldNames.add(fieldName);
+            }
+        }
+        return fieldNames;
+    }
+
+}

+ 20 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/Op.java

@@ -0,0 +1,20 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+/**
+ * 操作枚举类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public enum Op {
+
+    EQ, NOT_EQ, GT, LT, GTE, LTE, LIKE, L_LIKE, R_LIKE, IS_NULL, NOT_NULL, IN, NOT_IN
+
+}

+ 75 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/OrderBuilder.java

@@ -0,0 +1,75 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-23 16:47:59.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+import org.springframework.data.domain.Sort;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 排序条件构建类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/23
+ */
+public class OrderBuilder {
+
+    private List<Order> orders;
+
+    public OrderBuilder() {
+        orders = new ArrayList<>();
+    }
+
+    public OrderBuilder asc(String fieldName) {
+        orders.add(new Order(fieldName, Sort.Direction.ASC));
+        return this;
+    }
+
+    public OrderBuilder desc(String fieldName) {
+        orders.add(new Order(fieldName, Sort.Direction.DESC));
+        return this;
+    }
+
+    public Sort build() {
+        if (orders.size() == 0) {
+            return null;
+        }
+
+        List<Sort.Order> list = new ArrayList<>();
+        for (Order order : orders) {
+            list.add(new Sort.Order(order.getDirection(), order.getFieldName()));
+        }
+
+        return Sort.by(list);
+    }
+
+    public static class Order {
+
+        private String fieldName;
+
+        private Sort.Direction direction;
+
+        private Order(String fieldName, Sort.Direction direction) {
+            if (fieldName == null || "".equals(fieldName.trim())) {
+                throw new IllegalArgumentException("FieldName must be not empty.");
+            }
+            this.fieldName = fieldName.trim();
+            this.direction = direction;
+        }
+
+        public String getFieldName() {
+            return fieldName;
+        }
+
+        public Sort.Direction getDirection() {
+            return direction;
+        }
+    }
+
+}

+ 219 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SearchBuilder.java

@@ -0,0 +1,219 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-23 15:43:17.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+import org.springframework.data.jpa.domain.Specification;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 常用的查询条件构建类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/23
+ */
+public class SearchBuilder {
+    private List<Searcher> searchers;
+
+    public SearchBuilder() {
+        searchers = new ArrayList<>();
+    }
+
+    public List<Searcher> build() {
+        return searchers;
+    }
+
+    public <T> SearchBuilder eq(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.EQ));
+        return this;
+    }
+
+    public static <T> Specification EQ(String fieldName, T value) {
+        return (root, query, cb) -> cb.equal(root.get(fieldName), value);
+    }
+
+    public <T> SearchBuilder notEq(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.NOT_EQ));
+        return this;
+    }
+
+    public static <T> Specification NOT_EQ(String fieldName, T value) {
+        return (root, query, cb) -> cb.notEqual(root.get(fieldName), value);
+    }
+
+    public <T> SearchBuilder gt(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.GT));
+        return this;
+    }
+
+    public static <T> Specification GT(String fieldName, T value) {
+        return (root, query, cb) -> cb.greaterThan(root.get(fieldName), (Comparable) value);
+    }
+
+    public <T> SearchBuilder gte(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.GTE));
+        return this;
+    }
+
+    public static <T> Specification GTE(String fieldName, T value) {
+        return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(fieldName), (Comparable) value);
+    }
+
+    public <T> SearchBuilder lt(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.LT));
+        return this;
+    }
+
+    public static <T> Specification LT(String fieldName, T value) {
+        return (root, query, cb) -> cb.lessThan(root.get(fieldName), (Comparable) value);
+    }
+
+    public <T> SearchBuilder lte(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.LTE));
+        return this;
+    }
+
+    public static <T> Specification LTE(String fieldName, T value) {
+        return (root, query, cb) -> cb.lessThanOrEqualTo(root.get(fieldName), (Comparable) value);
+    }
+
+    public SearchBuilder like(String fieldName, CharSequence value) {
+        searchers.add(new Searcher(fieldName, value, Op.LIKE));
+        return this;
+    }
+
+    public static Specification LIKE(String fieldName, CharSequence value) {
+        return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value + "%");
+    }
+
+    public SearchBuilder leftLike(String fieldName, CharSequence value) {
+        searchers.add(new Searcher(fieldName, value, Op.L_LIKE));
+        return this;
+    }
+
+    public static Specification L_LIKE(String fieldName, CharSequence value) {
+        return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value);
+    }
+
+    public SearchBuilder rightLike(String fieldName, CharSequence value) {
+        searchers.add(new Searcher(fieldName, value, Op.R_LIKE));
+        return this;
+    }
+
+    public static Specification R_LIKE(String fieldName, CharSequence value) {
+        return (root, query, cb) -> cb.like(root.get(fieldName), value + "%");
+    }
+
+    public SearchBuilder in(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("In Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            searchers.add(new Searcher(fieldName, values.iterator().next(), Op.EQ));
+        } else {
+            searchers.add(new Searcher(fieldName, values.toArray(), Op.IN));
+        }
+        return this;
+    }
+
+    public static Specification IN(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("In Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            return (root, query, cb) -> cb.equal(root.get(fieldName), values.iterator().next());
+        }
+        return (root, query, cb) -> root.get(fieldName).in(values);
+    }
+
+    public SearchBuilder notIn(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("In Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            searchers.add(new Searcher(fieldName, values.iterator().next(), Op.NOT_EQ));
+        } else {
+            searchers.add(new Searcher(fieldName, values.toArray(), Op.NOT_IN));
+        }
+        return this;
+    }
+
+    public static Specification NOT_IN(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("In Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            return (root, query, cb) -> cb.notEqual(root.get(fieldName), values.iterator().next());
+        }
+        return (root, query, cb) -> root.get(fieldName).in(values).not();
+    }
+
+    public SearchBuilder isNull(String fieldName) {
+        searchers.add(new Searcher(fieldName, null, Op.IS_NULL));
+        return this;
+    }
+
+    public static Specification IS_NULL(String fieldName) {
+        return (root, query, cb) -> cb.isNull(root.get(fieldName));
+    }
+
+    public SearchBuilder notNull(String fieldName) {
+        searchers.add(new Searcher(fieldName, null, Op.NOT_NULL));
+        return this;
+    }
+
+    public static Specification NOT_NULL(String fieldName) {
+        return (root, query, cb) -> cb.isNotNull(root.get(fieldName));
+    }
+
+    public static <T> Specification between(String fieldName, T start, T end) {
+        return (root, query, cb) -> cb.between(root.get(fieldName), (Comparable) start, (Comparable) end);
+    }
+
+    public static class Searcher<T> {
+        /**
+         * 属性名
+         */
+        private String fieldName;
+        /**
+         * 属性值
+         */
+        private T value;
+        /**
+         * 操作的表达式
+         */
+        private Op op;
+
+        private Searcher(String fieldName, T value, Op op) {
+            if (fieldName == null || "".equals(fieldName.trim())) {
+                throw new IllegalArgumentException("FieldName must be not empty.");
+            }
+            if (op == null) {
+                throw new IllegalArgumentException("Operator must be not empty.");
+            }
+            this.fieldName = fieldName.trim();
+            this.value = value;
+            this.op = op;
+        }
+
+        public String getFieldName() {
+            return fieldName;
+        }
+
+        public T getValue() {
+            return value;
+        }
+
+        public Op getOp() {
+            return op;
+        }
+    }
+
+}

+ 213 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SpecUtils.java

@@ -0,0 +1,213 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.jpa.domain.Specifications;
+
+import javax.persistence.criteria.Path;
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JPA Spec常用的查询封装类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class SpecUtils {
+
+    /**
+     * 默认当前页数
+     */
+    public static final int DEFAULT_PAGE_NO = 1;
+
+    /**
+     * 默认每页条数
+     */
+    public static final int DEFAULT_PAGE_SIZE = 10;
+
+    /**
+     * 构建分页条件
+     *
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    public static Pageable buildPageable(Integer pageNo, Integer pageSize) {
+        return buildPageable(pageNo, pageSize, Sort.unsorted());
+    }
+
+    /**
+     * 构建分页条件
+     *
+     * @param pageNo
+     * @param pageSize
+     * @param sort
+     * @return
+     */
+    public static Pageable buildPageable(Integer pageNo, Integer pageSize, Sort sort) {
+        if (pageNo == null || pageNo < 1) {
+            pageNo = DEFAULT_PAGE_NO;
+        }
+
+        if (pageSize == null || pageSize < 1) {
+            pageSize = DEFAULT_PAGE_SIZE;
+        }
+
+        return PageRequest.of(pageNo - 1, pageSize, sort);
+    }
+
+    /**
+     * 构建查询条件
+     *
+     * @param clazz
+     * @param searchers
+     * @param <T>
+     * @return
+     */
+    public static <T> Specification<T> buildSearchers(final Class<T> clazz, final List<SearchBuilder.Searcher> searchers) {
+        return buildSearchers(clazz, searchers, false);
+    }
+
+    /**
+     * 构建查询条件
+     *
+     * @param clazz
+     * @param searchers
+     * @param isOR
+     * @param <T>
+     * @return
+     */
+    public static <T> Specification<T> buildSearchers(final Class<T> clazz, final List<SearchBuilder.Searcher> searchers, final boolean isOR) {
+        if (searchers == null || searchers.size() == 0) {
+            return null;
+        }
+
+        return (root, query, builder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            for (SearchBuilder.Searcher filter : searchers) {
+                if (filter.getFieldName() == null || "".equals(filter.getFieldName())) {
+                    continue;
+                }
+
+                String[] names = filter.getFieldName().split("\\.");
+                Path expression = root.get(names[0]);
+                for (int i = 1; i < names.length; i++) {
+                    expression = expression.get(names[i]);
+                }
+
+                switch (filter.getOp()) {
+                    case EQ:
+                        predicates.add(builder.equal(expression, filter.getValue()));
+                        break;
+                    case NOT_EQ:
+                        predicates.add(builder.notEqual(expression, filter.getValue()));
+                        break;
+                    case GT:
+                        predicates.add(builder.greaterThan(expression, (Comparable) filter.getValue()));
+                        break;
+                    case GTE:
+                        predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.getValue()));
+                        break;
+                    case LT:
+                        predicates.add(builder.lessThan(expression, (Comparable) filter.getValue()));
+                        break;
+                    case LTE:
+                        predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.getValue()));
+                        break;
+                    case LIKE:
+                        predicates.add(builder.like(expression, "%" + filter.getValue() + "%"));
+                        break;
+                    case L_LIKE:
+                        predicates.add(builder.like(expression, "%" + filter.getValue()));
+                        break;
+                    case R_LIKE:
+                        predicates.add(builder.like(expression, filter.getValue() + "%"));
+                        break;
+                    case IS_NULL:
+                        predicates.add(builder.isNull(expression));
+                        break;
+                    case NOT_NULL:
+                        predicates.add(builder.isNotNull(expression));
+                        break;
+                    case IN:
+                        predicates.add(expression.in((Object[]) filter.getValue()));
+                        break;
+                    case NOT_IN:
+                        predicates.add(expression.in((Object[]) filter.getValue()).not());
+                        break;
+                }
+            }
+
+            if (!predicates.isEmpty()) {
+                if (isOR) {
+                    //将所有条件用 or 联合起来
+                    return builder.or(predicates.toArray(new Predicate[predicates.size()]));
+                } else {
+                    //将所有条件用 and 联合起来
+                    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
+                }
+            }
+            return builder.conjunction();
+        };
+    }
+
+    /**
+     * 合并查询条件
+     *
+     * @param specs
+     * @return
+     */
+    public static Specification andMerge(Specification... specs) {
+        if (specs == null || specs.length == 0) {
+            return null;
+        }
+
+        Specifications result = null;
+        for (int i = 0; i < specs.length; i++) {
+            if (i == 0) {
+                result = Specifications.where(specs[i]);
+                continue;
+            }
+            result = Specifications.where(result).and(specs[i]);
+        }
+
+        return result;
+    }
+
+    /**
+     * 合并查询条件
+     * 注:SQL中AND优先级高于OR
+     * 示例:... t.name='' or t.age=1 and t.gender=1 等同于 ... t.name='' or (t.age=1 and t.gender=1)
+     *
+     * @param specs
+     * @return
+     */
+    public static Specification orMerge(Specification... specs) {
+        if (specs == null || specs.length == 0) {
+            return null;
+        }
+
+        Specifications result = null;
+        for (int i = 0; i < specs.length; i++) {
+            if (i == 0) {
+                result = Specifications.where(specs[i]);
+                continue;
+            }
+            result = Specifications.where(result).or(specs[i]);
+        }
+
+        return result;
+    }
+
+}

+ 314 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/jpa/SqlWrapper.java

@@ -0,0 +1,314 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.jpa;
+
+import java.util.Collection;
+
+import static cn.com.qmth.examcloud.core.print.common.jpa.SqlWrapper.Constant.*;
+
+/**
+ * SQL常用的语句构建类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class SqlWrapper {
+    private StringBuilder sql = new StringBuilder();
+
+    public String build() {
+        return sql.toString();
+    }
+
+    public SqlWrapper append(String str) {
+        sql.append(str);
+        return this;
+    }
+
+    public SqlWrapper append(Number str) {
+        sql.append(str);
+        return this;
+    }
+
+    public SqlWrapper as(String name) {
+        sql.append(AS).append(name);
+        return this;
+    }
+
+    public SqlWrapper select() {
+        sql.append(SELECT).append("*");
+        return this;
+    }
+
+    public SqlWrapper select(String columns) {
+        /* 多个字段须按逗号分隔 */
+        sql.append(SELECT).append(columns);
+        return this;
+    }
+
+    public SqlWrapper delete() {
+        sql.append(DELETE);
+        return this;
+    }
+
+    public SqlWrapper update(String tableName) {
+        sql.append(UPDATE).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper set() {
+        sql.append(SET);
+        return this;
+    }
+
+    public SqlWrapper count(String fieldName) {
+        sql.append(COUNT).append("(").append(fieldName).append(")");
+        return this;
+    }
+
+    public SqlWrapper sum(String fieldName) {
+        sql.append(SUM).append("(").append(fieldName).append(")");
+        return this;
+    }
+
+    public SqlWrapper distinct(String fieldName) {
+        sql.append(DISTINCT).append(fieldName);
+        return this;
+    }
+
+    public SqlWrapper from(String tableName) {
+        sql.append(FROM).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper innerJoin(String tableName) {
+        sql.append(INNER_JOIN).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper leftJoin(String tableName) {
+        sql.append(LEFT_JOIN).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper rightJoin(String tableName) {
+        sql.append(RIGHT_JOIN).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper union(String otherQuerySql) {
+        sql.append(UNION).append(otherQuerySql);
+        return this;
+    }
+
+    public SqlWrapper unionAll(String otherQuerySql) {
+        sql.append(UNION_ALL).append(otherQuerySql);
+        return this;
+    }
+
+    public SqlWrapper exists(String subQuerySql) {
+        sql.append(EXISTS).append("(").append(subQuerySql).append(")");
+        return this;
+    }
+
+    public SqlWrapper notExists(String subQuerySql) {
+        sql.append(NOT_EXISTS).append("(").append(subQuerySql).append(")");
+        return this;
+    }
+
+    public SqlWrapper on(String fieldName, String refFieldName) {
+        sql.append(ON).append(fieldName).append(" = ").append(refFieldName);
+        return this;
+    }
+
+    public SqlWrapper where() {
+        sql.append(WHERE);
+        return this;
+    }
+
+    public SqlWrapper oneEqOne() {
+        /* default condition */
+        sql.append("1=1").append(AND);
+        return this;
+    }
+
+    public SqlWrapper and() {
+        sql.append(AND);
+        return this;
+    }
+
+    public SqlWrapper or() {
+        sql.append(OR);
+        return this;
+    }
+
+    public SqlWrapper like(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(LIKE).append("'%").append(value).append("%'");
+        return this;
+    }
+
+    public SqlWrapper like(String fieldName, Number value) {
+        sql.append(fieldName).append(LIKE).append("'%").append(value).append("%'");
+        return this;
+    }
+
+    public SqlWrapper eq(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" = ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper eq(String fieldName, Number value) {
+        sql.append(fieldName).append(" = ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper notEq(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" != ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper notEq(String fieldName, Number value) {
+        sql.append(fieldName).append(" != ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gt(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" > ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gt(String fieldName, Number value) {
+        sql.append(fieldName).append(" > ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gte(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" >= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gte(String fieldName, Number value) {
+        sql.append(fieldName).append(" >= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lt(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" < ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lt(String fieldName, Number value) {
+        sql.append(fieldName).append(" < ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lte(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" <= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lte(String fieldName, Number value) {
+        sql.append(fieldName).append(" <= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper in(String fieldName, Collection<?> values) {
+        String str = this.spilt(values);
+        sql.append(fieldName).append(IN).append("(").append(str).append(")");
+        return this;
+    }
+
+    public SqlWrapper notIn(String fieldName, Collection<?> values) {
+        String str = this.spilt(values);
+        sql.append(fieldName).append(NOT_IN).append("(").append(str).append(")");
+        return this;
+    }
+
+    public SqlWrapper isNull(String fieldName) {
+        sql.append(fieldName).append(IS_NULL);
+        return this;
+    }
+
+    public SqlWrapper notNull(String fieldName) {
+        sql.append(fieldName).append(IS_NOT_NULL);
+        return this;
+    }
+
+    public SqlWrapper groupBy(String columns) {
+        /* 多个字段时须按逗号分隔 */
+        sql.append(GROUP_BY).append(columns);
+        return this;
+    }
+
+    public SqlWrapper orderBy(String columns, boolean isDesc) {
+        /* 多个字段时须按逗号分隔 */
+        if (isDesc) {
+            sql.append(ORDER_BY).append(columns).append(DESC);
+        } else {
+            sql.append(ORDER_BY).append(columns).append(ASC);
+        }
+        return this;
+    }
+
+    public SqlWrapper having() {
+        sql.append(HAVING);
+        return this;
+    }
+
+    private String spilt(Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Values must be not empty.");
+        }
+        StringBuilder str = new StringBuilder();
+        int index = 0, total = values.size();
+        for (Object value : values) {
+            if (!(value instanceof CharSequence || value instanceof Number)) {
+                throw new IllegalArgumentException("Values must be String or Number.");
+            }
+            str.append(value.toString());
+            if (index < (total - 1)) {
+                str.append(",");
+            }
+            index++;
+        }
+        return str.toString();
+    }
+
+    interface Constant {
+        String SELECT = " SELECT ";
+        String UPDATE = " UPDATE ";
+        String DELETE = " DELETE ";
+        String SET = " SET ";
+        String COUNT = " COUNT ";
+        String SUM = " SUM ";
+        String DISTINCT = " DISTINCT ";
+        String FROM = " FROM ";
+        String WHERE = " WHERE ";
+        String INNER_JOIN = " INNER JOIN ";
+        String RIGHT_JOIN = " RIGHT JOIN ";
+        String LEFT_JOIN = " LEFT JOIN ";
+        String UNION = " UNION ";
+        String UNION_ALL = " UNION ALL ";
+        String EXISTS = " EXISTS ";
+        String NOT_EXISTS = " NOT EXISTS ";
+        String ON = " ON ";
+        String AS = " AS ";
+        String AND = " AND ";
+        String OR = " OR ";
+        String LIKE = " LIKE ";
+        String IN = " IN ";
+        String NOT_IN = " NOT IN ";
+        String IS_NULL = " IS NULL ";
+        String IS_NOT_NULL = " IS NOT NULL ";
+        String GROUP_BY = " GROUP BY ";
+        String ORDER_BY = " ORDER BY ";
+        String HAVING = " HAVING ";
+        String ASC = " ASC ";
+        String DESC = " DESC ";
+    }
+
+}

+ 101 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/upyun/UpYunClient.java

@@ -0,0 +1,101 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 17:02:06.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.upyun;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.print.common.SysProperty;
+import cn.com.qmth.examcloud.core.print.common.utils.Check;
+import cn.com.qmth.examcloud.core.print.common.utils.FileUtils;
+import main.java.com.UpYun;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+
+import static cn.com.qmth.examcloud.core.print.common.Constants.SYS_CODE_400;
+import static cn.com.qmth.examcloud.core.print.common.Constants.SYS_CODE_500;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/1
+ */
+@Component
+public class UpYunClient {
+    private static final Logger log = LoggerFactory.getLogger(UpYunClient.class);
+    @Autowired
+    private SysProperty sysProperty;
+
+    public String upload(File file) {
+        Check.isEmpty(file, "上传的文件不能为空!");
+        if (!file.exists() || !file.isFile()) {
+            throw new StatusException(SYS_CODE_400, "上传的文件不存在!");
+        }
+
+        try {
+            final String newFileName = FileUtils.newFileName(file.getName());
+            final String newFilePath = sysProperty.getUploadUrl() + FileUtils.dateDir() + newFileName;
+            this.getInstance().writeFile(newFilePath, file, true);
+
+            //成功后,返回文件访问地址
+            return this.getUrlPrefix() + newFilePath;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException(SYS_CODE_500, "上传文件异常!");
+        }
+    }
+
+    public String upload(byte[] bytes, String originalFilename) {
+        Check.isEmpty(bytes, "上传的文件不能为空!");
+        Check.isBlank(originalFilename, "上传的文件名不正确!");
+
+        try {
+            final String newFileName = FileUtils.newFileName(originalFilename);
+            final String newFilePath = sysProperty.getUploadUrl() + FileUtils.dateDir() + newFileName;
+            this.getInstance().writeFile(newFilePath, bytes, true);
+
+            //成功后,返回文件访问地址
+            return this.getUrlPrefix() + newFilePath;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException(SYS_CODE_500, "上传文件异常!");
+        }
+    }
+
+    public File download(String filePath) {
+        try {
+            //final String rootDir = Constants.rootFileDir();
+            final String rootDir = sysProperty.getTempDir();
+            FileUtils.makeDirs(rootDir);
+            final String newFilePath = rootDir + "/" + FileUtils.newFileName(filePath);
+            File file = new File(newFilePath);
+            this.getInstance().readFile(filePath, file);
+            return file;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    public UpYun getInstance() {
+        UpYun upyun = new UpYun(sysProperty.getBucketName(), sysProperty.getUserName(), sysProperty.getPassword());
+        upyun.setDebug(true);
+        upyun.setApiDomain(UpYun.ED_AUTO);
+        return upyun;
+    }
+
+    public String getUrlPrefix() {
+        return sysProperty.getDomain();
+    }
+
+    public SysProperty getProperty() {
+        return sysProperty;
+    }
+
+}

+ 102 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/Check.java

@@ -0,0 +1,102 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+
+import static cn.com.qmth.examcloud.core.print.common.Constants.SYS_CODE_400;
+
+/**
+ * 参数校验类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class Check {
+
+    public static void isNull(Object obj, String message) {
+        if (obj == null) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Object obj, String message) {
+        if (isEmpty(obj)) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Object[] array, String message) {
+        if (isEmpty(array)) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Collection<?> collection, String message) {
+        if (isEmpty(collection)) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Map<?, ?> map, String message) {
+        if (isEmpty(map)) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isBlank(CharSequence str, String message) {
+        if (isBlank(str)) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isTrue(Boolean expression, String message) {
+        if (expression != null && expression) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    public static void isFalse(Boolean expression, String message) {
+        if (expression == null || !expression) {
+            throw new StatusException(SYS_CODE_400, message);
+        }
+    }
+
+    private static boolean isEmpty(Object obj) {
+        if (obj == null) {
+            return true;
+        } else if (obj instanceof CharSequence) {
+            return ((CharSequence) obj).length() == 0;
+        } else if (obj.getClass().isArray()) {
+            return Array.getLength(obj) == 0;
+        } else if (obj instanceof Collection) {
+            return ((Collection) obj).isEmpty();
+        } else {
+            return obj instanceof Map ? ((Map) obj).isEmpty() : false;
+        }
+    }
+
+    private static boolean isBlank(CharSequence cs) {
+        int strLen;
+        if (cs != null && (strLen = cs.length()) != 0) {
+            for (int i = 0; i < strLen; ++i) {
+                if (!Character.isWhitespace(cs.charAt(i))) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return true;
+        }
+    }
+
+}

+ 76 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/DateUtils.java

@@ -0,0 +1,76 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期工具类
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public class DateUtils {
+    private static final String YYYY_MM_DD = "yyyy-MM-dd";
+    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    public static Date parse(String dateStr) {
+        if (dateStr == null) {
+            return null;
+        }
+        try {
+            final int len = 10;
+            if (dateStr.length() == len) {
+                return new SimpleDateFormat(YYYY_MM_DD).parse(dateStr);
+            }
+            return new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS).parse(dateStr);
+        } catch (ParseException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String format(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS).format(date);
+    }
+
+    public static Date changeMinute(Date date, int minute) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.add(Calendar.MINUTE, minute);
+        return calendar.getTime();
+    }
+
+    /**
+     * 计算时间差
+     */
+    public static String diff(Date begin, Date end) {
+        long diff = end.getTime() - begin.getTime();
+        long day = diff / (24 * 60 * 60 * 1000);
+        long hour = (diff / (60 * 60 * 1000) - day * 24);
+        long minute = ((diff / (60 * 1000)) - day * 24 * 60 - hour * 60);
+        long second = (diff / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);
+        StringBuilder result = new StringBuilder();
+        if (day > 0) {
+            result.append(day).append("天");
+        }
+        if (hour > 0) {
+            result.append(hour).append("小时");
+        }
+        result.append(minute).append("分");
+        result.append(second).append("秒");
+        return result.toString();
+    }
+
+}

+ 129 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ElectronUtils.java

@@ -0,0 +1,129 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-28 16:17:55.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/28
+ */
+public class ElectronUtils {
+    private static final Logger log = LoggerFactory.getLogger(ElectronUtils.class);
+    private static final String WIN_PREFIX = "cmd /c ";
+    private static final String LINUX_PREFIX = "./";
+
+    /**
+     * 将URL响应内容转为PDF文件
+     */
+    public static boolean toPdf(String profile, String cmdDir, String targetUrl, String pdfPath) {
+        if (StringUtils.isBlank(profile)) {
+            log.warn("[toPdf] spring.profiles.active must be not empty.");
+            return false;
+        }
+
+        if (StringUtils.isBlank(targetUrl)) {
+            log.warn("[toPdf] targetUrl must be not empty.");
+            return false;
+        }
+
+        if (StringUtils.isBlank(pdfPath)) {
+            log.warn("[toPdf] pdfPath must be not empty.");
+            return false;
+        }
+
+        if (StringUtils.isBlank(cmdDir)) {
+            log.warn("[toPdf] cmdDir must be not empty.");
+            return false;
+        }
+
+        File dir = new File(cmdDir);
+        if (!dir.exists()) {
+            FileUtils.makeDirs(cmdDir);
+        }
+
+        final String parentDir = dir.getParent();
+
+        if (isWindows()) {
+            //Windows带盘符路径,去掉首个"/"字符
+            if (pdfPath.indexOf(":") > 0 && pdfPath.startsWith("/")) {
+                pdfPath = pdfPath.replaceFirst("/", "");
+            }
+
+            String command = String.format("cmd /c electron-pdf %s %s -p A3 -l true -e view-ready", targetUrl, pdfPath);
+            return executeCommand(command, "GBK");
+        } else {
+            //Linux
+            String command = String.format("%s/electron-pdf-%s.sh %s %s", parentDir, profile, targetUrl, pdfPath);
+            return executeCommand(command, "UTF-8");
+        }
+    }
+
+    /**
+     * 执行命令
+     */
+    private static boolean executeCommand(String command, String charsetName) {
+        log.debug("Execute Command:" + command);
+
+        Process process;
+        try {
+            process = Runtime.getRuntime().exec(command);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            return false;
+        }
+
+        try (BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream(), charsetName));
+             BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), charsetName));) {
+
+            String outStr;
+            while ((outStr = out.readLine()) != null) {
+                log.debug(outStr);
+            }
+
+            String errStr;
+            while ((errStr = err.readLine()) != null) {
+                log.debug("err:" + errStr);
+            }
+
+            int exitCode = process.waitFor();
+            if (exitCode == 0) {
+                log.debug("Execute Command Success.");
+                return true;
+            }
+
+            log.warn("Execute Command Error. exitCode:" + exitCode);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        } catch (InterruptedException e) {
+            log.error(e.getMessage(), e);
+            Thread.currentThread().interrupt();
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+        return false;
+    }
+
+    public static boolean isWindows() {
+        String os = System.getProperty("os.name").toLowerCase();
+        if (os.contains("windows") || os.startsWith("win")) {
+            return true;
+        }
+        return false;
+    }
+
+}

+ 81 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ExcelStyle.java

@@ -0,0 +1,81 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-22 10:01:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import cn.afterturn.easypoi.excel.export.styler.AbstractExcelExportStyler;
+import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler;
+import org.apache.poi.ss.usermodel.*;
+
+/**
+ * Excel导出默认样式
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/22
+ */
+public class ExcelStyle extends AbstractExcelExportStyler implements IExcelExportStyler {
+    private final short fontHeightInPoints = 16;
+
+    public ExcelStyle(Workbook workbook) {
+        super.createStyles(workbook);
+    }
+
+    public CellStyle getHeaderStyle(short headerColor) {
+        CellStyle headerStyle = this.workbook.createCellStyle();
+        Font font = this.workbook.createFont();
+        font.setBold(true);
+        font.setFontHeightInPoints(fontHeightInPoints);
+        headerStyle.setFont(font);
+        //headerStyle.setFillForegroundColor(headerColor);
+        //headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        headerStyle.setBorderLeft(BorderStyle.THIN);
+        headerStyle.setBorderRight(BorderStyle.THIN);
+        headerStyle.setBorderBottom(BorderStyle.THIN);
+        headerStyle.setBorderTop(BorderStyle.THIN);
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        return headerStyle;
+    }
+
+    public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setBorderRight(BorderStyle.THIN);
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setDataFormat(STRING_FORMAT);
+        if (isWarp) {
+            cellStyle.setWrapText(true);
+        }
+        return cellStyle;
+    }
+
+    public CellStyle getTitleStyle(short color) {
+        CellStyle titleStyle = this.workbook.createCellStyle();
+        Font font = this.workbook.createFont();
+        font.setBold(true);
+        font.setColor(IndexedColors.BLACK.getIndex());
+        titleStyle.setFont(font);
+        titleStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
+        titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        titleStyle.setBorderLeft(BorderStyle.THIN);
+        titleStyle.setBorderRight(BorderStyle.THIN);
+        titleStyle.setBorderBottom(BorderStyle.THIN);
+        titleStyle.setBorderTop(BorderStyle.THIN);
+        titleStyle.setAlignment(HorizontalAlignment.CENTER);
+        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        titleStyle.setWrapText(true);
+        return titleStyle;
+    }
+
+    public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
+        return isWarp ? this.stringNoneWrapStyle : this.stringNoneStyle;
+    }
+
+}

+ 68 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/ExcelUtils.java

@@ -0,0 +1,68 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-21 17:17:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/21
+ */
+public class ExcelUtils {
+    private static Logger log = LoggerFactory.getLogger(ExcelUtils.class);
+
+    public static <T> void exportExcel(Class<T> clazz, List<T> records, File excelFile) {
+        exportExcel(clazz, records, excelFile, null);
+    }
+
+    public static <T> void exportExcel(Class<T> clazz, List<T> records, File excelFile, String title) {
+        if (excelFile == null) {
+            log.warn("[ExportExcel] excelFile is null.");
+            return;
+        }
+
+        if (records == null) {
+            log.warn("[ExportExcel] records is null.");
+            return;
+        }
+
+        log.debug("[ExportExcel] records size is " + records.size());
+
+        ExportParams params = new ExportParams();
+        params.setStyle(ExcelStyle.class);
+        params.setType(ExcelType.XSSF);
+
+        if (StringUtils.isNotBlank(title)) {
+            params.setTitle(title);
+            params.setSheetName(title);
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(excelFile);
+             Workbook workbook = ExcelExportUtil.exportExcel(params, clazz, records);) {
+
+            workbook.write(fos);
+        } catch (FileNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}

+ 250 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/FileUtils.java

@@ -0,0 +1,250 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-02 10:01:13.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import org.apache.http.ssl.SSLContextBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/2
+ */
+public class FileUtils {
+    private static Logger log = LoggerFactory.getLogger(FileUtils.class);
+
+    public static String randomUUID() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    /**
+     * 生成日期目录
+     * PS: yyyyMM
+     */
+    public static String dateDir() {
+        final String pattern = "yyyyMM";
+        return "/" + new SimpleDateFormat(pattern).format(new Date()) + "/";
+    }
+
+    /**
+     * 获取文件后缀名(包含".")
+     */
+    public static String getFileSuffix(String filePath) {
+        if (filePath == null) {
+            return "";
+        }
+        int index = filePath.lastIndexOf(".");
+        if (index > 0 && index < filePath.length() - 1) {
+            return filePath.substring(index).toLowerCase();
+        }
+        return "";
+    }
+
+    /**
+     * 生成新的文件名(带后缀)
+     * PS: uuid.xyz
+     */
+    public static String newFileName(String fileName) {
+        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+        if (fileName != null) {
+            int index = fileName.lastIndexOf(".");
+            if (index > 0 && index < fileName.length() - 1) {
+                //获取原来文件的后缀名
+                return uuid + fileName.substring(index).toLowerCase();
+            }
+        }
+        return uuid;
+    }
+
+    /**
+     * 获取文件名(包含后缀名)
+     */
+    public static String getFileName(String filePath) {
+        if (filePath == null) {
+            return "";
+        }
+        filePath = filePath.replace("\\", "/");
+        int index = filePath.lastIndexOf("/");
+        if (index >= 0) {
+            return filePath.substring(index + 1);
+        }
+        return filePath;
+    }
+
+    /**
+     * 创建文件目录
+     */
+    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;
+    }
+
+    /**
+     * 文件压缩
+     *
+     * @param target  待压缩的目录或文件
+     * @param zipFile 压缩后的文件
+     */
+    public static boolean zip(File target, File zipFile) {
+        if (target == null || !target.exists()) {
+            log.error("目录或文件不能为空!");
+            return false;
+        }
+
+        if (zipFile == null) {
+            log.error("待压缩的文件不能为空!");
+            return false;
+        }
+
+        // try-with-resources (since jdk1.7)
+        try (OutputStream outStream = new FileOutputStream(zipFile);
+             ZipOutputStream zipStream = new ZipOutputStream(outStream, Charset.forName("UTF-8"));) {
+            //是否为文件夹
+            if (target.isDirectory()) {
+                File[] files = target.listFiles();
+                if (files.length == 0) {
+                    log.error("当前目录内未找到任何文件!");
+                    return false;
+                }
+
+                for (File file : files) {
+                    doZip(zipStream, file, null);
+                }
+            } else {
+                doZip(zipStream, target, null);
+            }
+            return true;
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    private static void doZip(ZipOutputStream zipStream, File curFile, String dirName) throws IOException {
+        if (dirName == null) dirName = "";
+        String entryName = dirName + curFile.getName();
+        //log.debug("entryName:" + entryName);
+
+        if (curFile.isDirectory()) {
+            String subDirName = entryName + File.separator;
+            File[] files = curFile.listFiles();
+            if (files.length > 0) {
+                for (File file : files) {
+                    doZip(zipStream, file, subDirName);
+                }
+            } else {
+                //空文件夹暂不处理
+                //zipStream.putNextEntry(new ZipEntry(subDirName));
+                //zipStream.closeEntry();
+            }
+        } else {
+            try (InputStream is = new FileInputStream(curFile);) {
+                zipStream.putNextEntry(new ZipEntry(entryName));
+
+                int len;
+                byte[] bytes = new byte[1024];
+                while ((len = is.read(bytes)) > 0) {
+                    zipStream.write(bytes, 0, len);
+                }
+
+                zipStream.closeEntry();
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+    }
+
+    public static void saveURLToFile(String link, File file) {
+        if (link == null || file == null) {
+            log.warn("link or file must be not null.");
+            return;
+        }
+
+        if (link.startsWith("https")) {
+            saveHttpsURLToFile(link, file);
+            return;
+        }
+
+        try {
+            URL url = new URL(link);
+            org.apache.commons.io.FileUtils.copyURLToFile(url, file);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    private static void saveHttpsURLToFile(String link, File file) {
+        SSLContext sslContext = null;
+        try {
+            //System.setProperty ("jsse.enableSNIExtension", "false");
+            sslContext = new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build();
+        } catch (NoSuchAlgorithmException e) {
+            log.error(e.getMessage(), e);
+        } catch (KeyStoreException e) {
+            log.error(e.getMessage(), e);
+        } catch (KeyManagementException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        if (sslContext == null) {
+            log.warn("http sslContext must be not null.");
+            return;
+        }
+
+        HttpsURLConnection connection;
+        try {
+            URL url = new URL(link);
+            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
+            connection = (HttpsURLConnection) url.openConnection();
+            connection.setConnectTimeout(15000);
+            connection.setReadTimeout(15000);
+
+            if (HttpURLConnection.HTTP_OK != connection.getResponseCode()) {
+                log.warn("http connection response fail." + connection.getResponseMessage());
+                return;
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return;
+        }
+
+        //响应成功
+        try (DataInputStream in = new DataInputStream(connection.getInputStream());
+             DataOutputStream out = new DataOutputStream(new FileOutputStream(file));) {
+            byte[] bytes = new byte[1024];
+            int len;
+            while ((len = in.read(bytes)) > 0) {
+                out.write(bytes, 0, len);
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+}

+ 227 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/JsonMapper.java

@@ -0,0 +1,227 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 简单封装Jackson,实现JSON 与 Java Object互相转换的Mapper
+ * 封装不同的输出风格, 使用不同的builder函数创建实例
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+@SuppressWarnings("unchecked")
+public class JsonMapper {
+    private static Logger log = LoggerFactory.getLogger(JsonMapper.class);
+    private ObjectMapper mapper;
+
+    public JsonMapper() {
+        this(null);
+    }
+
+    public JsonMapper(Include include) {
+        mapper = new ObjectMapper();
+        //设置输出时包含属性的风格
+        if (include != null) {
+            mapper.setSerializationInclusion(include);
+        }
+        //设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
+        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+    }
+
+    /**
+     * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用
+     */
+    public static JsonMapper nonEmptyMapper() {
+        return new JsonMapper(Include.NON_EMPTY);
+    }
+
+    public static JsonMapper nonNullMapper() {
+        return new JsonMapper(Include.NON_NULL);
+    }
+
+    /**
+     * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式,建议在内部接口中使用
+     */
+    public static JsonMapper nonDefaultMapper() {
+        return new JsonMapper(Include.NON_DEFAULT);
+    }
+
+    /**
+     * Object可以是POJO,也可以是Collection或数组
+     * 如果对象为Null, 返回"null"
+     * 如果集合为空集合, 返回"[]"
+     */
+    public String toJson(Object object) {
+        try {
+            return mapper.writeValueAsString(object);
+        } catch (IOException e) {
+            log.error("write to json string error:" + object);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化POJO或简单Collection如List<String>
+     * 如果JSON字符串为Null或"null"字符串, 返回Null
+     * 如果JSON字符串为"[]", 返回空集合
+     * 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, JavaType)
+     */
+    public <T> T fromJson(String jsonString, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(jsonString, clazz);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂Collection如List<Bean>, 先使用createCollectionType()或constructMapType()构造类型, 然后调用本函数
+     */
+    public <T> T fromJson(String jsonString, JavaType javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂的对象,如Page<Bean>
+     */
+    public <T> T fromJson(String jsonString, TypeReference javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * json to list
+     */
+    public <T> List<T> toList(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructCollectionType(List.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * json to simple HashMap
+     */
+    public <T> Map<String, T> toHashMap(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructMapType(HashMap.class, String.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error:", e);
+            return null;
+        }
+    }
+
+    /**
+     * 构造Collection类型
+     */
+    public JavaType constructCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {
+        return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);
+    }
+
+    /**
+     * 构造Map类型
+     */
+    public JavaType constructMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {
+        return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);
+    }
+
+    /**
+     * 当JSON里只含有Bean的部分屬性時,更新一個已存在Bean,只覆盖該部分的屬性
+     */
+    public void update(String jsonString, Object object) {
+        try {
+            mapper.readerForUpdating(object).readValue(jsonString);
+        } catch (JsonProcessingException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        } catch (IOException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        }
+    }
+
+    /**
+     * 輸出JSONP格式数据
+     */
+    public String toJsonP(String functionName, Object object) {
+        return toJson(new JSONPObject(functionName, object));
+    }
+
+    /**
+     * 設定是否使用Enum的toString函數來读写Enum
+     * 為False時使用Enum的name()函數來读写Enum, 默認為False
+     * 注意本函數一定要在Mapper創建後, 所有的读写動作之前調用
+     */
+    public void enableEnumUseToString() {
+        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+    }
+
+    /**
+     * 取出Mapper做进一步的设置或使用其他序列化API
+     */
+    public ObjectMapper getMapper() {
+        return mapper;
+    }
+
+    /***
+     * 把Json字符串转换成Node对象
+     */
+    public JsonNode getNode(String jsonStr) {
+        try {
+            //mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+            return mapper.readTree(jsonStr);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+}

+ 51 - 0
examcloud-core-print-common/src/main/java/cn/com/qmth/examcloud/core/print/common/utils/Pair.java

@@ -0,0 +1,51 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-12-05 16:06:17.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.common.utils;
+
+import java.io.Serializable;
+
+public class Pair<K, V> implements Serializable {
+    private K key;
+    private V value;
+
+    public Pair(K key, V value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    public V getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return key + "=" + value;
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode() * 13 + (value == null ? 0 : value.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o instanceof Pair) {
+            Pair pair = (Pair) o;
+            if (key != null ? !key.equals(pair.key) : pair.key != null) return false;
+            if (value != null ? !value.equals(pair.value) : pair.value != null) return false;
+            return true;
+        }
+        return false;
+    }
+
+}

+ 20 - 0
examcloud-core-print-dao/pom.xml

@@ -0,0 +1,20 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>examcloud-core-print-dao</artifactId>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-print</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-core-print-common</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 188 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/CoursePaper.java

@@ -0,0 +1,188 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 课程试卷关联信息
+ */
+@Entity
+@Table(name = "ec_prt_course_paper", indexes = {
+        @Index(name = "INDEX_PRT_COURSE_PAPER_01", columnList = "orgId"),
+        @Index(name = "INDEX_PRT_COURSE_PAPER_02", columnList = "examId"),
+        @Index(name = "INDEX_PRT_COURSE_PAPER_03", columnList = "examId,paperId", unique = true)})
+public class CoursePaper extends IdEntity {
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程code
+     */
+    @Column(length = 50)
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    @Column(length = 50)
+    private String courseName;
+    /**
+     * 试卷ID
+     */
+    @Column(length = 50)
+    private String paperId;
+    /**
+     * 试卷名称
+     */
+    @Column(length = 100)
+    private String paperName;
+    /**
+     * 试卷P数(1P为1面,1张纸为2P)
+     */
+    @Column(name = "paper_p")
+    private Integer paperP;
+    /**
+     * 试卷页面地址
+     */
+    private String paperHtmlUrl;
+    /**
+     * 试卷PDF文件地址
+     */
+    private String paperPdfUrl;
+    /**
+     * 答案页面地址
+     */
+    private String answerHtmlUrl;
+    /**
+     * 答案PDF文件地址
+     */
+    private String answerPdfUrl;
+    /**
+     * 是否已转换PDF
+     */
+    private Boolean converted;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public Integer getPaperP() {
+        return paperP;
+    }
+
+    public void setPaperP(Integer paperP) {
+        this.paperP = paperP;
+    }
+
+    public String getPaperHtmlUrl() {
+        return paperHtmlUrl;
+    }
+
+    public void setPaperHtmlUrl(String paperHtmlUrl) {
+        this.paperHtmlUrl = paperHtmlUrl;
+    }
+
+    public String getPaperPdfUrl() {
+        return paperPdfUrl;
+    }
+
+    public void setPaperPdfUrl(String paperPdfUrl) {
+        this.paperPdfUrl = paperPdfUrl;
+    }
+
+    public String getAnswerHtmlUrl() {
+        return answerHtmlUrl;
+    }
+
+    public void setAnswerHtmlUrl(String answerHtmlUrl) {
+        this.answerHtmlUrl = answerHtmlUrl;
+    }
+
+    public String getAnswerPdfUrl() {
+        return answerPdfUrl;
+    }
+
+    public void setAnswerPdfUrl(String answerPdfUrl) {
+        this.answerPdfUrl = answerPdfUrl;
+    }
+
+    public Boolean getConverted() {
+        return converted;
+    }
+
+    public void setConverted(Boolean converted) {
+        this.converted = converted;
+    }
+
+}

+ 139 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/CourseStatistic.java

@@ -0,0 +1,139 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.*;
+
+/**
+ * 课程统计信息
+ */
+@Entity
+@Table(name = "ec_prt_course_statistic", indexes = {
+        @Index(name = "INDEX_PRT_COURSE_STATISTIC_01", columnList = "orgId"),
+        @Index(name = "INDEX_PRT_COURSE_STATISTIC_02", columnList = "examId"),
+        @Index(name = "INDEX_PRT_COURSE_STATISTIC_03", columnList = "courseId")})
+public class CourseStatistic extends IdEntity {
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程code
+     */
+    @Column(length = 50)
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    @Column(length = 50)
+    private String courseName;
+    /**
+     * 试卷类型
+     */
+    @Column(length = 50)
+    private String paperType;
+    /**
+     * @See PaperStatus.java
+     * 考试的试卷状态
+     */
+    private Integer paperStatus;
+    /**
+     * 考生人数
+     */
+    private Integer totalStudent;
+    /**
+     * 绑定的课程试卷ID
+     * (表ec_prt_course_paper的ID)
+     */
+    @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
+    @JoinColumn(name = "course_paper_id")
+    private CoursePaper coursePaper;
+
+    public CoursePaper getCoursePaper() {
+        return coursePaper;
+    }
+
+    public void setCoursePaper(CoursePaper coursePaper) {
+        this.coursePaper = coursePaper;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public Integer getPaperStatus() {
+        return paperStatus;
+    }
+
+    public void setPaperStatus(Integer paperStatus) {
+        this.paperStatus = paperStatus;
+    }
+
+    public Integer getTotalStudent() {
+        return totalStudent;
+    }
+
+    public void setTotalStudent(Integer totalStudent) {
+        this.totalStudent = totalStudent;
+    }
+
+}

+ 56 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ExamQuestionStructure.java

@@ -0,0 +1,56 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import java.io.Serializable;
+
+/**
+ * 试题结构信息
+ *
+ * @See QuesStructType.java
+ */
+public class ExamQuestionStructure implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 单选题数量
+     */
+    private Integer singleChoiceTotal;
+    /**
+     * 多选题数量
+     */
+    private Integer multipleChoiceTotal;
+    /**
+     * 判断题数量
+     */
+    private Integer boolQuestionTotal;
+
+    public Integer getSingleChoiceTotal() {
+        return singleChoiceTotal != null ? singleChoiceTotal : 0;
+    }
+
+    public void setSingleChoiceTotal(Integer singleChoiceTotal) {
+        this.singleChoiceTotal = singleChoiceTotal;
+    }
+
+    public Integer getMultipleChoiceTotal() {
+        return multipleChoiceTotal != null ? multipleChoiceTotal : 0;
+    }
+
+    public void setMultipleChoiceTotal(Integer multipleChoiceTotal) {
+        this.multipleChoiceTotal = multipleChoiceTotal;
+    }
+
+    public Integer getBoolQuestionTotal() {
+        return boolQuestionTotal != null ? boolQuestionTotal : 0;
+    }
+
+    public void setBoolQuestionTotal(Integer boolQuestionTotal) {
+        this.boolQuestionTotal = boolQuestionTotal;
+    }
+
+}

+ 106 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ExamStructure.java

@@ -0,0 +1,106 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 考试结构设置信息
+ */
+@Entity
+@Table(name = "ec_prt_exam_structure", indexes = {
+        @Index(name = "INDEX_PRT_EXAM_STRUCTURE_01", columnList = "orgId,examId", unique = true)})
+public class ExamStructure extends IdEntity {
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称
+     */
+    @Column(length = 100)
+    private String orgName;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 考试名称
+     */
+    @Column(length = 100)
+    private String examName;
+    /**
+     * 考试类型(如:ONLINE,TRADITION)
+     *
+     * @See ExamType.java
+     */
+    @Column(length = 50)
+    private String examType;
+    /**
+     * @See ExamQuestionStructure.java
+     * <p>
+     * 试题结构内容(JSON格式):
+     * {"singleChoiceTotal":10,"multipleChoiceTotal":20,"boolQuestionTotal":30}
+     */
+    @Column(length = 500)
+    private String struct;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public String getExamType() {
+        return examType;
+    }
+
+    public void setExamType(String examType) {
+        this.examType = examType;
+    }
+
+    public String getStruct() {
+        return struct;
+    }
+
+    public void setStruct(String struct) {
+        this.struct = struct;
+    }
+
+}

+ 206 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ObjectiveQuestionStructure.java

@@ -0,0 +1,206 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+import cn.com.qmth.examcloud.core.print.enums.QuesStructType;
+
+import javax.persistence.*;
+import java.util.Map;
+
+/**
+ * 客观题信息(用于Excel导出)
+ */
+@Entity
+@Table(name = "ec_prt_objective_question_structure", indexes = {
+        @Index(name = "INDEX_PRT_OBJECTIVE_QUESTION_01", columnList = "examId"),
+        @Index(name = "INDEX_PRT_OBJECTIVE_QUESTION_02", columnList = "courseId"),
+        @Index(name = "INDEX_PRT_OBJECTIVE_QUESTION_03", columnList = "paperId")})
+public class ObjectiveQuestionStructure extends IdEntity {
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程代码
+     */
+    @Excel(name = "课程代码", orderNum = "0")
+    @Column(length = 50)
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    @Excel(name = "课程名称", orderNum = "1")
+    @Column(length = 50)
+    private String courseName;
+    /**
+     * 试卷ID
+     */
+    @Column(length = 50)
+    private String paperId;
+    /**
+     * 试卷名称
+     */
+    @Excel(name = "试卷名称", orderNum = "2")
+    @Column(length = 100)
+    private String paperName;
+    /**
+     * 大题号
+     */
+    @Excel(name = "大题号", orderNum = "4")
+    private Integer sectionNum;
+    /**
+     * 小题号
+     */
+    @Excel(name = "小题号", orderNum = "5")
+    private Integer unitNum;
+    /**
+     * 小题分数
+     */
+    @Excel(name = "小题分数", orderNum = "8")
+    private Double unitScore;
+    /**
+     * 标准答案
+     */
+    @Excel(name = "标准答案", orderNum = "7")
+    @Column(length = 50)
+    private String answer;
+    /**
+     * 试题类型
+     *
+     * @See QuesStructType.java
+     */
+    @Excel(name = "题目类型", orderNum = "6")
+    @Column(length = 50)
+    private String questionType;
+    /**
+     * 试卷类型(如:A、B)
+     * 该字段不持久化到数据库
+     */
+    @Excel(name = "试卷类型", orderNum = "3")
+    @Transient
+    private String paperType;
+
+    public ObjectiveQuestionStructure(QuesStructType questionType, Map<String, String> properties) {
+        this.courseCode = properties.get("courseCode");
+        this.courseName = properties.get("courseName");
+        this.paperName = properties.get("paperName");
+        this.paperType = properties.get("paperType");
+        this.questionType = questionType.getTitle();
+        this.sectionNum = questionType.getId();
+        this.unitNum = 0;
+        this.unitScore = 0D;
+        this.answer = "#";
+    }
+
+    public ObjectiveQuestionStructure() {
+
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public Integer getSectionNum() {
+        return sectionNum;
+    }
+
+    public void setSectionNum(Integer sectionNum) {
+        this.sectionNum = sectionNum;
+    }
+
+    public Integer getUnitNum() {
+        return unitNum;
+    }
+
+    public void setUnitNum(Integer unitNum) {
+        this.unitNum = unitNum;
+    }
+
+    public Double getUnitScore() {
+        return unitScore;
+    }
+
+    public void setUnitScore(Double unitScore) {
+        this.unitScore = unitScore;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+}

+ 210 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/PrintingProject.java

@@ -0,0 +1,210 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * 印刷项目信息
+ */
+@Entity
+@Table(name = "ec_prt_project", indexes = {
+        @Index(name = "INDEX_PRT_PROJECT_01", columnList = "orgId,examId", unique = true)})
+public class PrintingProject extends IdEntity {
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称(冗余字段)
+     */
+    @Column(length = 100)
+    private String orgName;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 考试名称(冗余字段)
+     */
+    @Column(length = 100)
+    private String examName;
+    /**
+     * 供应商ID(表ecs_core_org的ID)
+     */
+    private Long supplierId;
+    /**
+     * 供应商名称
+     */
+    @Column(length = 50)
+    private String supplierName;
+    /**
+     * 项目经理ID(表ecs_core_user的ID)
+     */
+    private Long pmId;
+    /**
+     * 项目经理名称
+     */
+    @Column(length = 50)
+    private String pmName;
+    /**
+     * 印刷数据准备开始时间
+     */
+    private Date prepareStartTime;
+    /**
+     * 印刷数据准备结束时间
+     */
+    private Date prepareEndTime;
+    /**
+     * 具体印刷开始时间
+     */
+    private Date printStartTime;
+    /**
+     * 具体印刷结束时间
+     */
+    private Date printEndTime;
+    /**
+     * 邮寄开始时间
+     */
+    private Date mailStartTime;
+    /**
+     * 邮寄结束时间
+     */
+    private Date mailEndTime;
+    /**
+     * 项目是否已完成
+     */
+    private Boolean completed;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public Long getSupplierId() {
+        return supplierId;
+    }
+
+    public void setSupplierId(Long supplierId) {
+        this.supplierId = supplierId;
+    }
+
+    public String getSupplierName() {
+        return supplierName;
+    }
+
+    public void setSupplierName(String supplierName) {
+        this.supplierName = supplierName;
+    }
+
+    public Long getPmId() {
+        return pmId;
+    }
+
+    public void setPmId(Long pmId) {
+        this.pmId = pmId;
+    }
+
+    public String getPmName() {
+        return pmName;
+    }
+
+    public void setPmName(String pmName) {
+        this.pmName = pmName;
+    }
+
+    public Date getPrepareStartTime() {
+        return prepareStartTime;
+    }
+
+    public void setPrepareStartTime(Date prepareStartTime) {
+        this.prepareStartTime = prepareStartTime;
+    }
+
+    public Date getPrepareEndTime() {
+        return prepareEndTime;
+    }
+
+    public void setPrepareEndTime(Date prepareEndTime) {
+        this.prepareEndTime = prepareEndTime;
+    }
+
+    public Date getPrintStartTime() {
+        return printStartTime;
+    }
+
+    public void setPrintStartTime(Date printStartTime) {
+        this.printStartTime = printStartTime;
+    }
+
+    public Date getPrintEndTime() {
+        return printEndTime;
+    }
+
+    public void setPrintEndTime(Date printEndTime) {
+        this.printEndTime = printEndTime;
+    }
+
+    public Date getMailStartTime() {
+        return mailStartTime;
+    }
+
+    public void setMailStartTime(Date mailStartTime) {
+        this.mailStartTime = mailStartTime;
+    }
+
+    public Date getMailEndTime() {
+        return mailEndTime;
+    }
+
+    public void setMailEndTime(Date mailEndTime) {
+        this.mailEndTime = mailEndTime;
+    }
+
+    public Boolean getCompleted() {
+        return completed;
+    }
+
+    public void setCompleted(Boolean completed) {
+        this.completed = completed;
+    }
+
+}

+ 86 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/PrintingTemplate.java

@@ -0,0 +1,86 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 印刷模板信息
+ */
+@Entity
+@Table(name = "ec_prt_template", indexes = {
+        @Index(name = "INDEX_PRT_TEMPLATE_01", columnList = "orgId"),
+        @Index(name = "INDEX_PRT_TEMPLATE_02", columnList = "examId")})
+public class PrintingTemplate extends IdEntity {
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * @See TemplateType.java
+     * 模板类型
+     */
+    private Integer templateType;
+    /**
+     * 模板文件名
+     */
+    private String fileName;
+    /**
+     * 模板文件地址
+     */
+    private String fileUrl;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Integer getTemplateType() {
+        return templateType;
+    }
+
+    public void setTemplateType(Integer templateType) {
+        this.templateType = templateType;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+}

+ 153 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectBackupSetting.java

@@ -0,0 +1,153 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+import cn.com.qmth.examcloud.core.print.enums.BackupGroupType;
+
+import javax.persistence.*;
+
+/**
+ * 印刷项目-备份设置信息
+ */
+@Entity
+@Table(name = "ec_prt_project_backup_setting", indexes = {
+        @Index(name = "INDEX_PRT_PROJECT_BACKUP_01", columnList = "projectId", unique = true)})
+public class ProjectBackupSetting extends IdEntity {
+    /**
+     * 项目ID
+     */
+    private Long projectId;
+
+    /* 1、每袋冗余设置 */
+
+    /**
+     * 是否需要每袋备份
+     */
+    private Boolean needEachPkg;
+    /**
+     * 每袋备份比例
+     */
+    private Double eachPkgPercent;
+    /**
+     * 每袋备份最大值
+     */
+    private Double eachPkgMax;
+    /**
+     * 每袋备份最小值
+     */
+    private Double eachPkgMin;
+
+
+    /* 2、单独备份袋 */
+
+    /**
+     * 是否需要单独备份袋
+     */
+    private Boolean needAlonePkg;
+    /**
+     * 归集类型
+     */
+    @Enumerated(EnumType.STRING)
+    private BackupGroupType groupType;
+    /**
+     * 单独备份比例
+     */
+    private Double alonePkgPercent;
+    /**
+     * 单独备份最大值
+     */
+    private Double alonePkgMax;
+    /**
+     * 单独备份最小值
+     */
+    private Double alonePkgMin;
+
+    public Long getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(Long projectId) {
+        this.projectId = projectId;
+    }
+
+    public Boolean getNeedEachPkg() {
+        //为空时,默认值
+        return needEachPkg != null ? needEachPkg : false;
+    }
+
+    public void setNeedEachPkg(Boolean needEachPkg) {
+        this.needEachPkg = needEachPkg;
+    }
+
+    public Boolean getNeedAlonePkg() {
+        //为空时,默认值
+        return needAlonePkg != null ? needAlonePkg : false;
+    }
+
+    public void setNeedAlonePkg(Boolean needAlonePkg) {
+        this.needAlonePkg = needAlonePkg;
+    }
+
+    public Double getEachPkgPercent() {
+        return eachPkgPercent;
+    }
+
+    public void setEachPkgPercent(Double eachPkgPercent) {
+        this.eachPkgPercent = eachPkgPercent;
+    }
+
+    public Double getEachPkgMax() {
+        return eachPkgMax;
+    }
+
+    public void setEachPkgMax(Double eachPkgMax) {
+        this.eachPkgMax = eachPkgMax;
+    }
+
+    public Double getEachPkgMin() {
+        return eachPkgMin;
+    }
+
+    public void setEachPkgMin(Double eachPkgMin) {
+        this.eachPkgMin = eachPkgMin;
+    }
+
+    public BackupGroupType getGroupType() {
+        return groupType;
+    }
+
+    public void setGroupType(BackupGroupType groupType) {
+        this.groupType = groupType;
+    }
+
+    public Double getAlonePkgPercent() {
+        return alonePkgPercent;
+    }
+
+    public void setAlonePkgPercent(Double alonePkgPercent) {
+        this.alonePkgPercent = alonePkgPercent;
+    }
+
+    public Double getAlonePkgMax() {
+        return alonePkgMax;
+    }
+
+    public void setAlonePkgMax(Double alonePkgMax) {
+        this.alonePkgMax = alonePkgMax;
+    }
+
+    public Double getAlonePkgMin() {
+        return alonePkgMin;
+    }
+
+    public void setAlonePkgMin(Double alonePkgMin) {
+        this.alonePkgMin = alonePkgMin;
+    }
+
+}

+ 50 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectOtherSetting.java

@@ -0,0 +1,50 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 印刷项目-其它设置信息
+ */
+@Entity
+@Table(name = "ec_prt_project_other_setting", indexes = {
+        @Index(name = "INDEX_PRT_PROJECT_OTHER_01", columnList = "projectId")})
+public class ProjectOtherSetting extends IdEntity {
+    /**
+     * 项目ID
+     */
+    private Long projectId;
+    /**
+     * 备注
+     */
+    @Column(length = 2000)
+    private String remark;
+
+    public Long getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(Long projectId) {
+        this.projectId = projectId;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+}

+ 132 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/ProjectStatistic.java

@@ -0,0 +1,132 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-02 17:35:31.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+/**
+ * 印刷统计信息
+ */
+@Entity
+@Table(name = "ec_prt_project_statistic", indexes = {
+        @Index(name = "INDEX_PRT_PROJECT_STATISTIC_01", columnList = "projectId", unique = true)})
+public class ProjectStatistic extends IdEntity {
+    /**
+     * 印刷项目ID
+     */
+    private Long projectId;
+    /**
+     * 人科次
+     */
+    private Integer totalStudent;
+    /**
+     * 课程数量
+     */
+    private Integer totalCourse;
+    /**
+     * 试卷数量
+     */
+    private Integer totalPaper;
+    /**
+     * 试卷袋数
+     */
+    private Integer totalPkg;
+    /**
+     * 常规 印刷数量A3
+     */
+    private Integer normalA3;
+    /**
+     * 常规 印刷数量A4
+     */
+    private Integer normalA4;
+    /**
+     * 备份 印刷数量A3
+     */
+    private Integer backupA3;
+    /**
+     * 备份 印刷数量A4
+     */
+    private Integer backupA4;
+
+    public Long getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(Long projectId) {
+        this.projectId = projectId;
+    }
+
+    public Integer getTotalStudent() {
+        return totalStudent;
+    }
+
+    public void setTotalStudent(Integer totalStudent) {
+        this.totalStudent = totalStudent;
+    }
+
+    public Integer getTotalCourse() {
+        return totalCourse;
+    }
+
+    public void setTotalCourse(Integer totalCourse) {
+        this.totalCourse = totalCourse;
+    }
+
+    public Integer getTotalPaper() {
+        return totalPaper;
+    }
+
+    public void setTotalPaper(Integer totalPaper) {
+        this.totalPaper = totalPaper;
+    }
+
+    public Integer getTotalPkg() {
+        return totalPkg;
+    }
+
+    public void setTotalPkg(Integer totalPkg) {
+        this.totalPkg = totalPkg;
+    }
+
+    public Integer getNormalA3() {
+        return normalA3;
+    }
+
+    public void setNormalA3(Integer normalA3) {
+        this.normalA3 = normalA3;
+    }
+
+    public Integer getNormalA4() {
+        return normalA4;
+    }
+
+    public void setNormalA4(Integer normalA4) {
+        this.normalA4 = normalA4;
+    }
+
+    public Integer getBackupA3() {
+        return backupA3;
+    }
+
+    public void setBackupA3(Integer backupA3) {
+        this.backupA3 = backupA3;
+    }
+
+    public Integer getBackupA4() {
+        return backupA4;
+    }
+
+    public void setBackupA4(Integer backupA4) {
+        this.backupA4 = backupA4;
+    }
+
+}

+ 173 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/entity/SubjectiveQuestionStructure.java

@@ -0,0 +1,173 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.entity;
+
+import cn.afterturn.easypoi.excel.annotation.Excel;
+import cn.com.qmth.examcloud.core.print.common.IdEntity;
+
+import javax.persistence.*;
+
+/**
+ * 主观题信息(用于Excel导出)
+ */
+@Entity
+@Table(name = "ec_prt_subjective_question_structure", indexes = {
+        @Index(name = "INDEX_PRT_SUBJECTIVE_QUESTION_01", columnList = "examId"),
+        @Index(name = "INDEX_PRT_SUBJECTIVE_QUESTION_02", columnList = "courseId"),
+        @Index(name = "INDEX_PRT_SUBJECTIVE_QUESTION_03", columnList = "paperId")})
+public class SubjectiveQuestionStructure extends IdEntity {
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程代码
+     */
+    @Excel(name = "课程代码", orderNum = "0")
+    @Column(length = 50)
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    @Excel(name = "课程名称", orderNum = "1")
+    @Column(length = 50)
+    private String courseName;
+    /**
+     * 试卷ID
+     */
+    @Column(length = 50)
+    private String paperId;
+    /**
+     * 试卷名称
+     */
+    @Excel(name = "试卷名称", orderNum = "2")
+    @Column(length = 100)
+    private String paperName;
+    /**
+     * 大题号
+     */
+    @Excel(name = "大题号", orderNum = "4")
+    @Column(length = 50)
+    private Integer sectionNum;
+    /**
+     * 大题名称
+     */
+    @Excel(name = "大题名称", orderNum = "5")
+    @Column(length = 100)
+    private String sectionName;
+    /**
+     * 小题号
+     */
+    @Excel(name = "小题号", orderNum = "6")
+    private Integer unitNum;
+    /**
+     * 小题分数
+     */
+    @Excel(name = "满分", orderNum = "7")
+    private Double unitScore;
+    /**
+     * 试卷类型(如:A、B)
+     * 该字段不持久化到数据库
+     */
+    @Excel(name = "试卷类型", orderNum = "3")
+    @Transient
+    private String paperType;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public Integer getSectionNum() {
+        return sectionNum;
+    }
+
+    public void setSectionNum(Integer sectionNum) {
+        this.sectionNum = sectionNum;
+    }
+
+    public String getSectionName() {
+        return sectionName;
+    }
+
+    public void setSectionName(String sectionName) {
+        this.sectionName = sectionName;
+    }
+
+    public Integer getUnitNum() {
+        return unitNum;
+    }
+
+    public void setUnitNum(Integer unitNum) {
+        this.unitNum = unitNum;
+    }
+
+    public Double getUnitScore() {
+        return unitScore;
+    }
+
+    public void setUnitScore(Double unitScore) {
+        this.unitScore = unitScore;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+}

+ 39 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/BackupGroupType.java

@@ -0,0 +1,39 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+/**
+ * 印刷项目备份归集类型
+ */
+public enum BackupGroupType {
+
+    LEARN_CENTER("学习中心"),
+
+    EXAM_SITE("考点");
+
+    private String title;
+
+    BackupGroupType(String title) {
+        this.title = title;
+    }
+
+    public static boolean isLeanCenter(BackupGroupType type) {
+        if (type == null) {
+            return false;
+        }
+        if (LEARN_CENTER.name().equals(type.name())) {
+            return true;
+        }
+        return false;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+}

+ 49 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/ExamType.java

@@ -0,0 +1,49 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-31 14:40:11.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+/**
+ * 考试类型
+ */
+public enum ExamType {
+    /**
+     * 传统
+     */
+    TRADITION,
+
+    /**
+     * 在线考试(网考)
+     */
+    ONLINE,
+
+    /**
+     * 练习
+     */
+    PRACTICE,
+
+    /**
+     * 离线
+     */
+    OFFLINE,
+
+    /**
+     * 分布式印刷考试
+     */
+    PRINT_EXAM;
+
+    /**
+     * 是否为传统考试
+     */
+    public static boolean isTradition(String name) {
+        if (TRADITION.name().equals(name)) {
+            return true;
+        }
+        return false;
+    }
+
+}

+ 55 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/ExportType.java

@@ -0,0 +1,55 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-20 13:52:45.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+/**
+ * 导出文件类型
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/20
+ */
+public enum ExportType {
+    /**
+     * 试卷
+     */
+    PAPER(1, "试卷"),
+    /**
+     * 答案
+     */
+    ANSWER(2, "答案"),
+    /**
+     * 试卷结构(用于阅卷)
+     * 客观题/主观题结构
+     */
+    STRUCT(3, "试卷结构"),
+    /**
+     * 机考数据包
+     */
+    COMPUTER_EXAM_PKG(4, "机考数据包"),
+    /**
+     * 分布式印刷数据包
+     */
+    PRINT_EXAM_PKG(5, "分布式印刷数据包");
+
+    private int id;
+    private String title;
+
+    ExportType(int id, String title) {
+        this.id = id;
+        this.title = title;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+}

+ 49 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/PaperStatus.java

@@ -0,0 +1,49 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+/**
+ * 传统考试的试卷状态
+ */
+public enum PaperStatus {
+    /**
+     * 试卷尚未从题库中推过来
+     */
+    无(0),
+    /**
+     * 试卷已推过来,但未分配
+     */
+    未指定(1),
+    /**
+     * 试卷已分配
+     */
+    已有(2);
+
+    private int index;
+
+    public static String getNameByIndex(Integer index) {
+        if (index == null) {
+            return null;
+        }
+        for (PaperStatus type : PaperStatus.values()) {
+            if (type.getIndex() == index) {
+                return type.name();
+            }
+        }
+        return null;
+    }
+
+    PaperStatus(int index) {
+        this.index = index;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+}

+ 58 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/QuesStructType.java

@@ -0,0 +1,58 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-30 11:39:43.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+public enum QuesStructType {
+    SINGLE_ANSWER_QUESTION(1, "单选", true, false),
+    MULTIPLE_ANSWER_QUESTION(2, "多选", true, false),
+    BOOL_ANSWER_QUESTION(3, "判断", true, false),
+    FILL_BLANK_QUESTION(4, "填空", false, false),
+    TEXT_ANSWER_QUESTION(5, "问答", false, false),
+    NESTED_ANSWER_QUESTION(6, "套题", false, true);
+
+    private Integer id;
+    private String title;
+    private boolean objective;//是否是客观题
+    private boolean combine;//是否是组合题
+
+    public static QuesStructType getByName(String name) {
+        if (name == null) {
+            return null;
+        }
+        for (QuesStructType type : QuesStructType.values()) {
+            if (type.name().equals(name)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    QuesStructType(Integer id, String title, boolean objective, boolean combine) {
+        this.id = id;
+        this.title = title;
+        this.objective = objective;
+        this.combine = combine;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public boolean isObjective() {
+        return objective;
+    }
+
+    public boolean isCombine() {
+        return combine;
+    }
+
+}

+ 64 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/enums/TemplateType.java

@@ -0,0 +1,64 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.enums;
+
+/**
+ * 模板类型
+ */
+public enum TemplateType {
+    考生数据表(1),
+
+    考场数据表(2),
+
+    卷袋贴模板(3),
+
+    签到表模板(4),
+
+    常规题卡模板(5),
+
+    特殊题卡模板(6),
+
+    备份卷贴模板(7),
+
+    试卷袋样式(8);
+
+    private int index;
+
+    public static boolean isExist(Integer index) {
+        if (index == null) {
+            return false;
+        }
+        for (TemplateType type : TemplateType.values()) {
+            if (type.getIndex() == index) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static String getNameByIndex(Integer index) {
+        if (index == null) {
+            return null;
+        }
+        for (TemplateType type : TemplateType.values()) {
+            if (type.getIndex() == index) {
+                return type.name();
+            }
+        }
+        return null;
+    }
+
+    TemplateType(Integer index) {
+        this.index = index;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+}

+ 29 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/CoursePaperRepository.java

@@ -0,0 +1,29 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.CoursePaper;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface CoursePaperRepository extends JpaRepository<CoursePaper, Long>, JpaSpecificationExecutor<CoursePaper> {
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE CoursePaper SET courseName=:courseName WHERE courseId=:courseId")
+    int updateCourseNameByCourseId(@Param("courseId") Long courseId, @Param("courseName") String courseName);
+
+    CoursePaper findByExamIdAndPaperId(Long examId, String paperId);
+
+}

+ 36 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/CourseStatisticRepository.java

@@ -0,0 +1,36 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.CourseStatistic;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface CourseStatisticRepository extends JpaRepository<CourseStatistic, Long>, JpaSpecificationExecutor<CourseStatistic> {
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE CourseStatistic SET courseName=:courseName WHERE courseId=:courseId")
+    int updateCourseNameByCourseId(@Param("courseId") Long courseId, @Param("courseName") String courseName);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE CourseStatistic SET totalStudent=:totalStudent WHERE orgId=:orgId AND examId=:examId AND courseId=:courseId AND paperType=:paperType")
+    int updateTotalStudentByOrgExamCourse(@Param("orgId") Long orgId, @Param("examId") Long examId, @Param("courseId") Long courseId, @Param("paperType") String paperType, @Param("totalStudent") Integer totalStudent);
+
+    int countByOrgIdAndExamId(Long orgId, Long examId);
+
+    int countByOrgIdAndExamIdAndPaperStatus(Long orgId, Long examId, int paperStatus);
+
+}

+ 34 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ExamStructureRepository.java

@@ -0,0 +1,34 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.ExamStructure;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface ExamStructureRepository extends JpaRepository<ExamStructure, Long>, JpaSpecificationExecutor<ExamStructure> {
+
+    ExamStructure findByOrgIdAndExamId(Long orgId, Long examId);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE ExamStructure SET orgName=:orgName WHERE orgId=:orgId")
+    int updateOrgNameByOrgId(@Param("orgId") Long orgId, @Param("orgName") String orgName);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE ExamStructure SET examName=:examName WHERE examId=:examId")
+    int updateExamNameByExamId(@Param("examId") Long examId, @Param("examName") String examName);
+
+}

+ 34 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ObjectiveQuestionStructureRepository.java

@@ -0,0 +1,34 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.ObjectiveQuestionStructure;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface ObjectiveQuestionStructureRepository extends JpaRepository<ObjectiveQuestionStructure, Long>, JpaSpecificationExecutor<ObjectiveQuestionStructure> {
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE ObjectiveQuestionStructure SET courseName=:courseName WHERE courseId=:courseId")
+    int updateCourseNameByCourseId(@Param("courseId") Long courseId, @Param("courseName") String courseName);
+
+    @Transactional
+    @Modifying
+    @Query("DELETE FROM ObjectiveQuestionStructure WHERE examId=:examId AND paperId=:paperId")
+    int deleteByExamIdAndPaperId(@Param("examId") Long examId, @Param("paperId") String paperId);
+
+    long countByExamIdAndPaperIdAndQuestionType(Long examId, String paperId, String questionType);
+
+}

+ 44 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/PrintingProjectRepository.java

@@ -0,0 +1,44 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.PrintingProject;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface PrintingProjectRepository extends JpaRepository<PrintingProject, Long>, JpaSpecificationExecutor<PrintingProject> {
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE PrintingProject SET orgName=:orgName WHERE orgId=:orgId")
+    int updateOrgNameByOrgId(@Param("orgId") Long orgId, @Param("orgName") String orgName);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE PrintingProject SET examName=:examName WHERE examId=:examId")
+    int updateExamNameByExamId(@Param("examId") Long examId, @Param("examName") String examName);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE PrintingProject SET pmName=:pmName WHERE pmId=:pmId")
+    int updatePmNameByPmId(@Param("pmId") Long pmId, @Param("pmName") String pmName);
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE PrintingProject SET supplierName=:supplierName WHERE supplierId=:supplierId")
+    int updateSupplierNameBySupplierId(@Param("supplierId") Long supplierId, @Param("supplierName") String supplierName);
+
+    PrintingProject findByOrgIdAndExamId(Long orgId, Long examId);
+
+}

+ 18 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/PrintingTemplateRepository.java

@@ -0,0 +1,18 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.PrintingTemplate;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PrintingTemplateRepository extends JpaRepository<PrintingTemplate, Long>, JpaSpecificationExecutor<PrintingTemplate> {
+
+}

+ 29 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectBackupSettingRepository.java

@@ -0,0 +1,29 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.ProjectBackupSetting;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface ProjectBackupSettingRepository extends JpaRepository<ProjectBackupSetting, Long>, JpaSpecificationExecutor<ProjectBackupSetting> {
+
+    @Transactional
+    @Modifying
+    @Query("DELETE FROM ProjectBackupSetting WHERE projectId=:projectId")
+    int deleteByProjectId(@Param("projectId") Long projectId);
+
+    ProjectBackupSetting findByProjectId(Long projectId);
+
+}

+ 18 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectOtherSettingRepository.java

@@ -0,0 +1,18 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.ProjectOtherSetting;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ProjectOtherSettingRepository extends JpaRepository<ProjectOtherSetting, Long>, JpaSpecificationExecutor<ProjectOtherSetting> {
+
+}

+ 20 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/ProjectStatisticRepository.java

@@ -0,0 +1,20 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-02 17:44:58.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.ProjectStatistic;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ProjectStatisticRepository extends JpaRepository<ProjectStatistic, Long>, JpaSpecificationExecutor<ProjectStatistic> {
+
+    ProjectStatistic getProjectStatisticByProjectId(Long projectId);
+
+}

+ 32 - 0
examcloud-core-print-dao/src/main/java/cn/com/qmth/examcloud/core/print/repository/SubjectiveQuestionStructureRepository.java

@@ -0,0 +1,32 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:28:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.repository;
+
+import cn.com.qmth.examcloud.core.print.entity.SubjectiveQuestionStructure;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public interface SubjectiveQuestionStructureRepository extends JpaRepository<SubjectiveQuestionStructure, Long>, JpaSpecificationExecutor<SubjectiveQuestionStructure> {
+
+    @Transactional
+    @Modifying
+    @Query("UPDATE SubjectiveQuestionStructure SET courseName=:courseName WHERE courseId=:courseId")
+    int updateCourseNameByCourseId(@Param("courseId") Long courseId, @Param("courseName") String courseName);
+
+    @Transactional
+    @Modifying
+    @Query("DELETE FROM SubjectiveQuestionStructure WHERE examId=:examId AND paperId=:paperId")
+    int deleteByExamIdAndPaperId(@Param("examId") Long examId, @Param("paperId") String paperId);
+
+}

+ 211 - 0
examcloud-core-print-dao/src/main/resources/db-schema.sql

@@ -0,0 +1,211 @@
+DROP TABLE ec_prt_project;
+DROP TABLE ec_prt_project_backup_setting;
+DROP TABLE ec_prt_project_other_setting;
+DROP TABLE ec_prt_project_statistic;
+DROP TABLE ec_prt_course_statistic;
+DROP TABLE ec_prt_course_paper;
+DROP TABLE ec_prt_template;
+DROP TABLE ec_prt_exam_structure;
+DROP TABLE ec_prt_objective_question_structure;
+DROP TABLE ec_prt_subjective_question_structure;
+
+CREATE TABLE ec_prt_course_paper (
+  id              bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time   datetime            DEFAULT NULL,
+  update_time     datetime            DEFAULT NULL,
+  answer_html_url varchar(255)        DEFAULT NULL,
+  answer_pdf_url  varchar(255)        DEFAULT NULL,
+  converted       bit(1)              DEFAULT NULL,
+  course_code     varchar(50)         DEFAULT NULL,
+  course_id       bigint(20)          DEFAULT NULL,
+  course_name     varchar(50)         DEFAULT NULL,
+  exam_id         bigint(20)          DEFAULT NULL,
+  org_id          bigint(20)          DEFAULT NULL,
+  paper_html_url  varchar(255)        DEFAULT NULL,
+  paper_id        varchar(50)         DEFAULT NULL,
+  paper_name      varchar(100)        DEFAULT NULL,
+  paper_p         int(11)             DEFAULT NULL,
+  paper_pdf_url   varchar(255)        DEFAULT NULL,
+  PRIMARY KEY (id),
+  UNIQUE KEY INDEX_PRT_COURSE_PAPER_03 (exam_id, paper_id),
+  KEY INDEX_PRT_COURSE_PAPER_01 (org_id),
+  KEY INDEX_PRT_COURSE_PAPER_02 (exam_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_course_statistic (
+  id              bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time   datetime            DEFAULT NULL,
+  update_time     datetime            DEFAULT NULL,
+  course_code     varchar(50)         DEFAULT NULL,
+  course_id       bigint(20)          DEFAULT NULL,
+  course_name     varchar(50)         DEFAULT NULL,
+  exam_id         bigint(20)          DEFAULT NULL,
+  org_id          bigint(20)          DEFAULT NULL,
+  paper_status    int(11)             DEFAULT NULL,
+  paper_type      varchar(50)         DEFAULT NULL,
+  total_student   int(11)             DEFAULT NULL,
+  course_paper_id bigint(20)          DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY INDEX_PRT_COURSE_STATISTIC_01 (org_id),
+  KEY INDEX_PRT_COURSE_STATISTIC_02 (exam_id),
+  KEY INDEX_PRT_COURSE_STATISTIC_03 (course_id),
+  KEY FK7nnn568j5img7r3fjbsni2t25 (course_paper_id),
+  CONSTRAINT FK7nnn568j5img7r3fjbsni2t25 FOREIGN KEY (course_paper_id) REFERENCES ec_prt_course_paper (id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_exam_structure (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  exam_id       bigint(20)          DEFAULT NULL,
+  exam_name     varchar(100)         DEFAULT NULL,
+  exam_type     varchar(50)         DEFAULT NULL,
+  org_id        bigint(20)          DEFAULT NULL,
+  org_name      varchar(100)         DEFAULT NULL,
+  struct        varchar(500)        DEFAULT NULL,
+  PRIMARY KEY (id),
+  UNIQUE KEY INDEX_PRT_EXAM_STRUCTURE_01 (org_id, exam_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_objective_question_structure (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  answer        varchar(50)         DEFAULT NULL,
+  course_code   varchar(50)         DEFAULT NULL,
+  course_id     bigint(20)          DEFAULT NULL,
+  course_name   varchar(50)         DEFAULT NULL,
+  exam_id       bigint(20)          DEFAULT NULL,
+  paper_id      varchar(50)         DEFAULT NULL,
+  paper_name    varchar(100)        DEFAULT NULL,
+  question_type varchar(50)         DEFAULT NULL,
+  section_num   int(11)             DEFAULT NULL,
+  unit_num      int(11)             DEFAULT NULL,
+  unit_score    double              DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY INDEX_PRT_OBJECTIVE_QUESTION_01 (exam_id),
+  KEY INDEX_PRT_OBJECTIVE_QUESTION_02 (course_id),
+  KEY INDEX_PRT_OBJECTIVE_QUESTION_03 (paper_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_project (
+  id                 bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time      datetime            DEFAULT NULL,
+  update_time        datetime            DEFAULT NULL,
+  completed          bit(1)              DEFAULT NULL,
+  exam_id            bigint(20)          DEFAULT NULL,
+  exam_name          varchar(100)         DEFAULT NULL,
+  mail_end_time      datetime            DEFAULT NULL,
+  mail_start_time    datetime            DEFAULT NULL,
+  org_id             bigint(20)          DEFAULT NULL,
+  org_name           varchar(100)         DEFAULT NULL,
+  pm_id              bigint(20)          DEFAULT NULL,
+  pm_name            varchar(50)         DEFAULT NULL,
+  prepare_end_time   datetime            DEFAULT NULL,
+  prepare_start_time datetime            DEFAULT NULL,
+  print_end_time     datetime            DEFAULT NULL,
+  print_start_time   datetime            DEFAULT NULL,
+  supplier_id        bigint(20)          DEFAULT NULL,
+  supplier_name      varchar(50)         DEFAULT NULL,
+  PRIMARY KEY (id),
+  UNIQUE KEY INDEX_PRT_PROJECT_01 (org_id, exam_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_project_backup_setting (
+  id                bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time     datetime            DEFAULT NULL,
+  update_time       datetime            DEFAULT NULL,
+  alone_pkg_max     double              DEFAULT NULL,
+  alone_pkg_min     double              DEFAULT NULL,
+  alone_pkg_percent double              DEFAULT NULL,
+  each_pkg_max      double              DEFAULT NULL,
+  each_pkg_min      double              DEFAULT NULL,
+  each_pkg_percent  double              DEFAULT NULL,
+  group_type        varchar(255)        DEFAULT NULL,
+  need_alone_pkg    bit(1)              DEFAULT NULL,
+  need_each_pkg     bit(1)              DEFAULT NULL,
+  project_id        bigint(20)          DEFAULT NULL,
+  PRIMARY KEY (id),
+  UNIQUE KEY INDEX_PRT_PROJECT_BACKUP_01 (project_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_project_other_setting (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  project_id    bigint(20)          DEFAULT NULL,
+  remark        varchar(2000)       DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY INDEX_PRT_PROJECT_OTHER_01 (project_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_project_statistic (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  backupa3      int(11)             DEFAULT NULL,
+  backupa4      int(11)             DEFAULT NULL,
+  normala3      int(11)             DEFAULT NULL,
+  normala4      int(11)             DEFAULT NULL,
+  project_id    bigint(20)          DEFAULT NULL,
+  total_course  int(11)             DEFAULT NULL,
+  total_paper   int(11)             DEFAULT NULL,
+  total_pkg     int(11)             DEFAULT NULL,
+  total_student int(11)             DEFAULT NULL,
+  PRIMARY KEY (id),
+  UNIQUE KEY INDEX_PRT_PROJECT_STATISTIC_01 (project_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_subjective_question_structure (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  course_code   varchar(50)         DEFAULT NULL,
+  course_id     bigint(20)          DEFAULT NULL,
+  course_name   varchar(50)         DEFAULT NULL,
+  exam_id       bigint(20)          DEFAULT NULL,
+  paper_id      varchar(50)         DEFAULT NULL,
+  paper_name    varchar(100)        DEFAULT NULL,
+  section_name  varchar(100)        DEFAULT NULL,
+  section_num   int(11)             DEFAULT NULL,
+  unit_num      int(11)             DEFAULT NULL,
+  unit_score    double              DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY INDEX_PRT_SUBJECTIVE_QUESTION_01 (exam_id),
+  KEY INDEX_PRT_SUBJECTIVE_QUESTION_02 (course_id),
+  KEY INDEX_PRT_SUBJECTIVE_QUESTION_03 (paper_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;
+
+CREATE TABLE ec_prt_template (
+  id            bigint(20) NOT NULL AUTO_INCREMENT,
+  creation_time datetime            DEFAULT NULL,
+  update_time   datetime            DEFAULT NULL,
+  exam_id       bigint(20)          DEFAULT NULL,
+  file_name     varchar(255)        DEFAULT NULL,
+  file_url      varchar(255)        DEFAULT NULL,
+  org_id        bigint(20)          DEFAULT NULL,
+  template_type int(11)             DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY INDEX_PRT_TEMPLATE_01 (org_id),
+  KEY INDEX_PRT_TEMPLATE_02 (exam_id)
+)
+  ENGINE = InnoDB
+  DEFAULT CHARSET = utf8;

+ 21 - 0
examcloud-core-print-provider/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>examcloud-core-print-provider</artifactId>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-print</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-core-print-service</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 127 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/bean/UserInfo.java

@@ -0,0 +1,127 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-16 16:01:07.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.Role;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
+import cn.com.qmth.examcloud.core.print.service.bean.common.OrgInfo;
+
+import java.util.List;
+
+/**
+ * 用户信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/16
+ */
+public class UserInfo {
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 用户名称
+     */
+    private String userName;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称
+     */
+    private String orgName;
+    /**
+     * 是否为印刷总负责人
+     */
+    private boolean isSuperLeader;
+    /**
+     * 是否为项目经理
+     */
+    private boolean isPM;
+    /**
+     * 是否为印刷供应商
+     */
+    private boolean isSupplier;
+    /**
+     * 是否为印刷学校管理员
+     */
+    private boolean isSchoolLeader;
+    /**
+     * 其它
+     */
+    private boolean isOther;
+
+    public UserInfo(User user) {
+        this.userId = user.getUserId();
+        this.userName = user.getDisplayName();
+        this.orgId = user.getRootOrgId();
+        this.orgName = user.getRootOrgName();
+
+        //角色类型
+        List<Role> roles = user.getRoleList();
+        if (roles == null || roles.isEmpty()) {
+            return;
+        }
+        for (Role role : roles) {
+            if (RoleMeta.PRINT_SUPER_LEADER.name().equals(role.getRoleCode())) {
+                this.isSuperLeader = true;
+            } else if (RoleMeta.PRINT_PROJECT_LEADER.name().equals(role.getRoleCode())) {
+                this.isPM = true;
+            } else if (RoleMeta.PRINT_SUPPLIER.name().equals(role.getRoleCode())) {
+                this.isSupplier = true;
+            } else if (RoleMeta.PRINT_SCHOOL_LEADER.name().equals(role.getRoleCode())) {
+                this.isSchoolLeader = true;
+            } else {
+                this.isOther = true;
+            }
+        }
+    }
+
+    public OrgInfo getOrgInfo() {
+        return new OrgInfo(orgId, orgName);
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public boolean isSuperLeader() {
+        return isSuperLeader;
+    }
+
+    public boolean isPM() {
+        return isPM;
+    }
+
+    public boolean isSupplier() {
+        return isSupplier;
+    }
+
+    public boolean isSchoolLeader() {
+        return isSchoolLeader;
+    }
+
+    public boolean isOther() {
+        return isOther;
+    }
+
+}

+ 83 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CommonController.java

@@ -0,0 +1,83 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 16:01:30.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.common.upyun.UpYunClient;
+import cn.com.qmth.examcloud.core.print.common.utils.FileUtils;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+
+import static cn.com.qmth.examcloud.core.print.common.Constants.SYS_CODE_400;
+import static cn.com.qmth.examcloud.core.print.common.Constants.SYS_CODE_500;
+
+/**
+ * 常用相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+@RestController
+@Api(tags = "常用相关接口")
+@RequestMapping("${$rmp.ctrl.print}/common")
+public class CommonController extends ControllerSupport {
+    private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+    @Autowired
+    private UpYunClient upYunClient;
+
+    @Naked
+    @PostMapping("/upload")
+    @ApiOperation(value = "上传文件")
+    public Result upload(HttpServletRequest request) throws Exception {
+        MultipartFile file = null;
+        try {
+            //暂时只支持单文件上传,默认处理一个文件
+            file = ((MultipartHttpServletRequest) request).getFile("file");
+        } catch (Exception e) {
+            log.warn(e.getMessage());
+        }
+
+        if (file == null || file.isEmpty()) {
+            throw new StatusException(SYS_CODE_400, "上传的文件不能为空!");
+        }
+
+        String fileUrl = upYunClient.upload(file.getBytes(), file.getOriginalFilename());
+        if (StringUtils.isNotEmpty(fileUrl)) {
+            return Result.success(fileUrl);
+        }
+
+        throw new StatusException(SYS_CODE_500, "上传失败!");
+    }
+
+    @Naked
+    @GetMapping("/download")
+    @ApiOperation(value = "下载文件")
+    public void download(@RequestParam String filePath) throws Exception {
+        File file = upYunClient.download(filePath);
+        if (file != null) {
+            super.exportFile(FileUtils.getFileName(filePath), file);
+        } else {
+            //文件不存在
+            super.exportFile("404", new byte[0]);
+        }
+    }
+
+}

+ 101 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CoursePaperController.java

@@ -0,0 +1,101 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-13 16:34:46.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.CoursePaperService;
+import cn.com.qmth.examcloud.core.print.service.ExamStructureService;
+import cn.com.qmth.examcloud.core.print.service.bean.coursepaper.*;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.File;
+import java.util.List;
+
+import static cn.com.qmth.examcloud.core.print.common.Constants.STRUCT_ZIP_NAME;
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 课程试卷相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/13
+ */
+@RestController
+@Api(tags = "课程试卷相关接口")
+@RequestMapping("${$rmp.ctrl.print}/course/paper")
+public class CoursePaperController extends ControllerSupport {
+    @Autowired
+    private CoursePaperService coursePaperService;
+    @Autowired
+    private ExamStructureService examStructureService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取课程试卷列表")
+    public List<CoursePaperLessInfo> list(@RequestBody CoursePaperQuery query) {
+        return coursePaperService.getCoursePaperList(query);
+    }
+
+    @PostMapping("/allot/{courseStatisticId}/{coursePaperId}")
+    @ApiOperation(value = "(单个)分配待指定试卷")
+    public Result allotCoursePaper(@PathVariable Long courseStatisticId, @PathVariable Long coursePaperId) {
+        coursePaperService.allotCoursePaper(courseStatisticId, coursePaperId);
+        return success();
+    }
+
+    @PostMapping("/allot/all/{orgId}/{examId}")
+    @ApiOperation(value = "(整体)分配待指定试卷")
+    public Result allotAllCoursePaper(@PathVariable Long orgId, @PathVariable Long examId) {
+        coursePaperService.allotAllCoursePaper(orgId, examId);
+        return success();
+    }
+
+    @PostMapping("/total/{orgId}/{examId}")
+    @ApiOperation(value = "获取某学校考试的试卷数量情况")
+    public CoursePaperTotalInfo paperTotal(@PathVariable Long orgId, @PathVariable Long examId) {
+        return coursePaperService.getPaperTotalByOrgIdAndExamId(orgId, examId);
+    }
+
+    @PostMapping("/check/structure/{orgId}/{examId}/{paperId}")
+    @ApiOperation(value = "校验考试试卷结构")
+    public Result checkPaperStructure(@PathVariable Long orgId, @PathVariable Long examId, @PathVariable String paperId) {
+        examStructureService.checkExamStructure(orgId, examId, paperId);
+        return success();
+    }
+
+    @Naked
+    @GetMapping("/export/batch")
+    @ApiOperation(value = "批量导出")
+    public void exportBatch(@RequestParam Long[] ids, @RequestParam Integer[] types) {
+        ExportBatchReq req = new ExportBatchReq(ids, types);
+        File file = coursePaperService.exportBatchCoursePaper(req);
+        if (file != null) {
+            super.exportFile(STRUCT_ZIP_NAME, file);
+        } else {
+            super.exportFile(STRUCT_ZIP_NAME, new byte[]{});
+        }
+    }
+
+    @Naked
+    @GetMapping("/export/all")
+    @ApiOperation(value = "整体导出")
+    public void exportAll(@RequestParam Long orgId, @RequestParam Long examId, @RequestParam Integer[] types) {
+        ExportAllReq req = new ExportAllReq(orgId, examId, types);
+        File file = coursePaperService.exportAllCoursePaper(req);
+        if (file != null) {
+            super.exportFile(STRUCT_ZIP_NAME, file);
+        } else {
+            super.exportFile(STRUCT_ZIP_NAME, new byte[]{});
+        }
+    }
+
+}

+ 60 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/CourseStatisticController.java

@@ -0,0 +1,60 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 09:37:37.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.CourseStatisticService;
+import cn.com.qmth.examcloud.core.print.service.bean.common.RefreshInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticQuery;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticRefreshReq;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 课程统计相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+@RestController
+@Api(tags = "课程统计相关接口")
+@RequestMapping("${$rmp.ctrl.print}/course/statistic")
+public class CourseStatisticController extends ControllerSupport {
+    @Autowired
+    private CourseStatisticService courseStatisticService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取课程统计信息列表(分页)")
+    public Page<CourseStatisticInfo> list(@RequestBody CourseStatisticQuery query) {
+        return courseStatisticService.getCourseStatisticList(query);
+    }
+
+    @PostMapping("/refresh")
+    @ApiOperation(value = "刷新某些课程的统计信息")
+    public Result refresh(@RequestBody CourseStatisticRefreshReq form) {
+        courseStatisticService.refreshCourseStatistic(form);
+        return success();
+    }
+
+    @GetMapping("/refresh/check")
+    @ApiOperation(value = "检查课程统计任务是否正在刷新中")
+    public Result refreshCheck(@RequestParam(required = false) Boolean reset) {
+        if (reset != null) {
+            RefreshInfo.coursesRefreshing = reset;
+        }
+        return success(RefreshInfo.coursesRefreshing);
+    }
+
+}

+ 62 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ExamStructureController.java

@@ -0,0 +1,62 @@
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.ExamStructureService;
+import cn.com.qmth.examcloud.core.print.service.bean.examstructure.ExamStructureInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.examstructure.ExamStructureQuery;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 考试结构相关接口
+ *
+ * @author: weiwenhai
+ * @since: 2018.10.30
+ * @author: fengdesheng
+ * @update: 2018/11/22
+ */
+@RestController
+@Api(tags = "考试结构相关接口")
+@RequestMapping("${$rmp.ctrl.print}/examStructure")
+public class ExamStructureController extends ControllerSupport {
+    @Autowired
+    private ExamStructureService examStructureService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取考试结构信息列表(分页)")
+    public Page<ExamStructureInfo> getExamStructureList(@RequestBody ExamStructureQuery query) {
+        return examStructureService.getExamStructureList(query);
+    }
+
+    @PostMapping("/{orgId}/{examId}")
+    @ApiOperation(value = "获取某个考试结构信息")
+    public ExamStructureInfo getExamStructure(@PathVariable Long orgId, @PathVariable Long examId) {
+        return examStructureService.getExamStructure(orgId, examId);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation(value = "保存某个考试结构信息")
+    public Result saveExamStructure(@RequestBody ExamStructureInfo info) {
+        examStructureService.saveExamStructure(info);
+        return Result.success();
+    }
+
+    @PostMapping("/clone")
+    @ApiOperation(value = "复用某个考试结构信息")
+    public Result cloneExamStructure(@RequestBody ExamStructureInfo info) {
+        examStructureService.cloneExamStructure(info);
+        return Result.success();
+    }
+
+    @PostMapping("/delete/{ids}")
+    @ApiOperation(value = "删除某个考试结构信息")
+    public Result deleteExamStructure(@PathVariable Long[] ids) {
+        examStructureService.deleteExamStructure(ids);
+        return Result.success();
+    }
+
+}

+ 95 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingProjectController.java

@@ -0,0 +1,95 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:33:36.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.api.bean.UserInfo;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.PrintingProjectService;
+import cn.com.qmth.examcloud.core.print.service.bean.common.ExamInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.common.OrgInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.PrintingProjectInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.PrintingProjectQuery;
+import com.google.common.collect.Lists;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 印刷项目相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+@RestController
+@Api(tags = "印刷项目相关接口")
+@RequestMapping("${$rmp.ctrl.print}/printing/project")
+public class PrintingProjectController extends ControllerSupport {
+    @Autowired
+    private PrintingProjectService printingProjectService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取印刷项目列表(分页)")
+    public Page<PrintingProjectInfo> getExamStudentList(@RequestBody PrintingProjectQuery query) {
+        return printingProjectService.getPrintingProjectList(query);
+    }
+
+    @PostMapping("/{id}")
+    @ApiOperation(value = "获取某印刷项目的信息")
+    public PrintingProjectInfo getExamStudent(@PathVariable Long id) {
+        return printingProjectService.getPrintingProjectById(id);
+    }
+
+    @PostMapping("/update")
+    @ApiOperation(value = "更新印刷项目信息")
+    public Result updateExamStudent(@RequestBody PrintingProjectInfo info) {
+        printingProjectService.updatePrintingProject(info);
+        return success();
+    }
+
+    @PostMapping("/org/list")
+    @ApiOperation(value = "获取当前用户相关的学校列表")
+    public List<OrgInfo> getOrgList() {
+        //根据登录用户获取该用户相关的学校数据
+        UserInfo user = new UserInfo(getAccessUser());
+
+        if (user.isSchoolLeader()) {
+            //学校印刷管理员直接返回该用户的学校机构信息
+            return Lists.newArrayList(user.getOrgInfo());
+        }
+
+        if (user.isPM()) {
+            return printingProjectService.getOrgList(user.getUserId(), null);
+        } else if (user.isSupplier()) {
+            return printingProjectService.getOrgList(null, user.getUserId());
+        } else {
+            return printingProjectService.getOrgList(null, null);
+        }
+    }
+
+    @PostMapping("/exam/list")
+    @ApiOperation(value = "获取当前用户相关的考试列表")
+    public List<ExamInfo> getExamList(@RequestParam Long orgId) {
+        //根据登录用户获取该用户相关的考试数据
+        UserInfo user = new UserInfo(getAccessUser());
+        if (user.isPM()) {
+            return printingProjectService.getExamList(orgId, user.getUserId(), null);
+        } else if (user.isSupplier()) {
+            return printingProjectService.getExamList(orgId, null, user.getUserId());
+        } else {
+            return printingProjectService.getExamList(orgId, null, null);
+        }
+    }
+
+}

+ 57 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingProjectStatisticController.java

@@ -0,0 +1,57 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-26 18:18:27.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.PrintingProjectStatisticService;
+import cn.com.qmth.examcloud.core.print.service.bean.common.RefreshInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingprojectstatistic.PrintingProjectStatisticInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 印刷项目统计相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+@RestController
+@Api(tags = "印刷项目统计相关接口")
+@RequestMapping("${$rmp.ctrl.print}/printing/project/statistic")
+public class PrintingProjectStatisticController extends ControllerSupport {
+    @Autowired
+    private PrintingProjectStatisticService printingProjectStatisticService;
+
+    @PostMapping("/{orgId}/{examId}")
+    @ApiOperation(value = "获取某个印刷项目的统计信息")
+    public PrintingProjectStatisticInfo list(@PathVariable Long orgId, @PathVariable Long examId) {
+        return printingProjectStatisticService.getPrintingProjectStatistic(orgId, examId);
+    }
+
+    @PostMapping("/refresh/{orgId}/{examId}")
+    @ApiOperation(value = "刷新某个印刷项目的统计信息")
+    public Result refresh(@PathVariable Long orgId, @PathVariable Long examId) {
+        printingProjectStatisticService.refreshPrintingProjectStatistic(orgId, examId);
+        return success();
+    }
+
+    @GetMapping("/refresh/check")
+    @ApiOperation(value = "检查项目统计任务是否正在刷新中")
+    public Result refreshCheck(@RequestParam(required = false) Boolean reset) {
+        if (reset != null) {
+            RefreshInfo.projectRefreshing = reset;
+        }
+        return success(RefreshInfo.projectRefreshing);
+    }
+
+}

+ 55 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/PrintingTemplateController.java

@@ -0,0 +1,55 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 10:24:13.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.service.PrintingTemplateService;
+import cn.com.qmth.examcloud.core.print.service.bean.printingtemplate.PrintingTemplateInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 印刷模板相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+@RestController
+@Api(tags = "印刷模板相关接口")
+@RequestMapping("${$rmp.ctrl.print}/project/template")
+public class PrintingTemplateController extends ControllerSupport {
+    @Autowired
+    private PrintingTemplateService printingTemplateService;
+
+    @PostMapping("/{orgId}/{examId}")
+    @ApiOperation(value = "获取模板信息列表")
+    public List<PrintingTemplateInfo> list(@PathVariable Long orgId, @PathVariable Long examId) {
+        return printingTemplateService.getPrintingTemplateList(orgId, examId);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation(value = "保存或更新某个模板信息")
+    public Result save(@RequestBody PrintingTemplateInfo info) {
+        printingTemplateService.savePrintingTemplate(info);
+        return success();
+    }
+
+    @PostMapping("/{id}")
+    @ApiOperation(value = "获取某个模板信息")
+    public PrintingTemplateInfo get(@PathVariable Long id) {
+        return printingTemplateService.getPrintingTemplateById(id);
+    }
+
+}

+ 55 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ProjectBackupSettingController.java

@@ -0,0 +1,55 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-26 14:32:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.entity.ProjectBackupSetting;
+import cn.com.qmth.examcloud.core.print.service.ProjectBackupSettingService;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.ProjectBackupSettingInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 印刷项目-备份设置相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+@RestController
+@Api(tags = "印刷项目-备份设置相关接口")
+@RequestMapping("${$rmp.ctrl.print}/project/backup/setting")
+public class ProjectBackupSettingController extends ControllerSupport {
+    @Autowired
+    private ProjectBackupSettingService projectBackupSettingService;
+
+    @PostMapping("/{projectId}")
+    @ApiOperation(value = "获取某个项目的备份设置信息")
+    public ProjectBackupSetting get(@PathVariable Long projectId) {
+        return projectBackupSettingService.getProjectBackupSettingById(projectId);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation(value = "保存或更新某个项目的备份设置信息")
+    public Result save(@RequestBody ProjectBackupSettingInfo info) {
+        projectBackupSettingService.saveProjectBackupSetting(info);
+        return success();
+    }
+
+    @PostMapping("/delete/{projectId}")
+    @ApiOperation(value = "清除某个项目的备份设置信息")
+    public Result delete(@PathVariable Long projectId) {
+        projectBackupSettingService.removeProjectBackupSettingById(projectId);
+        return success();
+    }
+
+}

+ 63 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/controller/ProjectOtherSettingController.java

@@ -0,0 +1,63 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-26 14:33:31.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.controller;
+
+import cn.com.qmth.examcloud.core.print.common.Result;
+import cn.com.qmth.examcloud.core.print.entity.ProjectOtherSetting;
+import cn.com.qmth.examcloud.core.print.service.ProjectOtherSettingService;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.ProjectOtherSettingInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.com.qmth.examcloud.core.print.common.Result.success;
+
+/**
+ * 印刷项目-其它事项设置相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+@RestController
+@Api(tags = "印刷项目-其它事项设置相关接口")
+@RequestMapping("${$rmp.ctrl.print}/project/other/setting")
+public class ProjectOtherSettingController extends ControllerSupport {
+    @Autowired
+    private ProjectOtherSettingService projectOtherSettingService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取其它事项设置列表")
+    public List<ProjectOtherSetting> list(@RequestParam Long projectId) {
+        return projectOtherSettingService.getProjectOtherSettingList(projectId);
+    }
+
+    @PostMapping("/{id}")
+    @ApiOperation(value = "获取某个其它事项设置的信息")
+    public ProjectOtherSetting get(@PathVariable Long id) {
+        return projectOtherSettingService.getProjectOtherSettingById(id);
+    }
+
+    @PostMapping("/save")
+    @ApiOperation(value = "保存或更新某个其它事项设置信息")
+    public Result save(@RequestBody ProjectOtherSettingInfo info) {
+        projectOtherSettingService.saveProjectOtherSetting(info);
+        return success();
+    }
+
+    @PostMapping("/delete/{id}")
+    @ApiOperation(value = "删除某个其它事项设置信息")
+    public Result delete(@PathVariable Long id) {
+        projectOtherSettingService.deleteProjectOtherSettingById(id);
+        return success();
+    }
+
+}

+ 53 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/provider/CoursePaperCloudServiceProvider.java

@@ -0,0 +1,53 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-15 10:08:45.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.provider;
+
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.core.print.api.CoursePaperCloudService;
+import cn.com.qmth.examcloud.core.print.api.request.SyncCoursePaperReq;
+import cn.com.qmth.examcloud.core.print.api.response.SyncCoursePaperResp;
+import cn.com.qmth.examcloud.core.print.common.utils.Check;
+import cn.com.qmth.examcloud.core.print.entity.CoursePaper;
+import cn.com.qmth.examcloud.core.print.service.CoursePaperService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 课程试卷相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/15
+ */
+@RestController
+@Api(tags = "课程试卷相关接口")
+@RequestMapping("${$rmp.cloud.print}/course/paper")
+public class CoursePaperCloudServiceProvider extends ControllerSupport implements CoursePaperCloudService {
+    @Autowired
+    private CoursePaperService coursePaperService;
+
+    /**
+     * 同步考试课程的试卷
+     */
+    @PostMapping("/save")
+    @ApiOperation(value = "同步考试课程的试卷")
+    public SyncCoursePaperResp syncCoursePaper(@RequestBody SyncCoursePaperReq req) {
+        Check.isNull(req, "请求不能为空!");
+        Check.isNull(req.getBean(), "试卷信息不能为空!");
+        CoursePaper coursePaper = new CoursePaper();
+        BeanUtils.copyProperties(req.getBean(), coursePaper);
+        coursePaperService.syncCoursePaper(coursePaper);
+        return new SyncCoursePaperResp();
+    }
+
+}

+ 123 - 0
examcloud-core-print-provider/src/main/java/cn/com/qmth/examcloud/core/print/api/provider/SyncCloudServiceProvider.java

@@ -0,0 +1,123 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-31 10:59:58.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.api.provider;
+
+import cn.com.qmth.examcloud.core.print.enums.ExamType;
+import cn.com.qmth.examcloud.core.print.service.*;
+import cn.com.qmth.examcloud.core.print.service.bean.common.ExamInfo;
+import cn.com.qmth.examcloud.global.api.HandleSyncCloudService;
+import cn.com.qmth.examcloud.global.api.request.*;
+import cn.com.qmth.examcloud.global.api.response.*;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 数据同步相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/31
+ */
+@RestController
+@RequestMapping("${$rmp.cloud.print}/data")
+public class SyncCloudServiceProvider extends ControllerSupport implements HandleSyncCloudService {
+    @Autowired
+    private PrintingProjectService printingProjectService;
+    @Autowired
+    private ExamStructureService examStructureService;
+    @Autowired
+    private ExamQuestionStructureService examQuestionStructureService;
+    @Autowired
+    private CoursePaperService coursePaperService;
+    @Autowired
+    private CourseStatisticService courseStatisticService;
+
+    /**
+     * 同步学校机构
+     */
+    @PostMapping("/syncOrg")
+    public SyncOrgResp syncOrg(@RequestBody SyncOrgReq req) {
+        if (StringUtils.isNotBlank(req.getName())) {
+            printingProjectService.syncOrgNameByOrgId(req.getId(), req.getName());
+            examStructureService.syncOrgNameByOrgId(req.getId(), req.getName());
+        }
+        return new SyncOrgResp();
+    }
+
+    /**
+     * 同步考试
+     */
+    @PostMapping("syncExam")
+    public SyncExamResp syncExam(@RequestBody SyncExamReq req) {
+        if (StringUtils.isNotBlank(req.getName())) {
+            printingProjectService.syncExamNameByExamId(req.getId(), req.getName());
+            examStructureService.syncExamNameByExamId(req.getId(), req.getName());
+
+            if (ExamType.isTradition(req.getExamType())) {
+                ExamInfo info = new ExamInfo(req.getRootOrgId(), req.getRootOrgName(), req.getId(), req.getName());
+                printingProjectService.syncPrintingProject(info);
+            }
+        }
+        return new SyncExamResp();
+    }
+
+    /**
+     * 同步考生
+     */
+    @PostMapping("/syncExamStudent")
+    public SyncExamStudentResp syncExamStudent(@RequestBody SyncExamStudentReq req) {
+        return new SyncExamStudentResp();
+    }
+
+    /**
+     * 同步学生
+     */
+    @PostMapping("/syncStudent")
+    public SyncStudentResp syncStudent(@RequestBody SyncStudentReq req) {
+        return new SyncStudentResp();
+    }
+
+    /**
+     * 同步课程
+     */
+    @PostMapping("/syncCourse")
+    public SyncCourseResp syncCourse(@RequestBody SyncCourseReq req) {
+        if (StringUtils.isNotBlank(req.getName())) {
+            coursePaperService.syncCourseNameByCourseId(req.getId(), req.getName());
+            courseStatisticService.syncCourseNameByCourseId(req.getId(), req.getName());
+            examQuestionStructureService.syncObjectiveCourseNameByCourseId(req.getId(), req.getName());
+            examQuestionStructureService.syncSubjectiveCourseNameByCourseId(req.getId(), req.getName());
+        }
+        return new SyncCourseResp();
+    }
+
+    /**
+     * 同步专业
+     */
+    @PostMapping("syncSpecialty")
+    public SyncSpecialtyResp syncSpecialty(@RequestBody SyncSpecialtyReq req) {
+        return new SyncSpecialtyResp();
+    }
+
+    /**
+     * 同步用户
+     */
+    @PostMapping("syncUser")
+    public SyncUserResp syncUser(SyncUserReq req) {
+        if (StringUtils.isNotBlank(req.getName())) {
+            printingProjectService.syncPmNameByPmId(req.getId(), req.getName());
+            printingProjectService.syncSupplierNameBySupplierId(req.getId(), req.getName());
+        }
+        return new SyncUserResp();
+    }
+
+}

+ 21 - 0
examcloud-core-print-service/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>examcloud-core-print-service</artifactId>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-print</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-core-print-dao</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 75 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/CoursePaperService.java

@@ -0,0 +1,75 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:24:57.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.entity.CoursePaper;
+import cn.com.qmth.examcloud.core.print.service.bean.coursepaper.*;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface CoursePaperService {
+
+    /**
+     * 获取课程试卷(简要)信息列表
+     */
+    List<CoursePaperLessInfo> getCoursePaperList(CoursePaperQuery query);
+
+    /**
+     * 获取课程试卷数量
+     */
+    long countCoursePaper(Long orgId, Long examId, Long courseId);
+
+    /**
+     * 同步更新课程名称信息
+     */
+    void syncCourseNameByCourseId(Long courseId, String courseName);
+
+    /**
+     * 同步考试课程的试卷
+     */
+    void syncCoursePaper(CoursePaper coursePaper);
+
+    /**
+     * 转换尚未转换过PDF的所有试卷
+     */
+    void convertCoursePapers();
+
+    /**
+     * (单个)分配待指定试卷
+     *
+     * @param courseStatisticId 课程统计ID
+     * @param coursePaperId     课程试卷ID
+     */
+    void allotCoursePaper(Long courseStatisticId, Long coursePaperId);
+
+    /**
+     * (整体)分配待指定试卷
+     */
+    void allotAllCoursePaper(Long orgId, Long examId);
+
+    /**
+     * 获取某学校考试的试卷数量情况
+     */
+    CoursePaperTotalInfo getPaperTotalByOrgIdAndExamId(Long orgId, Long examId);
+
+    /**
+     * 批量导出(导出试卷、答案、试卷结构等文件)
+     */
+    File exportBatchCoursePaper(ExportBatchReq req);
+
+    /**
+     * 整体导出(导出试卷、答案、试卷结构等文件)
+     */
+    File exportAllCoursePaper(ExportAllReq req);
+
+}

+ 51 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/CourseStatisticService.java

@@ -0,0 +1,51 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:25:58.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticLessInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticQuery;
+import cn.com.qmth.examcloud.core.print.service.bean.coursestatistic.CourseStatisticRefreshReq;
+import org.springframework.data.domain.Page;
+
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface CourseStatisticService {
+
+    /**
+     * 获取课程统计信息列表(分页)
+     */
+    Page<CourseStatisticInfo> getCourseStatisticList(CourseStatisticQuery query);
+
+    /**
+     * 同步更新课程名称信息
+     */
+    void syncCourseNameByCourseId(Long courseId, String courseName);
+
+    /**
+     * 获取某考试的所有课程的试卷页数和考生人数信息列表
+     */
+    List<CourseStatisticLessInfo> getCourseStatisticLessInfoList(Long orgId, Long examId);
+
+    /**
+     * 刷新某些课程的统计信息
+     *
+     * @param req 请求表单
+     */
+    void refreshCourseStatistic(CourseStatisticRefreshReq req);
+
+    /**
+     * 初始所有课程的统计信息
+     */
+    void initAllCourseStatistic();
+
+}

+ 46 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ExamQuestionStructureService.java

@@ -0,0 +1,46 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:31:08.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.entity.ObjectiveQuestionStructure;
+import cn.com.qmth.examcloud.core.print.entity.SubjectiveQuestionStructure;
+
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface ExamQuestionStructureService {
+
+    /**
+     * 保存试卷试题结构
+     */
+    void savePaperQuestionStructure(Long examId, String paperId);
+
+    /**
+     * 获取客观题结构
+     */
+    List<ObjectiveQuestionStructure> getObjectiveQuestionStructureList(Long orgId, Long examId, String paperId, String paperType);
+
+    /**
+     * 获取主观题结构
+     */
+    List<SubjectiveQuestionStructure> getSubjectiveQuestionStructureList(Long orgId, Long examId, String paperId, String paperType);
+
+    /**
+     * 同步更新课程名称信息
+     */
+    void syncObjectiveCourseNameByCourseId(Long courseId, String courseName);
+
+    /**
+     * 同步更新课程名称信息
+     */
+    void syncSubjectiveCourseNameByCourseId(Long courseId, String courseName);
+
+}

+ 62 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ExamStructureService.java

@@ -0,0 +1,62 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:26:26.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.service.bean.examstructure.ExamStructureInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.examstructure.ExamStructureQuery;
+import org.springframework.data.domain.Page;
+
+/**
+ * @author: weiwenhai
+ * @since: 2018.10.30
+ * @author: fengdesheng
+ * @update: 2018/11/22
+ */
+public interface ExamStructureService {
+
+    /**
+     * 获取考试结构信息列表(分页)
+     */
+    Page<ExamStructureInfo> getExamStructureList(ExamStructureQuery query);
+
+    /**
+     * 获取某个考试结构信息
+     */
+    ExamStructureInfo getExamStructure(Long orgId, Long examId);
+
+    /**
+     * 保存某个考试结构信息
+     */
+    void saveExamStructure(ExamStructureInfo info);
+
+    /**
+     * 复用某个考试结构信息
+     */
+    void cloneExamStructure(ExamStructureInfo info);
+
+    /**
+     * 删除某个考试结构信息
+     */
+    void deleteExamStructure(Long[] ids);
+
+    /**
+     * 校验某个考试试卷结构信息
+     */
+    void checkExamStructure(Long orgId, Long examId, String paperId);
+
+    /**
+     * 同步更新学校名称信息
+     */
+    void syncOrgNameByOrgId(Long orgId, String orgName);
+
+    /**
+     * 同步更新考试名称信息
+     */
+    void syncExamNameByExamId(Long examId, String examName);
+
+}

+ 94 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingProjectService.java

@@ -0,0 +1,94 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 16:31:14.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.entity.PrintingProject;
+import cn.com.qmth.examcloud.core.print.service.bean.common.ExamInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.common.OrgInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.PrintingProjectInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.PrintingProjectLessInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.PrintingProjectQuery;
+import org.springframework.data.domain.Page;
+
+import java.util.List;
+
+/**
+ * 印刷项目相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public interface PrintingProjectService {
+
+    /**
+     * 查询印刷项目列表(分页)
+     */
+    Page<PrintingProjectInfo> getPrintingProjectList(PrintingProjectQuery query);
+
+    /**
+     * 获取某印刷项目的信息
+     */
+    PrintingProjectInfo getPrintingProjectById(Long id);
+
+    /**
+     * 获取某印刷项目的信息
+     */
+    PrintingProject getPrintingProjectByOrgIdAndExamId(Long orgId, Long examId);
+
+    /**
+     * 更新印刷项目信息
+     */
+    void updatePrintingProject(PrintingProjectInfo info);
+
+    /**
+     * 同步(新增或更新)某个印刷项目的基本信息
+     * 仅同步学校、考试等信息
+     */
+    void syncPrintingProject(ExamInfo examInfo);
+
+    /**
+     * 初始所有印刷项目的基本信息
+     */
+    void initAllPrintingProject();
+
+    /**
+     * 同步更新学校名称信息
+     */
+    void syncOrgNameByOrgId(Long orgId, String orgName);
+
+    /**
+     * 同步更新考试名称信息
+     */
+    void syncExamNameByExamId(Long examId, String examName);
+
+    /**
+     * 同步更新项目经理名称信息
+     */
+    void syncPmNameByPmId(Long pmId, String pmName);
+
+    /**
+     * 同步更新供应商名称信息
+     */
+    void syncSupplierNameBySupplierId(Long supplierId, String supplierName);
+
+    /**
+     * 获取印刷学校列表
+     */
+    List<OrgInfo> getOrgList(Long pmId, Long supplierId);
+
+    /**
+     * 获取印刷考试列表
+     */
+    List<ExamInfo> getExamList(Long orgId, Long pmId, Long supplierId);
+
+    /**
+     * 获取印刷项目的(简要)信息列表
+     */
+    List<PrintingProjectLessInfo> getPrintingProjectLessInfoList();
+
+}

+ 39 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingProjectStatisticService.java

@@ -0,0 +1,39 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-26 18:28:14.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.service.bean.printingprojectstatistic.PrintingProjectStatisticInfo;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+public interface PrintingProjectStatisticService {
+
+    /**
+     * 获取某个印刷项目的统计信息
+     *
+     * @param orgId
+     * @param examId
+     * @return
+     */
+    PrintingProjectStatisticInfo getPrintingProjectStatistic(@NotNull Long orgId, @NotNull Long examId);
+
+    /**
+     * 刷新某个印刷项目的统计信息
+     */
+    void refreshPrintingProjectStatistic(@NotNull Long orgId, @NotNull Long examId);
+
+    /**
+     * 初始所有印刷项目的统计信息
+     */
+    void initAllPrintingProjectStatistic();
+
+}

+ 35 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/PrintingTemplateService.java

@@ -0,0 +1,35 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:28:57.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.service.bean.printingtemplate.PrintingTemplateInfo;
+
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface PrintingTemplateService {
+
+    /**
+     * 获取模板信息列表
+     */
+    List<PrintingTemplateInfo> getPrintingTemplateList(Long orgId, Long examId);
+
+    /**
+     * 保存或更新某个模板信息
+     */
+    void savePrintingTemplate(PrintingTemplateInfo info);
+
+    /**
+     * 获取某个模板信息
+     */
+    PrintingTemplateInfo getPrintingTemplateById(Long id);
+
+}

+ 34 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ProjectBackupSettingService.java

@@ -0,0 +1,34 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:29:39.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.entity.ProjectBackupSetting;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.ProjectBackupSettingInfo;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface ProjectBackupSettingService {
+
+    /**
+     * 获取某个项目的备份设置信息
+     */
+    ProjectBackupSetting getProjectBackupSettingById(Long projectId);
+
+    /**
+     * 保存或更新某个项目的备份设置信息
+     */
+    void saveProjectBackupSetting(ProjectBackupSettingInfo info);
+
+    /**
+     * 清除某个项目的备份设置信息
+     */
+    void removeProjectBackupSettingById(Long projectId);
+
+}

+ 41 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/ProjectOtherSettingService.java

@@ -0,0 +1,41 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-22 15:30:11.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.entity.ProjectOtherSetting;
+import cn.com.qmth.examcloud.core.print.service.bean.printingproject.ProjectOtherSettingInfo;
+
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/22
+ */
+public interface ProjectOtherSettingService {
+
+    /**
+     * 获取其它事项设置列表
+     */
+    List<ProjectOtherSetting> getProjectOtherSettingList(Long projectId);
+
+    /**
+     * 获取某个其它事项设置的信息
+     */
+    ProjectOtherSetting getProjectOtherSettingById(Long id);
+
+    /**
+     * 保存或更新某个其它事项设置信息
+     */
+    void saveProjectOtherSetting(ProjectOtherSettingInfo info);
+
+    /**
+     * 删除某个其它事项设置信息
+     */
+    void deleteProjectOtherSettingById(Long id);
+
+}

+ 88 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/StatisticService.java

@@ -0,0 +1,88 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-23 17:52:37.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service;
+
+import cn.com.qmth.examcloud.core.print.service.bean.common.CourseInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.common.ExamCourseInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.common.ExamInfo;
+import cn.com.qmth.examcloud.core.print.service.bean.examquestionstructure.ExamQuestionStructureInfo;
+
+import java.util.List;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/23
+ */
+public interface StatisticService {
+
+    /**
+     * 获取某考试的课程和试卷类型列表
+     */
+    List<ExamCourseInfo> findExamCourseAndPaperTypes(Long orgId, Long examId);
+
+    /**
+     * 获取考试的实际课程数量
+     */
+    int countExamTotalCourse(List<ExamCourseInfo> coursePaperTypes);
+
+    /**
+     * 获取考试的试卷袋列表
+     */
+    List<String> findExamPackageCodes(Long orgId, Long examId);
+
+    /**
+     * 获取考试的考点列表
+     */
+    List<String> findExamSites(Long examId);
+
+    /**
+     * 获取考试的学习中心列表
+     */
+    List<Long> findExamLearnCenters(Long examId);
+
+    /**
+     * 通过考试获取考生的数量(人科次)
+     */
+    int countExamTotalStudentByExamId(Long examId);
+
+    /**
+     * 通过课程和试卷类型获取考生的数量
+     */
+    int countExamTotalStudentByCourseIdAndPaperType(Long examId, Long courseId, String paperType);
+
+    /**
+     * 通过试卷袋编号获取考生的数量
+     */
+    int countExamTotalStudentByPackageCode(Long examId, String packageCode);
+
+    /**
+     * 通过学习中心获取考生的数量
+     */
+    int countExamTotalStudentByLearnCenter(Long examId, Long learnCenterId);
+
+    /**
+     * 通过考点获取考生的数量
+     */
+    int countExamTotalStudentBySite(Long examId, String site);
+
+    /**
+     * 获取课程基本信息
+     */
+    CourseInfo findCourseInfo(Long orgId, Long courseId);
+
+    /**
+     * 获取考试的试卷试题结构信息
+     */
+    ExamQuestionStructureInfo findStructureByPaperId(Long examId, String paperId);
+
+    /**
+     * 获取传统考试列表
+     */
+    List<ExamInfo> findTraditionExams();
+
+}

+ 67 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/CourseInfo.java

@@ -0,0 +1,67 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-30 11:10:19.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.common;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 课程信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+public class CourseInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 课程ID
+     */
+    private Long id;
+    /**
+     * 课程代码
+     */
+    private String code;
+    /**
+     * 课程名称
+     */
+    private String name;
+
+    public CourseInfo(Long id, String code, String name) {
+        this.id = id;
+        this.code = code;
+        this.name = name;
+    }
+
+    public CourseInfo() {
+
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}

+ 117 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/ExamCourseInfo.java

@@ -0,0 +1,117 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-07 14:58:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.common;
+
+import java.io.Serializable;
+
+/**
+ * 考试课程信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/07
+ */
+public class ExamCourseInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程代码
+     */
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    private String courseName;
+    /**
+     * 试卷类型
+     */
+    private String paperType;
+
+    /**
+     * 考生人数
+     */
+    private Integer totalStudent;
+
+    public ExamCourseInfo(Long orgId, Long examId, Long courseId, String paperType) {
+        this.orgId = orgId;
+        this.examId = examId;
+        this.courseId = courseId;
+        this.paperType = paperType;
+    }
+
+    public ExamCourseInfo() {
+
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public Integer getTotalStudent() {
+        return totalStudent;
+    }
+
+    public void setTotalStudent(Integer totalStudent) {
+        this.totalStudent = totalStudent;
+    }
+
+}

+ 80 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/ExamInfo.java

@@ -0,0 +1,80 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-07 13:38:08.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.common;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 学校机构的考试信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+public class ExamInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称
+     */
+    private String orgName;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 考试名称
+     */
+    private String examName;
+
+    public ExamInfo(Long orgId, String orgName, Long examId, String examName) {
+        this.orgId = orgId;
+        this.orgName = orgName;
+        this.examId = examId;
+        this.examName = examName;
+    }
+
+    public ExamInfo() {
+
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+}

+ 54 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/OrgInfo.java

@@ -0,0 +1,54 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-14 13:53:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.common;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 学校信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/26
+ */
+public class OrgInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称
+     */
+    private String orgName;
+
+    public OrgInfo(Long orgId, String orgName) {
+        this.orgId = orgId;
+        this.orgName = orgName;
+    }
+
+    public OrgInfo() {
+
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+}

+ 26 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/common/RefreshInfo.java

@@ -0,0 +1,26 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-12-20 13:32:18.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.common;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/12/20
+ */
+public class RefreshInfo {
+
+    /**
+     * 是否项目统计刷新中
+     */
+    public static boolean projectRefreshing = false;
+
+    /**
+     * 是否课程统计刷新中
+     */
+    public static boolean coursesRefreshing = false;
+
+}

+ 38 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperConvert.java

@@ -0,0 +1,38 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-13 16:49:32.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import cn.com.qmth.examcloud.core.print.entity.CoursePaper;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/13
+ */
+public class CoursePaperConvert {
+
+    public static CoursePaperLessInfo of(CoursePaper coursePaper) {
+        CoursePaperLessInfo info = new CoursePaperLessInfo();
+        info.setCoursePaperId(coursePaper.getId());
+        info.setPaperId(coursePaper.getPaperId());
+        info.setPaperName(coursePaper.getPaperName());
+        info.setPaperP(coursePaper.getPaperP());
+        return info;
+    }
+
+    public static List<CoursePaperLessInfo> ofList(List<CoursePaper> entities) {
+        if (entities == null) {
+            return Lists.newArrayList();
+        }
+        return entities.stream().map(entity -> of(entity)).collect(Collectors.toList());
+    }
+
+}

+ 66 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperLessInfo.java

@@ -0,0 +1,66 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-13 16:39:06.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import java.io.Serializable;
+
+/**
+ * 课程试卷的简要信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/13
+ */
+public class CoursePaperLessInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Long coursePaperId;
+    /**
+     * 试卷ID
+     */
+    private String paperId;
+    /**
+     * 试卷名称
+     */
+    private String paperName;
+    /**
+     * 试卷P数(1P为1面,1张纸为2P)
+     */
+    private Integer paperP;
+
+    public Long getCoursePaperId() {
+        return coursePaperId;
+    }
+
+    public void setCoursePaperId(Long coursePaperId) {
+        this.coursePaperId = coursePaperId;
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public Integer getPaperP() {
+        return paperP;
+    }
+
+    public void setPaperP(Integer paperP) {
+        this.paperP = paperP;
+    }
+
+}

+ 55 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperQuery.java

@@ -0,0 +1,55 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-13 16:41:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+public class CoursePaperQuery implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+}

+ 67 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/CoursePaperTotalInfo.java

@@ -0,0 +1,67 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-13 16:40:16.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import java.io.Serializable;
+
+/**
+ * 试卷数量情况
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/13
+ */
+public class CoursePaperTotalInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 试卷总数
+     */
+    private Integer allNum;
+    /**
+     * 已有试卷数
+     */
+    private Integer existNum;
+    /**
+     * 缺少试卷数
+     */
+    private Integer missNum;
+
+    public CoursePaperTotalInfo(Integer allNum, Integer existNum, Integer missNum) {
+        this.allNum = allNum;
+        this.existNum = existNum;
+        this.missNum = missNum;
+    }
+
+    public CoursePaperTotalInfo() {
+
+    }
+
+    public Integer getAllNum() {
+        return allNum != null ? allNum : 0;
+    }
+
+    public void setAllNum(Integer allNum) {
+        this.allNum = allNum;
+    }
+
+    public Integer getExistNum() {
+        return existNum != null ? existNum : 0;
+    }
+
+    public void setExistNum(Integer existNum) {
+        this.existNum = existNum;
+    }
+
+    public Integer getMissNum() {
+        return missNum != null ? missNum : 0;
+    }
+
+    public void setMissNum(Integer missNum) {
+        this.missNum = missNum;
+    }
+
+}

+ 114 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportAllReq.java

@@ -0,0 +1,114 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-20 14:16:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.enums.ExportType;
+
+/**
+ * 整体导出(请求)
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+public class ExportAllReq implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 是否需要导出试卷
+     */
+    private Boolean needPaper;
+    /**
+     * 是否需要导出答案
+     */
+    private Boolean needAnswer;
+    /**
+     * 是否需要导出试卷结构
+     */
+    private Boolean needStruct;
+
+    public ExportAllReq(Long orgId, Long examId, Integer[] types) {
+        this.orgId = orgId;
+        this.examId = examId;
+        for (Integer type : types) {
+            if (ExportType.PAPER.getId() == type) {
+                this.needPaper = true;
+            } else if (ExportType.ANSWER.getId() == type) {
+                this.needAnswer = true;
+            } else if (ExportType.STRUCT.getId() == type) {
+                this.needStruct = true;
+            }
+        }
+    }
+
+    public boolean required() {
+        //至少选择一种
+        if (getNeedPaper()) {
+            return true;
+        }
+        if (getNeedAnswer()) {
+            return true;
+        }
+        if (getNeedStruct()) {
+            return true;
+        }
+        return false;
+    }
+
+    public ExportAllReq() {
+
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Boolean getNeedPaper() {
+        return needPaper != null ? needPaper : false;
+    }
+
+    public void setNeedPaper(Boolean needPaper) {
+        this.needPaper = needPaper;
+    }
+
+    public Boolean getNeedAnswer() {
+        return needAnswer != null ? needAnswer : false;
+    }
+
+    public void setNeedAnswer(Boolean needAnswer) {
+        this.needAnswer = needAnswer;
+    }
+
+    public Boolean getNeedStruct() {
+        return needStruct != null ? needStruct : false;
+    }
+
+    public void setNeedStruct(Boolean needStruct) {
+        this.needStruct = needStruct;
+    }
+
+}

+ 104 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportBatchReq.java

@@ -0,0 +1,104 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-20 14:22:20.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.enums.ExportType;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 批量导出(请求)
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+public class ExportBatchReq implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * ID列表
+     */
+    private List<Long> ids;
+    /**
+     * 是否需要导出试卷
+     */
+    private Boolean needPaper;
+    /**
+     * 是否需要导出答案
+     */
+    private Boolean needAnswer;
+    /**
+     * 是否需要导出试卷结构
+     */
+    private Boolean needStruct;
+
+    public ExportBatchReq(Long[] ids, Integer[] types) {
+        this.ids = Arrays.asList(ids);
+        for (Integer type : types) {
+            if (ExportType.PAPER.getId() == type) {
+                this.needPaper = true;
+            } else if (ExportType.ANSWER.getId() == type) {
+                this.needAnswer = true;
+            } else if (ExportType.STRUCT.getId() == type) {
+                this.needStruct = true;
+            }
+        }
+    }
+
+    public boolean required() {
+        //至少选择一种
+        if (getNeedPaper()) {
+            return true;
+        }
+        if (getNeedAnswer()) {
+            return true;
+        }
+        if (getNeedStruct()) {
+            return true;
+        }
+        return false;
+    }
+
+    public ExportBatchReq() {
+
+    }
+
+    public List<Long> getIds() {
+        return ids;
+    }
+
+    public void setIds(List<Long> ids) {
+        this.ids = ids;
+    }
+
+    public Boolean getNeedPaper() {
+        return needPaper != null ? needPaper : false;
+    }
+
+    public void setNeedPaper(Boolean needPaper) {
+        this.needPaper = needPaper;
+    }
+
+    public Boolean getNeedAnswer() {
+        return needAnswer != null ? needAnswer : false;
+    }
+
+    public void setNeedAnswer(Boolean needAnswer) {
+        this.needAnswer = needAnswer;
+    }
+
+    public Boolean getNeedStruct() {
+        return needStruct != null ? needStruct : false;
+    }
+
+    public void setNeedStruct(Boolean needStruct) {
+        this.needStruct = needStruct;
+    }
+
+}

+ 44 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursepaper/ExportFileInfo.java

@@ -0,0 +1,44 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-20 17:02:16.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursepaper;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.common.utils.Pair;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/20
+ */
+public class ExportFileInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 试卷PDF文件 <名称,地址>
+     */
+    private Pair<String, String> paperPdf;
+    /**
+     * 答案PDF文件 <名称,地址>
+     */
+    private Pair<String, String> answerPdf;
+
+    public Pair<String, String> getPaperPdf() {
+        return paperPdf;
+    }
+
+    public void setPaperPdf(Pair<String, String> paperPdf) {
+        this.paperPdf = paperPdf;
+    }
+
+    public Pair<String, String> getAnswerPdf() {
+        return answerPdf;
+    }
+
+    public void setAnswerPdf(Pair<String, String> answerPdf) {
+        this.answerPdf = answerPdf;
+    }
+
+}

+ 60 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticConvert.java

@@ -0,0 +1,60 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-05 16:58:51.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursestatistic;
+
+import cn.com.qmth.examcloud.core.print.entity.CoursePaper;
+import cn.com.qmth.examcloud.core.print.entity.CourseStatistic;
+import com.google.common.collect.Lists;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/05
+ */
+public class CourseStatisticConvert {
+
+    public static CourseStatisticInfo of(CourseStatistic statistic) {
+        CourseStatisticInfo info = new CourseStatisticInfo();
+        info.setId(statistic.getId());
+        info.setOrgId(statistic.getOrgId());
+        info.setExamId(statistic.getExamId());
+        info.setCourseId(statistic.getCourseId());
+        info.setCourseCode(statistic.getCourseCode());
+        info.setCourseName(statistic.getCourseName());
+        info.setPaperType(statistic.getPaperType());
+        info.setPaperStatus(statistic.getPaperStatus());
+        info.setTotalStudent(statistic.getTotalStudent());
+
+        CoursePaper paper = statistic.getCoursePaper();
+        if (paper != null) {
+            info.setCoursePaperId(paper.getId());
+            info.setPaperId(paper.getPaperId());
+            info.setPaperName(paper.getPaperName());
+            info.setPaperPdfUrl(paper.getPaperPdfUrl());
+            info.setPaperP(paper.getPaperP());
+        }
+        return info;
+    }
+
+    public static Page<CourseStatisticInfo> ofPage(Page<CourseStatistic> page) {
+        Pageable pageable = new PageRequest(page.getNumber(), page.getSize());
+        List<CourseStatistic> entities = page.getContent();
+        if (entities == null || entities.isEmpty()) {
+            return new PageImpl<>(Lists.newArrayList(), pageable, page.getTotalElements());
+        }
+        List<CourseStatisticInfo> list = entities.stream().map(entity -> of(entity)).collect(Collectors.toList());
+        return new PageImpl<>(list, pageable, page.getTotalElements());
+    }
+
+}

+ 193 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticInfo.java

@@ -0,0 +1,193 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 09:42:18.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursestatistic;
+
+import cn.com.qmth.examcloud.core.print.enums.PaperStatus;
+
+import java.io.Serializable;
+
+/**
+ * 课程的统计信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+public class CourseStatisticInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 课程代码
+     */
+    private String courseCode;
+    /**
+     * 课程名称
+     */
+    private String courseName;
+    /**
+     * @See PaperStatus.java
+     * 考试的试卷状态
+     */
+    private Integer paperStatus;
+    /**
+     * 试卷类型
+     */
+    private String paperType;
+    /**
+     * 试卷ID
+     */
+    private String paperId;
+    /**
+     * 试卷名称
+     */
+    private String paperName;
+    /**
+     * 试卷PDF文件地址(用于预览)
+     */
+    private String paperPdfUrl;
+    /**
+     * 试卷P数
+     */
+    private Integer paperP;
+    /**
+     * 考生人数
+     */
+    private Integer totalStudent;
+    /**
+     * 课程试卷ID
+     */
+    private Long coursePaperId;
+
+    public String getPaperStatusName() {
+        return PaperStatus.getNameByIndex(paperStatus);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public Integer getPaperStatus() {
+        return paperStatus;
+    }
+
+    public void setPaperStatus(Integer paperStatus) {
+        this.paperStatus = paperStatus;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public String getPaperPdfUrl() {
+        return paperPdfUrl;
+    }
+
+    public void setPaperPdfUrl(String paperPdfUrl) {
+        this.paperPdfUrl = paperPdfUrl;
+    }
+
+    public Integer getPaperP() {
+        return paperP != null ? paperP : 0;
+    }
+
+    public void setPaperP(Integer paperP) {
+        this.paperP = paperP;
+    }
+
+    public Integer getTotalStudent() {
+        return totalStudent != null ? totalStudent : 0;
+    }
+
+    public void setTotalStudent(Integer totalStudent) {
+        this.totalStudent = totalStudent;
+    }
+
+    public Long getCoursePaperId() {
+        return coursePaperId;
+    }
+
+    public void setCoursePaperId(Long coursePaperId) {
+        this.coursePaperId = coursePaperId;
+    }
+
+}

+ 63 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticLessInfo.java

@@ -0,0 +1,63 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-28 09:19:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursestatistic;
+
+import java.io.Serializable;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/13
+ */
+public class CourseStatisticLessInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /**
+     * 试卷P数(1P为1面,1张纸为2P)
+     */
+    private Integer paperP;
+    /**
+     * 考生人数
+     */
+    private Integer totalStudent;
+
+    public Integer getMultiplicationValue() {
+        if (paperP == null) {
+            paperP = 0;
+        }
+        if (totalStudent == null) {
+            totalStudent = 0;
+        }
+        //返回相乘的结果
+        return paperP * totalStudent;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Integer getPaperP() {
+        return paperP;
+    }
+
+    public void setPaperP(Integer paperP) {
+        this.paperP = paperP;
+    }
+
+    public Integer getTotalStudent() {
+        return totalStudent;
+    }
+
+    public void setTotalStudent(Integer totalStudent) {
+        this.totalStudent = totalStudent;
+    }
+
+}

+ 81 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticQuery.java

@@ -0,0 +1,81 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 09:42:42.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursestatistic;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.common.PageQuery;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/11/01
+ */
+public class CourseStatisticQuery extends PageQuery implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+    /**
+     * 试卷名称
+     */
+    private String paperName;
+    /**
+     * @See PaperStatus.java
+     * 考试的试卷状态
+     */
+    private Integer paperStatus;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName;
+    }
+
+    public Integer getPaperStatus() {
+        return paperStatus;
+    }
+
+    public void setPaperStatus(Integer paperStatus) {
+        this.paperStatus = paperStatus;
+    }
+
+}

+ 104 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/coursestatistic/CourseStatisticRefreshReq.java

@@ -0,0 +1,104 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-08 14:10:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.coursestatistic;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 待刷新统计的课程(请求表单)
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/08
+ */
+public class CourseStatisticRefreshReq implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 格式:
+     * [{"courseId":1,"paperType":"A"},{"courseId":2,"paperType":"B"}]
+     */
+    private List<Course> courses;
+
+    public CourseStatisticRefreshReq(Long orgId, Long examId) {
+        this.orgId = orgId;
+        this.examId = examId;
+    }
+
+    public CourseStatisticRefreshReq() {
+
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public List<Course> getCourses() {
+        return courses;
+    }
+
+    public void setCourses(List<Course> courses) {
+        this.courses = courses;
+    }
+
+    public static class Course {
+        /**
+         * 课程ID
+         */
+        private Long courseId;
+        /**
+         * 试卷类型
+         */
+        private String paperType;
+
+        public Course(Long courseId, String paperType) {
+            this.courseId = courseId;
+            this.paperType = paperType;
+        }
+
+        public Course() {
+
+        }
+
+        public Long getCourseId() {
+            return courseId;
+        }
+
+        public void setCourseId(Long courseId) {
+            this.courseId = courseId;
+        }
+
+        public String getPaperType() {
+            return paperType;
+        }
+
+        public void setPaperType(String paperType) {
+            this.paperType = paperType;
+        }
+    }
+
+}

+ 64 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examquestionstructure/ExamQuestionStructureInfo.java

@@ -0,0 +1,64 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-16 14:34:43.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.examquestionstructure;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.entity.ObjectiveQuestionStructure;
+import cn.com.qmth.examcloud.core.print.entity.SubjectiveQuestionStructure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试卷的试题结构
+ *
+ * @author: fengdesheng
+ * @since: 2018/11/16
+ */
+public class ExamQuestionStructureInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 客观题列表
+     */
+    private List<ObjectiveQuestionStructure> objectives;
+    /**
+     * 主观题列表
+     */
+    private List<SubjectiveQuestionStructure> subjectives;
+
+    public void addObjective(ObjectiveQuestionStructure objective) {
+        if (objectives == null) {
+            objectives = new ArrayList<>();
+        }
+        objectives.add(objective);
+    }
+
+    public void addSubjective(SubjectiveQuestionStructure subjective) {
+        if (subjectives == null) {
+            subjectives = new ArrayList<>();
+        }
+        subjectives.add(subjective);
+    }
+
+    public List<ObjectiveQuestionStructure> getObjectives() {
+        return objectives;
+    }
+
+    public void setObjectives(List<ObjectiveQuestionStructure> objectives) {
+        this.objectives = objectives;
+    }
+
+    public List<SubjectiveQuestionStructure> getSubjectives() {
+        return subjectives;
+    }
+
+    public void setSubjectives(List<SubjectiveQuestionStructure> subjectives) {
+        this.subjectives = subjectives;
+    }
+
+}

+ 51 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureConvert.java

@@ -0,0 +1,51 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-05 14:27:26.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.examstructure;
+
+import cn.com.qmth.examcloud.core.print.common.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.print.entity.ExamQuestionStructure;
+import cn.com.qmth.examcloud.core.print.entity.ExamStructure;
+import com.google.common.collect.Lists;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ExamStructureConvert {
+
+    public static ExamStructure of(ExamStructureInfo info) {
+        ExamStructure entity = new ExamStructure();
+        BeanUtils.copyProperties(info, entity);
+        String struct = new JsonMapper().toJson(info.getQuestionStructure());
+        entity.setStruct(struct);
+        return entity;
+    }
+
+    public static ExamStructureInfo of(ExamStructure entity) {
+        ExamStructureInfo info = new ExamStructureInfo();
+        BeanUtils.copyProperties(entity, info);
+        ExamQuestionStructure questionStructure = new JsonMapper().fromJson(entity.getStruct(), ExamQuestionStructure.class);
+        info.setQuestionStructure(questionStructure);
+        return info;
+    }
+
+    public static Page<ExamStructureInfo> ofPage(Page<ExamStructure> page) {
+        Pageable pageable = new PageRequest(page.getNumber(), page.getSize());
+        List<ExamStructure> entities = page.getContent();
+        if (entities == null || entities.isEmpty()) {
+            return new PageImpl<>(Lists.newArrayList(), pageable, page.getTotalElements());
+        }
+        List<ExamStructureInfo> list = entities.stream().map(entity -> of(entity)).collect(Collectors.toList());
+        return new PageImpl<>(list, pageable, page.getTotalElements());
+    }
+
+}

+ 155 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureInfo.java

@@ -0,0 +1,155 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-05 14:27:26.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.examstructure;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.entity.ExamQuestionStructure;
+
+public class ExamStructureInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+
+    /**
+     * 考试ID
+     */
+    private Long examId;
+
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+
+    /**
+     * 学校机构名称
+     */
+    private String orgName;
+
+    /**
+     * 考试名称
+     */
+    private String examName;
+
+    /**
+     * 考试类型
+     */
+    private String examType;
+
+    /**
+     * 试题结构信息
+     */
+    private ExamQuestionStructure questionStructure;
+
+    /**
+     * 复用后的机构id
+     */
+    private Long newOrgId;
+
+    /**
+     * 复用后的考试id
+     */
+    private Long newExamId;
+
+    /**
+     * 复用后的学校机构名称
+     */
+    private String newOrgName;
+
+    /**
+     * 复用后的考试名称
+     */
+    private String newExamName;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public String getExamType() {
+        return examType;
+    }
+
+    public void setExamType(String examType) {
+        this.examType = examType;
+    }
+
+    public ExamQuestionStructure getQuestionStructure() {
+        return questionStructure;
+    }
+
+    public void setQuestionStructure(ExamQuestionStructure questionStructure) {
+        this.questionStructure = questionStructure;
+    }
+
+    public Long getNewOrgId() {
+        return newOrgId;
+    }
+
+    public void setNewOrgId(Long newOrgId) {
+        this.newOrgId = newOrgId;
+    }
+
+    public Long getNewExamId() {
+        return newExamId;
+    }
+
+    public void setNewExamId(Long newExamId) {
+        this.newExamId = newExamId;
+    }
+
+    public String getNewOrgName() {
+        return newOrgName;
+    }
+
+    public void setNewOrgName(String newOrgName) {
+        this.newOrgName = newOrgName;
+    }
+
+    public String getNewExamName() {
+        return newExamName;
+    }
+
+    public void setNewExamName(String newExamName) {
+        this.newExamName = newExamName;
+    }
+
+}

+ 40 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/examstructure/ExamStructureQuery.java

@@ -0,0 +1,40 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-05 14:27:26.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.examstructure;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.print.common.PageQuery;
+
+public class ExamStructureQuery extends PageQuery implements JsonSerializable {
+    private static final long serialVersionUID = 1;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+}

+ 49 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/printingproject/PrintingProjectConvert.java

@@ -0,0 +1,49 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 10:06:56.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.printingproject;
+
+import cn.com.qmth.examcloud.core.print.entity.PrintingProject;
+import com.google.common.collect.Lists;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/24
+ */
+public class PrintingProjectConvert {
+
+    public static PrintingProject of(PrintingProjectInfo info) {
+        PrintingProject entity = new PrintingProject();
+        BeanUtils.copyProperties(info, entity);
+        return entity;
+    }
+
+    public static PrintingProjectInfo of(PrintingProject entity) {
+        PrintingProjectInfo info = new PrintingProjectInfo();
+        BeanUtils.copyProperties(entity, info);
+        return info;
+    }
+
+    public static Page<PrintingProjectInfo> ofPage(Page<PrintingProject> page) {
+        Pageable pageable = new PageRequest(page.getNumber(), page.getSize());
+        List<PrintingProject> entities = page.getContent();
+        if (entities == null || entities.isEmpty()) {
+            return new PageImpl<>(Lists.newArrayList(), pageable, page.getTotalElements());
+        }
+        List<PrintingProjectInfo> list = entities.stream().map(entity -> of(entity)).collect(Collectors.toList());
+        return new PageImpl<>(list, pageable, page.getTotalElements());
+    }
+
+}

+ 210 - 0
examcloud-core-print-service/src/main/java/cn/com/qmth/examcloud/core/print/service/bean/printingproject/PrintingProjectInfo.java

@@ -0,0 +1,210 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-11-01 10:06:56.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.print.service.bean.printingproject;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+import java.util.Date;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/10/24
+ */
+public class PrintingProjectInfo implements JsonSerializable {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /**
+     * 学校机构ID
+     */
+    private Long orgId;
+    /**
+     * 学校机构名称
+     */
+    private String orgName;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 考试名称
+     */
+    private String examName;
+    /**
+     * 供应商ID
+     */
+    private Long supplierId;
+    /**
+     * 供应商名称
+     */
+    private String supplierName;
+    /**
+     * 项目经理ID
+     */
+    private Long pmId;
+    /**
+     * 项目经理名称
+     */
+    private String pmName;
+    /**
+     * 印刷数据准备开始时间
+     */
+    private Date prepareStartTime;
+    /**
+     * 印刷数据准备结束时间
+     */
+    private Date prepareEndTime;
+    /**
+     * 具体印刷开始时间
+     */
+    private Date printStartTime;
+    /**
+     * 具体印刷结束时间
+     */
+    private Date printEndTime;
+    /**
+     * 邮寄开始时间
+     */
+    private Date mailStartTime;
+    /**
+     * 邮寄结束时间
+     */
+    private Date mailEndTime;
+    /**
+     * 项目是否已完成
+     */
+    private Boolean completed;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public Long getSupplierId() {
+        return supplierId;
+    }
+
+    public void setSupplierId(Long supplierId) {
+        this.supplierId = supplierId;
+    }
+
+    public String getSupplierName() {
+        return supplierName;
+    }
+
+    public void setSupplierName(String supplierName) {
+        this.supplierName = supplierName;
+    }
+
+    public Long getPmId() {
+        return pmId;
+    }
+
+    public void setPmId(Long pmId) {
+        this.pmId = pmId;
+    }
+
+    public String getPmName() {
+        return pmName;
+    }
+
+    public void setPmName(String pmName) {
+        this.pmName = pmName;
+    }
+
+    public Date getPrepareStartTime() {
+        return prepareStartTime;
+    }
+
+    public void setPrepareStartTime(Date prepareStartTime) {
+        this.prepareStartTime = prepareStartTime;
+    }
+
+    public Date getPrepareEndTime() {
+        return prepareEndTime;
+    }
+
+    public void setPrepareEndTime(Date prepareEndTime) {
+        this.prepareEndTime = prepareEndTime;
+    }
+
+    public Date getPrintStartTime() {
+        return printStartTime;
+    }
+
+    public void setPrintStartTime(Date printStartTime) {
+        this.printStartTime = printStartTime;
+    }
+
+    public Date getPrintEndTime() {
+        return printEndTime;
+    }
+
+    public void setPrintEndTime(Date printEndTime) {
+        this.printEndTime = printEndTime;
+    }
+
+    public Date getMailStartTime() {
+        return mailStartTime;
+    }
+
+    public void setMailStartTime(Date mailStartTime) {
+        this.mailStartTime = mailStartTime;
+    }
+
+    public Date getMailEndTime() {
+        return mailEndTime;
+    }
+
+    public void setMailEndTime(Date mailEndTime) {
+        this.mailEndTime = mailEndTime;
+    }
+
+    public Boolean getCompleted() {
+        return completed;
+    }
+
+    public void setCompleted(Boolean completed) {
+        this.completed = completed;
+    }
+
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác