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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-87
@@ -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;
|
||||
|
||||
+127
@@ -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);
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package org.springblade.modules.martial.service.schedule.generator;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for TimeSlotGenerator
|
||||
*/
|
||||
@DisplayName("TimeSlotGenerator Tests")
|
||||
class TimeSlotGeneratorTest {
|
||||
|
||||
private TimeSlotGenerator generator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
generator = new TimeSlotGenerator();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate 3 slots per day")
|
||||
void shouldGenerate3SlotsPerDay() {
|
||||
LocalDate date = LocalDate.of(2026, 1, 15);
|
||||
List<TimeSlot> slots = generator.generateDailySlots(date);
|
||||
|
||||
assertEquals(3, slots.size());
|
||||
assertTrue(slots.get(0).isMorning());
|
||||
assertTrue(slots.get(1).isAfternoon());
|
||||
assertTrue(slots.get(2).isEvening());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate correct time slots for single day competition")
|
||||
void shouldGenerateSlotsForSingleDay() {
|
||||
LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0);
|
||||
LocalDateTime end = LocalDateTime.of(2026, 1, 15, 22, 0);
|
||||
|
||||
List<TimeSlot> slots = generator.generate(start, end);
|
||||
|
||||
assertEquals(3, slots.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate correct time slots for multi-day competition")
|
||||
void shouldGenerateSlotsForMultiDay() {
|
||||
LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0);
|
||||
LocalDateTime end = LocalDateTime.of(2026, 1, 17, 22, 0);
|
||||
|
||||
List<TimeSlot> slots = generator.generate(start, end);
|
||||
|
||||
assertEquals(9, slots.size()); // 3 days * 3 slots
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when times are null")
|
||||
void shouldReturnEmptyWhenTimesNull() {
|
||||
List<TimeSlot> slots = generator.generate(null, null);
|
||||
assertTrue(slots.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate from competition entity")
|
||||
void shouldGenerateFromCompetition() {
|
||||
MartialCompetition competition = new MartialCompetition();
|
||||
competition.setCompetitionStartTime(LocalDateTime.of(2026, 1, 15, 8, 0));
|
||||
competition.setCompetitionEndTime(LocalDateTime.of(2026, 1, 16, 22, 0));
|
||||
|
||||
List<TimeSlot> slots = generator.generate(competition);
|
||||
|
||||
assertEquals(6, slots.size()); // 2 days * 3 slots
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when competition is null")
|
||||
void shouldReturnEmptyWhenCompetitionNull() {
|
||||
List<TimeSlot> slots = generator.generate((MartialCompetition) null);
|
||||
assertTrue(slots.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should calculate correct total capacity")
|
||||
void shouldCalculateTotalCapacity() {
|
||||
LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0);
|
||||
LocalDateTime end = LocalDateTime.of(2026, 1, 15, 22, 0);
|
||||
|
||||
List<TimeSlot> slots = generator.generate(start, end);
|
||||
int totalCapacity = generator.calculateTotalCapacity(slots);
|
||||
|
||||
// 240 (morning) + 240 (afternoon) + 180 (evening) = 660
|
||||
assertEquals(660, totalCapacity);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate correct slot keys")
|
||||
void shouldGenerateCorrectSlotKeys() {
|
||||
LocalDate date = LocalDate.of(2026, 1, 15);
|
||||
List<TimeSlot> slots = generator.generateDailySlots(date);
|
||||
|
||||
assertEquals("2026-01-15_morning", slots.get(0).getSlotKey());
|
||||
assertEquals("2026-01-15_afternoon", slots.get(1).getSlotKey());
|
||||
assertEquals("2026-01-15_evening", slots.get(2).getSlotKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Morning slot should have correct configuration")
|
||||
void morningShouldHaveCorrectConfig() {
|
||||
LocalDate date = LocalDate.of(2026, 1, 15);
|
||||
List<TimeSlot> slots = generator.generateDailySlots(date);
|
||||
TimeSlot morning = slots.get(0);
|
||||
|
||||
assertEquals("08:00", morning.getStartTime());
|
||||
assertEquals(240, morning.getCapacity());
|
||||
assertTrue(morning.isMorning());
|
||||
assertFalse(morning.isAfternoon());
|
||||
assertFalse(morning.isEvening());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Afternoon slot should have correct configuration")
|
||||
void afternoonShouldHaveCorrectConfig() {
|
||||
LocalDate date = LocalDate.of(2026, 1, 15);
|
||||
List<TimeSlot> slots = generator.generateDailySlots(date);
|
||||
TimeSlot afternoon = slots.get(1);
|
||||
|
||||
assertEquals("14:00", afternoon.getStartTime());
|
||||
assertEquals(240, afternoon.getCapacity());
|
||||
assertFalse(afternoon.isMorning());
|
||||
assertTrue(afternoon.isAfternoon());
|
||||
assertFalse(afternoon.isEvening());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Evening slot should have correct configuration")
|
||||
void eveningShouldHaveCorrectConfig() {
|
||||
LocalDate date = LocalDate.of(2026, 1, 15);
|
||||
List<TimeSlot> slots = generator.generateDailySlots(date);
|
||||
TimeSlot evening = slots.get(2);
|
||||
|
||||
assertEquals("19:00", evening.getStartTime());
|
||||
assertEquals(180, evening.getCapacity());
|
||||
assertFalse(evening.isMorning());
|
||||
assertFalse(evening.isAfternoon());
|
||||
assertTrue(evening.isEvening());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user