From 0a043be325ed4006374e1a9fbaaf9e04f4d8bc11 Mon Sep 17 00:00:00 2001 From: Lqc Date: Fri, 5 Sep 2025 15:52:13 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=98=E5=BA=93=E9=A2=98=E7=9B=AE=E7=9A=84ex?= =?UTF-8?q?cel=E5=AF=BC=E5=85=A5=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiol/controller/AiolRepoController.java | 382 +++++++++++++++++- .../modules/aiol/dto/QuestionExcelDTO.java | 74 ++++ 2 files changed, 452 insertions(+), 4 deletions(-) create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/dto/QuestionExcelDTO.java diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolRepoController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolRepoController.java index a31db7af..e6a7bc06 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolRepoController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolRepoController.java @@ -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 questions = questionService.list(new LambdaQueryWrapper() + .inSql(AiolQuestion::getId, "SELECT question_id FROM aiol_question_repo WHERE repo_id = '" + repoId + "'")); + List questionList = new ArrayList<>(); + if (questions.isEmpty()) { + throw new RuntimeException("题库下没有题目"); + } else { + //获取复合题id + List parentIds = questions.stream().filter(q -> q.getType() == 5).map(AiolQuestion::getId).collect(Collectors.toList()); + //获取复合题下的子题 + if (!parentIds.isEmpty()) { + List childQuestions = questionService.list(new LambdaQueryWrapper().in(AiolQuestion::getParentId, parentIds)); + //将子题目放到复合题后面 + // 创建一个映射,用于快速查找父ID对应的子题目 + Map> parentToChildrenMap = childQuestions.stream() + .collect(Collectors.groupingBy(AiolQuestion::getParentId)); + // 将子题目添加到原始列表中,保持原有顺序 + for (AiolQuestion question : questions) { + questionList.add(question); + if (question.getType() == 5) { + List children = parentToChildrenMap.getOrDefault(question.getId(), Collections.emptyList()); + questionList.addAll(children); + } + } + } else { + questionList.addAll(questions); + } + } + // 3. 转换为ExcelDTO + List 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 options = questionOptionService.list( + new LambdaQueryWrapper() + .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 answers = questionAnswerService.list( + new LambdaQueryWrapper() + .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 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 dtoList = ExcelImportUtil.importExcel(file.getInputStream(), QuestionExcelDTO.class, params); + if (CollectionUtils.isEmpty(dtoList)) { + return Result.error("Excel文件为空或格式不正确"); + } + + // 3. 处理导入数据 + List questions = new ArrayList<>(); + List options = new ArrayList<>(); + List answers = new ArrayList<>(); + List 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 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 choiceDetail(String questionId) { diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/dto/QuestionExcelDTO.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/dto/QuestionExcelDTO.java new file mode 100644 index 00000000..b88e0900 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/dto/QuestionExcelDTO.java @@ -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 options = new ArrayList<>(); + + @Data + public static class OptionEntry { + private String content; + private boolean isCorrect; + private Integer orderNo; + } +} \ No newline at end of file