user paper exam

dev-tmp1
luotaiqian 2026-08-24 13:43:16 +08:00
parent 40e941bd14
commit 9eab9bf747
9 changed files with 244 additions and 28 deletions

View File

@ -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();
}
}

View File

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

View File

@ -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 = "作答记录idpaper_exam_question_user.id", required = true)
@NotNull(message = "作答记录id不能为空")
private Long id;
@ApiModelProperty("用户作答答案")
private String userAnswer;
@ApiModelProperty("用户作答答案选项id 多个逗号隔开")
private String userAnswerQuestionOptionIds;
}

View File

@ -57,7 +57,4 @@ public class PaperExamDTO {
@ApiModelProperty(value = "考试状态 " + PaperExamStatusEnum.REMARKS, required = true)
@NotBlank(message = "考试状态不能为空")
private String paperExamStatus;
@ApiModelProperty("考试试卷状态 normal 正常")
private String status;
}

View File

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

View File

@ -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
*/

View File

@ -50,7 +50,4 @@ public class PaperExamVO extends BaseVO {
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
private String paperExamStatus;
@ApiModelProperty("考试试卷状态 normal 正常")
private String status;
}

View File

@ -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 ididController
* @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);
}

View File

@ -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);