在高校后勤管理体系中,宿舍管理长期面临着数据分散、流程繁琐、效率低下的挑战。传统依赖纸质档案和人工协调的方式,在新生入学、宿舍调换、日常运维等高峰期往往不堪重负,易出现分配冲突、信息更新滞后、维修响应缓慢等问题。数字化转型升级成为提升管理效能、优化学生居住体验的必然选择。
本项目正是针对这一痛点,设计并实现了一套集宿舍资源管理、学生入住办理、维修申报、信息查询于一体的综合管理平台。系统采用业界成熟的SSM框架组合进行构建,旨在通过技术手段将线下业务流程标准化、线上化,为高校后勤部门、宿舍管理员及在校学生提供全流程、可追溯的数字化解决方案。
技术架构与选型
系统采用经典的三层架构模式,清晰分离表示层、业务逻辑层和数据持久层,确保系统具备良好的可维护性、可扩展性和松耦合特性。
后端技术栈:
- Spring Framework:作为项目的核心控制容器,负责管理所有Bean的生命周期,并通过依赖注入机制实现各组件间的解耦。其声明式事务管理功能为宿舍分配、调换等关键操作提供了数据一致性保障。
- Spring MVC:承担Web请求的调度职责。通过基于注解的控制器设计,简化了请求映射、参数绑定和视图解析的过程。集成自定义拦截器,实现了统一的用户身份认证与权限校验逻辑。
- MyBatis:作为数据持久层框架,通过XML映射文件将Java对象与数据库表进行灵活映射。其强大的动态SQL能力,有效支持了系统中多条件、分页查询等复杂数据操作需求。
前端与数据层:
- 前端界面采用JSP动态页面技术,结合jQuery库处理页面交互逻辑,并使用Bootstrap前端框架构建响应式、风格统一的用户界面,确保在不同设备上均有良好的操作体验。
- 数据库选用MySQL,利用其稳定性和事务支持特性存储系统所有业务数据。项目通过Maven进行依赖管理和构建,确保了第三方库版本的一致性和项目构建的自动化。
核心数据库设计剖析
一个稳健的数据库设计是系统高效运行的基石。本系统共设计18张数据表,以下选取几个核心表进行深入分析。
1. 宿舍楼与宿舍信息表 (dormitory_building, dormitory_room)
宿舍资源的管理是系统的核心。设计上采用两级结构,先定义宿舍楼,再定义具体的宿舍房间。
CREATE TABLE `dormitory_building` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`building_no` varchar(20) NOT NULL COMMENT '楼栋编号',
`building_name` varchar(50) DEFAULT NULL COMMENT '楼栋名称',
`total_floors` int(11) DEFAULT NULL COMMENT '总层数',
`manager_id` int(11) DEFAULT NULL COMMENT '负责管理员ID',
`description` text COMMENT '描述信息',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_building_no` (`building_no`),
KEY `idx_manager` (`manager_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍楼栋表';
CREATE TABLE `dormitory_room` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`room_no` varchar(20) NOT NULL COMMENT '房间号',
`building_id` int(11) NOT NULL COMMENT '所属楼栋ID',
`floor` int(11) NOT NULL COMMENT '所在楼层',
`bed_count` int(11) NOT NULL DEFAULT '4' COMMENT '床位数量',
`occupied_bed_count` int(11) NOT NULL DEFAULT '0' COMMENT '已入住床位数量',
`room_type` varchar(10) DEFAULT 'STANDARD' COMMENT '房间类型',
`status` varchar(20) DEFAULT 'AVAILABLE' COMMENT '房间状态',
`telephone` varchar(20) DEFAULT NULL COMMENT '房间电话',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_building_room` (`building_id`, `room_no`),
KEY `idx_building` (`building_id`),
KEY `idx_status` (`status`),
CONSTRAINT `fk_room_building` FOREIGN KEY (`building_id`) REFERENCES `dormitory_building` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍房间表';
设计亮点:
- 数据完整性:通过外键约束
FOREIGN KEY确保每个房间都必须归属于一个已存在的楼栋,删除楼栋时ON DELETE CASCADE会自动级联删除其下所有房间,避免脏数据。 - 业务逻辑前置:
bed_count和occupied_bed_count字段的设计,使得快速判断房间是否已满(occupied_bed_count < bed_count)无需关联查询床位表,提升了查询效率。 - 状态管理:
status字段用于标识房间是否可用、维修中或已满员,便于进行条件筛选和业务控制。
2. 学生住宿信息表 (student_accommodation)
此表是连接学生与宿舍床位的关键枢纽,记录了学生的入住历史。
CREATE TABLE `student_accommodation` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) NOT NULL COMMENT '学生ID',
`bed_id` int(11) NOT NULL COMMENT '床位ID',
`check_in_date` date NOT NULL COMMENT '入住日期',
`expected_check_out_date` date DEFAULT NULL COMMENT '预计退宿日期',
`actual_check_out_date` date DEFAULT NULL COMMENT '实际退宿日期',
`accommodation_status` varchar(20) NOT NULL DEFAULT 'CHECKED_IN' COMMENT '住宿状态',
`operator_id` int(11) DEFAULT NULL COMMENT '操作员ID',
`remarks` text COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_active` (`student_id`, `accommodation_status`),
KEY `idx_bed` (`bed_id`),
KEY `idx_check_in_date` (`check_in_date`),
CONSTRAINT `fk_accommodation_student` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`),
CONSTRAINT `fk_accommodation_bed` FOREIGN KEY (`bed_id`) REFERENCES `dormitory_bed` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生住宿信息表';
设计亮点:
- 唯一性约束:
UNIQUE KEY uk_student_active (student_id, accommodation_status)是关键设计。它允许一个学生有多条历史住宿记录,但只能有一条状态为“已入住”的活跃记录,从数据库层面防止了学生被重复分配床位。 - 状态与时间跟踪:通过
accommodation_status和三个日期字段,可以清晰追踪学生从入住到退宿的完整生命周期,便于生成统计报表和办理流程控制。
核心功能模块实现解析
1. 学生宿舍分配与入住办理
新生入学或老生调换宿舍时,宿舍分配是核心流程。系统提供了自动分配和手动分配两种模式。
后端控制器 (DormAllocationController.java) 处理分配请求:
@Controller
@RequestMapping("/admin/allocation")
public class DormAllocationController {
@Autowired
private DormAllocationService allocationService;
/**
* 批量自动分配宿舍
* @param batchReq 包含专业、班级、性别等分配条件
* @return 分配结果
*/
@PostMapping("/batchAuto")
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResult> batchAutoAllocation(@RequestBody BatchAllocationRequest batchReq) {
try {
// 参数校验
if (batchReq.getDepartmentId() == null || batchReq.getStudentCount() <= 0) {
return ResponseEntity.badRequest().body(ApiResult.error("参数错误"));
}
// 调用服务层进行分配逻辑
AllocationResult result = allocationService.executeBatchAutoAllocation(batchReq);
return ResponseEntity.ok(ApiResult.success("分配成功", result));
} catch (NoAvailableRoomException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ApiResult.error(e.getMessage()));
} catch (Exception e) {
log.error("批量分配异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResult.error("系统内部错误"));
}
}
}
服务层 (DormAllocationServiceImpl.java) 包含核心分配算法:
@Service
@Transactional
public class DormAllocationServiceImpl implements DormAllocationService {
@Override
public AllocationResult executeBatchAutoAllocation(BatchAllocationRequest request) throws NoAvailableRoomException {
// 1. 根据条件(如性别、专业)筛选出可用的宿舍房间列表
List<DormitoryRoom> availableRooms = dormitoryRoomMapper.selectAvailableRoomsByCriteria(request);
if (availableRooms.isEmpty()) {
throw new NoAvailableRoomException("当前没有符合条件的可用宿舍");
}
// 2. 按房间类型、楼层等规则排序,确定分配优先级
availableRooms.sort((r1, r2) -> {
// 优先分配剩余床位多的房间,使住宿相对集中
int availableBed1 = r1.getBedCount() - r1.getOccupiedBedCount();
int availableBed2 = r2.getBedCount() - r2.getOccupiedBedCount();
return Integer.compare(availableBed2, availableBed1);
});
AllocationResult result = new AllocationResult();
int remainingStudents = request.getStudentCount();
List<AllocationDetail> details = new ArrayList<>();
// 3. 循环分配,直到所有学生都被分配或房间用完
for (DormitoryRoom room : availableRooms) {
if (remainingStudents <= 0) break;
int bedsToAssign = Math.min(remainingStudents, room.getAvailableBedCount());
List<DormitoryBed> beds = dormitoryBedMapper.selectEmptyBedsByRoomId(room.getId(), bedsToAssign);
for (DormitoryBed bed : beds) {
// 4. 为每个床位创建预分配记录(此时学生ID可能暂未绑定)
AllocationDetail detail = createPreAllocation(bed, request);
details.add(detail);
remainingStudents--;
}
// 5. 更新房间已入住床位数量
dormitoryRoomMapper.increaseOccupiedCount(room.getId(), beds.size());
}
if (remainingStudents > 0) {
throw new NoAvailableRoomException("宿舍资源不足,尚有 " + remainingStudents + " 名学生未分配");
}
result.setAllocationDetails(details);
result.setTotalAllocated(request.getStudentCount() - remainingStudents);
return result;
}
private AllocationDetail createPreAllocation(DormitoryBed bed, BatchAllocationRequest request) {
StudentDormAllocation allocation = new StudentDormAllocation();
allocation.setBedId(bed.getId());
allocation.setAllocationStatus("PRE_ALLOCATED");
allocation.setAllocationDate(new Date());
allocation.setAcademicYear(request.getAcademicYear());
allocation.setOperatorId(request.getOperatorId());
studentDormAllocationMapper.insert(allocation);
AllocationDetail detail = new AllocationDetail();
detail.setAllocationId(allocation.getId());
detail.setBuildingNo(bed.getDormitoryRoom().getDormitoryBuilding().getBuildingNo());
detail.setRoomNo(bed.getDormitoryRoom().getRoomNo());
detail.setBedNo(bed.getBedNo());
return detail;
}
}
管理员在批量分配界面,可设定专业、班级、性别等条件,系统自动筛选并分配可用宿舍。
2. 宿舍报修流程管理
学生可在线提交维修申请,宿舍管理员进行审核、派单和完成确认,形成闭环管理。
报修实体与Mapper接口:
// 报修单实体类
public class RepairOrder {
private Long id;
private Long studentId; // 报修学生
private Long dormitoryId; // 报修宿舍
private String repairItem; // 报修项目
private String description; // 问题描述
private String imageUrls; // 图片URL,多个以逗号分隔
private String status; // 状态:PENDING, ASSIGNED, PROCESSING, COMPLETED, CANCELLED
private Long assigneeId; // 指派给的后勤人员
private Date expectedRepairTime; // 期望维修时间
private Date createTime;
private Date updateTime;
// ... getters and setters
}
// Mapper接口中的动态SQL查询
public interface RepairOrderMapper {
List<RepairOrder> selectByCondition(@Param("dormitoryId") Long dormitoryId,
@Param("status") String status,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
}
对应的MyBatis映射文件 (RepairOrderMapper.xml) 展示了动态SQL的应用:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dormitory.mapper.RepairOrderMapper">
<sql id="Base_Column_List">
id, student_id, dormitory_id, repair_item, description, image_urls, status,
assignee_id, expected_repair_time, create_time, update_time
</sql>
<select id="selectByCondition" resultType="com.dormitory.entity.RepairOrder">
SELECT <include refid="Base_Column_List" />
FROM repair_order
WHERE 1=1
<if test="dormitoryId != null">
AND dormitory_id = #{dormitoryId}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="startDate != null">
AND create_time >= #{startDate}
</if>
<if test="endDate != null">
AND create_time <= #{endDate}
</if>
ORDER BY
CASE status
WHEN 'PENDING' THEN 1
WHEN 'ASSIGNED' THEN 2
WHEN 'PROCESSING' THEN 3
ELSE 4
END,
create_time DESC
</select>
<!-- 更新报修单状态 -->
<update id="updateStatus">
UPDATE repair_order
SET status = #{status},
update_time = NOW()
<if test="assigneeId != null">
, assignee_id = #{assigneeId}
</if>
WHERE id = #{id}
</update>
</mapper>
学生登录系统后,可在线提交维修申请,填写报修项目和问题描述,并可上传现场图片。
3. 多角色权限控制与界面定制
系统基于Spring Security实现了基于角色的访问控制,不同角色登录后看到的功能菜单和操作权限完全不同。
Spring Security配置类:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/student/**").hasAnyRole("STUDENT", "ADMIN")
.antMatchers("/dorm-admin/**").hasAnyRole("DORM_ADMIN", "ADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/css/**", "/js/**", "/images/**", "/login", "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/auth/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
.and()
.logout()
.logoutUrl("/auth/logout")
.logoutSuccessUrl("/login")
.and()
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired=true");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
自定义用户详情服务:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 从数据库查询用户角色权限
List<SimpleGrantedAuthority> authorities = userMapper.selectUserRoles(user.getId())
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getStatus() == 1, // 是否启用
true, true, true, // 账户未过期、凭证未过期、未锁定
authorities);
}
}
前端菜单动态渲染(JSP + JSTL):
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<div class="sidebar">
<ul class="nav nav-pills flex-column">