wangliang 4 سال پیش
والد
کامیت
d18c9b5c1b

+ 1 - 1
themis-business/src/main/resources/db/init.sql

@@ -451,7 +451,7 @@ INSERT INTO `t_b_privilege` VALUES (57, '查询监考信息', '/api/admin/invigi
 INSERT INTO `t_b_privilege` VALUES (58, '导入监考信息', '/api/admin/invigilateUser/import', 'LINK', 9, 2, NULL, '2020-08-01 12:08:31');
 INSERT INTO `t_b_privilege` VALUES (59, '修改监考信息', '/api/admin/invigilateUser/save', 'LINK', 9, 3, NULL, '2020-08-01 12:08:31');
 INSERT INTO `t_b_privilege` VALUES (60, '考生端', 'oeManager', 'MENU', NULL, 1, NULL, '2020-08-01 12:08:31');
-INSERT INTO `t_b_privilege` VALUES (61, '验证考试口令', '/api/oe/exam/shortCode', 'LINK', 60, 1, NULL, '2020-08-01 12:08:31');
+INSERT INTO `t_b_privilege` VALUES (61, '验证考试口令', '/api/oe/exam/short_code', 'LINK', 60, 1, NULL, '2020-08-01 12:08:31');
 INSERT INTO `t_b_privilege` VALUES (62, '获取待考列表', '/api/oe/exam/waiting', 'LINK', 60, 2, NULL, '2020-08-01 12:08:31');
 INSERT INTO `t_b_privilege` VALUES (63, '开始候考', '/api/oe/exam/prepare', 'LINK', 60, 3, NULL, '2020-08-01 12:08:31');
 INSERT INTO `t_b_privilege` VALUES (64, '断点恢复', '/api/oe/exam/resume', 'LINK', 60, 4, NULL, '2020-08-01 12:08:31');

+ 262 - 257
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -7,6 +7,8 @@ import java.util.Objects;
 
 import javax.annotation.Resource;
 
+import com.qmth.themis.business.annotation.ApiJsonObject;
+import com.qmth.themis.business.annotation.ApiJsonProperty;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -55,274 +57,277 @@ import io.swagger.annotations.ApiResponses;
 @RequestMapping("/${prefix.url.exam}/exam")
 public class TEExamController {
 
-	@Resource
-	TEExamService teExamService;
-	@Resource
-	RedisUtil redisUtil;
-	@Resource
-	MqDtoService mqDtoService;
+    @Resource
+    TEExamService teExamService;
+    @Resource
+    RedisUtil redisUtil;
+    @Resource
+    MqDtoService mqDtoService;
 
-	@ApiOperation(value = "验证考试口令接口")
-	@RequestMapping(value = "/shortCode", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "考试信息", response = TEExam.class) })
-	public Result shortCode(@ApiParam(value = "考试口令", required = true) @RequestParam String shortCode) {
-		if (Objects.isNull(shortCode) || Objects.equals(shortCode, "")) {
-			throw new BusinessException(ExceptionResultEnum.SHORT_CODE_IS_NULL);
-		}
-		QueryWrapper<TEExam> teExamQueryWrapper = new QueryWrapper<>();
-		teExamQueryWrapper.lambda().eq(TEExam::getShortCode, shortCode);
-		TEExam teExam = teExamService.getOne(teExamQueryWrapper);
-		if (Objects.isNull(teExam)) {
-			throw new BusinessException(ExceptionResultEnum.EXAM_NO);
-		}
-		Map<String, Object> map = new HashMap<>();
-		map.put(SystemConstant.ID, teExam.getId());
-		map.put(SystemConstant.NAME, teExam.getName());
-		return ResultUtil.ok(map);
-	}
+    @ApiOperation(value = "验证考试口令接口")
+    @RequestMapping(value = "/short_code", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "考试信息", response = TEExam.class)})
+    public Result shortCode(@ApiJsonObject(name = "loginAccount", value = {
+            @ApiJsonProperty(key = "shortCode", description = "考试口令"),
+    }) @ApiParam(value = "考试口令", required = true) @RequestBody Map<String, Object> mapParameter) {
+        if (Objects.isNull(mapParameter.get("shortCode")) || Objects.equals(mapParameter.get("shortCode"), "")) {
+            throw new BusinessException(ExceptionResultEnum.SHORT_CODE_IS_NULL);
+        }
+        String shortCode = String.valueOf(mapParameter.get("shortCode"));
+        QueryWrapper<TEExam> teExamQueryWrapper = new QueryWrapper<>();
+        teExamQueryWrapper.lambda().eq(TEExam::getShortCode, shortCode);
+        TEExam teExam = teExamService.getOne(teExamQueryWrapper);
+        if (Objects.isNull(teExam)) {
+            throw new BusinessException(ExceptionResultEnum.EXAM_NO);
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put(SystemConstant.ID, teExam.getId());
+        map.put(SystemConstant.NAME, teExam.getName());
+        return ResultUtil.ok(map);
+    }
 
-	@ApiOperation(value = "获取待考列表接口")
-	@RequestMapping(value = "/waiting", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "考试信息", response = TEExamResultDto.class) })
-	public Result waiting() {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
-		List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), null, teStudent.getOrgId());
-		Map<String, Object> map = new HashMap<>();
-		if (Objects.nonNull(list) && list.size() > 0) {
-			map.put("waiting", list);
-		}
-		return ResultUtil.ok(map);
-	}
+    @ApiOperation(value = "获取待考列表接口")
+    @RequestMapping(value = "/waiting", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "考试信息", response = TEExamResultDto.class)})
+    public Result waiting() {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
+        List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), null, teStudent.getOrgId());
+        Map<String, Object> map = new HashMap<>();
+        if (Objects.nonNull(list) && list.size() > 0) {
+            map.put("waiting", list);
+        }
+        return ResultUtil.ok(map);
+    }
 
-	@ApiOperation(value = "开始候考")
-	@RequestMapping(value = "/prepare", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result prepare(@RequestBody PrepareParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getExamStudentId() == null) {
-				throw new BusinessException("考生id不能为空");
-			}
-			return ResultUtil.ok(teExamService.prepare(teStudent.getId(), param.getExamStudentId()));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "开始候考")
+    @RequestMapping(value = "/prepare", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result prepare(@RequestBody PrepareParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getExamStudentId() == null) {
+                throw new BusinessException("考生id不能为空");
+            }
+            return ResultUtil.ok(teExamService.prepare(teStudent.getId(), param.getExamStudentId()));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "开始考试")
-	@RequestMapping(value = "/start", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result start(@RequestBody ExamStartParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getRecordId() == null) {
-				throw new BusinessException("考试记录id不能为空");
-			}
-			return ResultUtil.ok(teExamService.start(teStudent.getId(), param.getRecordId()));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "开始考试")
+    @RequestMapping(value = "/start", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result start(@RequestBody ExamStartParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getRecordId() == null) {
+                throw new BusinessException("考试记录id不能为空");
+            }
+            return ResultUtil.ok(teExamService.start(teStudent.getId(), param.getRecordId()));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "上传个人试卷结构")
-	@RequestMapping(value = "/student_paper_struct/upload", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result studentPaperStruct(@RequestBody StudentPaperStructParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getRecordId() == null) {
-				throw new BusinessException("考试记录id不能为空");
-			}
-			if (param.getContent() == null) {
-				throw new BusinessException("试卷结构json字符串不能为空");
-			}
-			return ResultUtil
-					.ok(teExamService.studentPaperStruct(teStudent.getId(), param.getRecordId(), param.getContent()));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "上传个人试卷结构")
+    @RequestMapping(value = "/student_paper_struct/upload", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result studentPaperStruct(@RequestBody StudentPaperStructParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getRecordId() == null) {
+                throw new BusinessException("考试记录id不能为空");
+            }
+            if (param.getContent() == null) {
+                throw new BusinessException("试卷结构json字符串不能为空");
+            }
+            return ResultUtil
+                    .ok(teExamService.studentPaperStruct(teStudent.getId(), param.getRecordId(), param.getContent()));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "提交作答结果")
-	@RequestMapping(value = "/answer/submit", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result answerSubmit(@RequestBody AnswerSubmitParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
-		if (param.getRecordId() == null) {
-			throw new BusinessException("考试记录id不能为空");
-		}
-		if (param.getMainNumber() == null) {
-			throw new BusinessException("大题号不能为空");
-		}
-		if (param.getSubNumber() == null) {
-			throw new BusinessException("小题号不能为空");
-		}
-		if (param.getAnswer() == null) {
-			throw new BusinessException("答案json字符串不能为空");
-		}
-		if (param.getVersion() == null) {
-			throw new BusinessException("时间戳不能为空");
-		}
-		AnswerSubmitBean ret = teExamService.answerSubmit(teStudent.getId(), param.getRecordId(), param.getMainNumber(),
-				param.getSubNumber(), param.getSubIndex(), param.getAnswer(), param.getVersion(),
-				param.getDurationSeconds());
-		// 发消息计算客观分
-		calculateObjectiveScore(param.getRecordId(), param.getMainNumber(), param.getSubNumber(),
-				param.getSubIndex());
-		return ResultUtil.ok(ret);
-	}
+    @ApiOperation(value = "提交作答结果")
+    @RequestMapping(value = "/answer/submit", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result answerSubmit(@RequestBody AnswerSubmitParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
+        if (param.getRecordId() == null) {
+            throw new BusinessException("考试记录id不能为空");
+        }
+        if (param.getMainNumber() == null) {
+            throw new BusinessException("大题号不能为空");
+        }
+        if (param.getSubNumber() == null) {
+            throw new BusinessException("小题号不能为空");
+        }
+        if (param.getAnswer() == null) {
+            throw new BusinessException("答案json字符串不能为空");
+        }
+        if (param.getVersion() == null) {
+            throw new BusinessException("时间戳不能为空");
+        }
+        AnswerSubmitBean ret = teExamService.answerSubmit(teStudent.getId(), param.getRecordId(), param.getMainNumber(),
+                param.getSubNumber(), param.getSubIndex(), param.getAnswer(), param.getVersion(),
+                param.getDurationSeconds());
+        // 发消息计算客观分
+        calculateObjectiveScore(param.getRecordId(), param.getMainNumber(), param.getSubNumber(),
+                param.getSubIndex());
+        return ResultUtil.ok(ret);
+    }
 
-	@ApiOperation(value = "更新音频剩余播放次数")
-	@RequestMapping(value = "/audio_left_play_count/submit", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result audioLeftPlayCountSubmit(@RequestBody AudioLeftPlayCountSubmitParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getRecordId() == null) {
-				throw new BusinessException("考试记录id不能为空");
-			}
-			if (param.getKey() == null) {
-				throw new BusinessException("音频标识不能为空");
-			}
-			if (param.getCount() == null) {
-				throw new BusinessException("剩余播放次数不能为空");
-			}
-			return ResultUtil.ok(teExamService.audioLeftPlayCountSubmit(teStudent.getId(), param.getRecordId(), param.getKey(), param.getCount()));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "更新音频剩余播放次数")
+    @RequestMapping(value = "/audio_left_play_count/submit", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result audioLeftPlayCountSubmit(@RequestBody AudioLeftPlayCountSubmitParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getRecordId() == null) {
+                throw new BusinessException("考试记录id不能为空");
+            }
+            if (param.getKey() == null) {
+                throw new BusinessException("音频标识不能为空");
+            }
+            if (param.getCount() == null) {
+                throw new BusinessException("剩余播放次数不能为空");
+            }
+            return ResultUtil.ok(teExamService.audioLeftPlayCountSubmit(teStudent.getId(), param.getRecordId(), param.getKey(), param.getCount()));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "文件上传")
-	@RequestMapping(value = "/file/upload", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result fileUpload(@ApiParam(value = "考试记录ID", required = true) @RequestParam Long recordId,
-            @ApiParam(value = "上传文件", required = true) @RequestParam MultipartFile file,
-            @ApiParam(value = "后缀名", required = true) @RequestParam String suffix,
-            @ApiParam(value = "md5", required = true) @RequestParam String md5) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 ResultUtil.ok(teExamService.fileUpload(teStudent.getId(), recordId, file, suffix, md5));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "文件上传")
+    @RequestMapping(value = "/file/upload", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result fileUpload(@ApiParam(value = "考试记录ID", required = true) @RequestParam Long recordId,
+                             @ApiParam(value = "上传文件", required = true) @RequestParam MultipartFile file,
+                             @ApiParam(value = "后缀名", required = true) @RequestParam String suffix,
+                             @ApiParam(value = "md5", required = true) @RequestParam String md5) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 ResultUtil.ok(teExamService.fileUpload(teStudent.getId(), recordId, file, suffix, md5));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "断点恢复")
-	@RequestMapping(value = "/resume", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result resume(@RequestBody ResumeParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getRecordId() == null) {
-				throw new BusinessException("考试记录id不能为空");
-			}
-			return ResultUtil.ok(teExamService.resume(teStudent.getId(), param.getRecordId()));
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
-	
-	@ApiOperation(value = "结束考试")
-	@RequestMapping(value = "/finish", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result finish(@RequestBody FinishParamBean param) {
-		TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
-			if (param.getRecordId() == null) {
-				throw new BusinessException("考试记录id不能为空");
-			}
-			if (param.getType() == null) {
-				throw new BusinessException("结束类型不能为空");
-			}
-			if (param.getDurationSeconds() == null) {
-				throw new BusinessException("总用时秒数不能为空");
-			}
-			Result re = ResultUtil.ok(teExamService.finish(teStudent.getId(), param.getRecordId(), param.getType(), param.getDurationSeconds()));
-			checkToPersisted(param.getRecordId());
-			return re;
-		} finally {
-			redisUtil.releaseLock(lockKey);
-		}
-	}
+    @ApiOperation(value = "断点恢复")
+    @RequestMapping(value = "/resume", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result resume(@RequestBody ResumeParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getRecordId() == null) {
+                throw new BusinessException("考试记录id不能为空");
+            }
+            return ResultUtil.ok(teExamService.resume(teStudent.getId(), param.getRecordId()));
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	private void calculateObjectiveScore(Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex) {
-		Map<String, Object> transMap = new HashMap<String, Object>();
-		transMap.put("recordId", recordId);
-		transMap.put("mainNumber", mainNumber);
-		transMap.put("subNumber", subNumber);
-		transMap.put("subIndex", subIndex);
-		// mq发送消息start
-		MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.calculateObjectiveScore.name(), transMap,
-				MqEnum.EXAM, null, null);
-		mqDtoService.assembleSendOneWayMsg(mqDto);
-	}
+    @ApiOperation(value = "结束考试")
+    @RequestMapping(value = "/finish", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result finish(@RequestBody FinishParamBean param) {
+        TEStudentCacheDto teStudent = (TEStudentCacheDto) 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 {
+            if (param.getRecordId() == null) {
+                throw new BusinessException("考试记录id不能为空");
+            }
+            if (param.getType() == null) {
+                throw new BusinessException("结束类型不能为空");
+            }
+            if (param.getDurationSeconds() == null) {
+                throw new BusinessException("总用时秒数不能为空");
+            }
+            Result re = ResultUtil.ok(teExamService.finish(teStudent.getId(), param.getRecordId(), param.getType(), param.getDurationSeconds()));
+            checkToPersisted(param.getRecordId());
+            return re;
+        } finally {
+            redisUtil.releaseLock(lockKey);
+        }
+    }
 
-	@ApiOperation(value = "查询交卷结果")
-	@RequestMapping(value = "/result", method = RequestMethod.POST)
-	@ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
-	public Result result(@RequestBody ResultParamBean param) {
-		if (param.getRecordId() == null) {
-			throw new BusinessException("考试记录id不能为空");
-		}
-		return ResultUtil.ok(teExamService.result(param.getRecordId()));
-	}
+    private void calculateObjectiveScore(Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex) {
+        Map<String, Object> transMap = new HashMap<String, Object>();
+        transMap.put("recordId", recordId);
+        transMap.put("mainNumber", mainNumber);
+        transMap.put("subNumber", subNumber);
+        transMap.put("subIndex", subIndex);
+        // mq发送消息start
+        MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.calculateObjectiveScore.name(), transMap,
+                MqEnum.EXAM, null, null);
+        mqDtoService.assembleSendOneWayMsg(mqDto);
+    }
 
-	/**
-	 * 检查条件并发送持久化消息
-	 * 
-	 * @param recordId
-	 */
-	private void checkToPersisted(Long recordId) {
-		ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
-		if (!ExamRecordStatusEnum.FINISHED.equals(status)) {
-			return;
-		}
-		Double score = ExamRecordCacheUtil.getObjectiveScore(recordId);
-		if (score == null) {
-			return;
-		}
+    @ApiOperation(value = "查询交卷结果")
+    @RequestMapping(value = "/result", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result result(@RequestBody ResultParamBean param) {
+        if (param.getRecordId() == null) {
+            throw new BusinessException("考试记录id不能为空");
+        }
+        return ResultUtil.ok(teExamService.result(param.getRecordId()));
+    }
 
-		Map<String, Object> transMap = new HashMap<String, Object>();
-		transMap.put("recordId", recordId);
-		// mq发送消息start
-		MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.examRecordPersisted.name(), transMap,
-				MqEnum.EXAM, null, null);
-		mqDtoService.assembleSendOneWayMsg(mqDto);
-	}
+    /**
+     * 检查条件并发送持久化消息
+     *
+     * @param recordId
+     */
+    private void checkToPersisted(Long recordId) {
+        ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
+        if (!ExamRecordStatusEnum.FINISHED.equals(status)) {
+            return;
+        }
+        Double score = ExamRecordCacheUtil.getObjectiveScore(recordId);
+        if (score == null) {
+            return;
+        }
+
+        Map<String, Object> transMap = new HashMap<String, Object>();
+        transMap.put("recordId", recordId);
+        // mq发送消息start
+        MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.examRecordPersisted.name(), transMap,
+                MqEnum.EXAM, null, null);
+        mqDtoService.assembleSendOneWayMsg(mqDto);
+    }
 }