package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.exception.DataException;
|
import jnpf.permission.UserApi;
|
import jnpf.permission.entity.UserEntity;
|
import jnpf.tmsEntity.TmsExamEntity;
|
import jnpf.tmsEntity.TmsExamItemEntity;
|
import jnpf.tmsEntity.TmsPaperEntity;
|
import jnpf.tmsEntity.examscore.TmsExamScoreDetailVO;
|
import jnpf.tmsEntity.examscore.TmsExamScoreOptionVO;
|
import jnpf.tmsEntity.examscore.TmsExamScorePaperItemVO;
|
import jnpf.tmsEntity.examscore.TmsExamScorePaperVO;
|
import jnpf.tmsEntity.examscore.TmsExamScoreQuery;
|
import jnpf.tmsEntity.examscore.TmsExamScoreRowVO;
|
import jnpf.tmsEntity.examscore.TmsExamScoreSummaryVO;
|
import jnpf.tmsMapper.TmsExamItemMapper;
|
import jnpf.tmsMapper.TmsExamMapper;
|
import jnpf.tmsMapper.TmsPaperMapper;
|
import jnpf.tmsService.TmsExamScoreService;
|
import jnpf.util.DateUtil;
|
import jnpf.util.StringUtil;
|
import lombok.Data;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.BeanUtils;
|
import org.springframework.stereotype.Service;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
public class TmsExamScoreServiceImpl implements TmsExamScoreService {
|
|
private static final String EXAM_SUBMITTED = "submitted";
|
private static final String STATUS_INVALID = "invalid";
|
private static final String FMT = "yyyy-MM-dd HH:mm:ss";
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
private final TmsExamMapper examMapper;
|
private final TmsExamItemMapper examItemMapper;
|
private final TmsPaperMapper paperMapper;
|
private final UserApi userApi;
|
|
@Override
|
public List<TmsExamScoreSummaryVO> getList(TmsExamScoreQuery query) {
|
List<TmsExamEntity> exams = loadSubmittedExams();
|
Map<String, List<TmsExamEntity>> byPaper = groupByPaper(exams);
|
Map<String, TmsPaperEntity> papers = loadPapers(new ArrayList<>(byPaper.keySet()));
|
|
List<TmsExamScoreSummaryVO> all = new ArrayList<>();
|
for (Map.Entry<String, List<TmsExamEntity>> entry : byPaper.entrySet()) {
|
TmsPaperEntity paper = papers.get(entry.getKey());
|
all.add(toSummary(entry.getKey(), paper, entry.getValue()));
|
}
|
all.sort(Comparator.comparing(TmsExamScoreSummaryVO::getPaperName, Comparator.nullsLast(String::compareTo)).reversed());
|
|
String keyword = trim(query.getKeyword());
|
String taskNo = trim(query.getTaskNo());
|
if (StringUtil.isNotEmpty(keyword)) {
|
String kw = keyword.toLowerCase();
|
all = all.stream()
|
.filter(x -> contains(x.getTaskSubject(), kw)
|
|| contains(x.getPaperName(), kw)
|
|| contains(x.getTaskNo(), kw))
|
.collect(Collectors.toList());
|
}
|
if (StringUtil.isNotEmpty(taskNo)) {
|
String tn = taskNo.toLowerCase();
|
all = all.stream().filter(x -> contains(x.getTaskNo(), tn)).collect(Collectors.toList());
|
}
|
|
long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1;
|
long size = query.getPageSize() > 0 ? query.getPageSize() : 20;
|
int from = (int) ((current - 1) * size);
|
int to = Math.min(from + (int) size, all.size());
|
List<TmsExamScoreSummaryVO> page = from >= all.size() ? new ArrayList<>() : new ArrayList<>(all.subList(from, to));
|
query.setData(page, all.size());
|
return page;
|
}
|
|
@Override
|
public TmsExamScoreDetailVO getDetail(String summaryId) {
|
if (StringUtil.isEmpty(summaryId)) {
|
throw new DataException("成绩汇总不存在");
|
}
|
List<TmsExamEntity> exams = loadSubmittedByPaper(summaryId);
|
if (exams.isEmpty()) {
|
throw new DataException("成绩汇总不存在");
|
}
|
TmsPaperEntity paper = paperMapper.selectById(summaryId);
|
TmsExamScoreSummaryVO summary = toSummary(summaryId, paper, exams);
|
Map<String, String> names = loadUserNames(exams.stream().map(TmsExamEntity::getUserId).collect(Collectors.toList()));
|
|
List<TmsExamScoreRowVO> rows = new ArrayList<>();
|
for (TmsExamEntity exam : exams) {
|
TmsExamScoreRowVO row = new TmsExamScoreRowVO();
|
row.setId(exam.getId());
|
row.setSummaryId(summaryId);
|
row.setTaskSubject(summary.getTaskSubject());
|
row.setTaskNo(summary.getTaskNo());
|
row.setUserId(exam.getUserId());
|
row.setUserName(names.getOrDefault(exam.getUserId(), exam.getUserId()));
|
row.setExamStart(fmt(exam.getStartTime()));
|
row.setExamEnd(fmt(exam.getSubmitTime()));
|
row.setScore(exam.getTotalScore());
|
row.setPassScore(exam.getPassScore() != null ? exam.getPassScore() : summary.getPassScore());
|
row.setPassFlag(exam.getPassFlag());
|
rows.add(row);
|
}
|
rows.sort(Comparator.comparing(TmsExamScoreRowVO::getExamEnd, Comparator.nullsLast(String::compareTo)).reversed());
|
|
TmsExamScoreDetailVO detail = new TmsExamScoreDetailVO();
|
BeanUtils.copyProperties(summary, detail);
|
detail.setRows(rows);
|
return detail;
|
}
|
|
@Override
|
public TmsExamScorePaperVO getPaperView(String examId) {
|
TmsExamEntity exam = examMapper.selectById(examId);
|
if (exam == null || !EXAM_SUBMITTED.equals(exam.getExamStatus())) {
|
throw new DataException("答卷不存在或尚未交卷");
|
}
|
TmsPaperEntity paper = paperMapper.selectById(exam.getPaperId());
|
Map<String, String> names = loadUserNames(Collections.singletonList(exam.getUserId()));
|
|
LambdaQueryWrapper<TmsExamItemEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsExamItemEntity::getForeignId, examId);
|
qw.orderByAsc(TmsExamItemEntity::getSortNo);
|
List<TmsExamItemEntity> items = examItemMapper.selectList(qw);
|
|
BigDecimal paperTotal = BigDecimal.ZERO;
|
List<TmsExamScorePaperItemVO> itemVos = new ArrayList<>();
|
for (TmsExamItemEntity item : items) {
|
paperTotal = paperTotal.add(nvl(item.getScore()));
|
TmsExamScorePaperItemVO vo = new TmsExamScorePaperItemVO();
|
vo.setId(item.getId());
|
vo.setQuestionType(item.getQuestionType());
|
vo.setStem(item.getStem());
|
vo.setOptions(toOptions(item.getOptionsJson()));
|
vo.setCorrectAnswer(item.getCorrectAnswer());
|
vo.setUserAnswer(item.getUserAnswer());
|
vo.setScore(item.getScore());
|
vo.setGotScore(item.getGotScore());
|
vo.setIsSubjective(item.getIsSubjective());
|
if ("1".equals(item.getIsSubjective())) {
|
vo.setIsCorrect(null);
|
} else if (item.getScore() != null && item.getScore().compareTo(BigDecimal.ZERO) > 0) {
|
vo.setIsCorrect(nvl(item.getGotScore()).compareTo(item.getScore()) >= 0);
|
} else {
|
vo.setIsCorrect(Objects.equals(normalize(item.getCorrectAnswer()), normalize(item.getUserAnswer())));
|
}
|
itemVos.add(vo);
|
}
|
|
TmsExamScorePaperVO vo = new TmsExamScorePaperVO();
|
vo.setExamId(exam.getId());
|
vo.setSummaryId(exam.getPaperId());
|
vo.setTaskSubject(paper != null ? paper.getPaperName() : exam.getPaperName());
|
vo.setTaskNo(paper != null && StringUtil.isNotEmpty(paper.getPaperNo()) ? paper.getPaperNo() : exam.getPaperId());
|
vo.setPaperName(exam.getPaperName());
|
vo.setUserId(exam.getUserId());
|
vo.setUserName(names.getOrDefault(exam.getUserId(), exam.getUserId()));
|
vo.setExamStart(fmt(exam.getStartTime()));
|
vo.setExamEnd(fmt(exam.getSubmitTime()));
|
vo.setScore(exam.getTotalScore());
|
vo.setPassScore(exam.getPassScore());
|
vo.setTotalScore(paper != null && paper.getTotalScore() != null ? paper.getTotalScore() : paperTotal);
|
vo.setPassFlag(exam.getPassFlag());
|
vo.setItems(itemVos);
|
return vo;
|
}
|
|
@Override
|
public List<TmsExamScoreRowVO> exportRows(List<String> summaryIds) {
|
if (summaryIds == null || summaryIds.isEmpty()) {
|
throw new DataException("请选择要导出的考试");
|
}
|
List<TmsExamScoreRowVO> rows = new ArrayList<>();
|
for (String id : summaryIds) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
List<TmsExamEntity> exams = loadSubmittedByPaper(id);
|
if (exams.isEmpty()) {
|
continue;
|
}
|
TmsExamScoreDetailVO detail = getDetail(id);
|
if (detail.getRows() != null) {
|
rows.addAll(detail.getRows());
|
}
|
}
|
if (rows.isEmpty()) {
|
throw new DataException("所选试卷暂无已交卷成绩");
|
}
|
return rows;
|
}
|
|
private TmsExamScoreSummaryVO toSummary(String paperId, TmsPaperEntity paper, List<TmsExamEntity> exams) {
|
TmsExamScoreSummaryVO vo = new TmsExamScoreSummaryVO();
|
vo.setId(paperId);
|
vo.setPaperId(paperId);
|
String paperName = paper != null ? paper.getPaperName() : (exams.isEmpty() ? paperId : exams.get(0).getPaperName());
|
vo.setPaperName(paperName);
|
vo.setTaskSubject(paperName);
|
String paperNo = paper != null && StringUtil.isNotEmpty(paper.getPaperNo()) ? paper.getPaperNo() : paperId;
|
vo.setTaskNo(paperNo);
|
vo.setPaperInvalid(paper != null && STATUS_INVALID.equals(paper.getBizStatus()));
|
|
BigDecimal passScore = paper != null && paper.getPassScore() != null
|
? paper.getPassScore()
|
: exams.stream().map(TmsExamEntity::getPassScore).filter(Objects::nonNull).findFirst().orElse(BigDecimal.ZERO);
|
BigDecimal totalScore = paper != null && paper.getTotalScore() != null
|
? paper.getTotalScore()
|
: BigDecimal.ZERO;
|
vo.setPassScore(passScore);
|
vo.setTotalScore(totalScore);
|
|
int fail = 0;
|
BigDecimal max = null;
|
BigDecimal min = null;
|
BigDecimal sum = BigDecimal.ZERO;
|
int scored = 0;
|
for (TmsExamEntity exam : exams) {
|
if ("0".equals(exam.getPassFlag())) {
|
fail++;
|
}
|
BigDecimal score = exam.getTotalScore();
|
if (score == null) {
|
continue;
|
}
|
scored++;
|
sum = sum.add(score);
|
if (max == null || score.compareTo(max) > 0) {
|
max = score;
|
}
|
if (min == null || score.compareTo(min) < 0) {
|
min = score;
|
}
|
}
|
vo.setParticipantCount(exams.size());
|
vo.setFailCount(fail);
|
vo.setMaxScore(max == null ? BigDecimal.ZERO : max);
|
vo.setMinScore(min == null ? BigDecimal.ZERO : min);
|
vo.setAvgScore(scored == 0
|
? BigDecimal.ZERO
|
: sum.divide(BigDecimal.valueOf(scored), 1, RoundingMode.HALF_UP));
|
return vo;
|
}
|
|
private List<TmsExamEntity> loadSubmittedExams() {
|
LambdaQueryWrapper<TmsExamEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsExamEntity::getExamStatus, EXAM_SUBMITTED);
|
qw.isNotNull(TmsExamEntity::getPaperId);
|
qw.orderByDesc(TmsExamEntity::getSubmitTime);
|
return examMapper.selectList(qw);
|
}
|
|
private List<TmsExamEntity> loadSubmittedByPaper(String paperId) {
|
LambdaQueryWrapper<TmsExamEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsExamEntity::getExamStatus, EXAM_SUBMITTED);
|
qw.eq(TmsExamEntity::getPaperId, paperId);
|
qw.orderByDesc(TmsExamEntity::getSubmitTime);
|
return examMapper.selectList(qw);
|
}
|
|
private Map<String, List<TmsExamEntity>> groupByPaper(List<TmsExamEntity> exams) {
|
Map<String, List<TmsExamEntity>> map = new LinkedHashMap<>();
|
for (TmsExamEntity exam : exams) {
|
if (StringUtil.isEmpty(exam.getPaperId())) {
|
continue;
|
}
|
map.computeIfAbsent(exam.getPaperId(), k -> new ArrayList<>()).add(exam);
|
}
|
return map;
|
}
|
|
private Map<String, TmsPaperEntity> loadPapers(List<String> paperIds) {
|
if (paperIds == null || paperIds.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
Map<String, TmsPaperEntity> map = new HashMap<>();
|
for (TmsPaperEntity paper : paperMapper.selectBatchIds(paperIds)) {
|
map.put(paper.getId(), paper);
|
}
|
return map;
|
}
|
|
private Map<String, String> loadUserNames(List<String> userIds) {
|
if (userIds == null || userIds.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
List<String> ids = userIds.stream().filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
try {
|
List<UserEntity> users = userApi.getUserName(ids);
|
if (users == null || users.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
return users.stream()
|
.filter(Objects::nonNull)
|
.collect(Collectors.toMap(UserEntity::getId, UserEntity::getRealName, (a, b) -> a));
|
} catch (Exception ex) {
|
return Collections.emptyMap();
|
}
|
}
|
|
private List<TmsExamScoreOptionVO> toOptions(String json) {
|
if (StringUtil.isEmpty(json)) {
|
return Collections.emptyList();
|
}
|
try {
|
List<StoredOption> stored = OBJECT_MAPPER.readValue(json, new TypeReference<List<StoredOption>>() {
|
});
|
List<TmsExamScoreOptionVO> list = new ArrayList<>();
|
for (StoredOption opt : stored) {
|
TmsExamScoreOptionVO vo = new TmsExamScoreOptionVO();
|
vo.setOptionLabel(opt.getOptionLabel());
|
vo.setOptionContent(opt.getOptionContent());
|
list.add(vo);
|
}
|
return list;
|
} catch (Exception e) {
|
return Collections.emptyList();
|
}
|
}
|
|
private String normalize(String text) {
|
if (StringUtil.isEmpty(text)) {
|
return "";
|
}
|
return text.trim();
|
}
|
|
private String trim(String text) {
|
if (StringUtil.isEmpty(text) || "null".equalsIgnoreCase(text)) {
|
return null;
|
}
|
return text.trim();
|
}
|
|
private boolean contains(String text, String kw) {
|
return text != null && text.toLowerCase().contains(kw);
|
}
|
|
private String fmt(java.util.Date date) {
|
return date == null ? null : DateUtil.dateToString(date, FMT);
|
}
|
|
private BigDecimal nvl(BigDecimal value) {
|
return value == null ? BigDecimal.ZERO : value;
|
}
|
|
@Data
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
private static class StoredOption {
|
private String optionLabel;
|
private String optionContent;
|
private String isCorrect;
|
}
|
}
|