资讯动态

SpringBoot+Vue宿舍管理系统全栈项目实战:从数据库设计到部署上线

发布时间:2026/10/5 8:24:27 来源:尧图企业网站定制
写这个项目的时候我连续泡在机房两个通宵把SpringBoot和Vue之间那点交互逻辑彻底捋了个透。宿舍管理系统是Java Web毕设里最经典、也最容易被问住题目的方向之一——学生会查、宿管要管、辅导员要审批一套系统把角色权限数据库设计前后端分离这些核心考点全占了。所以这个完整项目源码加上SQL脚本和接口文档正好适合正在找毕设项目的同学、以及想快速上手SpringBootVue全栈开发的初学者。我把整个项目的设计思路、核心模块、部署流程和一些踩过的坑完整梳理了一遍从环境搭建到上线联调每一步都拆开讲清楚。这个项目不是那种只给个压缩包就完事的资源。源码、SQL脚本、接口文档三件套配齐说白了就是把你从看视频觉得会了到真正跑起来自己改之间那段路直接铺好。我尽量把每一个设计决策背后的原因也写出来这样就算你后面要拆成别的系统比如图书馆管理系统、实验室预约系统也能顺着这套思路改。1. 项目整体设计与技术选型思路1.1 为什么是SpringBoot加Vue这套组合宿舍管理系统这种典型的管理信息系统核心需求就是两个字增删改查。听起来简单但加上不同角色看到不同界面审批流程要流转数据要能统计这些现实约束之后前端要有交互流程后端要管事务逻辑两头都得硬。我选SpringBoot的理由很简单它能让我把精力放在业务逻辑上而不是浪费在配置XML和搭框架上。SpringBoot内置了Tomcat写好Controller就能直接跑配合MyBatis-Plus单表CRUD几乎不用写SQLSpring Security做登录认证和角色鉴权官方文档和社区例子都足够多毕设答辩被问到也答得上。Vue这边我选了Vue 2。我知道Vue 3已经稳定好几年了但做毕设有个现实考量Vue 2的Element UI组件库特别成熟表格、表单、弹窗、分页这些管理后台常用的组件全都有现成封装写起来效率非常高。加上网上的教程和二手资料多遇到问题一搜就能找到解决方案。如果后续想升Vue 3组件库换成Element Plus风格基本一脉相承迁移成本其实不高。1.2 宿舍管理系统的需求拆解很多同学拿到毕设题目就直接开写写到一半发现权限乱了、数据对不上、流程走不通。我拆解需求的时候习惯先画一张角色和权限的网格图这里不说用专业工具拿Excel就能做功能模块学生宿管员辅导员/管理员宿舍查询只看自己宿舍看管辖楼栋全部数据入住/退宿提交申请审核并分配查看记录调宿申请提交申请审核处理审批报修管理提交/确认完成派单/处理查看统计晚归/归寝记录查看个人登记/管理查看统计学生信息管理查看个人维护本楼管理系统全部系统管理账号/角色无权限部分权限完全权限数据统计与导出无权限本楼数据全校数据这个网格理清楚之后后端接口怎么设计就一目了然了——每个角色对应一套接口权限前端路由用动态路由做控制后端在SpringSecurity的Filter链里做统一校验。防君子也防小人前端隐藏入口只是体验层面的真正兜底的还是后端权限校验。1.3 项目结构规划项目分为后端dormitory-backend和前端dormitory-web两个独立目录这也是大多数前后端分离项目的常规做法。后端的包结构我按MVC经典三层来划分com.dormitory ├── config // 配置类放WebMvcConfig、SecurityConfig ├── controller // 控制层接前端请求不写业务逻辑 ├── service // 业务层接口加实现类 ├── mapper // MyBatis-Plus的Mapper接口继承BaseMapper ├── entity // 数据库表对应的实体类 ├── dto // 接收前端参数的对象避免直接暴露实体类 ├── vo // 返回给前端展示的对象例如宿舍详情 ├── utils // 工具类JWT工具、日期处理等 └── common // 通用结果封装、全局异常处理前端的src目录按Vue项目惯例来src ├── api // 按模块拆分的接口请求文件 ├── assets // 静态资源 ├── components // 公共组件如上传组件、分页组件 ├── router // 路由配置动态路由在这里维护 ├── store // Vuex管理登录状态和用户信息 ├── views // 页面组件按模块放子目录 ├── utils // 封装的axios实例、工具函数 └── App.vue // 根组件这个结构有一点值得说明dto和vo分开而不是共用一套对象。我见过很多项目让前端传过来的参数直接绑定实体类结果把createTime、status这种后端才应该管的字段也暴露给了前端。严格区分入参和出参第一个好处是安全第二个好处是Swagger生成的接口文档更清晰。你可以多传我不管但我不想接收的字段你传了我也忽略。2. 数据库设计与SQL脚本的落地要点2.1 核心数据表的设计思路SQL脚本是整个项目的基石表关系设计得好不好直接决定了后面写代码是顺水推舟还是处处补丁。宿舍管理系统的核心表我梳理成了这几类第一类是基础信息表包括用户表sys_user、楼栋表dormitory_building、宿舍表dormitory_room、床位表dormitory_bed。第二类是业务流转表包括入住记录表、调宿申请表、退宿记录表、报修单表和归寝记录表。第三类是系统支撑表包括角色表、菜单表和角色菜单关联表。说一下sys_user表的设计CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, sex tinyint(1) DEFAULT NULL COMMENT 性别 0未知 1男 2女, student_no varchar(20) DEFAULT NULL COMMENT 学号, phone varchar(11) DEFAULT NULL COMMENT 手机号, role tinyint(1) NOT NULL DEFAULT 3 COMMENT 角色 1管理员 2宿管 3学生, building_id bigint(20) DEFAULT NULL COMMENT 宿舍楼ID, room_id bigint(20) DEFAULT NULL COMMENT 宿舍ID, bed_id bigint(20) DEFAULT NULL COMMENT 床位ID, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态 1正常 0停用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;这里有个细节密码字段用varchar(100)而不是varchar(32)因为BCrypt加密后的字符串长度是60位MD5加密才是32位。我见过有人用固定32位字段存BCrypt结果结果怎么测都登录失败。宿舍和床位的设计我用了宿舍表床位表两张表而不是直接在宿舍表里放已住人数和容纳人数。后来项目做完复盘这个设计有两个好处一是每个床位可以单独关联一个学生二是查空床位时直接查bed_id为null的用户即可不用做复杂的统计。当然这个方案会带来一个小问题如果某学生退宿后床位被释放但室友间换了床位关联关系怎么处理这在后面调宿模块里单独解决了。2.2 SQL脚本里那些容易被忽略的细节第一个坑建库语句建议显式指定字符集CREATE DATABASE IF NOT EXISTS dormitory_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;很多人直接在Navicat里点新建数据库默认字符集可能是latin1或者utf8中文存进去没问题但查出来显示乱码排查半天其实字符集没对齐。用utf8mb4不是为了花哨是因为以前的utf8字符集在MySQL里最多3个字节存不了emoji和个别生僻字utf8mb4才是完整版。第二个坑导入SQL脚本的方式要和MySQL版本匹配。MySQL 5.7和8.0在默认认证插件上不一样mysql_native_password和caching_sha2_password如果你用的是8.0的库但程序连接时用的驱动版本是5.x就会报Public Key Retrieval is not allowed这个错。解决方式要么在JDBC连接串里加allowPublicKeyRetrievaltrue要么给用户指定旧的认证方式。第三个坑脚本末尾不要忘了加索引和初始化数据。尤其是菜单表sys_menu和角色菜单关联表如果不初始化前端动态路由配了也白配——登录之后菜单不显示很多人第一反应是代码写错了实际是SQL脚本少了两行INSERT。SQL脚本文件我用注释分好段落每个段落能独立执行方便排错-- 1. 创建数据库 -- 2. 创建表结构按依赖顺序先建基础表再建业务表 -- 3. 初始化基础数据管理员账号、默认角色、菜单权限 -- 4. 测试数据仅供开发环境使用上线前记得清理初始化管理员账号的密码写的是123456但存到库里的是BCrypt加密后的值。我在脚本注释里写清楚了明文密码和对应关系不然你自己导入脚本后拿明文密码去登录肯定失败。这个细节对拿到项目第一次跑通的同学特别重要。2.3 表关系设计里最精髓的一张表宿舍分配这块我用了一张被很多人忽视的关联表——宿舍事务记录表。所有入住、调宿、退宿、维修确认都在这张表里留痕。整个系统的关联字段如果不建约束随意删数据后面报表统计会很难看。做的过程中我保留了关键外键约束并且索引了外键字段ALTER TABLE dormitory_check_record ADD KEY idx_building_id (building_id), ADD KEY idx_room_id (room_id), ADD KEY idx_student_id (student_id);索引不是越多越好但查询频繁的字段值得加。我遇到过慢SQL就是查宿舍报表时没走索引导致全表扫描几万条数据就把接口拖到两三秒。加了索引之后直接回到几十毫秒这一点后面在优化章节还会再提。3. 后端核心模块设计与实现3.1 登录认证与JWT令牌机制Spring Boot做毕设项目的登录大概有几个流派靠Session、靠Spring Security加Session、靠JWT。我做这套系统选用的是JWT方案因为前后端分离项目的常规姿势就是这样后端签发Token前端每次请求在请求头里带Authorization: Bearer token后端拦截器校验Token并解析出用户信息。JWT的核心逻辑并不复杂关键就三步// 1. 登录成功后生成Token String token Jwts.builder() .setSubject(username) // 用户名 .claim(role, user.getRole()) // 角色 .setExpiration(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000)) // 24小时过期 .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); // 2. 在Spring Security的过滤器中校验Token String token request.getHeader(Authorization).replace(Bearer , ); Claims claims Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); // 3. 解析出用户信息后放入SecurityContext UsernamePasswordAuthenticationToken auth new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth);这里有一个比较关键的经验SECRET_KEY不要用太短的字符串至少32位最好是字母数字混带一点特殊字符不然生成签名时容易被反解出来。密钥放在配置文件里别写死在代码里。虽然毕设项目不会真的被攻击但是从写代码的习惯上养成安全思维是好事。还需要注意的是Token过期策略我给前端约定的是后端返回code401表示登录已过期前端axios拦截器收到这个状态码就自动跳转登录页并清除本地缓存。如果不做这个约定用户Token过期后项目还会继续请求每次返回都是错误信息界面却还停留在操作页体验很差。3.2 宿舍分配与调宿的业务逻辑这个模块是整个系统里最容易写乱的部分因为牵扯到的事务比较多。分配床位时我要保证三个一致性学生当前未被分配、宿舍未满员、目标床位是空床。同一时间只能有一个线程把这个床位分出去否则并发请求会造成超员。我在代码里用一个简单但可靠的策略悲观地先在数据库里把床位标记为已占用再更新学生关联。核心逻辑是这样Transactional(rollbackFor Exception.class) public Result assignRoom(AssignRoomDTO dto) { // 1. 检查学生是否已有宿舍 if (userService.hasRoom(dto.getStudentId())) { return Result.error(该学生已分配过宿舍); } // 2. 用UPDATE判断床位是否空闲CAS思想锁住这行记录 int update bedMapper.updateStatus(dto.getBedId(), 0, 1); // 参数: where status0 and id? if (update 0) { return Result.error(该床位已被占用请刷新后重试); } // 3. 关联学生和床位 userService.bindRoom(dto.getStudentId(), dto.getRoomId(), dto.getBedId()); // 4. 写入分配记录 recordService.addRecord(new CheckRecord(...)); return Result.success(); }第2步里那个UPDATE语句是关键它利用数据库行锁把查询并更新这个非原子操作变成了原子操作。很多初学者在这里犯的错误是先用SELECT查床位是否为空再用UPDATE去改状态——中间隔了几微秒但并发场景下依然会出问题。毕设答辩时把这个逻辑讲清楚通常能让你高出同学一大截。调宿流程类似但多了一步释放旧床位再分配新床位。我把这两个操作放在一个事务里要么都成功要么都失败回滚。注意这里是先分配新床位成功再释放旧床位释放失败会回滚学生不会出现两头空的情况。3.3 报修模块和晚归记录模块的后端实现要点报修模块看起来简单但隐藏着一个状态机的设计问题。我记得自己第一次实现的时候用了一个整数字段status取值1、2、3分别代表待处理处理中已完成然后用if-else去判断状态流转。代码写了不少逻辑还容易漏。后来我换了一种方式状态变更直接由Service层来控制Controller只负责接收请求不直接去改状态字段。核心就是一张状态流转表防止非法跳转操作原状态目标状态允许的角色提交报修无待处理学生接单待处理处理中宿管完工确认处理中已完成宿管验收关闭已完成已关闭学生学生提交报修单时我还做了个防抖处理同一宿舍同一时间段内比如2小时内只能提交一次同类报修防止恶意刷单。这个逻辑在Service层实现// 查询最近2小时内同一宿舍同一类型是否已有待处理报修单 long count repairMapper.selectCount(new LambdaQueryWrapperRepair() .eq(Repair::getBuildingId, buildingId) .eq(Repair::getRepairType, repairType) .eq(Repair::getStatus, 待处理) .ge(Repair::getCreateTime, DateUtils.addHours(new Date(), -2))); if (count 0) { return Result.error(该宿舍已有同类待处理报修单请勿重复提交); }晚归记录模块更简单一点就是一个登记加查询。但提醒一下学生端和宿管端看到的字段最好有区别。学生端不需要看到登记人是谁宿管需要看到这个通过一个VO对象来切换即可没必要写两套接口。4. 接口文档与前端Vue的协作方式4.1 接口文档怎么设计才不至于前后端互相扯皮这个项目自带一份接口文档我把文档和源码放在同级的docs目录下包含接口说明、请求参数、响应示例和错误码。整个项目接口统一遵循RESTful风格模块前缀用/api区分。一个标准接口的约定是这样的比如宿舍查询GET /api/room/list?buildingId1pageNum1pageSize10keyword501响应统一格式{ code: 200, message: success, data: { total: 20, list: [ { id: 1, buildingName: 1号楼, roomNo: 101, beds: 6, usedBeds: 5, status: 已满, score: 4.5 } ] } }我在写接口文档时遵循三个原则语义化URL。用名词描述资源比如/api/student/info不用动词如/api/getStudentInfo。明确泛化返回结构。所有接口统一返回一个ResultT对象包含code、message、data三个字段。不用约定俗成的直接返回对象出错抛异常这套因为前端处理完全没有统一入口。标注权限要求。文档里每个接口都标清楚学生可调用宿管可调用管理员可调用前端做调试切换身份时就不会糊涂。写文档的工具我推荐直接用Swagger注解挂在接口上然后导出成Markdown或者HTML页面省得另维护文档文件后来跟代码脱节。Swagger注解写起来也不麻烦几个核心注解就把参数说明、返回值说明覆盖了。4.2 Vue前端项目的脚手架搭建和路由设计前端项目的搭建可以借助Vue CLI的交互命令但实际上手写一把也很快。我用Element UI做组件库用Axios发请求用Vuex管理用户状态用Vue Router做路由。路由权限是动态的。这里有个常见误区很多同学在路由表里把所有页面全部注册然后靠v-ifrole1控制页面显示。这种做法的问题在于虽然页面上看不到入口但通过改路由或者直接访问路径还是能进到页面里。真正的动态路由是在前端登录后根据后端返回的权限菜单列表动态注册对应路由组件// 路由守卫中根据用户角色过滤路由 router.beforeEach((to, from, next) { if (!store.getters.token) { if (to.path /login) { next(); } else { next(/login); } } else { // 拉取菜单 - setRoutes动态注册 - 确保刷新后不白屏 if (store.getters.routes.length 0) { store.dispatch(generateRoutes).then(routes { router.addRoutes(routes); next({ ...to, replace: true }); }); } else { next(); } } });这个思路能保证前端路由和服务端的菜单权限保持同步刷新页面时也不会因为路由丢失而白屏。我的经验是路由权限这块是前端代码里最值得花时间调的部分也是容易在答辩现场被问到的部分。4.3 前端Axios封装与跨域问题前端封装Axios是必要的。我封装了一个request.js统一处理BaseURL、Token注入、错误提示和401跳转const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }); // 请求拦截器注入token service.interceptors.request.use( config { if (store.getters.token) { config.headers[Authorization] Bearer store.getters.token; } return config; }, error Promise.reject(error) ); // 响应拦截器统一处理code和401 service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { Message.error(res.message || 请求错误); return Promise.reject(new Error(res.message || 请求错误)); } return res; }, error { if (error.response.status 401) { store.dispatch(logout).then(() { router.push(/login); }); } Message.error(error.response.data.message || 网络异常); return Promise.reject(error); } );跨域这块在开发环境用Vue CLI的代理很方便// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } };生产环境有两个处理路径一是后端加CrossOrigin或全局CORS配置二是把前端打出来的dist目录直接放到SpringBoot的src/main/resources/static下同一个端口访问自然没有跨域问题。这个方向热搜词里也提到了vue打包放进springboot我在部署章节会重点说明。5. 部署流程与联调实录5.1 本地开发环境与配置检查跑这个项目的环境是JDK 8或11也行、Maven 3.6、MySQL 5.7、Node.js 14。先依次填好这几处配置再往下走。后端配置在application.yml里server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/dormitory_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里说一个比较容易让人卡住的地方serverTimezone参数。MySQL 8.x的驱动对时区敏感不配这个参数启动时经常报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。我看到评论区不少同学卡在这个错上其实就是两个解决办法一个是在JVM参数里加-Duser.timezoneGMT8另一个是URL里配上serverTimezoneAsia/Shanghai。前端环境配置在根目录的.env.development和.env.production两个文件里# .env.development VUE_APP_BASE_APIhttp://localhost:8081/api VUE_APP_MOCKfalse后端的接口前缀和前端配置要对应上不然请求路径会404。如果后端Controller的RequestMapping是/api/room前端请求应该写/room/listAxios的baseURL里已经带了/api。5.2 从源码到跑通的完整操作步骤给第一次接触这个项目的人一个完整步骤照着做不要跳步先用Navicat或命令行工具执行dormitory_db.sql脚本导入数据库。导入之后能看到14张表左右可以在sys_user表里查一下管理员账号是否初始化成功。用IDEA打开dormitory-backend目录等Maven把依赖下载完。依赖下载慢的话在settings.xml里把镜像源换成阿里云镜像不然等到天荒地老。改配置文件里的数据库用户名和密码。密码千万记得改成你自己的不然启动到一半报Access denied for user很多人都忘在启动之前先检查这一项。启动SpringBoot应用。看到日志里出现Started Application in xx seconds并且没有异常后端就起来了。用VS Code或WebStorm打开dormitory-web目录在终端执行npm install。这里提醒一下安装依赖卡住时不要反复CtrlC先看是不是镜像源问题。执行npm run serve浏览器访问http://localhost:8080用管理员账号登录。管理员账号初始值我通常在文档里写明比如admin / 123456。学生测试账号可以自己注册或者直接用脚本里的测试数据。如果一切正常登录后第一眼看到的应该是系统首页左侧菜单按角色动态渲染。如果左侧菜单空荡荡先回数据库确认菜单表和角色表的数据在不在。5.3 将Vue打包放进SpringBoot中的集中部署有不少同学问过开发环境的跨域代理解决了部署到服务器或者给老师演示时怎么办。最省事的是把Vue打包后的dist目录放进SpringBoot的静态资源目录然后后端同时管接口和页面。打包命令npm run build打包完成后dist目录里有index.html和static子目录。我把dist里的文件整体拷贝到SpringBoot项目的src/main/resources/static目录下然后重新打包后端。这样部署后访问http://服务器IP:8081直接就能看到前端页面。但有一个坑必须提醒前端路由用的history模式刷新非首页路由时会404。比如我访问/system/user刷新页面后端返回404因为后端找不到对应的Controller。在SpringBoot里解决方法是把404错误重定向到index.html页面Controller public class IndexController { RequestMapping(value /**/{path:[^\\.]*}) public String redirectToIndex() { return forward:/index.html; } }这个Controller的目的是让前端路由刷新时不落空。如果你觉得这个方案有点绕也可以把前端路由改成hash模式URL会带上/#/标记刷新时就不存在404问题。两者各有利弊我个人的习惯是history模式体验更好多写一个转发Controller也不算麻烦。5.4 配置Maven项目构建与常见启动报错很多同学第一次用Maven构建SpringBoot项目会遇到依赖下载失败或者JDK版本不匹配。先说JDK版本项目pom.xml里明确写成parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.8/version relativePath/ /parent这个版本能跑在JDK 8上且不会出现SpringBoot 3.x那种强制要求JDK 17的尴尬。如果你的本机只有JDK 17建议还是装个JDK 8或者11不然踩兼容性坑得不偿失。热搜词里提到springboot版本太高导致的问题大概率就是指SpringBoot 3.x的这些兼容成本。项目里如果用到MyBatis-Plus、Shiro这类老牌组件官方适配SpringBoot 3还需要多一个桥接依赖毕设没必要冒这个险。Maven构建时还容易遇到报错Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin。原因多半是编译级别不对pom里我设置了properties java.version1.8/java.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties如果IDEA里设置了项目的SDK是17但pom要求1.8会有一个互不匹配的报错。在项目结构里把Project SDK和Modules的Language level统一改成8基本就能消掉。Maven项目的构建还有一种顺序误区先改了pom文件依赖再手动去下载jar包其实完全没必要。直接在IDEA右侧Maven工具窗口双击clean和install依赖会自动拉取。构建完成后target目录下会出现dormitory-0.0.1-SNAPSHOT.jar整个SpringBoot项目就可以用java -jar运行了。6. 常见问题排查与避坑心得6.1 前后端联调时最常见的四个报错我整理了一份排查速查表整理了我自己做这个项目时踩过以及被别人追着问过的问题现象可能原因排查方向前端请求接口404API前缀不一致检查request.js里的baseURL和后端RequestMapping前缀前端请求接口200但数据为空跨域被拦截打开浏览器F12看Console有没有CORS报错登录提示用户名密码正确但进不去Token没放进请求头检查axios拦截器里Header拼写Authorization别写错数据库中文乱码连接串字符集不对URL里加characterEncodingutf8useUnicodetrue查询报Unknown column xxxSQL脚本和实体类字段名不一致检查MyBatis-Plus的驼峰映射配置下划线和驼峰要对应前端页面刷新后404历史和模式问题使用history模式时后端加转发Controller或者改hash模式启动报Consider defining a bean of type xxxMapperMapper接口没加扫描注解启动类加MapperScan(com.dormitory.mapper)报Field id doesnt have a default value主键没有设置自增检查表的id字段AUTO_INCREMENT是否设置插入时是否显式给了id这些排查有一个共同原则先在前后端分离的边界上定位问题。请求到了后端没有看后端日志就没有对应访问记录问题多半出在路由或代理请求到了后端但响应异常看异常堆栈再决定是SQL问题还是业务问题。6.2 SpringBoot版本和高版本JDK的兼容性取舍做这个项目时我特意把SpringBoot版本固定在2.7.x而不是最新版。有一个热搜词是springboot版本太高这个我太有感触了。选型时的新版本往往意味着新特性但也有很多连带问题SpringBoot 3.x最低要求JDK 17很多学校机房、老师电脑上还是JDK 8。SpringBoot 3.x里javax.servlet变成了jakarta.servlet很多老教程里的import javax.servlet直接报错。MyBatis-Plus、Shiro这类生态组件的适配版本起初没跟上遇到问题在国内社区能搜到的答案也少一些。做毕设的原则是稳定压倒一切。SpringBoot 2.7.8足够新修掉了前代版本一堆漏洞同时能兼容JDK 8/11。如果读者自己手里有别的项目非要切到SpringBoot 3也要先检查一遍所有第三方依赖是不是有支持SpringBoot 3的版本再动手改代码。6.3 SQL脚本导入失败和SQL性能优化经验导入SQL脚本时如果报错最常见的两种一是脚本里的SQL用了MySQL 8.0特有的语法比如窗口函数而在5.7上不支持。另一个是导入时某张表已经存在没有DROP TABLE IF EXISTS语句。我给的脚本里都处理过这两点但如果你在自己电脑上导入失败先看具体报错行号往往就在这两类问题里。SQL性能优化上我做这个系统最深刻的体会是统计接口的SQL才是测试的大头。宿舍入住率报表要跨表查询sys_user关联dormitory_room按楼栋分组统计每个宿舍的已住人数。没建索引之前这个接口响应在2秒左右建了联合索引后直接掉到几十毫秒。索引不是银弹但针对高频查询建索引改善效果立竿见影。-- 常见慢SQL及其优化思路 -- 原对楼栋名做了模糊查询并分组全表扫描 SELECT r.building_id, COUNT(r.id) FROM dormitory_room r WHERE r.building_id LIKE %1% GROUP BY r.building_id; -- 调整先过滤出需要的楼栋ID再分组统计 SELECT u.building_id, COUNT(*) FROM sys_user u WHERE u.building_id IN (SELECT id FROM dormitory_building WHERE name LIKE %1号楼%) GROUP BY u.building_id;这段优化并不高深实际工作中最常用的手法尽量在索引字段上做等值匹配少在索引字段上用函数和模糊前缀匹配避免回表次数过多。6.4 上手这个项目源码的正确打开方式最后贴点实用心得拿到别人的项目源码先别急着双击运行更别急着删掉重写。建议按这条路径读一遍代码先读SQL脚本花半小时把表结构关系和初始化数据理解透。再从后端启动类入手看config包下的配置类了解哪些拦截器生效、哪些配置项可以调。接着顺着一个完整业务流程登录→查询列表→提交申请→管理员审批读代码把Controller、Service、Mapper这条链路串起来。最后打开前端项目对着页面元素找对应API请求两边对照着看才算真正吃透项目。我见过不少学员跟着视频敲代码的时候挺熟练考试完就忘。这个项目里最值得研究的其实是宿舍分配那一段的并发控制、动态路由的权限设计以及报修流程的状态机设计把这三块搞明白比把整个项目重抄一遍管用得多。自己做毕设或者面试作业时建议先把系统跑起来再按上面路线读一遍核心代码最后选一个模块试着改改功能。比如把宿舍管理改成实验室预约你会发现改流程的时候才真正理解为什么表要这样设计、接口要这样拆分。改动过程中遇到报错对照我前面整理的排查表百分之八十的问题都能自己定位解决。对我来说写这套系统的最大收获不是跑起来了而是理解了为什么会这样——为什么数据库要设计五张关联表为什么宿舍分配要用乐观锁为什么路由权限不能只在前端控制。把这些为什么都想通了毕业设计的答辩也好以后在工作中真正接手业务系统也好才算是真正入了Java Web的门。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑