☰
前后端分离的课程管理系统:SpringBoot+Vue+MySQL开发与部署实践
2026/10/21 22:16:30 网站建设 项目流程

1. 项目功能全景与核心设计思路

1.1 这个系统到底在解决什么问题

先说结论:这是一个典型的“前后端分离 + 基础权限模型 + 核心业务CRUD”教学级项目。我之前接触过好几个类似的课设题——在线选课、在线考试、课程管理、教学资源管理,本质上是同一套东西换了一层皮。这个系统把核心业务收敛在“课程”这个实体上:管理员负责基础数据维护,教师负责课程内容与作业发布,学生负责选课、学习和提交作业。三方角色各管一段,权限边界清晰,业务逻辑完整度足够撑起一份毕业设计论文。

很多人拿到这类题目会犯一个毛病——一上来就抢着做前端界面,表格画得漂漂亮亮,结果后端接口对不上,数据表设计漏洞百出,等联调的时候才发现要推翻重来。真正合理的顺序应该是:先梳理角色和权限边界,再画数据表关系,最后才写代码。这套系统的角色模型很干净,无非就是三张权限视图:

  • 管理员:用户管理、课程审核或下架、全局数据统计
  • 教师:创建课程、上传课件、发布作业、批改打分
  • 学生:浏览已上架课程、选课退课、观看视频、提交作业、查看成绩

如果把这个业务模型映射到数据库表,你大概需要开头就能想到七八张表。我当时给这个项目设计的表结构有用户表、课程表、选课记录表、作业表、作业提交表、公告表、课件表,加起来差不多十张表。不要觉得表多,表之间的关系撑起来的业务数据流动,才是论文里“系统设计”章节的素材来源。

1.2 技术选型为什么是 SpringBoot + Vue + MySQL

我从一个带过不少新人做毕设的角度,认真说一下技术选型背后的逻辑。SpringBoot 解决的是后端“配置地狱”的问题——没有满屏的 XML 配置文件,内嵌了服务容器,依赖管理用 Maven 或 Gradle 一把梭。相比传统 SSM 框架需要手动整合 Spring MVC 和 MyBatis,SpringBoot 用自动配置把 90% 的样板工作吞掉了,对毕设级别的项目来说能省出大量时间写业务代码。

前端选 Vue 的原因也比较现实。Vue 在国内面试和课设里都是主流,学习曲线平缓,模板语法直观,配合 Element UI 或 Element Plus 组件库可以快速组装后台管理界面。再加上 Vue Router 做前端路由,Axios 做 HTTP 请求,Vite 做构建工具,整个前端工程化的闭环就通了。这完全是一套“拿简历上写出去不吃亏”的技术组合。

数据库选 MySQL 没有悬念。对这种典型的 Web 管理系统,关系型数据库在数据一致性、事务支持、联表查询上的优势是 NoSQL 替代不了的。比如选课操作需要扣减课程剩余名额,同时插入一条选课记录,这种操作必须在一个事务里完成。MySQL 的 InnoDB 引擎配合 Spring 的声明式事务,写起来既简洁又安全。

1.3 功能模块拆分方案

按照功能域划分,这个系统可以拆成以下模块:

  • 用户认证模块:登录、注册、JWT 令牌签发与校验、密码加密存储
  • 用户管理模块:管理员对用户进行增删改查、角色分配
  • 课程管理模块:课程信息的增删改查、上下架状态切换
  • 选课业务模块:学生选课、退课、选课列表查询、防重复选课校验
  • 作业模块:教师发布作业、学生提交作业、教师批改与打分
  • 公告模块:系统管理员或教师发布教学公告

这种模块划分好处在于,每个模块都可以独立写进论文的“系统功能”章节,同时也能对应到后端的 Controller、Service、Mapper 三层结构。对毕业设计而言,功能粒度太粗会显得工作量不足,太细又会拖慢开发进度,上面的拆分刚好处于一个合理区间。

2. 数据库表设计与后端核心实现

2.1 数据表关系设计与字段规划

数据表是整个项目的根基,表结构定得不合理,后面写代码时处处难受。我直接给出这套系统核心表的字段设计思路,你可以根据自己的业务场景微调。

用户表(sys_user)是整套权限体系的锚点,常见的字段有:id、username、password、real_name、role(student / teacher / admin)、avatar、create_time。密码字段存的是 BCrypt 加密后的哈希值,绝对不允许明文存储。角色字段用字符串枚举比用数字更直观,查问题时不需要翻字典。

课程表(course)记录了课程静态信息:id、course_name、teacher_id、course_desc、cover_image、video_url、status(0下架/1上架)、selected_count、max_count、create_time。注意 teacher_id 要和用户表关联,这个字段体现出教师和课程的从属关系。视频地址的字段建议存相对路径,不要拼完整 URL,后续部署换域名会省很多事情。

选课记录表(course_selection)是学生和课程之间的多对多关系表:id、course_id、student_id、select_time。为了防止学生重复选课,在 course_id 和 student_id 上建联合唯一索引。这条索引会在数据库层面兜住重复数据,后端代码里也要做二次判断,双层保险。

作业表(homework)和作业提交表(homework_submit)是最体现联表查询设计的地方。homework 表有 id、teacher_id、course_id、title、content、deadline、create_time。homework_submit 表有 id、homework_id、student_id、submit_content、file_url、score、submit_time、update_time。作业批改就是一次简单的 UPDATE 操作,但涉及到“该课程下所有学生的作业列表”这种查询时,必须用 JOIN 关联三张表才能出结果。这块逻辑写得好不好,直接影响论文里“系统实现难点”的质量。

下面我用一个表格把主要表的关系列一下,方便你建表时对照。

表名核心字段关联关系
sys_userid, username, password, role与课程表 teacher_id 关联
courseid, course_name, teacher_id, status与选课表、作业表、公告表关联
course_selectionid, course_id, student_id联合唯一索引防重复
homeworkid, course_id, teacher_id, deadline与提交表关联
homework_submitid, homework_id, student_id, score多对一关联作业表
noticeid, user_id, title, content与用户表关联

2.2 后端分层结构与关键代码实现

后端我遵循的标准写法是 Controller 接收请求、Service 写业务逻辑、Mapper 做数据库操作。如果你用 MyBatis,Mapper 接口加 XML 文件或者注解 SQL 二选一。毕设项目表数量不大,我建议直接用注解 SQL,省去维护 XML 文件的麻烦。

看一个核心业务——选课的代码实现。这段逻辑涉及的坑比较多,我贴出来逐行说明。

@Service public class CourseSelectionService { @Autowired private CourseMapper courseMapper; @Autowired private CourseSelectionMapper selectionMapper; @Transactional(rollbackFor = Exception.class) public Result selectCourse(Long courseId, Long studentId) { Course course = courseMapper.selectById(courseId); if (course == null) { return Result.error("课程不存在"); } if (course.getStatus() != 1) { return Result.error("课程未上架"); } if (course.getSelectedCount() >= course.getMaxCount()) { return Result.error("课程名额已满"); } int count = selectionMapper.countByCourseIdAndStudentId(courseId, studentId); if (count > 0) { return Result.error("请勿重复选课"); } selectionMapper.insert(courseId, studentId); courseMapper.incrementSelectedCount(courseId); return Result.success("选课成功"); } }

这里有几个细节要提醒你。第一行代码就用了事务注解,因为选课要同时操作两张表:插入选课记录、更新课程已选人数。如果不加事务,万一第二步失败,学生就会定在“选课记录存在但课程人数没增加”的中间状态。另外,判断selectedCount >= maxCount只能拦住大部分情况,高并发场景下还需要用悲观锁或乐观锁来保证数据安全,这块可以作为论文里的“系统优化”章节素材。

用户认证我用的是 JWT + 拦截器的方式。用户在登录接口拿到 token,之后每次请求在请求头里带上Authorization: Bearer <token>,后端拦截器解析 token 并取出用户身份。

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); try { Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write("token无效"); return false; } } response.setStatus(401); response.getWriter().write("未登录"); return false; } }

拦截器注册的时候要设置白名单,登录接口、注册接口、验证码接口这些不需要鉴权的请求放行,其余接口统一拦截。这里有一个新手容易忽略的点:前端路由做了权限控制不等于后端就可以放松。单纯依赖前端隐藏按钮来“防越权”是最脆弱的设计,接口层面的角色校验才是底线。比如教师批改作业的接口,必须在后端校验当前登录用户的角色和课程归属关系,否则换个请求就能操作别人的课程数据。

2.3 文件上传与静态资源配置

课程封面、课件、作业附件都涉及文件上传。在 application.yml 里配置上传路径:

spring: servlet: multipart: max-file-size: 100MB max-request-size: 100MB file: upload-dir: ./upload/

文件上传的 Controller 返回一个相对路径给前端,前端直接把路径拼到请求域名后面访问。Windows 环境下部署时有一个常用的坑:路径分隔符不一致。我这个项目里写过一个小工具方法统一处理File.separator,避免在 Windows 环境用的是反斜杠、Linux 环境又是正斜杠的问题。

3. 前端工程化设计与页面实现

3.1 Vue 项目结构设计与路由权限控制

前端我用的是 Vue 3 + Vite + Pinia + Element Plus 的组合,如果你用的是 Vue 2 也完全没问题,核心思路相通。目录结构大致如下:

src/ api/ # 所有 axios 请求封装 assets/ # 静态资源 components/ # 通用组件 router/ # 路由配置 stores/ # Pinia 状态管理(或 Vuex) views/ # 页面视图 admin/ # 管理员页面 teacher/ # 教师页面 student/ # 学生页面 login.vue register.vue

路由这块建议用动态路由方式。系统初始只加载登录页和注册页,用户登录成功后根据角色动态添加路由。这样做的效果是:学生账号根本拿不到管理端的路由映射表,从 URL 层面就进不去。当然这个方案要在路由守卫里配合addRoute方法使用。

// 路由守卫示例 router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (!token && to.path !== '/login') { next('/login'); } else if (token && to.meta.roles && !to.meta.roles.includes(store.user.role)) { next('/403'); } else { next(); } });

这里给新手一个建议:路由守卫里to.meta.roles这个 meta 字段一定要写全,因为它是前端权限控制的最后一层防线。漏掉一个页面,就等于给对应角色白送了一个未授权入口。

3.2 核心交互流程:选课与课程列表

以学生视角举例。学生登录后进入课程列表页,调/api/course/list接口拉取上架课程。列表渲染用 Element Plus 的el-table或el-card都行,关键是操作列的状态判断得仔细——已选课程显示“退课”按钮,可选课程显示“选课”按钮,满员课程按钮置灰。

<el-table :data="courseList" stripe> <el-table-column prop="courseName" label="课程名称" /> <el-table-column prop="teacherName" label="授课教师" /> <el-table-column prop="selectedCount" label="已选人数" /> <el-table-column label="操作" width="160"> <template #default="scope"> <el-button v-if="scope.row.selected" type="warning" @click="cancelCourse(scope.row.id)" >退课</el-button> <el-button v-else-if="scope.row.selectedCount < scope.row.maxCount" type="primary" @click="selectCourse(scope.row.id)" >选课</el-button> <el-button v-else disabled>已满员</el-button> </template> </el-table-column> </el-table>

前端交互和后端接口的设计节奏要匹配。后端接口返回的数据结构最好统一封装,我会在 Result 类里固定返回 code、message、data 三个字段,前端 axios 响应拦截器统一判断 code 是否为 200。这样前端组件里就只需要关心数据,不用每个请求都写一遍错误提示。

3.3 教师工作台:课程维护与作业批改

教师端页面我拆成了三个核心区域:课程管理、课件管理、作业管理。课程管理里,教师能够创建新课程,填写课程名称、封面、简介和视频素材,提交后课程的状态默认是上架。部分设计里也有需要管理员审核后上架的流程,这取决于你的需求文档怎么规定。

作业批改页面是联调工作量最大的模块。页面需要加载当前教师所有课程、每门课程下的作业、每份作业下所有学生的提交记录。这个三层钻取结构如果没有处理好,会导致前端页面出现大量嵌套循环,性能很差。我的建议是后端直接提供一个“汇总查询接口”,用 VO(View Object)对象一次性返回结构化数据,前端只做渲染,不在前端做内存里的二次聚合。这类查询 SQL 可以用嵌套查询或流式处理,但毕设层面一次性 JOIN 出来就够了。

4. 部署流程与真实环境避坑

4.1 构建打包与环境配置

这个项目要交付部署文档,所以打包和环境配置部分值得好好写。后端的流程是 Maven 打包成 jar 包,前端 npm run build 生成 dist 目录。dist 目录里的静态文件有两种接入方式:一种是部署到 Nginx,再通过反向代理把/api请求转发到后端端口;另一种是把 dist 扔进 SpringBoot 的 resources/static 目录,让 jar 包同时负责静态资源和接口服务。

我推荐用 Nginx 方案。理由有三个:一是 Nginx 处理静态文件性能更好;二是线上环境修改前端配置不需要重新打包后端;三是反向代理配好以后,前后端域名完全一致,没有跨域问题。我是这样配置 Nginx 的:

server { listen 80; server_name localhost; location / { root /opt/course-frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

后端打包时要注意配置文件按环境拆分。开发环境连本地数据库,生产环境连服务器数据库,用spring.profiles.active参数区分。部署命令:

# 本地启动参数 java -jar course-system.jar --spring.profiles.active=dev # 生产环境启动参数(指定端口和数据库配置) java -jar course-system.jar --spring.profiles.active=prod --server.port=8080

4.2 部署过程中的典型问题速查表

我把自己和几个同事做类似项目时踩过的坑整理成了一张表,这些内容在论文里不用写,但对实操的人来说是保命级别的。

问题现象原因分析解决方案
前端请求接口报 404Nginx 配置了 history 模式但缺少 try_files 回退在 location / 里加try_files $uri $uri/ /index.html;
后端接口能通但前端显示跨域前端页面和后端接口不在同一个域名下开发环境用 Vite 代理,生产环境用 Nginx 反向代理
上传中文文件名时访问失败静态资源映射没处理 URL 编码后端做 URL 解码或上传时统一重命名文件
打包后接口地址仍是 localhost前端 .env 文件里 API 基础路径写死使用环境变量区分 dev 和 prod 环境
数据库导入中文乱码MySQL 表默认字符集不是 utf8mb4建表时指定DEFAULT CHARSET=utf8mb4
jar 包运行一段时间内存耗尽未设置 JVM 运行时内存参数用java -Xms256m -Xmx512m -jar控制堆内存

第六条我单独说一下。很多毕设项目的服务器配置都不高,默认 JVM 堆内存设置过大的话,小内存机器跑几天就被系统给 OOM 杀掉了。我当时就吃过这个亏,部署在同学宿舍的旧电脑上,跑了一个周末服务器直接挂了,后面加了-Xmx512m稳了很多。

4.3 几个部署前的必查项

第一,数据库脚本要在干净环境里完整跑一遍。我见过太多项目交付的 .sql 文件只有在原作者本地才能成功导入,换一台电脑就报主键冲突或者外键缺失。原因是建表语句里混入了测试数据,或者随手改过表结构但脚本没同步。解决方法是:新建一个空数据库,从头执行一遍 SQL 脚本,确保无报错。

第二,所有配置文件里的密码、密钥不要用默认值。生产环境至少把数据库密码改成强密码,JWT 密钥换成一长串随机字符串。虽然作为毕设项目安全威胁不大,但答辩老师如果问这类问题,你能说出“已修改默认配置”就是加分项。

第三,手册要写清楚从零到一的全过程。部署文档里我把 Java 环境安装、MySQL 初始化、Nginx 配置、项目启动分成了四个章节,每步截图,保证一个没接触过这个项目的人照着文档做也能跑起来。

5. 论文结构安排与答辩准备建议

5.1 论文的章节组织思路

论文写得好不好,很大程度上决定了你这个项目的最终评分。系统做得再漂亮,论文逻辑混乱也会拖后腿。我建议按下面的骨架来写:

  • 第一章 绪论:课题背景、国内外研究现状、研究内容、论文组织结构
  • 第二章 相关技术概述:SpringBoot、Vue、MySQL、前后端分离架构
  • 第三章 系统分析:可行性分析、需求分析(功能需求 + 非功能需求)、用例图
  • 第四章 系统设计:总体架构图、功能模块设计、数据库设计(E-R 图和表结构)
  • 第五章 系统实现:每个核心模块的界面截图 + 核心代码 + 实现说明
  • 第六章 系统测试:测试环境、功能测试用例表、部分性能测试数据
  • 第七章 总结与展望:项目总结、不足与未来改进方向

注意第三章和第四章绝不是抄接口文档。需求分析要有用例图和业务流程图,数据库设计要有 E-R 图,实现章节代码不能贴大段直接展示,要挑“难点”和“重点”来讲。

5.2 答辩中最高频的五个问题

根据我带过的团队经验,答辩现场问得最多的问题集中在以下五个方面,提前准备答案效果会好很多。

第一,“你的系统有什么亮点?”别说用了前后端分离这种话。正确说法是:系统的核心亮点在于选课流程中通过数据库联合索引与事务控制,保证了数据的一致性;同时系统通过角色权限控制,实现了学生、教师、管理员三段隔离的界面与接口访问范围。

第二,“为什么选这个技术栈?”从开发效率、生态成熟度、资料丰富程度、就业市场匹配几个维度回答。记住不要说“因为网上教程多”。

第三,“如果同一时间大量学生抢同一门课,系统怎么保证不超选?”这是考察并发能力的经典题目。你可以回答当前方案是乐观校验,也可以提可以演进为带版本号的乐观锁或 Redis 原子操作方案,说明你思考过。

第四,“你的数据库设计有什么考虑?”选课记录表联合唯一索引、外键设计、课程表冗余了已选人数字段以降低查询压力——这些都是加分项。

第五,“项目部署在什么环境?如何保障线上稳定运行?”结合部署文档,回答服务器环境配置、JVM 参数、Nginx 反向代理、日志排查方式即可。

5.3 答辩演示的准备建议

前面再充分,演示翻车也非常可惜。做一个答辩前 Checklist:

一是演示账号准备三个角色,每个账号密码提前登录一遍确认状态,不要把 token 过期或密码记混的情况留到现场。二是演示网络环境需要全程联网,至少提前把数据库服务、后端服务、前端页面全部跑通一遍。三是准备一份“B 计划”:如果现场网络断了,提前录一份完整的操作演示视频放在桌面。

我自己遇到过的情况是答辩当天校园网抖动,前端资源加载不出来,还好提前录了视频,全程看着录像讲解,评委也没觉得突兀。

6. 写在最后的经验总结

这个项目做完,最大的收获不是什么高深技术,而是对“完整交付”有了更具体的概念。源码、数据库脚本、论文、部署文档、演示账号,一样都不能缺。很多同学把毕设做成了一个“能跑就行”的 Demo,但真正的系统要经得起换环境、换数据、换操作者的考验。

后面如果有精力,我建议在现有系统上做两件事扩展:一是把课程视频接入对象存储,解决本地磁盘存放大视频的压力;二是给选课模块叠加一个简单的消息通知功能,学生选课成功或退课后能收到站内信提醒。这两个方向的改动量都不大,但能让你在论文“展望”章节有话可写。

最后分享一个小技巧:项目交付前一定要做一次“冷启动验证”。把系统环境全部关掉,数据库删掉重建,然后严格按照部署文档从零操作一遍。凡是文档里漏掉的步骤、写错的路径,都在这个过程中暴露无遗。我见过的很多部署文档没经过这个验证,最后都变成了“只有作者本人能看懂”的备忘录。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询