PaperBasicInfo

dev-tmp1
luotaiqian 2026-08-21 10:16:15 +08:00
parent 7775928024
commit 1e8386f7c4
7 changed files with 188 additions and 29 deletions

View File

@ -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<Void> modify(@Validated @RequestBody PaperQuestionRelUpdateDTO dto) {
paperQuestionRelService.modify(dto);
return SingleResponse.success();
}
}

View File

@ -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<QuestionOptionVO> convertOptionEListToVoList(List<QuestionOptionDO> list);
QuestionDO convertPaperQuestionRelUpdateDTOToE(PaperQuestionRelUpdateDTO dto);
}

View File

@ -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<QuestionOptionUpdateDTO> questionOptionList;
}

View File

@ -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<PaperQuestionRelDO> {
* @param questionId id
*/
void deletePaperQuestion(@NotNull Long paperId, @NotNull Long questionId);
/**
* modify
* @param dto dto
*/
void modify(PaperQuestionRelUpdateDTO dto);
}

View File

@ -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<PaperQuestionRelMap
}
}
@Override
public void modify(PaperQuestionRelUpdateDTO dto) {
// 校验试题存在且未删除
QuestionDO exist = questionMapper.selectOne(new LambdaQueryWrapper<QuestionDO>().eq(QuestionDO::getId, dto.getId())
.eq(QuestionDO::getDeleteEnum, DeleteEnum.FALSE.getCode()));
if (exist == null) {
throw new IllegalArgumentException("试题不存在");
}
this.update(new LambdaUpdateWrapper<PaperQuestionRelDO>()
.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<QuestionOptionDO> 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("编辑习题失败");
}
}
/**
* ididid
*
* @param questionId id
* @param optionDTOs
* @return id
*/
private List<QuestionOptionDO> syncOptions(Long questionId, List<QuestionOptionUpdateDTO> optionDTOs) {
// 查询该试题现存未删除的选项id
Set<Long> existingIds = questionOptionMapper.selectList(new LambdaQueryWrapper<QuestionOptionDO>()
.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<QuestionOptionDO> options = questionConvertor.convertOptionDtoListToEList(optionDTOs);
List<QuestionOptionDO> toUpdate = new ArrayList<>();
List<QuestionOptionDO> 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<Long> keepIds = toUpdate.stream().map(QuestionOptionDO::getId).collect(Collectors.toSet());
List<Long> toDeleteIds = existingIds.stream()
.filter(id -> !keepIds.contains(id))
.collect(Collectors.toList());
if (!toDeleteIds.isEmpty()) {
questionOptionMapper.update(new LambdaUpdateWrapper<QuestionOptionDO>()
.set(QuestionOptionDO::getDeleteEnum, DeleteEnum.TRUE.getCode())
.set(QuestionOptionDO::getUpdateTime, now)
.in(QuestionOptionDO::getId, toDeleteIds));
}
Db.updateBatchById(toUpdate);
Db.saveBatch(toInsert);
return options;
}
}

View File

@ -160,6 +160,8 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, PaperDO> 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

View File

@ -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<QuestionMapper, QuestionDO> implements QuestionService {
@Autowired
private QuestionOptionService questionOptionService;
private final QuestionOptionMapper questionOptionMapper;
private final QuestionConvertor questionConvertor;
@Override
@ -59,15 +58,14 @@ public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, QuestionDO>
throw new IllegalArgumentException("试题不存在");
}
// 查询选项信息仅查询VO需要的字段
List<QuestionOptionDO> options = questionOptionService.lambdaQuery()
List<QuestionOptionDO> options = questionOptionMapper.selectList(new LambdaQueryWrapper<QuestionOptionDO>()
.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<QuestionMapper, QuestionDO>
*/
private List<QuestionOptionDO> syncOptions(Long questionId, List<QuestionOptionUpdateDTO> optionDTOs) {
// 查询该试题现存未删除的选项id
Set<Long> existingIds = questionOptionService.lambdaQuery()
.select(QuestionOptionDO::getId)
.eq(QuestionOptionDO::getQuestionId, questionId)
.eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
.list()
Set<Long> existingIds = questionOptionMapper.selectList(new LambdaQueryWrapper<QuestionOptionDO>()
.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<QuestionMapper, QuestionDO>
.filter(id -> !keepIds.contains(id))
.collect(Collectors.toList());
if (!toDeleteIds.isEmpty()) {
questionOptionService.lambdaUpdate()
questionOptionMapper.update(new LambdaUpdateWrapper<QuestionOptionDO>()
.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<QuestionMapper, QuestionDO>
if (questionDO == null) {
return null;
}
List<QuestionOptionDO> 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<QuestionMapper, QuestionDO>
questionDO.setUpdateTime(now);
boolean updated = this.updateById(questionDO);
if (updated) {
questionOptionService.remove(Wrappers.<QuestionOptionDO>lambdaQuery()
questionOptionMapper.delete(new LambdaUpdateWrapper<QuestionOptionDO>()
.eq(QuestionOptionDO::getQuestionId, questionDO.getId()));
saveOptions(questionDO.getId(), options, now);
}
@ -230,11 +221,10 @@ public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, QuestionDO>
.eq(QuestionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
.update();
if (deleted) {
questionOptionService.lambdaUpdate()
questionOptionMapper.update(new LambdaUpdateWrapper<QuestionOptionDO>()
.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<QuestionMapper, QuestionDO>
option.setCreateTime(now);
option.setUpdateTime(now);
});
questionOptionService.saveBatch(options);
Db.saveBatch(options);
}
}