做Java毕设或者课设的时候,“基于SpringBoot的校园平台综合服务系统”绝对算是高频选题了。它不追求业务复杂度,但覆盖面恰到好处,从用户登录、角色权限,到课程查询、教室预约、二手交易、失物招领这类校园日常服务,都能在一个工程里展开,既能体现技术功底,又能撑起一整篇论文的章节结构。这篇博文我打算结合自己实际做过的一个完整交付项目(源码+lw+部署文档+讲解材料),把从需求拆解、技术选型、数据库设计、核心代码实现,到服务器部署、答辩讲解的完整链路都过一遍。不管你是准备拿它当毕设、拿来练手,还是想了解这类项目怎么从零组织起来,都能从里面直接抄到能用的东西。
我经手的这个版本不是简单的CRUD堆砌,而是把学生、教师、管理员三种角色真正区分开,权限控制落到接口级别。项目交付时包含完整源码、毕业论文(lw)、部署文档和讲解PPT,这也是目前大多数学校对毕设类项目的标准要求。后面所有内容都基于这套实际方案展开,参数和代码都是我跑通后整理出来的,可以直接参照落地。
1. 项目整体设计与需求拆解
1.1 校园平台综合服务系统到底在解决什么问题
校园里学生和老师的高频需求其实很零碎:查课表、找空闲教室、发布闲置物品、丢东西找失物、看学院通知。过去这些事分散在教务系统、QQ群、公告栏各个角落,体验很差。这个系统的核心目标,就是用一个统一的Web平台把这些场景收拢起来,让不同角色各取所需。
从需求分析角度,我把功能拆成了三层:
- 学生端:登录注册、课表查询、成绩查询、教室预约、二手交易(发布/浏览/留言)、失物招领(发布/认领)、修改个人信息。
- 教师端:课程信息维护、所授课程成绩录入、教室预约审批、公告发布。
- 管理员端:用户管理、院系与专业管理、教室资源管理、预约单审核、二手交易内容审核、数据统计。
这个划分方式在毕设论文里非常好写,每一层对应一个功能模块,模块之间通过角色权限隔离,逻辑清楚,画用例图和数据流图都顺手。你可以把它看成一套带权限控制的业务系统,而不是单纯的增删改查练习。
1.2 交付物不止是代码:源码、lw、部署文档三者怎么配合
提这个标题的时候,很多同学会忽略“lw”和“部署文档”的分量。lw是毕业论文(LunWen)的缩写,它跟源码是相互印证的:论文里的“系统设计”章节要放架构图、数据库ER图、核心接口设计,这些内容必须和源码实际实现一致,否则答辩时一问就穿帮。
- 源码:解决“系统是怎么实现的”,是技术能力的直接证据。
- lw(论文):解决“为什么这么做”,包含选题背景、需求分析、系统设计、实现与测试,逻辑要自洽。
- 部署文档:解决“怎么跑起来”,从JDK安装到数据库初始化,一步步操作。导师和评审通常不会去看代码,但一定会照着部署文档去试运行。
- 讲解材料:答辩时用的PPT和演示脚本,核心是讲清楚业务痛点、技术选型理由、关键模块实现。
实际经验是:四者必须在同一份Git仓库里管理,源码每个模块的命名、数据库脚本的表名、部署文档里的配置项三处要严格对应。我见过太多项目源码完整但论文里的ER图画错表关系,或者部署文档里的SQL文件名和实际脚本对不上,答辩直接扣分。
1.3 需求边界怎么控制才不会被项目拖死
做这类综合服务系统最常见的翻车点,是功能越加越多,最后变成一个四不像。我的建议是砍掉所有“只做展示不做闭环”的功能。比如“论坛”模块:发帖、回帖、楼层、置顶、删帖、敏感词过滤,这一套下来工作量翻倍,但论文里只能写两三页,性价比极低。
我在需求阶段就明确了两个原则:
- 每个功能必须有三层闭环:用户操作、后台管理、状态变更记录。二手交易要有“发布->待审核->上架->下架”,教室预约要有“申请->审批->通过/驳回->使用”。
- 数据表数量控制在12张以内。这个规模对毕设论文来说是舒服的,ER图一页画得下,MySQL初始化脚本不会超过200行,管理员端的统计SQL也容易写。
如果你想把界面做得更漂亮,可以把前端独立成Vue工程,但功能边界我建议严格按上面来。技术上可以多炫技,业务上别贪多,这是这类项目能按时交付的关键。
2. 技术栈选型与核心架构解析
2.1 为什么是SpringBoot而不是其他方案
选SpringBoot做这类系统,最大的理由不是它“流行”,而是它把大量繁琐的配置工程化掉了。传统SSM项目要写一堆XML配置,数据源、事务、MyBatis映射都要手动组装,对单人开发的项目来说根本没意义。SpringBoot的自动配置机制,能做到“依赖一加,约定即成”,开发效率完全不在一个量级。
它的生态成熟度也是实打实的:Spring Security做权限、Redis做缓存、MyBatis Plus做数据访问、JWT做无状态登录,这些组件在SpringBoot体系下都有官方或社区成熟的整合方案。遇到问题基本一搜就有答案,对时间紧张的毕设项目来说,这是最大的隐性成本优势。
另外要提一点:SpringBoot 3.x和2.x的选择要提前定下来。2023年以后新项目普遍用3.x,但它基于Jakarta命名空间,部分旧教程里的javax.*包名全部要换成jakarta.*,数据库驱动、MyBatis Plus的版本都有对应要求。如果你参考的论文或博客是2.x写法,直接照搬会报一堆编译错误。我这次用的是SpringBoot 2.7.18,原因很简单:网上资料最全,踩坑成本最低。
2.2 单体架构还是微服务,这类项目怎么判断
对校园平台综合服务系统这种量级,微服务是纯粹的负资产。微服务的价值在于独立部署和弹性伸缩,代价是分布式事务、服务治理、链路追踪一堆复杂度。单机运行的校园系统引入微服务,论文里写起来好看,但部署文档会变成灾难,答辩时一个“服务注册中心挂了怎么办”就能问倒一片。
单体架构在这个场景下的优势很明确:一套应用搞定所有接口,事务直接依赖数据库天然保证,部署就是一个JAR包加一个MySQL实例。系统并发量就是校园几百人同时用,单体能轻松扛住,完全不存在性能瓶颈。
架构层面我做了适度分层,保证代码不写成大泥球:
- Controller层:只做参数校验和结果封装,不写业务逻辑。
- Service层:业务规则、事务控制、模块间调用都在这层。
- Mapper层:MyBatis Plus的BaseMapper,复杂查询用注解SQL或XML。
- common模块:统一返回结果、异常处理、工具类、JWT工具、全局配置。
这个分层的好处,体现在写论文的“系统设计”章节时,可以直接画一张四层架构图,每一层的职责、关键类、调用关系都能写清楚,评阅老师会认为你是有工程意识的,而不只是会写Controller里堆SQL。
2.3 技术栈版本清单与选型理由
| 组件 | 版本 | 选型理由 |
|---|---|---|
| JDK | 1.8 / 8 | 兼容性最好,很多学校机房还是JDK8,服务器也容易找 |
| SpringBoot | 2.7.18 | 资料多、稳定性高,javax命名空间避免迁移坑 |
| MyBatis Plus | 3.5.3 | 单表CRUD零SQL,分页插件好用,节省大量样板代码 |
| MySQL | 8.0.x | 主流版本,本地和服务器都方便安装 |
| Redis | 6.x / 7.x | 存验证码、Token黑名单、热点数据缓存 |
| JWT | jjwt 0.9.1 | 无状态登录,前端存Token,后端不需要Session |
| Hutool | 5.8.x | 工具类库,ID生成、日期处理、加密等,少写很多工具方法 |
关于前端,我提供两套方案:一套是Thymeleaf服务端渲染,适合纯后端同学;另一套是Vue3 + Element Plus前后端分离,适合想展示全栈能力的。两套共享同一套后端接口,部署方式不同。下面的实操部分会以Vue3作为主前端方案,因为现在毕设答辩时前后端分离已经是默认印象分项了。
3. 核心模块设计与数据库建模
3.1 用户权限模型:三种角色如何落到代码里
权限设计我采用的是“RBAC简化版”,不建五张标准表,而是用角色字段加Spring Security的注解控制。原因很简单:系统里功能权限是按角色整体划分的,粒度到角色就够,不需要角色-权限多对多。
用户表设计:
CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '姓名', `role` varchar(20) NOT NULL COMMENT '角色: STUDENT/TEACHER/ADMIN', `student_no` varchar(20) DEFAULT NULL COMMENT '学号,教师和admin可空', `email` varchar(50) DEFAULT NULL, `phone` varchar(20) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `status` tinyint DEFAULT '1' COMMENT '1启用 0禁用', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段role直接决定了这个用户能进入哪些Controller和Service。在Spring Security的配置里,我用@PreAuthorize("hasAnyRole('ADMIN','TEACHER')")这类注解去锁接口,既拦截未登录的匿名请求,也顺带做角色校验。这样做安全性和代码简洁度平衡得最好,答辩时也能讲清楚RBAC的最简落地方式。
密码加密必须用BCrypt,这个点务必要在论文里体现。Spring Security自带的BCryptPasswordEncoder每次加密都会生成随机盐,同样的密码存进数据库是两个完全不同的密文,能有效防御彩虹表攻击。绝对不要用MD5存储,现在MD5碰撞已经不是新闻了,评审老师看到MD5基本会直接问密码安全问题。
3.2 核心业务表设计:教室预约和二手交易的数据闭环
教室预约是整个系统里最能体现业务逻辑的模块,也是我论文里的核心章节。它的核心是要处理好“时间冲突检测”,涉及三张表。
-- 教室表 CREATE TABLE `classroom` ( `id` bigint NOT NULL AUTO_INCREMENT, `building` varchar(50) NOT NULL COMMENT '教学楼编号,如A栋', `room_no` varchar(20) NOT NULL COMMENT '房间号', `capacity` int NOT NULL COMMENT '容纳人数', `has_projector` tinyint DEFAULT '0', `has_air_conditioner` tinyint DEFAULT '0', `status` tinyint DEFAULT '1' COMMENT '1可用 0维护', PRIMARY KEY (`id`), UNIQUE KEY `uk_building_room` (`building`, `room_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 预约单表 CREATE TABLE `reservation` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `classroom_id` bigint NOT NULL, `purpose` varchar(200) DEFAULT NULL COMMENT '预约用途', `start_time` datetime NOT NULL, `end_time` datetime NOT NULL, `status` tinyint DEFAULT '0' COMMENT '0待审核 1已通过 2已驳回 3已取消', `audit_user_id` bigint DEFAULT NULL COMMENT '审批人', `audit_remark` varchar(200) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_classroom_time` (`classroom_id`, `start_time`, `end_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;冲突检测的SQL是这模块的精华。当教师或管理员提交一个新预约时,要判断该教室在时间段上是否被其他已通过的状态为1的预约占用。判断逻辑用时间重叠条件的取反来表达:
// 查询该教室时间段内是否已有冲突预约 LambdaQueryWrapper<Reservation> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Reservation::getClassroomId, classroomId) .eq(Reservation::getStatus, 1) // 只查已通过的 .and(w -> w .lt(Reservation::getStartTime, endTime).ge(Reservation::getEndTime, startTime) );这个lt(endTime)+ge(startTime)就是经典的时间段重叠判断。开始时间早于新预约结束时间,且结束时间晚于新预约开始时间,两者一定重叠。这段可以原样写进论文,再配一张时间轴重叠示意图,评审一看就懂。
二手交易模块则简单直接:商品表加订单意向表。商品表包含标题、描述、图片、价格、发布人、状态(在售/已下架/已成交);订单意向表在有人留言或点击“我想要”时生成一条记录,含买卖双方ID。这个模块不太需要复杂设计,重点是状态流转展示清楚。
3.3 统一返回结果与全局异常处理
接口设计如果不规范,前端对接就是灾难。我定义了一个统一的JSON返回结构Result<T>,所有Controller方法都必须返回它。
@Data public class Result<T> { private Integer code; // 200成功,400业务错误,401未登录,500系统异常 private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }配套的全局异常处理,用@RestControllerAdvice统一拦截业务异常、参数校验异常和兜底的系统异常,避免把异常堆栈直接甩给前端。这块在论文“系统实现”里是独立一个小节,标题就叫“统一异常处理的设计与实现”,内容非常充实。
4. 实操过程与关键模块实现
4.1 项目骨架搭建与依赖配置
用Spring Initializr创建基础工程,groupId用com.campus,artifactId用campus-platform。核心依赖集中在pom.xml里,这里是我实际跑通的配置:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这里有个细节要注意:mysql-connector-java在SpringBoot 2.7里不需要写版本号,但显式写上8.0.33更安全。还有一个坑是mybatis-plus-boot-starter与SpringBoot 2.7完全兼容,但如果你手滑用了3.5.7以上版本,部分分页插件API有调整,所以我锁定3.5.3,稳。
application.yml里最关键的是数据源和MyBatis Plus配置:
spring: datasource: url: jdbc:mysql://localhost:3306/campus_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case必须开,否则数据库的real_name映射不到Java的realName属性。serverTimezone=Asia/Shanghai不加的话,MySQL 8连接会报时区错误,这个问题在部署文档里一定要写。
4.2 JWT登录鉴权的完整实现思路
登录鉴权我用的是Spring Security + JWT组合,核心流程分四步:
- 登录接口接收用户名密码,用
AuthenticationManager做认证,认证成功则用JwtUtil生成Token返回前端。 - 前端把Token放在请求头的
Authorization字段里。 - 后端加一个
JwtAuthenticationFilter,继承OncePerRequestFilter,从请求头解析Token,解析成功就把用户信息放进SecurityContext。 - 配置
SecurityConfig,放行登录、注册、静态资源等匿名接口,其余全部要认证。
JwtUtil的关键代码:
@Component public class JwtUtil { private String secret = "campus-platform-secret-key-please-change-in-production"; private long expire = 24 * 60 * 60 * 1000L; // 24小时 public String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }注意jjwt 0.9.1依然用SignatureAlgorithm,如果是新版jjwt 0.11+,API换成Keys.hmacShaKeyFor,写法完全不同。部署文档里建议写明选择旧版本是为了简化代码、减少依赖,这样答辩被问到也不慌。
生成Token后,前端通过axios的请求拦截器统一携带Token:
axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config })拿到401响应时,统一跳转到登录页,并清除本地Token。这个小细节我也会写进部署文档的“联调测试”章节,因为每次答辩演示时最容易出的问题就是Token过期后页面白屏,有这个拦截器至少能优雅跳转而不是报错。
4.3 典型业务实现:教室预约的Service层
教室预约的Service层是这个系统里业务逻辑最厚的部分,包含了时间冲突检测和事务控制。我把核心方法贴出来:
@Service public class ReservationService { @Transactional(rollbackFor = Exception.class) public Result<String> createReservation(ReservationDTO dto, LoginUser loginUser) { // 1. 校验时间合法性 if (dto.getStartTime().isAfter(dto.getEndTime())) { return Result.error(400, "开始时间不能晚于结束时间"); } if (dto.getStartTime().isBefore(LocalDateTime.now())) { return Result.error(400, "不能预约过去的时间"); } // 2. 校验教室存在且可用 Classroom classroom = classroomService.getById(dto.getClassroomId()); if (classroom == null || classroom.getStatus() != 1) { return Result.error(400, "教室不存在或当前不可预约"); } // 3. 冲突检测 LambdaQueryWrapper<Reservation> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Reservation::getClassroomId, dto.getClassroomId()) .eq(Reservation::getStatus, 1) .and(w -> w .lt(Reservation::getStartTime, dto.getEndTime()) .ge(Reservation::getEndTime, dto.getStartTime()) ); long count = this.count(wrapper); if (count > 0) { return Result.error(400, "该时间段教室已被预约"); } // 4. 创建预约单 Reservation reservation = new Reservation(); BeanUtil.copyProperties(dto, reservation); reservation.setUserId(loginUser.getUserId()); reservation.setStatus(0); // 待审核 this.save(reservation); return Result.success("预约申请已提交,等待管理员审核"); } }这段代码在答辩时非常能打。评审看到@Transactional知道你有事务意识,看到时间重叠判断知道你不是只会CRUD,看到状态初始化为待审核知道你有完整业务状态机思维。三个点全是得分点。
教室预约的状态流转我建议做成一个枚举类,在论文里用表格把状态机画出来:0待审核 -> 1已通过 / 2已驳回,1已通过 -> 3已取消。状态机画清楚,数据表里status字段的注释也写明白,代码里的魔法数字减到最少。
4.4 前后端联调与跨域处理
使用Vue3 + Element Plus作为前端时,跨域是绕不开的问题。开发阶段我在SecurityConfig里配置了CORS规则:
@Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/register", "/api/announcement/list").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); return request -> config; }setAllowCredentials(true)和addAllowedOriginPattern("*")组合,能兼容前端携带Cookie的场景。虽然JWT一般不用Cookie,但部分浏览器环境下不配置这个会出问题,加上保险。
生产环境则用Nginx反向代理,前端静态资源由Nginx托管,/api路径转发到后端8080端口。这样不仅解决了跨域,还提升了访问速度,部署文档里会给出完整Nginx配置示例。答辩演示时直接用生产IP访问,比本地localhost:8080显得专业很多。
5. 部署打包与文档撰写要点
5.1 SpringBoot项目打包与配置分离
我建议采用“一行命令出两个包”的策略:先用Maven打包出可执行JAR,再把application-prod.yml独立放到服务器上,通过启动参数指定外部配置文件。这样本地和服务器用不同数据库配置,互不干扰。
打包命令:
mvn clean package -DskipTests执行完会在target目录下生成campus-platform-0.0.1-SNAPSHOT.jar。检查一下文件大小,正常应该在50MB以上(因为SpringBoot内置了Tomcat)。如果只有几百KB,说明没有打成可执行JAR,多半是spring-boot-maven-plugin没配好。
服务器的/prod目录结构我习惯这样规划:
/opt/campus/ ├── app/ │ ├── campus-platform.jar │ └── config/ │ └── application-prod.yml ├── logs/ │ └── campus-platform.log └── deploy.sh启动脚本deploy.sh内容:
#!/bin/bash APP_NAME=campus-platform JAR_PATH=/opt/campus/app/campus-platform.jar LOG_PATH=/opt/campus/logs/campus-platform.log # 找到并杀掉旧进程 PID=$(ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}') if [ -n "$PID" ]; then kill -9 $PID echo "Stop old process: $PID" fi # 启动新进程 nohup java -jar $JAR_PATH \ --spring.profiles.active=prod \ --logging.file.name=$LOG_PATH \ > /dev/null 2>&1 & echo "Start success, pid: $!"5.2 服务器环境部署全流程
服务器我用的是CentOS 7.9,以下步骤在部署文档中逐条列出,保证小白也能操作:
- 安装JDK 8:
yum install -y java-1.8.0-openjdk - 安装MySQL 8:配好yum源后
yum install mysql-server,启动并设置root密码 - 初始化数据库:
mysql -uroot -p < /opt/campus/sql/campus_db.sql - 上传JAR包和
application-prod.yml - 安装Redis:
yum install redis,启动并设为开机自启 - 改配置文件里的数据库地址和密码
- 执行
./deploy.sh启动 - 用
curl http://localhost:8080/api/announcement/list测试接口响应
服务器上配置Redis主要是为了会话和缓存,但要注意修改Redis默认端口或设置密码,至少把requirepass加上。这点也值得在论文安全章节里写一笔,体现安全的考虑。
如果要做得更专业,可以用systemd托管服务,而不是裸跑nohup。部署文档里我给了两种方式,nohup适合快速演示,systemd适合长期运行和开机自启。
5.3 论文(lw)的章节组织与答辩讲解节奏
论文写作我按这样的章节结构安排,评审挑不出结构性毛病:
- 第一章 绪论:背景与意义、国内外研究现状、论文结构安排。
- 第二章 相关技术介绍:SpringBoot、MyBatis Plus、JWT、Redis、Vue。注意这里每项技术写两段就行,要结合本项目怎么用,不要写成百度百科。
- 第三章 系统分析:可行性分析、需求分析、用例图、功能模块图。
- 第四章 系统设计:架构设计、数据库设计(ER图加表结构说明)、接口设计。
- 第五章 系统实现:核心功能模块的代码截图加文字说明,教室预约和权限控制是重点。
- 第六章 系统测试:功能测试用例表格、部分性能测试结果、测试结论。
答辩讲解时间一般是10到15分钟,我的节奏安排是:前面一分钟快速讲背景和意义,三分钟讲技术选型理由(重点讲为什么选SpringBoot、怎么解决权限问题),五分钟现场演示核心模块(登录、教室预约完整流程、后台审批),最后两分钟讲部署方式和不足与展望。全程不要念PPT,重点放在代码实现思路和现场演示上。
6. 常见问题与避坑经验实录
6.1 高频问题排查速查表
| 问题表现 | 原因 | 解决方案 |
|---|---|---|
启动时报Access denied for user | 数据库账号密码错误或权限不足 | 检查application.yml,用命令行mysql测试登录 |
启动时报Unknown database | 数据库没创建 | 先执行CREATE DATABASE campus_db DEFAULT CHARACTER SET utf8mb4; |
| 连接MySQL报时区错误 | JDBC URL没加serverTimezone | 加?serverTimezone=Asia/Shanghai |
| 访问接口返回401 | Token缺失或过期 | 检查前端有无携带Authorization头,重新登录 |
| JAR包启动后立即退出 | 端口被占用或配置文件读取失败 | 查看日志文件定位具体异常,lsof -i:8080查端口 |
| 前端页面能开但接口404 | 前后端分离跨域或Nginx路径配置不对 | 检查Nginx的location /api代理配置 |
| Redis连不上导致启动失败 | Redis服务未启动 | systemctl start redis,确认6379端口监听 |
| 上传图片失败 | SpringBoot默认上传文件大小限1MB | 在配置里调大spring.servlet.multipart.max-file-size |
6.2 我在做这类项目时踩过的坑
第一个坑是MyBatis Plus的LambdaQueryWrapper里的时间条件。我在冲突检测里最初写成了lt(start_time, startTime).gt(end_time, endTime),结果导致边界时刻的预约判断错误,两个预约一个结束时间等于另一个开始时间时被误判冲突。后来改成区间重叠判断的标准写法才算彻底解决。这个坑我写进了论文的测试章节,反而成了加分项。
第二个坑是Spring Security的放行配置。初始版本我把所有/api/**都设为permitAll(),结果登录接口能通,但其他接口也没鉴权了,等于裸奔。后来改用antMatchers精确放行白名单,其余全部走JWT过滤器。
第三个坑是前端跨域的allowCredentials(true)与allowedOrigin("*")并存时,部分浏览器会拒绝请求。必须用allowedOriginPattern("*")替代allowedOrigin("*"),这个细节不踩一次真的发现不了,搜索资料时也很少有人提到。
第四个坑是打包后本地能跑、服务器起不来。排查半天发现是JDK版本不一致:本地JDK8编译的包跑到服务器JDK11上出现了兼容问题。统一两端JDK版本后一切正常,所以部署文档里我会特别强调“请先确认服务器JDK版本与本地一致”。
6.3 几个值得写在论文里的小亮点
如果你想在答辩时多拿几分,可以主动在系统里加两个不算复杂但“有想法”的点:
- Redis缓存公告列表和教室列表:首次查询从数据库加载,之后走Redis,设置10分钟过期。代码量很小,但可以讲清楚缓存穿透和缓存一致性的基本概念。
- 操作日志记录:用一个
@LogAnnotation注解加AOP切面,记录谁在什么时间做了什么操作。controller层加一行注解就能落地,论文里能写两页AOP原理。
这些点本质上都不难,但能让评审认为你思考过“非功能性需求”,这比功能堆砌更拉好感。
写在最后
这个项目我前前后后做了三周,第一版只求功能跑通,第二版才把权限、缓存、日志、部署脚本这些工程化细节补齐,而真正让答辩顺利通过的反而是第二轮,因为技术深度和工程规范性都在那里了。做这类SpringBoot校园平台系统,最忌讳的就是只盯着Controller写CRUD,把Controller堆到两三千行,而把权限、事务、异常处理、部署方式全丢到一边。源码、论文、部署文档、讲解PPT这四样交付物,本质上是同一个系统从四个角度的呈现,代码写得再漂亮,如果论文里的架构图画的是另一个系统,那还是白搭。
如果你正在准备类似的毕设,我的建议是:先把数据库脚本和数据字典定下来,再动手写代码;项目结构按Controller、Service、Mapper严格分层;部署文档从零开始一步步写,能做到一个没接触过这个项目的人照着操作就能在服务器上访问系统,这就合格了。框架代码可以直接从成熟项目里借鉴,但业务模块一定要亲手写一遍,不然后面答辩问细节的时候真的答不上来。
最后分享一个小技巧:把源码连同部署脚本放到一个Git仓库里,每个模块完成就提交一次,最后统计commit记录时你会拥有一份非常漂亮的开发过程证据。答辩时如果老师问“这个系统是你自己做的吗”,你可以打开Git log展示你的开发时间线,这是最有说服力的回答。