
做计算机毕业设计的人越来越多选题往往比实现本身更让人纠结。如果你正在考虑“基于Spring Boot Vue的教务管理系统”这个方向或者已经拿到类似题目这篇内容应该能帮你节省很多弯路。教务管理系统属于典型的业务型全栈项目后端用Spring Boot做接口服务前端用Vue搭建页面整个系统围绕学生、教师、课程和成绩四条线展开覆盖了从数据库设计到前后端联调再到打包部署的完整流程。我过去带过不少学生走完这类项目的全过程也帮人排查过很多毕设里奇奇怪怪的问题。这篇文章就基于“Spring Boot Vue教务管理系统”这个题目把整体的架构设计、数据库核心表、后端权限与选课逻辑、前端路由控制、打包部署以及高频踩坑点一次性讲清楚。语言尽量直白能直接抄作业的配置和代码我都会放出来。1. 为什么教务管理这个题目值得做1.1 教务管理系统作为毕业设计的性价比分析选毕业设计题目核心要看三点技术栈主流程度、业务复杂度适中度、答辩时的可展示性。教务管理系统在这三点上都踩得比较准。技术上Spring Boot是现在企业级Java开发的事实标准Vue又是前端领域最流行的框架之一前后端分离的开发模式本身就是当前行业的主流形态。这就意味着你做完这个项目简历上可以写出一整条完整的技术链路Spring Boot、Spring Security或JWT、MyBatis-Plus、MySQL、Vue、Axios、Element UI、Nginx部署。这一串技术名词在校园招聘和日常开发中都有很高的命中率不是那种做完就废的玩具项目。业务上教务管理包含多角色、多权限、多实体关联但又没有复杂到一个人的力量搞不定。管理员管基础数据老师管课程和成绩学生管选课和查分每个角色的功能边界清晰数据库表结构天然具有一对多、多对多的关联关系恰好能体现关系型数据库的设计能力。代码量大概在数千行级别一个人花三到四周时间可以完整开发出来比纯增删改查的管理系统有内容又比电商、社交类项目可控得多。答辩时你可以演示的效果点很多管理员创建一门课程学生端立刻能看到并可选选完课之后生成个人课表老师登录录入成绩学生端马上刷新出分数。这些都是闭环的、可验证的页面操作比对着PPT讲一堆论文框架要扎实得多。1.2 源码、数据库、文档三件套到底该怎么理解“源码数据库文档”这三个交付物分别对应着不同层面的能力考察。源码部分考察的是代码组织能力和工程化习惯。Spring Boot后端要求包结构清晰比如config、controller、service、mapper、entity各司其职Vue前端要求组件划分合理页面、路由、请求封装不能全塞在一个文件里。导师翻代码的时候第一眼看的往往不是业务功能而是目录结构和命名规范。数据库部分考察的是建模能力。教务系统最核心的几张表怎么设计选课关系表如何避免重复选课成绩表如何与课程表和学生表关联这些面试时很可能被追问到。数据库脚本一定要能直接执行MySQL版本要明确字符集和存储引擎要规范不然你发给答辩老师一份跑不起来的SQL脚本会很尴尬。文档部分就是毕业设计论文的辅助材料通常包括需求分析、系统设计、数据库设计、功能测试这几块。注意不要把它写成代码的堆砌导师想看的是你的设计思路和取舍过程比如为什么选JWT而不是Session为什么用MyBatis-Plus而不是JPA这些“为什么”才是文档里最加分的内容。1.3 这套技术方案适合什么样的人如果你是Java基础还算扎实但没什么完整项目经验的同学这个方案是你的舒适区。如果你前端不太熟但后端可以同样可以选因为Vue部分这几年资料极多照着官方文档和成熟模板走一个礼拜能上手。反过来如果你Java基础很薄弱连Maven依赖和Spring Boot的启动流程都没概念又指望三两天速通全栈那这个题目可能会让你比较难受。教务管理系统的代码量摆在那里数据库脚本也有十张左右的表没基础不建议硬上。2. 教务管理系统的业务拆解拿到需求先别急着写代码2.1 三类用户角色决定了功能边界教务管理系统的第一件事不是写登录接口而是把用户角色捋清楚。角色不同权限不同看到的菜单和能操作的按钮也就不同。管理员是整个系统的核心。基础数据维护是管理员的主战场系部、专业、班级、学生信息、教师信息、课程信息、教学计划这些基础数据全部由管理员维护。课程信息包括课程名称、学分、授课教师、上课时间、上课地点这些字段会被后续的选课功能消费。管理员还可以查看系统内的选课数据和成绩数据做一些统计和导出操作。教师角色主要做两件事查看自己的开课列表和个人课表录入和修改自己课程下学生的成绩。这里要注意一个细节教师的操作范围必须被限制他只能看到自己授课的课程和学生名单不能看到其他老师的数据这种边界控制在权限设计时就要规划好。学生角色的操作频率最高选课、退课、查看个人课表、查看已修课程及成绩。学生端是页面最多的地方也是前后端交互最频繁的地方。选课有时间窗口的概念管理员可以设置选课开放时间窗口时间窗口内学生才能操作选课时间窗口外只能查看课表和成绩。三个角色的权限模型画出来就是一个清晰的RBAC基于角色的访问控制模型。前端根据角色渲染不同的菜单和按钮后端根据角色决定接口是否放行两层都要做控制不能只依赖前端隐藏按钮。2.2 业务闭环开课、选课、上课、成绩教务系统不是一个孤立的增删改查集合它有一条完整的业务链建议在开发前先走一遍这条链。管理员先维护基础数据包括班级、学生、教师、课程然后制定本学期的开课计划。开课计划核心是确定这学期哪些课程开放选修。接着管理员发布选课通知学生登录系统查看可选课程列表选课系统自动检查上课时间是否冲突以及是否已经修过该课程。选课结束后教师登录系统查看自己课程的选课名单录入期中期末成绩。学生再次登录可以查看最终成绩和GPA。这条链会直接影响数据库表的设计也会直接影响接口的开发顺序。很多同学一上来就写CRUD写完发现业务串不起来就是因为在数据库设计阶段没有考虑事务和状态流转。比如选课表需要一个status字段来标记已选、退课、已确认这些状态成绩表需要和开课记录关联而不是直接和课程关联。2.3 功能清单与优先级划分从答辩和实用角度出发按优先级把功能分三批。第一批是保底功能没有这些系统无法演示登录/注册/退出、JWT鉴权、学生管理、教师管理、班级管理、课程管理、个人信息修改。第二批是核心业务功能用来体现系统业务价值选课/退课、课程冲突检测、课表查询、成绩录入、成绩查询、开课计划管理。第三批是加分功能有时间就做没时间不影响及格公告发布、学生选课统计图表、成绩导出Excel、教师工作量统计、系统操作日志。每个功能背后都对应一个或几个接口。第一批功能大概需要十五个接口左右第二批加十个第三批加五个总共三十个左右的后端接口足够撑起整套系统。前端页面对应后台管理页面、教师页面、学生页面三大类每个大类下三到五个子页面。3. 技术选型的底层逻辑版本与组件怎么配最稳3.1 Spring Boot选择2.x还是3.xSpring Boot 3.x已经正式发布很久了但站在毕业设计角度我还是建议用Spring Boot 2.7.x。原因很朴素稳定、教程多、兼容性广。Spring Boot 3基于JDK 17而很多学校的教学和考试环境还停留在JDK 8。如果你用了Spring Boot 3本地的JDK版本首先要升级到17Maven插件、MyBatis相关依赖也要跟着调整任何版本不匹配都可能浪费大量时间。Spring Boot 2.7.x基于JDK 8从JDK版本到Spring官方文档再到你在网上搜到的几乎所有中文教程全都是对齐的出问题时能搜到现成的答案。JDK版本选择8或11Maven用3.8.xSpring Boot用2.7.18这是目前最稳妥的毕业设计组合。等答辩结束以后工作中再研究升级3.x也不迟。3.2 Vue 2还是Vue 3Element UI还是Element Plus前端框架的选型纠结程度不亚于后端。我的建议是如果你Vue基础较弱或者希望遇到问题时有海量现成案例就使用Vue 2.7 Element UI 2.x。这套组合在中文开发圈里积累了多年资料几乎你能遇到的每一种报错都能搜到对应的解决方案。如果你对Vue 3的Composition API比较熟悉可以使用Vue 3 Element Plus这是更具前瞻性的选择未来工作岗位上Vue 3的占比会越来越高。但代价是踩坑成本更高比如Element Plus的按需引入配置、Vue 3的响应式原理差异等处理不当会浪费一两天时间。我个人推荐一个折中方案如果你主要是Java后端方向前端只是配合毕设展示选Vue 2.7 Element UI省心最重要。如果你就业目标就是前端或者全栈选Vue 3 Element Plus花时间学一点Composition API对以后工作帮助更大。两种方案都能做出来没有对错只有电量。3.3 JWT还是Session权限方案怎么选传统的单体Web应用大多使用Session Cookie做登录态管理但前后端分离架构下JWT已经成了更顺手的方案。理由有三点第一后端只需要在登录成功后生成一个签名Token返回给前端前端请求时放进请求头里后端无状态验证不需要在服务端保存登录数据。第二JWT里可以直接携带用户ID、用户名、角色这些数据后端拦截器解析Token后就能拿到当前用户身份不用每次查数据库。第三与Spring Boot的整合非常成熟写一个拦截器和处理逻辑就能完成权限校验。Session方案在单体应用里没有任何问题但前后端分离以后跨域场景下处理Cookie会比较麻烦还要额外维护Session存续状态对毕设项目来说没有必要增加这个复杂度。推荐的组件组合是JWT HandlerInterceptor。拦截器拦截需要登录的接口路径解析请求头里的Token校验签名和过期时间通过后把用户信息放入请求上下文。具体到工程里可以配合自定义注解和AOP实现更细粒度的权限控制但毕业设计用拦截器已经足够。3.4 ORM选型MyBatis-Plus还是Spring Data JPA国内Java后端开发使用MyBatis-Plus的比例相当高特别是管理信息系统这类以CRUD为主的项目。MyBatis-Plus的BaseMapper提供了insert、deleteById、selectById、selectPage这些常用方法不需要手写SQL就能完成大部分数据库操作。复杂查询再用注解或XML写自定义SQL保留对SQL的完全控制权。Spring Data JPA在实体映射上更省代码但关联查询和动态SQL处理起来反而不如MyBatis灵活。教务系统里有很多联表查询比如查询学生某个学期的所有课表要同时关联课程表、选课表和排课信息再比如查询老师的授课学生名单要关联教学计划和选课表。这类查询用MyBatis-Plus写起来思路更清晰。我建议直接使用MyBatis-Plus。除了基础的CRUD封装它还提供一个很关键的机制字段填充和逻辑删除。比如创建时间、更新时间可以自动填充学生退课可以配置逻辑删除而不是真的删除记录这些能力在业务开发中用得很频繁。4. 数据库设计核心表结构如何规划才经得起推敲4.1 核心表一览十张表的职责划分教务管理系统建议从最小可行集合开始设计不要一上来就画几十张表。以下十张表足以支撑完整业务闭环每一张都有明确的职责。sys_user表是统一登录表包含id、username、password、real_name、role、email、phone、avatar、status、create_time、update_time。密码保存BCrypt加密后的哈希值不要存明文。role字段使用字符串保存比如ADMIN、TEACHER、STUDENT。student表存学生基本信息字段有id、user_id、student_no、class_id、gender、birth_date、enroll_year等。student表和sys_user表通过user_id做前驱外键关联实现登录账号和学生信息的分离。teacher表类似包含id、user_id、teacher_no、title、department_id、phone等字段。教师所属系部和专业可以单独做department表也可以在teacher表里直接用字符串字段存系部名称看你的需求复杂度。department系部表包含id、name、code三个字段数据结构简单但管理员维护基础数据时会用到。classes班级表包含id、name、department_id、grade、head_teacher等字段。班级属于某个系部学生属于某个班级这套层级关系是教务管理的基础维度。course课程表包含id、name、credit、course_type、description等字段。课程本身是基础数据不直接关联教师和学生。一门课可以由多个教师在多个学期授课这就需要一个承载教学计划的中间表。course_arrangement教学计划表是最核心的桥接表字段包含id、course_id、teacher_id、semester、day_of_week、start_time、end_time、location、max_student_count、status。这张表表达的是“这学期某某老师在某时间某地点开设了某课程”也就是开课记录。学生选课选的是开课记录而不是课程表里的抽象课程。course_selection选课表是关键的关系表字段包含id、arrangement_id、student_id、select_time、status。status标记选课记录的状态比如已选、退课、已计成绩。同一个学生同一门开课记录只能有一条有效记录。score成绩表字段包含id、arrangement_id、student_id、score、grade_level、create_time、update_time。成绩表关联开课记录和学生补考、重修这些复杂情况毕设不需要处理普通成绩就够了。notice公告表包含id、title、content、publisher_id、publish_time用于管理员发布选课通知和系统公告。这张表存在可以让系统首页不至于太空。4.2 选课表为什么必须加联合唯一索引选课系统的核心需求是防止学生重复选择同一门课程。处理这个问题不能只靠后端代码判断数据库层面也要设约束。在course_selection表中可以针对arrangement_id和student_id建立一个唯一的联合索引。该约束保证同一个学生只能有一条有效的选课记录。但要注意如果退课记录了status状态真正的记录仍然存在要准确理解逻辑。更好的方案是在表中增加一个有效标识字段配合唯一索引一起使用。设计时可以在course_selection表中增加一个字段来标记有效状态比如status字段取值ACTIVE和INACTIVE。因为联合唯一索引对有效状态的处理比较麻烦更简单的做法是通过索引限制重复插入时设置对应的处理策略。在MySQL中可以考虑使用其它方式处理退课再选课的情况也可以配合状态字段做复杂的逻辑判断。实际运用中选课逻辑上用事务包裹先检查重复再插入同时数据库层面配合唯一索引作为最后一道防线这个方案最稳。4.3 SQL脚本的关键配置数据库脚本直接用SQL文件交付。以下配置是经验之谈可以降低不少环境问题。数据库使用MySQL 5.7或8.0创建数据库时指定utf8mb4字符集。字段中的中文才不会有乱码问题。每张表的主键建议使用BIGINT类型自增InnoDB存储引擎则是必须的因为选课和成绩表都要靠外键关联和事务处理。如果使用MyBatis-Plus实体类的主键策略可以配置为AUTO。时间字段建议用datetime类型自动填充的create_time字段在插入时使用DEFAULT CURRENT_TIMESTAMPupdate_time字段使用ON UPDATE CURRENT_TIMESTAMP这样后端代码里少写两个时间赋值逻辑。一个标准的建表语句示例如下CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, real_name varchar(50) NOT NULL, role varchar(20) NOT NULL, phone varchar(20) DEFAULT NULL, status tinyint(1) DEFAULT 1, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;course_selection表的联合索引建议如下ALTER TABLE course_selection ADD UNIQUE KEY uk_arrangement_student (arrangement_id, student_id);5. 后端核心实现的几个关键环节5.1 项目结构与统一返回结果后端项目结构按业务分层。controller包只处理参数接收和返回结果service包写业务逻辑mapper层使用MyBatis-Plus的BaseMapper接口entity包对表结构。config包配置拦截器、跨域、MyBatis-Plus分页插件util包放JWT工具类、加密工具类。返回结果统一封装成Result对象结构如下public class ResultT { private Integer code; private String message; private T data; // 省略构造函数和getter/setter }成功时code为200异常时code为500或自定义业务码。前端Axios拦截器拿到结果后统一根据code判断请求是否成功弹出提示信息。这里顺手说一下前端统一处理的好处是后端不用每个接口都写一堆try-catch全局异常处理器能兜底未捕获的业务异常。5.2 JWT拦截器的实现思路用户登录成功后会生成一个Token有效时间建议设置为两小时。JWT的信息结构包含用户ID、用户名、角色在生成Token时用私钥签名。前端将Token存储到localStorage中在每次请求时通过请求头携带。后端拦截器处理逻辑public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取Authorization请求头 String authHeader request.getHeader(Authorization); // 判断请求头是否存在且以特定前缀开头 // 解析Token过期或签名错误则抛出业务异常 // 将用户信息放入request的attribute中 return true; } }拦截器注册时排除登录接口和注册接口其余全部拦截。角色判断在具体的业务方法里通过Service层来实现例如课程添加接口要求管理员角色选课接口要求学生角色。这里注意一个坑放行了登录接口也不要忘了放行一些不需要登录就能访问的公共接口比如验证码接口和公告查询接口否则前端页面初始化时会遇到拦截报错。5.3 选课接口的事务与冲突检测选课是整个系统业务复杂度最高的接口。学生点击选课按钮后后端要做三件事判断该课程的当前已选人数是否达到上限、判断学生选课时间是否与已有课程冲突、执行插入操作并更新已选人数。这三步必须放在同一个事务里不然并发情况下会出现超选和错选。代码逻辑可以参考下面的伪代码Transactional public ResultString selectCourse(Long arrangementId, Long studentId) { // 1. 校验选课时间窗口是否开放 // 2. 判断该开课记录的选课人数是否已达上限 // 3. 查询学生当前有效的选课记录结合开课时间的day_of_week、start_time、end_time // 判断是否时间重叠 String sql SELECT COUNT(*) FROM course_selection cs JOIN course_arrangement ca ON cs.arrangement_id ca.id WHERE cs.student_id ? AND cs.status ACTIVE AND ca.day_of_week ? AND ca.start_time ? AND ca.end_time ?; // 4. 插入选课记录状态设置为ACTIVE // 5. 更新course_arrangement表中的selected_count字段 }时间冲突判断的原理是区间重叠检测。已选课程的时间段是[start1, end1]新课程是[start2, end2]如果start1end2且start2end1就重叠。这个判断条件用一条SQL就能完成不需要把学生课表全查出来在Java内存里比。事务注解推荐加在Service方法上配合MyBatis-Plus自带的事务管理器即可不需要额外配置。需要特别注意的是如果选课人数达到上限要抛出业务异常并回滚事务不能只返回一个错误消息然后事务继续提交。5.4 成绩录入的业务边界教师的成绩录入接口必须校验两件事当前登录教师和该开课记录的teacher_id是否一致以及该学生是否真的选过这门课。这两个校验不做系统就存在越权操作的风险答辩时如果被追问到权限控制边界这也是你文档里可以突出展示的点。成绩表里记录的是分数grade_level字段可以存优、良、中、及格、不及格这类的等级描述。等级可以由后端根据分数自动计算也可以老师手动选择。一般建议后端自动计算保证成绩等级的一致性。成绩录入完成之后学生端查询成绩的信息来源是score表关联course表。学生成绩查询页面展示课程名、学分、分数、等级就是一次简单的联表查询MyBatis-Plus的selectPage配合自定义SQL就能实现。6. 前端实现的几个重点模块6.1 路由权限控制Vue前端路由控制的核心是路由守卫。登录成功后前端拿到用户角色信息动态生成对应该角色的菜单和路由。最简单的实现方式是在路由配置中给每个路由的meta字段加roles数组。比如管理员路由meta.roles包含ADMIN学生页面meta.roles包含STUDENT教师页面包含TEACHER。全局前置守卫中获取当前用户的角色将要访问的路由meta和用户角色做比对不匹配则强制跳转登录页或404页。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); } else if (token) { const userRole localStorage.getItem(userRole); const requiredRoles to.meta.roles; if (requiredRoles !requiredRoles.includes(userRole)) { next(/401); } else { next(); } } else { next(); } });注意一个细节路由守卫只做前端页面跳转的权限拦截真正的安全防线是后端接口的JWT鉴权。前者提升用户体验后者保证数据安全两者缺一不可。6.2 Axios请求封装与拦截器前端发请求统一通过Axios实例来完成这么做的好处是统一配置基础URL、超时时间、请求拦截和响应拦截。在request.js中创建一个Axios实例设置baseURL为/api或/prod-api配置超时时间为10秒。请求拦截器中从localStorage获取Token放入请求头。响应拦截器中统一处理业务状态码code为200直接返回data其他状态码弹出对应错误提示后端返回401时强制退出登录并跳转登录页。封装完成后页面里调用接口就非常简洁// src/api/course.js import request from /utils/request; export function getCourseList(params) { return request({ url: /course/list, method: get, params }); }6.3 表格页面的标准开发模板教务系统的前端页面前提是大量表格和表单。以课程管理页面为例页面就是一个El-Table顶部是搜索条件和新增按钮右侧是编辑和删除操作。配合El-Pagination分页组件通过当前页码和每页条数向后端发起请求。前端分页组件和后端MyBatis-Plus的Page对象对接非常简单pageNum和pageSize两个参数后端返回总记录数和当前页记录列表。这里有一个值得注意的体验优化删除操作要加二次确认成绩提交要提示老师确认选课成功和失败要有明确的通知反馈。这些交互细节在答辩演示时很加分因为评委能直观看到你考虑了业务场景中的真实使用习惯。7. 打包、部署与演示环境搭建7.1 前端打包并集成到Spring Boot开发完成后前端项目在本地通过npm run serve启动开发服务器。联调完成的最终交付版本需要构建成静态文件。npm run build执行后生成dist目录。最简单的集成方式是把dist目录下的文件全部复制到Spring Boot项目的src/main/resources/static目录下然后重新打包后端jar。这样前端页面和接口在同一个服务中提供部署时只需要一个jar包加一个MySQL非常省事。Spring Boot对static目录下的静态文件有默认映射只要接口路径和页面路径不冲突就不会出问题。常见的坑是刷新前端页面时出现404因为直接访问页面路径时后端找不到对应路由。这种情况下可以配置一个控制器把所有非接口路径重新指向index.html但这部分在毕设中可以不做使用hash模式路由可以完全避免这个坑。7.2 基于Nginx的反向代理部署如果希望更接近生产环境在本地使用Nginx部署前端反向代理后端接口。方案如下Nginx监听80端口root指向dist目录location /prod-api/将以prod-api开头的请求转发到后端的8080端口并去掉prod-api前缀。server { listen 80; server_name localhost; root /usr/local/myedu/dist; index index.html; location /prod-api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } }这种部署方式对未来扩展更友好前端和后端可以独立升级也符合企业里前端工程化的常规操作。但毕业设计环境里的兼容性一般如果你在Windows上部署Nginx有时会存在端口占用或路径配置不熟的问题用第一种jar包方式更省心。7.3 答辩演示环境的准备建议答辩当天最怕的不是功能缺失而是环境起不来。强烈建议提前把整套环境准备好并做几次完整的演示走查。本地环境要保证MySQL服务启动、数据库脚本执行成功、后端jar包能启动、前端页面能访问。如果使用前后端分离部署记得排查CORS跨域配置是否正确。如果使用集成后的jar包确保静态资源文件已经拷进resources目录不然前端页面一片空白。实际演示时的机器建议准备一台备用笔记本或者把项目运行在虚拟机中避免现场因为网络环境、端口冲突等问题翻车。考虑到很多答辩教室的电脑配置较低建议提前把项目导成可在本机直接运行的jar包版本避免现场再执行npm install和maven install这种耗时操作。8. 毕业设计高频问题与排查技巧8.1 前端和后端开发的经典报错排查表带学生做这套系统过程中经常遇到以下几类问题我在表格里整理了一份速查。问题现象常见原因处理方案前端npm install卡住网络对默认源访问慢切换npm镜像源使用国内镜像后端启动时端口被占用8080端口被其他进程占用使用netstat命令查找进程并释放端口前端调用接口报CORS错误后端没有配置跨域放行在Spring Boot中实现WebMvcConfigurer添加CORS映射MySQL连接报错提示时区问题MySQL 8.0默认时区配置连接地址加上serverTimezoneAsia/ShanghaiVue用了低版本语法但打包报错构建环境Node版本不匹配检查Node版本建议不低于14打包后前端页面显示404刷新页面路由位置错误配置路由使用hash模式或后端做路由重写中文数据入库后变成问号数据库或表字符集不是utf8修改数据库和表字符集为utf8mb4选课记录重复插入缺少唯一索引或事务处理增加联合唯一索引并确保事务包裹JWT解析报签名不匹配不同工具类生成的Token不校验同一个密钥统一使用同一个密钥和算法配置8.2 数据库层面的严重错误处理数据库出问题在毕设里占比不小很多情况是操作顺序影响。一个典型场景是学生退课之后重新选课由于唯一索引限制插入失败。解决思路前面提到过course_selection表中用status标记有效状态。实际项目采用的方案是当学生退课时把原记录的status改为INACTIVE同时更新开课记录的已选人数减一。重新选课时新增一条ACTIVE记录不受唯一索引影响因为索引里既包含了有效记录也包含了退课记录但业务查询只查有效状态保证了数据一致性。另一个注意点是分页查询。教务系统里列表页非常多MyBatis-Plus分页插件需要单独配置PaginationInnerInterceptor很多同学忘记配置就调用selectPage结果查出全表数据。需要在配置类中注册Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }8.3 时间管理和进度安排最后说一个容易被忽视的维度进度管理。毕业设计周期通常三到四个月容易前松后紧最后一周赶工导致质量崩塌。建议按阶段切分第一到第二周完成需求分析和数据库设计产出SQL脚本第三到第五周完成后端全部基础CRUD和权限控制第六到第七周完成前端页面和前后端联调第八周到第九周集中处理选课、成绩等核心业务的细节第十到第十一周部署、测试、修复Bug同步开始写论文最后两周做答辩PPT和预演。按这个节奏走每天投入三小时左右周末稍微多花一点时间整体压力可控不会出现最后时刻通宵赶代码的被动局面。写得越早预留的缓冲时间越充足尤其要避免体检要求改需求、换技术栈这种伤筋动骨的变更。教务管理系统这个题目的上限其实不低。如果你能把选课冲突判断、JWT权限、动态路由这些点做扎实再补充一些数据可视化或Excel导出功能放在毕设里绝对是中上水平。这套经验也只代表我个人的实操心得每个人的基础和能力都不同你可以根据自己情况灵活调整技术栈和功能范围把项目打磨成能体现自己真实水平的作品。