merge
This commit is contained in:
GoCo 2025-08-29 17:21:51 +08:00
commit 337ba0f42a
7 changed files with 230 additions and 36 deletions

View File

@ -1,5 +1,7 @@
package org.jeecg.modules.biz.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@ -18,11 +20,16 @@ import org.jeecg.modules.gen.paper.entity.Paper;
import org.jeecg.modules.gen.paper.service.IPaperService;
import org.jeecg.modules.gen.paperquestion.entity.PaperQuestion;
import org.jeecg.modules.gen.paperquestion.service.IPaperQuestionService;
import org.jeecg.modules.gen.question.controller.QuestionController;
import org.jeecg.modules.gen.question.entity.Question;
import org.jeecg.modules.gen.question.entity.QuestionRequest;
import org.jeecg.modules.gen.question.service.IQuestionService;
import org.jeecg.modules.gen.questionoption.entity.QuestionOption;
import org.jeecg.modules.gen.questionoption.service.IQuestionOptionService;
import org.jeecg.modules.gen.questionrepo.entity.QuestionRepo;
import org.jeecg.modules.gen.questionrepo.service.IQuestionRepoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@ -48,10 +55,13 @@ public class ExamBizController {
private IQuestionService questionService;
@Autowired
private IQuestionRepoService questionRepoService;
@Autowired
private IQuestionOptionService questionOptionService;
//获取考试试题
@RequestMapping("/getExamQuestions/{examId}")
@Operation(summary = "获取考试试题")
@Transactional
public Result<?> getExamQuestions(@PathVariable String examId, @RequestParam String studentId) {
Exam exam = examService.getById(examId);
if(exam.getPaperId().isEmpty()){
@ -67,9 +77,8 @@ public class ExamBizController {
new LambdaQueryWrapper<QuestionRepo>().
eq(QuestionRepo::getRepoId, paper.getRepoId())
);
questionIds = list.stream()
.map(QuestionRepo::getQuestionId)
.collect(Collectors.toList());
//筛选试卷
questionIds = random(list, paper.getRules());
}else {
//固定组卷
// 获取试卷中的试题关联信息
@ -168,6 +177,7 @@ public class ExamBizController {
@PostMapping("/submitExam")
@Operation(summary = "提交考试")
@Transactional
public Result<?> submitExam(@RequestBody ExamRecord examRecord,
HttpServletRequest req) {
examRecord.setIpAddress(getClientIp(req));
@ -186,6 +196,9 @@ public class ExamBizController {
if (examRecord.getDeviceInfo() != null) {
updateWrapper.set(ExamRecord::getDeviceInfo, examRecord.getDeviceInfo());
}
// 阅卷
List<ExamAnswer> gradedAnswers = gradeExam(examRecord.getExamId(), examRecord.getUserId());
examAnswerService.updateBatchById(gradedAnswers);
// 更新考试状态提交时间
updateWrapper.
set(ExamRecord::getStatus,1).
@ -194,6 +207,67 @@ public class ExamBizController {
return examRecordService.update(updateWrapper) ? Result.OK() : Result.error("提交考试失败");
}
@GetMapping("/queryExamProgress")
@Operation(summary = "查询考试进度")
public Result<?> queryExamProgress(@RequestParam String examId, @RequestParam String userId) {
Exam byId = examService.getById(examId);
if(byId == null){
return Result.error("考试不存在");
}
//判断考试结束时间
if(byId.getEndTime().before(new Date())){
return Result.error("考试已结束");
}
ExamRecord one = examRecordService.getOne(
new LambdaQueryWrapper<ExamRecord>()
.eq(ExamRecord::getExamId, examId)
.eq(ExamRecord::getUserId, userId)
);
if(one == null){
return Result.error("用户暂未考试,可获取考试题目");
}
return Result.OK(examAnswerService.
list(new LambdaQueryWrapper<ExamAnswer>().
eq(ExamAnswer::getExamId, examId).
eq(ExamAnswer::getUserId, userId))
);
}
//根据考试规则随机组卷
public List<String> random(List<QuestionRepo> list, String rules) {
JSONObject ruleJson = JSON.parseObject(rules);
// 根据规则筛选和随机抽取题目
Map<Integer, List<String>> typeQuestions = new HashMap<>();
// 先按题目类型分组
list.forEach(qr -> {
Question question = questionService.getById(qr.getQuestionId());
if (!typeQuestions.containsKey(question.getType())) {
typeQuestions.put(question.getType(), new ArrayList<>());
}
typeQuestions.get(question.getType()).add(question.getId());
});
// 根据规则随机抽取题目
List<String> questionIds = new ArrayList<>();
for (Integer type : typeQuestions.keySet()) {
int count = ruleJson.getInteger("type"+type + "_count"); // 例如single_choice_count
List<String> typeQuestionIds = typeQuestions.get(type);
// 随机抽取指定数量的题目
if (typeQuestionIds.size() <= count) {
questionIds.addAll(typeQuestionIds);
} else {
// 打乱顺序后取前count个
Collections.shuffle(typeQuestionIds);
questionIds.addAll(typeQuestionIds.subList(0, count));
}
}
return questionIds;
}
//获取ip信息
public String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
@ -219,6 +293,91 @@ public class ExamBizController {
return ip;
}
/**
* 批量阅卷
* @param examId 考试ID
* @param userId 用户ID
* @return 阅卷后的答题列表
*/
private List<ExamAnswer> gradeExam(String examId, String userId) {
// 获取学生的答题列表
List<ExamAnswer> examAnswerList = examAnswerService.list(new LambdaQueryWrapper<ExamAnswer>()
.eq(ExamAnswer::getExamId, examId)
.eq(ExamAnswer::getUserId, userId)
);
// 提取所有题目ID
List<String> questionIds = examAnswerList.stream()
.map(ExamAnswer::getQuestionId)
.collect(Collectors.toList());
// 查询题目
List<Question> questions = questionService.list(new LambdaQueryWrapper<Question>()
.in(Question::getId, questionIds)
.lt(Question::getType, 3)
);
// 创建题目ID到题目的映射
Map<String, Question> questionMap = questions.stream()
.collect(Collectors.toMap(Question::getId, question -> question));
// 获取 选择多选判断 题目ID列表
List<String> questionIdsFromQuestions = questions.stream()
.map(Question::getId)
.collect(Collectors.toList());
// 查询这些题目的正确选项
List<QuestionOption> questionOptions = questionOptionService.list(new LambdaQueryWrapper<QuestionOption>()
.in(QuestionOption::getQuestionId, questionIdsFromQuestions)
.eq(QuestionOption::getIzCorrent, 1)
);
// 将选项转换为Map结构为题目ID -> 正确答案选项ID列表
Map<String, List<String>> correctAnswerMap = questionOptions.stream()
.collect(Collectors.groupingBy(
QuestionOption::getQuestionId,
Collectors.mapping(QuestionOption::getId, Collectors.toList())
));
// 遍历学生的答案进行评分
for (ExamAnswer examAnswer : examAnswerList) {
String studentAnswer = examAnswer.getAnswer();
Question question = questionMap.get(examAnswer.getQuestionId());
List<String> correctAnswers = correctAnswerMap.get(examAnswer.getQuestionId());
// 比较答案并设置分数
if (studentAnswer != null && question != null && correctAnswers != null) {
// 将学生答案按逗号分割成列表
List<String> studentAnswers = Arrays.asList(studentAnswer.split(","));
double score = 0.0;
// 根据题目类型进行评分
if (question.getType() == 1 || question.getType() == 2) { // 单选题或判断题
if (studentAnswers.get(0).equals(correctAnswers.get(0))) {
score = question.getScore(); // 使用题目设定的分值
}
} else if (question.getType() == 3) { // 多选题
// 检查学生答案数量是否正确
if (studentAnswers.size() == correctAnswers.size()) {
// 检查每个答案是否都正确
boolean allCorrect = studentAnswers.stream()
.allMatch(correctAnswers::contains);
if (allCorrect) {
score = question.getScore(); // 使用题目设定的分值
}
}
}
examAnswer.setIzCorrect(score> 0.0 ? 1 : 0);
examAnswer.setScore(score);
} else {
examAnswer.setScore(0.0); // 答案为空或题目不存在
}
}
return examAnswerList;
}
//获取设备信息
public String getDeviceInfo(HttpServletRequest request) {
// 获取User-Agent
String userAgent = request.getHeader("User-Agent");
@ -226,7 +385,6 @@ public class ExamBizController {
String deviceInfo = parseDeviceInfo(userAgent);
return deviceInfo;
}
private String parseDeviceInfo(String userAgent) {
if (userAgent == null) {
return "Unknown";

View File

@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.biz.constant.EntityLinkConst;
import org.jeecg.modules.biz.dto.QuestionAnswerDTO;
import org.jeecg.modules.biz.service.EntityLinkBizService;
import org.jeecg.modules.gen.question.entity.Question;
import org.jeecg.modules.gen.question.service.IQuestionService;
@ -41,7 +42,7 @@ public class RepoBizController {
@Autowired
private EntityLinkBizService entityLinkBizService;
@Autowired
private IQuestionService questionService;
private IQuestionService questionService;
@Autowired
private IQuestionOptionService questionOptionService;
@Autowired
@ -64,19 +65,33 @@ public class RepoBizController {
return Result.OK(repo.getId());
}
@GetMapping("repoList")
@Operation(summary = "获取所有题库")
public Result<List<Repo>> repoList() {
return Result.ok(repoService.list());
}
@PostMapping(value = "/courseAdd")
@Operation(summary = "课程新建题库")
public Result<String> courseAdd(@RequestBody Repo repo) {
return repoService.save(repo)?Result.OK("添加成功!"):Result.error("添加失败!");
return repoService.save(repo) ? Result.OK("添加成功!") : Result.error("添加失败!");
}
@GetMapping("/questionList/{repoId}")
@Operation(summary = "查询题库下题目")
public Result<List<Question>> questionList(@PathVariable String repoId) {
List<String> list = questionRepoService.questionList(repoId);
List<Question> questions = questionService.listByIds(list);
// 获取题库中的题目ID列表
List<String> repoQuestionIds = questionRepoService.questionList(repoId);
if (repoQuestionIds.isEmpty()) {
return Result.error("该题库下没有题目或该题库不存在");
}
// 根据ID列表查询题目
List<Question> questions = questionService.listByIds(repoQuestionIds);
if(questions.isEmpty()){
return Result.error("题目不存在");
}
//获取复选题id
List<String> type5Ids = questions.stream()
.filter(question -> question.getType() == 5)
@ -121,13 +136,20 @@ public class RepoBizController {
@GetMapping("/repoList/{questionId}")
@Operation(summary = "查询题目详情")
public Result<?> questionDetail(@PathVariable String questionId,@RequestParam Integer type) {
if (type == 0 || type == 1 || type == 2) {
List<QuestionOption> questionOptions = choiceDetail(questionId);
return Result.ok(questionOptions);
}else if(type == 3 || type == 4) {
return Result.ok(answerDetail(questionId));
}else {
public Result<?> questionDetail(@PathVariable String questionId) {
Question rootQuestion = questionService.getById(questionId);
if (rootQuestion == null) {
return Result.error("题目不存在");
}
QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
questionAnswerDTO.setQuestion(rootQuestion);
if (rootQuestion.getType() >= 0 && rootQuestion.getType() <= 2) {
questionAnswerDTO.setAnswer(choiceDetail(questionId));
return Result.ok(questionAnswerDTO);
} else if (rootQuestion.getType() == 3 || rootQuestion.getType() == 4) {
questionAnswerDTO.setAnswer(answerDetail(questionId));
return Result.ok(questionAnswerDTO);
} else {
//查询复合题所包含的题目
List<Question> list = questionService.list(
new LambdaQueryWrapper<Question>().
@ -138,35 +160,31 @@ public class RepoBizController {
.collect(Collectors.partitioningBy(
q -> q.getType() > 2
));
Map<String, Map<String, List<?>>> questionOptionMap = new HashMap<>();
//获取选择题多选题判断题答案
List<Question> question = groupedQuestions.get(false);
if(!question.isEmpty()){
Map<String, List<?>> questionListHashMap = new HashMap<>();
if (!question.isEmpty()) {
question.forEach(q -> {
ArrayList<Object> objects = new ArrayList<>();
objects.add(q);
objects.add(choiceDetail(q.getId()));
questionListHashMap.put(q.getId(), objects);
QuestionAnswerDTO qad = new QuestionAnswerDTO();
qad.setQuestion(q);
qad.setAnswer(choiceDetail(q.getId()));
questionAnswerDTO.getChildren().add(qad);
});
questionOptionMap.put("questionOption", questionListHashMap);
}
//获取填空题简答题答案
List<Question> question1 = groupedQuestions.get(true);
if(!question1.isEmpty()){
Map<String, List<?>> questionAnswerMap = new HashMap<>();
if (!question1.isEmpty()) {
question1.forEach(q -> {
ArrayList<Object> objects = new ArrayList<>();
objects.add(q);
objects.add(answerDetail(q.getId()));
questionAnswerMap.put(q.getId(), objects);
QuestionAnswerDTO qad = new QuestionAnswerDTO();
qad.setQuestion(q);
qad.setAnswer(answerDetail(q.getId()));
questionAnswerDTO.getChildren().add(qad);
});
questionOptionMap.put("questionAnswer", questionAnswerMap);
}
return Result.ok(questionOptionMap);
return Result.ok(questionAnswerDTO);
}
}
//查询选择题多选题判断题答案
public List<QuestionOption> choiceDetail(String questionId) {
return questionOptionService.list(
@ -180,7 +198,8 @@ public class RepoBizController {
public List<QuestionAnswer> answerDetail(String questionId) {
return questionAnswerService.list(
new LambdaQueryWrapper<QuestionAnswer>().
eq(QuestionAnswer::getQuestionId, questionId)
eq(QuestionAnswer::getQuestionId, questionId).
orderByAsc(QuestionAnswer::getOrderNo)
);
}

View File

@ -0,0 +1,17 @@
package org.jeecg.modules.biz.dto;
import lombok.Data;
import org.jeecg.modules.gen.question.entity.Question;
import java.util.ArrayList;
import java.util.List;
@Data
public class QuestionAnswerDTO {
//题目内容
private Question question;
//答案
private List<?> answer;
//子题目列表
private List<QuestionAnswerDTO> children = new ArrayList<>();
}

View File

@ -89,7 +89,7 @@ public class ExamController extends JeecgController<Exam, IExamService> {
public Result<String> add(@RequestBody Exam exam) {
examService.save(exam);
return Result.OK("添加成功!");
return Result.OK(exam.getId());
}
/**

View File

@ -102,7 +102,7 @@ public class PaperController extends JeecgController<Paper, IPaperService> {
public Result<String> add(@RequestBody Paper paper) {
paperService.save(paper);
return Result.OK("添加成功!");
return Result.OK(paper.getId());
}
/**

View File

@ -89,7 +89,7 @@ public class PaperQuestionController extends JeecgController<PaperQuestion, IPap
public Result<String> add(@RequestBody PaperQuestion paperQuestion) {
paperQuestionService.save(paperQuestion);
return Result.OK("添加成功!");
return Result.OK(paperQuestion.getId());
}
/**

View File

@ -91,7 +91,7 @@ public class QuestionController extends JeecgController<Question, IQuestionServi
public Result<String> add(@RequestBody Question question) {
questionService.save(question);
return Result.OK("添加成功!");
return Result.OK(question.getId());
}
/**