user paper exam

dev-tmp1
luotaiqian 2026-08-24 12:02:13 +08:00
parent 3da4937be0
commit 075af2a649
16 changed files with 212 additions and 67 deletions

View File

@ -1,18 +1,17 @@
package org.qinan.cedu.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jjb.saas.framework.auth.utils.AuthContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.qinan.cedu.model.dto.PaperExamDTO;
import org.qinan.cedu.model.dto.PaperExamPageQueryDTO;
import org.qinan.cedu.model.vo.PaperExamVO;
import org.qinan.cedu.model.vo.StartExamVO;
import org.qinan.cedu.service.PaperExamService;
import org.qinan.safetyeval.client.dto.PageResponse;
import org.qinan.safetyeval.client.dto.SingleResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
*
@ -26,49 +25,11 @@ public class PaperExamController {
private final PaperExamService paperExamService;
/**
*
*
*/
@ApiOperation("分页查询考试试卷")
@GetMapping("/page")
public PageResponse<PaperExamVO> page(PaperExamPageQueryDTO query) {
IPage<PaperExamVO> page = paperExamService.pageQuery(query);
return PageResponse.of(page.getRecords(), page.getTotal());
}
/**
*
*/
@ApiOperation("考试试卷详情")
@GetMapping("/{id}")
public SingleResponse<PaperExamVO> get(@ApiParam("主键id") @PathVariable Long id) {
return SingleResponse.success(paperExamService.getDetail(id));
}
/**
*
*/
@ApiOperation("新增考试试卷")
@PostMapping("/save")
public SingleResponse<Boolean> save(@Validated @RequestBody PaperExamDTO dto) {
return SingleResponse.success(paperExamService.savePaperExam(dto));
}
/**
*
*/
@ApiOperation("修改考试试卷")
@PutMapping("/update")
public SingleResponse<Boolean> update(@Validated @RequestBody PaperExamDTO dto) {
return SingleResponse.success(paperExamService.updatePaperExam(dto));
}
/**
*
*/
@ApiOperation("删除考试试卷")
@DeleteMapping("/{id}")
public SingleResponse<Void> delete(@ApiParam("主键id") @PathVariable Long id) {
paperExamService.deleteById(id);
return SingleResponse.success();
@ApiOperation("去考试")
@PostMapping("/startExam")
public SingleResponse<StartExamVO> startExam(@ApiParam(value = "试卷id", required = true) @RequestParam Long paperId) {
return SingleResponse.success(paperExamService.startExam(paperId, AuthContext.getUserId()));
}
}

View File

@ -1,9 +1,11 @@
package org.qinan.cedu.convertor;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.qinan.cedu.model.dto.PaperExamDTO;
import org.qinan.cedu.model.entity.PaperExamDO;
import org.qinan.cedu.model.vo.PaperExamVO;
import org.qinan.cedu.model.vo.StartExamVO;
import java.util.List;
@ -17,6 +19,15 @@ public interface PaperExamConvertor {
PaperExamVO convertEToVo(PaperExamDO entity);
/**
* DOVOid paperExamId
*
* @param entity
* @return VO
*/
@Mapping(source = "id", target = "paperExamId")
StartExamVO convertEToStartExamVo(PaperExamDO entity);
List<PaperExamDO> convertDtoListToEList(List<PaperExamDTO> list);
List<PaperExamVO> convertEListToVoList(List<PaperExamDO> list);

View File

@ -4,6 +4,7 @@ import org.mapstruct.Mapper;
import org.qinan.cedu.model.dto.PaperExamQuestionUserDTO;
import org.qinan.cedu.model.entity.PaperExamQuestionUserDO;
import org.qinan.cedu.model.vo.PaperExamQuestionUserVO;
import org.qinan.cedu.model.vo.StartExamQuestionVO;
import java.util.List;
@ -17,6 +18,22 @@ public interface PaperExamQuestionUserConvertor {
PaperExamQuestionUserVO convertEToVo(PaperExamQuestionUserDO entity);
/**
* VODOid
*
* @param vo VO
* @return DO
*/
PaperExamQuestionUserDO convertStartExamVoToE(StartExamQuestionVO vo);
/**
* DOVO
*
* @param entity DO
* @return VO
*/
StartExamQuestionVO convertEToStartExamVo(PaperExamQuestionUserDO entity);
List<PaperExamQuestionUserDO> convertDtoListToEList(List<PaperExamQuestionUserDTO> list);
List<PaperExamQuestionUserVO> convertEListToVoList(List<PaperExamQuestionUserDO> list);

View File

@ -12,6 +12,7 @@ import org.qinan.cedu.model.vo.PaperQuestionDetailVO;
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.model.vo.StartExamQuestionVO;
import java.util.List;
/**
@ -54,6 +55,14 @@ public interface QuestionMapper extends BaseMapper<QuestionDO> {
*/
List<QuestionVO> listPaperQuestions(@Param("paperId") Long paperId);
/**
*
*
* @param paperId id
* @return
*/
List<StartExamQuestionVO> listExamQuestions(@Param("paperId") Long paperId);
/**
* SQL
*

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.qinan.cedu.enums.PaperExamStatusEnum;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
@ -53,7 +54,7 @@ public class PaperExamDTO {
@NotNull(message = "用户id不能为空")
private Long userId;
@ApiModelProperty(value = "考试状态 non_start 未开始, doing 进行中, finished 已完成", required = true)
@ApiModelProperty(value = "考试状态 " + PaperExamStatusEnum.REMARKS, required = true)
@NotBlank(message = "考试状态不能为空")
private String paperExamStatus;

View File

@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.qinan.cedu.enums.PaperExamStatusEnum;
import org.qinan.cedu.model.query.BasePageQuery;
/**
@ -23,7 +24,7 @@ public class PaperExamPageQueryDTO extends BasePageQuery {
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("考试状态 non_start 未开始, doing 进行中, finished 已完成")
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
private String paperExamStatus;
@ApiModelProperty("考试是否通过 1是 0否")

View File

@ -33,11 +33,17 @@ public class PaperExamQuestionUserDTO {
@NotNull(message = "试题id不能为空")
private Long questionId;
@ApiModelProperty("答案:逗号隔开")
private String answer;
@ApiModelProperty("用户作答答案")
private String userAnswer;
@ApiModelProperty("答案选项id 多个逗号隔开")
private String answerQuestionOptionIds;
@ApiModelProperty("用户作答答案选项id 多个逗号隔开")
private String userAnswerQuestionOptionIds;
@ApiModelProperty("作答状态 non 未作答done 已作答")
private String answerStatus;
@ApiModelProperty("正确状态 false 作答错误true 作答正确")
private String rightStatus;
@ApiModelProperty(value = "试题分数", required = true)
@NotNull(message = "试题分数不能为空")

View File

@ -8,6 +8,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.qinan.cedu.enums.PaperExamStatusEnum;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@ -54,7 +55,7 @@ public class PaperExamDO extends BaseEntity {
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("考试状态 non_start 未开始, doing 进行中, finished 已完成")
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
private String paperExamStatus;
@ApiModelProperty("考试试卷状态 normal 正常")

View File

@ -37,11 +37,17 @@ public class PaperExamQuestionUserDO extends BaseEntity {
@ApiModelProperty("试题id")
private Long questionId;
@ApiModelProperty("答案:逗号隔开")
private String answer;
@ApiModelProperty("用户作答答案")
private String userAnswer;
@ApiModelProperty("答案选项id 多个逗号隔开")
private String answerQuestionOptionIds;
@ApiModelProperty("用户作答答案选项id 多个逗号隔开")
private String userAnswerQuestionOptionIds;
@ApiModelProperty("作答状态 non 未作答done 已作答")
private String answerStatus;
@ApiModelProperty("正确状态 false 作答错误true 作答正确")
private String rightStatus;
@ApiModelProperty("试题分数")
private BigDecimal questionScore;

View File

@ -27,11 +27,17 @@ public class PaperExamQuestionUserVO extends BaseVO {
@ApiModelProperty("试题id")
private String questionId;
@ApiModelProperty("答案:逗号隔开")
private String answer;
@ApiModelProperty("用户作答答案")
private String userAnswer;
@ApiModelProperty("答案选项id 多个逗号隔开")
private String answerQuestionOptionIds;
@ApiModelProperty("用户作答答案选项id 多个逗号隔开")
private String userAnswerQuestionOptionIds;
@ApiModelProperty("作答状态 non 未作答done 已作答")
private String answerStatus;
@ApiModelProperty("正确状态 false 作答错误true 作答正确")
private String rightStatus;
@ApiModelProperty("试题分数")
private BigDecimal questionScore;

View File

@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.qinan.cedu.enums.PaperExamStatusEnum;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
@ -47,7 +48,7 @@ public class PaperExamVO extends BaseVO {
@ApiModelProperty("用户id")
private String userId;
@ApiModelProperty("考试状态 non_start 未开始, doing 进行中, finished 已完成")
@ApiModelProperty("考试状态 " + PaperExamStatusEnum.REMARKS)
private String paperExamStatus;
@ApiModelProperty("考试试卷状态 normal 正常")

View File

@ -6,6 +6,7 @@ import org.qinan.cedu.model.dto.PaperExamDTO;
import org.qinan.cedu.model.dto.PaperExamPageQueryDTO;
import org.qinan.cedu.model.entity.PaperExamDO;
import org.qinan.cedu.model.vo.PaperExamVO;
import org.qinan.cedu.model.vo.StartExamVO;
/**
*
@ -51,4 +52,13 @@ public interface PaperExamService extends IService<PaperExamDO> {
* @return
*/
boolean deleteById(Long id);
/**
*
*
* @param paperId id
* @param userId id
* @return
*/
StartExamVO startExam(Long paperId, Long userId);
}

View File

@ -5,18 +5,31 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.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.PaperExamDTO;
import org.qinan.cedu.model.dto.PaperExamPageQueryDTO;
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.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.infrastructure.support.InsertFieldDefaults;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
*
@ -27,8 +40,22 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
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;
private final PaperExamQuestionUserConvertor paperExamQuestionUserConvertor;
private final PaperMapper paperMapper;
private final QuestionMapper questionMapper;
private final PaperExamQuestionUserService paperExamQuestionUserService;
@Override
public IPage<PaperExamVO> pageQuery(PaperExamPageQueryDTO query) {
return this.lambdaQuery()
@ -82,4 +109,79 @@ public class PaperExamServiceImpl extends ServiceImpl<PaperExamMapper, PaperExam
.eq(PaperExamDO::getDeleteEnum, DeleteEnum.FALSE.getCode())
.update();
}
@Override
@Transactional(rollbackFor = Exception.class)
public StartExamVO startExam(Long paperId, Long userId) {
// 查询试卷信息(逻辑删除由 MyBatis-Plus 自动过滤)
PaperDO paper = paperMapper.selectById(paperId);
if (paper == null) {
throw new IllegalArgumentException("试卷不存在");
}
// 查询试卷试题及参考答案信息
List<StartExamQuestionVO> questionList = questionMapper.listExamQuestions(paperId);
if (questionList.isEmpty()) {
throw new IllegalArgumentException("试卷未配置试题,无法开始考试");
}
// 生成并保存考试记录
PaperExamDO paperExam = buildPaperExam(paper, userId);
this.save(paperExam);
// 生成并保存用户试题作答记录,同时回填作答记录信息到返回列表
saveQuestionUserList(paperExam, questionList);
// 组装返回结果
StartExamVO startExamVO = paperExamConvertor.convertEToStartExamVo(paperExam);
startExamVO.setPaperType(paper.getPaperType());
startExamVO.setConfigRule(paper.getConfigRule());
startExamVO.setQuestionList(questionList);
return startExamVO;
}
/**
*
* <p>使 MapStruct /</p>
*/
private PaperExamDO buildPaperExam(PaperDO paper, Long userId) {
PaperExamDO paperExam = new PaperExamDO();
paperExam.setPaperId(paper.getId());
paperExam.setPaperName(paper.getPaperName());
paperExam.setPaperTotalScore(paper.getPaperTotalScore());
paperExam.setPaperPassScore(paper.getPaperPassScore());
paperExam.setExamTime(paper.getExamTime());
paperExam.setUserId(userId);
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;
}
/**
* 0
*/
private void saveQuestionUserList(PaperExamDO paperExam, List<StartExamQuestionVO> questionList) {
List<PaperExamQuestionUserDO> questionUserList = new ArrayList<>(questionList.size());
for (StartExamQuestionVO question : questionList) {
PaperExamQuestionUserDO questionUser = paperExamQuestionUserConvertor.convertStartExamVoToE(question);
questionUser.setPaperExamId(paperExam.getId());
questionUser.setUserId(paperExam.getUserId());
questionUser.setPaperId(paperExam.getPaperId());
questionUser.setAnswerStatus(ANSWER_STATUS_NON);
questionUser.setRightStatus(RIGHT_STATUS_FALSE);
questionUser.setAnswerScore(BigDecimal.ZERO);
InsertFieldDefaults.apply(questionUser);
questionUserList.add(questionUser);
}
paperExamQuestionUserService.saveBatch(questionUserList);
// 回填保存后的作答记录信息,参考答案字段保留试题查询结果
for (int i = 0; i < questionList.size(); i++) {
String answer = questionList.get(i).getAnswer();
String answerQuestionOptionIds = questionList.get(i).getAnswerQuestionOptionIds();
StartExamQuestionVO questionVO = paperExamQuestionUserConvertor.convertEToStartExamVo(questionUserList.get(i));
questionVO.setAnswer(answer);
questionVO.setAnswerQuestionOptionIds(answerQuestionOptionIds);
questionList.set(i, questionVO);
}
}
}

View File

@ -80,4 +80,14 @@
ORDER BY RAND()
LIMIT #{num}
</select>
<select id="listExamQuestions" resultType="org.qinan.cedu.model.vo.StartExamQuestionVO">
SELECT pqr.question_id, pqr.score AS question_score, q.answer, q.answer_question_option_ids
FROM paper_question_rel pqr
JOIN question q ON pqr.question_id = q.id
WHERE pqr.paper_id = #{paperId}
AND pqr.delete_enum = 'false'
AND q.delete_enum = 'false'
ORDER BY pqr.question_num ASC, pqr.id ASC
</select>
</mapper>

View File

@ -28,4 +28,7 @@ public class ClassSelectPersonnelCO {
@ApiModelProperty(value = "部门ID")
private Long deptId;
@ApiModelProperty(value = "部门名称")
private String deptName;
}

View File

@ -112,7 +112,7 @@
<!-- 班级选择人员分页查询,直接返回 CO -->
<select id="classSelectPage" resultType="org.qinan.safetyeval.client.co.ClassSelectPersonnelCO">
SELECT op.id, op.user_name, op.account, op.id_card_no, oi.unit_name, op.dept_id
SELECT op.id, op.user_name, op.account, op.id_card_no, oi.unit_name, op.dept_id, od.dept_name
FROM org_personnel op
JOIN org_info oi ON op.org_id = oi.id AND oi.delete_enum = 'false'
JOIN org_department od ON od.id = op.dept_id AND od.delete_enum = 'false'