瀏覽代碼

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	themis-business/src/main/java/com/qmth/themis/business/cache/bean/ExamRecordCacheBean.java
#	themis-business/src/main/java/com/qmth/themis/business/enums/FinishExamTypeEnum.java
#	themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java
#	themis-task/src/main/java/com/qmth/themis/task/quartz/ExamActivityJob.java
wangliang 4 年之前
父節點
當前提交
dcde44fcd2

+ 53 - 0
themis-business/src/main/java/com/qmth/themis/business/bean/exam/ExamFinishBean.java

@@ -0,0 +1,53 @@
+package com.qmth.themis.business.bean.exam;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@ApiModel("结束考试返回信息")
+public class ExamFinishBean {
+
+	@ApiModelProperty("finishTime")
+	private Long finishTime;
+
+	@ApiModelProperty("交卷后状态")
+	private String status;
+
+	@ApiModelProperty("客观分")
+	private Double objectiveScore;
+
+	@ApiModelProperty("审核结果")
+	private String reviewResult;
+
+	public Long getFinishTime() {
+		return finishTime;
+	}
+
+	public void setFinishTime(Long finishTime) {
+		this.finishTime = finishTime;
+	}
+
+	public String getStatus() {
+		return status;
+	}
+
+	public void setStatus(String status) {
+		this.status = status;
+	}
+
+	public Double getObjectiveScore() {
+		return objectiveScore;
+	}
+
+	public void setObjectiveScore(Double objectiveScore) {
+		this.objectiveScore = objectiveScore;
+	}
+
+	public String getReviewResult() {
+		return reviewResult;
+	}
+
+	public void setReviewResult(String reviewResult) {
+		this.reviewResult = reviewResult;
+	}
+
+}

+ 45 - 0
themis-business/src/main/java/com/qmth/themis/business/cache/ExamRecordCacheUtil.java

@@ -0,0 +1,45 @@
+package com.qmth.themis.business.cache;
+
+import java.util.Date;
+
+import com.qmth.themis.business.constant.SpringContextHolder;
+import com.qmth.themis.business.enums.ExamRecordStatusEnum;
+import com.qmth.themis.business.util.RedisUtil;
+
+/**
+ * 考试记录缓存hash值操作
+ * 
+ * @Description:
+ * @Author: xiatian
+ * @Date: 2020-07-29
+ */
+public class ExamRecordCacheUtil {
+	private static RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
+
+	public static Long getId(Long recordId) {
+		return (Long) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "id");
+	}
+
+	public static Long getExamStudentId(Long recordId) {
+		return (Long) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "examStudentId");
+	}
+
+	public static Long getPaperId(Long recordId) {
+		return (Long) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "paperId");
+	}
+	public static void setFirstStartTime(Long recordId,Date date) {
+		redisUtil.set(RedisKeyHelper.examRecordCacheKey(recordId), "firstStartTime",date);
+	}
+	public static void setStatus(Long recordId,ExamRecordStatusEnum status) {
+		redisUtil.set(RedisKeyHelper.examRecordCacheKey(recordId), "status",status);
+	}
+	
+	public static Integer getDurationSeconds(Long recordId) {
+		return (Integer) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "surationSeconds");
+	}
+	
+	public static Double getObjectiveScore(Long recordId) {
+		return (Double) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "objectiveScore");
+	}
+	
+}

+ 0 - 485
themis-business/src/main/java/com/qmth/themis/business/cache/bean/ExamRecordCacheBean.java

@@ -1,485 +0,0 @@
-package com.qmth.themis.business.cache.bean;
-
-import com.qmth.themis.business.enums.EntryAuthenticationPolicyEnum;
-import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.FinishTypeEnum;
-import com.qmth.themis.business.enums.ReviewResultEnum;
-
-import java.io.Serializable;
-import java.util.Date;
-
-public class ExamRecordCacheBean implements Serializable {
-
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 4130111410312390960L;
-
-	//主键
-    private Long id;
-
-    //考试批次ID
-    private Long examId;
-
-    //考试场次ID
-    private Long examActivityId;
-
-    //考生ID
-    private Long examStudentId;
-
-    //实际使用的试卷ID
-    private Long paperId;
-
-    //全部作答保存地址
-    private String answerPath;
-
-    //当前状态
-    private ExamRecordStatusEnum status;
-
-    //首次进入候考时间
-    private Date firstPrepareTime;
-
-    //首次开考时间
-    private Date firstStartTime;
-
-    //最近断点时间
-    private Date lastBreakTime;
-
-    //最近恢复候考时间
-    private Date lastPrepareTime;
-
-    //最近恢复开考时间
-    private Date lastStartTime;
-
-    //剩余断点续考次数
-    private Integer leftBreakResumeCount;
-
-    //客户端当前IP地址
-    private String clientCurrentIp;
-
-    //客户端websocket状态
-    private Integer clientWebsocketStatus;
-
-    //客户端websocket连接标识
-    private String clientWebsocketId;
-
-    //客户端最近同步时间
-    private Date clientLastSyncTime;
-
-    //微信小程序websocket状态
-    private Integer wxappWebsocketStatus;
-
-    //微信小程序websocket连接标识
-    private String wxappWebsocketId;
-
-    //微信小程序最近同步时间
-    private Date wxappLastSyncTime;
-
-    //客户端推流状态
-    private Integer clientVideoPushStatus;
-
-    //客户端推流地址
-    private String clientVideoPushKey;
-
-    //微信小程序推流状态
-    private Integer wxappVideoPushStatus;
-
-    //微信小程序推流地址
-    private String wxappVideoPushKey;
-
-    //答题进度
-    private Double answerProgress;
-
-    //累计考试用时
-    private Integer durationSeconds;
-
-    //交卷时间
-    private Date finishTime;
-
-    //交卷原因
-    private FinishTypeEnum finishType;
-
-    //预警次数
-    private Integer warningCount;
-
-    //审核结果
-    private ReviewResultEnum reviewResult;
-
-    //客观题分数
-    private Double objectiveScore;
-
-    //试题下载,0:是,1:不是
-    private Integer paperDownload;
-
-    //是否违纪,0:是,1:不是
-    private Integer breachStatus;
-
-    //个人试卷结构
-    private String paperStruct;
-
-    //个人试卷结构是否已上传,0:未上传,1:已上传
-    private Integer paperStructUpload;
-
-    //第几次考试
-    private Integer serialNumber;
-
-    //最近断点记录ID
-    private Long lastBreakId;
-
-    //开考身份验证结果
-    private EntryAuthenticationPolicyEnum entryAuthenticationResult;
-
-    //开考身份验证记录ID
-    private Long entryAuthenticationId;
-
-    //当前过程人脸检测状态
-    private Integer inProcessFaceVerifyStatus;
-
-    //当前过程活体验证状态
-    private Integer inProcessLivenessVerifyStatus;
-
-    //已完成过程活体验证次数
-    private Integer inProcessLivenessVerifyCount;
-
-	public Long getId() {
-		return id;
-	}
-
-	public void setId(Long id) {
-		this.id = id;
-	}
-
-	public Long getExamId() {
-		return examId;
-	}
-
-	public void setExamId(Long examId) {
-		this.examId = examId;
-	}
-
-	public Long getExamActivityId() {
-		return examActivityId;
-	}
-
-	public void setExamActivityId(Long examActivityId) {
-		this.examActivityId = examActivityId;
-	}
-
-	public Long getExamStudentId() {
-		return examStudentId;
-	}
-
-	public void setExamStudentId(Long examStudentId) {
-		this.examStudentId = examStudentId;
-	}
-
-	public Long getPaperId() {
-		return paperId;
-	}
-
-	public void setPaperId(Long paperId) {
-		this.paperId = paperId;
-	}
-
-	public String getAnswerPath() {
-		return answerPath;
-	}
-
-	public void setAnswerPath(String answerPath) {
-		this.answerPath = answerPath;
-	}
-
-	public ExamRecordStatusEnum getStatus() {
-		return status;
-	}
-
-	public void setStatus(ExamRecordStatusEnum status) {
-		this.status = status;
-	}
-
-	public static long getSerialVersionUID() {
-		return serialVersionUID;
-	}
-
-	public Date getFirstPrepareTime() {
-		return firstPrepareTime;
-	}
-
-	public void setFirstPrepareTime(Date firstPrepareTime) {
-		this.firstPrepareTime = firstPrepareTime;
-	}
-
-	public Date getFirstStartTime() {
-		return firstStartTime;
-	}
-
-	public void setFirstStartTime(Date firstStartTime) {
-		this.firstStartTime = firstStartTime;
-	}
-
-	public Date getLastBreakTime() {
-		return lastBreakTime;
-	}
-
-	public void setLastBreakTime(Date lastBreakTime) {
-		this.lastBreakTime = lastBreakTime;
-	}
-
-	public Date getLastPrepareTime() {
-		return lastPrepareTime;
-	}
-
-	public void setLastPrepareTime(Date lastPrepareTime) {
-		this.lastPrepareTime = lastPrepareTime;
-	}
-
-	public Date getLastStartTime() {
-		return lastStartTime;
-	}
-
-	public void setLastStartTime(Date lastStartTime) {
-		this.lastStartTime = lastStartTime;
-	}
-
-	public Integer getLeftBreakResumeCount() {
-		return leftBreakResumeCount;
-	}
-
-	public void setLeftBreakResumeCount(Integer leftBreakResumeCount) {
-		this.leftBreakResumeCount = leftBreakResumeCount;
-	}
-
-	public String getClientCurrentIp() {
-		return clientCurrentIp;
-	}
-
-	public void setClientCurrentIp(String clientCurrentIp) {
-		this.clientCurrentIp = clientCurrentIp;
-	}
-
-	public Integer getClientWebsocketStatus() {
-		return clientWebsocketStatus;
-	}
-
-	public void setClientWebsocketStatus(Integer clientWebsocketStatus) {
-		this.clientWebsocketStatus = clientWebsocketStatus;
-	}
-
-	public String getClientWebsocketId() {
-		return clientWebsocketId;
-	}
-
-	public void setClientWebsocketId(String clientWebsocketId) {
-		this.clientWebsocketId = clientWebsocketId;
-	}
-
-	public Date getClientLastSyncTime() {
-		return clientLastSyncTime;
-	}
-
-	public void setClientLastSyncTime(Date clientLastSyncTime) {
-		this.clientLastSyncTime = clientLastSyncTime;
-	}
-
-	public Integer getWxappWebsocketStatus() {
-		return wxappWebsocketStatus;
-	}
-
-	public void setWxappWebsocketStatus(Integer wxappWebsocketStatus) {
-		this.wxappWebsocketStatus = wxappWebsocketStatus;
-	}
-
-	public String getWxappWebsocketId() {
-		return wxappWebsocketId;
-	}
-
-	public void setWxappWebsocketId(String wxappWebsocketId) {
-		this.wxappWebsocketId = wxappWebsocketId;
-	}
-
-	public Date getWxappLastSyncTime() {
-		return wxappLastSyncTime;
-	}
-
-	public void setWxappLastSyncTime(Date wxappLastSyncTime) {
-		this.wxappLastSyncTime = wxappLastSyncTime;
-	}
-
-	public Integer getClientVideoPushStatus() {
-		return clientVideoPushStatus;
-	}
-
-	public void setClientVideoPushStatus(Integer clientVideoPushStatus) {
-		this.clientVideoPushStatus = clientVideoPushStatus;
-	}
-
-	public String getClientVideoPushKey() {
-		return clientVideoPushKey;
-	}
-
-	public void setClientVideoPushKey(String clientVideoPushKey) {
-		this.clientVideoPushKey = clientVideoPushKey;
-	}
-
-	public Integer getWxappVideoPushStatus() {
-		return wxappVideoPushStatus;
-	}
-
-	public void setWxappVideoPushStatus(Integer wxappVideoPushStatus) {
-		this.wxappVideoPushStatus = wxappVideoPushStatus;
-	}
-
-	public String getWxappVideoPushKey() {
-		return wxappVideoPushKey;
-	}
-
-	public void setWxappVideoPushKey(String wxappVideoPushKey) {
-		this.wxappVideoPushKey = wxappVideoPushKey;
-	}
-
-	public Double getAnswerProgress() {
-		return answerProgress;
-	}
-
-	public void setAnswerProgress(Double answerProgress) {
-		this.answerProgress = answerProgress;
-	}
-
-	public Integer getDurationSeconds() {
-		return durationSeconds;
-	}
-
-	public void setDurationSeconds(Integer durationSeconds) {
-		this.durationSeconds = durationSeconds;
-	}
-
-	public Date getFinishTime() {
-		return finishTime;
-	}
-
-	public void setFinishTime(Date finishTime) {
-		this.finishTime = finishTime;
-	}
-
-	public FinishTypeEnum getFinishType() {
-		return finishType;
-	}
-
-	public void setFinishType(FinishTypeEnum finishType) {
-		this.finishType = finishType;
-	}
-
-	public Integer getWarningCount() {
-		return warningCount;
-	}
-
-	public void setWarningCount(Integer warningCount) {
-		this.warningCount = warningCount;
-	}
-
-	public ReviewResultEnum getReviewResult() {
-		return reviewResult;
-	}
-
-	public void setReviewResult(ReviewResultEnum reviewResult) {
-		this.reviewResult = reviewResult;
-	}
-
-	public Double getObjectiveScore() {
-		return objectiveScore;
-	}
-
-	public void setObjectiveScore(Double objectiveScore) {
-		this.objectiveScore = objectiveScore;
-	}
-
-	public Integer getPaperDownload() {
-		return paperDownload;
-	}
-
-	public void setPaperDownload(Integer paperDownload) {
-		this.paperDownload = paperDownload;
-	}
-
-	public Integer getBreachStatus() {
-		return breachStatus;
-	}
-
-	public void setBreachStatus(Integer breachStatus) {
-		this.breachStatus = breachStatus;
-	}
-
-	public String getPaperStruct() {
-		return paperStruct;
-	}
-
-	public void setPaperStruct(String paperStruct) {
-		this.paperStruct = paperStruct;
-	}
-
-	public Integer getPaperStructUpload() {
-		return paperStructUpload;
-	}
-
-	public void setPaperStructUpload(Integer paperStructUpload) {
-		this.paperStructUpload = paperStructUpload;
-	}
-
-	public Integer getSerialNumber() {
-		return serialNumber;
-	}
-
-	public void setSerialNumber(Integer serialNumber) {
-		this.serialNumber = serialNumber;
-	}
-
-	public Long getLastBreakId() {
-		return lastBreakId;
-	}
-
-	public void setLastBreakId(Long lastBreakId) {
-		this.lastBreakId = lastBreakId;
-	}
-
-	public EntryAuthenticationPolicyEnum getEntryAuthenticationResult() {
-		return entryAuthenticationResult;
-	}
-
-	public void setEntryAuthenticationResult(EntryAuthenticationPolicyEnum entryAuthenticationResult) {
-		this.entryAuthenticationResult = entryAuthenticationResult;
-	}
-
-	public Long getEntryAuthenticationId() {
-		return entryAuthenticationId;
-	}
-
-	public void setEntryAuthenticationId(Long entryAuthenticationId) {
-		this.entryAuthenticationId = entryAuthenticationId;
-	}
-
-	public Integer getInProcessFaceVerifyStatus() {
-		return inProcessFaceVerifyStatus;
-	}
-
-	public void setInProcessFaceVerifyStatus(Integer inProcessFaceVerifyStatus) {
-		this.inProcessFaceVerifyStatus = inProcessFaceVerifyStatus;
-	}
-
-	public Integer getInProcessLivenessVerifyStatus() {
-		return inProcessLivenessVerifyStatus;
-	}
-
-	public void setInProcessLivenessVerifyStatus(Integer inProcessLivenessVerifyStatus) {
-		this.inProcessLivenessVerifyStatus = inProcessLivenessVerifyStatus;
-	}
-
-	public Integer getInProcessLivenessVerifyCount() {
-		return inProcessLivenessVerifyCount;
-	}
-
-	public void setInProcessLivenessVerifyCount(Integer inProcessLivenessVerifyCount) {
-		this.inProcessLivenessVerifyCount = inProcessLivenessVerifyCount;
-	}
-
-    
-}

+ 35 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/FinishExamResultEnum.java

@@ -0,0 +1,35 @@
+package com.qmth.themis.business.enums;
+
+/**结束考试后返回页面类型
+ * @Description: 
+ * @Author: xiatian
+ * @Date: 2020-07-29
+ */
+public enum FinishExamResultEnum {
+	/**
+	 * 分数计算中
+	 */
+	SCORE_CALCULATE("分数计算中"),
+	/**
+	 * 待审核
+	 */
+	AUDITING("待审核"),
+	/**
+	 * 分数显示
+	 */
+	SCORE_VIEW("分数显示"),
+	/**
+	 * 正常交卷
+	 */
+	NORMAL("正常交卷");
+
+	private String title;
+
+	private FinishExamResultEnum(String title) {
+		this.title = title;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+}

+ 33 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/FinishExamTypeEnum.java

@@ -0,0 +1,33 @@
+package com.qmth.themis.business.enums;
+
+/**
+ * @Description: 结束考试 enum
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/27
+ */
+public enum FinishExamTypeEnum {
+	/**
+	 * 手动
+	 */
+	MANUAL("手动"),
+	/**
+	 * 自动
+	 */
+	AUTO("自动"),
+	/**
+	 * 监考强制
+	 */
+	INTERRUPT("监考强制");
+
+	private String title;
+
+	private FinishExamTypeEnum(String title) {
+		this.title = title;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+}

+ 10 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TEExamService.java

@@ -8,6 +8,7 @@ import org.springframework.web.multipart.MultipartFile;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
+import com.qmth.themis.business.bean.exam.ExamFinishBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
@@ -116,4 +117,13 @@ public interface TEExamService extends IService<TEExam> {
 	 * @return
 	 * @return
 	 */
 	 */
 	public ExamResumeBean resume(Long studentId, Long recordId);
 	public ExamResumeBean resume(Long studentId, Long recordId);
+
+	/**结束考试
+	 * @param id
+	 * @param recordId
+	 * @param type
+	 * @param durationSeconds
+	 * @return
+	 */
+	public ExamFinishBean finish(Long studentId, Long recordId, String type, Integer durationSeconds);
 }
 }

+ 101 - 53
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -14,7 +14,6 @@ import java.util.UUID;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 
 
-import com.qmth.themis.business.enums.ExamRecordStatusEnum;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -24,20 +23,22 @@ import org.springframework.web.multipart.MultipartFile;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
+import com.qmth.themis.business.bean.exam.ExamFinishBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
+import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.cache.RedisKeyHelper;
 import com.qmth.themis.business.cache.RedisKeyHelper;
 import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
 import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
 import com.qmth.themis.business.cache.bean.ExamCourseCacheBean;
 import com.qmth.themis.business.cache.bean.ExamCourseCacheBean;
 import com.qmth.themis.business.cache.bean.ExamPaperCacheBean;
 import com.qmth.themis.business.cache.bean.ExamPaperCacheBean;
-import com.qmth.themis.business.cache.bean.ExamRecordCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentAnswerCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentAnswerCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentPaperStructCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentPaperStructCacheBean;
 import com.qmth.themis.business.config.SystemConfig;
 import com.qmth.themis.business.config.SystemConfig;
 import com.qmth.themis.business.dao.TEExamMapper;
 import com.qmth.themis.business.dao.TEExamMapper;
 import com.qmth.themis.business.entity.TEExam;
 import com.qmth.themis.business.entity.TEExam;
+import com.qmth.themis.business.enums.ExamRecordStatusEnum;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamCourseService;
 import com.qmth.themis.business.service.TEExamCourseService;
 import com.qmth.themis.business.service.TEExamPaperService;
 import com.qmth.themis.business.service.TEExamPaperService;
@@ -234,22 +235,24 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 		return -1;
 		return -1;
 	}
 	}
 
 
-	@Transactional
 	@Override
 	@Override
 	public ExamStartBean start(Long studentId, Long recordId) {
 	public ExamStartBean start(Long studentId, Long recordId) {
 		ExamStartBean ret = null;
 		ExamStartBean ret = null;
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
+
 		Long activityId = es.getExamActivityId();
 		Long activityId = es.getExamActivityId();
 		ExamActivityCacheBean ac = teExamActivityService.getExamActivityCacheBean(activityId);
 		ExamActivityCacheBean ac = teExamActivityService.getExamActivityCacheBean(activityId);
 		if (ac == null) {
 		if (ac == null) {
@@ -265,11 +268,8 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 			throw new BusinessException("允许开考的时间已结束");
 			throw new BusinessException("允许开考的时间已结束");
 		}
 		}
 
 
-		// 修改考试记录数据
-		er.setFirstStartTime(new Date());
-		er.setStatus(ExamRecordStatusEnum.break_off);
-
-		ExamPaperCacheBean ep = teExamPaperService.getExamPaperCacheBean(er.getPaperId());
+		Long paperId = ExamRecordCacheUtil.getPaperId(recordId);
+		ExamPaperCacheBean ep = teExamPaperService.getExamPaperCacheBean(paperId);
 		if (ep == null) {
 		if (ep == null) {
 			throw new BusinessException("未找到试卷");
 			throw new BusinessException("未找到试卷");
 		}
 		}
@@ -279,50 +279,57 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 		ret.setPaperDecryptVector(ep.getDecryptVector());
 		ret.setPaperDecryptVector(ep.getDecryptVector());
 
 
 		// 更新考试记录缓存
 		// 更新考试记录缓存
-		redisUtil.set(RedisKeyHelper.examRecordCacheKey(recordId), er);
+		ExamRecordCacheUtil.setFirstStartTime(recordId, new Date());
+		ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.first_prepare);
 
 
 		return ret;
 		return ret;
 	}
 	}
 
 
-	@Transactional
 	@Override
 	@Override
 	public Long studentPaperStruct(Long studentId, Long recordId, String content) {
 	public Long studentPaperStruct(Long studentId, Long recordId, String content) {
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
+		
+
 		ExamStudentPaperStructCacheBean struct = new ExamStudentPaperStructCacheBean();
 		ExamStudentPaperStructCacheBean struct = new ExamStudentPaperStructCacheBean();
 		struct.setContent(content);
 		struct.setContent(content);
-		struct.setTime(System.currentTimeMillis());
+		struct.setTime(new Date().getTime());
 		// 更新考生试卷结构
 		// 更新考生试卷结构
 		redisUtil.set(RedisKeyHelper.studentPaperStructKey(recordId), struct);
 		redisUtil.set(RedisKeyHelper.studentPaperStructKey(recordId), struct);
 		return struct.getTime();
 		return struct.getTime();
 	}
 	}
 
 
-	@Transactional
 	@Override
 	@Override
 	public Long answerSubmit(Long studentId, Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex,
 	public Long answerSubmit(Long studentId, Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex,
 			String answer, Long version, Integer durationSeconds) {
 			String answer, Long version, Integer durationSeconds) {
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
+		
+
 		ExamStudentAnswerCacheBean answerCache = (ExamStudentAnswerCacheBean) redisUtil.get(
 		ExamStudentAnswerCacheBean answerCache = (ExamStudentAnswerCacheBean) redisUtil.get(
 				RedisKeyHelper.examAnswerKey(recordId),
 				RedisKeyHelper.examAnswerKey(recordId),
 				RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex));
 				RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex));
@@ -348,21 +355,24 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 		return version;
 		return version;
 	}
 	}
 
 
-	@Transactional
 	@Override
 	@Override
 	public Integer audioLeftPlayCountSubmit(Long studentId, Long recordId, String key, Integer count) {
 	public Integer audioLeftPlayCountSubmit(Long studentId, Long recordId, String key, Integer count) {
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
+		
+
 		// 音频剩余播放次数缓存
 		// 音频剩余播放次数缓存
 		redisUtil.set(RedisKeyHelper.audioLeftPlayCountKey(recordId), key, count);
 		redisUtil.set(RedisKeyHelper.audioLeftPlayCountKey(recordId), key, count);
 
 
@@ -371,31 +381,35 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 
 
 	@Override
 	@Override
 	public ExamFileUploadBean fileUpload(Long studentId, Long recordId, MultipartFile file, String suffix, String md5) {
 	public ExamFileUploadBean fileUpload(Long studentId, Long recordId, MultipartFile file, String suffix, String md5) {
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
+		
+
 		String filePath = "upload/" + sdf.format(new Date()) + "/" + uuid() + ".json";
 		String filePath = "upload/" + sdf.format(new Date()) + "/" + uuid() + ".json";
 		InputStream in = null;
 		InputStream in = null;
 		try {
 		try {
 			String fileMd5 = DigestUtils.md5Hex(file.getBytes());
 			String fileMd5 = DigestUtils.md5Hex(file.getBytes());
-			if(!md5.equals(fileMd5)) {
+			if (!md5.equals(fileMd5)) {
 				throw new BusinessException("文件md5不一致");
 				throw new BusinessException("文件md5不一致");
 			}
 			}
 			in = file.getInputStream();
 			in = file.getInputStream();
 			OssUtil.ossUploadStream(systemConfig.getOssEnv(3), filePath, in);
 			OssUtil.ossUploadStream(systemConfig.getOssEnv(3), filePath, in);
-			String url=systemConfig.getProperty("aliyun.oss.url")+"/"+filePath;
-			ExamFileUploadBean ret=new ExamFileUploadBean();
+			String url = systemConfig.getProperty("aliyun.oss.url") + "/" + filePath;
+			ExamFileUploadBean ret = new ExamFileUploadBean();
 			ret.setUrl(url);
 			ret.setUrl(url);
-			ret.setUploadTime(System.currentTimeMillis());
+			ret.setUploadTime(new Date().getTime());
 			return ret;
 			return ret;
 		} catch (IOException e) {
 		} catch (IOException e) {
 			throw new BusinessException("文件读取出错");
 			throw new BusinessException("文件读取出错");
@@ -416,49 +430,57 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 	@SuppressWarnings("unchecked")
 	@SuppressWarnings("unchecked")
 	@Override
 	@Override
 	public ExamResumeBean resume(Long studentId, Long recordId) {
 	public ExamResumeBean resume(Long studentId, Long recordId) {
-		ExamRecordCacheBean er = (ExamRecordCacheBean) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId));
-		if (er == null) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
 			throw new BusinessException("未找到考试记录");
 			throw new BusinessException("未找到考试记录");
 		}
 		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
 		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
-				.get(RedisKeyHelper.examStudentCacheKey(er.getExamStudentId()));
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
 		if (es == null) {
 		if (es == null) {
 			throw new BusinessException("未找到考生");
 			throw new BusinessException("未找到考生");
 		}
 		}
 		if (studentId.equals(es.getStudentId())) {
 		if (studentId.equals(es.getStudentId())) {
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
 		}
 		}
-		ExamPaperCacheBean ep = teExamPaperService.getExamPaperCacheBean(er.getPaperId());
+		
+
+		Long paperId = ExamRecordCacheUtil.getPaperId(recordId);
+		ExamPaperCacheBean ep = teExamPaperService.getExamPaperCacheBean(paperId);
 		if (ep == null) {
 		if (ep == null) {
 			throw new BusinessException("未找到试卷");
 			throw new BusinessException("未找到试卷");
 		}
 		}
-		
-		ExamResumeBean ret=new ExamResumeBean();
-		ret.setDurationSeconds(er.getDurationSeconds());
+
+		ExamResumeBean ret = new ExamResumeBean();
+		ret.setDurationSeconds(ExamRecordCacheUtil.getDurationSeconds(recordId));
 		ret.setPaperUrl(OssUtil.getUrlForPrivateBucket(systemConfig.getOssEnv(3), ep.getPaperPath()));
 		ret.setPaperUrl(OssUtil.getUrlForPrivateBucket(systemConfig.getOssEnv(3), ep.getPaperPath()));
 		ret.setStructUrl(OssUtil.getUrlForPrivateBucket(systemConfig.getOssEnv(3), ep.getStructPath()));
 		ret.setStructUrl(OssUtil.getUrlForPrivateBucket(systemConfig.getOssEnv(3), ep.getStructPath()));
-		ret.setHasAudio((ep.getHasAudio()!=null&&ep.getHasAudio().intValue()==1?true:false));
+		ret.setHasAudio((ep.getHasAudio() != null && ep.getHasAudio().intValue() == 1 ? true : false));
 		ret.setAudioPlayCount(ep.getAudioPlayCount());
 		ret.setAudioPlayCount(ep.getAudioPlayCount());
-		//TODO 9527
+		// TODO 9527
 		ret.setMonitorKey(recordId.toString());
 		ret.setMonitorKey(recordId.toString());
-		
-		ExamStudentPaperStructCacheBean struct=(ExamStudentPaperStructCacheBean)redisUtil.get(RedisKeyHelper.studentPaperStructKey(recordId));
-		if(struct!=null) {
+
+		ExamStudentPaperStructCacheBean struct = (ExamStudentPaperStructCacheBean) redisUtil
+				.get(RedisKeyHelper.studentPaperStructKey(recordId));
+		if (struct != null) {
 			ret.setStudentPaperStruct(struct.getContent());
 			ret.setStudentPaperStruct(struct.getContent());
 		}
 		}
-		Map<String,ExamStudentAnswerCacheBean> answers=redisUtil.getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
-		if(answers!=null&&answers.size()>0) {
+		Map<String, ExamStudentAnswerCacheBean> answers = redisUtil
+				.getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
+		if (answers != null && answers.size() > 0) {
 			ret.setAnswerList(sortAnswers(answers));
 			ret.setAnswerList(sortAnswers(answers));
 		}
 		}
-		Map<String,Integer> audioLeftPlayCounts=redisUtil.getHashEntries(RedisKeyHelper.audioLeftPlayCountKey(recordId));
-		if(audioLeftPlayCounts!=null&&audioLeftPlayCounts.size()>0) {
+		Map<String, Integer> audioLeftPlayCounts = redisUtil
+				.getHashEntries(RedisKeyHelper.audioLeftPlayCountKey(recordId));
+		if (audioLeftPlayCounts != null && audioLeftPlayCounts.size() > 0) {
 			ret.setAudioLeftPlayCount(audioLeftPlayCounts);
 			ret.setAudioLeftPlayCount(audioLeftPlayCounts);
 		}
 		}
 		return ret;
 		return ret;
 	}
 	}
-	
-	//作答排序
-	private List<ExamStudentAnswerCacheBean> sortAnswers(Map<String,ExamStudentAnswerCacheBean> answers) {
+
+	// 作答排序
+	private List<ExamStudentAnswerCacheBean> sortAnswers(Map<String, ExamStudentAnswerCacheBean> answers) {
 		List<ExamStudentAnswerCacheBean> ret = new ArrayList<ExamStudentAnswerCacheBean>(answers.values());
 		List<ExamStudentAnswerCacheBean> ret = new ArrayList<ExamStudentAnswerCacheBean>(answers.values());
 		Collections.sort(ret, new Comparator<ExamStudentAnswerCacheBean>() {
 		Collections.sort(ret, new Comparator<ExamStudentAnswerCacheBean>() {
 
 
@@ -474,9 +496,9 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 					} else if (o1.getSubNumber() < o2.getSubNumber()) {
 					} else if (o1.getSubNumber() < o2.getSubNumber()) {
 						return -1;
 						return -1;
 					} else {
 					} else {
-						if (o1.getSubIndex()==null || o2.getSubIndex()==null) {
+						if (o1.getSubIndex() == null || o2.getSubIndex() == null) {
 							return 0;
 							return 0;
-						}else {
+						} else {
 							if (o1.getSubIndex() > o2.getSubIndex()) {
 							if (o1.getSubIndex() > o2.getSubIndex()) {
 								return 1;
 								return 1;
 							} else if (o1.getSubIndex() < o2.getSubIndex()) {
 							} else if (o1.getSubIndex() < o2.getSubIndex()) {
@@ -492,4 +514,30 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 		});
 		});
 		return ret;
 		return ret;
 	}
 	}
+
+	@Override
+	public ExamFinishBean finish(Long studentId, Long recordId, String type, Integer durationSeconds) {
+
+		// 校验当前登录用户和参数一致性
+		if (ExamRecordCacheUtil.getId(recordId) == null) {
+			throw new BusinessException("未找到考试记录");
+		}
+		Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
+		ExamStudentCacheBean es = (ExamStudentCacheBean) redisUtil
+				.get(RedisKeyHelper.examStudentCacheKey(examStudentId));
+		if (es == null) {
+			throw new BusinessException("未找到考生");
+		}
+		if (studentId.equals(es.getStudentId())) {
+			throw new BusinessException("考试记录的学生Id和当前登录用户不一致");
+		}
+		
+
+		ExamFinishBean ret = new ExamFinishBean();
+		ret.setFinishTime(new Date().getTime());
+		ret.setObjectiveScore(ExamRecordCacheUtil.getObjectiveScore(recordId));
+		// TODO
+		ret.setReviewResult("");
+		return null;
+	}
 }
 }

+ 47 - 48
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -13,11 +13,11 @@ import org.springframework.transaction.annotation.Transactional;
 
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.cache.RedisKeyHelper;
 import com.qmth.themis.business.cache.RedisKeyHelper;
-import com.qmth.themis.business.cache.bean.ExamRecordCacheBean;
 import com.qmth.themis.business.dao.TOeExamRecordMapper;
 import com.qmth.themis.business.dao.TOeExamRecordMapper;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.util.RedisUtil;
 import com.qmth.themis.business.util.RedisUtil;
+import com.qmth.themis.common.util.SimpleBeanUtil;
 
 
 /**
 /**
  * @Description: 考试记录 服务实现类
  * @Description: 考试记录 服务实现类
@@ -101,55 +101,54 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     	er.setFirstPrepareTime(new Date());
     	er.setFirstPrepareTime(new Date());
     	er.setStatus(ExamRecordStatusEnum.first_prepare);
     	er.setStatus(ExamRecordStatusEnum.first_prepare);
     	saveOrUpdate(er);
     	saveOrUpdate(er);
-    	ExamRecordCacheBean erCache=new ExamRecordCacheBean();
-    	copy(erCache, er);
-    	redisUtil.set(RedisKeyHelper.examRecordCacheKey(er.getId()), erCache);
+    	redisUtil.set(RedisKeyHelper.examRecordCacheKey(er.getId()), SimpleBeanUtil.objectToMap(er));
     	return er.getId();
     	return er.getId();
     }
     }
+     
     
     
     
     
-    private void copy(ExamRecordCacheBean tb,TOeExamRecord sb) {
-    	tb.setId(sb.getId());
-    	tb.setExamId(sb.getExamId());
-    	tb.setExamActivityId(tb.getExamActivityId());
-    	tb.setPaperId(sb.getPaperId());
-    	tb.setAnswerPath(sb.getAnswerPath());
-    	tb.setStatus(sb.getStatus());
-    	tb.setFirstPrepareTime(sb.getFirstPrepareTime());
-    	tb.setFirstStartTime(sb.getFirstStartTime());
-    	tb.setLastPrepareTime(sb.getLastPrepareTime());
-    	tb.setLastBreakTime(sb.getLastBreakTime());
-    	tb.setLastStartTime(sb.getLastStartTime());
-    	tb.setLeftBreakResumeCount(sb.getLeftBreakResumeCount());
-    	tb.setClientCurrentIp(sb.getClientCurrentIp());
-    	tb.setClientLastSyncTime(sb.getClientLastSyncTime());
-    	tb.setClientVideoPushKey(sb.getClientVideoPushKey());
-    	tb.setClientVideoPushStatus(sb.getClientVideoPushStatus());
-    	tb.setClientWebsocketId(sb.getClientWebsocketId());
-    	tb.setClientWebsocketStatus(sb.getClientWebsocketStatus());
-    	tb.setWxappLastSyncTime(sb.getWxappLastSyncTime());
-    	tb.setWarningCount(sb.getWarningCount());
-    	tb.setWxappVideoPushKey(sb.getWxappVideoPushKey());
-    	tb.setWxappVideoPushStatus(sb.getWxappVideoPushStatus());
-    	tb.setWxappWebsocketId(sb.getWxappWebsocketId());
-    	tb.setWxappWebsocketStatus(sb.getWxappWebsocketStatus());
-    	tb.setAnswerProgress(sb.getAnswerProgress());
-    	tb.setDurationSeconds(sb.getDurationSeconds());
-    	tb.setFinishTime(sb.getFinishTime());
-    	tb.setFinishType(sb.getFinishType());
-    	tb.setWarningCount(sb.getWarningCount());
-    	tb.setReviewResult(sb.getReviewResult());
-    	tb.setObjectiveScore(sb.getObjectiveScore());
-    	tb.setPaperDownload(sb.getPaperDownload());
-    	tb.setBreachStatus(sb.getBreachStatus());
-    	tb.setPaperStruct(sb.getPaperStruct());
-    	tb.setPaperStructUpload(sb.getPaperStructUpload());
-    	tb.setSerialNumber(sb.getSerialNumber());
-    	tb.setLastBreakId(sb.getLastBreakId());
-    	tb.setEntryAuthenticationId(sb.getEntryAuthenticationId());
-    	tb.setEntryAuthenticationResult(sb.getEntryAuthenticationResult());
-    	tb.setInProcessFaceVerifyStatus(sb.getInProcessFaceVerifyStatus());
-    	tb.setInProcessLivenessVerifyCount(sb.getInProcessLivenessVerifyCount());
-    	tb.setInProcessLivenessVerifyStatus(sb.getInProcessLivenessVerifyStatus());
-    }
+//    private void copy(ExamRecordCacheBean tb,TOeExamRecord sb) {
+//    	tb.setId(sb.getId());
+//    	tb.setExamId(sb.getExamId());
+//    	tb.setExamActivityId(tb.getExamActivityId());
+//    	tb.setPaperId(sb.getPaperId());
+//    	tb.setAnswerPath(sb.getAnswerPath());
+//    	tb.setStatus(sb.getStatus());
+//    	tb.setFirstPrepareTime(sb.getFirstPrepareTime());
+//    	tb.setFirstStartTime(sb.getFirstStartTime());
+//    	tb.setLastPrepareTime(sb.getLastPrepareTime());
+//    	tb.setLastBreakTime(sb.getLastBreakTime());
+//    	tb.setLastStartTime(sb.getLastStartTime());
+//    	tb.setLeftBreakResumeCount(sb.getLeftBreakResumeCount());
+//    	tb.setClientCurrentIp(sb.getClientCurrentIp());
+//    	tb.setClientLastSyncTime(sb.getClientLastSyncTime());
+//    	tb.setClientVideoPushKey(sb.getClientVideoPushKey());
+//    	tb.setClientVideoPushStatus(sb.getClientVideoPushStatus());
+//    	tb.setClientWebsocketId(sb.getClientWebsocketId());
+//    	tb.setClientWebsocketStatus(sb.getClientWebsocketStatus());
+//    	tb.setWxappLastSyncTime(sb.getWxappLastSyncTime());
+//    	tb.setWarningCount(sb.getWarningCount());
+//    	tb.setWxappVideoPushKey(sb.getWxappVideoPushKey());
+//    	tb.setWxappVideoPushStatus(sb.getWxappVideoPushStatus());
+//    	tb.setWxappWebsocketId(sb.getWxappWebsocketId());
+//    	tb.setWxappWebsocketStatus(sb.getWxappWebsocketStatus());
+//    	tb.setAnswerProgress(sb.getAnswerProgress());
+//    	tb.setDurationSeconds(sb.getDurationSeconds());
+//    	tb.setFinishTime(sb.getFinishTime());
+//    	tb.setFinishType(sb.getFinishType());
+//    	tb.setWarningCount(sb.getWarningCount());
+//    	tb.setReviewResult(sb.getReviewResult());
+//    	tb.setObjectiveScore(sb.getObjectiveScore());
+//    	tb.setPaperDownload(sb.getPaperDownload());
+//    	tb.setBreachStatus(sb.getBreachStatus());
+//    	tb.setPaperStruct(sb.getPaperStruct());
+//    	tb.setPaperStructUpload(sb.getPaperStructUpload());
+//    	tb.setSerialNumber(sb.getSerialNumber());
+//    	tb.setLastBreakId(sb.getLastBreakId());
+//    	tb.setEntryAuthenticationId(sb.getEntryAuthenticationId());
+//    	tb.setEntryAuthenticationResult(sb.getEntryAuthenticationResult());
+//    	tb.setInProcessFaceVerifyStatus(sb.getInProcessFaceVerifyStatus());
+//    	tb.setInProcessLivenessVerifyCount(sb.getInProcessLivenessVerifyCount());
+//    	tb.setInProcessLivenessVerifyStatus(sb.getInProcessLivenessVerifyStatus());
+//    }
 }
 }

+ 8 - 0
themis-business/src/main/java/com/qmth/themis/business/util/RedisUtil.java

@@ -277,4 +277,12 @@ public class RedisUtil {
     public void delete(String key) {
     public void delete(String key) {
         redisTemplate.expire(key, 0, TimeUnit.SECONDS);
         redisTemplate.expire(key, 0, TimeUnit.SECONDS);
     }
     }
+    
+    /**保存hash结构
+     * @param key
+     * @param map
+     */
+    public void setForHash(String key,Map<String,Object> map) {
+        redisTemplate.opsForHash().putAll(key, map);
+    }
 }
 }

+ 47 - 0
themis-common/src/main/java/com/qmth/themis/common/util/SimpleBeanUtil.java

@@ -0,0 +1,47 @@
+package com.qmth.themis.common.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SimpleBeanUtil {
+	public static Map<String, Object> objectToMap(Object obj) {
+		if (obj == null) {
+			throw new RuntimeException("Object is null");
+		}
+		// 获取关联的所有类,本类以及所有父类
+		boolean ret = true;
+		Class oo = obj.getClass();
+		List<Class> clazzs = new ArrayList<Class>();
+		while (ret) {
+			clazzs.add(oo);
+			oo = oo.getSuperclass();
+			if (oo == null || oo == Object.class)
+				break;
+		}
+
+		Map<String, Object> map = new HashMap<String, Object>();
+
+		for (int i = 0; i < clazzs.size(); i++) {
+			Field[] declaredFields = clazzs.get(i).getDeclaredFields();
+			for (Field field : declaredFields) {
+				int mod = field.getModifiers();
+				// 过滤 static 和 final 类型
+				if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
+					continue;
+				}
+				field.setAccessible(true);
+				try {
+					map.put(field.getName(), field.get(obj));
+				} catch (Exception e) {
+					throw new RuntimeException(e);
+				}
+			}
+		}
+
+		return map;
+	}
+}

+ 20 - 0
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
 import com.qmth.themis.business.bean.exam.ExamFileUploadBean;
+import com.qmth.themis.business.bean.exam.ExamFinishBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamPrepareBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamResumeBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
 import com.qmth.themis.business.bean.exam.ExamStartBean;
@@ -194,4 +195,23 @@ public class TEExamController {
 			redisUtil.releaseLock(lockKey);
 			redisUtil.releaseLock(lockKey);
 		}
 		}
 	}
 	}
+	
+	@ApiOperation(value = "结束考试")
+	@RequestMapping(value = "/finish", method = RequestMethod.POST)
+	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
+	public ExamFinishBean finish(@ApiParam(value = "考试记录ID", required = true) @RequestParam Long recordId,
+			@ApiParam(value = "结束类型", required = true) @RequestParam String type,
+			@ApiParam(value = "总用时秒数", required = true) @RequestParam Integer durationSeconds) {
+		TEStudent teStudent = (TEStudent) ServletUtil.getRequestStudentAccount();
+		String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + teStudent.getId();
+		Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
+		if (!lock) {
+			throw new BusinessException(ExceptionResultEnum.REQUEST_AWAIT);
+		}
+		try {
+			return teExamService.finish(teStudent.getId(), recordId,type,durationSeconds);
+		} finally {
+			redisUtil.releaseLock(lockKey);
+		}
+	}
 }
 }

+ 3 - 3
themis-task/src/main/java/com/qmth/themis/task/quartz/ExamActivityJob.java

@@ -5,7 +5,7 @@ import com.qmth.themis.business.entity.TEExamActivity;
 import com.qmth.themis.business.entity.TEExamStudent;
 import com.qmth.themis.business.entity.TEExamStudent;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.enums.ExamRecordStatusEnum;
 import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.FinishTypeEnum;
+import com.qmth.themis.business.enums.FinishExamTypeEnum;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamStudentService;
 import com.qmth.themis.business.service.TEExamStudentService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.service.TOeExamRecordService;
@@ -62,9 +62,9 @@ public class ExamActivityJob extends QuartzJobBean {
                 List<TOeExamRecord> tOeExamRecordList = tOeExamRecordService.list(tOeExamRecordQueryWrapper);
                 List<TOeExamRecord> tOeExamRecordList = tOeExamRecordService.list(tOeExamRecordQueryWrapper);
                 List<Long> examStudentIdList = new ArrayList<>();
                 List<Long> examStudentIdList = new ArrayList<>();
                 tOeExamRecordList.forEach(s -> {
                 tOeExamRecordList.forEach(s -> {
-                    s.setStatus(ExamRecordStatusEnum.finished);
+                    s.setStatus(ExamRecordStatusEnum.finished.ordinal());
                     s.setFinishTime(new Date());
                     s.setFinishTime(new Date());
-                    s.setFinishType(FinishTypeEnum.auto);
+                    s.setFinishType(FinishExamTypeEnum.INTERRUPT.ordinal());
                     examStudentIdList.add(s.getExamStudentId());
                     examStudentIdList.add(s.getExamStudentId());
                 });
                 });
                 tOeExamRecordService.updateBatchById(tOeExamRecordList);
                 tOeExamRecordService.updateBatchById(tOeExamRecordList);