基于 Spring Boot 2.7 + Maven Multi-Module 的 Monorepo 多微服务演示项目,带有独立 Helm Chart。 一个仓库、一条流水线、多个可独立部署的服务。
⭐ 前端静态演示页已内嵌到
rps-game-service,启动 Spring Boot 即可同时拿到前后端。
方式 1:直接跑 Spring Boot(本地最推荐)
./mvnw -pl rps-game-service -am spring-boot:run
# 打开浏览器 → http://localhost:8081
打开页面就能看到 CNB 演示页 + 石头剪刀布小游戏,后端 API 同样由 rps-game-service 提供。
💡 文档中的
./mvnw都可以替换成本机已安装的mvn(要求 Maven ≥ 3.6)。 Wrapper 的好处是免安装、版本固定(见.mvn/wrapper/maven-wrapper.properties,当前 3.8.7),CI 与团队成员保持一致。
方式 2:K8s 上用 Helm 部署
# 先构建并推送镜像(见下文 §5),然后:
helm install rps ./rps-game-service/helm
helm install gateway ./gateway-service/helm
helm install weather ./weather-service/helm
helm install user ./user-service/helm
所有微服务都作为顶层目录存在(不再套在 services/ 下),每个目录都可独立编译、打包、构建镜像、发布 Helm。
demo-java-monorepo/
├── pom.xml # 父 POM,声明子模块与依赖管理
├── Dockerfile # 通用后端 Dockerfile,--build-arg SERVICE=xxx
├── docs/
│ └── ARCHITECTURE.md # 架构与服务拓扑说明
│
├── gateway-service/ # 8080 · 聚合网关
│ ├── pom.xml · src/
│ └── helm/ # ⎈ 独立 Helm Chart
│
├── rps-game-service/ # 8081 · 石头剪刀布 + 前端演示页(一键启动主服务)
│ ├── pom.xml
│ ├── src/main/java/ # 后端 ApiController: /api/rps/**
│ ├── src/main/resources/static/ # ⭐ 前端静态页(index.html / styles.css / app.js)
│ └── helm/
│
├── weather-service/ # 8082 · 天气查询(mock)
│ ├── pom.xml · src/
│ └── helm/
│
├── user-service/ # 8083 · 用户 CRUD(内存)
│ ├── pom.xml · src/
│ └── helm/
│
├── assets/ # 文档插图
└── mvnw / mvnw.cmd
| 服务 | 类型 | 端口 | 主要接口 / 入口 | Helm Chart |
|---|---|---|---|---|
rps-game-service ⭐ | Spring Boot | 8081 | /(前端页)· /api/rps/play | rps-game-service/helm |
gateway-service | Spring Boot | 8080 | /api/gateway/overview | gateway-service/helm |
weather-service | Spring Boot | 8082 | /api/weather/current | weather-service/helm |
user-service | Spring Boot | 8083 | /api/users | user-service/helm |
所有服务都完全独立:ApiResponse / ServiceConstants 等共享类已下沉到各服务自身包内,服务之间无 Maven 依赖,可在任一服务目录下直接 ../mvnw clean package 单独构建。
rps-game-service/src/main/resources/static/
index.html · styles.css · app.jsfetch('/api/rps/play') 直接命中同进程的 ApiController,无跨域、无反代。rps-game-service 后浏览器打开 http://localhost:8081/。Spring Boot 默认会把
classpath:/static/作为静态资源根目录,index.html作为/的欢迎页,无需额外配置。
# 编译所有模块
./mvnw -q -DskipTests -T 1C compile
# 跑全部单测(带 Jacoco 覆盖率)
./mvnw -q -T 1C test
# 打包单个服务
./mvnw -pl rps-game-service -am -DskipTests package
# 本地运行(含前端)
./mvnw -pl rps-game-service -am spring-boot:run
# 或者: java -jar rps-game-service/target/rps-game-service.jar
# 已装 Maven 的话,等价命令是把 ./mvnw 换成 mvn,例如:
# mvn -pl rps-game-service -am spring-boot:run
所有后端服务共用根目录的通用 Dockerfile,同时支持「全量构建」和「按需构建」两种姿势,本地与 CI 行为一致。
A. 全量构建 — 一次把 4 个服务镜像全打出来:
# 本地:4 条命令各打一个镜像(也可以写成 for 循环)
docker build --build-arg SERVICE=rps-game-service -t demo/rps-game-service .
docker build --build-arg SERVICE=weather-service -t demo/weather-service .
docker build --build-arg SERVICE=user-service -t demo/user-service .
docker build --build-arg SERVICE=gateway-service -t demo/gateway-service .
CI 上对应的全量入口:master 分支「手动发版」按钮里 var4 留空(默认全选 4 个服务),或在 dev 分支按下联调按钮。
B. 按需构建 — 只编译/推送你关心的几个服务,避免改 1 个服务时把 4 个全部重打:
# 本地:先增量编译,再单独打这一个镜像
./mvnw -pl rps-game-service -am -DskipTests package
docker build --build-arg SERVICE=rps-game-service -t demo/rps-game-service .
CI 上对应的按需入口:
| 触发方式 | 按需粒度 | 怎么选 |
|---|---|---|
| PR 流水线 | 自动按变更 | 只对 PR 里改动到的服务跑构建(无需手动选) |
| master 手动发版按钮 | 表单显式勾选 | var4 多选你想发的服务,仅这些镜像会被 build & push |
| dev 联调按钮 | 默认全量 | 不需要按需时不用动;想按需也支持手填 var4 |
前端资源随 rps-game-service 的 jar 一起打包,不需要单独构建前端镜像。
💡 详细机制(var3/var4 字段含义、
mvn -pl增量编译、镜像 tag 规则、本地等价命令、FAQ)见docs/CI-ON-DEMAND-BUILD.md。
每个服务都带有独立的 helm/ 目录:
# 渲染查看
helm template demo ./rps-game-service/helm
# 安装到当前 namespace
helm install rps ./rps-game-service/helm
helm install gateway ./gateway-service/helm
helm install weather ./weather-service/helm
helm install user ./user-service/helm
# 升级 / 卸载
helm upgrade rps ./rps-game-service/helm -f custom-values.yaml
helm uninstall rps
公共的 values.yaml 字段包括 replicaCount / image.* / service.* / resources / env /
livenessProbe / readinessProbe / ingress.*,足够演示常见改造需要。
rps-game-service 的 chart 发布后,同时暴露前端页面与 /api/rps/**,通过 ingress 对外只需要一个 Host。
# 前端页
open http://localhost:8081/
# 后端 API(前端内部调用的就是同一个地址)
curl -X POST http://localhost:8081/api/rps/play \
-H "Content-Type: application/json" \
-d '{"choice":"rock"}'
curl http://localhost:8081/api/rps/health
# 其他后端(独立启动 / 部署到 K8s 后访问)
curl "http://localhost:8082/api/weather/current?city=Shanghai"
curl http://localhost:8083/api/users
curl "http://localhost:8080/api/gateway/overview?userId=1&city=Beijing"
docs/ARCHITECTURE.md — 服务拓扑 / 模块划分 / 扩展方向docs/CNB-PIPELINE.md — .cnb.yml 流水线功能解读(事件总览、3 个公共锚点、tag 部署链路、关键陷阱)docs/CI-ON-DEMAND-BUILD.md — CI 按需构建说明(var4 多选、partial 编译、镜像 tag 规则)docs/SECURITY-SCAN-CHECKLIST.md — 安全扫描自查清单SecureRandom、不硬编码密钥Made with ☕ & ❤️ for CNB demos.