package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.exception.DataException;
|
import jnpf.tmsEntity.TmsQuestionBankEntity;
|
import jnpf.tmsEntity.TmsQuestionEntity;
|
import jnpf.tmsEntity.TmsQuestionOptionEntity;
|
import jnpf.tmsEntity.TmsSelfTestEntity;
|
import jnpf.tmsEntity.TmsSelfTestItemEntity;
|
import jnpf.tmsEntity.selftest.TmsSelfTestOptionVO;
|
import jnpf.tmsEntity.selftest.TmsSelfTestPaperVO;
|
import jnpf.tmsEntity.selftest.TmsSelfTestQuery;
|
import jnpf.tmsEntity.selftest.TmsSelfTestQuestionVO;
|
import jnpf.tmsEntity.selftest.TmsSelfTestRecordVO;
|
import jnpf.tmsEntity.selftest.TmsSelfTestStartForm;
|
import jnpf.tmsEntity.selftest.TmsSelfTestSubmitForm;
|
import jnpf.tmsEntity.selftest.TmsSelfTestSubmitResultVO;
|
import jnpf.tmsEntity.selftest.TmsSelfTestTypeSetting;
|
import jnpf.tmsMapper.TmsQuestionBankMapper;
|
import jnpf.tmsMapper.TmsQuestionMapper;
|
import jnpf.tmsMapper.TmsQuestionOptionMapper;
|
import jnpf.tmsMapper.TmsSelfTestItemMapper;
|
import jnpf.tmsMapper.TmsSelfTestMapper;
|
import jnpf.tmsService.TmsSelfTestService;
|
import jnpf.util.DateUtil;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.StringUtil;
|
import jnpf.util.UserProvider;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
public class TmsSelfTestServiceImpl implements TmsSelfTestService {
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
/** 自我检测 UI 难度 → 试题 difficulty 值 */
|
private static final Map<String, List<String>> DIFFICULTY_MAP = new HashMap<>();
|
|
static {
|
DIFFICULTY_MAP.put("veryEasy", Arrays.asList("easy", "veryEasy"));
|
DIFFICULTY_MAP.put("easier", Arrays.asList("easy", "easier"));
|
DIFFICULTY_MAP.put("normal", Collections.singletonList("normal"));
|
DIFFICULTY_MAP.put("harder", Arrays.asList("hard", "harder"));
|
DIFFICULTY_MAP.put("veryHard", Arrays.asList("hard", "veryHard"));
|
}
|
|
private final TmsSelfTestMapper selfTestMapper;
|
private final TmsSelfTestItemMapper selfTestItemMapper;
|
private final TmsQuestionMapper questionMapper;
|
private final TmsQuestionOptionMapper optionMapper;
|
private final TmsQuestionBankMapper bankMapper;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsSelfTestPaperVO start(TmsSelfTestStartForm form) {
|
if (form == null || StringUtil.isEmpty(form.getBankId())) {
|
throw new DataException("请选择题库");
|
}
|
TmsQuestionBankEntity bank = bankMapper.selectById(form.getBankId());
|
if (bank == null) {
|
throw new DataException("题库不存在或已删除");
|
}
|
|
int singleCnt = countOf(form.getSingle());
|
int multiCnt = countOf(form.getMulti());
|
int judgeCnt = countOf(form.getJudge());
|
if (singleCnt + multiCnt + judgeCnt <= 0) {
|
throw new DataException("请至少设置一种题型的抽题数量");
|
}
|
|
List<TmsQuestionEntity> drawn = new ArrayList<>();
|
drawn.addAll(drawQuestions(form.getBankId(), "single", form.getSingle()));
|
drawn.addAll(drawQuestions(form.getBankId(), "multi", form.getMulti()));
|
drawn.addAll(drawQuestions(form.getBankId(), "judge", form.getJudge()));
|
if (drawn.isEmpty()) {
|
throw new DataException("当前条件下没有可抽题目,请调整数量或难度");
|
}
|
|
Map<String, List<TmsQuestionOptionEntity>> optionMap = loadOptions(
|
drawn.stream().map(TmsQuestionEntity::getId).collect(Collectors.toList()));
|
|
String userId = UserProvider.getLoginUserId();
|
Date now = new Date();
|
String paperId = RandomUtil.uuId();
|
|
TmsSelfTestEntity entity = new TmsSelfTestEntity();
|
entity.setId(paperId);
|
entity.setBankId(bank.getId());
|
entity.setBankName(StringUtil.isNotEmpty(form.getBankName()) ? form.getBankName() : bank.getBankName());
|
entity.setUserId(userId);
|
entity.setSingleCount(singleCnt);
|
entity.setMultiCount(multiCnt);
|
entity.setJudgeCount(judgeCnt);
|
entity.setSingleDifficulty(diffOf(form.getSingle()));
|
entity.setMultiDifficulty(diffOf(form.getMulti()));
|
entity.setJudgeDifficulty(diffOf(form.getJudge()));
|
entity.setTotalCount(drawn.size());
|
entity.setStartTime(now);
|
entity.setTestStatus("doing");
|
entity.setCreatorUserId(userId);
|
entity.setCreatorTime(now);
|
selfTestMapper.insert(entity);
|
|
List<TmsSelfTestQuestionVO> questions = new ArrayList<>(drawn.size());
|
int sort = 0;
|
for (TmsQuestionEntity q : drawn) {
|
List<TmsQuestionOptionEntity> opts = optionMap.getOrDefault(q.getId(), Collections.emptyList());
|
List<TmsSelfTestOptionVO> optionVos = toOptionVos(opts);
|
String correct = opts.stream()
|
.filter(o -> "1".equals(o.getIsCorrect()))
|
.map(TmsQuestionOptionEntity::getOptionLabel)
|
.sorted()
|
.collect(Collectors.joining(","));
|
|
TmsSelfTestItemEntity item = new TmsSelfTestItemEntity();
|
item.setId(RandomUtil.uuId());
|
item.setForeignId(paperId);
|
item.setQuestionId(q.getId());
|
item.setQuestionNo(q.getQuestionNo());
|
item.setQuestionType(q.getQuestionType());
|
item.setDifficulty(q.getDifficulty());
|
item.setStem(q.getStem());
|
item.setOptionsJson(toJson(optionVos));
|
item.setCorrectAnswer(correct);
|
item.setSortNo(++sort);
|
item.setCreatorUserId(userId);
|
item.setCreatorTime(now);
|
selfTestItemMapper.insert(item);
|
|
TmsSelfTestQuestionVO vo = new TmsSelfTestQuestionVO();
|
vo.setId(q.getId());
|
vo.setQuestionNo(q.getQuestionNo());
|
vo.setQuestionType(q.getQuestionType());
|
vo.setDifficulty(q.getDifficulty());
|
vo.setStem(q.getStem());
|
vo.setOptions(optionVos);
|
questions.add(vo);
|
}
|
|
TmsSelfTestPaperVO paper = new TmsSelfTestPaperVO();
|
paper.setPaperId(paperId);
|
paper.setBankId(entity.getBankId());
|
paper.setBankName(entity.getBankName());
|
paper.setQuestions(questions);
|
return paper;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void saveAnswers(String id, TmsSelfTestSubmitForm form) {
|
TmsSelfTestEntity entity = requireDoingPaper(id, "无权暂存该检测");
|
String userId = UserProvider.getLoginUserId();
|
List<TmsSelfTestItemEntity> items = listItems(id);
|
Map<String, Object> answers = form != null && form.getAnswers() != null
|
? form.getAnswers()
|
: Collections.emptyMap();
|
|
Date now = new Date();
|
for (TmsSelfTestItemEntity item : items) {
|
// 仅更新前端已作答的题,未传的保持原值,方便增量暂存
|
if (!answers.containsKey(item.getQuestionId())) {
|
continue;
|
}
|
String userAnswer = normalizeAnswer(answers.get(item.getQuestionId()), item.getQuestionType());
|
item.setUserAnswer(userAnswer);
|
item.setIsRight(null);
|
item.setLastModifyUserId(userId);
|
item.setLastModifyTime(now);
|
selfTestItemMapper.updateById(item);
|
}
|
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
selfTestMapper.updateById(entity);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsSelfTestSubmitResultVO submit(String id, TmsSelfTestSubmitForm form) {
|
TmsSelfTestEntity entity = requireDoingPaper(id, "无权提交该检测");
|
String userId = UserProvider.getLoginUserId();
|
List<TmsSelfTestItemEntity> items = listItems(id);
|
|
Map<String, Object> answers = form != null && form.getAnswers() != null
|
? form.getAnswers()
|
: Collections.emptyMap();
|
|
Date now = new Date();
|
int correct = 0;
|
for (TmsSelfTestItemEntity item : items) {
|
String userAnswer = normalizeAnswer(answers.get(item.getQuestionId()), item.getQuestionType());
|
boolean right = Objects.equals(userAnswer, normalizeCorrect(item.getCorrectAnswer()));
|
item.setUserAnswer(userAnswer);
|
item.setIsRight(right ? "1" : "0");
|
item.setLastModifyUserId(userId);
|
item.setLastModifyTime(now);
|
selfTestItemMapper.updateById(item);
|
if (right) {
|
correct++;
|
}
|
}
|
|
int total = items.size();
|
BigDecimal rate = total <= 0
|
? BigDecimal.ZERO
|
: BigDecimal.valueOf(correct * 100.0 / total).setScale(2, RoundingMode.HALF_UP);
|
|
entity.setCorrectCount(correct);
|
entity.setTotalCount(total);
|
entity.setScoreRate(rate);
|
entity.setSubmitTime(now);
|
entity.setTestStatus("submitted");
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
selfTestMapper.updateById(entity);
|
|
TmsSelfTestSubmitResultVO result = new TmsSelfTestSubmitResultVO();
|
result.setPaperId(id);
|
result.setTotalCount(total);
|
result.setCorrectCount(correct);
|
result.setScoreRate(rate);
|
return result;
|
}
|
|
private TmsSelfTestEntity requireDoingPaper(String id, String denyMsg) {
|
TmsSelfTestEntity entity = selfTestMapper.selectById(id);
|
if (entity == null) {
|
throw new DataException("检测记录不存在");
|
}
|
String userId = UserProvider.getLoginUserId();
|
if (!Objects.equals(userId, entity.getUserId())) {
|
throw new DataException(denyMsg);
|
}
|
if ("submitted".equals(entity.getTestStatus())) {
|
throw new DataException("已交卷,请勿重复操作");
|
}
|
return entity;
|
}
|
|
private List<TmsSelfTestItemEntity> listItems(String paperId) {
|
LambdaQueryWrapper<TmsSelfTestItemEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsSelfTestItemEntity::getForeignId, paperId);
|
qw.orderByAsc(TmsSelfTestItemEntity::getSortNo);
|
return selfTestItemMapper.selectList(qw);
|
}
|
|
@Override
|
public List<TmsSelfTestRecordVO> getList(TmsSelfTestQuery query) {
|
String userId = UserProvider.getLoginUserId();
|
LambdaQueryWrapper<TmsSelfTestEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsSelfTestEntity::getUserId, userId);
|
if (StringUtil.isNotEmpty(query.getBankId())) {
|
qw.eq(TmsSelfTestEntity::getBankId, query.getBankId());
|
}
|
if (StringUtil.isNotEmpty(query.getTestStatus())) {
|
qw.eq(TmsSelfTestEntity::getTestStatus, query.getTestStatus());
|
}
|
if (StringUtil.isNotEmpty(query.getKeyword()) && !"null".equalsIgnoreCase(query.getKeyword())) {
|
qw.like(TmsSelfTestEntity::getBankName, query.getKeyword().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getStartTimeBegin())) {
|
Date begin = DateUtil.stringToDate(query.getStartTimeBegin());
|
if (begin != null) {
|
qw.ge(TmsSelfTestEntity::getStartTime, begin);
|
}
|
}
|
if (StringUtil.isNotEmpty(query.getStartTimeEnd())) {
|
Date end = DateUtil.stringToDate(query.getStartTimeEnd());
|
if (end != null) {
|
qw.le(TmsSelfTestEntity::getStartTime, end);
|
}
|
}
|
qw.orderByDesc(TmsSelfTestEntity::getCreatorTime);
|
|
Page<TmsSelfTestEntity> page = new Page<>(query.getCurrentPage(), query.getPageSize());
|
Page<TmsSelfTestEntity> result = selfTestMapper.selectPage(page, qw);
|
|
List<TmsSelfTestRecordVO> list = new ArrayList<>();
|
for (TmsSelfTestEntity row : result.getRecords()) {
|
TmsSelfTestRecordVO vo = new TmsSelfTestRecordVO();
|
vo.setId(row.getId());
|
vo.setBankId(row.getBankId());
|
vo.setBankName(row.getBankName());
|
vo.setTotalCount(row.getTotalCount());
|
vo.setCorrectCount(row.getCorrectCount());
|
vo.setScoreRate(row.getScoreRate());
|
vo.setTestStatus(row.getTestStatus());
|
vo.setStartTime(row.getStartTime() == null ? null : DateUtil.dateToString(row.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
|
vo.setSubmitTime(row.getSubmitTime() == null ? null : DateUtil.dateToString(row.getSubmitTime(), "yyyy-MM-dd HH:mm:ss"));
|
list.add(vo);
|
}
|
query.setData(list, result.getTotal());
|
return list;
|
}
|
|
@Override
|
public TmsSelfTestPaperVO getInfo(String id) {
|
TmsSelfTestEntity entity = selfTestMapper.selectById(id);
|
if (entity == null) {
|
throw new DataException("检测记录不存在");
|
}
|
if (!Objects.equals(UserProvider.getLoginUserId(), entity.getUserId())) {
|
throw new DataException("无权查看该检测");
|
}
|
|
LambdaQueryWrapper<TmsSelfTestItemEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsSelfTestItemEntity::getForeignId, id);
|
qw.orderByAsc(TmsSelfTestItemEntity::getSortNo);
|
List<TmsSelfTestItemEntity> items = selfTestItemMapper.selectList(qw);
|
|
List<TmsSelfTestQuestionVO> questions = new ArrayList<>(items.size());
|
for (TmsSelfTestItemEntity item : items) {
|
TmsSelfTestQuestionVO vo = new TmsSelfTestQuestionVO();
|
vo.setId(item.getQuestionId());
|
vo.setQuestionNo(item.getQuestionNo());
|
vo.setQuestionType(item.getQuestionType());
|
vo.setDifficulty(item.getDifficulty());
|
vo.setStem(item.getStem());
|
vo.setOptions(parseOptions(item.getOptionsJson()));
|
vo.setCorrectAnswer(item.getCorrectAnswer());
|
vo.setUserAnswer(item.getUserAnswer());
|
vo.setIsRight(item.getIsRight());
|
vo.setSortNo(item.getSortNo());
|
questions.add(vo);
|
}
|
|
TmsSelfTestPaperVO paper = new TmsSelfTestPaperVO();
|
paper.setPaperId(entity.getId());
|
paper.setBankId(entity.getBankId());
|
paper.setBankName(entity.getBankName());
|
paper.setTestStatus(entity.getTestStatus());
|
paper.setTotalCount(entity.getTotalCount());
|
paper.setCorrectCount(entity.getCorrectCount());
|
paper.setScoreRate(entity.getScoreRate());
|
paper.setStartTime(entity.getStartTime() == null ? null : DateUtil.dateToString(entity.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
|
paper.setSubmitTime(entity.getSubmitTime() == null ? null : DateUtil.dateToString(entity.getSubmitTime(), "yyyy-MM-dd HH:mm:ss"));
|
paper.setQuestions(questions);
|
return paper;
|
}
|
|
private List<TmsQuestionEntity> drawQuestions(String bankId, String type, TmsSelfTestTypeSetting setting) {
|
int count = countOf(setting);
|
if (count <= 0) {
|
return Collections.emptyList();
|
}
|
LambdaQueryWrapper<TmsQuestionEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsQuestionEntity::getBankId, bankId);
|
qw.eq(TmsQuestionEntity::getQuestionType, type);
|
qw.eq(TmsQuestionEntity::getBizStatus, "open");
|
List<String> diffs = resolveDifficulties(diffOf(setting));
|
if (!diffs.isEmpty()) {
|
qw.in(TmsQuestionEntity::getDifficulty, diffs);
|
}
|
List<TmsQuestionEntity> pool = questionMapper.selectList(qw);
|
if (pool.isEmpty()) {
|
return Collections.emptyList();
|
}
|
Collections.shuffle(pool);
|
if (pool.size() <= count) {
|
return pool;
|
}
|
return new ArrayList<>(pool.subList(0, count));
|
}
|
|
private List<String> resolveDifficulties(String uiDifficulty) {
|
if (StringUtil.isEmpty(uiDifficulty)) {
|
return Collections.emptyList();
|
}
|
return DIFFICULTY_MAP.getOrDefault(uiDifficulty, Collections.singletonList(uiDifficulty));
|
}
|
|
private Map<String, List<TmsQuestionOptionEntity>> loadOptions(List<String> questionIds) {
|
if (questionIds == null || questionIds.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
LambdaQueryWrapper<TmsQuestionOptionEntity> qw = new LambdaQueryWrapper<>();
|
qw.in(TmsQuestionOptionEntity::getForeignId, questionIds);
|
qw.orderByAsc(TmsQuestionOptionEntity::getSortNo);
|
List<TmsQuestionOptionEntity> all = optionMapper.selectList(qw);
|
return all.stream().collect(Collectors.groupingBy(TmsQuestionOptionEntity::getForeignId));
|
}
|
|
private List<TmsSelfTestOptionVO> toOptionVos(List<TmsQuestionOptionEntity> opts) {
|
List<TmsSelfTestOptionVO> list = new ArrayList<>(opts.size());
|
for (TmsQuestionOptionEntity opt : opts) {
|
TmsSelfTestOptionVO vo = new TmsSelfTestOptionVO();
|
vo.setOptionLabel(opt.getOptionLabel());
|
vo.setOptionContent(opt.getOptionContent());
|
vo.setIsCorrect(opt.getIsCorrect());
|
list.add(vo);
|
}
|
return list;
|
}
|
|
private String toJson(Object obj) {
|
try {
|
return OBJECT_MAPPER.writeValueAsString(obj);
|
} catch (Exception e) {
|
throw new DataException("选项序列化失败");
|
}
|
}
|
|
private List<TmsSelfTestOptionVO> parseOptions(String json) {
|
if (StringUtil.isEmpty(json)) {
|
return Collections.emptyList();
|
}
|
try {
|
return OBJECT_MAPPER.readValue(json, new TypeReference<List<TmsSelfTestOptionVO>>() {
|
});
|
} catch (Exception e) {
|
return Collections.emptyList();
|
}
|
}
|
|
private int countOf(TmsSelfTestTypeSetting setting) {
|
if (setting == null || setting.getCount() == null) {
|
return 0;
|
}
|
return Math.max(0, setting.getCount());
|
}
|
|
private String diffOf(TmsSelfTestTypeSetting setting) {
|
return setting == null ? null : setting.getDifficulty();
|
}
|
|
/** 归一化用户答案:多选排序后逗号拼接 */
|
private String normalizeAnswer(Object raw, String questionType) {
|
if (raw == null) {
|
return "";
|
}
|
if ("multi".equals(questionType)) {
|
Set<String> labels = new LinkedHashSet<>();
|
if (raw instanceof List) {
|
for (Object o : (List<?>) raw) {
|
if (o != null && StringUtil.isNotEmpty(String.valueOf(o))) {
|
labels.add(String.valueOf(o).trim());
|
}
|
}
|
} else {
|
String s = String.valueOf(raw).trim();
|
if (StringUtil.isNotEmpty(s)) {
|
for (String part : s.split("[,,]")) {
|
if (StringUtil.isNotEmpty(part.trim())) {
|
labels.add(part.trim());
|
}
|
}
|
}
|
}
|
return labels.stream().sorted().collect(Collectors.joining(","));
|
}
|
return String.valueOf(raw).trim();
|
}
|
|
private String normalizeCorrect(String correct) {
|
if (StringUtil.isEmpty(correct)) {
|
return "";
|
}
|
return Arrays.stream(correct.split("[,,]"))
|
.map(String::trim)
|
.filter(StringUtil::isNotEmpty)
|
.sorted()
|
.collect(Collectors.joining(","));
|
}
|
}
|