题库题目的excel导入导出

This commit is contained in:
Lqc 2025-09-05 15:52:13 +08:00
parent 00615712f8
commit 0a043be325
2 changed files with 452 additions and 4 deletions

View File

@ -1,5 +1,7 @@
package org.jeecg.modules.aiol.controller;
import java.io.ObjectOutputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
import java.io.IOException;
@ -9,16 +11,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.aiol.constant.EntityLinkConst;
import org.jeecg.modules.aiol.dto.QuestionAnswerDTO;
import org.jeecg.modules.aiol.entity.AiolQuestion;
import org.jeecg.modules.aiol.entity.AiolQuestionAnswer;
import org.jeecg.modules.aiol.entity.AiolQuestionOption;
import org.jeecg.modules.aiol.entity.AiolRepo;
import org.jeecg.modules.aiol.dto.QuestionExcelDTO;
import org.jeecg.modules.aiol.entity.*;
import org.jeecg.modules.aiol.mapper.AiolRepoMapper;
import org.jeecg.modules.aiol.service.*;
@ -27,6 +33,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;
@ -335,6 +342,373 @@ public class AiolRepoController extends JeecgController<AiolRepo, IAiolRepoServi
}
}
//题库题目excel导出
@GetMapping("/exportXls")
@Operation(summary = "题库题目excel导出")
public void exportXls(@RequestParam(name = "repoId", required = true) String repoId, HttpServletResponse response) {
try {
// 1. 获取题库信息
AiolRepo repo = aiolRepoService.getById(repoId);
if (repo == null) {
throw new RuntimeException("题库不存在");
}
// 2. 获取题库下的所有题目
List<AiolQuestion> questions = questionService.list(new LambdaQueryWrapper<AiolQuestion>()
.inSql(AiolQuestion::getId, "SELECT question_id FROM aiol_question_repo WHERE repo_id = '" + repoId + "'"));
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(question.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(repo.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导入")
public Result<String> importXls(@RequestParam(name = "file", required = true) MultipartFile file,
@RequestParam(name = "repoId", required = true) String repoId) {
try {
// 1. 验证题库是否存在
AiolRepo repo = aiolRepoService.getById(repoId);
if (repo == 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<AiolQuestionRepo> questionRepos = new ArrayList<>();
String parentId = null;
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());
if (StringUtils.isNotBlank(dto.getParentId()) && dto.getParentId().equals("^")) {
question.setParentId(parentId);
} else {
parentId = question.getId();
}
System.out.println(dto.getType());
// 设置题目类型
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[] answerLabels = dto.getCorrectAnswers().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[] answerTexts = dto.getCorrectAnswers().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 创建题库题目关联关系
AiolQuestionRepo questionRepo = new AiolQuestionRepo();
questionRepo.setRepoId(repoId);
questionRepo.setQuestionId(question.getId());
questionRepos.add(questionRepo);
}
// 4. 批量保存数据
questionService.saveBatch(questions);
questionOptionService.saveBatch(options);
questionAnswerService.saveBatch(answers);
questionRepoService.saveBatch(questionRepos);
// 5. 更新题库题目数量
repo.setQuestionCount(repo.getQuestionCount() != null ? repo.getQuestionCount()+questionRepos.size():questionRepos.size());
aiolRepoService.updateById(repo);
return Result.OK("导入成功");
} catch (Exception e) {
log.error("导入题库题目失败", e);
return Result.error("导入失败:" + e.getMessage());
}
}
//查询选择题多选题判断题答案
public List<AiolQuestionOption> choiceDetail(String questionId) {

View File

@ -0,0 +1,74 @@
package org.jeecg.modules.aiol.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.util.ArrayList;
import java.util.List;
@Data
public class QuestionExcelDTO {
@Excel(name = "题目ID", width = 15)
private String questionId;
@Excel(name = "父题目ID", width = 15)
private String parentId;
@Excel(name = "题目类型", width = 15)
private String type;
@Excel(name = "题干", width = 50)
private String content;
@Excel(name = "选项A", width = 30)
private String optionA;
@Excel(name = "选项B", width = 30)
private String optionB;
@Excel(name = "选项C", width = 30)
private String optionC;
@Excel(name = "选项D", width = 30)
private String optionD;
@Excel(name = "选项E", width = 30)
private String optionE;
@Excel(name = "选项F", width = 30)
private String optionF;
@Excel(name = "选项G", width = 30)
private String optionG;
@Excel(name = "正确答案", width = 15)
private String correctAnswers;
@Excel(name = "题目解析", width = 50)
private String analysis;
@Excel(name = "难度", width = 15)
private String difficulty;
@Excel(name = "程度", width = 15)
private String degree;
@Excel(name = "能力", width = 15)
private String ability;
@Excel(name = "分值", width = 15)
private Integer score;
// 内部使用存储解析后的选项列表
private List<OptionEntry> options = new ArrayList<>();
@Data
public static class OptionEntry {
private String content;
private boolean isCorrect;
private Integer orderNo;
}
}