package com.rongyichuang.player.service; import com.rongyichuang.activity.entity.Activity; import com.rongyichuang.activity.entity.ActivityPlayerRating; import com.rongyichuang.activity.repository.ActivityPlayerRatingRepository; import com.rongyichuang.activity.repository.ActivityRepository; import com.rongyichuang.judge.entity.Judge; import com.rongyichuang.judge.repository.JudgeRepository; import com.rongyichuang.player.dto.response.ProjectStageTimelineItemResponse; import com.rongyichuang.player.dto.response.ProjectStageTimelineResponse; import com.rongyichuang.player.dto.response.StageJudgeRatingDetailResponse; import com.rongyichuang.player.dto.response.StageJudgeRatingItemResponse; import com.rongyichuang.player.entity.ActivityPlayer; import com.rongyichuang.player.repository.ActivityPlayerRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * 项目阶段评分相关服务 */ @Service public class ProjectStageRatingService { private static final Logger log = LoggerFactory.getLogger(ProjectStageRatingService.class); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private final ActivityPlayerRepository activityPlayerRepository; private final ActivityRepository activityRepository; private final ActivityPlayerRatingRepository activityPlayerRatingRepository; private final JudgeRepository judgeRepository; public ProjectStageRatingService(ActivityPlayerRepository activityPlayerRepository, ActivityRepository activityRepository, ActivityPlayerRatingRepository activityPlayerRatingRepository, JudgeRepository judgeRepository) { this.activityPlayerRepository = activityPlayerRepository; this.activityRepository = activityRepository; this.activityPlayerRatingRepository = activityPlayerRatingRepository; this.judgeRepository = judgeRepository; } /** * 获取项目在各阶段的时间轴与评分概况 */ public ProjectStageTimelineResponse getProjectStageTimeline(Long activityPlayerId) { ActivityPlayer current = activityPlayerRepository.findById(activityPlayerId) .orElseThrow(() -> new IllegalArgumentException("未找到参赛记录,ID: " + activityPlayerId)); Long activityId = current.getActivityId(); Long playerId = current.getPlayerId(); ProjectStageTimelineResponse response = new ProjectStageTimelineResponse(); response.setActivityId(activityId); Activity activity = activityRepository.findActivityById(activityId); if (activity != null) { response.setActivityName(activity.getName()); } List stages = new ArrayList<>(activityRepository.findByPidAndStateOrderBySortOrderAsc(activityId, 1)); Map stagePlayerMap = activityPlayerRepository .findByActivityIdAndPlayerIdOrderByCreateTimeDesc(activityId, playerId) .stream() .filter(ap -> ap.getStageId() != null) .collect(Collectors.toMap(ActivityPlayer::getStageId, ap -> ap, (existing, replacement) -> existing)); // 确保当前阶段包含在时间轴中 if (current.getStageId() != null && !stagePlayerMap.containsKey(current.getStageId())) { stagePlayerMap.put(current.getStageId(), current); } // 如果阶段列表中缺少已参赛阶段,补全它们 Set stageIdsInList = stages.stream().map(Activity::getId).collect(Collectors.toSet()); for (Long stageId : stagePlayerMap.keySet()) { if (!stageIdsInList.contains(stageId)) { Activity stage = activityRepository.findActivityById(stageId); if (stage != null) { stages.add(stage); stageIdsInList.add(stageId); } } } // 按 sortOrder 及创建时间排序,确保展示顺序 stages.sort(Comparator .comparing((Activity stage) -> Optional.ofNullable(stage.getSortOrder()).orElse(Integer.MAX_VALUE)) .thenComparing(Activity::getId)); List items = new ArrayList<>(); for (Activity stage : stages) { ProjectStageTimelineItemResponse item = new ProjectStageTimelineItemResponse(); item.setStageId(stage.getId()); item.setStageName(stage.getName()); item.setMatchTime(formatDateTime(stage.getMatchTime())); item.setSortOrder(stage.getSortOrder()); ActivityPlayer stagePlayer = stagePlayerMap.get(stage.getId()); boolean participated = stagePlayer != null; item.setParticipated(participated); if (participated) { item.setActivityPlayerId(stagePlayer.getId()); applyRatingMetrics(item, stagePlayer.getId()); } else { item.setHasRating(false); item.setRatingCount(0); } items.add(item); } response.setStages(items); return response; } /** * 获取指定阶段的评委评分详情 */ public StageJudgeRatingDetailResponse getStageJudgeRatings(Long activityPlayerId) { ActivityPlayer stagePlayer = activityPlayerRepository.findById(activityPlayerId) .orElseThrow(() -> new IllegalArgumentException("未找到参赛记录,ID: " + activityPlayerId)); StageJudgeRatingDetailResponse response = new StageJudgeRatingDetailResponse(); response.setActivityPlayerId(activityPlayerId); Activity stage = null; if (stagePlayer.getStageId() != null) { stage = activityRepository.findActivityById(stagePlayer.getStageId()); } if (stage != null) { response.setStageId(stage.getId()); response.setStageName(stage.getName()); response.setMatchTime(formatDateTime(stage.getMatchTime())); } else { response.setStageId(stagePlayer.getStageId()); response.setStageName("阶段信息缺失"); } List ratings = activityPlayerRatingRepository.findByActivityPlayerId(activityPlayerId); Map latestRatingByJudge = new HashMap<>(); for (ActivityPlayerRating rating : ratings) { Long judgeId = rating.getJudgeId(); if (judgeId == null) { continue; } ActivityPlayerRating existing = latestRatingByJudge.get(judgeId); if (existing == null) { latestRatingByJudge.put(judgeId, rating); } else { LocalDateTime existingTime = existing.getUpdateTime(); LocalDateTime currentTime = rating.getUpdateTime(); if (existingTime == null || (currentTime != null && currentTime.isAfter(existingTime))) { latestRatingByJudge.put(judgeId, rating); } } } Collection effectiveRatings = latestRatingByJudge.values(); List validScores = effectiveRatings.stream() .map(ActivityPlayerRating::getTotalScore) .filter(Objects::nonNull) .collect(Collectors.toList()); int ratingCount = validScores.size(); response.setRatingCount(ratingCount); if (ratingCount > 0) { BigDecimal sum = validScores.stream().reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal avg = sum.divide(BigDecimal.valueOf(ratingCount), 2, RoundingMode.HALF_UP); response.setAverageScore(avg.doubleValue()); } if (!effectiveRatings.isEmpty()) { Set judgeIds = effectiveRatings.stream() .map(ActivityPlayerRating::getJudgeId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map judgeNameMap = new HashMap<>(); if (!judgeIds.isEmpty()) { List judges = judgeRepository.findAllById(judgeIds); for (Judge judge : judges) { judgeNameMap.put(judge.getId(), judge.getName()); } } List judgeItems = effectiveRatings.stream() .sorted(Comparator.comparing(ActivityPlayerRating::getUpdateTime, Comparator.nullsLast(LocalDateTime::compareTo)).reversed()) .map(rating -> { StageJudgeRatingItemResponse item = new StageJudgeRatingItemResponse(); Long judgeId = rating.getJudgeId(); item.setJudgeId(judgeId); item.setJudgeName(judgeNameMap.getOrDefault(judgeId, "未知评委")); if (rating.getTotalScore() != null) { item.setTotalScore(rating.getTotalScore().doubleValue()); } item.setFeedback(rating.getFeedback()); item.setRatingTime(formatDateTime(rating.getUpdateTime())); return item; }) .collect(Collectors.toList()); response.setJudgeRatings(judgeItems); } return response; } private void applyRatingMetrics(ProjectStageTimelineItemResponse item, Long stageActivityPlayerId) { List ratings = activityPlayerRatingRepository .findCompletedRatingsByActivityPlayerId(stageActivityPlayerId); List validScores = ratings.stream() .map(ActivityPlayerRating::getTotalScore) .filter(Objects::nonNull) .collect(Collectors.toList()); int ratingCount = validScores.size(); item.setRatingCount(ratingCount); item.setHasRating(ratingCount > 0); if (ratingCount > 0) { BigDecimal sum = validScores.stream().reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal avg = sum.divide(BigDecimal.valueOf(ratingCount), 2, RoundingMode.HALF_UP); item.setAverageScore(avg.doubleValue()); } ratings.stream() .map(ActivityPlayerRating::getUpdateTime) .filter(Objects::nonNull) .max(LocalDateTime::compareTo) .ifPresent(time -> item.setLatestRatingTime(formatDateTime(time))); } private String formatDateTime(LocalDateTime time) { if (time == null) { return null; } return DATE_TIME_FORMATTER.format(time); } }