十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

学生宿舍管理系统设计与实现:从数据库设计到前后端联调

学生宿舍管理系统设计与实现:从数据库设计到前后端联调 简介《学生宿舍管理系统设计与实现》是一份完整的本科毕业设计论文材料面向计算机、信息管理与信息系统等专业的学生可作为宿舍管理类C/S架构项目的选题参考与文档样板。资源包仅含1个doc文件约924KB已有157人学习。论文围绕C# 2005与SQL Server 2000开发环境系统介绍了宿舍管理系统的分析、设计、实现与测试全过程内容涵盖学生基本信息管理、宿舍分配与调整、登录权限控制、数据备份与恢复等核心模块。文中还阐述了审计登录信息、客户机/服务器远程管理、数据库完全备份与差异备份等难点问题的解决方案并给出任务书、摘要、目录及参考文献等规范写作要素。读者既可借鉴其模块划分与数据库设计思路也可参考论文结构完成自己的课程设计或毕业设计写作。1. 学生宿舍管理系统设计与实现先想清楚“住宿状态”再开工学生宿舍管理系统的业务逻辑乍一看就是楼栋、房间、学生三个对象的增删改查但真正落到设计与实现时最花时间的反而是那些状态字段一个学生入住之后房间的已住人数什么时候变化退宿之后这笔住宿记录如何保留换宿操作到底是“改房间号”还是“先退再入”。如果上来就写代码大概率会做出一套只能演示、经不起追问的界面。这篇文章从数据库设计开始把宿舍管理系统的核心数据模型、后端分配与费用计算、前端联调、部署验证这几个关键环节串起来采用 Spring Boot 2.7 Vue 3 MySQL 8 这套最常见的组合给出可以直接运行的实现片段适合正在做这套毕业设计或刚接触后台系统的开发人员作为设计参考。2. 学生宿舍管理系统数据库设计ER 模型、核心表与关键字段取舍先想清楚一个问题为什么很多学生宿舍管理系统的第一版能用第二版要改表原因几乎都出在“住宿”没有被建模成独立实体。把宿舍号直接写在学生表里查当前住宿很方便但换宿、退宿后原有数据被覆盖住宿天数、费用结算、历史追溯全部失去依据。所以在设计阶段先把实体关系梳理清楚再做字段选型。2.1 六张核心表的职责与关系宿舍管理系统的实体关系可以概括为一句话楼栋包含房间房间在不同时间段被住宿记录占用住宿记录对应一个学生水电抄表和访客登记都挂在房间维度而不是学生维度。这里面有两组关系最容易画错住宿记录与房间是多对一一个房间会有多条历史住宿记录但同一时刻只能有一条有效记录住宿记录与学生是一对多一个学生可以有多条“曾住过”的记录但状态为“在住”的记录同样只能有一条。六张核心表的职责边界如下表名核心字段职责边界dorm_buildingid、building_no、building_name、manager_name只保存楼栋级信息不承担房间和床位数据dorm_roomid、building_id、room_no、bed_count、used_bed_count、room_type、status房间基础配置加“已住人数”冗余字段studentid、student_no、name、gender、college、grade、phone学生基础资料不关心当前住在哪checkinid、student_id、room_id、checkin_date、checkout_date、status一次完整住宿周期对应一条记录meter_readingid、room_id、record_date、start_water、end_water、start_elec、end_elec抄表原始数据不保存金额visitor_logid、room_id、visitor_name、visitor_phone、in_time、out_time访客进出记录画 ER 图时可以直接沿用这六张表的连线关系楼栋到房间是一对多房间到住宿记录是一对多学生到住宿记录是一对多。水电抄表和访客登记都从房间出发不跟学生产生直接外键。需求分析阶段如果出现“某个学生某个月的水电费”这种查询需求最快的路径也是通过住宿记录关联到房间再关联抄表数据而不是在学生表里增加费用字段。2.2 建表 SQL 与关键字段的取舍逻辑下面给出三张最核心表的建表语句其中 checkin 表的字段设计是整个系统的关键。CREATE TABLE dorm_building ( id INT AUTO_INCREMENT PRIMARY KEY, building_no VARCHAR(10) NOT NULL, building_name VARCHAR(50) NOT NULL, manager_name VARCHAR(20) NOT NULL DEFAULT , UNIQUE KEY uk_building_no (building_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宿舍楼栋表; CREATE TABLE dorm_room ( id INT AUTO_INCREMENT PRIMARY KEY, building_id INT NOT NULL, room_no VARCHAR(20) NOT NULL, room_type TINYINT NOT NULL DEFAULT 1 COMMENT 1-四人间 2-六人间, bed_count TINYINT NOT NULL COMMENT 床位总数, used_bed_count TINYINT NOT NULL DEFAULT 0 COMMENT 当前已住人数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-启用 0-停用, UNIQUE KEY uk_building_room (building_id, room_no), KEY idx_room_status (status, used_bed_count), CONSTRAINT fk_room_building FOREIGN KEY (building_id) REFERENCES dorm_building(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宿舍房间表; CREATE TABLE checkin ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, room_id INT NOT NULL, checkin_date DATE NOT NULL COMMENT 入住日期, checkout_date DATE DEFAULT NULL COMMENT 退宿日期在住为NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-在住 0-已退宿, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_student (student_id), KEY idx_room (room_id), CONSTRAINT fk_checkin_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_checkin_room FOREIGN KEY (room_id) REFERENCES dorm_room(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT住宿记录表;dorm_room 表里有两个字段需要特别注意。bed_count 是房间固定的床位数used_bed_count 是当前在住人数used_bed_count 是冗余字段用来在宿舍列表页面快速显示“3/4”这样的入住状态避免每次查询都去 checkin 表做聚合。既然用了冗余就要求分配房间和退宿时必须用事务同时修改它否则就会出现“checkin 里有记录但房间已住人数不对”的数据不一致。checkin 表把入住时间和退宿时间拆成两个字段checkout_date 为空就表示当前仍在住。status 字段只取 1 和 0 两个值1 代表在住0 代表已退宿。这样设计之后查询当前空宿舍只需要看 dorm_room 的 used_bed_count查询学生住宿历史只需要按 student_id 过滤 checkin 表两个场景互不干扰。换宿则通过“先退宿再入住”两步完成旧的 checkin 记录保持原样新记录重新生成。2.3 字段选型里容易踩的三个坑第一个坑是日期字段用 VARCHAR 存储。宿舍管理经常要计算住宿天数比如按天计费的退宿结算VARCHAR 比较大小依赖字符串格式一旦出现“2024-1-5”和“2024-01-05”混存排序和计算都会出错。MySQL 里直接用 DATE 类型业务代码用 LocalDate 处理边界情况会少很多。第二个坑是所有表都加 status 字段。楼栋、宿舍有启用停用状态是必要的但住宿记录表里的状态语义完全不同。checkin 表的 status 表达的是“这条记录当前是否有效”跟字典里的启用停用没关系容易在写查询时把两个 status 混在一起过滤。第三个坑是外键约束图省事不添加。宿舍管理系统数据量不大InnoDB 外键开销完全可以接受而且能挡住一部分脏数据。至少 checkin 指向 student 和 dorm_room 的外键必须加上否则录入时可能把学生分到不存在的房间。注意设计宿舍表时房间号不要用自增主键。主键是供程序引用的房间号是业务上要展示给用户看的两者耦合在一起会导致换房号时外键冲突。房间号用普通字段加联合唯一索引就足够了。3. 学生宿舍管理系统后端实现宿舍分配事务、退宿结算与状态联动后端部分选用 Spring Boot MyBatis-Plus。单表的增删改查交给 MyBatis-Plus 的 BaseMapper 完成涉及多表写操作时用 Transactional 控制事务。系统的核心难点集中在两个接口宿舍分配和退宿结算其余查询接口基本都是围绕这两条主线的读操作。3.1 先把业务规则固化成状态定义实现后端前先定义好状态值避免多人协作时各写各的数字对象状态字段取值含义dorm_roomstatus1 / 0启用 / 停用dorm_roomused_bed_count0 到 bed_count实时已住人数checkinstatus1 / 0在住 / 已退宿checkincheckout_dateNULL / DATENULL 代表在住非 NULL 代表退宿日期这四类状态组合起来可以推导出系统的核心约束一个学生只能有一条 status1 的 checkin 记录一个房间的 used_bed_count 不能超过 bed_count退宿时 checkout_date 必须写入日期status 同时置 0。把这些约束写进 service 层而不是靠页面控制才能保证接口被直接调用时也安全。3.2 宿舍分配接口从查空位到更新床位计数宿舍分配最常见的错误是先查房间是否有空位再插入 checkin再更新 used_bed_count。这个方法在单用户演示时没问题但两个管理员同时操作时就会出现两个请求都查到“有空位”最后把房间住满的情况。要避免这个问题不能靠程序加锁而是靠一条原子 SQL。Transactional(rollbackFor Exception.class) public void assignRoom(CheckinRequest request) { DormRoom room roomMapper.selectById(request.getRoomId()); if (room null || room.getStatus() ! 1) { throw new BusinessException(房间不存在或已停用); } Checkin activeCheckin checkinMapper.findActiveByStudentId(request.getStudentId()); if (activeCheckin ! null) { throw new BusinessException(该学生当前已有在住记录); } int updated roomMapper.tryOccupy(request.getRoomId()); if (updated 0) { throw new BusinessException(该房间已无空床位); } Checkin checkin new Checkin(); checkin.setStudentId(request.getStudentId()); checkin.setRoomId(request.getRoomId()); checkin.setCheckinDate(LocalDate.now()); checkin.setStatus(1); checkinMapper.insert(checkin); }tryOccupy 对应的 SQL 是宿舍分配接口的关键UPDATE dorm_room SET used_bed_count used_bed_count 1 WHERE id #{roomId} AND used_bed_count bed_count AND status 1;这条 UPDATE 把“检查空位”和“占用床位”合并成一步数据库在更新时会对 dorm_room 这行记录加行锁并发条件下只有一个请求能成功把 used_bed_count 加一另一个请求会因为条件不满足而得到更新行数 0从而走异常逻辑。参数里除了 roomId还要把房间状态和床位条件都放进 WHERE这样即使前端传进来一个已停用房间也会被数据库层面挡住。初始房间和学生的检查放前checkin 插入放后顺序不能颠倒。如果先插入 checkin 再更新 dorm_room一旦房间更新失败抛出异常事务回滚会把 checkin 一起撤销但期间如果有其他请求读到该学生的在住记录就会读到一条“即将回滚”的数据。3.3 退宿接口住宿天数、水电费和房间释放一起处理退宿比入住多一个费用结算环节逻辑上分成三步查出当前有效住宿记录计算应缴费用最后释放房间并更新记录。Transactional(rollbackFor Exception.class) public void checkout(CheckoutRequest request) { Checkin checkin checkinMapper.findActiveByStudentId(request.getStudentId()); if (checkin null) { throw new BusinessException(该学生没有在住记录); } DormRoom room roomMapper.selectById(checkin.getRoomId()); LocalDate checkoutDate request.getCheckoutDate() null ? LocalDate.now() : request.getCheckoutDate(); long days ChronoUnit.DAYS.between(checkin.getCheckinDate(), checkoutDate) 1; BigDecimal dormFee roundFee(room.getBedCount() 4 ? dormPriceSix : dormPriceFour, days); MeterReading reading meterReadingMapper.findLatestByRoomId(room.getId()); BigDecimal waterFee BigDecimal.ZERO; BigDecimal elecFee BigDecimal.ZERO; if (reading ! null) { waterFee reading.getEndWater().subtract(reading.getStartWater()) .multiply(waterUnitPrice); elecFee reading.getEndElec().subtract(reading.getStartElec()) .multiply(elecUnitPrice); } feeRecordMapper.insert(new FeeRecord(checkin.getId(), request.getStudentId(), dormFee, waterFee, elecFee, checkoutDate, 正常退宿)); checkin.setCheckoutDate(checkoutDate); checkin.setStatus(0); checkinMapper.updateById(checkin); roomMapper.releaseRoom(room.getId()); }住宿天数用 ChronoUnit.DAYS.between 计算加 1 是因为入住当天也算一天。这个细节在需求文档里往往不会写明但 UI 上“算几天”“舍不舍尾数”都需要有明确口径否则退宿高峰期宿管和学生对不上账。水电费取最新一次抄表记录用止数减起数再乘以单价新系统在没有历史抄表数据时可以让起数为 0这样首月计费也不会报空指针。releaseRoom 的 SQL 同样使用条件更新Update(UPDATE dorm_room SET used_bed_count used_bed_count - 1 WHERE id #{roomId} AND used_bed_count 0) int releaseRoom(Long roomId);退宿时如果把“插入费用记录”放在更新 checkin 之后一旦费用插入失败已经置为已退宿的 checkin 会随事务回滚看似安全但费用表可能已经写入了一段无法关联的数据。把写费用放在更新状态之前同时保证二者处于同一事务才能让回滚时所有表保持一致。3.4 费用计算的模板方法设计住宿费、水费、电费三套计算逻辑未来都可能调整住宿费可能变成“超过一个月打折”水电费可能改成阶梯计价。如果直接在一个方法里按顺序写死三段计算代码价格调整时就要改动退宿方法本身。可以抽一个费用计算模板把三套算法分别独立实现这一思路对应设计模式里的模板方法模式在宿舍管理系统的扩展点上很合适。public abstract class FeeCalculator { public final BigDecimal calculate(CalcContext ctx) { BigDecimal original compute(ctx); BigDecimal discount applyDiscount(original, ctx); return roundFee(discount); } protected abstract BigDecimal compute(CalcContext ctx); protected BigDecimal applyDiscount(BigDecimal fee, CalcContext ctx) { return fee; } }新增一个“寒暑假住宿半价”规则时只需要写一个子类重写 applyDiscount不需要改动退宿主流程。宿舍管理系统的收费规则在高校里几乎每年都有调整把变动的部分隔离在子类中比在 checkin 表里堆一堆费用字段要容易维护得多。4. 学生宿舍管理系统前端页面与跨浏览器支持的设计与实现前端部分按 Vue 3 Element Plus 搭建页面数量控制在五个以内会比较容易维护登录页、宿舍总览页、入住登记页、退宿结算页、费用记录页。小体量系统不需要引入复杂的状态管理库组件内部维护数据和调用接口已经够用。4.1 页面与后端接口的映射关系前端每个页面只对后端的少数几个接口负责提前把映射关系列出来可以避免联调时出现“页面找不到数据”的糊涂问题。页面核心组件后端接口关键交互宿舍总览楼栋下拉框、宿舍卡片、分页GET /room/list、GET /room/detail下标显示已住人数/床位数入住登记学生搜索框、房间选择器、日期选择GET /student/search、POST /checkin/assign提交前校验学生无在住记录退宿结算学生搜索框、费用明细表GET /checkin/active、POST /checkout费用明细回显确认后提交费用记录日期范围选择、费用表格GET /fee/record查询条件必传学生ID或房间ID宿舍总览页是高频页面类似“A栋 301 房间 4/6”这种信息适合用卡片列表呈现而不是传统表格。每张卡片显示楼栋号-房间号右下角放已住人数和床位数数据来自 room/list 接口。这里有一个小技巧筛选条件放在前端做还是后端做如果宿舍总量在几千条以内整个楼栋的数据一次拉到前端用 computed 过滤楼层即可交互流畅且减少请求量如果宿舍过万就改成后端分页查询。4.2 跨浏览器支持的设计与实现日期格式和表格渲染的两个问题宿舍管理系统运行环境常在学校的机房电脑或图书馆终端上这些浏览器版本可能落后于个人电脑。跨浏览器支持最常出问题的是日期处理。后端返回的日期字符串形如 “2025-06-03T00:00:00”在 Chrome 下直接 new Date() 没问题在部分旧版浏览器的 parse 规则里却可能解析失败导致日期显示成 NaN。// 统一处理日期字符串避免不同浏览器对 - 分隔符解析不一致 export function formatDate(str) { if (!str) return ; const date new Date(String(str).replace(/-/g, /)); if (Number.isNaN(date.getTime())) return ; const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); return ${y}-${m}-${d}; }这段代码把 ISO 日期中的-统一替换成/再交给 Date 构造器可以规避多数浏览器对日期字符串解析规则不同的问题。退宿结算页里的“住宿天数”显示不要直接拿两个 Date 相减而是先 formatDate 再按天数计算否则会出现跨月时差算错一天的情况。另一个跨浏览器问题是表格列宽度。校长办公室的老电脑分辨率还是 1366x768Element Plus 表格如果列太多横向滚动条会被截断在页面下方用户根本没意识到能左右滑动。做法是列表页只展示 6 个以内核心字段费用明细等次要字段放进“详情”抽屉里。这样页面在窄屏下不会被压扁信息层级也更清楚。4.3 前后端联调阶段的三个状态码排查联调时遇到接口不通先用浏览器开发者工具看 Network 面板的响应状态码不同状态码指向的问题完全不同。状态码问题范围排查步骤401Token 失效或未携带检查登录后本地存储的 token 是否在请求头里带上403权限不足确认当前登录角色是否有该接口的访问权限404接口路径不匹配对照后端 Controller 的 RequestMapping 路径500后端异常查看控制台异常堆栈重点看事务回滚提示404 最常见的伪造原因是后端接口路径带了项目名比如/dorm/room/list而前端请求的是/api/room/list此时在后端统一配置 context-path 或在 controller 类上加 RequestMapping 就能解决。500 状态在退宿接口出现时优先检查是否由事务回滚导致因为回滚后的异常会吞掉原始业务错误信息。提示调接口时如果前端报的错跟后端对不上先把 Network 面板里这条请求的完整响应体复制出来再决定查前端还是查后端。看 Response 里的错误 message 比猜状态码快得多。5. 学生宿舍管理系统部署验证与查询优化细节5.1 部署配置里最容易漏掉的三个参数Spring Boot 项目打包成 jar 后部署到服务器最容易出问题的不是代码而是配置。application.yml 里数据源连接串的三个参数建议直接写全spring: datasource: url: jdbc:mysql://localhost:3306/dorm_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/ShanghaiserverTimezone 不配服务器时区是 UTC 时查询出来的时间会比数据库实际时间早 8 小时。useSSL 和 allowPublicKeyRetrieval 是 MySQL 8 连接时常见的两个报错来源提前加上能省去部署后的排查。jackson 的 date-format 统一了后端返回给前端的日期格式避免前端自行猜测解析格式。5.2 端到端验证用例怎么写部署完成后按宿管的操作习惯走一遍完整流程比检查任何代码都有效。步骤操作预期结果1新增一栋楼、一间 4 人间宿舍宿舍总览页显示 0/42用两个学生先后入住该宿舍前后两次分配成功第二次后显示 2/43第三个学生尝试入住已满房间前端提示“该房间已无空床位”4给其中一个学生做退宿结算费用明细出现且房间显示 1/45退宿后再次查询该学生历史能看到原住宿记录状态为已退宿第 5 步最容易验证失败原因是部分实现里退宿操作直接 DELETE 了 checkin 记录导致历史记录丢失。若出现这种情况应把 DELETE 改为 UPDATE status 为 0。5.3 列表查询慢时先看索引宿舍总览页在大批量数据下响应变慢优先用 EXPLAIN 看慢查询的执行计划。比如按楼栋查房间列表如果没有 indexMySQL 会全表扫描EXPLAIN SELECT * FROM dorm_room WHERE building_id 3 AND status 1;当 Extra 列出现Using where且 type 为ALL时说明 building_id 和 status 的联合索引用不上。此时在 dorm_room 上建(building_id, status)联合索引即可解决。宿舍管理系统的数据量级根本到不了需要分库分表的程度绝大多数慢查询都是漏建索引造成的排查顺序应该是“索引 → SQL 写法 → 代码逻辑”不要一上来就引入 Redis 缓存。本文还有配套的精品资源点击获取
返回列表