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:
2026-01-18 00:34:11 +08:00
parent a678ee1a6f
commit 76812dde03
@@ -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;
}
}