ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

在线教育系统微服务架构拆解:SpringCloud+Vue项目实战解析

在线教育系统微服务架构拆解:SpringCloud+Vue项目实战解析 简介在线教育系统online_edu项目包面向Java后端开发学习者与微服务架构实践者提供一套完整的前后台分离教学案例。系统涵盖课程、问答、文章三大前台模块后端采用SpringBoot SpringCloud MyBatis-Plus整合方案结合MySQL分库设计、Redis缓存、ActiveMQ消息队列并接入阿里云OSS与视频点播服务前端基于Node.js Vue.js构建。业务层还包含JWT单点登录、Swagger接口文档、POI批量导入用户、ECharts图表展示等实用功能可作为毕业设计或简历项目参考。压缩包为zip格式大小约198KB目前已有1154人学习下载。资源内含项目源码与配套说明便于按模块梳理微服务拆分、分库设计、分布式登录及第三方视频/存储接入等完整实现思路适合希望从单体转向微服务开发、快速理解前后端分离协作模式的初中级开发者使用。1. online_edu 在线教育系统拆解这套 SpringCloud Vue 微服务项目能直接用来做什么把 online_edu 这套在线教育系统按 SpringCloud Vue.js 微服务项目来拆核心是覆盖在线教育产品的最小闭环前台用户有课程、问答、文章三个入口后台运营平台管内容、管数据、出报表。前后端分离是骨架后端是 SpringBoot SpringCloud MyBatis-Plus MySQL Docker Maven前端是 Node.js Vue.js中间件还带 Redis、ActiveMQ、阿里云 OSS 和视频点播报表用 EChartsExcel 导入导出用 POI。对正在找 SpringBoot、SpringCloud 落地范例的人来说这套代码最直接的价值是能看一个真实项目怎么把服务拆分、数据持久化、异步消息、文件存储串成一条完整链路。适合刚学完 Spring、想上手微服务项目的 Java 从业者也适合需要一套完整可跑系统的毕业设计。下面把技术栈和链路拆开说。2. 服务划分与中间件选型SpringCloud 微服务、MySQL、Docker 是怎么搭起来的在动手看代码之前先把架构讲清楚。online_edu 虽然叫“一个系统”但落地到 Maven 工程里是多个 SpringBoot 模块组成的。前后端分离是最外层的边界Vue.js 工程负责页面渲染SpringBoot 服务只开放 JSON 接口。2.1 前台网站与后台运营平台课程、问答、文章三条业务线前台用户系统按业务分三块课程负责课程分类、课程详情、视频播放、课程资料下载问答负责用户提问、回答和采纳文章负责图文内容展示。课程和文章的区别在内容形态课程走“章节 视频 资料”的结构文章走“富文本 标签”的结构问答则是互动型业务数据关系更复杂。这三块业务如果塞进一个单体工程里后期改一个课程的字段可能把文章和问答的服务一起重启这在运营上完全不能接受。微服务化的直接收益就是三个模块各自开发、各自部署、各自扩容。后台运营平台则是反过来的它不直接面对 C 端用户而是对课程、问答、文章做审核和管理。运营端主要处理课程上下架、文章置顶、问答审核以及课程数据的统计分析。统计结果的展示用 ECharts运营数据的 Excel 导入导出用 POI。前台用户看到的课程列表和后台运营端管理的课程字段基本共用一套 DTO但需要把课程查询做成一个公共服务前台和后台都走这个服务再按角色过滤字段避免运营数据被误打到前台接口里。2.2 SpringCloud 微服务模块划分与 Gateway 路由把工程展开后典型结构是注册中心负责服务发现网关做统一入口和路由转发业务服务各自注册。我习惯把模块划分成下面这样模块职责关键依赖服务注册中心服务注册与发现SpringCloudNacos 或 EurekaAPI Gateway统一入口、路由转发、跨域处理SpringCloud Gatewaycourse-service课程、分类、视频点播、课程资料MyBatis-Plus、OSS、视频点播 SDKarticle-service文章管理、标签、关键字MyBatis-Plus、HttpClientqa-service问答、评论、采纳MyBatis-Plus、Redisadmin-service运营后台、审核、统计、报表ECharts 数据接口、POI 导出 Excel提示原文里没有指定注册中心具体用 Nacos 还是 Eureka。我一般建议新手优先用 Nacos控制台对注册列表、配置推送的展示更直观配置中心还能少写很多环境差异配置。但照着 SpringCloud 官方示例学的话用 Eureka 也完全能跑。Gateway 路由的配置是这套系统里最容易踩坑的地方先给一个常见的 path 路由写法spring: cloud: gateway: routes: - id: course-route uri: lb://course-service predicates: - Path/api/course/** - id: article-route uri: lb://article-service predicates: - Path/api/article/** - id: qa-route uri: lb://qa-service predicates: - Path/api/qa/**这个路由配置的要点是 uri 写成lb://表示走客户端负载均衡去调用注册中心里的服务名而不是直接写死 localhost。Path 断言用来区分同一端口下的不同前缀。为什么要统一走网关如果不经过网关前端要记住每个微服务的端口CORS 也得各自配一遍更麻烦的是跨服务调用时身份信息很难传递。网关里做一次统一的 token 校验把用户信息写进请求头下游服务直接读请求头这个链路是最省事的。2.3 Docker 部署 MySQL 与 Redis容器化环境的“后悔药”MySQL 在整套系统里是核心存储Redis 主要负责缓存课程分类、热点文章和问答的会话。开发环境我习惯用 Docker 把这两个中间件先拉起来避免本机装一堆东西翻车了不用重装系统。一个 docker-compose.yml 就能起两个服务version: 3.8 services: mysql: image: mysql:8.0 container_name: edu-mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-time-zone08:00 volumes: - ./mysql-data:/var/lib/mysql restart: unless-stopped redis: image: redis:7-alpine container_name: edu-redis ports: - 6379:6379 volumes: - ./redis-data:/data command: redis-server --appendonly yes restart: unless-stopped这里有几个参数值得说明。MySQL 的MYSQL_ROOT_PASSWORD是容器首次初始化时设置 root 密码的环境变量只对第一次启动生效所以很多人改密码改了没用因为数据卷里已经初始化过了。--character-set-serverutf8mb4指定编码避免插入 emoji 或中文生僻字时报 Incorrect string value 错误。时区参数TZ和--default-time-zone08:00一起设置防止数据库时间与服务器时间差八小时。Redis 的--appendonly yes开启 AOF 持久化防止容器重启后缓存数据全部丢失。我一般会把这套 compose 文件放在项目根目录的 docker/ 目录下执行docker compose up -d就能起服务。新手在装 MySQL 时经常会直接把宿主机已经占用的 3306 端口再映射一遍然后报 Address already in use。遇到这种情况先lsof -i:3306查占用再决定是停掉旧服务还是把容器端口换成 3307。血泪经验是这两条命令要在执行 docker compose 之前跑不要在容器启动失败之后才开始查。3. 后端实战SpringBoot 整合 MyBatis-Plus、ActiveMQ 与视频点播这一章进入代码层面。后端工程是标准的 Maven 多模块结构父子工程统一管理版本各个业务模块再引入自己需要的中间件依赖。对只想快速跑通系统的人来说pom.xml 的版本对齐往往比业务代码更费神。3.1 pom.xml 依赖与版本选择springboot 版本太高引发的连环坑先给父子工程的依赖框架。父 pom 用 dependencyManagement 统一版本这是 Maven 多模块项目的基本素养否则十个模块每个写一个版本的 SpringBoot光依赖冲突就够喝一壶。一个常见的配置是parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version spring-cloud.version2021.0.8/spring-cloud.version mybatis-plus.version3.5.3.1/mybatis-plus.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这里最需要注意的就是 SpringBoot 和 SpringCloud 的版本映射。Spring Cloud 官方给了一张版本对应表2021.0.x 对应 SpringBoot 2.6.x/2.7.x2022.0.x 对应 SpringBoot 3.0.x。很多人直接去 Maven 仓库拉最新版 SpringBoot 3.x结果 SpringCloud Gateway 的依赖引入之后类都找不到。这就是常说的 springboot 版本太高带来的问题3.x 把javax.*改成了jakarta.*MyBatis-Plus 在 3.5.3 之前用的是 javax老用法直接编译失败。如果你拿到的 online_edu 源码是 2.x 时代的写法最稳的是沿用 2.7.x 而不是升到 3.x。另一个容易忽略的是 Lombok 和 MyBatis-Plus 的配合。新版 MyBatis-Plus 需要 Lombok 在编译期生成 getter/setter如果你把 Lombok 从依赖里剔掉了MyBatis-Plus 的实体类在反射时会拿不到属性查出来的 Map 和实体类对不上这种报错非常难排查。3.2 application.yml 多环境配置端口、数据源、Redis多环境放在 application.yml 里管理实际项目中用 spring.profiles.active 区分 dev、test、prod 三个环境。核心配置长这样spring: profiles: active: dev --- spring: config.activate.on-profile: dev datasource: url: jdbc:mysql://localhost:3306/online_edu?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 activemq: broker-url: tcp://localhost:61616 user: admin password: admin cloud: nacos: discovery: server-addr: localhost:8848 mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.edu.online.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl server: port: 8081连接串里useSSLfalse这个参数要重点记后面避坑章节会专门讲 MySQL 的 SSL 连接错误。serverTimezoneAsia/Shanghai指定时区避免驱动 8.0 之后不认默认时区导致差八小时。map-underscore-to-camel-case打开下划线转驼峰数据库字段course_name能自动映射到courseName不用手写一堆 resultMap。log-impl配成 StdOutImpl 是为了在控制台直接看到 SQL新手调试阶段建议保留等上生产再关掉。SpringBoot 自带默认数据源 HikariCP不需要额外配连接池。如果你的工程里还引入了 DBCP 或 C3P0 的依赖要手动排除否则启动时会随机挑一个连接池实现行为表现完全不可控。3.3 MyBatis-Plus 实现课程分页与条件查询MyBatis-Plus 在这套系统里的定位是简化单表 CRUD。课程列表的典型查询是按分类筛选 按关键字模糊匹配 按销量排序 分页。用 MyBatis-Plus 的 QueryWrapper 写出来非常短RestController RequestMapping(/api/course) public class CourseController { Autowired private CourseService courseService; GetMapping(/page) public IPageCourse page(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer limit, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword) { PageCourse pageParam new Page(page, limit); LambdaQueryWrapperCourse wrapper new LambdaQueryWrapper(); wrapper.eq(categoryId ! null, Course::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), Course::getCourseName, keyword) .orderByDesc(Course::getBuyCount); return courseService.page(pageParam, wrapper); } }这段代码的逻辑是先构造 Page 对象LambdaQueryWrapper 用 lambda 表达式指向实体字段而不是写列名字段名改了编译器直接报错不会等运行时才返回空数据。eq方法的第一个参数是布尔条件categoryId 为空时直接跳过这个条件不用写 if 判断。like同理keyword 没传就不拼模糊查询。最后orderByDesc按销量倒序排。返回值用IPageCourse是为了把 total、pages 这些分页元数据一起返回给前端。这里要补一个关键点MyBatis-Plus 的分页不是开了就有的需要注册分页拦截器。很多人复现项目时发现 page 方法返回的 total 一直是 0或者 SQL 里没有 LIMIT就是没配这个拦截器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }PaginationInnerInterceptor传DbType.MYSQL是让插件生成 MySQL 方言的分页 SQL。如果你把数据库切到 PostgreSQL 还传 MYSQL分页 SQL 会按 MySQL 语法拼直接报语法错误。3.4 ActiveMQ 异步解耦视频发布与审核消息队列视频点播是这套系统里最容易理解异步消息价值的地方。运营在后台发布一门课程上传视频后如果立刻做转码、审核同步接口会长时间阻塞前端体验就是转圈圈。实际项目中用 ActiveMQ 做解耦上传接口只管把视频文件存到阿里云 OSS、拿到视频 ID然后往消息队列里丢一条“视频上传完成”的消息转码和审核由监听器异步处理。生产者的写法大致这样Service public class VideoPublishService { Resource private JmsMessagingTemplate jmsMessagingTemplate; public void publishVideo(VideoInfo videoInfo) { // 先存业务数据保证后续查询能看到记录 videoInfoMapper.insert(videoInfo); // 发送消息通知转码/审核模块异步处理 VideoPublishEvent event new VideoPublishEvent(); event.setVideoId(videoInfo.getId()); event.setOssUrl(videoInfo.getOssUrl()); jmsMessagingTemplate.convertAndSend(video.publish.queue, event); } }消费者的核心逻辑JmsListener(destination video.publish.queue) public void handlePublish(VideoPublishEvent event) { // 这里做视频转码或调用第三方审核接口 boolean success videoTranscodeService.transcode(event.getOssUrl()); if (success) { videoInfoMapper.updateStatus(event.getVideoId(), 1); } else { videoInfoMapper.updateStatus(event.getVideoId(), -1); } }关键点在于消息里不要带整个业务的完整数据只带 videoId 和 OSS 的 URL消费者需要更多信息时再查库。为什么不直接同步调用转码服务因为转码服务不可控第三方 API 可能出现几十秒的延迟同步调用会拖垮上传接口的吞吐量。发送消息基本是毫秒级操作接口能立刻返回这就是解耦的价值。ActiveMQ 的注意点集中在 broker-url 和连接方式上。SpringBoot 集成 ActiveMQ 时如果spring.activemq.in-memorytrue消息会自动落在一个内存 broker 里一旦应用重启消息全丢。联调阶段无所谓但如果你发现重启后消息不见了先看这个开关是不是被写成了默认 true。4. 前端 Vue.js 与 ECharts前后端分离、代理转发与打包部署后端接口就绪后进入前端的部分。online_edu 的 Vue.js 工程分成前台门户和后台运营两个入口但它们共用一套组件库和请求封装。前端拿到的接口地址是网关地址开发环境走 webpack 的 devServer 代理生产环境则直接把 dist 打包产物交给 SpringBoot 或 Nginx 托管。4.1 Vue 工程初始化与接口代理拿到 Vue 工程以后先不急着看业务页面把环境跑起来。npm install是第一个坎建议 Node.js 版本保持在 16 或 18webpack 4 和 node 18 容易有 OpenSSL 相关的报错这时需要修改 NODE_OPTIONS 参数。工程里最关键的配置文件是 vue.config.js接口代理就写在这里// vue.config.js module.exports { devServer: { port: 9528, // 前端开发服务端口 proxy: { /api: { target: http://localhost:8080, // 网关地址 changeOrigin: true, // 透传 Host 头 pathRewrite: { ^/api: /api } } } } }changeOrigin: true的意思是把请求头里的 Host 改成 target 地址很多后端服务会根据 Host 做校验不设置会偶发 403。pathRewrite里我保留了/api前缀因为网关的 Predicate 就是按/api/**匹配的如果前端把前缀去掉了网关直接返回 404。这里不建议用^/api: 这种重写除非后端接口本来就不带 /api 前缀。4.2 把 vue 打包放进 SpringBoot 中合并部署的两种做法前后端分离的开发环境是理所当然的但真正的坑在部署阶段。在服务器资源有限的环境中最常见的做法是“把 vue 打包放进 SpringBoot 里”也就是把前端构建产物放到 SpringBoot 的静态目录下然后一个进程对外提供页面和接口两种服务。操作分三步# 1. 前端工程根目录执行构建 npm run build # 2. 把构建产物复制到后端静态目录 cp -r dist/* ../online_edu-admin/src/main/resources/static/ # 3. 重新打包并启动 SpringBoot 服务 mvn clean package -DskipTests java -jar online_edu-admin/target/online_edu-admin.jar复制完 dist 之后目录结构大概是src/main/resources/ ├── static/ │ ├── index.html │ ├── css/ │ ├── js/ │ └── favicon.ico └── application.ymlSpringBoot 的静态资源匹配是有顺序的默认先匹配classpath:/static/命中不了再走 Controller。这意味着如果你在 Controller 里写了一个RequestMapping(/index.html)它会优先被静态目录的 index.html 拦截。还有一个更隐蔽的问题前端路由如果是 Vue Router 的 history 模式刷新/article/123时 SpringBoot 找不到对应的静态文件直接 404。解决办法是写一个 forward 的 ControllerController public class SpaForwardController { RequestMapping(value {/{path:[^\\.]*}, /**/{path:[^\\.]*}}) public String forward() { return forward:/index.html; } }这个正则的意思是不带点的路径都转发到 index.html避免图片、JS 文件被误转发。如果你用的是 hash 模式的路由则不存在这个问题所以初次跑通项目时先切回 hash 模式能省不少时间。4.3 ECharts 课程数据看板图表配置与后端 API 对齐后台运营平台的首页通常是一个数据看板用 ECharts 渲染课程销量、用户活跃度、问答趋势这些数据。前端画图本身不难难的是和后端接口的返回结构对齐。我比较习惯约定一个统一的图表数据结构后端返回菜单级的 JSON{ dates: [2026-04-01, 2026-04-02, 2026-04-03], series: [ { name: 课程销量, data: [102, 134, 90] }, { name: 新增用户, data: [45, 78, 66] } ] }前端 ECharts 的核心配置长这样// 课程销量趋势图 chart.setOption({ tooltip: { trigger: axis }, legend: { data: [课程销量, 新增用户] }, xAxis: { type: category, data: resp.dates }, yAxis: { type: value }, series: resp.series.map(item ({ name: item.name, type: line, smooth: true, // 开启平滑曲线 data: item.data })) });这里的重点是tooltip的 trigger 设为 axis鼠标滑过某个日期时把所有系列的值一起显示如果是单条数据用 item 更合适。legend的 data 要和 series 的 name 一一对应否则图例显示不出来。如果课程数据量大比如一年 365 天x 轴的刻度会挤成一团常见做法是在 xAxis 上配axisLabel: { interval: 30 }让标签每 30 条显示一次或者用 dataZoom 组件做缩放。数据量处理是 ECharts 环节最常见的翻车点后端一次性吐 365 个日期前端不做 dataZoom图表看起来就是一条黑带。加了 dataZoom 之后用户能自己拖拽看区间体验完全不同。5. 避坑指南从 Docker 到 MySQL 的常见问题与排查记录这部分内容是我在拆这个项目以及带团队复现时踩过的真实问题每一条按“现象 → 原因 → 解决”的顺序写。5.1 docker 安装 mysql 失败容器启动即退出的排查现象Docker 里拉取 mysql:8.0 镜像后执行 docker run容器状态永远显示 Exiteddocker logs 只有几行初始化日志就停了。原因八成是权限或目录的问题。MySQL 8.0 的镜像内部用 mysql 用户跑服务如果把宿主机目录挂载到 /var/lib/mysql但宿主机目录权限是 root 所有容器内服务无法写入启动直接失败。另一个高频原因是端口被宿主机已有服务占用比如本机装了 MySQL 又在跑 3306。解决先docker logs container_id看最后几行日志如果是 Permission denied则在挂载目录上执行chown -R 1000:1000 ./mysql-data之后再启动。如果日志显示 Address already in use说明端口冲突把宿主机映射端口改成 3307 或者先停掉本机 MySQL。从那以后我每次启动容器前都先执行docker ps -a和lsof -i:3306检查一遍。5.2 MySQL SSL 连接错误连接串里加哪个参数现象SpringBoot 项目启动时日志里出现Communications link failure或者SSL connection error: protocol version mismatch在 mysql 8.0 客户端驱动连接 MySQL 5.7 或某些云数据库时偶发。原因MySQL 8.0 之后 JDBC 驱动默认开启 SSL 连接服务端的 SSL 证书或协议跟客户端不对齐时握手阶段直接失败。这是纯粹的配置不一致问题不是连接被拦截。解决在 JDBC 连接串中显式加useSSLfalse并且可以加allowPublicKeyRetrievaltrue。这两个参数在开发环境没有影响生产环境如果确实需要加密连接则改成useSSLtrue并配合sslModeVERIFY_CA和证书路径。很多项目从 5.7 升到 8.0连接串里没动启动后就开始玄学报错其实只要这两个参数加上不用改代码就能解。从那以后我只要写 MySQL 连接串就显式把 useSSL 写出来不依赖驱动默认值。5.3 SpringCloud 服务注册不上Nacos 地址与网络命名空间的问题现象网关和业务服务都启动了但网关转发请求时报 503到注册中心控制台一看业务服务压根不在服务列表里。原因大多数情况下是 Nacos 的 server-addr 写错了或者业务服务在容器里跑、Nacos 在宿主机跑服务里配置的 localhost 指向容器内部找不到 Nacos。还有一种情况application.yml 里配置了 server-addr但启动参数里又覆盖了另一个地址两个不一致。解决先确认网络互通在容器内执行telnet nacos主机名 8848能通再看服务配置。配置统一收敛到 bootstrap.yml 里并检查启动日志里 Nacos 注册成功那一条。如果是在 Docker 容器中需要把 localhost 改成宿主机 IP 或 Docker 网络中的服务名。排查这类问题的顺序是先 ping 通不通再看日志有没有 Nacos registry 记录最后才怀疑代码。5.4 IDEA 2026 配置 SpringBoot 启动端口编辑配置数据里的端口改不动现象在 IDEA 里用 spring-boot:run 启动多个微服务模块在 Edit Configurations 的 VM options 里填了端口参数启动后还是用 application.yml 里的端口。原因IDEA 的 spring-boot:run 属于 Maven 插件执行端口优先级来自spring-boot-maven-plugin的 jvmArguments而不是 VM options。如果你在 application.yml 里写了 server.port在 Program arguments 里没有显式传--server.port8083那么 VM options 里的-Dserver.port8083可能被 application.yml 覆盖也可能被 Maven 插件忽略。解决最稳妥的方式是在 Edit Configurations → Spring Boot → Program arguments 里显式传--server.port8083。或者直接用不同环境 profile--spring.profiles.activedevdev 配置里写各自不同的端口。从那以后我再遇到“端口明明改了却不变”的问题都先回去查 Program arguments而不是改 VM options。5.5 前端接口 404代理配置对了但页面还是拿不到数据现象Vue 工程启动后浏览器访问前端页面正常但 Network 面板里所有 /api 开头的请求全部 404直接在后端接口地址访问却是通的。原因前端 devServer 代理取 vue.config.js 里的配置后端网关预期的是 /api 开头。一旦中间某层把 /api 前缀改掉了网关路径匹配不上就返回 404。另一个隐蔽原因是 SpringBoot 的静态资源处理和 Controller 映射冲突比如后端写了一个/api/course/*的接口返回值被静态资源处理器误拦截。解决把代理配置改为保留 /api 前缀并确保网关的路径断言也是 /api 开头。同时在 SpringBoot 侧把所有接口统一放到 /api 命名空间下静态目录只放前端构建产物。排查时先看 Network 里请求的完整 URL从真实 URL 反推是哪一层改动了路径比从头看代码快得多。6. 用脚本验证全部服务状态Actuator 健康检查与端口探活模拟一个真实场景你刚从 Git 仓库拉下来一套 online_edu 工程第一次启动想确认几层依赖之间的关系对不对。与其一个个浏览器打开试不如用一个脚本把“中间件 → 网关 → 业务服务”三层全部探一遍。我习惯在 bin 目录放一份。check_http() { curl -s -o /dev/null -w %{http_code} http://localhost:$1$2 } check_port() { nc -z -w 2 localhost $1 echo [OK] $1 || echo [FAIL] $1 } echo 中间件 check_port 3306 # MySQL check_port 6379 # Redis check_port 61616 # ActiveMQ echo 网关 echo gateway - $(check_http 8080 /api/course/index) echo 业务服务 for p in 8081 8082 8083 8084; do echo $p - $(check_http $p /actuator/health) done逻辑说明nc -z -w 2探测端口是否开放2 秒超时避免卡死。check_http用 curl 拿 HTTP 状态码200 表示服务正常503 说明服务还在启动中000 说明端口没起来。循环把四个业务服务端口挨个探一遍。如果你发现网关能通但业务服务 503通常意味着业务服务还没完成注册或者健康检查没通过。实际边界是如果工程里没有引入 Actuator/actuator/health会返回 404。此时可以直接看网关转发一个真实接口的状态码比如 GET /api/course/index。所以使用 Actuator 之前要先确认依赖spring-boot-starter-actuator存在而这个依赖在 online_edu 的 admin-service 里一般是配好的。我的建议是把它作为每次启动项目的第一道工序先跑这个脚本把中间件连通性、网关状态、业务服务健康状态三行结果同时摆在面前。从那以后我每次换机器重新部署这套系统时都强制走一遍这个脚本再也不会出现“网关起来了、MySQL 没起来、前端疯狂转圈”的尴尬局面。希望这份拆解笔记能帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进