☰
SpringCloud微服务脚手架实战:Nacos注册配置中心与Gateway网关搭建
2026/10/20 19:13:24 网站建设 项目流程

简介:一套面向Java后端开发者的微服务实战资源包,围绕SpringBoot、SpringCloud、Nacos、Gateway与MyBatis的整合,演示从服务拆分、注册发现、配置中心到网关路由、数据库持久化的完整搭建过程。资源共含71个文件,涵盖21个Java源码、21个编译后的class文件、10个XML映射/配置、8个YAML环境配置及SQL初始化脚本等,压缩包大小53KB,精炼紧凑,便于对照学习。已有2419人学习下载。内容以IT-Demo项目为蓝本,包含user-service、product-service、gateway、feign-api等模块,清晰展示微服务间远程调用、网关统一入口及MyBatis数据访问的实现方式;配套POM依赖和cloud_user.sql脚本,可直接导入IDE复现运行,适合具备一定Java基础、希望快速掌握微服务组件集成方法的开发者参考。

1. 从单体拆到微服务:这套脚手架想解决什么问题

前阵子一个做电商中台的团队找到我,说他们的单体应用已经跑不动了——订单、用户、商品三个模块耦合在一起,每次发版都要全体加班,想要拆分微服务却不知道从哪下手。我给他们推荐的就是这套以 SpringBoot + SpringCloud 为基础、nacos 做注册与配置中心、gateway 做统一入口、mybatis 做持久层的微服务脚手架。它不是一个空洞的框架集合,而是一个能直接跑起来的最小可用微服务系统:启动 nacos、连上数据库、把三个服务注册进去、通过网关路由到对应业务接口,全链路都是通的。

这个项目特别适合两类人:一类是想从单体转向微服务的 Java 后端,需要一套标准工程做参考;另一类是已经在做微服务但被网关、注册中心、配置中心之间的版本兼容问题折磨过的开发者。它把服务注册发现、网关路由转发、统一配置管理、数据持久化这几块最常见的底座能力全部落地,拿到手改一改就能接自己的业务。下面我会从工程结构、核心代码、运行步骤到改造思路一层层拆开讲。

2. 工程结构与技术选型:模块划分、版本搭配、配置分层的落地细节

2.1 模块划分:父工程与五个子模块的角色分配

这套脚手架采用 Maven 多模块结构,一个父工程统一管理依赖版本,下面挂五个子模块。这种结构在微服务工程里是标准姿势,好处是依赖版本集中管控,不会出现各个服务各自为政、jar 包冲突的乱象。我拆项目时第一件事就是看 pom.xml,这里能看出整个工程的骨架。

springcloud-scaffold/ ├── pom.xml # 父工程,统一管理依赖版本 ├── scaffold-gateway/ # 网关服务,端口 8080 ├── scaffold-auth/ # 认证服务,端口 8100 ├── scaffold-user/ # 用户服务,端口 8200 ├── scaffold-order/ # 订单服务,端口 8300 └── scaffold-common/ # 公共模块,放工具类、统一返回体

父 pom 里用 dependencyManagement 锁定 SpringBoot 版本、SpringCloud 版本、SpringCloud Alibaba 版本,子模块只声明依赖不写版本号。这样做的直接好处是升级框架时只改父 pom 一处,所有子模块同步生效,不用每个服务都去翻版本。

网关服务是流量的唯一入口,负责路由转发和统一鉴权;认证服务负责登录签发 token;用户服务和订单服务是业务服务,相互之间通过 OpenFeign 调用,底层走 nacos 服务发现。common 模块被所有服务依赖,放统一返回结果、异常处理器、工具类,避免每个服务各写一套重复代码。

2.2 版本选型:SpringCloud Alibaba 各组件的匹配关系

拆这类项目我一般先确认版本兼容性,因为 SpringCloud Alibaba 的组件版本跟 SpringBoot 版本是强绑定的,版本不对直接启动报错,属于那种查半天才发现是版本问题的坑。这套脚手架用的是一套经过验证的稳定组合,照着搭基本不会翻车。

组件版本
SpringBoot2.6.13
SpringCloud2021.0.5
SpringCloud Alibaba2021.0.5.0
nacos client2.2.1
mybatis-spring-boot-starter2.2.2
mysql-connector-java8.0.30

选这套版本的原因很直接:2021.0.5.0 是 SpringCloud Alibaba 对 SpringBoot 2.6.x 支持最稳的一个版本线,nacos 2.x 的服务端和客户端配合也成熟,网上能查到的坑基本都被踩平了。如果你的项目要用 SpringBoot 3.x,那整套 Alibaba 组件的版本都要换,这个后面在避坑章节细说。

2.3 配置分层:bootstrap.yml 与 nacos 配置中心的协作机制

微服务项目里配置文件不只是本地 application.yml 那一层,这套脚手架把配置拆成了本地 bootstrap.yml 加 nacos 远端配置两部分。bootstrap.yml 负责在应用启动早期就去 nacos 拉取配置,这个文件里只放连接 nacos 的必要信息,业务配置全部放到 nacos 上管理。

# scaffold-user/src/main/resources/bootstrap.yml spring: application: name: scaffold-user cloud: nacos: server-addr: 127.0.0.1:8848 discovery: namespace: dev group: SCAFFOLD_GROUP config: namespace: dev group: SCAFFOLD_GROUP file-extension: yaml shared-configs: -># scaffold-gateway/src/main/resources/application.yml spring: cloud: gateway: routes: - id: user-route uri: lb://scaffold-user predicates: - Path=/api/user/** filters: - StripPrefix=1 - id: order-route uri: lb://scaffold-order predicates: - Path=/api/order/** filters: - StripPrefix=1 discovery: locator: enabled: true server: port: 8080

路由规则的匹配流程是这样的:外部请求进来,网关拿请求路径跟 predicates 的 Path 条件做匹配。比如请求是 /api/user/info,命中了 user-route,于是转发到 lb://scaffold-user 这个地址。lb:// 前缀表示用负载均衡方式从 nacos 注册中心找到名为 scaffold-user 的服务实例,而不是直连某个 IP。StripPrefix=1 表示转发前去掉路径的第一段,也就是把 /api 剥掉,下游服务收到的路径变成 /user/info,避免了网关路径和服务自身接口路径之间的映射冲突。

除了路由,网关还承担鉴权职责。这套脚手架在网关层写了一个全局过滤器,校验每个请求是否携带合法 token。用 GlobalFilter 接口和 Ordered 接口实现,每次请求进来先判断是否放行白名单,不是白名单的请求统一走 token 校验逻辑。

@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private static final List<String> WHITE_LIST = Arrays.asList("/api/auth/login", "/api/auth/register"); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); // 白名单路径直接放行,不需要校验 token if (WHITE_LIST.contains(path)) { return chain.filter(exchange); } // 从请求头中取出 token String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if (!StringUtils.hasText(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 校验通过的请求,把用户信息放进 header 透传给下游服务 ServerHttpRequest request = exchange.getRequest().mutate() .header("X-User-Info", parseToken(token)) .build(); return chain.filter(exchange.mutate().request(request).build()); } private String parseToken(String token) { // 实际项目中这里调用认证服务解析 token,返回 userId 等关键信息 return "userId:1001"; } @Override public int getOrder() { return -100; } }

这段过滤器的逻辑不难,但有一个设计点值得注意:过滤器中通过 mutate 方法把解析出来的用户信息塞进请求头,下游服务直接从 header 里取 X-User-Info 就知道当前操作人是谁,不需要每个业务服务都解析一遍 token,这个模式下网关成了统一的鉴权入口,业务服务只关心业务逻辑。getOrder 返回 -100 是为了让这个过滤器优先执行,Ordered 值越小优先级越高。

3.2 注册与发现:nacos 客户端接入与服务间调用

服务要注册到 nacos,只需要在业务服务里引入 discovery 依赖,并在 bootstrap.yml 里配好注册中心地址即可,不需要额外写注册代码。应用启动时,nacos 客户端会自动向服务端发起注册请求,把服务名、IP、端口、健康状态上报上去。

<!-- scaffold-user/pom.xml --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>

服务注册上去后,服务间的调用用的是 OpenFeign。这套脚手架里用户服务需要查询订单数据,就在用户服务里定义一个 Feign 客户端接口,指向订单服务,方法签名和订单服务的 Controller 保持一致。

@FeignClient(name = "scaffold-order", path = "/order") public interface OrderFeignClient { @GetMapping("/list/{userId}") List<OrderVO> listOrdersByUserId(@PathVariable("userId") Long userId); }

Feign 接口定义好后,在启动类上加上 @EnableFeignClients 注解开启扫描,用户服务里注入这个接口就能像调本地方法一样调远程服务。调用发生时,底层 ribbon 或 spring-cloud-loadbalancer 组件会从 nacos 拉取 scaffold-order 的实例列表,按负载均衡策略选一个实例发起 HTTP 请求。这里要特别注意:Feign 接口的 name 属性值必须是目标服务在 nacos 上注册的服务名,拼错一个字母就找不到服务,报错信息还不太明显。

3.3 持久层:mybatis 多数据源与 mapper 扫描机制

持久层这块,脚手架没有用 mybatis-plus,而是用原生 mybatis 配合通用 mapper 扫描。这样选择的好处是依赖少、易理解,适合作为学习骨架。用户服务和订单服务分别连接各自的业务库,为了演示多数据源场景,脚手架在公共配置里放了两组数据源定义。

@Configuration @MapperScan(basePackages = "com.scaffold.order.mapper", sqlSessionFactoryRef = "orderSqlSessionFactory") public class OrderDataSourceConfig { @Bean(name = "orderDataSource") @ConfigurationProperties(prefix = "spring.datasource.order") public DataSource orderDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "orderSqlSessionFactory") public SqlSessionFactory orderSqlSessionFactory(@Qualifier("orderDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); // 指定 mapper xml 文件位置 sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/order/*.xml")); return sessionFactoryBean.getObject(); } }

这段配置的核心是 @MapperScan 注解,basePackages 指定了 mapper 接口所在的包路径,sqlSessionFactoryRef 把该包下的 mapper 绑定到订单数据源对应的 SqlSessionFactory。一个服务里可以配置多个这样的数据源配置类,分别指向不同的包和不同的数据库,实现读写分离或多库访问。如果多个数据源的 mapper 包路径写在同一个 @MapperScan 里,所有 mapper 都会被绑定到同一个 SqlSessionFactory,数据源就串了,这个细节不留意非常容易翻车。

mybatis 的 mapper 接口和 xml 映射文件是配套的,接口方法名跟 xml 里的 statement id 一一对应。mapper 接口里只声明方法,具体 SQL 写在 xml 里,通过 namespace 关联到接口全限定名。

public interface OrderMapper { List<OrderDO> selectOrdersByUserId(@Param("userId") Long userId); }
<mapper namespace="com.scaffold.order.mapper.OrderMapper"> <select id="selectOrdersByUserId" resultType="com.scaffold.order.dal.entity.OrderDO"> SELECT id, user_id, order_amount, order_status FROM t_order WHERE user_id = #{userId} AND is_deleted = 0 </select> </mapper>

xml 文件的 namespace 必须写接口的全限定名,select 标签的 id 必须和接口方法名一致,否则启动时扫描会抱错或者运行时报 Invalid bound statement。mybatis 的参数传递这里用了 @Param 注解指定参数名,xml 里就用 #{userId} 引用,如果接口方法有多个参数而不加 @Param,xml 里只能通过 param1、param2 这种别扭的方式拿参数,代码可读性会很差。

4. 从零搭建与验证:nacos 初始化、启动顺序、接口联调全流程

4.1 环境准备:nacos 单机部署与数据库初始化

要把这套脚手架跑起来,第一步是准备基础环境。我一般建议先把 nacos 和 MySQL 装好再动代码。nacos 下载解压后,单机模式直接启动即可,不需要连外部数据库,内置的 derby 存储足够本地开发用。MySQL 需要手动建库,用户库和订单库是两个独立 schema,再各自执行脚手架自带的建表 SQL,表结构对应上面的 t_user 和 t_order。

# 启动 nacos 单机模式(Linux/Mac) sh bin/startup.sh -m standalone # 初始化两个业务库 mysql -uroot -p123456 -e "CREATE DATABASE IF NOT EXISTS db_user DEFAULT CHARSET utf8mb4;" mysql -uroot -p123456 -e "CREATE DATABASE IF NOT EXISTS db_order DEFAULT CHARSET utf8mb4;" # 建表脚本分别在 scaffold-user 和 scaffold-order 模块的 sql 目录下 mysql -uroot -p123456 db_user < scaffold-user/sql/init.sql mysql -uroot -p123456 db_order < scaffold-order/sql/init.sql

nacos 是注册中心也是配置中心,所以启动前还要确认 bootstrap.yml 里配置的 namespace 在 nacos 控制台里已经创建好。老版本的 nacos 控制台默认只有 public 命名空间,如果配置文件里指定了 dev 命名空间而控制台里没有,服务会注册不上去,报错会提示 namespace 找不到。这个操作一般是在 nacos 控制台的命名空间页面手动创建一个 namespace ID 为 dev 的隔离环境。

数据库初始化这一步容易忽略一个细节:MySQL 的连接参数里要加上 useUnicode=true 和 characterEncoding=utf8,否则插入中文数据时可能出现乱码。这套脚手架里数据源连接串已经带了这两个参数,如果你参考这个工程自己写连接串,注意别丢掉。

4.2 启动与验证:从网关到业务服务的完整联调

环境就绪后按顺序启动服务:先启动认证服务,再启动用户服务和订单服务,最后启动网关。顺序不是强制的,因为服务之间通过 nacos 做发现,谁先谁后都能互相找到,但先启动业务服务再启动网关,联调时第一步走网关就能直接看到效果,排查问题少绕弯。

# 分别在三个服务目录下启动 cd scaffold-auth && mvn spring-boot:run cd scaffold-user && mvn spring-boot:run cd scaffold-order && mvn spring-boot:run cd scaffold-gateway && mvn spring-boot:run

服务启动完成后,打开 nacos 控制台的服务列表页面,确认三个业务服务都出现在列表中且状态为健康。这里有个经验:如果 nacos 控制台能看到服务但状态显示不健康,多半是服务实例的 IP 在 nacos 上注册成了内网不可达地址,需要在 bootstrap.yml 里显式配置 spring.cloud.nacos.discovery.ip 指定本机局域网 IP。

验证全链路只需要走一次网关调用。假设用户服务的 /user/info 接口返回用户基本信息,通过网关访问时路径要带上前缀 /api,网关才会命中 user-route 路由规则。

# 注册并登录拿到 token curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' # 带 token 通过网关访问用户服务 curl http://localhost:8080/api/user/info \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

到这里链路就是完整的:请求先到网关 8080 端口,网关校验 token 后按 Path 规则转发到用户服务,用户服务收到请求后执行 mybatis 查询数据库,返回结果再逐层回写。看到响应数据能正常返回,说明网关路由、nacos 注册发现、数据库连接三块核心能力已经全部打通了。

5. 避坑排查笔记:微服务启动中五个高频翻车点

5.1 服务注册不上:nacos 控制台看不到服务实例

现象是服务启动日志显示注册成功,但控制台的服务列表里就是找不到。排查下来原因通常是版本不匹配或者 namespace 不对:SpringCloud Alibaba 版本太老时,nacos 客户端注册用的还是旧协议,和服务端 2.x 通信会偶发失败。另一个常见原因就是 bootstrap.yml 里 namespace 配了 dev,但控制台里根本没有这个命名空间。解决方式是先确认版本组合跟脚手架一致,再去控制台把 namespace 创建好,最后看 nacos 日志确认注册请求是否真正到达服务端。

5.2 网关转发 404:路由匹配成功但 StripPrefix 没配

请求打到网关后返回 404,这时候要看两个地方:网关日志里有没有打印匹配到的路由 id,如果匹配到了,问题基本出在 StripPrefix 上。前端请求路径是 /api/order/list,网关转发时默认原样转发给下游服务,而下游服务的接口路径是 /order/list,多了一层 /api 自然 404。解决方式就是在路由配置里加 StripPrefix=1,让网关转发前剥掉第一段前缀。这个坑非常典型,十个玩 gateway 的里至少有五个遇到过。

5.3 mybatis 报 Invalid bound statement:mapper 接口和 xml 没绑定

启动时一切正常,一调用 mapper 方法就报 Invalid bound statement (not found)。这个报错的根源是 mapper 接口和 xml 映射文件没有建立对应关系,要么是 xml 的 namespace 写错了,要么是 xml 文件没有被打包进 target 目录。Maven 默认只把 resources 目录下的文件打进包,如果你的 xml 放在 java 目录下且没有在 pom 里配置 resource 节点,xml 就不会出现在 classpath 里,运行时自然找不到。解决方式是把 xml 统一放在 resources/mapper 目录下,或在 pom 里显式声明资源路径。

5.4 配置修改后不生效:nacos 推送了但本地 Bean 不刷新

场景是这样:在 nacos 配置中心改了数据源连接串,配置中心显示发布成功,但服务用的还是旧值。原因有两层,第一层是 bootstrap.yml 里的 shared-configs 没配 refresh: true,配置变更不会推给客户端;第二层是即便推送了,业务 Bean 没有加 @RefreshScope 注解,Spring 容器里的 Bean 还是旧实例。这两个条件得同时满足才能实现动态刷新,缺一个都会让人误以为配置中心坏了。排查思路是先看 nacos 控制台的配置监听日志,确认客户端有没有收到变更事件,再检查对应 Bean 是否加了 @RefreshScope。

5.5 服务间 Feign 调用报连接拒绝:负载均衡依赖缺失

用户服务调订单服务时,报连接拒绝或找不到路由,控制台日志还会提示 No instances available for scaffold-order。这个问题的原因通常是项目里引入了 nacos discovery 但没有引入负载均衡组件。SpringCloud 2020 版本之后,Ribbon 被移出了默认依赖,OpenFeign 需要配合 spring-cloud-starter-loadbalancer 才能从注册中心拿到实例列表并做负载均衡。少了这个依赖,Feign 只知道服务名,但不知道怎么解析服务名对应的可用 IP 列表,调用就失败。解决方式是在 pom 里补上对应的 loadbalancer 依赖。

6. 二次开发:如何把这套脚手架扩展成自己的业务底座

拿到这套脚手架后,最常见的需求是增加一个新的业务服务。我一般走五个步骤:第一步,复制 scaffold-order 目录改成新服务名 scaffold-goods,全局替换包名和服务名;第二步,在 nacos 配置中心新建一个 goods 的配置文件,数据源指向新库;第三步,在网关的 application.yml 里加一条路由规则,路径前缀用 /api/goods;第四步,在新服务里定义自己的 mapper 和 service;第五步,启动服务,去 nacos 控制台确认注册成功。整套流程熟练后十分钟就能扩展一个服务,不用动脚手架的任何公共代码。

改造网关的鉴权逻辑也是高频需求,脚手架里目前是简单的 token 透传,实际业务可以扩展成对接统一认证平台的过滤器:在 AuthGlobalFilter 的 parseToken 方法里调用认证服务器校验 token,解析出的用户信息塞进 header 后,下游服务从 header 取值做越权校验。如果需要支持接口级别的权限控制,可以在过滤器里查一次权限表,把用户角色写进 header,业务服务再根据角色判断操作权限。

统一异常处理和统一返回体是二次开发中容易忽略但价值很高的点。脚手架 common 模块里有 Result 统一返回对象和全局异常捕获器,新写的业务接口一定要返回 Result 包装类型,不要在 Controller 里直接返回业务对象。这样前段拿到的是格式一致的 JSON,错误码和错误信息也有统一规范,联调时不会出现同一个接口有时返回对象有时返回裸字符串的情况。

我把这套脚手架在本地跑过很多次,每次在新的机器上搭建环境都会踩一遍版本兼容的坑。从那以后我每换一个项目,都强制先把 SpringBoot、SpringCloud、SpringCloud Alibaba 三个版本的匹配关系查清楚,再开始引依赖。这个习惯帮我避开了大量启动阶段的问题,也希望帮你少走点弯路。希望这篇文章能让你把项目顺畅跑起来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询