
简介这是一套基于 SpringCloud 微服务架构与 Vue3 前端框架搭建的在线学习平台完整源码面向希望深入微服务与前后端分离开发的学习者也可直接作为毕业设计、课程设计、大作业或工程实训项目使用。项目采用 SpringCloud 实现服务注册、网关与业务拆分前端以 Vue3 构建交互界面覆盖课程管理、订单、文件上传等核心业务模块初始账号 admin、密码 123456 可直接登录体验。压缩包共 150 个文件约 2.76MB其中 93 个 Java 源文件承载后端业务逻辑19 个 XML 与 7 个 YML、6 个 Properties 负责依赖与配置另有 17 张 PNG 界面截图、1 个 SQL 建表脚本及若干 JSON、MD 等辅助文件结构清晰便于按模块阅读。目前已有 223 人学习下载适合借此理解微服务拆分、接口调用与前端联调思路快速搭建可运行的学习平台原型。1. 从单体到微服务在线学习平台为什么值得用 SpringCloud Vue3 重做一遍很多团队做在线学习平台第一版几乎都是单体架构一个 SpringBoot 工程塞下用户、课程、订单、直播、题库所有模块前端一个 Vue 项目全量打包。上线初期没问题等到课程数破万、日活过千、营销活动一上问题就集中爆发——课程列表接口拖垮整个应用改一个优惠券逻辑要全量回归前端首屏加载三秒起步。这不是技术选型错了是架构没跟上业务节奏。SpringCloud 解决的是后端「拆」和「治」的问题把用户、课程、订单、学习记录拆成独立服务各自选型、独立部署、按需扩容再用注册中心、网关、配置中心把它们重新组织成一个整体。Vue3 解决的是前端「快」和「稳」的问题Composition API 让课程播放页这种重交互逻辑可以按功能聚合Vite 冷启动秒级配合后台管理系统能快速搭出运营需要的课程管理、订单查询、数据看板。这套组合不是赶时髦是当你的平台从「能跑」走向「能扛」时最稳妥的一条路。下面按我实际落地的顺序把选型、拆分、联调、避坑一次讲透。2. 服务拆分与 SpringCloud 组件选型哪些必须上哪些可以先省2.1 在线学习平台的领域边界怎么切拆服务最容易翻车的地方是按技术分层拆Controller 一个服务、Service 一个服务而不是按业务能力拆。在线学习平台的核心领域其实很清晰用户与权限、课程与章节、订单与支付、学习进度与笔记、题库与考试、消息通知。我一般会先把这六块画出来再判断哪些必须独立部署。判断标准有三条数据变更频率是否差异大、是否有独立扩容需求、故障是否会互相拖垮。课程服务读多写少订单服务写多且涉及金额一致性学习进度服务写入极频繁每次心跳都可能更新这三者必须拆开。用户和权限初期可以合在一起消息通知可以最后拆。常见做法是先拆出网关、用户、课程、订单四个服务跑通链路再逐步把学习进度和题库独立出去。提示不要一上来就拆十个服务。服务越多分布式事务、链路追踪、联调成本呈指数上升。四个服务能跑通再谈扩展。2.2 SpringCloud 组件清单与版本对齐SpringCloud 的版本坑是新手第一大血泪经验SpringCloud 版本必须和 SpringBoot 版本严格对应否则启动直接报NoSuchMethodError。截至 2026 年主流稳定组合是 SpringBoot 3.2.x 配 SpringCloud 2023.0.x注册中心用 Nacos网关用 Spring Cloud Gateway配置中心也用 Nacos远程调用用 OpenFeign熔断降级用 Sentinel。组件作用是否必须替代方案Nacos注册中心 配置中心必须Eureka ConfigGateway统一入口、鉴权、限流必须Zuul已过时OpenFeign服务间声明式调用必须RestTemplateSentinel熔断、限流、降级强烈建议Hystrix已停更Seata分布式事务按需本地消息表Sleuth Zipkin链路追踪按需SkyWalking选型理由很直接Nacos 一个组件同时解决注册和配置省一套部署Gateway 基于 WebFlux 非阻塞比 Zuul1 性能好一个量级Sentinel 有控制台可以实时调规则比改配置文件重启友好得多。Seata 我建议订单和课程库存扣减场景再上初期用本地消息表 最终一致性就够别为了「微服务完整」硬上分布式事务。2.3 最小可运行骨架的搭建步骤先建一个父工程统一管理依赖版本再逐个建子模块。父工程pom.xml关键部分如下!-- 父工程 dependencyManagement统一锁版本 -- dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version3.2.5/version typepom/type scopeimport/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2023.0.1/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2023.0.1.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement逻辑说明dependencyManagement只声明版本不引入依赖子模块用到时无需再写版本号避免各模块版本打架。参数说明SpringBoot 3.2.5 对应 SpringCloud 2023.0.1SpringCloud Alibaba 2023.0.1.0 是与之匹配的版本三者必须成套使用。改版本时三个一起改不要只动一个。网关模块的核心配置# gateway 模块 application.yml server: port: 8080 spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: routes: - id: course-service uri: lb://course-service # lb 表示从注册中心负载均衡 predicates: - Path/api/course/** filters: - StripPrefix1 # 去掉第一层路径再转发逻辑说明lb://course-service让网关从 Nacos 找到 course-service 的实例列表并轮询转发StripPrefix1把/api/course/list变成/course/list再转发给后端。参数说明server-addr是 Nacos 地址生产环境要配集群地址用逗号分隔Path断言支持通配/api/course/**匹配所有子路径。启动顺序必须是 Nacos 先起再起各业务服务最后起网关否则网关启动时找不到实例会报错但服务本身能跑容易误判。3. Vue3 前端工程化从 Vite 初始化到与微服务网关对接3.1 用 Vite 建 Vue3 项目与目录约定Vue3 项目现在没有理由不用 Vite冷启动和热更新比 Webpack 快太多。初始化命令# 创建 Vue3 TypeScript 项目 npm create vitelatest study-platform-web -- --template vue-ts cd study-platform-web npm install # 安装路由、状态管理、请求库、UI 组件库 npm install vue-router4 pinia axios element-plus npm run dev逻辑说明--template vue-ts直接生成 TypeScript 模板省去手动配 tsconfig。参数说明vue-router4是 Vue3 专用版本装成 3.x 会报错pinia是 Vue3 官方推荐的状态管理替代 Vuex。目录我一般这样约定src/api放接口封装src/stores放 Piniasrc/views放页面src/components放公共组件src/utils放请求拦截器。3.2 Composition API 组织课程播放页的复杂逻辑课程播放页是在线学习平台最复杂的页面视频播放、进度上报、笔记、评论、章节切换全在一个页面。用 Options API 写data、methods、computed 会被拆得七零八落改一个进度上报要跳三个地方。Composition API 按功能聚合一个useProgress管进度一个useNote管笔记互不干扰。// src/composables/useProgress.js import { ref, onUnmounted } from vue import { reportProgress } from /api/learning export function useProgress(courseId, chapterId) { const currentTime ref(0) const timer ref(null) // 每 15 秒上报一次避免频繁写库 const startReport () { timer.value setInterval(async () { await reportProgress({ courseId, chapterId, progress: Math.floor(currentTime.value) }) }, 15000) } const stopReport () { if (timer.value) clearInterval(timer.value) } // 组件卸载必须清理定时器否则内存泄漏 onUnmounted(stopReport) return { currentTime, startReport, stopReport } }逻辑说明把进度上报逻辑封装成组合式函数播放组件里只需const { currentTime, startReport } useProgress(id, cid)逻辑内聚且可复用。参数说明15000是上报间隔毫秒数太短会给学习进度服务造成写压力太长会丢进度onUnmounted里清理定时器是必须的否则切换章节时旧定时器还在跑进度会串。3.3 Axios 拦截器对接网关鉴权前端所有请求都走网关Token 统一在请求头带401 统一跳登录。封装如下// src/utils/request.js import axios from axios import { useUserStore } from /stores/user import router from /router const service axios.create({ baseURL: import.meta.env.VITE_API_BASE, // 指向网关地址 timeout: 10000 }) service.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) service.interceptors.response.use( res res.data, err { if (err.response?.status 401) { router.push(/login) // Token 失效统一跳登录 } return Promise.reject(err) } ) export default service逻辑说明请求拦截器统一注入 Token响应拦截器统一处理 401业务代码里不用再关心鉴权。参数说明baseURL从环境变量读开发环境指向http://localhost:8080网关生产环境指向域名timeout设 10 秒视频相关接口可单独放宽。注意网关的鉴权过滤器要放行/login和静态资源路径否则登录请求本身会被拦。注意Vue3 的reactive解构后会丢失响应性这是新手高频翻车点。要么用toRefs包一层要么直接用ref。课程列表这种数组用ref更省心。4. 联调与数据一致性跨服务调用最容易出事的三个环节4.1 OpenFeign 调用与超时配置订单服务下单后要扣课程库存、写学习记录这两个动作分别调课程服务和进度服务。Feign 默认超时很短网络抖动就报Read timed out必须显式配置# order-service application.yml feign: client: config: default: connectTimeout: 3000 readTimeout: 5000 loggerLevel: basic sentinel: enabled: true # 开启 Sentinel 对 Feign 的熔断支持逻辑说明connectTimeout是建立连接超时readTimeout是等待响应超时分开配置便于定位是网络问题还是下游处理慢。参数说明连接 3 秒、读取 5 秒是经验值下游有复杂查询可放宽到 8 秒loggerLevel: basic只打印方法和 URL排查时够用full会打印请求体生产环境别开日志量爆炸。4.2 学习进度上报的幂等处理学习进度服务写入极频繁用户网络重试、前端定时器重复触发都会导致同一条进度被写多次。幂等方案我一般用「课程 ID 章节 ID 用户 ID」做唯一键数据库层ON DUPLICATE KEY UPDATE-- 学习进度表唯一键保证幂等 CREATE TABLE learning_progress ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, course_id BIGINT NOT NULL, chapter_id BIGINT NOT NULL, progress INT DEFAULT 0, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_chapter (user_id, course_id, chapter_id) ); -- 上报时用 upsert重复上报只更新进度 INSERT INTO learning_progress (user_id, course_id, chapter_id, progress) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE progress GREATEST(progress, VALUES(progress));逻辑说明唯一键uk_user_chapter保证同一用户同一章节只有一条记录ON DUPLICATE KEY UPDATE让重复插入变成更新。参数说明GREATEST(progress, VALUES(progress))保证进度只增不减防止用户拖回进度条导致进度倒退。这个方案比在应用层查一次再决定插入还是更新少一次数据库往返高并发下差别明显。4.3 订单与库存的最终一致性下单扣库存不能简单用 Feign 同步调用课程服务挂了订单就下不了。我一般用本地消息表订单服务在本地事务里同时写下订单和一条「待发送」消息然后异步发 MQ 通知课程服务扣库存扣减成功回执后把消息标记为已完成。// 订单服务本地事务内写订单 消息 Transactional public void createOrder(OrderDTO dto) { orderMapper.insert(dto.toOrder()); // 同事务写消息保证订单和消息要么都成功要么都失败 messageMapper.insert(new Message(COURSE_STOCK_DEDUCT, dto.getCourseId())); }逻辑说明订单和消息在同一本地事务避免订单成功但消息丢失。参数说明消息表要有状态字段待发送/已发送/已完成和重试次数字段定时任务扫描待发送消息投递到 MQ消费方幂等处理。这套方案比 Seata 轻适合课程库存这种允许短暂不一致的场景。提示跨服务调用一定要加 Sentinel 熔断否则下游一个慢接口会把上游线程池占满引发雪崩。这是微服务最经典的故障模式。5. 避坑与排查这套组合上线前后最容易踩的五个坑坑一Nacos 服务注册上了但网关 503。现象是服务在 Nacos 控制台能看到但通过网关访问一直 503。原因通常是服务注册的是内网 IP网关和业务服务不在同一网段或者服务没配spring.cloud.nacos.discovery.ip。解决显式指定注册 IP或在同一台机器上先跑通再上多机。排查时先直接访问业务服务端口能通说明是网关或网络问题不通说明是服务本身问题。坑二Vue3 打包后接口 404开发环境正常。现象是npm run dev一切正常npm run build部署后所有接口 404。原因是开发环境配了 Vite 代理生产环境没配 Nginx 反向代理或者baseURL写死了 localhost。解决生产环境用环境变量注入网关域名Nginx 配置/api转发到网关。检查import.meta.env.VITE_API_BASE在生产构建时是否被正确替换。坑三Feign 调用报No qualifying bean。现象是启动时提示找不到 FeignClient 的 Bean。原因通常是启动类没加EnableFeignClients或者 Feign 接口所在包不在启动类扫描范围内。解决启动类加注解并指定basePackagesFeign 接口放在能被扫描到的包下。这个错误信息有迷惑性实际和 Bean 定义无关是扫描路径问题。坑四跨域配置在网关和业务服务重复导致响应头重复。现象是浏览器报The Access-Control-Allow-Origin header contains multiple values。原因是网关配了 CORS下游服务也配了两个头叠加。解决CORS 统一在网关配业务服务全部去掉跨域配置。网关的 CORS 配置要用globalcors而不是过滤器否则预检请求会被鉴权拦截。坑五学习进度定时器在组件切换后仍在跑。现象是切换章节后进度数据错乱或者控制台报组件已卸载还在更新状态。原因是setInterval没在onUnmounted里清理。解决所有定时器、WebSocket 连接、事件监听都在onUnmounted里清理这是 Composition API 里最容易忘的一步。我习惯把清理逻辑和创建逻辑写在同一个组合式函数里强制成对出现。6. 进阶技巧用 Pinia 持久化 路由守卫把登录态和权限做扎实登录态管理是在线学习平台的高频需求用户刷新页面不能掉登录不同角色学员、讲师、运营看到的菜单还不一样。我一般用 Pinia 配合持久化插件再加路由守卫做权限拦截这套组合比在组件里到处判断localStorage干净得多。先装持久化插件npm install pinia-plugin-persistedstate然后在 store 里声明持久化字段// src/stores/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: , role: , // student / teacher / admin userInfo: {} }), persist: { key: study-user, storage: localStorage, paths: [token, role] // 只持久化这两个userInfo 每次重新拉 }, actions: { logout() { this.token this.role this.userInfo {} } } })逻辑说明paths指定只持久化 token 和 roleuserInfo 不持久化是为了保证用户信息实时性避免改了昵称刷新后还是旧的。参数说明key是 localStorage 的键名改键名会导致老用户登录态丢失上线后别随便改。storage可以换成 sessionStorage关浏览器就失效看安全要求。路由守卫做权限拦截// src/router/index.js router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.token) { next({ path: /login, query: { redirect: to.fullPath } }) } else if (to.meta.roles !to.meta.roles.includes(userStore.role)) { next(/403) // 角色不匹配跳无权限页 } else { next() } })逻辑说明requiresAuth标记需要登录的路由roles标记允许访问的角色守卫里统一判断。参数说明redirect参数记录用户原本要去的页面登录成功后跳回去体验更好。注意守卫里用useUserStore()必须在router创建之后调用否则 Pinia 还没初始化会报错。验证这套方案是否做扎实我一般做三个测试刷新页面登录态是否还在、直接输入无权限路由是否被拦、Token 过期后请求是否自动跳登录。三个都过登录态这块基本可以放心。最后说个我自己的习惯微服务项目我从不追求一次拆到位而是先让四个服务跑通一条完整链路——登录、看课程、下单、上报进度链路通了再谈优化。Vue3 这边我坚持所有副作用逻辑都封装成组合式函数组件里只留模板和调用这样半年后回来看代码还能快速定位。这套 SpringCloud Vue3 的组合难的不是单个技术是让它们协同起来不出乱子慢一点拆、稳一点联调比什么都重要。希望帮到你。本文还有配套的精品资源点击获取