xiatian 1 rok temu
commit
aa29c8f7cb
39 zmienionych plików z 2325 dodań i 0 usunięć
  1. 51 0
      .gitignore
  2. 47 0
      db/am_db.sql
  3. 139 0
      pom.xml
  4. 21 0
      src/main/java/cn/com/qmth/am/AmApplication.java
  5. 51 0
      src/main/java/cn/com/qmth/am/bean/ImageSlice.java
  6. 39 0
      src/main/java/cn/com/qmth/am/bean/ImportResult.java
  7. 22 0
      src/main/java/cn/com/qmth/am/config/FillMetaObjectHandler.java
  8. 36 0
      src/main/java/cn/com/qmth/am/config/InitData.java
  9. 15 0
      src/main/java/cn/com/qmth/am/config/MyBatisPlusConfig.java
  10. 65 0
      src/main/java/cn/com/qmth/am/config/SysProperty.java
  11. 9 0
      src/main/java/cn/com/qmth/am/dao/QuestionDao.java
  12. 9 0
      src/main/java/cn/com/qmth/am/dao/StudentDao.java
  13. 9 0
      src/main/java/cn/com/qmth/am/dao/StudentScoreDao.java
  14. 87 0
      src/main/java/cn/com/qmth/am/entity/QuestionEntity.java
  15. 74 0
      src/main/java/cn/com/qmth/am/entity/StudentEntity.java
  16. 97 0
      src/main/java/cn/com/qmth/am/entity/StudentScoreEntity.java
  17. 44 0
      src/main/java/cn/com/qmth/am/entity/base/BaseEntity.java
  18. 27 0
      src/main/java/cn/com/qmth/am/entity/base/IdEntity.java
  19. 34 0
      src/main/java/cn/com/qmth/am/enums/DataStatus.java
  20. 32 0
      src/main/java/cn/com/qmth/am/enums/DataType.java
  21. 20 0
      src/main/java/cn/com/qmth/am/enums/LockType.java
  22. 18 0
      src/main/java/cn/com/qmth/am/service/QuestionService.java
  23. 13 0
      src/main/java/cn/com/qmth/am/service/StudentScoreService.java
  24. 20 0
      src/main/java/cn/com/qmth/am/service/StudentService.java
  25. 344 0
      src/main/java/cn/com/qmth/am/service/impl/QuestionServiceImpl.java
  26. 14 0
      src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java
  27. 247 0
      src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java
  28. 36 0
      src/main/java/cn/com/qmth/am/task/AiMarkingJob.java
  29. 36 0
      src/main/java/cn/com/qmth/am/task/QuestionImportJob.java
  30. 36 0
      src/main/java/cn/com/qmth/am/task/StudentImportJob.java
  31. 170 0
      src/main/java/cn/com/qmth/am/utils/Calculator.java
  32. 91 0
      src/main/java/cn/com/qmth/am/utils/HttpClientBuilder.java
  33. 96 0
      src/main/java/cn/com/qmth/am/utils/JsonHelper.java
  34. 88 0
      src/main/java/cn/com/qmth/am/utils/MD5Util.java
  35. 64 0
      src/main/java/cn/com/qmth/am/utils/ResouceUtil.java
  36. 74 0
      src/main/java/cn/com/qmth/am/utils/SpringContextHolder.java
  37. 45 0
      src/main/resources/application.properties
  38. 5 0
      src/main/resources/mapper/QuestionMapper.xml
  39. BIN
      template/struct-import.xlsx

+ 51 - 0
.gitignore

@@ -0,0 +1,51 @@
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+
+
+### Eclipse & STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+/logs/
+
+
+### 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 ###
+*.zip
+*.war
+*.ear
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+target/
+
+.flattened-pom.xml
+.DS_Store
+

+ 47 - 0
db/am_db.sql

@@ -0,0 +1,47 @@
+DROP TABLE IF EXISTS `am_question`;
+CREATE TABLE `am_question` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `create_time` datetime(6) DEFAULT NULL,
+  `update_time` datetime(6) DEFAULT NULL,
+  `answer` longtext COLLATE utf8mb4_bin NOT NULL,
+  `content` longtext COLLATE utf8mb4_bin NOT NULL,
+  `exam_id` bigint NOT NULL,
+  `full_score` double NOT NULL,
+  `image_slice` varchar(1000) COLLATE utf8mb4_bin NOT NULL,
+  `main_number` int NOT NULL,
+  `page_index` int NOT NULL,
+  `sub_number` int NOT NULL,
+  `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_QUESTION_01` (`exam_id`, `subject_code`, `main_number`, `sub_number`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+DROP TABLE IF EXISTS `am_student`;
+CREATE TABLE `am_student` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `create_time` datetime(6) DEFAULT NULL,
+  `update_time` datetime(6) DEFAULT NULL,
+  `data_status` varchar(255) NOT NULL,
+  `exam_id` bigint NOT NULL,
+  `student_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+  `subject_code` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+  `subject_name` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_STUDENT_01` (`exam_id`, `subject_code`, `student_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+DROP TABLE IF EXISTS `am_student_score`;
+CREATE TABLE `am_student_score` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `create_time` datetime(6) DEFAULT NULL,
+  `update_time` datetime(6) DEFAULT NULL,
+  `ai_score` double DEFAULT NULL,
+  `data_status` varchar(255) int NOT NULL,
+  `err_msg` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
+  `main_number` int NOT NULL,
+  `marking_score` double DEFAULT NULL,
+  `score_ratio` double DEFAULT NULL,
+  `student_id` bigint NOT NULL,
+  `sub_number` int NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

+ 139 - 0
pom.xml

@@ -0,0 +1,139 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>cn.com.qmth.scancloud</groupId>
+	<artifactId>ai-marking</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>
+
+	<dependencies>
+		<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-solar</artifactId>
+            <version>${qmth-boot-version}</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-cache</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.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.4</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.10.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>3.6.1</version>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<finalName>${project.artifactId}</finalName>
+
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<includeSystemScope>true</includeSystemScope>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>

+ 21 - 0
src/main/java/cn/com/qmth/am/AmApplication.java

@@ -0,0 +1,21 @@
+package cn.com.qmth.am;
+
+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.EnableScheduling;
+
+import com.github.jeffreyning.mybatisplus.conf.EnableMPP;
+
+@EnableMPP
+@SpringBootApplication
+@EnableScheduling
+@Configuration
+@MapperScan("cn.com.qmth.am.dao")
+public class AmApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(AmApplication.class, args);
+    }
+
+}

+ 51 - 0
src/main/java/cn/com/qmth/am/bean/ImageSlice.java

@@ -0,0 +1,51 @@
+package cn.com.qmth.am.bean;
+
+public class ImageSlice {
+	private Double x;
+	private Double y;
+	private Double w;
+	private Double h;
+	private Double i;
+	public Double getX() {
+		return x;
+	}
+	public void setX(Double x) {
+		this.x = x;
+	}
+	public Double getY() {
+		return y;
+	}
+	public void setY(Double y) {
+		this.y = y;
+	}
+	public Double getW() {
+		return w;
+	}
+	public void setW(Double w) {
+		this.w = w;
+	}
+	public Double getH() {
+		return h;
+	}
+	public void setH(Double h) {
+		this.h = h;
+	}
+	public Double getI() {
+		return i;
+	}
+	public void setI(Double i) {
+		this.i = i;
+	}
+	public ImageSlice() {
+		super();
+	}
+	public ImageSlice(Double x, Double y, Double w, Double h, Double i) {
+		super();
+		this.x = x;
+		this.y = y;
+		this.w = w;
+		this.h = h;
+		this.i = i;
+	}
+	
+}

+ 39 - 0
src/main/java/cn/com/qmth/am/bean/ImportResult.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.am.bean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImportResult {
+
+    private String countInfo;
+
+    private List<String> errMsg;
+
+    public String getCountInfo() {
+        return countInfo;
+    }
+
+    public void setCountInfo(String countInfo) {
+        this.countInfo = countInfo;
+    }
+
+    public List<String> getErrMsg() {
+        return errMsg;
+    }
+
+    public void setErrMsg(List<String> errMsg) {
+        this.errMsg = errMsg;
+    }
+
+	public ImportResult() {
+		super();
+	}
+
+	public ImportResult(String err) {
+		super();
+		errMsg=new ArrayList<>();
+		errMsg.add(err);
+	}
+
+    
+}

+ 22 - 0
src/main/java/cn/com/qmth/am/config/FillMetaObjectHandler.java

@@ -0,0 +1,22 @@
+package cn.com.qmth.am.config;
+
+import java.util.Date;
+
+import org.apache.ibatis.reflection.MetaObject;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+
+public class FillMetaObjectHandler implements MetaObjectHandler {
+
+	@Override
+	public void insertFill(MetaObject metaObject) {
+		this.setFieldValByName("creationTime", new Date(), metaObject);
+		this.setFieldValByName("updateTime", new Date(), metaObject);
+	}
+
+	@Override
+	public void updateFill(MetaObject metaObject) {
+		this.setFieldValByName("updateTime", new Date(), metaObject);
+	}
+
+}

+ 36 - 0
src/main/java/cn/com/qmth/am/config/InitData.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.am.config;
+
+import java.io.File;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+@Component
+public class InitData implements CommandLineRunner {
+
+	@Autowired
+	private SysProperty sysProperty;
+
+	@Override
+	public void run(String... args) throws Exception {
+		File dataDir=new File(sysProperty.getDataDir());
+		if(!dataDir.exists()) {
+			dataDir.mkdir();
+		}
+		File stuImpDir=new File(sysProperty.getDataDir()+"/student-import");
+		if(!stuImpDir.exists()) {
+			stuImpDir.mkdir();
+		}
+		File quesImpDir=new File(sysProperty.getDataDir()+"/question-import");
+		if(!quesImpDir.exists()) {
+			quesImpDir.mkdir();
+		}
+		File mscoreImpDir=new File(sysProperty.getDataDir()+"/marking-score-import");
+		if(!mscoreImpDir.exists()) {
+			mscoreImpDir.mkdir();
+		}
+	}
+	
+
+}

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

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

+ 65 - 0
src/main/java/cn/com/qmth/am/config/SysProperty.java

@@ -0,0 +1,65 @@
+package cn.com.qmth.am.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.am.enums.DataType;
+
+@Component
+public class SysProperty {
+	
+    @Value("${com.qmth.solar.app-version}")
+    private String version;
+    @Value("${com.qmth.fss.config}")
+    private String fssConfig;
+
+    @Value("${com.qmth.fss.server}")
+    private String fssServer;
+
+    @Value("${am.data-dir}")
+    private String dataDir;
+
+    @Value("${am.data-type}")
+    private DataType dataType;
+
+	public String getVersion() {
+		return version;
+	}
+
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	public String getFssConfig() {
+		return fssConfig;
+	}
+
+	public void setFssConfig(String fssConfig) {
+		this.fssConfig = fssConfig;
+	}
+
+	public String getFssServer() {
+		return fssServer;
+	}
+
+	public void setFssServer(String fssServer) {
+		this.fssServer = fssServer;
+	}
+
+	public String getDataDir() {
+		return dataDir;
+	}
+
+	public void setDataDir(String dataDir) {
+		this.dataDir = dataDir;
+	}
+
+	public DataType getDataType() {
+		return dataType;
+	}
+
+	public void setDataType(DataType dataType) {
+		this.dataType = dataType;
+	}
+
+}

+ 9 - 0
src/main/java/cn/com/qmth/am/dao/QuestionDao.java

@@ -0,0 +1,9 @@
+package cn.com.qmth.am.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import cn.com.qmth.am.entity.QuestionEntity;
+
+public interface QuestionDao extends BaseMapper<QuestionEntity> {
+
+}

+ 9 - 0
src/main/java/cn/com/qmth/am/dao/StudentDao.java

@@ -0,0 +1,9 @@
+package cn.com.qmth.am.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import cn.com.qmth.am.entity.StudentEntity;
+
+public interface StudentDao extends BaseMapper<StudentEntity> {
+
+}

+ 9 - 0
src/main/java/cn/com/qmth/am/dao/StudentScoreDao.java

@@ -0,0 +1,9 @@
+package cn.com.qmth.am.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import cn.com.qmth.am.entity.StudentScoreEntity;
+
+public interface StudentScoreDao extends BaseMapper<StudentScoreEntity> {
+
+}

+ 87 - 0
src/main/java/cn/com/qmth/am/entity/QuestionEntity.java

@@ -0,0 +1,87 @@
+package cn.com.qmth.am.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+
+import cn.com.qmth.am.bean.ImageSlice;
+import cn.com.qmth.am.entity.base.IdEntity;
+
+@TableName("am_question")
+public class QuestionEntity extends IdEntity {
+
+	private static final long serialVersionUID = -6261302618070108336L;
+
+	private Long examId;
+
+	private String subjectCode;
+	private Integer mainNumber;
+	private Integer subNumber;
+
+	// 满分
+	private Double fullScore;
+	// 试题内容
+	private String content;
+	// 试题答案
+	private String answer;
+
+	private Integer pageIndex;
+	@TableField(value = "image_slice", typeHandler = JacksonTypeHandler.class)
+	private ImageSlice imageSlice;
+	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 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 getFullScore() {
+		return fullScore;
+	}
+	public void setFullScore(Double fullScore) {
+		this.fullScore = fullScore;
+	}
+	public String getContent() {
+		return content;
+	}
+	public void setContent(String content) {
+		this.content = content;
+	}
+	public String getAnswer() {
+		return answer;
+	}
+	public void setAnswer(String answer) {
+		this.answer = answer;
+	}
+	public Integer getPageIndex() {
+		return pageIndex;
+	}
+	public void setPageIndex(Integer pageIndex) {
+		this.pageIndex = pageIndex;
+	}
+	public ImageSlice getImageSlice() {
+		return imageSlice;
+	}
+	public void setImageSlice(ImageSlice imageSlice) {
+		this.imageSlice = imageSlice;
+	}
+
+
+}

+ 74 - 0
src/main/java/cn/com/qmth/am/entity/StudentEntity.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.am.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import cn.com.qmth.am.entity.base.IdEntity;
+import cn.com.qmth.am.enums.DataStatus;
+
+@TableName("am_student")
+public class StudentEntity extends IdEntity {
+
+	private static final long serialVersionUID = -6261302618070108336L;
+
+	private Long examId;
+
+	private String subjectCode;
+	private String subjectName;
+	private String studentCode;
+
+
+	private DataStatus dataStatus;
+
+
+	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 getStudentCode() {
+		return studentCode;
+	}
+
+
+	public void setStudentCode(String studentCode) {
+		this.studentCode = studentCode;
+	}
+
+
+	public DataStatus getDataStatus() {
+		return dataStatus;
+	}
+
+
+	public void setDataStatus(DataStatus dataStatus) {
+		this.dataStatus = dataStatus;
+	}
+
+
+	public String getSubjectName() {
+		return subjectName;
+	}
+
+
+	public void setSubjectName(String subjectName) {
+		this.subjectName = subjectName;
+	}
+
+
+
+}

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

@@ -0,0 +1,97 @@
+package cn.com.qmth.am.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import cn.com.qmth.am.entity.base.IdEntity;
+import cn.com.qmth.am.enums.DataStatus;
+
+@TableName("am_student_score")
+public class StudentScoreEntity extends IdEntity {
+
+	private static final long serialVersionUID = -6261302618070108336L;
+
+	private Long studentId;
+
+	private Integer mainNumber;
+	private Integer subNumber;
+	
+	//机评分
+	private Double aiScore;
+	//人评分
+	private Double markingScore;
+	//机评得分率
+	private Double scoreRatio;
+	
+	private DataStatus dataStatus;
+	
+	//错误信息
+	private String errMsg;
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	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 getAiScore() {
+		return aiScore;
+	}
+
+	public void setAiScore(Double aiScore) {
+		this.aiScore = aiScore;
+	}
+
+	public Double getMarkingScore() {
+		return markingScore;
+	}
+
+	public void setMarkingScore(Double markingScore) {
+		this.markingScore = markingScore;
+	}
+
+
+	public Double getScoreRatio() {
+		return scoreRatio;
+	}
+
+	public void setScoreRatio(Double scoreRatio) {
+		this.scoreRatio = scoreRatio;
+	}
+
+	public DataStatus getDataStatus() {
+		return dataStatus;
+	}
+
+	public void setDataStatus(DataStatus dataStatus) {
+		this.dataStatus = dataStatus;
+	}
+
+	public String getErrMsg() {
+		return errMsg;
+	}
+
+	public void setErrMsg(String errMsg) {
+		this.errMsg = errMsg;
+	}
+
+
+
+}

+ 44 - 0
src/main/java/cn/com/qmth/am/entity/base/BaseEntity.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.am.entity.base;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+/**
+ * 实体类基类
+ */
+public abstract class BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+	 * 更新时间
+	 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+	private Date updateTime;
+
+	/**
+	 * 创建时间
+	 */
+    @TableField(fill = FieldFill.INSERT)
+	private Date creationTime;
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public Date getCreationTime() {
+		return creationTime;
+	}
+
+	public void setCreationTime(Date creationTime) {
+		this.creationTime = creationTime;
+	}
+
+}

+ 27 - 0
src/main/java/cn/com/qmth/am/entity/base/IdEntity.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.am.entity.base;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+/**
+ * 实体类基类
+ */
+public abstract class IdEntity extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+
+    @TableId(type = IdType.AUTO)
+	private Long id;
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+
+}

+ 34 - 0
src/main/java/cn/com/qmth/am/enums/DataStatus.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.am.enums;
+
+/**数据处理状态
+ *
+ */
+public enum DataStatus {
+
+	WAITING("待处理"),
+	PROCESSING("处理中"),
+	SUCCESS("成功"),
+	FAILED("失败"),
+	;
+
+	private DataStatus(String name){
+		this.name = name;
+	}
+
+	private String name;
+
+	public String getName() {
+		return name;
+	}
+
+	
+	public static DataStatus getByName(String name) {
+    	for(DataStatus r:DataStatus.values()) {
+    		if(r.getName().equals(name)) {
+    			return r;
+    		}
+    	}
+    	return null;
+    }
+
+}

+ 32 - 0
src/main/java/cn/com/qmth/am/enums/DataType.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.am.enums;
+
+/**数据源类型
+ *
+ */
+public enum DataType {
+
+	MSRKING_CLOUD("云阅卷"),
+	TEACH_CLOUD("知学知考"),
+	;
+
+	private DataType(String name){
+		this.name = name;
+	}
+
+	private String name;
+
+	public String getName() {
+		return name;
+	}
+
+	
+	public static DataType getByName(String name) {
+    	for(DataType r:DataType.values()) {
+    		if(r.getName().equals(name)) {
+    			return r;
+    		}
+    	}
+    	return null;
+    }
+
+}

+ 20 - 0
src/main/java/cn/com/qmth/am/enums/LockType.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.am.enums;
+
+public enum LockType {
+	STUDENT_IMPORT("student_import"),
+	QUESTION_IMPORT("question_import"),
+	MARKING_SCORE_IMPORT("marking_score_import"),
+    AI_MARKING("ai_marking"),
+    ;
+
+    private String name;
+
+    private LockType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+}

+ 18 - 0
src/main/java/cn/com/qmth/am/service/QuestionService.java

@@ -0,0 +1,18 @@
+package cn.com.qmth.am.service;
+
+import java.io.InputStream;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.entity.QuestionEntity;
+
+/**
+ * 类注释
+ */
+public interface QuestionService  extends IService<QuestionEntity> {
+
+	void importQuestion();
+
+	public ImportResult disposeFile(InputStream inputStream);
+}

+ 13 - 0
src/main/java/cn/com/qmth/am/service/StudentScoreService.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.am.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import cn.com.qmth.am.entity.StudentScoreEntity;
+
+/**
+ * 类注释
+ */
+public interface StudentScoreService  extends IService<StudentScoreEntity> {
+
+
+}

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

@@ -0,0 +1,20 @@
+package cn.com.qmth.am.service;
+
+import java.io.InputStream;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.entity.StudentEntity;
+
+/**
+ * 类注释
+ */
+public interface StudentService  extends IService<StudentEntity> {
+
+	void importStudent();
+
+	ImportResult disposeFile(InputStream inputStream);
+
+
+}

+ 344 - 0
src/main/java/cn/com/qmth/am/service/impl/QuestionServiceImpl.java

@@ -0,0 +1,344 @@
+package cn.com.qmth.am.service.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.tools.excel.ExcelReader;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.excel.model.DataMap;
+
+import cn.com.qmth.am.bean.ImageSlice;
+import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.config.SysProperty;
+import cn.com.qmth.am.dao.QuestionDao;
+import cn.com.qmth.am.entity.QuestionEntity;
+import cn.com.qmth.am.service.QuestionService;
+
+@Service
+public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity> implements QuestionService {
+
+	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "大题号", "小题号", "满分", "试题内容", "试题答案",
+			"作答页码", "作答坐标" };
+	@Autowired
+	private SysProperty sysProperty;
+	@Autowired
+	private QuestionService questionService;
+
+	@Override
+	public void importQuestion() {
+		File dir = new File(sysProperty.getDataDir() + "/question-import");
+		File[] fs = dir.listFiles();
+		if (fs == null || fs.length == 0) {
+			return;
+		}
+		for (File file : fs) {
+			if (!file.isFile() || !file.getName().endsWith(".xlsx")) {
+				continue;
+			}
+			InputStream inputStream = null;
+			ImportResult ret = null;
+			try {
+				inputStream = new FileInputStream(file);
+				ret = questionService.disposeFile(inputStream);
+			} catch (Exception e) {
+				String errMsg;
+				if (e instanceof FileNotFoundException) {
+					errMsg = "未找到文件:" + file.getAbsolutePath();
+				} else {
+					errMsg = "系统错误:" + e.getMessage();
+				}
+				ret = new ImportResult(errMsg);
+			} finally {
+				if (inputStream != null) {
+					try {
+						inputStream.close();
+					} catch (IOException e) {
+					}
+				}
+			}
+			moveFile(dir, file, ret);
+		}
+	}
+
+	private void moveFile(File dir, File file, ImportResult ret) {
+		try {
+			boolean succss = CollectionUtils.isEmpty(ret.getErrMsg());
+			if (succss) {
+				File sucDir = new File(dir.getAbsoluteFile() + "/success/");
+				if (!sucDir.exists()) {
+					sucDir.mkdir();
+				}
+				File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
+				if (targetFile.exists()) {
+					targetFile.delete();
+				}
+				FileUtils.copyFile(file, targetFile);
+				file.delete();
+				String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
+				File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
+				if (msgFile.exists()) {
+					msgFile.delete();
+				}
+				FileUtils.write(msgFile, ret.getCountInfo(), "UFT-8");
+			} else {
+				File sucDir = new File(dir.getAbsoluteFile() + "/failed/");
+				if (!sucDir.exists()) {
+					sucDir.mkdir();
+				}
+				File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
+				if (targetFile.exists()) {
+					targetFile.delete();
+				}
+				FileUtils.copyFile(file, targetFile);
+				file.delete();
+				String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
+				File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
+				if (msgFile.exists()) {
+					msgFile.delete();
+				}
+				FileUtils.writeLines(msgFile, ret.getErrMsg(), "UFT-8");
+			}
+		} catch (IOException e) {
+			throw new StatusException("文件处理出错", e);
+		}
+
+	}
+
+	private String errorMsg(int lineNum, String msg) {
+		return "第" + lineNum + "行 " + msg;
+	}
+
+	private String trimAndNullIfBlank(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		return s.trim();
+	}
+
+	@Transactional
+	@Override
+	public ImportResult disposeFile(InputStream inputStream) {
+		List<DataMap> lineList = null;
+		ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
+		try {
+			lineList = reader.getDataMapList();
+		} catch (Exception e) {
+			throw new StatusException("Excel 解析失败");
+		}
+		if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
+			throw new StatusException("Excel表头错误");
+		}
+		if (CollectionUtils.isEmpty(lineList)) {
+			throw new StatusException("Excel无内容");
+		}
+		if (100001 < lineList.size()) {
+			throw new StatusException("数据行数不能超过100000");
+		}
+		List<QuestionEntity> ss = new ArrayList<>();
+		ImportResult ret = new ImportResult();
+		List<String> failRecords = new ArrayList<>();
+		ret.setErrMsg(failRecords);
+		for (int i = 0; i < lineList.size(); i++) {
+			DataMap line = lineList.get(i);
+
+			StringBuilder msg = new StringBuilder();
+
+			QuestionEntity imp = new QuestionEntity();
+			String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
+			if (StringUtils.isBlank(examId)) {
+				msg.append("  考试ID不能为空");
+			} else if (examId.length() > 20) {
+				msg.append("  考试ID不能超过20个字符");
+			} else {
+				try {
+					Long examIdVal = Long.parseLong(examId);
+					imp.setExamId(examIdVal);
+				} catch (NumberFormatException e) {
+					msg.append("  考试ID只能是数字");
+				}
+			}
+
+			String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
+			if (StringUtils.isBlank(subjectCode)) {
+				msg.append("  科目代码不能为空");
+			} else if (subjectCode.length() > 100) {
+				msg.append("  科目代码不能超过100个字符");
+			}
+			imp.setSubjectCode(subjectCode);
+
+			String mainNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
+			if (StringUtils.isBlank(mainNum)) {
+				msg.append("  大题号不能为空");
+			} else if (mainNum.length() > 10) {
+				msg.append("  大题号不能超过10个字符");
+			} else {
+				try {
+					Integer mainNumVal = Integer.parseInt(mainNum);
+					if (mainNumVal <= 0) {
+						msg.append("  大题号必须大于0");
+					}
+					imp.setMainNumber(mainNumVal);
+				} catch (NumberFormatException e) {
+					msg.append("  大题号格式错误");
+				}
+			}
+
+			String subNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
+			if (StringUtils.isBlank(subNum)) {
+				msg.append("  小题号不能为空");
+			} else if (subNum.length() > 10) {
+				msg.append("  小题号不能超过10个字符");
+			} else {
+				try {
+					Integer subNumVal = Integer.parseInt(subNum);
+					if (subNumVal <= 0) {
+						msg.append("  小题号必须大于0");
+					}
+					imp.setSubNumber(subNumVal);
+				} catch (NumberFormatException e) {
+					msg.append("  小题号格式错误");
+				}
+			}
+
+			String fullScore = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
+			if (StringUtils.isBlank(fullScore)) {
+				msg.append("  满分不能为空");
+			} else if (fullScore.length() > 10) {
+				msg.append("  满分不能超过10个字符");
+			} else {
+				try {
+					Double fullScoreVal = Double.parseDouble(fullScore);
+					if (fullScoreVal <= 0) {
+						msg.append("  满分必须大于0");
+					}
+					imp.setFullScore(fullScoreVal);
+				} catch (NumberFormatException e) {
+					msg.append("  满分格式错误");
+				}
+			}
+
+			String content = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
+			if (StringUtils.isBlank(content)) {
+				msg.append("  试题内容不能为空");
+			}
+			imp.setContent(content);
+
+			String answer = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
+			if (StringUtils.isBlank(answer)) {
+				msg.append("  试题答案不能为空");
+			}
+			imp.setAnswer(answer);
+
+			String pageIndex = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
+			if (StringUtils.isBlank(pageIndex)) {
+				msg.append("  作答页码不能为空");
+			} else if (pageIndex.length() > 10) {
+				msg.append("  作答页码不能超过10个字符");
+			} else {
+				try {
+					Integer pageIndexVal = Integer.parseInt(pageIndex);
+					if (pageIndexVal <= 0) {
+						msg.append("  作答页码必须大于0");
+					}
+					imp.setPageIndex(pageIndexVal);
+				} catch (NumberFormatException e) {
+					msg.append("  作答页码格式错误");
+				}
+			}
+
+			String imageSlice = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
+			if (StringUtils.isBlank(imageSlice)) {
+				msg.append("  作答坐标不能为空");
+			} else if (imageSlice.length() > 1000) {
+				msg.append("  作答坐标不能超过1000个字符");
+			} else {
+				ImageSlice val=getImageSlice(imageSlice);
+				if(val==null) {
+					msg.append("  作答坐标格式有误");
+				}else {
+					imp.setImageSlice(val);
+				}
+			}
+
+			if (msg.length() > 0) {
+				failRecords.add(errorMsg(i + 1, msg.toString()));
+			} else {
+				ss.add(imp);
+			}
+
+		}
+
+		if (CollectionUtils.isNotEmpty(failRecords)) {
+			return ret;
+		}
+		try {
+			int addCount = saveQuestionBatch(ss);
+			ret.setCountInfo("新增数量:" + addCount);
+		} catch (Exception e) {
+			failRecords.add("系统错误:" + e.getMessage());
+		}
+		return ret;
+	}
+
+	private ImageSlice getImageSlice(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		try {
+			ImageSlice ret = JSONObject.parseObject(s, ImageSlice.class);
+			if (ret.getH() == null || ret.getI() == null || ret.getW() == null || ret.getX() == null
+					|| ret.getY() == null) {
+				return null;
+			}
+			return ret;
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	private int saveQuestionBatch(List<QuestionEntity> ss) {
+		if (CollectionUtils.isEmpty(ss)) {
+			return 0;
+		}
+		List<QuestionEntity> all = this.list();
+		Set<String> set = new HashSet<>();
+		if (CollectionUtils.isNotEmpty(all)) {
+			for (QuestionEntity s : all) {
+				String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber()+"-"+s.getSubNumber();
+				set.add(key);
+			}
+		}
+		List<QuestionEntity> adds = new ArrayList<>();
+		for (QuestionEntity s : ss) {
+			String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber()+"-"+s.getSubNumber();
+			if (!set.contains(key)) {
+				adds.add(s);
+			} else {
+				set.add(key);
+			}
+		}
+		if (CollectionUtils.isNotEmpty(adds)) {
+			saveBatch(adds);
+		}
+		return adds.size();
+	}
+
+}

+ 14 - 0
src/main/java/cn/com/qmth/am/service/impl/StudentScoreServiceImpl.java

@@ -0,0 +1,14 @@
+package cn.com.qmth.am.service.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import cn.com.qmth.am.dao.StudentScoreDao;
+import cn.com.qmth.am.entity.StudentScoreEntity;
+import cn.com.qmth.am.service.StudentScoreService;
+
+@Service
+public class StudentScoreServiceImpl extends ServiceImpl<StudentScoreDao, StudentScoreEntity> implements StudentScoreService {
+
+}

+ 247 - 0
src/main/java/cn/com/qmth/am/service/impl/StudentServiceImpl.java

@@ -0,0 +1,247 @@
+package cn.com.qmth.am.service.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.tools.excel.ExcelReader;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.excel.model.DataMap;
+
+import cn.com.qmth.am.bean.ImportResult;
+import cn.com.qmth.am.config.SysProperty;
+import cn.com.qmth.am.dao.StudentDao;
+import cn.com.qmth.am.entity.StudentEntity;
+import cn.com.qmth.am.service.StudentService;
+
+@Service
+public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
+	private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "科目名称", "考生编号" };
+	@Autowired
+	private SysProperty sysProperty;
+	@Autowired
+	private StudentService studentService;
+
+	@Override
+	public void importStudent() {
+		File dir = new File(sysProperty.getDataDir() + "/student-import");
+		File[] fs = dir.listFiles();
+		if (fs == null || fs.length == 0) {
+			return;
+		}
+		for (File file : fs) {
+			if (!file.isFile() || !file.getName().endsWith(".xlsx")) {
+				continue;
+			}
+			InputStream inputStream = null;
+			ImportResult ret=null;
+			try {
+				inputStream = new FileInputStream(file);
+				ret = studentService.disposeFile(inputStream);
+			} catch (Exception e) {
+				String errMsg;
+				if(e instanceof FileNotFoundException) {
+					errMsg="未找到文件:" + file.getAbsolutePath();
+				}else {
+					errMsg="系统错误:" + e.getMessage();
+				}
+				ret=new ImportResult(errMsg);
+			} finally {
+				if (inputStream != null) {
+					try {
+						inputStream.close();
+					} catch (IOException e) {
+					}
+				}
+			}
+			moveFile(dir,file,ret);
+		}
+	}
+
+	private void moveFile(File dir,File file, ImportResult ret) {
+		try {
+			boolean succss=CollectionUtils.isEmpty(ret.getErrMsg());
+			if(succss) {
+				File sucDir=new File(dir.getAbsoluteFile()+"/success/");
+				if(!sucDir.exists()) {
+					sucDir.mkdir();
+				}
+				File targetFile=new File(sucDir.getAbsoluteFile()+"/"+file.getName());
+				if(targetFile.exists()) {
+					targetFile.delete();
+				}
+				FileUtils.copyFile(file, targetFile);
+				file.delete();
+				String fname=file.getName().substring(0, file.getName().lastIndexOf("."));
+				File msgFile=new File(sucDir.getAbsoluteFile()+"/"+fname+"_info.txt");
+				if(msgFile.exists()) {
+					msgFile.delete();
+				}
+				FileUtils.write(msgFile, ret.getCountInfo(), "UFT-8");
+			}else {
+				File sucDir=new File(dir.getAbsoluteFile()+"/failed/");
+				if(!sucDir.exists()) {
+					sucDir.mkdir();
+				}
+				File targetFile=new File(sucDir.getAbsoluteFile()+"/"+file.getName());
+				if(targetFile.exists()) {
+					targetFile.delete();
+				}
+				FileUtils.copyFile(file, targetFile);
+				file.delete();
+				String fname=file.getName().substring(0, file.getName().lastIndexOf("."));
+				File msgFile=new File(sucDir.getAbsoluteFile()+"/"+fname+"_info.txt");
+				if(msgFile.exists()) {
+					msgFile.delete();
+				}
+				FileUtils.writeLines(msgFile, ret.getErrMsg(), "UFT-8");
+			}
+		} catch (IOException e) {
+			throw new StatusException("文件处理出错", e);
+		}
+		
+	}
+	
+
+	private String errorMsg(int lineNum, String msg) {
+		return "第" + lineNum + "行 " + msg;
+	}
+
+	private String trimAndNullIfBlank(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		return s.trim();
+	}
+
+	@Transactional
+	@Override
+	public ImportResult disposeFile(InputStream inputStream) {
+		List<DataMap> lineList = null;
+		ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
+		try {
+			lineList = reader.getDataMapList();
+		} catch (Exception e) {
+			throw new StatusException("Excel 解析失败");
+		}
+		if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
+			throw new StatusException("Excel表头错误");
+		}
+		if (CollectionUtils.isEmpty(lineList)) {
+			throw new StatusException("Excel无内容");
+		}
+		if (100001 < lineList.size()) {
+			throw new StatusException("数据行数不能超过100000");
+		}
+		List<StudentEntity> ss = new ArrayList<>();
+		ImportResult ret = new ImportResult();
+		List<String> failRecords = new ArrayList<>();
+		ret.setErrMsg(failRecords);
+		for (int i = 0; i < lineList.size(); i++) {
+			DataMap line = lineList.get(i);
+
+			StringBuilder msg = new StringBuilder();
+
+			StudentEntity imp = new StudentEntity();
+			String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
+			if (StringUtils.isBlank(examId)) {
+				msg.append("  考试ID不能为空");
+			} else if (examId.length() > 20) {
+				msg.append("  考试ID不能超过20个字符");
+			} else {
+				try {
+					Long examIdVal = Long.parseLong(examId);
+					imp.setExamId(examIdVal);
+				} catch (NumberFormatException e) {
+					msg.append("  考试ID只能是数字");
+				}
+			}
+
+			String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
+			if (StringUtils.isBlank(subjectCode)) {
+				msg.append("  科目代码不能为空");
+			} else if (subjectCode.length() > 100) {
+				msg.append("  科目代码不能超过100个字符");
+			}
+			imp.setSubjectCode(subjectCode);
+
+			String subjectName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
+			if (StringUtils.isBlank(subjectName)) {
+				msg.append("  科目名称不能为空");
+			} else if (subjectName.length() > 100) {
+				msg.append("  科目名称不能超过100个字符");
+			}
+			imp.setSubjectName(subjectName);
+
+			String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
+			if (StringUtils.isBlank(studentCode)) {
+				msg.append("  考生编号不能为空");
+			} else if (studentCode.length() > 100) {
+				msg.append("  考生编号不能超过100个字符");
+			}
+			imp.setStudentCode(studentCode);
+
+			if (msg.length() > 0) {
+				failRecords.add(errorMsg(i + 1, msg.toString()));
+			} else {
+				ss.add(imp);
+			}
+
+		}
+
+		if (CollectionUtils.isNotEmpty(failRecords)) {
+			return ret;
+		}
+		try {
+			int addCount = saveStudentBatch(ss);
+			ret.setCountInfo("新增数量:" + addCount);
+		} catch (Exception e) {
+			failRecords.add("系统错误:" + e.getMessage());
+		}
+		return ret;
+	}
+
+	private int saveStudentBatch(List<StudentEntity> ss) {
+		if (CollectionUtils.isEmpty(ss)) {
+			return 0;
+		}
+		List<StudentEntity> all = this.list();
+		Set<String> set = new HashSet<>();
+		if (CollectionUtils.isNotEmpty(all)) {
+			for (StudentEntity s : all) {
+				String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getStudentCode();
+				set.add(key);
+			}
+		}
+		List<StudentEntity> adds = new ArrayList<>();
+		for (StudentEntity s : ss) {
+			String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getStudentCode();
+			if (!set.contains(key)) {
+				adds.add(s);
+			}else {
+				set.add(key);
+			}
+		}
+		if (CollectionUtils.isNotEmpty(adds)) {
+			saveBatch(adds);
+		}
+		return adds.size();
+	}
+
+}

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

@@ -0,0 +1,36 @@
+package cn.com.qmth.am.task;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+
+import cn.com.qmth.am.enums.LockType;
+
+@Service
+public class AiMarkingJob {
+
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    public void doJob() {
+        boolean lock = concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().tryLock();
+        try {
+            if (!lock) {
+                return;
+            }
+            this.dispose();
+        } finally {
+            if (lock) {
+                concurrentService.getReadWriteLock(LockType.AI_MARKING.name()).writeLock().unlock();
+            }
+        }
+    }
+
+    private void dispose() {
+    }
+
+}

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

@@ -0,0 +1,36 @@
+package cn.com.qmth.am.task;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+
+import cn.com.qmth.am.enums.LockType;
+import cn.com.qmth.am.service.QuestionService;
+
+@Service
+public class QuestionImportJob {
+
+    @Autowired
+    private ConcurrentService concurrentService;
+    
+    @Autowired
+    private QuestionService questionService;
+
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    public void doJob() {
+        boolean lock = concurrentService.getReadWriteLock(LockType.QUESTION_IMPORT.name()).writeLock().tryLock();
+        try {
+            if (!lock) {
+                return;
+            }
+            questionService.importQuestion();
+        } finally {
+            if (lock) {
+                concurrentService.getReadWriteLock(LockType.QUESTION_IMPORT.name()).writeLock().unlock();
+            }
+        }
+    }
+
+}

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

@@ -0,0 +1,36 @@
+package cn.com.qmth.am.task;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+
+import cn.com.qmth.am.enums.LockType;
+import cn.com.qmth.am.service.StudentService;
+
+@Service
+public class StudentImportJob {
+
+    @Autowired
+    private ConcurrentService concurrentService;
+    
+    @Autowired
+    private StudentService studentService;
+
+    @Scheduled(fixedDelay = 5 * 1000, initialDelay = 10 * 1000)
+    public void doJob() {
+        boolean lock = concurrentService.getReadWriteLock(LockType.STUDENT_IMPORT.name()).writeLock().tryLock();
+        try {
+            if (!lock) {
+                return;
+            }
+            studentService.importStudent();
+        } finally {
+            if (lock) {
+                concurrentService.getReadWriteLock(LockType.STUDENT_IMPORT.name()).writeLock().unlock();
+            }
+        }
+    }
+
+}

+ 170 - 0
src/main/java/cn/com/qmth/am/utils/Calculator.java

@@ -0,0 +1,170 @@
+package cn.com.qmth.am.utils;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import com.qmth.boot.core.exception.StatusException;
+
+/**
+ * 计算器
+ *
+ * @author
+ * @date 2019年7月30日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class Calculator {
+
+    /**
+     * 加法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double add(double v1, double v2) {
+        return add(v1, v2, 2);
+
+    }
+
+    /**
+     * 加法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double add(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.add(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    
+    /** 加法 保留指定位小数
+     * @param ds
+     * @param len
+     * @return
+     */
+    public static double add(List<Double> ds, int len) {
+        if(CollectionUtils.isEmpty(ds)) {
+            throw new StatusException("数组为空");
+        }
+        BigDecimal ret = new BigDecimal(0.0);
+        for(Double d:ds) {
+            if(d==null) {
+                throw new StatusException("数组元素为空");
+            }
+            ret=ret.add(new BigDecimal(d));
+        }
+        return ret.setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+
+    /**
+     * 减法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double subtract(double v1, double v2) {
+        return subtract(v1, v2, 2);
+
+    }
+
+    /**
+     * 减法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double subtract(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.subtract(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    /**差值绝对值
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double absoluteDiff(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        if(v1>v2) {
+            return b1.subtract(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }else {
+            return b2.subtract(b1).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }
+
+    }
+    /**
+     * 乘法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @return
+     */
+    public static double multiply(double v1, double v2) {
+        return multiply(v1, v2, 2);
+
+    }
+
+    /**
+     * 乘法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double multiply(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.multiply(b2).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();
+
+    }
+    
+    
+    /**
+     * 除法 保留两位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double divide(double v1, double v2) {
+        return divide(v1, v2, 2);
+    }
+
+    /**
+     * 除法 保留指定位小数
+     * 
+     * @param v1
+     * @param v2
+     * @param len
+     * @return
+     */
+    public static double divide(double v1, double v2, int len) {
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return b1.divide(b2, len, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    public static String divide2String(Double v1, Double v2, int len) {
+        if(v1==null||v2==null||v2==0) {
+            return "-";
+        }
+        BigDecimal b1 = new BigDecimal(v1);
+        BigDecimal b2 = new BigDecimal(v2);
+        return String.valueOf(b1.divide(b2, len, BigDecimal.ROUND_HALF_UP).doubleValue());
+    }
+}

+ 91 - 0
src/main/java/cn/com/qmth/am/utils/HttpClientBuilder.java

@@ -0,0 +1,91 @@
+package cn.com.qmth.am.utils;
+
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeUnit;
+
+public class HttpClientBuilder {
+
+    private static final Logger log = LoggerFactory.getLogger(HttpClientBuilder.class);
+
+    private static OkHttpClient client;
+
+    static {
+        client = Client.INSTANCE.getInstance();
+    }
+
+    public static OkHttpClient getClient() {
+        return client;
+    }
+
+    private enum Client {
+
+        INSTANCE;
+
+        Client() {
+            ConnectionPool connectionPool = new ConnectionPool(10, 5L, TimeUnit.MINUTES);
+
+            instance = new OkHttpClient
+                    .Builder()
+                    // .retryOnConnectionFailure(false)
+                    .connectionPool(connectionPool)
+                    .connectTimeout(30, TimeUnit.SECONDS)
+                    .readTimeout(60, TimeUnit.SECONDS)
+                    .writeTimeout(60, TimeUnit.SECONDS)
+                    .sslSocketFactory(sslSocketFactory(), trustAllCert())
+                    .hostnameVerifier(trustAllHost())
+                    .build();
+
+            log.debug("OkHttpClient init..");
+        }
+
+        private OkHttpClient instance;
+
+        public OkHttpClient getInstance() {
+            return instance;
+        }
+    }
+
+    private static SSLSocketFactory sslSocketFactory() {
+        try {
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, new TrustManager[]{trustAllCert()}, new SecureRandom());
+            return sslContext.getSocketFactory();
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static HostnameVerifier trustAllHost() {
+        return (hostname, sslSession) -> true;
+    }
+
+    private static X509TrustManager trustAllCert() {
+        return new X509TrustManager() {
+
+            @Override
+            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+            }
+
+            @Override
+            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+            }
+
+            @Override
+            public X509Certificate[] getAcceptedIssuers() {
+                return new X509Certificate[0];
+            }
+        };
+    }
+
+}

+ 96 - 0
src/main/java/cn/com/qmth/am/utils/JsonHelper.java

@@ -0,0 +1,96 @@
+package cn.com.qmth.am.utils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qmth.boot.core.exception.StatusException;
+
+public class JsonHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(JsonHelper.class);
+
+    public static String toJson(Object object) {
+        if (object == null) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+            return mapper.writeValueAsString(object);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("JSON转换失败!");
+        }
+    }
+
+    public static <T> T toObj(String jsonStr, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+            return mapper.readValue(jsonStr, clazz);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("JSON转换失败!");
+        }
+    }
+
+    public static <T> List<T> toList(String jsonStr, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+            JavaType javaType = mapper.getTypeFactory().constructCollectionType(List.class, clazz);
+            return mapper.readValue(jsonStr, javaType);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("JSON转换失败!");
+        }
+    }
+
+    public static <T> Map<String, T> toMap(String jsonStr, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JavaType javaType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, clazz);
+            return mapper.readValue(jsonStr, javaType);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("JSON转换失败!");
+        }
+    }
+
+    public static JsonNode getNode(String jsonStr) {
+        if (StringUtils.isEmpty(jsonStr)) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            return mapper.readTree(jsonStr);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("JSON转换失败!");
+        }
+    }
+
+}

+ 88 - 0
src/main/java/cn/com/qmth/am/utils/MD5Util.java

@@ -0,0 +1,88 @@
+package cn.com.qmth.am.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+/**
+ * @Description: MD5加密工具类
+ */
+public class MD5Util {
+
+    /**
+     * MD5加密
+     *
+     * @param text
+     * @return
+     * @throws Exception
+     */
+    public static String encoder(String text) throws NoSuchAlgorithmException {
+        text = Optional.of(text).get();
+        MessageDigest digest = MessageDigest.getInstance("MD5");
+        digest.update(text.getBytes(Charset.forName("UTF-8")));
+        byte s[] = digest.digest();
+        StringJoiner result = new StringJoiner("");
+        for (int i = 0; i < s.length; i++) {
+            result.add(Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6));
+        }
+        return result.toString();
+    }
+
+    /**
+     * MD5校验
+     *
+     * @param text
+     * @param md5
+     * @return
+     * @throws Exception
+     */
+    public static boolean verify(String text, String md5) throws NoSuchAlgorithmException {
+        text = Optional.of(text).get();
+        md5 = Optional.of(md5).get();
+        //根据传入的密钥进行验证
+        String md5Text = encoder(text);
+        if (md5Text.equalsIgnoreCase(md5)) {
+            return true;
+        }
+        return false;
+    }
+    
+	public static String md5Hex(File file) {
+		FileInputStream in = null;
+		try {
+			in = new FileInputStream(file);
+			return DigestUtils.md5Hex(in);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if(in!=null)
+				try {
+					in.close();
+				} catch (IOException e) {
+				}
+		}
+	}
+	
+	public static String md5Hex(InputStream in) {
+		try {
+			return DigestUtils.md5Hex(in);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if(in!=null)
+				try {
+					in.close();
+				} catch (IOException e) {
+				}
+		}
+	}
+}
+

+ 64 - 0
src/main/java/cn/com/qmth/am/utils/ResouceUtil.java

@@ -0,0 +1,64 @@
+package cn.com.qmth.am.utils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+import org.apache.commons.io.IOUtils;
+
+public class ResouceUtil {
+	public static URL getUrl(String path) {
+		try {
+			ClassLoader classLoader = ResouceUtil.class.getClassLoader();
+
+			URL url = classLoader.getResource(path);
+			return url;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+	public static InputStream getStream(String path) {
+		try {
+			ClassLoader classLoader = ResouceUtil.class.getClassLoader();
+
+			URL url = classLoader.getResource(path);
+			return url.openStream();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+	public static String getContent(String path) {
+		try {
+			ClassLoader classLoader = ResouceUtil.class.getClassLoader();
+
+			URL url = classLoader.getResource(path);
+			return readFileContent(url.openStream());
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	@SuppressWarnings("deprecation")
+	private static String readFileContent(InputStream in) {
+		StringBuilder content = new StringBuilder();
+		InputStreamReader streamReader = null;
+		BufferedReader bufferedReader = null;
+		try {
+			String encoding = "UTF-8";
+			streamReader = new InputStreamReader(in, encoding);
+			bufferedReader = new BufferedReader(streamReader);
+			String line;
+			while ((line = bufferedReader.readLine()) != null) {
+				content.append(line);
+			}
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(in);
+			IOUtils.closeQuietly(streamReader);
+			IOUtils.closeQuietly(bufferedReader);
+		}
+		return content.toString();
+	}
+}

+ 74 - 0
src/main/java/cn/com/qmth/am/utils/SpringContextHolder.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.am.utils;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.ResolvableType;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring context holder.
+ *
+ */
+@Component
+public class SpringContextHolder implements ApplicationContextAware {
+
+	private static ApplicationContext ctx = null;
+
+	@Override
+	public void setApplicationContext(ApplicationContext ctx) {
+		SpringContextHolder.ctx = ctx;
+	}
+
+	public static ApplicationContext getApplicationContext() {
+		return ctx;
+	}
+
+	public static Object getBean(String name) {
+		return ctx.getBean(name);
+	}
+
+	public static <T> T getBean(String name, Class<T> requiredType) {
+		return ctx.getBean(name, requiredType);
+	}
+
+	public static <T> T getBean(Class<T> requiredType) {
+		return ctx.getBean(requiredType);
+	}
+
+	public static Object getBean(String name, Object... args) {
+		return ctx.getBean(name, args);
+	}
+
+	public static <T> T getBean(Class<T> requiredType, Object... args) {
+		return ctx.getBean(requiredType, args);
+	}
+
+	public static boolean containsBean(String name) {
+		return ctx.containsBean(name);
+	}
+
+	public static boolean isSingleton(String name) {
+		return ctx.isSingleton(name);
+	}
+
+	public static boolean isPrototype(String name) {
+		return ctx.isPrototype(name);
+	}
+
+	public static boolean isTypeMatch(String name, ResolvableType typeToMatch) {
+		return ctx.isTypeMatch(name, typeToMatch);
+	}
+
+	public static boolean isTypeMatch(String name, Class<?> typeToMatch) {
+		return ctx.isTypeMatch(name, typeToMatch);
+	}
+
+	public static Class<?> getType(String name) {
+		return ctx.getType(name);
+	}
+
+	public static String[] getAliases(String name) {
+		return ctx.getAliases(name);
+	}
+
+}

+ 45 - 0
src/main/resources/application.properties

@@ -0,0 +1,45 @@
+#
+# ********** server config **********
+#
+server.port=8083
+server.servlet.session.timeout=PT2H
+server.servlet.context-path=/
+spring.servlet.multipart.max-request-size=100MB
+spring.servlet.multipart.max-file-size=100MB
+com.qmth.mybatis.log-level=error
+com.qmth.mybatis.block-attack=false
+#
+# ********** db config **********
+#
+db.host=localhost
+db.port=3306
+db.database=ai-marking
+com.qmth.datasource.username=root
+com.qmth.datasource.password=123456
+com.qmth.datasource.url=jdbc:mysql://${db.host}:${db.port}/${db.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
+
+#
+# ********** sys config **********
+#
+com.qmth.logging.root-level=info
+com.qmth.logging.file-path=/home/admin/project/ai-marking/log/ai-marking.log
+
+#OSS config {oss://key:secret@bucket.host}
+#com.qmth.fss.config=oss://LTAI4FboXLCJzrjVo5dUoXaU:O0my6eSAl1Ic62WvxEf3WlMXox1LNX@examcloud-test.oss-cn-shenzhen.aliyuncs.com
+#com.qmth.fss.server=https://ecs-test-static.qmth.com.cn
+
+#DISK
+com.qmth.fss.config=/home/admin/project/ai-marking/static
+com.qmth.fss.server=/file
+
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+
+
+com.qmth.solar.app-version=@project.version@
+com.qmth.solar.server=https://solar.qmth.com.cn
+com.qmth.solar.access-key=
+com.qmth.solar.access-secret=
+
+am.data-type=MSRKING_CLOUD
+am.data-dir=../data

+ 5 - 0
src/main/resources/mapper/QuestionMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.qmth.am.dao.QuestionDao">
+
+</mapper>

BIN
template/struct-import.xlsx