
简介一份SpringBootSpringCloud微服务搭建实战资源覆盖Spring Boot启动框架、Spring Cloud分布式工具集、Nacos服务注册与配置中心、Gateway API网关及MyBatis持久层的完整整合。适合需要掌握分布式系统核心组件和微服务架构技术选型的Java开发者内容呈现服务拆分、注册发现、远程调用、数据库交互及熔断缓存等落地实践。资源包为RAR格式共71个文件、53KB含21个Java源文件、21个class文件、10个XML配置、8个YML配置及SQL脚本与POM文件等项目模块清晰便于对照学习。目前已有2400余人学习适合正在搭建或优化微服务工程的技术人员参考。1. 一套能直接落地的微服务脚手架从零搭出 SpringBoot Cloud 全链路微服务架构喊了很多年真正动手时才发现光是把 Nacos、Gateway、Feign 这几个组件串起来就能耗掉一整天。这份资源解决的核心问题很直接把 SpringBoot SpringCloud Nacos Gateway MyBatis 这套企业级技术栈打包成一套可复用的脚手架从服务拆分、注册发现、路由转发到数据持久化每一步都有对应代码和配置。适合正在做单体改造的技术负责人也适合想系统掌握微服务落地细节的 Java 开发——你不是来学概念的你是来抄作业的。资源里没有花哨的炫技全是生产环境里的常规操作Nacos 做注册中心和配置中心、Gateway 做统一入口、OpenFeign 做服务间调用、MyBatis 做数据访问。每个模块都能独立运行、独立扩展照着搭完你得到的不只是 Demo而是一个能继续往上加业务的骨架。下面按实际落地顺序拆开讲从环境准备到最终联调每一步都会说清楚为什么这么选、参数该怎么调、卡住时看哪里。2. 微服务拆分与技术选型为什么是 Nacos Gateway MyBatis 这套组合2.1 技术选型不是追新是看团队能不能接住很多团队在微服务选型时容易被带偏今天听说 Service Mesh 好就上 Istio明天看到云原生就全盘容器化。但现实情况是绝大多数团队的运维能力和研发规模根本撑不起那么重的架构。这套脚手架选 SpringCloud Alibaba 这套生态核心原因是它在国内落地最成熟、资料最多、坑都被踩平了。Nacos 比 Eureka 强在两点一是同时具备注册中心和配置中心两个能力少部署一套组件二是支持临时实例和非临时实例服务下线状态对调用方可见这在生产排障时有很大帮助。Gateway 替代 Zuul 已经是共识性能和响应式编程模型都是碾压级别的。MyBatis 虽然在灵活性上不如 JPA 省事但在复杂 SQL 和团队上手成本上国内团队几乎没有适应门槛。资源里的服务拆分也值得参考它按业务边界拆了三个核心服务订单服务、用户服务、库存服务再配上 Gateway 作为唯一入口。没有为了微服务而微服务每个服务都有明确的业务职责和独立数据库这是最典型的业务型拆分方式。2.2 环境准备版本对齐是最容易翻车的环节这套脚手架对版本敏感JDK、SpringBoot、SpringCloud、SpringCloud Alibaba 四者必须严格对齐。我复现时用的是 JDK 8 SpringBoot 2.7.x SpringCloud 2021.0.3 SpringCloud Alibaba 2021.0.5.0 这个组合这是目前生产环境里最稳的一组版本搭配。如果你用 JDK 17 或者更高版本的 SpringBoot 3.x组件之间的兼容性会带来一堆意想不到的问题不建议新手上来就追新版本。版本对齐的具体操作用 Maven 的 dependencyManagement 统一管理properties java.version1.8/java.version spring-boot.version2.7.8/spring-boot.version spring-cloud.version2021.0.3/spring-cloud.version spring-cloud-alibaba.version2021.0.5.0/spring-cloud-alibaba.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version${spring-cloud-alibaba.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这段配置是整个项目的基石。用 dependencyManagement 统一管理版本而不是在每个模块里写死版本号是为了保证所有子模块引用的 jar 包版本一致。后续升级组件时只需要改这一个地方版本冲突的概率会小很多。SpringBoot 和 SpringCloud 的版本对应关系可以在官方文档里查到SpringCloud Alibaba 的版本对应关系需要单独去 GitHub 上看这是最容易踩坑的地方。2.3 Nacos 的两种模式注册中心与配置中心要分开用资源里把 Nacos 同时用作注册中心和配置中心但这两者的使用方式差别很大。作为注册中心服务启动时自动注册、心跳维持、下线自动摘除作为配置中心则是把配置文件从本地挪到 Nacos 上实现配置的动态刷新。很多人在这一步搞混以为把依赖都加上了就能跑实际上两者的配置项和注解都不一样。注册中心的客户端配置只需要加依赖和注解SpringBootApplication EnableDiscoveryClient public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }配置文件里指定 Nacos 地址spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev group: DEFAULT_GROUPEnableDiscoveryClient 是开启注册发现能力的开关spring.cloud.nacos.discovery.server-addr 是 Nacos 服务的地址namespace 用来隔离环境不同的环境dev、test、prod配不同的 namespace 可以避免服务串环境。group 一般保持默认即可只有在需要进一步划分服务分组时才改。配置中心模式需要额外引入 spring-cloud-starter-alibaba-nacos-config 依赖并且 bootstrap.yml 里的配置方式完全不同spring: application: name: order-service cloud: nacos: config: server-addr: 127.0.0.1:8848 namespace: dev file-extension: yaml group: DEFAULT_GROUP refresh-enabled: true这里要注意配置中心的 dataId 默认规则是 spring.application.name . file-extension也就是 order-service.yaml。file-extension 默认是 properties如果 Nacos 上存的是 yaml 格式必须显式声明。refresh-enabled 控制是否允许配置动态刷新加了 RefreshScope 注解的类就能在配置变更时自动更新属性值。3. 核心链路搭建从网关到服务调用的完整骨架3.1 Gateway 网关路由配置与跨域问题一起解决网关是整个微服务架构的流量入口所有外部请求先到网关再由网关转发到具体服务。资源里 Gateway 模块承担了三个职责路由转发、跨域处理、全局过滤器。路由配置是最基础的部分用代码方式配置更能看清逻辑Configuration public class GatewayConfig { Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(order-service-route, r - r .path(/api/order/**) .filters(f - f .stripPrefix(1) .addRequestHeader(X-Proxy-From, gateway)) .uri(lb://order-service)) .route(user-service-route, r - r .path(/api/user/**) .filters(f - f.stripPrefix(1)) .uri(lb://user-service)) .build(); } }这段路由配置里lb:// 的前缀是关键它表示走负载均衡策略去找到名为 order-service 的服务实例。stripPrefix(1) 是去掉路径中的第一级前缀比如请求 /api/order/save 会被转发成 /order/save 到下游服务。这里的设计意图是统一对外暴露 /api 前缀内部服务不需要感知外部路径规则。addRequestHeader 是网关过滤器的一种用法可以在请求转发前注入额外的请求头常用于内部服务之间传递调用方身份标识。实际业务中还需要解决跨域问题如果前端和后端域名不一致网关这一层必须统一处理Configuration public class CorsConfig { Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsWebFilter(source); } }注意这里用的是 CorsWebFilter 而不是 Spring MVC 里的 CorsFilterGateway 基于 WebFlux两者的包名和实现类完全不同。addAllowedOriginPattern() 配合 setAllowCredentials(true) 是必须的组合如果只写 addAllowedOrigin() 再开 allowCredentials 会直接报错。maxAge 设置预检请求的缓存时间3600 秒可以避免前端每次请求都先发 OPTIONS 预检。3.2 OpenFeign 服务调用从接口定义到超时踩坑服务之间的调用用的是 OpenFeign它让服务间的 HTTP 调用像调用本地方法一样自然。资源里订单服务需要查用户信息就通过 Feign 客户端调用用户服务FeignClient(name user-service, path /user) public interface UserClient { GetMapping(/info/{id}) UserInfoDTO getUserInfo(PathVariable(id) Long id); }FeignClient 注解的 name 属性是目标服务的注册名必须和用户服务在 Nacos 上注册的名称一致。path 是统一前缀。接口方法的注解和服务提供方的 Controller 映射保持一致。但 Feign 的坑不在定义上而在调用时——默认的超时时间和连接数配置在生产环境完全不够用feign: client: config: default: connectTimeout: 5000 readTimeout: 10000 user-service: connectTimeout: 3000 readTimeout: 20000 compression: request: enabled: true mime-types: text/xml,application/json,application/xml min-request-size: 2048 response: enabled: trueconnectTimeout 是建立连接的超时readTimeout 是等待响应的超时默认值都偏小业务接口处理超过 1 秒就会报超时。按服务配置单独调优比统一配置更合理比如用户服务查询慢但订单服务需要等它就单独把 user-service 的 readTimeout 调大。压缩配置能减少传输体积对大报文场景效果显著但注意 min-request-size 设了 2048 字节才触发压缩小请求不会被压缩。Feign 调用时的日志也是调试利器但默认是不开启的logging: level: com.example.order.client.UserClient: debug注意 Feign 的日志级别和普通日志不一样需要在配置类里额外声明 Feign 的 Logger.Leveldebug 级别才会打印请求头、请求体和响应体。这个配置在日常联调时非常有用。3.3 服务间数据一致性Seata 分布式事务的接入方式微服务拆分后最头疼的问题就是分布式事务。资源里订单服务和库存服务分别操作自己的数据库扣库存和创建订单必须保持一致性这里接入了 Seata 的 AT 模式。Seata 的 AT 模式对业务代码侵入最小只需要在每个参与事务的服务里加上数据源代理和全局事务注解GlobalTransactional(name order-create-tx, rollbackFor Exception.class) public boolean createOrder(OrderCreateDTO dto) { // 1. 本地事务创建订单 orderMapper.insert(dto.toOrder()); // 2. 远程调用扣库存 inventoryClient.deductStock(dto.getSkuId(), dto.getQuantity()); // 3. 插入订单明细 orderItemMapper.insertBatch(dto.toOrderItems()); return true; }GlobalTransactional 是全局事务的入口name 是事务分组名称rollbackFor 指定哪些异常触发回滚。AT 模式的工作原理是Seata 在本地事务执行时自动记录数据变更的前后镜像如果后续步骤出错TC 会通知各分支事务根据镜像做反向补偿整个过程对业务代码无感知。但接入 Seata 有个前置条件每张业务表都要额外建一张 undo_log 表CREATE TABLE undo_log ( id BIGINT(20) NOT NULL AUTO_INCREMENT, branch_id BIGINT(20) NOT NULL, xid VARCHAR(100) NOT NULL, context VARCHAR(128) NOT NULL, rollback_info LONGBLOB NOT NULL, log_status INT(11) NOT NULL, log_created DATETIME NOT NULL, log_modified DATETIME NOT NULL, ext VARCHAR(100) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY ux_undo_log (xid,branch_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8;这张表是 AT 模式回滚的核心依赖缺了它事务协调器无法找到回滚数据。很多人在集成 Seata 时忽略建表步骤服务启动时不报错但事务一执行就抛异常排查半天才能发现是表缺失。使用 Seata 还要注意一个边界AT 模式对跨服务调用时传入的全局事务上下文有要求Feign 调用时必须把 RootContext 里的 XID 传递到下游服务。资源里已经写好了对应的请求拦截器如果你自己从零接入千万别漏掉这一步否则全局事务会退化成多个本地事务数据一致性完全没保障。4. MyBatis 持久层生成、分页与多数据源的实战处理4.1 代码生成器与分页插件减少重复劳动的常规操作MyBatis 在微服务里的定位很清晰SQL 好写、容易调优、团队上手快。资源里采用了 MyBatis-Plus 增强框架内置了通用 CRUD、分页插件和代码生成器。代码生成器是最节省时间的部分通过配置数据库连接和表名就能生成完整的实体类、Mapper 接口和 XML 文件public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create(jdbc:mysql://127.0.0.1:3306/order_db, root, your_password) .globalConfig(builder - builder .author(your-name) .outputDir(/project/order-service/src/main/java) .disableOpenDir()) .packageConfig(builder - builder .parent(com.example.order) .entity(entity) .mapper(mapper) .service(service) .serviceImpl(service.impl) .controller(controller) .xml(mapper.xml)) .strategyConfig(builder - builder .addInclude(t_order, t_order_item) .addTablePrefix(t_) .entityBuilder() .enableLombok() .controllerBuilder() .enableRestStyle()) .execute(); } }FastAutoGenerator 是 MyBatis-Plus 提供的快速生成器入口outputDir 指定生成代码的目录addInclude 指定要生成哪几张表addTablePrefix 配置表前缀过滤生成后实体类不会带上前缀。enableLombok 让实体类自动生成 getter/setterenableRestStyle 让 Controller 自动加上 RestController 注解。这套生成器生成完代码后基本的 CRUD 接口就能直接用了省掉大量手工写 Mapper 的时间。分页插件是另一个高频使用点微服务分页查列表用 MyBatis-Plus 的分页插件比手写 limit 更安全Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } } // 业务中使用分页查询 PageOrderDO page new Page(current, size); LambdaQueryWrapperOrderDO wrapper new LambdaQueryWrapper(); wrapper.eq(OrderDO::getUserId, userId) .orderByDesc(OrderDO::getCreateTime); IPageOrderDO result orderMapper.selectPage(page, wrapper);setMaxLimit(500L) 是强制保护机制防止有人传一个超大分页参数把数据库打崩。LambdaQueryWrapper 用 lambda 表达式代替字符串列名编译期就能发现字段名拼写错误比普通 QueryWrapper 更推荐。资源里 MyBatis 模块还配了 SQL 打印插件开发环境能看到完整的 SQL 语句和参数这个在上线前调试 SQL 时几乎是必需品。要注意的是这个插件不能带到生产环境日志量会拖垮性能。4.2 多数据源的坑区分读写分离和不同业务库拆分微服务后一个服务可能同时访问多个数据库。资源里订单服务需要同时读订单库和商品信息库这个时候就涉及到多数据源的配置。MyBatis-Plus 提供了动态数据源方案用 DS 注解就能切换数据源spring: datasource: dynamic: primary: order strict: false datasource: order: url: jdbc:mysql://127.0.0.1:3306/order_db username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver product: url: jdbc:mysql://127.0.0.1:3306/product_db username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driverprimary: order 是指默认走订单库strict: false 允许找不到指定数据源时走默认库。在 Mapper 或 Service 上切换DS(product) public interface ProductMapper extends BaseMapperProductDO { }DS 标注在 Mapper 接口上时该 Mapper 的所有方法都走指定数据源。这个方案比配置多个 SqlSessionFactory 简单得多不用写一堆 Java 配置类。但要注意的是不要在一个事务方法里切换数据源本地事务和动态数据源的配合存在限制跨库事务还是要靠 Seata 解决而不是靠多数据源硬撑。4.3 基础 CRUD 到复杂查询Mapper XML 的管理经验MyBatis 的强项是复杂查询的 SQL 掌控力这一点在资源里体现得比较充分。通用 Mapper 能覆盖 80% 的 CRUD但真正需要人工写 SQL 的查询还是集中在关联查询、聚合统计、动态条件拼写上。资源里规范了 XML 文件的管理方式所有的 SQL 都放在 resources/mapper 目录下Java 接口不写注解 SQL这样 SQL 和代码分开便于 DBA 审查。一个典型的动态查询写法select idselectOrderListByCondition resultTypecom.example.order.entity.OrderDO SELECT * FROM t_order where if testuserId ! null AND user_id #{userId} /if if teststatus ! null AND status #{status} /if if teststartTime ! null AND create_time #{startTime} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{limit} /select标签会自动去掉多余的 AND 或 OR避免手写 where 11 这种丑结构。 标签做动态条件拼装必须注意每个条件前要加 AND 或 OR。#{offset} 是预编译占位符防止 SQL 注入。这里能看出来 MyBatis 在 SQL 控制力上的价值场景复杂时不用为了一个查询去改 ORM 的映射逻辑。5. 避坑排查合集微服务联调的 6 个高频翻车点5.1 服务注册不上控制台卡在启动页不动现象服务启动后 Nacos 控制台看不到服务实例日志没有任何报错。原因最常见的是依赖漏引或者版本冲突。spring-cloud-starter-alibaba-nacos-discovery 依赖没加对版本会直接静默失效SpringCloud 2021 版本之后还必须在 pom 里显式引入 spring-cloud-loadbalancer否则服务发现拿到的是空列表服务能启动但注册不进去。解决依次检查 pom 里是否同时存在 nacos-discovery 和 loadbalancer 依赖检查 bootstrap.yml 里的 namespace 是否和生产环境一致。最后用 curl http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceNameorder-service 直接调 Nacos API 验证注册情况。5.2 网关转发 503服务名大小写带来的玄学问题现象Nacos 控制台能看见服务实例但网关路由转发时报 503 Service Unavailable。原因服务注册名和路由 uri 里的服务名大小写不一致。Nacos 服务名默认是实例元数据里的 spring.application.name但某些版本会自动转成大写存储如果路由配置里写的是小写负载均衡器就找不到匹配实例。解决统一把 spring.application.name 和路由配置里的服务名全部改成小写Nacos 注册中心本身不区分大小写但 Gateway 的 lb:// 协议解析是按精确匹配来的。5.3 Feign 调用超时微服务诊断先看超时参数现象服务间调用偶尔成功、偶尔超时特别是第一次调用时大概率失败。原因Feign 的默认连接超时和响应超时都非常小第一次调用涉及连接池初始化、路由表刷新、懒加载耗时会远超预期。另一个隐藏原因是 Ribbon 的默认重试机制在 Feign 下实际不生效超时后不会自动重试。解决按服务的真实响应时间设置 feign.client.config 的 connectTimeout 和 readTimeout网关层再配置 retry 过滤器做一层兜底重试。注意幂等性校验重试像扣库存这类接口时要做好防重。5.4 配置中心不生效改了配置但代码读的还是旧值现象Nacos 配置中心改了配置项服务日志显示已经拉取新配置但业务代码里读到的还是旧值。原因要么是没有加 RefreshScope 注解导致 bean 不刷新要么是配置文件放到了 bootstrap.yml 和 application.yml 混用的位置。SpringBoot 2.4 之后默认不再自动加载 bootstrap.yml需要引入 spring-cloud-starter-bootstrap 依赖或者在 application.yml 里指定 spring.config.import。解决确保 pom 里有 spring-cloud-starter-bootstrap需要动态刷新的 bean 上加 RefreshScope然后确认 Nacos 配置的 dataId 和应用名、file-extension 完全对齐。5.5 Seata 分支事务异常undo_log 表缺失导致的回滚失败现象开启分布式事务后业务正常执行但一旦发生异常回滚日志报 branch rollback failed。原因AT 模式需要 undo_log 表记录回滚快照表缺失会导致事务协调器无法执行回滚操作。还有一个常见的原因是事务分支里的 SQL 操作了 undo_log 无法解析的字段比如 JSON 字段或 BLOB 字段。解决建好 undo_log 表后检查字段格式尤其是 decimal 和 json 类型。回滚失败时去 Seata 控制台查看分支事务的 status结合 TC 日志定位是哪个分支的事务回滚失败。5.6 多数据源事务失效Transactional 开了个寂寞现象在动态数据源场景下Service 方法加了 Transactional 注解但数据没有回滚。原因动态数据源的切换是通过 AOP 实现的事务管理器绑定的数据源是启动时确定的默认数据源DS 注解切换数据源发生在事务开启之后事务就已经绑到默认库上了。解决多数据源和本地事务不要混用。如果确实需要保证多个数据源操作的原子性服务内走 Seata 的 TCC 模式跨服务用 AT 模式。只涉及单一数据源的场景再考虑 Transactional。6. 进阶落地JWT 鉴权与网关全局过滤器打通全链路网关这层除了路由转发还有一个绕不开的工作是鉴权。资源里采用 JWT Redis 的方案把认证逻辑收敛到网关层下游服务不再关心用户是谁只信任网关传过来的用户身份头。这个做法比每个服务各自校验 token 更合理也方便后续做统一权限审计。网关的全局过滤器是核心入口Component public class AuthGlobalFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest(); String path request.getURI().getPath(); // 白名单直接放行 if (isWhitelist(path)) { return chain.filter(exchange); } // 提取 token String token resolveToken(request); if (token null || !jwtUtil.validateToken(token)) { return unauthorizedResponse(exchange); } // 解析用户信息并写入请求头 Claims claims jwtUtil.parseToken(token); ServerHttpRequest mutatedRequest request.mutate() .header(X-User-Id, claims.get(userId).toString()) .header(X-Username, claims.get(username).toString()) .build(); return chain.filter(exchange.mutate().request(mutatedRequest).build()); } }过滤器把用户身份解析出来后放进请求头下游服务通过 RequestHeader(X-User-Id) 或 Spring Security 的 PreAuthenticatedAuthenticationToken 直接获取当前用户。这比在下游服务里重复解析 token 省事很多避免了每个服务都要维护密钥和解析逻辑的重复工作。白名单路径如 /auth/login、/auth/register 直接放行这块一定要维护好漏一个路径就可能导致未登录用户打到内部服务。我一般会在网关后面接一个独立的认证服务专门负责登录、刷新 token、注销等操作订单和用户服务完全不碰密码和 token 的解析逻辑。这样整个微服务架构的认证边界就非常清晰外部请求只有两条路进到服务集群要么走白名单直达不受保护的资源要么经过网关鉴权后带着明确的用户身份进来。灰度发布是我在真实项目里加上的另一个实用功能Nacos 结合 Gateway 的权重路由可以分批次把流量切到新版本。Lb 路由支持 weights 参数在 Nacos 控制台给不同版本实例配置不同权重就能按比例慢慢放开流量。这套脚手架底子已经够扎实从鉴权到灰度都有预留位置接新业务不会卡壳。从那以后我每次搭新的微服务项目都会强制走一遍这套验证流程先把 Gateway 路由调通再验证跨服务 Feign 调用最后用 Seata 跑一个需要回滚的用例。这套流程走通了后续加业务模块的风险就都在可控范围里。希望帮到你。本文还有配套的精品资源点击获取