diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/convertor/QuestionConvertor.java b/safety-cedu-service/src/main/java/org/qinan/cedu/convertor/QuestionConvertor.java index f34dda64..edaef296 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/convertor/QuestionConvertor.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/convertor/QuestionConvertor.java @@ -1,6 +1,7 @@ package org.qinan.cedu.convertor; import org.mapstruct.Mapper; +import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; import org.qinan.cedu.model.dto.QuestionOptionUpdateDTO; import org.qinan.cedu.model.dto.QuestionUpdateDTO; import org.qinan.cedu.model.entity.QuestionDO; @@ -25,6 +26,14 @@ public interface QuestionConvertor { */ QuestionDO convertDtoToE(QuestionUpdateDTO dto); + /** + * 新增试卷试题请求转试题实体 + * + * @param dto 新增内容 + * @return 试题实体 + */ + QuestionDO convertPaperDtoToE(PaperQuestionSaveDTO dto); + /** * 选项编辑DTO列表转选项实体列表 * diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionSaveDTO.java b/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionSaveDTO.java index 6b47a90c..fc787bf3 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionSaveDTO.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionSaveDTO.java @@ -4,18 +4,14 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import javax.validation.constraints.DecimalMax; -import javax.validation.constraints.DecimalMin; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; +import javax.validation.Valid; + +import javax.validation.constraints.*; import java.math.BigDecimal; import java.util.List; /** - * 新增试卷试题(习题)请求 + * 新增试卷试题(习题)请求(字段参考 QuestionUpdateDTO,另含试卷id和试题类型) */ @Data @ApiModel(value = "PaperQuestionSaveDTO", description = "新增试卷试题请求") @@ -25,45 +21,34 @@ public class PaperQuestionSaveDTO { @NotNull(message = "试卷id不能为空") private Long paperId; - @ApiModelProperty(value = "题目类型:single单选/multiple多选", required = true) - @NotBlank(message = "题目类型不能为空") - @Pattern(regexp = "single|multiple", message = "题目类型只能填写single(单选)或multiple(多选)") + @ApiModelProperty(value = "试题类型:single单选/multiple多选", required = true) + @NotBlank(message = "试题类型不能为空") + @Pattern(regexp = "single|multiple", message = "试题类型只能填写single(单选)或multiple(多选)") private String questionType; - @ApiModelProperty(value = "题干", required = true) + @ApiModelProperty(value = "题目内容", required = true) @NotBlank(message = "题干不能为空") private String title; @ApiModelProperty(value = "分值", required = true) @NotNull(message = "分值不能为空") - @DecimalMin(value = "0.01", message = "分值必须大于0") - @DecimalMax(value = "999.99", message = "分值不能超过999.99") + @DecimalMin(value = "0.1", message = "分值必须大于0") + @DecimalMax(value = "999.9", message = "分值不能超过999.9") + @Digits(integer = 4, fraction = 1) private BigDecimal score; - @ApiModelProperty(value = "选项A内容", required = true) - @NotBlank(message = "选项A不能为空") - private String optionA; - - @ApiModelProperty(value = "选项B内容", required = true) - @NotBlank(message = "选项B不能为空") - private String optionB; - - @ApiModelProperty(value = "选项C内容") - @Size(max = 64, message = "选项C长度不能超过64个字符") - private String optionC; - - @ApiModelProperty(value = "选项D内容") - @Size(max = 64, message = "选项D长度不能超过64个字符") - private String optionD; - - @ApiModelProperty(value = "答案:单选填写A,多选填写A,B", required = true) - @NotEmpty(message = "答案不能为空") - private List answer; - - @ApiModelProperty(value = "关联课件id courseware_management_id", required = true) - @NotNull(message = "关联课件不能为空") - private Long coursewareManagementId; - @ApiModelProperty(value = "答案解析") private String analysis; + + @ApiModelProperty(value = "标签类型") + private String tagType; + + @ApiModelProperty(value = "关联课件管理id", required = true) + @NotNull(message = "关联课件不能为空") + private Long coursewareManagementId; + + @ApiModelProperty(value = "试题选项信息(isCorrect标记正确答案)", required = true) + @NotEmpty(message = "试题选项不能为空") + @Valid + private List questionOptionList; } diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/service/PaperQuestionRelService.java b/safety-cedu-service/src/main/java/org/qinan/cedu/service/PaperQuestionRelService.java index 3e31e65e..9fbda863 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/service/PaperQuestionRelService.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/service/PaperQuestionRelService.java @@ -12,9 +12,9 @@ import javax.validation.constraints.NotNull; public interface PaperQuestionRelService extends IService { /** - * 新增试卷试题(习题):保存试题+选项,并绑定试卷 + * 新增试卷试题(习题):保存试题+选项(isCorrect标记正确答案),并绑定试卷 * - * @param dto 试卷id+题目内容 + * @param dto 试卷id+试题类型+题目内容+选项列表 */ void savePaperQuestion(PaperQuestionSaveDTO dto); diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperQuestionRelServiceImpl.java b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperQuestionRelServiceImpl.java index 523a72b6..599376be 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperQuestionRelServiceImpl.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/PaperQuestionRelServiceImpl.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.toolkit.Db; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.qinan.cedu.convertor.QuestionConvertor; import org.qinan.cedu.enums.DeleteEnum; import org.qinan.cedu.enums.QuestionTypeEnum; import org.qinan.cedu.mapper.PaperMapper; @@ -13,6 +14,7 @@ import org.qinan.cedu.mapper.PaperQuestionRelMapper; import org.qinan.cedu.mapper.QuestionMapper; import org.qinan.cedu.mapper.QuestionOptionMapper; import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; +import org.qinan.cedu.model.dto.QuestionOptionUpdateDTO; import org.qinan.cedu.model.entity.PaperDO; import org.qinan.cedu.model.entity.PaperQuestionRelDO; import org.qinan.cedu.model.entity.QuestionDO; @@ -20,13 +22,9 @@ import org.qinan.cedu.model.entity.QuestionOptionDO; import org.qinan.cedu.service.PaperQuestionRelService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; /** @@ -40,78 +38,52 @@ public class PaperQuestionRelServiceImpl extends ServiceImpl() .eq(PaperDO::getId, dto.getPaperId()) .eq(PaperDO::getDeleteEnum, DeleteEnum.FALSE.getCode())); - if (exists) { + if (!exists) { throw new IllegalArgumentException("试卷不存在:id=" + dto.getPaperId()); } - // 组装选项(按A-D列,空白列不生成选项) - Map optionTexts = new LinkedHashMap<>(); - optionTexts.put("A", dto.getOptionA().trim()); - optionTexts.put("B", dto.getOptionB().trim()); - putIfHasText(optionTexts, "C", dto.getOptionC()); - putIfHasText(optionTexts, "D", dto.getOptionD()); - - // 校验答案必须能找到对应的选项 - List answers = dto.getAnswer().stream() - .map(String::trim) - .map(String::toUpperCase) - .distinct() - .sorted() - .collect(Collectors.toList()); - List notMatched = answers.stream() - .filter(answer -> !optionTexts.containsKey(answer)) - .collect(Collectors.toList()); - if (!notMatched.isEmpty()) { - throw new IllegalArgumentException("答案[" + String.join(",", notMatched) + "]找不到对应的选项"); + // 校验正确答案:至少一个;单选题只能有一个 + List optionDTOs = dto.getQuestionOptionList(); + long correctCount = optionDTOs.stream() + .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) + .count(); + if (correctCount == 0) { + throw new IllegalArgumentException("正确答案不能为空"); } boolean single = QuestionTypeEnum.SINGLE.getCode().equals(dto.getQuestionType()); - if (single && answers.size() != 1) { + if (single && correctCount != 1) { throw new IllegalArgumentException("单选题答案只能有一个"); } // 保存试题和选项(id用雪花id) LocalDateTime now = LocalDateTime.now(); - QuestionDO question = new QuestionDO(); + QuestionDO question = questionConvertor.convertPaperDtoToE(dto); question.setId(IdUtil.getSnowflakeNextId()); - question.setTitle(dto.getTitle().trim()); - question.setQuestionType(dto.getQuestionType()); - question.setAnswer(String.join(",", answers)); - question.setScore(dto.getScore()); - question.setAnalysis(StringUtils.hasText(dto.getAnalysis()) ? dto.getAnalysis().trim() : null); - question.setCoursewareManagementId(dto.getCoursewareManagementId()); - question.setCreateTime(now); - question.setUpdateTime(now); - question.setVersion(0); + question.setAnswer(optionDTOs.stream() + .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) + .map(QuestionOptionUpdateDTO::getOptionKey) + .map(String::trim) + .map(String::toUpperCase) + .collect(Collectors.joining(","))); - List options = new ArrayList<>(); - int sortOrder = 1; - for (Map.Entry entry : optionTexts.entrySet()) { - QuestionOptionDO option = new QuestionOptionDO(); - option.setId(IdUtil.getSnowflakeNextId()); - option.setQuestionId(question.getId()); - option.setOptionKey(entry.getKey()); - option.setOptionName(entry.getKey()); - option.setOptionText(entry.getValue()); - option.setIsCorrect(answers.contains(entry.getKey())); - option.setSortOrder(sortOrder++); - option.setCreateTime(now); - option.setUpdateTime(now); - option.setVersion(0); - options.add(option); - } + List options = buildOptions(question.getId(), optionDTOs, now); question.setAnswerQuestionOptionIds(options.stream() .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) .map(option -> String.valueOf(option.getId())) .collect(Collectors.joining(","))); + question.setCreateTime(now); + question.setUpdateTime(now); + question.setVersion(0); questionMapper.insert(question); Db.saveBatch(options); @@ -124,6 +96,31 @@ public class PaperQuestionRelServiceImpl extends ServiceImpl buildOptions(Long questionId, List optionDTOs, LocalDateTime now) { + List options = questionConvertor.convertOptionDtoListToEList(optionDTOs); + int sortOrder = 1; + for (QuestionOptionDO option : options) { + String key = option.getOptionKey().trim().toUpperCase(); + option.setId(IdUtil.getSnowflakeNextId()); + option.setQuestionId(questionId); + option.setOptionKey(key); + option.setOptionName(key); + option.setSortOrder(sortOrder++); + option.setCreateTime(now); + option.setUpdateTime(now); + option.setVersion(0); + } + return options; + } + @Override @Transactional(rollbackFor = Exception.class) public void deletePaperQuestion(Long paperId, Long questionId) { @@ -143,9 +140,4 @@ public class PaperQuestionRelServiceImpl extends ServiceImpl optionTexts, String key, String value) { - if (StringUtils.hasText(value)) { - optionTexts.put(key, value.trim()); - } - } } diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java index f2bbbbd7..e4d9bbd2 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java @@ -181,6 +181,7 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { cmd.setJoinWorkDate(resolveJoinWorkDate(cmd.getWorkDateAge(), cmd.getJoinWorkDate())); OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd); entity.setId(cmd.getId()); + entity.setIsCertComplete(true); OrgPersonnelEntity result = orgPersonnelDomainService.modify(entity); // 组织信息变更,清理该用户机构缓存 if (userOrgIdResolver != null) {