企业级微服务开发流程详解:从本地开发、测试到 Jenkins 自动部署
本文定位:黑马程序员《天机学堂》Day1 深度学习笔记,面向初学者,打通从编码到上线的全链路认知。
核心命题:SwitchHosts、Nginx、Git、Docker、Jenkins、Nacos、测试体系,这些工具到底是什么关系?
前言:为什么初学者觉得微服务知识很"碎"
刚接触微服务时,一天之内要学 SwitchHosts 改 hosts、Nginx 反向代理、Git 版本管理、Docker 容器化、Jenkins 自动部署、Nacos 注册中心、JUnit 单元测试、Postman 接口测试……每个单独学都不难,但堆在一起就懵了——它们之间到底是什么关系?我为什么需要这么多工具?
其实这堆工具不是杂乱的,而是围绕一条主线展开——企业软件开发生命周期:
需求分析 → 编码开发 → 本地测试 → Git提交 → 自动构建部署 → 开发环境验证 → 联调 → 测试环境 → 生产环境
每个阶段有对应的工具支撑,本文按这个顺序逐一打通。
一、灵魂拷问:为什么企业不把 30 个微服务全在本地启动?
一句话回答:没必要,也做不到。
假设公司有 30 个微服务——用户服务、课程服务、订单服务、支付服务、积分服务、消息服务……你只负责其中的 learning-service。全部本地启动会:
| 问题 | 后果 |
|---|---|
| 内存爆炸 | 每个服务至少 256MB-512MB,30 个 = 8-15GB,IDEA 本身还吃 2-3GB |
| CPU 拉满 | 30 个 Spring Boot 启动 + 数据库连接池 + 心跳检测 |
| 启动太慢 | 一个一个起,30 分钟过去了 |
| 维护成本高 | 你不能保证每个服务的配置都正确,别人的服务改了配置你也不知道 |
企业的做法:一台开发服务器上部署了全部公共依赖,你本地只跑自己负责的那一个服务:
┌─────────────────────────────┐
│ 开发服务器 │
│ │
│ course-service user-service│
│ order-service pay-service │
│ trade-service ... │
│ MySQL Redis RabbitMQ ES │
│ Nacos Jenkins Nginx │
└──────────┬──────────────────┘
│ 网络调用(Feign/HTTP)
┌──────────┴──────────────────┐
│ 你的本地电脑 │
│ │
│ IDEA → learning-service │
│ (只启动这一个) │
└─────────────────────────────┘
你的 learning-service 需要调 course-service 时,通过 Nacos 发现开发服务器上的 course-service 地址,发起远程调用。这就是**“本地单服务 + 开发环境公共服务”**的开发模式。
二、企业完整开发流程(一天的工作流)
一个开发人员每天的真实工作流:
早上到公司
│
├─ ① 拉最新代码:git pull
│
├─ ② 写代码:Service → Mapper → Controller
│
├─ ③ 本地单元测试:JUnit 验证核心逻辑,不依赖外部组件
│
├─ ④ 本地接口测试:启动 Spring Boot → Postman/Apifox 调接口
│ 需要调其他微服务 → 改 Feign 配置指向开发服务器
│
├─ ⑤ Git 提交:git add → commit → push
│
├─ ⑥ Jenkins 自动构建:收到 push 事件 → Maven 编译 → Docker 打包 → 部署到开发环境
│
├─ ⑦ 开发环境验证:确认服务在开发环境正常运行
│
├─ ⑧ 组件测试:验证 Redis/MQ/DB/Feign 等中间件交互
│
├─ ⑨ 前后端联调:前端同事调你的接口
│
└─ ⑩ 提测/发布:测试环境 → 生产环境
关键理解:Git 管版本、Jenkins 管自动化、Docker 管环境一致性、Nacos 管服务发现。下面逐个展开。
三、测试体系详解(四种测试,四种目的)
3.1 本地单元测试
目的:验证一个方法的逻辑是否正确。
特点:不连数据库、不连 Redis、不连 MQ、不连任何外部依赖。纯内存运行,毫秒级完成。
// 例:测试积分计算逻辑,不依赖任何外部组件
@Test
public void testCalculatePoints() {
// Given:一笔 99 元的订单
Order order = new Order();
order.setAmount(new BigDecimal("99"));
// When:计算应得积分
int points = pointsService.calculate(order);
// Then:99 元应得 99 积分(1元=1积分)
assertEquals(99, points);
}
单元测试的核心价值:改了代码之后跑一遍,3 秒内告诉你有没有把之前正常的功能改坏。不需要启动整个 Spring Boot,不需要连数据库。
3.2 本地接口测试
目的:启动当前服务,验证整个 HTTP 请求链路。
// Spring Boot Test + MockMvc,启动真实 Spring 容器但可以 Mock 外部依赖
@SpringBootTest
@AutoConfigureMockMvc
public class LearningControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testQueryLearningProgress() throws Exception {
mockMvc.perform(get("/learning/progress/1001")
.header("Authorization", "Bearer " + testToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.userId").value(1001));
}
}
或用 Postman/Apifox 手动发 HTTP 请求:
POST http://localhost:8081/learning/record
Header: Authorization: Bearer <jwt_token>
Body: { "courseId": 100, "sectionId": 5, "progress": 0.8 }
如果接口内部调了其他微服务(比如 FeignClient("course-service")),本地没有这个服务怎么办?改配置指向开发服务器:
# 本地开发时,Feign 调开发服务器上的 course-service
spring:
cloud:
openfeign:
client:
config:
course-service:
url: http://192.168.150.101:8082 # 开发服务器的 course-service 地址
3.3 组件测试
目的:代码部署到开发环境后,验证微服务和中间件的交互是否正常。
验证清单:
- 连上开发环境的 Redis 了吗?
redisTemplate.opsForValue().set("test", "hello")能写能读吗? - MQ 收发正常吗?发一条测试消息,消费端能收到吗?
- Feign 远程调用的网络通吗?超时重试策略正常吗?
- 数据库事务正常吗?异常时能正确回滚吗?
和接口测试的区别:接口测试在本地跑,外部依赖可能是 Mock 的。组件测试在开发环境跑,跟真实中间件交互,验证的是你的服务作为一个"组件"在真实环境中能否正常工作。
3.4 开发环境联调
目的:多个服务一起跑通完整业务流程。
比如天机学堂的核心链路:
用户登录 → 浏览课程 → 选择课程 → 下单购买 → 支付 → 开始学习 → 记录进度
│ │ │ │ │ │ │
auth course course trade pay learning learning
-service -service -service -service -service -service -service
联调时请求经过 Gateway → 路由到各个微服务 → 每个服务各查各的数据库、各调各的缓存。这个链路比单服务的接口测试覆盖面大得多,能暴露跨服务的序列化问题、事务边界问题、超时配置问题等。
四、SwitchHosts 与 Nginx —— 流量入口的守门人
4.1 SwitchHosts:域名到 IP 的本地映射
企业内网的开发服务器通常只有 IP 没有公网域名。没有 SwitchHosts 时你需要这样访问:
http://192.168.150.101:8080/nacos
http://192.168.150.101:9090/jenkins
http://192.168.150.101:3000/git
不仅难记,而且一旦服务器 IP 变了,所有配置都要改。
用 SwitchHosts 在本地 hosts 文件里加一条映射:
192.168.150.101 nacos.tianji.com
192.168.150.101 jenkins.tianji.com
192.168.150.101 git.tianji.com
192.168.150.101 api.tianji.com
之后就用 http://nacos.tianji.com 访问 Nacos 了。IP 变了你只改 hosts 文件一行。
4.2 Nginx:反向代理,统一入口
hosts 只能把域名指向 IP,但没法区分端口。Nginx 补上这一环——根据域名或路径把请求转发到不同端口的服务:
# 不同域名 → 转发到不同服务
server {
listen 80;
server_name nacos.tianji.com;
location / {
proxy_pass http://127.0.0.1:8848; # Nacos 在 8848 端口
}
}
server {
listen 80;
server_name jenkins.tianji.com;
location / {
proxy_pass http://127.0.0.1:9090; # Jenkins 在 9090 端口
}
}
server {
listen 80;
server_name api.tianji.com;
location / {
proxy_pass http://127.0.0.1:10010; # Gateway 在 10010 端口
}
}
SwitchHosts 和 Nginx 的配合关系:
你在浏览器输入 nacos.tianji.com
→ SwitchHosts 把域名解析成 192.168.150.101
→ 请求到达 Nginx(开发服务器 80 端口)
→ Nginx 根据 server_name 匹配规则
→ 转发到 127.0.0.1:8848(Nacos)
五、Nacos 为什么最重要——没有它微服务就瘫痪
Nacos 做两件事:服务注册与发现 + 配置中心。
5.1 服务注册与发现
没有 Nacos 的时候,你的代码里要硬编码 IP:
// 硬编码 IP 的灾难
Course course = restTemplate.getForObject(
"http://192.168.150.101:8082/course/" + courseId, Course.class);
问题:course-service 有三台实例,其中一台挂了怎么办?加新实例怎么办?IP 变了怎么办?——全是手工维护,改到崩溃。
有了 Nacos:
@FeignClient("course-service") // 只写服务名,不写 IP
public interface CourseClient {
@GetMapping("/course/{id}")
Course getById(@PathVariable Long id);
}
底层流程:
你的服务启动 → 向 Nacos 注册:我是 learning-service,IP=192.168.1.5:8081
course-service 启动 → 向 Nacos 注册:我是 course-service,IP=192.168.150.101:8082
你的服务调 course-service → 问 Nacos 它在哪
→ Nacos 返回 ["192.168.150.101:8082"] (所有健康的实例列表)
→ Feign 内置 LoadBalancer 挑一个调
→ 某个实例 15 秒没发心跳 → Nacos 把它踢掉 → 不再返回给调用方
全程零硬编码 IP。
5.2 配置中心
没有 Nacos 配置中心时,数据库连接写在 application.yml 里:
spring:
datasource:
url: jdbc:mysql://192.168.150.101:3306/tianji_learning
username: root
password: root123
问题是:数据库密码改了怎么办?——改 yml → 重新打包 → 重新部署 → 重启服务。一个密码变更要折腾半小时。
配置放到 Nacos 后:
# 本地 bootstrap.yml 只写 Nacos 地址
spring:
cloud:
nacos:
config:
server-addr: nacos.tianji.com:8848
file-extension: yaml
config:
import:
- nacos:learning-service-dev.yaml # 实际配置从 Nacos 拉
数据库密码改了就改 Nacos 控制台 → @RefreshScope 注解自动刷新 → 不重启。改了即时生效。
六、Jenkins —— 把重复劳动自动化
没有 Jenkins 时,每次提测的流程:
# 每次都要手动敲一遍
git pull
mvn clean package -DskipTests
docker build -t learning-service:1.0 .
docker stop learning-service
docker rm learning-service
docker run -d --name learning-service -p 8081:8081 learning-service:1.0
一天提交 5 次 = 手动重复 5 遍 = 30 分钟浪费在敲命令上。
有了 Jenkins:
你做的事情:git push
Jenkins 自动做的事情:
① 检测到 Git 有新提交 → 拉代码
② Maven 编译 + 跑测试
③ Docker Build 打镜像
④ Docker Run 启动容器
⑤ 发通知:部署成功/失败
整个流程你只敲了一行 git push。
这就是 CI(持续集成) ——代码提交后自动构建、自动测试、自动部署。把人工重复操作变成机器自动执行。
七、Docker —— 消除"在我电脑上能跑"
7.1 核心价值
新同事入职第一天的噩梦:装 MySQL 8.0、装 Redis 7.0、装 RabbitMQ、装 ES、装 Nacos、配环境变量、装各种依赖……版本还要跟团队一致,一上午就过去了。
Docker 的解法:把依赖和环境全部写进 docker-compose.yml:
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
ports:
- "3306:3306"
redis:
image: redis:7.0
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq:3.9-management
ports:
- "5672:5672"
- "15672:15672"
nacos:
image: nacos/nacos-server:2.2.0
ports:
- "8848:8848"
新同事来了一行搞定:
docker-compose up -d # 一分钟全部就绪
Docker 和虚拟机的本质区别:Docker 共享宿主机内核,启动秒级,一个 16G 机器能跑几十个容器。虚拟机是完整 OS,启动分钟级,16G 只能跑几个。
八、完整请求链路(一张图打通所有工具)

你的电脑 开发服务器
───────── ────────────
IDEA 写代码
│
├─ JUnit 单元测试
│ 验证方法逻辑
│
├─ Postman 接口测试
│ 启动 learning-service
│ → 注册到 Nacos(开发服务器)
│ → Feign 调 course-service(开发服务器)
│
├─ git commit → git push
│ ─────────────────────────────→ Git 仓库(Gogs/GitLab)
│ │
│ Jenkins 检测到 push
│ ├─ mvn package
│ ├─ docker build
│ └─ docker run
│ │
│ 开发环境部署完成
│ │
├─ 组件测试 ←───────────────────────┘
│ 验证 Redis/MQ/DB/Feign 正常
│
├─ 前后端联调
│ 前端 → Nginx → Gateway → 各微服务
│
└─ 提测
测试环境 → 生产环境
九、面试高频问题与回答话术
Q1:为什么不把全部微服务在本地启动?
“没必要也做不到。30 个服务全启动内存就爆了,CPU 也扛不住。企业做法是开发服务器部署公共服务和中间件,本地只启动自己负责的那一个服务,需要调别的服务通过 Nacos 发现远程地址,Feign 调用。”
Q2:Nacos 到底解决了什么问题?
“两个问题。一是服务发现——不用硬编码 IP,Feign 写服务名就行,Nacos 返回健康的实例列表,挂了自动踢掉。二是配置中心——数据库密码、JWT 密钥这些配置放 Nacos 控制台,改了即时生效不重启,不用改代码重新部署。”
Q3:Jenkins 和 Docker 是什么关系?
“Jenkins 管流程,Docker 管运行。Jenkins 负责收到 Git Push 后自动拉代码、编译、打包、部署这条流水线。Docker 负责把编译好的 jar 包和环境打包成镜像,在任何服务器上都能一致运行。两者配合实现 CI/CD。”
Q4:本地测试和组件测试的区别?
“本地测试跑在自己电脑上,外部依赖可能是 Mock 的或者指向开发服务器。组件测试是代码部署到开发环境之后,验证服务与真实 Redis、MQ、数据库、其他微服务的交互是否正常。前者验证代码逻辑,后者验证环境集成。”
Q5:SwitchHosts 和 Nginx 配合做什么?
“SwitchHosts 把域名映射到开发服务器 IP,省得记 IP。Nginx 根据域名或路径把请求转发到不同端口的服务。两者配合——浏览器输域名 → hosts 解析成 IP → 请求到 Nginx → Nginx 按规则转发。用户全程只记域名。”
十、工具职责速查表
| 工具 | 一句话职责 | 没它会怎样 |
|---|---|---|
| SwitchHosts | 域名 → IP 本地映射 | 记一堆 IP 地址 |
| Nginx | 反向代理,统一入口 | 每个服务暴露独立端口,管理混乱 |
| Git | 版本管理 + 团队协作 | 代码靠 U 盘拷 |
| Maven | 依赖管理 + 项目构建 | 手动下载 jar,版本冲突自己扛 |
| JUnit | 单元测试 | 改代码不知道有没有改坏 |
| Postman/Apifox | 接口调试 | 靠前端配合才能验证接口 |
| Nacos | 服务发现 + 配置中心 | 硬编码 IP,改配置要重启 |
| Feign | 声明式 HTTP 调用 | 手写 RestTemplate + 拼 URL + 解析 JSON |
| Jenkins | 自动构建部署 | 每次手动 mvn package → docker build → docker run |
| Docker | 环境一致性 | “我电脑能跑,服务器不行” |
| Gateway | 统一 API 入口 | 前端调 5 个不同地址,鉴权逻辑散落各处 |
十一、总结
微服务学的不是某一个框架,而是一整套工程方法论。
学 Spring Cloud 是学怎么把一个大项目拆成小服务。但拆完之后怎么开发、怎么测试、怎么部署、怎么运维——这些不是写业务代码能解决的,需要一整套工具链。Day1 的内容就是帮你建立这个"工具链全景图"。
理解了这张全景图,后面再学 Gateway 的路由规则、OpenFeign 的拦截器、RabbitMQ 的可靠性投递、Docker 的网络模式、Kubernetes 的编排调度,你会发现它们都不是孤立的——每个工具都在这个生命周期里扮演各自的角色。
更多推荐

所有评论(0)