diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/model/entity/CourseManagementDO.java b/safety-cedu-service/src/main/java/org/qinan/cedu/model/entity/CourseManagementDO.java index fcf20410..e6e3a98c 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/model/entity/CourseManagementDO.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/model/entity/CourseManagementDO.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.jjb.saas.framework.repository.basedo.BaseDO; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperServiceImpl.java b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperServiceImpl.java index 6079aee2..93622c90 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperServiceImpl.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperServiceImpl.java @@ -1,12 +1,12 @@ package org.qinan.cedu.service.impl; +import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.toolkit.Db; -import cn.hutool.core.util.IdUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.qinan.cedu.convertor.PaperConvertor; @@ -27,6 +27,7 @@ import org.qinan.cedu.service.PaperQuestionRelService; import org.qinan.cedu.service.PaperService; import org.qinan.cedu.service.QuestionOptionService; import org.qinan.cedu.service.support.QuestionExcelImporter; +import org.qinan.safetyeval.infrastructure.support.InsertFieldDefaults; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -115,14 +116,14 @@ public class PaperServiceImpl extends ServiceImpl implemen List questionIds = rels.stream().map(PaperQuestionRelDO::getQuestionId).collect(Collectors.toList()); List questions = questionIds.isEmpty() ? new ArrayList<>() : Db.listByIds(questionIds, QuestionDO.class).stream() - .filter(q -> DeleteEnum.FALSE.getCode().equals(q.getDeleteEnum())) - .collect(Collectors.toList()); + .filter(q -> DeleteEnum.FALSE.getCode().equals(q.getDeleteEnum())) + .collect(Collectors.toList()); List existQuestionIds = questions.stream().map(QuestionDO::getId).collect(Collectors.toList()); List allOptions = existQuestionIds.isEmpty() ? new ArrayList<>() : questionOptionService.lambdaQuery() - .in(QuestionOptionDO::getQuestionId, existQuestionIds) - .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) - .list(); + .in(QuestionOptionDO::getQuestionId, existQuestionIds) + .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) + .list(); // 新增试卷(使用请求中的名称/总分/合格分数) LocalDateTime now = LocalDateTime.now(); @@ -247,6 +248,7 @@ public class PaperServiceImpl extends ServiceImpl implemen paper.setPaperName(importDTO.getPaperName().trim()); paper.setPaperTotalScore(importDTO.getPaperTotalScore()); paper.setPaperPassScore(importDTO.getPaperPassScore()); + InsertFieldDefaults.applyIgnoreOrgId(paper); this.savePaper(paper); // 保存试题和选项(id已预置雪花id) diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/service/support/QuestionExcelImporter.java b/safety-cedu-service/src/main/java/org/qinan/cedu/service/support/QuestionExcelImporter.java index 3e5d3e43..672ec69c 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/service/support/QuestionExcelImporter.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/service/support/QuestionExcelImporter.java @@ -4,15 +4,18 @@ import cn.hutool.core.util.IdUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.read.metadata.ReadSheet; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.qinan.cedu.enums.DeleteEnum; import org.qinan.cedu.enums.QuestionTypeEnum; +import org.qinan.cedu.mapper.CoursewareManagementMapper; import org.qinan.cedu.model.entity.CoursewareManagementDO; import org.qinan.cedu.model.entity.QuestionDO; import org.qinan.cedu.model.entity.QuestionOptionDO; import org.qinan.cedu.model.vo.PaperImportErrorVO; -import org.qinan.cedu.service.CoursewareManagementService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -33,6 +36,8 @@ import java.util.stream.Collectors; * 其余sheet忽略。 */ @Component +@RequiredArgsConstructor +@Slf4j public class QuestionExcelImporter { /** @@ -46,18 +51,36 @@ public class QuestionExcelImporter { private static final String[] CHOICE_OPTION_KEYS = {"A", "B", "C", "D"}; - @Autowired - private CoursewareManagementService coursewareManagementService; + private static final String UPLOAD_EMPTY_MESSAGE = "请上传试题Excel文件"; + private static final String READ_FILE_FAIL_MESSAGE = "读取上传文件失败"; + private static final String READ_SINGLE_SHEET_FAIL_PREFIX = "读取单选题sheet失败:"; + private static final String READ_MULTIPLE_SHEET_FAIL_PREFIX = "读取多选题sheet失败:"; + private static final String READ_JUDGE_SHEET_FAIL_PREFIX = "读取判断题sheet失败:"; + private static final String SHEET_FALLBACK_NAME_TEMPLATE = "sheet[%s]"; + private static final String OPTION_AT_LEAST_TWO_MESSAGE = "选项至少需要填写2个"; + private static final String CHOICE_ANSWER_LETTER_MESSAGE = "答案只能填写A、B、C、D、E、F中的字母"; + private static final String SINGLE_ANSWER_LETTER_MESSAGE = "单选题答案只能填写A、B、C、D中的一个"; + private static final String JUDGE_ANSWER_LETTER_MESSAGE = "判断题答案只能填写T或F"; + private static final String ANSWER_NOT_MATCH_OPTION_TEMPLATE = "答案[%s]找不到对应的选项"; + private static final String COURSEWARE_NOT_EXIST_TEMPLATE = "关联课件名称[%s]不存在"; + private static final String OPTION_KEY_A = "A"; + private static final String OPTION_KEY_B = "B"; + private static final String JUDGE_ANSWER_TRUE = "T"; + private static final String JUDGE_ANSWER_FALSE = "F"; + private static final String JUDGE_CORRECT_OPTION_TEXT = "对"; + private static final String JUDGE_WRONG_OPTION_TEXT = "错"; + private static final String COMMA_DELIMITER = ","; + private static final String VIOLATION_DELIMITER = ";"; - @Autowired - private Validator validator; + private final CoursewareManagementMapper coursewareManagementMapper; + private final Validator validator; /** * 解析上传的试题Excel,返回校验通过的试题(含选项,已预置雪花id)和异常明细 */ public ParseResult parse(MultipartFile file) { if (file == null || file.isEmpty()) { - throw new IllegalArgumentException("请上传试题Excel文件"); + throw new IllegalArgumentException(UPLOAD_EMPTY_MESSAGE); } byte[] bytes = readBytes(file); Map sheetNames = readSheetNames(bytes); @@ -70,19 +93,22 @@ public class QuestionExcelImporter { singleRows = readSheet(bytes, SINGLE_SHEET_NO, ChoiceQuestionImportRow.class); } catch (Exception e) { singleRows = new ArrayList<>(); - errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, SINGLE_SHEET_NO), "读取单选题sheet失败:" + e.getMessage())); + errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, SINGLE_SHEET_NO) + , READ_SINGLE_SHEET_FAIL_PREFIX + e.getMessage())); } try { multipleRows = readSheet(bytes, MULTIPLE_SHEET_NO, ChoiceQuestionImportRow.class); } catch (Exception e) { multipleRows = new ArrayList<>(); - errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, MULTIPLE_SHEET_NO), "读取多选题sheet失败:" + e.getMessage())); + errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, MULTIPLE_SHEET_NO) + , READ_MULTIPLE_SHEET_FAIL_PREFIX + e.getMessage())); } try { judgeRows = readSheet(bytes, JUDGE_SHEET_NO, JudgeQuestionImportRow.class); } catch (Exception e) { judgeRows = new ArrayList<>(); - errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, JUDGE_SHEET_NO), "读取判断题sheet失败:" + e.getMessage())); + errors.add(new PaperImportErrorVO(null, sheetName(sheetNames, JUDGE_SHEET_NO) + , READ_JUDGE_SHEET_FAIL_PREFIX + e.getMessage())); } Map coursewareManagementIdByName = loadCoursewareIdByName(singleRows, multipleRows, judgeRows); @@ -98,7 +124,7 @@ public class QuestionExcelImporter { try { return file.getBytes(); } catch (IOException e) { - throw new IllegalArgumentException("读取上传文件失败", e); + throw new IllegalArgumentException(READ_FILE_FAIL_MESSAGE, e); } } @@ -115,7 +141,7 @@ public class QuestionExcelImporter { } private String sheetName(Map sheetNames, int sheetNo) { - return sheetNames.getOrDefault(sheetNo, "sheet[" + (sheetNo + 1) + "]"); + return sheetNames.getOrDefault(sheetNo, String.format(SHEET_FALLBACK_NAME_TEMPLATE, sheetNo + 1)); } private List readSheet(byte[] bytes, int sheetNo, Class headClass) { @@ -139,11 +165,11 @@ public class QuestionExcelImporter { if (names.isEmpty()) { return Collections.emptyMap(); } - List coursewares = coursewareManagementService.lambdaQuery() - .select(CoursewareManagementDO::getId, CoursewareManagementDO::getCoursewareName) - .eq(CoursewareManagementDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) - .in(CoursewareManagementDO::getCoursewareName, names) - .list(); + List coursewares = coursewareManagementMapper.selectList( + new LambdaQueryWrapper() + .select(CoursewareManagementDO::getId, CoursewareManagementDO::getCoursewareName) + .eq(CoursewareManagementDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) + .in(CoursewareManagementDO::getCoursewareName, names)); Map idByName = new HashMap<>(); for (CoursewareManagementDO courseware : coursewares) { idByName.putIfAbsent(courseware.getCoursewareName(), courseware.getId()); @@ -189,14 +215,14 @@ public class QuestionExcelImporter { } } if (options.size() < 2) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "选项至少需要填写2个")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, OPTION_AT_LEAST_TWO_MESSAGE)); continue; } // 校验答案:必须有答案且答案必须能找到对应的选项 String answerRaw = row.getAnswer().trim().toUpperCase(); if (!answerRaw.chars().allMatch(c -> c >= 'A' && c <= 'F')) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "答案只能填写A、B、C、D、E、F中的字母")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, CHOICE_ANSWER_LETTER_MESSAGE)); continue; } List answerLetters = answerRaw.chars() @@ -205,14 +231,15 @@ public class QuestionExcelImporter { .sorted() .collect(Collectors.toList()); if (single && answerLetters.size() != 1) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "单选题答案只能填写A、B、C、D中的一个")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, SINGLE_ANSWER_LETTER_MESSAGE)); continue; } List notMatched = answerLetters.stream() .filter(letter -> !options.containsKey(letter)) .collect(Collectors.toList()); if (!notMatched.isEmpty()) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "答案[" + String.join("", notMatched) + "]找不到对应的选项")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, + String.format(ANSWER_NOT_MATCH_OPTION_TEMPLATE, String.join("", notMatched)))); continue; } @@ -220,7 +247,7 @@ public class QuestionExcelImporter { String coursewareName = row.getCoursewareName().trim(); Long coursewareManagementId = coursewareManagementIdByName.get(coursewareName); if (coursewareManagementId == null) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "关联课件名称[" + coursewareName + "]不存在")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, String.format(COURSEWARE_NOT_EXIST_TEMPLATE, coursewareName))); continue; } @@ -235,7 +262,7 @@ public class QuestionExcelImporter { question.setId(IdUtil.getSnowflakeNextId()); question.setTitle(row.getTitle().trim()); question.setQuestionType(questionType.getCode()); - question.setAnswer(String.join(",", answerLetters)); + question.setAnswer(String.join(COMMA_DELIMITER, answerLetters)); question.setScore(row.getScore()); question.setAnalysis(row.getAnalysis().trim()); question.setTagType(row.getTagType().trim()); @@ -266,33 +293,33 @@ public class QuestionExcelImporter { } String answerRaw = row.getAnswer().trim().toUpperCase(); - if (!"T".equals(answerRaw) && !"F".equals(answerRaw)) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "判断题答案只能填写T或F")); + if (!JUDGE_ANSWER_TRUE.equals(answerRaw) && !JUDGE_ANSWER_FALSE.equals(answerRaw)) { + errors.add(new PaperImportErrorVO(rowIndex, sheetName, JUDGE_ANSWER_LETTER_MESSAGE)); continue; } String coursewareName = row.getCoursewareName().trim(); Long coursewareManagementId = coursewareManagementIdByName.get(coursewareName); if (coursewareManagementId == null) { - errors.add(new PaperImportErrorVO(rowIndex, sheetName, "关联课件名称[" + coursewareName + "]不存在")); + errors.add(new PaperImportErrorVO(rowIndex, sheetName, String.format(COURSEWARE_NOT_EXIST_TEMPLATE, coursewareName))); continue; } - boolean correct = "T".equals(answerRaw); + boolean correct = JUDGE_ANSWER_TRUE.equals(answerRaw); QuestionDO question = new QuestionDO(); question.setId(IdUtil.getSnowflakeNextId()); question.setTitle(row.getTitle().trim()); question.setQuestionType(QuestionTypeEnum.JUDGE.getCode()); - question.setAnswer(correct ? "A" : "B"); + question.setAnswer(correct ? OPTION_KEY_A : OPTION_KEY_B); question.setScore(row.getScore()); question.setAnalysis(row.getAnalysis().trim()); question.setTagType(row.getTagType().trim()); question.setCoursewareManagementId(coursewareManagementId); Map optionTexts = new LinkedHashMap<>(); - optionTexts.put("A", "对"); - optionTexts.put("B", "错"); + optionTexts.put(OPTION_KEY_A, JUDGE_CORRECT_OPTION_TEXT); + optionTexts.put(OPTION_KEY_B, JUDGE_WRONG_OPTION_TEXT); question.setOptions(buildOptions(question.getId(), optionTexts, - Collections.singletonList(correct ? "A" : "B"))); + Collections.singletonList(correct ? OPTION_KEY_A : OPTION_KEY_B))); question.setAnswerQuestionOptionIds(joinCorrectOptionIds(question.getOptions())); questions.add(question); } @@ -320,14 +347,14 @@ public class QuestionExcelImporter { return options.stream() .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) .map(option -> String.valueOf(option.getId())) - .collect(Collectors.joining(",")); + .collect(Collectors.joining(COMMA_DELIMITER)); } private String joinViolationMessages(Set> violations) { return violations.stream() .map(ConstraintViolation::getMessage) .distinct() - .collect(Collectors.joining(";")); + .collect(Collectors.joining(VIOLATION_DELIMITER)); } private boolean isEmptyChoiceRow(ChoiceQuestionImportRow row) {