谷粒学院改造Spring Cloud微服务视频点播平台完整攻略
简介:这是一份基于Spring Cloud微服务架构的在线视频点播系统毕业设计源码,源于谷粒学院项目改造,适合计算机相关专业学生用于课程设计与毕业设计,也适合希望实践微服务组件整合的Java开发者。包内共257个文件,其中Java源码176个、XML配置46个、YML配置15个,并辅以Spring factories、日志、图片、Excel说明与Markdown文档等,可支撑服务注册发现、API网关、断路器、配置中心等模块的本地搭建与运行;整体仅496KB,内容紧凑。项目重点覆盖Eureka、Zuul/Gateway、Hystrix、Spring Cloud Config与Ribbon/Feign等常用组件,同时涉及Redis或Elasticsearch等中间件场景,便于深入学习服务治理、负载均衡、容错处理与配置管理等关键机制。压缩包内置部署说明和演示材料,兼容Windows 10/11环境,可使用文档从源码启动、配置服务并逐步完成调试。已有194人学习/下载,适合希望以完整项目快速上手Spring Cloud微服务开发、理解服务治理与容错机制的读者。
1. 当谷粒学院变成毕业设计:Spring Cloud 在线视频点播平台的完整解法
“把谷粒学院改造为基于 Spring Cloud 的在线视频点播平台”这个需求,在每年的毕业季都会集中出现。谷粒学院是尚硅谷课堂里被反复练习的教学项目,课程、讲师、订单、支付、权限,业务面齐全得不像一份毕设,但它默认是单体结构。毕业设计的真正难点不是页面少一个轮播图,而是把微服务架构完整落地,把服务拆分依据、配置中心用法、服务间调用这些问题在答辩时讲清楚。
这篇文章守一个原则:能演示优先于能并发。我会给你一条可复现的改造路线,把单体谷粒学院按业务域切出四个核心服务,接入 Nacos 做注册与配置中心,再叠加一层网关把视频点播的鉴权链路打通。适合能写后端但对微服务项目缺乏整体经验的在校学生,也适合想用同一套模型快速改造自己毕设的开发者。
2. 谷粒学院的微服务拆分:先画边界,再动代码
2.1 单体时代的谷粒学院模块构成与拆解依据
在动手改项目之前,要先把原项目的包名和依赖图翻一遍。谷粒学院的后端通常按 controller、service、mapper 三层组织,前台接口和后台管理接口混在同一个 web 模块里,登录用的 JWT 工具类被多个模块复制引用,视频上传则直接写本地磁盘路径。改造的第一步不是新建工程,而是把这些横向依赖列出来:哪些是无状态的公共工具,哪些是带事务的业务单元。
我习惯把服务边界画在“数据归属”上。一个数据表只归一个服务写,其他服务只能通过接口读,这是拆分时不打架的前提。毕业设计里如果被问“为什么不把所有表放一个库”,答案不是性能,而是团队协作和独立交付的边界。面试官想听的是高内聚低耦合的理解,而不是把数据库主从复制那一套套进来。
2.2 从数据库视角看服务边界:单库单类到多库独立
在线视频点播平台的核心数据集可以分成四组:用户权限组、课程内容组、视频资源组、运营记录组。按这个分组去定位谷粒学院原有的表,得到下表:
| 微服务名 | 对应谷粒学院的原有功能 | 独立库中的核心表 |
|---|---|---|
| edu-user | 登录注册、权限管理 | user、role、user_role、user_course |
| edu-course | 课程与讲师展示、课程分类、章节管理 | course、teacher、course_chapter、course_section |
| edu-video | 视频上传、播放地址、观看记录 | video、video_category、play_record |
| edu-data | 运营统计与订单快照(可选保留) | statistics_daily、order_snapshot |
一个典型的谷粒学院改造项目会按这个粒度拆分服务,再决定每个服务是独立数据库还是物理共库逻辑隔离。如果答辩设备是一台 8G 内存的笔记本,我一般建议每个服务独立 schema,但共用同一个 MySQL 实例,这样部署时不至于因为要开四个 MySQL 容器而把资源耗尽。独立 schema 同样能以“独立数据库”的身份进入架构图,概念上不亏。
2.3 拆分后的工程目录与 Maven 父工程搭建
拆分后的工程结构我通常用一套 common 加四个业务的 maven 多模块模式。父 pom 只管理依赖版本,业务服务不直接使用 spring-boot-maven-plugin 的 repackage,否则构建时会多出可执行包,导致模块间引用异常。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
父 pom 里的两段 dependencyManagement 是最关键的部分。spring-cloud-dependencies 负责 gateway、openfeign 这些组件版本,spring-cloud-alibaba-dependencies 负责 nacos discovery、nacos config、sentinel 的版本。两者版本号不能随意配,要跟 Spring Boot 主版本对应上:Boot 2.7 对应 Cloud 2021.0.x,Alibaba 则用 2021.0.5.0 这一档。版本组合写岔了,启动时最典型的报错是 Nacos 客户端类找不到或 DispatcherServlet 路径被注册中心覆盖。
<modules>
<module>guli-common</module>
<module>edu-user</module>
<module>edu-course</module>
<module>edu-video</module>
<module>edu-gateway</module>
</modules>
module 列表保持五个模块,common 放工具类、统一返回体、全局异常拦截器,gateway 单独占一个模块,因为 spring mvc 和 webflux 的依赖冲突问题,只有在父子模块隔离时最干净。后续每新增一个服务,在 modules 里加一行即可,常量类和工具类统一走 common,禁止再出现跨服务的业务代码直接 import。
2.4 为什么这样拆:毕业设计里最容易被追问的服务粒度问题
拆分粒度在答辩时最容易翻车。常见两种极端:一种是把 controller 拆开但 service 还是互相 new,等于拆了个寂寞;另一种是把一张表拆成一个服务,接口调用链路变成俄罗斯套娃。这里选用户、课程、视频三个服务,是因为它们各自有独立的数据生命周期,视频服务可以脱离课程服务单测,课程服务不依赖视频服务的表也能展示。即便课程下架,视频记录仍然保留在视频库,这就构成一个基本完整的服务边界故事。
服务间通信我用 OpenFeign 而不是直接 RestTemplate,理由是 Feign 把调用方要用的接口签名暴露出来,交接给队友时能一眼看清依赖关系。另外,gateway、ribbon、hystrix 这三个词经常出现在 Spring Cloud 面试题中被问,但在这套点播平台里,真正演给老师看的服务治理是 nacos 下的实例上下线与 Feign 的负载均衡,次要的容错可以交给 sentinel。把这几个点想清楚,拆分的理由就立得住。
3. Spring Cloud 整合 Nacos:注册中心加配置中心的落地参数
3.1 注册中心选型:为什么是 Nacos 而不是 Eureka
Spring Cloud 的老牌注册中心 Eureka 2.0 停止维护后,国内新项目基本都走向 Nacos。对毕业设计来说,选 Nacos 的另一个理由是它自带控制台,可以在答辩现场直接展示服务列表和健康状态,比只能写几张配置文件截图更有说服力。Nacos 同时承担注册中心和配置中心两个角色,省掉一个 Spring Cloud Config Server,模块数量少一个,部署机器的内存压力也小一些。
Eureka 只解决服务注册发现,配置管理还要另接 Spring Cloud Config;Nacos 把两者合并,且配置更新支持 HTTP 长轮询,客户端监听命名空间下的 Data ID,变化几秒内就能推送到本地。对于在线视频点播这种需要快速调整视频转码参数、播放防盗链开关的场景,这个动态能力正好用得上。
| 对比项 | Eureka | Nacos |
|---|---|---|
| 服务注册发现 | 支持 | 支持,自带控制台 |
| 配置管理 | 需额外组件 | 原生支持 |
| 支持命名空间隔离 | 无 | 支持 |
| 毕业设计演示友好度 | 一般 | 高 |
3.2 搭建 Nacos 并配置 bootstrap.yml 的最小启动流程
Nacos 的安装有两条路:Windows 本地直接用压缩包启动,或者用 docker-compose 单机拉起。我只说最常见的 Windows 场景:拿到 nacos-server 压缩包后解压,进入 bin 目录执行 startup.cmd -m standalone ,看到日志里出现 "Nacos started successfully" 后打开 http://localhost:8848/nacos,默认账号密码都是 nacos。
服务接注册中心的配置分两层。bootstrap.yml 写 Nacos 地址和配置中心绑定关系,application.yml 保留业务数据源、redis、mybatis 等本地配置。把这两层拆开,是因为配置中心的 Data ID 和 group 属于启动早期就要确定的元信息,如果放 application.yml 里,加载顺序会造成 Nacos 还没连接上就从本地读了旧值。
spring:
application:
name: edu-video
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: dev
group: GULI_VOD_GROUP
config:
server-addr: 127.0.0.1:8848
namespace: dev
group: GULI_VOD_GROUP
file-extension: yaml
namespace 我固定用 dev,group 用 GULI_VOD_GROUP,这样四个服务共享一组配置,但不同服务的 Data ID 不同。Data ID 的默认命名规则是 ${spring.application.name}.${file-extension} ,即 edu-video.yaml。配置中心里已经存在的同名 Data ID 会覆盖本地同名配置,这是很多人改配置不生效的第一个排查点:先看 Nacos 控制台里的配置值改没改,再往下看服务端日志的 refresh 记录。
3.3 服务间调用落地:OpenFeign 对接课程服务与视频服务
注册中心连通后,第一件该做的事是验证服务发现,而不是马上做业务。最简单的验证方法是写一个 feign 接口,从一个服务去调另一个服务。课程服务需要在配置里引入 spring-cloud-starter-openfeign,并且把接口扫描挂到启动类上。
@FeignClient(name = "edu-video", path = "/api/video", fallback = VideoClientFallback.class)
public interface VideoClient {
@GetMapping("/play/{videoId}")
R getPlayInfo(@PathVariable("videoId") String videoId);
}
Feign 接口的 name 要和目标服务的 spring.application.name 完全一致,大小写都不能差。path 是目标服务在网关或自身 controller 上的统一前缀。fallback 开启的前提是熔断组件已引入,不然 fallback 类一直不会触发;某些教学版本里只加 openfeign 不加 sentinel,fallback 写了也在运行时不生效,这一点答辩前必须验证一次。
3.4 配置动态刷新与命名空间隔离:毕设必备的分环境配置方案
毕业设计通常需要同时演示本地开发和打包部署两套环境。配置中心里建两个命名空间,dev 指向本地数据库 3306,demo 指向部署服务器上的数据库。服务端代码不需要改,启动时通过 --spring.cloud.nacos.config.namespace=demo 覆盖即可。
对需要热更新的参数,在类上标 @RefreshScope ,修改 Nacos 配置后无需重启服务。视频服务的转码参数就可以做成动态配置,例如封面图尺寸、预览视频时长、上传临时目录。
@RefreshScope
@Component
@ConfigurationProperties(prefix = "vod")
public class VodProperties {
private Integer previewSeconds = 30;
private Long maxUploadSizeM = 100L;
// getter setter 省略
}
配置项出现在 Nacos 的 edu-video.yaml 里时,优先读 Nacos 值;没配时回退到本地的默认值。要小心的是 Redis 连接和线程池大小这类配置不适合配在 Nacos 的动态刷新范围里,它们涉及资源初始化,刷新只改变数值但底层连接池已经建好,容易产生一半新一半旧的状态。把 VodProperties 严格限制在业务可变的参数上,才是动态刷新的正确用法。
4. 在线视频点播核心改造:上传、转码与基于 Spring Cloud 的调用链
4.1 视频文件落位:从本地磁盘改成对象存储加媒体处理
教学内容里的谷粒学院默认把视频文件往本地磁盘存。这个做法在单体测试时看不出毛病,一旦服务实例超过一个,文件路径就失效了:网关把请求转发给实例 A,视频却被上传到了实例 B 的磁盘。所以改造第一步要移动存储边界,常见做法是对象存储配合媒体处理服务:对象存储负责保存原始文件和转码产物,媒体服务负责生成播放地址和封面截图,前端拿到直传地址后直接播放。
选择哪一家的对象存储不必纠结,毕业设计重点关注的是“换掉本地路径”这个架构动作。上传逻辑要改成两段式:先由视频服务签发一个临时授权和目录 key,再把文件由前端直传进对象的临时目录。这样大文件不经过业务服务器,内存和带宽都省下来,答辩时能讲出至少一个性能优化的落点。
转码参数可以做成 Nacos 动态配置,这也是前面把配置中心用起来的地方。比如定义一组码率和清晰度档位,低码率版本用于预览,原画留着给完整播放,两档转码任务由媒体处理服务异步执行,视频服务的接口先返回“转码中”状态,等服务回调通知后再更新状态。这个异步回调用可靠消息还是 HTTP 回调,是后续会被追问的点,实际用 HTTP 状态回调加定时任务补偿就能在毕设里讲通。
4.2 拆出一个 video-service:上传凭证、播放信息与观看进度接口
一个常规的视频服务需要提供三类接口:上传凭证接口、播放详情接口、观看进度上报接口。上传凭证接口负责校验登录状态并返回真实上传的临时 key;播放详情接口给课程服务下调用,返回不同清晰度的地址;观看进度接口记录每节课看到第几秒,便于下次续播。把这三类接口放进同一个服务,课程模块和用户模块都不必再接触视频文件的原始逻辑。
@RestController
public class VideoController {
private final VideoService videoService;
@GetMapping("/api/video/upload/token")
public Result<Map<String, String>> token() {
Map<String, String> token = videoService.buildUploadToken();
return Result.ok(token);
}
@GetMapping("/api/video/play/{videoId}")
public Result<VideoPlayInfo> play(@PathVariable Long videoId) {
return Result.ok(videoService.getPlayInfo(videoId));
}
@PostMapping("/api/video/progress")
public Result<Boolean> progress(@RequestBody ProgressRequest request) {
return Result.ok(videoService.saveProgress(request));
}
}
controller 层只保留一个薄壳,核心逻辑在 VideoService 里构建上传凭证。注意所有接口都走 /api/video 前缀,这个前缀被网关路由和 Feign 的 path 同时引用,改前缀要同步改两处,否则网关和后端 controller 能通,Feign 客户端却报 404。play 接口要做两层权限判断:一是用户是否已登录,二是这个课程是否已购买或试听,视频服务和课程服务通过 Feign 查一次订单状态,再决定能不能返回播放地址。
4.3 网关层对播放鉴权的处理:JWT 在点播链路里生效
网关是整条链路的第一个入口。把 JWT 校验放在网关,业务服务就不再重复解析 token,这是微服务改造必然要做的一步。Spring Cloud Gateway 里放 JWT 校验可以用 GlobalFilter,更可控的做法是定义一个全局过滤器,对白名单之外的路径统一校验。
spring:
cloud:
gateway:
routes:
- id: video-route
uri: lb://edu-video
predicates:
- Path=/api/video/**
filters:
- StripPrefix=1
这里 lb://edu-video 让网关通过 Nacos 的服务发现去转发,服务名和注册中心里的完全一致。Path 断言覆盖视频服务的所有接口,StripPrefix 把 /api/video 去掉一层。我的习惯是把登录接口加进白名单,其余全部走 JWT 校验,JWT 过期或签名不对时直接返回 401,不让请求进入业务引擎。
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("Authorization");
// 校验失败:设置 401 响应,不继续转发
// 校验成功:把 userId 写入请求头 X-User-Id,供下游服务读取
}
}
AuthFilter 里校验 token 的下游透传要留一个单独的 header,避免业务服务去解析 JWT 时出现依赖冲突。视频服务从 header 里读用户 ID。这里有一个毕设项目经常踩的坑:网关用的 webflux 和业务服务用的 spring mvc 不在同一个上下文,不能直接把 servlet 的工具类放进网关模块。公用的 JWT 工具类要抽到 guli-common,确保它不依赖 HttpServletRequest。
4.4 跨服务的课程与视频绑定:最终一致性怎么在毕设里落地
课程服务发布新课时,需要创建课程记录,同时让视频服务把上传的视频挂到课程章节下。两个操作跨服务、跨库,分布式事务的存在会拖累性能,毕设里也不需要引入 Seata 全套。常见的表达方式是本地消息表或任务补偿,我选用后者,原因是可解释性好:
task:
video-bind-interval-ms: 30000
定时任务每 30 秒扫一遍“已发布但未绑定视频”的课程,对缺少 binding 的章节,重新调用视频服务补一次绑定。接口做幂等,视频服务以 courseId + chapterId 作为唯一索引,重复绑定直接返回成功。这个方案在答辩时能对应上“最终一致性”的概念,也不会因为引入太多中间件把部署复杂度抬上去。
5. 毕业设计部署到 Windows 服务器:开一套能演示的 Spring Cloud 系统
5.1 打包前的检查与一条命令全量构建
部署前先把本地环境清一遍:确认 Nacos、MySQL、Redis 都已启动,且四个服务的数据源都指向同一个 MySQL 实例的四个 schema。线上与本地用不同命名空间处理后,打包时不需要改代码。构建命令固定为下方这条,只打包可执行模块:
mvn clean package -DskipTests -pl edu-gateway,edu-user,edu-course,edu-video -am
-pl 指定要打包的模块, -am 会把它们共同依赖的 guli-common 也一起构建。不要直接对整个父工程执行 mvn package ,否则 guli-common 也被打成可执行 jar,被多个服务同时引用后,类加载器会出现相同全限定名的类,启动时偶尔报 NoSuchMethodError,排查起来很耗时。
5.2 用批处理脚本按顺序拉起全套服务
在 Windows 服务器上跑微服务,最忌一股脑开十个黑窗口。我用一个 deploy.bat 管理启动顺序,每启一个服务就检测一次端口是否监听,再启下一个,失败时窗口停住方便看日志:
@echo off
start "nacos" /min cmd /c "startup.cmd -m standalone"
timeout /t 15 /nobreak >nul
start "gateway" /min java -jar edu-gateway.jar --spring.profiles.active=prod --server.port=9000
timeout /t 5 /nobreak >nul
start "user" /min java -jar edu-user.jar --spring.profiles.active=prod --server.port=9001
start "course" /min java -jar edu-course.jar --spring.profiles.active=prod --server.port=9002
start "video" /min java -jar edu-video.jar --spring.profiles.active=prod --server.port=9003
这段脚本里每个服务都指定独立端口,网关固定 9000,业务服务从 9001 起排。网关即使在业务服务之前启动,也要等到至少一个服务注册进 Nacos 后才会产生可用的路由目标,所以留出 5 秒间隔再启动下一批。
5.3 答辩演示顺序与三个必查的验证点
演示不要从首页开始,要先展示 Nacos 控制台的实例列表,再打开网关地址访问登录接口,最后播放一段视频。三个验证点依次是服务注册、服务间调用、视频鉴权,正好对应项目里三个最难讲的部分。
| 演示节点 | 操作 | 验证结果 |
|---|---|---|
| 服务注册 | 打开 Nacos 服务列表 | 四个服务状态为上线,健康实例数大于 0 |
| 服务间调用 | 用 curl 调课程服务中的播放信息接口 | 返回包含视频服务的播放地址,证明 Feign 链路通 |
| 视频鉴权 | 不带 token 访问播放地址 | 返回 401,网关拦截生效 |
最后检查网关日志里有没有一条请求从 /api/video/play/1 转发到 lb://edu-video 的记录。毕业答辩时被问到“你怎么证明服务间真的调通了”,把这段日志和 Feign 的调用时间一起贴出来,比任何架构图都直接。顺手用下面这条命令验证一下网关到视频服务的整条链路返回值:
curl http://127.0.0.1:9000/api/video/play/1 -H "Authorization: Bearer ${TOKEN}" -o /dev/null -w "%{http_code}"
返回 200 后再在 Nacos 控制台把 edu-video 的实例停掉一个,重新执行上面的 curl,观察网关是否自动把流量切到另一个实例,这就是给评委看的服务治理演示。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)