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