deason 5 lat temu
rodzic
commit
f3b65c92e4
66 zmienionych plików z 6535 dodań i 0 usunięć
  1. 30 0
      assembly.xml
  2. 91 0
      pom.xml
  3. 1 0
      shell/start.args
  4. 29 0
      shell/start.sh
  5. 1 0
      shell/start.vmoptions
  6. 18 0
      shell/stop.sh
  7. 1 0
      shell/version
  8. 97 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/Task.java
  9. 49 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/Tianji2App.java
  10. 64 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_data/ExportData.java
  11. 429 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/ExportExamStudentScore.java
  12. 93 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamQuestionVO.java
  13. 44 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamRecordQuestionVO.java
  14. 192 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamStudentVO.java
  15. 83 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ScoreVO.java
  16. 174 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/GetStduentAnswerByIdentityNumberService.java
  17. 1 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/package-info.java
  18. 14 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/query_exam_record_data_1.sql
  19. 297 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/GetStduentAnswerDetailService.java
  20. 15 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_record_data_1.sql
  21. 16 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_record_data_2.sql
  22. 8 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_student_info.sql
  23. 134 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_one_question_answer/GetStduentOneAnswerService.java
  24. 188 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/ImportData.java
  25. 2 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/delete_data.sql
  26. 12 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/insert_data.sql
  27. 399 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/FixCorrectAnswerAndResetScoreService.java
  28. 344 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/ResetScoreService.java
  29. 196 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamQuestionEntity.java
  30. 141 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordDataEntity.java
  31. 68 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordQuestionsEntity.java
  32. 105 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamScoreEntity.java
  33. 15 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExchangeBean.java
  34. 24 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerReq.java
  35. 27 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerResp.java
  36. 31 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseRequest.java
  37. 44 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseResponse.java
  38. 16 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/JsonSerializable.java
  39. 58 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/FormFilePart.java
  40. 299 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/OKHttpUtil.java
  41. 156 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/QmthUtil.java
  42. 650 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/UpdateQuestionScoreService.java
  43. 56 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultPaperBean.java
  44. 56 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionGroupBean.java
  45. 107 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionStructureWrapperBean.java
  46. 67 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionUnitWrapperBean.java
  47. 43 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/ExamRecordPaperStructBean.java
  48. 59 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/OuterGetPaperStructReq.java
  49. 27 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/OuterGetPaperStructResp.java
  50. 59 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswer.java
  51. 171 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswerService.java
  52. 240 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswerThread.java
  53. 193 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamQuestionEntity.java
  54. 55 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamRecordEntity.java
  55. 62 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamRecordQuestionsEntity.java
  56. 15 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/FixAnswerType.java
  57. 60 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/QuestionType.java
  58. 49 0
      src/main/resources/application.properties
  59. 1 0
      src/main/resources/classpath.location
  60. 57 0
      src/main/resources/log4j2.xml
  61. 14 0
      src/main/resources/sql/export_data/export_data.sql
  62. 14 0
      src/main/resources/sql/get_student_one_question_answer/query_exam_record_data.sql
  63. 87 0
      src/main/resources/temp.txt
  64. 315 0
      src/main/resources/temp1.txt
  65. 33 0
      src/main/resources/upyun.xml
  66. 39 0
      src/test/java/cn/com/qmth/dp/examcloud/oe/test/ExportTest.java

+ 30 - 0
assembly.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+	<id>distribution</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<fileSets>
+		<fileSet>
+			<directory>${project.basedir}/src/main/resources</directory>
+			<outputDirectory>/config</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>${project.basedir}/shell</directory>
+			<excludes>
+				<exclude>start.args</exclude>
+				<exclude>start.vmoptions</exclude>
+			</excludes>
+			<outputDirectory>/</outputDirectory>
+			<fileMode>0777</fileMode>
+		</fileSet>
+	</fileSets>
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>true</useProjectArtifact>
+			<outputDirectory>lib</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 91 - 0
pom.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>data-processing-examcloud-oe</artifactId>
+    <version>v3.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-parent</artifactId>
+        <version>v3.0-SNAPSHOT</version>
+    </parent>
+
+    <properties>
+        <!-- 云平台版本 -->
+        <examcloud.version>v3.0-SNAPSHOT</examcloud.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-web</artifactId>
+            <version>${examcloud.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.cloud</groupId>
+                    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>cn.com.qmth.dp.examcloud.oe.Tianji2App</mainClass>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>./</classpathPrefix>
+                        </manifest>
+                        <manifestEntries>
+                            <Class-Path>../config/</Class-Path>
+                        </manifestEntries>
+                    </archive>
+                    <excludes>
+                        <exclude>*.sql</exclude>
+                        <exclude>*.properties</exclude>
+                        <exclude>*.xml</exclude>
+                        <exclude>classpath.location</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <finalName>data-processing-examcloud-oe</finalName>
+                    <descriptors>
+                        <descriptor>assembly.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>install</phase>
+                        <goals>
+                            <goal>assembly</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 1 - 0
shell/start.args

@@ -0,0 +1 @@
+--spring.profiles.active=test

+ 29 - 0
shell/start.sh

@@ -0,0 +1,29 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="data-processing-examcloud-oe-"$APP_VERSION"-SNAPSHOT.jar"
+
+JAVA_OPTS=`cat $FILE_PATH/start.vmoptions`
+APP_ARGS=`cat $FILE_PATH/start.args`
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "[ERROR] : APP is already running!"
+    exit -1
+fi
+
+echo "java options:"
+echo "$JAVA_OPTS"
+echo "args:"
+echo "$APP_ARGS"
+    
+nohup java $JAVA_OPTS -jar $FILE_PATH/lib/$APP_MAIN_JAR $APP_ARGS >/dev/null 2>&1 &
+
+echo "starting......"
+
+exit 0
+
+
+

+ 1 - 0
shell/start.vmoptions

@@ -0,0 +1 @@
+-server -Xms2g -Xmx2g

+ 18 - 0
shell/stop.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="data-processing-examcloud-oe-"$APP_VERSION"-SNAPSHOT.jar"
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "Runnable jar is $APP_MAIN_JAR."
+    for PID in $PID_LIST 
+    do
+        kill -9 $PID
+    done
+    echo "stopped !"
+fi
+
+exit 0

+ 1 - 0
shell/version

@@ -0,0 +1 @@
+2019

+ 97 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/Task.java

@@ -0,0 +1,97 @@
+package cn.com.qmth.dp.examcloud.oe;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+import cn.com.qmth.examcloud.web.upyun.UpYunPathInfo;
+import cn.com.qmth.examcloud.web.upyun.UpyunPathEnvironmentInfo;
+import cn.com.qmth.examcloud.web.upyun.UpyunService;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import org.apache.commons.lang3.RandomUtils;
+import org.bson.Document;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 任务
+ *
+ * @author WANGWEI
+ * @date 2019年9月6日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component
+public class Task {
+
+    private static ExamCloudLog log = ExamCloudLogFactory.getLog(Task.class);
+
+    /**
+     * 方法注释
+     *
+     * @author WANGWEI
+     */
+    public void start() {
+        try {
+            // ResetScoreService bean1 = SpringContextHolder.getBean(ResetScoreService.class);
+            // bean1.start();
+
+            // FixCorrectAnswerAndResetScoreService bean = SpringContextHolder.getBean(FixCorrectAnswerAndResetScoreService.class);
+            // bean.start();
+
+            // GetStduentAnswerDetailService bean = SpringContextHolder.getBean(GetStduentAnswerDetailService.class);
+            // bean.start(1627L, "000004");
+
+            // UpdateQuestionScoreService  bean = SpringContextHolder.getBean(UpdateQuestionScoreService.class);
+            // bean.start();
+
+            // ExportExamStudentScore bean = SpringContextHolder.getBean(ExportExamStudentScore.class);
+            // bean.start();
+        } catch (Exception e) {
+            log.error("unexpected", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 连接测试
+     *
+     * @author WANGWEI
+     */
+    public static void test() {
+
+        UpyunService upyunService = SpringContextHolder.getBean(UpyunService.class);
+        UpyunPathEnvironmentInfo env = new UpyunPathEnvironmentInfo();
+        env.setFileSuffix(".jpg");
+        env.setRootOrgId(String.valueOf(RandomUtils.nextLong()));
+        UpYunPathInfo upYunPathInfo = upyunService.writeFile("test", env,
+                new File("D:/Temp/111111X.jpg"), true);
+        System.out.println("upYunPathInfo: " + JsonUtil.toPrettyJson(upYunPathInfo));
+
+        JdbcTemplate jdbcTemplate = SpringContextHolder.getBean(JdbcTemplate.class);
+        List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT now() FROM dual");
+        System.out.println(JsonUtil.toJson(list));
+
+        MongoTemplate mongoTemplate = SpringContextHolder.getBean(MongoTemplate.class);
+        String dbName = mongoTemplate.getDb().getName();
+        System.out.println("mongo.db=" + dbName);
+        MongoCollection<Document> collection = mongoTemplate.getCollection("examRecordQuestions");
+        Document filter = new Document();
+        filter.append("examRecordDataId", 101373L);
+        FindIterable<Document> iterable = collection.find(filter);
+        MongoCursor<Document> iterator = iterable.iterator();
+        while (iterator.hasNext()) {
+            Document next = iterator.next();
+
+            System.out.println("_id=" + next.getObjectId("_id"));
+            System.out.println(JsonUtil.toPrettyJson(next));
+        }
+    }
+
+}

+ 49 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/Tianji2App.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.dp.examcloud.oe;
+
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * 启动类
+ *
+ * @author WANGWEI
+ * @date 2019年3月29日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@SpringBootApplication
+@Configuration
+@EnableAsync
+@ComponentScan(basePackages = {"cn.com.qmth"})
+@EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class})
+public class Tianji2App {
+
+    static {
+        System.setProperty("hibernate.dialect.storage_engine", "innodb");
+        PropertiesUtil.loadFromResource("application.properties");
+        PropertyHolder.loadFromResource("application.properties");
+    }
+
+    /**
+     * main
+     *
+     * @param args
+     * @author WANGWEI
+     */
+    public static void main(String[] args) {
+        SpringApplication.run(Tianji2App.class, args);
+
+        // UpyunSiteManager.init();
+
+        Task task = SpringContextHolder.getBean(Task.class);
+        task.start();
+    }
+
+}

+ 64 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_data/ExportData.java

@@ -0,0 +1,64 @@
+package cn.com.qmth.dp.examcloud.oe.modules.export_data;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.ResourceLoader;
+
+@Component
+public class ExportData {
+
+	private static ExamCloudLog log = ExamCloudLogFactory.getLog(ExportData.class);
+
+	private static ExamCloudLog resultlog = ExamCloudLogFactory.getLog("RESULT_LOGGER");
+
+	@Autowired
+	JdbcTemplate jdbcTemplate;
+
+	public void start() {
+		String sql = ResourceLoader.getResource("sql/export_data/export_data.sql");
+
+		try {
+			Connection connection = jdbcTemplate.getDataSource().getConnection();
+
+			PreparedStatement ps = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_READ_ONLY);
+			ps.setFetchSize(Integer.MIN_VALUE);
+			ResultSet rs = ps.executeQuery(sql);
+
+			int count = 0;
+			while (rs.next()) {
+				Map<String, Object> map = Maps.newHashMap();
+				ResultSetMetaData metaData = rs.getMetaData();
+				int columnCount = metaData.getColumnCount();
+				for (int i = 0; i < columnCount; i++) {
+					String columnName = metaData.getColumnName(i + 1);
+					Object columnValue = rs.getObject(i + 1);
+					map.put(columnName, columnValue);
+				}
+
+				count++;
+				log.info("[ExportData]. count=" + count);
+				resultlog.info(JsonUtil.toJson(map));
+
+			}
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+
+	}
+
+}

+ 429 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/ExportExamStudentScore.java

@@ -0,0 +1,429 @@
+package cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score;
+
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ExamQuestionVO;
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ExamRecordQuestionVO;
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ExamStudentVO;
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ScoreVO;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.assertj.core.util.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 导出 - 小题分
+ */
+@Component
+public class ExportExamStudentScore {
+
+    private static Logger log = LoggerFactory.getLogger(ExportExamStudentScore.class);
+
+    private static final Long MARKING_TYPE_KEY = 24L;// 阅卷方式KEY
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    public void start() {
+        /*
+            待导出的“考试 <课程...课程>”列表
+            Map : <examId, <courseId...courseId>
+            示例1:<1L, 不指定任何课程时,则默认导所有课程>
+            示例2:<1L, 指定某些课程时,则只导指定的课程>
+         */
+        HashMap<Long, List<Long>> examMaps = new HashMap<>();
+
+        // examMaps.put(1L, Lists.list(1L, 2L, 3L));
+        // examMaps.put(2L, Lists.list());
+
+        this.export(examMaps);
+    }
+
+    private void export(HashMap<Long, List<Long>> examMaps) {
+        Map<Long, String> orgNameMaps = new HashMap<>(), courseNameMaps = new HashMap<>();
+
+        log.info("Total size is " + examMaps.size());
+
+        int count = 1;
+        for (Map.Entry<Long, List<Long>> entry : examMaps.entrySet()) {
+            log.info((count++) + "===> export starting, examId = " + entry.getKey());
+
+            // 获取考试下待导的课程
+            List<Long> examCourseIds;
+            if (CollectionUtils.isEmpty(entry.getValue())) {
+                // 导考试下全部课程
+                examCourseIds = this.queryExamCourseIds(entry.getKey());
+            } else {
+                // 导考试下指定课程
+                examCourseIds = entry.getValue();
+            }
+
+            log.info("---> examCourseIds size is " + examCourseIds.size());
+
+            // 获取考试的阅卷方式
+            String markingType = this.queryExamMarkingType(entry.getKey(), MARKING_TYPE_KEY);
+            log.info("---> markingType is " + markingType);
+
+            for (Long courseId : examCourseIds) {
+                // 按考试的每个课程依次导出
+                this.export(entry.getKey(), courseId, markingType, orgNameMaps, courseNameMaps);
+            }
+
+            log.info("===> export finished, examId = " + entry.getKey() + "\n");
+        }
+    }
+
+    private void export(Long examId, Long courseId, String markingType, Map<Long, String> orgNameMaps, Map<Long, String> courseNameMaps) {
+        List<ExamStudentVO> examStudents = this.queryExamStudents(examId, courseId);
+        log.info(String.format("---> examId = %s, courseId = %s, examStudentSize = %s", examId, courseId, examStudents.size()));
+
+        boolean hasDynamicExcelHeaderSetting = false;
+        List<String> dynamicExcelHeaders = new ArrayList<>();// Excel动态列
+        List<String> defaultDynamicColumnValues = new ArrayList<>();// Excel动态列默认值
+        String paperStructKey = "";// 试卷结构关键值
+
+        int index = 0;
+        for (ExamStudentVO examStudent : examStudents) {
+            log.debug((++index) + "---> examStudentId is " + examStudent.getExamStudentId());
+
+            if (!orgNameMaps.containsKey(examStudent.getOrgId())) {
+                String orgName = this.queryOrgName(examStudent.getOrgId());
+                orgNameMaps.put(examStudent.getOrgId(), orgName);
+            }
+
+            if (!courseNameMaps.containsKey(examStudent.getCourseId())) {
+                String courseName = this.queryCourseName(examStudent.getCourseId());
+                courseNameMaps.put(examStudent.getCourseId(), courseName);
+            }
+
+            // 设置学习中心名称、课程名称
+            examStudent.setOrgName(orgNameMaps.get(examStudent.getOrgId()));
+            examStudent.setCourseName(courseNameMaps.get(examStudent.getCourseId()));
+
+            // 成绩默认值
+            examStudent.setObjectiveScore(0d);
+            examStudent.setSubjectiveScore(0d);
+            examStudent.setTotalScore(0d);
+
+            if (!examStudent.getFinished()) {
+                // 跳过缺考的情况
+                continue;
+            }
+
+            // 获取考试记录
+            List<Map<String, Object>> examRecordDataList = this.queryExamRecordDataList(examId, courseId, examStudent.getExamStudentId());
+            if (CollectionUtils.isEmpty(examRecordDataList)) {
+                // 跳过暂无有效考试记录的情况
+                continue;
+            }
+
+            Long okExamRecordDataId;
+            if (examRecordDataList.size() == 1) {
+                // 只有一条考试记录情况
+                okExamRecordDataId = (Long) examRecordDataList.get(0).get("id");
+
+                // 获取考试记录对应的成绩
+                ScoreVO okScore = this.queryExamScore(okExamRecordDataId);
+                examStudent.setObjectiveScore(okScore.getObjectiveScore());
+                examStudent.setSubjectiveScore(okScore.getSubjectiveScore());
+                examStudent.setTotalScore(okScore.getTotalScore());
+            } else {
+                // 多条考试记录情况
+                List<ScoreVO> scores = new ArrayList<>();
+                for (Map<String, Object> examRecordData : examRecordDataList) {
+                    Long examRecordDataId = (Long) examRecordData.get("id");
+                    // 分别获取考试记录对应的成绩
+                    ScoreVO score = this.queryExamScore(examRecordDataId);
+                    scores.add(score);
+                }
+
+                ScoreVO okScore;
+                if (markingType.equals("ALL") || markingType.equals("OBJECT_SCORE_MAX")) {
+                    // 全部评阅规则 或 客观分最高规则:取总分最高
+                    okScore = scores.stream()
+                            .sorted((obj1, obj2) -> obj2.getTotalScore().compareTo(obj1.getTotalScore()))
+                            .collect(Collectors.toList())
+                            .get(0);
+                } else {
+                    // 否则:取最后一次的成绩
+                    okScore = scores.stream()
+                            .sorted((obj1, obj2) -> obj2.getId().compareTo(obj1.getId()))
+                            .collect(Collectors.toList())
+                            .get(0);
+                }
+
+                okExamRecordDataId = okScore.getExamRecordDataId();
+                examStudent.setObjectiveScore(okScore.getObjectiveScore());
+                examStudent.setSubjectiveScore(okScore.getSubjectiveScore());
+                examStudent.setTotalScore(okScore.getTotalScore());
+            }
+
+            // 获取试题作答记录
+            ExamRecordQuestionVO questions = this.queryExamRecordQuestions(okExamRecordDataId);
+            if (questions == null || CollectionUtils.isEmpty(questions.getExamQuestionEntities())) {
+                continue;
+            }
+
+            List<ExamQuestionVO> examQuestionEntities = questions.getExamQuestionEntities();
+
+            if (!hasDynamicExcelHeaderSetting) {
+                for (ExamQuestionVO question : examQuestionEntities) {
+                    String questionType = this.convertQuestionType(question.getQuestionType());
+
+                    if ("SINGLE_CHOICE".equals(question.getQuestionType()) || "MULTIPLE_CHOICE".equals(question.getQuestionType()) || "TRUE_OR_FALSE".equals(question.getQuestionType())) {
+                        dynamicExcelHeaders.add(String.format("%s%s-%s作答", questionType, question.getMainNumber(), question.getOrder()));
+                        defaultDynamicColumnValues.add("");
+
+                        dynamicExcelHeaders.add(String.format("%s%s-%s得分", questionType, question.getMainNumber(), question.getOrder()));
+                        defaultDynamicColumnValues.add("0");
+                    } else {
+                        // 主观题不设置“作答”列
+                        dynamicExcelHeaders.add(String.format("%s%s-%s得分", questionType, question.getMainNumber(), question.getOrder()));
+                        defaultDynamicColumnValues.add("0");
+                    }
+
+                    hasDynamicExcelHeaderSetting = true;
+
+                    paperStructKey += (question.getOrder() + question.getQuestionType());
+                }
+            }
+
+            String curPaperStructKey = "";// 当前试卷结构关键值
+            List<String> curDynamicColumnValues = new ArrayList<>();// Excel动态列当前值
+            for (ExamQuestionVO question : examQuestionEntities) {
+                if ("SINGLE_CHOICE".equals(question.getQuestionType()) || "MULTIPLE_CHOICE".equals(question.getQuestionType()) || "TRUE_OR_FALSE".equals(question.getQuestionType())) {
+                    if ("TRUE_OR_FALSE".equals(question.getQuestionType())) {
+                        curDynamicColumnValues.add(this.convertTrueFalse(question.getStudentAnswer()));
+                    } else {
+                        curDynamicColumnValues.add(this.convertChar(question.getStudentAnswer()));
+                    }
+
+                    // 注:客观题得分,目前只能通过比较作答答案是否一致来确定得分
+                    String correctAnswer = question.getCorrectAnswer() != null ? question.getCorrectAnswer() : "";
+                    String studentAnswer = question.getStudentAnswer() != null ? question.getStudentAnswer() : "";
+                    if (correctAnswer.equals(studentAnswer)) {
+                        curDynamicColumnValues.add(question.getQuestionScore() != null ? question.getQuestionScore().toString() : "0");
+                    } else {
+                        curDynamicColumnValues.add("0");
+                    }
+                } else {
+                    curDynamicColumnValues.add(question.getStudentScore() != null ? question.getStudentScore().toString() : "0");
+                }
+
+                curPaperStructKey += (question.getOrder() + question.getQuestionType());
+            }
+
+            if (!curPaperStructKey.equals(paperStructKey)) {
+                String msg = String.format("考生之间存在试卷结构不一致的情况!%s-%s-%s-", examId, courseId, examStudent.getExamStudentId());
+                log.warn(msg);
+                continue;
+                // throw new StatusException("500", msg);
+            }
+
+            examStudent.setDetails(curDynamicColumnValues);
+        }
+
+        // 补全缺失的作答区
+        for (ExamStudentVO examStudent : examStudents) {
+            if (CollectionUtils.isNotEmpty(examStudent.getDetails())) {
+                continue;
+            }
+            examStudent.setDetails(defaultDynamicColumnValues);
+        }
+
+        List<String> fixedExcelHeaders = Lists.newArrayList("学习中心", "课程代码", "课程名称", "层次", "是否缺考", "身份证号", "学号", "姓名", "年级", "专业", "客观总分", "主观总分", "总分");
+        fixedExcelHeaders.addAll(dynamicExcelHeaders);
+
+        this.toExcel(examId, courseId, examStudents, fixedExcelHeaders);
+
+        examStudents.clear();
+    }
+
+    private void toExcel(Long examId, Long courseId, List<ExamStudentVO> examStudents, List<String> excelHeaders) {
+        // 处理Excel
+        List<Object[]> excelRows = new ArrayList<>();
+
+        if (CollectionUtils.isNotEmpty(examStudents)) {
+            for (ExamStudentVO vo : examStudents) {
+                List<Object> rowValues = Lists.newArrayList(vo.getOrgName(), vo.getCourseCode(), vo.getCourseName(),
+                        vo.getCourseLevel(), vo.getFinished() ? "否" : "是", vo.getIdentityNumber(),
+                        vo.getStudentCode(), vo.getStudentName(), vo.getGrade(), vo.getSpecialtyName(),
+                        vo.getObjectiveScore().toString(), vo.getSubjectiveScore().toString(),
+                        vo.getTotalScore().toString());
+
+                if (CollectionUtils.isNotEmpty(vo.getDetails())) {
+                    rowValues.addAll(vo.getDetails());
+                }
+
+                excelRows.add(rowValues.toArray(new Object[rowValues.size()]));
+            }
+        }
+
+        Class<String>[] excelTypes = new Class[excelHeaders.size()];
+        for (int n = 0; n < excelHeaders.size(); n++) {
+            excelTypes[n] = String.class;
+        }
+
+        final String filePath = String.format("/home/admin/project/oe/exam_%s/exam_student_score_%s.xlsx", examId, courseId);
+        ExcelWriter.write(excelHeaders.toArray(new String[excelHeaders.size()]), excelTypes, excelRows, new File(filePath));
+    }
+
+    private String queryOrgName(Long orgId) {
+        final String querySql = String.format("select name from ec_b_org where id = %s", orgId);
+        try {
+            return jdbcTemplate.queryForObject(querySql, String.class);
+        } catch (EmptyResultDataAccessException e) {
+            return "";
+        }
+    }
+
+    private String queryCourseName(Long courseId) {
+        final String querySql = String.format("select name from ec_b_course where id = %s", courseId);
+        try {
+            return jdbcTemplate.queryForObject(querySql, String.class);
+        } catch (EmptyResultDataAccessException e) {
+            return "";
+        }
+    }
+
+    private List<Long> queryExamCourseIds(Long examId) {
+        final String querySql = String.format("select distinct course_id from ec_oe_exam_student where exam_id = %s and enable = 1", examId);
+        return jdbcTemplate.queryForList(querySql, Long.class);
+    }
+
+    private List<ExamStudentVO> queryExamStudents(Long examId, Long courseId) {
+        final String columns = "exam_student_id,student_code,student_name,identity_number,org_id,course_id,course_code,course_level,grade,specialty_name,finished";
+        final String querySql = String.format("select %s from ec_oe_exam_student where exam_id = %s and course_id = %s and enable = 1", columns, examId, courseId);
+        return jdbcTemplate.query(querySql, new BeanPropertyRowMapper(ExamStudentVO.class));
+    }
+
+    private String queryExamMarkingType(Long examId, Long markingTypeKey) {
+        final String querySql = String.format("select value from ec_e_exam_prop where exam_id = %s and key_id = %s", examId, markingTypeKey);
+        try {
+            String result = jdbcTemplate.queryForObject(querySql, String.class);
+            return StringUtils.isNoneBlank(result) ? result.trim() : "ALL";
+        } catch (EmptyResultDataAccessException e) {
+            return "ALL";// 默认值
+        }
+    }
+
+    private List<Map<String, Object>> queryExamRecordDataList(Long examId, Long courseId, Long examStudentId) {
+        final String columns = "id,exam_record_status,is_illegality,is_warn,is_audit";
+        final String querySql = String.format("select %s from ec_oe_exam_record_data where exam_id = %s and course_id = %s and exam_student_id = %s", columns, examId, courseId, examStudentId);
+        List<Map<String, Object>> result = jdbcTemplate.queryForList(querySql);
+
+        List<Map<String, Object>> okList = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(result)) {
+            for (Map<String, Object> row : result) {
+                // 过滤考试记录状态情况
+                String examRecordStatus = (String) row.get("exam_record_status");
+                if (!"EXAM_END".equals(examRecordStatus) && !"EXAM_OVERDUE".equals(examRecordStatus)) {
+                    continue;
+                }
+
+                Boolean isIllegality = (Boolean) row.get("is_illegality");// 是否违纪
+                Boolean isWarn = (Boolean) row.get("is_warn");// 是否警告
+                Boolean isAudit = (Boolean) row.get("is_audit");// 是否被审核过
+
+                // 没有违纪的 且 (没有警告 或 有警告已审核通过的),则有效
+                if (!isIllegality && (!isWarn || (isWarn && isAudit))) {
+                    okList.add(row);
+                }
+            }
+        }
+
+        return okList;
+    }
+
+    private ScoreVO queryExamScore(Long examRecordDataId) {
+        final String querySql = String.format("select id,objective_score,subjective_score,total_score from ec_oe_exam_score where exam_record_data_id = %s", examRecordDataId);
+        List<ScoreVO> result = jdbcTemplate.query(querySql, new BeanPropertyRowMapper(ScoreVO.class));
+
+        ScoreVO score;
+        if (CollectionUtils.isNotEmpty(result)) {
+            score = result.get(0);
+        } else {
+            score = new ScoreVO();
+        }
+
+        // 默认值
+        score.setExamRecordDataId(examRecordDataId);
+        score.setObjectiveScore(score.getObjectiveScore() != null ? score.getObjectiveScore() : 0d);
+        score.setSubjectiveScore(score.getSubjectiveScore() != null ? score.getSubjectiveScore() : 0d);
+        score.setTotalScore(score.getTotalScore() != null ? score.getTotalScore() : 0d);
+
+        return score;
+    }
+
+    private ExamRecordQuestionVO queryExamRecordQuestions(Long examRecordDataId) {
+        Query query = new Query();
+        query.addCriteria(Criteria.where("examRecordDataId").is(examRecordDataId));
+        List<ExamRecordQuestionVO> result = mongoTemplate.find(query, ExamRecordQuestionVO.class, "examRecordQuestions");
+        if (CollectionUtils.isNotEmpty(result)) {
+            return result.get(0);
+        }
+        return null;
+    }
+
+    private String convertQuestionType(String type) {
+        switch (type) {
+            case "SINGLE_CHOICE":
+                return "单选题";
+            case "MULTIPLE_CHOICE":
+                return "多选题";
+            case "FILL_UP":
+                return "填空题";
+            case "TRUE_OR_FALSE":
+                return "判断题";
+            case "ESSAY":
+                return "问答题";
+            default:
+                return "";
+        }
+    }
+
+    private String convertChar(String value) {
+        if (StringUtils.isBlank(value)) {
+            return "";
+        }
+        return value.replaceAll("0", "A")
+                .replaceAll("1", "B")
+                .replaceAll("2", "C")
+                .replaceAll("3", "D")
+                .replaceAll("4", "E")
+                .replaceAll("5", "F")
+                .replaceAll("6", "G")
+                .replaceAll("7", "H")
+                .replaceAll("8", "I")
+                .replaceAll("9", "J");
+    }
+
+    private String convertTrueFalse(String value) {
+        if ("1".equals(value)) {
+            return "对";
+        } else if ("0".equals(value)) {
+            return "错";
+        } else {
+            return "";
+        }
+    }
+
+}

+ 93 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamQuestionVO.java

@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-03-11 10:29:32
+ */
+
+package cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo;
+
+import java.io.Serializable;
+
+public class ExamQuestionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+
+    private String questionType;
+
+    private Integer mainNumber;
+
+    private Integer order;
+
+    private String correctAnswer;
+
+    private String studentAnswer;
+
+    private Double questionScore;
+
+    private Double studentScore;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
+
+    public Integer getMainNumber() {
+        return mainNumber;
+    }
+
+    public void setMainNumber(Integer mainNumber) {
+        this.mainNumber = mainNumber;
+    }
+
+    public Integer getOrder() {
+        return order;
+    }
+
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    public String getCorrectAnswer() {
+        return correctAnswer;
+    }
+
+    public void setCorrectAnswer(String correctAnswer) {
+        this.correctAnswer = correctAnswer;
+    }
+
+    public String getStudentAnswer() {
+        return studentAnswer;
+    }
+
+    public void setStudentAnswer(String studentAnswer) {
+        this.studentAnswer = studentAnswer;
+    }
+
+    public Double getQuestionScore() {
+        return questionScore;
+    }
+
+    public void setQuestionScore(Double questionScore) {
+        this.questionScore = questionScore;
+    }
+
+    public Double getStudentScore() {
+        return studentScore;
+    }
+
+    public void setStudentScore(Double studentScore) {
+        this.studentScore = studentScore;
+    }
+}

+ 44 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamRecordQuestionVO.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-03-11 10:29:32
+ */
+
+package cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class ExamRecordQuestionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+
+    private String examRecordDataId;
+
+    private List<ExamQuestionVO> examQuestionEntities;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(String examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public List<ExamQuestionVO> getExamQuestionEntities() {
+        return examQuestionEntities;
+    }
+
+    public void setExamQuestionEntities(List<ExamQuestionVO> examQuestionEntities) {
+        this.examQuestionEntities = examQuestionEntities;
+    }
+}

+ 192 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ExamStudentVO.java

@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-03-11 10:29:32
+ */
+
+package cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExamStudentVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long examStudentId;//考生ID
+
+    private Long orgId;//学习中心ID
+
+    private String orgName;//学习中心名称
+
+    private Long courseId;//课程ID
+
+    private String courseCode;//课程代码
+
+    private String courseName;//课程名称
+
+    private String courseLevel;//课程层次
+
+    private Boolean finished;//是否缺考
+
+    private String identityNumber;//身份证号
+
+    private String studentCode;//学号
+
+    private String studentName;//姓名
+
+    private String grade;//年级
+
+    private String specialtyName;//专业
+
+    private Double objectiveScore;//客观总分
+
+    private Double subjectiveScore;//主观总分
+
+    private Double totalScore;//总分
+
+    private List<String> details;//作答明细
+
+    public void addDetail(String detail) {
+        if (this.details == null) {
+            this.details = new ArrayList<>();
+        }
+        this.details.add(detail);
+    }
+
+    public Long getExamStudentId() {
+        return examStudentId;
+    }
+
+    public void setExamStudentId(Long examStudentId) {
+        this.examStudentId = examStudentId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getCourseLevel() {
+        return courseLevel;
+    }
+
+    public void setCourseLevel(String courseLevel) {
+        this.courseLevel = courseLevel;
+    }
+
+    public Boolean getFinished() {
+        return finished;
+    }
+
+    public void setFinished(Boolean finished) {
+        this.finished = finished;
+    }
+
+    public String getIdentityNumber() {
+        return identityNumber;
+    }
+
+    public void setIdentityNumber(String identityNumber) {
+        this.identityNumber = identityNumber;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getStudentName() {
+        return studentName;
+    }
+
+    public void setStudentName(String studentName) {
+        this.studentName = studentName;
+    }
+
+    public String getGrade() {
+        return grade;
+    }
+
+    public void setGrade(String grade) {
+        this.grade = grade;
+    }
+
+    public String getSpecialtyName() {
+        return specialtyName;
+    }
+
+    public void setSpecialtyName(String specialtyName) {
+        this.specialtyName = specialtyName;
+    }
+
+    public Double getObjectiveScore() {
+        return objectiveScore;
+    }
+
+    public void setObjectiveScore(Double objectiveScore) {
+        this.objectiveScore = objectiveScore;
+    }
+
+    public Double getSubjectiveScore() {
+        return subjectiveScore;
+    }
+
+    public void setSubjectiveScore(Double subjectiveScore) {
+        this.subjectiveScore = subjectiveScore;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public List<String> getDetails() {
+        return details;
+    }
+
+    public void setDetails(List<String> details) {
+        this.details = details;
+    }
+}

+ 83 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/export_exam_student_score/vo/ScoreVO.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-03-11 10:29:32
+ */
+
+package cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo;
+
+import java.io.Serializable;
+
+public class ScoreVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    private Long examRecordDataId;
+
+    private Double objectiveScore;
+
+    private Double subjectiveScore;
+
+    private Double totalScore;
+
+    private Double objectiveAccuracy;
+
+    private Double succPercent;
+
+    public Double getObjectiveAccuracy() {
+        return objectiveAccuracy;
+    }
+
+    public void setObjectiveAccuracy(Double objectiveAccuracy) {
+        this.objectiveAccuracy = objectiveAccuracy;
+    }
+
+    public Double getSuccPercent() {
+        return succPercent;
+    }
+
+    public void setSuccPercent(Double succPercent) {
+        this.succPercent = succPercent;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Double getObjectiveScore() {
+        return objectiveScore;
+    }
+
+    public void setObjectiveScore(Double objectiveScore) {
+        this.objectiveScore = objectiveScore;
+    }
+
+    public Double getSubjectiveScore() {
+        return subjectiveScore;
+    }
+
+    public void setSubjectiveScore(Double subjectiveScore) {
+        this.subjectiveScore = subjectiveScore;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+}

+ 174 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/GetStduentAnswerByIdentityNumberService.java

@@ -0,0 +1,174 @@
+package cn.com.qmth.dp.examcloud.oe.modules.get_student_answer_by_identity_number;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bson.Document;
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.mongodb.core.DocumentCallbackHandler;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
+import com.mongodb.MongoException;
+
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import cn.com.qmth.examcloud.commons.util.ResourceLoader;
+
+/**
+ * 获取一个考生的作答明细
+ *
+ * @author WANGWEI
+ * @date 2019年7月11日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Service
+public class GetStduentAnswerByIdentityNumberService {
+
+	@Autowired
+	JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	MongoTemplate mongoTemplate;
+
+	private int sqlIndex = 1;
+
+	// @Async
+	public void start(Long examId, String courseCode, String identityNumber) throws Exception {
+		String packageName = this.getClass().getPackage().getName();
+		String packagePath = packageName.replaceAll("\\.", "/");
+		String sql = ResourceLoader
+				.getResource(packagePath + "/query_exam_record_data_" + sqlIndex + ".sql");
+		Object[] args = new Object[]{examId, courseCode, identityNumber};
+		List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, args);
+
+		List<Long> examRecordDataIdList = Lists.newArrayList();
+
+		for (Map<String, Object> map : result) {
+			Long examRecordDataId = (Long) map.get("EXAM_RECORD_DATA_ID");
+			String curIdentityNumber = (String) map.get("IDENTITY_NUMBER");
+			String curCourseCode = (String) map.get("CODE");
+
+			if (!curCourseCode.equals(courseCode)) {
+				throw new RuntimeException("unexpected exception. courseCode=" + courseCode);
+			}
+
+			if (!curIdentityNumber.equals(identityNumber)) {
+				throw new RuntimeException("unexpected exception. courseCode=" + courseCode);
+			}
+
+			examRecordDataIdList.add(examRecordDataId);
+		}
+
+		List<Object[]> datas = Lists.newArrayList();
+
+		for (Long cur : examRecordDataIdList) {
+
+			List<Document> answers = getAnswers(cur);
+
+			for (Document doc : answers) {
+				Integer order = doc.getInteger("order");
+				Double studentScore = doc.getDouble("studentScore");
+				String questionId = doc.getString("questionId");
+				String studentAnswer = doc.getString("studentAnswer");
+				String questionType = doc.getString("questionType");
+
+				if (null == order) {
+					throw new RuntimeException("order is null. examRecordDataId=" + cur);
+				}
+
+				if (null == questionType) {
+					throw new RuntimeException(
+							"questionType is null. examRecordDataId=" + cur + "; order=" + order);
+				}
+
+				if (null == studentScore) {
+					// 无作答时,0分
+					if (StringUtils.isBlank(studentAnswer)) {
+						studentScore = 0D;
+					}
+				}
+
+				if (null != studentAnswer && !studentAnswer.contains("<img")) {
+					studentAnswer = Jsoup.clean(studentAnswer, Whitelist.simpleText());
+				}
+
+				if (null != studentAnswer) {
+					if (questionType.equals("SINGLE_CHOICE")
+							|| questionType.equals("MULTIPLE_CHOICE")) {
+						studentAnswer = studentAnswer.replaceAll("0", "A");
+						studentAnswer = studentAnswer.replaceAll("1", "B");
+						studentAnswer = studentAnswer.replaceAll("2", "C");
+						studentAnswer = studentAnswer.replaceAll("3", "D");
+						studentAnswer = studentAnswer.replaceAll("4", "E");
+						studentAnswer = studentAnswer.replaceAll("5", "F");
+						studentAnswer = studentAnswer.replaceAll("6", "G");
+						studentAnswer = studentAnswer.replaceAll("7", "H");
+						studentAnswer = studentAnswer.replaceAll("8", "I");
+					}
+				}
+
+				Object[] row = new Object[]{identityNumber, courseCode, String.valueOf(cur),
+						String.valueOf(order), questionId, studentAnswer,
+						null != studentScore ? String.valueOf(studentScore) : null};
+
+				datas.add(row);
+			}
+
+		}
+
+		String filePath = "D:/Temp/answers-" + examId + "-" + courseCode + "-" + identityNumber
+				+ ".xlsx";
+
+		final String[] EXCEL_HEADER = new String[]{"身份证号码", "课程代码", "考试记录ID", "题号", "题目ID", "答案",
+				"分数"};
+
+		ExcelWriter
+				.write(EXCEL_HEADER,
+						new Class[]{String.class, String.class, String.class, String.class,
+								String.class, String.class, String.class},
+						datas, new File(filePath));
+
+		System.out.println("OVER !   identityNumber=" + identityNumber);
+	}
+
+	/**
+	 * 获取作答
+	 *
+	 * @author WANGWEI
+	 * @param examRecordDataId
+	 * @return
+	 */
+	private List<Document> getAnswers(Long examRecordDataId) {
+		Query query = Query.query(Criteria.where("examRecordDataId").is(examRecordDataId));
+		final ObjectHolder<List<Document>> answersHolder = new ObjectHolder<List<Document>>(null);
+		mongoTemplate.executeQuery(query, "examRecordQuestions", new DocumentCallbackHandler() {
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public void processDocument(Document document)
+					throws MongoException, DataAccessException {
+				List<Document> answers = (List<Document>) document.get("examQuestionEntities");
+				answersHolder.set(answers);
+			}
+		});
+
+		List<Document> answers = answersHolder.get();
+		return answers;
+	}
+
+	public void setSqlIndex(int sqlIndex) {
+		this.sqlIndex = sqlIndex;
+	}
+
+}

+ 1 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/package-info.java

@@ -0,0 +1 @@
+package cn.com.qmth.dp.examcloud.oe.modules.get_student_answer_by_identity_number;

+ 14 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_by_identity_number/query_exam_record_data_1.sql

@@ -0,0 +1,14 @@
+SELECT
+	t1.identity_number,
+	t2.id AS exam_record_data_id,
+	t4.`code` 
+FROM
+	ec_oe_exam_record t1
+	LEFT JOIN ec_oe_exam_record_data t2 ON t1.id = t2.exam_record_id
+	LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id 
+WHERE
+	(( t2.is_warn = 0 ) OR ( t2.is_warn = 1 AND t2.is_audit = 1 ) ) 
+	AND t2.exam_record_status != 'EXAM_INVALID' 
+	AND t1.exam_id = ? 
+	AND t4.`code` = ?
+	AND t1.identity_number = ?

+ 297 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/GetStduentAnswerDetailService.java

@@ -0,0 +1,297 @@
+package cn.com.qmth.dp.examcloud.oe.modules.get_student_answer_detail;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.Document;
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.mongodb.core.DocumentCallbackHandler;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.mongodb.MongoException;
+
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.ResourceLoader;
+
+/**
+ * 获取考生作答明细
+ *
+ * @author WANGWEI
+ * @date 2019年7月11日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Service
+public class GetStduentAnswerDetailService {
+
+	@Autowired
+	JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	MongoTemplate mongoTemplate;
+
+	private int sqlIndex = 1;
+
+	private boolean mustHaveScore = false;
+
+	public void start(Long examId, String... courseCodeLIst) throws Exception {
+		for (String courseCode : courseCodeLIst) {
+			start(examId, courseCode);
+		}
+	}
+
+	@Async
+	public void start(Long examId, String courseCode) throws Exception {
+		String packageName = this.getClass().getPackage().getName();
+		String packagePath = packageName.replaceAll("\\.", "/");
+		//E:\qmthPro\dataProcess\data-processing-examcloud-oe\src\main\java\cn\com\qmth\dp\examcloud\oe\modules\get_student_answer_detail
+		//cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_record_data_1.sql
+//		String sql = ResourceLoader
+//				.getResource(packagePath + "/query_exam_record_data_" + sqlIndex + ".sql");
+		String sql="SELECT " +
+				"t1.identity_number, " +
+				" t1.id AS exam_record_data_id, " +
+				"t3.total_score as score, " +
+				"t4.`code`  " +
+				"FROM " +
+				"ec_oe_exam_record_data t1 " +
+				"LEFT JOIN ec_oe_exam_score t3 ON t3.exam_record_data_id =  t1.id " +
+				"LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id  " +
+				"WHERE " +
+				"((  t1.is_warn = 0 ) OR (  t1.is_warn = 1 AND  t1.is_audit = 1 ) )  " +
+				"AND  t1.exam_record_status != 'EXAM_INVALID'  " +
+				"AND t1.exam_id = ?  " +
+				"AND t4.`code` = ?";
+		Object[] args = new Object[]{examId, courseCode};
+		List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, args);
+
+		Map<String, Double> maxScoreOf = Maps.newHashMap();
+		Map<String, Long> examRecordDataIdOf = Maps.newHashMap();
+		Map<Long, String> identityNumberOf = Maps.newHashMap();
+
+		for (Map<String, Object> map : result) {
+			Long examRecordDataId = (Long) map.get("EXAM_RECORD_DATA_ID");
+			Double score = (Double) map.get("SCORE");
+			String identityNumber = (String) map.get("IDENTITY_NUMBER");
+			String curCourseCode = (String) map.get("CODE");
+
+			if (null == score) {
+				throw new RuntimeException("score is null. examRecordDataId=" + examRecordDataId);
+			}
+
+			if (!curCourseCode.equals(courseCode)) {
+				throw new RuntimeException("unexpected exception. courseCode=" + courseCode);
+			}
+
+			identityNumberOf.put(examRecordDataId, identityNumber);
+
+			Double maxScore = maxScoreOf.get(identityNumber);
+			if (null == maxScore || maxScore <= score) {
+				examRecordDataIdOf.put(identityNumber, examRecordDataId);
+				maxScoreOf.put(identityNumber, score);
+			}
+		}
+
+		List<Long> examRecordDataIdList = new ArrayList<>(examRecordDataIdOf.values());
+
+		List<Object[]> datas = Lists.newArrayList();
+
+		List<Object[]> noStudentScoreList = Lists.newArrayList();
+
+		int total = examRecordDataIdList.size();
+		int count = 0;
+		for (Long cur : examRecordDataIdList) {
+			count++;
+			System.out.println("total: " + total + "; count: " + count);
+
+			List<Document> answers = getAnswers(cur);
+
+			String identityNumber = identityNumberOf.get(cur);
+
+			for (Document doc : answers) {
+				Integer order = doc.getInteger("order");
+				Double studentScore = doc.getDouble("studentScore");
+				Double questionScore = doc.getDouble("questionScore");
+				String questionId = doc.getString("questionId");
+				String studentAnswer = doc.getString("studentAnswer");
+				String correctAnswer = doc.getString("correctAnswer");
+				String questionType = doc.getString("questionType");
+
+				if (null == order) {
+					throw new RuntimeException("order is null. examRecordDataId=" + cur);
+				}
+
+				if (null == questionType) {
+					throw new RuntimeException(
+							"questionType is null. examRecordDataId=" + cur + "; order=" + order);
+				}
+
+				if (null == studentScore) {
+					// 无作答时,0分
+					if (StringUtils.isBlank(studentAnswer)) {
+						studentScore = 0D;
+					}
+					// 计算分数
+					else {
+
+						if (null == questionScore) {
+							throw new RuntimeException("questionScore is null. examRecordDataId="
+									+ cur + ";courseCode=" + courseCode + ";order=" + order);
+						}
+
+						// 客观题
+						if (questionType.equals("SINGLE_CHOICE")
+								|| questionType.equals("MULTIPLE_CHOICE")
+								|| questionType.equals("TRUE_OR_FALSE")) {
+
+							// 无正确答案时
+							if (StringUtils.isBlank(correctAnswer)) {
+								noStudentScoreList.add(new Object[]{cur, courseCode, order});
+								studentScore = null;
+							}
+							// 有正确答案时
+							else {
+								if (correctAnswer.equals(studentAnswer)) {
+									studentScore = questionScore;
+								} else {
+									studentScore = 0D;
+								}
+							}
+
+						}
+						// 主观题
+						else if (questionType.equals("FILL_UP") || questionType.equals("ESSAY")) {
+							noStudentScoreList.add(new Object[]{cur, courseCode, order});
+							studentScore = null;
+						} else {
+							throw new RuntimeException("unknow questionType. questionType="
+									+ questionType + "; examRecordDataId=" + cur + ";courseCode="
+									+ courseCode + ";order=" + order);
+						}
+
+					}
+				}
+
+				if (null != studentAnswer && !studentAnswer.contains("<img")) {
+					studentAnswer = Jsoup.clean(studentAnswer, Whitelist.simpleText());
+				}
+
+				//TEMP TODO 用完即删除
+//				if (null != studentAnswer) {
+//					if (questionType.equals("SINGLE_CHOICE")
+//							|| questionType.equals("MULTIPLE_CHOICE")) {
+//						studentAnswer = studentAnswer.replaceAll("0", "A");
+//						studentAnswer = studentAnswer.replaceAll("1", "B");
+//						studentAnswer = studentAnswer.replaceAll("2", "C");
+//						studentAnswer = studentAnswer.replaceAll("3", "D");
+//						studentAnswer = studentAnswer.replaceAll("4", "E");
+//						studentAnswer = studentAnswer.replaceAll("5", "F");
+//						studentAnswer = studentAnswer.replaceAll("6", "G");
+//						studentAnswer = studentAnswer.replaceAll("7", "H");
+//						studentAnswer = studentAnswer.replaceAll("8", "I");
+//					}
+//				}
+
+				Object[] row = new Object[]{identityNumber, courseCode, String.valueOf(order),
+						questionId, studentAnswer, String.valueOf(studentScore)};
+
+				datas.add(row);
+			}
+
+		}
+
+		if (CollectionUtils.isNotEmpty(noStudentScoreList)) {
+			System.out.println("无分数 ============>");
+			Set<Long> examRecordDataIdSet = Sets.newHashSet();
+			for (Object[] objects : noStudentScoreList) {
+				examRecordDataIdSet.add((Long) objects[0]);
+				System.out.println(JsonUtil.toJson(objects));
+			}
+
+//			String sql4QueryExamStudentInfo = ResourceLoader
+//					.getResource(packagePath + "/query_exam_student_info.sql");
+			String sql4QueryExamStudentInfo=
+					"SELECT t1.student_name, t1.identity_number, " +
+					"( SELECT x.`code` FROM ec_b_course x WHERE x.id = t1.course_id ) AS course_code  " +
+					"FROM " +
+					" ec_oe_exam_record_data t1 " +
+					"WHERE " +
+					"t1.id IN ($$)";
+
+			String ids = StringUtils.join(examRecordDataIdSet, ",");
+			sql4QueryExamStudentInfo = sql4QueryExamStudentInfo.replace("$$", ids);
+
+			List<Map<String, Object>> studentInfoList = jdbcTemplate
+					.queryForList(sql4QueryExamStudentInfo);
+
+			for (Map<String, Object> curStudent : studentInfoList) {
+				System.out.println(JsonUtil.toJson(curStudent));
+			}
+
+			if (mustHaveScore) {
+				throw new RuntimeException("无分数");
+			}
+		}
+
+		String filePath = "D:/Temp/answers-detail-" + examId + "-" + courseCode + ".xlsx";
+
+		final String[] EXCEL_HEADER = new String[]{"身份证号码", "课程代码", "题号(跟蓝图里面的题号或者题目ID是对应的)",
+				"题目ID", "答案", "分数"};
+
+		ExcelWriter.write(EXCEL_HEADER, new Class[]{String.class, String.class, String.class,
+				String.class, String.class, String.class}, datas, new File(filePath));
+
+		System.out.println("OVER !   courseCode=" + courseCode);
+	}
+
+	/**
+	 * 获取作答
+	 *
+	 * @author WANGWEI
+	 * @param examRecordDataId
+	 * @return
+	 */
+	private List<Document> getAnswers(Long examRecordDataId) {
+		Query query = Query.query(Criteria.where("examRecordDataId").is(examRecordDataId));
+		final ObjectHolder<List<Document>> answersHolder = new ObjectHolder<List<Document>>(null);
+		mongoTemplate.executeQuery(query, "examRecordQuestions", new DocumentCallbackHandler() {
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public void processDocument(Document document)
+					throws MongoException, DataAccessException {
+				List<Document> answers = (List<Document>) document.get("examQuestionEntities");
+				answersHolder.set(answers);
+			}
+		});
+
+		List<Document> answers = answersHolder.get();
+		return answers;
+	}
+
+	public void setSqlIndex(int sqlIndex) {
+		this.sqlIndex = sqlIndex;
+	}
+
+	public void setMustHaveScore(boolean mustHaveScore) {
+		this.mustHaveScore = mustHaveScore;
+	}
+
+}

+ 15 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_record_data_1.sql

@@ -0,0 +1,15 @@
+SELECT
+	t1.identity_number,
+	t2.id AS exam_record_data_id,
+	t3.total_score as score,
+	t4.`code` 
+FROM
+	ec_oe_exam_record t1
+	LEFT JOIN ec_oe_exam_record_data t2 ON t1.id = t2.exam_record_id
+	LEFT JOIN ec_oe_exam_score t3 ON t3.exam_record_data_id = t2.id
+	LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id 
+WHERE
+	(( t2.is_warn = 0 ) OR ( t2.is_warn = 1 AND t2.is_audit = 1 ) ) 
+	AND t2.exam_record_status != 'EXAM_INVALID' 
+	AND t1.exam_id = ? 
+	AND t4.`code` = ?

+ 16 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_record_data_2.sql

@@ -0,0 +1,16 @@
+SELECT
+	t1.identity_number,
+	t2.id AS exam_record_data_id,
+	t3.total_score as score,
+	t4.`code` 
+FROM
+	ec_oe_exam_record t1
+	LEFT JOIN ec_oe_exam_record_data t2 ON t1.id = t2.exam_record_id
+	LEFT JOIN ec_oe_exam_score t3 ON t3.exam_record_data_id = t2.id
+	LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id 
+	LEFT JOIN ec_m_student_paper t5 ON t5.exam_record_data_id = t2.id
+WHERE
+	t5.id is not null
+	AND t5.student_paper_status != 0 	
+	AND t1.exam_id = ? 
+	AND t4.`code` = ?

+ 8 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/query_exam_student_info.sql

@@ -0,0 +1,8 @@
+SELECT t1.student_name, t1.identity_number,
+( SELECT x.`code` FROM ec_b_course x WHERE x.id = t1.course_id ) AS course_code 
+FROM
+	ec_oe_exam_record t1,
+	ec_oe_exam_record_data t2 
+WHERE
+	t2.exam_record_id = t1.id 
+	AND t2.id IN ($$)

+ 134 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_one_question_answer/GetStduentOneAnswerService.java

@@ -0,0 +1,134 @@
+package cn.com.qmth.dp.examcloud.oe.modules.get_student_one_question_answer;
+
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import com.google.common.collect.Lists;
+import com.mongodb.MongoException;
+import org.bson.Document;
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.mongodb.core.DocumentCallbackHandler;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 导出 - 考生作答结果(带手机号)
+ */
+@Service
+public class GetStduentOneAnswerService {
+
+    @Autowired
+    JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    MongoTemplate mongoTemplate;
+
+    public void start(Long examId, int questionOrder, String... courseCodeLIst) throws Exception {
+        for (String courseCode : courseCodeLIst) {
+            start(examId, questionOrder, courseCode);
+        }
+    }
+
+    // @Async
+    public void start(Long examId, int questionOrder, String courseCode) throws Exception {
+        StringBuilder sql = new StringBuilder();
+        sql.append("SELECT r.id AS exam_record_data_id, r.exam_id, stu.`name` AS student_name,");
+        sql.append(" r.student_code, r.identity_number, c.`code` AS course_code");
+        sql.append(" FROM ec_oe_exam_record_data r");
+        sql.append(" LEFT JOIN ec_b_course c ON c.id = r.course_id");
+        sql.append(" LEFT JOIN ec_b_student stu ON stu.id = r.student_id");
+        sql.append(" WHERE r.exam_id = ? AND c.`code` = ?");
+
+        Object[] args = new Object[]{examId, courseCode};
+        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql.toString(), args);
+
+        List<Object[]> datas = Lists.newArrayList();
+        int total = result.size();
+        int count = 0;
+
+        for (Map<String, Object> map : result) {
+            Long examRecordDataId = (Long) map.get("EXAM_RECORD_DATA_ID");
+            String studentName = (String) map.get("STUDENT_NAME");
+            String studentCode = (String) map.get("STUDENT_CODE");
+            String curCourseCode = (String) map.get("COURSE_CODE");
+            if (!curCourseCode.equals(courseCode)) {
+                throw new RuntimeException("unexpected exception. courseCode=" + courseCode);
+            }
+
+            count++;
+            System.out.println("total: " + total + "; count: " + count);
+
+            List<Document> answers = getAnswers(examRecordDataId);
+
+            for (Document doc : answers) {
+                Integer order = doc.getInteger("order");
+                String studentAnswer = doc.getString("studentAnswer");
+
+                if (null == order) {
+                    throw new RuntimeException("order is null. examRecordDataId=" + examRecordDataId);
+                }
+
+                if (!order.equals(questionOrder)) {
+                    continue;
+                }
+
+                if (null != studentAnswer && !studentAnswer.contains("<img")) {
+                    studentAnswer = Jsoup.clean(studentAnswer, Whitelist.simpleText());
+                }
+
+                Object[] row = new Object[]{String.valueOf(examId), courseCode, studentName,
+                        studentCode, studentAnswer};
+
+                datas.add(row);
+                break;
+            }
+
+        }
+
+        String filePath = "/home/admin/project/oe/one-answer-" + examId + "-" + courseCode + ".xlsx";
+
+        final String[] EXCEL_HEADER = new String[]{"考试批次ID", "课程代码", "学生姓名", "学号",
+                "第" + questionOrder + "题作答"};
+
+        ExcelWriter.write(EXCEL_HEADER,
+                new Class[]{String.class, String.class, String.class, String.class, String.class},
+                datas, new File(filePath));
+
+        System.out.println("OVER !   courseCode=" + courseCode);
+    }
+
+    /**
+     * 获取作答
+     *
+     * @param examRecordDataId
+     * @return
+     * @author WANGWEI
+     */
+    private List<Document> getAnswers(Long examRecordDataId) {
+        Query query = Query.query(Criteria.where("examRecordDataId").is(examRecordDataId));
+        final ObjectHolder<List<Document>> answersHolder = new ObjectHolder<List<Document>>(null);
+        mongoTemplate.executeQuery(query, "examRecordQuestions", new DocumentCallbackHandler() {
+
+            @SuppressWarnings("unchecked")
+            @Override
+            public void processDocument(Document document)
+                    throws MongoException, DataAccessException {
+                List<Document> answers = (List<Document>) document.get("examQuestionEntities");
+                answersHolder.set(answers);
+            }
+        });
+
+        List<Document> answers = answersHolder.get();
+        return answers;
+    }
+
+}

+ 188 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/ImportData.java

@@ -0,0 +1,188 @@
+package cn.com.qmth.dp.examcloud.oe.modules.import_data;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import cn.com.qmth.examcloud.commons.helpers.Counter;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.DBUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.commons.util.ResourceLoader;
+import cn.com.qmth.examcloud.commons.util.Util;
+
+@Component
+public class ImportData {
+
+	private static ExamCloudLog log = ExamCloudLogFactory.getLog(ImportData.class);
+
+	public void start(String dirPath) {
+
+		new Thread(new Runnable() {
+			@Override
+			public void run() {
+				Counter counter = new Counter(0, Long.MAX_VALUE);
+				while (true) {
+					File dir = new File(dirPath);
+
+					File[] fileArr = dir.listFiles();
+
+					for (File file : fileArr) {
+						processFile(counter, file);
+					}
+
+					Util.sleep(60);
+				}
+
+			}
+		}).start();
+	}
+
+	private void processFile(Counter counter, File file) {
+
+		String packageName = this.getClass().getPackage().getName();
+		String packagePath = packageName.replaceAll("\\.", "/");
+		String insertSql = ResourceLoader.getResource(packagePath + "/insert_data.sql");
+		String deleteSql = ResourceLoader.getResource(packagePath + "/delete_data.sql");
+
+		BufferedReader br = null;
+		InputStreamReader reader = null;
+
+		boolean hasError = false;
+		try {
+			reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
+			br = new BufferedReader(reader);
+
+			Connection ds2Conn = DBUtil.getConnection("ds2");
+
+			String line = null;
+			while (null != (line = br.readLine())) {
+				Object[] deleteParams = null;
+				Object[] columValues = null;
+				try {
+					columValues = toColumValues(line);
+					deleteParams = getDeleteParams(columValues);
+				} catch (Exception e) {
+					log.error("[DataImport]. ErrorLine: " + line, e);
+					continue;
+				}
+
+				try {
+					delete(ds2Conn, deleteSql, deleteParams);
+				} catch (Exception e) {
+					hasError = true;
+					log.error("[DataImport]. DeleteError: " + line, e);
+				}
+
+				try {
+					insert(ds2Conn, insertSql, columValues);
+					counter.next();
+					log.info("[DataImport]. count=" + counter.get());
+				} catch (Exception e) {
+					hasError = true;
+					log.error("[DataImport]. InsertError: " + line, e);
+				}
+
+			}
+
+		} catch (Exception e) {
+			hasError = true;
+			log.error("[DataImport]. unexpected", e);
+		} finally {
+			IOUtils.closeQuietly(br);
+			IOUtils.closeQuietly(reader);
+		}
+
+		if (!hasError) {
+			try {
+				FileUtils.forceDelete(file);
+			} catch (IOException e) {
+				log.error("[DataImport]. fail to delete file. path="
+						+ PathUtil.getCanonicalPath(file), e);
+			}
+		}
+
+	}
+
+	private void insert(Connection ds2Conn, String insertSql, Object[] columValues)
+			throws SQLException {
+
+		while (!ds2Conn.isValid(1)) {
+			ds2Conn = DBUtil.getConnection("ds2");
+		}
+
+		PreparedStatement ps2 = null;
+		try {
+			ps2 = ds2Conn.prepareStatement(insertSql);
+			for (int i = 0; i < columValues.length; i++) {
+				ps2.setObject(i + 1, columValues[i]);
+			}
+			ps2.execute();
+		} finally {
+			DBUtil.close(ps2);
+		}
+
+	}
+
+	private void delete(Connection ds2Conn, String deleteSql, Object[] params) throws SQLException {
+
+		while (!ds2Conn.isValid(1)) {
+			ds2Conn = DBUtil.getConnection("ds2");
+		}
+
+		PreparedStatement ps2 = null;
+		try {
+			ps2 = ds2Conn.prepareStatement(deleteSql);
+			for (int i = 0; i < params.length; i++) {
+				ps2.setObject(i + 1, params[i]);
+			}
+			ps2.execute();
+		} finally {
+			DBUtil.close(ps2);
+		}
+
+	}
+
+	private Object[] toColumValues(String str) {
+		JsonObject jsonObj = JsonParser.parseString(str).getAsJsonObject();
+
+		Object[] arr = new Object[9];
+
+		arr[0] = jsonObj.get("id").getAsLong();
+		arr[1] = jsonObj.get("root_org_id").getAsLong();
+		arr[2] = jsonObj.get("exam_student_id").getAsLong();
+		arr[3] = jsonObj.get("exam_record_data_id").getAsLong();
+		arr[4] = jsonObj.get("photo_path").getAsString();
+		arr[5] = jsonObj.get("file_url").getAsString();
+		arr[6] = jsonObj.get("face_compare_result").getAsString();
+		arr[7] = DateUtil.parse(jsonObj.get("creation_time").getAsString(),
+				DatePatterns.CHINA_DEFAULT);
+		if (!jsonObj.get("used_time").isJsonNull()) {
+			arr[8] = jsonObj.get("used_time").getAsLong();
+		}
+
+		return arr;
+	}
+
+	private Object[] getDeleteParams(Object[] columValues) {
+		Object[] arr = new Object[1];
+		arr[0] = columValues[0];
+		return arr;
+	}
+
+}

+ 2 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/delete_data.sql

@@ -0,0 +1,2 @@
+delete from `student_face_result_2` 
+where  id = ?

+ 12 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/import_data/insert_data.sql

@@ -0,0 +1,12 @@
+INSERT INTO `student_face_result_2` 
+( `id`,
+`root_org_id`, 
+`exam_student_id`,
+`exam_record_data_id`,
+`photo_path`,
+`file_url`,
+`face_compare_result`, 
+`creation_time`,
+`used_time` )
+VALUES
+	( ?, ?, ?, ?, ?, ?, ?, ?, ? )

+ 399 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/FixCorrectAnswerAndResetScoreService.java

@@ -0,0 +1,399 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity.*;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util.OKHttpUtil;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util.QmthUtil;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.HttpMethod;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
+import com.mongodb.client.result.UpdateResult;
+import com.mysql.cj.util.StringUtils;
+import okhttp3.Response;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 更新标准答案,并重新计算分数
+ * @Author lideyin
+ * @Date 2020/4/25 12:52
+ * @Version 1.0
+ */
+@Service
+public class FixCorrectAnswerAndResetScoreService {
+
+    @Autowired
+    JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    MongoTemplate mongoTemplate;
+
+    // @Async
+    public void start() {
+        String absolutePath= PropertiesUtil.getString("fixAnswer.data.path");
+//        ClassPathResource classPathResource = new ClassPathResource("temp1.txt");
+        FileInputStream fis=null;
+        InputStreamReader isr = null;
+        BufferedReader br = null;
+
+        try {
+            fis = new FileInputStream(new File(absolutePath));
+            isr = new InputStreamReader(fis);
+            br = new BufferedReader(isr);
+            String strExamIdAndCourseCode = "";
+            while ((strExamIdAndCourseCode = br.readLine()) != null) {
+                Long examId = Long.valueOf(strExamIdAndCourseCode.split(",")[0]);
+                String courseCode = strExamIdAndCourseCode.split(",")[1];
+                System.out.println(String.format("examId:%s,courseCode:%s", examId, courseCode));
+                run(examId,courseCode);
+                Thread.sleep(10);
+                //19,"03013750"
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (isr != null) {
+                try {
+                    isr.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+
+    }
+
+    public void run(long examId, String courseCode) {
+        List<ExamRecordDataEntity> examRecordDataList = queryExamRecordDataList(examId, courseCode);
+
+        if (examRecordDataList.isEmpty()) {
+            throw new StatusException("100001", "找不到对应的考试记录");
+        }
+
+        //首先更新mongo中的作答记录
+        modifyMongoQuestionAnswer(examRecordDataList);
+
+        //重新计算分数并保存
+        modifyDbScore(examRecordDataList);
+
+        System.out.println("999.all is over.....");
+    }
+
+    private void modifyDbScore(List<ExamRecordDataEntity> examRecordDataList) {
+        Set<Long> examStudentIdSet = new HashSet<>();
+        int effectiveNum = 0;//有效数量
+        //按考试记录更新分数表
+        for (ExamRecordDataEntity record : examRecordDataList) {
+            Query query = new Query();
+            query.addCriteria(Criteria.where("_id").is(record.getExamRecordQuestionsId()));
+            ExamRecordQuestionsEntity erq = mongoTemplate.findOne(query, ExamRecordQuestionsEntity.class, "examRecordQuestions");
+            if (erq == null) continue;
+
+            List<ExamQuestionEntity> objectiveQuesList = erq.getExamQuestionEntities().stream()
+                    .filter(p -> isObjectiveQues(p.getQuestionType())).collect(Collectors.toList());
+
+            double objectiveScore = calcTotalObjectiveScore(objectiveQuesList);
+            double objectiveAccuracy = calcTotalObjectiveAccuracy(objectiveQuesList);
+
+            updateExamScore(objectiveScore, objectiveAccuracy, record.getId());
+            System.out.println(String.format("2.更新第%d条分数成功---examRecordDataId=%s", ++effectiveNum, record.getId()));
+            examStudentIdSet.add(record.getExamStudentId());
+        }
+
+        int esNum = 0;
+        //按考生id,删除考生最终分数表(此表如果不存在数据,获取时,系统会重新计算)
+        for (Long estId : examStudentIdSet) {
+            deleteFinalScore(estId);
+            System.out.println(String.format("3.删除第%d条最终分数成功---examStudentId=%s", ++esNum, estId));
+        }
+
+    }
+
+    private void deleteFinalScore(Long estId) {
+        String strSql = String.format("delete from ec_oe_exam_student_final_score where exam_student_id=%d ", estId);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    /**
+     * 计算客观题总分
+     *
+     * @param objectiveQuesList
+     * @return
+     */
+    private double calcTotalObjectiveScore(List<ExamQuestionEntity> objectiveQuesList) {
+        double totalObjectiveScore = 0d;//客观题总分
+
+        for (ExamQuestionEntity eq : objectiveQuesList) {
+            if (!StringUtils.isNullOrEmpty(eq.getStudentAnswer())
+                    && !StringUtils.isNullOrEmpty(eq.getCorrectAnswer())
+                    && eq.getStudentAnswer().equals(eq.getCorrectAnswer())) {
+                totalObjectiveScore += eq.getQuestionScore();
+            }
+        }
+
+        return totalObjectiveScore;
+    }
+
+    /**
+     * 计算客观题正确率
+     *
+     * @param objectiveQuesList
+     * @return
+     */
+    private double calcTotalObjectiveAccuracy(List<ExamQuestionEntity> objectiveQuesList) {
+        double correctNum = 0d;
+        double totalNum = 0d;
+        for (ExamQuestionEntity eq : objectiveQuesList) {
+            if (!StringUtils.isNullOrEmpty(eq.getStudentAnswer())
+                    && !StringUtils.isNullOrEmpty(eq.getCorrectAnswer())
+                    && eq.getStudentAnswer().equals(eq.getCorrectAnswer())) {
+                correctNum += 1;
+            }
+            totalNum += 1;
+        }
+
+        if (totalNum == 0) {
+            return 0;
+        }
+
+        return Double.valueOf(new DecimalFormat("#.00").format(correctNum * 100D / totalNum));
+    }
+
+    /**
+     * 更新考试作答记录
+     *
+     * @param examRecordDataList
+     */
+    private void modifyMongoQuestionAnswer(List<ExamRecordDataEntity> examRecordDataList) {
+        int rubbishNum = 0;
+        int effectiveNum = 0;//有效数量
+        for (ExamRecordDataEntity record : examRecordDataList) {
+            try {
+                String examRecordQuestionsId = record.getExamRecordQuestionsId();
+                Query query = new Query();
+                query.addCriteria(Criteria.where("_id").is(examRecordQuestionsId));
+                ExamRecordQuestionsEntity erq = mongoTemplate.findOne(query, ExamRecordQuestionsEntity.class, "examRecordQuestions");
+
+                if (erq == null) {
+                    System.out.println(String.format("发现%d条垃圾数据:examRecordDataId=%s", ++rubbishNum, record.getId()));
+                    continue;//垃圾数据
+                }
+
+                List<ExamQuestionEntity> examQuestionList = erq.getExamQuestionEntities();
+
+                //所有客观题的试题id集合
+                List<String> questionIdList = examQuestionList.stream()
+                        .filter(p -> isObjectiveQues(p.getQuestionType()))
+                        .map(ExamQuestionEntity::getQuestionId)
+                        .collect(Collectors.toList());
+
+
+                for (String quesId : questionIdList) {
+                    //最小维度的小题单元集合
+                    List<ExamQuestionEntity> questionUnitList = examQuestionList.stream()
+                            .filter(p -> p.getQuestionId().equals(quesId))
+                            .sorted((o1, o2) -> o1.getOrder().intValue() - o2.getOrder().intValue())
+                            .collect(Collectors.toList());
+                    //根据题目id获取正确答案集合
+                    List<String> rightAnswerList = getRightAnswerList(quesId);
+
+                    if (rightAnswerList == null) {
+                        throw new StatusException("102003", String.format("找不到quesId=%s的答案数据", quesId));
+                    }
+
+                    //循环保存所有小题单元
+                    for (int i = 0; i < questionUnitList.size(); i++) {
+                        String rightAnswer = rightAnswerList.get(i);
+                        ExamQuestionEntity curQues = questionUnitList.get(i);
+                        String curCorrectAnswer =curQues.getCorrectAnswer();
+
+                        //只有无答案,或者有答案,答案不一致的才更新
+                        if (StringUtils.isNullOrEmpty(curCorrectAnswer)
+                                || (!StringUtils.isNullOrEmpty(curCorrectAnswer) && !curCorrectAnswer.equals(rightAnswer))) {
+                            updatePartialQuestionAnswer(examRecordQuestionsId, curQues.getOrder(),
+                                    rightAnswer, calcStudentUnitScore(rightAnswer, curQues));
+                        }
+
+                    }
+                }
+                System.out.println(String.format("1.更新第%d条作答记录成功---examRecordDataId=%s", ++effectiveNum, record.getId()));
+            } catch (Exception e) {
+                System.out.println(String.format("1.更新第%d条作答记录失败---examRecordDataId=%s", ++effectiveNum, record.getId()));
+                throw new StatusException("100002", e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * 计算学生的小题得分
+     *
+     * @param rightAnswer
+     * @param curQues
+     * @return
+     */
+    private Double calcStudentUnitScore(String rightAnswer, ExamQuestionEntity curQues) {
+        String studentAnswer = curQues.getStudentAnswer();
+        Double studentUnitScore = 0D;
+        if (!StringUtils.isNullOrEmpty(rightAnswer)
+                && !(StringUtils.isNullOrEmpty(studentAnswer))
+                && rightAnswer.equals(studentAnswer)) {
+            studentUnitScore = curQues.getQuestionScore();
+        }
+        return studentUnitScore;
+    }
+
+    /**
+     * 获取题目正式答案
+     *
+     * @param quesId
+     * @return
+     */
+    private List<String> getRightAnswerList(String quesId) {
+//        if (1 == 1) {
+//            List<String> list = new ArrayList<>();
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            return list;
+//        }
+
+        OuterGetQuestionAnswerReq reqBody = new OuterGetQuestionAnswerReq();
+        reqBody.setQuestionId(quesId);
+
+        String url = QmthUtil.buildUrl("/api/exchange/outer/question/getQuestionAnswer");
+        Response resp = null;
+        try {
+            resp = OKHttpUtil.call(HttpMethod.POST, url, QmthUtil.getSecurityHeaders(),
+                    JsonUtil.toJson(reqBody));
+            if (resp.code() == 200) {
+                String resultJson = resp.body().string();
+                OuterGetQuestionAnswerResp outerGetQuestionAnswerResp = JsonUtil.fromJson(resultJson, OuterGetQuestionAnswerResp.class);
+
+                return outerGetQuestionAnswerResp.getAnswerList();
+
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new StatusException("102002", e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+        return null;
+    }
+
+    /**
+     * 是否为客观题
+     *
+     * @param questionType
+     * @return
+     */
+    private boolean isObjectiveQues(String questionType) {
+        return "SINGLE_CHOICE".equals(questionType)
+                || "MULTIPLE_CHOICE".equals(questionType)
+                || "TRUE_OR_FALSE".equals(questionType);
+    }
+
+    private List<ExamRecordDataEntity> queryExamRecordDataList(Long examId, String courseCode) {
+        String strSql = String.format("SELECT " +
+                "t1.`id`, " +
+                "t1.`exam_student_id`, " +
+                "t1.exam_record_questions_id " +
+                "FROM " +
+                "ec_oe_exam_record_data t1 " +
+                "INNER JOIN ec_b_course t2 ON t1.course_id = t2.id " +
+                "WHERE " +
+                "t1.exam_id = %d " +
+                "AND t2.`code` ='%s' ", examId, courseCode);
+
+        java.util.List<Map<String, Object>> mapList = jdbcTemplate.queryForList(strSql);
+        java.util.List<ExamRecordDataEntity> resultList = new ArrayList<>();
+        for (Map<String, Object> map : mapList) {
+            ExamRecordDataEntity entity = new ExamRecordDataEntity();
+            if (map.get("id") != null) {
+                entity.setId(Long.valueOf(map.get("id").toString()));
+            }
+
+            if (map.get("exam_student_id") != null) {
+                entity.setExamStudentId(Long.valueOf(map.get("exam_student_id").toString()));
+            }
+
+            if (map.get("exam_record_questions_id") != null) {
+                entity.setExamRecordQuestionsId(map.get("exam_record_questions_id").toString());
+            }
+            resultList.add(entity);
+        }
+        return resultList;
+    }
+
+    private void updateExamScore(double objectiveScore, double objectiveAccuracy,
+                                 Long examRecordDataId) {
+        String strSql = String.format("UPDATE ec_oe_exam_score " +
+                "SET objective_score = %s, " +
+                "objective_accuracy = %s, " +
+                "total_score = objective_score+ IFNULL(subjective_score,0) " +
+                "WHERE " +
+                "exam_record_data_id = %d  ", objectiveScore, objectiveAccuracy, examRecordDataId);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    /**
+     * 更新学生作答的部分数据
+     *
+     * @param examRecordQuestionsId
+     * @param order
+     * @param newAnswer
+     * @param studentScore
+     * @return
+     */
+    public long updatePartialQuestionAnswer(String examRecordQuestionsId, Integer order, String newAnswer, Double studentScore) {
+        // 查询相应的题目
+        Query query = Query.query(Criteria.where("_id").is(examRecordQuestionsId)
+                .and("examQuestionEntities.order").is(order));
+        Update update = new Update();
+        update.set("examQuestionEntities.$.correctAnswer", newAnswer);
+        update.set("examQuestionEntities.$.studentScore", studentScore);
+
+        UpdateResult upResult = mongoTemplate.updateFirst(query, update, "examRecordQuestions");
+        return upResult.getMatchedCount();
+    }
+
+}

+ 344 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/ResetScoreService.java

@@ -0,0 +1,344 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer;
+
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ExamStudentVO;
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.vo.ScoreVO;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 重新计算分数
+ * @Author lideyin
+ * @Date 2020/4/25 12:52
+ * @Version 1.0
+ */
+@Service
+public class ResetScoreService {
+
+    @Autowired
+    JdbcTemplate jdbcTemplate;
+
+    private static final Long MARKING_TYPE_KEY = 24L;// 阅卷方式KEY
+
+    // @Async
+    public void start() {
+        String absolutePath = PropertiesUtil.getString("fixAnswer.data.path");
+//        ClassPathResource classPathResource = new ClassPathResource("temp1.txt");
+        FileInputStream fis = null;
+        InputStreamReader isr = null;
+        BufferedReader br = null;
+
+        try {
+            fis = new FileInputStream(new File(absolutePath));
+            isr = new InputStreamReader(fis);
+            br = new BufferedReader(isr);
+            String strExamIdAndCourseCode = "";
+            while ((strExamIdAndCourseCode = br.readLine()) != null) {
+                Long examId = Long.valueOf(strExamIdAndCourseCode.split(",")[0]);
+                Long courseId = Long.valueOf(strExamIdAndCourseCode.split(",")[1]);
+
+                run(examId, courseId);
+
+                Thread.sleep(10);
+                //19,"03013750"
+            }
+
+            System.out.println("999.all is over.....");
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (isr != null) {
+                try {
+                    isr.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+
+    }
+
+    public void run(long examId, Long courseId) {
+        //考试下所有考试记录
+        List<ExamRecordDataEntity> allExamRecordDataList = queryExamRecordDataList(examId, courseId);
+
+        //取正式有效的考试记录数据
+        List<ExamRecordDataEntity> effectiveExamRecordDataList =
+                allExamRecordDataList.stream()
+                        .filter(p -> !p.getIllegality() && (!p.getWarn() || (p.getWarn() && p.getAudit())))
+                        .collect(Collectors.toList());
+
+        //当前考试课程下的所有的课程id
+        List<ExamStudentVO> examStudentList = queryExamStudents(examId, courseId);
+
+        // 获取考试的阅卷方式
+        String markingType = this.queryExamMarkingType(examId, MARKING_TYPE_KEY);
+        double objectiveScore = 0;
+        double subjectiveScore = 0;
+        double objectiveAccuracy = 0;
+        double successPercent = 0;
+        double totalScore = 0;
+
+        int esTotalNum = examStudentList.size();
+        int esIndex = 0;
+        for (ExamStudentVO esVo : examStudentList) {
+            esIndex++;
+            
+            Long esId = esVo.getExamStudentId();
+            System.out.println(String.format("[examId_stuId_total]:%s_%s_total---start---共%s个考生,当前正在执行第%s条...",
+                    examId, esId,esTotalNum, esIndex));
+            //如果当前考生的考试成绩已存在则直接跳过
+            if (existsFinalScore(esId)) {
+                System.out.println(String.format("[examId_stuId_total]:%s_%s_total---exist---共%s个考生,当前正在执行第%s条...",
+                        examId, esId,esTotalNum, esIndex));
+                continue;
+            }
+
+            //当前考生的所有考试记录
+            List<ExamRecordDataEntity> curEstRecordList = effectiveExamRecordDataList.stream().filter(p -> p.getExamStudentId().equals(esId)).collect(Collectors.toList());
+
+            if (curEstRecordList == null || curEstRecordList.isEmpty()) {
+                System.out.println(String.format("[examId_stuId_total]:%s_%s_total---warn---共%s个考生,当前正在执行第%s条...",
+                        examId, esId,esTotalNum, esIndex));
+                continue;//缺考考生数据不处理
+            }
+
+            //最终有效的分数
+            ScoreVO effectiveScore;
+            if (curEstRecordList.size() == 1) {
+                // 只有一条考试记录情况
+                Long okExamRecordDataId = curEstRecordList.get(0).getId();
+
+                // 获取考试记录对应的成绩
+                effectiveScore = this.queryExamScore(okExamRecordDataId);
+
+            } else{
+                // 多条考试记录情况
+                List<ScoreVO> scores = new ArrayList<>();
+                for (ExamRecordDataEntity examRecordData : effectiveExamRecordDataList) {
+                    Long examRecordDataId = examRecordData.getId();
+                    // 分别获取考试记录对应的成绩
+                    ScoreVO score = this.queryExamScore(examRecordDataId);
+                    scores.add(score);
+                }
+
+                if (markingType.equals("ALL") || markingType.equals("OBJECT_SCORE_MAX")) {
+                    // 全部评阅规则 或 客观分最高规则:取总分最高
+                    effectiveScore = scores.stream()
+                            .sorted((obj1, obj2) -> obj2.getTotalScore().compareTo(obj1.getTotalScore()))
+                            .collect(Collectors.toList())
+                            .get(0);
+                } else {
+                    // 否则:取最后一次的成绩
+                    effectiveScore = scores.stream()
+                            .sorted((obj1, obj2) -> obj2.getId().compareTo(obj1.getId()))
+                            .collect(Collectors.toList())
+                            .get(0);
+                }
+            }
+
+            objectiveScore = effectiveScore.getObjectiveScore();
+            subjectiveScore = effectiveScore.getSubjectiveScore();
+            objectiveAccuracy = effectiveScore.getObjectiveAccuracy();
+            successPercent = effectiveScore.getSuccPercent();
+            totalScore = effectiveScore.getTotalScore();
+
+            //再次判断数据是否已存在
+            if (!existsFinalScore(esId)) {
+                insertFinalScore(effectiveScore.getExamRecordDataId(), esId, objectiveScore, objectiveAccuracy, subjectiveScore, successPercent, totalScore);
+
+                System.out.println(String.format("[examId_stuId_total]:%s_%s_total---success---共%s个考生,当前正在执行第%s条...",
+                        examId, esId,esTotalNum, esIndex));
+            }
+        }
+
+    }
+
+    /**
+     * 插入最终分数
+     * @param examRecordDataId
+     * @param esId
+     * @param objectiveScore
+     * @param objectiveAccuracy
+     * @param subjectiveScore
+     * @param successPercent
+     * @param totalScore
+     */
+    private void insertFinalScore(Long examRecordDataId, Long esId, double objectiveScore, double objectiveAccuracy,
+                                  double subjectiveScore, double successPercent, double totalScore) {
+        String strSql = String.format("INSERT INTO `ec_oe_exam_student_final_score` " +
+                "( `creation_time`, `update_time`, `exam_record_data_id`, `exam_student_id`, " +
+                "`objective_accuracy`, `objective_score`, `subjective_score`, `succ_percent`, `total_score` ) " +
+                "VALUES " +
+                " ( NOW(), NOW(), %d, %d, " +
+                "%s, %s, %s, %s, %s ) ", examRecordDataId, esId, objectiveAccuracy, objectiveScore, subjectiveScore, successPercent, totalScore);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    /**
+     * 是否存在最终分数
+      * @param estId
+     * @return
+     */
+    private boolean existsFinalScore(Long estId) {
+        String strSql = String.format("select 1 from ec_oe_exam_student_final_score where exam_student_id=%d ", estId);
+
+        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(strSql);
+
+        return !(mapList == null || mapList.isEmpty());
+    }
+
+    /**
+     * 查询考试记录
+     * @param examId
+     * @param courseId
+     * @return
+     */
+    private List<ExamRecordDataEntity> queryExamRecordDataList(Long examId, Long courseId) {
+        String strSql = String.format("SELECT " +
+                "t1.`id`, " +
+                "t1.`exam_student_id`, " +
+                "t1.`course_id`, " +
+                "t1.`is_illegality`, " +
+                "t1.`is_warn`, " +
+                "t1.`is_audit`, " +
+                "t1.exam_record_questions_id " +
+                "FROM " +
+                "ec_oe_exam_record_data t1 " +
+                "WHERE " +
+                "t1.exam_id = %d " +
+                "AND t1.`course_id` =%d ", examId, courseId);
+
+        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(strSql);
+        List<ExamRecordDataEntity> resultList = new ArrayList<>();
+        for (Map<String, Object> map : mapList) {
+            ExamRecordDataEntity entity = new ExamRecordDataEntity();
+            if (map.get("id") != null) {
+                entity.setId(Long.valueOf(map.get("id").toString()));
+            }
+
+            if (map.get("exam_student_id") != null) {
+                entity.setExamStudentId(Long.valueOf(map.get("exam_student_id").toString()));
+            }
+
+            if (map.get("exam_record_questions_id") != null) {
+                entity.setExamRecordQuestionsId(map.get("exam_record_questions_id").toString());
+            }
+
+            if (map.get("course_id") != null) {
+                entity.setCourseId(Long.valueOf(map.get("course_id").toString()));
+            }
+
+            if (map.get("is_illegality") != null) {
+                entity.setIllegality(Boolean.valueOf(map.get("is_illegality").toString()));
+            } else {
+                entity.setIllegality(false);
+            }
+
+            if (map.get("is_warn") != null) {
+                entity.setWarn(Boolean.valueOf(map.get("is_warn").toString()));
+            } else {
+                entity.setWarn(false);
+            }
+
+            if (map.get("is_audit") != null) {
+                entity.setAudit(Boolean.valueOf(map.get("is_audit").toString()));
+            } else {
+                entity.setAudit(false);
+            }
+
+            resultList.add(entity);
+        }
+        return resultList;
+    }
+
+
+    /**
+     * 查询考试下所有考生
+     * @param examId
+     * @param courseId
+     * @return
+     */
+    private List<ExamStudentVO> queryExamStudents(Long examId, Long courseId) {
+        final String columns = "exam_student_id,student_code,student_name,identity_number,org_id,course_id,course_code,course_level,grade,specialty_name,finished";
+        final String querySql = String.format("select %s from ec_oe_exam_student where exam_id = %s and course_id = %s and enable = 1", columns, examId, courseId);
+        return jdbcTemplate.query(querySql, new BeanPropertyRowMapper(ExamStudentVO.class));
+    }
+
+    /**
+     * 查询考试的阅卷类型
+     * @param examId
+     * @param markingTypeKey
+     * @return
+     */
+    private String queryExamMarkingType(Long examId, Long markingTypeKey) {
+        final String querySql = String.format("select value from ec_e_exam_prop where exam_id = %s and key_id = %s", examId, markingTypeKey);
+        try {
+            String result = jdbcTemplate.queryForObject(querySql, String.class);
+            return org.apache.commons.lang3.StringUtils.isNoneBlank(result) ? result.trim() : "ALL";
+        } catch (EmptyResultDataAccessException e) {
+            return "ALL";// 默认值
+        }
+    }
+
+    /**
+     * 查询考试分数
+     * @param examRecordDataId
+     * @return
+     */
+    private ScoreVO queryExamScore(Long examRecordDataId) {
+        final String querySql = String.format("select id,objective_score,subjective_score,total_score,objective_accuracy,succ_percent,total_score from ec_oe_exam_score where exam_record_data_id = %s", examRecordDataId);
+        List<ScoreVO> result = jdbcTemplate.query(querySql, new BeanPropertyRowMapper(ScoreVO.class));
+
+        ScoreVO score;
+        if (CollectionUtils.isNotEmpty(result)) {
+            score = result.get(0);
+        } else {
+            score = new ScoreVO();
+        }
+
+        // 默认值
+        score.setExamRecordDataId(examRecordDataId);
+        score.setObjectiveScore(score.getObjectiveScore() != null ? score.getObjectiveScore() : 0d);
+        score.setSubjectiveScore(score.getSubjectiveScore() != null ? score.getSubjectiveScore() : 0d);
+        score.setTotalScore(score.getTotalScore() != null ? score.getTotalScore() : 0d);
+        score.setObjectiveAccuracy(score.getObjectiveAccuracy() != null ? score.getObjectiveAccuracy() : 0d);
+        score.setSuccPercent(score.getSuccPercent() != null ? score.getSuccPercent() : 0d);
+
+        return score;
+    }
+
+}

+ 196 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamQuestionEntity.java

@@ -0,0 +1,196 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import java.io.Serializable;
+
+/**
+ * 考生作答明细
+ */
+public class ExamQuestionEntity implements Serializable{
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -6141069483774400912L;
+	
+	private String id;
+	/**
+	 * 考试记录Data Id
+	 */
+    private Long examRecordDataId;
+    /**
+     * 大题号
+     */
+    private Integer mainNumber;
+    /**
+     * 原题ID
+     */
+    private String questionId;
+    /**
+     * 顺序
+     */
+    private Integer order;
+    /**
+     * 小题分数
+     */
+    private Double questionScore;
+    /**
+     * 小题类型
+     */
+    private String questionType;
+    /**
+     * 标准答案
+     */
+    private String correctAnswer;
+    /**
+     * 考生作答
+     */
+    private String studentAnswer;
+    /**
+     * 学生小题得分
+     */
+    private Double studentScore;
+    /**
+     * 是否作答
+     */
+    private Boolean isAnswer;
+    /**
+     * 是否标记
+     */
+    private Boolean isSign;
+    
+    /**
+	 * 选项排序值
+	 */
+	private Integer[] optionPermutation;
+	
+	/**
+	 * 音频播放次数
+	 */
+	private String audioPlayTimes;
+	/**
+	 * 题目作答类型
+	 */
+	private String answerType;
+    
+	public ExamQuestionEntity() {}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Integer getMainNumber() {
+		return mainNumber;
+	}
+
+	public void setMainNumber(Integer mainNumber) {
+		this.mainNumber = mainNumber;
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+
+	public Integer getOrder() {
+		return order;
+	}
+
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+
+	public Double getQuestionScore() {
+		return questionScore;
+	}
+
+	public void setQuestionScore(Double questionScore) {
+		this.questionScore = questionScore;
+	}
+
+	public String getQuestionType() {
+		return questionType;
+	}
+
+	public void setQuestionType(String questionType) {
+		this.questionType = questionType;
+	}
+
+	public String getCorrectAnswer() {
+		return correctAnswer;
+	}
+
+	public void setCorrectAnswer(String correctAnswer) {
+		this.correctAnswer = correctAnswer;
+	}
+
+	public String getStudentAnswer() {
+		return studentAnswer;
+	}
+
+	public void setStudentAnswer(String studentAnswer) {
+		this.studentAnswer = studentAnswer;
+	}
+
+	public Double getStudentScore() {
+		return studentScore;
+	}
+
+	public void setStudentScore(Double studentScore) {
+		this.studentScore = studentScore;
+	}
+
+	public Boolean getAnswer() {
+		return isAnswer;
+	}
+
+	public void setAnswer(Boolean answer) {
+		isAnswer = answer;
+	}
+
+	public Boolean getSign() {
+		return isSign;
+	}
+
+	public void setSign(Boolean sign) {
+		isSign = sign;
+	}
+
+	public Integer[] getOptionPermutation() {
+		return optionPermutation;
+	}
+
+	public void setOptionPermutation(Integer[] optionPermutation) {
+		this.optionPermutation = optionPermutation;
+	}
+
+	public String getAudioPlayTimes() {
+		return audioPlayTimes;
+	}
+
+	public void setAudioPlayTimes(String audioPlayTimes) {
+		this.audioPlayTimes = audioPlayTimes;
+	}
+
+	public String getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(String answerType) {
+		this.answerType = answerType;
+	}
+}

+ 141 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordDataEntity.java

@@ -0,0 +1,141 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import javax.persistence.Column;
+import java.io.Serializable;
+
+/**
+ * @Description 考试记录实体
+ * @Author lideyin
+ * @Date 2020/4/25 13:23
+ * @Version 1.0
+ */
+public class ExamRecordDataEntity implements Serializable {
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 考试作答记录id
+     */
+    private String examRecordQuestionsId;
+
+    /**
+     * 考生id
+     */
+    private Long examStudentId;
+
+    /**
+     * 课程id
+     */
+    private Long courseId;
+
+    /**
+     * 是否异常数据
+     */
+    private Boolean isWarn;
+    /**
+     * 是否被审核过
+     */
+    private Boolean isAudit;
+    /**
+     * 是否违纪
+     */
+    private Boolean isIllegality;
+
+    /**
+     * 试卷结构id
+     */
+    private String paperStructId;
+
+    /**
+     * 试卷类型
+     */
+    private String paperType;
+
+    /**
+     * 试卷id
+     */
+    private String basePaperId;
+
+    public Boolean getWarn() {
+        return isWarn;
+    }
+
+    public void setWarn(Boolean warn) {
+        isWarn = warn;
+    }
+
+    public Boolean getAudit() {
+        return isAudit;
+    }
+
+    public void setAudit(Boolean audit) {
+        isAudit = audit;
+    }
+
+    public Boolean getIllegality() {
+        return isIllegality;
+    }
+
+    public void setIllegality(Boolean illegality) {
+        isIllegality = illegality;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getExamRecordQuestionsId() {
+        return examRecordQuestionsId;
+    }
+
+    public void setExamRecordQuestionsId(String examRecordQuestionsId) {
+        this.examRecordQuestionsId = examRecordQuestionsId;
+    }
+
+    public Long getExamStudentId() {
+        return examStudentId;
+    }
+
+    public void setExamStudentId(Long examStudentId) {
+        this.examStudentId = examStudentId;
+    }
+
+    public String getPaperStructId() {
+        return paperStructId;
+    }
+
+    public void setPaperStructId(String paperStructId) {
+        this.paperStructId = paperStructId;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public String getBasePaperId() {
+        return basePaperId;
+    }
+
+    public void setBasePaperId(String basePaperId) {
+        this.basePaperId = basePaperId;
+    }
+}

+ 68 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordQuestionsEntity.java

@@ -0,0 +1,68 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Description 考生作答集合
+ * @Author lideyin
+ * @Date 2020/4/25 14:14
+ * @Version 1.0
+ */
+public class ExamRecordQuestionsEntity implements Serializable {
+    private static final long serialVersionUID = -1688201571728312142L;
+
+    private String id;
+
+    private Long examRecordDataId;
+
+    private Date creationTime;
+
+    private List<ExamQuestionEntity> examQuestionEntities;
+    
+    /**
+	 * 题目作答类型
+	 */
+	private String answerType;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public List<ExamQuestionEntity> getExamQuestionEntities() {
+        return examQuestionEntities;
+    }
+
+    public void setExamQuestionEntities(List<ExamQuestionEntity> examQuestionEntities) {
+        this.examQuestionEntities = examQuestionEntities;
+    }
+
+    public String getAnswerType() {
+        return answerType;
+    }
+
+    public void setAnswerType(String answerType) {
+        this.answerType = answerType;
+    }
+}

+ 105 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamScoreEntity.java

@@ -0,0 +1,105 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+import org.hibernate.annotations.DynamicInsert;
+
+import javax.persistence.*;
+
+/**
+ * @Description 考试分数实体
+ * @Author lideyin
+ * @Date 2020/4/25 15:37
+ * @Version 1.0
+ */
+public class ExamScoreEntity implements JsonSerializable {
+
+    private Long id;
+
+    /**
+     * 考试记录
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 总分
+     */
+    private Double totalScore;
+
+    /**
+     * 客观题得分总分
+     */
+    private Double objectiveScore;
+
+    /**
+     * 客观题答对的比率
+     * (客观题答对的题数/客观题总题数)*100  取2位小数
+     */
+    private Double objectiveAccuracy;
+
+    /**
+     * 主观题得分总分
+     */
+    private Double subjectiveScore;
+
+    /**
+     * 答题正确率
+     */
+    private Double succPercent;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Double getObjectiveScore() {
+        return objectiveScore;
+    }
+
+    public void setObjectiveScore(Double objectiveScore) {
+        this.objectiveScore = objectiveScore;
+    }
+
+    public Double getObjectiveAccuracy() {
+        return objectiveAccuracy;
+    }
+
+    public void setObjectiveAccuracy(Double objectiveAccuracy) {
+        this.objectiveAccuracy = objectiveAccuracy;
+    }
+
+    public Double getSubjectiveScore() {
+        return subjectiveScore;
+    }
+
+    public void setSubjectiveScore(Double subjectiveScore) {
+        this.subjectiveScore = subjectiveScore;
+    }
+
+    public Double getSuccPercent() {
+        return succPercent;
+    }
+
+    public void setSuccPercent(Double succPercent) {
+        this.succPercent = succPercent;
+    }
+}

+ 15 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExchangeBean.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange.JsonSerializable;
+
+/**
+ * bean 基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class ExchangeBean implements JsonSerializable {
+
+	private static final long serialVersionUID = 3913250969569367810L;
+
+}

+ 24 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerReq.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange.EnterpriseRequest;
+
+/**
+ * @Description 获取试题答案请求类
+ * @Author lideyin
+ * @Date 2020/3/30 15:25
+ * @Version 1.0
+ */
+public class OuterGetQuestionAnswerReq extends EnterpriseRequest {
+
+    private static final long serialVersionUID = 8303860788475413215L;
+
+    private String questionId;
+
+    public String getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(String questionId) {
+        this.questionId = questionId;
+    }
+}

+ 27 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerResp.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange.EnterpriseResponse;
+
+import java.util.List;
+
+/**
+ * @Description 获取题目作答
+ * @Author lideyin
+ * @Date 2020/4/25 18:56
+ * @Version 1.0
+ */
+public class OuterGetQuestionAnswerResp extends EnterpriseResponse {
+
+	private static final long serialVersionUID = 8290190579593586203L;
+
+	//答案集合
+	private List<String> answerList;
+
+	public List<String> getAnswerList() {
+		return answerList;
+	}
+
+	public void setAnswerList(List<String> answerList) {
+		this.answerList = answerList;
+	}
+}

+ 31 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseRequest.java

@@ -0,0 +1,31 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity.ExchangeBean;
+
+/**
+ * 请求体基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class EnterpriseRequest extends ExchangeBean {
+
+	private static final long serialVersionUID = 6465330136225230063L;
+
+	/**
+	 * 数据执行状态
+	 */
+	@Deprecated
+	private String des;
+
+	@Deprecated
+	public String getDes() {
+		return des;
+	}
+
+	@Deprecated
+	public void setDes(String des) {
+		this.des = des;
+	}
+
+}

+ 44 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseResponse.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity.ExchangeBean;
+
+/**
+ * 响应体基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class EnterpriseResponse extends ExchangeBean {
+
+	private static final long serialVersionUID = 1755304211766414171L;
+
+	/**
+	 * 耗时(毫秒)
+	 */
+	private Long cost;
+
+	/**
+	 * 数据执行状态
+	 */
+	@Deprecated
+	private String des;
+
+	public Long getCost() {
+		return cost;
+	}
+
+	public void setCost(Long cost) {
+		this.cost = cost;
+	}
+
+	@Deprecated
+	public String getDes() {
+		return des;
+	}
+
+	@Deprecated
+	public void setDes(String des) {
+		this.des = des;
+	}
+
+}

+ 16 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/JsonSerializable.java

@@ -0,0 +1,16 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.exchange;
+
+import java.io.Serializable;
+
+/**
+ * 可序列化为JSON<br>
+ * <p>
+ * 严重警告: 此接口为标识接口,禁止添加属性和方法. by wangwei
+ * </p>
+ * 
+ * @author WANGWEI
+ *
+ */
+public interface JsonSerializable extends Serializable {
+
+}

+ 58 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/FormFilePart.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util;
+
+import java.io.File;
+
+/**
+ * 表单文件参数
+ *
+ * @author WANGWEI
+ * @date 2019年5月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class FormFilePart {
+
+	private String paramName;
+
+	private String filename;
+
+	private File file;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param paramName
+	 * @param filename
+	 * @param file
+	 */
+	public FormFilePart(String paramName, String filename, File file) {
+		super();
+		this.paramName = paramName;
+		this.filename = filename;
+		this.file = file;
+	}
+
+	public String getParamName() {
+		return paramName;
+	}
+
+	public void setParamName(String paramName) {
+		this.paramName = paramName;
+	}
+
+	public String getFilename() {
+		return filename;
+	}
+
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+
+	public File getFile() {
+		return file;
+	}
+
+	public void setFile(File file) {
+		this.file = file;
+	}
+
+}

+ 299 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/OKHttpUtil.java

@@ -0,0 +1,299 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util;
+
+import cn.com.qmth.examcloud.commons.util.HttpMethod;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import okhttp3.*;
+import okhttp3.Request.Builder;
+import org.apache.commons.collections.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OKHttp
+ *
+ * @author WANGWEI
+ * @date 2018年9月6日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+public class OKHttpUtil {
+
+//	private static final Logger LOG = LoggerFactory.getLogger(OKHttpUtil.class);
+
+	public static final class MediaTypes {
+
+		public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+	}
+
+	/**
+	 * 请求体构建器
+	 *
+	 * @author WANGWEI
+	 * @date 2019年4月10日
+	 * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+	 */
+	public static interface RequestBodyBuilder {
+		RequestBody build();
+	}
+
+	/**
+	 * json请求体构建器
+	 *
+	 * @author WANGWEI
+	 * @date 2019年4月10日
+	 * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+	 */
+	public static final class JsonBodyBuilder implements RequestBodyBuilder {
+
+		private String json;
+
+		public JsonBodyBuilder(String json) {
+			super();
+			this.json = json;
+		}
+
+		@Override
+		public RequestBody build() {
+			return RequestBody.create(MediaTypes.JSON, json);
+		}
+
+		@Override
+		public String toString() {
+			return json;
+		}
+	}
+
+	private static OkHttpClient okHttpClient;
+
+	static {
+		okHttpClient = new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)
+				.readTimeout(60, TimeUnit.SECONDS).build();
+	}
+
+	public static OkHttpClient getOkHttpClient() {
+		return okHttpClient;
+	}
+
+	/**
+	 * 发送请求 (带json请求体)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param jsonBody
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+								String jsonBody) {
+		return call(httpMethod, url, headers, new JsonBodyBuilder(jsonBody));
+	}
+
+	/**
+	 * 发送请求 (带请求体)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param requestBodyBuilder
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			RequestBodyBuilder requestBodyBuilder) {
+
+		/*LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+		LOG.info("[okhttp3] body: " + requestBodyBuilder);*/
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.POST)) {
+			builder = new Builder().url(url).post(requestBodyBuilder.build());
+		} else if (httpMethod.equals(HttpMethod.PUT)) {
+			builder = new Builder().url(url).put(requestBodyBuilder.build());
+		} else if (httpMethod.equals(HttpMethod.DELETE)) {
+			builder = new Builder().url(url).delete(requestBodyBuilder.build());
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url) {
+		return call(httpMethod, url, null);
+	}
+
+	/**
+	 * 发送请求
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers) {
+
+//		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+//		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.GET)) {
+			builder = new Builder().url(url);
+		} else if (httpMethod.equals(HttpMethod.POST)) {
+			builder = new Builder().url(url).post(new FormBody.Builder().build());
+		} else if (httpMethod.equals(HttpMethod.PUT)) {
+			builder = new Builder().url(url).put(new FormBody.Builder().build());
+		} else if (httpMethod.equals(HttpMethod.DELETE)) {
+			builder = new Builder().url(url).delete(new FormBody.Builder().build());
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求 (表单)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			Map<String, String> params) {
+
+//		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+//		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+//		LOG.info("[okhttp3] params: " + JsonUtil.toJson(params));
+
+		FormBody.Builder formBody = new FormBody.Builder();
+
+		if (null != params && 0 != params.size()) {
+			for (Entry<String, String> entry : params.entrySet()) {
+				formBody.add(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.POST)) {
+			builder = new Builder().url(url).post(formBody.build());
+		} else if (httpMethod.equals(HttpMethod.PUT)) {
+			builder = new Builder().url(url).put(formBody.build());
+		} else if (httpMethod.equals(HttpMethod.DELETE)) {
+			builder = new Builder().url(url).delete(formBody.build());
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求 (包含文件表单)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @param formFilePartList
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			Map<String, String> params, List<FormFilePart> formFilePartList) {
+
+//		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+//		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+//		LOG.info("[okhttp3] params: " + JsonUtil.toJson(params));
+
+		MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder()
+				.setType(MultipartBody.ALTERNATIVE);
+
+		if (null != params) {
+			for (Entry<String, String> entry : params.entrySet()) {
+				multipartBodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
+			}
+		}
+
+		if (CollectionUtils.isNotEmpty(formFilePartList)) {
+			MediaType type = MediaType.parse("application/octet-stream");
+			for (FormFilePart part : formFilePartList) {
+				RequestBody fileBody = RequestBody.create(type, part.getFile());
+				multipartBodyBuilder.addFormDataPart(part.getParamName(), part.getFilename(),
+						fileBody);
+			}
+		}
+
+		Builder builder = new Builder().url(url).post(multipartBodyBuilder.build());
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}

+ 156 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/QmthUtil.java

@@ -0,0 +1,156 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util;
+
+import cn.com.qmth.examcloud.commons.util.ByteUtil;
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
+import cn.com.qmth.examcloud.commons.util.SHA256;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 启明泰和-接口安全工具
+ *
+ * @author WANGWEI
+ * @date 2018年11月23日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+public class QmthUtil {
+
+	/**
+	 * 获取安全请求头信息
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static Map<String, String> getSecurityHeaders() {
+
+		long timestamp = System.currentTimeMillis();
+		String rootOrgId = String.valueOf(getRootOrgId());
+		String appId = PropertiesUtil.getString("qmth.appId");
+		String secretKey = PropertiesUtil.getString("qmth.secretKey");
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(rootOrgId).append(appId).append(timestamp).append(secretKey);
+
+		byte[] bytes = SHA256.encode(sb.toString());
+		String accessToken = ByteUtil.toHexAscii(bytes);
+
+		Map<String, String> headers = new HashMap<String, String>();
+
+		headers.put("rootOrgId", String.valueOf(rootOrgId));
+		headers.put("timestamp", String.valueOf(timestamp));
+		headers.put("appId", appId);
+		headers.put("access_token", accessToken);
+
+		return headers;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static Long getRootOrgId() {
+		Long rootOrgId = PropertiesUtil.getLong("qmth.rootOrgId", -1L);
+		return rootOrgId;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 */
+	public static String buildCommonUserAccessUrl(String loginName) {
+		String rootOrgId = String.valueOf(getRootOrgId());
+		long timestamp = System.currentTimeMillis();
+		String appId =  PropertiesUtil.getString("qmth.appId");
+		String secretKey =  PropertiesUtil.getString("qmth.secretKey");
+		String accessUrl =  PropertiesUtil.getString("qmth.commonUserAccessUrl");
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(loginName).append(rootOrgId).append(appId).append(timestamp).append(secretKey);
+
+		byte[] bytes = SHA256.encode(sb.toString());
+		String accessToken = ByteUtil.toHexAscii(bytes);
+
+		StringBuilder params = new StringBuilder();
+		params.append("loginName").append("=").append(urlEncode(loginName));
+		params.append("&").append("orgId").append("=").append(rootOrgId);
+		params.append("&").append("appId").append("=").append(appId);
+		params.append("&").append("timestamp").append("=").append(timestamp);
+		params.append("&").append("token").append("=").append(accessToken);
+
+		return accessUrl + "?" + params.toString();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 */
+	public static String buildStudentAccessUrl(String accountType, String accountValue) {
+		String rootOrgId = String.valueOf(getRootOrgId());
+		long timestamp = System.currentTimeMillis();
+		String appId =  PropertiesUtil.getString("qmth.appId");
+		String secretKey =  PropertiesUtil.getString("qmth.secretKey");
+		String accessUrl =  PropertiesUtil.getString("qmth.studentAccessUrl");
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(accountType).append(accountValue).append(rootOrgId).append(appId)
+				.append(timestamp).append(secretKey);
+
+		byte[] bytes = SHA256.encode(sb.toString());
+		String accessToken = ByteUtil.toHexAscii(bytes);
+
+		StringBuilder params = new StringBuilder();
+		params.append("accountType").append("=").append(urlEncode(accountType));
+		params.append("&").append("accountValue").append("=").append(urlEncode(accountValue));
+		params.append("&").append("rootOrgId").append("=").append(rootOrgId);
+		params.append("&").append("appId").append("=").append(appId);
+		params.append("&").append("timestamp").append("=").append(timestamp);
+		params.append("&").append("token").append("=").append(accessToken);
+
+		return accessUrl + "?" + params.toString();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	private static String urlEncode(String s) {
+		try {
+			return URLEncoder.encode(s, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 构建URL
+	 *
+	 * @author WANGWEI
+	 * @param uri
+	 * @return
+	 */
+	public static String buildUrl(String uri) {
+		String host =  PropertiesUtil.getString("qmth.server.host");
+		String port =  PropertiesUtil.getString("qmth.server.port");
+		StringBuilder sb = new StringBuilder();
+
+		sb.append("http://").append(host).append(":").append(port);
+
+		if (!uri.startsWith("/")) {
+			sb.append("/");
+		}
+		sb.append(uri);
+
+		return sb.toString();
+	}
+
+}

+ 650 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/UpdateQuestionScoreService.java

@@ -0,0 +1,650 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity.*;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util.OKHttpUtil;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.util.QmthUtil;
+import cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity.*;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.HttpMethod;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
+import com.mongodb.client.result.UpdateResult;
+import com.mysql.cj.util.StringUtils;
+import okhttp3.Response;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 更新小题分数(涉及到分数重新计算,待阅卷表的数据重新更新)
+ * @Author lideyin
+ * @Date 2020/5/25 14:51
+ * @Version 1.0
+ */
+@Service
+public class UpdateQuestionScoreService {
+
+    @Autowired
+    JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    MongoTemplate mongoTemplate;
+
+    //试卷结构本地缓存
+    private final Map<String, DefaultPaperBean> paperCacheMap = new HashMap<>();
+
+    //小题标准答案本地缓存
+    private final Map<String, List<String>> rightAnswerCacheMap = new HashMap<>();
+
+    //部分平面化的试卷结构缓存
+    private final Map<String, Map<String, Object>> partialFacetPaperCacheMap = new HashMap<>();
+
+    //没有标答的小题id
+    private Set<String> noAnswerQuestionIdSet = new HashSet<>();
+
+    @Async
+    public void start() {
+
+        String absolutePath = PropertiesUtil.getString("fixAnswer.data.path");
+//        ClassPathResource classPathResource = new ClassPathResource("temp1.txt");
+        FileInputStream fis = null;
+        InputStreamReader isr = null;
+        BufferedReader br = null;
+
+        try {
+            fis = new FileInputStream(new File(absolutePath));
+            isr = new InputStreamReader(fis);
+            br = new BufferedReader(isr);
+            String strExamIdAndCourseCode = "";
+            while ((strExamIdAndCourseCode = br.readLine()) != null) {
+                Long examId = Long.valueOf(strExamIdAndCourseCode.split(",")[0]);
+                String courseCode = strExamIdAndCourseCode.split(",")[1];
+                System.out.println(String.format("examId:%s,courseCode:%s", examId, courseCode));
+                run(examId, courseCode);
+                Thread.sleep(10);
+                //19,"03013750"
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (isr != null) {
+                try {
+                    isr.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+
+    }
+
+    public void run(long examId, String courseCode) {
+        List<ExamRecordDataEntity> examRecordDataList = queryExamRecordDataList(examId, courseCode);
+
+        if (examRecordDataList.isEmpty()) {
+            throw new StatusException("100001", "找不到对应的考试记录");
+        }
+
+        //首先更新mongo中的作答记录
+        modifyMongoQuestionScore(examRecordDataList, examId, courseCode);
+
+        //重新计算分数并保存
+        modifyDbScore(examRecordDataList);
+
+        //如果存在,则打印错误数据
+        printErrorDataIfExist();
+
+        System.out.println("999.all is over.....");
+    }
+
+    private void printErrorDataIfExist() {
+        if (noAnswerQuestionIdSet.size() > 0) {
+            for (String quesId : noAnswerQuestionIdSet)
+                System.out.println(String.format("找不到quesId=%s的标答数据", quesId));
+        }
+    }
+
+    private void modifyDbScore(List<ExamRecordDataEntity> examRecordDataList) {
+        Set<Long> examStudentIdSet = new HashSet<>();
+        int effectiveNum = 0;//有效数量
+        //按考试记录更新分数表
+        for (ExamRecordDataEntity record : examRecordDataList) {
+            Query query = new Query();
+            query.addCriteria(Criteria.where("_id").is(record.getExamRecordQuestionsId()));
+            ExamRecordQuestionsEntity erq = mongoTemplate.findOne(query, ExamRecordQuestionsEntity.class, "examRecordQuestions");
+            if (erq == null) continue;
+
+            List<ExamQuestionEntity> objectiveQuesList = erq.getExamQuestionEntities().stream()
+                    .filter(p -> isObjectiveQues(p.getQuestionType())).collect(Collectors.toList());
+
+            double objectiveScore = calcTotalObjectiveScore(objectiveQuesList);
+            double objectiveAccuracy = calcTotalObjectiveAccuracy(objectiveQuesList);
+
+            updateExamScore(objectiveScore, objectiveAccuracy, record.getId());
+            updateExamRecord4Marking(objectiveScore, record.getId());
+            System.out.println(String.format("2.更新第%d条分数成功---examRecordDataId=%s", ++effectiveNum, record.getId()));
+            examStudentIdSet.add(record.getExamStudentId());
+        }
+
+//        int esNum = 0;
+//        //按考生id,删除考生最终分数表(此表如果不存在数据,获取时,系统会重新计算)
+//        for (Long estId : examStudentIdSet) {
+//            deleteFinalScore(estId);
+//            System.out.println(String.format("3.删除第%d条最终分数成功---examStudentId=%s", ++esNum, estId));
+//        }
+
+    }
+
+    private void deleteFinalScore(Long estId) {
+        String strSql = String.format("delete from ec_oe_exam_student_final_score where exam_student_id=%d ", estId);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    /**
+     * 计算客观题总分
+     *
+     * @param objectiveQuesList
+     * @return
+     */
+    private double calcTotalObjectiveScore(List<ExamQuestionEntity> objectiveQuesList) {
+        double totalObjectiveScore = 0d;//客观题总分
+
+        for (ExamQuestionEntity eq : objectiveQuesList) {
+            if (!StringUtils.isNullOrEmpty(eq.getStudentAnswer())
+                    && !StringUtils.isNullOrEmpty(eq.getCorrectAnswer())
+                    && eq.getStudentAnswer().equals(eq.getCorrectAnswer())) {
+                totalObjectiveScore += eq.getQuestionScore();
+            }
+        }
+
+        return totalObjectiveScore;
+    }
+
+    /**
+     * 计算客观题正确率
+     *
+     * @param objectiveQuesList
+     * @return
+     */
+    private double calcTotalObjectiveAccuracy(List<ExamQuestionEntity> objectiveQuesList) {
+        double correctNum = 0d;
+        double totalNum = 0d;
+        for (ExamQuestionEntity eq : objectiveQuesList) {
+            if (!StringUtils.isNullOrEmpty(eq.getStudentAnswer())
+                    && !StringUtils.isNullOrEmpty(eq.getCorrectAnswer())
+                    && eq.getStudentAnswer().equals(eq.getCorrectAnswer())) {
+                correctNum += 1;
+            }
+            totalNum += 1;
+        }
+
+        if (totalNum == 0) {
+            return 0;
+        }
+
+        return Double.valueOf(new DecimalFormat("#.00").format(correctNum * 100D / totalNum));
+    }
+
+    /**
+     * 更新作答记录中的小题分数
+     *
+     * @param examRecordDataList
+     */
+    private void modifyMongoQuestionScore(List<ExamRecordDataEntity> examRecordDataList, long examId, String courseCode) {
+        int rubbishNum = 0;
+        int effectiveNum = 0;//有效数量
+        int rubbishNum2 = 0;
+        int effectiveNum2 = 0;//有效数量
+        for (ExamRecordDataEntity record : examRecordDataList) {
+            //更新作答记录中的小题分数
+            try {
+                String examRecordQuestionsId = record.getExamRecordQuestionsId();
+                Query query = new Query();
+                query.addCriteria(Criteria.where("_id").is(examRecordQuestionsId));
+                //作答记录
+                ExamRecordQuestionsEntity erq = mongoTemplate.findOne(query, ExamRecordQuestionsEntity.class, "examRecordQuestions");
+
+                if (erq == null) {
+                    System.out.println(String.format("1.作答记录发现%d条垃圾数据:examRecordDataId=%s", ++rubbishNum, record.getId()));
+                    continue;//垃圾数据
+                }
+
+                List<ExamQuestionEntity> examQuestionList = erq.getExamQuestionEntities();
+
+                for (ExamQuestionEntity ques : examQuestionList) {
+                    String quesId = ques.getQuestionId();
+
+                    //最小维度的小题单元集合
+                    List<ExamQuestionEntity> questionUnitList = examQuestionList.stream()
+                            .filter(p -> p.getQuestionId().equals(quesId))
+                            .sorted((o1, o2) -> o1.getOrder().intValue() - o2.getOrder().intValue())
+                            .collect(Collectors.toList());
+                    //根据题目id获取标答集合
+                    List<String> rightAnswerList = getRightAnswerList(quesId);
+                    if (rightAnswerList == null) {
+                        noAnswerQuestionIdSet.add(quesId);
+                    }
+
+                    //根据题目id获取小题分集合
+                    List<Double> questionScoreList =
+                            getQuestionScoreList(quesId, examId, courseCode, record.getPaperType(), record.getBasePaperId());
+
+                    //循环保存所有小题单元
+                    for (int i = 0; i < questionUnitList.size(); i++) {
+                        Double questionScore = questionScoreList.get(i);
+                        //更新小题分数
+                        updateQuestionScore(examRecordQuestionsId, ques.getOrder(), questionScore);
+
+                        //如果为客观题重新算分并保存
+                        if (isObjectiveQues(ques.getQuestionType())) {
+                            String rightAnswer = rightAnswerList.get(i);
+                            if (!StringUtils.isNullOrEmpty(rightAnswer)) {
+                                ExamQuestionEntity curQues = questionUnitList.get(i);
+
+                                updatePartialQuestionAnswer(examRecordQuestionsId, curQues.getOrder(),
+                                        rightAnswer, calcStudentUnitScore(rightAnswer, curQues.getStudentAnswer(), questionScore));
+                            }
+                        }
+                    }
+                }
+                System.out.println(String.format("1.更新第%d条作答记录成功---examRecordDataId=%s", ++effectiveNum, record.getId()));
+            } catch (Exception e) {
+                System.out.println(String.format("1.更新第%d条作答记录失败---examRecordDataId=%s", ++effectiveNum, record.getId()));
+                throw new StatusException("100002", e.getMessage(), e);
+            }
+
+            //更新试卷结构中的小题分数
+            try {
+                String paperStructId = record.getPaperStructId();
+                Query query = new Query();
+                query.addCriteria(Criteria.where("_id").is(paperStructId));
+                //试卷结构
+                ExamRecordPaperStructBean paperStructBean = mongoTemplate.findOne(query, ExamRecordPaperStructBean.class, "examRecordPaperStruct");
+
+                if (paperStructBean == null) {
+                    System.out.println(String.format("1.2.试卷结构发现%d条垃圾数据:examRecordDataId=%s", ++rubbishNum2, record.getId()));
+                    continue;//垃圾数据
+                }
+
+                DefaultPaperBean defaultPaper = paperStructBean.getDefaultPaper();
+
+                //题组
+                List<DefaultQuestionGroupBean> questionGroupList = defaultPaper.getQuestionGroupList();
+                for (DefaultQuestionGroupBean group : questionGroupList) {
+                    //题包装器
+                    List<DefaultQuestionStructureWrapperBean> questionWrapperList = group.getQuestionWrapperList();
+                    for (DefaultQuestionStructureWrapperBean qw : questionWrapperList) {
+                        Map<String, Object> partialFacetPaper =
+                                getPartialFacetPaper(qw.getQuestionId(), examId, courseCode, record.getPaperType(), record.getBasePaperId());
+                        if (null != partialFacetPaper) {
+                            if (null != partialFacetPaper.get("groupScore")) {
+                                group.setGroupScore(Double.valueOf(partialFacetPaper.get("groupScore").toString()));
+                            }
+
+                            if (null != partialFacetPaper.get("questionWrapperScore")) {
+                                qw.setQuestionScore(Double.valueOf(partialFacetPaper.get("questionWrapperScore").toString()));
+                            }
+
+                            if (null != partialFacetPaper.get("questionUnitList")) {
+                                List<DefaultQuestionUnitWrapperBean> questionUnitList =
+                                        (List<DefaultQuestionUnitWrapperBean>) partialFacetPaper.get("questionUnitList");
+
+                                List<DefaultQuestionUnitWrapperBean> questionUnitWrapperList = qw.getQuestionUnitWrapperList();
+                                for (int i = 0; i < questionUnitWrapperList.size(); i++) {
+                                    questionUnitWrapperList.get(i).setQuestionScore(questionUnitList.get(i).getQuestionScore());
+                                }
+                            }
+
+                        }
+                    }
+
+                }
+
+                //更新修改后的试卷结构
+                mongoTemplate.save(paperStructBean, "examRecordPaperStruct");
+                System.out.println(String.format("1.2.更新第%d条试卷结构数据成功---examRecordDataId=%s,paperStructId=%s",
+                        ++effectiveNum2, record.getId(), record.getPaperStructId()));
+
+            } catch (Exception e) {
+                System.out.println(String.format("1.2.更新第%d条试卷结构数据失败---examRecordDataId=%s,paperStructId=%s",
+                        ++effectiveNum2, record.getId(), record.getPaperStructId()));
+                throw new StatusException("100003", e.getMessage(), e);
+            }
+
+        }
+    }
+
+    /**
+     * 获取当前考试id下的所有小题分集合
+     *
+     * @param questionId
+     * @param examId
+     * @param courseCode
+     * @param paperType
+     * @param basePaperId
+     * @return
+     */
+    private List<Double> getQuestionScoreList(String questionId, Long examId, String courseCode,
+                                              String paperType, String basePaperId) {
+
+        Map<String, Object> partialFacetPaper = getPartialFacetPaper(questionId, examId, courseCode, paperType, basePaperId);
+        if (null == partialFacetPaper) {
+            return null;
+        }
+
+        List<DefaultQuestionUnitWrapperBean> questionUnitList = (List<DefaultQuestionUnitWrapperBean>) partialFacetPaper.get("questionUnitList");
+
+        return questionUnitList.stream()
+                .map(DefaultQuestionUnitWrapperBean::getQuestionScore).collect(Collectors.toList());
+    }
+
+    /**
+     * 获取部分平面化的试卷信息
+     *
+     * @param questionId
+     * @param examId
+     * @param courseCode
+     * @param paperType
+     * @param basePaperId
+     * @return
+     */
+    private Map<String, Object> getPartialFacetPaper(String questionId, Long examId, String courseCode,
+                                                     String paperType, String basePaperId) {
+        String cacheKey = String.format("questionScore_%s", questionId);
+        if (partialFacetPaperCacheMap.containsKey(cacheKey)) {
+            return partialFacetPaperCacheMap.get(cacheKey);
+        }
+
+        DefaultPaperBean defaultPaper = getPaper(examId, courseCode, paperType, basePaperId);
+        if (null == defaultPaper) {
+            return null;
+        }
+
+        Map<String, Object> resultMap = new HashMap<>();
+        List<DefaultQuestionGroupBean> questionGroupList = defaultPaper.getQuestionGroupList();
+        for (DefaultQuestionGroupBean group : questionGroupList) {
+            List<DefaultQuestionStructureWrapperBean> questionWrapperList = group.getQuestionWrapperList();
+            for (DefaultQuestionStructureWrapperBean qw : questionWrapperList) {
+                if (qw.getQuestionId().equals(questionId)) {
+                    Map<String, Object> curQuestionMap = new HashMap<>();
+                    //当前试题id对应的题组分数
+                    curQuestionMap.put("groupScore", group.getGroupScore());
+                    //当前试题id对应的题包装器分数
+                    curQuestionMap.put("questionWrapperScore", qw.getQuestionScore());
+                    //当前试题id对应的题单元集合
+                    curQuestionMap.put("questionUnitList", qw.getQuestionUnitWrapperList());
+
+                    partialFacetPaperCacheMap.put(cacheKey, curQuestionMap);
+                    return curQuestionMap;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private DefaultPaperBean getPaper(Long examId, String courseCode,
+                                      String paperType, String basePaperId) {
+        String cacheKey = String.format("paper_%s_%s_%s_%s", examId, courseCode, paperType, basePaperId);
+        if (paperCacheMap.containsKey(cacheKey)) {
+            return paperCacheMap.get(cacheKey);
+        }
+
+        OuterGetPaperStructReq reqBody = new OuterGetPaperStructReq();
+        reqBody.setBasePaperId(basePaperId);
+        reqBody.setCourseCode(courseCode);
+        reqBody.setExamId(examId);
+        reqBody.setPaperType(paperType);
+
+        String url = QmthUtil.buildUrl("/api/exchange/outer/question/getPaperStruct");
+        Response resp = null;
+        try {
+            resp = OKHttpUtil.call(HttpMethod.POST, url, QmthUtil.getSecurityHeaders(),
+                    JsonUtil.toJson(reqBody));
+            if (resp.code() == 200) {
+                String resultJson = resp.body().string();
+                OuterGetPaperStructResp outerGetQuestionAnswerResp = JsonUtil.fromJson(resultJson, OuterGetPaperStructResp.class);
+
+                DefaultPaperBean defaultPaper = outerGetQuestionAnswerResp.getDefaultPaper();
+                paperCacheMap.putIfAbsent(cacheKey, defaultPaper);
+
+                return defaultPaper;
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new StatusException("102002", e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+        return null;
+    }
+
+    /**
+     * 计算学生的小题得分
+     *
+     * @param rightAnswer
+     * @param studentAnswer
+     * @param questionScore
+     * @return
+     */
+    private Double calcStudentUnitScore(String rightAnswer, String studentAnswer, Double questionScore) {
+        Double studentUnitScore = 0D;
+        if (!StringUtils.isNullOrEmpty(rightAnswer)
+                && !(StringUtils.isNullOrEmpty(studentAnswer))
+                && rightAnswer.equals(studentAnswer)) {
+            studentUnitScore = questionScore;
+        }
+        return studentUnitScore;
+    }
+
+    /**
+     * 获取题目正确答案
+     *
+     * @param quesId
+     * @return
+     */
+    private List<String> getRightAnswerList(String quesId) {
+//        if (1 == 1) {
+//            List<String> list = new ArrayList<>();
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            list.add("2");
+//            return list;
+//        }
+
+        if (rightAnswerCacheMap.containsKey(quesId)) {
+            return rightAnswerCacheMap.get(quesId);
+        }
+
+        OuterGetQuestionAnswerReq reqBody = new OuterGetQuestionAnswerReq();
+        reqBody.setQuestionId(quesId);
+
+        String url = QmthUtil.buildUrl("/api/exchange/outer/question/getQuestionAnswer");
+        Response resp = null;
+        try {
+            resp = OKHttpUtil.call(HttpMethod.POST, url, QmthUtil.getSecurityHeaders(),
+                    JsonUtil.toJson(reqBody));
+            if (resp.code() == 200) {
+                String resultJson = resp.body().string();
+                OuterGetQuestionAnswerResp outerGetQuestionAnswerResp = JsonUtil.fromJson(resultJson, OuterGetQuestionAnswerResp.class);
+
+                rightAnswerCacheMap.putIfAbsent(quesId, outerGetQuestionAnswerResp.getAnswerList());
+                return outerGetQuestionAnswerResp.getAnswerList();
+
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new StatusException("102002", e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+        return null;
+    }
+
+    /**
+     * 是否为客观题
+     *
+     * @param questionType
+     * @return
+     */
+    private boolean isObjectiveQues(String questionType) {
+        return "SINGLE_CHOICE".equals(questionType)
+                || "MULTIPLE_CHOICE".equals(questionType)
+                || "TRUE_OR_FALSE".equals(questionType);
+    }
+
+    private List<ExamRecordDataEntity> queryExamRecordDataList(Long examId, String courseCode) {
+        String strSql = String.format("SELECT " +
+                "t1.`id`, " +
+                "t1.`exam_student_id`, " +
+                "t1.`paper_struct_id`, " +
+                "t1.`paper_type`, " +
+                "t1.`base_paper_id`, " +
+                "t1.exam_record_questions_id " +
+                "FROM " +
+                "ec_oe_exam_record_data t1 " +
+                "INNER JOIN ec_b_course t2 ON t1.course_id = t2.id " +
+                "WHERE " +
+                "t1.exam_id = %d " +
+                "AND t2.`code` ='%s' ", examId, courseCode);
+
+        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(strSql);
+        List<ExamRecordDataEntity> resultList = new ArrayList<>();
+        for (Map<String, Object> map : mapList) {
+            ExamRecordDataEntity entity = new ExamRecordDataEntity();
+            if (map.get("id") != null) {
+                entity.setId(Long.valueOf(map.get("id").toString()));
+            }
+
+            if (map.get("exam_student_id") != null) {
+                entity.setExamStudentId(Long.valueOf(map.get("exam_student_id").toString()));
+            }
+
+            if (map.get("exam_record_questions_id") != null) {
+                entity.setExamRecordQuestionsId(map.get("exam_record_questions_id").toString());
+            }
+
+            if (map.get("paper_struct_id") != null) {
+                entity.setPaperStructId(map.get("paper_struct_id").toString());
+            }
+
+            if (map.get("paper_type") != null) {
+                entity.setPaperType(map.get("paper_type").toString());
+            }
+
+            if (map.get("base_paper_id") != null) {
+                entity.setBasePaperId(map.get("base_paper_id").toString());
+            }
+            resultList.add(entity);
+        }
+        return resultList;
+    }
+
+    private void updateExamScore(double objectiveScore, double objectiveAccuracy,
+                                 Long examRecordDataId) {
+        String strSql = String.format("UPDATE ec_oe_exam_score " +
+                "SET objective_score = %s, " +
+                "objective_accuracy = %s, " +
+                "total_score = objective_score+ IFNULL(subjective_score,0) " +
+                "WHERE " +
+                "exam_record_data_id = %d  ", objectiveScore, objectiveAccuracy, examRecordDataId);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    private void updateExamRecord4Marking(double objectiveScore, Long examRecordDataId) {
+        String strSql = String.format("UPDATE ec_oe_exam_record_4_marking " +
+                "SET objective_score = %s " +
+                "WHERE " +
+                " exam_record_data_id = %s ", objectiveScore, examRecordDataId);
+
+        jdbcTemplate.execute(strSql);
+    }
+
+    /**
+     * 更新小题分数
+     *
+     * @param examRecordQuestionsId
+     * @param order
+     * @param newQuestionScore
+     * @return
+     */
+    public long updateQuestionScore(String examRecordQuestionsId, Integer order, Double newQuestionScore) {
+        // 查询相应的题目
+        Query query = Query.query(Criteria.where("_id").is(examRecordQuestionsId)
+                .and("examQuestionEntities.order").is(order));
+        Update update = new Update();
+        update.set("examQuestionEntities.$.questionScore", newQuestionScore);
+
+        UpdateResult upResult = mongoTemplate.updateFirst(query, update, "examRecordQuestions");
+        long res = upResult.getMatchedCount();
+
+        System.out.println(String.format("更新作答记录中小题分数成功effectNum=%s,id=%s,order=%s,newQuestionScore=%s",
+                res, examRecordQuestionsId, order, newQuestionScore));
+        return res;
+    }
+
+    /**
+     * 更新学生作答的部分数据
+     *
+     * @param examRecordQuestionsId
+     * @param order
+     * @param newAnswer
+     * @param studentScore
+     * @return
+     */
+    public long updatePartialQuestionAnswer(String examRecordQuestionsId, Integer order, String newAnswer, Double studentScore) {
+        // 查询相应的题目
+        Query query = Query.query(Criteria.where("_id").is(examRecordQuestionsId)
+                .and("examQuestionEntities.order").is(order));
+        Update update = new Update();
+        update.set("examQuestionEntities.$.correctAnswer", newAnswer);
+        update.set("examQuestionEntities.$.studentScore", studentScore);
+
+        UpdateResult upResult = mongoTemplate.updateFirst(query, update, "examRecordQuestions");
+        long res = upResult.getMatchedCount();
+
+        System.out.println(String.format("更新作答记录中学生分数成功effectNum=%s,id=%s,order=%s,studentScore=%s,correctAnswer=%s",
+                res, examRecordQuestionsId, order, studentScore, newAnswer));
+        return res;
+    }
+
+
+}

+ 56 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultPaperBean.java

@@ -0,0 +1,56 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 试卷结构
+ *
+ * @author WANGWEI
+ * @date 2018年8月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class DefaultPaperBean implements Serializable {
+
+	private static final long serialVersionUID = -5979287118960427883L;
+
+	/**
+	 * 试卷名称
+	 */
+	private String name;
+
+	/**
+	 * 是否全是客观题
+	 */
+	private Boolean fullyObjective;
+
+	/**
+	 * 分组集合
+	 */
+	private List<DefaultQuestionGroupBean> questionGroupList;
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Boolean getFullyObjective() {
+		return fullyObjective;
+	}
+
+	public void setFullyObjective(Boolean fullyObjective) {
+		this.fullyObjective = fullyObjective;
+	}
+
+	public List<DefaultQuestionGroupBean> getQuestionGroupList() {
+		return questionGroupList;
+	}
+
+	public void setQuestionGroupList(List<DefaultQuestionGroupBean> questionGroupList) {
+		this.questionGroupList = questionGroupList;
+	}
+
+}

+ 56 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionGroupBean.java

@@ -0,0 +1,56 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 题分组集合
+ *
+ * @author WANGWEI
+ * @date 2018年8月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class DefaultQuestionGroupBean implements Serializable {
+
+	private static final long serialVersionUID = 2149814711274942645L;
+
+	/**
+	 * 题组名称
+	 */
+	private String groupName;
+
+	/**
+	 * 题包装器集合<br>
+	 */
+	private List<DefaultQuestionStructureWrapperBean> questionWrapperList;
+
+	/**
+	 * 题组总分
+	 */
+	private Double groupScore;
+
+	public String getGroupName() {
+		return groupName;
+	}
+
+	public void setGroupName(String groupName) {
+		this.groupName = groupName;
+	}
+
+	public List<DefaultQuestionStructureWrapperBean> getQuestionWrapperList() {
+		return questionWrapperList;
+	}
+
+	public void setQuestionWrapperList(List<DefaultQuestionStructureWrapperBean> questionWrapperList) {
+		this.questionWrapperList = questionWrapperList;
+	}
+
+	public Double getGroupScore() {
+		return groupScore;
+	}
+
+	public void setGroupScore(Double groupScore) {
+		this.groupScore = groupScore;
+	}
+
+}

+ 107 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionStructureWrapperBean.java

@@ -0,0 +1,107 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 题结构包装器
+ *
+ * @author WANGWEI
+ * @date 2018年8月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class DefaultQuestionStructureWrapperBean implements Serializable {
+
+	private static final long serialVersionUID = 8423916951155451548L;
+
+	/**
+	 * 题ID
+	 */
+	private String questionId;
+
+	/**
+	 * 版本号
+	 */
+	private String version;
+
+	/**
+	 * 题分数
+	 */
+	private Double questionScore;
+
+	/**
+	 * 限制播放次数
+	 */
+	private Integer limitedPlayTimes;
+
+	/**
+	 * 已播放次数
+	 */
+	private Integer playedTimes;
+
+	/**
+	 * 作答限时
+	 */
+	private Long timeLimit;
+
+	/**
+	 * 题单元包装器
+	 */
+	private List<DefaultQuestionUnitWrapperBean> questionUnitWrapperList;
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+
+	public String getVersion() {
+		return version;
+	}
+
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	public Double getQuestionScore() {
+		return questionScore;
+	}
+
+	public void setQuestionScore(Double questionScore) {
+		this.questionScore = questionScore;
+	}
+
+	public Integer getLimitedPlayTimes() {
+		return limitedPlayTimes;
+	}
+
+	public void setLimitedPlayTimes(Integer limitedPlayTimes) {
+		this.limitedPlayTimes = limitedPlayTimes;
+	}
+
+	public Integer getPlayedTimes() {
+		return playedTimes;
+	}
+
+	public void setPlayedTimes(Integer playedTimes) {
+		this.playedTimes = playedTimes;
+	}
+
+	public Long getTimeLimit() {
+		return timeLimit;
+	}
+
+	public void setTimeLimit(Long timeLimit) {
+		this.timeLimit = timeLimit;
+	}
+
+	public List<DefaultQuestionUnitWrapperBean> getQuestionUnitWrapperList() {
+		return questionUnitWrapperList;
+	}
+
+	public void setQuestionUnitWrapperList(List<DefaultQuestionUnitWrapperBean> questionUnitWrapperList) {
+		this.questionUnitWrapperList = questionUnitWrapperList;
+	}
+}

+ 67 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/DefaultQuestionUnitWrapperBean.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import java.io.Serializable;
+
+/**
+ * 题单元包装器
+ *
+ * @author WANGWEI
+ * @date 2018年8月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class DefaultQuestionUnitWrapperBean implements Serializable {
+
+	private static final long serialVersionUID = 7584275153456817959L;
+
+	/**
+	 * 选项排序值
+	 */
+	private Integer[] optionPermutation;
+
+	/**
+	 * 题分数
+	 */
+	private Double questionScore;
+
+	/**
+	 * 题型
+	 */
+	private String questionType;
+
+	/**
+	 * 作答类型
+	 */
+	private String answerType;
+
+	public Integer[] getOptionPermutation() {
+		return optionPermutation;
+	}
+
+	public void setOptionPermutation(Integer[] optionPermutation) {
+		this.optionPermutation = optionPermutation;
+	}
+
+	public Double getQuestionScore() {
+		return questionScore;
+	}
+
+	public void setQuestionScore(Double questionScore) {
+		this.questionScore = questionScore;
+	}
+
+	public String getQuestionType() {
+		return questionType;
+	}
+
+	public void setQuestionType(String questionType) {
+		this.questionType = questionType;
+	}
+
+	public String getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(String answerType) {
+		this.answerType = answerType;
+	}
+}

+ 43 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/ExamRecordPaperStructBean.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+
+/**
+ *
+ * @author  	chenken
+ * @date    	2018年8月27日 下午3:26:27
+ * @company 	QMTH
+ * @description ExamRecordPaper.java
+ */
+@Document(collection="examRecordPaperStruct")
+public class ExamRecordPaperStructBean implements Serializable{
+
+	/**
+	 *
+	 */
+	private static final long serialVersionUID = -4672132535133881321L;
+
+	@Id
+	private String id;
+
+	private DefaultPaperBean defaultPaper;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public DefaultPaperBean getDefaultPaper() {
+		return defaultPaper;
+	}
+
+	public void setDefaultPaper(DefaultPaperBean defaultPaper) {
+		this.defaultPaper = defaultPaper;
+	}
+}

+ 59 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/OuterGetPaperStructReq.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import cn.com.qmth.examcloud.api.commons.exchange.EnterpriseRequest;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 获取试卷结构
+ * @Author lideyin
+ * @Date 2020/5/25 15:56
+ * @Version 1.0
+ */
+public class OuterGetPaperStructReq extends EnterpriseRequest {
+
+    private static final long serialVersionUID = -8374755306658040184L;
+
+    @ApiModelProperty(value = "考试id", example = "123", required = true)
+    private Long examId;
+
+    @ApiModelProperty(value = "课程code", example = "123", required = true)
+    private String courseCode;
+
+    @ApiModelProperty(value = "卷型", example = "123", required = true)
+    private String paperType;
+
+    @ApiModelProperty(value = "原始试卷id", example = "123", required = true)
+    private String basePaperId;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public String getBasePaperId() {
+        return basePaperId;
+    }
+
+    public void setBasePaperId(String basePaperId) {
+        this.basePaperId = basePaperId;
+    }
+}

+ 27 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_question_score/entity/OuterGetPaperStructResp.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_question_score.entity;
+
+import cn.com.qmth.examcloud.api.commons.exchange.EnterpriseResponse;
+
+/**
+ * @Description 获取试卷结构
+ * @Author lideyin
+ * @Date 2020/5/25 15:40
+ * @Version 1.0
+ */
+public class OuterGetPaperStructResp extends EnterpriseResponse {
+
+    private static final long serialVersionUID = 8290190579593586203L;
+
+    /**
+     * 试卷结构
+     */
+    private DefaultPaperBean defaultPaper;
+
+    public DefaultPaperBean getDefaultPaper() {
+        return defaultPaper;
+    }
+
+    public void setDefaultPaper(DefaultPaperBean defaultPaper) {
+        this.defaultPaper = defaultPaper;
+    }
+}

+ 59 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswer.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer;
+
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity.FixAnswerType;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+/**
+ * 执行图片处理逻辑
+ */
+@Service
+public class FixStudentAnswer {
+
+	private static final Logger log = LoggerFactory.getLogger(FixStudentAnswer.class);
+
+	/**
+	 * 修复图片
+	 */
+	public void startFixPicAnswer(Long examId, String courseCode, int order) {
+		FixStudentAnswerService service = SpringContextHolder
+				.getBean(FixStudentAnswerService.class);
+		String srcPicFileDirectory = "D:/Temp/srcPic";
+		try {
+			service.fixStudentAnswer(srcPicFileDirectory, examId, courseCode, order,
+					FixAnswerType.PICTURE);
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+			log.error("FileNotFoundException", e);
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+			log.error("UnsupportedEncodingException", e);
+		} catch (Exception e) {
+			log.error("UnkonwnException ", e);
+		}
+	}
+
+	public void startFixAudioAnswer(Long examId, String courseCode, int order) {
+		FixStudentAnswerService service = SpringContextHolder
+				.getBean(FixStudentAnswerService.class);
+		String srcPicFileDirectory = "D:/Temp/srcAudio";
+		try {
+			service.fixStudentAnswer(srcPicFileDirectory, examId, courseCode, order,
+					FixAnswerType.AUDIO);
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+			log.error("FileNotFoundException", e);
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+			log.error("UnsupportedEncodingException", e);
+		} catch (Exception e) {
+			log.error("UnkonwnException ", e);
+		}
+	}
+}

+ 171 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswerService.java

@@ -0,0 +1,171 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.RegExUtils;
+import org.assertj.core.util.Sets;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity.FixAnswerType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.Counter;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.Util;
+import cn.com.qmth.examcloud.web.upyun.UpyunService;
+
+/**
+ * @Description 修复学生作答
+ * @Author THINKPAD
+ * @Date 2019/7/8 10:16
+ * @Version 1.0
+ */
+@Service
+public class FixStudentAnswerService {
+
+	@Autowired
+	JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	MongoTemplate mongoTemplate;
+
+	@Autowired
+	UpyunService upyunService;
+
+	private static Counter counter = new Counter(0L, 1000000L);
+
+	public void fixStudentAnswer(String filePath, Long examId, String courseCode, Integer order,
+			FixAnswerType fixAnswerType)
+			throws FileNotFoundException, UnsupportedEncodingException {
+		if (fixAnswerType != FixAnswerType.AUDIO && fixAnswerType != FixAnswerType.PICTURE) {
+			throw new StatusException("100001", "修改的文件类型格式不正确");
+		}
+		// 错误提示
+		List<Map<String, Object>> failList = Lists.newArrayList();
+		File file = new File(filePath);
+
+		if (file.isDirectory()) {
+			String[] list = file.list();
+			// 循环查库
+			// 获取身份证键值对信息
+			Map<String, List<Map<String, Object>>> identityMap = getIdentityMap(filePath, list);
+
+			Set<String> keys = identityMap.keySet();
+			Set<String> tempKeys = Sets.newHashSet();
+			int index = 0;
+			int threadSize = 10;
+			int batchSize = keys.size() / threadSize + 1;
+			for (String key : keys) {
+				index++;
+				tempKeys.add(key);
+				if (index == batchSize) {
+					new FixStudentAnswerThread(jdbcTemplate, mongoTemplate, tempKeys, examId,
+							courseCode, failList, identityMap, order, fixAnswerType, upyunService,
+							counter).start();
+					index = 0;
+					tempKeys = Sets.newHashSet();
+				}
+			}
+
+			if (CollectionUtils.isNotEmpty(tempKeys)) {
+				new FixStudentAnswerThread(jdbcTemplate, mongoTemplate, tempKeys, examId,
+						courseCode, failList, identityMap, order, fixAnswerType, upyunService,
+						counter).start();
+			}
+
+		}
+
+		while (true) {
+			Util.sleep(5);
+			System.out.println("count: " + counter.get());
+			System.out.println("failList: \n" + JsonUtil.toPrettyJson(failList));
+		}
+
+	}
+
+	/**
+	 * 获取身份证和文件地址的对应关系
+	 * 
+	 * @param list
+	 * @return
+	 */
+	private Map<String, List<Map<String, Object>>> getIdentityMap(String filePath, String[] list) {
+		// 所有的图片
+		List<Map<String, Object>> fileMapList = new ArrayList<>();
+		for (String fileName : list) {
+			String fullFilePath = filePath + File.separator + fileName;
+			fileName = RegExUtils.replaceAll(fileName, "\\s+", "");
+			// 如果有-,则证明有多张图片
+			if (fileName.indexOf("-") != -1) {
+				String identityNumber = fileName.substring(0, fileName.indexOf("-"));
+				Map<String, Object> map = new HashMap<>();
+				map.put("identityNumber", identityNumber);
+				map.put("filePath", fullFilePath);
+				// 排序号
+				int sortOrder = 0;
+				if (fileName.indexOf("_") != -1) {
+					try {
+						sortOrder = Integer.parseInt(fileName.substring(fileName.indexOf("-") + 1,
+								fileName.indexOf("_")));
+					} catch (NumberFormatException e) {
+						e.printStackTrace();
+					}
+				} else {
+					try {
+						sortOrder = Integer.parseInt(fileName.substring(fileName.indexOf("-") + 1,
+								fileName.indexOf(".")));
+					} catch (NumberFormatException e) {
+						e.printStackTrace();
+					}
+				}
+				map.put("order", sortOrder);
+				fileMapList.add(map);
+			} else {
+				String identityNumber = "";
+				if (fileName.indexOf("_") > -1) {
+					identityNumber = fileName.substring(0, fileName.indexOf("_"));
+				} else {
+					identityNumber = fileName.substring(0, fileName.indexOf("."));
+				}
+				Map<String, Object> map = new HashMap<>();
+				map.put("identityNumber", identityNumber);
+				map.put("filePath", fullFilePath);
+				map.put("order", 0);
+				fileMapList.add(map);
+			}
+		}
+		Map<String, List<Map<String, Object>>> resultMap = new HashMap<>();
+		for (Map<String, Object> fm : fileMapList) {
+			List<Map<String, Object>> mapList = fileMapList.stream()
+					.filter(p -> p.get("identityNumber").equals(fm.get("identityNumber")))
+					.collect(Collectors.toList());
+			// 对文件进行排序
+			Collections.sort(mapList, new Comparator<Map<String, Object>>() {
+				public int compare(Map<String, Object> o1, Map<String, Object> o2) {
+					int map1value = Integer.parseInt(o1.get("order").toString());
+					int map2value = Integer.parseInt(o2.get("order").toString());
+					return map1value - map2value;
+				}
+			});
+			if (resultMap.get(fm.get("identityNumber")) == null) {
+				resultMap.put(fm.get("identityNumber").toString(), mapList);
+			}
+		}
+		return resultMap;
+	}
+}

+ 240 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/FixStudentAnswerThread.java

@@ -0,0 +1,240 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import com.mongodb.client.result.UpdateResult;
+
+import cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity.ExamRecordEntity;
+import cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity.FixAnswerType;
+import cn.com.qmth.examcloud.commons.helpers.Counter;
+import cn.com.qmth.examcloud.web.upyun.UpYunPathInfo;
+import cn.com.qmth.examcloud.web.upyun.UpyunPathEnvironmentInfo;
+import cn.com.qmth.examcloud.web.upyun.UpyunService;
+
+/**
+ * @Description TODO
+ * @Author THINKPAD
+ * @Date 2019/7/8 19:51
+ * @Version 1.0
+ */
+public class FixStudentAnswerThread extends Thread {
+	private Set<String> identityKeys;
+
+	JdbcTemplate jdbcTemplate;
+
+	MongoTemplate mongoTemplate;
+
+	Long examId;
+
+	String courseCode;
+
+	List<Map<String, Object>> failList;
+
+	Map<String, List<Map<String, Object>>> identityMap;
+
+	Integer order;
+
+	FixAnswerType fixAnswerType;
+
+	UpyunService upyunService;
+
+	Counter counter;
+
+	public FixStudentAnswerThread(JdbcTemplate jdbcTemplate, MongoTemplate mongoTemplate,
+			Set<String> identityKeys, Long examId, String courseCode,
+			List<Map<String, Object>> failList, Map<String, List<Map<String, Object>>> identityMap,
+			Integer order, FixAnswerType fixAnswerType, UpyunService upyunService,
+			Counter counter) {
+		this.identityKeys = identityKeys;
+		this.jdbcTemplate = jdbcTemplate;
+		this.mongoTemplate = mongoTemplate;
+		this.examId = examId;
+		this.courseCode = courseCode;
+		this.failList = failList;
+		this.identityMap = identityMap;
+		this.order = order;
+		this.fixAnswerType = fixAnswerType;
+		this.upyunService = upyunService;
+		this.counter = counter;
+	}
+
+	@Override
+	public void run() {
+		// 改成多线程
+		for (String identityNumber : identityKeys) {
+			List<ExamRecordEntity> recordList = getExamRecordList(examId, courseCode,
+					identityNumber);
+			// 学生的考试记录(一个学生可能有多次考试)
+			List<ExamRecordEntity> examRecords = getExamRecordListByIdentityNumber(identityNumber,
+					recordList);
+			if (examRecords == null || examRecords.isEmpty()) {
+				logErr(identityNumber, "找不到身份证号为:" + identityNumber + "的考试记录数据", failList);
+				continue;
+			}
+			for (ExamRecordEntity examRecord : examRecords) {
+				// 考试记录id
+				Long examRecordDataId = examRecord.getExamRecordDataId();
+				// 学生id
+				Long studnetId = examRecord.getStudentId();
+				// 如果没有找到考试记录id
+				try {
+					String newAnswer = "";
+					// 第一步,上传又拍云(多个文件的话,一个个追加)
+					// 当前学生的文件集合
+					List<Map<String, Object>> fileByIdnetityNumberList = identityMap
+							.get(identityNumber);
+					for (Map<String, Object> m : fileByIdnetityNumberList) {
+						this.counter.next();
+						UpyunPathEnvironmentInfo env = new UpyunPathEnvironmentInfo();
+						env.setUserId(studnetId.toString());
+						env.setExt1(examRecordDataId.toString());
+						env.setExt2(order.toString());
+						env.setFileSuffix(getFileSuffix(fixAnswerType));
+
+						String tempFilePath = m.get("filePath").toString();
+						String fullFilePath = tempFilePath;
+						System.out.println("正在上传文件:" + fullFilePath);
+						UpYunPathInfo upYunPathInfo = upyunService.writeFile(
+								getSiteId(fixAnswerType), env, new File(fullFilePath), true);
+						// 上传U盘云的完整地址
+						String upYunFileUrl = upYunPathInfo.getUrl();
+
+						newAnswer += buildFileAnswer(fixAnswerType, upYunFileUrl) + ",";
+					}
+					if (newAnswer.indexOf(",") > -1) {
+						newAnswer = newAnswer.substring(0, newAnswer.lastIndexOf(","));
+					}
+					// 第二步,更新答案
+					long res = updateAnswer(examRecordDataId, order, newAnswer);
+					if (res < 1) {
+						logErr(identityNumber, "更新答案失败", failList);
+						System.out.println("[Fail]:身份证号:" + identityNumber + ",考试记录id:"
+								+ examRecordDataId + ",答案更新失败");
+					} else {
+						System.out.println("[Success]:身份证号:" + identityNumber + ",考试记录id:"
+								+ examRecordDataId + ",答案更新成功");
+					}
+				} catch (Exception e) {
+					logErr(identityNumber, e.getMessage(), failList);
+					continue;
+				}
+			}
+		}
+	}
+
+	private String getFileSuffix(FixAnswerType fixAnswerType) {
+		if (fixAnswerType == FixAnswerType.PICTURE) {
+			return ".jpg";
+		} else {
+			return ".mp3";
+		}
+	}
+
+	public List<ExamRecordEntity> getExamRecordList(Long examId, String courseCode,
+			String identityNumber) {
+		String strSql = "select erd.id as examRecordDataId,er.exam_id as examId, "
+				+ "er.identity_number as identityNumber,c.`code` as courseCode,er.student_id as studentId "
+				+ "from ec_oe_exam_record er "
+				+ "inner join ec_oe_exam_record_data erd on er.id=erd.exam_record_id "
+				+ "inner join ec_b_course c on er.course_id=c.id " + "where er.exam_id=" + examId
+				+ " and c.`code`='" + courseCode + "'" + " and er.identity_number='"
+				+ identityNumber + "'";
+
+		java.util.List<Map<String, Object>> mapList = jdbcTemplate.queryForList(strSql);
+		java.util.List<ExamRecordEntity> resultList = new ArrayList<ExamRecordEntity>();
+		for (Map<String, Object> map : mapList) {
+			ExamRecordEntity entity = new ExamRecordEntity();
+			if (map.get("courseCode") != null) {
+				entity.setCourseCode(map.get("courseCode").toString());
+			}
+			if (map.get("examId") != null) {
+				entity.setExamId(Long.parseLong(map.get("examId").toString()));
+			}
+			if (map.get("examRecordDataId") != null) {
+				entity.setExamRecordDataId(Long.parseLong(map.get("examRecordDataId").toString()));
+			}
+			if (map.get("identityNumber") != null) {
+				entity.setIdentityNumber(map.get("identityNumber").toString());
+			}
+			if (map.get("studentId") != null) {
+				entity.setStudentId(Long.parseLong(map.get("studentId").toString()));
+			}
+			resultList.add(entity);
+		}
+		return resultList;
+	}
+
+	// 根据身份证号获取考试记录id(同一个学生可能有多次考试)
+	private List<ExamRecordEntity> getExamRecordListByIdentityNumber(String identityNumber,
+			List<ExamRecordEntity> examRecordList) {
+		List<ExamRecordEntity> resultList = examRecordList.stream()
+				.filter(record -> record.getIdentityNumber().equals(identityNumber))
+				.collect(Collectors.toList());
+		return resultList;
+	}
+
+	private String getSiteId(FixAnswerType fixAnswerType) {
+		if (fixAnswerType == FixAnswerType.PICTURE) {
+			return "photoAnswer";
+		} else {
+			return "audioAnswer";
+		}
+	}
+
+	/**
+	 * 更新指定试题的答案
+	 *
+	 * @param examRecordDataId
+	 * @param order
+	 * @param newAnswer
+	 * @return
+	 */
+	public long updateAnswer(Long examRecordDataId, Integer order, String newAnswer) {
+		// 查询相应的题目
+		Query query = Query.query(Criteria.where("examRecordDataId").is(examRecordDataId)
+				.and("examQuestionEntities.order").is(order));
+		Update update = Update.update("examQuestionEntities.$.studentAnswer", newAnswer);
+		UpdateResult upResult = mongoTemplate.updateFirst(query, update, "examRecordQuestions");
+		return upResult.getMatchedCount();
+	}
+
+	/**
+	 * 构建文件格式
+	 *
+	 * @param fileUrl
+	 * @return
+	 */
+	private String buildFileAnswer(FixAnswerType fixAnswerType, String fileUrl) {
+		if (fixAnswerType == FixAnswerType.PICTURE) {
+			StringBuilder sb = new StringBuilder();
+
+			sb.append("<div class='photo-answers-block'>");
+			sb.append("<a href='" + fileUrl + "' target='_blank' >");
+			sb.append("<img class='photo-answer' src='" + fileUrl + "!/both/200x200' />");
+			sb.append("</a>");
+			sb.append("</div>");
+			return sb.toString();
+		} else {
+			return fileUrl;
+		}
+	}
+
+	private void logErr(String identityNumber, String errMsg, List<Map<String, Object>> failList) {
+		Map<String, Object> errMap = new HashMap<>();
+		errMap.put("identityNumber", identityNumber);
+		errMap.put("err", errMsg);
+		failList.add(errMap);
+	}
+}

+ 193 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamQuestionEntity.java

@@ -0,0 +1,193 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author chenken
+ * @date 2018/8/17 10:18
+ * @company QMTH
+ * @description 考生单题作答记录
+ */
+//@Document(collection="examRecordQuestion")
+public class ExamQuestionEntity implements Serializable{
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -6141069483774400912L;
+	
+//	@Id
+	private String id;
+	/**
+	 * 考试记录Data Id
+	 */
+    private Long examRecordDataId;
+    /**
+     * 大题号
+     */
+    private Integer mainNumber;
+    /**
+     * 原题ID
+     */
+    private String questionId;
+    /**
+     * 顺序
+     */
+    private Integer order;
+    /**
+     * 小题分数
+     */
+    private Double questionScore;
+    /**
+     * 小题类型
+     */
+    private QuestionType questionType;
+    /**
+     * 标准答案
+     */
+    private String correctAnswer;
+    /**
+     * 考生作答
+     */
+    private String studentAnswer;
+    /**
+     * 学生小题得分
+     */
+    private Double studentScore;
+    /**
+     * 是否作答
+     */
+    private Boolean isAnswer;
+    /**
+     * 是否标记
+     */
+    private Boolean isSign;
+    
+    /**
+	 * 选项排序值
+	 */
+	private Integer[] optionPermutation;
+	
+	/**
+	 * 音频播放次数
+	 */
+	private String audioPlayTimes;
+    
+	public ExamQuestionEntity() {}
+	
+	public String getId() {
+		return id;
+	}
+	public void setId(String id) {
+		this.id = id;
+	}
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+	public Integer getMainNumber() {
+		return mainNumber;
+	}
+	/**
+	 * 设置 大题号
+	 * @param mainNumber
+	 */
+	public void setMainNumber(Integer mainNumber) {
+		this.mainNumber = mainNumber;
+	}
+	public String getQuestionId() {
+		return questionId;
+	}
+	/**
+	 * 设置题库试题ID
+	 * @param questionId
+	 */
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+	public Integer getOrder() {
+		return order;
+	}
+	/**
+	 * 设置小题号
+	 * @param order
+	 */
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+	public String getStudentAnswer() {
+		return studentAnswer;
+	}
+	public void setStudentAnswer(String studentAnswer) {
+		this.studentAnswer = studentAnswer;
+	}
+	public Double getStudentScore() {
+		return studentScore;
+	}
+	/**
+	 * 设置考生得分
+	 * @param studentScore
+	 */
+	public void setStudentScore(Double studentScore) {
+		this.studentScore = studentScore;
+	}
+	public Double getQuestionScore() {
+		return questionScore;
+	}
+	/**
+	 * 设置试题分数
+	 * @param questionScore
+	 */
+	public void setQuestionScore(Double questionScore) {
+		this.questionScore = questionScore;
+	}
+	public QuestionType getQuestionType() {
+		return questionType;
+	}
+	/**
+	 * 设置题型
+	 * @param questionType
+	 */
+	public void setQuestionType(QuestionType questionType) {
+		this.questionType = questionType;
+	}
+	public Boolean getIsAnswer() {
+		return isAnswer;
+	}
+	public void setIsAnswer(Boolean isAnswer) {
+		this.isAnswer = isAnswer;
+	}
+	public Boolean getIsSign() {
+		return isSign;
+	}
+	public void setIsSign(Boolean isSign) {
+		this.isSign = isSign;
+	}
+
+	public String getCorrectAnswer() {
+		return correctAnswer;
+	}
+
+	public void setCorrectAnswer(String correctAnswer) {
+		this.correctAnswer = correctAnswer;
+	}
+
+	public Integer[] getOptionPermutation() {
+		return optionPermutation;
+	}
+
+	public void setOptionPermutation(Integer[] optionPermutation) {
+		this.optionPermutation = optionPermutation;
+	}
+
+	public String getAudioPlayTimes() {
+		return audioPlayTimes;
+	}
+
+	public void setAudioPlayTimes(String audioPlayTimes) {
+		this.audioPlayTimes = audioPlayTimes;
+	}
+	
+}

+ 55 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamRecordEntity.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity;
+
+/**
+ * @Description TODO
+ * @Author THINKPAD
+ * @Date 2019/7/8 11:33
+ * @Version 1.0
+ */
+public class ExamRecordEntity {
+    private Long examId;
+    private String courseCode;
+    private Long examRecordDataId;
+    private String identityNumber;
+    private Long studentId;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public String getIdentityNumber() {
+        return identityNumber;
+    }
+
+    public void setIdentityNumber(String identityNumber) {
+        this.identityNumber = identityNumber;
+    }
+
+    public Long getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Long studentId) {
+        this.studentId = studentId;
+    }
+}

+ 62 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/ExamRecordQuestionsEntity.java

@@ -0,0 +1,62 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity;
+
+//import org.springframework.data.mongodb.core.mapping.Document;
+
+//import javax.persistence.Id;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018年9月3日 上午10:46:06
+ * @company QMTH
+ * @description 考试作答试题集合
+ */
+//@Document(collection = "examRecordQuestions")
+public class ExamRecordQuestionsEntity implements Serializable {
+    private static final long serialVersionUID = -1688201571728312142L;
+
+//    @Id
+    private String id;
+
+    private Long examRecordDataId;
+
+    private Date creationTime;
+
+    private List<ExamQuestionEntity> examQuestionEntities;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public List<ExamQuestionEntity> getExamQuestionEntities() {
+        return examQuestionEntities;
+    }
+
+    public void setExamQuestionEntities(
+            List<ExamQuestionEntity> examQuestionEntities) {
+        this.examQuestionEntities = examQuestionEntities;
+    }
+
+}

+ 15 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/FixAnswerType.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity;
+
+/**
+ * 修复答案的类型
+ */
+public enum FixAnswerType {
+    /**
+     * 图片
+     */
+    PICTURE,
+    /**
+     * 音频
+     */
+    AUDIO
+}

+ 60 - 0
src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_student_answer/entity/QuestionType.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_student_answer.entity;
+
+/**
+ * 题型
+ *
+ * @author WANGWEI
+ * @date 2018年8月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum QuestionType {
+
+	/**
+	 * 单选题
+	 */
+	SINGLE_CHOICE("单选题"),
+
+	/**
+	 * 多选题
+	 */
+	MULTIPLE_CHOICE("多选题"),
+
+	/**
+	 * 填空题
+	 */
+	FILL_UP("填空题"),
+
+	/**
+	 * 问答题
+	 */
+	ESSAY("问答题"),
+
+	/**
+	 * 判断题
+	 */
+	TRUE_OR_FALSE("判断题");
+
+	// ===========================================================================
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param desc
+	 */
+	private QuestionType(String desc) {
+		this.desc = desc;
+	}
+
+	/**
+	 * @return the desc
+	 */
+	public String getDesc() {
+		return desc;
+	}
+
+}

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

@@ -0,0 +1,49 @@
+#mvc
+server.port=2008
+spring.application.name=DATA-PROCESSING-OE
+spring.main.allow-bean-definition-overriding=true
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+
+#datasource
+dsurl.host=qmth-db4.mysql.rds.aliyuncs.com
+dsurl.port=3306
+dsurl.database=exam_cloud_test
+spring.datasource.username=exam_cloud_test
+spring.datasource.password=Examcloud123
+spring.datasource.url=jdbc:mysql://${dsurl.host}:${dsurl.port}/${dsurl.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2b8&rewriteBatchedStatements=true
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
+spring.jpa.hibernate.ddl-auto=validate
+
+#druid
+spring.datasource.druid.initial-size=5
+spring.datasource.druid.max-active=15
+spring.datasource.druid.min-idle=5
+spring.datasource.druid.max-wait=2000
+spring.datasource.druid.validation-query=SELECT 1
+spring.datasource.druid.validation-query-timeout=2000
+spring.datasource.druid.test-on-borrow=false
+spring.datasource.druid.test-on-return=false
+spring.datasource.druid.test-while-idle=true
+
+#mongodb
+mguri.hostAndPortGroup=dds-wz972dde5d2d78e433270.mongodb.rds.aliyuncs.com:3717
+mguri.username=exam-cloud-dev
+mguri.password=Examcloud123
+mguri.database=admin
+spring.data.mongodb.uri=mongodb://${mguri.username}:${mguri.password}@${mguri.hostAndPortGroup}/${mguri.database}
+spring.data.mongodb.grid-fs-database=examcloud-core-oe
+spring.data.mongodb.database=examcloud-core-oe
+
+#exchange
+qmth.rootOrgId=0
+qmth.secretKey=123456
+qmth.appId=11
+qmth.server.host=192.168.1.91
+qmth.server.port=8007
+
+#fixParam's path
+fixAnswer.data.path=/home/admin/project/temp1.txt
+
+

+ 1 - 0
src/main/resources/classpath.location

@@ -0,0 +1 @@
+classpath 定位文件

+ 57 - 0
src/main/resources/log4j2.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" monitorInterval="30">
+
+	<Properties>
+		<Property name="commonLevel" value="${sys:log.commonLevel}" />
+		<Property name="logDir" value="${sys:logDir}" />
+	</Properties>
+
+	<Appenders>
+		<Console name="Console" target="SYSTEM_OUT">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} | %clr{%level} | %clr{%c{1.1}:%L}{cyan} | %X{TRACE_ID} %m%n" />
+		</Console>
+		<!-- debug 日志 -->
+		<RollingFile name="DEBUG_APPENDER" fileName="./logs/debug/debug.log" filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} | %clr{%level} | %clr{%c{1.1}:%L}{cyan} | %X{TRACE_ID} %m%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/debug" maxDepth="1">
+					<IfFileName glob="debug-*.log">
+						<IfAccumulatedFileSize exceeds="2 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+		<!-- result -->
+		<RollingFile name="RESULT_APPENDER" filePattern="./logs/result/result-%i.txt">
+			<PatternLayout pattern="%m%n" />
+			<Policies>
+				<SizeBasedTriggeringPolicy size="512 MB" />
+			</Policies>
+		</RollingFile>
+	</Appenders>
+
+	<Loggers>
+
+		<Logger name="cn.com.qmth" level="INFO" additivity="false">
+			<AppenderRef ref="DEBUG_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="RESULT_LOGGER" level="DEBUG" additivity="false">
+			<AppenderRef ref="RESULT_APPENDER" />
+		</Logger>
+
+		<Logger name="org.quartz.core" level="ERROR" />
+
+		<Root level="ERROR">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPENDER" />
+		</Root>
+
+	</Loggers>
+
+</Configuration>

+ 14 - 0
src/main/resources/sql/export_data/export_data.sql

@@ -0,0 +1,14 @@
+SELECT
+	t1.id AS exam_record_data_id,
+	t1.exam_id,
+	t5.`name` AS student_name,
+	t1.student_code,
+	t1.identity_number,
+	t4.`code` AS course_code 
+FROM
+	ec_oe_exam_record_data t1
+	LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id
+	LEFT JOIN ec_b_student t5 ON t5.id = t1.student_id 
+WHERE
+	t1.exam_id = 741 
+	AND t4.`code` = '01'

+ 14 - 0
src/main/resources/sql/get_student_one_question_answer/query_exam_record_data.sql

@@ -0,0 +1,14 @@
+SELECT
+	t1.id AS exam_record_data_id,
+	t1.exam_id,
+	t5.`name` AS student_name,
+	t1.student_code,
+	t1.identity_number,
+	t4.`code` AS course_code 
+FROM
+	ec_oe_exam_record_data t1
+	LEFT JOIN ec_b_course t4 ON t4.id = t1.course_id
+	LEFT JOIN ec_b_student t5 ON t5.id = t1.student_id 
+WHERE
+	t1.exam_id = ? 
+	AND t4.`code` = ?

+ 87 - 0
src/main/resources/temp.txt

@@ -0,0 +1,87 @@
+INSERT INTO ec_oe_exam_record_data (
+  id,
+  root_org_id,
+  org_id,
+  exam_id,
+  exam_type,
+  course_id,
+  course_level,
+  exam_student_id,
+  student_id,
+  student_code,
+  student_name,
+  identity_number,
+  base_paper_id,
+  paper_struct_id,
+  paper_type,
+  exam_record_questions_id,
+  exam_record_status,
+  start_time,
+  used_exam_time,
+  is_illegality,
+  is_warn,
+  is_audit,
+  is_reexamine,
+  is_continued,
+  continued_count,
+  is_exceed,
+  is_all_objective_paper,
+  clean_time,
+  end_time,
+  exam_order,
+  baidu_face_liveness_success_percent,
+  face_failed_count,
+  face_landmark_val,
+  face_stranger_count,
+  face_success_count,
+  face_success_percent,
+  face_total_count,
+  face_verify_result,
+  info_collector,
+  creation_time,
+  update_time
+)
+VALUES
+	(
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	?,
+	? 
+	)

+ 315 - 0
src/main/resources/temp1.txt

@@ -0,0 +1,315 @@
+1225,80000012
+1225,80000006
+1225,80000011
+1225,80000001
+1225,90000003
+1225,80003030
+1225,82721003
+1225,82721002
+1225,82721005
+1225,82722006
+1225,82722008
+1225,82722007
+1225,82721004
+1225,82722005
+1225,81422003
+1225,81422006
+1225,81422005
+1225,82721001
+1225,81921001
+1225,81421002
+1225,81422001
+1225,81921002
+1225,81421004
+1225,81421001
+1225,81422004
+1225,81421005
+1225,81421003
+1225,81422002
+1225,81921003
+1225,81922001
+1225,81922015
+1225,81921004
+1225,81921005
+1225,81922005
+1225,81922002
+1225,81922008
+1225,81922014
+1225,90000004
+1225,82622007
+1225,82622009
+1225,90522003
+1225,91323003
+1225,91322004
+1225,91322001
+1225,82621003
+1225,90522001
+1225,81411001
+1225,82621005
+1225,81412005
+1225,82622001
+1225,81411003
+1225,91323007
+1225,81411004
+1225,91323005
+1225,81411002
+1225,90522005
+1225,82621002
+1225,91322005
+1225,82621006
+1225,82621004
+1225,82621001
+1225,81511002
+1225,81511003
+1225,81511001
+1225,81411005
+1225,80311005
+1225,81412001
+1225,80311002
+1225,91923005
+1225,80311003
+1225,80311001
+1225,80922006
+1225,91922005
+1225,80311004
+1225,80911001
+1225,80912001
+1225,91922002
+1225,80911003
+1225,80911002
+1225,80921002
+1225,81512002
+1225,80921001
+1225,81512004
+1225,81511004
+1225,81511005
+1225,80921003
+1225,91722001
+1225,91722005
+1225,81911003
+1225,81911002
+1225,91722002
+1225,81911004
+1225,81911005
+1225,81912011
+1225,91722004
+1225,90822004
+1225,80811004
+1225,91723002
+1225,81111002
+1225,80211002
+1225,81111003
+1225,90822005
+1225,80811003
+1225,80211003
+1225,80211004
+1225,80212006
+1225,80211001
+1225,80921004
+1225,80911005
+1225,80911004
+1225,91313005
+1225,80811002
+1225,81111001
+1225,80912003
+1225,92223007
+1225,91312005
+1225,90822001
+1225,80811001
+1225,91312002
+1225,82011001
+1225,80921005
+1225,92523009
+1225,91312001
+1225,91312003
+1225,91313002
+1225,91312004
+1225,80811005
+1225,91313003
+1225,80411003
+1225,80812005
+1225,81911001
+1225,81912010
+1225,92223002
+1225,92223006
+1225,81912001
+1225,81912004
+1225,100108
+1225,80411001
+1225,80812009
+1225,80411002
+1225,80412002
+1225,80412006
+1225,80412009
+1225,80412007
+1225,80411004
+1225,300101
+1225,80411005
+1225,81111005
+1225,81111004
+1225,91122005
+1225,90512004
+1225,210301
+1225,92523004
+1225,90512001
+1225,81912016
+1225,80212003
+1225,80211006
+1225,80212007
+1225,92223008
+1225,80211005
+1225,91713007
+1225,81711001
+1225,92222006
+1225,82012002
+1225,90512002
+1225,91213005
+1225,90412005
+1225,81711002
+1225,81811001
+1225,81811003
+1225,81611001
+1225,81711004
+1225,81112008
+1225,90412004
+1225,81711003
+1225,90422005
+1225,90412001
+1225,92222007
+1225,91712002
+1225,91212004
+1225,91212005
+1225,90412003
+1225,82011003
+1225,82011002
+1225,91712003
+1225,92222003
+1225,90422004
+1225,91212003
+1225,91712001
+1225,91212001
+1225,81811006
+1225,90813009
+1225,90422003
+1225,81811005
+1225,81712007
+1225,92522003
+1225,81712006
+1225,81712001
+1225,90122006
+1225,82011005
+1225,82011004
+1225,92222005
+1225,90213006
+1225,81711005
+1225,90122004
+1225,81811004
+1225,90213004
+1225,81712002
+1225,90212005
+1225,90212001
+1225,90122005
+1225,90312006
+1225,80611004
+1225,81712005
+1225,81711006
+1225,90122001
+1225,81712004
+1225,90222003
+1225,80611001
+1225,90212004
+1225,90222005
+1225,80611005
+1225,91412005
+1225,90812004
+1225,91412004
+1225,90812002
+1225,80612001
+1225,90812003
+1225,90112002
+1225,90312003
+1225,90312005
+1225,90223004
+1225,90812005
+1225,91413001
+1225,80612003
+1225,92922004
+1225,90312004
+1225,90313002
+1225,90222002
+1225,80611003
+1225,90112004
+1225,90122009
+1225,80611002
+1225,82012003
+1225,90222001
+1225,90313001
+1225,90112003
+1225,90112005
+1225,92922001
+1225,91412002
+1225,92213003
+1225,90122008
+1225,92213005
+1225,90112006
+1225,90122007
+1225,90112001
+1225,92212002
+1225,92213006
+1225,92212005
+1225,92922005
+1225,82411001
+1225,90112007
+1225,80711002
+1225,91413006
+1225,80612006
+1225,82411002
+1225,80111001
+1225,82411003
+1225,91022006
+1225,82412001
+1225,80711001
+1225,82412005
+1225,81612002
+1225,80711003
+1225,82211005
+1225,81611005
+1225,90912007
+1225,81612004
+1225,81611004
+1225,90912006
+1225,92922002
+1225,80712009
+1225,91623003
+1225,80111004
+1225,80111005
+1225,80712003
+1225,82411005
+1225,80111002
+1225,80111003
+1225,80711005
+1225,81611003
+1225,82411004
+1225,80111006
+1225,81611002
+1225,80711004
+1225,91622005
+1225,82412003
+1225,82412002
+1225,80712002
+1225,91112002
+1225,91112005
+1225,80512001
+1225,91112003
+1225,90912009
+1225,80511004
+1225,80511005
+1225,82211001
+1225,82211002
+1225,82412008
+1225,82412007
+1225,91112001
+1225,82311004
+1225,82311005
+1225,82311002
+1225,82312008

+ 33 - 0
src/main/resources/upyun.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sites>
+
+	<site>
+		<!--ID -->
+		<id>test</id>
+		<!--名称 -->
+		<name>测试照片</name>
+		<!--又拍云账户ID标识 -->
+		<upyunId>1</upyunId>
+		<!--文件大小限制(IO流限制) -->
+		<maxSize>1M</maxSize>
+		<!--又拍云存储路径 -->
+		<path>/test/${rootOrgId}/${timeMillis}${fileSuffix}</path>
+	</site>
+
+	<site>
+		<id>photoAnswer</id>
+		<name>学生作答照片</name>
+		<upyunId>1</upyunId>
+		<maxSize>1M</maxSize>
+		<path>/oe-answer-file/${userId}/${ext1}/${ext2}/${userId}_${ext1}_${ext2}_${timeMillis}${fileSuffix}</path>
+	</site>
+
+	<site>
+		<id>audioAnswer</id>
+		<name>学生作答音频</name>
+		<upyunId>1</upyunId>
+		<maxSize>1M</maxSize>
+		<path>/oe-answer-file/${userId}/${ext1}/${ext2}/${userId}_${ext1}_${ext2}_${timeMillis}${fileSuffix}</path>
+	</site>
+
+</sites>

+ 39 - 0
src/test/java/cn/com/qmth/dp/examcloud/oe/test/ExportTest.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.dp.examcloud.oe.test;
+
+import cn.com.qmth.dp.examcloud.oe.Tianji2App;
+import cn.com.qmth.dp.examcloud.oe.modules.export_exam_student_score.ExportExamStudentScore;
+import cn.com.qmth.dp.examcloud.oe.modules.get_student_answer_detail.GetStduentAnswerDetailService;
+import cn.com.qmth.dp.examcloud.oe.modules.get_student_one_question_answer.GetStduentOneAnswerService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Tianji2App.class)
+public class ExportTest {
+
+    @Autowired
+    private ExportExamStudentScore exportExamStudentScore;
+
+    @Autowired
+    private GetStduentOneAnswerService studentOneAnswerService;
+
+    @Autowired
+    private GetStduentAnswerDetailService studentAnswerDetailService;
+
+    @Test
+    public void exportTest() throws Exception {
+        // 导出 - 小题分
+        // exportExamStudentScore.start();
+
+        // 导出 - 考生作答结果(带手机号)
+        // studentOneAnswerService.start(1213L, 1, "01");
+        // studentOneAnswerService.start(1213L, 1, "28");
+
+        // 导出 - 考生得分明细
+        // studentAnswerDetailService.start(1606L, "410901031");
+    }
+
+}