
做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 技术栈版本清单与选型理由组件版本选型理由JDK1.8 / 8兼容性最好很多学校机房还是JDK8服务器也容易找SpringBoot2.7.18资料多、稳定性高javax命名空间避免迁移坑MyBatis Plus3.5.3单表CRUD零SQL分页插件好用节省大量样板代码MySQL8.0.x主流版本本地和服务器都方便安装Redis6.x / 7.x存验证码、Token黑名单、热点数据缓存JWTjjwt 0.9.1无状态登录前端存Token后端不需要SessionHutool5.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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 预约单表 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;冲突检测的SQL是这模块的精华。当教师或管理员提交一个新预约时要判断该教室在时间段上是否被其他已通过的状态为1的预约占用。判断逻辑用时间重叠条件的取反来表达// 查询该教室时间段内是否已有冲突预约 LambdaQueryWrapperReservation 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返回结构ResultT所有Controller方法都必须返回它。Data public class ResultT { private Integer code; // 200成功400业务错误401未登录500系统异常 private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT error(Integer code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }配套的全局异常处理用RestControllerAdvice统一拦截业务异常、参数校验异常和兜底的系统异常避免把异常堆栈直接甩给前端。这块在论文“系统实现”里是独立一个小节标题就叫“统一异常处理的设计与实现”内容非常充实。4. 实操过程与关键模块实现4.1 项目骨架搭建与依赖配置用Spring Initializr创建基础工程groupId用com.campusartifactId用campus-platform。核心依赖集中在pom.xml里这里是我实际跑通的配置parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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属性。serverTimezoneAsia/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.11API换成Keys.hmacShaKeyFor写法完全不同。部署文档里建议写明选择旧版本是为了简化代码、减少依赖这样答辩被问到也不慌。生成Token后前端通过axios的请求拦截器统一携带Tokenaxios.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 ResultString 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. 冲突检测 LambdaQueryWrapperReservation 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_NAMEcampus-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.activeprod \ --logging.file.name$LOG_PATH \ /dev/null 21 echo Start success, pid: $!5.2 服务器环境部署全流程服务器我用的是CentOS 7.9以下步骤在部署文档中逐条列出保证小白也能操作安装JDK 8yum 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安装Redisyum 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加?serverTimezoneAsia/Shanghai访问接口返回401Token缺失或过期检查前端有无携带Authorization头重新登录JAR包启动后立即退出端口被占用或配置文件读取失败查看日志文件定位具体异常lsof -i:8080查端口前端页面能开但接口404前后端分离跨域或Nginx路径配置不对检查Nginx的location /api代理配置Redis连不上导致启动失败Redis服务未启动systemctl start redis确认6379端口监听上传图片失败SpringBoot默认上传文件大小限1MB在配置里调大spring.servlet.multipart.max-file-size6.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展示你的开发时间线这是最有说服力的回答。