☰
毕业设计实战:SpringBoot+Vue+MySQL在线课程管理系统全解析
2026/10/15 18:15:27 网站建设 项目流程

每年到这个时间段,“毕业设计选什么题目比较稳”就成了后台私信里最高频的问题。如果让我给一个既能体现主流技术栈、又有足够业务逻辑可以展开讲的方案,SpringBoot + Vue + MySQL 组合做一套在线课程管理系统,绝对是最不容易翻车的选择之一。这个选题我前后辅导过不少学生,也亲眼见过从需求梳理、建表、后端接口、前端对接,到最后打包部署的全过程。这篇就把整条链路拆开揉碎讲清楚——从项目定位到数据库设计,从鉴权方案到接口实现,从前端路由到部署上线,再到论文素材怎么攒,全盘过一遍。想拿这个题目做毕设、或者正在做同类管理系统的同学,可以直接把这篇当操作手册用。

1. 项目定位:这个系统到底解决什么问题

在线课程管理系统,本质上解决的是高校或培训机构里课程资源数字化管理的业务问题。上课点名靠口头通知、课件资料靠微信群文件、选课靠接龙Excel,这些都是真实场景里反复出现的痛点。这个系统要回答三个核心问题:学生怎么找课、选课、上课;教师怎么发布课程、维护内容、查看选课名单;管理员怎么统筹用户和课程数据。

以角色维度拆开来看功能边界:

  • 学生端:注册登录、浏览课程列表、按分类或关键词检索、查看课程详情、选课退课、我的课程、个人信息维护
  • 教师端:课程信息增删改查、课程封面与简介维护、查看选课学生名单
  • 管理员端:用户管理(启停用、分配角色)、课程审核与上下架、分类管理、基础数据统计

这套功能设计最聪明的地方在于:三套角色界面,工作量足够撑起一篇毕设论文,又不至于复杂到无法收尾。整个系统是一个完整的业务闭环——从角色权限、课程发布、选课记录到个人中心,每一环都有数据流可以追踪,这在答辩时非常加分,因为老师顺着业务逻辑就能问出“为什么这样设计”“表结构怎么关联”这些问题,你每个答案都能落地到具体代码和表字段上。

1.1 为什么这个选题适合毕业设计

毕业设计评审最看重的其实是三件事:系统能不能跑通、工作量是否饱满、你能否讲清楚设计与实现过程。课程管理系统在这三点上天然占优势:

  • 业务模型清晰,不会出现“选题太大做不完”的情况
  • 数据库表结构有典型的多对多关系(选课),有讲解深度
  • 前后端分离架构可以直接展示工程化能力

换个角度想,一个校园二手交易平台、一个社团管理系统、一个实验室预约系统,本质上和课程管理系统属于同一类“业务CRUD + 权限控制”的模式,但课程管理系统的业务边界更贴近教育教学场景,数据和逻辑关系更丰富,复用性也更强。这个选题做完了,后续想扩展成在线考试系统、培训报名平台,底子都是现成的。

1.2 功能模块全景展示

我习惯在动手写代码前先把功能模块图画出来,这比直接建表要高效得多。这个系统的核心模块可以划分为:

模块子功能涉及角色
用户模块注册、登录、个人资料修改、密码重置全部
课程模块课程发布、列表查询、详情展示、上下架教师、管理员
选课模块选课、退课、选课名单查看、选课记录学生、教师
分类模块课程分类维护、分类查询管理员
公告模块公告发布与展示管理员、全部用户
统计模块课程数量统计、选课人数排行管理员

模块划分得越清晰,后续的编码和论文章节就越好写。每个模块都可以对应论文里的一个小节,页面截图、核心代码、测试用例,全部能对上号。

2. 技术选型:SpringBoot、Vue、MySQL各自承担什么角色

技术选型是论文第一章简介里必写的内容,也是答辩时老师必然追问的方向。这套组合被称为“Java Web开发三板斧”,三个角色在业务层、表现层、数据层各司其职,各有一个非常清晰的定位。

2.1 SpringBoot负责什么

SpringBoot在系统里承担后端服务层的全部职责:接收前端请求、处理业务逻辑、访问数据库、返回JSON数据。它的价值在于大幅降低了Spring框架的配置成本——以前搭一个SSH项目要写一堆XML配置文件,现在SpringBoot通过自动配置和starter机制,几行代码就能把一个可运行的Web服务拉起来。

它内部集成了内嵌的Tomcat容器,这意味着后端打完包就是一个可以直接执行的Jar文件,部署时不用再单独装Tomcat或配置Web服务器。这一点对整个项目的交付和演示非常重要:在答辩现场环境不可控的情况下,一条java -jar命令能跑起来,比什么都强。

技术上核心会用到几个组件:

  • Spring MVC:处理HTTP请求路由和参数绑定
  • Spring Data / MyBatis:负责数据库的ORM映射和持久化
  • Spring Security 或 自定义拦截器:负责登录鉴权和角色权限控制
  • JWT:前后端分离场景下的无状态token方案

2.2 Vue在系统中的位置

Vue负责的是整个用户界面层。前端通过Axios发请求访问后端接口,拿到JSON数据后渲染成页面组件。Vue的核心价值在于组件化开发——把页面拆成一个个独立组件,每个组件只关注自己那部分UI和逻辑,头部导航栏、课程卡片、分页组件、表单弹窗,都是可以复用的模块。

Vue的响应式数据绑定让页面和业务数据天然联动:修改了数组里的一个字段,页面上对应的DOM会自动更新,不需要手动操作DOM。这在做课程列表筛选、选课状态切换这些交互时效率极高。对于毕设项目而言,Vue可以让前端代码的组织结构保持清晰,Vue Router管路由、Vuex/Pinia管全局状态、Axios管网络请求,整个前端工程化的思路可以在论文里单独成章。

2.3 MySQL凭什么成为数据底座

MySQL在系统里承担数据持久化的职责。SpringBoot应用程序一重启,内存里的数据就可能丢失,所有用户信息、课程数据、选课记录,都必须落进数据库。MySQL选型的原因很朴素——开源免费、轻量可靠、生态成熟,并且是Java技术栈联合使用频率最高的数据存储方案,遇到问题网上资料一搜一大把。

虽然很多公司实际生产环境会用PostgreSQL或收费版数据库,但以课程管理系统的数据规模和并发量,MySQL的性能和功能表现都绰绰有余。配合Navicat或DataGrip这类图形化工具,建表、导数据、调试SQL都很方便。在论文里你可以顺带写一句“基于MySQL的事务机制保证选课操作的原子性和一致性”,这句话就体现出你对数据库的理解不是停留在增删改查上。

3. 数据库设计:表结构才是整个系统的地基

很多新手写毕设喜欢先写代码,写到哪缺表就补一张表,这种做法的坑会在后期全面爆发:字段命名不统一、关系混乱、数据冗余、业务逻辑没法落地。正确顺序永远是先画ER图、建表、再写代码。

课程管理系统的核心关系其实就一句话:一个用户可以学多门课程,一门课程可以被多个用户选。这是典型的多对多关系,必须通过中间表来解耦。

3.1 五张核心表的角色分配

这里给出最精简且完整的五表方案:

表名作用关键字段
user存储三类角色账号id、username、password、role、status
course_category课程分类id、name、sort
course课程信息id、title、category_id、teacher_id、cover_url、status
course_enroll选课中间表id、course_id、student_id、enroll_time、status
notice公告信息id、title、content、create_time

需要特别注意的是中间表 course_enroll。它既承载“选课”这个业务动作的时间、状态(选课中/退课),也把course和user两张表的多对多关系拆成了两个一对多关系:一个用户可以拥有多条选课记录,一门课程也可以拥有多条选课记录。这条设计本身就可以作为论文里数据库设计章节的重点内容来写。

3.2 字段设计里的几个讲究

字段设计决定系统的扩展性和查询效率。基于实际踩坑经验,下面几个点非常值得重视:

  • 主键统一用 Long 自增 id,不要用 UUID 做主键。自增索引在InnoDB下有更好的顺序写入性能
  • 密码字段不能存明文,至少用BCrypt或MD5加盐处理。毕设虽不至于被攻击,但这点体现了安全意识
  • 状态字段用 tinyint 而不用 varchar,比如用户 status 0禁用/1正常、课程 status 0待审核/1已上架/2下架,查询过滤时既快又不容易出错
  • 时间字段统一用 datetime,配合数据库的 CURRENT_TIMESTAMP 默认值,避免前后端时区不一致问题

建表SQL参考(用户表):

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT '加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `role` varchar(20) DEFAULT 'STUDENT' COMMENT 'ADMIN/TEACHER/STUDENT', `phone` varchar(20) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `status` tinyint(1) DEFAULT '1' COMMENT '1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

这里插一句踩坑提醒:user是MySQL的保留字之一,虽然目前多数版本不直接报错,但某些场景下仍然可能触发解析问题。稳妥做法是表名加反引号`user`,或者直接换成sys_user、t_user这类前缀命名。后端写SQL时要格外注意字段名不要和order、group等关键字冲突。

3.3 ER关系图在论文里的呈现

论文里的数据库设计章节,通常需要放一张ER图。这里不推荐话大段文字描述,而是建议用表格配合一段说明文字:

  • user 与 course 之间通过 course_enroll 关联,一个学生可以选多门课,一门课可以被多个学生选
  • course 与 course_category 是多对一关系,一个分类下可以有多门课程
  • course 与 user(教师)是多对一关系,一个教师可以发布多门课程

ER图画清楚,老师一眼就能判断你的业务逻辑是否成立。更重要的是,你在答辩时要能指着ER图把每张表的主外键关系、中间表的作用讲清楚,讲明白就是一次高质量的加分。

4. 后端核心实现:三层架构与登录鉴权

后端代码的核心在三层架构和登录授权逻辑。这部分也是论文详细设计章节的主要素材来源。用SpringBoot + MyBatis-Plus(或MyBatis + PageHelper)的组合,能省掉大量重复的DAO代码。

4.1 Controller、Service、Mapper各管什么

后端严格遵循分层结构,这个结构不是套路,而是有明确职权划分的:

  • Controller层:接收前端请求、参数校验、返回统一响应体,不写任何业务逻辑
  • Service层:承载所有业务规则,例如选课时的重复校验、退课时的状态判断
  • Mapper层:操作数据库,一个方法对应一条SQL或一个MyBatis-Plus条件构造器

以“课程分页查询”为例,Controller只负责把请求参数拿过来丢给Service:

@RestController @RequestMapping("/api/course") public class CourseController { @Resource private CourseService courseService; @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Long categoryId, @RequestParam(required = false) String keyword) { // Service层返回统一分页结果对象 return Result.ok(courseService.queryCoursePage(page, size, categoryId, keyword)); } }

Service实现里,如果用的是MyBatis-Plus,代码非常简洁:

@Service public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService { @Override public Page<CourseVO> queryCoursePage(Integer page, Integer size, Long categoryId, String keyword) { LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(categoryId != null, Course::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), Course::getTitle, keyword) .eq(Course::getStatus, 1) // 只查已上架课程 .orderByDesc(Course::getCreateTime); return this.page(new Page<>(page, size), wrapper); } }

这段代码里值得在答辩时展开讲的是条件构造器的用法:eq条件里的第一个参数是布尔判断,满足条件才拼接该SQL条件,从而实现了“分类可选、关键词可选”的灵活查询,避免手工拼接SQL导致的SQL注入风险。

4.2 JWT登录鉴权怎么设计最稳妥

前后端分离架构下,最推荐的毕设方案不是把Spring Security全家桶都搬进来,而是用“JWT工具类 + HandlerInterceptor拦截器”的组合。原因很简单:Spring Security的过滤器链和配置项对于毕设项目来说是黑盒,一旦被老师追问细节就非常难过关。而JWT这套方案,从生成token到解析token,每一行代码都能讲出明确逻辑。

登录接口的核心流程:

@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 根据用户名查用户 User user = userService.getUserByUsername(loginDTO.getUsername()); // 2. 校验密码(BCrypt加密存储) if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) { return Result.error("账号或密码错误"); } // 3. 账号状态校验 if (user.getStatus() == 0) { return Result.error("该账号已被禁用"); } // 4. 签发JWT,携带用户id和角色 String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(new LoginVO(token, user)); }

JWT工具类里做的事情拆开讲就是:用HMAC算法对payload(用户id、角色、过期时间)做签名,生成一段三段式的token字符串。后续每次请求在请求头带上这个token,后端通过拦截器解析,就能确认请求者身份和权限。

拦截器注册这里有个细节:要放行登录、注册、课程列表查询等白名单接口,只拦截需要身份的接口。

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求,避免跨域时OPTIONS请求被拦截 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (StringUtils.isEmpty(token) || !JwtUtil.validateToken(token)) { throw new BusinessException(401, "登录状态已失效"); } // 解析token并存入request作用域,后续Controller可直接取用户信息 request.setAttribute("userId", JwtUtil.getUserId(token)); request.setAttribute("role", JwtUtil.getRole(token)); return true; } }

4.3 选课接口的唯一性约束和并发思考

选课是整个系统业务逻辑的“题眼”,也是答辩时老师最容易深挖的地方。选课接口如果只做“插入一条course_enroll记录”,那只能算完成50%。真正的关键问题是:同一个学生重复选同一门课怎么办?

最直接的做法是在Service层加校验代码:

@Override @Transactional(rollbackFor = Exception.class) public void enroll(Long studentId, Long courseId) { Course course = this.getById(courseId); if (course == null || course.getStatus() != 1) { throw new BusinessException("课程不存在或未上架"); } // 重复选课校验 long count = enrollMapper.selectCount(new LambdaQueryWrapper<CourseEnroll>() .eq(CourseEnroll::getCourseId, courseId) .eq(CourseEnroll::getStudentId, studentId) .eq(CourseEnroll::getStatus, 1)); if (count > 0) { throw new BusinessException("您已选过该课程,请勿重复选择"); } // 插入选课记录 CourseEnroll enroll = new CourseEnroll(); enroll.setCourseId(courseId); enroll.setStudentId(studentId); enroll.setStatus(1); enrollMapper.insert(enroll); // 课程选课人数+1 courseMapper.incrementStudentCount(courseId); }

这段代码里值得展开讲的重要底层原理是数据库层面的唯一性保障。如果只靠Java代码里的count校验,在高并发场景下仍可能出现两个请求同时通过校验、插入两条重复记录的情况。更稳妥的做法是在course_enroll表上建唯一索引(course_id + student_id + status),让数据库在物理层面兜底。不过在毕设答辩阶段,能把这个思考过程讲出来——先业务校验、再数据库约束兜底,已经完全能体现你对并发和数据一致性问题的理解了。

另外一个容易被忽略的细节是事务。选课操作涉及“插入选课记录”和“课程人数+1”两个写操作,必须放到同一个事务里,要么全部成功、要么全部回滚。@Transactional注解在这里不是装饰,而是实实在在数据一致性保障。退课接口也是同理,但退课时需要判断当前状态是否为“学习中”,否则会重复退课。

5. 前端实现:Vue工程的结构与关键配置

Vue项目最大的好处是文件结构清晰,一个页面就是一个目录,组件可以拆得稀碎再拼装,前端代码量看起来也足够饱满。如果论文里要截图展示代码,Vue的组件结构比一坨JS更直观。

5.1 路由设计与权限控制

前端路由建议按角色划分模块,而不是把所有页面堆在一起。登录后跳转的首页要根据当前用户的角色动态决定:

// router/index.js 核心路由配置 const routes = [ { path: '/login', component: Login }, { path: '/', component: Layout, redirect: '/dashboard', meta: { requiresAuth: true }, children: [ { path: 'dashboard', component: Dashboard, meta: { title: '首页' } }, { path: 'course/list', component: CourseList, meta: { title: '课程列表', role: ['STUDENT', 'TEACHER', 'ADMIN'] } }, { path: 'course/add', component: CourseEdit, meta: { title: '发布课程', role: ['TEACHER'] } }, { path: 'course/enroll-record', component: EnrollRecord, meta: { title: '我的选课', role: ['STUDENT'] } }, { path: 'user/manage', component: UserManage, meta: { title: '用户管理', role: ['ADMIN'] } } ] } ]

路由守卫的逻辑是:每次跳转前检查localStorage是否有token,没有就强制回登录页;有token还要额外检查目标路由的meta.role是否包含当前用户角色,不匹配就跳回首页并给出无权限提示。这套route meta声明式权限写法简洁易读,答辩时也很容易讲清楚。

5.2 Axios封装与Token携带

前端所有网络请求统一走封装过的Axios实例,避免每个页面都写一遍拦截逻辑。Token统一在请求拦截器里从localStorage取出、放入请求头;响应拦截器里统一处理业务状态码和HTTP异常:

// utils/request.js import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use(response => { const res = response.data // code为401表示token过期或未登录 if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } if (res.code !== 200) { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { Message.error('网络请求异常,请检查后端服务是否启动') return Promise.reject(error) }) export default service

这里的统一错误处理有一个很实际的体格价值:后端如果启动失败、接口路径写错、参数类型不对,页面不会白屏,而是弹出一条清晰的错误提示,这对于答辩演示时临场调试非常有用。

5.3 页面组件拆分的思路

以前端“课程列表页”为例,可以拆成这些组件:

  • CourseList.vue:页面入口,负责数据加载和组合子组件
  • CourseCard.vue:单张课程卡片,展示封面、标题、教师、选课人数
  • SearchBar.vue:分类筛选和关键词搜索
  • Pagination.vue:分页器,父组件监听页码变化后重新请求接口

组件拆分的好处是:每个组件代码量控制在100行左右,阅读负担小;且同一套CourseCard可以在“课程列表”“我的课程”“推荐课程”多个页面复用。这也是前端章节论文素材的重要来源,贴代码时只需贴一个组件并解释其props、emit设计即可。

另外,打包时有一个SpringBoot后端项目鲜有人注意的坑:前端开发时和后端联调用的是代理地址,但打包部署后接口地址可能变成了线上域名,建议不要直接把接口地址硬编码在代码里,而是通过.env.development和.env.production两个环境变量文件区分,构建时自动切换。

6. 打包部署与论文素材准备

开发完的那一天不是结束,真正让人熬夜的是部署和录屏截图。可能听上去很俗,但这部分是决定最终交付体验的关键。

6.1 后端打Jar包

后端项目使用Maven构建,在项目根目录执行:

mvn clean package -DskipTests

打包完成后,target目录下会生成一个可执行的Jar文件(比如course-system.jar)。在服务器或本地测试环境用java -jar course-system.jar就能启动。这里要提醒的是:

  • JDK版本必须和开发环境保持一致,否则会报UnsupportedClassVersionError
  • 数据库连接地址、账号密码建议放配置文件,打包时排除,部署时用外部配置覆盖
  • 如果用了文件上传功能,上传目录不要放在Jar包内部路径,否则重启后文件会丢

6.2 前端打包与Nginx配置

前端在项目根目录执行:

npm run build

生成dist目录。传统方案是把dist目录交给Nginx托管,并配置反向代理解决跨域:

server { listen 80; server_name localhost; location / { root /usr/share/nginx/html/dist; index index.html; # 解决Vue Router刷新404问题 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; } }

这个配置里try_files是关键。如果不加这行,在Vue Router的history模式下,用户刷新/course/list页面时Nginx会找不到了真实文件而报404。增加try_files后,所有前端路由都回退到index.html,由Vue Router接管。如果想完全省掉这个配置,也可以在路由初始化时改成createWebHashHistory(),路径会变成/#/course/list形式。

6.3 论文结构和素材积累方法

论文结构建议直接对标开发流程,各学校模板不同但骨架相通:

  1. 绪论:背景、意义、国内外现状、技术选型
  2. 需求分析:角色分析、用例图、功能与非功能需求
  3. 概要设计:架构图、功能模块划分、数据库设计(ER图 + 表结构)
  4. 详细设计:核心流程、类图、接口设计、时序图
  5. 系统实现:页面截图 + 关键代码 + 功能说明
  6. 系统测试:测试用例表、测试结果、结论
  7. 总结与展望:完成内容、不足、改进方向

素材积累最重要的原则是“边开发边截图”。很多学生最后一周才开始补截图,结果系统早就被改得跟论文描述对不上了。建议每完成一个模块,就顺手记录测试数据和页面效果,后续写实现章节就不是从零开始而是直接整理素材。测试数据也一个重要技巧:用脚本一次性生成几十个用户、课程和选课记录,截图才好看,统计图才有数据量。

7. 实测踩坑记录:新手最容易卡住的四类问题

这一节的价值来自真实调试经验。下面这些问题基本每个做这个项目的同学都会碰到,提前排雷能省下大量时间。

7.1 跨域问题

前后端分离联调时,前端地址是http://localhost:5173(开发服务器),后端是http://localhost:8080,浏览器会拦截跨域请求。常见表现就是前端请求报CORS错误,请求根本到不了后端。

如果后端用了拦截器,跨域配置必须和拦截器配置共存才能正确生效。推荐实现WebMvcConfigurer统一配置:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

这段代码里最坑的一点是:allowCredentials(true)时,allowedOrigins不能用"*",必须用allowedOriginPatterns("*"),否则浏览器依然会拦截。前端请求会先发OPTIONS预检,拦截器必须放行。

7.2 MySQL驱动和时区导致连接失败

使用较新版本的MySQL驱动(比如8.x)时,数据库连接URL必须加上时区参数,否则启动报时区异常:

spring: datasource: url: jdbc:mysql://localhost:3306/course_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

历史老项目中常见com.mysql.jdbc.Driver写法,在MySQL 8版本下会提示已废弃或报ClassNotFoundException,需要改成com.mysql.cj.jdbc.Driver。这个报错信息非常迷惑人,很多新手以为是驱动没导入,其实只是类名写旧了。

7.3 前端打包后接口调不通

开发模式一切正常,打包部署后接口全挂,多半是因为baseURL写死了开发环境的地址。上述proxy_pass方案已经能解决跨域,后端就不用在代码里写@CrossOrigin。前端生产环境请求路径建议直接用相对路径/api,由Nginx转发到后端。

还有一个比较隐蔽的问题是后端上下文路径。如果后端的server.servlet.context-path配了/api,前端baseURL就不要再带/api前缀,否则等于出现/api/api/login。我见过学员在这里调了大半天,最后发现路径重复了。

7.4 答辩演示的临场翻车场景

演示阶段最容易翻车的几个问题,提前照做即可规避:

  • 演示前用测试账号录好一段“从登录到选课成功”的完整流程视频,哪怕现场数据库被还原,也有完整素材
  • 数据库初始化脚本准备好,演示前一键重置数据,防止上次调试把某个账号状态改了或数据弄脏
  • 后端启动方式使用java -jar而不是直接在IDEA里跑,避免临场依赖开发环境
  • 准备一份“功能速查表”:每个角色能做什么操作,分别对应哪个菜单、哪个接口,答辩时老师问起来对答如流

写在最后

整套系统做下来,我个人最大的体会是:毕业设计不在于引入多高级的技术,而在于把一件小事的链路完整走通。从业务分析到数据库设计,从后端接口到前端联调,从本地测试到部署上线,每一个环节都不算难,但合在一起需要的是条理和耐心。课程管理系统恰好是这样一条完整链路的最优载体,做完之后无论是对SpringBoot的理解、对数据库设计的敏感度,还是对前后端分离工程化流程的体感,都会有一个质变。如果你正卡在这个项目的某个环节,建议从数据库的表关系重新捋一遍,十有八九问题就出在最底层的数据模型上。项目做完以后,想继续扩展的话,可以往在线考试、视频点播、线上作业提交这些方向延伸,底子都是这套代码的变体。

最后再分享一个小技巧:整个项目里最值得你多花时间打磨的,不是代码写得多么炫,而是“演示数据的真实性”。预先准备一套带真实感的课程数据——课程名像模像样、封面图风格统一、选课数量有梯度,演示效果会立刻高一个档次。这个细节,往往比你在代码里多写几个接口更让人眼前一亮。

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

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

立即咨询