news 2026/10/2 10:03:20

SpringBoot+Vue网络课程管理系统开发全攻略:从设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue网络课程管理系统开发全攻略:从设计到部署

每年一到毕业设计选题季,"SpringBoot+Vue 网络课程管理系统"这类题目都会毫无悬念地冲上热搜。我前前后后带过不少这个方向的课题,也见过太多答辩现场翻车的案例:有的同学把系统做成了纯CRUD,评委一句"这和仓库管理系统有什么区别"就把人问愣了;有的同学辛辛苦苦录了视频,结果播放器压根跑不起来,页面一片黑;还有的部署环节崩在MinIO端口上,演示时图都加载不出来。这个题目看起来简单,实际上是把用户权限、课程流转、视频存储、学习行为记录、前后端联调、容器化部署全揉在了一起,任何一个环节出问题,整个项目都会被拖垮。

这篇文章我就把这个题目从头到尾拆一遍。内容包括:这类系统的真实业务边界在哪里、SpringBoot和Vue各自该承担什么、数据库表怎么设计才不返工、视频点播和文件存储怎么接入才稳、前端动态路由和播放页怎么实现、最后部署和答辩有哪些保命细节。不管你是准备拿它做毕设,还是想快速搭一套在线课程平台的雏形,这篇文章都能直接当作选型参考和开发手册来用。

1. 这类选题经久不衰的真实原因:先搞清楚系统要解决什么问题

很多人在选题时只是觉得"在线教育热门、SpringBoot熟悉、Vue学过一点"就选了它,但完全没想明白系统到底在解决什么问题。这直接导致后面开发时东一榔头西一棒子,做着做着把自己绕晕。所以第一步,先把需求盘清楚。

1.1 一个在线教学运营平台的完整业务闭环

网络课程管理系统不是"一个课程表 + 一堆视频链接"这么简单。从运营视角看,它至少要跑通这样一个闭环:

  • 管理员维护课程分类、审核教师上传的课程、管理首页轮播和公告;
  • 教师创建课程、维护章节和视频、发布作业、查看选课学生列表;
  • 学生浏览课程、加入学习(报名或购买)、观看视频、记录学习进度、参与讨论和作业提交。

这个闭环再往下拆,就变成一张清晰的功能清单。我在实际做项目规划时,通常会把它分成七个功能域:

功能域核心功能点后端核心任务
用户体系注册、登录、角色切换、个人中心JWT鉴权、RBAC权限控制
课程管理分类、课程信息、章节、视频关联课程状态机流转(草稿→待审→已上架→下架)
教学运营选课报名、课程评论、公告轮播报名关系维护、热门课程统计
学习过程视频播放、断点续播、学习时长记录学习记录表增量更新、进度计算
文件存储视频上传、封面图片、文档附件MinIO对象存储、URL签名
统计分析(可选加分项)选课人数趋势、课程热度榜、学习完成率定时任务聚合统计数据
系统管理菜单管理、角色管理、数据字典动态菜单权限接口

这里要注意一个取舍问题:作业、考试、题库这些功能,如果你是准备冲刺高分或者导师明确要求,可以做;如果只是想稳稳落地,建议放在"二开扩展"里,先把主链路打磨好。很多同学一上来就想做"全覆盖大平台",结果光数据库就建了四十多张表,最后连登录都调不通,这是最常见的翻车原因。

1.2 从需求反推边界:哪些功能决定系统的"成色"

这个题目最容易被低估的是三个功能点,它们恰恰是区分"普通管理系统"和"在线教学运营平台"的分水岭。

第一个是课程上下架流转。课程不是创建完就直接能看的,必须有管理员审核环节,涉及草稿、待审核、审核通过、已上架、被驳回、已下架这些状态。这一条状态机是课程系统的灵魂,没有它系统就是一张静态信息表。

第二个是学习进度记录。学生把视频看到一半退出,再进来要能接着播。这要求前端按一定频率上报播放进度,后端维护一条"用户-课程-章节-视频-进度"维度的最新记录。这也是评委最容易追问的点:你系统里"学习完成率"是怎么算出来的?如果答不上来,基本就是重大扣分项。

第三个是视频存储与播放。视频文件不能打进项目里,也不能上传到服务器本地随便仍,一般要接MinIO这类对象存储,然后用HLS切片(m3u8)的方式让浏览器流畅播放,还要考虑防盗链和URL签名。这一块做得怎么样,直接决定演示时会不会当场翻车。

把这三点想清楚、做透,系统就已经超过80%的同类毕设了。剩下的就是工程化细节:参数校验、异常处理、日志埋点、HTTP状态码规范、Vue路由守卫和接口统一封装,这些属于"面子里子"各占一半的活儿。

2. 技术选型与项目骨架:SpringBoot+Vue这套组合的平衡点

选型这块最容易踩的坑不是"不知道该选什么",而是"听网上说新的好,结果把自己坑了"。

2.1 版本选型的三个坑:SpringBoot、JDK、Vue

先说后端框架版本。现在网上铺天盖地都是SpringBoot 3.x的教程,但毕设项目我强烈建议用SpringBoot 2.7.x。原因很朴素:很多学校机房、实验室电脑装的还是JDK 8,SpringBoot 3.x强制要求JDK 17,你写的时候在自己电脑上跑得飞起,到答辩演示换成教室机器或者老师电脑,一跑就报"UnsupportedClassVersionError",这是真实发生过的惨案。SpringBoot 2.7 + JDK 8这套组合,兼容性最稳,资料最多,网上任何问题都能搜到答案。

另一个是前端框架版本。建议直接用Vue 3 + Vite + Element Plus。市场趋势摆在那里,Vue 2已经停止维护了,现在新开项目还选Vue 2等于给自己埋雷。Vite比Webpack快得多,更重要的是开发体验好——改完代码秒级热更新,做页面调试的时候效率差好几倍。

版本选型总结成一句话:后端求稳,前端求新。SpringBoot 2.7.x + JDK 8 + Vue 3 + Element Plus + MyBatis-Plus + MySQL 5.7或8.0,这套组合已经经受过大量毕设项目检验,不会出幺蛾子。

2.2 后端项目结构:从目录就能看出你"懂行"

项目结构体现的是一个人的工程素养,也是答辩时老师快速判断你是不是网上随便抄一个单模块Demo的标准。在线课程系统这种规模,无论是单模块还是多模块Maven工程都是合理的,但内部目录一定要按业务分包,而不是按技术类型分包。

我习惯的分包方式是:

com.example.course ├── config // 配置类:跨域、MyBatis-Plus分页、MinIO客户端、WebMvc拦截器 ├── controller // 控制层:按业务域拆,如AuthController、CourseController、VideoController ├── service // 业务层:接口 + impl实现 ├── mapper // 数据访问层(MyBatis-Plus的Mapper接口) ├── entity // 实体类(对应数据库表) ├── dto // 前端传入的请求参数对象 ├── vo // 输出给前端的视图对象 ├── common // 全局返回结果、状态码枚举、异常处理、常量 ├── security // JWT拦截器、用户上下文、鉴权注解 └── utils // 工具类:日期、文件、MD5等

注意controller里面只放参数接收和返回,业务逻辑全部下沉到service层。很多新手喜欢在Controller里写几百行SQL查询,这在大项目里是灾难。service层不光是"中转站",还要承担事务控制——比如学生报名课程时,要同时写入报名关系表和累加课程的选课人数,这两个操作必须共用一个@Transactional。这些细节在你写代码的时候做好,后面写论文时"系统设计"这一章几乎可以直接套用,浑然天成。

另外专门提一个很多人忽略的点:统一返回结构。后面Vue前端要根据状态码做统一的请求拦截,接口返回格式必须一致,不能有的接口返回{code:200, data:...},有的直接返回一个数组。我在项目里用的返回格式是:

{ "code": 200, "message": "操作成功", "data": {} }

错误状态码用枚举管理,比如400参数错误、401未登录、403无权限、404资源不存在、500服务器异常。前端拿到非200的状态码时弹出统一提示。这套规范越早定好,前后端联调时越省心。

2.3 鉴权方案:JWT + Sa-Token还是Shiro?

在线课程系统最麻烦的一个点是:同一个用户可能是学生,也可能是老师。所以登录鉴权不能只判断"有没有登录",还要判断"以什么角色在操作"。

常见的三种方案是Spring Security、Sa-Token和手写JWT拦截器。毕设这个规模,我强烈推荐Sa-Token,原因有三个:

  1. 它对JWT、登录、权限注解、踢人下线、记住我等功能都做了开箱即用的封装,引入依赖后配置几十行就能用,比Spring Security那套复杂的过滤器链友好得多;
  2. 它内置了@SaCheckRole("teacher")这种注解,直接在Controller方法上标一下就能做角色校验,代码可读性很高,答辩时也容易讲;
  3. 它支持token自动续期和同端互斥登录,能体现你对安全的思考。

用Sa-Token做角色权限的基本姿势是这样的:

// 登录成功后会返回token,前端存起来 StpUtil.login(userId); String token = StpUtil.getTokenValue(); // 需要教师权限才能调用的接口 @SaCheckRole("teacher") @PostMapping("/course") public Result<Void> createCourse(@RequestBody CourseForm form) { ... } // 不需要登录也能访问的接口(比如首页课程列表) @SaIgnore @GetMapping("/course/published") public Result<PageResult<CourseVO>> publishedCourseList() { ... }

这套方案的落地性很强,而且它比手写JWT拦截器更能体现"工程规范性"。手写JWT不是不行,但你要自己处理token过期、重复校验、权限匹配这些问题,调试起来非常耗时,容易陷进去出不来。

3. 数据库与后端核心模块:课程、用户、学习进度三者的关系设计

数据库设计是整个系统最能看出"下过功夫"的部分。这一节我直接给出经过反复实践验证的核心表设计逻辑,以及几个关键接口的实现思路。

3.1 核心表的拆分与字段设计

在线课程系统最少需要这几类表,我按业务域列出来:

用户域:tb_user、tb_role、tb_user_role

  • tb_user:id、username、password(BCrypt加密存储)、nickname、avatar、phone、email、status(启用/禁用)、create_time
  • tb_user_role:user_id、role_id,因为一个人可能同时是"学生+老师",所以不能只给user表加一个role字段了事

课程域:tb_course_category、tb_course、tb_course_chapter、tb_course_video

  • tb_course:id、teacher_id、category_id、title、cover_url、intro、price、status(草稿0/待审1/上架2/驳回3/下架4)、student_count、create_time、publish_time
  • tb_course_chapter:id、course_id、title、sort(章节排序)
  • tb_course_video:id、chapter_id、title、video_url、duration(秒)、video_size、sort

学习域:tb_course_student(选课报名关系)、tb_learn_record(学习记录)

  • tb_course_student:id、course_id、user_id、join_time,唯一索引(course_id, user_id)
  • tb_learn_record:id、user_id、course_id、chapter_id、video_id、progress(0-100的整数或小数保持)、last_watch_time、update_time

运营域:tb_course_comment、tb_notice、tb_carousel(首页轮播)

这里有两个容易踩的坑:

一个是学习记录表要不要存明细历史。如果为了统计"每天学习时长",需要一张明细表,每次上报进度都插一行;如果只是做"续播"功能,只需要保存最新一条进度。我建议先做最新记录这张表保住主线,把每日学习时长的统计做成定时任务,从明细表聚合。千万别一上来两张明细表一起搞,数据量虽然不大但逻辑会绕,调试起来是真痛苦。

另一个是课程价格字段。如果系统有免费课程也有付费课程,price字段建议用整数分值存储,比如19.9元存为1990,前端展示时再除以100。一分钱误差引发的脏数据,排查起来非常恼火。

3.2 三种角色的权限设计:一张用户表如何优雅落地

权限这块用"用户-角色-菜单"三层结构来做最清晰。数据库里维护角色表和用户角色关联表,菜单维护在前端动态路由里。

关键点是后端接口必须做角色校验,不能只靠前端隐藏按钮。因为前端隐藏是没有安全性的,懂行的人打开开发者工具就能直接调接口。我每个需要权限的Controller方法上都加了Sa-Token的角色注解,比如:

  • 课程新增、修改、视频上传:@SaCheckRole("teacher")
  • 课程审核上架、用户禁用:@SaCheckRole("admin")
  • 获取我的课程、上报学习进度:@SaCheckRole("student")

前端路由守卫配合后端的401/403状态码,形成双重保障。答辩时如果老师问"如果学生绕过前端直接调用教师接口怎么办",你就说"后端有角色校验,会被Sa-Token拦截并返回403",这一句话就能明显加分。

3.3 课程审核上架流程:状态机必须由后端掌控

课程状态流转这个功能,新手最容易做成"在数据库里直接改status字段"。表面上能用,但埋下了严重的一致性隐患:谁改的?什么时候改的?为什么改的?都没有记录。

更好的做法是设计一个独立的审核表,把每次审核动作留痕:

tb_course_audit - id - course_id - audit_user_id - action // 提交审核、审核通过、驳回、下架 - reason // 驳回原因 - create_time

学生端永远只能查询status = 2(已上架)的课程;教师端能看到自己课程的全状态列表;管理员看到待审核课程时,选择通过或驳回并填写理由。实现起来其实不复杂,就是"修改状态+插入一条审核记录"两个操作放在同一个事务里,但带来的效果是系统逻辑严密了一大截。

3.4 断点续播和学习时长统计:接口怎么做才合理

学习进度上报接口是前端播放器和后端配合的关键。前端播放器需要在timeupdate事件里节流上报进度,比如每隔5秒上报一次,带三个参数:videoId、progress(当前播放秒数)、duration(视频总长)。后端处理逻辑:

  1. 查询该用户对该视频的学习记录是否存在;
  2. 存在则更新进度字段,不存在则创建;
  3. 进度超过95%的,视为视频学习完成,打上完成标记。

这里注意一个细节:判断"完成"不要用精确到100%,因为视频最后一秒由于网络或播放器原因很可能永远播不到,95%是更合理的阈值。我见过一个项目,完成率老是统计不出来,最后发现就是卡在进度必须等于1.0这个死条件上。

学习时长统计可以做成一个定时任务,每天凌晨扫描昨天的学习记录明细,计算出每个用户每个课程的学习时长,写入一张汇总表。前端"学习中心"页面直接查汇总表,不用实时去聚合,性能好且统计口径稳定。

4. 视频点播是分水岭:MinIO存储、m3u8切片与前端播放器

这个模块是全网课程管理系统翻车的重灾区,我单独拿出一整节说,因为它足够复杂,也足够能体现你的工程能力。

4.1 为什么直接传MP4不行

很多第一次做这个系统的同学会想:课程视频不就是上传一个MP4文件,然后前端用<video src>标签播放吗?

在只有一两个视频、只有你一个人看的Demo环境里确实可以。但稍微往真实场景想一下,问题就全冒出来了:

  • MP4文件的moov原子通常在文件末尾,浏览器播放时必须先下载到文件尾才能开始渲染,一个200MB的视频用户可能要先等很久才能看到第一帧(虽然也有渐进式播放优化,但体验和可靠性很难控);
  • 用户拖动进度条时,播放器需要向服务器发起Range请求,大文件拖拽响应非常慢;
  • 视频没有加密保护,拿到链接就能随便下载;
  • 文件全塞在一台服务器上,流量一大就崩。

所以行业主流的方案是文件存MinIO,视频处理成HLS流(m3u8+ts切片),前端用HLS播放器播放。这在国内视频SaaS和大部分在线教育平台里是标配方案。

4.2 MinIO接入SpringBoot的核心要点

MinIO是一个开源的轻量级对象存储,S3协议兼容,本地就能跑,特别适合毕设和中小型项目。接入SpringBoot时,先把依赖加好:

<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.7</version> </dependency>

然后在application.yml里配置连接信息:

minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket: course-bucket

接着写一个MinioService,把常见的上传、下载、删除、生成签名URL都封装好。核心方法长这样:

public String uploadFile(MultipartFile file, String objectName) { try { minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); // 返回可访问的完整URL(一般在Nginx里配个转发,把 /minio/ 指到MinIO) return "http://localhost:9000/" + bucketName + "/" + objectName; } catch (Exception e) { throw new BusinessException("文件上传失败:" + e.getMessage()); } }

这里有个我踩过的大坑:MinIO默认端口是9000,很多同学本机的后端端口也是8080,前端代理也是8080,三者很容易混。建议后端端口用8081,MinIO的API端口用9000,控制台端口用9001,前端Vite开发服务器用5173,一口一个端口,联调时映射关系清清楚楚。

另一个坑是存储桶内文件覆盖问题。课程视频的文件名必须唯一,我建议用course/{courseId}/{chapterId}/{videoId}/{timestamp}.mp4这种带业务含义的路径,配合时间戳。千万不要用用户上传的原文件名,因为中文字符和空格在URL里会被编码,播放器解析时各种意外情况。

4.3 m3u8切片:视频处理怎么做才不阻塞主流程

视频不处理直接放MinIO,播放器能用,但体验差。要做HLS转换,技术栈是FFmpeg。在服务器上装一份FFmpeg,然后在视频上传后异步执行切片命令:

ffmpeg -i input.mp4 -codec copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls output.m3u8

-hls_time 10表示每10秒切成一个ts分片,-codec copy表示不重新编码视频流,速度很快。如果源视频编码格式比较古老(比如H.264之外的),就需要先转一次码,命令会更长。

关键点是这个转码过程不能放在请求接口的同步流程里,因为一个大视频转码可能要几十秒甚至几分钟,前端早就超时了。我会做一个异步的消息队列任务:上传原始视频到MinIO后,接口立即返回"上传成功,转码中",同时向SpringBoot的事件监听器或者简单的线程池提交转码任务,后端转码完成后更新视频表里的状态和m3u8地址。前端播放页根据视频状态轮询或手动刷新,状态变成"已转码"后再渲染播放器。

如果你不想引入复杂的消息中间件,Spring的@Async配合线程池就足够撑起毕设级别的转码任务了。等做完初版,再考虑上ActiveMQ之类的消息队列,属于锦上添花。

4.4 前端播放器:怎么把m3u8放出来

前端播放器的选型我推荐这套:video.js+videojs-contrib-hls,或者直接用hls.js配原生的<video>标签。因为Vue 3项目里vue-video-player这个老库的更新情况不太稳定,很多同学装完发现报错一大堆,折腾几天都跑不起来,性价比太低。

用hls.js最干净的做法是:

import Hls from 'hls.js' function playVideo(videoEl: HTMLVideoElement, src: string) { if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(src) hls.attachMedia(videoEl) } else if (videoEl.canPlayType('application/vnd.apple.mpegurl')) { // Safari等原生支持HLS的浏览器 videoEl.src = src } }

这样就不用管播放器插件的怪问题了,直接在封装的VideoPlayer.vue组件里调用。写完这个播放器组件,整个系统最硬核的部分就落地了。

视频播放时还要注意URL签名时效的问题。MinIO默认的永久URL是公开的,别人拿到就能随便下载,所以正式项目里要改用getPresignedObjectUrl生成临时签名URL,比如有效期2小时。前端播放器和下载链接都走这个签名URL,过期后重新向后端请求。这个细节很能体现你懂业务安全。

5. Vue前端的关键实现:从登录到学习页的完整链路

前端工作量其实比后端大得多,因为要写登录、首页、课程详情、播放页、个人中心、教师后台、管理后台,这套下来至少20个页面起步。我挑三个最关键的实现展开说。

5.1 动态路由与菜单权限:不同角色看到的侧边栏不一样

学生进来看到的是"首页、课程中心、我的学习";教师看到的是"课程管理、我的学生、数据统计";管理员看到的是"用户管理、课程审核、平台数据"。这不能用三个独立路由表硬写,因为角色可能叠加,而且后续加角色会非常难受。

标准做法是:后端登录接口返回当前用户的角色列表和可访问菜单列表,前端登录后调用router.addRoute动态注册路由。

项目里我把路由拆成静态路由和动态路由两部分。静态路由只包含登录页、404页、首页,动态路由就是需要权限的路由。路由定义里加meta信息:

{ path: '/course/manage', component: () => import('@/views/teacher/CourseManage.vue'), meta: { roles: ['teacher'], title: '课程管理' } }

然后在路由守卫里做判断:

router.beforeEach((to, from, next) => { const userStore = useUserStore() if (!userStore.token) { next('/login') } else if (!userStore.menusLoaded) { userStore.fetchMenus().then(menus => { // 遍历菜单并 addRoute menus.forEach(menu => router.addRoute(menu)) next({ ...to, replace: true }) }) } else { next() } })

这里有个隐藏坑:动态添加路由后,如果下次刷新页面,动态路由会全部丢失(因为它们是浏览器内存里的),所以必须在刷新后、路由守卫第一次跳转前重新请求菜单并addRoute。逻辑写对后,刷新页面也不会白屏或者跳到404。

5.2 视频学习页:UI要克制,功能要闭环

学习页是整个前端交互最重的页面。布局方面我推荐左侧章节目录、中间视频播放器、下方评论区,右侧显示课程信息和学习进度。核心交互就一回事:点章节,换视频,记进度。

实现思路:

  • 进入页面时请求课程详情接口,拿到课程的基本信息和章节列表;
  • 同时请求学习记录接口,拿到该用户在每个视频上的进度,按videoId映射成一个字典;
  • 播放器加载时,根据当前视频的进度设置currentTime,实现断点续播;
  • 监听timeupdate事件,节流调用后端上报进度接口;
  • 切换章节时,记录当前播放位置再切到下一个视频。

这个流程看起来不难,但有一个很实际的问题:切换章节时如果直接把播放器src换掉,播放器会黑屏一下然后重新加载,体验一般。我会在组件里用key绑定的方式强制重新挂载整个播放器,每次切换都重新初始化,杜绝各种状态残留问题。代码大概长这样:

<VideoPlayer :key="currentVideo.id" :src="signedVideoUrl" :start-time="currentProgress" @progress="handleProgressReport" />

这个"用key强制重挂载"的思路,在很多场景里都能省掉一堆头疼的问题,不只是播放器,比如表单重置、图表刷新、图片验证码切换,都能用。

5.3 教师端课程管理:表单校验和文件上传的细节

教师端最核心的页面是"课程编辑页",基本要包含:课程基本信息表单(分类选择、封面上传、简介富文本)、章节管理(增删改排序)、视频管理(每个章节下面挂视频)。

视频上传这个交互一定要做好上传进度条和失败重试。前端用axios的onUploadProgress拿上传进度百分比,接口超时时间拉长到10分钟。上传成功后的返回结果要回填到表单里,比如把视频地址存进表单的videoUrl字段。因为这一步是学生看课的前提,交互不稳,演示时必炸。

还有分类选择,不要用<select>硬编码一个下拉列表,要让后端提供分类接口,前端动态加载。这样管理员后台新增分类后,前端不用改代码就能同步。这种"数据驱动配置"的思想在答辩时也是一个加分点。

5.4 前后端联调:跨域与接口规范

Vue开发服务器默认在5173端口,后端在8081端口,跨域是绕不开的。开发阶段用Vite代理最省事,在vite.config.ts里配置:

server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } }

这样前端代码里请求路径全写/api/...,就不用在后端配繁琐的CORS了。但注意,线上部署时代理就失效了,所以生产环境要么用Nginx反向代理,要么后端统一配好CORS。一般情况下,我会在后端SecurityConfig里把跨域配置写好,前端线上环境直接请求后端域名,省去Nginx配代理的复杂度。

接口规范方面,和前面讲的统一返回结构配合,前端封装一个request工具函数,在拦截器里统一处理token附带和错误提示。token存localStorage,每次请求头带上Authorization: Bearer {token},后端Sa-Token自动识别。

6. 部署与答辩:本地跑通不算完,能演示才算数

很多人的项目在本地开发环境跑得飞起,一到答辩现场就四处冒烟。提前把部署链路打通,能救你于水火。

6.1 Docker Compose编排依赖服务

后端是SpringBoot,前端是Vue构建后的静态资源,再加上MySQL、MinIO、Redis(如果用了的话),手工一个个启动太容易乱。Docker Compose可以直接编排一套环境,我通常的编排方案是这样:

services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: course_db ports: - "3306:3306" volumes: - ./mysql-data:/var/lib/mysql minio: image: minio/minio command: server /data --console-address ":9001" ports: - "9000:9000" - "9001:9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin volumes: - ./minio-data:/data backend: build: ./backend # Dockerfile,里面打SpringBoot的jar包 depends_on: - mysql - minio ports: - "8081:8081" frontend: build: ./frontend # Dockerfile,先npm build再用nginx托管 depends_on: - backend ports: - "80:80"

这套编排在本地和答辩教室都能一键启动,而且MySQL和MinIO的数据目录都挂载到了宿主机,换机器部署时把数据目录拷走即可,演示时即使断网,本地数据都完好。

6.2 环境配置和接口地址管理的细节

SpringBoot里我建议用多个配置文件管理不同环境:

application.yml // 公共配置 application-dev.yml // 本地开发:localhost application-prod.yml // 服务器部署:正式域名和IP

启动时用--spring.profiles.active=prod指定环境。这样本地跑和部署是两个配置,不会出现"本地好好的,服务器接口全是localhost"这种低级错误。

前端环境变量我用Vite的模式机制,建立两个环境文件:

.env.development // VITE_API_BASE_URL=/api 走代理 .env.production // VITE_API_BASE_URL=http://your-server-ip/api 直连后端

构建时Vite自动读取对应文件。部署到服务器后,前端Nginx里再把/api反向代理到后端8081端口。这一套走下来,前后端跨域问题彻底告别。

6.3 答辩演示时的几条保命细节

最后说几个我见过的惨痛教训,你们绕开走就行:

  • Demo数据一定要准备好。别用空库演示,至少放5门课程、每门课2-3个视频(短视频1-2分钟即可)、若干用户、几条评论和公告,演示时点击每一步都能看到数据变化,观感完全不同。
  • 视频要提前转码好。如果演示时现场等着FFmpeg转码,体验很灾难。做演示视频的流程是:先上传到MinIO,转码完成后再去答辩现场。现场直接播放m3u8,秒开才是正常状态。
  • 网络断了也能演示。视频切片和封面图都在本地MinIO里,接口也在本地,答辩现场如果没网,只要电脑能访问localhost,整个系统完整可用。这是Docker Compose本地化部署的一个隐藏优势。
  • 演示脚本要过一遍。从登录开始,演示学生看课、教师发布课程、管理员审核,每个步骤2分钟,总共10分钟以内。过程中鼠标别乱点,手别抖,因为视频学习进度上报是定时调用的,手指一动可能就调了接口。

说到最后,这个题目之所以每年都火,是因为它离真实产品很近:有用户体系、有内容生产、有学习消费、有内容运营,是一套完整的业务小闭环。把它做完做透,你收获的不只是一篇论文和一个能演示的系统,而是真正理解了一个"平台型产品"从数据库设计到前后端联调再到部署上线的全过程。如果你能在这个基础上再加上一两个亮点功能,比如学习行为分析、课程推荐、授课评价等,整个项目的完成度和答辩表现会再上一个档次。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 10:02:46

RHCSA实操备考指南:环境搭建、核心考点与错题复盘

很多准备考 RHCSA 的朋友来找我时&#xff0c;第一句话基本都是&#xff1a;命令我也看了&#xff0c;真题也刷过&#xff0c;怎么一上考场还是手忙脚乱&#xff1f;我跟他们说&#xff0c;你缺的不是知识量&#xff0c;而是把备考当成一份正经“作业”来做的习惯。RHCSA 这套认…

作者头像 李华
网站建设 2026/10/2 10:00:40

知识图谱+推荐系统:药物靶点交互预测的Python工程化落地

简介&#xff1a;本资源是一套基于知识图谱与推荐系统的药物靶点相互作用预测Python项目源码&#xff0c;面向计算机相关专业学生&#xff0c;适用于课程设计、期末大作业或项目实战练习&#xff0c;也可作为生物信息学交叉方向的入门参考。压缩包共40个文件&#xff0c;约56KB…

作者头像 李华
网站建设 2026/10/2 10:00:27

Claude Code接入MCP全攻略:从原理到配置实战

开头 先说一个我踩过的坑。去年年底我在终端里用 Claude Code 做代码重构&#xff0c;让它帮忙把一个老项目的配置文件批量迁移。模型理解得挺好&#xff0c;回答得也有模有样&#xff0c;但一旦涉及到“读取我硬盘上的某个具体文件”“跑一下某个数据库脚本”“打开某个网页看…

作者头像 李华
网站建设 2026/10/2 9:59:08

别再重复造轮子了:用TaoToken把全球开源宝库变成个人技能库

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 9:59:06

Spring Security 6 + JWT 前后端分离认证授权实战指南

不夸张地讲&#xff0c;Spring Security 是 Java 生态里最让人头疼、但又最值得花时间吃透的框架之一。我在做一个前后端分离项目时&#xff0c;技术栈选了 SpringBoot Vue&#xff0c;安全这块要求必须支持 Token 登录、接口权限控制、返回统一 JSON&#xff0c;最终用 Sprin…

作者头像 李华