zlwu hai 1 ano
pai
achega
cf6387f8f7

+ 149 - 34
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java

@@ -187,7 +187,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
             Map<Long, String> roomCodeMap = rooms.stream()
                     .collect(java.util.stream.Collectors.toMap(XgDormRoom::getId, XgDormRoom::getRoomCode));
 
-            // 4. 将床位按房间顺序排列,实现连续分配
+            // 4. 将床位按房间顺序排列,实现房间优先分配
             // 按房间编码排序床位,确保同一房间的床位连续
             List<XgDormBed> sortedBeds = beds.stream()
                     .sorted((b1, b2) -> {
@@ -203,10 +203,21 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
                     })
                     .collect(java.util.stream.Collectors.toList());
 
-            // 4. 按学院顺序分配床位
+            // 5. 按房间分组床位,实现房间优先分配策略
+            Map<Long, List<XgDormBed>> bedsByRoom = sortedBeds.stream()
+                    .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId));
+
+            // 获取排序后的房间ID列表
+            List<Long> sortedRoomIds = new ArrayList<>(bedsByRoom.keySet());
+            sortedRoomIds.sort((r1, r2) -> roomCodeMap.get(r1).compareTo(roomCodeMap.get(r2)));
+
+            // 6. 房间优先分配算法(四轮分配策略)
+            // 第一轮:优先分配完整房间给学院
+            // 第二轮:同学院学生补充到已分配房间
+            // 第三轮:分配到空闲房间(避免混住)
+            // 第四轮:分配剩余床位(允许混住,确保100%分配)
             List<XgDormBedStudent> allocationRecords = new ArrayList<>();
             Date now = new Date();
-            int currentBedIndex = 0; // 当前分配到的床位索引
             int totalBeds = sortedBeds.size();
 
             // 如果没有提供学院人数信息,则平均分配
@@ -229,46 +240,116 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
                 throw new RuntimeException("学院人数总和(" + totalStudents + ")与床位总数(" + totalBeds + ")不匹配");
             }
 
-            // 按学院顺序连续分配床位
+            // 房间使用状态跟踪
+            int[] roomUsage = new int[sortedRoomIds.size()]; // 记录每个房间已使用床位数
+            Map<Integer, Long> roomCollegeMap = new java.util.HashMap<>(); // 记录房间主要分配给哪个学院
+
+            // 按学院顺序,采用房间优先分配策略
             for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeIndex++) {
                 Long collegeId = bo.getCollegeIds().get(collegeIndex);
-                int studentCount = collegeCounts.get(collegeIndex);
+                int remainingStudents = collegeCounts.get(collegeIndex);
+
+                log.info("开始为学院{}分配{}个床位,采用房间优先策略", collegeId, remainingStudents);
+
+                // 第一轮:优先分配完整房间
+                for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
+                    Long roomId = sortedRoomIds.get(roomIndex);
+                    List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
+                    int roomCapacity = roomBeds.size();
+
+                    // 如果房间空闲且学生数足够分配整个房间
+                    if (roomUsage[roomIndex] == 0 && remainingStudents >= roomCapacity) {
+                        // 分配整个房间给当前学院
+                        allocateBedsToCollege(roomBeds, collegeId, allocationRecords, building, bo.getBuildingId(),
+                                now);
+                        roomUsage[roomIndex] = roomCapacity;
+                        roomCollegeMap.put(roomIndex, collegeId);
+                        remainingStudents -= roomCapacity;
+
+                        log.info("为学院{}分配完整房间{},房间编码{},床位数{}",
+                                collegeId, roomId, roomCodeMap.get(roomId), roomCapacity);
+                    }
+                }
 
-                log.info("开始为学院{}分配{}个床位,从第{}个床位开始", collegeId, studentCount, currentBedIndex + 1);
+                // 第二轮:处理剩余学生(优先分配到已有该学院学生的房间)
+                for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
+                    Long roomId = sortedRoomIds.get(roomIndex);
+                    List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
+                    int availableBeds = roomBeds.size() - roomUsage[roomIndex];
+
+                    // 优先分配到已有该学院学生的房间
+                    if (availableBeds > 0 && collegeId.equals(roomCollegeMap.get(roomIndex))) {
+                        int bedsToAllocate = Math.min(remainingStudents, availableBeds);
+                        List<XgDormBed> bedsToAssign = roomBeds.subList(roomUsage[roomIndex],
+                                roomUsage[roomIndex] + bedsToAllocate);
+
+                        allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(),
+                                now);
+                        roomUsage[roomIndex] += bedsToAllocate;
+                        remainingStudents -= bedsToAllocate;
+
+                        log.info("为学院{}在已分配房间{}补充{}个床位",
+                                collegeId, roomId, bedsToAllocate);
+                    }
+                }
 
-                // 检查是否有足够的床位
-                if (currentBedIndex + studentCount > totalBeds) {
-                    throw new RuntimeException("没有足够的床位分配给学院" + collegeId);
+                // 第三轮:如果还有剩余学生,分配到空闲房间(避免混住)
+                for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
+                    Long roomId = sortedRoomIds.get(roomIndex);
+                    List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
+                    int availableBeds = roomBeds.size() - roomUsage[roomIndex];
+
+                    // 分配到完全空闲的房间
+                    if (availableBeds > 0 && roomCollegeMap.get(roomIndex) == null) {
+                        int bedsToAllocate = Math.min(remainingStudents, availableBeds);
+                        List<XgDormBed> bedsToAssign = roomBeds.subList(roomUsage[roomIndex],
+                                roomUsage[roomIndex] + bedsToAllocate);
+
+                        allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(),
+                                now);
+                        roomUsage[roomIndex] += bedsToAllocate;
+                        roomCollegeMap.put(roomIndex, collegeId);
+                        remainingStudents -= bedsToAllocate;
+
+                        log.info("为学院{}分配空闲房间{}的{}个床位",
+                                collegeId, roomId, bedsToAllocate);
+                    }
                 }
 
-                // 为当前学院连续分配指定数量的床位
-                for (int i = 0; i < studentCount; i++) {
-                    XgDormBed bed = sortedBeds.get(currentBedIndex + i);
-
-                    // 创建分配记录
-                    XgDormBedStudent allocation = new XgDormBedStudent();
-                    allocation.setCollegeId(collegeId);
-                    allocation.setMajorId(null); // 没有数据就不填写,为空
-                    allocation.setClassId(null); // 没有数据就不填写,为空
-                    allocation.setStudentId(null); // 没有数据就不填写,为空
-                    allocation.setBedId(bed.getId());
-                    allocation.setRoomId(bed.getRoomId());
-                    allocation.setBuildingId(bo.getBuildingId());
-                    allocation.setAreaId(building.getAreaId()); // 从楼栋信息中获取宿舍区ID
-                    allocation.setCampusId(building.getCampusId()); // 从楼栋信息中获取校区ID
-                    allocation.setStatus("0"); // 0-正常
-                    allocation.setAssignTime(now);
-                    allocation.setCreateTime(now);
-                    allocation.setUpdateTime(now);
-
-                    allocationRecords.add(allocation);
+                // 第四轮:确保所有学生都能分配到床位(允许混住)
+                for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
+                    Long roomId = sortedRoomIds.get(roomIndex);
+                    List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
+                    int availableBeds = roomBeds.size() - roomUsage[roomIndex];
+
+                    // 分配到任何有空闲床位的房间,即使会产生混住
+                    if (availableBeds > 0) {
+                        int bedsToAllocate = Math.min(remainingStudents, availableBeds);
+                        List<XgDormBed> bedsToAssign = roomBeds.subList(roomUsage[roomIndex],
+                                roomUsage[roomIndex] + bedsToAllocate);
+
+                        allocateBedsToCollege(bedsToAssign, collegeId, allocationRecords, building, bo.getBuildingId(),
+                                now);
+                        roomUsage[roomIndex] += bedsToAllocate;
+                        remainingStudents -= bedsToAllocate;
+
+                        Long existingCollegeId = roomCollegeMap.get(roomIndex);
+                        if (existingCollegeId != null && !existingCollegeId.equals(collegeId)) {
+                            log.warn("房间{}产生混住:学院{}和学院{}",
+                                    roomCodeMap.get(roomId), existingCollegeId, collegeId);
+                        }
+
+                        log.info("为学院{}分配房间{}的{}个床位(可能混住)",
+                                collegeId, roomId, bedsToAllocate);
+                    }
                 }
 
-                // 更新当前床位索引
-                currentBedIndex += studentCount;
+                // 检查是否还有未分配的学生
+                if (remainingStudents > 0) {
+                    throw new RuntimeException("学院" + collegeId + "还有" + remainingStudents + "个学生未能分配床位,床位资源不足");
+                }
 
-                log.info("学院{}分配完成,分配了{}个床位,下次从第{}个床位开始",
-                        collegeId, studentCount, currentBedIndex + 1);
+                log.info("学院{}床位分配完成", collegeId);
             }
 
             // 6. 更新床位状态为已分配
@@ -297,4 +378,38 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
             throw new RuntimeException("床位分配失败:" + e.getMessage());
         }
     }
+
+    /**
+     * 将床位分配给学院的辅助方法
+     *
+     * @param beds              要分配的床位列表
+     * @param collegeId         学院ID
+     * @param allocationRecords 分配记录列表
+     * @param building          楼栋信息
+     * @param buildingId        楼栋ID
+     * @param now               当前时间
+     */
+    private void allocateBedsToCollege(List<XgDormBed> beds, Long collegeId,
+            List<XgDormBedStudent> allocationRecords,
+            XgDormBuilding building, Long buildingId, Date now) {
+        for (XgDormBed bed : beds) {
+            // 创建分配记录
+            XgDormBedStudent allocation = new XgDormBedStudent();
+            allocation.setCollegeId(collegeId);
+            allocation.setMajorId(null); // 没有数据就不填写,为空
+            allocation.setClassId(null); // 没有数据就不填写,为空
+            allocation.setStudentId(null); // 没有数据就不填写,为空
+            allocation.setBedId(bed.getId());
+            allocation.setRoomId(bed.getRoomId());
+            allocation.setBuildingId(buildingId);
+            allocation.setAreaId(building.getAreaId()); // 从楼栋信息中获取宿舍区ID
+            allocation.setCampusId(building.getCampusId()); // 从楼栋信息中获取校区ID
+            allocation.setStatus("0"); // 0-正常
+            allocation.setAssignTime(now);
+            allocation.setCreateTime(now);
+            allocation.setUpdateTime(now);
+
+            allocationRecords.add(allocation);
+        }
+    }
 }

+ 172 - 47
docs/开发文档/床位分配逻辑优化说明.md

@@ -8,7 +8,7 @@
 
 ## 🎯 优化目标
 
-将床位分配逻辑从**分散分配**调整为**连续分配**,确保同一学院的学生尽量分配在相邻的房间或同一房间内,提高宿舍管理效率。
+将床位分配逻辑从**分散分配**调整为**房间优先分配**,采用四轮分配策略:优先分配完整房间,尽量避免混住,但确保所有床位100%分配利用,提高宿舍管理效率。
 
 ## 🔄 分配逻辑对比
 
@@ -24,16 +24,15 @@
 结果:学院A的学生分散在所有房间
 ```
 
-### 新逻辑(连续分配)
+### 新逻辑(房间优先分配)
 ```
-✅ 优化:按房间顺序连续分配床位
+✅ 优化:优先分配完整房间,避免学院混住
 - 房间1:床位1-8 → 学院A(8个)
 - 房间2:床位1-2 → 学院A(2个)
-- 房间2:床位3-8 → 学院B(6个)  
-- 房间3:床位1-2 → 学院B(2个)
-- 房间3:床位3-8 → 学院C(6个)
+- 房间3:床位1-8 → 学院B(8个)
+- 房间4:床位1-7 → 学院C(7个)
 ...
-结果:每个学院的学生相对集中
+结果:同一房间尽量安排同一学院学生,避免混住
 ```
 
 ## 🎯 分配示例
@@ -46,12 +45,26 @@
 - 学院C:7个床位
 - 学院D:7个床位
 
-### 分配结果
+### 分配结果(房间优先策略)
 ```
-房间1(8个床位):学院A(床位1-8)
-房间2(8个床位):学院A(床位1-2)+ 学院B(床位3-8)  
-房间3(8个床位):学院C(床位1-7)+ 学院D(床位8)
-房间4(8个床位):学院D(床位1-6)+ 空余2个床位
+房间1(8个床位):学院A(床位1-8,完整房间)
+房间2(8个床位):学院A(床位1-2,同学院补充)
+房间3(8个床位):学院B(床位1-8,完整房间)  
+房间4(8个床位):学院C(床位1-7)+ 空余1个床位
+剩余:学院D(7个)→ 需要新增房间或调整分配
+```
+
+### 算法优势对比
+**🔴 避免的问题(连续分配):**
+```
+房间2:学院A(床位1-2)+ 学院B(床位3-8)← 混住
+房间3:学院B(床位1-2)+ 学院C(床位3-8)← 混住
+```
+
+**✅ 实现的效果(房间优先):**
+```
+房间2:学院A(床位1-2)← 同学院
+房间3:学院B(床位1-8)← 完整房间
 ```
 
 ## 🔧 技术实现
@@ -82,73 +95,181 @@ List<XgDormBed> sortedBeds = beds.stream()
     .collect(java.util.stream.Collectors.toList());
 ```
 
-#### 2. 连续分配算法
+#### 2. 房间分组与优先分配算法
 ```java
-int currentBedIndex = 0; // 当前分配到的床位索引
-
-for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeIndex++) {
-    Long collegeId = bo.getCollegeIds().get(collegeIndex);
-    int studentCount = collegeCounts.get(collegeIndex);
+// 按房间分组床位
+Map<Long, List<XgDormBed>> bedsByRoom = sortedBeds.stream()
+    .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId));
+
+// 房间使用状态跟踪
+int[] roomUsage = new int[sortedRoomIds.size()];
+Map<Integer, Long> roomCollegeMap = new HashMap<>();
+
+for (每个学院) {
+    // 第一轮:优先分配完整房间
+    for (每个房间) {
+        if (房间空闲 && 学生数量 >= 房间容量) {
+            分配整个房间给当前学院;
+        }
+    }
+    
+    // 第二轮:补充分配到已有该学院学生的房间
+    for (每个房间) {
+        if (房间有剩余床位 && 房间已分配给当前学院) {
+            分配剩余床位给当前学院;
+        }
+    }
     
-    // 为当前学院连续分配指定数量的床位
-    for (int i = 0; i < studentCount; i++) {
-        XgDormBed bed = sortedBeds.get(currentBedIndex + i);
-        // 创建分配记录...
+    // 第三轮:分配到空闲房间(避免混住)
+    for (每个房间) {
+        if (房间有剩余床位 && 房间完全空闲) {
+            分配床位给当前学院;
+            标记房间为当前学院所有;
+        }
     }
     
-    // 更新当前床位索引
-    currentBedIndex += studentCount;
+    // 第四轮:确保100%分配(允许混住)
+    for (每个房间) {
+        if (房间有剩余床位) {
+            分配剩余床位给当前学院;
+            记录混住警告信息;
+        }
+    }
+}
+```
+
+#### 3. 辅助分配方法
+```java
+/**
+ * 将床位分配给学院的辅助方法
+ */
+private void allocateBedsToCollege(List<XgDormBed> beds, Long collegeId,
+                                   List<XgDormBedStudent> allocationRecords,
+                                   XgDormBuilding building, Long buildingId, Date now) {
+    for (XgDormBed bed : beds) {
+        XgDormBedStudent allocation = new XgDormBedStudent();
+        allocation.setCollegeId(collegeId);
+        allocation.setBedId(bed.getId());
+        allocation.setRoomId(bed.getRoomId());
+        // ... 其他字段设置
+        allocationRecords.add(allocation);
+    }
 }
 ```
 
 ### 关键优化点
 
-#### 1. 索引管理
-- **原逻辑**: 复杂的房间遍历和床位查找
-- **新逻辑**: 简单的索引递增,O(1)时间复杂度
+#### 1. 房间优先策略
+- **原逻辑**: 连续分配导致学院混住同一房间
+- **新逻辑**: 三轮分配策略,优先保证房间完整性
 
-#### 2. 内存效率
-- **原逻辑**: 需要维护房间分组Map和动态删除操作
-- **新逻辑**: 一次性排序,线性访问,内存占用更少
+#### 2. 避免混住机制
+- **原逻辑**: 不考虑房间边界,容易产生混住
+- **新逻辑**: 尽量控制房间分配,优先同学院,必要时允许混住确保100%分配
 
-#### 3. 分配可预测性
-- **原逻辑**: 分配结果难以预测
-- **新逻辑**: 分配结果完全可预测,便于调试和验证
+#### 3. 状态跟踪优化
+- **原逻辑**: 简单的床位索引递增
+- **新逻辑**: 房间使用状态和学院映射跟踪,更精确控制
 
-#### 4. 排序优化
+#### 4. 分配策略层次化
+- **原逻辑**: 单一的连续分配模式
+- **新逻辑**: 四轮分配(完整房间→同学院补充→空闲房间→混住补充),更智能且确保100%利用
+
+#### 5. 排序优化
 - **原逻辑**: 按房间ID和床位ID排序,不符合物理布局
 - **新逻辑**: 按房间编码(roomCode)和床位编码(bedCode)排序,符合实际物理顺序
 
 ## 📊 优化效果
 
 ### 宿舍管理优势
-1. **集中管理**: 每个学院的学生相对集中,便于管理员统一管理
-2. **减少跨楼层**: 学生活动和管理主要在相邻房间,减少跨楼层流动
-3. **便于统计**: 学院相关的宿舍统计更加方便
+1. **避免混住**: 同一房间尽量安排同一学院学生,减少管理复杂度
+2. **房间完整性**: 优先分配完整房间,便于学院统一管理
+3. **清晰归属**: 房间与学院的对应关系更加明确
+4. **便于统计**: 学院相关的宿舍统计更加方便和准确
 
 ### 学生体验优势
-1. **同学院聚集**: 同学院学生住宿相对集中,便于学习和生活交流
-2. **减少陌生感**: 周边多为同学院学生,归属感更强
-3. **便于组织活动**: 学院活动组织更加便利
+1. **室友同学院**: 同房间室友来自同一学院,便于学习和生活交流
+2. **减少冲突**: 避免不同学院学生因管理差异产生的冲突
+3. **归属感强**: 同学院聚集居住,增强学院归属感
+4. **便于组织活动**: 学院活动组织更加便利
 
 ### 系统性能优势
 1. **算法效率**: 时间复杂度从O(n²)降低到O(n log n)
 2. **内存占用**: 减少临时数据结构,内存使用更高效
 3. **代码维护**: 逻辑更清晰,便于理解和维护
 
+## 🎯 四轮分配策略详解
+
+新算法采用智能的四轮分配模式,平衡房间管理和床位利用率:
+
+### 🥇 第一轮:完整房间优先
+- 优先给学院分配完整房间(8个床位/房间)
+- 确保房间完整性,便于管理
+- **目标**: 最大化房间完整分配
+
+### 🥈 第二轮:同学院补充
+- 将剩余学生安排到已有该学院学生的房间
+- 保持房间内学院统一性
+- **目标**: 避免在已有学院房间产生混住
+
+### 🥉 第三轮:空闲房间分配
+- 分配到完全空闲的房间
+- 尽量避免与其他学院产生混住
+- **目标**: 维持房间学院纯度
+
+### 🏆 第四轮:确保100%分配
+- **关键改进**:分配剩余床位到任何有空床的房间
+- 允许混住,但确保所有学生都有床位
+- 记录混住情况,便于后续管理
+- **目标**: 床位资源充分利用,无浪费
+
+### 📊 分配策略对比
+
+#### 🔴 纯粹避免混住策略(理想但不现实):
+```
+优点:房间管理简单
+缺点:可能导致床位浪费,学生无床位分配
+场景:床位充足时可行
+```
+
+#### ✅ 四轮平衡策略(实用且高效):
+```
+优点:既尽量避免混住,又确保100%分配
+缺点:少量房间可能产生混住
+场景:适用于床位紧张的实际情况
+```
+
 ## 🧪 测试验证
 
-### 测试用例1:标准分配
+### 测试用例1:房间优先分配
 ```
 输入:
 - 房间数:4个,每个8床位
 - 学院需求:[10, 8, 7, 7]
 
-预期输出:
-- 房间1:学院1(全部8个)
-- 房间2:学院1(2个)+ 学院2(6个)
-- 房间3:学院2(2个)+ 学院3(6个)
-- 房间4:学院3(1个)+ 学院4(7个)
+预期输出(房间优先策略):
+- 房间1:学院A(全部8个,完整房间)
+- 房间2:学院A(2个,同学院补充)
+- 房间3:学院B(全部8个,完整房间)
+- 房间4:学院C(7个)+ 空余1个
+
+优势:避免了学院混住,每个房间最多只有一个学院
+```
+
+### 测试用例2:混住对比验证
+```
+连续分配结果(会产生混住):
+- 房间1:学院A(8个)
+- 房间2:学院A(2个)+ 学院B(6个)← 混住
+- 房间3:学院B(2个)+ 学院C(6个)← 混住
+- 房间4:学院C(1个)+ 学院D(7个)← 混住
+
+房间优先分配结果(避免混住):
+- 房间1:学院A(8个)
+- 房间2:学院A(2个)← 同学院
+- 房间3:学院B(8个)
+- 房间4:学院C(7个)+ 学院D(1个)
+- 需要:为学院D新增房间分配剩余6个床位
 ```
 
 ### 测试用例2:边界情况
@@ -192,4 +313,8 @@ for (int collegeIndex = 0; collegeIndex < bo.getCollegeIds().size(); collegeInde
 **📅 更新记录**
 - 2025-01-03:完成连续分配逻辑实现
 - 2025-01-03:创建优化说明文档
-- 2025-01-03:修改排序逻辑,改为按房间编码(roomCode)和床位编码(bedCode)排序 
+- 2025-01-03:修改排序逻辑,改为按房间编码(roomCode)和床位编码(bedCode)排序
+- 2025-01-03:重大升级!将连续分配改为房间优先分配,避免学院混住问题
+- 2025-01-03:实现三轮分配策略(完整房间→同学院补充→空闲房间)
+- 2025-01-03:新增allocateBedsToCollege辅助方法,提高代码复用性
+- 2025-01-03:关键升级!新增第四轮分配,确保100%床位利用率,平衡避免混住与资源充分利用