手把手教你用 Spring Cloud Alibaba 搭建毕设级微服务架构:从单体到分布式的完整演进实录

7 阅读13分钟

前言:为什么我要把毕设从单体改成微服务?

去年帮实验室学弟看系统,一个标准的 Spring Boot + Vue 二手交易平台,功能很完整,但答辩时导师问了一句:

"你这个用户模块和订单模块耦合在一起,如果用户量大了,怎么独立扩容?"

学弟当场愣住。后来分数出来,技术架构项被扣了 8 分。

这件事让我意识到:2026 年的毕设评审,已经不只是看"功能有没有",而是看"架构怎么想"。但本科生搞微服务,最怕两件事:一是搞成"分布式单体"(拆了一堆服务,数据库还是共用的,代码也没解耦),二是搞成"过度设计"(拆出十几个服务,答辩时自己都讲不清调用链路)。

本文记录了我自己毕设项目的微服务改造全过程,从单体 Spring Boot 拆成 3 个业务服务 + 1 个网关,用 Spring Cloud Alibaba 2023.x 全家桶,配合 Docker Compose 一键部署。整个改造周期 2 周,答辩时导师主动问"能不能把这套架构的思路整理一下,给下一届参考"。


一、架构演进:单体 → 微服务的决策过程

1.1 原始单体架构的问题

学弟原来的项目结构:

second-hand-platform/
├── src/main/java/com/platform/
│   ├── controller/     # 所有 Controller 放一起
│   ├── service/        # 所有 Service 放一起
│   ├── mapper/         # 所有 Mapper 放一起
│   └── entity/         # 所有 Entity 放一起
└── src/main/resources/
    └── application.yml

问题很明显

  • UserControllerOrderController 在同一个 JVM 里,一个内存泄漏全挂
  • 改用户模块的字段,不小心影响到订单模块的 SQL(因为共用一个 entity 包)
  • 部署时只有一个 JAR,导师问"怎么扩容"时只能回答"换台更好的服务器"

1.2 我的拆分策略:"3+1"模型

我没有盲目学大厂拆出十几个服务。本科毕设,能把 3 个业务域 + 1 个网关 的协作关系讲清楚,已经能拉开差距。

服务端口职责拆分依据
gateway-service8080统一入口、JWT 鉴权、路由转发横切关注点,所有请求必经
user-service9001注册、登录、JWT 签发、角色权限用户域(User Context)
biz-service9002商品、订单、支付、核心业务业务域(Business Context)
base-service9003文件上传、短信、字典、日志基础域(Infrastructure Context)

为什么不是"订单服务""支付服务"分开?

因为毕设不需要解决分布式事务。订单和支付放在 biz-service 里,一个 @Transactional 就能保证一致性。拆太细 = 给自己挖坑。


二、版本选型:这个组合我踩了 3 天才调通

微服务最怕版本冲突。以下组合已在本地(MacOS + Docker Desktop)和 Linux 服务器验证通过:

组件版本备注
Spring Boot3.2.5别用 3.3.x,和 Gateway 有兼容问题
Spring Cloud2023.0.1与 Boot 3.2.x 严格对应
Spring Cloud Alibaba2023.0.1.0Nacos 2.3 支持
Nacos2.3.0单机版够用,集群版答辩讲不清
Gateway4.x基于 WebFlux,性能比 Zuul 好
OpenFeign4.x声明式调用,配合 LoadBalancer
MyBatis Plus3.5.6简化 CRUD
MySQL8.0驱动用 mysql-connector-j
Redis7.x缓存 Token、热点数据
Vue3.4.x前端配合 Element Plus

⚠️ 踩坑记录 1:一开始用了 Spring Boot 3.3.0,结果 Gateway 启动报错 ReactiveAdapterRegistry 找不到。降级到 3.2.5 立刻解决。微服务版本必须严格锁定,这是血泪教训。


三、父工程 POM:版本锁定的正确姿势

很多人微服务项目搭不起来,根因是 pom.xml 版本混乱。我的父工程用 dependencyManagement 严格锁定三件套:

<?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,版本全部由父工程托管:

<!-- 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(单机版)

# 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 上下文刷新前加载。

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 路由配置

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

⚠️ 踩坑记录 3StripPrefix=1 的意思是去掉 /api 前缀。前端请求 /api/user/login,Gateway 转发给 user-service 时变成 /user/login。如果你的 Controller 映射的是 /api/user/login,就会 404。我在这调试了 1 小时。

5.2 JWT 全局过滤器

这是 Gateway 最核心的自定义逻辑,实现统一鉴权:

@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 客户端定义

@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 业务层使用

@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@FeignClientpath 必须和服务端的 @RequestMapping 一致。如果服务端是 @RestController @RequestMapping("/api/user"),而 Feign 写的是 path = "/user",就会 404。建议服务端 Controller 统一不加 /api 前缀,由 Gateway 统一处理。


七、数据库设计:单库 + 前缀隔离

毕设不要搞分库,一个 MySQL 实例,用前缀区分归属:

服务前缀示例表
user-servicesys_sys_user, sys_role
biz-servicebiz_biz_product, biz_order
base-servicebase_base_file, base_log
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 连上服务器,一条命令启动全部服务:

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

启动命令

docker-compose up -d --build
docker-compose ps
curl http://localhost:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10

⚠️ 踩坑记录 5depends_on 只保证启动顺序,不保证服务就绪。如果 user-service 比 Nacos 先启动完成,会注册失败。解决方案:给 user-servicerestart: always,或者写个健康检查脚本。我偷懒用了 restart: always,答辩时没问题。


九、前端 Vue3:只认 Gateway 一个地址

// .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个微服务实例的动态注册与健康检查
GatewayAPI 网关统一系统入口,实现路由转发、JWT 鉴权、跨域处理
OpenFeign声明式 HTTP 客户端简化服务间调用,内置客户端负载均衡
Docker容器化部署实现开发环境与生产环境的一致性

十一、10 个真实踩坑记录(建议截图保存)

序号现象根因解决方案
1Nacos 启动报错 No DataSource set2.x 默认需要外置 MySQL配置 SPRING_DATASOURCE_PLATFORM=mysql
2Feign 调用 404path 与 Controller 映射不一致统一服务端不加 /api,Gateway 统一处理
3Gateway 路由不生效StripPrefix 后路径错误确认转发后的路径与 Controller 映射匹配
4JWT 传到下游丢失Gateway 未写入 Headermutate().header("Authorization", token)
5服务循环依赖A 调 B,B 调 A毕设中避免双向调用,数据冗余处理
6端口冲突本地已有服务占用统一规划:Gateway 8080,Nacos 8848,服务 9001+
7Docker 启动顺序错depends_on 不保证就绪restart: always 或健康检查
8前端跨域报错浏览器拦截Gateway 配置 globalcors,前端不处理
9MySQL 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 句都有用


如果本文对你有帮助,欢迎点赞 + 收藏,答辩前回来查踩坑表。你在毕设中还遇到过哪些坑?欢迎在评论区交流。