diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 6f64feb..a660807 100644 Binary files a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta and b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index 5e32f85..5eb6779 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta index e800fd9..d22f73a 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage.json/xl.meta b/minio_data/.minio.sys/buckets/.usage.json/xl.meta index 2258b4b..2884fb9 100644 Binary files a/minio_data/.minio.sys/buckets/.usage.json/xl.meta and b/minio_data/.minio.sys/buckets/.usage.json/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta index c7cbbac..d5341bd 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta index 26f419f..d1e3b67 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta index 25beed0..fd0f469 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta and b/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta index 7597c00..d76c49f 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta index 48ca106..9324f62 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta differ diff --git a/src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleArrangeServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleArrangeServiceImpl.java index db2c79b..1344cc9 100644 --- a/src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleArrangeServiceImpl.java +++ b/src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleArrangeServiceImpl.java @@ -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 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 generateTimeSlots(MartialCompetition competition) { - List 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; diff --git a/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java b/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java new file mode 100644 index 0000000..449c530 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java @@ -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 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 generate(LocalDateTime startTime, LocalDateTime endTime) { + List 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 generateDailySlots(LocalDate date) { + List 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 timeSlots) { + return timeSlots.stream() + .mapToInt(TimeSlot::getCapacity) + .sum(); + } +} diff --git a/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java b/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java new file mode 100644 index 0000000..8302bf6 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java @@ -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); + } +} diff --git a/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java b/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java new file mode 100644 index 0000000..cd06463 --- /dev/null +++ b/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java @@ -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 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 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 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 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 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 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 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 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 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 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 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()); + } +}