前后端分离的学生宿舍管理系统,每年毕业季在Java Web方向的项目列表里出现频率非常高。我刷到过很多次类似标题的源码包,也帮人跑通过不少。这类项目本质上是典型的CRUD加权限控制的业务系统,技术栈固定为SpringBoot + Vue + MyBatis + MySQL,适合用来练手、交课程设计,或者作为简历上的项目经历。市面上流传的大部分“完整源码”其实部署起来并不省心,版本不对、环境不匹配、前端依赖装不上,每一个坑都能卡住人。这篇博文直接把项目拆开讲透,从架构设计、数据库表结构、前后端核心实现,到环境部署和常见问题的排查,一步不落。不管你是打算拿这套系统做毕设二次开发,还是纯粹想搞懂前后端分离项目怎么落地,都能找到能直接用的东西。
1. 项目概述与架构设计决策
1.1 宿舍管理系统到底在解决什么问题
学生宿舍管理这个业务场景,核心是若干个实体之间的关联关系维护。宿舍楼、房间、床位、学生、入住记录、报修单、水电费,这些实体不是孤立存在的,它们之间有着清晰的数据链路。传统的人工台账方式,最头疼的问题是数据更新不及时,学生调宿了、退宿了,纸面记录改起来麻烦,月底统计入住率的时候更是费劲。软件系统的价值就在于把这条链路上的数据统一管起来,让管理员能随时掌握宿舍资源的实时状态。
从功能边界看,一个完整的学生宿舍管理系统至少要覆盖以下模块:
- 宿舍资源管理:宿舍楼、楼层、房间、床位的基础信息维护,床位状态(空闲、已入住、维修中)的管理
- 学生入住管理:安排入住、调宿、退宿、退宿历史记录查询
- 报修管理:学生提交报修单,宿管员接单、派工、完成、关闭
- 水电费管理:按房间记录水电用量,生成费用账单,标记缴费状态
- 公告通知:管理员发公告,学生端查看
- 访客登记:外来人员访问宿舍的登记与记录
- 系统管理:用户管理、角色权限、基础数据字典
如果只做后端接口,做出来的东西没法直观演示,评委或者导师看的时候体验不好。如果只做前端页面,数据全是写死的,又缺乏说服力。前后端分离的好处在于两部分可以独立开发、独立部署,也符合现在企业里主流团队协作模式。这套系统选SpringBoot做后端、Vue做前端、MySQL做存储、MyBatis做数据访问层,是Java技术栈里最稳的一套组合拳,不会有任何“用了小众框架”的争议。
1.2 为什么是SpringBoot + Vue这套组合
先说SpringBoot。SpringBoot的出现大幅降低了Spring应用的配置成本,以前写SpringMVC要配一堆XML,现在一个注解就搞定。我见过不少吐槽SpringBoot版本太高的,明明照着教程写代码,结果注解路径变了、方法签名变了、依赖找不到了。这些问题的根源通常是教程用的2.x版本,而自己下的时候直接拉到了3.x甚至4.x。SpringBoot 3.x把javax换成了jakarta,MyBatis相关的starter也变了,导致很多老教程直接失效。对于宿舍管理系统这类业务,我的建议是锁定SpringBoot 2.7.x这个版本段,稳定、资料多、坑少。
再说MyBatis。有人会问为什么不用MyBatis-Plus,后者确实能减少SQL编写量,但MyBatis-Plus封装程度高,很多初学者用着用着连SQL都不知道怎么写了。面试的时候问到底层原理,答不上来就容易露馅。MyBatis原生XML写SQL,虽然代码量多一些,但每一步数据操作都是明确的,对理解数据访问层的运作机制帮助很大。而且在简历上写“熟练使用MyBatis”比“会用MyBatis-Plus”更有说服力。
Vue这边,Vue 2和Vue 3的选择要慎重。如果教程用的Vue 2,配套的是Element UI,那你的依赖安装命令就是npm install element-ui。如果用的是Vue 3,配的是Element Plus,API有差异,组件引用方式也不同。前后端分离项目里,前端的主要工作是路由配置、状态管理、请求封装和页面渲染。Vue Router负责页面跳转控制和登录鉴权守卫,Axios负责与后端接口通信,这两块是必须吃透的。
1.3 项目目录结构与分层思想
一套能称为“完整源码”的项目,目录结构一定是分层清晰的。后端的标准分层是Controller、Service、Mapper三层。Controller负责接收HTTP请求,做参数校验,返回JSON结果;Service负责业务逻辑编排,比如入住操作需要同时更新床位状态和写入入住记录;Mapper负责与数据库交互,定义接口和SQL映射。实体类放在entity或pojo包,公共返回结果封装成Result类,异常处理用全局ExceptionHandler。有一个很实用的习惯是把配置类和工具类单独放,比如JwtUtil、RedisConfig(如果用到缓存)、MybatisPlusConfig(分页插件)等。
前端部分的目录结构大致是views、router、api、components、layout。Views放页面组件,一个功能模块对应一个文件夹,比如views/dormitory放宿舍管理页面,views/repair放报修管理页面。Router集中管理路由表并配置路由守卫。Api目录里按模块拆分请求方法,每个方法的返回值统一处理成Promise。Components放通用组件,比如分页组件、表单弹窗、上传组件等。Layout是主框架布局,包含侧边栏菜单和导航栏。
项目结构不对,后面写功能代码会越写越乱。我的经验是拿到任何源码包,先花十分钟看目录结构,再跑通构建流程,最后看数据库脚本,顺序不能反。
2. 数据库设计与核心表结构
2.1 核心数据表的设计思路
宿舍管理系统的数据库设计是整个项目的根基。表设计合理,业务代码写起来才能顺畅;表设计混乱,后面写SQL会各种别扭。我无论是在实际指导还是在评审项目时,都特别看重表结构是否能支撑业务扩展。宿舍系统的核心表我列一下:
- d_building(宿舍楼):楼栋编号、楼栋名称、楼层数、房间数、宿管员ID
- d_room(房间):房间编号、所属楼栋、所在楼层、房间类型(四人间/六人间)、床位数量、已住人数、状态(空闲/部分入住/已满/维修)、电表编号
- d_bed(床位):床号、所属房间、状态(空闲/使用中)
- d_student(学生):学号、姓名、性别、学院、专业、班级、手机号、入住状态
- d_checkin(入住记录):学号、房间ID、床位ID、入住时间、退宿时间、状态(在住/已退)
- d_repair(报修单):报修人、房间号、报修类型、故障描述、状态(待处理/处理中/已完成)、提交时间
- d_water_electric(水电费):房间ID、月份、用电量、用水量、电费金额、水费金额、缴费状态
- d_notice(公告):标题、内容、发布时间、发布人
- d_visitor(访客登记):访客姓名、证件号、来访时间、离开时间、被访人、事由
- sys_user(用户):用户名、密码、角色、关联学生或管理员信息
这些表之间的关系是:宿舍楼一对多房间,房间一对多床位,学生入住时关联到某张床位,再通过床位关联到房间和楼栋,形成一条完整的定位链。入住记录表同时保存学号和床位ID,是为了能查历史记录,比如一个学生曾经住过哪栋楼哪间房、搬过几次宿舍,这张表全都记下来。
2.2 字段设计时的几个关键决策
一个是主键策略。很多教程喜欢用自增ID做物理主键,简单直接。但在我实操中更推荐学生表用学号做主键,房间表用“楼栋+房间号”组合逻辑标识,主键用自增ID。原因很简单:业务上通过自然键定位数据方便,但联合主键在作为外键引用时非常啰嗦,自增ID作为内部关联是更工程化的选择。
另一个是时间字段。所有涉及时间的字段,包括入住时间、退宿时间、报修提交时间、公告发布时间,统一用datetime类型存。有的项目喜欢用varchar存时间字符串“2025-05-01 10:00:00”,这样在SQL里做区间查询、按月分组统计水费电费时就非常痛苦,还得用STR_TO_DATE转来转去。时间用datetime,排序和范围查询都很自然。
数据库字符集必须是utf8mb4,而不是utf8。utf8mb4是utf8的超集,支持表情符号和更多生僻汉字,现在MySQL 8.0默认就是utf8mb4,建库的时候注意显式指定。另外常见一个问题:导入SQL脚本后中文乱码,十次有九次是命令行客户端编码和连接编码不一致导致的,这个后面说部署的时候再展开。
2.3 初始化SQL脚本的组织方式
一份好的初始化脚本应该分成三部分:库表结构、基础数据、演示数据。库表结构就是CREATE TABLE语句,基础数据是管理员账号、宿舍楼信息、房间信息这些跑系统必须有的数据,演示数据就是几十个学生信息、几条报修单、几条水电记录,目的让你登录进去之后有东西可看。
我看到过有些源码包把SQL全部写在一个文件里,结构、数据混在一起,导入倒是能成功,但你要单独重置学生数据就很麻烦。自己写项目的时候建议分开三个脚本:schema.sql、init_data.sql、demo_data.sql。MySQL命令行执行source时按顺序执行即可。
3. 后端核心实现:从Controller到Mapper
3.1 JWT认证与登录鉴权的实现逻辑
宿舍管理系统有管理员、宿管员、学生三种角色,接口必须区分权限。这套系统的认证方案我推荐JWT(JSON Web Token),无状态、不依赖Session、后端不需要单独存储登录态。登录成功后后端生成一个token返回给前端,前端每次请求都把它放在请求头里,后端通过过滤器解析token进而识别身份和角色。
具体实现链路是:用户提交用户名密码,查询sys_user表做校验,校验通过后用JwtUtil生成token,token里存userId和role,有效期设24小时。写一个拦截器(HandlerInterceptor)拦截所有接口请求,拦截器里从Header取token,解析失败或过期则直接返回401。对于登录接口和一些公开接口,在拦截器配置类里通过excludePathPatterns排除掉。
这里有个容易踩的坑:JWT依赖的jjwt库版本不同,API变化很大。0.9.x版本用Jwts.builder()方法,1.x版本改成了调用方式不同,签名算法传参方式也不一样。选版本之前先确定你引的jjwt版本,再去查对应API,别拿旧代码直接编译,否则一堆飘红。
角色权限这块,常见做法是在拦截器里验证登录状态,在Service层验证操作权限。管理员可以做全部操作,宿管员只能做宿舍管理相关操作,学生只能提交报修和查看自己的信息。前端区分按钮显示,后端区分是否放行,两层保险。
3.2 学生入住与调宿的事务处理
入住和调宿是本系统最核心的两个业务动作,也是最能体现事务控制的地方。先看入住流程:管理员选择一名未入住学生,选择楼栋、房间、床位,点击入住。后端需要做的事有三件:首先把床位状态改成使用中,其次把房间的已住人数加1,最后往入住记录表插一条记录,状态为在住。这三个操作任何一个失败,整个数据就处于不一致状态。假设床位更新成功了,但入住记录插入失败,床位被占,学生却查不到入住记录,这就出问题了。所以必须用@Transactional注解把这些操作包在一个事务里。
调宿流程更考验设计:一个学生要从A房间搬到B房间,后端先查出他的当前入住记录,把状态更新为已退宿,退宿时间设为当前时间;再把A房间人数减1,床位释放;接着处理B房间,如果B房间的目标床位状态是空闲,就把床位改为使用中,B房间人数加1,最后新建一条入住记录。这个流程操作的表更多,事务一定要开启,否则中间任何一步报错,学生的住宿状态就悬空了。
我反复强调一个观点:写这类代码,先画数据状态流转图,再动手写SQL,比凭感觉写代码可靠得多。入住状态、床位状态、房间状态,三者的变更必须保持一致。
3.3 MyBatis的XML映射与动态SQL写法
MyBatis的Mapper层写法有两种,一种是注解直接写在接口方法上,一种是XML文件写SQL。对于这个项目,我强烈建议用XML方式。原因很简单,查询条件稍微一复杂,尤其是组合条件查询(楼栋+房间类型+状态),用注解拼SQL字符串会让你怀疑人生。XML里 标签配合 标签能优雅地处理动态条件。
比如查询房间信息列表的SQL:
<select id="listRooms" resultType="com.example.entity.Room"> SELECT r.*, b.building_name FROM d_room r LEFT JOIN d_building b ON r.building_id = b.id <where> <if test="buildingId != null"> AND r.building_id = #{buildingId} </if> <if test="roomType != null and roomType != ''"> AND r.room_type = #{roomType} </if> <if test="status != null and status != ''"> AND r.status = #{status} </if> </where> ORDER BY r.building_id, r.floor_num, r.room_no </select>看见没,这样写查询逻辑的灵活性就出来了。查询条件可变,SQL动态拼接,前端只要传不同的参数组合,就能查不同的数据范围。
还有一个实用技巧是resultMap的使用,尤其是多表关联查询返回的结果集包含关联表字段时,用resultMap明确字段映射关系可以省掉很多别名转换的麻烦。
3.4 统一返回结果与全局异常处理
前端拿到的接口返回格式最好是统一的。我自己习惯封装一个Result类,包含code、message、data三个字段。成功时code是200,message是"操作成功",data放业务数据。失败时code是错误码,message是人类可读的提示。这样前端Axios拦截器只需要判断code就能统一处理错误情况,不需要针对每个接口单独写错误逻辑。
全局异常处理用@RestControllerAdvice注解,捕获业务异常、参数校验异常、数据库异常。特别要处理的是SQLIntegrityConstraintViolationException,比如用户删除了一个存在入住记录的房间,数据库主外键约束会报错,不拦截的话前端会收到一大堆堆栈信息,体验非常差。拦截之后转成“该房间存在入住记录,无法删除”这样的友好提示。
4. 前端核心实现:Vue与页面交互
4.1 Vue项目环境搭建与依赖安装的那些事
前端这块,很多小白卡在环境搭建。Node.js和npm是Vue项目运行的基础环境,但Node版本太高或太低都有问题。Vue 2项目建议Node 14到16,Vue 3项目建议Node 16到18。如果你npm install的时候遇到node-sass安装失败,专案十有八九是Node版本太新导致node-sass编译不通过。解决办法是改用sass或dart-sass,或者在package.json里锁定兼容的node-sass版本。
cnpm的使用要谨慎。国内npm install慢是常态,很多教程让换淘宝镜像,这没问题。但cnpm install装的依赖有时会出现node_modules结构不完整的问题,运行起来各种诡异报错。我个人更建议用npm官方源加淘宝镜像的方式,也就是npm config set registry https://registry.npmmirror.com后用npm install,装出来的依赖更干净,遇到问题也好排查。
TypeScript在这里看情况。有的Vue项目模板默认启用TypeScript,如果你对TS不熟,跑起来之后会多很多类型报错的代码。做毕业设计或者练手项目,用JavaScript的Vue模板更省心,不要去碰TS配置,尤其是看到报错failed to load tsconfig '@vue/tsconfig/tsconfig.web.json'这类信息时,直接换模板。这个报错本质上是TS配置引用了找不到的路径,新手处理起来非常消耗时间。
4.2 Axios封装与跨域代理配置
Axios封装,核心是做三层处理。第一层是请求拦截器,统一从localStorage拿token,注入到request header的Authorization字段里。第二层是响应拦截器,检查返回的code,如果是200直接返回data,如果不是则用Element Plus的Message组件弹出错误提示。第三层是401处理,token过期时跳转到登录页并清除本地缓存。
跨域问题是前后端分离项目的必经之路。开发环境里,前端跑在localhost:5173,后端跑在localhost:8080,端口不同,浏览器会拦截跨域请求。解决方案有两个,后端加CORS配置,或者前端开发服务器配置代理。我自己更推荐开发环境用Vite代理,生产环境用Nginx反向代理。配置代理的好处是前端的请求路径不写IP不写端口,全部走/api前缀,由代理转发,环境切换的时候只需要改配置文件。Vite的配置方法是在vite.config.js的server.proxy里设置目标地址和路径重写规则。
4.3 路由守卫与登录鉴权的前端联动
前端路由必须配合登录状态做限制。比如访问系统首页、宿舍管理页面时,如果没有登录,直接跳转登录页。这块用Vue Router的beforeEach全局守卫实现,具体逻辑是:判断目标路由是否需要登录权限,如果需要,就查localStorage是否有token,没有则跳转登录页并携带redirect参数,登录成功之后回到原目标页。
动态路由这块做深度一点的系统可以加上。不同角色登录后看到的菜单不一样,学生不显示宿舍管理菜单,管理员看不到报修提交入口。实现方式有两种:一种是前端写死三个角色的路由表再做合并,另一种是后端接口返回菜单列表,前端动态注册路由。商业项目多用后者,毕设项目用前者就够,因为动态加载路由需要额外处理刷新后路由丢失的问题,处理不好会出现刷新404,反而增加复杂度。
4.4 关键页面的实现要点:房间状态可视化与报修流程
前端页面设计的重点我认为是房间状态可视化。宿舍管理系统是个管理工具,管理员一屏就要看到哪栋楼哪个房间还有空床位。我推荐用卡片或者宫格布局实现房间平面图,绿色代表空闲,蓝色代表部分入住,灰色代表已满,红色代表维修中。点击卡片弹出房间详情抽屉,展示房间信息和入住学生列表。这种交互体验比表格形式直观太多。
报修页面则是典型的一多两少流程:学生提交,宿管员处理,状态变化。学生端页面比较简单,一个报修表单,选择报修类型、填写故障描述、上传图片。宿管员端是报修列表,通过状态Tab切换待处理和处理中的单子,点击处理填写处理结果。这类页面的通用要点是:列表加载用分页,状态变更后调用刷新接口,不要把状态变更的UI操作强依赖在路由跳转上。
5. 环境准备与部署实施全流程
5.1 后端环境准备:JDK、Maven、MySQL版本匹配
环境这块是让源码跑起来的第一步,也是最容易出问题的一步。我见过太多人卡在环境版本上,一连串的报错直接心态爆炸。先说结论:JDK建议用8或11,Maven用3.6.3或3.8.x,MySQL用5.7或8.0,SpringBoot锁2.7.x。
为什么不建议用高版本JDK?因为SpringBoot 2.7.x针对JDK 8和11做过充分测试,到JDK 17虽然能跑,但某些依赖包可能没跟上。SpringBoot 3.x必须JDK 17起,同时整个MyBatis生态的依赖都要升级到对应版本。如果你手里拿到的项目基于SpringBoot 2.x,硬用JDK 17去编译,大概率遇到javax.servlet相关类加载错误。
Maven安装之后,做两件事。第一,配置阿里云镜像,不然从中央仓库拉依赖的速度能让你怀疑人生。修改settings.xml的mirror配置为阿里云仓库地址。第二,确认本地仓库路径,避免C盘塞满。
MySQL的安装,Windows环境一路Next就行,注意选对版本。MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,连接URL需要指定时区serverTimezone=Asia/Shanghai。MySQL 5.7的驱动类是com.mysql.jdbc.Driver。如果代码里驱动类写错,启动直接报ClassNotFoundException。这个是我排查时发现的高频问题,十次里至少有四次是驱动类不匹配。
5.2 数据库导入的正确姿势
拿到SQL脚本后,用Navicat或者MySQL命令行导入都行。命令行导入要注意的是先设置编码再执行source。如果你用Navicat,导入之前先确认连接字符串里选了utf8mb4的编码。最容易踩坑的场景是:在Navicat里执行SQL脚本,表建成功了,数据也导进去了,但中文全部显示乱码。这是因为脚本本身的字符集和客户端会话的字符集不一致。解决方法是执行前先运行SET NAMES utf8mb4,再source脚本。
数据库创建完成后,打开后端的application.yml配置文件,检查数据源配置。重点关注几个点:数据库名、用户名、密码、驱动类、时区。比对这些值和你本地环境是否一致。很多新手直接把原作者的配置原封不动拿来用,连接串里写的IP是远程服务器的,密码是别人设的,跑不起来就开始怀疑代码,其实是配置没改。
5.3 前端依赖安装与开发服务器启动
前端项目启动之前,先看package.json里的scripts字段,一般有dev、build、serve几个命令。开发环境下用npm run dev。
npm install这一步,如果网络不好或者依赖版本冲突,会报各种错。ERESOLVE错误可以通过--legacy-peer-deps参数规避,这是npm 7以上版本依赖树解析策略严格导致的,许多老项目在npm 7+下安装都会遇到。还有一种情况是npm install到一半卡住,重新执行时建议先删除node_modules和package-lock.json再执行,避免残留依赖影响。
Vite启动之后,终端会打印出Local的访问地址,一般是http://localhost:5173。在这个阶段,如果前端页面能显示出来但接口请求失败,先打开浏览器开发者工具看Network面板,查看请求是404还是500还是CORS错误。404通常是代理路径配错;500是后端服务没起或者接口报错;CORS是后端未配置跨域或代理没走对。
5.4 前后端联调与生产部署经验
开发完成之后,如果要把项目部署出去给别人用,有两种常见方式。
第一种最简单,把前端打包的dist目录放到SpringBoot的resources/static目录下,打成Jar包,让SpringBoot直接托管前端静态资源并处理MVC映射,这样打出来的是一个完整可运行的单体Jar,Java -jar一条命令启动。这种方式适合演示和交作业,部署成本最低,但失去了前后端分离部署的意义。
第二种是标准的前后端分离部署,前端nginx托管,后端Java进程独立运行。Nginx配置里做一个location,把/api前缀的请求反向代理到后端服务的地址。前端页面请求的接口路径都是相对路径/api开头,Nginx收到之后转发到后端8080端口。这样做的优势是前端页面任何变化不需要重启后端,静态文件更新直接替换即可。
我推荐初学者先掌握第二种方式,哪怕只是在自己电脑上模拟。因为前后端分离项目的面试考点往往就是部署架构,别人问“前端怎么访问后端接口的”或者“你们的跨域怎么解决的”,你把Nginx反代和开发代理的链路讲清楚,加分效果立竿见影。
6. 常见问题速查与避坑技巧实录
6.1 高频问题汇总表
我把实操中最常遇到的问题整理成一张速查表,方便开发时快速定位。
| 问题现象 | 排查方向 | 解决方案 |
|---|---|---|
| SpringBoot启动报javax.servlet不存在 | SpringBoot 3.x下运行了基于2.x的项目 | 切换SpringBoot版本为2.7.x,或升级项目代码适配jakarta命名空间 |
| 启动报Failed to configure a DataSource | application.yml没有配置数据源或配置项错误 | 检查spring.datasource.url、username、password是否正确 |
| MySQL连接报Public Key Retrieval is not allowed | MySQL 8.0的缓存SHA-2密码插件问题 | URL参数追加allowPublicKeyRetrieval=true&useSSL=false |
| 中文乱码 | 数据库字符集或连接字符集不一致 | 统一为utf8mb4,执行SET NAMES utf8mb4后重试 |
| MyBatis报BindingException | Mapper接口与XML文件绑定失败 | 检查接口全限定名与XML命名空间是否一致、方法ID是否存在 |
| MyBatis报Invalid bound statement | XML文件里没有对应id或namespace错误 | 检查XML文件中mapper命名空间与接口路径匹配 |
| 前端请求接口404 | 代理路径与后端Controller路径不一致 | 确认前端API路径前缀和后端RequestMapping是否匹配 |
| 前端请求接口CORS错误 | 跨域配置缺失 | 后端添加@CrossOrigin或配置CorsFilter,开发环境可用Vite代理 |
| npm install报ERESOLVE | 依赖树解析冲突 | npm install --legacy-peer-deps |
| npm install报node-sass失败 | Node版本与node-sass不兼容 | 卸载node-sass,改用sass或dart-sass |
| 端口占用 | 后端8080或前端5173被占用 | 换端口,或使用lsof/任务管理器查找并结束占用进程 |
6.2 SpringBoot版本过高导致的连锁反应
这个值得单独拿出来说。现在很多人新建项目直接去start.spring.io上勾选最新版本,下载下来的SpringBoot直接是3.x甚至更高。这时候跑旧项目的代码,报错满天飞。
最典型的连锁反应是:项目里import javax.servlet.http.HttpServletRequest,但这个包在SpringBoot 3.x里已经没有了,改成jakarta.servlet.http.HttpServletRequest。然后是MyBatis相关的starter,SpringBoot 3.x下要用mybatis-spring-boot-starter 3.x版本,旧版本不兼容新SpringBoot。再往后,很多第三方组件都没有跟上3.x的适配。做毕业设计或者快速交付的项目,认准2.7.x就够了,没必要追求所谓新版本。如果非要升级,多留出两周时间做兼容性改造。
6.3 一个非常隐蔽的时区问题
这个问题不容易发现但影响巨大。在application.yml里,MySQL连接URL加了serverTimezone=Asia/Shanghai,但Java进程所在的系统时区默认可能不是东八区。这时候用SpringBoot的LocalDateTime接收时间并存入数据库,数据库存的时间跟现实时间可能差8个小时。报修单提交时间比实际晚8小时,水电费月份统计错位,全部乱套。
排查方法很简单,在启动日志里看JVM默认时区。解决方式是启动参数加-Duser.timezone=Asia/Shanghai,或者在application.yml里配置spring.jackson.time-zone=GMT+8。这个坑在Windows上不多见,但部署到云服务器或者Docker容器时经常冒出来。
6.4 前端播放视频文件时的踩坑记录
这个系统偶尔会涉及公告视频、宿舍宣传片之类的需求,有一些项目里嵌入过视频播放功能。如果你要在系统里播放视频,尤其是m3u8格式的流媒体文件,直接用H5的video标签是没法播的。m3u8是HLS协议的分片索引文件,浏览器原生不支持,需要引入hls.js或者video.js库才能播。
我当年弄这个的时候也掉过坑:把m3u8文件直接丢给video标签,结果白屏。排查了半天,发现视频文件是需要转码和分片的,一般流程是先把MP4转成m3u8加ts分片文件,放在Nginx或者OSS上,前端引入hls.js动态加载。这个需求延伸出去就是一套独立的视频处理链路。如果不是硬性需求,我建议在毕设或者练手项目里别加播放功能,加了一定会牵扯出转码、存储路径、防盗链等一系列问题。
6.5 拿到源码之后的必做三件事
每当我拿到一份别人的源码包,我都有一个固定的排查顺序,按照这个顺序走能省掉大半的无效挣扎。
第一件事,全局搜索所有的IP地址和数据库密码配置项,把配置集中确认一遍。很多旧项目里写的是原作者本机的IP,或者某个远程数据库地址,不看一遍全盘跑不起来。
第二件事,确认JDK版本和Maven版本。看pom.xml里的spring-boot-parent版本,对应确认JDK范围。不看这个直接跑,光是Maven编译报错就能耗两小时。
第三件事,确认前端依赖安装的Node版本。看项目里有没有.nvmrc文件,没有的话根据Vue版本判断合适的Node版本段,再执行npm install。这几个版本确认完毕,项目能跑起来的概率至少七成。
7. 一次完整的从零到一运行实录
这一节我用自己的实际操作过程来还原一个完整流程,让你对整体步骤有个更直观的底稿。
我用一台干净的新电脑做测试,系统Windows 11,环境完全空白。第一步装JDK 8,装完配好JAVA_HOME,命令行验证java -version。第二步装Maven 3.8.8,配好M2_HOME,修改settings.xml加入阿里云镜像。第三步装MySQL 8.0,端口3306,密码设置简单点方便开发。
然后创建数据库,执行source命令导入schema.sql和init_data.sql。接着打开后端代码目录,修改application.yml里的数据源密码为本机密码,执行mvn spring-boot:run启动后端。看到Tomcat started on port 8080,说明后端已经就绪。
后端就绪后用Postman测试登录接口,拿到token。确认接口返回正常后,开始装前端。Node装的是16.20.2版本,npm install装依赖大概花了三分钟,随后npm run dev启动,浏览器访问看到登录页。输入管理员账号登录,进入系统首页,可以看到数据统计面板,此时前后端联调全部打通。
整个流程看起来简单,但每一步都有潜在问题。我在新电脑上操作时也碰到过:MySQL 8.0安装到最后一步报错,原因是3306端口被之前残留的旧MySQL占用,卸载干净后重装才成功。还有一次是前端npm install卡在node-sass,直接换了Node 16版本后问题消失。这些经历让我意识到环境问题占整个项目时间的比重远超想象,所以每一条踩坑记录都是值得沉淀的素材。
8. 结尾
宿舍管理系统这个项目,看似只是几个增删改查页面的堆叠,但真正深入进去后你会发现,从数据库表关系的设计,到业务代码事务的控制,到前后端如何配合完成一个完整流程,每一个细节都能挖出值得思考的内容。我自己在一次次跑通类似项目的过程中,最大的体会就是把权限链路和核心业务数据流先打通,再去补页面细节,项目进度会推进得特别顺利。先做登录认证把门守住,再做宿舍分配这条核心链路让数据转起来,最后才是报表、图表、样式这些锦上添花的模块。另外真心建议大家不要过度依赖一键生成代码的工具,手写几遍CRUD后,你对Mapper、Service、Controller之间数据是怎么流转的,会有完全不同的理解。希望这份拆解能帮你把项目跑起来,也让你真正搞懂里面的门道。