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 getList(TmsExamScoreQuery query) { List exams = loadSubmittedExams(); Map> byPaper = groupByPaper(exams); Map papers = loadPapers(new ArrayList<>(byPaper.keySet())); List all = new ArrayList<>(); for (Map.Entry> 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 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 exams = loadSubmittedByPaper(summaryId); if (exams.isEmpty()) { throw new DataException("成绩汇总不存在"); } TmsPaperEntity paper = paperMapper.selectById(summaryId); TmsExamScoreSummaryVO summary = toSummary(summaryId, paper, exams); Map names = loadUserNames(exams.stream().map(TmsExamEntity::getUserId).collect(Collectors.toList())); List 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 names = loadUserNames(Collections.singletonList(exam.getUserId())); LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsExamItemEntity::getForeignId, examId); qw.orderByAsc(TmsExamItemEntity::getSortNo); List items = examItemMapper.selectList(qw); BigDecimal paperTotal = BigDecimal.ZERO; List 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 exportRows(List summaryIds) { if (summaryIds == null || summaryIds.isEmpty()) { throw new DataException("请选择要导出的考试"); } List rows = new ArrayList<>(); for (String id : summaryIds) { if (StringUtil.isEmpty(id)) { continue; } List 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 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 loadSubmittedExams() { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsExamEntity::getExamStatus, EXAM_SUBMITTED); qw.isNotNull(TmsExamEntity::getPaperId); qw.orderByDesc(TmsExamEntity::getSubmitTime); return examMapper.selectList(qw); } private List loadSubmittedByPaper(String paperId) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsExamEntity::getExamStatus, EXAM_SUBMITTED); qw.eq(TmsExamEntity::getPaperId, paperId); qw.orderByDesc(TmsExamEntity::getSubmitTime); return examMapper.selectList(qw); } private Map> groupByPaper(List exams) { Map> 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 loadPapers(List paperIds) { if (paperIds == null || paperIds.isEmpty()) { return Collections.emptyMap(); } Map map = new HashMap<>(); for (TmsPaperEntity paper : paperMapper.selectBatchIds(paperIds)) { map.put(paper.getId(), paper); } return map; } private Map loadUserNames(List userIds) { if (userIds == null || userIds.isEmpty()) { return Collections.emptyMap(); } List ids = userIds.stream().filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList()); if (ids.isEmpty()) { return Collections.emptyMap(); } try { List 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 toOptions(String json) { if (StringUtil.isEmpty(json)) { return Collections.emptyList(); } try { List stored = OBJECT_MAPPER.readValue(json, new TypeReference>() { }); List 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; } }