☰
基于微服务架构的学校培训管理系统毕业设计实战指南
2026/10/3 14:25:12 网站建设 项目流程

简介:这份资源是面向高校计算机相关专业学生与Java开发学习者的微服务架构实战项目,可直接用于毕业设计或课程设计。项目采用前后端分离方案,前端基于Vue.js,搭配Element UI与Ant Design Vue组件库;后端以Spring Boot为核心,通过MyBatis Plus操作MySQL,借助Dubbo与OpenFeign完成跨服务调用,并使用Nacos作为注册中心与配置中心。功能覆盖学员报名、签到、分组、座位分配、云直播、在线考试、结业证下载等培训业务模块,同时包含系统日志、基础参数管理与网关非法请求过滤。压缩包共913个文件,约2.28MB,以454个Java源码、102个Vue组件、93个JS脚本及69个XML配置为主,另含Dockerfile、SQL脚本与Jenkinsfile等部署运维文件,代码可直接编译运行。目前已有389人学习,适合需要完整微服务案例、分布式调用与容器化部署参考的读者。

1. 微服务架构的学校培训管理系统:毕业设计选它到底图什么

如果你正在翻毕业设计选题,看到“基于微服务架构的学校培训管理系统”这个题目,第一反应大概率是:听起来比图书管理系统高级,但会不会把自己埋进坑里?我带过几届学生的课程设计和毕设,也帮人排查过不少“跑不起来”的项目,说句实在话,这个题目的性价比取决于你怎么拆。学校培训管理系统本身业务不复杂——学员报名、课程排期、缴费记录、考勤签到、结课评价,这些功能用单体 Spring Boot 一个 jar 就能写完。但一旦挂上“微服务架构”四个字,答辩老师会追问服务怎么拆、注册中心用什么、网关怎么配、分布式事务怎么处理。所以这个题目的真正价值不是业务多难,而是让你在可控范围内把微服务那套东西跑通一遍。适合计算机毕业设计、软件工程课程设计,也适合想拿一个能写进简历的 Java 项目练手的人。下面我按实际落地的顺序,把选型、拆分、编码、踩坑一条线讲清楚。

2. 服务怎么拆:从培训业务里切出四个能独立跑的模块

2.1 先定边界再写代码,别按数据库表拆服务

很多人做微服务毕设,第一步就翻车:打开数据库,看到几张表就建几个服务。学员表一个服务、课程表一个服务、订单表一个服务,结果一个报名动作要跨三个服务查数据,接口调用来回绕,最后自己都理不清。正确的做法是按业务能力拆,不是按数据表拆。学校培训管理系统的核心业务能力其实就四块:用户与权限、课程与排期、报名与缴费、考勤与评价。每一块内部的数据强相关,块与块之间通过明确的接口交互。

我一般会拆成这四个服务:

服务名职责核心表
auth-service登录、注册、角色权限、JWT 签发user、role、user_role
course-service课程 CRUD、班级排期、讲师分配course、class_schedule、teacher
enroll-service报名下单、缴费记录、订单状态enrollment、payment
attend-service签到打卡、考勤统计、结课评价attendance、evaluation

这样拆的好处是每个服务可以独立启动、独立建库,服务之间只通过 HTTP 或消息队列通信。答辩的时候老师问“为什么这么拆”,你就说“按业务能力边界拆分,每个服务对应一个限界上下文,数据私有,避免跨服务直接查库”。这句话比“因为表多”有说服力得多。

2.2 技术选型:Spring Cloud Alibaba 是毕设最稳的一套

选型这件事,我踩过最大的坑就是追新。有一年学生非要上 Spring Cloud Gateway + Consul + gRPC,结果环境配了三天,代码没写几行。对于毕业设计和课程设计,时间有限,稳定比先进重要。目前国内 Java 微服务毕设最稳的组合是 Spring Cloud Alibaba 全家桶:

  • 注册中心与配置中心:Nacos,一个组件顶两个用,省事
  • 网关:Spring Cloud Gateway,路由转发和鉴权过滤都在这里做
  • 服务调用:OpenFeign,声明式调用,代码量少
  • 熔断限流:Sentinel,可选,但加上去答辩加分
  • 分布式事务:Seata,只在报名扣名额和缴费这种场景用,别全局上

版本上,Spring Boot 用 2.7.x 或 3.x 都行,但要注意 Spring Cloud Alibaba 的版本对应关系。我一般建议用 Spring Boot 2.7.18 + Spring Cloud 2021.0.9 + Spring Cloud Alibaba 2021.0.5.0,这套组合资料多、坑少。如果你学校要求用 JDK 17,那就上 Spring Boot 3.2.x + Spring Cloud 2023.0.x + Spring Cloud Alibaba 2023.0.1.0,但 Nacos 版本要跟着升到 2.3.x。

注意:Nacos 默认用嵌入式 Derby 存配置,毕设演示够用,但如果你重启 Nacos 后发现配置丢了,换成 MySQL 存储,建库脚本在 Nacos 的 conf 目录里。

2.3 用 Docker Compose 把 Nacos 和 MySQL 先拉起来

环境搭建是第一个劝退点。我见过太多人卡在“Nacos 启动报错”上,一卡就是一下午。最省事的办法是用 Docker Compose 把中间件一次性拉起来,别在 Windows 上折腾安装包。

# docker-compose.yml version: '3.8' services: mysql: image: mysql:8.0 container_name: training-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: training_system ports: - "3306:3306" volumes: - ./mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql command: --default-authentication-plugin=mysql_native_password nacos: image: nacos/nacos-server:v2.3.0 container_name: training-nacos environment: MODE: standalone SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root123 ports: - "8848:8848" - "9848:9848" depends_on: - mysql

这段 Compose 文件做了三件事:启动 MySQL 并自动执行初始化 SQL,启动 Nacos 单机模式并让它把配置存到 MySQL,暴露 Nacos 控制台端口 8848。init.sql里你要提前建好nacos_config库和业务库training_system。启动命令就一句docker compose up -d,等半分钟访问http://localhost:8848/nacos,默认账号密码都是 nacos。这一步跑通,后面写代码才有底气。

3. 从零搭一个能注册能调用的最小服务

3.1 父工程与依赖管理:把版本锁死在一个地方

微服务项目最怕版本冲突。四个服务如果各自引不同版本的 Spring Cloud,启动时各种NoSuchMethodError。我一般建一个 Maven 父工程,用dependencyManagement统一锁版本,子服务只引 starter,不写版本号。

<!-- 父工程 pom.xml 关键片段 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <properties> <spring-cloud.version>2021.0.9</spring-cloud.version> <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

父工程只做依赖管理,不写业务代码。子服务继承父工程后,引 Nacos Discovery 和 OpenFeign 都不需要写版本号。这样做的逻辑是:版本只在父工程改一次,所有服务同步生效。参数上唯一要注意的是spring-cloud-alibaba.version必须和 Spring Cloud 版本匹配,2021.0.5.0 对应 Spring Cloud 2021.0.x,别乱配。

3.2 auth-service 的注册与登录:JWT 怎么发怎么验

auth-service 是整个系统的入口,负责发 token。我见过有人把登录逻辑写在网关里,结果网关又连数据库又发 token,职责混乱。正确做法是网关只做路由和 token 校验,登录接口转发到 auth-service。

// AuthController.java 核心登录逻辑 @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 查用户 User user = userMapper.selectByUsername(dto.getUsername()); if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) { return Result.fail("用户名或密码错误"); } // 2. 签发 JWT,有效期 2 小时 String token = JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); // 3. 把 token 和用户信息返回 Map<String, Object> data = new HashMap<>(); data.put("token", token); data.put("username", user.getUsername()); data.put("role", user.getRole()); return Result.success(data); }

这段代码的逻辑很直白:校验密码用 BCrypt,别用 MD5,答辩时老师问密码安全你能答上来。JWT 的 payload 里放 userId、username、role,过期时间设 2 小时,毕设演示够用。JwtUtil用 hutool 或者 jjwt 都行,密钥写在 Nacos 配置里,别硬编码在代码中。参数说明:passwordEncoder是 BCryptPasswordEncoder 的实例,注册时存的是加密后的密码;Result是统一返回体,包含 code、msg、data 三个字段。

3.3 course-service 与 enroll-service 的 Feign 调用

报名服务需要查课程信息,比如课程还剩多少名额、上课时间是否冲突。这时候用 OpenFeign 调 course-service,而不是直接查 course 库。

// enroll-service 中的 Feign 客户端 @FeignClient(name = "course-service", fallback = CourseClientFallback.class) public interface CourseClient { @GetMapping("/course/{id}") Result<CourseVO> getCourseById(@PathVariable("id") Long id); @PostMapping("/course/{id}/decrease") Result<Boolean> decreaseQuota(@PathVariable("id") Long id); }

@FeignClient的name写的是 Nacos 里注册的服务名,不是 IP。fallback是熔断降级类,当 course-service 挂了或者超时,返回一个兜底结果,避免报名服务跟着崩。这里有个细节:@PathVariable必须显式写 value,否则 Feign 在拼 URL 时会报错。调用逻辑是 enroll-service 先查课程,确认有名额再调decreaseQuota扣减,扣减成功才写报名记录。如果扣减失败,报名直接返回“名额不足”。

提示:Feign 默认超时是 1 秒,如果 course-service 响应慢,报名会失败。在 Nacos 配置里加feign.client.config.default.readTimeout=5000把超时调到 5 秒。

4. 网关、鉴权与分布式事务:三个最容易翻车的地方

4.1 网关路由配置:把 /api/auth/** 转发到 auth-service

网关是微服务的门面,所有外部请求先到网关,再由网关转发到具体服务。Spring Cloud Gateway 的路由配置可以写在 Nacos 里,也可以写在本地application.yml。毕设建议写本地,方便调试。

# gateway 的 application.yml spring: cloud: gateway: routes: - id: auth-service uri: lb://auth-service predicates: - Path=/api/auth/** filters: - StripPrefix=1 - id: course-service uri: lb://course-service predicates: - Path=/api/course/** filters: - StripPrefix=1 - id: enroll-service uri: lb://enroll-service predicates: - Path=/api/enroll/** filters: - StripPrefix=1

uri里的lb://表示从 Nacos 拉服务列表做负载均衡,不是写死 IP。StripPrefix=1表示转发时去掉第一层路径,比如/api/auth/login转发到 auth-service 时变成/login。这样网关只负责路由,业务路径由各服务自己定义。鉴权过滤器在网关层做,校验请求头里的 token,校验通过放行,不通过直接返回 401。注意白名单要放行/api/auth/login和/api/auth/register,否则登录都进不去。

4.2 分布式事务:报名扣名额和缴费怎么保证一致

这是微服务毕设最容易被问倒的地方。报名服务扣课程名额,同时要写缴费记录,两个操作在不同服务、不同数据库。如果扣完名额缴费失败,名额就白白少了。解决方案用 Seata 的 AT 模式,在报名方法上加@GlobalTransactional。

// enroll-service 中的报名方法 @GlobalTransactional(name = "enroll-tx", rollbackFor = Exception.class) public Result enroll(Long courseId, Long userId) { // 1. 远程扣减课程名额 Result<Boolean> decreaseResult = courseClient.decreaseQuota(courseId); if (!decreaseResult.getData()) { throw new RuntimeException("名额不足"); } // 2. 本地写报名记录 Enrollment enrollment = new Enrollment(); enrollment.setCourseId(courseId); enrollment.setUserId(userId); enrollment.setStatus("PENDING"); enrollmentMapper.insert(enrollment); // 3. 模拟缴费,失败则全局回滚 if (!mockPayment(userId, courseId)) { throw new RuntimeException("缴费失败"); } return Result.success("报名成功"); }

@GlobalTransactional的作用是:只要方法里任何一步抛异常,Seata 会通知 course-service 把扣掉的名额加回来。参数上,name是事务名,随便起但要有意义;rollbackFor指定哪些异常触发回滚。注意 Seata 需要每个服务的数据库里建 undo_log 表,脚本在 Seata 的 GitHub 仓库里有。如果你觉得 Seata 太重,毕设也可以用“本地消息表 + 定时补偿”代替,但答辩时 Seata 更拿得出手。

4.3 用 Sentinel 给报名接口加限流

答辩老师喜欢问“高并发怎么办”。虽然学校培训系统没多少并发,但你可以用 Sentinel 给报名接口加个限流,展示你知道怎么保护服务。在 enroll-service 里引 Sentinel 依赖,然后在 Nacos 里配规则。

// 在报名接口上加 Sentinel 资源注解 @SentinelResource(value = "enroll", blockHandler = "enrollBlockHandler") @PostMapping("/enroll") public Result enroll(@RequestParam Long courseId, @RequestParam Long userId) { return enrollService.enroll(courseId, userId); } // 被限流后的兜底方法 public Result enrollBlockHandler(Long courseId, Long userId, BlockException ex) { return Result.fail("当前报名人数过多,请稍后再试"); }

@SentinelResource的value是资源名,在 Sentinel 控制台里按这个名配限流规则,比如 QPS 超过 10 就拒绝。blockHandler指定被限流后调用的方法,参数要和原方法一致,最后多一个BlockException。这样即使有人恶意刷报名接口,系统也不会崩。参数上,限流阈值根据你演示的机器性能设,一般 QPS 设 5 到 10 就行,别设太高看不出效果。

5. 避坑与排查:那些让我熬夜到凌晨的报错

5.1 Nacos 注册不上:服务列表里空空如也

现象:服务启动日志显示nacos registry, DEFAULT_GROUP xxx register finished,但 Nacos 控制台的服务列表里就是没有。原因通常是 Nacos 2.x 除了 8848 端口,还需要 9848 端口做 gRPC 通信。如果你用 Docker 只映射了 8848,服务注册会失败。解决:在docker-compose.yml里把 9848 也映射出来,或者直接用 host 网络模式。另外检查spring.cloud.nacos.discovery.server-addr是不是写成了127.0.0.1:8848,在 Docker 里跑的服务要用宿主机 IP 或 Docker 网络别名。

5.2 Feign 调用报 404:路径拼错了

现象:enroll-service 调 course-service 的/course/1,返回 404。原因通常是 Feign 客户端接口上的@GetMapping路径和 course-service Controller 里的路径不一致。比如 Feign 写的是/course/{id},但 Controller 写的是/courses/{id},差一个字母就 404。解决:把 Feign 接口的路径和 Controller 的@RequestMapping对齐,建议先写 Controller 再照着写 Feign。另外注意@PathVariable的 value 必须和路径变量名一致。

5.3 Seata 回滚不生效:undo_log 表没建

现象:报名方法抛异常了,但 course-service 扣掉的名额没有加回来。原因通常是 course-service 的数据库里没有建undo_log表,Seata 没法记录回滚日志。解决:在每个参与全局事务的数据库里执行 Seata 提供的undo_log建表语句。另外检查seata.enabled=true和seata.application-id是否配了,事务分组名要和 Seata Server 的配置一致。

5.4 网关转发后 401:token 校验把登录接口也拦了

现象:访问/api/auth/login返回 401,根本没法登录。原因是网关的鉴权过滤器没有放行登录和注册接口。解决:在过滤器里维护一个白名单,比如/api/auth/login、/api/auth/register,这些路径直接放行,不校验 token。白名单建议写在 Nacos 配置里,改的时候不用重启网关。

5.5 跨服务查数据:别直接连别人的库

现象:enroll-service 为了查课程名,直接连了 course 库的course表。原因图省事。解决:这是微服务的大忌,答辩时被指出来很扣分。正确做法是通过 Feign 调 course-service 的接口拿数据,或者用消息队列做数据同步。如果嫌 Feign 慢,可以在 enroll-service 本地建一张冗余表,通过 MQ 异步更新,但毕设用 Feign 就够了。

6. 答辩前怎么验证你的微服务真的跑通了

6.1 用 Nacos 控制台做一次服务健康检查

答辩前一周,把四个服务和网关全部启动,打开 Nacos 控制台,看服务列表里是不是有五个服务:auth-service、course-service、enroll-service、attend-service、gateway。点进每个服务的详情,看实例数是不是 1,健康状态是不是 true。如果某个服务显示不健康,点进去看心跳时间,超过 15 秒没心跳就是挂了。这一步能提前发现注册问题,别等到答辩现场才发现服务没起来。

6.2 用 Postman 跑一遍完整业务链路

从登录开始,拿 token,然后调课程列表、报名、签到、评价,每个接口都跑一遍。重点看报名接口:先查课程名额,报名成功后名额减一,再查报名记录有没有生成。如果报名失败,看是 Feign 超时还是 Seata 回滚。我一般会准备一个api-test.http文件,把请求按顺序写好,答辩前跑一遍,两分钟就能确认系统是否正常。

6.3 故意制造一次故障看熔断降级

把 course-service 停掉,然后调报名接口。如果配置了 Sentinel 或 Feign 的 fallback,应该返回“课程服务暂时不可用”而不是一堆异常堆栈。这个演示在答辩时很加分,老师会觉得你考虑了容错。注意停服务之前先把 Nacos 里的实例下线,否则 Feign 还会往挂掉的实例发请求,等 Ribbon 刷新需要时间。

6.4 看日志定位问题:别只盯着控制台

微服务日志分散在四个服务里,出问题时要学会看日志。我一般会在每个服务的application.yml里配日志输出到文件,格式带上服务名和 traceId。比如logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n。如果用了 Sleuth 或 Micrometer Tracing,日志里会有 traceId,一个请求跨多个服务也能串起来。答辩时如果老师问“怎么排查线上问题”,你就说“通过 traceId 串联链路日志,定位到具体服务再查”。

6.5 一个让我长记性的习惯

最后说个我自己的教训。第一次做微服务项目时,我觉得本地跑通就行了,没管 Docker 镜像和启动脚本。结果答辩那天换了一台机器,Nacos 连不上,MySQL 密码不对,四个服务只起来两个,当场翻车。从那以后,我养成了一个习惯:所有中间件用 Docker Compose 管理,所有服务的启动命令写在一个start-all.sh里,数据库初始化 SQL 放在init.sql,换机器只需要装 Docker 和 JDK,十分钟就能把整套环境拉起来。这个习惯让我后来带学生做毕设时,再也没在环境上浪费过时间。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询