feat: 题目解析和错题回顾

This commit is contained in:
2026-05-26 18:14:23 +08:00
parent faecd5586b
commit e4b4b89354
13 changed files with 168 additions and 2 deletions

View File

@@ -119,4 +119,9 @@ public class ExamPaperQuestionDetailVo implements Serializable {
* 题库题目逻辑删除标记hot_question_bank.is_deleted
*/
private Long questionIsDeleted;
/**
* 题目解析
*/
private String caseDesc;
}

View File

@@ -84,6 +84,10 @@ public class HotExamPaperQuestionServiceImpl implements IHotExamPaperQuestionSer
lqw.eq(bo.getQuestionId() != null, HotExamPaperQuestion::getQuestionId, bo.getQuestionId());
lqw.eq(bo.getSortOrder() != null, HotExamPaperQuestion::getSortOrder, bo.getSortOrder());
lqw.eq(bo.getScore() != null, HotExamPaperQuestion::getScore, bo.getScore());
// 修复试卷试题顺序问题
lqw.orderByAsc(HotExamPaperQuestion::getQuestionType)
.orderByAsc(HotExamPaperQuestion::getSortOrder)
.orderByAsc(HotExamPaperQuestion::getId);
return lqw;
}

View File

@@ -53,7 +53,7 @@ public class HotQuestionBankImportVo implements Serializable {
@ExcelDictFormat(readConverterExp = "1=普通,2=困难,3=极难")
private Long difficulty;
@ExcelProperty(value = "案例描述", index = 11)
@ExcelProperty(value = "解析", index = 11)
private String caseDesc;
@ExcelProperty(value = "题型分类", index = 12, converter = ExcelDictConvert.class)

View File

@@ -3,6 +3,7 @@ package com.hotwj.platform.securityManagement.trainingAnswer.controller;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.SubmitTrainingAnswerBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.TrainingAnswerRecordBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingAnswerRecordVo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingWrongQuestionReviewVo;
import com.hotwj.platform.securityManagement.trainingAnswer.service.IHotTrainingAnswerRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -12,6 +13,7 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
@@ -20,6 +22,7 @@ import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -138,4 +141,16 @@ public class HotTrainingAnswerRecordController extends BaseController {
public R<Void> submit(@RequestBody @Validated(AddGroup.class) SubmitTrainingAnswerBo bo) {
return toAjax(hotTrainingAnswerRecordService.submitWithSignature(bo));
}
@GetMapping("/wrongQuestion/review")
@Operation(summary = "查询错题回顾(当前用户)")
public R<List<HotTrainingWrongQuestionReviewVo>> wrongQuestionReview(TrainingAnswerRecordBo bo) {
Long companyId = LoginHelper.getCompanyId();
String userId = LoginHelper.getBusinessUserId();
if (StringUtils.isBlank(userId)) {
userId = LoginHelper.getUserIdStr();
}
List<HotTrainingWrongQuestionReviewVo> result = hotTrainingAnswerRecordService.queryWrongQuestionReview(companyId, userId, bo);
return R.ok(result);
}
}

View File

@@ -130,6 +130,16 @@ public class HotTrainingAnswerRecord extends BaseEntity {
*/
private Date answerTime;
/**
* 错误次数
*/
private Integer errorCount;
/**
* 错误答案,逗号拼接
*/
private String errorAnswers;
/**
* 0=正常, 1=已删除
*/

View File

@@ -73,6 +73,10 @@ public class TrainingAnswerRecordBo extends BaseEntity {
private Date answerTime;
private Integer errorCount;
private String errorAnswers;
private Long isDeleted;
}

View File

@@ -216,5 +216,7 @@ public class HotTrainingAnswerRecordVo implements Serializable {
private Boolean passed;
private Integer errorCount;
private String errorAnswers;
}

View File

@@ -0,0 +1,31 @@
package com.hotwj.platform.securityManagement.trainingAnswer.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class HotTrainingWrongQuestionReviewVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long questionId;
private String questionType;
private String questionDesc;
private String imageUrl;
private String optionA;
private String optionB;
private String optionC;
private String optionD;
private String optionE;
private String optionF;
private String correctAnswers;
private String analysis;
private Integer errorCount;
}

View File

@@ -1,7 +1,10 @@
package com.hotwj.platform.securityManagement.trainingAnswer.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.HotTrainingAnswerRecord;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.TrainingAnswerRecordBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingAnswerRecordVo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingWrongQuestionReviewVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
@@ -27,4 +30,8 @@ public interface HotTrainingAnswerRecordMapper extends BaseMapperPlus<HotTrainin
@Param("userId") String userId
);
java.util.List<HotTrainingWrongQuestionReviewVo> selectWrongQuestionReviewList(@Param("companyId") Long companyId,
@Param("userId") String userId,
@Param("bo") TrainingAnswerRecordBo bo);
}

View File

@@ -3,6 +3,7 @@ package com.hotwj.platform.securityManagement.trainingAnswer.service;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.TrainingAnswerRecordBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.SubmitTrainingAnswerBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingAnswerRecordVo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingWrongQuestionReviewVo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -84,4 +85,6 @@ public interface IHotTrainingAnswerRecordService {
* @return 是否成功
*/
Boolean submitWithSignature(SubmitTrainingAnswerBo bo);
List<HotTrainingWrongQuestionReviewVo> queryWrongQuestionReview(Long companyId, String userId, TrainingAnswerRecordBo bo);
}

View File

@@ -1,8 +1,11 @@
package com.hotwj.platform.securityManagement.trainingAnswer.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hotwj.platform.config.examPaperQuestion.domain.vo.ExamPaperQuestionDetailVo;
import com.hotwj.platform.config.examPaperQuestion.mapper.HotExamPaperQuestionMapper;
import com.hotwj.platform.flow.service.ISysFlowService;
import com.hotwj.platform.integration.ocr.SignatureVerifyService;
import com.hotwj.platform.noticeManagerment.systemNotification.domain.bo.HotSystemNotificationGroupBo;
@@ -17,6 +20,7 @@ import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.SubmitTrai
import com.hotwj.platform.securityManagement.trainingAnswer.domain.bo.TrainingAnswerRecordBo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.enums.TrainingSceneType;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingAnswerRecordVo;
import com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingWrongQuestionReviewVo;
import com.hotwj.platform.securityManagement.trainingAnswer.mapper.HotTrainingAnswerRecordMapper;
import com.hotwj.platform.securityManagement.trainingAnswer.service.IHotTrainingAnswerRecordService;
import com.hotwj.platform.securityManagement.trainingAudit.domain.HotTrainingAudit;
@@ -64,6 +68,7 @@ public class HotTrainingAnswerRecordServiceImpl implements IHotTrainingAnswerRec
private final TrainingProgressService trainingProgressService;
private final HotTrainingAuditMapper trainingAuditMapper;
private final SignatureVerifyService signatureVerifyService;
private final HotExamPaperQuestionMapper examPaperQuestionMapper;
/**
* 查询培训课程答题详情记录
@@ -392,6 +397,69 @@ public class HotTrainingAnswerRecordServiceImpl implements IHotTrainingAnswerRec
return Boolean.TRUE;
}
@Override
public List<HotTrainingWrongQuestionReviewVo> queryWrongQuestionReview(Long companyId, String userId, TrainingAnswerRecordBo bo) {
if (companyId == null || StringUtils.isBlank(userId)) {
return List.of();
}
// 1.查询试卷题目
List<ExamPaperQuestionDetailVo> paperQuestions = loadPlanPaperQuestionList(bo);
if (CollUtil.isEmpty(paperQuestions)) {
return List.of();
}
// 2.查询本次培训计划中错题信息
List<HotTrainingWrongQuestionReviewVo> wrongList = baseMapper.selectWrongQuestionReviewList(companyId, userId, bo);
if (CollUtil.isEmpty(wrongList)) {
return List.of();
}
// 3.组装返回给前端的错题信息
Map<Long, HotTrainingWrongQuestionReviewVo> wrongByQuestionId = wrongList.stream()
.filter(v -> v.getQuestionId() != null)
.collect(Collectors.toMap(HotTrainingWrongQuestionReviewVo::getQuestionId, Function.identity(), (a, b) -> a));
List<HotTrainingWrongQuestionReviewVo> orderedWrong = new ArrayList<>();
Set<Long> addedQuestionIds = new HashSet<>();
for (ExamPaperQuestionDetailVo questionDetail : paperQuestions) {
if (questionDetail == null || questionDetail.getQuestionId() == null) {
continue;
}
Long questionId = questionDetail.getQuestionId();
if (!addedQuestionIds.add(questionId)) {
continue;
}
HotTrainingWrongQuestionReviewVo vo = wrongByQuestionId.get(questionId);
if (vo == null) {
continue;
}
vo.setQuestionType(questionDetail.getQuestionType());
vo.setQuestionDesc(questionDetail.getQuestionDesc());
vo.setImageUrl(questionDetail.getImageUrl());
vo.setOptionA(questionDetail.getOptionA());
vo.setOptionB(questionDetail.getOptionB());
vo.setOptionC(questionDetail.getOptionC());
vo.setOptionD(questionDetail.getOptionD());
vo.setOptionE(questionDetail.getOptionE());
vo.setOptionF(questionDetail.getOptionF());
vo.setCorrectAnswers(questionDetail.getCorrectAnswers());
vo.setAnalysis(questionDetail.getCaseDesc());
orderedWrong.add(vo);
}
return orderedWrong;
}
private List<ExamPaperQuestionDetailVo> loadPlanPaperQuestionList(TrainingAnswerRecordBo bo) {
if (bo == null || bo.getExamId() == null) {
return List.of();
}
List<ExamPaperQuestionDetailVo> list = examPaperQuestionMapper.selectPaperQuestionDetailList(bo.getExamId());
if (CollUtil.isEmpty(list)) {
return List.of();
}
return list;
}
private void clearPlanAuditRecord(SubmitTrainingAnswerBo bo) {
if (bo.getCompanyId() == null || bo.getTrainingId() == null || StringUtils.isBlank(bo.getUserId())) {
return;

View File

@@ -26,7 +26,8 @@
qb.option_f AS optionF,
qb.correct_answers AS correctAnswers,
qb.score AS questionScore,
qb.is_deleted AS questionIsDeleted
qb.is_deleted AS questionIsDeleted,
qb.case_desc AS caseDesc
FROM hot_exam_paper_question epq
LEFT JOIN hot_question_bank qb ON qb.id = epq.question_id
WHERE epq.paper_id = #{paperId}

View File

@@ -136,4 +136,20 @@
ORDER BY ar.question_order ASC, ar.id ASC
</select>
<!-- 错题回顾 -->
<select id="selectWrongQuestionReviewList"
resultType="com.hotwj.platform.securityManagement.trainingAnswer.domain.vo.HotTrainingWrongQuestionReviewVo">
SELECT ar.question_id AS questionId,
count(ar.question_id) AS errorCount
FROM hot_training_answer_record ar
WHERE ar.is_deleted = 0
AND ar.company_id = #{companyId}
AND ar.user_id = #{userId}
AND ar.scene_type = 'PLAN'
AND ar.training_id = #{bo.trainingId}
AND ar.exam_id = #{bo.examId}
AND ar.error_answers IS NOT NULL
AND ar.error_answers &lt;&gt; ''
GROUP BY ar.question_id
</select>
</mapper>