training-server 是一个面向企业培训场景的微服务后端项目,覆盖账号权限、部门组织、培训计划、课程资源、学习互动、学习进度和站内通知等核心能力。
项目当前采用 Spring Boot 3 + Spring Cloud Alibaba 的多模块结构,围绕“创建计划 -> 组织课程 -> 上传资源 -> 学员学习 -> 记录进度 -> 推送通知”这条主链路展开实现。
| 模块 | 说明 |
|---|---|
training-gateway |
网关入口,负责统一路由和基于请求头的鉴权校验 |
training-user-service |
用户账号、登录认证、权限信息维护 |
training-department-service |
部门管理、负责人维护、成员归属关系维护 |
training-resource |
普通资料、课程资源、Markdown 笔记的上传、查询、删除和文件输出 |
training-plan-service |
培训计划、课程、章节、测试题以及计划状态流转 |
training-learn-service |
学习答题、评论、回复、点赞等互动行为 |
training-progress-service |
章节完成记录、课程进度统计、计划维度进度聚合 |
training-push-service |
站内通知持久化、消息消费、WebSocket 实时推送 |
common |
通用响应模型、HTTP 客户端、JSON 编解码、链路透传、幂等组件 |
- Java 17
- Spring Boot 3
- Spring Cloud Gateway
- Spring Cloud Alibaba Nacos
- MyBatis-Plus
- MySQL
- Redis
- RabbitMQ
- WebSocket
- Springdoc OpenAPI
training-server
├─ common
├─ config
│ └─ nacos
├─ scripts
├─ training-gateway
├─ training-user-service
├─ training-department-service
├─ training-resource
├─ training-plan-service
├─ training-learn-service
├─ training-progress-service
└─ training-push-service
本地启动建议准备以下依赖:
- JDK 17
- MySQL
- Redis
- RabbitMQ
- Nacos
- Python 3
- PowerShell 7 或 Windows PowerShell
mp4box
说明:
mp4box仅在上传.mp4课程资源并执行视频处理时需要。- 项目使用 Maven Wrapper,无需单独安装 Maven。
项目已将模块配置收敛到 Nacos。各服务自身只保留最小 application.properties,通过 spring.config.import 从配置中心拉取共享配置和模块配置。
Nacos Data ID 约定如下:
training-common.yaml${spring.application.name}.yaml
仓库内已提供配置模板:
config/nacos/training-common.yamlconfig/nacos/training-gateway.yamlconfig/nacos/training-user-service.yamlconfig/nacos/training-department-service.yamlconfig/nacos/training-resource.yamlconfig/nacos/training-plan-service.yamlconfig/nacos/training-learn-service.yamlconfig/nacos/training-progress-service.yamlconfig/nacos/training-push-service.yaml
常用环境变量:
NACOS_SERVER_ADDRNACOS_GROUPNACOS_NAMESPACENACOS_USERNAMENACOS_PASSWORDRABBITMQ_HOSTRABBITMQ_PORTRABBITMQ_USERNAMERABBITMQ_PASSWORDREDIS_HOSTREDIS_PORTREDIS_DATABASE
将 config/nacos 下的模板导入 Nacos,默认使用 DEFAULT_GROUP。
先启动:
- Nacos
- MySQL
- Redis
- RabbitMQ
Linux / macOS:
./mvnw -DskipTests compileWindows:
.\mvnw.cmd -DskipTests compile可以按下面顺序逐个启动:
training-user-servicetraining-department-servicetraining-resourcetraining-plan-servicetraining-progress-servicetraining-learn-servicetraining-push-servicetraining-gateway
示例:
./mvnw -pl training-user-service -am spring-boot:run
./mvnw -pl training-department-service -am spring-boot:run
./mvnw -pl training-resource -am spring-boot:run
./mvnw -pl training-plan-service -am spring-boot:run
./mvnw -pl training-progress-service -am spring-boot:run
./mvnw -pl training-learn-service -am spring-boot:run
./mvnw -pl training-push-service -am spring-boot:run
./mvnw -pl training-gateway -am spring-boot:run编译校验:
./mvnw -DskipTests compile依赖守卫与编码检查:
powershell -ExecutionPolicy Bypass -File .\scripts\check-no-fastjson.ps1 -VerboseOutput
python .\scripts\check-source-encoding.py仓库包含 GitHub Actions 工作流 openapi-contract-check,用于做两类检查:
- 源码守卫:检查 JSON 依赖禁用规则和源码 UTF-8 编码完整性
- OpenAPI 合同检查:启动各微服务并校验
/v3/api-docs
CI 使用仓库根目录的 application-openapi-ci.properties 作为统一测试配置。在该场景下会关闭 Nacos 配置拉取、关闭消息监听、关闭网关服务发现,并切换到 H2 内存库,以保证每个服务都能独立启动并输出接口文档。
资源服务将普通资料和课程资源都设计成“按块上传 + 按哈希去重”的模式:
- 上传请求携带
fileHash、总分片数、当前分片序号、文件大小等信息 - 服务端通过
RandomAccessFile按偏移量写入临时分片文件 - 最后一个分片到达后,将临时文件原子移动到正式目录
- 如果数据库里已经存在相同
fileHash的物理文件,则直接复用原路径,不重复落盘 - 上传阶段用 Redis 幂等锁限制同一个文件并发合并,避免重复写入和脏数据
普通资料和课程资源采用不同的目录组织方式:
- 普通资料:按日期和上传者分目录保存
- 课程资源:按教师、课程、章节三级目录保存
课程资源在合并完成后,如果文件扩展名为 .mp4,会继续调用 mp4box 做一次处理:
- 使用
MP4BoxUtil启动外部进程 - 将原始 MP4 转成更适合分段加载的 fMP4 结果
- 处理成功后替换原文件,处理失败则记录日志并回滚临时产物
资源读取时会根据文件类型输出不同内容:
- Markdown 资源直接按文本返回
- 视频资源通过
Range请求按字节范围输出,支持前端边播边加载
笔记资源与普通资料分开建模,单独限制为 .md 文件:
- 上传前先校验课程和章节是否存在
- 文件按
lessonId/chapterId/upId-uuid.md方式落盘 - 删除章节或课程时,会同步清理对应目录和笔记记录
这样可以把课程正文资料和学员、教师补充说明分开管理,避免资源目录混杂。
项目中的 RabbitMQ 主要有两种用法。
第一种是异步事件投递:
- 学习服务提交试卷后投递判卷消息
- 学习服务的点赞、回复、考试发布等行为会投递通知消息
- 进度服务会消费章节完成消息并更新课程完成数
- 推送服务会消费通知消息,再转成站内通知和 WebSocket 推送
第二种是请求-回调协作:
- 学习服务在创建评论前,会向用户服务和计划服务发送校验消息
- 用户服务校验用户是否存在,计划服务校验课程章节是否存在
- 两边都将结果写回同一个回调队列
- 学习服务按
requestId聚合回调结果,只有都成功才继续写评论
这类设计把跨服务前置校验从同步 HTTP 依赖改成了消息协作,同时保留了结果可追踪能力。
所有核心消费者都复用了 IdempotencyGuard:
- 消费前按
consumerName + msgId生成 Redis 幂等键 - 重复消息直接丢弃
- 处理异常时释放幂等键,允许后续重试
这个模式已经应用在评论校验、章节完成、点赞通知、回复通知、试卷处理等多个链路中。
培训计划模块负责计划、课程、章节和测试题的主数据维护,进度模块负责学习结果落地:
- 计划服务维护计划状态,并在启动时和每日定时任务中更新过期计划状态
- 学习行为完成后,通过消息把章节完成事件发送给进度服务
- 进度服务在章节完成表落库成功后,再更新课程已完成章节数
这样把“计划定义”和“学习结果统计”拆到了两个模块里,职责更清晰,后续扩展统计口径也更容易。
推送模块不是单纯的 WebSocket 转发,而是先落库再推送:
NotificationServiceImpl先写通知主表和接收表- 同事务内补写一条 outbox 事件记录,保留后续补偿和扩展空间
- 在线用户通过
/push/{uid}/{username}建立 WebSocket 连接 - 消费到通知消息后,服务会把消息写入数据库并向在线会话推送文本内容
这种做法保证了通知既能实时触达,也能在用户离线时保留站内信记录。
项目内部的同步调用通过 common 模块统一封装:
- 使用
WebClientProxyFactory生成声明式 HTTP 客户端 - 自动透传
traceId和requestId - 对慢调用做统一日志记录
这样每个服务只需要定义自己的 Client 接口,不需要重复写大量样板 WebClient 代码。