package com.ibeetl.jlw.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.json.JSONUtil; import cn.jlw.util.ToolUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ibeetl.admin.core.entity.CoreUser; import com.ibeetl.admin.core.service.CoreBaseService; import com.ibeetl.admin.core.service.CoreDictService; import com.ibeetl.admin.core.util.PlatformException; import com.ibeetl.admin.core.util.TimeTool; import com.ibeetl.admin.core.web.JsonResult; import com.ibeetl.admin.core.web.JsonReturnCode; import com.ibeetl.jlw.dao.GeneralQuestionLogDao; import com.ibeetl.jlw.dao.GeneralResourcesQuestionSnapshotDao; import com.ibeetl.jlw.dao.StudentDao; import com.ibeetl.jlw.entity.*; import com.ibeetl.jlw.entity.dto.QuestionLogAddDTO; import com.ibeetl.jlw.entity.vo.QuestionLogAnswerLockVO; import com.ibeetl.jlw.enums.GlobalStatusEnum; import com.ibeetl.jlw.enums.QuestionBusinessTypeEnum; import com.ibeetl.jlw.enums.QuestionLogAddTypeEnum; import com.ibeetl.jlw.enums.ResourcesQuestionSnapshotFromTypeEnum; import com.ibeetl.jlw.web.query.GeneralQuestionLogQuery; import com.ibeetl.jlw.web.query.GeneralQuestionSettingQuery; import com.ibeetl.jlw.web.query.QuestionLogSummaryQuery; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.beetl.sql.core.SqlId; import org.beetl.sql.core.engine.PageQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.MultiValueMapAdapter; import org.springframework.validation.annotation.Validated; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.io.*; import java.math.BigDecimal; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import static cn.hutool.core.date.DateUnit.SECOND; import static cn.hutool.core.util.ArrayUtil.isAllNotEmpty; import static cn.hutool.core.util.ArrayUtil.join; import static cn.hutool.core.util.ObjectUtil.defaultIfNull; import static cn.jlw.util.CacheUserUtil.getStudent; import static com.ibeetl.admin.core.util.ExcelUtil.getCellFormatValue; import static com.ibeetl.admin.core.util.StreamUtils.listJoin; import static com.ibeetl.jlw.enums.QuestionLogAddTypeEnum.*; import static com.ibeetl.jlw.enums.ResourcesQuestionSnapshotFromTypeEnum.CHAPTER_EXERCISE; import static com.ibeetl.jlw.enums.ResourcesQuestionTypeEnum.isOneQuestion; import static java.lang.Math.abs; import static java.lang.Math.toIntExact; import static java.math.BigDecimal.ZERO; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.joining; /** * 通用做题日志 Service * 当分布式ID开启后请勿使用insert(*,true) */ @Service @Transactional @Validated public class GeneralQuestionLogService extends CoreBaseService{ @Autowired private GeneralQuestionLogDao generalQuestionLogDao; @Autowired private GeneralResourcesQuestionSnapshotDao generalResourcesQuestionSnapshotDao; @Autowired private StudentDao studentDao; @Autowired private QuestionLogSummaryService questionLogSummaryService; @Autowired private CoreDictService coreDictService; @Autowired @Lazy private GeneralQuestionSettingService generalQuestionSettingService; public PageQueryqueryByCondition(PageQuery query){ PageQuery ret = generalQuestionLogDao.queryByCondition(query); queryListAfter(ret.getList()); return ret; } public PageQueryqueryByConditionQuery(PageQuery query){ PageQuery ret = generalQuestionLogDao.queryByConditionQuery(query); queryListAfter(ret.getList()); return ret; } public void deleteByList(List list){ String ids = ""; ToolUtils.deleteNullList(list); for(int i=0;null != list && i generalQuestionLogList = new ArrayList<>(); try { generalQuestionLogList = JSON.parseArray(generalQuestionLogQuery.getGeneralQuestionLogJsonStr(), GeneralQuestionLog.class); } catch (Exception e) { try { generalQuestionLogList.add(JSONObject.parseObject(generalQuestionLogQuery.getGeneralQuestionLogJsonStr(), GeneralQuestionLog.class)); } catch (Exception e1) {} } ToolUtils.deleteNullList(generalQuestionLogList); if(null != generalQuestionLogList && generalQuestionLogList.size()>0){ for(int i=0;i 0; if(!flag){ msg = "更新指定参数失败"; } }else{ msg = "指定参数为空"; } return msg; } public List getValues (Object paras){ return sqlManager.select(SqlId.of("jlw.generalQuestionLog.getGeneralQuestionLogValues"), GeneralQuestionLog.class, paras); } public List getValuesByQuery (GeneralQuestionLogQuery generalQuestionLogQuery){ return generalQuestionLogDao.getValuesByQuery(generalQuestionLogQuery); } public List getValuesByQueryNotWithPermission (GeneralQuestionLogQuery generalQuestionLogQuery){ return generalQuestionLogDao.getValuesByQueryNotWithPermission(generalQuestionLogQuery); } public GeneralQuestionLog getInfo (Long generalQuestionLogId){ GeneralQuestionLogQuery generalQuestionLogQuery = new GeneralQuestionLogQuery(); generalQuestionLogQuery.setGeneralQuestionLogId(generalQuestionLogId); generalQuestionLogQuery.setGeneralQuestionLogStatusPlural("1,2");//需要根据实际情况来 List list = generalQuestionLogDao.getValuesByQuery(generalQuestionLogQuery); if(null != list && list.size()>0){ return list.get(0); }else{ return null; } } public GeneralQuestionLog getInfo (GeneralQuestionLogQuery generalQuestionLogQuery){ List list = generalQuestionLogDao.getValuesByQuery(generalQuestionLogQuery); if(null != list && list.size()>0){ return list.get(0); }else{ return null; } } public JsonResult importTemplate(List fileEntityList,Listlist,CoreUser coreUser){ ListerrMsg = new ArrayList<>(); String msg =""; int count = 0; Date date = new Date(); for(int item=0;null != fileEntityList && item map = new HashMap<>();//获取需要的表头的列 //从第一列找到需要的表头 for (int i=0; i0?JsonReturnCode.SUCCESS.getCode():JsonReturnCode.FAIL.getCode()); jsonResult.setData(errMsg); jsonResult.setMsg((count>0?"导入成功,共导入"+count+"条":"导入失败")+(StringUtils.isNotBlank(msg)?"
"+msg:"")); return jsonResult; } public List> getExcelValues (GeneralQuestionLogQuery generalQuestionLogQuery){ return generalQuestionLogDao.getExcelValues(generalQuestionLogQuery); } /** * 教师端-手动批改一些题目的分数 * 教师端-给学生打分 * * @param generalQuestionLogIds 指定更新分数的题目日志IDs * @param score 分数 * @param reply 评语 */ public void manualModifyQuestionScores(@NotNull(message = "题目做题日志ID不能为空!") final String generalQuestionLogIds, @NotNull(message = "设置的分数不能为空!") final BigDecimal score, @Nullable final String reply) { GeneralQuestionLogQuery generalQuestionLogQuery = new GeneralQuestionLogQuery(); generalQuestionLogQuery.setGeneralQuestionLogIdPlural(generalQuestionLogIds); generalQuestionLogQuery.setGeneralQuestionLogStatus(1); List updateList = generalQuestionLogDao.getValuesByQueryNotWithPermission(generalQuestionLogQuery); final Date now = new Date(); // 批量更新分数。支持重复设置分数和评语 updateList.forEach(questionLog -> { BigDecimal questionScore = questionLog.getQuestionScore(); questionLog.setStudentScore(score); questionLog.setGeneralQuestionLogReply(reply); // 这里不是记录用时,只是记录教师批阅的时间 questionLog.setGeneralQuestionLogUpdateTime(now); // 是否错题库,也可以用作标记答案是对的错的 questionLog.setIsErrorFavorite(NumberUtil.equals(questionScore, score)); }); updateBatchTemplate(updateList); } /** * 和题目有关的做题提交方法 * 直接计算出学生得分情况 * * @param questionLogAddDTO 题目信息 */ public void addQuestionLog(@NotNull(message = "提交题目信息不能为空") QuestionLogAddDTO questionLogAddDTO) { Long questionSettingId = questionLogAddDTO.getQuestionSettingId(); Map questionLogMap = questionLogAddDTO.getQuestionLogMap(); // 查询学生身份 final Student student = getStudent(); Assert.notNull(student, "非学生身份,无法提交!"); GeneralQuestionSetting questionSettingInfo = generalQuestionSettingService.getInfo(questionSettingId); Assert.notNull(questionSettingInfo, "未匹配到试卷信息!"); // 校验当前时间是否在考试的时间段 generalQuestionSettingService.verifyQuestionStartAndEndTimeWithNowTimeThrow(questionSettingId); switch(questionSettingInfo.getGeneralQuestionSettingType()) { case HOMEWORK_FILE: { addFileRelatedLog(join(questionLogMap.values().toArray(), ","), questionSettingId, student); } break; // 这些都和题目相关,暂时先放在一个方法 case CHAPTER_EXERCISE: case EXAM: case HOMEWORK_QUESTION: { addQuestionRelatedLog(questionLogAddDTO, questionSettingInfo, student); } break; } } /** * 和题目有关的做题提交方法 * 直接计算出学生得分情况 * 只支持练习 * * @param questionLogAddDTO 题目信息 */ public void addQuestionLogTest(@NotNull(message = "提交题目信息不能为空") QuestionLogAddDTO questionLogAddDTO) { final Long questionSettingId = questionLogAddDTO.getQuestionSettingId(); // 查询学生身份 final Student student = getStudent(); Assert.notNull(student, "非学生身份,无法提交!"); GeneralQuestionSetting questionSetting = generalQuestionSettingService.getInfo(questionSettingId); Assert.notNull(questionSetting, "未匹配到试卷信息!"); // 校验当前时间是否在考试的时间段 generalQuestionSettingService.verifyQuestionStartAndEndTimeWithNowTimeThrow(questionSettingId); switch(questionSetting.getGeneralQuestionSettingType()) { // 这些都和题目相关,暂时先放在一个方法 case CHAPTER_EXERCISE: { addQuestionRelatedLog(questionLogAddDTO, questionSetting, student); } break; } } /** * 跟题目相关的题目日志提交方法 * 附件类型的需要单独,拆分除去 * * @param absFilePath * @param questionSettingId * @param student */ private void addFileRelatedLog(@NotBlank(message = "上传的附件不能为空!") String absFilePath, Long questionSettingId, Student student) { Date now = new Date(); GeneralQuestionLogQuery query = new GeneralQuestionLogQuery(); query.setGeneralQuestionLogUploadFile(absFilePath); query.setGeneralQuestionSettingId(questionSettingId); query.setGeneralQuestionLogAddTime(now); query.setGeneralQuestionLogFinishTime(RandomUtil.randomLong(100, 3600)); query.setGeneralQuestionLogStatus(1); // 附件作业,只有一题,所有默认是全部提交的方式 query.setQuestionLogAddType(FINALLY_SUBMIT); query.setOrgId(student.getOrgId()); query.setUserId(student.getUserId()); query.setStudentId(student.getStudentId()); add(query); } /** * 跟题目相关的题目日志提交方法 * 附件类型的需要单独,拆分除去 * 提交试卷 试卷提交 * * @param questionLogAddDTO 题目信息 * @param questionSetting 题目配置信息 * @param student 学生信息 */ private void addQuestionRelatedLog(@NotNull(message = "提交题目信息不能为空") QuestionLogAddDTO questionLogAddDTO, GeneralQuestionSetting questionSetting, Student student) { // 参数获取 QuestionLogAddTypeEnum addType = questionLogAddDTO.getQuestionLogAddType(); Date addTime = questionLogAddDTO.getAddTime(); Long studentId = student.getStudentId(); Long questionSettingId = questionLogAddDTO.getQuestionSettingId(); Map questionLogMap = questionLogAddDTO.getQuestionLogMap(); // 查询符合条件的日志表 String questionSnapshotIds = join(questionLogMap.keySet().toArray(), ","); // 做题思路:先从通用GeneralQuestionSetting表中生成题目日志,到GeneralQuestionLog中,学生这个时候提交答案,再修改日志中的结果。 // 查询条件 GeneralQuestionLogQuery questionLogQuery = GeneralQuestionLogQuery.builder() .generalResourcesQuestionSnapshotIdPlural(questionSnapshotIds) .generalQuestionLogStatus(1) .studentId(student.getStudentId()) .generalQuestionSettingId(questionSettingId) .build(); List logList = generalQuestionLogDao.getValuesByQuery(questionLogQuery); final List tempList = Arrays.asList(PRE_SUBMIT, null); // 只是未提交的数据 logList = logList.stream().filter(item -> tempList.contains(item.getQuestionLogAddType())).collect(Collectors.toList()); Assert.notEmpty(logList, "未查询到题目信息!"); // 题目日志IDs汇总 String logsIds = logList.stream().map(item -> item.getGeneralQuestionLogId().toString()).collect(joining(",")); // 当前时间, 存储要更新的题目日志集合 Date now = new Date(); List updateList = new ArrayList<>(); // 代表填空题和分析题的类型 List questionType = Arrays.asList(4, 5); // 处理答案和分数 logList.forEach(questionLog -> { // 验证最新的提交时间,防止网络延迟 if (!validateQuestionLogAddTimeLatest(questionSettingId, questionLog.getGeneralResourcesQuestionSnapshotId().toString(), studentId, addTime)) { return; } // 验证提交类型 validateQuestionLogAddTypeThrow(questionLog.getQuestionLogAddType(), questionLogAddDTO.getQuestionLogAddType()); // 学生提交的答案 String answer = questionLogMap.get(questionLog.getGeneralQuestionLogId().toString()); // 默认0分 questionLog.setStudentScore(BigDecimal.valueOf(0)); // 学生提交的答案处理 String answersText = null; if (questionType.contains(questionLog.getQuestionType())) { answersText = answer; // 学生答案和正确答案集合 cn.hutool.json.JSONObject studentAnswerArr = JSONUtil.parseObj(answersText); cn.hutool.json.JSONObject questionAnswerArr = JSONUtil.parseObj(questionLog.getQuestionAnswer()); // 每题分数 BigDecimal eachScore = NumberUtil.div(questionLog.getQuestionScore(), questionAnswerArr.size(), 1); BigDecimal studentScore = new BigDecimal("0"); for (Map.Entry map : questionAnswerArr.entrySet()) { Object questionAns = map.getValue(); String k = map.getKey(); String studentAns = studentAnswerArr.getStr(k, ""); // 比对答案 if (studentAns.equalsIgnoreCase(questionAns.toString()) && StrUtil.isAllNotBlank(questionAns.toString(), studentAns)) { studentScore.add(eachScore); } } // 填空题和分析题计算分数 // 计算该题目学生的得分情况 questionLog.setStudentScore(studentScore); // 是否是错题 questionLog.setIsErrorFavorite(NumberUtil.isLess(studentScore, questionLog.getQuestionScore())); }else { answersText = join(answer.split(","), ","); // 是否是正确答案 Boolean isCorrectAnswer = questionLog.getQuestionAnswer().equalsIgnoreCase(answersText); // 学生得分 questionLog.setStudentScore(isCorrectAnswer ? questionLog.getQuestionScore() : ZERO); questionLog.setIsErrorFavorite(!isCorrectAnswer); } // 完成时间 long finishSecondTime = DateUtil.between(questionLog.getGeneralQuestionLogAddTime(), now, SECOND); // 填充属性 questionLog.setGeneralQuestionLogUpdateTime(now); questionLog.setGeneralQuestionLogFinishTime(finishSecondTime); questionLog.setGeneralQuestionLogAnswer(answersText); questionLog.setQuestionLogAddType(addType); questionLog.setOrgId(student.getOrgId()); questionLog.setUserId(student.getUserId()); // 只添加可以更新的数据 updateList.add(questionLog); }); // 学生做的题目的答案与日志关联 updateBatchTemplate(updateList); // 最后提交试卷答案时候,才会记录错题 if (addType.equals(FINALLY_SUBMIT)) { // 计算学生分数,并标记错题 calculateScoreOnSubmit(updateList); // 学生做的题目的答案与日志关联 updateBatchTemplate(updateList); // 添加到题目日志汇总中 addQuestionLogSummary(student, questionSetting.getGeneralQuestionSettingId(), updateList); // 最终提交以后,将日志更改为2 删除状态,仅用于查看 deleteGeneralQuestionLog(listJoin(updateList, GeneralQuestionLog::getGeneralQuestionLogId)); } } /** * 功能描述:
* 计算学生分数 * 会判断题目是否打完,并抛出异常 * * @param logList * @Author: lx * @Date: 2022/12/4 20:16 */ public void calculateScoreOnSubmit(@NotEmpty(message = "学生题目日志不能为空!") List logList) { logList.forEach(questionLog -> { // 题目原本设置的分数 final BigDecimal questionScore = questionLog.getQuestionScore(); // 学生提交的结果 final String studentAnswer = defaultIfNull(questionLog.getGeneralQuestionLogAnswer(), ""); // 题目的答案 final String questionAnswer = defaultIfNull(questionLog.getQuestionAnswer(), ""); // 判断答案和学生提交的结果,都不能为空 final boolean allNotEmpty = isAllNotEmpty(questionAnswer, studentAnswer); // 题目类型,答案属于一道题 final boolean oneQuestion = isOneQuestion(questionLog.getQuestionType()); // 一条日志记录,属于一道题 if (oneQuestion) { boolean isTrue = allNotEmpty && studentAnswer.equalsIgnoreCase(questionAnswer); final BigDecimal mySimpleScore = isTrue ? questionScore: ZERO; // 计算学生最后的得分 questionLog.setStudentScore(mySimpleScore); if (isTrue) { questionLog.setSuccessCount(1); } else { questionLog.setErrorCount(1); } // 错题标记 questionLog.setIsErrorFavorite(!isTrue); } // 一条日志记录,属于多道题的逻辑处理 else { // 分数,正确数量 BigDecimal score = ZERO; int successCount = 0; // 学生提交的题目结果 final String[] studentAnswerArr = studentAnswer.split(","); // 正确答案和学生答案对比 String[] successAnswerArr = questionAnswer.split(","); final int studentAnswerLength = studentAnswerArr.length; final int questionLength = successAnswerArr.length; // 断言需要判断,题目答案的数量是否相等,不然会对不上答案,导致分数计算错误 // Assert.isTrue(studentAnswerLength == questionLength, "题干:" + questionLog.getQuestionStem()+ ",有选项未提交!"); // 一道题的分数 BigDecimal simpleQuestionScore = NumberUtil.mul(questionScore, questionLength); // 循环比对这个题目,是否是复杂的题目:如选词填空或者分析题 for (int i = 0; i < questionLength; i++) { String sqs = successAnswerArr[i]; boolean isTrue = allNotEmpty && sqs.equalsIgnoreCase(studentAnswerArr[i]); BigDecimal oneScore = isTrue ? simpleQuestionScore : ZERO; score = score.add(oneScore); // 正确的数量计数 if (isTrue) { successCount++; } } // 全对的题目,才算做正确答案 final boolean isAllTrue = successCount == questionLength; // 计算学生最后的得分 questionLog.setStudentScore(score); questionLog.setSuccessCount(successCount); questionLog.setErrorCount(abs(questionLength - successCount)); // 错题标记 questionLog.setIsErrorFavorite(!isAllTrue); } }); } /** * 根据题目实时计算,计算分数。并在做题统计表表中添加一条记录 * @param student * @param questionSettingId */ private void addQuestionLogSummary(Student student, Long questionSettingId, List updateList) { if (student == null || questionSettingId == null) { return; } List logList = getValuesBySettingIds(questionSettingId.toString()); GeneralQuestionSetting questionSetting = generalQuestionSettingService.getInfo(questionSettingId); setErrorSuccessCountField(updateList, logList); if (CollectionUtil.isNotEmpty(logList) && questionSetting != null) { String logIds = logList.stream().map(GeneralQuestionLog::getGeneralQuestionLogId).map(Objects::toString).collect(joining(",")); addQuestionLogSummary(logIds, logList, student, questionSetting.getGeneralQuestionSettingName(), questionSetting.getBusinessType(), questionSetting.getGeneralQuestionSettingType()); } } /** * 拷贝正确错误的字段 * * @param updateList * @param logList */ private void setErrorSuccessCountField(List updateList, List logList) { Map> listMap = updateList.stream() .collect(groupingBy(GeneralQuestionLog::getGeneralQuestionLogId)); MultiValueMapAdapter mapAdapter = new MultiValueMapAdapter<>(listMap); logList.forEach(item -> { GeneralQuestionLog first = mapAdapter.getFirst(item.getGeneralQuestionLogId()); if (first != null) { item.setErrorCount(first.getErrorCount()); item.setSuccessCount(first.getSuccessCount()); } }); } private List getValuesBySettingIds(String settingIds) { GeneralQuestionLogQuery logQuery = new GeneralQuestionLogQuery(); logQuery.setGeneralQuestionSettingIdPlural(settingIds); logQuery.setGeneralQuestionLogStatus(1); return generalQuestionLogDao.getValuesByQueryNotWithPermission(logQuery); } /** * 功能描述:
* 题目日志信息,分析汇总到某张表里 * * @param logsIds 题目日志IDs保存 * @param logList 某个questionSettingId对应的题目日志列表 * @param student 学生 * @param questionSettingName 题目配置的名称(试卷名称) * @param fromType 题目配置来源类型 * @param snapshotFromTypeEnum 题目来源 * @Author: lx * @Date: 2022/12/4 20:16 */ public void addQuestionLogSummary( String logsIds, @NotEmpty(message = "学生题目日志不能为空!") List logList, @NotNull Student student, @NotBlank String questionSettingName, @NotNull QuestionBusinessTypeEnum fromType, @NotNull ResourcesQuestionSnapshotFromTypeEnum snapshotFromTypeEnum) { // 构建实体 final QuestionLogSummary questionLogSummary = new QuestionLogSummary(); final Long questionSettingId = logList.get(0).getGeneralQuestionSettingId(); final Integer questionTotalCount = generalResourcesQuestionSnapshotDao.getQuestionTotalCountBySettingId(questionSettingId); // 做对数,做错数,总题数 Integer sumSuccessCount = 0, sumErrorCount = 0, sumDoCount = 0; // 试卷总分数, 我的分数, 题目日志ID集 BigDecimal questionTotalScore = ZERO, myTotalScore = ZERO; final Set idsSet = new HashSet<>(); // 获取最大的完成时间 GeneralQuestionLog maxFinish = logList.stream() .max((o1, o2) -> toIntExact(defaultIfNull(o1.getGeneralQuestionLogFinishTime(), 0L) - defaultIfNull(o2.getGeneralQuestionLogFinishTime(), 0L))) .get(); for (GeneralQuestionLog questionLog : logList) { Integer successCount = questionLog.getSuccessCount(); Integer errorCount = questionLog.getErrorCount(); Assert.isTrue(ObjectUtil.isAllNotEmpty(successCount, errorCount), "题目正确数量和错误数量,计算出了点小问题!"); sumSuccessCount += successCount; sumErrorCount += errorCount; sumDoCount += ObjectUtil.isNotEmpty(questionLog.getGeneralQuestionLogAnswer()) ? 1 : 0; questionTotalScore = questionTotalScore.add(defaultIfNull(questionLog.getQuestionScore(), questionLog.getStudentScore())); myTotalScore = myTotalScore.add(defaultIfNull(questionLog.getStudentScore(), ZERO)); idsSet.add(questionLog.getGeneralQuestionLogId().toString()); } // 及格分值比率 BigDecimal passRateSetting = new BigDecimal("0.6"); // 查询数据字典配置项 String passRateVal = coreDictService.getOrDefault("pass_rate", "0.6"); if (NumberUtil.isNumber(passRateVal)) { passRateSetting = new BigDecimal(passRateVal); } boolean isPass = NumberUtil.isGreaterOrEqual(myTotalScore, NumberUtil.mul(questionTotalScore, passRateSetting)); questionLogSummary.setQuestionSettingId(questionSettingId); questionLogSummary.setQuestionSettingName(questionSettingName); questionLogSummary.setQuestionSettingType(snapshotFromTypeEnum); questionLogSummary.setQuestionLogSummaryFromType(fromType); questionLogSummary.setQuestionSettingTotalScore(questionTotalScore); questionLogSummary.setQuestionLogSummaryStudentTotalScore(myTotalScore); questionLogSummary.setPersonId(student.getStudentId()); questionLogSummary.setQuestionLogIds(logsIds); questionLogSummary.setQuestionLogSummaryQuestionTotalCount(questionTotalCount); questionLogSummary.setQuestionLogSummaryStudentDoCount(sumDoCount); questionLogSummary.setQuestionLogSummarySuccessCount(sumSuccessCount); questionLogSummary.setQuestionLogSummaryErrorCount(sumErrorCount); BigDecimal successRate = NumberUtil.div(sumSuccessCount, questionTotalCount, 3).multiply(new BigDecimal(100)); questionLogSummary.setQuestionLogSummarySuccessRate(successRate); questionLogSummary.setQuestionLogSummaryCurrentPassRate(passRateSetting); questionLogSummary.setQuestionLogSummaryIsPass(BooleanUtil.toInteger(isPass)); questionLogSummary.setQuestionLogSummaryStatus(1); questionLogSummary.setQuestionLogSummaryAddTime(new Date()); questionLogSummary.setOrgId(student.getOrgId()); questionLogSummary.setUserId(student.getUserId()); if (null != maxFinish) { Long finishTime = maxFinish.getGeneralQuestionLogFinishTime(); questionLogSummary.setFinishTime(DateUtil.secondToTime(finishTime.intValue())); questionLogSummary.setFinishSecondTime(finishTime); } QuestionLogSummaryQuery summaryQuery = new QuestionLogSummaryQuery(); summaryQuery.setQuestionLogSummaryStatus(1); summaryQuery.setPersonId(student.getStudentId()); summaryQuery.setQuestionSettingId(questionSettingId); summaryQuery.setQuestionLogSummaryFromType(fromType); QuestionLogSummary logSummary = questionLogSummaryService.getInfo(summaryQuery); //// 这里是确保只有一条记录存在,同一个学生同一套试题 if (logSummary != null) { //questionLogSummary.setQuestionLogSummaryId(logSummary.getQuestionLogSummaryId()); // 这个学生之前的其他试卷都设置成删除状态。 QuestionLogSummary updatePO = new QuestionLogSummary(); updatePO.setQuestionLogSummaryId(logSummary.getQuestionLogSummaryId()); updatePO.setQuestionLogSummaryStatus(GlobalStatusEnum.DELETED.getCode()); questionLogSummaryService.updateTemplate(updatePO); } // // 更新或插入统计数据 // questionLogSummaryService.upsertByTemplate(questionLogSummary); // 插入统计数据 questionLogSummaryService.insert(questionLogSummary); } /** * 验证前端传递过来的添加时间是否是最新的 * @param questionSettingId 题目配置ID * @param questionSnapshotIds 题目快照ID * @param studentId 学生ID * @param addTime 前端传递的添加时间 * @return */ private boolean validateQuestionLogAddTimeLatest(@NotNull(message = "开课题目配置ID不能为空!") Long questionSettingId, @NotBlank(message = "题目快照IDs不能为空!") String questionSnapshotIds, @NotNull(message = "学生ID不能为空!") Long studentId, @NotNull(message = "提交时间不能为空!") Date addTime) { return generalQuestionLogDao.validateQuestionLogAddTimeLatest(questionSettingId, questionSnapshotIds, studentId, addTime); } /** * 日志关联学生信息和配置ID信息 * * @param studentId 学生ID * @param questionSettingId 题目配置ID * @param preAddType 初始化的提交类型 * @param resourcesQuestionSnapshots 题目快照结合 * @param isReSend 是否根据题目快照强制重新发题 */ public void preSubmitStudentQuestionLog(@NotNull(message = "学生ID不能为空!") final Long studentId, @NotNull(message = "题目配置ID不能为空!") final Long questionSettingId, @Nullable QuestionLogAddTypeEnum preAddType, @NotEmpty(message = "题目快照列表不能为空!") List resourcesQuestionSnapshots, boolean isReSend ) { if (CollectionUtil.isEmpty(resourcesQuestionSnapshots)) { return; } // 不是强制发题,则不覆盖现有的题目日志,则继续做题 // 验证题目日志,是否已经存在试卷 if(!isReSend) { long logCount = generalQuestionLogDao.getNotSubmitQuestionLogsCount(studentId, questionSettingId); // 如果题目日志里存在预先布置的题目,则直接返回 if (logCount > 0) { return; } } // generalQuestionSettingId:类似试卷ID,题目日志里会出现重复的试卷ID // 逻辑删除之前的题目日志,防止学生做题统计数据异常 // 保证试卷是最新的 // 考试逻辑删除记录,作业或者章节练习都是直接删除记录。 // 强制发题 if (isReSend) { logicDeleteBySettingIds(questionSettingId.toString()); } // 断言 Assert.notBlank(resourcesQuestionSnapshots.get(0).getQuestionAnswer(), "题目快照选项不能为空!"); List generalQuestionLogs = BeanUtil.copyToList(resourcesQuestionSnapshots, GeneralQuestionLog.class); // 设计个单表,后面进行修改操作 generalQuestionLogs.forEach(questionLog -> { // 题目的答案,如果最后一位是逗号,则删除掉 String questionAnswer = ReUtil.replaceAll(questionLog.getQuestionAnswer(), "\\,$", ""); questionLog.setGeneralQuestionSettingId(questionSettingId); questionLog.setGeneralQuestionLogStatus(1); questionLog.setQuestionLogAddType(preAddType); questionLog.setStudentId(studentId); questionLog.setGeneralQuestionLogAddTime(new Date()); questionLog.setQuestionAnswer(questionAnswer); }); insertBatch(generalQuestionLogs); } /** * 根据题目配置ID查询题目快照,并根据类型分组 * * @param questionSettingId * @return */ public Map> questionAnalysis(@NotNull(message = "题目配置ID不能为空!") Long questionSettingId, @NotNull(message = "学生ID不能为空!") Long studentId) { GeneralQuestionLogQuery query = new GeneralQuestionLogQuery(); query.setGeneralQuestionSettingId(questionSettingId); query.setGeneralQuestionLogStatus(1); query.setStudentId(studentId); List valuesByQuery = getValuesByQueryNotWithPermission(query); return questionAnalysis(valuesByQuery); } /** * 根据题目快照ID查询题目快照,并根据类型分组 * * @param questionSnapshotIds * @return */ public Map> questionAnalysis(@NotNull(message = "题目配置ID不能为空!") TreeSet questionSnapshotIds, @NotNull(message = "学生ID不能为空!") Long studentId) { GeneralQuestionLogQuery query = new GeneralQuestionLogQuery(); query.setGeneralQuestionLogIdPlural(join(questionSnapshotIds.toArray(), ",")); query.setGeneralQuestionLogStatus(1); query.setStudentId(studentId); List valuesByQuery = getValuesByQueryNotWithPermission(query); return questionAnalysis(valuesByQuery); } /** * 题目相关-考试或者作业,答题提交后,获取问题解析。根据配置获取 * @param questionLogList * @return */ public Map> questionAnalysis(@NotEmpty(message = "未查询到题目列表!") final List questionLogList) { GeneralQuestionSettingQuery settingQuery = new GeneralQuestionSettingQuery(); settingQuery.setGeneralQuestionSettingId(questionLogList.get(0).getGeneralQuestionSettingId()); final GeneralQuestionSetting hwSetting = generalQuestionSettingService.getInfo(settingQuery); // 答卷后显示答案解析 final Boolean isEndShowQa = BooleanUtil.toBoolean(String.valueOf(hwSetting.getGeneralQuestionSettingEndShowQa())); // 答卷后显示答案对错 final Boolean isEndShowTrueFalse = BooleanUtil.toBoolean(String.valueOf(hwSetting.getGeneralQuestionSettingEndShowTrueFalse())); return questionLogList.stream().map(item -> { GeneralQuestionLog questionLog = new GeneralQuestionLog(); questionLog.setGeneralResourcesQuestionSnapshotId(item.getGeneralResourcesQuestionSnapshotId()); questionLog.setQuestionAnswer(isEndShowQa ? item.getQuestionAnswer() : ""); questionLog.setQuestionAnalysis(isEndShowTrueFalse ? item.getQuestionAnswer(): ""); return questionLog; }).collect(groupingBy(GeneralQuestionLog::getQuestionType)); } /** * 查询学生的分数列表信息(分页) * 不带权限 * * @param query */ public PageQuery studentScoreList(PageQuery query) { PageQuery ret = generalQuestionLogDao.studentScoreList(query); queryListAfter(ret.getList()); return ret; } /** * 收藏题目,根据题目配置ID + 题目快照ID * * @param questionSettingId 题目配置ID * @param questionSnapIds 题目快照IDs * @param student 学生信息 */ public List getListBySettingIdAndSnapIds( @NotNull(message = "题目配置ID不能为空!") Long questionSettingId, @NotBlank(message = "题目快照IDs不能为空!") String questionSnapIds, @NotNull Student student) { GeneralQuestionLogQuery logQuery = new GeneralQuestionLogQuery(); logQuery.setGeneralQuestionSettingId(questionSettingId); logQuery.setGeneralResourcesQuestionSnapshotIdPlural(questionSnapIds); logQuery.setGeneralQuestionLogStatus(1); logQuery.setStudentId(student.getStudentId()); return getValuesByQueryNotWithPermission(logQuery); } /** * 收藏题目,根据题目配置ID + 题目快照ID * * @param questionSettingId 题目配置ID * @param questionSnapIds 题目快照IDs * @param isTuck 是否收藏 true收藏 false取消收藏 * @param student 学生信息 */ public void tuck(@NotNull(message = "题目配置ID不能为空!") Long questionSettingId, @NotBlank(message = "题目快照IDs不能为空!") String questionSnapIds, boolean isTuck, @NotNull(message = "学生信息不能为空!") Student student) { List list = getListBySettingIdAndSnapIds(questionSettingId, questionSnapIds, student); Assert.notEmpty(list, "收藏失败,未查询到要收藏的题目"); String logIds = list.stream() .map(o -> o.getGeneralQuestionLogId().toString()).collect(joining(",")); // 根据做题的日志ID,收藏题目 tuckByLogIds(logIds, isTuck); } /** * 批量收藏题目 * @param generalQuestionLogIds * @param isTuck */ public void tuckByLogIds(@NotEmpty(message = "题目日志ID不能为空!") String generalQuestionLogIds, final boolean isTuck) { Function stringQuestionLogFunction = logId -> { GeneralQuestionLog questionLog = new GeneralQuestionLog(); questionLog.setGeneralQuestionLogId(Long.valueOf(logId)); questionLog.setIsTuck(isTuck); return questionLog; }; List logSet = Arrays.asList(generalQuestionLogIds.split(",")).stream() .map(stringQuestionLogFunction).collect(Collectors.toList()); updateBatchTemplate(logSet); } /** * 批量收藏到错题库 * @param generalQuestionLogIds * @param isErrorFavorite */ public void errorFavorite(@NotEmpty(message = "题目日志ID不能为空!") String generalQuestionLogIds, final boolean isErrorFavorite) { Function stringgeneralQuestionLogFunction = logId -> { GeneralQuestionLog questionLog = new GeneralQuestionLog(); questionLog.setGeneralQuestionLogId(Long.valueOf(logId)); questionLog.setIsErrorFavorite(isErrorFavorite); return questionLog; }; List logSet = Arrays.asList(generalQuestionLogIds.split(",")).stream() .map(stringgeneralQuestionLogFunction).collect(Collectors.toList()); updateBatchTemplate(logSet); } /** * 查询分数详细信息(系统,非教师开课) * @param query 题目分数查询实体 * @return */ public PageQuery getQuestionLogScoreDetailsInfo(PageQuery query) { return generalQuestionLogDao.getQuestionLogScoreDetailsInfo(query); } /** * 验证是否需要重新发题,则返回重新发题的标记 * @param questionSettingId 题目配置ID * @param studentId 学生ID * @param questionSettingType 题目配置类型 * @param questionLogAddType 题目日志添加类型(用于判断是否交卷) * @return boolean */ public boolean verifyLogAddTypeIsReSend(Long questionSettingId, Long studentId, ResourcesQuestionSnapshotFromTypeEnum questionSettingType, QuestionLogAddTypeEnum questionLogAddType) { // 只有章节练习,才能进行重发试题的操作 if (!questionSettingType.equals(CHAPTER_EXERCISE)) { return false; } if (ObjectUtil.isAllNotEmpty(questionSettingId, studentId, questionSettingType, questionLogAddType)) { return generalQuestionLogDao.verifyLogAddTypeIsReSend(questionSettingId, studentId, questionSettingType, questionLogAddType); } return false; } /** * 导出 * @param request * @param response * @param generalQuestionLogQuery * @param coreUser */ public void exportErrorFavorite(HttpServletRequest request, HttpServletResponse response, GeneralQuestionLogQuery generalQuestionLogQuery, CoreUser coreUser) { if(null == coreUser){ return; } HSSFWorkbook workbook = null; try { //表头数据 String[] header = { "通用题目配置", "题目快照", "学生提交的答案", "附件,学生端上传附件", "创建时间", "状态", "学生", "学生得分", "题型", "分值", "题干", "选项A", "选项B", "选项C", "选项D", "选项E", "答案", "解析", "是否收藏夹", "是否错题库", }; String[] headerCode = { "generalQuestionSettingId", "generalResourcesQuestionSnapshotId", "generalQuestionLogAnswer", "generalQuestionLogUploadFile", "generalQuestionLogAddTime", "generalQuestionLogStatus", "studentId", "studentScore", "questionType", "questionScore", "questionStem", "questionOptionA", "questionOptionB", "questionOptionC", "questionOptionD", "questionOptionE", "questionAnswer", "questionAnalysis", "isTuck", "isErrorFavorite", }; //数据内容 List> mapList = getExcelValues(generalQuestionLogQuery); //内容宽度 Map widthMap = mapList.get(0); mapList.remove(0); //声明一个工作簿 workbook = new HSSFWorkbook(); //生成一个表格,设置表格名称为"Sheet1" HSSFSheet sheet = workbook.createSheet("Sheet1"); //冻结表头 sheet.createFreezePane(0, 1, 0, 1); //设置默认列宽度为5个字节 sheet.setDefaultColumnWidth(5); //创建第一行表头 HSSFRow headRow = sheet.createRow(0); //头部样式 HSSFCellStyle headerStyle = workbook.createCellStyle(); //垂直居中 headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); //水平居中 headerStyle.setAlignment(HorizontalAlignment.CENTER); //单元格样式 HSSFCellStyle cellStyle = workbook.createCellStyle(); //垂直居中 cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //水平居左 cellStyle.setAlignment(HorizontalAlignment.CENTER); //自动换行 cellStyle.setWrapText(true); //遍历添加表头 for (int i = 0; i < header.length; i++) { //设置表格特定的列宽度 if (null != widthMap.get(headerCode[i])) { String width = widthMap.get(headerCode[i]).toString().split("\\.")[0]; Integer w = Integer.parseInt(width) > header[i].length()*3?Integer.parseInt(width):header[i].length()*3; sheet.setColumnWidth(i, w * 190); } //创建一个单元格 HSSFCell cell = headRow.createCell(i); //创建一个内容对象 HSSFRichTextString text = new HSSFRichTextString(header[i]); //将内容对象的文字内容写入到单元格中 cell.setCellValue(text); //设置样式 cell.setCellStyle(headerStyle); } //遍历结果集,把内容加入表格 for (int i = 0; i < mapList.size(); i++) { HSSFRow row = sheet.createRow(i + 1); row.setHeight((short) (50*10)); Map map = mapList.get(i); for (int j = 0; j < headerCode.length; j++) { HSSFCell cell = row.createCell(j); cell.setCellStyle(cellStyle); HSSFRichTextString text = new HSSFRichTextString(null != map.get(headerCode[j]) ? map.get(headerCode[j]).toString() : " "); cell.setCellValue(text); } } //准备将Excel的输出流通过response输出到页面下载 //八进制输出流 response.setContentType("application/octet-stream"); //这后面可以设置导出Excel的名称,此例中名为student.xls String fileName = ToolUtils.web2fileName(request,"generalQuestionLog(" + TimeTool.getNowTime("YMD") + ").xls"); response.setHeader("Content-disposition", "attachment;filename="+fileName); //刷新缓冲 response.flushBuffer(); //workbook将Excel写入到response的输出流中,供页面下载 workbook.write(response.getOutputStream()); }catch (Exception e){ e.printStackTrace(); } finally { try { if (null != workbook) { workbook.close(); } if (null != response && null != response.getOutputStream()) { response.getOutputStream().close(); } } catch (Exception e) { } } } /** * 功能描述:
* 获取实体的答案和解析,并锁定题目日志的状态,无法导致不能去修改选项 * * @param questionSettingId 题目配置ID * @param questionSnapIds 题目快照IDs * @param student 学生信息 * @return {@link List< QuestionLogAnswerLockVO>} * @Author: lx * @Date: 2022/12/1 0:55 */ public List questionLogAnswerLock( @NotNull(message = "题目配置ID不能为空!") Long questionSettingId, @NotBlank(message = "题目快照IDs不能为空!") String questionSnapIds, @NotNull Student student) { final List list = getListBySettingIdAndSnapIds(questionSettingId, questionSnapIds, student); Assert.notEmpty(list, "未查询到试卷题目信息!"); // 只取非完成状态的数据 Predicate notFinishPredicate = questionLog -> !questionLog.getQuestionLogAddType().equals(FINALLY_SUBMIT); // 构建数据 List questionLogs = list.stream().filter(notFinishPredicate).map(questionLog -> { GeneralQuestionLog generalQuestionLog = new GeneralQuestionLog(); generalQuestionLog.setGeneralQuestionLogId(questionLog.getGeneralQuestionLogId()); generalQuestionLog.setQuestionLogAddType(LOCK); // 锁定状态 return generalQuestionLog; }).collect(Collectors.toList()); // 批量修改锁定的状态 updateBatchTemplate(questionLogs); // 只取部分属性给到前端 return BeanUtil.copyToList(list, QuestionLogAnswerLockVO.class); } }