说句实话“宿舍管理系统”这六个字在计算机专业毕业设计和课程设计里出现的频率高到几乎每个学生都绕不过去。但真动手做起来你会发现它远不是“CRUD四件套”那么简单。最近我整理了一套基于JavaSSM和Django的宿舍管理系统源码、答辩文档、调试记录、讲解视频一应俱全算是把两条技术栈的完整闭环都走了一遍。这篇文章就把项目从选型、设计、编码到部署调试的完整链路拆开来讲过程中会穿插大量实操细节和踩坑实录给正在做类似系统或想用这项目练手的朋友一份能直接“抄作业”的参考。这套系统的核心业务是宿舍资源管理、学生入住退宿、水电费统计、报修工单、访客登记和门禁联动。代码量不算大但涉及多角色权限、状态流转、资源占用校验和费用计算逻辑细节相当多。无论你是Java方向的SSM选手还是Python方向的Django选手都能从这套东西里找到可复用的模块和避坑经验。1. 整体设计与技术选型思路1.1 为什么是“SSM Django”双技术栈第一次看这个项目标题很多人会好奇SSM是Java体系的SpringSpringMVCMyBatisDjango是Python的全栈框架这两个怎么凑到一起去了我实际做下来的理解是这套宿舍管理系统本质上是用两种技术分别实现同一套业务或者在某一个统一MySQL数据库之上分别构建面向不同场景的服务端。先放一张我对这两个技术栈的选型判断表后面写代码时会反复用到技术栈优点适合干什么典型坑Java SSM三层架构清晰、事务管理成熟、MyBatis对复杂SQL友好、企业面试高频考点核心业务接口、复杂报表、权限逻辑配置文件多环境敏感JSON序列化容易踩Recursion坑Django自带ORM、Admin后台、认证体系开发效率极高快速搭建管理端、数据录入界面、简单APIORM封装太深复杂统计SQL不容易调优DRF要额外学MySQL两者都能完美兼容事务和索引成熟唯一数据源保证两套代码共库字符集、时区、布尔类型必须两边设一致我当时定的方案是JavaSSM负责业务主接口比如登录认证、宿舍分配、退宿、报修处理Django负责管理后台和数据看板利用自带的Admin和ORM快速实现信息维护和统计页面。前端页面用Vue或简单HTMLAjax调用后端两边共用同一个MySQL库。这样做的好处是同一份需求能同时验证两套方案的设计思想。Java端把事务控制和复杂查询写明白Django端把快速开发和权限配置跑通。对毕设答辩来说这也是一个天然的加分项老师一问“你为什么同时用两套技术”答案不是“为了凑字数”而是“通过对比分别用最合适的工具解决最擅长解决的问题”。1.2 角色与功能定位宿舍管理系统表面上只有“宿舍”两个字实际上至少要拆出三类角色系统管理员管理宿舍楼、楼层、房间、床位基础数据查看全系统报表。宿管员审批入住、退宿、调宿请求登记水电表数处理报修工单登记来访人员。学生提交入住申请、查看床位信息、提交报修、查看水电账单、预约访客。角色之间是严格的纵向权限关系。学生在宿舍管理系统里只能看到自己的房间、自己的账单、自己的报修单宿管员能管理整栋楼的数据管理员能跨楼管理。代码层面我用JWT令牌携带角色信息后端在Controller层做拦截器校验Django端用DRF的权限类实现同样的逻辑。1.3 数据库设计是整套系统最重要的地基很多新手一上来就写代码写到一半发现业务逻辑塞不进表结构里回头改表改到崩溃。这套项目的表结构我是这么设计的大家可以直接参照表名关键字段说明tb_buildingid, name, address, manager_name宿舍楼基础信息tb_roomid, building_id, room_no, capacity, gender_type, status房间表性别标记很关键0代表男寝1代表女寝tb_bedid, room_id, bed_no, status床位表一个房间对应多个床位tb_studentid, user_id, student_no, name, major, class_name, phone学生信息表关联登录账号IDtb_live_recordid, student_id, bed_id, checkin_date, checkout_date, status入住/退宿流水表核心状态表tb_reportid, student_id, room_id, report_type, description, status报修工单表tb_water_electricid, room_id, record_month, water_num, electric_num, fee, status水电费月度账单表tb_visitorid, room_id, visitor_name, visitor_phone, reason, status访客登记表两个设计细节值得重点说。第一房间表的gender_type和床位分配逻辑是绑定在一起的。系统分配床位时必须校验房间容量是否已满房属性是否与学生性别匹配学生是否已有未退的入住记录这三点看起来简单但忘记任何一条都会在生产环境闹出“女生分到男寝”的麻烦。第二tb_live_record是典型的流水表不能只在学生表上放一个“当前宿舍ID”字段。因为入住、调宿、退宿都要留痕这既是学校管理的合规要求也是后期做住宿历史统计的数据基础。每次分配床位就在流水表里插入一条status1的记录同时把该床位的status置为占用。1.4 接口设计统一规范两套后端共用一个数据库接口风格必须克制统一。我当时定了几个硬性约定强烈建议照做所有接口统一返回{ code: 200, msg: success, data: {...} }结构前端只看code。分页参数统一用page和size后端返回{ total, list }。日期时间统一使用yyyy-MM-dd HH:mm:ss字符串传输时区统一用Asia/Shanghai。所有涉及金额的字段用DECIMAL(10,2)后端用BigDecimalDjango用DecimalField。这样做的最大好处是两边代码维护成本低前端对接时不用区分“这个接口是Java写的还是Django写的”。2. 核心功能模块与业务规则拆解2.1 登录认证与权限控制SSM端的登录流程是学生输入账号密码后端校验通过后生成JWT令牌前端每次请求在Header携带令牌拦截器解析令牌并取出角色ID。这里有个很容易被忽略的细节JWT签名密钥要放到配置文件里硬编码在Java类里既不安全换环境时也得改代码重新编译。Django端的认证用得比较顺手因为框架自带User表和session机制。但如果要和Java端共用同一张tb_user表我建议直接上DRF的JWT方案配置REST_FRAMEWORK的DEFAULT_AUTHENTICATION_CLASSES然后把tb_user的表结构和Django的User模型做一对一映射。这样两边的登录状态互相独立但账号数据是同一份。权限控制有一个非常容易踩的坑学生调用“查询所有宿舍”接口返回了全部房间数据。问题根源在于Controller只校验了“已登录”没校验“是否是宿管员”。我最终在每个需要权限的Controller方法加了自定义注解RequireRole(admin)配合拦截器统一校验比在每个方法里手写if判断干净得多。2.2 宿舍资源管理与床位分配算法宿舍资源维护本身不复杂就是楼栋、房间、床位三层树形结构。真正的难点在自动分配床位。我的实现思路是根据学生性别筛选目标楼栋和房间。按学院、班级优先级排序让同班同学优先同房间。查询该房间内状态为空闲的床位。用事务锁定该床位并写入入住流水。核心SQL长这样可以拿去直接套用SELECT b.id, r.room_no, b.bed_no FROM tb_bed b INNER JOIN tb_room r ON b.room_id r.id WHERE r.building_id #{buildingId} AND r.gender_type #{genderType} AND b.status 0 AND b.id NOT IN ( SELECT bed_id FROM tb_live_record WHERE status 1 ) ORDER BY r.room_no, b.bed_no LIMIT #{limit}第一次跑这条SQL时遇到一个性能问题NOT IN子查询在数据量大时全表扫描会拖慢响应。后来改成LEFT JOINIS NULL的写法效率提升很明显。这说明“能跑”和“跑得快”是两回事调试记录里这一步也写得很详细。2.3 入住、调宿、退宿的状态机设计业务流程不是简单的“插入一条记录”而是一组状态迁移需要一张状态机表来管理。我把入住、调宿、退宿拆成三种状态流转空闲床位 - 申请入住 - 入住生效 - 申请退宿 - 退宿生效 - 床位释放入住生效 - 申请调宿 - 调宿审核 - 原床位释放新床位占用每次状态流转都会写入tb_live_record并在原记录上置为历史状态。这样随时可以回答“这个床位现在谁住着”“这个学生上一学期住哪”这类问题。调试时最容易出现的问题是什么是退宿时只删了入住记录忘记更新床位状态。我做了一个统一的BedService.changeBedStatus()方法所有涉及床位状态变更的操作都必须经过它彻底避免“改了床位但没改流水”或“改了流水但没改床位”的脏数据问题。2.4 水电费、报修与访客门禁逻辑水电费的业务规则是每月由宿管员录入每个房间的本月水表和电表读数系统自动减去上月底数算出本月用量再乘单价。这个逻辑本身不难但要注意“首次录入没有上月底数”的情况我用了IFNULL(上月底数, 0)处理保证不会算出负数。报修工单模块真正常见的需求是状态闭环待处理 - 处理中 - 已完成 - 已评价。每个状态变化都要记录操作人和操作时间方便追溯。Django实现这个模块时特别省事因为ORM的Choices字段类型天然适合枚举状态。当时我花时间最多的地方是“报修照片上传”Django简单配一下MEDIA_ROOT就搞定Java端反而要写文件上传接口和访问映射。门禁和访客是这套系统比较亮眼的小功能。访客预约流程是学生填写来访人姓名电话、被访房间、来访时间宿管员审核通过后生成一个二维码访客在门禁处扫码验证。二维码内容我用的不是简单字符串而是“访客记录ID生成时间随机数”的JSON串再加密避免被伪造。Java端用hutool的QrCodeUtil生成二维码图Django端用qrcode库两边都跑得很稳。2.5 数据统计与可视化宿舍管理系统如果没有统计看板演示效果会大打折扣。我做了三个核心统计接口整体入住率已占用床位总数 / 总床位数各宿舍楼入住率排行按building分组统计空余床位明细按楼栋、性别、房间维度展开这段SQL是全项目里最复杂的需要先算每个房间的已占用数再关联宿舍楼分组聚合。MyBatis写多表统计SQL时建议先用Navicat跑通原始SQL再翻译成Mapper XML千万别直接在注解里写长SQL可读性和调试体验都太差。3. 关键实现细节与踩坑记录3.1 SSM框架的配置“三件套”Java端的SSM配置是新手最容易卡住的地方。核心配置无非三件事spring-dao.xml配数据源、MyBatis扫描、事务管理器。spring-mvc.xml配注解驱动、包扫描、视图解析器、JSON消息转换。web.xml配Spring容器、SpringMVC的前端控制器、字符编码过滤器。我用的数据库连接池是Druid配置文件里有一行关键参数initialSize5, maxActive20。项目跑起来后一次高并发请求把连接池打满了报“Cannot get a connection, pool exhausted”。排查半天发现是某次查询没有关闭Connection直接在finally里补上关闭问题解决。很多人的配置文件里还会漏掉 MyBatis 的驼峰映射configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings /configuration不加这行student_no在Java对象里永远是student_no而不是studentNo查询结果一大堆值为null代码却完全不报错排查起来特别费劲。3.2 MyBatis动态SQL的加分写法宿舍管理系统的大多数查询都是“多条件筛选”比如查房间时可能按楼栋、楼层、房间号、状态筛选。这种需求必须用MyBatis的动态SQLselect idlistRooms resultTypecom.dormitory.entity.Room SELECT * FROM tb_room where if testbuildingId ! null AND building_id #{buildingId} /if if testroomNo ! null and roomNo ! AND room_no LIKE CONCAT(%, #{roomNo}, %) /if if teststatus ! null AND status #{status} /if /where /selectwhere标签会自动去掉首尾多余的AND或OR这是必须会的写法。另外还有一个铁律能写#{}就不要写${}。${}是字符串拼接有SQL注入风险我在文档里专门用红字标了一句所有用户输入值一律用#{}传参只有表名、排序字段这类“内部白名单”才允许用${}。3.3 Django端的ORM实现与DRF接口Django项目创建流程就是三板斧django-admin startproject dormitory_admin cd dormitory_admin python manage.py startapp student_api然后把student_api加到settings.py的INSTALLED_APPS写Model、迁移。我在Model层遇到的一个典型坑是Django的BooleanField在MySQL 8中默认映射为tinyint(1)而Java端的tb_bed.status用的是tinyint两边默认值如果不一致Java写入1Django读出来可能变成True但JPA和ORM映射又有差异最后统一字段默认值才彻底安稳。DRF实现接口我用的套路是一个ModelViewSet搞定CRUD一个Serializer控制字段输出一个PageNumberPagination子类搞定分页。整套组合拳对新手的友好度远超Java但也暴露一个问题ORM写复杂关联和聚合查询时不如原生SQL直观。所以在统计数据模块我直接用了connection.cursor()执行原生SQL效率最高代码也最清晰。3.4 两套系统共用一个数据库的兼容规则如果按“JavaSSM做主接口、Django做管理后台”的模式落地有几个跨技术栈的兼容问题必须提前定规矩字符集统一utf8mb4排序规则建议utf8mb4_general_ci中英文混排不会乱码。datetime字段两边都用datetimeJava用LocalDateTime接收Django直接用DateTimeField。布尔值字段统一建表时用tinyint(1)Java用Integer接收Django用BooleanField两边映射到MySQL层面是一致的。MySQL时区连接串里显式加serverTimezoneAsia/ShanghaiDjango的TIME_ZONE设为Asia/Shanghai避免差8小时的问题。3.5 那些调试到深夜的经典Bug印象最深的一个Bug是前端提交入住申请后画面停留在“请稍候”后端日志显示报错“BadSqlGrammarException”。排查发现是tb_live_record里的checkin_date字段在MySQL里是date类型而Java端请求参数传的是2025-05-20T10:30类型转换失败。解决方式是前端统一传yyyy-MM-ddJava用DateTimeFormat(pattern yyyy-MM-dd)解析后端再截取时分秒。另一个高频Bug是Jackson序列化循环引用当时做宿舍楼查询接口返回的数据里嵌套了房间列表房间列表又嵌套了宿舍楼结果Jackson直接把递归栈给压爆了。解决方案是在JsonIgnoreProperties标注反向引用或者直接把VO设计成扁平结构不返回嵌套对象。4. 环境配置、部署调试与答辩讲解准备4.1 从零搭建可运行环境Java侧我用的版本是JDK 8、Tomcat 9、IDEA 2023MySQL 8.0。JDK一定不要装太高版本我帮人调过JDK 17跑老SSM项目的Spring版本不兼容时各种反射报错折腾一下午最后换了JDK 8直接好了。Django侧推荐Python 3.10创建虚拟环境python -m venv venv source venv/bin/activate pip install django djangorestframework pymysql qrcode项目启动顺序是先启动MySQL并执行建库脚本再启动Tomcat跑Java后端最后跑Django的runserver。两边端口我约定为Java后端8080Django后台8000。有一个很实用的细节在CORS配置里只需允许前端域名不要用*全开放否则演示现场被跨域问题折磨到崩溃。4.2 调试文档怎么整理才有价值很多同学的“调试文档”就是把异常截图贴上去毫无记忆点。我建议按“现象-原因-解决-验证”四段式记录一个Bug一条记录并在文档末尾附“已解决Bug和解决时间”清单。这套项目交付的调试文档里记录了大概30条问题我随便挑几条展示格式现象原因解决方案前端拿不到后端返回的JSONSpringMVC没配JSON消息转换器在spring-mvc.xml加MappingJackson2HttpMessageConverter并设置UTF-8Django登录接口403未关闭CSRF中间件对API路径应用csrf_exempt或改用JWT认证彻底不依赖CSRF宿舍入住率统计为0统计SQL里入住状态没限定status1修正WHERE条件增加测试数据复验时间字段少了8小时数据库时区和JVM时区不一致统一连接串加serverTimezoneDjango设置USE_TZFalse读一遍这些记录你就能感觉到项目不是“写完就完”而是“踩坑才有经验”。这些内容拿到答辩现场比空洞的“我很熟练”有说服力一百倍。4.3 课件文档和演示讲稿怎么搭如果这项目用于毕设配套说明文档也就是标题里那个LW一般按传统结构写研究背景、需求分析、概要设计、详细设计、系统测试、总结。但要想写得不像模板三个点要下功夫需求分析里画用例图没问题但配合“具体业务场景描述”比如“学生登录后查看空余床位申请入住管理员在后台审批”这种用户故事比干巴巴的用例图有血有肉。详细设计里每个功能模块都给出核心代码片段旁边用一段话解释为什么这么写。我当时是贴了分配床位的SQL和事务控制代码再加上一段性能优化说明老师一眼就看出是真实做过的东西。系统测试部分不要写“系统已通过测试”要把测试用例表完整列出来包含输入数据、预期结果、实际结果至少10条以上。4.4 答辩时的高频追问与回答思路技术答辩时老师大概率会围绕SSM和Django问这几类问题这里提前给一套相对稳妥的答法“Spring里事务的传播行为是什么” 直接结合宿舍分配场景回答分配床位和插入流水必须是REQUIRED传播行为要么都成功要么都不成功不能出现床位变了流水没变的情况。“MyBatis的#{}和${}有什么区别” 结合登录SQL说#{}是PreparedStatement占位符${}是字符串拼接前者防注入。“Django的ORM和MyBatis怎么选” 答案从业务复杂度切入复杂动态查询用MyBatis标准CRUD用Django ORM两者本质都是方便开发。“登录为什么选JWT而不是Session” 答无状态、分布式友好、前后端分离更匹配还能解决Tomcat多实例session同步的问题。5. 常见问题速查与避坑清单5.1 实战高频问题速查表问题现象排查方向解决方案打包部署后中文全部乱码JVM默认编码与源码文件编码不一致IDEA里统一File Encoding为UTF-8Tomcat配置URIEncodingMyBatis查询结果某个字段一直为null忘了mapUnderscoreToCamelCase配置加驼峰映射或SQL里写别名插入记录后拿不到自增主键Mapper XML漏配主键回填使用useGeneratedKeystrue keyPropertyidDjango迁移文件冲突多人改动Model后顺序混乱删除冲突的migration后重新makemigrations但要在已上线数据时慎用高并发下数据库连接池被打满SQL连接未关闭所有获取连接的操作统一在finally释放调大Druid初始连接数前端跨域请求失败后端未配置CORS指定允许跨域路径和来源不要*登录成功后接口仍返回401JWT令牌未在拦截器白名单放行登录和注册接口加入白名单不经过认证拦截宿舍分配出现“重复分配同一床位”并发下两个请求同时查到空闲床位分配操作加数据库行锁或乐观锁版本号报表统计结果和预期不符状态字段过滤条件缺失检查是否遗漏status1这类隐含条件图片上传后无法访问静态资源映射没配SpringMVC配资源映射Django配MEDIA_URL5.2 几条抛开代码的经验心得调试这套系统时我形成了一个习惯每次都先跑最小可复现用例再逐步增加条件。比如床位分配出错我先用一条只查一个房间的SQL复现而不是立刻在完整项目里翻找。这种方法对SSM和Django都适用因为问题定位越聚焦解决速度越快。还有一个容易忽略但很务实的点演示数据要造得有说服力。空宿舍系统查出来全空答辩和演示时观感极差。提前写一个demo_data.sql插入3栋楼、20个房间、100个床位、50个学生把入住率控制在60%统计图表才能展现出来。这个脚本同时也是系统测试的输入标准一举两得。5.3 如果还想继续扩展这个项目宿舍管理系统做到这一步完全可以横向扩展出很多实用方向。比如给管理后台加Excel批量导入导出学生名单一键导入账单按月导出Java用POI、Python用openpyxl两边我都验证过没问题。再比如把门禁模块升级成“宿舍门禁记录大屏”用ECharts展示不同楼栋的进出流量。这些扩展点每一个都能在答辩时成为“后续展望”的素材也不会推翻现有架构。我个人在实际操作中最深的一个体会是这种管理系统类项目真正拉开差距的地方从来不在于“会增删改查”而在于你对业务规则的拆解够不够细对异常情况的处理够不够全。把分配床位时“性别不匹配”“房间已满”“重复入住”这几种异常用统一的提示反馈给用户比在答辩PPT里写一百句“系统功能齐全”都管用。希望这篇拆解能给正在做或准备做宿舍管理系统的同学省下几个通宵把时间花在真正有价值的地方。