yin 9 bulan lalu
melakukan
7d525cbf8a
100 mengubah file dengan 7653 tambahan dan 0 penghapusan
  1. 49 0
      .gitignore
  2. 192 0
      pom.xml
  3. 26 0
      src/main/java/cn/com/qmth/scancentral/ApiApplication.java
  4. 94 0
      src/main/java/cn/com/qmth/scancentral/bean/AbsentQueryDomain.java
  5. 29 0
      src/main/java/cn/com/qmth/scancentral/bean/AnswerDeleteDomain.java
  6. 253 0
      src/main/java/cn/com/qmth/scancentral/bean/AnswerQueryDomain.java
  7. 293 0
      src/main/java/cn/com/qmth/scancentral/bean/AssignedQueryDomain.java
  8. 43 0
      src/main/java/cn/com/qmth/scancentral/bean/BatchCreateDomain.java
  9. 60 0
      src/main/java/cn/com/qmth/scancentral/bean/BatchQueryDomain.java
  10. 28 0
      src/main/java/cn/com/qmth/scancentral/bean/CardAnswerDomain.java
  11. 125 0
      src/main/java/cn/com/qmth/scancentral/bean/ExamConfigDomain.java
  12. 34 0
      src/main/java/cn/com/qmth/scancentral/bean/ImportCetAbsentDomain.java
  13. 149 0
      src/main/java/cn/com/qmth/scancentral/bean/ImportExamDomain.java
  14. 144 0
      src/main/java/cn/com/qmth/scancentral/bean/ImportStudentDomain.java
  15. 35 0
      src/main/java/cn/com/qmth/scancentral/bean/ImportSubjectDomain.java
  16. 59 0
      src/main/java/cn/com/qmth/scancentral/bean/ImportUserDomain.java
  17. 30 0
      src/main/java/cn/com/qmth/scancentral/bean/MismatchQueryDomain.java
  18. 29 0
      src/main/java/cn/com/qmth/scancentral/bean/MismatchToggleDomain.java
  19. 28 0
      src/main/java/cn/com/qmth/scancentral/bean/OmrTaskDto.java
  20. 45 0
      src/main/java/cn/com/qmth/scancentral/bean/OmrTaskPageDto.java
  21. 43 0
      src/main/java/cn/com/qmth/scancentral/bean/PageDeleteDomain.java
  22. 31 0
      src/main/java/cn/com/qmth/scancentral/bean/ScannerLoginInfo.java
  23. 62 0
      src/main/java/cn/com/qmth/scancentral/bean/SchoolSession.java
  24. 42 0
      src/main/java/cn/com/qmth/scancentral/bean/SubjectConfigDomain.java
  25. 115 0
      src/main/java/cn/com/qmth/scancentral/bean/User.java
  26. 26 0
      src/main/java/cn/com/qmth/scancentral/bean/WorkloadDomain.java
  27. 267 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudentAnswerGroupCondition.java
  28. 17 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupCountVo.java
  29. 20 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupIdVo.java
  30. 107 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupMarkedVo.java
  31. 20 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSaveVo.java
  32. 25 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSummaryQuery.java
  33. 25 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSummaryVo.java
  34. 75 0
      src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupVo.java
  35. 59 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerDomain.java
  36. 103 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerPage.java
  37. 107 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerPaper.java
  38. 29 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/ArrayResult.java
  39. 27 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/BoolResult.java
  40. 27 0
      src/main/java/cn/com/qmth/scancentral/bean/answersave/StringResult.java
  41. 17 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/ReleaseVo.java
  42. 37 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskPageVo.java
  43. 45 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskPaperVo.java
  44. 25 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskSaveDomain.java
  45. 32 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskSaveVo.java
  46. 30 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskStatusVo.java
  47. 107 0
      src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskVo.java
  48. 29 0
      src/main/java/cn/com/qmth/scancentral/bean/card/AnswerArea.java
  49. 110 0
      src/main/java/cn/com/qmth/scancentral/bean/card/CardFile.java
  50. 86 0
      src/main/java/cn/com/qmth/scancentral/bean/card/CardPage.java
  51. 14 0
      src/main/java/cn/com/qmth/scancentral/bean/card/CardPageWrapper.java
  52. 59 0
      src/main/java/cn/com/qmth/scancentral/bean/card/FillArea.java
  53. 40 0
      src/main/java/cn/com/qmth/scancentral/bean/card/FillItem.java
  54. 27 0
      src/main/java/cn/com/qmth/scancentral/bean/card/Locator.java
  55. 41 0
      src/main/java/cn/com/qmth/scancentral/bean/card/SliceConfig.java
  56. 46 0
      src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditDomain.java
  57. 76 0
      src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditPage.java
  58. 59 0
      src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditPaper.java
  59. 74 0
      src/main/java/cn/com/qmth/scancentral/bean/papermigrate/PaperMigrateDomain.java
  60. 108 0
      src/main/java/cn/com/qmth/scancentral/bean/papermigrate/PaperMigratePage.java
  61. 67 0
      src/main/java/cn/com/qmth/scancentral/bean/refix/AnswerRefixDomain.java
  62. 108 0
      src/main/java/cn/com/qmth/scancentral/bean/refix/PageRefixDomain.java
  63. 57 0
      src/main/java/cn/com/qmth/scancentral/bean/refix/PaperRefixDomain.java
  64. 103 0
      src/main/java/cn/com/qmth/scancentral/client/MarkingcloudApiClient.java
  65. 28 0
      src/main/java/cn/com/qmth/scancentral/client/MarkingcloudApiConfiguration.java
  66. 53 0
      src/main/java/cn/com/qmth/scancentral/client/MarkingcloudProperties.java
  67. 35 0
      src/main/java/cn/com/qmth/scancentral/config/FillMetaObjectHandler.java
  68. 183 0
      src/main/java/cn/com/qmth/scancentral/config/InitData.java
  69. 15 0
      src/main/java/cn/com/qmth/scancentral/config/MyBatisPlusConfig.java
  70. 31 0
      src/main/java/cn/com/qmth/scancentral/config/ScanResourceManager.java
  71. 24 0
      src/main/java/cn/com/qmth/scancentral/config/ScanWebMvcConfigurer.java
  72. 37 0
      src/main/java/cn/com/qmth/scancentral/config/SwaggerConfig.java
  73. 51 0
      src/main/java/cn/com/qmth/scancentral/config/SysProperty.java
  74. 45 0
      src/main/java/cn/com/qmth/scancentral/consumer/PictureCopyConsumer.java
  75. 45 0
      src/main/java/cn/com/qmth/scancentral/consumer/UploadConsumer.java
  76. 184 0
      src/main/java/cn/com/qmth/scancentral/controller/BaseController.java
  77. 114 0
      src/main/java/cn/com/qmth/scancentral/controller/SystemController.java
  78. 71 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ArbitrateController.java
  79. 108 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/AssignedCheckController.java
  80. 53 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/AuditorController.java
  81. 34 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/AuthController.java
  82. 157 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/CardController.java
  83. 181 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/CheckController.java
  84. 78 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/CheckImageController.java
  85. 194 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ExamController.java
  86. 148 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ExamStatusCheckController.java
  87. 66 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ExamWorkController.java
  88. 100 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/FileController.java
  89. 142 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/OmrGroupController.java
  90. 235 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ScanAnswerController.java
  91. 108 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ScannerController.java
  92. 139 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/StudentAnswerGroupController.java
  93. 168 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/ToolController.java
  94. 34 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/UserController.java
  95. 51 0
      src/main/java/cn/com/qmth/scancentral/controller/admin/VerifyController.java
  96. 119 0
      src/main/java/cn/com/qmth/scancentral/controller/scan/AnswerController.java
  97. 72 0
      src/main/java/cn/com/qmth/scancentral/controller/scan/OmrTaskController.java
  98. 42 0
      src/main/java/cn/com/qmth/scancentral/controller/scan/ScanExamController.java
  99. 35 0
      src/main/java/cn/com/qmth/scancentral/controller/scan/ScannerAuthController.java
  100. 104 0
      src/main/java/cn/com/qmth/scancentral/controller/scan/StudentAnswerTaskController.java

+ 49 - 0
.gitignore

@@ -0,0 +1,49 @@
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+
+
+### Eclipse & STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+
+### VS Code ###
+.vscode
+node_modules
+package-lock.json
+yarn.lock
+
+
+### Package Files ###
+*.war
+*.ear
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+target/
+logs/
+
+.flattened-pom.xml
+.DS_Store
+

+ 192 - 0
pom.xml

@@ -0,0 +1,192 @@
+<?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>
+    <groupId>cn.com.qmth</groupId>
+    <artifactId>scan-central</artifactId>
+    <version>1.0.0</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.12.RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <properties>
+        <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
+        <mybatis-plus.version>3.4.3.3</mybatis-plus.version>
+        <maven-compiler-version>3.8.1</maven-compiler-version>
+        <maven-surefire-version>2.22.2</maven-surefire-version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <qmth-boot-version>1.0.4</qmth-boot-version>
+    </properties>
+
+    <repositories>
+        <repository>
+            <id>nexus</id>
+            <name>nexus</name>
+            <url>http://192.168.10.201:8081/repository/maven-public/</url>
+        </repository>
+    </repositories>
+    <pluginRepositories>
+        <pluginRepository>
+            <id>nexus</id>
+            <name>nexus</name>
+            <url>http://192.168.10.201:8081/repository/maven-public/</url>
+        </pluginRepository>
+    </pluginRepositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-security</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>starter-api</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>data-mybatis-plus</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-schedule</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.jeffreyning</groupId>
+            <artifactId>mybatisplus-plus</artifactId>
+            <version>1.5.1-RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-fss</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-retrofit</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-concurrent</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-poi</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+
+
+        <!-- Swagger jars start -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>2.0.9</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.24</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.5.24</version>
+        </dependency>
+        <!-- Swagger jars end -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>30.1.1-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.thoughtworks.xstream</groupId>
+            <artifactId>xstream</artifactId>
+            <version>1.4.16</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.9</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>c3p0</artifactId>
+                    <groupId>com.mchange</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>*</artifactId>
+                    <groupId>com.zaxxer</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz-jobs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.json-lib</groupId>
+            <artifactId>json-lib-ext-spring</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-cache</artifactId>
+            <version>${qmth-boot-version}</version>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 26 - 0
src/main/java/cn/com/qmth/scancentral/ApiApplication.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.scancentral;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import com.github.jeffreyning.mybatisplus.conf.EnableMPP;
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitScan;
+
+@EnableMPP
+@SpringBootApplication
+@EnableAsync
+@EnableScheduling
+@Configuration
+@MapperScan("cn.com.qmth.scancloud.dao")
+@RetrofitScan("cn.com.qmth.scancloud.client")
+public class ApiApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ApiApplication.class, args);
+    }
+
+}

+ 94 - 0
src/main/java/cn/com/qmth/scancentral/bean/AbsentQueryDomain.java

@@ -0,0 +1,94 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.GroupType;
+import cn.com.qmth.scancentral.util.PagerQuery;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AbsentQueryDomain extends PagerQuery {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    @NotNull(message = "分组类型不能为空")
+    private GroupType groupType;
+
+    private List<String> status = new ArrayList<>();
+
+    private String campusCode;
+
+    private String subjectCode;
+
+    private String examSite;
+
+    private String examRoom;
+
+    private Boolean scanned;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public GroupType getGroupType() {
+        return groupType;
+    }
+
+    public void setGroupType(GroupType groupType) {
+        this.groupType = groupType;
+    }
+
+    public List<String> getStatus() {
+        return status;
+    }
+
+    public void setStatus(List<String> status) {
+        this.status = status;
+    }
+
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getExamSite() {
+        return examSite;
+    }
+
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    public Boolean getScanned() {
+        return scanned;
+    }
+
+    public void setScanned(Boolean scanned) {
+        this.scanned = scanned;
+    }
+
+}

+ 29 - 0
src/main/java/cn/com/qmth/scancentral/bean/AnswerDeleteDomain.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.scancentral.bean;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+public class AnswerDeleteDomain {
+
+    @NotNull(message = "考试Id不能为空")
+    private Long examId;
+
+    @NotBlank(message = "准考证号不能为空")
+    private String examNumber;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+}

+ 253 - 0
src/main/java/cn/com/qmth/scancentral/bean/AnswerQueryDomain.java

@@ -0,0 +1,253 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.ExamStatus;
+import cn.com.qmth.scancentral.enums.PaperTypeStatus;
+import cn.com.qmth.scancentral.enums.ScanStatus;
+import cn.com.qmth.scancentral.util.PagerQuery;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class AnswerQueryDomain extends PagerQuery {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    private List<ScanStatus> status;
+
+    private ExamStatus examStatus;
+
+    private String examNumber;
+
+    private String studentCode;
+
+    private String name;
+
+    private String packageCode;
+
+    private String campusCode;
+
+    private String campusName;
+
+    private String subjectCode;
+
+    private String examSite;
+
+    private String examRoom;
+
+    private String province;
+
+    private PaperTypeStatus paperTypeStatus;
+
+    private String device;
+
+    private Boolean absentSuspect;
+
+    private Boolean omrAbsent;
+
+    private Boolean assigned;
+
+    private Boolean incomplete;
+
+    private Boolean questionFilled;
+
+    private Boolean withOmrDetail;
+
+    private Boolean subjectiveFilled;
+
+    private Boolean assignedSuspect;
+
+    private Integer assignedCheckCount;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public List<ScanStatus> getStatus() {
+        return status;
+    }
+
+    public void setStatus(List<ScanStatus> status) {
+        this.status = status;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPackageCode() {
+        return packageCode;
+    }
+
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getExamSite() {
+        return examSite;
+    }
+
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    public Boolean getAbsentSuspect() {
+        return absentSuspect;
+    }
+
+    public void setAbsentSuspect(Boolean absentSuspect) {
+        this.absentSuspect = absentSuspect;
+    }
+
+    public Boolean getOmrAbsent() {
+        return omrAbsent;
+    }
+
+    public void setOmrAbsent(Boolean omrAbsent) {
+        this.omrAbsent = omrAbsent;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public Boolean getIncomplete() {
+        return incomplete;
+    }
+
+    public void setIncomplete(Boolean incomplete) {
+        this.incomplete = incomplete;
+    }
+
+    public Boolean getQuestionFilled() {
+        return questionFilled;
+    }
+
+    public void setQuestionFilled(Boolean questionFilled) {
+        this.questionFilled = questionFilled;
+    }
+
+    public Boolean getWithOmrDetail() {
+        return withOmrDetail;
+    }
+
+    public void setWithOmrDetail(Boolean withOmrDetail) {
+        this.withOmrDetail = withOmrDetail;
+    }
+
+    public ExamStatus getExamStatus() {
+        return examStatus;
+    }
+
+    public void setExamStatus(ExamStatus examStatus) {
+        this.examStatus = examStatus;
+    }
+
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+    public PaperTypeStatus getPaperTypeStatus() {
+        return paperTypeStatus;
+    }
+
+    public void setPaperTypeStatus(PaperTypeStatus paperTypeStatus) {
+        this.paperTypeStatus = paperTypeStatus;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public String getCampusName() {
+        return campusName;
+    }
+
+    public void setCampusName(String campusName) {
+        this.campusName = campusName;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+    public Boolean getSubjectiveFilled() {
+        return subjectiveFilled;
+    }
+
+    public void setSubjectiveFilled(Boolean subjectiveFilled) {
+        this.subjectiveFilled = subjectiveFilled;
+    }
+
+    public Boolean getAssignedSuspect() {
+        return assignedSuspect;
+    }
+
+    public void setAssignedSuspect(Boolean assignedSuspect) {
+        this.assignedSuspect = assignedSuspect;
+    }
+
+    public Integer getAssignedCheckCount() {
+        return assignedCheckCount;
+    }
+
+    public void setAssignedCheckCount(Integer assignedCheckCount) {
+        this.assignedCheckCount = assignedCheckCount;
+    }
+}

+ 293 - 0
src/main/java/cn/com/qmth/scancentral/bean/AssignedQueryDomain.java

@@ -0,0 +1,293 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.ExamStatus;
+import cn.com.qmth.scancentral.enums.PaperTypeStatus;
+import cn.com.qmth.scancentral.enums.ScanStatus;
+import cn.com.qmth.scancentral.util.PagerQuery;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class AssignedQueryDomain extends PagerQuery {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    private String examNumber;
+
+    private String studentCode;
+
+    private String name;
+
+    private String packageCode;
+
+    private String subjectCode;
+
+    private String examSite;
+
+    private String examRoom;
+
+    private String province;
+
+    private List<ScanStatus> status;
+
+    private ExamStatus examStatus;
+
+    private String campusCode;
+
+    private String campusName;
+
+    private PaperTypeStatus paperTypeStatus;
+
+    private String device;
+
+    private Boolean absentSuspect;
+
+    private Boolean omrAbsent;
+
+    private Boolean assigned;
+
+    private Boolean incomplete;
+
+    private Boolean questionFilled;
+
+    private Boolean withOmrDetail;
+
+    private Boolean subjectiveFilled;
+
+    private Boolean assignedSuspect;
+
+    private Integer assignedCheckCount;
+
+    private Boolean withPaper;
+
+    private Long startTime;
+
+    private Long endTime;
+
+    private Long auditorId;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPackageCode() {
+        return packageCode;
+    }
+
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getExamSite() {
+        return examSite;
+    }
+
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+    public Boolean getAssignedSuspect() {
+        return assignedSuspect;
+    }
+
+    public void setAssignedSuspect(Boolean assignedSuspect) {
+        this.assignedSuspect = assignedSuspect;
+    }
+
+    public Integer getAssignedCheckCount() {
+        return assignedCheckCount;
+    }
+
+    public void setAssignedCheckCount(Integer assignedCheckCount) {
+        this.assignedCheckCount = assignedCheckCount;
+    }
+
+    public Boolean getWithPaper() {
+        return withPaper;
+    }
+
+    public void setWithPaper(Boolean withPaper) {
+        this.withPaper = withPaper;
+    }
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+
+    public List<ScanStatus> getStatus() {
+        return status;
+    }
+
+    public void setStatus(List<ScanStatus> status) {
+        this.status = status;
+    }
+
+    public ExamStatus getExamStatus() {
+        return examStatus;
+    }
+
+    public void setExamStatus(ExamStatus examStatus) {
+        this.examStatus = examStatus;
+    }
+
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+    public String getCampusName() {
+        return campusName;
+    }
+
+    public void setCampusName(String campusName) {
+        this.campusName = campusName;
+    }
+
+    public PaperTypeStatus getPaperTypeStatus() {
+        return paperTypeStatus;
+    }
+
+    public void setPaperTypeStatus(PaperTypeStatus paperTypeStatus) {
+        this.paperTypeStatus = paperTypeStatus;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public Boolean getAbsentSuspect() {
+        return absentSuspect;
+    }
+
+    public void setAbsentSuspect(Boolean absentSuspect) {
+        this.absentSuspect = absentSuspect;
+    }
+
+    public Boolean getOmrAbsent() {
+        return omrAbsent;
+    }
+
+    public void setOmrAbsent(Boolean omrAbsent) {
+        this.omrAbsent = omrAbsent;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public Boolean getIncomplete() {
+        return incomplete;
+    }
+
+    public void setIncomplete(Boolean incomplete) {
+        this.incomplete = incomplete;
+    }
+
+    public Boolean getQuestionFilled() {
+        return questionFilled;
+    }
+
+    public void setQuestionFilled(Boolean questionFilled) {
+        this.questionFilled = questionFilled;
+    }
+
+    public Boolean getWithOmrDetail() {
+        return withOmrDetail;
+    }
+
+    public void setWithOmrDetail(Boolean withOmrDetail) {
+        this.withOmrDetail = withOmrDetail;
+    }
+
+    public Boolean getSubjectiveFilled() {
+        return subjectiveFilled;
+    }
+
+    public void setSubjectiveFilled(Boolean subjectiveFilled) {
+        this.subjectiveFilled = subjectiveFilled;
+    }
+
+    public Long getAuditorId() {
+        return auditorId;
+    }
+
+    public void setAuditorId(Long auditorId) {
+        this.auditorId = auditorId;
+    }
+}

+ 43 - 0
src/main/java/cn/com/qmth/scancentral/bean/BatchCreateDomain.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.scancentral.bean;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class BatchCreateDomain {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    @NotNull(message = "准考证号不能为空")
+    @Size(min = 1, message = "准考证号不能为空")
+    private List<String> examNumbers;
+    
+    private String subjectCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public List<String> getExamNumbers() {
+        return examNumbers;
+    }
+
+    public void setExamNumbers(List<String> examNumbers) {
+        this.examNumbers = examNumbers;
+    }
+
+    
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+    
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+}

+ 60 - 0
src/main/java/cn/com/qmth/scancentral/bean/BatchQueryDomain.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.util.PagerQuery;
+
+import javax.validation.constraints.NotNull;
+
+public class BatchQueryDomain extends PagerQuery {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    private String device;
+
+    private Long startTime;
+
+    private Long endTime;
+
+    private Boolean withVerify;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+
+    public Boolean getWithVerify() {
+        return withVerify;
+    }
+
+    public void setWithVerify(Boolean withVerify) {
+        this.withVerify = withVerify;
+    }
+
+}

+ 28 - 0
src/main/java/cn/com/qmth/scancentral/bean/CardAnswerDomain.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.scancentral.bean;
+
+import java.util.List;
+
+public class CardAnswerDomain {
+	private Long examId;
+	private List<String> device;
+	private Integer cardNumber;
+	public Long getExamId() {
+		return examId;
+	}
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+	public List<String> getDevice() {
+		return device;
+	}
+	public void setDevice(List<String> device) {
+		this.device = device;
+	}
+	public Integer getCardNumber() {
+		return cardNumber;
+	}
+	public void setCardNumber(Integer cardNumber) {
+		this.cardNumber = cardNumber;
+	}
+	
+}

+ 125 - 0
src/main/java/cn/com/qmth/scancentral/bean/ExamConfigDomain.java

@@ -0,0 +1,125 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.ExamMode;
+import cn.com.qmth.scancentral.enums.ImageTransferMode;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class ExamConfigDomain {
+
+    public interface ExamConfigInit {
+
+    }
+
+    @NotNull(message = "考试ID不能为空")
+    private Long id;
+
+    @NotNull(message = "扫描模式不能为空", groups = ExamConfigInit.class)
+    private ExamMode mode;
+
+    @NotNull(message = "整袋扫描不能为空", groups = ExamConfigInit.class)
+    private Boolean scanByPackage;
+
+    @NotNull(message = "启用实时审核不能为空", groups = ExamConfigInit.class)
+    private Boolean enableSyncVerify;
+
+    @NotNull(message = "是否允许缺页不能为空", groups = ExamConfigInit.class)
+    private Boolean allowUnexistPaper;
+
+    @NotNull(message = "答题卡正面题卡类型不能为空", groups = ExamConfigInit.class)
+    @Min(value = 1, message = "题卡类型不能小于1", groups = ExamConfigInit.class)
+    private Integer answerFrontCardType;
+
+    @NotNull(message = "是否允许单页题卡不能为空", groups = ExamConfigInit.class)
+    private Boolean enableSinglePageAnswer;
+
+    @NotNull(message = "图片转存规则不能为空", groups = ExamConfigInit.class)
+    private ImageTransferMode imageTransferMode;
+
+    private List<String> paperTypeBarcodeContent;
+
+    private String absentBarcodeContent;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public ExamMode getMode() {
+        return mode;
+    }
+
+    public void setMode(ExamMode mode) {
+        this.mode = mode;
+    }
+
+    public Boolean getScanByPackage() {
+        return scanByPackage;
+    }
+
+    public void setScanByPackage(Boolean scanByPackage) {
+        this.scanByPackage = scanByPackage;
+    }
+
+    public Boolean getEnableSyncVerify() {
+        return enableSyncVerify;
+    }
+
+    public void setEnableSyncVerify(Boolean enableSyncVerify) {
+        this.enableSyncVerify = enableSyncVerify;
+    }
+
+    public Boolean getAllowUnexistPaper() {
+        return allowUnexistPaper;
+    }
+
+    public void setAllowUnexistPaper(Boolean allowUnexistPaper) {
+        this.allowUnexistPaper = allowUnexistPaper;
+    }
+
+    public Integer getAnswerFrontCardType() {
+        return answerFrontCardType;
+    }
+
+    public void setAnswerFrontCardType(Integer answerFrontCardType) {
+        this.answerFrontCardType = answerFrontCardType;
+    }
+
+    public Boolean getEnableSinglePageAnswer() {
+        return enableSinglePageAnswer;
+    }
+
+    public void setEnableSinglePageAnswer(Boolean enableSinglePageAnswer) {
+        this.enableSinglePageAnswer = enableSinglePageAnswer;
+    }
+
+    public ImageTransferMode getImageTransferMode() {
+        return imageTransferMode;
+    }
+
+    public void setImageTransferMode(ImageTransferMode imageTransferMode) {
+        this.imageTransferMode = imageTransferMode;
+    }
+
+    public List<String> getPaperTypeBarcodeContent() {
+        return paperTypeBarcodeContent;
+    }
+
+    public void setPaperTypeBarcodeContent(List<String> paperTypeBarcodeContent) {
+        this.paperTypeBarcodeContent = paperTypeBarcodeContent;
+    }
+
+    public String getAbsentBarcodeContent() {
+        return absentBarcodeContent;
+    }
+
+    public void setAbsentBarcodeContent(String absentBarcodeContent) {
+        this.absentBarcodeContent = absentBarcodeContent;
+    }
+
+}

+ 34 - 0
src/main/java/cn/com/qmth/scancentral/bean/ImportCetAbsentDomain.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.scancentral.bean;
+
+public class ImportCetAbsentDomain {
+
+    private Long examId;
+    private String examNumber;
+    private String breachCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getBreachCode() {
+        return breachCode;
+    }
+
+    public void setBreachCode(String breachCode) {
+        this.breachCode = breachCode;
+    }
+
+
+}

+ 149 - 0
src/main/java/cn/com/qmth/scancentral/bean/ImportExamDomain.java

@@ -0,0 +1,149 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.ExamMode;
+import cn.com.qmth.scancentral.enums.ImageTransferMode;
+
+import java.util.List;
+
+public class ImportExamDomain {
+
+    private Long id;
+
+    private Long schoolId;
+
+    private String schoolName;
+
+    private String name;
+
+    private ExamMode mode;
+
+    private Boolean scanByPackage;
+
+    private Boolean enableSyncVerify;
+
+    private Boolean allowUnexistPaper;
+
+    private Integer answerFrontCardType;
+
+    private Boolean enableSinglePageAnswer;
+
+    private ImageTransferMode imageTransferMode;
+
+    private List<String> paperTypeBarcodeContent;
+
+    private String absentBarcodeContent;
+
+    private Integer examNumberFillCount;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public ExamMode getMode() {
+        return mode;
+    }
+
+    public void setMode(ExamMode mode) {
+        this.mode = mode;
+    }
+
+    public Boolean getScanByPackage() {
+        return scanByPackage;
+    }
+
+    public void setScanByPackage(Boolean scanByPackage) {
+        this.scanByPackage = scanByPackage;
+    }
+
+    public Boolean getEnableSyncVerify() {
+        return enableSyncVerify;
+    }
+
+    public void setEnableSyncVerify(Boolean enableSyncVerify) {
+        this.enableSyncVerify = enableSyncVerify;
+    }
+
+    public Boolean getAllowUnexistPaper() {
+        return allowUnexistPaper;
+    }
+
+    public void setAllowUnexistPaper(Boolean allowUnexistPaper) {
+        this.allowUnexistPaper = allowUnexistPaper;
+    }
+
+    public Integer getAnswerFrontCardType() {
+        return answerFrontCardType;
+    }
+
+    public void setAnswerFrontCardType(Integer answerFrontCardType) {
+        this.answerFrontCardType = answerFrontCardType;
+    }
+
+    public Boolean getEnableSinglePageAnswer() {
+        return enableSinglePageAnswer;
+    }
+
+    public void setEnableSinglePageAnswer(Boolean enableSinglePageAnswer) {
+        this.enableSinglePageAnswer = enableSinglePageAnswer;
+    }
+
+    public ImageTransferMode getImageTransferMode() {
+        return imageTransferMode;
+    }
+
+    public void setImageTransferMode(ImageTransferMode imageTransferMode) {
+        this.imageTransferMode = imageTransferMode;
+    }
+
+    public List<String> getPaperTypeBarcodeContent() {
+        return paperTypeBarcodeContent;
+    }
+
+    public void setPaperTypeBarcodeContent(List<String> paperTypeBarcodeContent) {
+        this.paperTypeBarcodeContent = paperTypeBarcodeContent;
+    }
+
+    public String getAbsentBarcodeContent() {
+        return absentBarcodeContent;
+    }
+
+    public void setAbsentBarcodeContent(String absentBarcodeContent) {
+        this.absentBarcodeContent = absentBarcodeContent;
+    }
+
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSchoolName() {
+        return schoolName;
+    }
+
+    public void setSchoolName(String schoolName) {
+        this.schoolName = schoolName;
+    }
+
+    public Integer getExamNumberFillCount() {
+        return examNumberFillCount;
+    }
+
+    public void setExamNumberFillCount(Integer examNumberFillCount) {
+        this.examNumberFillCount = examNumberFillCount;
+    }
+}

+ 144 - 0
src/main/java/cn/com/qmth/scancentral/bean/ImportStudentDomain.java

@@ -0,0 +1,144 @@
+package cn.com.qmth.scancentral.bean;
+
+public class ImportStudentDomain {
+
+    private Long examId;
+    private String subjectCode;
+    private String subjectName;
+    private String examNumber;
+    private String name;
+    private String studentCode;
+    private String packageCode;
+    private String examSite;
+    private String examSiteName;
+    private String examRoom;
+    private String seatNumber;
+    private String campusName;
+    private String campusCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    
+    public String getName() {
+        return name;
+    }
+
+    
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    
+    public String getPackageCode() {
+        return packageCode;
+    }
+
+    
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
+    }
+
+    
+    public String getExamSite() {
+        return examSite;
+    }
+
+    
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    
+    public String getSeatNumber() {
+        return seatNumber;
+    }
+
+    
+    public void setSeatNumber(String seatNumber) {
+        this.seatNumber = seatNumber;
+    }
+
+    
+    public String getCampusName() {
+        return campusName;
+    }
+
+    
+    public void setCampusName(String campusName) {
+        this.campusName = campusName;
+    }
+
+	public String getSubjectName() {
+		return subjectName;
+	}
+
+	public void setSubjectName(String subjectName) {
+		this.subjectName = subjectName;
+	}
+
+    
+    public String getExamSiteName() {
+        return examSiteName;
+    }
+
+    
+    public void setExamSiteName(String examSiteName) {
+        this.examSiteName = examSiteName;
+    }
+
+    
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+
+}

+ 35 - 0
src/main/java/cn/com/qmth/scancentral/bean/ImportSubjectDomain.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.scancentral.bean;
+
+public class ImportSubjectDomain {
+
+    private Long examId;
+
+    private String subjectCode;
+
+    private String subjectName;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+}

+ 59 - 0
src/main/java/cn/com/qmth/scancentral/bean/ImportUserDomain.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.Role;
+
+public class ImportUserDomain {
+
+	private Long schoolId;
+	
+    private String name;
+
+    private String loginName;
+
+    private String password;
+
+    private Role role;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getLoginName() {
+        return loginName;
+    }
+
+    public void setLoginName(String loginName) {
+        this.loginName = loginName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public Role getRole() {
+        return role;
+    }
+
+    public void setRole(Role role) {
+        this.role = role;
+    }
+
+	public Long getSchoolId() {
+		return schoolId;
+	}
+
+	public void setSchoolId(Long schoolId) {
+		this.schoolId = schoolId;
+	}
+    
+    
+
+}

+ 30 - 0
src/main/java/cn/com/qmth/scancentral/bean/MismatchQueryDomain.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.util.PagerQuery;
+
+import javax.validation.constraints.NotNull;
+
+public class MismatchQueryDomain extends PagerQuery {
+
+    @NotNull(message = "examId不能为空")
+    private Long examId;
+
+    private String subjectCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+}

+ 29 - 0
src/main/java/cn/com/qmth/scancentral/bean/MismatchToggleDomain.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.scancentral.bean;
+
+import javax.validation.constraints.NotNull;
+
+public class MismatchToggleDomain {
+
+    @NotNull(message = "paperId不能为空")
+    private Long paperId;
+
+    @NotNull(message = "enable不能为空")
+    private Boolean enable;
+
+    public Long getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(Long paperId) {
+        this.paperId = paperId;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+}

+ 28 - 0
src/main/java/cn/com/qmth/scancentral/bean/OmrTaskDto.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.scancentral.bean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cn.com.qmth.scancentral.entity.OmrTaskEntity;
+
+public class OmrTaskDto {
+	private OmrTaskEntity task;
+	List<OmrTaskPageDto> page=new ArrayList<>();
+	public OmrTaskDto(OmrTaskEntity task) {
+		super();
+		this.task = task;
+	}
+	public OmrTaskEntity getTask() {
+		return task;
+	}
+	public void setTask(OmrTaskEntity task) {
+		this.task = task;
+	}
+	public List<OmrTaskPageDto> getPage() {
+		return page;
+	}
+	public void setPage(List<OmrTaskPageDto> page) {
+		this.page = page;
+	}
+	
+}

+ 45 - 0
src/main/java/cn/com/qmth/scancentral/bean/OmrTaskPageDto.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.scancentral.bean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cn.com.qmth.scancentral.model.OmrTaskItem;
+
+public class OmrTaskPageDto {
+	private Integer pageIndex;
+	List<OmrTaskItem> items=new ArrayList<>();
+	List<OmrTaskItem> pageMultiBlankItems=new ArrayList<>();
+	List<OmrTaskItem> pageSingleBlankItems=new ArrayList<>();
+	
+	
+	public OmrTaskPageDto(Integer pageIndex) {
+		super();
+		this.pageIndex = pageIndex;
+	}
+	public Integer getPageIndex() {
+		return pageIndex;
+	}
+	public void setPageIndex(Integer pageIndex) {
+		this.pageIndex = pageIndex;
+	}
+	public List<OmrTaskItem> getItems() {
+		return items;
+	}
+	public void setItems(List<OmrTaskItem> items) {
+		this.items = items;
+	}
+	public List<OmrTaskItem> getPageMultiBlankItems() {
+		return pageMultiBlankItems;
+	}
+	public void setPageMultiBlankItems(List<OmrTaskItem> pageMultiBlankItems) {
+		this.pageMultiBlankItems = pageMultiBlankItems;
+	}
+	public List<OmrTaskItem> getPageSingleBlankItems() {
+		return pageSingleBlankItems;
+	}
+	public void setPageSingleBlankItems(List<OmrTaskItem> pageSingleBlankItems) {
+		this.pageSingleBlankItems = pageSingleBlankItems;
+	}
+
+	
+}

+ 43 - 0
src/main/java/cn/com/qmth/scancentral/bean/PageDeleteDomain.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.scancentral.bean;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+public class PageDeleteDomain {
+
+    @NotNull(message = "考试Id不能为空")
+    private Long examId;
+
+    @NotBlank(message = "准考证号不能为空")
+    private String examNumber;
+
+    @NotNull(message = "张数不能为空")
+    @Min(value = 1, message = "张数不能小于1")
+    private Integer paperNumber;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public Integer getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(Integer paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+
+}

+ 31 - 0
src/main/java/cn/com/qmth/scancentral/bean/ScannerLoginInfo.java

@@ -0,0 +1,31 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.support.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 登陆信息
+ * 
+ */
+public class ScannerLoginInfo implements JsonSerializable {
+
+	private static final long serialVersionUID = 1305354276321732681L;
+
+
+	/**
+	 * 密码
+	 */
+	@ApiModelProperty(value = "密码")
+	private String password;
+
+
+	public String getPassword() {
+		return password;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	
+}

+ 62 - 0
src/main/java/cn/com/qmth/scancentral/bean/SchoolSession.java

@@ -0,0 +1,62 @@
+package cn.com.qmth.scancentral.bean;
+
+import cn.com.qmth.scancentral.enums.Role;
+
+public class SchoolSession{
+
+    private Long id;
+
+    private String account;
+
+    private Role role;
+    
+    private String markingCloudToken;
+
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+
+    public Role getRole() {
+        return role;
+    }
+
+    public void setRole(Role role) {
+        this.role = role;
+    }
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+
+    
+    public String getMarkingCloudToken() {
+        return markingCloudToken;
+    }
+
+    
+    public void setMarkingCloudToken(String markingCloudToken) {
+        this.markingCloudToken = markingCloudToken;
+    }
+    
+    public static SchoolSession of(User user) {
+    	SchoolSession s=new SchoolSession();
+    	s.setId(user.getSchoolId());
+    	s.setAccount(user.getAccount());
+    	s.setMarkingCloudToken(user.getMarkingCloudToken());
+    	s.setRole(user.getRole());
+    	return s;
+    }
+    
+}

+ 42 - 0
src/main/java/cn/com/qmth/scancentral/bean/SubjectConfigDomain.java

@@ -0,0 +1,42 @@
+package cn.com.qmth.scancentral.bean;
+
+import java.util.List;
+
+import javax.validation.constraints.NotNull;
+
+public class SubjectConfigDomain {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    @NotNull(message = "扫描模式不能为空")
+    private String subjectCode;
+
+    private List<String> paperTypeBarcodeContent;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public List<String> getPaperTypeBarcodeContent() {
+        return paperTypeBarcodeContent;
+    }
+
+    public void setPaperTypeBarcodeContent(List<String> paperTypeBarcodeContent) {
+        this.paperTypeBarcodeContent = paperTypeBarcodeContent;
+    }
+
+}

+ 115 - 0
src/main/java/cn/com/qmth/scancentral/bean/User.java

@@ -0,0 +1,115 @@
+package cn.com.qmth.scancentral.bean;
+
+import com.qmth.boot.core.security.model.AccessEntity;
+
+import cn.com.qmth.scancentral.enums.Role;
+
+public class User implements AccessEntity{
+
+    private Long id;
+
+    private String name;
+
+    private String account;
+
+    private String sessionId;
+
+    private String accessToken;
+
+    private Role role;
+
+    private Long activeTime;
+
+    private Long schoolId;
+    
+    private String markingCloudToken;
+
+    public String buildKey() {
+        this.sessionId = new StringBuilder().append(role.name()).append("_").append(account).toString();
+        return this.sessionId;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    public void setSessionId(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public Role getRole() {
+        return role;
+    }
+
+    public void setRole(Role role) {
+        this.role = role;
+    }
+
+    public Long getActiveTime() {
+        return activeTime;
+    }
+
+    public void setActiveTime(Long activeTime) {
+        this.activeTime = activeTime;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+    @Override
+    public String getIdentity() {
+        return sessionId;
+    }
+
+    @Override
+    public String getSecret() {
+        return accessToken;
+    }
+
+    
+    public String getMarkingCloudToken() {
+        return markingCloudToken;
+    }
+
+    
+    public void setMarkingCloudToken(String markingCloudToken) {
+        this.markingCloudToken = markingCloudToken;
+    }
+    
+}

+ 26 - 0
src/main/java/cn/com/qmth/scancentral/bean/WorkloadDomain.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.scancentral.bean;
+
+public class WorkloadDomain {
+	private Long examId;
+	private Long startTime;
+	private Long endTime;
+	public Long getExamId() {
+		return examId;
+	}
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+	public Long getStartTime() {
+		return startTime;
+	}
+	public void setStartTime(Long startTime) {
+		this.startTime = startTime;
+	}
+	public Long getEndTime() {
+		return endTime;
+	}
+	public void setEndTime(Long endTime) {
+		this.endTime = endTime;
+	}
+	
+}

+ 267 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudentAnswerGroupCondition.java

@@ -0,0 +1,267 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+import cn.com.qmth.scancentral.enums.ExamStatus;
+import cn.com.qmth.scancentral.enums.PaperTypeStatus;
+import cn.com.qmth.scancentral.enums.ScanStatus;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+public class StudentAnswerGroupCondition {
+
+    private Long examId;
+
+    private ExamStatus examStatus;
+
+    private String examNumber;
+
+    private String studentCode;
+
+    private String name;
+
+    private String packageCode;
+
+    private String campusCode;
+
+    private String campusName;
+
+    private String subjectCode;
+
+    private String examSite;
+
+    private String examRoom;
+
+    private String province;
+
+    private PaperTypeStatus paperTypeStatus;
+
+    private String device;
+
+    private Boolean absentSuspect;
+
+    private Boolean omrAbsent;
+
+    private Boolean assigned;
+
+    private Boolean incomplete;
+
+    private Boolean questionFilled;
+
+    private Boolean subjectiveFilled;
+
+    private Long startTime;
+
+    private Long endTime;
+
+    private Boolean assignedSuspect;
+
+    private Integer assignedCheckCount;
+
+    @ApiModelProperty(hidden = true)
+    @JsonIgnore
+    private List<ScanStatus> status;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPackageCode() {
+        return packageCode;
+    }
+
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getExamSite() {
+        return examSite;
+    }
+
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    public Boolean getAbsentSuspect() {
+        return absentSuspect;
+    }
+
+    public void setAbsentSuspect(Boolean absentSuspect) {
+        this.absentSuspect = absentSuspect;
+    }
+
+    public Boolean getOmrAbsent() {
+        return omrAbsent;
+    }
+
+    public void setOmrAbsent(Boolean omrAbsent) {
+        this.omrAbsent = omrAbsent;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public Boolean getIncomplete() {
+        return incomplete;
+    }
+
+    public void setIncomplete(Boolean incomplete) {
+        this.incomplete = incomplete;
+    }
+
+    public Boolean getQuestionFilled() {
+        return questionFilled;
+    }
+
+    public void setQuestionFilled(Boolean questionFilled) {
+        this.questionFilled = questionFilled;
+    }
+
+    public ExamStatus getExamStatus() {
+        return examStatus;
+    }
+
+    public void setExamStatus(ExamStatus examStatus) {
+        this.examStatus = examStatus;
+    }
+
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+    public PaperTypeStatus getPaperTypeStatus() {
+        return paperTypeStatus;
+    }
+
+    public void setPaperTypeStatus(PaperTypeStatus paperTypeStatus) {
+        this.paperTypeStatus = paperTypeStatus;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public String getCampusName() {
+        return campusName;
+    }
+
+    public void setCampusName(String campusName) {
+        this.campusName = campusName;
+    }
+
+    public List<ScanStatus> getStatus() {
+        return status;
+    }
+
+    public void setStatus(List<ScanStatus> status) {
+        this.status = status;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+
+    public Boolean getSubjectiveFilled() {
+        return subjectiveFilled;
+    }
+
+    public void setSubjectiveFilled(Boolean subjectiveFilled) {
+        this.subjectiveFilled = subjectiveFilled;
+    }
+
+    public Boolean getAssignedSuspect() {
+        return assignedSuspect;
+    }
+
+    public void setAssignedSuspect(Boolean assignedSuspect) {
+        this.assignedSuspect = assignedSuspect;
+    }
+
+    public Integer getAssignedCheckCount() {
+        return assignedCheckCount;
+    }
+
+    public void setAssignedCheckCount(Integer assignedCheckCount) {
+        this.assignedCheckCount = assignedCheckCount;
+    }
+}

+ 17 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupCountVo.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupCountVo {
+
+    private Integer totalCount;
+
+
+    public Integer getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(Integer totalCount) {
+        this.totalCount = totalCount;
+    }
+
+
+}

+ 20 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupIdVo.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupIdVo {
+
+    private Long id;
+
+    
+    public Long getId() {
+        return id;
+    }
+
+    
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+
+
+
+}

+ 107 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupMarkedVo.java

@@ -0,0 +1,107 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+import cn.com.qmth.scancentral.enums.ScanStatus;
+
+public class StudnetAnswerGroupMarkedVo {
+
+    private String examNumber;
+
+    private String name;
+
+    private String studentCode;
+
+    private String subjectCode;
+
+    private String subjectName;
+
+    private String packageCode;
+
+    private String campusCode;
+
+    private String examSite;
+
+    private ScanStatus status;
+
+    private String paperType;
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public String getPackageCode() {
+        return packageCode;
+    }
+
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
+    }
+
+    public String getCampusCode() {
+        return campusCode;
+    }
+
+    public void setCampusCode(String campusCode) {
+        this.campusCode = campusCode;
+    }
+
+    public String getExamSite() {
+        return examSite;
+    }
+
+    public void setExamSite(String examSite) {
+        this.examSite = examSite;
+    }
+
+    public ScanStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(ScanStatus status) {
+        this.status = status;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+}

+ 20 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSaveVo.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupSaveVo {
+
+    private Long id;
+
+    
+    public Long getId() {
+        return id;
+    }
+
+    
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+
+
+
+}

+ 25 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSummaryQuery.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupSummaryQuery {
+
+    private Long examId;
+
+    private String subjectCode;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+}

+ 25 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupSummaryVo.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupSummaryVo {
+
+    private Integer system;
+
+    private Integer mark;
+
+    public Integer getSystem() {
+        return system;
+    }
+
+    public void setSystem(Integer system) {
+        this.system = system;
+    }
+
+    public Integer getMark() {
+        return mark;
+    }
+
+    public void setMark(Integer mark) {
+        this.mark = mark;
+    }
+
+}

+ 75 - 0
src/main/java/cn/com/qmth/scancentral/bean/answergroup/StudnetAnswerGroupVo.java

@@ -0,0 +1,75 @@
+package cn.com.qmth.scancentral.bean.answergroup;
+
+public class StudnetAnswerGroupVo {
+
+    private Long id;
+
+    private StudentAnswerGroupCondition conditions;
+
+    private Boolean reseting;
+
+    private Boolean building;
+
+    private Boolean deleting;
+
+    private Integer totalCount;
+
+    private Integer todoCount;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public StudentAnswerGroupCondition getConditions() {
+        return conditions;
+    }
+
+    public void setConditions(StudentAnswerGroupCondition conditions) {
+        this.conditions = conditions;
+    }
+
+    public Integer getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(Integer totalCount) {
+        this.totalCount = totalCount;
+    }
+
+    public Integer getTodoCount() {
+        return todoCount;
+    }
+
+    public void setTodoCount(Integer todoCount) {
+        this.todoCount = todoCount;
+    }
+
+    public Boolean getReseting() {
+        return reseting;
+    }
+
+    public void setReseting(Boolean reseting) {
+        this.reseting = reseting;
+    }
+
+    public Boolean getBuilding() {
+        return building;
+    }
+
+    public void setBuilding(Boolean building) {
+        this.building = building;
+    }
+
+    public Boolean getDeleting() {
+        return deleting;
+    }
+
+    public void setDeleting(Boolean deleting) {
+        this.deleting = deleting;
+    }
+
+}

+ 59 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerDomain.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class AnswerDomain {
+
+    @NotNull(message = "batchId不能为空")
+    private Long batchId;
+
+    @NotBlank(message = "examNumber不能为空")
+    private String examNumber;
+
+    @NotNull(message = "cardNumber不能为空")
+    @Min(value = 1, message = "cardNumber不能小于1")
+    private Integer cardNumber;
+
+    @Valid
+    @NotNull(message = "papers不能为空")
+    @Size(min = 1, message = "papers不能为空")
+    private List<AnswerPaper> papers;
+
+    public Long getBatchId() {
+        return batchId;
+    }
+
+    public void setBatchId(Long batchId) {
+        this.batchId = batchId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public Integer getCardNumber() {
+        return cardNumber;
+    }
+
+    public void setCardNumber(Integer cardNumber) {
+        this.cardNumber = cardNumber;
+    }
+
+    public List<AnswerPaper> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(List<AnswerPaper> papers) {
+        this.papers = papers;
+    }
+
+}

+ 103 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerPage.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class AnswerPage {
+
+    @NotNull(message = "paper.page.index不能为空")
+    @Min(value = 1, message = "paper.page.index不能小于1")
+    private Integer index;
+
+    @NotBlank(message = "paper.page.sheetUri不能为空")
+    private String sheetUri;
+
+    private List<String> sliceUri;
+
+    private BoolResult absent;
+
+    private BoolResult breach;
+
+    private StringResult paperType;
+
+    private ArrayResult question;
+
+    private ArrayResult selective;
+
+    private String recogData;
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public BoolResult getAbsent() {
+        return absent;
+    }
+
+    public void setAbsent(BoolResult absent) {
+        this.absent = absent;
+    }
+
+    public BoolResult getBreach() {
+        return breach;
+    }
+
+    public void setBreach(BoolResult breach) {
+        this.breach = breach;
+    }
+
+    public StringResult getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(StringResult paperType) {
+        this.paperType = paperType;
+    }
+
+    public ArrayResult getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(ArrayResult question) {
+        this.question = question;
+    }
+
+    public ArrayResult getSelective() {
+        return selective;
+    }
+
+    public void setSelective(ArrayResult selective) {
+        this.selective = selective;
+    }
+
+    public String getSheetUri() {
+        return sheetUri;
+    }
+
+    public void setSheetUri(String sheetUri) {
+        this.sheetUri = sheetUri;
+    }
+
+    public List<String> getSliceUri() {
+        return sliceUri;
+    }
+
+    public void setSliceUri(List<String> sliceUri) {
+        this.sliceUri = sliceUri;
+    }
+
+    public String getRecogData() {
+        return recogData;
+    }
+
+    public void setRecogData(String recogData) {
+        this.recogData = recogData;
+    }
+
+}

+ 107 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/AnswerPaper.java

@@ -0,0 +1,107 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import cn.com.qmth.scancentral.entity.AnswerCardEntity;
+import cn.com.qmth.scancentral.entity.PaperPageEntity;
+import com.qmth.boot.core.exception.ParameterException;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AnswerPaper {
+
+    @NotNull(message = "paper.number不能为空")
+    @Min(value = 1, message = "paper.number不能小于1")
+    private Integer number;
+
+    @NotNull(message = "paper.assigned不能为空")
+    private Boolean assigned;
+
+    @NotNull(message = "paper.subjectiveFilled不能为空")
+    private Boolean subjectiveFilled;
+
+    @NotNull(message = "paper.omrExamNumber不能为空")
+    private String omrExamNumber;
+
+    @Valid
+    @NotNull(message = "paper.pages不能为空")
+    @Size(min = 1, message = "paper.pages不能为空")
+    private List<AnswerPage> pages;
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public List<AnswerPage> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<AnswerPage> pages) {
+        this.pages = pages;
+    }
+
+    public Boolean getSubjectiveFilled() {
+        return subjectiveFilled;
+    }
+
+    public void setSubjectiveFilled(Boolean subjectiveFilled) {
+        this.subjectiveFilled = subjectiveFilled;
+    }
+
+    public String getOmrExamNumber() {
+        return omrExamNumber;
+    }
+
+    public void setOmrExamNumber(String omrExamNumber) {
+        this.omrExamNumber = omrExamNumber;
+    }
+
+    /**
+     * 根据卡格式定义验证页数
+     *
+     * @param answerCard
+     */
+    public void answerCardValidate(AnswerCardEntity answerCard) {
+        if (answerCard.getSinglePage() && getPages().size() != 1) {
+            throw new ParameterException("卡格式页数不一致");
+        }
+        if (!answerCard.getSinglePage() && getPages().size() != 2) {
+            throw new ParameterException("卡格式页数不一致");
+        }
+    }
+
+    public List<PaperPageEntity> buildPageList(List<String> barcodeContents) {
+        List<PaperPageEntity> list = new ArrayList<>();
+        for (AnswerPage answerPage : pages) {
+            PaperPageEntity page = new PaperPageEntity();
+            page.setPageIndex(answerPage.getIndex());
+            page.setSheetPath(answerPage.getSheetUri());
+            page.setSlicePath(answerPage.getSliceUri());
+            page.setAbsent(answerPage.getAbsent());
+            page.setBreach(answerPage.getBreach());
+            page.setPaperType(answerPage.getPaperType());
+            page.setQuestion(answerPage.getQuestion());
+            page.setSelective(answerPage.getSelective());
+            page.setRecogData(answerPage.getRecogData());
+            page.checkPaperType(barcodeContents);
+            list.add(page);
+        }
+        return list;
+    }
+
+}

+ 29 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/ArrayResult.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import java.util.List;
+
+import cn.com.qmth.scancentral.enums.OmrType;
+
+public class ArrayResult {
+    private OmrType type;
+
+    private List<String> result;
+
+	public OmrType getType() {
+		return type;
+	}
+
+	public void setType(OmrType type) {
+		this.type = type;
+	}
+
+	public List<String> getResult() {
+		return result;
+	}
+
+	public void setResult(List<String> result) {
+		this.result = result;
+	}
+    
+    
+}

+ 27 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/BoolResult.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import cn.com.qmth.scancentral.enums.OmrType;
+
+public class BoolResult {
+    private OmrType type;
+
+    private Boolean result;
+
+	public OmrType getType() {
+		return type;
+	}
+
+	public void setType(OmrType type) {
+		this.type = type;
+	}
+
+	public Boolean getResult() {
+		return result;
+	}
+
+	public void setResult(Boolean result) {
+		this.result = result;
+	}
+    
+    
+}

+ 27 - 0
src/main/java/cn/com/qmth/scancentral/bean/answersave/StringResult.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.scancentral.bean.answersave;
+
+import cn.com.qmth.scancentral.enums.OmrType;
+
+public class StringResult {
+
+    private OmrType type;
+
+    private String result;
+
+    public OmrType getType() {
+        return type;
+    }
+
+    public void setType(OmrType type) {
+        this.type = type;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+}

+ 17 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/ReleaseVo.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+
+public class ReleaseVo {
+    private Boolean success;
+
+    
+    public Boolean getSuccess() {
+        return success;
+    }
+
+    
+    public void setSuccess(Boolean success) {
+        this.success = success;
+    }
+    
+}

+ 37 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskPageVo.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+import java.util.List;
+
+public class StudentAnswerTaskPageVo {
+
+    private Integer index;
+
+    private String sheetUri;
+
+    private List<String> sliceUri;
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public String getSheetUri() {
+        return sheetUri;
+    }
+
+    public void setSheetUri(String sheetUri) {
+        this.sheetUri = sheetUri;
+    }
+
+    public List<String> getSliceUri() {
+        return sliceUri;
+    }
+
+    public void setSliceUri(List<String> sliceUri) {
+        this.sliceUri = sliceUri;
+    }
+
+}

+ 45 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskPaperVo.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StudentAnswerTaskPaperVo {
+
+    private Integer number;
+
+    private Boolean assigned;
+
+    private List<StudentAnswerTaskPageVo> pages;
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public List<StudentAnswerTaskPageVo> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<StudentAnswerTaskPageVo> pages) {
+        this.pages = pages;
+    }
+    
+    public void addPage(StudentAnswerTaskPageVo page) {
+        if(pages==null) {
+            pages=new ArrayList<>();
+        }
+        pages.add(page);
+    }
+
+}

+ 25 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskSaveDomain.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+public class StudentAnswerTaskSaveDomain {
+
+    private Long id;
+
+    private Boolean tag;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Boolean getTag() {
+        return tag;
+    }
+
+    public void setTag(Boolean tag) {
+        this.tag = tag;
+    }
+
+}

+ 32 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskSaveVo.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+public class StudentAnswerTaskSaveVo {
+
+    private Long id;
+
+    private StudentAnswerTaskStatusVo status;
+
+    
+    public Long getId() {
+        return id;
+    }
+
+    
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    
+    public StudentAnswerTaskStatusVo getStatus() {
+        return status;
+    }
+
+    
+    public void setStatus(StudentAnswerTaskStatusVo status) {
+        this.status = status;
+    }
+
+    
+
+
+}

+ 30 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskStatusVo.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+public class StudentAnswerTaskStatusVo {
+
+    private Integer finishCount;
+
+    private Integer todoCount;
+
+    
+    public Integer getFinishCount() {
+        return finishCount;
+    }
+
+    
+    public void setFinishCount(Integer finishCount) {
+        this.finishCount = finishCount;
+    }
+
+    
+    public Integer getTodoCount() {
+        return todoCount;
+    }
+
+    
+    public void setTodoCount(Integer todoCount) {
+        this.todoCount = todoCount;
+    }
+
+
+}

+ 107 - 0
src/main/java/cn/com/qmth/scancentral/bean/answertask/StudentAnswerTaskVo.java

@@ -0,0 +1,107 @@
+package cn.com.qmth.scancentral.bean.answertask;
+
+import java.util.List;
+
+public class StudentAnswerTaskVo {
+
+    private Long id;
+
+    private String examNumber;
+
+    private String name;
+
+    private String subjectCode;
+
+    private String subjectName;
+
+    private Integer cardNumber;
+
+    private Boolean omrAbsent;
+
+    private Boolean assigned;
+
+    private Boolean checkMark;
+
+    private List<StudentAnswerTaskPaperVo> papers;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public Integer getCardNumber() {
+        return cardNumber;
+    }
+
+    public void setCardNumber(Integer cardNumber) {
+        this.cardNumber = cardNumber;
+    }
+
+    public Boolean getOmrAbsent() {
+        return omrAbsent;
+    }
+
+    public void setOmrAbsent(Boolean omrAbsent) {
+        this.omrAbsent = omrAbsent;
+    }
+
+    public Boolean getAssigned() {
+        return assigned;
+    }
+
+    public void setAssigned(Boolean assigned) {
+        this.assigned = assigned;
+    }
+
+    public List<StudentAnswerTaskPaperVo> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(List<StudentAnswerTaskPaperVo> papers) {
+        this.papers = papers;
+    }
+
+    public Boolean getCheckMark() {
+        return checkMark;
+    }
+
+    public void setCheckMark(Boolean checkMark) {
+        this.checkMark = checkMark;
+    }
+
+}

+ 29 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/AnswerArea.java

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

+ 110 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/CardFile.java

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

+ 86 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/CardPage.java

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

+ 14 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/CardPageWrapper.java

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

+ 59 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/FillArea.java

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

+ 40 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/FillItem.java

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

+ 27 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/Locator.java

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

+ 41 - 0
src/main/java/cn/com/qmth/scancentral/bean/card/SliceConfig.java

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

+ 46 - 0
src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditDomain.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.scancentral.bean.omredit;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class OmrEditDomain {
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    @NotBlank(message = "准考证号不能为空")
+    private String examNumber;
+
+    @Valid
+    @NotNull(message = "papers不能为空")
+    @Size(min = 1, message = "papers不能为空")
+    private List<OmrEditPaper> papers;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public List<OmrEditPaper> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(List<OmrEditPaper> papers) {
+        this.papers = papers;
+    }
+
+}

+ 76 - 0
src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditPage.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.scancentral.bean.omredit;
+
+import cn.com.qmth.scancentral.bean.answersave.ArrayResult;
+import cn.com.qmth.scancentral.bean.answersave.BoolResult;
+import cn.com.qmth.scancentral.bean.answersave.StringResult;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class OmrEditPage {
+
+    @NotNull(message = "pageIndex不能为空")
+    @Min(value = 1, message = "pageIndex不能小于1")
+    @Max(value = 2, message = "pageIndex不能大于2")
+    private Integer index;
+
+    private BoolResult absent;
+
+    private BoolResult breach;
+
+    private StringResult paperType;
+
+    private ArrayResult question;
+
+    private ArrayResult selective;
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public BoolResult getAbsent() {
+        return absent;
+    }
+
+    public void setAbsent(BoolResult absent) {
+        this.absent = absent;
+    }
+
+    public BoolResult getBreach() {
+        return breach;
+    }
+
+    public void setBreach(BoolResult breach) {
+        this.breach = breach;
+    }
+
+    public StringResult getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(StringResult paperType) {
+        this.paperType = paperType;
+    }
+
+    public ArrayResult getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(ArrayResult question) {
+        this.question = question;
+    }
+
+    public ArrayResult getSelective() {
+        return selective;
+    }
+
+    public void setSelective(ArrayResult selective) {
+        this.selective = selective;
+    }
+
+}

+ 59 - 0
src/main/java/cn/com/qmth/scancentral/bean/omredit/OmrEditPaper.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.scancentral.bean.omredit;
+
+import cn.com.qmth.scancentral.entity.PaperPageEntity;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class OmrEditPaper {
+
+    @NotNull(message = "paper.number不能为空")
+    @Min(value = 1, message = "paper.number不能小于1")
+    private Integer number;
+
+    @Valid
+    @NotNull(message = "paper.pages不能为空")
+    @Size(min = 1, message = "paper.pages不能为空")
+    private List<OmrEditPage> pages;
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public List<OmrEditPage> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<OmrEditPage> pages) {
+        this.pages = pages;
+    }
+
+    public void updatePage(PaperPageEntity page) {
+        for (OmrEditPage pageEdit : pages) {
+            if (pageEdit.getIndex().equals(page.getPageIndex())) {
+                if (pageEdit.getAbsent() != null) {
+                    page.setAbsent(pageEdit.getAbsent());
+                }
+                if (pageEdit.getBreach() != null) {
+                    page.setBreach(pageEdit.getBreach());
+                }
+                if (pageEdit.getPaperType() != null) {
+                    page.setPaperType(pageEdit.getPaperType());
+                }
+                if (pageEdit.getQuestion() != null) {
+                    page.setQuestion(pageEdit.getQuestion());
+                }
+                if (pageEdit.getSelective() != null) {
+                    page.setSelective(pageEdit.getSelective());
+                }
+            }
+        }
+    }
+}

+ 74 - 0
src/main/java/cn/com/qmth/scancentral/bean/papermigrate/PaperMigrateDomain.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.scancentral.bean.papermigrate;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class PaperMigrateDomain {
+
+    @NotNull(message = "paperId不能为空")
+    private Long paperId;
+
+    @NotNull(message = "考试ID不能为空")
+    private Long examId;
+
+    @NotBlank(message = "准考证号不能为空")
+    private String examNumber;
+
+    @NotNull(message = "paperNumber不能为空")
+    private Integer paperNumber;
+
+    @NotNull(message = "cardNumber不能为空")
+    private Integer cardNumber;
+
+    private List<PaperMigratePage> pages;
+
+    public Long getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(Long paperId) {
+        this.paperId = paperId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public Integer getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(Integer paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+
+    public Integer getCardNumber() {
+        return cardNumber;
+    }
+
+    public void setCardNumber(Integer cardNumber) {
+        this.cardNumber = cardNumber;
+    }
+
+    public List<PaperMigratePage> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<PaperMigratePage> pages) {
+        this.pages = pages;
+    }
+
+}

+ 108 - 0
src/main/java/cn/com/qmth/scancentral/bean/papermigrate/PaperMigratePage.java

@@ -0,0 +1,108 @@
+package cn.com.qmth.scancentral.bean.papermigrate;
+
+import java.util.List;
+
+import com.qmth.boot.core.exception.ParameterException;
+
+import cn.com.qmth.scancentral.bean.answersave.ArrayResult;
+import cn.com.qmth.scancentral.bean.answersave.BoolResult;
+import cn.com.qmth.scancentral.bean.answersave.StringResult;
+import cn.com.qmth.scancentral.entity.PaperPageEntity;
+
+public class PaperMigratePage {
+
+    private Integer index;
+
+    private List<String> sliceUri;
+
+    private BoolResult absent;
+
+    private BoolResult breach;
+
+    private StringResult paperType;
+
+    private ArrayResult question;
+
+    private ArrayResult selective;
+
+    private String recogData;
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public BoolResult getAbsent() {
+        return absent;
+    }
+
+    public void setAbsent(BoolResult absent) {
+        this.absent = absent;
+    }
+
+    public BoolResult getBreach() {
+        return breach;
+    }
+
+    public void setBreach(BoolResult breach) {
+        this.breach = breach;
+    }
+
+    public StringResult getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(StringResult paperType) {
+        this.paperType = paperType;
+    }
+
+    public ArrayResult getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(ArrayResult question) {
+        this.question = question;
+    }
+
+    public ArrayResult getSelective() {
+        return selective;
+    }
+
+    public void setSelective(ArrayResult selective) {
+        this.selective = selective;
+    }
+
+    public List<String> getSliceUri() {
+        return sliceUri;
+    }
+
+    public void setSliceUri(List<String> sliceUri) {
+        this.sliceUri = sliceUri;
+    }
+
+    public String getRecogData() {
+        return recogData;
+    }
+
+    public void setRecogData(String recogData) {
+        this.recogData = recogData;
+    }
+
+    public PaperPageEntity update(PaperPageEntity page, List<String> barcodeContents) {
+        if (page == null) {
+            throw new ParameterException("找不到对应的page信息, pageIndex=" + index);
+        }
+        page.setSlicePath(sliceUri);
+        page.setAbsent(absent);
+        page.setBreach(breach);
+        page.setPaperType(paperType);
+        page.setQuestion(question);
+        page.setSelective(selective);
+        page.setRecogData(recogData);
+        page.checkPaperType(barcodeContents);
+        return page;
+    }
+}

+ 67 - 0
src/main/java/cn/com/qmth/scancentral/bean/refix/AnswerRefixDomain.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.scancentral.bean.refix;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class AnswerRefixDomain {
+
+    @NotNull(message = "考试Id不能为空")
+    private Long examId;
+
+    @NotBlank(message = "准考证号不能为空")
+    private String examNumber;
+
+    @NotNull(message = "卡格式编号不能为空")
+    @Min(value = 1, message = "卡格式编号不能小于1")
+    private Integer cardNumber;
+
+    @Valid
+    @NotNull(message = "paper不能为空")
+    @Size(min = 1, message = "paper不能为空")
+    private List<PaperRefixDomain> papers;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public Integer getCardNumber() {
+        return cardNumber;
+    }
+
+    public void setCardNumber(Integer cardNumber) {
+        this.cardNumber = cardNumber;
+    }
+
+    public List<PaperRefixDomain> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(List<PaperRefixDomain> papers) {
+        this.papers = papers;
+    }
+
+    public PaperRefixDomain findPaperDomain(Integer paperNumber, Long paperId) {
+        for (PaperRefixDomain domain : papers) {
+            if (domain.getId().equals(paperId) && domain.getNumber().equals(paperNumber)) {
+                return domain;
+            }
+        }
+        return null;
+    }
+}

+ 108 - 0
src/main/java/cn/com/qmth/scancentral/bean/refix/PageRefixDomain.java

@@ -0,0 +1,108 @@
+package cn.com.qmth.scancentral.bean.refix;
+
+import java.util.List;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+import cn.com.qmth.scancentral.bean.answersave.ArrayResult;
+import cn.com.qmth.scancentral.bean.answersave.BoolResult;
+import cn.com.qmth.scancentral.bean.answersave.StringResult;
+import cn.com.qmth.scancentral.entity.PaperPageEntity;
+
+public class PageRefixDomain {
+
+    @NotNull(message = "paper.page.index不能为空")
+    @Min(value = 1, message = "paper.page.index不能小于1")
+    private Integer index;
+
+    private List<String> sliceUri;
+
+    private BoolResult absent;
+
+    private BoolResult breach;
+
+    private StringResult paperType;
+
+    private ArrayResult question;
+
+    private ArrayResult selective;
+
+    private String recogData;
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public List<String> getSliceUri() {
+        return sliceUri;
+    }
+
+    public void setSliceUri(List<String> sliceUri) {
+        this.sliceUri = sliceUri;
+    }
+
+    public BoolResult getAbsent() {
+        return absent;
+    }
+
+    public void setAbsent(BoolResult absent) {
+        this.absent = absent;
+    }
+
+    public BoolResult getBreach() {
+        return breach;
+    }
+
+    public void setBreach(BoolResult breach) {
+        this.breach = breach;
+    }
+
+    public StringResult getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(StringResult paperType) {
+        this.paperType = paperType;
+    }
+
+    public ArrayResult getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(ArrayResult question) {
+        this.question = question;
+    }
+
+    public ArrayResult getSelective() {
+        return selective;
+    }
+
+    public void setSelective(ArrayResult selective) {
+        this.selective = selective;
+    }
+
+    public String getRecogData() {
+        return recogData;
+    }
+
+    public void setRecogData(String recogData) {
+        this.recogData = recogData;
+    }
+
+    public PaperPageEntity update(PaperPageEntity page,List<String> barcodeContents) {
+        page.setSlicePath(sliceUri);
+        page.setAbsent(absent);
+        page.setBreach(breach);
+        page.setPaperType(paperType);
+        page.setQuestion(question);
+        page.setSelective(selective);
+        page.setRecogData(recogData);
+        page.checkPaperType(barcodeContents);
+        return page;
+    }
+}

+ 57 - 0
src/main/java/cn/com/qmth/scancentral/bean/refix/PaperRefixDomain.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.scancentral.bean.refix;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+public class PaperRefixDomain {
+
+    @NotNull(message = "paper.id不能为空")
+    private Long id;
+
+    @NotNull(message = "paper.number不能为空")
+    @Min(value = 1, message = "paper.number不能小于1")
+    private Integer number;
+
+    @NotNull(message = "paper.mismatch不能为空")
+    private Boolean mismatch;
+
+    @Valid
+    @NotNull(message = "pages不能为空")
+    @Size(min = 1, message = "pages不能为空")
+    private List<PageRefixDomain> pages;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Boolean getMismatch() {
+        return mismatch;
+    }
+
+    public void setMismatch(Boolean mismatch) {
+        this.mismatch = mismatch;
+    }
+
+    public List<PageRefixDomain> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<PageRefixDomain> pages) {
+        this.pages = pages;
+    }
+}

+ 103 - 0
src/main/java/cn/com/qmth/scancentral/client/MarkingcloudApiClient.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.scancentral.client;
+
+import cn.com.qmth.scancentral.entity.QuestionEntity;
+import cn.com.qmth.scancentral.enums.CardSource;
+import cn.com.qmth.scancentral.model.*;
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitClient;
+import com.qmth.boot.core.retrofit.utils.SignatureInfo;
+import okhttp3.MultipartBody;
+import retrofit2.http.*;
+
+import java.util.List;
+
+@RetrofitClient(configuration = MarkingcloudApiConfiguration.class)
+public interface MarkingcloudApiClient {
+
+    @FormUrlEncoded
+    @POST("card/answer/query")
+    List<AnswerCardInfo> getAnswerCardInfo(@Tag SignatureInfo signature,
+            @Field("examId") Long examId, @Field("number") Integer number);
+
+    @FormUrlEncoded
+    @POST("card/answer/delete")
+    String deleteAnswerCard(@Tag SignatureInfo signature, @Field("examId") Long examId,
+            @Field("number") Integer number);
+
+    @Multipart
+    @POST("card/answer/save")
+    AnswerCardInfo saveAnswerCard(@Tag SignatureInfo signature, @Query("examId") Long examId,
+            @Query("number") Integer number, @Query("source") CardSource source,
+            @Query("subjectCode") String subjectCode, @Query("parameter") String parameter,
+            @Query("remark") String remark, @Query("paperCount") Integer paperCount,
+            @Query("singlePage") Boolean singlePage, @Query("needAdapte") Boolean needAdapte,
+            @Query("sliceConfig") String sliceConfig, @Query("md5") String md5,@Query("dpi") Integer dpi, @Part MultipartBody.Part file);
+
+    @Multipart
+    @POST("file/sheet/upload")
+    FileUploadResponse sheetFileUpload(@Tag SignatureInfo signature,
+            @Query("examId") Long examId, @Query("examNumber") String examNumber, @Query("index") Integer index,
+            @Query("md5") String md5, @Part MultipartBody.Part file);
+
+    @Multipart
+    @POST("file/slice/upload")
+    FileUploadResponse sliceFileUpload(@Tag SignatureInfo signature,
+            @Query("examId") Long examId, @Query("examNumber") String examNumber, @Query("index") Integer index,
+            @Query("md5") String md5, @Part MultipartBody.Part file);
+    
+    @Multipart
+    @POST("file/paper/upload")
+    FileUploadResponse paperFileUpload(@Tag SignatureInfo signature,
+            @Query("examId") Long examId, @Query("subjectCode") String subjectCode, @Query("format") String format,
+            @Query("md5") String md5, @Part MultipartBody.Part file);
+    
+    @Multipart
+    @POST("file/answer/upload")
+    FileUploadResponse answerFileUpload(@Tag SignatureInfo signature,
+            @Query("examId") Long examId, @Query("subjectCode") String subjectCode, @Query("format") String format,
+            @Query("md5") String md5, @Part MultipartBody.Part file);
+
+    //    @FormUrlEncoded
+    //    @POST("scan/student/{examId}")
+    //    DataUploadResponse dataUpload(@Header(SignatureInfo.HEADER_NAME) String signatureInfo, @Path("examId") Long examId,
+    //            @Field("examNumber") String examNumber, @Field("sliceCount") Integer sliceCount,
+    //            @Field("sheetCount") Integer sheetCount, @Field("answers") String answers, @Field("absent") Boolean absent,
+    //            @Field("batchCode") String batchCode, @Field("paperType") String paperType,
+    //            @Field("manual") Boolean manual);
+
+    @POST("scan/student/{examId}")
+    List<DataUploadResponse> dataUpload(@Tag SignatureInfo signature,
+            @Path("examId") Long examId, @Body List<DataUploadDto> scStudentParameter);
+
+    @FormUrlEncoded
+    @POST("admin/login")
+    UserInfo login(@Field("loginName") String loginName, @Field("password") String password);
+
+    @FormUrlEncoded
+    @POST("exam/query")
+    List<ExamInfoDto> examQuery(@Tag SignatureInfo signature,
+            @Field("pageNumber") long pageNumber, @Field("pageSize") long pageSize);
+
+    @FormUrlEncoded
+    @POST("exam/config/save")
+    ExamInfoDto examConfig(@Tag SignatureInfo signature, @Field("examId") Long examId,
+            @Field("config") String config);
+
+    @FormUrlEncoded
+    @POST("exam/students/count")
+    long studentCount(@Tag SignatureInfo signature, @Field("examId") Long examId);
+
+    @FormUrlEncoded
+    @POST("exam/students")
+    List<StudentInfo> getStudents(@Tag SignatureInfo signature, @Field("examId") Long examId,
+            @Field("pageNumber") int pageNumber, @Field("pageSize") int pageSize);
+
+    @POST("scan/student/delete")
+    String studentFileDelete(@Tag SignatureInfo signature, @Query("examId") Long examId,
+            @Query("examNumber") String examNumber);
+
+    @FormUrlEncoded
+    @POST("exam/question/list")
+    List<QuestionEntity> getQuestion(@Tag SignatureInfo signature,
+            @Field("examId") Long examId, @Field("subjectCode") String subjectCode);
+
+}

+ 28 - 0
src/main/java/cn/com/qmth/scancentral/client/MarkingcloudApiConfiguration.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.scancentral.client;
+
+import com.qmth.boot.core.retrofit.interfaces.CustomizeRetrofitConfiguration;
+import com.qmth.boot.core.retrofit.interfaces.SignatureProvider;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MarkingcloudApiConfiguration implements CustomizeRetrofitConfiguration {
+
+    private MarkingcloudProperties properties;
+
+    public MarkingcloudApiConfiguration(MarkingcloudProperties properties) {
+        this.properties = properties;
+    }
+
+    public String getBaseUrl() {
+        String server = properties.getServer();
+        if (server.endsWith("/")) {
+            server = server.substring(0, server.length() - 1);
+        }
+        return server + "/api/";
+    }
+
+    public SignatureProvider getSignature() {
+        return null;
+    }
+
+}

+ 53 - 0
src/main/java/cn/com/qmth/scancentral/client/MarkingcloudProperties.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.scancentral.client;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+
+@Component
+@ConfigurationProperties(prefix = "scancloud.markingcloud")
+public class MarkingcloudProperties {
+
+    @NotNull
+    private String server = "https://www.markingcloud.com";
+
+    private String fileServer;
+
+    private String invoker;
+
+    private String accessToken;
+
+    public String getServer() {
+        return server;
+    }
+
+    public void setServer(String server) {
+        this.server = server;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getInvoker() {
+        return invoker;
+    }
+
+    public void setInvoker(String invoker) {
+        this.invoker = invoker;
+    }
+
+    public String getFileServer() {
+        return fileServer;
+    }
+
+    public void setFileServer(String fileServer) {
+        this.fileServer = fileServer;
+    }
+
+}

+ 35 - 0
src/main/java/cn/com/qmth/scancentral/config/FillMetaObjectHandler.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.scancentral.config;
+
+import java.util.Date;
+
+import org.apache.ibatis.reflection.MetaObject;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.util.ServletUtil;
+
+public class FillMetaObjectHandler implements MetaObjectHandler {
+
+	@Override
+	public void insertFill(MetaObject metaObject) {
+		User user = ServletUtil.getSessionUser();
+		if (user != null) {
+			this.setFieldValByName("creatorId", user.getId(), metaObject);
+		}
+		if(this.getFieldValByName("createTime", metaObject)==null) {
+			this.setFieldValByName("createTime", new Date().getTime(), metaObject);
+		}
+		this.setFieldValByName("updateTime", new Date().getTime(), metaObject);
+	}
+
+	@Override
+	public void updateFill(MetaObject metaObject) {
+		User user = ServletUtil.getSessionUser();
+		if (user != null) {
+			this.setFieldValByName("updaterId", user.getId(), metaObject);
+		}
+		this.setFieldValByName("updateTime", new Date().getTime(), metaObject);
+	}
+
+}

+ 183 - 0
src/main/java/cn/com/qmth/scancentral/config/InitData.java

@@ -0,0 +1,183 @@
+package cn.com.qmth.scancentral.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qmth.boot.mybatis.service.SqlProvider;
+import com.qmth.boot.tools.models.ByteArray;
+
+import cn.com.qmth.scancentral.entity.SystemConfigEntity;
+import cn.com.qmth.scancentral.entity.UserEntity;
+import cn.com.qmth.scancentral.enums.Role;
+import cn.com.qmth.scancentral.enums.SystemMode;
+import cn.com.qmth.scancentral.service.SystemConfigService;
+import cn.com.qmth.scancentral.service.UserService;
+
+@Component
+public class InitData implements SqlProvider, CommandLineRunner {
+
+    private static final Logger log = LoggerFactory.getLogger(InitData.class);
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private SystemConfigService systemConfigService;
+
+    @Value("${scancloud.mode}")
+    private String initMode;
+
+    @Override
+    public void run(String... args) throws IOException {
+        log.info("开始系统初始化...");
+        // 自动创建图片转存目录
+        String dir = sysProperty.getTransferDir();
+        File dfile = new File(dir);
+        if (!dfile.exists()) {
+            dfile.mkdirs();
+            log.info("创建图片转存目录:{}", dir);
+        }
+
+        // 初始化系统配置
+        SystemConfigEntity config = systemConfigService.find();
+        if (config == null) {
+            config = new SystemConfigEntity();
+            config.setId(1L);
+            config.setScannerEnableLogin(true);
+            config.setScannerPassword("");
+            // 根据配置项读取初始化模式
+            SystemMode mode = SystemMode.findByCode(initMode);
+            if (mode == null) {
+                throw new RuntimeException("Invalid mode property");
+            }
+            log.info("初始化系统模式:{}", mode.getName());
+            config.setMode(mode);
+            systemConfigService.save(config);
+            log.info("初始化sc_system_config记录");
+        }
+        SystemMode.setCurrent(config.getMode());
+        // 按系统模式分别初始化
+        if (SystemMode.current() == SystemMode.STANDALONE) {
+            initByStandalone();
+        }
+        // else if (SystemMode.current() == SystemMode.MARKINGCLOUD) {
+        // initByMarkingcloud();
+        // }
+        log.info("系统初始化完成!");
+    }
+
+    private void initByStandalone() throws IOException {
+        // 创建含考生编号+科目代码唯一索引的考生表
+        // executeSql("script/init_standalone.sql");
+        // 按照学校ID=1创建管理员账号
+        if (userService.count() == 0) {
+            UserEntity user = new UserEntity();
+            user.setSchoolId(1L);
+            user.setLoginName("admin");
+            user.setName("学校管理员");
+            user.setPassword("123456");
+            user.setRole(Role.SCHOOL_ADMIN);
+            user.setEnable(true);
+            user.setCreateTime(System.currentTimeMillis());
+            user.setUpdateTime(user.getCreateTime());
+            userService.save(user);
+            log.info("创建初始用户账号:{}", user.getLoginName());
+        }
+    }
+
+    // private void initByMarkingcloud() throws IOException {
+    // // 创建含考生编号+科目代码普通索引的考生表
+    // executeSql("script/init_markingcloud.sql");
+    // }
+
+    // private void executeSql(String path) throws IOException {
+    // String[] array = StringUtils.split(
+    // ByteArray.fromInputStream(this.getClass().getClassLoader().getResourceAsStream(path)).toString(),
+    // ";");
+    // int count = 0;
+    // for (String sql : array) {
+    // if (StringUtils.isNotBlank(sql)) {
+    // jdbcTemplate.execute(sql);
+    // count++;
+    // }
+    // }
+    // log.info("表结构初始化:{},完成{}条", path, count);
+    // }
+
+    @Override
+    public String get() {
+        // 初始化通用表结构
+        StringBuilder sb = new StringBuilder();
+        try {
+            sb.append(ByteArray.fromInputStream(this.getClass().getClassLoader().getResourceAsStream("script/init.sql"))
+                    .toString());
+        } catch (IOException e) {
+            throw new RuntimeException("get init.sql failed", e);
+        }
+        // 按系统模式分别初始化表结构
+        SystemMode mode = null;
+        if (isConfigTableExists()) {
+            SystemConfigEntity config = systemConfigService.find();
+            if (config == null) {
+                // 根据配置项读取初始化模式
+                mode = SystemMode.findByCode(initMode);
+                if (mode == null) {
+                    throw new RuntimeException("Invalid mode property");
+                }
+            } else {
+                mode = config.getMode();
+            }
+        } else {
+            // 根据配置项读取初始化模式
+            mode = SystemMode.findByCode(initMode);
+            if (mode == null) {
+                throw new RuntimeException("Invalid mode property");
+            }
+        }
+        if (SystemMode.STANDALONE.equals(mode)) {
+            try {
+                sb.append(ByteArray
+                        .fromInputStream(
+                                this.getClass().getClassLoader().getResourceAsStream("script/init_standalone.sql"))
+                        .toString());
+            } catch (IOException e) {
+                throw new RuntimeException("get init_standalone.sql failed", e);
+            }
+        } else if (SystemMode.MARKINGCLOUD.equals(mode)) {
+            try {
+                sb.append(ByteArray
+                        .fromInputStream(
+                                this.getClass().getClassLoader().getResourceAsStream("script/init_markingcloud.sql"))
+                        .toString());
+            } catch (IOException e) {
+                throw new RuntimeException("get init_markingcloud.sql failed", e);
+            }
+        }
+        return sb.toString();
+
+    }
+
+    private boolean isConfigTableExists() {
+        TableName[] annotationsByType = SystemConfigEntity.class.getAnnotationsByType(TableName.class);
+        String tableName = annotationsByType[0].value();
+        List<String> table = jdbcTemplate.queryForList("show tables like '" + tableName + "'", String.class);
+        return CollectionUtils.isNotEmpty(table);
+    }
+}

+ 15 - 0
src/main/java/cn/com/qmth/scancentral/config/MyBatisPlusConfig.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.scancentral.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisPlusConfig {
+
+
+    @Bean
+    public FillMetaObjectHandler metaObjectHandler() {
+        return new FillMetaObjectHandler();
+    }
+
+}

+ 31 - 0
src/main/java/cn/com/qmth/scancentral/config/ScanResourceManager.java

@@ -0,0 +1,31 @@
+package cn.com.qmth.scancentral.config;
+
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.scancentral.support.ApiInfo;
+import cn.com.qmth.scancentral.support.ResourceManager;
+
+
+@Component
+public class ScanResourceManager implements ResourceManager {
+
+    @Override
+    public boolean isNaked(ApiInfo apiInfo, String mapping) {
+        if (null == apiInfo) {
+            return true;
+        }
+
+        if (mapping.matches(".*swagger.*")) {
+            return true;
+        }
+
+        if (null != apiInfo) {
+            if (apiInfo.isNaked()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+}

+ 24 - 0
src/main/java/cn/com/qmth/scancentral/config/ScanWebMvcConfigurer.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.scancentral.config;
+
+import cn.com.qmth.scancentral.support.ResourceManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * WebMvcConfigurer
+ */
+@Configuration
+public class ScanWebMvcConfigurer implements WebMvcConfigurer {
+
+    @Autowired
+    ResourceManager resourceManager;
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**").allowedOrigins("*").allowCredentials(false).allowedMethods("*").maxAge(3600);
+    }
+
+}
+

+ 37 - 0
src/main/java/cn/com/qmth/scancentral/config/SwaggerConfig.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.scancentral.config;
+
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+@EnableSwagger2WebMvc
+@Configuration
+@ConditionalOnProperty(name = "scancloud.swagger.enable", havingValue = "true")
+public class SwaggerConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(SwaggerConfig.class);
+
+    @Bean
+    public Docket buildDocket(SysProperty sysProperty) {
+        log.info("swagger init...");
+        return new Docket(DocumentationType.SWAGGER_2).groupName("default").apiInfo(buildApiInfo(sysProperty))
+                .useDefaultResponseMessages(false).select()
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any())
+                .build();
+    }
+
+    public ApiInfo buildApiInfo(SysProperty sysProperty) {
+        return new ApiInfoBuilder().title("接口文档").version(sysProperty.getVersion()).build();
+    }
+
+}

+ 51 - 0
src/main/java/cn/com/qmth/scancentral/config/SysProperty.java

@@ -0,0 +1,51 @@
+package cn.com.qmth.scancentral.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SysProperty {
+
+    private String version = "1.0.3";
+
+    /**
+     * 心跳超时时间(秒)
+     */
+    @Value("${scancloud.heartbeat-timeout}")
+    private Integer heartbeatTimeout;
+
+    @Value("${scancloud.file-uri-prefix}")
+    private String fileUriPrefix;
+
+    @Value("${scancloud.image-transfer.dir}")
+    private String transferDir;
+
+    public Integer getHeartbeatTimeout() {
+        return heartbeatTimeout;
+    }
+
+    public void setHeartbeatTimeout(Integer heartbeatTimeout) {
+        this.heartbeatTimeout = heartbeatTimeout;
+    }
+
+    public String getFileUriPrefix() {
+        return fileUriPrefix;
+    }
+
+    public void setFileUriPrefix(String fileUriPrefix) {
+        this.fileUriPrefix = fileUriPrefix;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getTransferDir() {
+        return transferDir;
+    }
+
+    public void setTransferDir(String transferDir) {
+        this.transferDir = transferDir;
+    }
+
+}

+ 45 - 0
src/main/java/cn/com/qmth/scancentral/consumer/PictureCopyConsumer.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.scancentral.consumer;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.vo.StudentUploadVo;
+
+@Scope("prototype")
+@Service
+public class PictureCopyConsumer implements Runnable {
+	private CountDownLatch endGate;
+    private StudentUploadVo vo;
+    @Autowired
+    private StudentService studentService;
+
+    @Override
+    public void run() {
+    	try {
+			studentService.pictureCopy(vo);
+		} finally {
+			endGate.countDown();
+		}
+    }
+
+	public StudentUploadVo getVo() {
+		return vo;
+	}
+
+	public void setVo(StudentUploadVo vo) {
+		this.vo = vo;
+	}
+
+	public CountDownLatch getEndGate() {
+		return endGate;
+	}
+
+	public void setEndGate(CountDownLatch endGate) {
+		this.endGate = endGate;
+	}
+
+}

+ 45 - 0
src/main/java/cn/com/qmth/scancentral/consumer/UploadConsumer.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.scancentral.consumer;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.vo.StudentUploadVo;
+
+@Scope("prototype")
+@Service
+public class UploadConsumer implements Runnable {
+	private CountDownLatch endGate;
+    private StudentUploadVo vo;
+    @Autowired
+    private StudentService studentService;
+
+    @Override
+    public void run() {
+    	try {
+			studentService.uploadData(vo);
+		} finally {
+			endGate.countDown();
+		}
+    }
+
+	public StudentUploadVo getVo() {
+		return vo;
+	}
+
+	public void setVo(StudentUploadVo vo) {
+		this.vo = vo;
+	}
+
+	public CountDownLatch getEndGate() {
+		return endGate;
+	}
+
+	public void setEndGate(CountDownLatch endGate) {
+		this.endGate = endGate;
+	}
+
+}

+ 184 - 0
src/main/java/cn/com/qmth/scancentral/controller/BaseController.java

@@ -0,0 +1,184 @@
+package cn.com.qmth.scancentral.controller;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.util.ObjectUtil;
+import cn.com.qmth.scancentral.util.ServletUtil;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class BaseController {
+
+    protected HttpServletRequest currentRequest() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+
+    /**
+     * 获取接入用户
+     *
+     * @return
+     */
+    protected User getAccessUser() {
+        User user = (User) ServletUtil.getRequest().getAttribute("accessEntity");
+        return user;
+    }
+
+    /**
+     * 参数trim
+     *
+     * @param bean
+     * @param nullIfBlank
+     * @author
+     */
+    protected void trim(Object bean, boolean nullIfBlank) {
+        if (null == bean) {
+            return;
+        }
+        Class<? extends Object> clazz = bean.getClass();
+        if (clazz.equals(Map.class)) {
+            return;
+        } else if (clazz.equals(List.class)) {
+            return;
+        } else if (clazz.equals(Set.class)) {
+            return;
+        } else if (clazz.isEnum()) {
+            return;
+        } else if (ObjectUtil.isBaseDataType(clazz)) {
+            return;
+        }
+
+        Field[] fields = clazz.getDeclaredFields();
+        try {
+            for (int i = 0; i < fields.length; i++) {
+                Field f = fields[i];
+                f.setAccessible(true);
+                Object value = f.get(bean);
+                if (null == value) {
+                    continue;
+                }
+                if (f.getType().equals(String.class)) {
+                    if (null != value) {
+                        String s = (String) value;
+                        s = s.trim();
+                        if (nullIfBlank && StringUtils.isBlank(s)) {
+                            s = null;
+                        }
+                        f.set(bean, s);
+                    }
+                } else if (f.getType().equals(Map.class)) {
+                } else if (f.getType().equals(List.class)) {
+                } else if (f.getType().equals(Set.class)) {
+                } else if (f.getType().isEnum()) {
+                } else if (ObjectUtil.isBaseDataType(f.getType())) {
+                } else {
+                    trim(value);
+                }
+
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 参数trim.<br>
+     * set null if blank.
+     *
+     * @param bean
+     * @author
+     */
+    protected void trim(Object bean) {
+        trim(bean, false);
+    }
+
+    protected void exportFile(String fileName, InputStream in) {
+        OutputStream out = null;
+        try {
+            fileName = URLEncoder.encode(fileName, "UTF-8");
+            HttpServletResponse response = getResponse();
+            response.reset();
+            response.setHeader("Content-Disposition", "inline; filename=" + fileName);
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            out = new BufferedOutputStream(response.getOutputStream());
+            IOUtils.copy(in, out);
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    protected void exportFile(String fileName, ExcelWriter excelWriter) {
+        OutputStream out = null;
+        try {
+            fileName = URLEncoder.encode(fileName, "UTF-8");
+            HttpServletResponse response = getResponse();
+            response.reset();
+            response.setHeader("Content-Disposition", "inline; filename=" + fileName);
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            out = response.getOutputStream();
+            excelWriter.output(out);
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(out);
+        }
+    }
+
+    protected void exportFile(String fileName, File file) {
+        OutputStream out = null;
+        InputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            fileName = URLEncoder.encode(fileName, "UTF-8");
+            HttpServletResponse response = getResponse();
+            response.reset();
+            response.setHeader("Content-Disposition", "inline; filename=" + fileName);
+            response.addHeader("Content-Length", "" + file.length());
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            out = new BufferedOutputStream(response.getOutputStream());
+            IOUtils.copy(in, out);
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    protected HttpServletResponse getResponse() {
+        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
+                .getRequestAttributes();
+        HttpServletResponse response = requestAttributes.getResponse();
+        return response;
+    }
+
+    protected UpdateTimeVo result(Long updateTime) {
+        UpdateTimeVo vo = new UpdateTimeVo();
+        vo.setUpdateTime(System.currentTimeMillis());
+        return vo;
+    }
+
+    protected Object success(boolean success) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("success", success);
+        return map;
+    }
+}

+ 114 - 0
src/main/java/cn/com/qmth/scancentral/controller/SystemController.java

@@ -0,0 +1,114 @@
+package cn.com.qmth.scancentral.controller;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.api.utils.RequestUtil;
+import com.qmth.boot.core.exception.ParameterException;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.config.SysProperty;
+import cn.com.qmth.scancentral.entity.AnswerCardEntity;
+import cn.com.qmth.scancentral.entity.ExamEntity;
+import cn.com.qmth.scancentral.enums.Role;
+import cn.com.qmth.scancentral.enums.SystemMode;
+import cn.com.qmth.scancentral.exception.ParameterExceptions;
+import cn.com.qmth.scancentral.service.AdapteFileService;
+import cn.com.qmth.scancentral.service.AnswerCardService;
+import cn.com.qmth.scancentral.service.AnswerCardSubjectService;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.service.SessionService;
+import cn.com.qmth.scancentral.vo.ActiveTimeVo;
+import cn.com.qmth.scancentral.vo.AnswerCardVo;
+import cn.com.qmth.scancentral.vo.LogoutTimeVo;
+import cn.com.qmth.scancentral.vo.apistatus.ApiStatusVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "系统接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX)
+@Aac(strict = false, auth = true)
+public class SystemController extends BaseController {
+
+    @Autowired
+    private SessionService sessionService;
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    @Autowired
+    private AnswerCardService answerCardService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private AdapteFileService adapteFileService;
+    
+    @Autowired
+    private AnswerCardSubjectService answerCardSubjectService;
+
+    @ApiOperation(value = "心跳接口")
+    @PostMapping("heartbeat")
+    public ActiveTimeVo heartbeat() {
+        User user = getAccessUser();
+        ActiveTimeVo vo = new ActiveTimeVo();
+        vo.setActiveTime(sessionService.updateUserSession(user));
+        return vo;
+    }
+
+    @ApiOperation(value = "登出接口")
+    @PostMapping("logout")
+    public LogoutTimeVo logout() {
+        User user = getAccessUser();
+        sessionService.userLogout(user);
+        LogoutTimeVo vo = new LogoutTimeVo();
+        vo.setLogoutTime(System.currentTimeMillis());
+        return vo;
+    }
+
+    @Aac(auth = false)
+    @ApiOperation(value = "服务端状态")
+    @RequestMapping("status")
+    public ApiStatusVo status(HttpServletRequest request) {
+        ApiStatusVo vo = new ApiStatusVo();
+        vo.setFileUriPrefix(sysProperty.getFileUriPrefix());
+        vo.setVersion(sysProperty.getVersion());
+        vo.setTime(System.currentTimeMillis());
+        vo.setClientIP(RequestUtil.getIpAddress(request));
+        vo.setSystemMode(SystemMode.current());
+        return vo;
+    }
+
+    @ApiOperation(value = "答题卡卡格式查询")
+    @PostMapping("/card/answer")
+    public AnswerCardVo cardAnswer(HttpServletRequest request, @RequestParam Long examId,
+            @RequestParam Integer number) {
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null || !exam.getEnable()) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        AnswerCardEntity card = answerCardService.findByExamAndNumber(examId, number);
+        if (card == null) {
+            throw new ParameterException("找不到卡格式");
+        }
+        AnswerCardVo vo = new AnswerCardVo(card);
+        User user = getAccessUser();
+        if (user.getRole().equals(Role.SCANNER)) {
+            vo.setAdapteFile(adapteFileService
+                    .listByExamIdAndCardNumberForScanner(examId, number, RequestUtil.getIpAddress(request)));
+        } else {
+            vo.setAdapteFile(adapteFileService.listByExamIdAndCardNumber(examId, card.getNumber()));
+        }
+        vo.setSubjectList(answerCardSubjectService.findByExamAndNumber(examId, card.getNumber()));
+        return vo;
+    }
+}

+ 71 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ArbitrateController.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.OmrTaskService;
+import cn.com.qmth.scancentral.vo.task.TaskResultVo;
+import cn.com.qmth.scancentral.vo.task.TaskSaveVo;
+import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
+import cn.com.qmth.scancentral.vo.task.TaskVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "识别对照接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/check/omr/arbitrate")
+@Aac(strict = false, auth = true)
+public class ArbitrateController extends BaseController {
+
+    protected static final Logger log = LoggerFactory.getLogger(ArbitrateController.class);
+
+    @Autowired
+    private OmrTaskService omrTaskService;
+
+    @ApiOperation(value = "识别对照仲裁任务获取")
+    @RequestMapping(value = "/get", method = RequestMethod.POST)
+    public TaskVo get(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return omrTaskService.getArbitrateTask(examId, user.getAccount());
+    }
+
+    @ApiOperation(value = "识别对照仲裁结果提交")
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    public TaskSaveVo save(@RequestBody TaskResultVo result) {
+        User user = getAccessUser();
+        return omrTaskService.submitTask(result, user);
+    }
+
+    @ApiOperation(value = "识别对照仲裁进度状态")
+    @RequestMapping(value = "/status", method = RequestMethod.POST)
+    public TaskStatusVo status(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return omrTaskService.getStatus(examId, true, user.getAccount());
+    }
+
+    @ApiOperation(value = "识别对照仲裁任务释放")
+    @RequestMapping(value = "/release", method = RequestMethod.POST)
+    public Object release(@RequestParam Long examId) {
+        User user = getAccessUser();
+        omrTaskService.releaseByUser(examId, user.getAccount());
+        return success(true);
+    }
+    
+    @ApiOperation(value = "识别对照仲裁任务历史")
+    @RequestMapping(value = "/history", method = RequestMethod.POST)
+    public TaskVo history(@RequestParam Long examId,@RequestParam(required = false) Long id,@RequestParam(required = false) Boolean next) {
+        User user = getAccessUser();
+        return omrTaskService.getArbitrateHistory(examId, id, user.getAccount(),next);
+    }
+}

+ 108 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/AssignedCheckController.java

@@ -0,0 +1,108 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import cn.com.qmth.scancentral.bean.AssignedQueryDomain;
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.vo.answerquery.AnswerQueryVo;
+import cn.com.qmth.scancentral.vo.assginedcheck.AssginedTaskResult;
+import cn.com.qmth.scancentral.vo.assginedcheck.AssignedCheckExport;
+import cn.com.qmth.scancentral.vo.assginedcheck.AssignedTaskSaveVo;
+import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.iterator.PageListIterator;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.List;
+
+@RestController
+@Api(tags = "人工绑定校验接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/assigned/check")
+@Aac(strict = false, auth = true)
+public class AssignedCheckController extends BaseController {
+
+    @Autowired
+    private StudentService studentService;
+
+    @ApiOperation(value = "列表")
+    @RequestMapping(value = "/list", method = RequestMethod.POST)
+    public PageResult<AnswerQueryVo> list(@Validated AssignedQueryDomain query) {
+        if (query.getAssignedCheckCount() == null || query.getAssignedCheckCount() <= 1) {
+            query.setAssignedCheckCount(1);
+        }
+        query.setAssigned(true);
+        return studentService.queryAssignedCheckPage(query);
+    }
+
+    @ApiOperation(value = "导出")
+    @RequestMapping(value = "/export", method = RequestMethod.POST)
+    public void export(@Validated AssignedQueryDomain query, HttpServletResponse response) throws IOException {
+        AssignedQueryDomain query1 = new AssignedQueryDomain();
+        query1.setExamId(query.getExamId());
+        query1.setAssigned(true);
+        query1.setAssignedSuspect(true);
+        String fileName = URLEncoder.encode("人工绑定", "UTF-8");
+        response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");
+        response.setContentType("application/vnd.ms-excel");
+        User user = getAccessUser();
+        ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+        PageListIterator<AssignedCheckExport> iterator = new PageListIterator<AssignedCheckExport>(100) {
+            @Override
+            public Collection<AssignedCheckExport> getPageList(int pageNumber, int pageSize) {
+                query1.setPageNumber(pageNumber);
+                query1.setPageSize(pageSize);
+                return studentService.exportAssignedCheckPage(query1);
+            }
+        };
+        writer.writeObjects("用户", null, AssignedCheckExport.class, iterator);
+        writer.output(response.getOutputStream());
+    }
+
+    @ApiOperation(value = "审核员任务获取")
+    @RequestMapping(value = "task/get", method = RequestMethod.POST)
+    public AnswerQueryVo get(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return studentService.getAssignedCheckTask(examId, user.getAccount());
+    }
+
+    @ApiOperation(value = "审核员任务历史")
+    @RequestMapping(value = "task/history", method = RequestMethod.POST)
+    public List<AnswerQueryVo> history(@RequestParam Long id, @RequestParam Long pageSize) {
+        User user = getAccessUser();
+        return studentService.getAssignedCheckTaskHistory(id, pageSize, user);
+    }
+
+    @ApiOperation(value = "审核员任务提交")
+    @RequestMapping(value = "task/save", method = RequestMethod.POST)
+    public AssignedTaskSaveVo submit(@Validated @RequestBody AssginedTaskResult result) {
+        User user = getAccessUser();
+        return studentService.submitAssignedCheckTask(result, user);
+    }
+
+    @ApiOperation(value = "答题卡扫描图片检查任务状态")
+    @PostMapping("task/status")
+    public TaskStatusVo status(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return studentService.getAssignedCheckTaskStatus(examId, user);
+    }
+
+    @ApiOperation(value = "审核员任务释放")
+    @PostMapping("task/release")
+    public Object release(@RequestParam Long examId) {
+        User user = getAccessUser();
+        studentService.releaseAssignedCheckTaskByUser(examId, user.getAccount());
+        return success(true);
+    }
+}

+ 53 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/AuditorController.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.UserService;
+import cn.com.qmth.scancentral.vo.AuditorVo;
+import cn.com.qmth.scancentral.vo.CreateCountVo;
+import cn.com.qmth.scancentral.vo.UpdateCountVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "审核员管理接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/auditor")
+@Aac(strict = false, auth = true)
+public class AuditorController extends BaseController {
+
+    @Autowired
+    private UserService userService;
+
+    @ApiOperation(value = "审核员列表")
+    @PostMapping("list")
+    public List<AuditorVo> list() {
+        User user = getAccessUser();
+        return userService.findAuditor(user.getSchoolId());
+    }
+
+    @ApiOperation(value = "审核员修改密码")
+    @PostMapping("password")
+    public UpdateCountVo password(@RequestParam List<Long> id, @RequestParam String password) {
+        return userService.updatePassword(id, password);
+    }
+
+    @ApiOperation(value = "批量创建审核员")
+    @PostMapping("create")
+    public CreateCountVo create(@RequestParam String prefix, @RequestParam Integer count,
+            @RequestParam String password) {
+        User user = getAccessUser();
+        return userService.create(user.getSchoolId(),prefix, count, password);
+    }
+
+}

+ 34 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/AuthController.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.api.utils.RequestUtil;
+
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.AuthService;
+import cn.com.qmth.scancentral.vo.AdminLoginVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "管理员登录接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin")
+public class AuthController extends BaseController {
+
+    @Autowired
+    private AuthService authService;
+
+    @ApiOperation(value = "管理员登录")
+    @PostMapping("/login")
+    public AdminLoginVo login(@RequestParam String loginName, @RequestParam String password, HttpServletRequest request) {
+        return AdminLoginVo.of(authService.adminLogin(loginName, password,RequestUtil.getIpAddress(request)));
+    }
+
+}

+ 157 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/CardController.java

@@ -0,0 +1,157 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.api.utils.RequestUtil;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ReentrantException;
+
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.AnswerCardEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.enums.SystemMode;
+import cn.com.qmth.scancentral.service.AdapteFileService;
+import cn.com.qmth.scancentral.service.AnswerCardService;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.task.thread.CardSyncThread;
+import cn.com.qmth.scancentral.vo.UriVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import net.sf.json.JSONObject;
+
+@RestController
+@Api(tags = "卡格式接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/card")
+@Aac(strict = false, auth = true)
+public class CardController extends BaseController {
+
+    protected static final Logger log = LoggerFactory.getLogger(CardController.class);
+
+    @Autowired
+    private AnswerCardService answerCardService;
+
+    @Resource
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private AdapteFileService adapteFileService;
+
+    @Autowired
+    private ExamService examService;
+
+    // @Qualifier("task-executor")
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @ApiOperation(value = "卡格式详情")
+    @RequestMapping(value = "/info", method = RequestMethod.POST)
+    public Map<String, Object> info(@RequestParam Long examId) {
+        Map<String, Object> status = new HashMap<String, Object>();
+        status.put("synching", concurrentService.isLocked(LockType.CARD_SYNC + "-" + examId));
+        status.put("updateTime", examService.getById(examId).getCardSyncTime());
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("status", status);
+        result.put("answer", answerCardService.listByExamId(examId));
+        result.put("package", new ArrayList<>());
+        return result;
+    }
+
+    @ApiOperation(value = "同步卡格式信息")
+    @RequestMapping(value = "/sync", method = RequestMethod.POST)
+    public JSONObject sync(@RequestParam Long examId) {
+        if (!SystemMode.MARKINGCLOUD.equals(SystemMode.current())) {
+            throw new ReentrantException("非云阅卷模式下不支持同步卡格式");
+        }
+        if (!concurrentService.isLocked(LockType.CARD_SYNC + "-" + examId)) {
+            taskExecutor.submit(new CardSyncThread(getAccessUser(), examId, answerCardService, concurrentService));
+            JSONObject status = new JSONObject();
+            status.accumulate("synching", true);
+            status.accumulate("startTime", System.currentTimeMillis());
+            return status;
+        } else {
+            throw new ReentrantException("正在同步卡格式,请稍后再试");
+        }
+    }
+
+    @ApiOperation(value = "卡格式同步状态息")
+    @RequestMapping(value = "/sync/status", method = RequestMethod.POST)
+    public JSONObject syncStatus(@RequestParam Long examId) {
+        JSONObject status = new JSONObject();
+        status.accumulate("synching", concurrentService.isLocked(LockType.CARD_SYNC + "-" + examId));
+        status.accumulate("updateTime", examService.getById(examId).getCardSyncTime());
+        return status;
+    }
+
+    @ApiOperation(value = "创建/修改题卡卡格式")
+    @RequestMapping(value = "/answer/save", method = RequestMethod.POST)
+    public JSONObject answerSave(@RequestParam Long examId, @RequestParam(required = false) Integer number,
+            @RequestParam(required = false) String subjectCode, @RequestParam(required = false) String parameter,
+            @RequestParam(required = false) String remark, @RequestParam Integer paperCount,
+            @RequestParam Boolean singlePage, @RequestParam String md5,@RequestParam(required = false) Integer dpi, @RequestParam MultipartFile file) {
+        if (concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).tryLock()) {
+            try {
+                JSONObject result = new JSONObject();
+                AnswerCardEntity card = answerCardService
+                        .save(getAccessUser(), examId, number, subjectCode, parameter, remark, paperCount, singlePage,
+                                md5,dpi, file);
+                result.accumulate("number", card.getNumber());
+                result.accumulate("updateTime", System.currentTimeMillis());
+                return result;
+            } finally {
+                concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).unlock();
+            }
+        } else {
+            throw new ReentrantException("正在同步卡格式,请稍后再试");
+        }
+    }
+
+    @ApiOperation(value = "删除答题卡卡格式")
+    @RequestMapping(value = "/answer/delete", method = RequestMethod.POST)
+    public JSONObject answerDelete(@RequestParam Long examId, @RequestParam Integer number) {
+        if (concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).tryLock()) {
+            try {
+                answerCardService.delete(getAccessUser(), examId, number);
+                JSONObject result = new JSONObject();
+                result.accumulate("number", number);
+                result.accumulate("updateTime", System.currentTimeMillis());
+                return result;
+            } finally {
+                concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).unlock();
+            }
+        } else {
+            throw new ReentrantException("正在同步卡格式,请稍后再试");
+        }
+    }
+
+    @ApiOperation(value = "答题卡适配卡格式上传")
+    @RequestMapping(value = "/answer/adapte/upload", method = RequestMethod.POST)
+    public UriVo adapteUpload(HttpServletRequest request, @RequestParam Long examId, @RequestParam Integer cardNumber,
+            @RequestParam MultipartFile file, @RequestParam String md5,@RequestParam Integer dpi) {
+        return adapteFileService
+                .save(RequestUtil.getIpAddress(request), getAccessUser().getRole(), examId, cardNumber, md5,dpi, file);
+    }
+
+    @ApiOperation(value = "创建/修改签到表卡格式")
+    @RequestMapping(value = "/package/save", method = RequestMethod.POST)
+    public JSONObject packageSave() {
+        JSONObject result = new JSONObject();
+        return result;
+    }
+}

+ 181 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/CheckController.java

@@ -0,0 +1,181 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.excel.model.DataMap;
+import com.qmth.boot.tools.iterator.PageListIterator;
+import com.qmth.boot.tools.iterator.SingletonIterator;
+
+import cn.com.qmth.scancentral.bean.AbsentQueryDomain;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.SubjectEntity;
+import cn.com.qmth.scancentral.enums.ConditionType;
+import cn.com.qmth.scancentral.enums.GroupType;
+import cn.com.qmth.scancentral.model.ManualAbsentImportDTO;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.service.SubjectService;
+import cn.com.qmth.scancentral.vo.AbsentInfoVo;
+import cn.com.qmth.scancentral.vo.AbsentManualImportVo;
+import cn.com.qmth.scancentral.vo.AbsentQueryVo;
+import cn.com.qmth.scancentral.vo.OmrConditionsVo;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "缺考接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/check")
+@Aac(strict = false, auth = true)
+public class CheckController extends BaseController {
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private SubjectService subjectService;
+
+    @ApiOperation(value = "所有可用的识别对照条件")
+    @PostMapping("omr/conditions")
+    public List<OmrConditionsVo> omrConditions() {
+        return Arrays.stream(ConditionType.values()).map(OmrConditionsVo::new).collect(Collectors.toList());
+    }
+
+    @ApiOperation(value = "缺考数据汇总")
+    @PostMapping("absent/info")
+    public AbsentInfoVo absentInfo(@RequestParam Long examId, @RequestParam(required = false) GroupType groupType,
+            @RequestParam(required = false) String groupName) {
+        return studentService.absentInfo(examId, groupType, groupName);
+    }
+
+    @ApiOperation(value = "查询缺考数据")
+    @RequestMapping(value = "absent/query", method = RequestMethod.POST)
+    public PageResult<AbsentQueryVo> absentQuery(@Validated AbsentQueryDomain query) {
+        return studentService.absentQuery(query);
+    }
+
+    @ApiOperation(value = "查询缺考数据标识")
+    @RequestMapping(value = "absent/summary", method = RequestMethod.POST)
+    public List<String> absentSummary(@Validated AbsentQueryDomain query) {
+        return studentService.absentSummary(query);
+    }
+
+    @ApiOperation(value = "导出缺考数据")
+    @RequestMapping(value = "absent/export", method = RequestMethod.POST)
+    public void absentExport(@Validated AbsentQueryDomain query, HttpServletResponse response) throws IOException {
+        String fileName = URLEncoder.encode("缺考数据", "UTF-8");
+        response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");
+        response.setContentType("application/vnd.ms-excel");
+
+        Map<String, SubjectEntity> subjectMap = new HashMap<>();
+        PageListIterator<DataMap> iterator = new PageListIterator<DataMap>(100) {
+
+            @Override
+            public Collection<DataMap> getPageList(int pageNumber, int pageSize) {
+                query.setPageNumber(pageNumber);
+                query.setPageSize(pageSize);
+                List<AbsentQueryVo> list = studentService.absentExportList(query);
+                List<DataMap> ret = new ArrayList<>();
+                if (CollectionUtils.isNotEmpty(list)) {
+                    for (AbsentQueryVo vo : list) {
+                        DataMap map = new DataMap();
+                        if (GroupType.SUBJECT.equals(query.getGroupType())) {
+                            map.put("科目代码", vo.getGroupName());
+                            map.put("科目名称", getSubjectName(subjectMap, vo.getGroupName(), query.getExamId()));
+                        } else {
+                            map.put(query.getGroupType().getName(), vo.getGroupName());
+                        }
+                        map.put("考生总数", getStringVal(vo.getTotalCount()));
+                        map.put("已扫描", getStringVal(vo.getScannedCount()));
+                        map.put("未扫描", getStringVal(vo.getUnexistCount()));
+                        map.put("指定缺考", getStringVal(vo.getManualAbsentCount()));
+                        map.put("识别缺考", getStringVal(vo.getOmrAbsentCount()));
+                        map.put("缺考异常", getStringVal(vo.getAbsentSuspectCount()));
+                        ret.add(map);
+                    }
+                }
+                return ret;
+            }
+        };
+        String[] head;
+        if (GroupType.SUBJECT.equals(query.getGroupType())) {
+            head = new String[] { "科目代码", "科目名称", "考生总数", "已扫描", "未扫描", "指定缺考", "识别缺考", "缺考异常" };
+        } else {
+            head = new String[] { query.getGroupType().getName(), "考生总数", "已扫描", "未扫描", "指定缺考", "识别缺考", "缺考异常" };
+        }
+        ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+        writer.writeDataMaps("缺考核对", null, head, iterator);
+        writer.output(response.getOutputStream());
+    }
+
+    private String getSubjectName(Map<String, SubjectEntity> subMap, String subjectCode, Long examId) {
+        SubjectEntity subject = subMap.get(subjectCode);
+        if (subMap.get(subjectCode) == null) {
+            subject = subjectService.findByExamIdAndCode(examId, subjectCode);
+            if (subject == null) {
+                throw new StatusException("未找到科目信息:" + subjectCode);
+            }
+            subMap.put(subjectCode, subject);
+        }
+        return subject.getName();
+    }
+
+    private String getStringVal(Integer val) {
+        if (val == null) {
+            return "0";
+        }
+        return val.toString();
+    }
+
+    @PostMapping("absent/manual/import")
+    @ApiOperation(value = "导入指定缺考名单")
+    public AbsentManualImportVo absentManualImport(@RequestParam Long examId, @RequestParam MultipartFile file) {
+        return studentService.absentManualImport(examId, file);
+    }
+
+    @ApiOperation(value = "下载导入指定缺考名单模板")
+    @PostMapping("absent/manual/template")
+    public void getImportTemplate() throws IOException {
+        ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+        writer.writeObjects("指定缺考名单", null, ManualAbsentImportDTO.class,
+                new SingletonIterator<>(new ManualAbsentImportDTO()));
+        exportFile("指定缺考名单导入模板.xlsx", writer);
+    }
+
+    @ApiOperation(value = "更新为指定缺考")
+    @PostMapping("absent/manual/update")
+    public UpdateTimeVo absentManualUpdate(@RequestParam Long examId, @RequestParam String examNumber) {
+        return studentService.absentManualUpdate(examId, examNumber);
+    }
+
+    @ApiOperation(value = "取消缺考标记嫌疑")
+    @PostMapping("absent/suspect/remove")
+    public UpdateTimeVo absentSuspectRemove(@RequestParam Long examId, @RequestParam String examNumber) {
+        return studentService.absentSuspectUpdate(examId, examNumber, false);
+    }
+}

+ 78 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/CheckImageController.java

@@ -0,0 +1,78 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.BatchService;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.vo.checkimage.CheckImageSubmitVo;
+import cn.com.qmth.scancentral.vo.checkimage.RatioVo;
+import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
+import cn.com.qmth.scancentral.vo.verify.VerifyTaskVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "图片审核接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/check/image")
+@Aac(strict = false, auth = true)
+public class CheckImageController extends BaseController {
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private BatchService batchService;
+
+    @ApiOperation(value = "修改答题卡扫描图片抽查比例")
+    @PostMapping("ratio")
+    public RatioVo ratio(@RequestParam Long examId, @RequestParam Double ratio) {
+        return examService.updateRatio(examId, ratio);
+    }
+
+    @ApiOperation(value = "答题卡扫描图片检查任务状态")
+    @PostMapping("status")
+    public TaskStatusVo status(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return batchService.getCheckImageStatus(examId, user);
+    }
+
+    @ApiOperation(value = "获取答题卡扫描图片检查任务")
+    @RequestMapping(value = "get", method = RequestMethod.POST)
+    public VerifyTaskVo get(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return batchService.getCheckImageTask(examId, user);
+    }
+
+    @ApiOperation(value = "历史答题卡扫描图片检查任务")
+    @RequestMapping(value = "history", method = RequestMethod.POST)
+    public VerifyTaskVo history(@RequestParam Long examId, @RequestParam(required = false) Long batchId) {
+        User user = getAccessUser();
+        return batchService.getHistoryCheckImageTask(examId, batchId, user);
+    }
+
+    @ApiOperation(value = "答题卡扫描图片检查任务提交")
+    @RequestMapping(value = "submit", method = RequestMethod.POST)
+    public CheckImageSubmitVo submit(@RequestParam Long examId, @RequestParam Long batchId) {
+        User user = getAccessUser();
+        return batchService.submitCheckImageTask(examId, batchId, user);
+    }
+
+    @ApiOperation(value = "答题卡扫描图片检查任务释放")
+    @PostMapping("release")
+    public Object release(@RequestParam Long examId) {
+        User user = getAccessUser();
+        batchService.releaseCheckImageTask(examId, user);
+        return success(true);
+    }
+
+}

+ 194 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ExamController.java

@@ -0,0 +1,194 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import cn.com.qmth.scancentral.bean.ExamConfigDomain;
+import cn.com.qmth.scancentral.bean.SubjectConfigDomain;
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.ExamEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.exception.ParameterExceptions;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.service.SubjectService;
+import cn.com.qmth.scancentral.task.thread.ExamDataSyncThread;
+import cn.com.qmth.scancentral.vo.*;
+import cn.com.qmth.scancentral.vo.examinfo.ExamInfoVo;
+import cn.com.qmth.scancentral.vo.examinfo.ExamQuery;
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ReentrantException;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import net.sf.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@Api(tags = "考试接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/exam")
+@Aac(strict = false, auth = true)
+public class ExamController extends BaseController {
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Resource
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @Autowired
+    private SubjectService subjectService;
+
+    @ApiOperation(value = "获取考试详情")
+    @RequestMapping(value = "/info", method = RequestMethod.POST)
+    public ExamInfoVo info(@RequestParam Long id) {
+        User user = getAccessUser();
+        return examService.getExamInfoVo(id, user);
+    }
+
+    @ApiOperation(value = "获取考试列表")
+    @RequestMapping(value = "/list", method = RequestMethod.POST)
+    public PageResult<ExamVo> list(ExamQuery query) {
+        User user = getAccessUser();
+        return examService.pageQuery(query, user);
+    }
+
+    @ApiOperation(value = "启用/关闭考试")
+    @RequestMapping(value = "/toggle", method = RequestMethod.POST)
+    public JSONObject enable(@RequestParam Long id, @RequestParam Boolean enable) {
+        User user = getAccessUser();
+        JSONObject result = new JSONObject();
+        ExamEntity exam = examService.getById(id);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        exam.setEnable(enable);
+        exam.setUpdaterId(user.getId());
+        exam.setUpdateTime(System.currentTimeMillis());
+        examService.saveOrUpdate(exam);
+        result.accumulate("enable", enable);
+        result.accumulate("updateTime", exam.getUpdateTime());
+        return result;
+    }
+
+    @ApiOperation(value = "未启用考试")
+    @RequestMapping(value = "/remote/list", method = RequestMethod.POST)
+    public PageResult<ExamRemoteVo> remote(BaseQuery<?> query) {
+        User user = getAccessUser();
+        return examService.listRemote(query, user);
+    }
+
+    @ApiOperation(value = "启用新考试")
+    @RequestMapping(value = "/init", method = RequestMethod.POST)
+    public ExamInitVo init(@Validated(ExamConfigDomain.ExamConfigInit.class) ExamConfigDomain domain) {
+        User user = getAccessUser();
+        ExamInitVo vo = examService.init(user, domain);
+        return vo;
+    }
+
+    @ApiOperation(value = "修改考试参数")
+    @RequestMapping(value = "/config", method = RequestMethod.POST)
+    public ExamConfigVo config(@Validated ExamConfigDomain domain) {
+        User user = getAccessUser();
+        return examService.config(user, domain);
+    }
+
+    @ApiOperation(value = "修改考试上传状态")
+    @RequestMapping(value = "/upload", method = RequestMethod.POST)
+    public ExamUploadVo upload(@RequestParam Long id, @RequestParam Boolean enable) {
+        User user = getAccessUser();
+        ExamUploadVo vo = new ExamUploadVo();
+        ExamEntity exam = examService.getById(id);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        exam.setEnableUpload(enable);
+        examService.saveOrUpdate(exam);
+        vo.setEnable(exam.getEnableUpload());
+        vo.setUpdateTime(exam.getUpdateTime());
+        vo.setProgress(studentService.getUploadProgress(id));
+        return vo;
+    }
+
+    @ApiOperation(value = "同步考务数据")
+    @RequestMapping(value = "/data/sync", method = RequestMethod.POST)
+    public JSONObject sync(@RequestParam Long examId) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!concurrentService.isLocked(LockType.EXAM_SYNC + "-" + examId)) {
+            taskExecutor.submit(new ExamDataSyncThread(getAccessUser(), examId, examService, concurrentService));
+        } else {
+            throw new ReentrantException("正在同步,请稍后再试");
+        }
+        JSONObject result = new JSONObject();
+        result.put("synching", concurrentService.isLocked(LockType.EXAM_SYNC + "-" + examId));
+        result.put("progress", examService.syncProgress(user, examId));
+        result.put("updateTime", examService.getById(examId).getDataSyncTime());
+        return result;
+    }
+
+    @ApiOperation(value = "考务数据同步状态")
+    @RequestMapping(value = "/data/sync/status", method = RequestMethod.POST)
+    public Map<String, Object> syncStatus(@RequestParam Long examId) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("synching", concurrentService.isLocked(LockType.EXAM_SYNC + "-" + examId));
+        result.put("progress", examService.syncProgress(user, examId));
+        result.put("updateTime", examService.getById(examId).getDataSyncTime());
+        return result;
+    }
+
+    @ApiOperation(value = "修改考试科目参数")
+    @RequestMapping(value = "/subject/config", method = RequestMethod.POST)
+    public SubjectConfigVo subjectConfig(@Validated SubjectConfigDomain domain) {
+        User user = getAccessUser();
+        return subjectService.config(user, domain);
+    }
+
+    @ApiOperation(value = "修改考试实时审核状态")
+    @RequestMapping(value = "/syncVerify", method = RequestMethod.POST)
+    public Map<String, Object> syncVerify(@RequestParam Long examId, @RequestParam Boolean enable) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.updateEnableSyncVerify(user,examId, enable);
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("enable", enable);
+        result.put("updateTime", exam.getUpdateTime());
+        return result;
+    }
+}

+ 148 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ExamStatusCheckController.java

@@ -0,0 +1,148 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import cn.com.qmth.scancentral.bean.AnswerQueryDomain;
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.ExamEntity;
+import cn.com.qmth.scancentral.enums.ExamStatusCheckMode;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.exception.ParameterExceptions;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.task.thread.ExamStatusImportThread;
+import cn.com.qmth.scancentral.task.thread.ExamStatusResetThread;
+import cn.com.qmth.scancentral.vo.ExamStatusSaveVo;
+import cn.com.qmth.scancentral.vo.answerquery.AnswerQueryVo;
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ReentrantException;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import net.sf.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@Api(tags = "缺考校验接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/examStatusCheck")
+@Aac(strict = false, auth = true)
+public class ExamStatusCheckController extends BaseController {
+
+    @Autowired
+    private StudentService studentService;
+    @Resource
+    private ConcurrentService concurrentService;
+    @Autowired
+    private ExamService examService;
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @ApiOperation(value = "列表")
+    @RequestMapping(value = "/list", method = RequestMethod.POST)
+    public PageResult<AnswerQueryVo> list(@Validated AnswerQueryDomain query) {
+        return studentService.query(query);
+    }
+
+    @ApiOperation(value = "提交")
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    public ExamStatusSaveVo save(@RequestParam Long id, @RequestParam Boolean absent) {
+        studentService.updateExamStatus(id, absent);
+        return ExamStatusSaveVo.create(absent);
+    }
+
+    @ApiOperation(value = "导入")
+    @RequestMapping(value = "/import", method = RequestMethod.POST)
+    public JSONObject importFile(@RequestParam Long examId, @RequestParam ExamStatusCheckMode mode, @RequestParam MultipartFile file) throws IOException {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!concurrentService.isLocked(LockType.EXAM_STATUS_RESET + "-" + examId)) {
+            taskExecutor.submit(new ExamStatusImportThread( examId,mode, file.getInputStream(), studentService, concurrentService));
+        } else {
+            throw new ReentrantException("正在导入,请稍后再试");
+        }
+        JSONObject result = new JSONObject();
+        result.put("updateTime", System.currentTimeMillis());
+        result.put("synching", true);
+        return result;
+    }
+
+    @ApiOperation(value = "导入状态")
+    @RequestMapping(value = "/import/status", method = RequestMethod.POST)
+    public Map<String, Object> importStatus(@RequestParam Long examId) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("synching", concurrentService.isLocked(LockType.EXAM_STATUS_RESET + "-" + examId));
+        return result;
+    }
+
+    @ApiOperation(value = "重新生成")
+    @RequestMapping(value = "/reset", method = RequestMethod.POST)
+    public JSONObject reset(@RequestParam Long examId, @RequestParam Integer examNumberFillCount) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!concurrentService.isLocked(LockType.EXAM_STATUS_RESET + "-" + examId)) {
+            taskExecutor.submit(new ExamStatusResetThread( examId,examNumberFillCount, studentService, concurrentService));
+        } else {
+            throw new ReentrantException("正在重新生成,请稍后再试");
+        }
+        JSONObject result = new JSONObject();
+        result.put("updateTime", System.currentTimeMillis());
+        result.put("synching", true);
+        return result;
+    }
+    @ApiOperation(value = "重新生成状态")
+    @RequestMapping(value = "/reset/status", method = RequestMethod.POST)
+    public Map<String, Object> resetStatus(@RequestParam Long examId) {
+        User user = getAccessUser();
+        ExamEntity exam = examService.getById(examId);
+        if (exam == null) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        if (!exam.getSchoolId().equals(user.getSchoolId())) {
+            throw ParameterExceptions.EXAM_NOT_FOUND;
+        }
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("synching", concurrentService.isLocked(LockType.EXAM_STATUS_RESET + "-" + examId));
+        return result;
+    }
+
+    @ApiOperation(value = "查询概要")
+    @RequestMapping(value = "/summary", method = RequestMethod.POST)
+    public List<String> summary(@Validated AnswerQueryDomain query) {
+        return studentService.summary(query);
+    }
+
+}

+ 66 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ExamWorkController.java

@@ -0,0 +1,66 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.entity.SubjectEntity;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.service.SubjectService;
+import cn.com.qmth.scancentral.vo.CampusVo;
+import cn.com.qmth.scancentral.vo.ExamSiteVo;
+import cn.com.qmth.scancentral.vo.student.StudentQuery;
+import cn.com.qmth.scancentral.vo.student.StudentVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "考务接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin")
+@Aac(strict = false, auth = true)
+public class ExamWorkController {
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private SubjectService subjectService;
+
+    @ApiOperation(value = "获取考生信息")
+    @RequestMapping(value = "/student/find", method = RequestMethod.POST)
+    public StudentVo student(@Validated StudentQuery query) {
+        return studentService.findOne(query);
+    }
+
+    @ApiOperation(value = "获取科目列表")
+    @RequestMapping(value = "/subject/list", method = RequestMethod.POST)
+    public List<SubjectEntity> subjectList(@RequestParam Long examId) {
+        return subjectService.listByExamId(examId);
+    }
+
+    @ApiOperation(value = "获取科目信息")
+    @RequestMapping(value = "/subject/find", method = RequestMethod.POST)
+    public SubjectEntity subjectFind(@RequestParam Long examId, @RequestParam String code) {
+        return subjectService.findByExamIdAndCode(examId, code);
+    }
+
+    @ApiOperation(value = "获取所有学习中心")
+    @RequestMapping(value = "/campus/list", method = RequestMethod.POST)
+    public List<CampusVo> campusList(@RequestParam Long examId) {
+        return studentService.listCampusByExamId(examId);
+    }
+
+    @ApiOperation(value = "获取所有考点")
+    @RequestMapping(value = "/exam_site/list", method = RequestMethod.POST)
+    public List<ExamSiteVo> siteList(@RequestParam Long examId) {
+        return studentService.listSiteByExamId(examId);
+    }
+}

+ 100 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/FileController.java

@@ -0,0 +1,100 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.core.retrofit.utils.SignatureInfo;
+import com.qmth.boot.core.retrofit.utils.UploadFile;
+import com.qmth.boot.tools.signature.SignatureType;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.client.MarkingcloudApiClient;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.SubjectEntity;
+import cn.com.qmth.scancentral.enums.FormatType;
+import cn.com.qmth.scancentral.enums.SystemMode;
+import cn.com.qmth.scancentral.model.FileUploadResponse;
+import cn.com.qmth.scancentral.service.SubjectService;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "试卷答案文件上传接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/file")
+@Aac(strict = false, auth = true)
+public class FileController extends BaseController {
+
+    private static final Logger log = LoggerFactory.getLogger(FileController.class);
+
+    @Autowired
+    private SubjectService subjectService;
+
+    @Autowired
+    private MarkingcloudApiClient markingcloudApiClient;
+
+    @ApiOperation(value = "答案上传接口")
+    @RequestMapping(value = "/answer/upload", method = RequestMethod.POST)
+    public UpdateTimeVo answerUpload(@RequestParam Long examId, @RequestParam String subjectCode,
+            @RequestParam String md5, @RequestParam MultipartFile file) {
+        if (!SystemMode.MARKINGCLOUD.equals(SystemMode.current())) {
+            throw new ParameterException("K12模式不允许创建通卡");
+        }
+        SubjectEntity sub = subjectService.findByExamIdAndCode(examId, subjectCode);
+        if (sub == null) {
+            throw new ParameterException("未找到科目,请先同步或创建考生获取科目");
+        }
+        User user = getAccessUser();
+        SignatureInfo signatureInfo = new SignatureInfo(SignatureType.TOKEN, user.getAccount(),
+                user.getMarkingCloudToken());
+        try {
+            FileUploadResponse res = markingcloudApiClient
+                    .answerFileUpload(signatureInfo, examId, subjectCode, FormatType.PDF.toString(), md5,
+                            UploadFile.build("file", "", file.getBytes()));
+            if (res == null || !res.getSuccess()) {
+                throw new StatusException("答案上传出错,examId=" + examId + ", subjectCode=" + subjectCode);
+            }
+        } catch (Exception e) {
+            log.error("答案上传出错,examId=" + examId + ", subjectCode=" + subjectCode, e);
+        }
+        return result(System.currentTimeMillis());
+    }
+
+    @ApiOperation(value = "试卷上传接口")
+    @RequestMapping(value = "/paper/upload", method = RequestMethod.POST)
+    public UpdateTimeVo paperUpload(@RequestParam Long examId, @RequestParam String subjectCode,
+            @RequestParam String md5, @RequestParam MultipartFile file) {
+        if (!SystemMode.MARKINGCLOUD.equals(SystemMode.current())) {
+            throw new ParameterException("K12模式不允许创建通卡");
+        }
+        SubjectEntity sub = subjectService.findByExamIdAndCode(examId, subjectCode);
+        if (sub == null) {
+            throw new ParameterException("未找到科目,请先同步或创建考生获取科目");
+        }
+        User user = getAccessUser();
+        SignatureInfo signatureInfo = new SignatureInfo(SignatureType.TOKEN, user.getAccount(),
+                user.getMarkingCloudToken());
+        try {
+            FileUploadResponse res = markingcloudApiClient
+                    .paperFileUpload(signatureInfo, examId, subjectCode, FormatType.PDF.toString(), md5,
+                            UploadFile.build("file", "", file.getBytes()));
+            if (res == null || !res.getSuccess()) {
+                throw new StatusException("试卷上传出错,examId=" + examId + ", subjectCode=" + subjectCode);
+            }
+        } catch (Exception e) {
+            log.error("试卷上传出错,examId=" + examId + ", subjectCode=" + subjectCode, e);
+        }
+        return result(System.currentTimeMillis());
+    }
+
+}

+ 142 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/OmrGroupController.java

@@ -0,0 +1,142 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.concurrent.model.Semaphore;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.ReentrantException;
+
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.OmrGroupEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.enums.Stage;
+import cn.com.qmth.scancentral.service.OmrGroupService;
+import cn.com.qmth.scancentral.task.thread.OmrGroupBuildThread;
+import cn.com.qmth.scancentral.task.thread.OmrGroupDeleteThread;
+import cn.com.qmth.scancentral.task.thread.OmrGroupResetThread;
+import cn.com.qmth.scancentral.vo.OmrGroupUpdateVo;
+import cn.com.qmth.scancentral.vo.OmrGroupVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import net.sf.json.JSONObject;
+
+@RestController
+@Api(tags = "识别对照任务组接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/check/omr/group")
+@Aac(strict = false, auth = true)
+public class OmrGroupController extends BaseController {
+
+    @Autowired
+    private OmrGroupService groupService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @ApiOperation(value = "查询识别对照任务列表")
+    @RequestMapping(value = "/list", method = RequestMethod.POST)
+    public List<OmrGroupVo> list(@RequestParam Long examId) {
+        return groupService.listByExamId(examId);
+    }
+
+    @ApiOperation(value = "创建修改识别对照任务组")
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    public JSONObject save(@RequestBody OmrGroupUpdateVo omrGroupUpdateVo) {
+        OmrGroupEntity group = groupService
+                .save(omrGroupUpdateVo.getId(), omrGroupUpdateVo.getExamId(), omrGroupUpdateVo.getConditionList(),
+                        getAccessUser().getId());
+        JSONObject result = new JSONObject();
+        result.accumulate("id", group.getId());
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+
+    @ApiOperation(value = "修改识别对照任务组阶段")
+    @RequestMapping(value = "/toggle", method = RequestMethod.POST)
+    public JSONObject toggle(@RequestParam Long id) {
+        groupService.updateStage(id, getAccessUser().getId());
+        JSONObject result = new JSONObject();
+        result.accumulate("stage", Stage.SECOND);
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+
+    @ApiOperation(value = "识别对照任务组生成任务")
+    @RequestMapping(value = "/build", method = RequestMethod.POST)
+    public JSONObject build(@RequestParam Long id) {
+        OmrGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        if (group.getFixed()) {
+            throw new ParameterException("固定分组不支持手动创建任务");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.OMR_GROUP_BUILD + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new OmrGroupBuildThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("id", id);
+        result.accumulate("building", !semaphore.isAvailable());
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+
+    @ApiOperation(value = "重置识别对照任务组")
+    @RequestMapping(value = "/reset", method = RequestMethod.POST)
+    public JSONObject reset(@RequestParam Long id) {
+        OmrGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.OMR_GROUP_RESET + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new OmrGroupResetThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("id", id);
+        result.accumulate("reseting", !semaphore.isAvailable());
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+
+    @ApiOperation(value = "删除识别对照任务组")
+    @RequestMapping(value = "/delete", method = RequestMethod.POST)
+    public JSONObject delete(@RequestParam Long id) {
+        OmrGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        if (group.getFixed()) {
+            throw new ParameterException("固定分组不能删除");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.OMR_GROUP_DELETE + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new OmrGroupDeleteThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("id", id);
+        result.accumulate("deleting", !semaphore.isAvailable());
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+}

+ 235 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ScanAnswerController.java

@@ -0,0 +1,235 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.iterator.PageListIterator;
+
+import cn.com.qmth.scancentral.bean.AnswerDeleteDomain;
+import cn.com.qmth.scancentral.bean.AnswerQueryDomain;
+import cn.com.qmth.scancentral.bean.BatchQueryDomain;
+import cn.com.qmth.scancentral.bean.MismatchQueryDomain;
+import cn.com.qmth.scancentral.bean.MismatchToggleDomain;
+import cn.com.qmth.scancentral.bean.PageDeleteDomain;
+import cn.com.qmth.scancentral.bean.omredit.OmrEditDomain;
+import cn.com.qmth.scancentral.bean.papermigrate.PaperMigrateDomain;
+import cn.com.qmth.scancentral.bean.refix.AnswerRefixDomain;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.ExamEntity;
+import cn.com.qmth.scancentral.entity.ExamSummaryEntity;
+import cn.com.qmth.scancentral.enums.ExamMode;
+import cn.com.qmth.scancentral.service.BatchService;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.service.ExamSummaryService;
+import cn.com.qmth.scancentral.service.PaperService;
+import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.vo.AnswerDeleteVo;
+import cn.com.qmth.scancentral.vo.AnswerExportK12Vo;
+import cn.com.qmth.scancentral.vo.AnswerExportVo;
+import cn.com.qmth.scancentral.vo.AnswerRefixVo;
+import cn.com.qmth.scancentral.vo.BatchQueryVo;
+import cn.com.qmth.scancentral.vo.MismatchToggleVo;
+import cn.com.qmth.scancentral.vo.PaperDeleteVo;
+import cn.com.qmth.scancentral.vo.PaperMigrateVo;
+import cn.com.qmth.scancentral.vo.ScanAnswerInfoVo;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
+import cn.com.qmth.scancentral.vo.UriVo;
+import cn.com.qmth.scancentral.vo.answerquery.AnswerQueryVo;
+import cn.com.qmth.scancentral.vo.batchdetail.BatchDetailVo;
+import cn.com.qmth.scancentral.vo.mismatchquery.MismatchQueryVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描管理-答题卡接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/scan/answer")
+@Aac(strict = false, auth = true)
+public class ScanAnswerController extends BaseController {
+
+    @Autowired
+    private BatchService batchService;
+
+    @Autowired
+    private PaperService paperService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private ExamSummaryService examSummaryService;
+
+    @ApiOperation(value = "查询答题卡扫描批次所有关联的扫描员")
+    @RequestMapping(value = "batch/scanner", method = RequestMethod.POST)
+    public List<String> batchScanner(@RequestParam Long examId) {
+        return batchService.batchScanner(examId);
+    }
+
+    @ApiOperation(value = "查询答题卡扫描批次信息")
+    @RequestMapping(value = "batch/query", method = RequestMethod.POST)
+    public PageResult<BatchQueryVo> batchQuery(@Validated BatchQueryDomain query) {
+        return batchService.batchQuery(query);
+    }
+
+    @ApiOperation(value = "查询答题卡扫描批次概要")
+    @RequestMapping(value = "batch/summary", method = RequestMethod.POST)
+    public List<Long> batchSummary(@Validated BatchQueryDomain query) {
+        return batchService.batchSummary(query);
+    }
+
+    @ApiOperation(value = "查询答题卡扫描批次详情")
+    @RequestMapping(value = "batch/detail", method = RequestMethod.POST)
+    public BatchDetailVo batchDetail(@RequestParam Long id) {
+        return batchService.batchDetail(id);
+    }
+
+    @ApiOperation(value = "查询异常答题卡信息")
+    @RequestMapping(value = "mismatch/query", method = RequestMethod.POST)
+    public PageResult<MismatchQueryVo> mismatchQuery(@Validated MismatchQueryDomain query) {
+        return paperService.mismatchQuery(query);
+    }
+
+    @ApiOperation(value = "修改异常答题卡标记")
+    @RequestMapping(value = "mismatch/toggle", method = RequestMethod.POST)
+    public MismatchToggleVo mismatchToggle(@Validated MismatchToggleDomain domain) {
+        return paperService.mismatchToggle(domain);
+    }
+
+    @ApiOperation(value = "答题卡扫描汇总")
+    @PostMapping("info")
+    public ScanAnswerInfoVo scanAnswerInfo(@RequestParam Long examId) {
+        ExamSummaryEntity es = examSummaryService.find(examId);
+        ScanAnswerInfoVo vo = new ScanAnswerInfoVo();
+        vo.setTotalCount(es.getStudentCount());
+        vo.setScannedCount(es.getAnswerScannedCount());
+        vo.setUnexistCount(es.getAnswerUnexistCount());
+        vo.setManualAbsentCount(es.getAnswerManualAbsentCount());
+        vo.setOmrAbsentCount(es.getAnswerOmrAbsentCount());
+        vo.setAbsentSuspectCount(es.getAnswerAbsentSuspectCount());
+        vo.setAssignedCount(es.getAnswerAssignedCount());
+        vo.setIncompleteCount(es.getAnswerIncompleteCount());
+        return vo;
+    }
+
+    @ApiOperation(value = "查询答题卡扫描详情")
+    @RequestMapping(value = "query", method = RequestMethod.POST)
+    public PageResult<AnswerQueryVo> query(@Validated AnswerQueryDomain query) {
+        return studentService.query(query);
+    }
+
+    @ApiOperation(value = "查询答题卡扫描概要")
+    @RequestMapping(value = "summary", method = RequestMethod.POST)
+    public List<String> summary(@Validated AnswerQueryDomain query) {
+        return studentService.summary(query);
+    }
+
+    @ApiOperation(value = "导出答题卡扫描详情")
+    @RequestMapping(value = "export", method = RequestMethod.POST)
+    public void export(@Validated AnswerQueryDomain query, HttpServletResponse response) throws IOException {
+        String fileName = URLEncoder.encode("答题卡扫描详情", "UTF-8");
+        response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");
+        response.setContentType("application/vnd.ms-excel");
+        ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+        ExamEntity exam = examService.getById(query.getExamId());
+        if (ExamMode.K12.equals(exam.getMode())) {
+            PageListIterator<AnswerExportK12Vo> iterator = new PageListIterator<AnswerExportK12Vo>(5000) {
+
+                @Override
+                public Collection<AnswerExportK12Vo> getPageList(int pageNumber, int pageSize) {
+                    query.setPageNumber(pageNumber);
+                    query.setPageSize(pageSize);
+                    return studentService.exportListK12(query);
+                }
+            };
+            writer.writeObjects("扫描详情", null, AnswerExportK12Vo.class, iterator);
+            writer.output(response.getOutputStream());
+        } else {
+            PageListIterator<AnswerExportVo> iterator = new PageListIterator<AnswerExportVo>(5000) {
+
+                @Override
+                public Collection<AnswerExportVo> getPageList(int pageNumber, int pageSize) {
+                    query.setPageNumber(pageNumber);
+                    query.setPageSize(pageSize);
+                    return studentService.exportList(query);
+                }
+            };
+            writer.writeObjects("扫描详情", null, AnswerExportVo.class, iterator);
+            writer.output(response.getOutputStream());
+
+        }
+
+    }
+
+    @ApiOperation(value = "修改答题卡识别结果")
+    @RequestMapping(value = "omr/edit", method = RequestMethod.POST)
+    public UpdateTimeVo omrEdit(@Validated @RequestBody OmrEditDomain domain) {
+        return studentService.omrEdit(getAccessUser(), domain);
+    }
+
+    @ApiOperation(value = "更新答题卡裁切图")
+    @RequestMapping(value = "slice/update", method = RequestMethod.POST)
+    public UriVo sliceUpdate(@RequestParam Long paperId, @RequestParam Integer pageIndex, @RequestParam Integer index,
+            @RequestParam MultipartFile file, @RequestParam String md5) {
+        return paperService.adminSliceUpdate(paperId, pageIndex, index, file, md5);
+    }
+
+    @ApiOperation(value = "更新答题卡原图")
+    @RequestMapping(value = "sheet/update", method = RequestMethod.POST)
+    public UriVo sheetUpdate(@RequestParam Long paperId, @RequestParam Integer pageIndex,
+            @RequestParam MultipartFile file, @RequestParam String md5) {
+        return paperService.adminSheetUpdate(paperId, pageIndex, file, md5);
+    }
+
+    @ApiOperation(value = "上传答题卡裁切图")
+    @RequestMapping(value = "slice/upload", method = RequestMethod.POST)
+    public UriVo sliceUpload(@RequestParam Long paperId, @RequestParam Integer pageIndex, @RequestParam Integer index,
+            @RequestParam MultipartFile file, @RequestParam String md5) {
+        return paperService.adminSliceUpload(paperId, pageIndex, index, file, md5);
+    }
+
+    @ApiOperation(value = "修改答题卡扫描图片绑定考生")
+    @RequestMapping(value = "paper/migrate", method = RequestMethod.POST)
+    public PaperMigrateVo paperMigrate(@Validated @RequestBody PaperMigrateDomain domain) {
+        return paperService.paperMigrate(getAccessUser(), domain);
+    }
+
+    @ApiOperation(value = "删除答题卡单页扫描结果")
+    @RequestMapping(value = "paper/delete", method = RequestMethod.POST)
+    public PaperDeleteVo paperDelete(@Validated PageDeleteDomain domain) {
+        return studentService.paperDelete(getAccessUser(), domain);
+    }
+
+    @ApiOperation(value = "删除答题卡扫描结果")
+    @RequestMapping(value = "delete", method = RequestMethod.POST)
+    public AnswerDeleteVo answerDelete(@Validated AnswerDeleteDomain domain) {
+        return studentService.answerDelete(getAccessUser(), domain);
+    }
+
+    @ApiOperation(value = "答题卡二次识别")
+    @RequestMapping(value = "refix", method = RequestMethod.POST)
+    public AnswerRefixVo refix(@Validated @RequestBody AnswerRefixDomain query) {
+        return studentService.answerRefix(getAccessUser(), query);
+    }
+
+}

+ 108 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ScannerController.java

@@ -0,0 +1,108 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+
+import cn.com.qmth.scancentral.bean.CardAnswerDomain;
+import cn.com.qmth.scancentral.bean.WorkloadDomain;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.BatchService;
+import cn.com.qmth.scancentral.service.ScannerCardService;
+import cn.com.qmth.scancentral.service.ScannerService;
+import cn.com.qmth.scancentral.service.SystemConfigService;
+import cn.com.qmth.scancentral.util.DateUtil;
+import cn.com.qmth.scancentral.vo.ScannerWorkloadVo;
+import cn.com.qmth.scancentral.vo.UpdateCountVo;
+import cn.com.qmth.scancentral.vo.scannerinfo.Control;
+import cn.com.qmth.scancentral.vo.scannerinfo.ScannerInfoVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描员管理接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/scanner")
+@Aac(strict = false, auth = true)
+public class ScannerController extends BaseController {
+
+    @Autowired
+    private ScannerService scannerService;
+
+    @Autowired
+    private ScannerCardService scannerCardService;
+
+    @Autowired
+    private BatchService batchService;
+
+    @Autowired
+    private SystemConfigService systemConfigService;
+
+    @ApiOperation(value = "扫描员管理详情")
+    @PostMapping("info")
+    public ScannerInfoVo scannerInfo(@RequestParam Long examId) {
+        return scannerService.scannerInfo(examId);
+    }
+
+    @ApiOperation(value = "扫描员工作量统计")
+    @PostMapping("workload")
+    public List<ScannerWorkloadVo> workload(WorkloadDomain domain) {
+        return batchService.workload(domain);
+    }
+
+    @ApiOperation(value = "修改扫描员登陆控制")
+    @PostMapping("control")
+    public Control control(Control control) {
+        return systemConfigService.control(control);
+    }
+
+    @ApiOperation(value = "修改扫描员绑定卡格式")
+    @PostMapping("card/answer")
+    public UpdateCountVo cardAnswer(CardAnswerDomain domain) {
+        return scannerCardService.cardAnswer(domain);
+    }
+
+    @ApiOperation(value = "扫描员工作量统计导出")
+    @PostMapping("workload/export")
+    public void workloadExport(WorkloadDomain domain, HttpServletResponse response) throws IOException {
+        String fileName = URLEncoder.encode("扫描员工作量统计", "UTF-8");
+        response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");
+        response.setContentType("application/vnd.ms-excel");
+        ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+        // PageListIterator<ScannerWorkloadVo> iterator = new
+        // PageListIterator<ScannerWorkloadVo>(100) {
+        //
+        // @Override
+        // public Collection<ScannerWorkloadVo> getPageList(int pageNumber, int
+        // pageSize) {
+        // return batchService.workload(domain);
+        // }
+        // };
+        List<ScannerWorkloadVo> ret = batchService.workload(domain);
+        String title = getText(domain.getStartTime()) + "-" + getText(domain.getEndTime());
+        String[] titles = new String[1];
+        titles[0] = title;
+        writer.writeObjects("扫描员工作量统计", titles, ScannerWorkloadVo.class, ret.iterator());
+        writer.output(response.getOutputStream());
+    }
+
+    private String getText(Long time) {
+        if (time == null) {
+            return "";
+        }
+        return DateUtil.format(new Date(time), DateUtil.DatePatterns.CHINA_DEFAULT);
+    }
+}

+ 139 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/StudentAnswerGroupController.java

@@ -0,0 +1,139 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.concurrent.model.Semaphore;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.ReentrantException;
+
+import cn.com.qmth.scancentral.bean.answergroup.StudentAnswerGroupCondition;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupCountVo;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupIdVo;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupMarkedVo;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupSummaryQuery;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupSummaryVo;
+import cn.com.qmth.scancentral.bean.answergroup.StudnetAnswerGroupVo;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.StudentAnswerGroupEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.service.StudentAnswerGroupService;
+import cn.com.qmth.scancentral.task.thread.StudentAnswerGroupBuildThread;
+import cn.com.qmth.scancentral.task.thread.StudentAnswerGroupDeleteThread;
+import cn.com.qmth.scancentral.task.thread.StudentAnswerGroupResetThread;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import net.sf.json.JSONObject;
+
+@RestController
+@Api(tags = "数据检查任务组接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/check/student/answer/group")
+@Aac(strict = false, auth = true)
+public class StudentAnswerGroupController extends BaseController {
+
+    @Autowired
+    private StudentAnswerGroupService groupService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @ApiOperation(value = "抽查任务列表")
+    @PostMapping("list")
+    public StudnetAnswerGroupVo list(@RequestParam Long examId) {
+        return groupService.listByExamId(examId);
+    }
+    
+    @ApiOperation(value = "抽查任务查询计数")
+    @PostMapping("count")
+    public StudnetAnswerGroupCountVo count(StudentAnswerGroupCondition domain) {
+        return groupService.countByCondition(domain);
+    }
+    
+    @ApiOperation(value = "抽查任务创建")
+    @PostMapping("save")
+    public StudnetAnswerGroupIdVo save(StudentAnswerGroupCondition domain) {
+        return groupService.saveByCondition(domain);
+    }
+
+
+    @ApiOperation(value = "抽查任务组生成任务")
+    @RequestMapping(value = "build", method = RequestMethod.POST)
+    public JSONObject build(@RequestParam Long id) {
+        StudentAnswerGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.STUDENT_ANSWER_GROUP_BUILD + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new StudentAnswerGroupBuildThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("id", id);
+        result.accumulate("building", !semaphore.isAvailable());
+        result.accumulate("updateTime", System.currentTimeMillis());
+        return result;
+    }
+
+    @ApiOperation(value = "重置抽查任务组")
+    @RequestMapping(value = "reset", method = RequestMethod.POST)
+    public JSONObject reset(@RequestParam Long id) {
+        StudentAnswerGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.STUDENT_ANSWER_GROUP_RESET + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new StudentAnswerGroupResetThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("id", id);
+        return result;
+    }
+
+    @ApiOperation(value = "删除抽查任务组")
+    @RequestMapping(value = "delete", method = RequestMethod.POST)
+    public JSONObject delete(@RequestParam Long id) {
+        StudentAnswerGroupEntity group = groupService.getById(id);
+        if (group == null) {
+            throw new ParameterException("分组不存在");
+        }
+        Semaphore semaphore = concurrentService.getSemaphore(LockType.STUDENT_ANSWER_GROUP_DELETE + "-" + id);
+        if (semaphore.tryAcquire()) {
+            taskExecutor.submit(new StudentAnswerGroupDeleteThread(id, groupService, concurrentService, semaphore));
+        } else {
+            throw new ReentrantException("该分组数据操作繁忙,请稍后重试");
+        }
+        JSONObject result = new JSONObject();
+        result.accumulate("success", true);
+        return result;
+    }
+    
+    @ApiOperation(value = "获取异常题卡概要")
+    @PostMapping("summary")
+    public StudnetAnswerGroupSummaryVo summary(StudnetAnswerGroupSummaryQuery query) {
+        return groupService.summary(query);
+    }
+    
+    @ApiOperation(value = "获取标记异常考生列表")
+    @PostMapping("marked")
+    public List<StudnetAnswerGroupMarkedVo> marked(@RequestParam Long examId) {
+        return groupService.marked(examId);
+    }
+}

+ 168 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/ToolController.java

@@ -0,0 +1,168 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import cn.com.qmth.scancentral.bean.*;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.AnswerCardEntity;
+import cn.com.qmth.scancentral.entity.ExamEntity;
+import cn.com.qmth.scancentral.entity.QuestionEntity;
+import cn.com.qmth.scancentral.entity.SubjectEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.enums.SystemMode;
+import cn.com.qmth.scancentral.service.*;
+import cn.com.qmth.scancentral.vo.*;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.ReentrantException;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RestController
+@Api(tags = "本地工具接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/tool")
+public class ToolController extends BaseController {
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private SubjectService subjectService;
+
+    @Autowired
+    private ToolExportService toolExportService;
+
+    @Autowired
+    private QuestionService questionService;
+
+    @Autowired
+    private AnswerCardService answerCardService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @ApiOperation(value = "批量创建管理员用户接口")
+    @PostMapping("/import/user")
+    public CountVo importUser(@RequestBody List<ImportUserDomain> users) {
+        return new CountVo(userService.importUser(users));
+    }
+
+    @ApiOperation(value = "创建考试接口")
+    @PostMapping("/import/exam")
+    public ImportExamVo importExam(@RequestBody ImportExamDomain exam) {
+        return new ImportExamVo(examService.save(exam).getId());
+    }
+
+    @ApiOperation(value = "批量创建科目接口")
+    @PostMapping("/import/course")
+    public CountVo importCourse(@RequestBody List<ImportSubjectDomain> subjects) {
+        return new CountVo(subjectService.importSubject(subjects));
+    }
+
+    @ApiOperation(value = "批量新建考生接口")
+    @PostMapping("/import/exam/student")
+    public CountVo importStudent(@RequestBody List<ImportStudentDomain> students) {
+        return new CountVo(studentService.importStudent(students));
+    }
+
+    @ApiOperation(value = "查询考生数量接口")
+    @PostMapping("/exam/student/count")
+    public CountVo countStudent(@RequestBody ImportStudentQueryVo query) {
+        return new CountVo(studentService.countByQuery(query));
+    }
+
+    @ApiOperation(value = "分页查询考生接口")
+    @PostMapping("/exam/student/find")
+    public List<ImportStudentVo> findStudent(@RequestBody ImportStudentQueryVo query) {
+        return studentService.findByQuery(query);
+    }
+
+    @ApiOperation(value = "检查并修复考生导出图片接口")
+    @PostMapping("/exam/student/check")
+    public void studentCheck(@RequestParam Long studentId) {
+        toolExportService.studentCheck(studentId);
+    }
+
+    @ApiOperation(value = "获取考试详情")
+    @RequestMapping(value = "/exam/info", method = RequestMethod.POST)
+    public ExamEntity examInfo(@RequestParam Long examId) {
+        return examService.getById(examId);
+    }
+
+    @ApiOperation(value = "获取科目详情")
+    @RequestMapping(value = "/exam/subject/info", method = RequestMethod.POST)
+    public SubjectEntity subjectInfo(@RequestBody SubjectCheckVo query) {
+        Long examId = query.getExamId();
+        String code = query.getSubjectCode();
+        if (examId == null) {
+            throw new ParameterException("examId 不能为空");
+        }
+        if (StringUtils.isBlank(code)) {
+            throw new ParameterException("subjectCode 不能为空");
+        }
+        return subjectService.findByExamIdAndCode(examId, code);
+    }
+
+    @ApiOperation(value = "清空考生数据")
+    @RequestMapping(value = "/exam/student/clean", method = RequestMethod.POST)
+    public void studentClean(@RequestParam Long examId) {
+        studentService.studentClean(examId);
+    }
+
+    @ApiOperation(value = "cet导入违纪接口")
+    @PostMapping("/import/cet/breach")
+    public CountVo importCetAbsent(@RequestBody List<ImportCetAbsentDomain> students) {
+        return new CountVo(studentService.importCetAbsent(students));
+    }
+
+    @ApiOperation(value = "cet导出扫描结果和评卷分配数据")
+    @PostMapping("/export/cet/data")
+    public List<ExportCetVo> exportCetData(@RequestBody ExportCetMarkingQueryVo query) {
+        return studentService.exportCetData(query);
+    }
+
+    @ApiOperation(value = "查询试卷结构")
+    @PostMapping("/import/question/query")
+    public List<QuestionEntity> questionQuery(@RequestParam Long examId, @RequestParam String subjectCode) {
+        return questionService.findByExamIdAndSubjectCode(examId, subjectCode);
+    }
+
+    @ApiOperation(value = "保存试卷结构")
+    @PostMapping("/import/question/save")
+    public CountVo importQuestion(@RequestBody List<QuestionEntity> questionList) {
+        return new CountVo(questionService.importQuestion(questionList));
+    }
+
+    @ApiOperation(value = "保存卡格式")
+    @PostMapping("/import/card/save")
+    public Integer importCard(@RequestParam Long examId,
+                              @RequestParam String subjectCode, @RequestParam Integer paperCount,
+                              @RequestParam Boolean singlePage, @RequestParam String md5, @RequestParam(required = false) Integer dpi, @RequestParam MultipartFile file) {
+        if (!SystemMode.STANDALONE.equals(SystemMode.current())) {
+            throw new ParameterException("非独立模式不可调用");
+        }
+        if (concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).tryLock()) {
+            try {
+                AnswerCardEntity card = answerCardService.save(null, examId, null, subjectCode,
+                        null, null, paperCount, singlePage, md5, dpi, file);
+                return card.getNumber();
+            } finally {
+                concurrentService.getLock(LockType.CARD_SYNC + "-" + examId).unlock();
+            }
+        } else {
+            throw new ReentrantException("正在同步卡格式,请稍后再试");
+        }
+    }
+
+}

+ 34 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/UserController.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.UserService;
+import cn.com.qmth.scancentral.vo.UserVo;
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@Api(tags = "用户接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/user")
+@Aac(strict = false, auth = true)
+public class UserController extends BaseController {
+
+    @Autowired
+    private UserService userService;
+
+    @ApiOperation(value = "审核员列表")
+    @PostMapping("list")
+    public List<UserVo> list() {
+        User user = getAccessUser();
+        return userService.findAdmin(user.getSchoolId());
+    }
+
+}

+ 51 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/VerifyController.java

@@ -0,0 +1,51 @@
+package cn.com.qmth.scancentral.controller.admin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.BatchService;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
+import cn.com.qmth.scancentral.vo.verify.VerifyTaskVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "答题卡扫描实时审核接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/verify/scan")
+@Aac(strict = false, auth = true)
+public class VerifyController extends BaseController {
+
+    @Autowired
+    private BatchService batchService;
+
+    @ApiOperation(value = "获取答题卡扫描实时审核任务")
+    @RequestMapping(value = "/answer/get", method = RequestMethod.POST)
+    public VerifyTaskVo answerGet(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return batchService.getVerifyTask(examId, user);
+    }
+
+    @ApiOperation(value = "答题卡扫描实时审核任务处理")
+    @RequestMapping(value = "/answer/submit", method = RequestMethod.POST)
+    public UpdateTimeVo answerSubmit(@RequestParam Long batchId, @RequestParam Boolean confirm) {
+        User user = getAccessUser();
+        batchService.verify(batchId, confirm, user);
+        return result(System.currentTimeMillis());
+    }
+
+    @ApiOperation(value = "答题卡扫描实时审核任务释放")
+    @RequestMapping(value = "/answer/release", method = RequestMethod.POST)
+    public Object answerRelease(@RequestParam Long examId) {
+        User user = getAccessUser();
+        batchService.releaseCheckImageTask(examId, user);
+        return success(true);
+    }
+}

+ 119 - 0
src/main/java/cn/com/qmth/scancentral/controller/scan/AnswerController.java

@@ -0,0 +1,119 @@
+package cn.com.qmth.scancentral.controller.scan;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.api.utils.RequestUtil;
+
+import cn.com.qmth.scancentral.bean.BatchCreateDomain;
+import cn.com.qmth.scancentral.bean.MismatchToggleDomain;
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.bean.answersave.AnswerDomain;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.AdapteFileService;
+import cn.com.qmth.scancentral.service.AnswerCardService;
+import cn.com.qmth.scancentral.service.BatchService;
+import cn.com.qmth.scancentral.service.PaperService;
+import cn.com.qmth.scancentral.vo.AnswerCardVo;
+import cn.com.qmth.scancentral.vo.BatchFinishVo;
+import cn.com.qmth.scancentral.vo.BatchVerifyVo;
+import cn.com.qmth.scancentral.vo.MismatchToggleVo;
+import cn.com.qmth.scancentral.vo.SheetUploadVo;
+import cn.com.qmth.scancentral.vo.SliceUploadVo;
+import cn.com.qmth.scancentral.vo.UriVo;
+import cn.com.qmth.scancentral.vo.batch.AnswerSaveVo;
+import cn.com.qmth.scancentral.vo.batch.BatchCreateVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描功能-答题卡接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/scan/answer")
+@Aac(strict = false, auth = true)
+public class AnswerController extends BaseController {
+
+    @Autowired
+    private AnswerCardService answerCardService;
+
+    @Autowired
+    private BatchService batchService;
+
+    @Autowired
+    private PaperService paperService;
+
+    @Autowired
+    private AdapteFileService adapteFileService;
+
+    @ApiOperation(value = "答题卡卡格式列表")
+    @RequestMapping(value = "/card/list", method = RequestMethod.POST)
+    public List<AnswerCardVo> cardList(HttpServletRequest request, @RequestParam Long examId) {
+        return answerCardService.listByExamIdForScanner(examId, getAccessUser(), RequestUtil.getIpAddress(request));
+    }
+
+    @ApiOperation(value = "答题卡适配卡格式上传")
+    @RequestMapping(value = "/card/adapte/upload", method = RequestMethod.POST)
+    public UriVo adapteUpload(HttpServletRequest request, @RequestParam Long examId, @RequestParam Integer cardNumber,
+            @RequestParam MultipartFile file, @RequestParam String md5,@RequestParam Integer dpi) {
+        return adapteFileService
+                .save(RequestUtil.getIpAddress(request), getAccessUser().getRole(), examId, cardNumber, md5,dpi, file);
+    }
+
+    @ApiOperation(value = "答题卡扫描批次创建")
+    @RequestMapping(value = "/batch/create", method = RequestMethod.POST)
+    public BatchCreateVo batchCreate(@Validated @RequestBody BatchCreateDomain domain) {
+        return batchService.batchCreate(domain, getAccessUser());
+    }
+
+    @ApiOperation(value = "答题卡扫描结果保存")
+    @RequestMapping(value = "/batch/save", method = RequestMethod.POST)
+    public AnswerSaveVo batchSave(@Validated @RequestBody AnswerDomain domain) {
+        User user = getAccessUser();
+        return batchService.batchSave(domain, user);
+    }
+
+    @ApiOperation(value = "答题卡扫描历史标记/取消异常")
+    @RequestMapping(value = "/mismatch/toggle", method = RequestMethod.POST)
+    public MismatchToggleVo mismatchToggle(@Validated MismatchToggleDomain domain) {
+        return paperService.mismatchToggle(domain);
+    }
+
+    @ApiOperation(value = "答题卡扫描原图上传")
+    @RequestMapping(value = "/sheet/upload", method = RequestMethod.POST)
+    public SheetUploadVo sheetUpload(@RequestParam Long batchId, @RequestParam String examNumber,
+            @RequestParam Integer paperNumber, @RequestParam Integer pageIndex, @RequestParam MultipartFile file,
+            @RequestParam String md5) {
+        return batchService.sheetUpload(batchId, examNumber, paperNumber, pageIndex, file, md5);
+    }
+
+    @ApiOperation(value = "答题卡扫描裁切图上传")
+    @RequestMapping(value = "/slice/upload", method = RequestMethod.POST)
+    public SliceUploadVo sliceUpload(@RequestParam Long batchId, @RequestParam String examNumber,
+            @RequestParam Integer paperNumber, @RequestParam Integer pageIndex, @RequestParam Integer index,
+            @RequestParam MultipartFile file, @RequestParam String md5) {
+        return batchService.sliceUpload(batchId, examNumber, paperNumber, pageIndex, index, file, md5);
+    }
+
+    @ApiOperation(value = "答题卡扫描批次提交审核/查询审核结果")
+    @RequestMapping(value = "/batch/verify", method = RequestMethod.POST)
+    public BatchVerifyVo batchVerify(@RequestParam Long id) {
+        return batchService.batchVerify(id);
+    }
+
+    @ApiOperation(value = "答题卡扫描批次完成")
+    @RequestMapping(value = "/batch/finish", method = RequestMethod.POST)
+    public BatchFinishVo batchFinish(@RequestParam Long id) {
+        return batchService.batchFinish(id);
+    }
+}

+ 72 - 0
src/main/java/cn/com/qmth/scancentral/controller/scan/OmrTaskController.java

@@ -0,0 +1,72 @@
+package cn.com.qmth.scancentral.controller.scan;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.OmrTaskService;
+import cn.com.qmth.scancentral.vo.task.TaskResultVo;
+import cn.com.qmth.scancentral.vo.task.TaskSaveVo;
+import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
+import cn.com.qmth.scancentral.vo.task.TaskVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "识别对照任务接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/scan/task/omr")
+@Aac(strict = false, auth = true)
+public class OmrTaskController extends BaseController {
+
+    protected static final Logger log = LoggerFactory.getLogger(OmrTaskController.class);
+
+    @Autowired
+    private OmrTaskService omrTaskService;
+
+    @ApiOperation(value = "识别对照任务获取")
+    @RequestMapping(value = "/get", method = RequestMethod.POST)
+    public TaskVo get(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return omrTaskService.getTask(examId, user.getAccount());
+    }
+
+    @ApiOperation(value = "识别对照任务提交")
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    public TaskSaveVo save(@Validated @RequestBody TaskResultVo result) {
+        User user = getAccessUser();
+        return omrTaskService.submitTask(result, user);
+    }
+
+    @ApiOperation(value = "识别对照任务状态")
+    @RequestMapping(value = "/status", method = RequestMethod.POST)
+    public TaskStatusVo status(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return omrTaskService.getStatus(examId, false, user.getAccount());
+    }
+
+    @ApiOperation(value = "识别对照任务状态")
+    @RequestMapping(value = "/release", method = RequestMethod.POST)
+    public Object release(@RequestParam Long examId) {
+        User user = getAccessUser();
+        omrTaskService.releaseByUser(examId, user.getAccount());
+        return success(true);
+    }
+    
+    @ApiOperation(value = "识别对照任务历史")
+    @RequestMapping(value = "/history", method = RequestMethod.POST)
+    public TaskVo history(@RequestParam Long examId,@RequestParam(required = false) Long id,@RequestParam(required = false) Boolean next) {
+        User user = getAccessUser();
+        return omrTaskService.history(examId, id, user.getAccount(),next);
+    }
+}

+ 42 - 0
src/main/java/cn/com/qmth/scancentral/controller/scan/ScanExamController.java

@@ -0,0 +1,42 @@
+package cn.com.qmth.scancentral.controller.scan;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.vo.scanexaminfo.ScanExamInfoVo;
+import cn.com.qmth.scancentral.vo.scanexamlist.ScanExamListVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描功能-考试接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/scan/exam")
+@Aac(strict = false, auth = true)
+public class ScanExamController extends BaseController {
+
+    @Autowired
+    private ExamService examService;
+
+    @ApiOperation(value = "考试详情")
+    @RequestMapping(value = "/info", method = RequestMethod.POST)
+    public ScanExamInfoVo info(@RequestParam Long id) {
+        return examService.getScanExamInfo(id,getAccessUser());
+    }
+    
+    @ApiOperation(value = "考试列表")
+    @RequestMapping(value = "/list", method = RequestMethod.POST)
+    public List<ScanExamListVo> list() {
+        return examService.getScanExamList();
+    }
+
+}

+ 35 - 0
src/main/java/cn/com/qmth/scancentral/controller/scan/ScannerAuthController.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.scancentral.controller.scan;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.api.utils.RequestUtil;
+
+import cn.com.qmth.scancentral.bean.ScannerLoginInfo;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.service.AuthService;
+import cn.com.qmth.scancentral.vo.ScannerLoginVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描员登录接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/scan")
+public class ScannerAuthController extends BaseController {
+
+    @Autowired
+    private AuthService authService;
+
+    @ApiOperation(value = "扫描员登录")
+    @PostMapping("login")
+    public ScannerLoginVo login(ScannerLoginInfo loginInfo, HttpServletRequest request) {
+        String ip = RequestUtil.getIpAddress(request);
+        return ScannerLoginVo.of(authService.login(ip, loginInfo));
+    }
+
+}

+ 104 - 0
src/main/java/cn/com/qmth/scancentral/controller/scan/StudentAnswerTaskController.java

@@ -0,0 +1,104 @@
+package cn.com.qmth.scancentral.controller.scan;
+
+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.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+
+import cn.com.qmth.scancentral.bean.User;
+import cn.com.qmth.scancentral.bean.answertask.ReleaseVo;
+import cn.com.qmth.scancentral.bean.answertask.StudentAnswerTaskSaveDomain;
+import cn.com.qmth.scancentral.bean.answertask.StudentAnswerTaskSaveVo;
+import cn.com.qmth.scancentral.bean.answertask.StudentAnswerTaskStatusVo;
+import cn.com.qmth.scancentral.bean.answertask.StudentAnswerTaskVo;
+import cn.com.qmth.scancentral.controller.BaseController;
+import cn.com.qmth.scancentral.entity.StudentAnswerTaskEntity;
+import cn.com.qmth.scancentral.enums.LockType;
+import cn.com.qmth.scancentral.enums.StudentAnswerTaskStatus;
+import cn.com.qmth.scancentral.service.StudentAnswerTaskService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "扫描功能-数据抽查接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/scan/task/student/answer")
+@Aac(strict = false, auth = true)
+public class StudentAnswerTaskController extends BaseController {
+
+    @Autowired
+    private StudentAnswerTaskService studentAnswerTaskService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @ApiOperation(value = "任务获取")
+    @PostMapping("get")
+    public StudentAnswerTaskVo get(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return studentAnswerTaskService.getTask(examId, user.getAccount());
+    }
+
+    @ApiOperation(value = "抽查任务提交")
+    @PostMapping("save")
+    public StudentAnswerTaskSaveVo save(@RequestBody StudentAnswerTaskSaveDomain domain) {
+        User user = getAccessUser();
+        if (domain.getId() == null) {
+            throw new ParameterException("id不能为空");
+        }
+        if (domain.getTag() == null) {
+            throw new ParameterException("tag不能为空");
+        }
+        StudentAnswerTaskEntity task = studentAnswerTaskService.getById(domain.getId());
+        if (task == null) {
+            throw new ParameterException("任务不存在");
+        }
+        // if (StudentAnswerTaskStatus.PROCESSED.equals(task.getStatus())) {
+        // throw new ParameterException("任务已处理");
+        // }
+        if (StudentAnswerTaskStatus.WAITING.equals(task.getStatus())
+                && !studentAnswerTaskService.hasApplied(task, user.getAccount())) {
+            throw new ParameterException("任务非本人领取");
+        }
+        concurrentService.getReadWriteLock(LockType.STUDENT_ANSWER_GROUP + "-" + task.getGroupId()).readLock().lock();
+        concurrentService.getReadWriteLock(LockType.STUDENT + "-" + task.getStudentId()).writeLock().lock();
+        try {
+            return studentAnswerTaskService.submitTask(domain, user);
+        } finally {
+            concurrentService.getReadWriteLock(LockType.STUDENT + "-" + task.getStudentId()).writeLock().unlock();
+            concurrentService.getReadWriteLock(LockType.STUDENT_ANSWER_GROUP + "-" + task.getGroupId()).readLock()
+                    .unlock();
+        }
+    }
+
+    @ApiOperation(value = "抽查任务状态")
+    @PostMapping("status")
+    public StudentAnswerTaskStatusVo getStatus(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return studentAnswerTaskService.getStatus(examId, user);
+    }
+
+    @ApiOperation(value = "抽查任务释放")
+    @PostMapping("release")
+    public ReleaseVo release(@RequestParam Long examId) {
+        User user = getAccessUser();
+        ReleaseVo vo = new ReleaseVo();
+        studentAnswerTaskService.releaseByUser(examId, user.getAccount());
+        vo.setSuccess(true);
+        return vo;
+    }
+
+    @ApiOperation(value = "抽查任务历史")
+    @RequestMapping(value = "history", method = RequestMethod.POST)
+    public StudentAnswerTaskVo history(@RequestParam Long examId, @RequestParam(required = false) Long id,@RequestParam(required = false) Boolean next) {
+        User user = getAccessUser();
+        return studentAnswerTaskService.history(examId, id, user.getAccount(),next);
+    }
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini