paper
parent
9c9844f9b8
commit
a6bc4606c3
|
|
@ -3,6 +3,7 @@ package org.qinan.cedu.controller;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.qinan.cedu.model.dto.PaperCopyDTO;
|
||||
import org.qinan.cedu.model.dto.PaperImportDTO;
|
||||
import org.qinan.cedu.model.dto.PaperPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.PaperUpdateDTO;
|
||||
|
|
@ -47,6 +48,13 @@ public class PaperController {
|
|||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
@ApiOperation("复制试卷")
|
||||
@PostMapping("/copy")
|
||||
public SingleResponse<Void> copy(@Validated @RequestBody PaperCopyDTO copyDTO) {
|
||||
paperService.copyPaper(copyDTO);
|
||||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation("新建试卷-导入试题")
|
||||
@PostMapping("/import")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package org.qinan.cedu.controller;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionDeleteDTO;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionSaveDTO;
|
||||
import org.qinan.cedu.service.PaperQuestionRelService;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 试卷试题关系管理
|
||||
*/
|
||||
@Api(tags = "试卷试题管理")
|
||||
@RestController
|
||||
@RequestMapping("/safetyEval/paperQuestionRel")
|
||||
public class PaperQuestionRelController {
|
||||
|
||||
@Autowired
|
||||
private PaperQuestionRelService paperQuestionRelService;
|
||||
|
||||
@ApiOperation("新增试卷试题(习题)")
|
||||
@PostMapping("/save")
|
||||
public SingleResponse<Void> save(@Validated @RequestBody PaperQuestionSaveDTO dto) {
|
||||
paperQuestionRelService.savePaperQuestion(dto);
|
||||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
@ApiOperation("删除习题")
|
||||
@PostMapping("/delete")
|
||||
public SingleResponse<Void> delete(@Validated @RequestBody PaperQuestionDeleteDTO dto) {
|
||||
paperQuestionRelService.deletePaperQuestion(dto.getPaperId(), dto.getQuestionId());
|
||||
return SingleResponse.success();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,19 @@
|
|||
package org.qinan.cedu.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.QuestionUpdateDTO;
|
||||
import org.qinan.cedu.model.vo.PaperQuestionVO;
|
||||
import org.qinan.cedu.service.QuestionService;
|
||||
import org.qinan.safetyeval.client.dto.PageResponse;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
|
@ -17,5 +28,17 @@ public class QuestionController {
|
|||
@Autowired
|
||||
private QuestionService questionService;
|
||||
|
||||
@ApiOperation("编辑习题")
|
||||
@PostMapping("/modify")
|
||||
public SingleResponse<Void> modify(@Validated @RequestBody QuestionUpdateDTO dto) {
|
||||
questionService.updateQuestion(dto);
|
||||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
@ApiOperation("试卷试题分页查询")
|
||||
@GetMapping("/page")
|
||||
public PageResponse<PaperQuestionVO> page(@Validated PaperQuestionPageQueryDTO query) {
|
||||
IPage<PaperQuestionVO> page = questionService.pagePaperQuestions(query);
|
||||
return PageResponse.of(page.getRecords(), page.getTotal());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,26 @@
|
|||
package org.qinan.cedu.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionPageQueryDTO;
|
||||
import org.qinan.cedu.model.entity.QuestionDO;
|
||||
import org.qinan.cedu.model.vo.PaperQuestionVO;
|
||||
|
||||
/**
|
||||
* 试题 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface QuestionMapper extends BaseMapper<QuestionDO> {
|
||||
|
||||
/**
|
||||
* 试卷试题分页查询(关联课件名称)
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param query 查询条件(试卷id、题干、题目类型)
|
||||
* @return 试题分页
|
||||
*/
|
||||
IPage<PaperQuestionVO> pagePaperQuestions(Page<PaperQuestionVO> page, @Param("query") PaperQuestionPageQueryDTO query);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package org.qinan.cedu.model.dto;
|
||||
|
||||
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.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 复制试卷请求
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PaperCopyDTO", description = "复制试卷请求")
|
||||
public class PaperCopyDTO {
|
||||
|
||||
@ApiModelProperty(value = "源试卷id", required = true)
|
||||
@NotNull(message = "试卷id不能为空")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "新试卷名称", required = true)
|
||||
@NotBlank(message = "试卷名称不能为空")
|
||||
@Size(max = 128, message = "试卷名称长度不能超过128个字符")
|
||||
private String paperName;
|
||||
|
||||
@ApiModelProperty(value = "新试卷总分", required = true)
|
||||
@NotNull(message = "试卷总分不能为空")
|
||||
@DecimalMin(value = "0.01", message = "试卷总分必须大于0")
|
||||
@DecimalMax(value = "9999.99", message = "试卷总分不能超过9999.99")
|
||||
private BigDecimal paperTotalScore;
|
||||
|
||||
@ApiModelProperty(value = "新合格分数", required = true)
|
||||
@NotNull(message = "合格分数不能为空")
|
||||
@DecimalMin(value = "0", message = "合格分数不能小于0")
|
||||
@DecimalMax(value = "9999.99", message = "合格分数不能超过9999.99")
|
||||
private BigDecimal paperPassScore;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.qinan.cedu.model.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 删除习题请求
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PaperQuestionDeleteDTO", description = "删除习题请求")
|
||||
public class PaperQuestionDeleteDTO {
|
||||
|
||||
@ApiModelProperty(value = "试卷id", required = true)
|
||||
@NotNull(message = "试卷id不能为空")
|
||||
private Long paperId;
|
||||
|
||||
@ApiModelProperty(value = "试题id", required = true)
|
||||
@NotNull(message = "试题id不能为空")
|
||||
private Long questionId;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package org.qinan.cedu.model.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.qinan.cedu.model.query.BasePageQuery;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 试卷试题分页查询请求
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel(value = "PaperQuestionPageQueryDTO", description = "试卷试题分页查询请求")
|
||||
public class PaperQuestionPageQueryDTO extends BasePageQuery {
|
||||
|
||||
@ApiModelProperty(value = "试卷id", required = true)
|
||||
@NotNull(message = "试卷id不能为空")
|
||||
private Long paperId;
|
||||
|
||||
@ApiModelProperty("题干(模糊)")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("题目类型:single单选/multiple多选/judge判断")
|
||||
private String questionType;
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package org.qinan.cedu.model.dto;
|
||||
|
||||
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 java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新增试卷试题(习题)请求
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PaperQuestionSaveDTO", description = "新增试卷试题请求")
|
||||
public class PaperQuestionSaveDTO {
|
||||
|
||||
@ApiModelProperty(value = "试卷id", required = true)
|
||||
@NotNull(message = "试卷id不能为空")
|
||||
private Long paperId;
|
||||
|
||||
@ApiModelProperty(value = "题目类型:single单选/multiple多选", required = true)
|
||||
@NotBlank(message = "题目类型不能为空")
|
||||
@Pattern(regexp = "single|multiple", message = "题目类型只能填写single(单选)或multiple(多选)")
|
||||
private String questionType;
|
||||
|
||||
@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")
|
||||
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<String> answer;
|
||||
|
||||
@ApiModelProperty(value = "关联课件id courseware_management_id", required = true)
|
||||
@NotNull(message = "关联课件不能为空")
|
||||
private Long coursewareManagementId;
|
||||
|
||||
@ApiModelProperty(value = "答案解析")
|
||||
private String analysis;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package org.qinan.cedu.model.dto;
|
||||
|
||||
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.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 编辑习题请求
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "QuestionUpdateDTO", description = "编辑习题请求")
|
||||
public class QuestionUpdateDTO {
|
||||
|
||||
@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.01", message = "分值必须大于0")
|
||||
@DecimalMax(value = "999.99", message = "分值不能超过999.99")
|
||||
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<String> answer;
|
||||
|
||||
@ApiModelProperty(value = "关联课件id courseware_management_id", required = true)
|
||||
@NotNull(message = "关联课件不能为空")
|
||||
private Long coursewareManagementId;
|
||||
|
||||
@ApiModelProperty(value = "答案解析")
|
||||
private String analysis;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package org.qinan.cedu.model.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 试卷试题分页返回
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PaperQuestionVO", description = "试卷试题")
|
||||
public class PaperQuestionVO {
|
||||
|
||||
@ApiModelProperty("试题id")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("题干")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("分值")
|
||||
private BigDecimal score;
|
||||
|
||||
@ApiModelProperty("答案选项id,多个逗号隔开")
|
||||
private String answerQuestionOptionIds;
|
||||
|
||||
@ApiModelProperty("答案解析")
|
||||
private String analysis;
|
||||
|
||||
@ApiModelProperty("题型:single单选/multiple多选/judge判断")
|
||||
private String questionType;
|
||||
|
||||
@ApiModelProperty("标签类型")
|
||||
private String tagType;
|
||||
|
||||
@ApiModelProperty("试题号")
|
||||
private Integer questionNum;
|
||||
|
||||
@ApiModelProperty("关联课件名称")
|
||||
private String coursewareName;
|
||||
}
|
||||
|
|
@ -1,10 +1,28 @@
|
|||
package org.qinan.cedu.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionSaveDTO;
|
||||
import org.qinan.cedu.model.entity.PaperQuestionRelDO;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 试卷试题关系服务接口
|
||||
*/
|
||||
public interface PaperQuestionRelService extends IService<PaperQuestionRelDO> {
|
||||
|
||||
/**
|
||||
* 新增试卷试题(习题):保存试题+选项,并绑定试卷
|
||||
*
|
||||
* @param dto 试卷id+题目内容
|
||||
*/
|
||||
void savePaperQuestion(PaperQuestionSaveDTO dto);
|
||||
|
||||
/**
|
||||
* 删除习题:同时删除试卷试题关系(paper_question_rel)和试题(question,含选项)
|
||||
*
|
||||
* @param paperId 试卷id
|
||||
* @param questionId 试题id
|
||||
*/
|
||||
void deletePaperQuestion(@NotNull Long paperId, @NotNull Long questionId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.qinan.cedu.service;
|
|||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.qinan.cedu.model.dto.PaperCopyDTO;
|
||||
import org.qinan.cedu.model.dto.PaperImportDTO;
|
||||
import org.qinan.cedu.model.dto.PaperPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.PaperUpdateDTO;
|
||||
|
|
@ -65,6 +66,13 @@ public interface PaperService extends IService<PaperDO> {
|
|||
*/
|
||||
List<Long> getQuestionIds(Long paperId);
|
||||
|
||||
/**
|
||||
* 复制试卷:新增试卷,并复制其下试题、选项、试卷试题关系
|
||||
*
|
||||
* @param copyDTO 源试卷id+新试卷信息
|
||||
*/
|
||||
void copyPaper(PaperCopyDTO copyDTO);
|
||||
|
||||
/**
|
||||
* 新建试卷-导入试题方式:解析试题Excel(单选/多选/判断),校验通过后创建试卷并绑定试题
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package org.qinan.cedu.service;
|
|||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.QuestionUpdateDTO;
|
||||
import org.qinan.cedu.model.entity.QuestionDO;
|
||||
import org.qinan.cedu.model.entity.QuestionOptionDO;
|
||||
import org.qinan.cedu.model.vo.PaperQuestionVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -12,6 +15,21 @@ import java.util.List;
|
|||
*/
|
||||
public interface QuestionService extends IService<QuestionDO> {
|
||||
|
||||
/**
|
||||
* 编辑习题(选项全量覆盖)
|
||||
*
|
||||
* @param dto 编辑内容
|
||||
*/
|
||||
void updateQuestion(QuestionUpdateDTO dto);
|
||||
|
||||
/**
|
||||
* 试卷试题分页查询(XML关联查询,含课件名称)
|
||||
*
|
||||
* @param query 查询条件(试卷id、题干、题目类型)
|
||||
* @return 试题分页
|
||||
*/
|
||||
IPage<PaperQuestionVO> pagePaperQuestions(PaperQuestionPageQueryDTO query);
|
||||
|
||||
/**
|
||||
* 分页查询试题
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,14 +1,152 @@
|
|||
package org.qinan.cedu.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.qinan.cedu.enums.DeleteEnum;
|
||||
import org.qinan.cedu.enums.QuestionTypeEnum;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionSaveDTO;
|
||||
import org.qinan.cedu.model.entity.PaperDO;
|
||||
import org.qinan.cedu.model.entity.PaperQuestionRelDO;
|
||||
import org.qinan.cedu.model.entity.QuestionDO;
|
||||
import org.qinan.cedu.model.entity.QuestionOptionDO;
|
||||
import org.qinan.cedu.mapper.PaperQuestionRelMapper;
|
||||
import org.qinan.cedu.service.PaperQuestionRelService;
|
||||
import org.qinan.cedu.service.PaperService;
|
||||
import org.qinan.cedu.service.QuestionOptionService;
|
||||
import org.qinan.cedu.service.QuestionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 试卷试题关系服务实现
|
||||
*/
|
||||
@Service
|
||||
public class PaperQuestionRelServiceImpl extends ServiceImpl<PaperQuestionRelMapper, PaperQuestionRelDO> implements PaperQuestionRelService {
|
||||
|
||||
@Autowired
|
||||
private PaperService paperService;
|
||||
|
||||
@Autowired
|
||||
private QuestionService questionService;
|
||||
|
||||
@Autowired
|
||||
private QuestionOptionService questionOptionService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void savePaperQuestion(PaperQuestionSaveDTO dto) {
|
||||
// 校验试卷存在
|
||||
PaperDO paper = paperService.lambdaQuery()
|
||||
.eq(PaperDO::getId, dto.getPaperId())
|
||||
.eq(PaperDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.one();
|
||||
if (paper == null) {
|
||||
throw new IllegalArgumentException("试卷不存在:id=" + dto.getPaperId());
|
||||
}
|
||||
|
||||
// 组装选项(按A-D列,空白列不生成选项)
|
||||
Map<String, String> 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<String> answers = dto.getAnswer().stream()
|
||||
.map(String::trim)
|
||||
.map(String::toUpperCase)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
List<String> notMatched = answers.stream()
|
||||
.filter(answer -> !optionTexts.containsKey(answer))
|
||||
.collect(Collectors.toList());
|
||||
if (!notMatched.isEmpty()) {
|
||||
throw new IllegalArgumentException("答案[" + String.join(",", notMatched) + "]找不到对应的选项");
|
||||
}
|
||||
boolean single = QuestionTypeEnum.SINGLE.getCode().equals(dto.getQuestionType());
|
||||
if (single && answers.size() != 1) {
|
||||
throw new IllegalArgumentException("单选题答案只能有一个");
|
||||
}
|
||||
|
||||
// 保存试题和选项(id用雪花id)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
QuestionDO question = new QuestionDO();
|
||||
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);
|
||||
|
||||
List<QuestionOptionDO> options = new ArrayList<>();
|
||||
int sortOrder = 1;
|
||||
for (Map.Entry<String, String> 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);
|
||||
}
|
||||
question.setAnswerQuestionOptionIds(options.stream()
|
||||
.filter(option -> Boolean.TRUE.equals(option.getIsCorrect()))
|
||||
.map(option -> String.valueOf(option.getId()))
|
||||
.collect(Collectors.joining(",")));
|
||||
|
||||
questionService.save(question);
|
||||
questionOptionService.saveBatch(options);
|
||||
|
||||
// 追加绑定试卷试题(不清空已有绑定)
|
||||
PaperQuestionRelDO rel = new PaperQuestionRelDO();
|
||||
rel.setPaperId(dto.getPaperId());
|
||||
rel.setQuestionId(question.getId());
|
||||
rel.setCreateTime(now);
|
||||
rel.setUpdateTime(now);
|
||||
this.save(rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deletePaperQuestion(Long paperId, Long questionId) {
|
||||
// 删除试卷试题关系(物理删除该试卷下的绑定)
|
||||
boolean removed = this.lambdaUpdate()
|
||||
.eq(PaperQuestionRelDO::getPaperId, paperId)
|
||||
.eq(PaperQuestionRelDO::getQuestionId, questionId)
|
||||
.remove();
|
||||
if (!removed) {
|
||||
throw new IllegalArgumentException("试卷试题关系不存在:paperId=" + paperId + ",questionId=" + questionId);
|
||||
}
|
||||
|
||||
// 逻辑删除试题及其选项
|
||||
boolean deleted = questionService.deleteById(questionId);
|
||||
if (!deleted) {
|
||||
throw new IllegalArgumentException("试题不存在:id=" + questionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfHasText(Map<String, String> optionTexts, String key, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
optionTexts.put(key, value.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ 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;
|
||||
import org.qinan.cedu.enums.DeleteEnum;
|
||||
import org.qinan.cedu.enums.PaperStatusEnum;
|
||||
import org.qinan.cedu.mapper.PaperMapper;
|
||||
import org.qinan.cedu.model.dto.PaperCopyDTO;
|
||||
import org.qinan.cedu.model.dto.PaperImportDTO;
|
||||
import org.qinan.cedu.model.dto.PaperPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.PaperUpdateDTO;
|
||||
|
|
@ -23,6 +25,7 @@ import org.qinan.cedu.model.vo.PaperImportVO;
|
|||
import org.qinan.cedu.model.vo.PaperPageVO;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -32,6 +35,7 @@ import org.springframework.util.StringUtils;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -44,6 +48,7 @@ import java.util.stream.Collectors;
|
|||
public class PaperServiceImpl extends ServiceImpl<PaperMapper, PaperDO> implements PaperService {
|
||||
|
||||
private final PaperQuestionRelService paperQuestionRelService;
|
||||
private final QuestionOptionService questionOptionService;
|
||||
private final QuestionExcelImporter questionExcelImporter;
|
||||
|
||||
@Autowired
|
||||
|
|
@ -90,6 +95,93 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, PaperDO> implemen
|
|||
return this.updateById(paper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void copyPaper(PaperCopyDTO copyDTO) {
|
||||
// 查询源试卷
|
||||
PaperDO source = this.lambdaQuery()
|
||||
.eq(PaperDO::getId, copyDTO.getId())
|
||||
.eq(PaperDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.one();
|
||||
if (source == null) {
|
||||
throw new IllegalArgumentException("试卷不存在:id=" + copyDTO.getId());
|
||||
}
|
||||
|
||||
// 查询源试卷绑定的试题及选项
|
||||
List<PaperQuestionRelDO> rels = paperQuestionRelService.lambdaQuery()
|
||||
.eq(PaperQuestionRelDO::getPaperId, source.getId())
|
||||
.eq(PaperQuestionRelDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.list();
|
||||
List<Long> questionIds = rels.stream().map(PaperQuestionRelDO::getQuestionId).collect(Collectors.toList());
|
||||
List<QuestionDO> questions = questionIds.isEmpty() ? new ArrayList<>()
|
||||
: Db.listByIds(questionIds, QuestionDO.class).stream()
|
||||
.filter(q -> DeleteEnum.FALSE.getCode().equals(q.getDeleteEnum()))
|
||||
.collect(Collectors.toList());
|
||||
List<Long> existQuestionIds = questions.stream().map(QuestionDO::getId).collect(Collectors.toList());
|
||||
List<QuestionOptionDO> allOptions = existQuestionIds.isEmpty() ? new ArrayList<>()
|
||||
: questionOptionService.lambdaQuery()
|
||||
.in(QuestionOptionDO::getQuestionId, existQuestionIds)
|
||||
.eq(QuestionOptionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.list();
|
||||
|
||||
// 新增试卷(使用请求中的名称/总分/合格分数)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PaperDO target = new PaperDO();
|
||||
target.setPaperName(copyDTO.getPaperName().trim());
|
||||
target.setPaperTotalScore(copyDTO.getPaperTotalScore());
|
||||
target.setPaperPassScore(copyDTO.getPaperPassScore());
|
||||
target.setStatus(source.getStatus());
|
||||
this.savePaper(target);
|
||||
|
||||
// 复制试题+选项+试卷试题关系(id全部换新雪花id)
|
||||
if (questions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<QuestionOptionDO> newOptions = new ArrayList<>();
|
||||
List<PaperQuestionRelDO> newRels = new ArrayList<>();
|
||||
for (QuestionDO question : questions) {
|
||||
Long oldQuestionId = question.getId();
|
||||
Long newQuestionId = IdUtil.getSnowflakeNextId();
|
||||
question.setId(newQuestionId);
|
||||
question.setCreateTime(now);
|
||||
question.setUpdateTime(now);
|
||||
question.setVersion(0);
|
||||
|
||||
List<QuestionOptionDO> options = allOptions.stream()
|
||||
.filter(option -> oldQuestionId.equals(option.getQuestionId()))
|
||||
.collect(Collectors.toList());
|
||||
for (QuestionOptionDO option : options) {
|
||||
Long oldOptionId = option.getId();
|
||||
option.setId(IdUtil.getSnowflakeNextId());
|
||||
option.setQuestionId(newQuestionId);
|
||||
option.setCreateTime(now);
|
||||
option.setUpdateTime(now);
|
||||
option.setVersion(0);
|
||||
if (StringUtils.hasText(question.getAnswerQuestionOptionIds())) {
|
||||
// 替换正确选项id引用
|
||||
String replaced = Arrays.stream(question.getAnswerQuestionOptionIds().split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(oldId -> oldId.equals(String.valueOf(oldOptionId))
|
||||
? String.valueOf(option.getId()) : oldId)
|
||||
.collect(Collectors.joining(","));
|
||||
question.setAnswerQuestionOptionIds(replaced);
|
||||
}
|
||||
newOptions.add(option);
|
||||
}
|
||||
|
||||
PaperQuestionRelDO rel = new PaperQuestionRelDO();
|
||||
rel.setPaperId(target.getId());
|
||||
rel.setQuestionId(newQuestionId);
|
||||
rel.setCreateTime(now);
|
||||
rel.setUpdateTime(now);
|
||||
newRels.add(rel);
|
||||
}
|
||||
Db.saveBatch(questions);
|
||||
Db.saveBatch(newOptions);
|
||||
paperQuestionRelService.saveBatch(newRels);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteById(Long id) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
package org.qinan.cedu.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
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 org.qinan.cedu.enums.QuestionTypeEnum;
|
||||
import org.qinan.cedu.model.dto.PaperQuestionPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.QuestionUpdateDTO;
|
||||
import org.qinan.cedu.model.entity.QuestionDO;
|
||||
import org.qinan.cedu.model.entity.QuestionOptionDO;
|
||||
import org.qinan.cedu.model.vo.PaperQuestionVO;
|
||||
import org.qinan.cedu.enums.DeleteEnum;
|
||||
import org.qinan.cedu.mapper.QuestionMapper;
|
||||
import org.qinan.cedu.service.QuestionOptionService;
|
||||
|
|
@ -17,7 +22,11 @@ import org.springframework.util.CollectionUtils;
|
|||
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;
|
||||
|
||||
/**
|
||||
* 试题服务实现
|
||||
|
|
@ -28,6 +37,94 @@ public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, QuestionDO>
|
|||
@Autowired
|
||||
private QuestionOptionService questionOptionService;
|
||||
|
||||
@Override
|
||||
public IPage<PaperQuestionVO> pagePaperQuestions(PaperQuestionPageQueryDTO query) {
|
||||
return this.baseMapper.pagePaperQuestions(new Page<>(query.getCurrent(), query.getSize()), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateQuestion(QuestionUpdateDTO dto) {
|
||||
// 校验试题存在且未删除
|
||||
QuestionDO exist = this.lambdaQuery()
|
||||
.eq(QuestionDO::getId, dto.getId())
|
||||
.eq(QuestionDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.one();
|
||||
if (exist == null) {
|
||||
throw new IllegalArgumentException("试题不存在:id=" + dto.getId());
|
||||
}
|
||||
|
||||
// 组装选项(按A-D,空白列不生成选项)
|
||||
Map<String, String> 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<String> answers = dto.getAnswer().stream()
|
||||
.map(String::trim)
|
||||
.map(String::toUpperCase)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
List<String> notMatched = answers.stream()
|
||||
.filter(answer -> !optionTexts.containsKey(answer))
|
||||
.collect(Collectors.toList());
|
||||
if (!notMatched.isEmpty()) {
|
||||
throw new IllegalArgumentException("答案[" + String.join(",", notMatched) + "]找不到对应的选项");
|
||||
}
|
||||
boolean single = QuestionTypeEnum.SINGLE.getCode().equals(exist.getQuestionType());
|
||||
if (single && answers.size() != 1) {
|
||||
throw new IllegalArgumentException("单选题答案只能有一个");
|
||||
}
|
||||
|
||||
// 更新试题(选项全量覆盖重建,id预置雪花id)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
QuestionDO question = new QuestionDO();
|
||||
question.setId(dto.getId());
|
||||
question.setTitle(dto.getTitle().trim());
|
||||
question.setAnswer(String.join(",", answers));
|
||||
question.setScore(dto.getScore());
|
||||
question.setAnalysis(StringUtils.hasText(dto.getAnalysis()) ? dto.getAnalysis().trim() : null);
|
||||
question.setCoursewareManagementId(dto.getCoursewareManagementId());
|
||||
question.setUpdateTime(now);
|
||||
|
||||
List<QuestionOptionDO> options = new ArrayList<>();
|
||||
int sortOrder = 1;
|
||||
for (Map.Entry<String, String> entry : optionTexts.entrySet()) {
|
||||
QuestionOptionDO option = new QuestionOptionDO();
|
||||
option.setId(IdUtil.getSnowflakeNextId());
|
||||
option.setQuestionId(dto.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);
|
||||
options.add(option);
|
||||
}
|
||||
question.setAnswerQuestionOptionIds(options.stream()
|
||||
.filter(option -> Boolean.TRUE.equals(option.getIsCorrect()))
|
||||
.map(option -> String.valueOf(option.getId()))
|
||||
.collect(Collectors.joining(",")));
|
||||
|
||||
boolean updated = this.updateById(question);
|
||||
if (!updated) {
|
||||
throw new IllegalArgumentException("编辑习题失败:id=" + dto.getId());
|
||||
}
|
||||
questionOptionService.remove(Wrappers.<QuestionOptionDO>lambdaQuery()
|
||||
.eq(QuestionOptionDO::getQuestionId, dto.getId()));
|
||||
questionOptionService.saveBatch(options);
|
||||
}
|
||||
|
||||
private void putIfHasText(Map<String, String> optionTexts, String key, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
optionTexts.put(key, value.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<QuestionDO> pageQuery(long current, long size, String title, String questionType) {
|
||||
return this.lambdaQuery()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.qinan.cedu.mapper.QuestionMapper">
|
||||
|
||||
<select id="pagePaperQuestions" resultType="org.qinan.cedu.model.vo.PaperQuestionVO">
|
||||
SELECT q.id, q.title, q.score, q.answer_question_option_ids, q.analysis
|
||||
, q.question_type, q.tag_type, q.question_num
|
||||
, cm.courseware_name
|
||||
FROM paper_question_rel pqr
|
||||
JOIN question q ON pqr.question_id = q.id
|
||||
LEFT JOIN courseware_management cm ON cm.id = q.courseware_management_id
|
||||
WHERE pqr.paper_id = #{query.paperId}
|
||||
AND pqr.delete_enum = 0
|
||||
AND q.delete_enum = 0
|
||||
<if test="query.title != null and query.title != ''">
|
||||
AND q.title LIKE CONCAT('%', #{query.title}, '%')
|
||||
</if>
|
||||
<if test="query.questionType != null and query.questionType != ''">
|
||||
AND q.question_type = #{query.questionType}
|
||||
</if>
|
||||
ORDER BY q.id DESC
|
||||
</select>
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue