瀏覽代碼

修复网考标准答案,并重新计算分数

lideyin 5 年之前
父節點
當前提交
fb8ce8f22d
共有 18 個文件被更改,包括 1546 次插入65 次删除
  1. 59 45
      src/main/java/cn/com/qmth/dp/examcloud/oe/Task.java
  2. 1 1
      src/main/java/cn/com/qmth/dp/examcloud/oe/Tianji2App.java
  3. 29 5
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/get_student_answer_detail/GetStduentAnswerDetailService.java
  4. 331 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/FixCorrectAnswerAndResetScoreService.java
  5. 196 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamQuestionEntity.java
  6. 51 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordDataEntity.java
  7. 68 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamRecordQuestionsEntity.java
  8. 105 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExamScoreEntity.java
  9. 15 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/ExchangeBean.java
  10. 24 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerReq.java
  11. 27 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/entity/OuterGetQuestionAnswerResp.java
  12. 31 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseRequest.java
  13. 44 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/EnterpriseResponse.java
  14. 16 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/exchange/JsonSerializable.java
  15. 58 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/FormFilePart.java
  16. 299 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/OKHttpUtil.java
  17. 156 0
      src/main/java/cn/com/qmth/dp/examcloud/oe/modules/update_correct_answer/util/QmthUtil.java
  18. 36 14
      src/main/resources/application.properties

+ 59 - 45
src/main/java/cn/com/qmth/dp/examcloud/oe/Task.java

@@ -1,9 +1,13 @@
 package cn.com.qmth.dp.examcloud.oe;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
+import cn.com.qmth.dp.examcloud.oe.modules.get_student_answer_detail.GetStduentAnswerDetailService;
+import cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.FixCorrectAnswerAndResetScoreService;
 import org.apache.commons.lang3.RandomUtils;
 import org.bson.Document;
 import org.springframework.data.mongodb.core.MongoTemplate;
@@ -33,58 +37,68 @@ import cn.com.qmth.examcloud.web.upyun.UpyunService;
 @Component
 public class Task {
 
-	private static ExamCloudLog log = ExamCloudLogFactory.getLog(Task.class);
+    private static ExamCloudLog log = ExamCloudLogFactory.getLog(Task.class);
 
-	/**
-	 * 方法注释
-	 *
-	 * @author WANGWEI
-	 */
-	public void start() {
-		try {
+    /**
+     * 方法注释
+     *
+     * @author WANGWEI
+     */
+    public void start() {
+        try {
+            FixCorrectAnswerAndResetScoreService bean = SpringContextHolder.getBean(FixCorrectAnswerAndResetScoreService.class);
+            bean.start(19,"03013750");
 
-			ExportData bean = SpringContextHolder.getBean(ExportData.class);
-			bean.start();
+/*//			ExportData bean = SpringContextHolder.getBean(ExportData.class);
+            GetStduentAnswerDetailService bean = SpringContextHolder.getBean(GetStduentAnswerDetailService.class);
+//			bean.start(1173L,"9999");
+            bean.start(1191L, "100", "200", "101", "201");
+			bean.start(1193L, "302", "303", "304", "305","306");
+			bean.start(1192L, "300", "301", "102", "202",
+					"103","203","104","204","105","205","106","206","401");
+//			bean.start(764L, "A149","A150","A154");
+//			bean.start(765L, "C149","C150","C154");
+//			bean.start(766L, "B149","B150","B154");*/
 
-		} catch (Exception e) {
-			log.error("unexpected", e);
-			throw new RuntimeException(e);
-		}
+        } catch (Exception e) {
+            log.error("unexpected", e);
+            throw new RuntimeException(e);
+        }
 
-	}
+    }
 
-	/**
-	 * 连接测试
-	 *
-	 * @author WANGWEI
-	 */
-	public static void test() {
+    /**
+     * 连接测试
+     *
+     * @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));
+        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));
+        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();
+        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));
-		}
-	}
+            System.out.println("_id=" + next.getObjectId("_id"));
+            System.out.println(JsonUtil.toPrettyJson(next));
+        }
+    }
 }

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

@@ -42,7 +42,7 @@ public class Tianji2App {
 	public static void main(String[] args) {
 		SpringApplication.run(Tianji2App.class, args);
 
-		UpyunSiteManager.init();
+//		UpyunSiteManager.init();
 
 		Task task = SpringContextHolder.getBean(Task.class);
 		task.start();

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

@@ -49,7 +49,7 @@ public class GetStduentAnswerDetailService {
 
 	private int sqlIndex = 1;
 
-	private boolean mustHaveScore = true;
+	private boolean mustHaveScore = false;
 
 	public void start(Long examId, String... courseCodeLIst) throws Exception {
 		for (String courseCode : courseCodeLIst) {
@@ -61,8 +61,24 @@ public class GetStduentAnswerDetailService {
 	public void start(Long examId, String courseCode) throws Exception {
 		String packageName = this.getClass().getPackage().getName();
 		String packagePath = packageName.replaceAll("\\.", "/");
-		String sql = ResourceLoader
-				.getResource(packagePath + "/query_exam_record_data_" + sqlIndex + ".sql");
+		//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);
 
@@ -208,8 +224,16 @@ public class GetStduentAnswerDetailService {
 				System.out.println(JsonUtil.toJson(objects));
 			}
 
-			String sql4QueryExamStudentInfo = ResourceLoader
-					.getResource(packagePath + "/query_exam_student_info.sql");
+//			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);
 

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

@@ -0,0 +1,331 @@
+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 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.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(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) {
+            totalObjectiveScore += eq.getStudentScore();
+        }
+
+        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);
+
+                        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();
+    }
+
+}

+ 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;
+	}
+}

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

@@ -0,0 +1,51 @@
+package cn.com.qmth.dp.examcloud.oe.modules.update_correct_answer.entity;
+
+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;
+
+    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;
+    }
+}

+ 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();
+	}
+
+}

+ 36 - 14
src/main/resources/application.properties

@@ -8,11 +8,12 @@ spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
 spring.jackson.time-zone=GMT+8
 
 #datasource
-dsurl.host=192.168.10.30
+#dsurl.host=qmth-db1.mysql.rds.aliyuncs.com
+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=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
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -30,24 +31,45 @@ spring.datasource.druid.test-on-return=false
 spring.datasource.druid.test-while-idle=true
 
 #mongodb
-#mguri.username=exam_cloud
-#mguri.password=XXX
-mguri.hostAndPortGroup=192.168.10.30:27017
-mguri.database=examcloud-core-oe
+mguri.username=root
+mguri.password=qmth87863577%21%40%23
+mguri.hostAndPortGroup=dds-wz972dde5d2d78e433270.mongodb.rds.aliyuncs.com:3717
+mguri.database=admin
+spring.data.mongodb.uri=mongodb://${mguri.username}:${mguri.password}@${mguri.hostAndPortGroup}/${mguri.database}
+#
+#mguri.username=root
+#mguri.password=qmth87863577!%40#
+#mguri.hostAndPortGroup=localhost:3717
+#mguri.database=admin
+##spring.data.mongodb.uri=mongodb://${mguri.username}:${mguri.password}@${mguri.hostAndPortGroup}/${mguri.database}?replicaSet=mgset-2892097
+#spring.data.mongodb.uri=mongodb://${mguri.username}:${mguri.password}@${mguri.hostAndPortGroup}/${mguri.database}
+
+
+##mongodb
+#mguri.username=root
+#mguri.password=qmth87863577!%40#
+#mguri.hostAndPortGroup=localhost:3717
+#mguri.database=admin
 #mguri.maxPoolSize=10
-
+#
 #spring.data.mongodb.uri=mongodb://${mguri.username}:${mguri.password}@${mguri.hostAndPortGroup}/${mguri.database}?replicaSet=mgset-2892097&maxPoolSize=${mguri.maxPoolSize}&maxIdleTimeMS=6000
-spring.data.mongodb.uri=mongodb://${mguri.hostAndPortGroup}/${mguri.database}
+##spring.data.mongodb.uri=mongodb://${mguri.hostAndPortGroup}/${mguri.database}
 
 spring.data.mongodb.grid-fs-database=examcloud-core-oe
 spring.data.mongodb.database=examcloud-core-oe
 
-#upyun
-$upyun.site.1.bucketName=exam-cloud-test
-$upyun.site.1.userName=examcloud
-$upyun.site.1.domain=https://ecs-test-static.qmth.com.cn 
-$upyun.site.1.password=ecs87863577!@#
-
+##upyun
+#$upyun.site.1.bucketName=exam-cloud-test
+#$upyun.site.1.userName=examcloud
+#$upyun.site.1.domain=https://ecs-test-static.qmth.com.cn
+#$upyun.site.1.password=ecs87863577!@#
+
+#exchange invoke
+qmth.rootOrgId=0
+qmth.secretKey=123456
+qmth.appId=11
+qmth.server.host=192.168.1.91
+qmth.server.port=8007