前言:为什么我要把毕设从单体改成微服务?
去年帮实验室学弟看系统,一个标准的 Spring Boot + Vue 二手交易平台,功能很完整,但答辩时导师问了一句:
"你这个用户模块和订单模块耦合在一起,如果用户量大了,怎么独立扩容?"
学弟当场愣住。后来分数出来,技术架构项被扣了 8 分。
这件事让我意识到:2026 年的毕设评审,已经不只是看"功能有没有",而是看"架构怎么想"。但本科生搞微服务,最怕两件事:一是搞成"分布式单体"(拆了一堆服务,数据库还是共用的,代码也没解耦),二是搞成"过度设计"(拆出十几个服务,答辩时自己都讲不清调用链路)。
本文记录了我自己毕设项目的微服务改造全过程,从单体 Spring Boot 拆成 3 个业务服务 + 1 个网关 ,用 Spring Cloud Alibaba 2023.x 全家桶,配合 Docker Compose 一键部署。整个改造周期 2 周,答辩时导师主动问"能不能把这套架构的思路整理一下,给下一届参考"。
一、架构演进:单体 → 微服务的决策过程
1.1 原始单体架构的问题
学弟原来的项目结构:
bash
second-hand-platform/
├── src/main/java/com/platform/
│ ├── controller/ # 所有 Controller 放一起
│ ├── service/ # 所有 Service 放一起
│ ├── mapper/ # 所有 Mapper 放一起
│ └── entity/ # 所有 Entity 放一起
└── src/main/resources/
└── application.yml
问题很明显:
UserController和OrderController在同一个 JVM 里,一个内存泄漏全挂- 改用户模块的字段,不小心影响到订单模块的 SQL(因为共用一个
entity包) - 部署时只有一个 JAR,导师问"怎么扩容"时只能回答"换台更好的服务器"
1.2 我的拆分策略:"3+1"模型
我没有盲目学大厂拆出十几个服务。本科毕设,能把 3 个业务域 + 1 个网关 的协作关系讲清楚,已经能拉开差距。
| 服务 | 端口 | 职责 | 拆分依据 |
|---|---|---|---|
gateway-service |
8080 | 统一入口、JWT 鉴权、路由转发 | 横切关注点,所有请求必经 |
user-service |
9001 | 注册、登录、JWT 签发、角色权限 | 用户域(User Context) |
biz-service |
9002 | 商品、订单、支付、核心业务 | 业务域(Business Context) |
base-service |
9003 | 文件上传、短信、字典、日志 | 基础域(Infrastructure Context) |
为什么不是"订单服务""支付服务"分开?
因为毕设不需要解决分布式事务。订单和支付放在 biz-service 里,一个 @Transactional 就能保证一致性。拆太细 = 给自己挖坑。
二、版本选型:这个组合我踩了 3 天才调通
微服务最怕版本冲突。以下组合已在本地(MacOS + Docker Desktop)和 Linux 服务器验证通过:
| 组件 | 版本 | 备注 |
|---|---|---|
| Spring Boot | 3.2.5 |
别用 3.3.x,和 Gateway 有兼容问题 |
| Spring Cloud | 2023.0.1 |
与 Boot 3.2.x 严格对应 |
| Spring Cloud Alibaba | 2023.0.1.0 |
Nacos 2.3 支持 |
| Nacos | 2.3.0 |
单机版够用,集群版答辩讲不清 |
| Gateway | 4.x |
基于 WebFlux,性能比 Zuul 好 |
| OpenFeign | 4.x |
声明式调用,配合 LoadBalancer |
| MyBatis Plus | 3.5.6 |
简化 CRUD |
| MySQL | 8.0 |
驱动用 mysql-connector-j |
| Redis | 7.x |
缓存 Token、热点数据 |
| Vue | 3.4.x |
前端配合 Element Plus |
⚠️ 踩坑记录 1 :一开始用了 Spring Boot 3.3.0,结果 Gateway 启动报错
ReactiveAdapterRegistry找不到。降级到 3.2.5 立刻解决。微服务版本必须严格锁定,这是血泪教训。
三、父工程 POM:版本锁定的正确姿势
很多人微服务项目搭不起来,根因是 pom.xml 版本混乱。我的父工程用 dependencyManagement 严格锁定三件套:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.graduation</groupId>
<artifactId>graduation-cloud</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>gateway-service</module>
<module>user-service</module>
<module>biz-service</module>
<module>base-service</module>
</modules>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.2.5</spring-boot.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba BOM -->
<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>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
子服务只需要引入需要的 Starter,版本全部由父工程托管:
xml
<!-- user-service/pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
四、Nacos:服务注册的"通讯录"
4.1 Docker 启动 Nacos(单机版)
yaml
# docker-compose.yml 片段
services:
nacos:
image: nacos/nacos-server:v2.3.0
container_name: nacos
ports:
- "8848:8848"
environment:
- MODE=standalone
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=root
- JVM_XMS=256m
- JVM_XMX=512m
⚠️ 踩坑记录 2 :Nacos 2.x 默认需要外置 MySQL,如果不配
SPRING_DATASOURCE_PLATFORM=mysql,启动会报错No DataSource set。我在这卡了 2 小时。
4.2 服务注册配置
必须用 bootstrap.yml,不是 application.yml。因为 Nacos 配置中心需要在 Spring 上下文刷新前加载。
yaml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: nacos:8848
namespace: dev
group: DEFAULT_GROUP
config:
server-addr: nacos:8848
namespace: dev
group: DEFAULT_GROUP
file-extension: yaml
refresh-enabled: true
server:
port: 9001
启动后访问 http://localhost:8848/nacos,账号密码都是 nacos,看到 4 个服务全部在线,截图保存------答辩 PPT 里这张图值 5 分。
五、Gateway:网关的"路由+鉴权"双杀配置
Gateway 是微服务的门面,所有请求都走这里。我配置了三个核心能力:动态路由 、JWT 鉴权 、跨域处理。
5.1 路由配置
yaml
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- id: biz-service-route
uri: lb://biz-service
predicates:
- Path=/api/biz/**
filters:
- StripPrefix=1
- id: base-service-route
uri: lb://base-service
predicates:
- Path=/api/base/**
filters:
- StripPrefix=1
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
maxAge: 3600
⚠️ 踩坑记录 3 :
StripPrefix=1的意思是去掉/api前缀。前端请求/api/user/login,Gateway 转发给user-service时变成/user/login。如果你的 Controller 映射的是/api/user/login,就会 404。我在这调试了 1 小时。
5.2 JWT 全局过滤器
这是 Gateway 最核心的自定义逻辑,实现统一鉴权:
java
@Component
@Slf4j
public class JwtAuthFilter implements GlobalFilter, Ordered {
private static final String SECRET =
"GraduationSecretKey2026GraduationSecretKey";
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 白名单放行
if (isWhiteList(path)) {
return chain.filter(exchange);
}
String authHeader = exchange.getRequest()
.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return unauthorized(exchange);
}
String token = authHeader.substring(7);
try {
SecretKey key = Keys.hmacShaKeyFor(
SECRET.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
// 关键:把用户信息传给下游服务
ServerHttpRequest mutatedRequest =
exchange.getRequest().mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Role",
claims.get("role", String.class))
.build();
return chain.filter(
exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
log.error("JWT 验证失败: {}", e.getMessage());
return unauthorized(exchange);
}
}
private boolean isWhiteList(String path) {
return path.contains("/login")
|| path.contains("/register")
|| path.contains("/public");
}
private Mono<Void> unauthorized(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":401,"
+ "\"message\":\"未授权或Token已过期\"}";
DataBuffer buffer = response.bufferFactory()
.wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100; // 最高优先级
}
}
答辩话术(建议背熟):
"Gateway 作为系统唯一入口,承担路由转发和统一鉴权职责。我实现了 JWT 全局过滤器,对登录注册以外的请求进行 Token 校验,并将解析后的用户信息通过 Header 传递给下游服务。这样每个微服务不需要重复实现鉴权逻辑,实现了横切关注点的统一处理。"
六、OpenFeign:服务间调用"像调本地方法"
这是微服务最爽的地方。biz-service 需要用户信息,不需要写 HTTP 请求代码,定义一个接口就行:
6.1 Feign 客户端定义
java
@FeignClient(
name = "user-service",
path = "/user",
fallbackFactory = UserFeignClientFallbackFactory.class
)
public interface UserFeignClient {
@GetMapping("/{id}")
UserDTO getUserById(@PathVariable("id") Long id);
@GetMapping("/batch")
List<UserDTO> getUserBatch(@RequestParam("ids") List<Long> ids);
}
6.2 业务层使用
java
@Service
@RequiredArgsConstructor
public class OrderServiceImpl
extends ServiceImpl<OrderMapper, Order>
implements OrderService {
private final UserFeignClient userFeignClient;
@Override
@Transactional(rollbackFor = Exception.class)
public OrderVO createOrder(OrderDTO dto) {
// 1. 校验商品
Product product = productMapper.selectById(dto.getProductId());
if (product == null || product.getStock() < 1) {
throw new BusinessException("商品不存在或库存不足");
}
// 2. 获取买家信息(Feign 自动处理 HTTP + 负载均衡)
UserDTO buyer = userFeignClient.getUserById(dto.getBuyerId());
// 3. 创建订单(本地事务,不引入分布式事务复杂度)
Order order = new Order();
order.setProductId(dto.getProductId());
order.setBuyerId(dto.getBuyerId());
order.setSellerId(product.getUserId());
order.setPrice(product.getPrice());
order.setStatus(0);
this.save(order);
// 4. 扣减库存
product.setStock(product.getStock() - 1);
productMapper.updateById(product);
// 5. 组装返回
OrderVO vo = new OrderVO();
BeanUtils.copyProperties(order, vo);
vo.setBuyerName(buyer.getUserName());
vo.setProductTitle(product.getTitle());
return vo;
}
}
⚠️ 踩坑记录 4 :
@FeignClient的path必须和服务端的@RequestMapping一致。如果服务端是@RestController @RequestMapping("/api/user"),而 Feign 写的是path = "/user",就会 404。建议服务端 Controller 统一不加/api前缀,由 Gateway 统一处理。
七、数据库设计:单库 + 前缀隔离
毕设不要搞分库,一个 MySQL 实例,用前缀区分归属:
| 服务 | 前缀 | 示例表 |
|---|---|---|
| user-service | sys_ |
sys_user, sys_role |
| biz-service | biz_ |
biz_product, biz_order |
| base-service | base_ |
base_file, base_log |
sql
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
role VARCHAR(20) DEFAULT 'USER',
status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE biz_product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE biz_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL,
buyer_id BIGINT NOT NULL,
seller_id BIGINT NOT NULL,
price DECIMAL(10,2) NOT NULL,
status TINYINT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_buyer (buyer_id),
INDEX idx_seller (seller_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
论文写法:
"本系统采用单数据库实例 + 逻辑表前缀隔离策略。用户域表以
sys_为前缀,业务域表以biz_为前缀,基础域表以base_为前缀。该策略在保证数据归属清晰的同时,避免了分布式事务的复杂性,为未来物理分库预留了扩展空间。"
八、Docker Compose:答辩现场一键启动
这是整个项目的"一键救命脚本"。答辩前 5 分钟,SSH 连上服务器,一条命令启动全部服务:
yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: graduation-mysql
environment:
MYSQL_ROOT_PASSWORD: Graduation@2026
MYSQL_DATABASE: graduation_db
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
- mysql_data:/var/lib/mysql
command: --default-authentication-plugin=mysql_native_password
networks:
- graduation-net
redis:
image: redis:7-alpine
container_name: graduation-redis
ports:
- "6379:6379"
networks:
- graduation-net
nacos:
image: nacos/nacos-server:v2.3.0
container_name: graduation-nacos
environment:
- MODE=standalone
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=Graduation@2026
- JVM_XMS=256m
- JVM_XMX=512m
ports:
- "8848:8848"
depends_on:
- mysql
networks:
- graduation-net
user-service:
build: ./user-service
container_name: graduation-user
ports:
- "9001:9001"
environment:
- SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
depends_on:
- nacos
- mysql
networks:
- graduation-net
biz-service:
build: ./biz-service
container_name: graduation-biz
ports:
- "9002:9002"
environment:
- SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
depends_on:
- nacos
- mysql
networks:
- graduation-net
base-service:
build: ./base-service
container_name: graduation-base
ports:
- "9003:9003"
environment:
- SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
depends_on:
- nacos
- mysql
networks:
- graduation-net
gateway:
build: ./gateway-service
container_name: graduation-gateway
ports:
- "8080:8080"
environment:
- SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
depends_on:
- nacos
- user-service
- biz-service
- base-service
networks:
- graduation-net
volumes:
mysql_data:
networks:
graduation-net:
driver: bridge
启动命令:
bash
docker-compose up -d --build
docker-compose ps
curl http://localhost:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10
⚠️ 踩坑记录 5 :
depends_on只保证启动顺序,不保证服务就绪。如果user-service比 Nacos 先启动完成,会注册失败。解决方案:给user-service加restart: always,或者写个健康检查脚本。我偷懒用了restart: always,答辩时没问题。
九、前端 Vue3:只认 Gateway 一个地址
javascript
// .env.development
VITE_API_BASE_URL = 'http://localhost:8080/api'
// utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 15000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export const login = (data) => service.post('/user/login', data)
export const getProducts = (params) => service.get('/biz/product/list', { params })
export const uploadFile = (data) => service.post('/base/upload', data)
十、论文里的"微服务架构设计"章节怎么写?
导师评审时,论文的技术章节是重点。以下是我论文里直接用的表格和论述:
10.1 架构选型对比
| 架构模式 | 开发效率 | 维护成本 | 扩展能力 | 技术复杂度 | 本系统适配性 |
|---|---|---|---|---|---|
| 单体架构 | 高 | 中 | 低 | 低 | ⭐⭐⭐ |
| SOA架构 | 中 | 中 | 中 | 高 | ⭐⭐ |
| 微服务架构 | 中 | 高 | 高 | 中 | ⭐⭐⭐⭐⭐ |
10.2 服务拆分理论支撑
"本系统基于领域驱动设计(DDD)中的限界上下文(Bounded Context)理论进行服务划分。将系统划分为用户域、业务域和基础域三个限界上下文,每个上下文对应一个独立部署的微服务。上下文之间通过 API Gateway 进行通信,保证了领域边界的清晰性与数据一致性。"
10.3 核心组件技术说明
| 组件 | 技术作用 | 本系统应用场景 |
|---|---|---|
| Nacos | 服务注册与发现、配置中心 | 管理4个微服务实例的动态注册与健康检查 |
| Gateway | API 网关 | 统一系统入口,实现路由转发、JWT 鉴权、跨域处理 |
| OpenFeign | 声明式 HTTP 客户端 | 简化服务间调用,内置客户端负载均衡 |
| Docker | 容器化部署 | 实现开发环境与生产环境的一致性 |
十一、10 个真实踩坑记录(建议截图保存)
| 序号 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 1 | Nacos 启动报错 No DataSource set |
2.x 默认需要外置 MySQL | 配置 SPRING_DATASOURCE_PLATFORM=mysql |
| 2 | Feign 调用 404 | path 与 Controller 映射不一致 |
统一服务端不加 /api,Gateway 统一处理 |
| 3 | Gateway 路由不生效 | StripPrefix 后路径错误 |
确认转发后的路径与 Controller 映射匹配 |
| 4 | JWT 传到下游丢失 | Gateway 未写入 Header | mutate().header("Authorization", token) |
| 5 | 服务循环依赖 | A 调 B,B 调 A | 毕设中避免双向调用,数据冗余处理 |
| 6 | 端口冲突 | 本地已有服务占用 | 统一规划:Gateway 8080,Nacos 8848,服务 9001+ |
| 7 | Docker 启动顺序错 | depends_on 不保证就绪 |
加 restart: always 或健康检查 |
| 8 | 前端跨域报错 | 浏览器拦截 | Gateway 配置 globalcors,前端不处理 |
| 9 | MySQL 8.0 连接失败 | 认证插件或 SSL | 连接串加 allowPublicKeyRetrieval=true |
| 10 | 答辩时 Nacos 打不开 | 导师质疑真实性 | 提前截图"服务列表"页面放入 PPT |
十二、关于"脚手架"的一些想法
写到这里,聊点题外话。
我在搭建这套架构的过程中,大约花了 3 天调版本兼容性 (Spring Boot / Cloud / Alibaba 的版本矩阵真的很坑),2 天写 Gateway 过滤器和 Feign 配置 ,1 天调 Docker Compose 启动顺序。真正写业务代码(商品、订单、用户)反而只花了 2 天。
也就是说,微服务毕设最大的时间黑洞不是业务,而是框架搭建。
后来我了解到,市面上有些毕设辅助工具如:智码方舟官网(thesis.polars.cc)可以一键生成 Spring Cloud Alibaba 的项目骨架(包含 Nacos 配置、Gateway 路由、Feign 客户端、Docker Compose 脚本)。如果你时间真的很紧(比如只剩 2 周),或者想先把骨架跑起来再专注业务设计,可以搜索了解一下这类工具。拿到骨架后,你只需要往里填业务代码,答辩时照样能讲清楚每一行配置的作用。
当然,如果你时间充裕,建议还是亲手搭一遍------踩过的坑才是自己的。
结语
微服务不是毕设的"炫技工具",而是你展示架构设计能力的窗口。导师真正想看的是:你知道为什么拆、怎么通信、怎么治理、怎么部署。
用本文的"3+1"模型,配合上面的完整代码和 10 个踩坑记录,2 周内完全可以落地一个"有架构深度"的毕设项目。
最后提醒 :答辩时一定现场打开 Nacos 控制台,给导师展示 4 个服务全部在线注册的截图------眼见为实,比你说 100 句都有用。
如果本文对你有帮助,欢迎点赞 + 收藏,答辩前回来查踩坑表。你在毕设中还遇到过哪些坑?欢迎在评论区交流。