refactor: Phase 1 - Extract TimeSlotGenerator from God Class

- Create TimeSlot model class in schedule/model/
- Create TimeSlotGenerator component in schedule/generator/
- Modify MartialScheduleArrangeServiceImpl to use TimeSlotGenerator
- Remove internal TimeSlot class (now standalone)
- Add 11 unit tests for TimeSlotGenerator
- All 438 tests passing

Related to Issue #11
This commit is contained in:
2026-01-16 23:51:55 +08:00
parent 776e9e289d
commit 596e4a274d
13 changed files with 359 additions and 87 deletions
@@ -25,7 +25,9 @@ import org.springblade.modules.martial.mapper.*;
import org.springblade.modules.martial.pojo.entity.*;
import org.springblade.modules.martial.config.ScheduleConfig;
import org.springblade.modules.martial.service.IMartialScheduleArrangeService;
import org.springblade.modules.martial.service.schedule.generator.TimeSlotGenerator;
import org.springframework.stereotype.Service;
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
@@ -55,6 +57,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
private final MartialVenueMapper venueMapper;
private final MartialProjectMapper projectMapper;
private final ScheduleConfig scheduleConfig;
private final TimeSlotGenerator timeSlotGenerator;
@Override
public List<Long> getUnlockedCompetitions() {
@@ -352,52 +355,14 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
return athleteMapper.selectList(wrapper);
}
/**
* Generate time slots for competition
* @deprecated Use TimeSlotGenerator.generate() directly
*/
@Deprecated
private List<TimeSlot> generateTimeSlots(MartialCompetition competition) {
List<TimeSlot> timeSlots = new ArrayList<>();
LocalDateTime startTime = competition.getCompetitionStartTime();
LocalDateTime endTime = competition.getCompetitionEndTime();
if (startTime == null || endTime == null) {
log.warn("赛事时间信息不完整, 使用默认时间段");
return timeSlots;
}
LocalDate currentDate = startTime.toLocalDate();
LocalDate endDate = endTime.toLocalDate();
while (!currentDate.isAfter(endDate)) {
// 上午时段 (08:00-12:00, 共240分钟)
TimeSlot morning = new TimeSlot();
morning.setDate(currentDate);
morning.setPeriod("morning");
morning.setStartTime("08:00");
morning.setCapacity(240); // 4小时 = 240分钟
timeSlots.add(morning);
// 下午时段 (14:00-18:00, 共240分钟)
TimeSlot afternoon = new TimeSlot();
afternoon.setDate(currentDate);
afternoon.setPeriod("afternoon");
afternoon.setStartTime("14:00");
afternoon.setCapacity(240); // 4小时 = 240分钟
timeSlots.add(afternoon);
// 晚上时段 (19:00-22:00, 共180分钟)
TimeSlot evening = new TimeSlot();
evening.setDate(currentDate);
evening.setPeriod("evening");
evening.setStartTime("19:00");
evening.setCapacity(180); // 3小时 = 180分钟
timeSlots.add(evening);
currentDate = currentDate.plusDays(1);
}
log.info("生成时间段: {}天, 每天3个时段(上午/下午/晚上), 共{}个时段",
java.time.temporal.ChronoUnit.DAYS.between(startTime.toLocalDate(), endDate) + 1, timeSlots.size());
return timeSlots;
return timeSlotGenerator.generate(competition);
}
@@ -953,48 +918,6 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
}
}
// ==================== 内部数据类 ====================
private static class TimeSlot {
private LocalDate date;
private String period; // morning/afternoon
private String startTime; // 08:30/13:30
private Integer capacity; // 容量(分钟)
// Getters and Setters
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public String getPeriod() {
return period;
}
public void setPeriod(String period) {
this.period = period;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public Integer getCapacity() {
return capacity;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
}
private static class ScheduleGroupData {
private String groupName;
private Long projectId;
@@ -0,0 +1,127 @@
package org.springblade.modules.martial.service.schedule.generator;
import lombok.extern.slf4j.Slf4j;
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
/**
* Time slot generator for schedule arrangement
* Generates available time slots based on competition dates
*
* @author Refactored from MartialScheduleArrangeServiceImpl
*/
@Slf4j
@Component
public class TimeSlotGenerator {
// Default time slot configurations
private static final String MORNING_START = "08:00";
private static final String AFTERNOON_START = "14:00";
private static final String EVENING_START = "19:00";
private static final int MORNING_CAPACITY = 240; // 4 hours = 240 minutes
private static final int AFTERNOON_CAPACITY = 240; // 4 hours = 240 minutes
private static final int EVENING_CAPACITY = 180; // 3 hours = 180 minutes
/**
* Generate time slots for a competition
*
* @param competition the competition entity
* @return list of time slots
*/
public List<TimeSlot> generate(MartialCompetition competition) {
if (competition == null) {
log.warn("Competition is null, returning empty time slots");
return new ArrayList<>();
}
return generate(
competition.getCompetitionStartTime(),
competition.getCompetitionEndTime()
);
}
/**
* Generate time slots between start and end time
*
* @param startTime competition start time
* @param endTime competition end time
* @return list of time slots
*/
public List<TimeSlot> generate(LocalDateTime startTime, LocalDateTime endTime) {
List<TimeSlot> timeSlots = new ArrayList<>();
if (startTime == null || endTime == null) {
log.warn("Competition time info incomplete, returning empty time slots");
return timeSlots;
}
LocalDate currentDate = startTime.toLocalDate();
LocalDate endDate = endTime.toLocalDate();
while (!currentDate.isAfter(endDate)) {
timeSlots.addAll(generateDailySlots(currentDate));
currentDate = currentDate.plusDays(1);
}
long days = ChronoUnit.DAYS.between(startTime.toLocalDate(), endDate) + 1;
log.info("Generated time slots: {} days, 3 slots per day (morning/afternoon/evening), total {} slots",
days, timeSlots.size());
return timeSlots;
}
/**
* Generate time slots for a single day
*
* @param date the date
* @return list of time slots for that day
*/
public List<TimeSlot> generateDailySlots(LocalDate date) {
List<TimeSlot> slots = new ArrayList<>();
// Morning slot (08:00-12:00)
slots.add(TimeSlot.builder()
.date(date)
.period("morning")
.startTime(MORNING_START)
.capacity(MORNING_CAPACITY)
.build());
// Afternoon slot (14:00-18:00)
slots.add(TimeSlot.builder()
.date(date)
.period("afternoon")
.startTime(AFTERNOON_START)
.capacity(AFTERNOON_CAPACITY)
.build());
// Evening slot (19:00-22:00)
slots.add(TimeSlot.builder()
.date(date)
.period("evening")
.startTime(EVENING_START)
.capacity(EVENING_CAPACITY)
.build());
return slots;
}
/**
* Calculate total capacity in minutes
*
* @param timeSlots list of time slots
* @return total capacity in minutes
*/
public int calculateTotalCapacity(List<TimeSlot> timeSlots) {
return timeSlots.stream()
.mapToInt(TimeSlot::getCapacity)
.sum();
}
}
@@ -0,0 +1,69 @@
package org.springblade.modules.martial.service.schedule.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import java.time.LocalDate;
/**
* Time slot model for schedule arrangement
* Represents a time period (morning/afternoon/evening) on a specific date
*
* @author Refactored from MartialScheduleArrangeServiceImpl
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TimeSlot {
/**
* Date of the time slot
*/
private LocalDate date;
/**
* Period: morning, afternoon, evening
*/
private String period;
/**
* Start time in HH:mm format (e.g., "08:00", "14:00", "19:00")
*/
private String startTime;
/**
* Capacity in minutes
*/
private Integer capacity;
/**
* Get unique key for this time slot
*/
public String getSlotKey() {
return date.toString() + "_" + period;
}
/**
* Check if this is a morning slot
*/
public boolean isMorning() {
return "morning".equals(period);
}
/**
* Check if this is an afternoon slot
*/
public boolean isAfternoon() {
return "afternoon".equals(period);
}
/**
* Check if this is an evening slot
*/
public boolean isEvening() {
return "evening".equals(period);
}
}