merge
This commit is contained in:
GoCo 2025-09-17 09:55:23 +08:00
commit 7dda8cae1c
3 changed files with 398 additions and 5 deletions

View File

@ -1,8 +1,10 @@
package org.jeecg.modules.aiol.controller;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -14,7 +16,9 @@ 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;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
@ -22,6 +26,7 @@ import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.aiol.dto.ExaminationResult;
import org.jeecg.modules.aiol.dto.QuestionAnswerDTO;
import org.jeecg.modules.aiol.dto.QuestionAnswerUser;
import org.jeecg.modules.aiol.dto.QuestionExcelDTO;
import org.jeecg.modules.aiol.entity.*;
import org.jeecg.modules.aiol.service.*;
@ -30,6 +35,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.poi.excel.ExcelExportUtil;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
@ -648,6 +654,385 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
return Result.OK(examinationResultList);
}
@GetMapping("/exportXls")
@Operation(summary = "试卷题目excel导出")
public void exportXls(@RequestParam(name = "paperId", required = true) String paperId, HttpServletResponse response) {
try {
// 获取试卷
AiolPaper paper = paperService.getById(paperId);
if (paper == null) {
throw new RuntimeException("试卷不存在");
}
// 获取所有题目
List<AiolPaperQuestion> list = paperQuestionService.list(new LambdaQueryWrapper<AiolPaperQuestion>().eq(AiolPaperQuestion::getPaperId, paperId));
Map<String, AiolPaperQuestion> questionMap = list.stream()
.collect(Collectors.toMap(AiolPaperQuestion::getQuestionId, Function.identity()));
List<AiolQuestion> questions = questionService.list(new LambdaQueryWrapper<AiolQuestion>().in(AiolQuestion::getId, list.stream().map(AiolPaperQuestion::getQuestionId).collect(Collectors.toList())));
List<AiolQuestion> questionList = new ArrayList<>();
if (questions.isEmpty()) {
throw new RuntimeException("没有题目");
} else {
//获取复合题id
List<String> parentIds = questions.stream().filter(q -> q.getType() == 5).map(AiolQuestion::getId).collect(Collectors.toList());
//获取复合题下的子题
if (!parentIds.isEmpty()) {
List<AiolQuestion> childQuestions = questionService.list(new LambdaQueryWrapper<AiolQuestion>().in(AiolQuestion::getParentId, parentIds));
//将子题目放到复合题后面
// 创建一个映射用于快速查找父ID对应的子题目
Map<String, List<AiolQuestion>> parentToChildrenMap = childQuestions.stream()
.collect(Collectors.groupingBy(AiolQuestion::getParentId));
// 将子题目添加到原始列表中保持原有顺序
for (AiolQuestion question : questions) {
questionList.add(question);
if (question.getType() == 5) {
List<AiolQuestion> children = parentToChildrenMap.getOrDefault(question.getId(), Collections.emptyList());
questionList.addAll(children);
}
}
} else {
questionList.addAll(questions);
}
}
// 3. 转换为ExcelDTO
List<QuestionExcelDTO> dtoList = new ArrayList<>();
for (AiolQuestion question : questionList) {
QuestionExcelDTO dto = new QuestionExcelDTO();
dto.setQuestionId(question.getId());
dto.setContent(question.getContent());
dto.setAnalysis(question.getAnalysis());
dto.setScore(questionMap.get(question.getId()).getScore());
// 设置题目类型
switch (question.getType()) {
case 0:
dto.setType("单选");
break;
case 1:
dto.setType("多选");
break;
case 2:
dto.setType("判断");
break;
case 3:
dto.setType("填空");
break;
case 4:
dto.setType("简答");
break;
case 5:
dto.setType("复合题");
break;
}
// 设置难度
switch (question.getDifficulty()) {
case 0:
dto.setDifficulty("简单");
break;
case 1:
dto.setDifficulty("中等");
break;
case 2:
dto.setDifficulty("困难");
break;
}
// 设置程度
switch (question.getDegree()) {
case 0:
dto.setDegree("了解");
break;
case 1:
dto.setDegree("熟悉");
break;
case 2:
dto.setDegree("掌握");
break;
}
// 设置能力
switch (question.getAbility()) {
case 0:
dto.setAbility("识记");
break;
case 1:
dto.setAbility("理解");
break;
case 2:
dto.setAbility("应用");
break;
}
// 设置选项和答案
if (question.getType() >= 0 && question.getType() <= 2) {
// 选择题多选题判断题
List<AiolQuestionOption> options = questionOptionService.list(
new LambdaQueryWrapper<AiolQuestionOption>()
.eq(AiolQuestionOption::getQuestionId, question.getId())
.orderByAsc(AiolQuestionOption::getOrderNo)
);
// 设置选项
for (int i = 0; i < options.size(); i++) {
AiolQuestionOption option = options.get(i);
char optionLabel = (char) ('A' + i);
switch (optionLabel) {
case 'A':
dto.setOptionA(option.getContent());
break;
case 'B':
dto.setOptionB(option.getContent());
break;
case 'C':
dto.setOptionC(option.getContent());
break;
case 'D':
dto.setOptionD(option.getContent());
break;
case 'E':
dto.setOptionE(option.getContent());
break;
}
}
// 设置正确答案
StringBuilder correctAnswers = new StringBuilder();
for (AiolQuestionOption option : options) {
if (option.getIzCorrent() == 1) {
char optionLabel = (char) ('A' + (option.getOrderNo() - 1));
if (!correctAnswers.isEmpty()) correctAnswers.append(",");
correctAnswers.append(optionLabel);
}
}
dto.setCorrectAnswers(correctAnswers.toString());
} else if (question.getType() == 3 || question.getType() == 4) {
// 填空题简答题
List<AiolQuestionAnswer> answers = questionAnswerService.list(
new LambdaQueryWrapper<AiolQuestionAnswer>()
.eq(AiolQuestionAnswer::getQuestionId, question.getId())
.orderByAsc(AiolQuestionAnswer::getOrderNo)
);
StringBuilder answerTexts = new StringBuilder();
int orderNo = 1;
for (AiolQuestionAnswer answer : answers) {
if (!answerTexts.isEmpty()) {
if (orderNo != answer.getOrderNo()) {
answerTexts.append(",");
orderNo = answer.getOrderNo();
} else {
answerTexts.append("|");
}
}
answerTexts.append(answer.getAnswerText());
}
dto.setCorrectAnswers(answerTexts.toString());
}
dtoList.add(dto);
}
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(paper.getTitle() + "_试卷导出", "UTF-8").replaceAll("\\+", "%");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 使用AutoPoi创建Workbook
ExportParams exportParams = new ExportParams("试卷列表", "试卷题目数据");
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, QuestionExcelDTO.class, dtoList);
// 写入响应流
workbook.write(response.getOutputStream());
// 关闭资源
workbook.close();
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (Exception e) {
log.error("导出题库题目失败", e);
throw new RuntimeException("导出题库题目失败:" + e.getMessage());
}
}
@PostMapping("/importXls")
@Operation(summary = "试卷题目excel导入")
@Transactional
public Result<String> importXls(@RequestParam(name = "file", required = true) MultipartFile file,
@RequestParam(name = "paperId", required = true) String paperId) {
try {
// 1. 判断试卷是否存在
AiolPaper paper = paperService.getById(paperId);
if (paper == null) {
return Result.error("试卷不存在");
}
// 2. 读取Excel文件
ImportParams params = new ImportParams();
params.setTitleRows(1); // 标题行数
params.setHeadRows(1); // 表头行数
List<QuestionExcelDTO> dtoList = ExcelImportUtil.importExcel(file.getInputStream(), QuestionExcelDTO.class, params);
if (CollectionUtils.isEmpty(dtoList)) {
return Result.error("Excel文件为空或格式不正确");
}
// 3. 处理导入数据
List<AiolQuestion> questions = new ArrayList<>();
List<AiolQuestionOption> options = new ArrayList<>();
List<AiolQuestionAnswer> answers = new ArrayList<>();
List<AiolPaperQuestion> paperQuestions = new ArrayList<>();
String parentId = null;
int orderNo = 1; // 从1开始计数
for (QuestionExcelDTO dto : dtoList) {
// 3.1 创建题目
AiolQuestion question = new AiolQuestion();
question.setId(String.valueOf(IdWorker.getId()));
question.setContent(dto.getContent());
question.setAnalysis(dto.getAnalysis());
question.setScore(dto.getScore().intValue());
if (StringUtils.isNotBlank(dto.getParentId()) && dto.getParentId().equals("^")) {
question.setParentId(parentId);
} else {
parentId = question.getId();
}
// 设置题目类型
switch (dto.getType()) {
case "单选":
question.setType(0);
break;
case "多选":
question.setType(1);
break;
case "判断":
question.setType(2);
break;
case "填空":
question.setType(3);
break;
case "简答":
question.setType(4);
break;
case "复合题":
question.setType(5);
break;
default:
throw new RuntimeException("题目类型不正确:" + dto.getType());
}
// 设置难度
switch (dto.getDifficulty()) {
case "简单":
question.setDifficulty(0);
break;
case "中等":
question.setDifficulty(1);
break;
case "困难":
question.setDifficulty(2);
break;
}
// 设置程度
switch (dto.getDegree()) {
case "了解":
question.setDegree(0);
break;
case "熟悉":
question.setDegree(1);
break;
case "掌握":
question.setDegree(2);
break;
}
// 设置能力
switch (dto.getAbility()) {
case "识记":
question.setAbility(0);
break;
case "理解":
question.setAbility(1);
break;
case "应用":
question.setAbility(2);
break;
}
questions.add(question);
// 3.2 处理选项和答案
if (question.getType() >= 0 && question.getType() <= 2) {
// 选择题多选题判断题
String correctAnswers = dto.getCorrectAnswers();
if (StringUtils.isEmpty(correctAnswers)) {
return Result.error("题目 {} 缺少正确答案", dto.getContent());
}
String[] answerLabels = correctAnswers.split(",");
List<String> optionContents = Arrays.asList(
dto.getOptionA(),
dto.getOptionB(),
dto.getOptionC(),
dto.getOptionD(),
dto.getOptionE()
).stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (int i = 0; i < optionContents.size(); i++) {
AiolQuestionOption option = new AiolQuestionOption();
option.setQuestionId(question.getId());
option.setContent(optionContents.get(i));
option.setOrderNo(i + 1);
// 检查是否为正确答案
boolean isCorrect = Arrays.asList(answerLabels)
.contains(String.valueOf((char) ('A' + i)));
option.setIzCorrent(isCorrect ? 1 : 0);
options.add(option);
}
} else if (question.getType() == 3 || question.getType() == 4) {
// 填空题简答题
String correctAnswers = dto.getCorrectAnswers();
if (StringUtils.isEmpty(correctAnswers)) {
return Result.error("题目 {} 缺少正确答案", dto.getContent());
}
String[] answerTexts = correctAnswers.split(",");
for (int i = 0; i < answerTexts.length; i++) {
AiolQuestionAnswer answer = new AiolQuestionAnswer();
answer.setQuestionId(question.getId());
answer.setAnswerText(answerTexts[i]);
answer.setOrderNo(i + 1);
answers.add(answer);
}
}
// 3.3 创建试卷题目关联关系
AiolPaperQuestion aiolPaperQuestion = new AiolPaperQuestion();
aiolPaperQuestion.setPaperId(paperId);
aiolPaperQuestion.setQuestionId(question.getId());
aiolPaperQuestion.setOrderNo(orderNo++);
aiolPaperQuestion.setScore(dto.getScore());
paperQuestions.add(aiolPaperQuestion);
}
// 4. 批量保存数据
questionService.saveBatch(questions);
questionOptionService.saveBatch(options);
questionAnswerService.saveBatch(answers);
paperQuestionService.saveBatch(paperQuestions);
return Result.OK("导入成功");
} catch (Exception e) {
log.error("导入题库题目失败", e);
return Result.error("导入失败:" + e.getMessage());
}
}
//根据考试规则随机组卷
public List<String> random(List<AiolQuestionRepo> list, String rules) {
JSONObject ruleJson = JSON.parseObject(rules);

View File

@ -559,7 +559,7 @@ public class AiolRepoController extends JeecgController<AiolRepo, IAiolRepoServi
dto.setQuestionId(question.getId());
dto.setContent(question.getContent());
dto.setAnalysis(question.getAnalysis());
dto.setScore(question.getScore());
dto.setScore(Double.valueOf(question.getScore()));
// 设置题目类型
switch (question.getType()) {
@ -746,7 +746,7 @@ public class AiolRepoController extends JeecgController<AiolRepo, IAiolRepoServi
question.setId(String.valueOf(IdWorker.getId()));
question.setContent(dto.getContent());
question.setAnalysis(dto.getAnalysis());
question.setScore(dto.getScore());
question.setScore(dto.getScore().intValue());
if (StringUtils.isNotBlank(dto.getParentId()) && dto.getParentId().equals("^")) {
question.setParentId(parentId);
@ -822,7 +822,11 @@ public class AiolRepoController extends JeecgController<AiolRepo, IAiolRepoServi
// 3.2 处理选项和答案
if (question.getType() >= 0 && question.getType() <= 2) {
// 选择题多选题判断题
String[] answerLabels = dto.getCorrectAnswers().split(",");
String correctAnswers = dto.getCorrectAnswers();
if (StringUtils.isEmpty(correctAnswers)) {
return Result.error("题目 {} 缺少正确答案", dto.getContent());
}
String[] answerLabels = correctAnswers.split(",");
List<String> optionContents = Arrays.asList(
dto.getOptionA(),
dto.getOptionB(),
@ -847,7 +851,11 @@ public class AiolRepoController extends JeecgController<AiolRepo, IAiolRepoServi
}
} else if (question.getType() == 3 || question.getType() == 4) {
// 填空题简答题
String[] answerTexts = dto.getCorrectAnswers().split(",");
String correctAnswers = dto.getCorrectAnswers();
if (StringUtils.isEmpty(correctAnswers)) {
return Result.error("题目 {} 缺少正确答案", dto.getContent());
}
String[] answerTexts = correctAnswers.split(",");
for (int i = 0; i < answerTexts.length; i++) {
AiolQuestionAnswer answer = new AiolQuestionAnswer();
answer.setQuestionId(question.getId());

View File

@ -58,7 +58,7 @@ public class QuestionExcelDTO {
private String ability;
@Excel(name = "分值", width = 15)
private Integer score;
private Double score;