From aee06393d7c4b3010c06be4b0824d5f6a9cc3cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=85=E6=88=BF?= Date: Fri, 9 Jan 2026 14:13:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(schedule):=20=E4=BC=98=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E6=8E=92=E6=97=B6=E9=97=B4=E5=88=86=E9=85=8D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加晚上时段(19:00-22:00, 180分钟) - 调整时段容量: 上午240分钟, 下午240分钟, 晚上180分钟 - 按实际时长累加分配,同一时段可安排多个项目 - 同一项目的所有分组优先安排在同一场地同一时段 --- .../buckets/.bloomcycle.bin/xl.meta | Bin 743 -> 743 bytes .../buckets/.usage-cache.bin.bkp/xl.meta | Bin 632 -> 637 bytes .../buckets/.usage-cache.bin/xl.meta | Bin 632 -> 637 bytes .../.minio.sys/buckets/.usage.json/xl.meta | Bin 2360 -> 2360 bytes .../.usage-cache.bin.bkp/xl.meta | Bin 636 -> 627 bytes .../000000-assets/.usage-cache.bin/xl.meta | Bin 636 -> 627 bytes .../assets/.usage-cache.bin.bkp/xl.meta | Bin 575 -> 575 bytes .../buckets/assets/.usage-cache.bin/xl.meta | Bin 575 -> 575 bytes .../MartialScheduleArrangeServiceImpl.java | 218 ++++++++---------- 9 files changed, 100 insertions(+), 118 deletions(-) diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 013bd5846f8107868d0ca2e201b69874953797ce..c8adccc9db643494749d248ceb6b11414d254848 100644 GIT binary patch delta 445 zcmaFP`kZxw2G8%%IX}X~j<8HTtYkUS#8{wP?}pl!7|)Iw?H8nN8a8a5SYFQtQ8V$6 zje>=NVTyTbqN$OonQ5|_NvctzrBSkhiFuNFs!>{EqT%ESM!ov*I;jVYjZ5-Mb8?O{ z2CLo@xxQTW#Nnzf!7r<7j~DIUv~sJ;{1nBXFRYp;EnYQ|-J1anT9zl578RxDl`t&H zORXq5>&?ErxFoTtBsJwY4=YGTX2P60!{grs*Ckgb=cGF4=N9Co0%h+o2*4H0_+^y2 zxSj#dpJ`^)>o)_zEi#(>*aE>_&u}C$7r}jHG%Gn6&Yh)ZbWr&jf}3RYJ75`tJJ0A? z@NWe7meJRb&Jb=g(517*jM>@uz@=vg8C`Y0fZ+BS8~NEIxTlPcm$SmTbJ&de_ab@P Q$(Z@m0)%Xx@rKA20BBE{&j0`b delta 445 zcmaFP`kZxw2G7rPb#M7s9${hpb0lS=iLpRLV^s027{>$Cd+w+ROxcn!vAmuQqGsYB z8wCS{l+;8619P(^OM}!z6ARyyj&zb5fo2a|?1(fwFfP1mFrLWf`vj zSTBX(FEczp<0^vt$ncqf6`VU+&hYOyF$6ct@bamA1b3R@+Yn0x_mbf+-Fi583ZIcg zW*LI(WyB)$5yDLdx^zmLkx2Irg!CaJmAStW+_fY*xDetWky_eQxM!u QMn>Dc5Zw2S*JBm|0E#S|c>n+a 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 b38fe2c28626ec732e25f21f524a52f4ed68f795..08c7f8fb9892c131216fa9641cacaaa7a528d23b 100644 GIT binary patch delta 280 zcmeyt@|R_T2G8%%Ij21Ajy|J zi%J}WGpkaICthSYwFJZh(o20qGILWeOLRaCnfS+4eMxFbV){)5GYj)%LsR24<3vM) zMANhsi^OD0%Ota80}CS{G)=ZTPuHjBA(JR=b@w3!`{BG`8yhbzu-?xXQ}F!ZrCIqoj;C+bW;L z;*!vUl*E$M<2-G+ aO+^e}8hC4%{Yv&FC5UB delta 274 zcmey%@`Gi92G7rPbqBxj9AWA2oUm}BiLrqFhe(T%wO)b?|JN1@KVQFPVtJhU(NzJ7 zMJ0~GnN_L96E89xT>@eO>7~9QnYpQ#B|0F6O#EXyIf79wYe{NJV){*mWFt#MV+&JL zBXctgBMVFOw8WG&gVYoQLo@TVBy)qLg=Oc`lTI_7yY%AzZpOwXd8IiyN1p0!O5T1& zU%i}hmYr?viACFt7pENRZtRkht5|EY?W@^#4vnq)e_a?v8P>8gT;ZI2kWo^`>(wfs z#Nv|Bf|SIP)Z;v?3_y^XF!eoSOOnClZ;VkI%NQ2r7c(#{)bCi7oH2=m!9uX{h>Av& W@dEao2WAhpUKViV3p%CzbO8X2WOF0{ 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 8efdcc13eb45d5741ecc24568bf543c7eb7415e5..3f518d51868dfe56cc9738ab05137d093ed75d35 100644 GIT binary patch delta 265 zcmeyt@|R_T2G8%%Ia3Tgj<8r5=l-8)Vk}TrwEO+svu+NHq&RH%FMCu!u{=)w)T)5Q zq7uj8%&OGli5D48EdjBB^itoD%-qz=5*-jjCjPNeFtadEHZ(O(Gfp%#NHk4Lu}Dm| zv`jKfHn1=PLesR#5sZ4)dOeF>7#o-5mFDCedFAbD7N=j9GoAaM&7u0!rE({I8-A`4 z+tloX1iLt=tc*cXT>m7u)rfBNHeAUanjEzh3N^^3Kvah-G zHSmk0;?;D4&n}`2k9qgbU1zjaOXe;2^NusWl75x9PF}+(Eof$CXl!L@p=V)cU}9`w MWMaJe24ey{0IEns4FCWD delta 163 zcmdlXv_oiu2G7rPb?>6jA7RN}y7=Wp6Jr7YCp~q|n>-jMT-H%DEPg6IvAmuQqGsYB z8-=vgRP$7W)Ktqv1B2vbW3v=v!&Ea1Q^OQP^JFvgG_%POjB0va>dAi?8<*sj=Hwh@ zUlXkKc9SNT)%(i8$2y9el#Bo0GA=n-_Rn&yLgKl^HvO>4YZ#>kO|48!tqd*o42(=o POpOfGox!iLt=@&vP!F5p%u1L-3`bqW?mNiRE$X2UZ0n z7L_;#XI7;aPrS%*UcG85+1 z8UE+in8YL!-?1n;W8xP5(t@1)#FRzFRSb*riy0P`6*DYO$psR{RmHP`+>GfA3zH9l z)J_1Zxy0BS>j6|J$Hc_V!N4wen1i8#L%~a9S80p=feVk!(u+fmp18p?X**Xw04)51 ArT_o{ delta 321 zcmey&@`q)D2G7rPb(y}Kj?B_I}%Ug{f?nVWi9q61>c#6LC)X(^^gDVD~@M&^l0$w^7ZX=W)V zsm7*>W{IXrMyZCWlOq`QEH^9USu-{+$t%stIr8$Jg>L41v4Z5}$ybkOggspL+LmEe zhUISGmMCkJXpgEJ92#5o|FSULWo2;Tn!KM;RmSVpDxbvSlF)*b#FEtGJgf{rkeM*` zJtOb*N0XRDGPdZK7Ubk7rgSVy&X};MxatyPYplnj{9?I9WyK7OQ*wbsaaHkb1|}wM z4hD9R?nPx8)8#-628M;nhe7%#0`)Nf^)N6TVrOXJ5Qs=(h$^Y+@vlFYvSEhu9L7~& GWGVp33w$>K 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 9df63ca6d5ea123f919f8fbe75dbcee121e19b8f..42565458a16e194e0e8ddc2fdb9702b741bf38a8 100644 GIT binary patch delta 326 zcmeyv@|k6V2G8%%+1|MykFcc1Osbh^Vl1Fi+{1ErwcppxjO-KbUcVNbSRSW-U{ye3 zQHf)4W>sqO#ET3EmVj75dZ}+nW^U?bi4KS%6aSb_j$l;FSdvP#wB^BIXOojOw;tA!1Ue2h^*wvnJ{i43I-wzIrt@?ji7^bo^Ea9BIpHWrDn{Aa(VsS}m zK}upt>Tw=c1|Y~xm{Vu?pIc)RlSq8WqU4N;Tl7l{a`F>X78O@9EXprtSX5Tbus9_b zNEBBU&jxZcrZX%|J_J%b0jTB@V{5DjP@NnT6E_C~yWC+8h6WA=FO6NLE%pa4JTgl! O4mo<_2G6AJT=@V2zlV+h delta 336 zcmey&@`q)D2G7rPb*ihDA7S};v7=?8iLt=`|M#b5*o1TIG{i zToPK4l30>@oQIVG2r?6o9;lwT~j zsH~V_aY`L+lI<90CzZ X3{fRDJ^uB_QZ~#`p2N86i%bOoo9>8y 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 e974690570e355055cf38f4d210182b021f06a90..3bec8f005d68412fe1b7f7d5c9bb076e1401fdd6 100644 GIT binary patch delta 191 zcmdnbvY%yw2G8%%InnbLA7OELGw#dd9{jd8IiyN7lqMJl(eR`d04kk3$=d6dC5d*XCGi)Og_4KCw?LR&1%CI(ajr rmW(&sDxbvSlF)*b#FEtGJgf{rkeM*2&gk&Wgvmb{omdXe6xIg-Ld{Xj delta 192 zcmV;x06+h~1iu83C=B_|a=4(3!~|LU^v01iGZ3Z*Z<+B&MI4!(v~xQ)>uQmAe+K!^ za=4(3k^D6$q-AtrXW1ZQF*Gw`V=*>2WHdE7Vl`qoG-fwuWH)1CVq;`EWjQdDF##qu zp?1z^V#@%|ueHQd0fD4$b!=?JtYyI=UZcR9=l10Bb?|RAq}s}~2+W6x*~zivf6u+O ufRnQUDjZ7cs!U;XbX9OGiGL+aHHE94Ru)d#}y0)Tr^mt9@dhR;<`kKXvkE sMlBg{wpBif#U-HyDTyVi$9Y&8fFLtrPMy)=nF*7BGCHvwoGGji05#N4TL1t6 delta 191 zcmV;w06_o01iu83C=B_|a)VW4#01JOh69l_GZ5DzNX~a}L?Wnr$6-HYMn{o%e+K!^ za)VW4k^D82SOF$nq-AtrXW1ZQF*Gw`V=*>2WHdE7Vl`qoG-fwuWH)1CVq;`EWjQdR zcFt#F%K*;4ZloyzfuwGAY;44=Wx*j{qrjWz_T=$(@NYDv+RC&D%!i5D$+6>q&%L#P tld}OT97^h{Oks0$Rd8fsbY;g31poj50BK;A?lA>77?b${LIgsnF06}8QkVb$ 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 d3c6634..672dc13 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 @@ -325,24 +325,36 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang TimeSlot morning = new TimeSlot(); morning.setDate(currentDate); morning.setPeriod("morning"); - morning.setStartTime("08:30"); - morning.setCapacity(480); // 上午480分钟(8小时,允许并发/灵活安排) + morning.setStartTime("08:00"); + morning.setCapacity(240); // 4小时 = 240分钟 timeSlots.add(morning); - // 下午时段 (13:00-18:00, 共300分钟) + // 下午时段 (14:00-18:00, 共240分钟) TimeSlot afternoon = new TimeSlot(); afternoon.setDate(currentDate); afternoon.setPeriod("afternoon"); - afternoon.setStartTime("13:30"); - afternoon.setCapacity(480); // 下午480分钟(8小时,允许并发/灵活安排) + 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; } + private List autoGroupParticipants(List athletes) { List groups = new ArrayList<>(); int displayOrder = 1; @@ -568,28 +580,22 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang log.info("=== 开始分配场地和时间段 ==="); log.info("场地数量: {}, 时间段数量: {}, 分组数量: {}", venues.size(), timeSlots.size(), groups.size()); - // 创建所有槽位(场地 × 时间段组合) - List slots = new ArrayList<>(); + // 为每个场地创建时间轴(记录当前已用时间) + // key = venueId + "_" + timeSlotIndex, value = 当前已用分钟数 + Map venueTimeUsed = new HashMap<>(); + Map venueTimeSlotMap = new HashMap<>(); + int timeSlotIndex = 0; for (TimeSlot timeSlot : timeSlots) { for (MartialVenue venue : venues) { - SlotInfo slot = new SlotInfo(); - slot.timeSlotIndex = timeSlotIndex; - slot.venueId = venue.getId(); - slot.venueName = venue.getVenueName(); - slot.date = timeSlot.getDate(); - slot.timeSlot = timeSlot.getStartTime(); - slot.period = timeSlot.getPeriod(); - slot.capacity = timeSlot.getCapacity(); - slot.currentLoad = 0; - slots.add(slot); + String key = venue.getId() + "_" + timeSlotIndex; + venueTimeUsed.put(key, 0); + venueTimeSlotMap.put(key, timeSlot); } timeSlotIndex++; } - log.info("总共初始化了 {} 个场地×时间段组合", slots.size()); - - // 按项目ID分组,计算每个项目的总时长 + // 按项目ID分组 Map> groupsByProject = groups.stream() .collect(Collectors.groupingBy(ScheduleGroupData::getProjectId)); @@ -600,7 +606,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang projectTotalDuration.put(projectId, total); }); - // 排序策略: 集体项目优先,然后按项目总时长降序(大项目优先安排) + // 排序: 集体项目优先,然后按时长降序 List sortedProjectIds = new ArrayList<>(groupsByProject.keySet()); sortedProjectIds.sort((a, b) -> { List groupsA = groupsByProject.get(a); @@ -608,20 +614,18 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang int typeA = groupsA.get(0).getProjectType() != null ? groupsA.get(0).getProjectType() : 1; int typeB = groupsB.get(0).getProjectType() != null ? groupsB.get(0).getProjectType() : 1; if (typeA != typeB) { - return typeB - typeA; // 集体项目优先 + return typeB - typeA; } - return projectTotalDuration.get(b) - projectTotalDuration.get(a); // 时长长的优先 + return projectTotalDuration.get(b) - projectTotalDuration.get(a); }); - log.info("项目排序(集体优先,时长降序): {}", sortedProjectIds.stream() + log.info("项目排序: {}", sortedProjectIds.stream() .map(id -> groupsByProject.get(id).get(0).getGroupName().split(" ")[0] + "(" + projectTotalDuration.get(id) + "分钟)") .collect(Collectors.joining(", "))); int assignedCount = 0; - Map projectVenueMap = new HashMap<>(); // 记录项目分配的场地 - Map projectSlotMap = new HashMap<>(); // 记录项目分配的时间段 + Map projectVenueSlotMap = new HashMap<>(); // 记录项目分配的场地+时段 - // 按项目分配(同一项目的所有分组尽量在同一场地同一时间段) for (Long projectId : sortedProjectIds) { List projectGroups = groupsByProject.get(projectId); int projectDuration = projectTotalDuration.get(projectId); @@ -629,104 +633,82 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang log.info("开始分配项目 '{}': {}个分组, 总时长{}分钟", projectName, projectGroups.size(), projectDuration); - // 找到能容纳整个项目的最佳槽位(优先选择负载最小的) - SlotInfo bestSlot = slots.stream() - .filter(s -> s.currentLoad + projectDuration <= s.capacity) - .min((a, b) -> { - // 优先选择负载最小的槽位 - return Integer.compare(a.currentLoad, b.currentLoad); - }) - .orElse(null); - - if (bestSlot != null) { - // 找到能容纳整个项目的槽位,所有分组都分配到这个槽位 - log.info("项目 '{}' 整体分配到: 场地={}, 时段={}, 剩余容量={}分钟", - projectName, bestSlot.venueName, bestSlot.timeSlot, bestSlot.capacity - bestSlot.currentLoad); + // 找到能容纳该项目的最佳场地+时段(优先选择已用时间最少的) + String bestKey = null; + int minUsed = Integer.MAX_VALUE; + + for (Map.Entry entry : venueTimeUsed.entrySet()) { + String key = entry.getKey(); + int used = entry.getValue(); + TimeSlot ts = venueTimeSlotMap.get(key); - for (ScheduleGroupData group : projectGroups) { - group.setAssignedVenueId(bestSlot.venueId); - group.setAssignedVenueName(bestSlot.venueName); - group.setAssignedDate(bestSlot.date); - group.setAssignedTimeSlot(bestSlot.timeSlot); - group.setAssignedTimeSlotIndex(bestSlot.timeSlotIndex); - group.setAssignedTimePeriod(bestSlot.period); - - bestSlot.currentLoad += group.getEstimatedDuration(); - assignedCount++; - - log.info(" 分组 '{}' 分配完成, 时长={}分钟, 槽位负载={}分钟", - group.getGroupName(), group.getEstimatedDuration(), bestSlot.currentLoad); + // 检查剩余容量是否足够 + if (used + projectDuration <= ts.getCapacity()) { + if (used < minUsed) { + minUsed = used; + bestKey = key; + } } - - projectVenueMap.put(projectId, bestSlot.venueId); - projectSlotMap.put(projectId, bestSlot.timeSlotIndex); - } else { - // 没有单个槽位能容纳整个项目,需要分散到多个槽位 - log.warn("项目 '{}' 总时长{}分钟超过单个时段容量,需要分散分配", projectName, projectDuration); - - // 优先选择同一场地的连续时间段 - final Long[] preferredVenueId = {null}; - - for (ScheduleGroupData group : projectGroups) { - SlotInfo targetSlot = null; - - // 如果已有优先场地,先尝试在同一场地找槽位 - if (preferredVenueId[0] != null) { - targetSlot = slots.stream() - .filter(s -> s.venueId.equals(preferredVenueId[0])) - .filter(s -> s.currentLoad + group.getEstimatedDuration() <= s.capacity) - .min((a, b) -> Integer.compare(a.timeSlotIndex, b.timeSlotIndex)) // 优先选择较早的时间段 - .orElse(null); - } - - // 如果优先场地没有容量,选择任意有容量的槽位 - if (targetSlot == null) { - targetSlot = slots.stream() - .filter(s -> s.currentLoad + group.getEstimatedDuration() <= s.capacity) - .min((a, b) -> Integer.compare(a.currentLoad, b.currentLoad)) - .orElse(null); - } - - // 如果都没有,选择负载最小的(允许超载) - if (targetSlot == null) { - targetSlot = slots.stream() - .min((a, b) -> Integer.compare(a.currentLoad, b.currentLoad)) - .orElse(slots.get(0)); - log.warn(" 分组 '{}' 需要超载分配", group.getGroupName()); - } - - group.setAssignedVenueId(targetSlot.venueId); - group.setAssignedVenueName(targetSlot.venueName); - group.setAssignedDate(targetSlot.date); - group.setAssignedTimeSlot(targetSlot.timeSlot); - group.setAssignedTimeSlotIndex(targetSlot.timeSlotIndex); - group.setAssignedTimePeriod(targetSlot.period); - - targetSlot.currentLoad += group.getEstimatedDuration(); - assignedCount++; - - // 记录优先场地 - if (preferredVenueId[0] == null) { - preferredVenueId[0] = targetSlot.venueId; - } - - log.info(" 分组 '{}' 分配到: 场地={}, 时段={}, 时长={}分钟", - group.getGroupName(), targetSlot.venueName, targetSlot.timeSlot, group.getEstimatedDuration()); - } - - projectVenueMap.put(projectId, preferredVenueId[0]); } + + // 如果没有单个时段能容纳,选择剩余容量最大的 + if (bestKey == null) { + int maxRemain = -1; + for (Map.Entry entry : venueTimeUsed.entrySet()) { + String key = entry.getKey(); + int used = entry.getValue(); + TimeSlot ts = venueTimeSlotMap.get(key); + int remain = ts.getCapacity() - used; + if (remain > maxRemain) { + maxRemain = remain; + bestKey = key; + } + } + log.warn("项目 '{}' 总时长{}分钟可能需要跨时段", projectName, projectDuration); + } + + // 解析场地ID和时段索引 + String[] parts = bestKey.split("_"); + Long venueId = Long.parseLong(parts[0]); + int slotIndex = Integer.parseInt(parts[1]); + TimeSlot timeSlot = venueTimeSlotMap.get(bestKey); + String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知场地"); + + log.info("项目 '{}' 分配到: 场地={}, 时段={}, 当前已用={}分钟", projectName, venueName, timeSlot.getStartTime(), venueTimeUsed.get(bestKey)); + + // 分配该项目的所有分组到同一场地+时段 + for (ScheduleGroupData group : projectGroups) { + group.setAssignedVenueId(venueId); + group.setAssignedVenueName(venueName); + group.setAssignedDate(timeSlot.getDate()); + group.setAssignedTimeSlot(timeSlot.getStartTime()); + group.setAssignedTimeSlotIndex(slotIndex); + group.setAssignedTimePeriod(timeSlot.getPeriod()); + assignedCount++; + + log.info(" 分组 '{}' 时长={}分钟", group.getGroupName(), group.getEstimatedDuration()); + } + + // 更新已用时间 + venueTimeUsed.put(bestKey, venueTimeUsed.get(bestKey) + projectDuration); + projectVenueSlotMap.put(projectId, bestKey); + + log.info(" 场地 {} 时段 {} 已用时间更新为 {} 分钟", venueName, timeSlot.getStartTime(), venueTimeUsed.get(bestKey)); } log.info("=== 分配完成: {}/{} 个分组成功分配 ===", assignedCount, groups.size()); - // 输出每个槽位的负载情况 - log.info("=== 各槽位负载统计 ==="); - for (SlotInfo slot : slots) { - if (slot.currentLoad > 0) { - log.info("场地={}, 日期={}, 时段={}, 负载={}/{}分钟 ({}%)", - slot.venueName, slot.date, slot.timeSlot, slot.currentLoad, slot.capacity, - (int)(slot.currentLoad * 100.0 / slot.capacity)); + // 输出各场地时段的使用情况 + log.info("=== 各场地时段使用统计 ==="); + for (Map.Entry entry : venueTimeUsed.entrySet()) { + if (entry.getValue() > 0) { + String[] parts = entry.getKey().split("_"); + Long venueId = Long.parseLong(parts[0]); + String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知"); + TimeSlot ts = venueTimeSlotMap.get(entry.getKey()); + log.info("场地={}, 日期={}, 时段={}, 已用={}/{}分钟 ({}%)", + venueName, ts.getDate(), ts.getStartTime(), entry.getValue(), ts.getCapacity(), + (int)(entry.getValue() * 100.0 / ts.getCapacity())); } } }