|
@@ -7,43 +7,60 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.qmth.boot.api.exception.ApiException;
|
|
|
+import com.qmth.boot.core.ai.client.OcrApiClient;
|
|
|
+import com.qmth.boot.core.ai.model.llm.score.AutoScoreRequest;
|
|
|
+import com.qmth.boot.core.ai.model.llm.score.AutoScoreResult;
|
|
|
+import com.qmth.boot.core.ai.model.llm.score.StandardAnswer;
|
|
|
+import com.qmth.boot.core.ai.model.ocr.OcrType;
|
|
|
+import com.qmth.boot.core.ai.service.AiService;
|
|
|
+import com.qmth.boot.core.retrofit.utils.UploadFile;
|
|
|
+import com.qmth.teachcloud.common.bean.dto.mark.PictureConfig;
|
|
|
+import com.qmth.teachcloud.common.bean.marking.MarkConfigItem;
|
|
|
+import com.qmth.teachcloud.common.bean.vo.FilePathVo;
|
|
|
import com.qmth.teachcloud.common.contant.SystemConstant;
|
|
|
import com.qmth.teachcloud.common.entity.BasicCourse;
|
|
|
+import com.qmth.teachcloud.common.entity.BasicSchool;
|
|
|
import com.qmth.teachcloud.common.entity.MarkQuestion;
|
|
|
import com.qmth.teachcloud.common.entity.SysUser;
|
|
|
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
|
|
|
import com.qmth.teachcloud.common.enums.ScorePolicy;
|
|
|
import com.qmth.teachcloud.common.enums.mark.*;
|
|
|
+import com.qmth.teachcloud.common.mapper.BasicCourseMapper;
|
|
|
+import com.qmth.teachcloud.common.mapper.BasicSchoolMapper;
|
|
|
import com.qmth.teachcloud.common.service.BasicCourseService;
|
|
|
import com.qmth.teachcloud.common.service.BasicOperationLogService;
|
|
|
+import com.qmth.teachcloud.common.service.FileUploadService;
|
|
|
import com.qmth.teachcloud.common.service.SysUserService;
|
|
|
+import com.qmth.teachcloud.common.util.FileUtil;
|
|
|
import com.qmth.teachcloud.common.util.ServletUtil;
|
|
|
+import com.qmth.teachcloud.mark.dto.ai.SheetImageDto;
|
|
|
import com.qmth.teachcloud.mark.dto.mark.ScoreItem;
|
|
|
import com.qmth.teachcloud.mark.dto.mark.manage.Task;
|
|
|
+import com.qmth.teachcloud.mark.dto.mark.manage.TrackDTO;
|
|
|
import com.qmth.teachcloud.mark.dto.mark.mark.MarkSettingDto;
|
|
|
import com.qmth.teachcloud.mark.dto.mark.mark.MarkStatusDto;
|
|
|
import com.qmth.teachcloud.mark.dto.mark.mark.SubmitResult;
|
|
|
import com.qmth.teachcloud.mark.entity.*;
|
|
|
-import com.qmth.teachcloud.mark.enums.ExamType;
|
|
|
-import com.qmth.teachcloud.mark.enums.LockType;
|
|
|
-import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
|
|
|
-import com.qmth.teachcloud.mark.enums.QuestionModel;
|
|
|
+import com.qmth.teachcloud.mark.enums.*;
|
|
|
import com.qmth.teachcloud.mark.lock.LockService;
|
|
|
import com.qmth.teachcloud.mark.params.MarkArbitrateResult;
|
|
|
import com.qmth.teachcloud.mark.params.MarkResult;
|
|
|
import com.qmth.teachcloud.mark.params.MarkResultQuestion;
|
|
|
import com.qmth.teachcloud.mark.service.*;
|
|
|
+import com.qmth.teachcloud.mark.utils.AiUtil;
|
|
|
import com.qmth.teachcloud.mark.utils.BigDecimalUtils;
|
|
|
import com.qmth.teachcloud.mark.utils.TaskLock;
|
|
|
import com.qmth.teachcloud.mark.utils.TaskLockUtil;
|
|
|
import io.lettuce.core.GeoArgs.Sort;
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import java.io.File;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
@@ -57,6 +74,16 @@ public class MarkServiceImpl implements MarkService {
|
|
|
|
|
|
private Map<Long, Long> markerLastUpdateTime = new ConcurrentHashMap<>();
|
|
|
|
|
|
+ @Resource
|
|
|
+ private BasicSchoolMapper basicSchoolMapper;
|
|
|
+ @Resource
|
|
|
+ private BasicCourseMapper basicCourseMapper;
|
|
|
+ @Resource
|
|
|
+ private AiService aiService;
|
|
|
+ @Resource
|
|
|
+ private FileUploadService fileUploadService;
|
|
|
+ @Resource
|
|
|
+ private MarkOcrStudentQuestionService markOcrStudentQuestionService;
|
|
|
@Resource
|
|
|
private BasicCourseService basicCourseService;
|
|
|
@Resource
|
|
@@ -104,6 +131,12 @@ public class MarkServiceImpl implements MarkService {
|
|
|
private BasicOperationLogService basicOperationLogService;
|
|
|
@Resource
|
|
|
private MarkAiQuestionParamService markAiQuestionParamService;
|
|
|
+ @Resource
|
|
|
+ private MarkAiQuestionPointService markAiQuestionPointService;
|
|
|
+ @Resource
|
|
|
+ private MarkAiQuestionLevelService markAiQuestionLevelService;
|
|
|
+ @Resource
|
|
|
+ private OcrApiClient ocrApiClient;
|
|
|
|
|
|
|
|
|
* 释放某个评卷员的考生
|
|
@@ -1478,4 +1511,270 @@ public class MarkServiceImpl implements MarkService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void aiMark(Long schoolId, Long examId, String paperNumber, Long questionId) {
|
|
|
+ SysUser aiUser = sysUserService.getAiUserBySchoolId(schoolId);
|
|
|
+ if (aiUser != null) {
|
|
|
+ BasicSchool basicSchool = basicSchoolMapper.selectByExamId(examId);
|
|
|
+ BasicCourse basicCourse = basicCourseMapper.selectByExamIdAndPaperNumber(examId, paperNumber);
|
|
|
+ MarkQuestion markQuestion = markQuestionService.getById(questionId);
|
|
|
+ MarkAiQuestionParam markAiQuestionParam = markAiQuestionParamService.getByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, questionId);
|
|
|
+ List<MarkTask> markTasks = null;
|
|
|
+ int pageNumber = 1;
|
|
|
+ do {
|
|
|
+ Set<Long> questions = new HashSet<>(Arrays.asList(questionId));
|
|
|
+ markTasks = markTaskService.findAiUnMarked(new Page<>(pageNumber, 20), examId, paperNumber, questionId);
|
|
|
+ if (markTasks.isEmpty() || markQuestion.getEnableAi()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ for (MarkTask t : markTasks) {
|
|
|
+ if (this.applyTask(examId, paperNumber, t.getStudentId(), null, questions, Arrays.asList(t.getId()))) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ Map<Integer, SheetImageDto> fileMap = new HashMap<>();
|
|
|
+ String studentQuestionAnswer = this.mergeQuestionOcrResult(this.listStudentQuestionOcrResult(basicSchool, t.getStudentId(), t.getQuestionId(), fileMap));
|
|
|
+
|
|
|
+ AutoScoreRequest request = new AutoScoreRequest();
|
|
|
+ request.setSubjectName(basicCourse.getName());
|
|
|
+ request.setTotalScore(markQuestion.getTotalScore());
|
|
|
+ request.setIntervalScore(markAiQuestionParam.getMinScore());
|
|
|
+ request.setQuestionBody(markAiQuestionParam.getMainTitle());
|
|
|
+ request.setStudentAnswer(studentQuestionAnswer);
|
|
|
+ request.setStandardAnswer(buildStandardAnswer(markAiQuestionParam));
|
|
|
+
|
|
|
+ AutoScoreResult autoScoreResult = aiService.autoScore(request, AiUtil.signature(basicSchool));
|
|
|
+ long endTime = System.currentTimeMillis();
|
|
|
+ submitAiTask(t, aiUser.getId(), markQuestion, autoScoreResult, buildTrack(autoScoreResult, markQuestion, fileMap), endTime - startTime);
|
|
|
+ } catch (Exception e) {
|
|
|
+ t.setAiMarkErrorMsg(e.getMessage());
|
|
|
+ } finally {
|
|
|
+ t.setAiMarkErrorMsg(null);
|
|
|
+ markTaskService.updateAiMarkErrorMsg(t);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pageNumber++;
|
|
|
+
|
|
|
+ markQuestion = markQuestionService.getById(questionId);
|
|
|
+ markTasks = markTaskService.findAiUnMarked(new Page<>(pageNumber, 20), examId, paperNumber, questionId);
|
|
|
+ } while (CollectionUtils.isNotEmpty(markTasks));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void resetAiTask(Long examId, String paperNumber, Long questionId) {
|
|
|
+ MarkQuestion markQuestion = markQuestionService.getById(questionId);
|
|
|
+ if (markQuestion == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
|
|
|
+ if (markPaper == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
|
|
|
+
|
|
|
+ List<MarkTaskStatus> statusList = Arrays.asList(MarkTaskStatus.WAITING);
|
|
|
+ List<MarkTask> markTaskList = markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndAiMarkedAndStatusNotIn(examId, paperNumber, questionId, true, statusList);
|
|
|
+ for (MarkTask markTask : markTaskList) {
|
|
|
+ Long studentId = markTask.getStudentId();
|
|
|
+ markTaskService.resetById(markTask.getId(), null, null, null, null, MarkTaskStatus.WAITING);
|
|
|
+ markSubjectiveScoreService.deleteByStudentIdAndQuestionId(studentId, questionId);
|
|
|
+ markRejectHistoryService.deleteByTaskId(markTask.getId());
|
|
|
+ markArbitrateHistoryService.deleteByExamIdAndPaperNumberAndStudentIdAndQuestionId(examId, paperNumber, studentId, questionId);
|
|
|
+ lockService.waitlock(LockType.STUDENT, markTask.getStudentId());
|
|
|
+ markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK);
|
|
|
+ lockService.unlock(LockType.STUDENT, markTask.getStudentId());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (markQuestion.getClearOcrResult()) {
|
|
|
+ markOcrStudentQuestionService.deleteByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, questionId);
|
|
|
+ markQuestionService.updateClearOcrResult(questionId, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.updateMarkedCount(examId, paperNumber, questionId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<TrackDTO> buildTrack(AutoScoreResult autoScoreResult, MarkQuestion markQuestion, Map<Integer, SheetImageDto> fileMap) {
|
|
|
+ List<TrackDTO> list = new ArrayList<>();
|
|
|
+ if (fileMap.isEmpty()) {
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+ double[] doubles = autoScoreResult.getStepScore();
|
|
|
+
|
|
|
+ List<MarkConfigItem> pictureConfigList = markQuestion.getPictureConfigList();
|
|
|
+ MarkConfigItem markConfigItem = pictureConfigList.get(0);
|
|
|
+ SheetImageDto sheetImageDto = fileMap.get(markConfigItem.getI());
|
|
|
+ int step = 10;
|
|
|
+ for (int i = 0; i < doubles.length; i++) {
|
|
|
+ int offsetX = (int) (sheetImageDto.getWidth() * markConfigItem.getX()) + step * (i + 1);
|
|
|
+ int offsetY = (int) (sheetImageDto.getHeight() * markConfigItem.getY()) + step;
|
|
|
+ list.add(new TrackDTO(markQuestion, i + 1, doubles[i], markConfigItem.getI(), offsetX, offsetY));
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<StandardAnswer> buildStandardAnswer(MarkAiQuestionParam markAiQuestionParam) {
|
|
|
+ List<StandardAnswer> standardAnswerList = new ArrayList<>();
|
|
|
+
|
|
|
+ if (AiQuestionParamModeStatus.POINT.equals(markAiQuestionParam.getMode())) {
|
|
|
+ List<MarkAiQuestionPoint> markAiQuestionPoints = markAiQuestionPointService.listByAiQuestionId(markAiQuestionParam.getId());
|
|
|
+ if (CollectionUtils.isEmpty(markAiQuestionPoints)) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("未设置得分点");
|
|
|
+ }
|
|
|
+ standardAnswerList = markAiQuestionPoints.stream().map(m -> {
|
|
|
+ StandardAnswer standardAnswer = new StandardAnswer();
|
|
|
+ standardAnswer.setScore(m.getScore());
|
|
|
+ standardAnswer.setContent(m.getAnswer());
|
|
|
+ return standardAnswer;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ else if (AiQuestionParamModeStatus.LEVEL.equals(markAiQuestionParam.getMode())) {
|
|
|
+ List<MarkAiQuestionLevel> markAiQuestionLevels = markAiQuestionLevelService.listByAiQuestionId(markAiQuestionParam.getId());
|
|
|
+ if (CollectionUtils.isEmpty(markAiQuestionLevels)) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("未设置档次");
|
|
|
+ }
|
|
|
+ standardAnswerList = markAiQuestionLevels.stream().map(m -> {
|
|
|
+ StandardAnswer standardAnswer = new StandardAnswer();
|
|
|
+ standardAnswer.setLowScore(m.getMinScore());
|
|
|
+ standardAnswer.setHighScore(m.getMaxScore());
|
|
|
+ standardAnswer.setContent(m.getAnswer());
|
|
|
+ return standardAnswer;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ } else {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("评分模式不存在");
|
|
|
+ }
|
|
|
+ return standardAnswerList;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String mergeQuestionOcrResult(List<MarkOcrStudentQuestion> markOcrStudentQuestionList) {
|
|
|
+ if (CollectionUtils.isEmpty(markOcrStudentQuestionList)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return markOcrStudentQuestionList.stream().filter(m -> StringUtils.isNotBlank(m.getOcrContent())).map(MarkOcrStudentQuestion::getOcrContent).collect(Collectors.joining(SystemConstant.COMMA_OF_CHINESE));
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<MarkOcrStudentQuestion> listStudentQuestionOcrResult(BasicSchool basicSchool, Long studentId, Long questionId, Map<Integer, SheetImageDto> fileMap) {
|
|
|
+ List<MarkOcrStudentQuestion> markOcrStudentQuestionList = markOcrStudentQuestionService.listByStudentIdAndQuestionId(studentId, questionId);
|
|
|
+ if (CollectionUtils.isNotEmpty(markOcrStudentQuestionList)) {
|
|
|
+ return markOcrStudentQuestionList;
|
|
|
+ }
|
|
|
+ MarkQuestion markQuestion = markQuestionService.getById(questionId);
|
|
|
+ if (markQuestion == null || StringUtils.isBlank(markQuestion.getPicList())) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("大题号[" + markQuestion.getMainNumber() + "]、小题号[" + markQuestion.getSubNumber() + "]未设置评卷区");
|
|
|
+ }
|
|
|
+
|
|
|
+ MarkStudent markStudent = markStudentService.getById(studentId);
|
|
|
+ if (markStudent == null) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("未找到考生" + ",考生ID[" + studentId);
|
|
|
+ } else if (StringUtils.isBlank(markStudent.getSheetPath())) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("未查询到原图" + ",考号[" + markStudent.getStudentCode());
|
|
|
+ }
|
|
|
+ List<File> deleteFileList = new ArrayList<>();
|
|
|
+
|
|
|
+ try {
|
|
|
+ List<FilePathVo> filePathVoList = markStudent.listSheetPath();
|
|
|
+ for (int i = 0; i < filePathVoList.size(); i++) {
|
|
|
+ FilePathVo filePathVo = filePathVoList.get(i);
|
|
|
+ File file = SystemConstant.getFileTempDirVar(SystemConstant.JPG_PREFIX);
|
|
|
+ try {
|
|
|
+ fileUploadService.downloadFile(filePathVo.getPath(), filePathVo.getUploadType(), filePathVo.getType(), file.getPath());
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("读取考生原图失败" + ",考号[" + markStudent.getStudentCode() + "。" + e.getMessage());
|
|
|
+ }
|
|
|
+ fileMap.put(i + 1, new SheetImageDto(i + 1, file, filePathVo.getMd5(), AiUtil.imageDim(file)));
|
|
|
+ deleteFileList.add(file);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ List<PictureConfig> pictureConfigList = JSON.parseArray(markQuestion.getPicList(), PictureConfig.class);
|
|
|
+
|
|
|
+ List<MarkOcrStudentQuestion> markOcrStudentQuestions = new ArrayList<>();
|
|
|
+ for (int i = 0; i < pictureConfigList.size(); i++) {
|
|
|
+ PictureConfig pictureConfig = pictureConfigList.get(i);
|
|
|
+ SheetImageDto sheetImageDto = fileMap.get(pictureConfig.getI());
|
|
|
+ File file = SystemConstant.getFileTempDirVar(SystemConstant.JPG_PREFIX);
|
|
|
+ AiUtil.subImg(sheetImageDto, pictureConfig, file);
|
|
|
+
|
|
|
+
|
|
|
+ String ocrResult;
|
|
|
+ try {
|
|
|
+ ocrResult = ocrApiClient.forImage(AiUtil.signature(basicSchool), OcrType.HANDWRITING, UploadFile.build("image", "", file));
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception("OCR识别失败" + ",考号[" + markStudent.getStudentCode() + "。" + e.getMessage());
|
|
|
+ }
|
|
|
+ MarkOcrStudentQuestion markOcrStudentQuestion = new MarkOcrStudentQuestion();
|
|
|
+ markOcrStudentQuestion.setId(SystemConstant.getDbUuid());
|
|
|
+ markOcrStudentQuestion.setExamId(markStudent.getExamId());
|
|
|
+ markOcrStudentQuestion.setPaperNumber(markStudent.getPaperNumber());
|
|
|
+ markOcrStudentQuestion.setStudentId(studentId);
|
|
|
+ markOcrStudentQuestion.setQuestionId(questionId);
|
|
|
+ markOcrStudentQuestion.setNumber(i + 1);
|
|
|
+ markOcrStudentQuestion.setMd5(sheetImageDto.getMd5());
|
|
|
+ markOcrStudentQuestion.setOcrContent(ocrResult);
|
|
|
+ markOcrStudentQuestion.setCreateTime(System.currentTimeMillis());
|
|
|
+ markOcrStudentQuestions.add(markOcrStudentQuestion);
|
|
|
+ deleteFileList.add(file);
|
|
|
+ }
|
|
|
+ markOcrStudentQuestionService.saveBatch(markOcrStudentQuestions);
|
|
|
+ return markOcrStudentQuestions;
|
|
|
+ } catch (RuntimeException e) {
|
|
|
+ throw ExceptionResultEnum.ERROR.exception(e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (CollectionUtils.isNotEmpty(deleteFileList)) {
|
|
|
+ for (File file : deleteFileList) {
|
|
|
+ FileUtil.deleteFile(file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean submitAiTask(MarkTask task, Long userId, MarkQuestion markQuestion, AutoScoreResult result, List<TrackDTO> trackDTOList, long spent) {
|
|
|
+
|
|
|
+ if (!MarkTaskStatus.WAITING.equals(task.getStatus())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (markTaskService.countByStudentIdAndMarkerIdAndIdNotEqual(task.getStudentId(), task.getExamId(),
|
|
|
+ task.getPaperNumber(), task.getQuestionId(), userId, task.getId()) > 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long now = System.currentTimeMillis();
|
|
|
+
|
|
|
+ if (!markTaskService.updateAiMarkResult(task.getId(), MarkTaskStatus.MARKED, userId, result.getTotalScore(), trackDTOList, spent, now, MarkTaskStatus.WAITING)) {
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ MarkArbitrateHistory history = null;
|
|
|
+ if (markQuestion.getArbitrateThreshold() != null && markQuestion.getArbitrateThreshold() > 0) {
|
|
|
+
|
|
|
+ List<MarkTask> list = markTaskService.findByStudentIdAndQuestionIdAndStatus(task.getStudentId(), task.getQuestionId(), MarkTaskStatus.MARKED);
|
|
|
+ for (MarkTask other : list) {
|
|
|
+
|
|
|
+ if (other.getId().equals(task.getId()) || other.getHeaderScore() != null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Math.abs(other.getMarkerScore() - result.getTotalScore()) > markQuestion.getArbitrateThreshold()) {
|
|
|
+ history = buildArbitrateHistory(task, now);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (history != null) {
|
|
|
+
|
|
|
+ markArbitrateHistoryService.save(history);
|
|
|
+
|
|
|
+ markTaskService.updateStatusByStudentIdAndQuestionId(task.getStudentId(), task.getQuestionId(), MarkTaskStatus.WAIT_ARBITRATE);
|
|
|
+
|
|
|
+ resetStudentStatus(task.getStudentId());
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|