From 1e8386f7c4eb8e65761853adaeee5172fcbad91f Mon Sep 17 00:00:00 2001 From: luotaiqian <1147642922@qq.com> Date: Fri, 21 Aug 2026 10:16:15 +0800 Subject: [PATCH] PaperBasicInfo --- .../PaperQuestionRelController.java | 9 ++ .../cedu/convertor/QuestionConvertor.java | 3 + .../model/dto/PaperQuestionRelUpdateDTO.java | 53 +++++++++++ .../cedu/service/PaperQuestionRelService.java | 7 ++ .../impl/PaperQuestionRelServiceImpl.java | 95 +++++++++++++++++++ .../cedu/service/impl/PaperServiceImpl.java | 2 + .../service/impl/QuestionServiceImpl.java | 48 ++++------ 7 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionRelUpdateDTO.java diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/controller/PaperQuestionRelController.java b/safety-cedu-service/src/main/java/org/qinan/cedu/controller/PaperQuestionRelController.java index 11c47e0a..030ed833 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/controller/PaperQuestionRelController.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/controller/PaperQuestionRelController.java @@ -4,7 +4,9 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.qinan.cedu.model.dto.PaperQuestionDeleteDTO; +import org.qinan.cedu.model.dto.PaperQuestionRelUpdateDTO; import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; +import org.qinan.cedu.model.dto.QuestionUpdateDTO; import org.qinan.cedu.service.PaperQuestionRelService; import org.qinan.safetyeval.client.dto.SingleResponse; import org.springframework.beans.factory.annotation.Autowired; @@ -38,4 +40,11 @@ public class PaperQuestionRelController { paperQuestionRelService.deletePaperQuestion(dto.getPaperId(), dto.getQuestionId()); return SingleResponse.success(); } + + @ApiOperation("编辑试卷习题") + @PostMapping("/modify") + public SingleResponse modify(@Validated @RequestBody PaperQuestionRelUpdateDTO dto) { + paperQuestionRelService.modify(dto); + return SingleResponse.success(); + } } 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 edaef296..bfa2fbba 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.PaperQuestionRelUpdateDTO; import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; import org.qinan.cedu.model.dto.QuestionOptionUpdateDTO; import org.qinan.cedu.model.dto.QuestionUpdateDTO; @@ -45,4 +46,6 @@ public interface QuestionConvertor { QuestionOptionVO convertOptionEToVo(QuestionOptionDO entity); List convertOptionEListToVoList(List list); + + QuestionDO convertPaperQuestionRelUpdateDTOToE(PaperQuestionRelUpdateDTO dto); } diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionRelUpdateDTO.java b/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionRelUpdateDTO.java new file mode 100644 index 00000000..95981326 --- /dev/null +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/model/dto/PaperQuestionRelUpdateDTO.java @@ -0,0 +1,53 @@ +package org.qinan.cedu.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.Valid; + +import javax.validation.constraints.*; +import java.math.BigDecimal; +import java.util.List; + +/** + * 编辑习题请求(字段参考 QuestionVO) + */ +@Data +@ApiModel(value = "QuestionUpdateDTO", description = "编辑习题请求") +public class PaperQuestionRelUpdateDTO { + + @ApiModelProperty(value = "试卷id", required = true) + @NotNull(message = "试卷id不能为空") + private Long paperId; + + @ApiModelProperty(value = "试题id", required = true) + @NotNull(message = "试题id不能为空") + private Long id; + + @ApiModelProperty(value = "题目内容", required = true) + @NotBlank(message = "题干不能为空") + private String title; + + @ApiModelProperty(value = "分值", required = true) + @NotNull(message = "分值不能为空") + @DecimalMin(value = "0.1", message = "分值必须大于0") + @DecimalMax(value = "999.9", message = "分值不能超过999.9") + @Digits(integer = 4, fraction = 1) + private BigDecimal score; + + @ApiModelProperty(value = "答案解析") + private String analysis; + + @ApiModelProperty(value = "标签类型") + private String tagType; + + @ApiModelProperty(value = "关联课件管理id", required = true) + @NotNull(message = "关联课件不能为空") + private Long coursewareManagementId; + + @ApiModelProperty(value = "试题选项信息", 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 9fbda863..af095fe5 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 @@ -1,6 +1,7 @@ package org.qinan.cedu.service; import com.baomidou.mybatisplus.extension.service.IService; +import org.qinan.cedu.model.dto.PaperQuestionRelUpdateDTO; import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; import org.qinan.cedu.model.entity.PaperQuestionRelDO; @@ -25,4 +26,10 @@ public interface PaperQuestionRelService extends IService { * @param questionId 试题id */ void deletePaperQuestion(@NotNull Long paperId, @NotNull Long questionId); + + /** + * modify + * @param dto dto + */ + void modify(PaperQuestionRelUpdateDTO 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 4498b24c..b92f3d4b 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 @@ -2,6 +2,7 @@ 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.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.toolkit.Db; import lombok.RequiredArgsConstructor; @@ -13,6 +14,7 @@ import org.qinan.cedu.mapper.PaperMapper; import org.qinan.cedu.mapper.PaperQuestionRelMapper; import org.qinan.cedu.mapper.QuestionMapper; import org.qinan.cedu.mapper.QuestionOptionMapper; +import org.qinan.cedu.model.dto.PaperQuestionRelUpdateDTO; import org.qinan.cedu.model.dto.PaperQuestionSaveDTO; import org.qinan.cedu.model.dto.QuestionOptionUpdateDTO; import org.qinan.cedu.model.entity.PaperDO; @@ -25,7 +27,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -130,4 +134,95 @@ public class PaperQuestionRelServiceImpl extends ServiceImpl().eq(QuestionDO::getId, dto.getId()) + .eq(QuestionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())); + if (exist == null) { + throw new IllegalArgumentException("试题不存在"); + } + + this.update(new LambdaUpdateWrapper() + .set(PaperQuestionRelDO::getScore, dto.getScore()) + .eq(PaperQuestionRelDO::getPaperId, dto.getPaperId()) + .eq(PaperQuestionRelDO::getQuestionId, dto.getId())); + + // 更新试题基本信息(MapStruct转换,答案归一化后回填) + QuestionDO question = questionConvertor.convertPaperQuestionRelUpdateDTOToE(dto); + question.setAnswer(dto.getQuestionOptionList().stream() + .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) + .map(QuestionOptionUpdateDTO::getOptionKey) + .collect(Collectors.joining(","))); + question.setUpdateTime(LocalDateTime.now()); + + // 同步选项:id在库的更新、无id的新增、多余的逻辑删除 + List options = syncOptions(dto.getId(), dto.getQuestionOptionList()); + question.setAnswerQuestionOptionIds(options.stream() + .filter(option -> Boolean.TRUE.equals(option.getIsCorrect())) + .map(option -> String.valueOf(option.getId())) + .collect(Collectors.joining(","))); + + InsertFieldDefaults.applyForUpdate(question); + boolean updated = questionMapper.updateById(question) != 0; + if (!updated) { + throw new IllegalArgumentException("编辑习题失败"); + } + } + + /** + * 同步试题选项:id在库中的更新、无id(或id不在库中)的新增、库中多余的选项逻辑删除 + * + * @param questionId 所属试题id + * @param optionDTOs 选项编辑内容 + * @return 处理后的选项实体列表(更新与新增的合集,含最终id) + */ + private List syncOptions(Long questionId, List optionDTOs) { + // 查询该试题现存未删除的选项id + Set existingIds = questionOptionMapper.selectList(new LambdaQueryWrapper() + .select(QuestionOptionDO::getId) + .eq(QuestionOptionDO::getQuestionId, questionId) + .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())) + .stream() + .map(QuestionOptionDO::getId) + .collect(Collectors.toSet()); + + // 组装选项:归一化标识、标记正确答案,按id是否在库分为更新与新增两组 + LocalDateTime now = LocalDateTime.now(); + List options = questionConvertor.convertOptionDtoListToEList(optionDTOs); + List toUpdate = new ArrayList<>(); + List toInsert = new ArrayList<>(); + int sortOrder = 1; + for (QuestionOptionDO option : options) { + String key = option.getOptionKey().trim().toUpperCase(); + option.setQuestionId(questionId); + option.setOptionKey(key); + option.setOptionName(key); + option.setSortOrder(sortOrder++); + if (option.getId() != null && existingIds.contains(option.getId())) { + toUpdate.add(option); + InsertFieldDefaults.applyForUpdate(option); + } else { + option.setId(IdUtil.getSnowflakeNextId()); + InsertFieldDefaults.applyIgnoreOrgId(option); + toInsert.add(option); + } + } + + // 库中多余(未出现在本次请求中)的选项逻辑删除 + Set keepIds = toUpdate.stream().map(QuestionOptionDO::getId).collect(Collectors.toSet()); + List toDeleteIds = existingIds.stream() + .filter(id -> !keepIds.contains(id)) + .collect(Collectors.toList()); + if (!toDeleteIds.isEmpty()) { + questionOptionMapper.update(new LambdaUpdateWrapper() + .set(QuestionOptionDO::getDeleteEnum, DeleteEnum.TRUE.getCode()) + .set(QuestionOptionDO::getUpdateTime, now) + .in(QuestionOptionDO::getId, toDeleteIds)); + } + Db.updateBatchById(toUpdate); + Db.saveBatch(toInsert); + return options; + } + } 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 f76ef80d..a79ce14f 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 @@ -160,6 +160,8 @@ public class PaperServiceImpl extends ServiceImpl implemen target.setStatus(source.getStatus()); target.setPaperType(source.getPaperType()); target.setConfigRule(source.getConfigRule()); + target.setExamTime(source.getExamTime()); + InsertFieldDefaults.applyIgnoreOrgId(target); this.savePaper(target); // 复制试题+选项+试卷试题关系(id全部换新雪花id) diff --git a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/QuestionServiceImpl.java b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/QuestionServiceImpl.java index 6bcab515..424f033d 100644 --- a/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/QuestionServiceImpl.java +++ b/safety-cedu-service/src/main/java/org/qinan/cedu/service/impl/QuestionServiceImpl.java @@ -1,15 +1,18 @@ 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.conditions.update.LambdaUpdateWrapper; 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 lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.qinan.cedu.convertor.QuestionConvertor; import org.qinan.cedu.enums.DeleteEnum; import org.qinan.cedu.mapper.QuestionMapper; +import org.qinan.cedu.mapper.QuestionOptionMapper; import org.qinan.cedu.model.dto.PaperQuestionPageQueryDTO; import org.qinan.cedu.model.dto.QuestionOptionUpdateDTO; import org.qinan.cedu.model.dto.QuestionTypeCountQueryDTO; @@ -19,10 +22,8 @@ import org.qinan.cedu.model.entity.QuestionOptionDO; import org.qinan.cedu.model.vo.PaperQuestionVO; import org.qinan.cedu.model.vo.QuestionTypeCountVO; import org.qinan.cedu.model.vo.QuestionVO; -import org.qinan.cedu.service.QuestionOptionService; import org.qinan.cedu.service.QuestionService; 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; import org.springframework.util.CollectionUtils; @@ -42,9 +43,7 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class QuestionServiceImpl extends ServiceImpl implements QuestionService { - @Autowired - private QuestionOptionService questionOptionService; - + private final QuestionOptionMapper questionOptionMapper; private final QuestionConvertor questionConvertor; @Override @@ -59,15 +58,14 @@ public class QuestionServiceImpl extends ServiceImpl throw new IllegalArgumentException("试题不存在"); } // 查询选项信息(仅查询VO需要的字段) - List options = questionOptionService.lambdaQuery() + List options = questionOptionMapper.selectList(new LambdaQueryWrapper() .select(QuestionOptionDO::getId, QuestionOptionDO::getQuestionId, QuestionOptionDO::getOptionKey, QuestionOptionDO::getOptionName, QuestionOptionDO::getOptionText, QuestionOptionDO::getIsCorrect, QuestionOptionDO::getSortOrder) .eq(QuestionOptionDO::getQuestionId, questionId) .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) - .orderByAsc(QuestionOptionDO::getSortOrder) - .list(); + .orderByAsc(QuestionOptionDO::getSortOrder)); questionVO.setQuestionOptionVOList(questionConvertor.convertOptionEListToVoList(options)); return questionVO; } @@ -121,11 +119,10 @@ public class QuestionServiceImpl extends ServiceImpl */ private List syncOptions(Long questionId, List optionDTOs) { // 查询该试题现存未删除的选项id - Set existingIds = questionOptionService.lambdaQuery() - .select(QuestionOptionDO::getId) - .eq(QuestionOptionDO::getQuestionId, questionId) - .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) - .list() + Set existingIds = questionOptionMapper.selectList(new LambdaQueryWrapper() + .select(QuestionOptionDO::getId) + .eq(QuestionOptionDO::getQuestionId, questionId) + .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())) .stream() .map(QuestionOptionDO::getId) .collect(Collectors.toSet()); @@ -158,14 +155,13 @@ public class QuestionServiceImpl extends ServiceImpl .filter(id -> !keepIds.contains(id)) .collect(Collectors.toList()); if (!toDeleteIds.isEmpty()) { - questionOptionService.lambdaUpdate() + questionOptionMapper.update(new LambdaUpdateWrapper() .set(QuestionOptionDO::getDeleteEnum, DeleteEnum.TRUE.getCode()) .set(QuestionOptionDO::getUpdateTime, now) - .in(QuestionOptionDO::getId, toDeleteIds) - .update(); + .in(QuestionOptionDO::getId, toDeleteIds)); } - questionOptionService.updateBatchById(toUpdate); - questionOptionService.saveBatch(toInsert); + Db.updateBatchById(toUpdate); + Db.saveBatch(toInsert); return options; } @@ -185,11 +181,6 @@ public class QuestionServiceImpl extends ServiceImpl if (questionDO == null) { return null; } - List options = questionOptionService.lambdaQuery() - .eq(QuestionOptionDO::getQuestionId, id) - .eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) - .orderByAsc(QuestionOptionDO::getSortOrder) - .list(); return questionDO; } @@ -213,7 +204,7 @@ public class QuestionServiceImpl extends ServiceImpl questionDO.setUpdateTime(now); boolean updated = this.updateById(questionDO); if (updated) { - questionOptionService.remove(Wrappers.lambdaQuery() + questionOptionMapper.delete(new LambdaUpdateWrapper() .eq(QuestionOptionDO::getQuestionId, questionDO.getId())); saveOptions(questionDO.getId(), options, now); } @@ -230,11 +221,10 @@ public class QuestionServiceImpl extends ServiceImpl .eq(QuestionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()) .update(); if (deleted) { - questionOptionService.lambdaUpdate() + questionOptionMapper.update(new LambdaUpdateWrapper() .set(QuestionOptionDO::getDeleteEnum, DeleteEnum.TRUE.getCode()) .set(QuestionOptionDO::getUpdateTime, LocalDateTime.now()) - .eq(QuestionOptionDO::getQuestionId, id) - .update(); + .eq(QuestionOptionDO::getQuestionId, id)); } return deleted; } @@ -256,6 +246,6 @@ public class QuestionServiceImpl extends ServiceImpl option.setCreateTime(now); option.setUpdateTime(now); }); - questionOptionService.saveBatch(options); + Db.saveBatch(options); } }