user paper exam
parent
40e941bd14
commit
9eab9bf747
|
|
@ -5,10 +5,14 @@ import io.swagger.annotations.Api;
|
|||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.qinan.cedu.model.dto.AnswerQuestionDTO;
|
||||
import org.qinan.cedu.model.dto.StartExamDTO;
|
||||
import org.qinan.cedu.model.vo.StartExamVO;
|
||||
import org.qinan.cedu.service.PaperExamService;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -29,7 +33,28 @@ public class PaperExamController {
|
|||
*/
|
||||
@ApiOperation("去考试")
|
||||
@PostMapping("/startExam")
|
||||
public SingleResponse<StartExamVO> startExam(@ApiParam(value = "试卷id", required = true) @RequestParam Long paperId) {
|
||||
return SingleResponse.success(paperExamService.startExam(paperId, AuthContext.getUserId()));
|
||||
public SingleResponse<StartExamVO> startExam(@Validated @RequestBody StartExamDTO dto) {
|
||||
dto.setUserId(AuthContext.getUserId());
|
||||
return SingleResponse.success(paperExamService.startExam(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 作答:保存用户作答答案,判定正确状态并计算作答得分
|
||||
*/
|
||||
@ApiOperation("作答")
|
||||
@PostMapping("/answerQuestion")
|
||||
public SingleResponse<Void> answerQuestion(@Validated @RequestBody AnswerQuestionDTO dto) {
|
||||
paperExamService.answerQuestion(dto, AuthContext.getUserId());
|
||||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 交卷:汇总各试题作答得分,更新考试成绩、是否通过并结束考试
|
||||
*/
|
||||
@ApiOperation("交卷")
|
||||
@PostMapping("/submitExam")
|
||||
public SingleResponse<Void> submitExam(@ApiParam(value = "试卷id", required = true) @RequestParam Long paperId) {
|
||||
paperExamService.submitExam(paperId, AuthContext.getUserId());
|
||||
return SingleResponse.success();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package org.qinan.cedu.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 作答状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AnswerStatusEnum {
|
||||
|
||||
/**
|
||||
* 未作答
|
||||
*/
|
||||
NON("non", "未作答"),
|
||||
|
||||
/**
|
||||
* 已作答
|
||||
*/
|
||||
DONE("done", "已作答");
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String label;
|
||||
|
||||
/**
|
||||
* 各状态说明(供 ApiModelProperty 引用)
|
||||
*/
|
||||
public static final String REMARKS = "non 未作答, done 已作答";
|
||||
|
||||
/**
|
||||
* 根据编码获取枚举
|
||||
*/
|
||||
public static AnswerStatusEnum fromValue(String value) {
|
||||
for (AnswerStatusEnum status : values()) {
|
||||
if (status.value.equals(value)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
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 = "AnswerQuestionDTO", description = "考试试题作答入参")
|
||||
public class AnswerQuestionDTO {
|
||||
|
||||
@ApiModelProperty(value = "作答记录id(paper_exam_question_user.id)", required = true)
|
||||
@NotNull(message = "作答记录id不能为空")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("用户作答答案")
|
||||
private String userAnswer;
|
||||
|
||||
@ApiModelProperty("用户作答答案选项id 多个逗号隔开")
|
||||
private String userAnswerQuestionOptionIds;
|
||||
}
|
||||
|
|
@ -57,7 +57,4 @@ public class PaperExamDTO {
|
|||
@ApiModelProperty(value = "考试状态 " + PaperExamStatusEnum.REMARKS, required = true)
|
||||
@NotBlank(message = "考试状态不能为空")
|
||||
private String paperExamStatus;
|
||||
|
||||
@ApiModelProperty("考试试卷状态 normal 正常")
|
||||
private String status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
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 = "StartExamDTO", description = "去考试入参")
|
||||
public class StartExamDTO {
|
||||
|
||||
@ApiModelProperty(value = "试卷id", required = true)
|
||||
@NotNull(message = "试卷id不能为空")
|
||||
private Long paperId;
|
||||
|
||||
@ApiModelProperty("用户id")
|
||||
private Long userId;
|
||||
}
|
||||
|
|
@ -58,9 +58,6 @@ public class PaperExamDO extends BaseEntity {
|
|||
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
|
||||
private String paperExamStatus;
|
||||
|
||||
@ApiModelProperty("考试试卷状态 normal 正常")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* paper_exam 表无 org_id 列,排除字段映射,避免生成 SQL 报错
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -50,7 +50,4 @@ public class PaperExamVO extends BaseVO {
|
|||
|
||||
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
|
||||
private String paperExamStatus;
|
||||
|
||||
@ApiModelProperty("考试试卷状态 normal 正常")
|
||||
private String status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ 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.AnswerQuestionDTO;
|
||||
import org.qinan.cedu.model.dto.PaperExamDTO;
|
||||
import org.qinan.cedu.model.dto.PaperExamPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.StartExamDTO;
|
||||
import org.qinan.cedu.model.entity.PaperExamDO;
|
||||
import org.qinan.cedu.model.vo.PaperExamVO;
|
||||
import org.qinan.cedu.model.vo.StartExamVO;
|
||||
|
|
@ -56,9 +58,24 @@ public interface PaperExamService extends IService<PaperExamDO> {
|
|||
/**
|
||||
* 去考试:根据试卷生成考试记录及用户试题作答记录
|
||||
*
|
||||
* @param paperId 试卷id
|
||||
* @param userId 用户id
|
||||
* @param dto 去考试入参(试卷id必填,用户id由Controller填充)
|
||||
* @return 考试信息(含试题列表及参考答案)
|
||||
*/
|
||||
StartExamVO startExam(Long paperId, Long userId);
|
||||
StartExamVO startExam(StartExamDTO dto);
|
||||
|
||||
/**
|
||||
* 作答:保存用户作答内容,判定正确状态并计算作答得分
|
||||
*
|
||||
* @param dto 作答入参(作答记录id必填)
|
||||
* @param userId 当前用户id
|
||||
*/
|
||||
void answerQuestion(AnswerQuestionDTO dto, Long userId);
|
||||
|
||||
/**
|
||||
* 交卷:汇总用户最近一次进行中考试各试题作答得分,更新考试成绩、是否通过并结束考试
|
||||
*
|
||||
* @param paperId 试卷id
|
||||
* @param userId 用户id
|
||||
*/
|
||||
void submitExam(Long paperId, Long userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,21 +6,26 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import org.qinan.cedu.convertor.PaperExamConvertor;
|
||||
import org.qinan.cedu.convertor.PaperExamQuestionUserConvertor;
|
||||
import org.qinan.cedu.enums.AnswerStatusEnum;
|
||||
import org.qinan.cedu.enums.DeleteEnum;
|
||||
import org.qinan.cedu.enums.PaperExamStatusEnum;
|
||||
import org.qinan.cedu.mapper.PaperExamMapper;
|
||||
import org.qinan.cedu.mapper.PaperMapper;
|
||||
import org.qinan.cedu.mapper.QuestionMapper;
|
||||
import org.qinan.cedu.model.dto.AnswerQuestionDTO;
|
||||
import org.qinan.cedu.model.dto.PaperExamDTO;
|
||||
import org.qinan.cedu.model.dto.PaperExamPageQueryDTO;
|
||||
import org.qinan.cedu.model.dto.StartExamDTO;
|
||||
import org.qinan.cedu.model.entity.PaperDO;
|
||||
import org.qinan.cedu.model.entity.PaperExamDO;
|
||||
import org.qinan.cedu.model.entity.PaperExamQuestionUserDO;
|
||||
import org.qinan.cedu.model.entity.QuestionDO;
|
||||
import org.qinan.cedu.model.vo.PaperExamVO;
|
||||
import org.qinan.cedu.model.vo.StartExamQuestionVO;
|
||||
import org.qinan.cedu.model.vo.StartExamVO;
|
||||
import org.qinan.cedu.service.PaperExamQuestionUserService;
|
||||
import org.qinan.cedu.service.PaperExamService;
|
||||
import org.qinan.safetyeval.domain.constant.BooleanQAEnum;
|
||||
import org.qinan.safetyeval.infrastructure.support.InsertFieldDefaults;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -29,7 +34,10 @@ import org.springframework.util.StringUtils;
|
|||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 考试试卷服务实现
|
||||
|
|
@ -38,13 +46,6 @@ import java.util.List;
|
|||
@RequiredArgsConstructor
|
||||
public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExamDO> implements PaperExamService {
|
||||
|
||||
private static final String STATUS_NORMAL = "normal";
|
||||
|
||||
/** 作答状态:未作答 */
|
||||
private static final String ANSWER_STATUS_NON = "non";
|
||||
|
||||
/** 正确状态:作答错误 */
|
||||
private static final String RIGHT_STATUS_FALSE = "false";
|
||||
|
||||
private final PaperExamConvertor paperExamConvertor;
|
||||
|
||||
|
|
@ -82,9 +83,6 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
|
|||
@Override
|
||||
public boolean savePaperExam(PaperExamDTO dto) {
|
||||
PaperExamDO entity = paperExamConvertor.convertDtoToE(dto);
|
||||
if (!StringUtils.hasText(entity.getStatus())) {
|
||||
entity.setStatus(STATUS_NORMAL);
|
||||
}
|
||||
// paper_exam 表无 org_id 列,插入前不填充机构id
|
||||
InsertFieldDefaults.applyIgnoreOrgId(entity);
|
||||
return this.save(entity);
|
||||
|
|
@ -112,7 +110,9 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public StartExamVO startExam(Long paperId, Long userId) {
|
||||
public StartExamVO startExam(StartExamDTO dto) {
|
||||
Long paperId = dto.getPaperId();
|
||||
Long userId = dto.getUserId();
|
||||
// 查询试卷信息(逻辑删除由 MyBatis-Plus 自动过滤)
|
||||
PaperDO paper = paperMapper.selectById(paperId);
|
||||
if (paper == null) {
|
||||
|
|
@ -136,6 +136,94 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
|
|||
return startExamVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void answerQuestion(AnswerQuestionDTO dto, Long userId) {
|
||||
PaperExamQuestionUserDO questionUser = paperExamQuestionUserService.lambdaQuery()
|
||||
.eq(PaperExamQuestionUserDO::getId, dto.getId())
|
||||
.eq(PaperExamQuestionUserDO::getUserId, userId)
|
||||
.one();
|
||||
if (questionUser == null) {
|
||||
throw new IllegalArgumentException("作答记录不存在");
|
||||
}
|
||||
QuestionDO question = questionMapper.selectById(questionUser.getQuestionId());
|
||||
if (question == null) {
|
||||
throw new IllegalArgumentException("试题不存在");
|
||||
}
|
||||
boolean right = isAnswerRight(question, dto);
|
||||
questionUser.setUserAnswer(dto.getUserAnswer());
|
||||
questionUser.setUserAnswerQuestionOptionIds(dto.getUserAnswerQuestionOptionIds());
|
||||
questionUser.setAnswerStatus(AnswerStatusEnum.DONE.getValue());
|
||||
questionUser.setRightStatus(right ? BooleanQAEnum.TRUE.getValue() : BooleanQAEnum.FALSE.getValue());
|
||||
questionUser.setAnswerScore(right ? questionUser.getQuestionScore() : BigDecimal.ZERO);
|
||||
InsertFieldDefaults.applyForUpdate(questionUser);
|
||||
paperExamQuestionUserService.updateById(questionUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void submitExam(Long paperId, Long userId) {
|
||||
PaperExamDO paperExam = this.lambdaQuery()
|
||||
.eq(PaperExamDO::getPaperId, paperId)
|
||||
.eq(PaperExamDO::getUserId, userId)
|
||||
.eq(PaperExamDO::getPaperExamStatus, PaperExamStatusEnum.DOING.getValue())
|
||||
.eq(PaperExamDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.orderByDesc(PaperExamDO::getId)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
if (paperExam == null) {
|
||||
throw new IllegalArgumentException("没有进行中的考试记录");
|
||||
}
|
||||
BigDecimal examScore = sumAnswerScore(paperExam.getId());
|
||||
paperExam.setExamScore(examScore);
|
||||
paperExam.setPassed(examScore.compareTo(paperExam.getPaperPassScore()) >= 0 ? 1 : 0);
|
||||
paperExam.setSubmitTime(LocalDateTime.now());
|
||||
paperExam.setPaperExamStatus(PaperExamStatusEnum.DONE.getValue());
|
||||
// paper_exam 表无 org_id 列,更新前不填充机构id
|
||||
InsertFieldDefaults.applyForUpdateIgnoreOrgId(paperExam);
|
||||
this.updateById(paperExam);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定作答是否正确:选项题比对选项id集合(忽略顺序),其余题型比对文本答案
|
||||
*/
|
||||
private boolean isAnswerRight(QuestionDO question, AnswerQuestionDTO dto) {
|
||||
if (StringUtils.hasText(question.getAnswerQuestionOptionIds())) {
|
||||
return normalizeOptionIds(question.getAnswerQuestionOptionIds())
|
||||
.equals(normalizeOptionIds(dto.getUserAnswerQuestionOptionIds()));
|
||||
}
|
||||
return dto.getUserAnswer() != null && question.getAnswer() != null
|
||||
&& dto.getUserAnswer().trim().equals(question.getAnswer().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 规整选项id串:按逗号拆分、去空、排序后重新拼接,用于忽略顺序比对
|
||||
*/
|
||||
private String normalizeOptionIds(String optionIds) {
|
||||
if (!StringUtils.hasText(optionIds)) {
|
||||
return "";
|
||||
}
|
||||
return Arrays.stream(optionIds.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.sorted()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总一次考试所有试题的作答得分(未作答按0分计入)
|
||||
*/
|
||||
private BigDecimal sumAnswerScore(Long paperExamId) {
|
||||
List<PaperExamQuestionUserDO> questionUserList = paperExamQuestionUserService.lambdaQuery()
|
||||
.eq(PaperExamQuestionUserDO::getPaperExamId, paperExamId)
|
||||
.eq(PaperExamQuestionUserDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
|
||||
.list();
|
||||
return questionUserList.stream()
|
||||
.map(PaperExamQuestionUserDO::getAnswerScore)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建考试记录实体(复制试卷基本信息,置为进行中状态)
|
||||
* <p>不使用 MapStruct 同名拷贝,避免把试卷的审计字段(创建人/创建时间等)带入考试记录</p>
|
||||
|
|
@ -151,7 +239,6 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
|
|||
paperExam.setExamScore(BigDecimal.ZERO);
|
||||
paperExam.setPassed(0);
|
||||
paperExam.setPaperExamStatus(PaperExamStatusEnum.DOING.getValue());
|
||||
paperExam.setStatus(STATUS_NORMAL);
|
||||
// paper_exam 表无 org_id 列,插入前不填充机构id
|
||||
InsertFieldDefaults.applyIgnoreOrgId(paperExam);
|
||||
return paperExam;
|
||||
|
|
@ -167,8 +254,8 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
|
|||
questionUser.setPaperExamId(paperExam.getId());
|
||||
questionUser.setUserId(paperExam.getUserId());
|
||||
questionUser.setPaperId(paperExam.getPaperId());
|
||||
questionUser.setAnswerStatus(ANSWER_STATUS_NON);
|
||||
questionUser.setRightStatus(RIGHT_STATUS_FALSE);
|
||||
questionUser.setAnswerStatus(AnswerStatusEnum.NON.getValue());
|
||||
questionUser.setRightStatus(BooleanQAEnum.FALSE.getValue());
|
||||
questionUser.setAnswerScore(BigDecimal.ZERO);
|
||||
InsertFieldDefaults.apply(questionUser);
|
||||
questionUserList.add(questionUser);
|
||||
|
|
|
|||
Loading…
Reference in New Issue