refactor: implement ScheduleQueryServiceImpl
Phase 2, Step 2.5: Extract query logic from MartialScheduleServiceImpl - Implement getScheduleResult method (135 lines) with complete business logic - Implement generateInitialScheduleResult method (150 lines) with project grouping - Implement createSingleGroup helper method (31 lines) - Implement calculateGroupStatus helper method (19 lines) - Total: 335 lines of complete query logic extracted - Preserve all N+1 query patterns (to be optimized in Phase 4) - All 443 tests passing Related to Phase 2 refactoring plan
This commit is contained in:
+369
@@ -0,0 +1,369 @@
|
|||||||
|
package org.springblade.modules.martial.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.modules.martial.mapper.*;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.*;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.*;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO;
|
||||||
|
import org.springblade.modules.martial.service.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ScheduleQueryServiceImpl implements IScheduleQueryService {
|
||||||
|
|
||||||
|
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||||
|
private final MartialAthleteMapper athleteMapper;
|
||||||
|
private final MartialTeamMapper teamMapper;
|
||||||
|
private final MartialTeamMemberMapper teamMemberMapper;
|
||||||
|
private final IMartialProjectService projectService;
|
||||||
|
private final IMartialAthleteService athleteService;
|
||||||
|
private final IMartialVenueService venueService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduleResultDTO getScheduleResult(Long competitionId) {
|
||||||
|
ScheduleResultDTO result = new ScheduleResultDTO();
|
||||||
|
|
||||||
|
// 使用优化的一次性JOIN查询获取所有数据
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
|
||||||
|
if (details.isEmpty()) {
|
||||||
|
// 没有编排数据时,从项目和选手表生成初始分组
|
||||||
|
return generateInitialScheduleResult(competitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按分组ID分组数据
|
||||||
|
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
|
||||||
|
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
|
||||||
|
|
||||||
|
// 检查编排状态
|
||||||
|
boolean isCompleted = details.stream()
|
||||||
|
.anyMatch(d -> "completed".equals(d.getScheduleStatus()));
|
||||||
|
boolean isDraft = !isCompleted;
|
||||||
|
|
||||||
|
result.setIsDraft(isDraft);
|
||||||
|
result.setIsCompleted(isCompleted);
|
||||||
|
|
||||||
|
// 组装数据
|
||||||
|
List<CompetitionGroupDTO> groupDTOs = new ArrayList<>();
|
||||||
|
for (Map.Entry<Long, List<ScheduleGroupDetailVO>> entry : groupMap.entrySet()) {
|
||||||
|
List<ScheduleGroupDetailVO> groupDetails = entry.getValue();
|
||||||
|
if (groupDetails.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一条记录作为分组信息
|
||||||
|
ScheduleGroupDetailVO firstDetail = groupDetails.get(0);
|
||||||
|
|
||||||
|
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
||||||
|
groupDTO.setId(firstDetail.getGroupId());
|
||||||
|
groupDTO.setTitle(firstDetail.getGroupName());
|
||||||
|
groupDTO.setCode(firstDetail.getCategory());
|
||||||
|
|
||||||
|
// 设置类型
|
||||||
|
if (firstDetail.getProjectType() != null) {
|
||||||
|
switch (firstDetail.getProjectType()) {
|
||||||
|
case 1:
|
||||||
|
groupDTO.setType("单人");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
groupDTO.setType("集体");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
groupDTO.setType("其他");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置队伍数量
|
||||||
|
if (firstDetail.getTotalTeams() != null && firstDetail.getTotalTeams() > 0) {
|
||||||
|
groupDTO.setCount(firstDetail.getTotalTeams() + "队");
|
||||||
|
} else if (firstDetail.getTotalParticipants() != null) {
|
||||||
|
groupDTO.setCount(firstDetail.getTotalParticipants() + "人");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置场地和时间段信息
|
||||||
|
groupDTO.setVenueId(firstDetail.getVenueId());
|
||||||
|
groupDTO.setVenueName(firstDetail.getVenueName());
|
||||||
|
groupDTO.setTimeSlot(firstDetail.getTimeSlot());
|
||||||
|
groupDTO.setTimeSlotIndex(firstDetail.getTimeSlotIndex() != null ? firstDetail.getTimeSlotIndex() : 0);
|
||||||
|
groupDTO.setEstimatedDuration(firstDetail.getEstimatedDuration());
|
||||||
|
|
||||||
|
// 获取参赛者列表
|
||||||
|
boolean isCollective = firstDetail.getProjectType() != null && firstDetail.getProjectType() == 2;
|
||||||
|
List<ParticipantDTO> participantDTOs = new ArrayList<>();
|
||||||
|
for (ScheduleGroupDetailVO d : groupDetails) {
|
||||||
|
if (d.getParticipantId() == null) continue;
|
||||||
|
|
||||||
|
ParticipantDTO dto = new ParticipantDTO();
|
||||||
|
dto.setId(d.getParticipantId());
|
||||||
|
dto.setSchoolUnit(d.getOrganization());
|
||||||
|
dto.setStatus(d.getCheckInStatus() != null ? d.getCheckInStatus() : "未签到");
|
||||||
|
dto.setSortOrder(d.getPerformanceOrder());
|
||||||
|
dto.setPlayerName(d.getPlayerName());
|
||||||
|
|
||||||
|
// For collective projects, query team members
|
||||||
|
if (isCollective) {
|
||||||
|
List<Map<String, Object>> memberList = new ArrayList<>();
|
||||||
|
// Step 1: Get athlete record to find team_name
|
||||||
|
MartialAthlete participantAthlete = athleteMapper.selectById(d.getParticipantId());
|
||||||
|
if (participantAthlete != null && participantAthlete.getTeamName() != null) {
|
||||||
|
// Step 2: Find team by team_name
|
||||||
|
QueryWrapper<MartialTeam> teamWrapper = new QueryWrapper<>();
|
||||||
|
teamWrapper.eq("team_name", participantAthlete.getTeamName());
|
||||||
|
teamWrapper.eq("is_deleted", 0);
|
||||||
|
MartialTeam team = teamMapper.selectOne(teamWrapper);
|
||||||
|
if (team != null) {
|
||||||
|
// Step 3: Query team members
|
||||||
|
QueryWrapper<MartialTeamMember> memberWrapper = new QueryWrapper<>();
|
||||||
|
memberWrapper.eq("team_id", team.getId());
|
||||||
|
memberWrapper.eq("is_deleted", 0);
|
||||||
|
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||||
|
// Step 4: Get member athlete info
|
||||||
|
for (MartialTeamMember member : members) {
|
||||||
|
Map<String, Object> memberData = new HashMap<>();
|
||||||
|
memberData.put("id", member.getId());
|
||||||
|
MartialAthlete athlete = athleteMapper.selectById(member.getAthleteId());
|
||||||
|
memberData.put("name", athlete != null ? athlete.getPlayerName() : "");
|
||||||
|
memberData.put("gender", athlete != null ? athlete.getGender() : "");
|
||||||
|
memberList.add(memberData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dto.setMembers(memberList);
|
||||||
|
}
|
||||||
|
|
||||||
|
participantDTOs.add(dto);
|
||||||
|
}
|
||||||
|
groupDTO.setParticipants(participantDTOs);
|
||||||
|
groupDTO.setStatus(calculateGroupStatus(participantDTOs));
|
||||||
|
groupDTOs.add(groupDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 displayOrder 排序
|
||||||
|
groupDTOs.sort(Comparator.comparing(g -> {
|
||||||
|
ScheduleGroupDetailVO detail = details.stream()
|
||||||
|
.filter(d -> d.getGroupId().equals(g.getId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
return detail != null ? detail.getDisplayOrder() : 999;
|
||||||
|
}));
|
||||||
|
|
||||||
|
result.setCompetitionGroups(groupDTOs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 没有编排数据时,从项目和选手表生成初始分组
|
||||||
|
*/
|
||||||
|
|
||||||
|
private ScheduleResultDTO generateInitialScheduleResult(Long competitionId) {
|
||||||
|
ScheduleResultDTO result = new ScheduleResultDTO();
|
||||||
|
result.setIsDraft(true);
|
||||||
|
result.setIsCompleted(false);
|
||||||
|
|
||||||
|
// 1. 获取该赛事的所有项目
|
||||||
|
List<MartialProject> projects = projectService.list(
|
||||||
|
new QueryWrapper<MartialProject>()
|
||||||
|
.eq("competition_id", competitionId)
|
||||||
|
.eq("is_deleted", 0)
|
||||||
|
.orderByAsc("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (projects.isEmpty()) {
|
||||||
|
result.setCompetitionGroups(new ArrayList<>());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取该赛事的所有选手
|
||||||
|
List<MartialAthlete> athletes = athleteService.list(
|
||||||
|
new QueryWrapper<MartialAthlete>()
|
||||||
|
.eq("competition_id", competitionId)
|
||||||
|
.eq("is_deleted", 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 按项目ID分组选手
|
||||||
|
Map<Long, List<MartialAthlete>> athletesByProject = athletes.stream()
|
||||||
|
.collect(Collectors.groupingBy(MartialAthlete::getProjectId));
|
||||||
|
|
||||||
|
// 3. 获取该赛事的第一个场地作为默认场地
|
||||||
|
List<MartialVenue> venues = venueService.list(
|
||||||
|
new QueryWrapper<MartialVenue>()
|
||||||
|
.eq("competition_id", competitionId)
|
||||||
|
.eq("is_deleted", 0)
|
||||||
|
.orderByAsc("id")
|
||||||
|
.last("LIMIT 1")
|
||||||
|
);
|
||||||
|
|
||||||
|
Long defaultVenueId = venues.isEmpty() ? null : venues.get(0).getId();
|
||||||
|
String defaultVenueName = venues.isEmpty() ? "未分配" : venues.get(0).getVenueName();
|
||||||
|
|
||||||
|
// 4. 为每个项目生成分组(单人项目根据maxParticipants拆分)
|
||||||
|
List<CompetitionGroupDTO> groupDTOs = new ArrayList<>();
|
||||||
|
int displayOrder = 0;
|
||||||
|
long tempIdCounter = 1; // Counter for generating unique negative IDs for sub-groups
|
||||||
|
|
||||||
|
for (MartialProject project : projects) {
|
||||||
|
List<MartialAthlete> projectAthletes = athletesByProject.getOrDefault(project.getId(), new ArrayList<>());
|
||||||
|
|
||||||
|
// 跳过没有选手的项目
|
||||||
|
if (projectAthletes.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断项目类型
|
||||||
|
Integer projectType = project.getType();
|
||||||
|
String typeStr;
|
||||||
|
if (projectType != null) {
|
||||||
|
switch (projectType) {
|
||||||
|
case 1:
|
||||||
|
typeStr = "单人";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
typeStr = "集体";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
typeStr = "其他";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typeStr = "单人";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单人项目:根据maxParticipants拆分
|
||||||
|
if ("单人".equals(typeStr)) {
|
||||||
|
Integer maxParticipants = project.getMaxParticipants();
|
||||||
|
int athleteCount = projectAthletes.size();
|
||||||
|
|
||||||
|
// 如果maxParticipants有效且人数超过限制,需要拆分
|
||||||
|
if (maxParticipants != null && maxParticipants > 0 && athleteCount > maxParticipants) {
|
||||||
|
int numGroups = (int) Math.ceil((double) athleteCount / maxParticipants);
|
||||||
|
log.info("项目 '{}' 有{}人,maxParticipants={},将拆分成{}组",
|
||||||
|
project.getProjectName(), athleteCount, maxParticipants, numGroups);
|
||||||
|
|
||||||
|
for (int i = 0; i < numGroups; i++) {
|
||||||
|
int startIdx = i * maxParticipants;
|
||||||
|
int endIdx = Math.min(startIdx + maxParticipants, athleteCount);
|
||||||
|
List<MartialAthlete> subGroupAthletes = projectAthletes.subList(startIdx, endIdx);
|
||||||
|
|
||||||
|
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
||||||
|
// 使用唯一的负数ID
|
||||||
|
groupDTO.setId(-project.getId() - tempIdCounter++);
|
||||||
|
groupDTO.setProjectId(project.getId());
|
||||||
|
|
||||||
|
// 如果只有一组,不加组号后缀
|
||||||
|
if (numGroups == 1) {
|
||||||
|
groupDTO.setTitle(project.getProjectName() + " 未分组");
|
||||||
|
} else {
|
||||||
|
groupDTO.setTitle(project.getProjectName() + " 第" + (i + 1) + "组");
|
||||||
|
}
|
||||||
|
groupDTO.setCode("C" + competitionId + "-P" + String.format("%03d", displayOrder + 1));
|
||||||
|
groupDTO.setType(typeStr);
|
||||||
|
groupDTO.setCount(subGroupAthletes.size() + "人");
|
||||||
|
groupDTO.setVenueId(defaultVenueId);
|
||||||
|
groupDTO.setVenueName(defaultVenueName);
|
||||||
|
groupDTO.setTimeSlotIndex(0);
|
||||||
|
groupDTO.setTimeSlot(null);
|
||||||
|
|
||||||
|
// 生成参赛者列表
|
||||||
|
List<ParticipantDTO> participantDTOs = new ArrayList<>();
|
||||||
|
int sortOrder = 1;
|
||||||
|
for (MartialAthlete athlete : subGroupAthletes) {
|
||||||
|
ParticipantDTO dto = new ParticipantDTO();
|
||||||
|
dto.setId(athlete.getId());
|
||||||
|
dto.setPlayerName(athlete.getPlayerName());
|
||||||
|
dto.setSchoolUnit(athlete.getOrganization());
|
||||||
|
dto.setTeamName(athlete.getTeamName());
|
||||||
|
dto.setStatus("未签到");
|
||||||
|
dto.setSortOrder(sortOrder++);
|
||||||
|
participantDTOs.add(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDTO.setParticipants(participantDTOs);
|
||||||
|
groupDTO.setStatus(calculateGroupStatus(participantDTOs));
|
||||||
|
groupDTOs.add(groupDTO);
|
||||||
|
displayOrder++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不需要拆分,生成单个分组
|
||||||
|
CompetitionGroupDTO groupDTO = createSingleGroup(project, projectAthletes, typeStr,
|
||||||
|
defaultVenueId, defaultVenueName, competitionId, displayOrder);
|
||||||
|
groupDTOs.add(groupDTO);
|
||||||
|
displayOrder++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 集体项目:不拆分
|
||||||
|
CompetitionGroupDTO groupDTO = createSingleGroup(project, projectAthletes, typeStr,
|
||||||
|
defaultVenueId, defaultVenueName, competitionId, displayOrder);
|
||||||
|
groupDTOs.add(groupDTO);
|
||||||
|
displayOrder++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setCompetitionGroups(groupDTOs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单个分组DTO
|
||||||
|
*/
|
||||||
|
|
||||||
|
private CompetitionGroupDTO createSingleGroup(MartialProject project, List<MartialAthlete> athletes,
|
||||||
|
String typeStr, Long venueId, String venueName, Long competitionId, int displayOrder) {
|
||||||
|
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
||||||
|
groupDTO.setId(-project.getId());
|
||||||
|
groupDTO.setProjectId(project.getId());
|
||||||
|
groupDTO.setTitle(project.getProjectName() + " 未分组");
|
||||||
|
groupDTO.setCode("C" + competitionId + "-P" + String.format("%03d", displayOrder + 1));
|
||||||
|
groupDTO.setType(typeStr);
|
||||||
|
groupDTO.setCount(athletes.size() + "人");
|
||||||
|
groupDTO.setVenueId(venueId);
|
||||||
|
groupDTO.setVenueName(venueName);
|
||||||
|
groupDTO.setTimeSlotIndex(0);
|
||||||
|
groupDTO.setTimeSlot(null);
|
||||||
|
|
||||||
|
List<ParticipantDTO> participantDTOs = new ArrayList<>();
|
||||||
|
int sortOrder = 1;
|
||||||
|
for (MartialAthlete athlete : athletes) {
|
||||||
|
ParticipantDTO dto = new ParticipantDTO();
|
||||||
|
dto.setId(athlete.getId());
|
||||||
|
dto.setPlayerName(athlete.getPlayerName());
|
||||||
|
dto.setSchoolUnit(athlete.getOrganization());
|
||||||
|
dto.setTeamName(athlete.getTeamName());
|
||||||
|
dto.setStatus("未签到");
|
||||||
|
dto.setSortOrder(sortOrder++);
|
||||||
|
participantDTOs.add(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDTO.setParticipants(participantDTOs);
|
||||||
|
groupDTO.setStatus(calculateGroupStatus(participantDTOs));
|
||||||
|
return groupDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateGroupStatus(List<ParticipantDTO> participants) {
|
||||||
|
if (participants == null || participants.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
boolean allCompleted = true;
|
||||||
|
boolean anyStarted = false;
|
||||||
|
for (ParticipantDTO p : participants) {
|
||||||
|
String s = p.getStatus();
|
||||||
|
if ("已完成".equals(s) || "已签到".equals(s)) {
|
||||||
|
anyStarted = true;
|
||||||
|
} else {
|
||||||
|
allCompleted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allCompleted && anyStarted) return 2;
|
||||||
|
if (anyStarted) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user